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

⌈⌋ ⎇ branch:  streamtuner2


Check-in [a4cb6da4ac]

Overview
Comment:Add old Compound★ example plugin, slightly updated for current meta data scheme.
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA1: a4cb6da4ac10fa22844cf96488041120357c851c
User & Date: mario on 2015-04-14 16:57:36
Other Links: manifest | tags
Context
2015-04-14
17:03
Not implemented: `8tracks` (plugin name suffers from identifier mismatch, and it's not quite doable in ST2, because 8tracks requires feedback shortly after playback has begun; yet streamtuner can't inspect any configured audio player for actually doing so.) check-in: 327d2ed94c user: mario tags: trunk
16:57
Add old Compound★ example plugin, slightly updated for current meta data scheme. check-in: a4cb6da4ac user: mario tags: trunk
16:43
Old helper script to make streamripper add genre. Though there are `-D` pattern options that often work better. And KStreamripper or fIcy/fPls might be more modern. check-in: 39e61e9915 user: mario tags: trunk
Changes

Added contrib/bieber.py version [42bda022a3].

































































































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
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
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
# api: streamtuner2
# title: Bieber
# description: Bieber music
# url: http://www.justinbiebermusic.com/
# version: 5.2
# type: channel
# category: example
# config: 
#     { "name": "bieber_filter", "type": "text", "value": "BIEBERBLAST", "description": "So and so." }
# priority: joke
#
# This was an entertaining test plugin for development. (Compound function
# went into the search feature, and the compound channel plugin obviously.)
#
# It's however a very simple plugin, and hence a good basis for writing
# your own extensions.


from channels import *


# Bieber music filter plugin
class bieber(ChannelPlugin):


    # config data
    config = [
    ]
    

    # category map
    categories = ['the Biebs']
    default = 'the Biebs'
    current = 'the Biebs'




    # static category list
    def update_categories(self):
        # nothing to do here
        pass


    # just runs over all channel plugins, and scans their streams{} for matching entries
    def update_streams(self, cat, force=0):

        # result list
        entries = []
        
        # kill our current list, so we won't find our own entries
        self.streams = {}
        
        # swamp through all plugins
        for name,p in self.parent.channels.iteritems():
            #print "bieberquest: channel", name

            # subcategories in plugins        
            for cat,stations in p.streams.iteritems():
                #print "   bq cat", cat
            
                # station entries
                for row in stations:

                    # collect text fields, do some typecasting, lowercasing
                    text = "|".join([str(e) for e in row.values()])
                    text = text.lower()

                    # compare
                    if text.find("bieb") >= 0:
                    
                        # add to result list
                        row["genre"] = name + ": " + row.get("genre", "")
                        entries.append(row)

        # return final rows list
        return entries
        


Added contrib/compound.py version [cde5ef1bb0].









































































































































































































































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
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
49
50
51
52
53
54
55
56
57
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
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
143
144
145
146
147
148
# encoding: UTF-8
# api: streamtuner2
# title: Compound★
# description: combines station lists from multiple channels
# version: 0.1
# type: channel
# category: virtual
# url: http://fossil.include-once.org/streamtuner2/
# config: -
#    { name: compound_channels, type: text, value: "shoutcast, internet_radio, xiph, surfmusic", description: "Merge channels, or use all (*)" }
#    { name: compound_genres, type: text, value: "Top 40, x", description: "Extract categories, or just use intersection (x)" }
# priority: unsupported
# 
# Use this plugin to mix categories and their station entries from two
# or more different directory channels. It merges the lists, albeit in
# a rather crude way. (If anyone is interested, I could add a proper
# regex or something now.)
#
# Per default it lists only selected categories. But can be configured to
# merge just intersectioning categories/genres. Entry order is determined
# from station occourence count in channels AND their individual listener
# count (where available) using some guesswork to eliminate duplicates.



from channels import *
import action
import http
from config import conf





# merging channel
class compound (ChannelPlugin):

    # runtime options
    has_search = False
    listformat = "href"  # row entries will contain exact `listformat` classification
    audioformat = "audio/*"   # same as for correct encoding mime type
    
    # references
    parent = None
    
    # data
    streams = {}
    categories = []
    
    
    
    # which categories
    def update_categories(self):

        # as-is category list
        cats = self.split(conf.compound_genres)
        self.categories = [c for c in cats if c != "x"]
        
        # if intersection
        if "x" in cats:
            once = []
            for chan in self.channels():
                for add in self.flatten(self.parent.channels[chan].categories):
                    # second occourence in two channels
                    if add.lower() in once:
                        if add not in self.categories:
                            self.categories.append(add)
                    else: #if add not in self.categories:
                        once.append(add.lower())
        pass
                        
        
    # flatten our two-level categories list
    def flatten(self, a):
        return [i for sub in a for i in (sub if type(sub)==list else [sub])]


    # break up name lists        
    def split(self, s):
        return [s.strip() for s in s.split(",")]

    # get list of channels
    def channels(self):

        # get list
        ls = self.split(conf.compound_channels)
      
        # resolve "*"
        if "*" in ls:
            ls = self.parent.channel_names  # includes bookmarks
            if self.module in ls:
                ls.remove(self.module)	    # but not compound

        return ls
          
          
    # combine stream lists
    def update_streams(self, cat):
        r = []
        have = []
    
        # look through channels
        if cat in self.categories:
            for cn in self.channels():
            
                # get channel, refresh list
                c = self.parent.channels[cn]
                
                # 
                for row in self.get_streams(c, cat):

                    # copy
                    row = dict(row)
                    
                    #row["listeners"] = 1000 + row.get("listeners", 0) / 10
                    row["extra"] = cn  # or genre?
                    row["listformat"] = c.listformat

                    # duplicates                    
                    if row["title"].lower() in have or row["url"] in have:
                        for i,cmp in enumerate(r):
                            if cmp["title"].lower()==row["title"].lower() or cmp["url"].find(row["url"])>=0:
                                r[i]["listeners"] = row.get("listeners",0) + 5000
                        pass
                    else:
                        r.append(row)
                        have.append(row["title"].lower())  # we're comparing lowercase titles
                        have.append(row["url"][:row["url"].find("http://")])  # save last http:// part (internet-radio redirects to shoutcast urls)
                        
        # sort by listeners
        r = sorted(r, key=lambda x: -x.get("listeners", 0))
        return r


    # extract station list from other channel plugin    
    def get_streams(self, c, cat):

        # if empty?
        #c.load(cat)

        return c.streams.get(cat) \
            or c.update_streams(cat.replace(" ","")) \
            or []