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

⌈⌋ ⎇ branch:  streamtuner2


Check-in [7c085d54f9]

Overview
Comment:Add windows media guide radio lists (ASX). Fixed action module to extract with case-insensitve matches for that playlist format. (It's not really XML after all.)
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA1: 7c085d54f9f21a9e0955e9cb0da6dfd3de894196
User & Date: mario on 2015-05-07 01:17:42
Other Links: manifest | tags
Context
2015-05-07
01:20
Add description: line. check-in: 749946913c user: mario tags: trunk
01:17
Add windows media guide radio lists (ASX). Fixed action module to extract with case-insensitve matches for that playlist format. (It's not really XML after all.) check-in: 7c085d54f9 user: mario tags: trunk
01:16
Another radio station directory: listenlive.eu check-in: b0e9e031d9 user: mario tags: trunk
Changes

Modified action.py from [d816e37503] to [02cf432a5f].

95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
)

# Playlist format content probing (assert type)
playlist_content_map = [
   ("pls",  r""" (?i)\[playlist\].*NumberOfEntries """),
   ("xspf", r""" <\?xml .* <playlist .* ((?i)http://xspf\.org)/ns/0/ """),
   ("m3u",  r""" ^ \s* \#(EXT)?M3U """),
   ("asx" , r""" <asx\b """),
   ("smil", r""" <smil[^>]*> .* <seq> """),
   ("html", r""" (?i)<(audio|video)\b[^>]+\bsrc\s*=\s*["']?https?:// """),
   ("wpl",  r""" <\?wpl \s+ version="1\.0" \s* \?> """),
   ("b4s",  r""" <WinampXML> """),   # http://gonze.com/playlists/playlist-format-survey.html
   ("jspf", r""" ^ \s* \{ \s* "playlist": \s* \{ """),
   ("asf",  r""" ^ \[Reference\] .*? ^Ref\d+= """),
   ("url",  r""" ^ \[InternetShortcut\] .*? ^URL= """),







|







95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
)

# Playlist format content probing (assert type)
playlist_content_map = [
   ("pls",  r""" (?i)\[playlist\].*NumberOfEntries """),
   ("xspf", r""" <\?xml .* <playlist .* ((?i)http://xspf\.org)/ns/0/ """),
   ("m3u",  r""" ^ \s* \#(EXT)?M3U """),
   ("asx" , r""" (?i) <asx\b """),
   ("smil", r""" <smil[^>]*> .* <seq> """),
   ("html", r""" (?i)<(audio|video)\b[^>]+\bsrc\s*=\s*["']?https?:// """),
   ("wpl",  r""" <\?wpl \s+ version="1\.0" \s* \?> """),
   ("b4s",  r""" <WinampXML> """),   # http://gonze.com/playlists/playlist-format-survey.html
   ("jspf", r""" ^ \s* \{ \s* "playlist": \s* \{ """),
   ("asf",  r""" ^ \[Reference\] .*? ^Ref\d+= """),
   ("url",  r""" ^ \[InternetShortcut\] .*? ^URL= """),
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
        # regex scheme
        rules = self.extr_urls[fmt]
        rows = []
        fields = [name for name in ("url", "title", "homepage", "genre", "playing") if rules.get(name)]

        # Block-wise processing
        if rules.get("split"):
            for part_src in re.split(rules["split"], self.src, re.X):
                row = {}
                for name in fields:
                    val = self.field(name, rules, part_src)
                    if val and val[0]:
                        row[name] = val[0]
                if row.get("url"):
                    rows.append(row)







|







362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
        # regex scheme
        rules = self.extr_urls[fmt]
        rows = []
        fields = [name for name in ("url", "title", "homepage", "genre", "playing") if rules.get(name)]

        # Block-wise processing
        if rules.get("split"):
            for part_src in re.split(rules["split"], self.src, 0, re.X):
                row = {}
                for name in fields:
                    val = self.field(name, rules, part_src)
                    if val and val[0]:
                        row[name] = val[0]
                if row.get("url"):
                    rows.append(row)
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
            url   = r"(?x) <location> (\w+://[^<>\s]+) </location> ",
            title = r"(?x) <title> ([^<>]+) ",
            homepage = r"(?x) <info> ([^<>]+) ",
            playing  = r"(?x) <annotation> ([^<>]+) ",
            unesc = "xml",
        ),
        "asx": dict(
            split = r" (?x) <entry[^>]*> ",
            url   = r" (?x) <ref \b[^>]+\b href \s*=\s* [\'\"] (\w+://[^\s\"\']+) [\'\"] ",
            title = r"(?x) <title> ([^<>]+) ",
            unesc = "xml",
        ),
        "smil": dict(
            url   = r" (?x) <(?:audio|video|media)\b [^>]+ \b src \s*=\s* [^\"\']? \s* (\w+://[^\"\'\s\>]+) ",
            unesc = "xml",
        ),
        "jspf": dict(







|
|
|







435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
            url   = r"(?x) <location> (\w+://[^<>\s]+) </location> ",
            title = r"(?x) <title> ([^<>]+) ",
            homepage = r"(?x) <info> ([^<>]+) ",
            playing  = r"(?x) <annotation> ([^<>]+) ",
            unesc = "xml",
        ),
        "asx": dict(
            split = r" (?ix) <entry[^>]*> ",
            url   = r" (?ix) <ref \b[^>]+\b href \s*=\s* [\'\"] (\w+://[^\s\"\']+) [\'\"] ",
            title = r" (?ix) <title> ([^<>]+) ",
            unesc = "xml",
        ),
        "smil": dict(
            url   = r" (?x) <(?:audio|video|media)\b [^>]+ \b src \s*=\s* [^\"\']? \s* (\w+://[^\"\'\s\>]+) ",
            unesc = "xml",
        ),
        "jspf": dict(

Added contrib/windowsmedia.py version [eb5ea6c6b8].

















































































































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
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
# encoding: UTF-8
# api: streamtuner2
# title: WindowsMedia
# description: 
# url: http://windowsmedia.com/
# version: 0.3
# type: channel
# category: radio
# config:
#   { name: windowsmedia_culture, type: select, value: en-gb, select: "en-gb|de-de|da-dk|cs-cz|es-es|fr-fr|it-it|nl-nl|pl-pl|tr-tr|pt-pt|pt-br|en-us", description: "Country/language preference (for localized ads:?)" }
# png:
#   iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAAk1BMVEWwMjIAAQACAAoSAwQ2CQcAH5VqERZsESgNK489KFwA
#   MrMAN6UQQwAXNpEAPakAQJ8AQ44RO7S2JQ8ASr6vLwqPOgoZYgCCSgvPPgVCbAF9XwzMTRrqUAHNY0VemADjbTiShjOYhiJR
#   oApJqgRerwlUtgBjwgDAsQDdqQB02AnowgP+soDfywDs2RL25Qr4727/8rdsT1F2AAAAAXRSTlMAQObYZgAAAAFiS0dEAIgF
#   HUgAAAAJcEhZcwAACxMAAAsTAQCanBgAAAAHdElNRQffBQcANDDneHDIAAAAlUlEQVQY02VPWxKCMAzcKmIF5GGrVkVAKA+x
#   Ve9/OhUK0xmTj2Q3yU4WAPnmL6Y69rDQwJB/bDOLpYNDEFgc352betYh4EIcK5lVgVHnIk/3kCdp5g7PHykv742sp43LFS4y
#   lMMNgbvebJl3U6ptjSxFFDPop+7NF5RFcVK8X6qfPIS+n0BrNf9FQ98retXZDlfoxuYDwYsJfXHQg0AAAAAASUVORK5CYII=
# png-orig: https://openclipart.org/detail/176727/windows-bug
# priority: extra
# status: unsupported
#
# Well, this one is Windows-specific, so naturally uses
# horrible formats WAX ( ASX ) for playlists. Still can
# be parsed by action module, but possibly falling back
# onto raw extraction etc.
#
# Only fetches the first page for each category anyway.
# And there's no specific category extraction, so stuck
# on the UK entries.
#
# Most entries are lower bitrates, 32 to 64 kbit/s MP3.


import re
from config import *
from channels import *
import ahttp


# Yay, windows playlists.
class windowsmedia (ChannelPlugin):


    # control flags
    has_search = False
    listformat = "wax"
    audioformat = "audio/mpeg"
    titles = dict(listeners=False, bitrate=False, playing="Location")
    _web = "http://www.windowsmedia.com/RadioUI/Home.aspx?g={}&culture=en-gb"
    base = "http://www.windowsmedia.com/RadioUI/getstationsforgenre.aspx?g={}&offset=0&culture={}"
    _url = "http://www.windowsmedia.com/RadioTunerAPI/Service.asmx/playStation?stationID={}&dialupDetected=true&useHighBandwidth=false&locale={}"

    categories = ["80s", "Adult Hits", "Adult Rock", "Alternative Rock",
    "Americana + Roots", "Big Band", "Blues", "Christian Hits", "Classic R&B",
    "Classic Rock", "Classical", "Comedy", "Country", "Dance + Electronica",
    "Holiday", "Indie", "International", "Jazz", "Latin", "Metal", "Miscellaneous",
    "New Age", "News + Talk", "Oldies", "Public Radio", "Rap + Hip Hop", "Reggae",
    "Religious", "Rock", "Smooth Jazz", "Soft Rock", "Soundtracks + Musicals",
    "Sports", "Top 40", "Urban/Modern R&B"]


    # static
    def update_categories(self):
        pass


    # Fetch entries
    def update_streams(self, cat, search=None):

        ucat = re.sub("\W+", "", cat.lower())
        html = ahttp.get(self.base.format(ucat, conf.windowsmedia_culture))

        r = []
        ls = re.findall("""
            stationid="([a-f0-9-]+)"  \s+
            onclick="Listen\('[\w-]+',\s*'(.+?)',\s*'(.+?)',
        """, html, re.X|re.S)
        for id, title, homepage in ls:
            r.append(dict(
                id = id,
                title = unhtml(title),
                homepage = homepage,
                url = self._url.format(id, conf.windowsmedia_culture),
                bitrate = 32,
            ))
        return r