class Handler: def __init__(self): self.API = None self.response = None self.target_station = None self.API = RadioBrowser() def station_validator(self): if len(self.response) == 0: log.error("No stations found by the name") sys.exit(0) if len(self.response) > 1: log.info("Multiple stations found by the name") stations_name = "" for station in self.response: # stations_name = stations_name + "," + station["name"] log.info("name: {} | id: {} | country: {}".format( station["name"], station["stationuuid"], station["country"])) log.info(stations_name) sys.exit(0) if len(self.response) == 1: log.info("Station found: {}".format(self.response[0]["name"])) log.debug(self.response[0]) self.target_station = self.response[0] self.API.click_counter(self.target_station["stationuuid"]) def play_by_station_name(self, _name=None): print(_name) self.response = self.API.search(name=_name, name_exact=False) self.station_validator() def play_by_station_uuid(self, _uuid): print(_uuid) # Pyradios by default don't let you search by uuid # a trick is to call click_counter(uuid) directly to get the statioon info is_ok = "false" try: self.target_station = self.API.click_counter(_uuid) log.debug(self.target_station) is_ok = self.target_station["ok"] except Exception as e: log.error("Could not find a station by the UUID") sys.exit(0) self.API.search(name=self.target_station["name"], name_exact=True) # againg register a valid click if is_ok == "false": res = self.API.click_counter(self.target_station["stationuuid"]) log.debug(res)
class Module(ModuleBase): def init(self, settings, q): self.module_path = os.path.dirname(os.path.abspath(__file__)) try: lang = gettext.translation('pext_module_radio', localedir=os.path.join( self.module_path, 'locale'), languages=[settings['_locale']]) except FileNotFoundError: lang = gettext.NullTranslations() print("No {} translation available for pext_module_radio".format( settings['_locale'])) lang.install() self.rb = RadioBrowser() self.settings = settings self.q = q self.favourites = [] try: with open(os.path.join(self.module_path, "_user_favourites.txt"), "r") as favourites_file: for favourite in favourites_file: self.favourites.append(favourite.strip()) except IOError: pass self.cached = { 'countries': { 'time': 0 }, 'codecs': { 'time': 0 }, 'languages': { 'time': 0 }, 'tags': { 'time': 0 } } self.cachedStations = { '_favourites': { 'time': 0 }, 'countries': {}, 'codecs': {}, 'languages': {}, 'tags': {}, 'topvote': { 'time': 0 }, 'topclick': { 'time': 0 }, 'lastclick': { 'time': 0 }, 'lastchange': { 'time': 0 } } self.nowPlaying = None if not which("ffplay"): self.q.put([ Action.critical_error, _("ffplay is not installed, please install it.") ]) return self._get_entries() def _cache_expired(self, cache): return cache['time'] < time.time() - 600 def _entry_depth(self, text): if self._menu_to_type(text) in self.cached: return 2 else: return 1 def _menu_to_type(self, text): if text == _('Favourites'): return '_favourites' elif text == _('By Country'): return 'countries' elif text == _('By Codec'): return 'codecs' elif text == _('By Language'): return 'languages' elif text == _('By Tags'): return 'tags' elif text == _('By Votes'): return 'topvote' elif text == _('By Most Tune-Ins'): return 'topclick' elif text == _('By Most Recent Listener'): return 'lastclick' elif text == _('By Most Recent Change'): return 'lastchange' else: raise ValueError("Invalid text") def _get_stations_by_menu_type(self, search_type): if search_type == 'countries': return self.rb.countries() elif search_type == 'codecs': return self.rb.codecs() elif search_type == 'languages': return self.rb.languages() elif search_type == 'tags': return self.rb.tags() else: return self._search_stations_by_type(search_type) def _search_stations_by_type(self, search_type, search_term=None): if search_type == 'countries': return self.rb.stations_by_country(search_term, True) elif search_type == 'codecs': return self.rb.stations_by_codec(search_term, True) elif search_type == 'languages': return self.rb.stations_by_language(search_term, True) elif search_type == 'tags': return self.rb.stations_by_tag(search_term, True) elif search_type == 'topvote': return self.rb.stations(order='votes') elif search_type == 'topclick': return self.rb.stations(order='clickcount') elif search_type == 'lastclick': return self.rb.stations(order='clicktimestamp') elif search_type == 'lastchange': return self.rb.stations(order='lastchangetime') else: raise ValueError("Invalid type") def _get_entries(self): if self.favourites: self.q.put([Action.add_entry, _('Favourites')]) self.q.put([Action.add_entry, _('By Country')]) self.q.put([Action.add_entry, _('By Codec')]) self.q.put([Action.add_entry, _('By Language')]) self.q.put([Action.add_entry, _('By Tags')]) self.q.put([Action.add_entry, _('By Votes')]) self.q.put([Action.add_entry, _('By Most Tune-Ins')]) self.q.put([Action.add_entry, _('By Most Recent Listener')]) self.q.put([Action.add_entry, _('By Most Recent Change')]) if self.settings['_api_version'] < [0, 11, 0] and self.nowPlaying: if self.nowPlaying['process']: self.q.put([Action.add_command, _('mute')]) else: self.q.put([Action.add_command, _('unmute')]) self.q.put([Action.add_command, _('stop')]) self.q.put([Action.add_command, _('vote')]) def _get_list(self, path): self.q.put([Action.replace_entry_list, []]) if self._cache_expired(self.cached[path]): self.cached[path] = { 'time': time.time(), 'data': self._get_stations_by_menu_type(path) } for entry in self.cached[path]['data']: self.q.put([ Action.add_entry, _('{} ({} stations)').format(entry['name'], entry['stationcount']) ]) def _get_stations(self, search_type, search_term): if search_type == '_favourites': if self._cache_expired(self.cachedStations[search_type]): data = [] for favourite in self.favourites: station_data = self.rb.station_by_uuid(favourite) if station_data: data.append(station_data[0]) self.cachedStations[search_type] = { 'time': time.time(), 'data': data } return self.cachedStations[search_type] if search_term: if search_term not in self.cachedStations[ search_type] or self._cache_expired( self.cachedStations[search_type][search_term]): self.cachedStations[search_type][search_term] = { 'time': time.time(), 'data': self._search_stations_by_type(search_type, search_term) } return self.cachedStations[search_type][search_term] if self._cache_expired(self.cachedStations[search_type]): self.cachedStations[search_type] = { 'time': time.time(), 'data': self._search_stations_by_type(search_type) } return self.cachedStations[search_type] def _list_stations(self, search_type, search_term): self.q.put([Action.replace_entry_list, []]) cache = self._get_stations(search_type, search_term) for entry in cache['data']: self.q.put([Action.add_entry, entry['name']]) if self.settings['_api_version'] >= [0, 3, 1]: self.q.put([ Action.set_entry_info, entry['name'], _("<b>{}</b><br/><br/><b>Bitrate: </b>{} kbps<br/><b>Codec: </b>{}<br/><b>Language: </b>{}<br/><b>Location: </b>{}<br/><b>Tags: </b>{}<br/><b>Homepage: </b><a href='{}'>{}</a>" ).format( html.escape(entry['name']), html.escape(str(entry['bitrate'])), html.escape(entry['codec']), html.escape(entry['language']), "{}, {}".format(html.escape(entry['state']), html.escape(entry['country'])) if entry['state'] else html.escape(entry['country']), html.escape(", ".join(entry['tags'].split(",")) if entry['tags'] else "None"), html.escape(entry['homepage']), html.escape(entry['homepage'])) ]) if self.settings['_api_version'] >= [ 0, 6, 0 ] and search_type == '_favourites': self.q.put([ Action.set_entry_context, entry['name'], [_("Unfavourite")] ]) def _play_station(self, byType, searchTerm, stationName): self._stop_playing() if searchTerm: cache = self.cachedStations[byType][searchTerm] else: cache = self.cachedStations[byType] for station in cache['data']: if station['name'] == stationName: station_uuid = station['stationuuid'] station_info = station break response = self.rb.click_counter(station_uuid) if response['ok'] == 'false': self.q.put([Action.add_error, response['message']]) return False # TODO: Replace ffplay with something more easily scriptable that # preferably notifies us of song changes on the station. self.nowPlaying = { 'id': station_uuid, 'name': stationName, 'url': response['url'], 'process': None } if self.settings['_api_version'] >= [0, 6, 0]: self.q.put([ Action.set_base_info, _("<b>Tuned into:</b><br/>{}<br/><br/><b>Bitrate: </b>{} kbps<br/><b>Codec: </b>{}<br/><b>Language: </b>{}<br/><b>Location: </b>{}<br/><b>Tags: </b>{}<br/><b>Homepage: </b><a href='{}'>{}</a>" ).format( html.escape(station_info['name']), html.escape(str(station_info['bitrate'])), html.escape(station_info['codec']), html.escape(station_info['language']), "{}, {}".format(html.escape(station_info['state']), html.escape(station_info['country'])) if station_info['state'] else html.escape( station_info['country']), html.escape(", ".join(station_info['tags'].split(",")) if station_info['tags'] else "None"), html.escape(station_info['homepage']), html.escape(station_info['homepage'])) ]) self._toggle_mute() return True def _toggle_mute(self): """Toggle mute. While this function technically disconnects or connects to the station, instead of just muting, it is simpler code-wise and has the added benefit of saving bandwidth. TODO: Replace this with an actual mute function. """ if self.nowPlaying: if self.nowPlaying['process']: os.kill(self.nowPlaying['process'].pid, SIGTERM) self.nowPlaying['process'] = None self.q.put([ Action.set_header, _('Tuned into {} (muted)').format(self.nowPlaying['name']) ]) if self.settings['_api_version'] >= [0, 6, 0]: self.q.put([ Action.set_base_context, [_("Unmute"), _("Stop"), _("Favourite"), _("Vote up")] ]) else: self.q.put([ Action.set_header, _('Tuned into {}').format(self.nowPlaying['name']) ]) self.nowPlaying['process'] = Popen([ 'ffplay', '-nodisp', '-nostats', '-loglevel', '0', self.nowPlaying['url'] ]) if self.settings['_api_version'] >= [0, 6, 0]: self.q.put([ Action.set_base_context, [_("Mute"), _("Stop"), _("Favourite"), _("Vote up")] ]) def _stop_playing(self): if self.nowPlaying: if self.nowPlaying['process']: os.kill(self.nowPlaying['process'].pid, SIGTERM) self.nowPlaying = None self.q.put([Action.set_header]) if self.settings['_api_version'] >= [0, 6, 0]: self.q.put([Action.set_base_info]) self.q.put([Action.set_base_context]) def _add_to_favourites(self, station_id): with open(os.path.join(self.module_path, "_user_favourites.txt"), "a") as favourites_file: favourites_file.write('{}\n'.format(station_id)) self.favourites.append(station_id) self.cachedStations['_favourites'] = {'time': 0} def _remove_from_favourites(self, station_name): for station in self._get_stations('_favourites', '')['data']: if station['name'] == station_name: self.favourites.remove(station['stationuuid']) with open( os.path.join(self.module_path, "_user_favourites.txt"), "w") as favourites_file: for favourite in self.favourites: favourites_file.write('{}\n'.format(favourite)) self.cachedStations['_favourites'] = {'time': 0} return self.q.put([ Action.add_error, _('Could not find {} in favourites').format(station_name) ]) def _vote_station(self): result = self.rb.client.get('vote/{}'.format(self.nowPlaying['id'])) if result['ok']: self.q.put([ Action.add_message, _('Voted for station {}').format(self.nowPlaying['name']) ]) else: self.q.put([ Action.add_error, _('Failed to vote for {}: {}').format(self.nowPlaying['name'], result['message']) ]) def stop(self): self._stop_playing() def selection_made(self, selection): if self.settings['_api_version'] >= [ 0, 6, 0 ] and len(selection) > 0 and selection[-1]['context_option']: if selection[-1]['type'] == SelectionType.none: if selection[-1]['context_option'] in [_('Mute'), _('Unmute')]: self._toggle_mute() elif selection[-1]['context_option'] == _('Stop'): self._stop_playing() elif selection[-1]['context_option'] == _('Favourite'): self._add_to_favourites(self.nowPlaying['id']) elif selection[-1]['context_option'] == _('Vote up'): self._vote_station() elif selection[-1]['context_option'] == _("Unfavourite"): self._remove_from_favourites(selection[-1]['value']) if not self.favourites: self.q.put([Action.set_selection, []]) return self.q.put([Action.set_selection, selection[:-1]]) return self.q.put([Action.replace_command_list, []]) if len(selection) == 0: self.q.put([Action.replace_entry_list, []]) self._get_entries() elif len(selection) == 1: # Force station list when no subcategories if self._entry_depth(selection[0]['value']) == 1: self._list_stations(self._menu_to_type(selection[0]['value']), '') return menu_text = selection[0]['value'] self._get_list(self._menu_to_type(menu_text)) elif len(selection) == 2: # Force playing when no subcategories if self._entry_depth(selection[0]['value']) == 1: if self._play_station( self._menu_to_type(selection[0]['value']), '', selection[1]['value']): self.q.put([Action.close]) else: self.q.put([Action.set_selection, selection[:-1]]) return # Remove station count from search term search_term = selection[1]['value'][:selection[1]['value']. rfind('(')].rstrip() self._list_stations(self._menu_to_type(selection[0]['value']), search_term) elif len(selection) == 3: # Remove station count from search term search_term = selection[1]['value'][:selection[1]['value']. rfind('(')].rstrip() if self._play_station(self._menu_to_type(selection[0]['value']), search_term, selection[2]['value']): self.q.put([Action.close]) else: self.q.put([Action.set_selection, selection[:-1]]) else: self.q.put([ Action.critical_error, _('Unexpected selection_made value: {}').format(selection) ]) def process_response(self, response): pass