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

⌈⌋ ⎇ branch:  streamtuner2


Check-in [ae2f48310a]

Overview
Comment:Add default filters only once in GenericChannel.__init__ Allow preprocess_filter callbacks access to current channel object. (Used by filter_bitrate to recognize .audioformat if row[format] is absent.)
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA1: ae2f48310aa69498c73e09f2a64fc7ff4c17b81a
User & Date: mario on 2015-05-12 22:18:54
Other Links: manifest | tags
Context
2015-05-13
00:00
Move mime_fmt() into regular function. Fix live365 ahttp feedback= bug. Regroup functions and update a few comments in channels/__init__ check-in: 2335ea7a46 user: mario tags: trunk
2015-05-12
22:18
Add default filters only once in GenericChannel.__init__ Allow preprocess_filter callbacks access to current channel object. (Used by filter_bitrate to recognize .audioformat if row[format] is absent.) check-in: ae2f48310a user: mario tags: trunk
22:17
Add plugin defaults (for newly added options, but previously active modules) in any case when starting with -D flag. Save settings.json in json.dumps(sort_keys=True) mode. check-in: 3497339549 user: mario tags: trunk
Changes

Modified channels/__init__.py from [8b2d122cbc] to [51e13176b4].

131
132
133
134
135
136
137

138
139
140
141
142
143
144








145
146
147
148
149
150
151
131
132
133
134
135
136
137
138







139
140
141
142
143
144
145
146
147
148
149
150
151
152
153







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








        # add default options values to config.conf.* dict
        conf.add_plugin_defaults(self.meta, self.module)
        
        # Only if streamtuner2 is run in graphical mode        
        if (parent):
            # Update/display stream processors
            if not self.prepare_filters:
            self.prepare_filters += [
                self.prepare_filter_icons,
            ]
            self.postprocess_filters += [
                self.postprocess_filter_required_fields,
                self.postprocess_filter_homepage,
            ]
                self.prepare_filters += [
                    self.prepare_filter_icons,
                ]
            if not self.postprocess_filters:
                self.postprocess_filters += [
                    self.postprocess_filter_required_fields,
                    self.postprocess_filter_homepage,
                ]
            # Load cache, instantiate Gtk widgets
            self.cache()
            self.gui(parent)

        # Stub for ST2 main window / dispatcher
        else:
            self.parent = stub_parent(None)
302
303
304
305
306
307
308
309

310
311
312
313
314
315
316
304
305
306
307
308
309
310

311
312
313
314
315
316
317
318







-
+







                new_streams = self.update_streams(category)
  
            # Postprocess new list of streams (e.g. assert existing title and url)
            if new_streams:
                try:
                    new_streams = self.postprocess(new_streams)
                except Exception as e:
                    log.ERR(e, "Updating new streams, postprocessing failed:", row)
                    log.ERR("Updating new streams, postprocessing failed:", e)
  
                # don't lose forgotten streams
                if conf.retain_deleted:
                   self.streams[category] = new_streams + self.deleted_streams(new_streams, self.streams.get(category,[]))
                else:
                   self.streams[category] = new_streams
  
361
362
363
364
365
366
367

368
369
370
371
372
373
374
375
376
377
378
379
380


381
382
383
384

385

386
387

388

389
390
391

392
393
394
395
396
397
398
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381


382
383
384
385
386

387
388
389
390

391
392
393
394
395

396
397
398
399
400
401
402
403







+











-
-
+
+



-
+

+

-
+

+


