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

⌈⌋ ⎇ branch:  streamtuner2


Check-in [8a77525fb0]

Overview
Comment:new plugin: script stations, for single-station extractiong/user scripts
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA1: 8a77525fb0a2517104b5d10333a80cc78e574cc2
User & Date: mario on 2016-12-17 11:14:01
Other Links: manifest | tags
Context
2016-12-17
11:22
Set Frequence3 as new default bookmark (curb the finder song) check-in: 0e8c56d915 user: mario tags: trunk
11:14
new plugin: script stations, for single-station extractiong/user scripts check-in: 8a77525fb0 user: mario tags: trunk
11:01
Adapt urn_resolve() invocation. Most handlers update the dict in place. A few will return a new row however. (This can be utilized to return a copy, instead of changing station entries → menat for user "script stations" plugin.) check-in: 0a425cfb5d user: mario tags: trunk
Changes

Modified channels/bookmarks.py from [767c735b6c] to [e1104d7956].

38
39
40
41
42
43
44

45
46
47
48
49
50
51

    # content
    listformat = "any"
    categories = ["favourite", ]  # timer, links, search, and links show up as needed
    finder_song = { "genre": "Youtube ", "format": "video/youtube", "playing": "current_", "title": "The Finder song", "url": "http://youtube.com/v/omyZy4H8y9M", "homepage": "http://youtu.be/omyZy4H8y9M" }
    streams = {"favourite":[finder_song], "search":[], "scripts":[], "timer":[], "history":[], }
    default = "favourite"



    # cache list, to determine if a PLS url is bookmarked
    urls = []

    def gui(self, parent):
        parent.notebook_channels.set_menu_label_text(parent.v_bookmarks, "bookmarks")







>







38
39
40
41
42
43
44
45
46
47
48
49
50
51
52

    # content
    listformat = "any"
    categories = ["favourite", ]  # timer, links, search, and links show up as needed
    finder_song = { "genre": "Youtube ", "format": "video/youtube", "playing": "current_", "title": "The Finder song", "url": "http://youtube.com/v/omyZy4H8y9M", "homepage": "http://youtu.be/omyZy4H8y9M" }
    streams = {"favourite":[finder_song], "search":[], "scripts":[], "timer":[], "history":[], }
    default = "favourite"
    fixed_size = [32,24]


    # cache list, to determine if a PLS url is bookmarked
    urls = []

    def gui(self, parent):
        parent.notebook_channels.set_menu_label_text(parent.v_bookmarks, "bookmarks")

Added contrib/scripts.py version [d8221b6773].

































































































































































































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
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
# encoding: UTF-8
# api: streamtuner2
# title: Script stations
# description: User scripts for individual stations
# type: feature
# category: bookmark
# version: 0.1
# priority: theoretical
#
# This plugin provides for a simpler alternative to channel plugins.
# It reads the ~./config/streamtuner2/script/ directory for script
# files, and shows them in the bookmarks channel. Each script may
# scan/uncover a station url at runtime.
# Which obviously isn't meant for easily parseable stations, but for
# the more difficult cases.
#
# There's support for python scripts obviously, but any executable
# file (scripting language) can be run and queried for urls.
#
# Each is supposed to contain a meta comment block (much like this
# plugin), except it's using more stream-oriented descriptors:
#
#   #!/bin/sh
#   # title: Station title
#   # description: Fetching a live stream
#   # genre: jazz
#   # homepage: http://example.org/
#   #
#   # Now normally, it would not just print something static.
#   
#   echo "http://example.org/.mp3"
#
# Obviously the purpose is more complicated extractions. Which is
# why a .py script had acces to all ST2s parsing tools already.
# 
# This is implemented using the action.handler hooks for urn:
# modules. But ensures the resolver script is run each time - by
# not caching the final stream url.
# Conversly this plugin prevents editing of script station entries.
#


import os, shutil, copy, subprocess, sys, StringIO
import csv, zipfile
import re, json, pq
import ahttp
import config
from config import *
import uikit
from compat2and3 import *
import action
from channels import *


# dynamic station extractors from ~/.config/streamtuner2/scripts/
class scripts (object):

    # plugin info
    module = "scripts"
    meta = plugin_meta()
    parent = None
    dir = conf.dir + "/scripts"

    # register hooks
    def __init__(self, parent):
        if not os.path.exists(self.dir):
            os.mkdir(self.dir)
        self.parent = parent
        self.bm = parent.bookmarks
        action.handler["urn:script"] = self.urn_resolve
        self.bm.add_category("scripts")
        self.bm.category_plugins["scripts"] = self

    # find script files and compile station dicts
    def update_streams(self, cat):
        r = []
        for fn in os.listdir(self.dir):
            meta = config.plugin_meta(fn=self.dir+"/"+fn)
            r.append(dict(
                genre = meta.get("genre", meta.get("type", "script")),
                title = meta.get("title", fn),
                playing = meta.get("description", meta.get("playing", "")),
                homepage = meta.get("homepage", ""),
                listeners = to_int(meta.get("listeners", "1")),
                bitrate = to_int(meta.get("bitrate", "64")),
                format = meta.get("format", "audio/mp3"),
                listformat = "href",
                file = fn,
                url = "urn:script:" + fn
            ))
        return r

    # run'em
    def urn_resolve(self, row, *x):
        if not row.get("file"):
            return

        # prepare
        fn = "%s/%s" % (self.dir, row["file"])
        row = copy.copy(row)
        row["url"] = ""
        output = None

        # executable
        if os.path.isfile(fn) and os.access(fn, os.X_OK):
            f = subprocess.Popen([fn], stdout=subprocess.PIPE)
            output, err = f.communicate()
        # plain python script
        elif re.match("^[\w+-]\.py$"):
            real_stdout, sys.stdout = sys.stdout, StringIO.StringIO()
            execfile(fn)
            output, sys.stdout = sys.stdout.getvalue(), real_stdout
        # none
        else:
            return

        # extract urls
        if output:
            urls = re.findall("(\w+://\S+)", output)
            if urls:
                row["url"] = urls[0] # action module does not currently support multi-urls
        return row
        # Unlike other .resolve_urn() handlers this one returns a copy,
        # does not modify the passed dict. And this is only handled in
        # action.run_fmt_url(), but not by GenericChannel.play(). Why
        # OTOH this incudes a *double script invocation*.