Internet radio browser GUI for music/video streams from various directory services.

⌈⌋ ⎇ branch:  streamtuner2


Check-in [b042a5112f]

Overview
Comment:Add dirble search (but keep disabled: error "504 not allowed"). Reintroduce pagination (slower, but with progress bar now). Add "Popular" and "Recent" categories. Reenable thumbnail fetching rather than plain favicons.
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA1: b042a5112fe7223f0e3a476b85162456afeec290
User & Date: mario on 2017-02-25 00:06:23
Other Links: manifest | tags
Context
2017-02-26
21:42
Support json= POST requests. check-in: ef2604c3a4 user: mario tags: trunk
2017-02-25
00:06
Add dirble search (but keep disabled: error "504 not allowed"). Reintroduce pagination (slower, but with progress bar now). Add "Popular" and "Recent" categories. Reenable thumbnail fetching rather than plain favicons. check-in: b042a5112f user: mario tags: trunk
2017-02-23
22:12
Relabel record option tabs to options/meta/network; regroup flags roughly. Add more options for wget and youtube-dl. check-in: e6fe0f52d5 user: mario tags: trunk
Changes

Modified channels/dirble.py from [3e36e0af87] to [8ed175109e].

1
2
3
4
5
6

7
8
9
10
11
12
13
1
2
3
4
5

6
7
8
9
10
11
12
13





-
+







# encoding: UTF-8
# api: streamtuner2
# title: Dirble
# description: Song history tracker for Internet radio stations.
# url: http://dirble.com/
# version: 2.2
# version: 2.3
# type: channel
# category: radio
# config:
#    { name: dirble_api_key,  value: "",  type: text,  description: Alternative API access key., hidden: 1 }
# png:
#    iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAIAAACQkWg2AAAAA3NCSVQICAjb4U/gAAACP0lE
#    QVQokVVSO0+UURA9M/d+jyWbBVcQFSQhPqJSYBRFA5pVoFGURApjYYWtvYUNP8FKOwsttDFq
23
24
25
26
27
28
29
30

31
32
33
34

35
36

37
38
39
40
41
42
43
23
24
25
26
27
28
29

30
31
32
33

34


35
36
37
38
39
40
41
42







-
+



-
+
-
-
+







#    u+uAt1KkwvVxAGJsEEWxEWzGm4iV8l1HM9K0BmEkrP8BlhoAUfmOxecAAAAASUVORK5CYII=
# priority: optional
# documentation: http://dirble.com/developer/api
# extraction-method: json
#
#
# Dirble is a user-contributed list of radio stations,
# auot-updating song titles and station information.
# auto-updating song titles and station information.
# Homepages are there now, and thus favicons readily
# available. Extra station banners aren't fetched.
#
# It provides a JSON API. Which in this newer version
# It provides a JSON API. Since plugin version 2.3
# is actually speedier, as it doesn't strictly impose
# pagination roundtrips anymore.
# we're back to slower pagination requests however.
#
# Response times are fixed now by overriding the HTTP
# encoding. (A python-requests issue mostly).


import json
from config import *
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
58
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







-
+














-
+





-
-
-
-
+
+
+
+
+
+
-
+




+










+







