This file is indexed.

/usr/share/streamtuner2/channels/shoutcast.py is in streamtuner2 2.0.8-5.

This file is owned by root:root, with mode 0o644.

The actual contents of the file can be viewed below.

  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
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
193
194
195
196
197
198
199
200
#
# api: streamtuner2
# title: shoutcast
# description: Channel/tab for Shoutcast.com directory
# depends: pq, re, http
# version: 1.2
# author: Mario
# original: Jean-Yves Lefort
#
# Shoutcast is a server software for audio streaming. It automatically spools
# station information on shoutcast.com, which this plugin can read out. But
# since the website format is often changing, we now use PyQuery HTML parsing
# in favour of regular expression (which still work, are faster, but not as
# reliable).
#
# This was previously a built-in channel plugin. It just recently was converted
# from a glade predefined GenericChannel into a ChannelPlugin.
#
#
# NOTES
#
# Just found out what Tunapie uses:
#    http://www.shoutcast.com/sbin/newxml.phtml?genre=Top500
# It's a simpler list format, no need to parse HTML. However, it also lacks
# homepage links. But maybe useful as alternate fallback...
# Also:
#   http://www.shoutcast.com/sbin/newtvlister.phtml?alltv=1
#   http://www.shoutcast.com/sbin/newxml.phtml?search=
#
#
#


import http
import urllib
import re
from pq import pq
from config import conf
#from channels import *    # works everywhere but in this plugin(???!)
import channels
__print__ = channels.__print__



