Index: PKG-INFO ================================================================== --- PKG-INFO +++ PKG-INFO @@ -6,5 +6,7 @@ Author: Mario Salzer Author-email: xmilky+st2@gmail.... License: Public Domain Description: Streamtuner2 lists radio directory services like Shoutcast, Xiph, Live365, MyOggRadio, Jamendo. It allows listening via any audio player, and recording of streams via streamripper. Platform: ALL +Keywords: internet-radio, python, streaming, audio + Index: Packfile ================================================================== --- Packfile +++ Packfile @@ -1,9 +1,7 @@ -# The "Packfile" is just a make script, which is used as mid-packing -# helper. It's run within fpm/xmp execution context in /tmp/staging123/, -# where final distribution file arrangements are made. -# Keeps the Makefile more „build“-related. +# See http://fossil.include-once.org/xpm/wiki/Packfile +# Applies minor file tweaks right before -t package generation. all: preprocess $(PACK_TYPE) # More selective file rewriting preprocess: usr/share/streamtuner2/channels/search.pym Index: README ================================================================== --- README +++ README @@ -32,10 +32,11 @@ · python | python3 · pygtk | python-gi · python-requests · python-pyquery · python-lxml + · python-imaging | pillow · python-keybinder (optional) · python-xdg (optional) Use your distro package manager with e.g.: @@ -57,11 +58,11 @@ PYZ --- Other users may wish to try the new Python archive (.PYZ) instead. Which -requires little installation, but can just be run in-place: +requires little installation and can be run asis: python streamtuner-2.1.5.pyz You could even make this Python ZIP executable, and copy it in your PATH. @@ -74,12 +75,12 @@ ./st2.py The easy way: - · Run `sudo make install` - which installs into the default location (/usr/share/streamtuner2). + · Run `sudo make install` + which installs into the default location (/usr/share/streamtuner2). To install it manually: · Create a /usr/share/streamtuner2/ · Copy all *.py files there. @@ -110,12 +111,12 @@ If it's just one channel plugin that hangs at startup, you can alternatively disable it once: streamtuner2 -d xiph -Use [save] in the settings dialog (via F12) if you wish to -permanently disable it. +Start the settings dialog (via F12) and press [save] there +if you wish to permanently disable it. You can also manually edit the configuration file, located in ~/.config/streamtuner2/settings.json @@ -152,10 +153,13 @@ fossil ui Alternatively there are git and svn exports. fossil export --svn + +Or via + http://fossil.include-once.org/streamtuner2/git-fast-export You can send in patches, a fossil bundle, or set up your cloned repo publically per `fossil cgi`. Else just create an account on fossil.include-once.org, and send a mail, so I can elevate that Index: channels/bookmarks.py ================================================================== --- channels/bookmarks.py +++ channels/bookmarks.py @@ -17,11 +17,11 @@ # Some feature extensions inject custom subcategories here. For example the # "search" feature adds its own result list here, as does the "timer" plugin. from config import * -from uikit import uikit +from uikit import * from channels import * # The bookmarks tab is a core feature and built into the GtkBuilder @@ -34,40 +34,55 @@ # # It's accessible as `parent.bookmarks` in the ST2 window and elsewhere. # class bookmarks(GenericChannel): - # desc - module = "bookmarks" - title = "bookmarks" - base_url = "file:.config/streamtuner2/bookmarks.json" + # content listformat = "any" - - # content categories = ["favourite", ] # timer, links, search, and links show up as needed - current = "favourite" - default = "favourite" 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":[], } # cache list, to determine if a PLS url is bookmarked urls = [] + drag_types = [ + ("UTF8_STRING", 0, 5), + ("STRING", 0, 5), + ("text/plain", 0, 10), + ("text/uri-list", 0, 11), + ("application/x-scpls", 0, 21), + ("*/*", 0, 22), + ] def gui(self, parent): GenericChannel.gui(self, parent) parent.notebook_channels.set_menu_label_text(parent.v_bookmarks, "bookmarks") + #DND + w = self.gtk_list + #self.gtk_list.drag_source_set_icon_stock("gtk-folder") + w.enable_model_drag_source(gtk.gdk.BUTTON1_MASK, self.drag_types, gtk.gdk.ACTION_DEFAULT|gtk.gdk.ACTION_COPY|gtk.gdk.ACTION_MOVE) + w.enable_model_drag_dest(self.drag_types, gtk.gdk.ACTION_DEFAULT|gtk.gdk.ACTION_COPY) + w.connect('drag_drop', self.drop_cb) + # function to print out the mime type of the drop item + def drop_cb(self, wid, context, x, y, time, *e): + print '\n'.join([str(t) for t in context.targets]) + # What should I put here to get the URL of the link? + context.finish(True, False, time) + return True + # this channel does not actually retrieve/parse data from anywhere def update_categories(self): pass # but category sub-plugins might provide a hook category_plugins = {} def update_streams(self, cat): + if cat in self.category_plugins: return self.category_plugins[cat].update_streams(cat) or [] else: return self.streams.get(cat, []) Index: channels/configwin.py ================================================================== --- channels/configwin.py +++ channels/configwin.py @@ -23,10 +23,13 @@ # Interacts with main.* window (gtkBuilder widgets) # and conf.* dictionary. # class configwin (AuxiliaryWindow): + # control flags + meta = plugin_meta() + # Display win_config, pre-fill text fields from global conf. object def open(self, widget): if self.first_open: self.add_plugins() Index: channels/exportcat.py ================================================================== --- channels/exportcat.py +++ channels/exportcat.py @@ -29,11 +29,10 @@ # provides another export window, and custom file generation - does not use action.save() class exportcat(): - module = "" meta = plugin_meta() # Register callback def __init__(self, parent): conf.add_plugin_defaults(self.meta, self.module) Index: channels/file.py ================================================================== --- channels/file.py +++ channels/file.py @@ -52,16 +52,12 @@ # file browser / mp3 directory listings class file (ChannelPlugin): - # info - module = "file" - title = "file browser" - listtype = "url/file" - # data + listtype = "href" streams = {} categories = [] dir = [] ext = [] Index: channels/global_key.py ================================================================== --- channels/global_key.py +++ channels/global_key.py @@ -27,12 +27,12 @@ # register a key class global_key(object): + # control attributes module = "global_key" - title = "keyboard shortcut" meta = plugin_meta() last = 0 # register Index: channels/history.py ================================================================== --- channels/history.py +++ channels/history.py @@ -17,15 +17,13 @@ from channels import * class history: - # plugin info + # plugin attributes module = "history" - title = "History" meta = plugin_meta() - # store bm = None Index: channels/icast.py ================================================================== --- channels/icast.py +++ channels/icast.py @@ -39,18 +39,15 @@ # Surfmusik sharing site class icast (ChannelPlugin): - # description - homepage = "http://www.icast.io/" + # control attributes has_search = True listformat = "pls" titles = dict(listeners=False, bitrate=False, playing=False) - categories = [] - base = "http://api.icast.io/1/" # Categories require little post-processing, just dict into list conversion def update_categories(self): Index: channels/internet_radio.py ================================================================== --- channels/internet_radio.py +++ channels/internet_radio.py @@ -36,21 +36,13 @@ # streams and gui class internet_radio (ChannelPlugin): - - # description - title = "InternetRadio" - module = "internet_radio" - homepage = "http://www.internet-radio.org.uk/" + # control data listformat = "pls" - - # category map categories = [] - current = "" - default = "" # load genres def update_categories(self): Index: channels/itunes.py ================================================================== --- channels/itunes.py +++ channels/itunes.py @@ -38,18 +38,17 @@ # Surfmusik sharing site class itunes (ChannelPlugin): - # description - title = "iTunes RS" - module = "itunes" - #module = "rs_playlist" - homepage = "http://www.itunes.com?" + # control attribues has_search = False listformat = "pls" titles = dict(listeners=False, bitrate=False, playing=False) + base = "http://lab.rolisoft.net/playlists/itunes.php" + #base = "http://aws-eu.rolisoft.net/playlists/itunes.php" + #base = "http://aws-us.rolisoft.net/playlists/itunes.php" categories = [ "Adult Contemporary", "Alternative Rock", "Ambient", @@ -75,14 +74,10 @@ "'70s Retro", "'80s Flashback", "'90s Hits", ] - base = "http://lab.rolisoft.net/playlists/itunes.php" - #base = "http://aws-eu.rolisoft.net/playlists/itunes.php" - #base = "http://aws-us.rolisoft.net/playlists/itunes.php" - # static list for iTunes def update_categories(self): pass Index: channels/jamendo.py ================================================================== --- channels/jamendo.py +++ channels/jamendo.py @@ -52,24 +52,17 @@ # # Seem to resolve to OGG Vorbis each. # class jamendo (ChannelPlugin): - # description - title = "Jamendo" - module = "jamendo" - homepage = "http://www.jamendo.com/" - version = 0.3 + # control flags has_search = True - base = "http://www.jamendo.com/en/" listformat = "srv" api_base = "http://api.jamendo.com/v3.0/" cid = "49daa4f5" - categories = [] - titles = dict( title="Title", playing="Album/Artist/User", bitrate=False, listeners=False ) # refresh category list def update_categories(self): Index: channels/links.py ================================================================== --- channels/links.py +++ channels/links.py @@ -22,12 +22,10 @@ # hooks into main.bookmarks class links (object): # plugin info module = "links" - title = "Links" - version = 0.1 meta = plugin_meta() # list streams = [ ] default = [ Index: channels/live365.py ================================================================== --- channels/live365.py +++ channels/live365.py @@ -51,25 +51,19 @@ # # /cgi-bin/play.pls?stationid=%s&direct=1&file=%s.pls # class live365(ChannelPlugin): - # desc - module = "live365" - title = "Live365" - homepage = "http://www.live365.com/" + # control attributes base_url = "http://www.live365.com/" has_search = True listformat = "pls" mediatype = "audio/mpeg" has_search = False # content categories = ['Alternative', 'Blues', 'Classical', 'Country', 'Easy Listening', 'Electronic/Dance', 'Folk', 'Freeform', 'Hip-Hop/Rap', 'Inspirational', 'International', 'Jazz', 'Latin', 'Metal', 'New Age', 'Oldies', 'Pop', 'R&B/Urban', 'Reggae', 'Rock', 'Seasonal/Holiday', 'Soundtracks', 'Talk'] - current = "Alternative" - default = "Pop" - empty = None # redefine streams = {} Index: channels/modarchive.py ================================================================== --- channels/modarchive.py +++ channels/modarchive.py @@ -38,15 +38,14 @@ # http://modarchive.org/index.php?xml-api # (If only it wasn't XML based..) # class modarchive (ChannelPlugin): - # description - title = "modarchive" - module = "modarchive" - homepage = "http://www.modarchive.org/" + # control attributes + has_search = False base = "http://modarchive.org/" + audioformat = "audio/mod+zip" listformat = "href" titles = dict(genre="Genre", title="Song", playing="File", listeners="Rating", bitrate=0) # keeps category titles->urls catmap = {"Chiptune": "54", "Electronic - Ambient": "2", "Electronic - Other": "100", "Rock (general)": "13", "Trance - Hard": "64", "Swing": "75", "Rock - Soft": "15", "R & B": "26", "Big Band": "74", "Ska": "24", "Electronic - Rave": "65", "Electronic - Progressive": "11", "Piano": "59", "Comedy": "45", "Christmas": "72", "Chillout": "106", "Reggae": "27", "Electronic - Industrial": "34", "Grunge": "103", "Medieval": "28", "Demo Style": "55", "Orchestral": "50", "Soundtrack": "43", "Electronic - Jungle": "60", "Fusion": "102", "Electronic - IDM": "99", "Ballad": "56", "Country": "18", "World": "42", "Jazz - Modern": "31", "Video Game": "8", "Funk": "32", "Electronic - Drum & Bass": "6", "Alternative": "48", "Electronic - Minimal": "101", "Electronic - Gabber": "40", "Vocal Montage": "76", "Metal (general)": "36", "Electronic - Breakbeat": "9", "Soul": "25", "Electronic (general)": "1", "Punk": "35", "Pop - Synth": "61", "Electronic - Dance": "3", "Pop (general)": "12", "Trance - Progressive": "85", "Trance (general)": "71", "Disco": "58", "Electronic - House": "10", "Experimental": "46", "Trance - Goa": "66", "Rock - Hard": "14", "Trance - Dream": "67", "Spiritual": "47", "Metal - Extreme": "37", "Jazz (general)": "29", "Trance - Tribal": "70", "Classical": "20", "Hip-Hop": "22", "Bluegrass": "105", "Halloween": "82", "Jazz - Acid": "30", "Easy Listening": "107", "New Age": "44", "Fantasy": "52", "Blues": "19", "Other": "41", "Trance - Acid": "63", "Gothic": "38", "Electronic - Hardcore": "39", "One Hour Compo": "53", "Pop - Soft": "62", "Electronic - Techno": "7", "Religious": "49", "Folk": "21"} Index: channels/myoggradio.py ================================================================== --- channels/myoggradio.py +++ channels/myoggradio.py @@ -41,26 +41,20 @@ # open source radio sharing stie class myoggradio(ChannelPlugin): - # settings - title ="MOR" - #module = "myoggradio" - api = "http://www.myoggradio.org/" + # control flags listformat = "mixed(pls/m3u/srv)" + has_search = False + api = "http://www.myoggradio.org/" # hide unused columns titles = dict(playing=False, listeners=False, bitrate=False) # category map categories = ['common', 'personal'] - default = 'common' - current = 'common' - - # netrc instance - netrc = None # prepare GUI def __init__(self, parent): Index: channels/punkcast.py ================================================================== --- channels/punkcast.py +++ channels/punkcast.py @@ -32,20 +32,13 @@ # basic.ch broadcast archive class punkcast (ChannelPlugin): - # description - title = "punkcast" - module = "punkcast" - homepage = "http://www.punkcast.com/" - # keeps category titles->urls catmap = {} categories = ["list"] - default = "list" - current = "list" titles = dict(playing=False, listeners=False, bitrate=False, homepage=False) # don't do anything def update_categories(self): Index: channels/radiobrowser.py ================================================================== --- channels/radiobrowser.py +++ channels/radiobrowser.py @@ -56,22 +56,19 @@ # "tags":"Pop Dance RnB Techno","country":"Germany","subcountry":"","language":"German", # "votes":4,"negativevotes":10}, # class radiobrowser (ChannelPlugin): - # description - homepage = "http://www.radio-browser.info/" + # control flags has_search = True listformat = "pls" titles = dict(listeners="Votes+", bitrate="Votes-", playing="Country") - + base = "http://www.radio-browser.info/webservice/json/" categories = [] pricat = ("topvote", "topclick") catmap = { "tags": "bytag", "countries": "bycountry", "languages": "bylanguage" } - base = "http://www.radio-browser.info/webservice/json/" - # votes, and tags, no countries or languages def update_categories(self): self.categories = list(self.pricat) for sub in [conf.radiobrowser_cat]: Index: channels/shoutcast.py ================================================================== --- channels/shoutcast.py +++ channels/shoutcast.py @@ -48,22 +48,18 @@ # We do need a catmap now too, but that's easy to aquire and will be kept # within the cache dirs. # class shoutcast(channels.ChannelPlugin): - # desc - module = "shoutcast" - title = "SHOUTcast" + # attrs base_url = "http://shoutcast.com/" listformat = "pls" + has_search = False # categories categories = [] catmap = {"Choral": 35, "Winter": 275, "JROCK": 306, "Motown": 237, "Political": 290, "Tango": 192, "Ska": 22, "Comedy": 283, "Decades": 212, "European": 143, "Reggaeton": 189, "Islamic": 307, "Freestyle": 114, "French": 145, "Western": 53, "Dancepunk": 6, "News": 287, "Xtreme": 23, "Bollywood": 138, "Celtic": 141, "Kids": 278, "Filipino": 144, "Hanukkah": 270, "Greek": 146, "Punk": 21, "Spiritual": 211, "Industrial": 14, "Baroque": 33, "Talk": 282, "JPOP": 227, "Scanner": 291, "Mediterranean": 154, "Swing": 174, "Themes": 89, "IDM": 75, "40s": 214, "Funk": 236, "Rap": 110, "House": 74, "Educational": 285, "Caribbean": 140, "Misc": 295, "30s": 213, "Anniversary": 266, "Sports": 293, "International": 134, "Tribute": 107, "Piano": 41, "Romantic": 42, "90s": 219, "Latin": 177, "Grunge": 10, "Dubstep": 312, "Government": 286, "Country": 44, "Salsa": 191, "Hardcore": 11, "Afrikaans": 309, "Downtempo": 69, "Merengue": 187, "Psychedelic": 260, "Female": 95, "Bop": 167, "Tribal": 80, "Metal": 195, "70s": 217, "Tejano": 193, "Exotica": 55, "Anime": 277, "BlogTalk": 296, "African": 135, "Patriotic": 101, "Blues": 24, "Turntablism": 119, "Chinese": 142, "Garage": 72, "Dance": 66, "Valentine": 273, "Barbershop": 222, "Alternative": 1, "Technology": 294, "Folk": 82, "Klezmer": 152, "Samba": 315, "Turkish": 305, "Trance": 79, "Dub": 245, "Rock": 250, "Polka": 59, "Modern": 39, "Lounge": 57, "Indian": 149, "Hindi": 148, "Brazilian": 139, "Eclectic": 93, "Korean": 153, "Creole": 316, "Dancehall": 244, "Surf": 264, "Reggae": 242, "Goth": 9, "Oldies": 226, "Zouk": 162, "Environmental": 207, "Techno": 78, "Adult": 90, "Rockabilly": 262, "Wedding": 274, "Russian": 157, "Sexy": 104, "Chill": 92, "Opera": 40, "Emo": 8, "Experimental": 94, "Showtunes": 280, "Breakbeat": 65, "Jungle": 76, "Soundtracks": 276, "LoFi": 15, "Metalcore": 202, "Bachata": 178, "Kwanzaa": 272, "Banda": 179, "Americana": 46, "Classical": 32, "German": 302, "Tamil": 160, "Bluegrass": 47, "Halloween": 269, "College": 300, "Ambient": 63, "Birthday": 267, "Meditation": 210, "Electronic": 61, "50s": 215, "Chamber": 34, "Heartache": 96, "Britpop": 3, "Soca": 158, "Grindcore": 199, "Reality": 103, "00s": 303, "Symphony": 43, "Pop": 220, "Ranchera": 188, "Electro": 71, "Christmas": 268, "Christian": 123, "Progressive": 77, "Jazz": 163, "Trippy": 108, "Instrumental": 97, "Tropicalia": 194, "Fusion": 170, "Healing": 209, "Glam": 255, "80s": 218, "KPOP": 308, "Worldbeat": 161, "Mixtapes": 117, "60s": 216, "Mariachi": 186, "Soul": 240, "Cumbia": 181, "Inspirational": 122, "Impressionist": 38, "Gospel": 129, "Disco": 68, "Arabic": 136, "Idols": 225, "Ragga": 247, "Demo": 67, "LGBT": 98, "Honeymoon": 271, "Japanese": 150, "Community": 284, "Weather": 317, "Asian": 137, "Hebrew": 151, "Flamenco": 314, "Shuffle": 105} - current = "" - default = "Alternative" - empty = "" # redefine streams = {} Index: channels/surfmusik.py ================================================================== --- channels/surfmusik.py +++ channels/surfmusik.py @@ -40,24 +40,22 @@ # Surfmusik sharing site class surfmusik (ChannelPlugin): - # description - title = "SurfMusik" + # module attributes module = "surfmusik" - homepage = "http://www.surfmusik.de/" listformat = "m3u" - + has_search = False lang = "DE" # last configured categories base = { "DE": ("http://www.surfmusik.de/", "genre/", "land/"), "EN": ("http://www.surfmusic.de/", "format/", "country/"), } - categories = [] titles = dict( genre="Genre", title="Station", playing="Location", bitrate=False, listeners=False ) + # Set channel title def __init__(self, parent=None): ChannelPlugin.__init__(self, parent) # title updating is a workaround, because the fixed .meta attribute are read first Index: channels/timer.py ================================================================== --- channels/timer.py +++ channels/timer.py @@ -34,11 +34,10 @@ # timed events (play/record) within bookmarks tab class timer: # plugin info module = "timer" - title = "Timer" meta = plugin_meta() # configuration settings timefield = "playing" Index: channels/tunein.py ================================================================== --- channels/tunein.py +++ channels/tunein.py @@ -33,14 +33,11 @@ # TuneIn radio directory class tunein (ChannelPlugin): - # description - title = "TuneIn" - module = "tunein" - homepage = "http://tunein.com/" + # control flags has_search = False listformat = "pls" titles = dict(listeners=False) base = "http://opml.radiotime.com/" Index: channels/ubuntuusers.py ================================================================== --- channels/ubuntuusers.py +++ channels/ubuntuusers.py @@ -29,11 +29,10 @@ # UU Wiki radio list class ubuntuusers (ChannelPlugin): # description - module = "ubuntuusers" has_search = False listformat = "srv" titles = dict(playing=False, listeners=False, bitrate=False) base = "http://wiki.ubuntuusers.de/Internetradio/Stationen?action=export&format=raw" categories = ["stations"] Index: channels/xiph.py ================================================================== --- channels/xiph.py +++ channels/xiph.py @@ -54,24 +54,18 @@ # · https://trac.xiph.org/ticket/1958 # · https://wiki.xiph.org/Summer_of_Code_2015#Stream_directory_API # class xiph (ChannelPlugin): - # desc - module = "xiph" - title = "Xiph.org" - homepage = "http://dir.xiph.org/" - #xml_url = "http://dir.xiph.org/yp.xml" - json_url = "http://api.include-once.org/xiph/cache.php" + # attributes listformat = "srv" has_search = True + json_url = "http://api.include-once.org/xiph/cache.php" + #xml_url = "http://dir.xiph.org/yp.xml" # content categories = [ "pop", "top40" ] - current = "" - default = "pop" - empty = None # prepare category names def __init__(self, parent=None): Index: channels/youtube.py ================================================================== --- channels/youtube.py +++ channels/youtube.py @@ -64,17 +64,14 @@ # guideCat Music id= GCTXVzaWM channelid= UCBR8-60-B28hp2BmDPdntcQ # topicId Music mid= /m/0kpv0g # class youtube (ChannelPlugin): - # description - title = "Youtube" - module = "youtube" - homepage = "http://www.youtube.com/" + # control attributes listformat = "url/youtube" has_search = True - fmt = "video/youtube" + audioformat = "video/youtube" titles = dict( genre="Channel", title="Title", playing="Playlist", bitrate=False, listeners=False ) # API config service = { 2: [ "http://gdata.youtube.com/", @@ -290,11 +287,11 @@ id = row["snippet"]["resourceId"]["videoId"] data.update(dict( url = "http://youtube.com/v/" + id, homepage = "http://youtu.be/" + id + ("?wadsworth=1" if conf.youtube_wadsworth else ""), - format = self.fmt, + format = self.audioformat, title = row["snippet"]["title"], )) # optional values if "playing" not in data: @@ -310,13 +307,13 @@ #__print__(dbg.DATA, row) return dict( genre = row["category"][1]["term"], title = row["title"]["$t"], playing = row["author"][0]["name"]["$t"], - format = self.fmt, + format = self.audioformat, url = row["content"]["src"].split("?")[0], homepage = row["media$group"]["media$player"]["url"], image = row["media$group"]["media$thumbnail"][0]["url"], )