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()
|
| ︙ | ︙ |