-
+







    
    # Prepare stream list for display
    prepare_filters = []
    def prepare(self, streams):
        for f in self.prepare_filters:
            map(f, streams)
        return streams

    # state icon: bookmark star, or deleted mark
    def prepare_filter_icons(self, row):
        if conf.show_bookmarks:# and "bookmarks" in self.parent.channels:
            row["favourite"] = self.parent.bookmarks.is_in(row.get("url", "file:///tmp/none"))
        if not row.get("state"):
            if row.get("favourite"):
                row["state"] = gtk.STOCK_ABOUT
            if row.get("deleted"):
                row["state"] = gtk.STOCK_DELETE


    # Stream list prepareations directly after reload,
    # can remove entries, or just update fields.
    # Stream list preparations - invoked directly after reload(),
    # callbacks can remove entries, or just update fields.
    postprocess_filters = []
    def postprocess(self, streams):
        for f in self.postprocess_filters:
            streams = filter(f, streams)
            streams = [row for row in streams if f(row, self)]
        return streams

    # Filter entries without title or url
    def postprocess_filter_required_fields(self, row):
    def postprocess_filter_required_fields(self, row, channel):
        return not len(set(["", None]) & set([row.get("title"), row.get("url")]))

    # Deduce homepage URLs from title
    # by looking for www.xyz.com domain names
    def postprocess_filter_homepage(self, row):
    def postprocess_filter_homepage(self, row, channel):
        if not row.get("homepage"):
            url = self.rx_www_url.search(row.get("title", ""))
            if url:
                url = url.group(0).lower().replace(" ", "")
                url = (url if url.find("www.") == 0 else "www."+url)
                row["homepage"] = ahttp.fix_url(url)
                print row

Modified channels/filter_bitrate.py from [123128ab48] to [1de3cdcb14].

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

35
36


37
38
39
40
41




42

43

44
45
46

47
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
35

36
37
38




39
40
41
42
43
44

45

46

47
48



-
-
+
+


-

-
-
-
+
+
+
+












-
+



-
+




-
+

-
+
+

-
-
-
-
+
+
+
+

+
-
+
-

-
+

# encoding: UTF-8
# api: streamtuner2
# title: Filter Bitrate
# description: Cleans up low-quality entries from all station lists.
# version: 0.1
# description: Cleans out low-quality entries from all station lists.
# version: 0.2
# type: filter
# category: audio
# priority: optional
# config:
#   { name: min_bitrate_mp3, value: 32, type: select, select: "32|48|64|80|96|112|128|144|160", description: Filter MP3 streams with lesser audio quality. }
#   [ name: min_bitrate_ogg, value: 32, type: select, select: "32|48|64|80|96|112|128|144|160", description: OggVorbis/AAC sound ok with slightly lower bitrates still. ]
# hooks: -
#   { name: min_bitrate_mp3, value: 32, type: select, select: "32=32kbit/s|48=48kbit/s|64=64kbit/s|80=80kbit/s|96=96kbit/s|112=112kbit/s|128=128kbit/s|144=144kbit/s|160=160kbit/s", description: Filter MP3 streams with lesser audio quality. }
#   { name: min_bitrate_ogg, value: 48, type: select, select: "32=32kbit/s|48=48kbit/s|64=64kbit/s|80=80kbit/s|96=96kbit/s|112=112kbit/s|128=128kbit/s|144=144kbit/s|160=160kbit/s", description: Minimum bitrate for Ogg Vorbis and AAC. }
# priority: optional
# hooks: postprocess_filters
#
# Plugin that filters radio stations on bitrate (audio quality).
# Anything below 64 kbit/s often sounds awful for MP3 streams.
# While AAC or Ogg Vorbis might still be acceptable sometimes.
#
# This functionality was previously just implemented for the Xiph
# plugin. It's now available as generic filter for all channels.
# Beware that some channels provide more entries with low bitrates,
# thus might appear completely empty.


from config import *
import channels
from channels import GenericChannel


# Filter streams by bitrate
class filter_bitrate():
class filter_bitrate(object):

    meta = plugin_meta()
    module = "filter_bitrate"

    # Hijack GenericChannel.prepare
    # Hijack postprocessing filters in stream_update handler 
    def __init__(self, parent):
        channels.GenericChannel.postprocess_filters.append(self.filter_rows)
        GenericChannel.postprocess_filters.append(self.filter_rows)
        print GenericChannel.postprocess_filters

    # filter bitrate
    def filter_rows(self, row):
        b = int(row.get("bitrate", 0))
        if b <= 10:
    # Filter row on bitrate
    def filter_rows(self, row, channel):
        bits = int(row.get("bitrate", 0))
        if bits <= 10:
            return True
        elif row.get("format", channel.audioformat) in ("audio/ogg", "audio/aac", "audio/aacp"):
        elif b < int(conf.min_bitrate_mp3):
            return bits >= int(conf.min_bitrate_ogg)
            return False
        else:
            return True
            return bits >= int(conf.min_bitrate_mp3)