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

        # 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

            self.prepare_filters += [
                self.prepare_filter_icons,
            ]

            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)







>
|
|
|
>
|
|
|
|







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,
                ]
            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
                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)
  
                # 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
  







|







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("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
    
    # 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.
    postprocess_filters = []
    def postprocess(self, streams):
        for f in self.postprocess_filters:
            streams = filter(f, streams)
        return streams

    # Filter entries without title or url
    def postprocess_filter_required_fields(self, row):
        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):
        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







>











|
|



|

>

|

>


|







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 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 = [row for row in streams if f(row, self)]
        return streams

    # Filter entries without title or url
    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, 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
# encoding: UTF-8
# api: streamtuner2
# title: Filter Bitrate
# description: Cleans up low-quality entries from all station lists.
# version: 0.1
# 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: -
#
# 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


# Filter streams by bitrate
class filter_bitrate():

    meta = plugin_meta()
    module = "filter_bitrate"

    # Hijack GenericChannel.prepare
    def __init__(self, parent):
        channels.GenericChannel.postprocess_filters.append(self.filter_rows)


    # filter bitrate
    def filter_rows(self, row):
        b = int(row.get("bitrate", 0))
        if b <= 10:
            return True

        elif b < int(conf.min_bitrate_mp3):
            return False
        else:
            return True




|
|


<

|
|
>
|












|



|




|

|
>

|
|
|
|

>
|
<

|

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 out low-quality entries from all station lists.
# version: 0.2
# type: filter
# category: audio

# config:
#   { 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 *
from channels import GenericChannel


# Filter streams by bitrate
class filter_bitrate(object):

    meta = plugin_meta()
    module = "filter_bitrate"

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

    # 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"):
            return bits >= int(conf.min_bitrate_ogg)

        else:
            return bits >= int(conf.min_bitrate_mp3)