Check-in [7ef1553f61]
Comment: | Move __print__ into config, add unified dbg.COLOR codes |
---|---|
Downloads: | Tarball | ZIP archive | SQL archive |
Timelines: | family | ancestors | descendants | both | trunk |
Files: | files | file ages | folders |
SHA1: |
7ef1553f61c9abcecfb85ac12064e265 |
User & Date: | mario on 2014-04-07 00:33:43 |
Other Links: | manifest | tags |
2014-04-27
| ||
22:19 | Python3 support back into trunk check-in: 9ecea4fb26 user: mario tags: trunk | |
2014-04-08
| ||
21:16 | rename http to ahttp to avoid conflict with Python3 modules, change .iteritems and xrange, remove same remaining plain print statements check-in: d3b1418bc6 user: mario tags: py3 | |
2014-04-07
| ||
00:33 | Move __print__ into config, add unified dbg.COLOR codes check-in: 7ef1553f61 user: mario tags: trunk | |
2014-04-06
| ||
02:16 | rename ui.xml to gtk2.xml for parity with gtk3.xml; Gtk3 suddenly works with gi 1.33 (well, lots of errors still, but main window ok) check-in: e7a0fb24c8 user: mario tags: trunk | |
Modified _package.epm from [036b8e5906] to [d2e2c94cae].
︙ | ︙ | |||
27 28 29 30 31 32 33 | d 755 root root /usr/share/doc/streamtuner2/contrib - f 644 root root /usr/share/doc/streamtuner2/contrib/streamripper_addgenre ./contrib/streamripper_addgenre f 755 root root /usr/bin/streamtuner2 ./st2.py f 644 root root /usr/share/applications/streamtuner2.desktop ./streamtuner2.desktop d 755 root root /usr/share/streamtuner2 - f 644 root root /usr/share/streamtuner2/streamtuner2.png ./streamtuner2.png f 644 root root /usr/share/pixmaps/streamtuner2.png ./logo.png | | > | 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 | d 755 root root /usr/share/doc/streamtuner2/contrib - f 644 root root /usr/share/doc/streamtuner2/contrib/streamripper_addgenre ./contrib/streamripper_addgenre f 755 root root /usr/bin/streamtuner2 ./st2.py f 644 root root /usr/share/applications/streamtuner2.desktop ./streamtuner2.desktop d 755 root root /usr/share/streamtuner2 - f 644 root root /usr/share/streamtuner2/streamtuner2.png ./streamtuner2.png f 644 root root /usr/share/pixmaps/streamtuner2.png ./logo.png f 644 root root /usr/share/streamtuner2/gtk2.xml ./gtk2.xml f 644 root root /usr/share/streamtuner2/gtk3.xml ./gtk3.xml f 644 root root /usr/share/streamtuner2/pson.py ./pson.py #f 644 root root /usr/share/streamtuner2/processing.py ./processing.py f 644 root root /usr/share/streamtuner2/action.py ./action.py f 644 root root /usr/share/streamtuner2/config.py ./config.py f 644 root root /usr/share/streamtuner2/http.py ./http.py f 644 root root /usr/share/streamtuner2/cli.py ./cli.py f 644 root root /usr/share/streamtuner2/mygtk.py ./mygtk.py |
︙ | ︙ |
Modified action.py from [f63b7b9614] to [fc0650246a].
︙ | ︙ | |||
20 21 22 23 24 25 26 | # # import re import os import http | | < < < < < < | 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 | # # import re import os import http from config import conf, __print__, dbg import platform main = None #-- media actions --------------------------------------------- # # implements "play" and "record" methods, |
︙ | ︙ |
Modified channels/__init__.py from [f76e54fd21] to [92d73c2e69].
1 2 3 4 5 6 7 8 | # # encoding: UTF-8 # api: python # type: R # from channels._generic import * | < | 1 2 3 4 5 6 7 8 | # # encoding: UTF-8 # api: python # type: R # from channels._generic import * |
Modified channels/_generic.py from [d30d0357de] to [f927301f5f].
︙ | ︙ | |||
18 19 20 21 22 23 24 | # file. They derive from the ChannelPlugins class instead, which # adds the required gtk Widgets manually. # import gtk from mygtk import mygtk | | < | 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 | # file. They derive from the ChannelPlugins class instead, which # adds the required gtk Widgets manually. # import gtk from mygtk import mygtk from config import conf, __print__, dbg import http import action import favicon import os.path import xml.sax.saxutils import re import copy # dict==object class struct(dict): def __init__(self, *xargs, **kwargs): self.__dict__ = self self.update(kwargs) |
︙ | ︙ | |||
205 206 207 208 209 210 211 | #if (self.liststore.has_key(category)): # del self.liststore[category] else: # parse error self.parent.status("category parsed empty.") self.streams[category] = [{"title":"no contents found on directory server","bitrate":0,"max":0,"listeners":0,"playing":"error","favourite":0,"deleted":0}] | | | 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 | #if (self.liststore.has_key(category)): # del self.liststore[category] else: # parse error self.parent.status("category parsed empty.") self.streams[category] = [{"title":"no contents found on directory server","bitrate":0,"max":0,"listeners":0,"playing":"error","favourite":0,"deleted":0}] __print__(dbg.ERR, "Oooops, parser returned nothing for category " + category) # assign to treeview model #self.streams[self.default] = [] #if (self.liststore.has_key(category)): # was already loded before # self.gtk_list.set_model(self.liststore[category]) #else: # currently list is new, had not been converted to gtk array before # self.liststore[category] = \ |
︙ | ︙ | |||
300 301 302 303 304 305 306 | self.load(self.current, force=1) def switch(self): self.load(self.current, force=0) # display .current category, once notebook/channel tab is first opened def first_show(self): | | | < < | | | | 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 | self.load(self.current, force=1) def switch(self): self.load(self.current, force=0) # display .current category, once notebook/channel tab is first opened def first_show(self): __print__(dbg.PROC, "first_show ", self.module, self.shown) if (self.shown != 55555): # if category tree is empty, initialize it if not self.categories: __print__(dbg.PROC, "first_show: reload_categories"); #self.parent.thread(self.reload_categories) print("reload categories"); self.reload_categories() self.display_categories() self.current = self.categories.keys()[0] print self.current self.load(self.current) # load current category else: __print__(dbg.STAT, "first_show: load current category"); self.load(self.current) # put selection/cursor on last position try: __print__(dbg.STAT, "first_show: select last known category treelist position") self.gtk_list.get_selection().select_path(self.shown) except: pass # this method will only be invoked once self.shown = 55555 |
︙ | ︙ | |||
527 528 529 530 531 532 533 | # add module to list #parent.channels[module] = None #parent.channel_names.append(module) """ -> already taken care of in main.load_plugins() """ | < < < < < < < < < < < | 524 525 526 527 528 529 530 531 532 533 | # add module to list #parent.channels[module] = None #parent.channel_names.append(module) """ -> already taken care of in main.load_plugins() """ |
Modified channels/global_key.py from [7c03f6331b] to [3bfb69eda6].
︙ | ︙ | |||
9 10 11 12 13 14 15 | # # Binds a key to global desktop (F13 = left windows key). On keypress # it switches the currently playing radio station to another one in # bookmarks list. # # Valid key names are for example F9, <Ctrl>G, <Alt>R, <Super>N # | < | 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | # # Binds a key to global desktop (F13 = left windows key). On keypress # it switches the currently playing radio station to another one in # bookmarks list. # # Valid key names are for example F9, <Ctrl>G, <Alt>R, <Super>N # import keybinder from config import conf import action import random |
︙ | ︙ |
Modified channels/internet_radio_org_uk.py from [5a7ebd0c8e] to [4dcc6f65d4].
︙ | ︙ | |||
10 11 12 13 14 15 16 | # # from channels import * import re | | | 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | # # from channels import * import re from config import conf, __print__, dbg import http from pq import pq # streams and gui |
︙ | ︙ |
Modified channels/live365.py from [9e6de8cad1] to [17860a6a46].
︙ | ︙ | |||
8 9 10 11 12 13 14 | # streamtuner2 modules from config import conf from mygtk import mygtk import http from channels import * | | | 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | # streamtuner2 modules from config import conf from mygtk import mygtk import http from channels import * from config import __print__, dbg # python modules import re import xml.dom.minidom from xml.sax.saxutils import unescape as entity_decode, escape as xmlentities import gtk import copy |
︙ | ︙ | |||
114 115 116 117 118 119 120 | =["']audioQuality.+?>(\d+)\w<.+? >DrawListenerStars\((\d+),.+? >DrawRatingStars\((\d+),\s+(\d+),.*? """, re.X|re.I|re.S|re.M) # src="(http://www.live365.com/.+?/stationlogo\w+.jpg)".+? # append entries to result list | | | | 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 | =["']audioQuality.+?>(\d+)\w<.+? >DrawListenerStars\((\d+),.+? >DrawRatingStars\((\d+),\s+(\d+),.*? """, re.X|re.I|re.S|re.M) # src="(http://www.live365.com/.+?/stationlogo\w+.jpg)".+? # append entries to result list __print__( dbg.DATA, html ) ls = [] for row in rx.findall(html): __print__( dbg.DATA, row ) points = int(row[8]) count = int(row[9]) ls.append({ "launch_id": row[0], "sofo": row[0], # subscribe-or-fuck-off status flags "state": ("" if row[0]=="OK" else gtk.STOCK_STOP), "homepage": entity_decode(row[1]), |
︙ | ︙ |
Modified channels/modarchive.py from [ac3a95273a] to [bf829e8b72].
︙ | ︙ | |||
11 12 13 14 15 16 17 | # import re import http from config import conf from channels import * | | | 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | # import re import http from config import conf from channels import * from config import __print__, dbg from xml.sax.saxutils import unescape |
︙ | ︙ | |||
107 108 109 110 111 112 113 | .*? /formats/(\w+).png" .*? title="([^">]+)">([^<>]+)</a> .*? >Rated</a>\s*(\d+) """, re.X|re.S) for uu in rx_mod.findall(html): (url, id, fmt, title, file, rating) = uu | | | 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 | .*? /formats/(\w+).png" .*? title="([^">]+)">([^<>]+)</a> .*? >Rated</a>\s*(\d+) """, re.X|re.S) for uu in rx_mod.findall(html): (url, id, fmt, title, file, rating) = uu __print__( dbg.DATA, uu ) entries.append({ "genre": cat, "url": url, "id": id, "format": self.mime_fmt(fmt) + "+zip", "title": title, "playing": file, |
︙ | ︙ |
Modified channels/punkcast.py from [ba986159c3] to [6f838d457b].
︙ | ︙ | |||
9 10 11 12 13 14 15 | import re import http from config import conf import action from channels import * | | | 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | import re import http from config import conf import action from channels import * from config import __print__, dbg # disable plugin per default if "punkcast" not in vars(conf): |
︙ | ︙ | |||
82 83 84 85 86 87 88 | def play(self, row): rx_sound = re.compile("""(http://[^"<>]+[.](mp3|ogg|m3u|pls|ram))""") html = http.get(row["homepage"]) # look up ANY audio url for uu in rx_sound.findall(html): | | | 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 | def play(self, row): rx_sound = re.compile("""(http://[^"<>]+[.](mp3|ogg|m3u|pls|ram))""") html = http.get(row["homepage"]) # look up ANY audio url for uu in rx_sound.findall(html): __print__( dbg.DATA, uu ) (url, fmt) = uu action.action.play(url, self.mime_fmt(fmt), "url/direct") return # or just open webpage action.action.browser(row["homepage"]) |
Modified channels/shoutcast.py from [8c7ec7957d] to [3296716516].
1 2 3 4 5 | # # api: streamtuner2 # title: shoutcast # description: Channel/tab for Shoutcast.com directory # depends: pq, re, http | | | < < < < < < < < < < < < < < < < > > > > < < | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | # # api: streamtuner2 # title: shoutcast # description: Channel/tab for Shoutcast.com directory # depends: pq, re, http # version: 1.3 # author: Mario # original: Jean-Yves Lefort # # Shoutcast is a server software for audio streaming. It automatically spools # station information on shoutcast.com, which this plugin can read out. # # After its recent aquisition the layout got slimmed down considerably. So # there's not a lot of information to fetch left. And this plugin is now back # to defaulting to regex extraction instead of HTML parsing & DOM extraction. # # # import http import urllib import re from config import conf, __print__, dbg from pq import pq #from channels import * # works everywhere but in this plugin(???!) import channels # SHOUTcast data module ---------------------------------------- class shoutcast(channels.ChannelPlugin): # desc |
︙ | ︙ | |||
73 74 75 76 77 78 79 | # extracts the category list from shoutcast.com, # sub-categories are queried per 'AJAX' def update_categories(self): html = http.get(self.base_url) self.categories = [] | | | | | | | | > > > > > > | | | 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 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 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 | # extracts the category list from shoutcast.com, # sub-categories are queried per 'AJAX' def update_categories(self): html = http.get(self.base_url) self.categories = [] __print__( dbg.DATA, html ) # <h2>Radio Genres</h2> rx = re.compile(r'<li((?:\s+id="\d+"\s+class="files")?)><a href="\?action=sub&cat=([\w\s]+)#(\d+)">[\w\s]+</a>', re.S) sub = [] for uu in rx.findall(html): __print__( dbg.DATA, uu ) (main,name,id) = uu name = urllib.unquote(name) # main category if main: if sub: self.categories.append(sub) sub = [] self.categories.append(name) else: sub.append(name) # it's done __print__( dbg.PROC, self.categories ) conf.save("cache/categories_shoutcast", self.categories) pass #def strip_tags(self, s): # rx = re.compile(""">(\w+)<""") # return " ".join(rx.findall(s)) # downloads stream list from shoutcast for given category def update_streams(self, cat, search=""): if (not cat or cat == self.empty): __print__( dbg.ERR, "nocat" ) return [] ucat = urllib.quote(cat) # loop entries = [] next = 0 max = int(conf.max_streams) count = max rx_stream = None try: if (next < max): #/radiolist.cfm?action=sub&string=&cat=Oldies&_cf_containerId=radiolist&_cf_nodebug=true&_cf_nocache=true&_cf_rc=0 #/radiolist.cfm?start=19&action=sub&string=&cat=Oldies&amount=18&order=listeners # page url = "http://www.shoutcast.com/radiolist.cfm?action=sub&string=&cat="+ucat+"&order=listeners&amount="+str(count) __print__(dbg.HTTP, url) referer = "http://www.shoutcast.com/?action=sub&cat="+ucat params = {} # "strIndex":"0", "count":str(count), "ajax":"true", "mode":"listeners", "order":"desc" } html = http.ajax(url, params, referer) #,feedback=self.parent.status) __print__(dbg.DATA, html) #__print__(re.compile("id=(\d+)").findall(html)); # With the new shallow <td> lists it doesn't make much sense to use # the pyquery DOM traversal. There aren't any sensible selectors to # extract values; it's just counting the tags. # regular expressions (default) if not conf.get("pyquery") or not pq: # new html """ <tr> <td width="6%"><a href="#" onClick="window.open('player/?radname=Schlagerhoelle%20%2D%20das%20Paradies%20fr%20Schlager%20%20und%20Discofox&stationid=14687&coding=MP3','radplayer','height=232,width=776')"><img class="icon transition" src="/img/icon-play.png" alt="Play"></a></td> <td width="30%"><a class="transition" href="http://yp.shoutcast.com/sbin/tunein-station.pls?id=14687">Schlagerhoelle - das Paradies fr Schlager und Discofox</a></td> <td width="12%" style="text-align:left;" width="10%">Oldies</td> |
︙ | ︙ | |||
163 164 165 166 167 168 169 | \s+ <td [^>]+ >([^<>]+)</td> \s+ <td [^>]+ >(\d+)</td> \s+ <td [^>]+ >(\d+)</td> \s+ <td [^>]+ >(\w+)</td> """, re.S|re.I|re.X ) | | > | | | > < | | | | | | 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 | \s+ <td [^>]+ >([^<>]+)</td> \s+ <td [^>]+ >(\d+)</td> \s+ <td [^>]+ >(\d+)</td> \s+ <td [^>]+ >(\w+)</td> """, re.S|re.I|re.X ) # extract entries self.parent.status("parsing document...") __print__(dbg.PROC, "channels.shoutcast.update_streams: regex scraping mode") for m in rx_stream.findall(html): #__print__(m) (id, title, genre, listeners, bitrate, fmt) = m entries += [{ "id": id, "url": "http://yp.shoutcast.com/sbin/tunein-station.pls?id=" + id, "title": self.entity_decode(title), #"homepage": http.fix_url(homepage), #"playing": self.entity_decode(playing), "genre": genre, "listeners": int(listeners), "max": 0, #int(uu[6]), "bitrate": int(bitrate), "format": self.mime_fmt(fmt), }] # PyQuery parsing else: # iterate over DOM for div in (pq(e) for e in pq(html).find("tr")): entries.append({ "title": div.find("a.transition").text(), "url": div.find("a.transition").attr("href"), "homepage": "", "listeners": int(div.find("td:eq(3)").text()), "bitrate": int(div.find("td:eq(4)").text()), "format": self.mime_fmt(div.find("td:eq(5)").text()), "max": 0, "genre": cat, }) # display partial results (not strictly needed anymore, because we fetch just one page) self.parent.status() self.update_streams_partially_done(entries) # more pages to load? next = 99999 except Exception as e: __print__(dbg.ERR, e) return entries #fin __print__(dbg.DATA, entries) return entries |
Modified channels/xiph.py from [8ad5a64170] to [6bbc72e8d5].
︙ | ︙ | |||
17 18 19 20 21 22 23 | # streamtuner2 modules from config import conf from mygtk import mygtk import http from channels import * | | | 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | # streamtuner2 modules from config import conf from mygtk import mygtk import http from channels import * from config import __print__, dbg # python modules import re from xml.sax.saxutils import unescape as entity_decode, escape as xmlentities import xml.dom.minidom |
︙ | ︙ | |||
86 87 88 89 90 91 92 | else: g[t] = 0 g = [ [v[1],v[0]] for v in g.items() ] g.sort() g.reverse() for row in g: pass | | | 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 | else: g[t] = 0 g = [ [v[1],v[0]] for v in g.items() ] g.sort() g.reverse() for row in g: pass __print__( dbg.DATA, ' "' + row[1] + '", #' + str(row[0]) ) # xml dom node shortcut to text content def x(self, entry, name): e = entry.getElementsByTagName(name) if (e): if (e[0].childNodes): |
︙ | ︙ |
Modified config.py from [0a6d293594] to [16fe14f094].
︙ | ︙ | |||
26 27 28 29 30 31 32 | conf = object() #-- global configuration data --------------------------------------------- class ConfigDict(dict): | < | 26 27 28 29 30 31 32 33 34 35 36 37 38 39 | conf = object() #-- global configuration data --------------------------------------------- class ConfigDict(dict): # start def __init__(self): # object==dict means conf.var is conf["var"] self.__dict__ = self # let's pray this won't leak memory due to recursion issues # prepare |
︙ | ︙ | |||
158 159 160 161 162 163 164 | f = open(file, "r") else: return # file not found # decode r = pson.load(f) f.close() return r | | | > > > > > > > > > > > > > > > > > > > > | 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 | f = open(file, "r") else: return # file not found # decode r = pson.load(f) f.close() return r except Exception as e: print("PSON parsing error (in "+name+")", e) # recursive dict update def update(self, with_new_data): for key,value in with_new_data.items(): if type(value) == dict: self[key].update(value) else: self[key] = value # descends into sub-dicts instead of wiping them with subkeys # check for existing filename in directory list 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): if conf.debug: print(" ".join([str(a) for a in args])) # error colorization dbg = type('obj', (object,), { "ERR": "[31m[ERR][0m", # red ERROR "INIT": "[31m[INIT][0m", # red INIT ERROR "PROC": "[32m[PROC][0m", # green PROCESS "CONF": "[33m[CONF][0m", # brown CONFIG DATA "UI": "[34m[UI][0m", # blue USER INTERFACE BEHAVIOUR "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 }) |
Modified http.py from [68776c8e4a] to [0d4dcfee94].
︙ | ︙ | |||
10 11 12 13 14 15 16 | # And a function to add trailings slashes on http URLs. # # The latter code is pretty much unreadable. But let's put the # blame on urllib2, the most braindamaged code in the Python # standard library. # | | | | > > > | | 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | # And a function to add trailings slashes on http URLs. # # The latter code is pretty much unreadable. But let's put the # blame on urllib2, the most braindamaged code in the Python # standard library. # try: import urllib2 from urllib import urlencode except: import urllib.request as urllib2 import urllib.parse.urlencode as urlencode import config from config import __print__, dbg #-- url download --------------------------------------------- |
︙ | ︙ |
Modified mygtk.py from [74d7664de2] to [98fa2cab88].
︙ | ︙ | |||
23 24 25 26 27 28 29 | # # # debug | | < | | 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 | # # # debug from config import __print__, dbg # gtk modules gtk = 0 # 0=gtk2, else gtk3 if gtk: from gi import pygtkcompat as pygtk pygtk.enable() pygtk.enable_gtk(version='3.0') from gi.repository import Gtk as gtk from gi.repository import GObject as gobject from gi.repository import GdkPixbuf |
︙ | ︙ |
Modified pson.py from [1f7ebfaebc] to [759da668d2].
︙ | ︙ | |||
17 18 19 20 21 22 23 24 25 26 27 28 29 30 | # pygtk-objects crawled into the streams[] lists, because rows # might have been queried from the widgets. # #-- reading and writing json (for the config module) ---------------------------------- # try to load the system module first try: from json import dump as json_dump, load as json_load except: print("no native Python JSON module") | > > > > | 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | # pygtk-objects crawled into the streams[] lists, because rows # might have been queried from the widgets. # #-- reading and writing json (for the config module) ---------------------------------- import sys if sys.version_info > (2, 9): unicode = str #dict.iteritems = dict.items # try to load the system module first try: from json import dump as json_dump, load as json_load except: print("no native Python JSON module") |
︙ | ︙ | |||
80 81 82 83 84 85 86 | elif type(obj) == unicode: return str(obj) elif type(obj) in (list, tuple, set): obj = list(obj) for i,v in enumerate(obj): obj[i] = filter_data(v) elif type(obj) == dict: | | | 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 | elif type(obj) == unicode: return str(obj) elif type(obj) in (list, tuple, set): obj = list(obj) for i,v in enumerate(obj): obj[i] = filter_data(v) elif type(obj) == dict: for i,v in list(obj.items()): i = filter_data(i) obj[i] = filter_data(v) else: print("invalid object in data, converting to string: ", type(obj), obj) obj = str(obj) return obj |
Modified st2.py from [4696fba60c] to [0b9e0b6b81].
︙ | ︙ | |||
95 96 97 98 99 100 101 102 103 104 | 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 import http import action # needs workaround... (action.main=main) from channels import * | > < | 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 | 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 |
︙ | ︙ | |||
126 127 128 129 130 131 132 | current_channel = "bookmarks" # currently selected channel name (as index in self.channels{}) # constructor def __init__(self): # gtkrc stylesheet | | | | | | | | 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 | current_channel = "bookmarks" # currently selected channel name (as index in self.channels{}) # constructor def __init__(self): # gtkrc stylesheet self.load_theme(), gui_startup(1/20.0) # instantiate gtk/glade widgets in current object gtk.Builder.__init__(self) gtk.Builder.add_from_file(self, conf.find_in_dirs([".", conf.share], ui_file)), gui_startup(2/20.0) # manual gtk operations self.extensionsCTM.set_submenu(self.extensions) # duplicates Station>Extension menu into stream context menu # initialize channels self.channels = { "bookmarks": bookmarks(parent=self), # this the remaining built-in channel "shoutcast": None,#shoutcast(parent=self), } gui_startup(3/20.0) self.load_plugin_channels() # append other channel modules / plugins # load application state (widget sizes, selections, etc.) try: winlayout = conf.load("window") if (winlayout): mygtk.app_restore(self, winlayout) # selection values winstate = conf.load("state") if (winstate): for id in winstate.keys(): self.channels[id].current = winstate[id]["current"] self.channels[id].shown = winlayout[id+"_list"].get("row:selected", 0) # actually just used as boolean flag (for late loading of stream list), selection bar has been positioned before already except: pass # fails for disabled/reordered plugin channels # display current open channel/notebook tab gui_startup(17/20.0) 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), |
︙ | ︙ | |||
219 220 221 222 223 224 225 | "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 | | | 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 | "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) |
︙ | ︙ | |||
279 280 281 282 283 284 285 | self.notebook_channels.set_current_page(self.channel_names.index(page)) # notebook invocation: else: #if type(page_num) == int: self.current_channel = self.channel_names[page_num] # if first selected, load current category try: | | | | | | 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 | self.notebook_channels.set_current_page(self.channel_names.index(page)) # notebook invocation: else: #if type(page_num) == int: self.current_channel = self.channel_names[page_num] # if first selected, load current category try: __print__(dbg.PROC, "channel_switch: try .first_show", self.channel().module); __print__(self.channel().first_show) __print__(self.channel().first_show()) except: __print__(dbg.INIT, "channel .first_show() initialization error") # convert ListStore iter to row number def rowno(self): (model, iter) = self.model_iter() return model.get_path(iter)[0] |
︙ | ︙ | |||
333 334 335 336 337 338 339 | url = self.selected("homepage") action.browser(url) # browse channel def on_homepage_channel_clicked(self, widget, event=2): if event == 2 or event.type == gtk.gdk._2BUTTON_PRESS: | | | | 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 | url = self.selected("homepage") action.browser(url) # browse channel def on_homepage_channel_clicked(self, widget, event=2): if event == 2 or event.type == gtk.gdk._2BUTTON_PRESS: __print__(dbg.UI, "dblclick") action.browser(self.channel().homepage) # reload stream list in current channel-category def on_reload_clicked(self, widget=None, reload=1): __print__(dbg.UI, "reload", reload, self.current_channel, self.channels[self.current_channel], self.channel().current) category = self.channel().current self.thread( lambda: ( self.channel().load(category,reload), reload and self.bookmarks.heuristic_update(self.current_channel,category) ) ) # thread a function, add to worker pool (for utilizing stop button) |
︙ | ︙ | |||
363 364 365 366 367 368 369 | thread = self.working.pop() thread.stop() # click in category list def on_category_clicked(self, widget, event, *more): category = self.channel().currentcat() | | | 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 | thread = self.working.pop() thread.stop() # click in category list def on_category_clicked(self, widget, event, *more): category = self.channel().currentcat() __print__(dbg.UI, "on_category_clicked", category, self.current_channel) self.on_reload_clicked(None, reload=0) pass # add current selection to bookmark store def bookmark(self, widget): self.bookmarks.add(self.row()) |
︙ | ︙ | |||
460 461 462 463 464 465 466 | # resort with tab order order = [module.strip() for module in conf.channel_order.lower().replace(".","_").replace("-","_").split(",")] ls = [module for module in (order) if (module in ls)] + [module for module in (ls) if (module not in order)] # step through for module in ls: | | | | | 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 | # resort with tab order order = [module.strip() for module in conf.channel_order.lower().replace(".","_").replace("-","_").split(",")] ls = [module for module in (order) if (module in ls)] + [module for module in (ls) if (module not in order)] # step through for module in ls: gui_startup(2/10.0 + 7/10.0 * float(ls.index(module))/len(ls), "loading module "+module) # skip module if disabled if conf.plugins.get(module, 1) == False: __print__(dbg.STAT, "disabled plugin:", module) continue # load plugin try: plugin = __import__("channels."+module, None, None, [""]) plugin_class = plugin.__dict__[module] # load .config settings from plugin conf.add_plugin_defaults(plugin_class.config, module) # add and initialize channel if issubclass(plugin_class, GenericChannel): self.channels[module] = plugin_class(parent=self) if module not in self.channel_names: # skip (glade) built-in channels self.channel_names.append(module) # other plugin types else: self.features[module] = plugin_class(parent=self) except Exception as e: __print__(dbg.INIT, "load_plugin_channels: error initializing:", module, ", exception:") import traceback traceback.print_exc() # default plugins conf.add_plugin_defaults(self.channels["bookmarks"].config, "bookmarks") #conf.add_plugin_defaults(self.channels["shoutcast"].config, "shoutcast") |
︙ | ︙ | |||
512 513 514 515 516 517 518 | conf.save("state", channelopts, nice=1) # apply gtkrc stylesheet def load_theme(self): if conf.get("theme"): for dir in (conf.dir, conf.share, "/usr/share"): | | | 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 | conf.save("state", channelopts, nice=1) # apply gtkrc stylesheet def load_theme(self): if conf.get("theme"): for dir in (conf.dir, conf.share, "/usr/share"): f = dir + "/themes/" + conf.theme + "/gtk-2.0/gtkrc" if os.path.exists(f): gtk.rc_parse(f) pass # end application and gtk+ main loop def gtk_main_quit(self, widget, *x): |
︙ | ︙ | |||
772 773 774 775 776 777 778 | # 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) | | | 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 | # 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) # load or set gtk.Entry text field elif (w and save and type(w)==gtk.Entry): config[key] = w.get_text() elif (w and type(w)==gtk.Entry): |
︙ | ︙ | |||
795 796 797 798 799 800 801 802 803 | # fill combobox def combobox_theme(self): # self.theme.combo_box_new_text() # find themes themedirs = (conf.share+"/themes", conf.dir+"/themes", "/usr/share/themes") themes = ["no theme"] [[themes.append(e) for e in os.listdir(dir)] for dir in themedirs if os.path.exists(dir)] # add to combobox for num,themename in enumerate(themes): | > > > > > > > | | 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 | # fill combobox def combobox_theme(self): # self.theme.combo_box_new_text() # find themes themedirs = (conf.share+"/themes", conf.dir+"/themes", "/usr/share/themes") themes = ["no theme"] [[themes.append(e) for e in os.listdir(dir)] for dir in themedirs if os.path.exists(dir)] __print__(dbg.STAT, themes) # prepare liststore store = gtk.ListStore(gobject.TYPE_STRING) self.theme.set_model(store) cell = gtk.CellRendererText() self.theme.pack_start(cell, True) self.theme.add_attribute(cell, "text", 0) # add to combobox for num,themename in enumerate(themes): store.append([themename]) if conf.theme == themename: self.theme.set_active(num) # erase this function, so it only ever gets called once self.combobox_theme = lambda: None # retrieve currently selected value |
︙ | ︙ | |||
1004 1005 1006 1007 1008 1009 1010 | self.load(self.default) self.urls.append(row["url"]) # simplified gtk TreeStore display logic (just one category for the moment, always rebuilt) def load(self, category, force=False): #self.liststore[category] = \ | | | 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 | self.load(self.default) self.urls.append(row["url"]) # simplified gtk TreeStore display logic (just one category for the moment, always rebuilt) def load(self, category, force=False): #self.liststore[category] = \ __print__(dbg.UI, category, self.streams.keys()) mygtk.columns(self.gtk_list, self.datamap, self.prepare(self.streams.get(category,[]))) # select a category in treeview def add_category(self, cat): if cat not in self.categories: # add category if missing self.categories.append(cat) |
︙ | ︙ | |||
1099 1100 1101 1102 1103 1104 1105 | #-- startup progress bar progresswin, progressbar = 0, 0 | | | | 1106 1107 1108 1109 1110 1111 1112 1113 1114 1115 1116 1117 1118 1119 1120 1121 1122 1123 1124 1125 1126 1127 1128 1129 1130 1131 1132 | #-- startup progress bar progresswin, progressbar = 0, 0 def gui_startup(p=0/100.0, msg="streamtuner2 is starting"): global progresswin,progressbar if not progresswin: # GtkWindow "progresswin" progresswin = gtk.Window() progresswin.set_property("title", "streamtuner2") progresswin.set_property("default_width", 300) progresswin.set_property("width_request", 300) progresswin.set_property("default_height", 30) progresswin.set_property("height_request", 30) #progresswin.set_property("window_position", "center") progresswin.set_property("decorated", False) progresswin.set_property("visible", True) # GtkProgressBar "progressbar" progressbar = gtk.ProgressBar() progressbar.set_property("visible", True) progressbar.set_property("show_text", True) |
︙ | ︙ | |||
1147 1148 1149 1150 1151 1152 1153 | # graphical if len(sys.argv) < 2: # prepare for threading in Gtk+ callbacks gobject.threads_init() | | | | 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 1181 1182 1183 1184 1185 | # 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 # run gui_startup(100/100.0) gtk.main() # invoke command-line interface else: import cli cli.StreamTunerCLI() |
︙ | ︙ |