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

βŒˆβŒ‹ βŽ‡ branch:  streamtuner2


compound.py at [8783b94a0a]

File contrib/compound.py artifact cde5ef1bb0 part of check-in 8783b94a0a


# 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 []