# SHOUTcast data module                                          ----------------------------------------
class shoutcast(channels.ChannelPlugin):

        # desc
        api = "streamtuner2"
        module = "shoutcast"
        title = "SHOUTcast"
        version = 1.2
        homepage = "http://www.shoutcast.com/" 
        base_url = "http://shoutcast.com/"
        listformat = "audio/x-scpls"

        # settings
        config = [
            dict(name="pyquery", type="boolean", value=0, description="Use more reliable PyQuery HTML parsing\ninstead of faster regular expressions."),
            dict(name="debug", type="boolean", value=0, description="enable debug output"),
        ]
        
        # categories
        categories = ['Alternative', ['Adult Alternative', 'Britpop', 'Classic Alternative', 'College', 'Dancepunk', 'Dream Pop', 'Emo', 'Goth', 'Grunge', 'Hardcore', 'Indie Pop', 'Indie Rock', 'Industrial', 'Modern Rock', 'New Wave', 'Noise Pop', 'Power Pop', 'Punk', 'Ska', 'Xtreme'], 'Blues', ['Acoustic Blues', 'Chicago Blues', 'Contemporary Blues', 'Country Blues', 'Delta Blues', 'Electric Blues'], 'Classical', ['Baroque', 'Chamber', 'Choral', 'Classical Period', 'Early Classical', 'Impressionist', 'Modern', 'Opera', 'Piano', 'Romantic', 'Symphony'], 'Country', ['Americana', 'Bluegrass', 'Classic Country', 'Contemporary Bluegrass', 'Contemporary Country', 'Honky Tonk', 'Hot Country Hits', 'Western'], 'Decades', ['30s', '40s', '50s', '60s', '70s', '80s', '90s'], 'Easy Listening', ['Exotica', 'Light Rock', 'Lounge', 'Orchestral Pop', 'Polka', 'Space Age Pop'], 'Electronic', ['Acid House', 'Ambient', 'Big Beat', 'Breakbeat', 'Dance', 'Demo', 'Disco', 'Downtempo', 'Drum and Bass', 'Electro', 'Garage', 'Hard House', 'House', 'IDM', 'Jungle', 'Progressive', 'Techno', 'Trance', 'Tribal', 'Trip Hop'], 'Folk', ['Alternative Folk', 'Contemporary Folk', 'Folk Rock', 'New Acoustic', 'Traditional Folk', 'World Folk'], 'Inspirational', ['Christian', 'Christian Metal', 'Christian Rap', 'Christian Rock', 'Classic Christian', 'Contemporary Gospel', 'Gospel', 'Southern Gospel', 'Traditional Gospel'], 'International', ['African', 'Arabic', 'Asian', 'Bollywood', 'Brazilian', 'Caribbean', 'Celtic', 'Chinese', 'European', 'Filipino', 'French', 'Greek', 'Hindi', 'Indian', 'Japanese', 'Jewish', 'Klezmer', 'Korean', 'Mediterranean', 'Middle Eastern', 'North American', 'Russian', 'Soca', 'South American', 'Tamil', 'Worldbeat', 'Zouk'], 'Jazz', ['Acid Jazz', 'Avant Garde', 'Big Band', 'Bop', 'Classic Jazz', 'Cool Jazz', 'Fusion', 'Hard Bop', 'Latin Jazz', 'Smooth Jazz', 'Swing', 'Vocal Jazz', 'World Fusion'], 'Latin', ['Bachata', 'Banda', 'Bossa Nova', 'Cumbia', 'Latin Dance', 'Latin Pop', 'Latin Rock', 'Mariachi', 'Merengue', 'Ranchera', 'Reggaeton', 'Regional Mexican', 'Salsa', 'Tango', 'Tejano', 'Tropicalia'], 'Metal', ['Black Metal', 'Classic Metal', 'Extreme Metal', 'Grindcore', 'Hair Metal', 'Heavy Metal', 'Metalcore', 'Power Metal', 'Progressive Metal', 'Rap Metal'], 'Misc', [], 'New Age', ['Environmental', 'Ethnic Fusion', 'Healing', 'Meditation', 'Spiritual'], 'Pop', ['Adult Contemporary', 'Barbershop', 'Bubblegum Pop', 'Dance Pop', 'Idols', 'JPOP', 'Oldies', 'Soft Rock', 'Teen Pop', 'Top 40', 'World Pop'], 'Public Radio', ['College', 'News', 'Sports', 'Talk'], 'Rap', ['Alternative Rap', 'Dirty South', 'East Coast Rap', 'Freestyle', 'Gangsta Rap', 'Hip Hop', 'Mixtapes', 'Old School', 'Turntablism', 'West Coast Rap'], 'Reggae', ['Contemporary Reggae', 'Dancehall', 'Dub', 'Ragga', 'Reggae Roots', 'Rock Steady'], 'Rock', ['Adult Album Alternative', 'British Invasion', 'Classic Rock', 'Garage Rock', 'Glam', 'Hard Rock', 'Jam Bands', 'Piano Rock', 'Prog Rock', 'Psychedelic', 'Rockabilly', 'Surf'], 'Soundtracks', ['Anime', 'Kids', 'Original Score', 'Showtunes', 'Video Game Music'], 'Talk', ['BlogTalk', 'Comedy', 'Community', 'Educational', 'Government', 'News', 'Old Time Radio', 'Other Talk', 'Political', 'Scanner', 'Spoken Word', 'Sports', 'Technology'], 'Themes', ['Adult', 'Best Of', 'Chill', 'Eclectic', 'Experimental', 'Female', 'Heartache', 'Instrumental', 'LGBT', 'Party Mix', 'Patriotic', 'Rainy Day Mix', 'Reality', 'Sexy', 'Shuffle', 'Travel Mix', 'Tribute', 'Trippy', 'Work Mix']]
        #["default", [], 'TopTen', [], 'Alternative', ['College', 'Emo', 'Hardcore', 'Industrial', 'Punk', 'Ska'], 'Americana', ['Bluegrass', 'Blues', 'Cajun', 'Folk'], 'Classical', ['Contemporary', 'Opera', 'Symphonic'], 'Country', ['Bluegrass', 'New Country', 'Western Swing'], 'Electronic', ['Acid Jazz', 'Ambient', 'Breakbeat', 'Downtempo', 'Drum and Bass', 'House', 'Trance', 'Techno'], 'Hip Hop', ['Alternative', 'Hardcore', 'New School', 'Old School', 'Turntablism'], 'Jazz', ['Acid Jazz', 'Big Band', 'Classic', 'Latin', 'Smooth', 'Swing'], 'Pop/Rock', ['70s', '80s', 'Classic', 'Metal', 'Oldies', 'Pop', 'Rock', 'Top 40'], 'R&B/Soul', ['Classic', 'Contemporary', 'Funk', 'Smooth', 'Urban'], 'Spiritual', ['Alternative', 'Country', 'Gospel', 'Pop', 'Rock'], 'Spoken', ['Comedy', 'Spoken Word', 'Talk'], 'World', ['African', 'Asian', 'European', 'Latin', 'Middle Eastern', 'Reggae'], 'Other/Mixed', ['Eclectic', 'Film', 'Instrumental']]
        current = ""
        default = "Alternative"
        empty = ""

        
        # redefine
        streams = {}
        
            
        # extracts the category list from shoutcast.com,
        # sub-categories are queried per 'AJAX'
        def update_categories(self):
            html = http.get(self.base_url)
            self.categories = ["default"]
            __print__( html )

            # <h2>Radio Genres</h2>
	    rx_main = re.compile(r'<li class="prigen" id="(\d+)".+?<a href="/radio/([\w\s]+)">[\w\s]+</a></li>', re.S)
	    rx_sub = re.compile(r'<a href="/radio/([\w\s\d]+)">[\w\s\d]+</a></li>')
            for uu in rx_main.findall(html):
                __print__(uu)
		(id,name) = uu
                name = urllib.unquote(name)

                # main category
                self.categories.append(name)

                # sub entries
                html = http.ajax("http://shoutcast.com/genre.jsp", {"genre":name, "id":id})
                __print__(html)
                sub = rx_sub.findall(html)
                self.categories.append(sub)

            # it's done
            __print__(self.categories)
            conf.save("cache/categories_shoutcast", self.categories)
            pass



        #def strip_tags(self, s):
        #    rx = re.compile(""">(\w+)<""")
        #    return " ".join(rx.findall(s))

        # downloads stream list from shoutcast for given category
        def update_streams(self, cat, search=""):

            if (not cat or cat == self.empty):
                __print__("nocat")
                return []
            ucat = urllib.quote(cat)

            
            # new extraction regex
            if not conf.get("pyquery") or not pq:
              rx_stream = re.compile("""
                 <a\s+class="?playbutton\d?[^>]+id="(\d+)".+?
		 <a\s+class="[\w\s]*title[\w\s]*"[^>]+href="(http://[^">]+)"[^>]*>([^<>]+)</a>.+?
                 (?:Recently\s*played|Coming\s*soon|Now\s*playing):\s*([^<]*).+?
                 ners">(\d*)<.+?
                 bitrate">(\d*)<.+?
                 type">([MP3AAC]*)
                 """, re.S|re.I|re.X)
            rx_next = re.compile("""onclick="showMoreGenre""")



            # loop
            entries = []
            next = 0
            max = int(conf.max_streams)
            count = max
            while (next < max):

               # page
               url = "http://www.shoutcast.com/genre-ajax/" + ucat
	       referer = url.replace("/genre-ajax", "/radio")
	       params = { "strIndex":"0", "count":str(count), "ajax":"true", "mode":"listeners", "order":"desc" }
               html = http.ajax(url, params, referer)   #,feedback=self.parent.status)

               __print__(html)

               # regular expressions
               if not conf.get("pyquery") or not pq:
               
                   # extract entries
                   self.parent.status("parsing document...")
                   __print__("loop-rx")
                   for uu in rx_stream.findall(html):
                       (id, homepage, title, playing, ls, bit, fmt) = uu
                       __print__(uu)
                       entries += [{
                           "title": self.entity_decode(title),
                           "url": "http://yp.shoutcast.com/sbin/tunein-station.pls?id=" + id,
                           "homepage": http.fix_url(homepage),
                           "playing": self.entity_decode(playing),
                           "genre": cat, #self.strip_tags(uu[4]),
                           "listeners": int(ls),
                           "max": 0, #int(uu[6]),
                           "bitrate": int(bit),
                           "format": self.mime_fmt(fmt),
                       }]

               # PyQuery parsing
               else:
                   # iterate over DOM
                   for div in (pq(e) for e in pq(html).find("div.dirlist")):

                       entries.append({
                            "title": div.find("a.clickabletitleGenre, div.stationcol a").attr("title"),
                            "url": div.find("a.playbutton, a.playbutton1, a.playimage").attr("href"),
                            "homepage": http.fix_url(div.find("a.playbutton.clickabletitle, a[target=_blank], a.clickabletitleGenre, a.clickabletitle, div.stationcol a, a").attr("href")),
                            "playing": div.find("div.playingtextGenre, div.playingtext").attr("title"),
                            "listeners": int(div.find("div.dirlistners").text()),
                            "bitrate": int(div.find("div.dirbitrate").text()),
                            "format": self.mime_fmt(div.find("div.dirtype").text()),
                            "max": 0,
                            "genre": cat,
                           # "title2": e.find("a.playbutton").attr("name"),
                       })


               # display partial results (not strictly needed anymore, because we fetch just one page)
               self.parent.status()
               self.update_streams_partially_done(entries)
               
               # more pages to load?
               if (re.search(rx_next, html)):
                  next += count
               else:
                  next = 99999
            
            #fin
            __print__(entries)
            return entries