#    station banners are not accessible per CDN.
#
class dirble (ChannelPlugin):

    # control flags
    has_search = False
    listformat = "srv"
    titles = dict(listeners=False, playing="Location")
    titles = dict(playing="Location")
    base = "http://api.dirble.com/v2/{}"
    key = "a0bdd7b8efc2f5d1ebdf1728b65a07ece4c73de5"


    # Retrieve cat list and map
    def update_categories(self):
        cats = []
        for row in self.api("categories/tree"):
            cats += [row["title"]]
            self.catmap[row["title"]] = row["id"]
            if row.get("children"):
                cats += [[c["title"] for c in row["children"]]]
                for c in row["children"]:
                    self.catmap[c["title"]] = c["id"]
        self.categories = cats
        self.categories = ["Popular", "Recent"] + cats


    # Fetch entries
    def update_streams(self, cat, search=None):
        self.progress(1)
        return [
            self.unpack(r)
               for r in
            self.api("category/{}/stations".format(self.catmap.get(cat, 0)), all=1)# per_page=200 won't work
        if search:
            r = self.api("search", query=search, page=0, pages=1)
        elif cat in ("Popular", "Recent"):
            r = self.api("stations/{}".format(cat.lower()), pages=15)
        else:
            r = self.api("category/{}/stations".format(self.catmap.get(cat, 0)), pages=10)
        ]
        return [self.unpack(row) for row in r]

    
    # Extract rows
    def unpack(self, r):
        listeners = 0

        # find stream
        if len(r.get("streams", [])):

            # compare against first entry
            s = r["streams"][0]

            # select "best" stream if there are alternatives
            if len(r["streams"]) > 0:
                for alt in r["streams"]:
                    listeners += alt.get("listeners", 0)

                    # set defaults
                    if not alt.get("content_type"):
                        alt["content_type"] = "?"
                    if not alt.get("bitrate"):
                        alt["bitrate"] = 16
                    alt["content_type"] = alt["content_type"].strip()  # There's a "\r\n" in nearly every entry :?
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
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
180
181
182
183
184


185
186
187
188
189
190
191
192







+
-
+
+












-
+
+
+
+
+
+


+
+
+
+
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+

+
-
-
+
+






            genre = " ".join(c["slug"] for c in r["categories"]),
            title = r["name"],
            playing = "{} {}".format(r.get("country"), r.get("description", "")),
            homepage = ahttp.fix_url(r["website"]),
            url = s["stream"],
            format = s["content_type"],
            bitrate = s["bitrate"],
            listeners = listeners,
           # img = r["image"]["image"]["thumb"]["url"], # CDN HTTPS trip up requests.get
            img = r.get("image", {}).get("thumb", {}).get("url", ""), # CDN HTTPS trip up requests.get
            img_resize = 32,
            state = self.state_map.get(int(s["status"]), ""),
            deleted = s.get("timedout", False),
        )

    # Streams contain a `status`, here mapped onto arbitrary Gtk icons        
    state_map = {0:"gtk-media-pause", 1:"gtk-media-next", 2:"gtk-media-rewind"}

    # Weighting bitrate and audio format for alternative stream URLs
    format_q = {"?":0.75, "audio/mpeg":1.0, "audio/aac":1.25, "audio/aacp":1.35, "audio/ogg":1.50}


    # Patch API url together, send request, decode JSON list
    def api(self, method, **params):
    def api(self, method, pages=1, **params):
        # pagination parameters
        if pages > 1:
            params["page"] = 0
            params["per_page"] = 30
            params["offset"] = 0
        params["token"] = conf.dirble_api_key or self.key
        try:
            r = []
            # paginate results
            for params["page"] in range(0, pages):
                self.progress(pages)
            # HTTP request and JSON decoding take a while
            r = ahttp.get(self.base.format(method), params, encoding="utf-8")
            r = json.loads(r)
            if isinstance(r, dict) and "error" in r:
                log.ERR(r["error"])
                raise Exception
                # send HTTP request and extract JSON
                add = ahttp.get(self.base.format(method), params, encoding="utf-8")
                add = json.loads(add)
                # check for errors
                if isinstance(add, dict) and add.get("error"):
                    if r:
                        log.WARN(add["error"])
                        break
                    else:
                        raise Exception(add)
                r += add
            # cut down stream list
            self.progress(0)
            if len(r) > int(conf.max_streams):
                del r[int(conf.max_streams):]
            #if len(r) > int(conf.max_streams):
            #    del r[int(conf.max_streams):]
        except Exception as e:
            log.ERR("Dirble API retrieval failure:", e)
            r = []
        return r