async def main(): print('Jukebox Pi') signal.signal(signal.SIGINT, signal_handler) client = MPDClient() atexit.register(client.disconnect) lcd = LCD(make_callback(client)) lcd.turn_on() atexit.register(lcd.stop) clock = Clock(lcd) atexit.register(clock.stop) try: await client.connect('localhost', 6600) except Exception as e: print('Connection to MPD failed:', e) return print('Connected to MPD version ', client.mpd_version) current_status = await get_status(client) show_track(lcd, clock, {}, current_status) async for _ in client.idle(['player']): status = await get_status(client) print(status) show_track(lcd, clock, current_status, status) current_status = status
def __init__(self, host, port): self.host = host self.port = port self.mpd = MPDClient() self._playlist_empty = asyncio.Event() self._adding_song = False
def __init__(self, server, port, password, name): """Initialize the MPD device.""" self.server = server self.port = port self._name = name self.password = password self._status = None self._currentsong = None self._playlists = None self._currentplaylist = None self._is_connected = False self._muted = False self._muted_volume = None self._media_position_updated_at = None self._media_position = None self._media_image_hash = None # Track if the song changed so image doesn't have to be loaded every update. self._media_image_file = None self._commands = None # set up MPD client self._client = MPDClient() self._client.timeout = 30 self._client.idletimeout = None
async def setup_mpd(self, mpd_server: str) -> None: self.mpd = MPDClient() await self.mpd.connect(mpd_server) # get initial state of outputs async for output in self.mpd.outputs(): output = MPDOutput(output) self.mpd_outputs[output.name] = output # add idle command to to event loop self._loop.create_task(self.listen_mpd())
async def main(): print("Create MPD client") client = MPDClient() try: await client.connect('localhost', 6600) except Exception as e: print("Connection failed:", e) return print("Connected to MPD version", client.mpd_version) try: status = await client.status() except Exception as e: print("Status error:", e) return else: print("Status success:", status) print(list(await client.commands())) import time start = time.time() for x in await client.listall(): print("sync:", x) print("Time to first sync:", time.time() - start) break start = time.time() async for x in client.listall(): print("async:", x) print("Time to first async:", time.time() - start) break try: await client.addid() except Exception as e: print("An erroneous command, as expected, raised:", e) try: async for x in client.plchangesposid(): print("Why does this work?") except Exception as e: print("An erroneous asynchronously looped command, as expected, raised:", e) i = 0 async for subsystem in client.idle(): print("Idle change in", subsystem) i += 1 if i > 5: print("Enough changes, quitting") break
def __init__(self, sfavs): # Do Startup tasks super().__init__("Library", "Lib", "Locally Stored Audio") self.sfavs = sfavs self.mpdc = MPDClient() self.loop = asyncio.get_event_loop() self.REQ_MET_MPD = False self.start_client() # This is part of the Streaming hierarchy self.component = "library" # Local pointers into the radio state and the streaming client # Zero the client state on startup self.clear_client() # Setup the menu items self.favs_save_file = "saved/{}_favorites.json".format(self.component) self.favorites_node.menu_labels['comment'] = "Library Favorites" self.favorites_node.component = self.component self.load_favorites() self.playlists_node = LibraryMenuList("Playlists", "Pls", "My playlists.") self.add_child(self.playlists_node) self.genres_node = LibraryMenuList("Genres", "Gnr", "Library By Genre") self.add_child(self.genres_node) self.artists_node = LibraryMenuList("Artists", "Art", "Library By Artist") self.add_child(self.artists_node) self.albums_node = LibraryMenuList("Albums", "Alb", "Library By Album") self.add_child(self.albums_node) # Populate the Library asyncio.run_coroutine_threadsafe(self.refresh_library(), self.loop)
class MPDConnection: def __init__(self, host, port): self.host = host self.port = port self.mpd = MPDClient() self._playlist_empty = asyncio.Event() self._adding_song = False async def start(self): await self.mpd.connect(self.host, self.port) asyncio.ensure_future(self._event_loop()) async def wait_for_song(self): '''Returns when the playlist is empty''' await self._playlist_empty.wait() def is_ready(self): '''Immediately returns true if the playlist is empty, false otherwise.''' return self._playlist_empty.is_set() async def add_to_playlist(self, url): if self._adding_song: raise Exception self._adding_song = True self._playlist_empty.clear() print() print(url) print() try: await self.mpd.add(url) await self.mpd.play() except mpd.CommandError: pass self._adding_song = False async def skip(self): await self.mpd.clear() async def _event_loop(self): while True: status = await self.mpd.status() if status['playlistlength'] == '0': if not self._adding_song: self._playlist_empty.set() else: self._playlist_empty.clear() async for subsystems in self.mpd.idle(): if 'playlist' in subsystems or 'player' in subsystems: break
def __init__(self, server, port, password, name): """Initialize the MPD device.""" self.server = server self.port = port self._name = name self.password = password self._status = None self._currentsong = None self._playlists = None self._currentplaylist = None self._is_connected = False self._muted = False self._muted_volume = None self._media_position_updated_at = None self._media_position = None self._commands = None # set up MPD client self._client = MPDClient() self._client.timeout = 30 self._client.idletimeout = None
async def main(*, host='127.0.0.1', port=50051): client = MPDClient() await client.connect('localhost', 6600) print(f'mpd version: {client.mpd_version}') queue = asyncio.Queue() commands_queue = asyncio.Queue() task1 = asyncio.create_task(worker(queue)) # TODO: remove task2 = asyncio.create_task(notifier(client, commands_queue)) server = Server([WallboxApi(commands_queue, queue, client)]) with graceful_exit([server]): await server.start(host, port) await server.wait_closed() task1.cancel() task2.cancel() client.disconnect()
class MpdDevice(MediaPlayerEntity): """Representation of a MPD server.""" # pylint: disable=no-member def __init__(self, server, port, password, name): """Initialize the MPD device.""" self.server = server self.port = port self._name = name self.password = password self._status = None self._currentsong = None self._playlists = None self._currentplaylist = None self._is_connected = False self._muted = False self._muted_volume = None self._media_position_updated_at = None self._media_position = None self._media_image_hash = None # Track if the song changed so image doesn't have to be loaded every update. self._media_image_file = None self._commands = None # set up MPD client self._client = MPDClient() self._client.timeout = 30 self._client.idletimeout = None async def _connect(self): """Connect to MPD.""" try: await self._client.connect(self.server, self.port) if self.password is not None: await self._client.password(self.password) except mpd.ConnectionError: return self._is_connected = True def _disconnect(self): """Disconnect from MPD.""" with suppress(mpd.ConnectionError): self._client.disconnect() self._is_connected = False self._status = None async def _fetch_status(self): """Fetch status from MPD.""" self._status = await self._client.status() self._currentsong = await self._client.currentsong() await self._async_update_media_image_hash() if (position := self._status.get("elapsed")) is None: position = self._status.get("time") if isinstance(position, str) and ":" in position: position = position.split(":")[0] if position is not None and self._media_position != position: self._media_position_updated_at = dt_util.utcnow() self._media_position = int(float(position)) await self._update_playlists()
def __init__(self, host, port): self.host, self.port = host, port self.client = MPDClient()
class MpdDevice(MediaPlayerEntity): """Representation of a MPD server.""" # pylint: disable=no-member def __init__(self, server, port, password, name): """Initialize the MPD device.""" self.server = server self.port = port self._name = name self.password = password self._status = None self._currentsong = None self._playlists = None self._currentplaylist = None self._is_connected = False self._muted = False self._muted_volume = None self._media_position_updated_at = None self._media_position = None self._commands = None # set up MPD client self._client = MPDClient() self._client.timeout = 30 self._client.idletimeout = None async def _connect(self): """Connect to MPD.""" try: await self._client.connect(self.server, self.port) if self.password is not None: await self._client.password(self.password) except mpd.ConnectionError: return self._is_connected = True def _disconnect(self): """Disconnect from MPD.""" try: self._client.disconnect() except mpd.ConnectionError: pass self._is_connected = False self._status = None async def _fetch_status(self): """Fetch status from MPD.""" self._status = await self._client.status() self._currentsong = await self._client.currentsong() position = self._status.get("elapsed") if position is None: position = self._status.get("time") if isinstance(position, str) and ":" in position: position = position.split(":")[0] if position is not None and self._media_position != position: self._media_position_updated_at = dt_util.utcnow() self._media_position = int(float(position)) await self._update_playlists() @property def available(self): """Return true if MPD is available and connected.""" return self._is_connected async def async_update(self): """Get the latest data and update the state.""" try: if not self._is_connected: await self._connect() self._commands = list(await self._client.commands()) await self._fetch_status() except (mpd.ConnectionError, OSError, BrokenPipeError, ValueError) as error: # Cleanly disconnect in case connection is not in valid state _LOGGER.debug("Error updating status: %s", error) self._disconnect() @property def name(self): """Return the name of the device.""" return self._name @property def state(self): """Return the media state.""" if self._status is None: return STATE_OFF if self._status["state"] == "play": return STATE_PLAYING if self._status["state"] == "pause": return STATE_PAUSED if self._status["state"] == "stop": return STATE_OFF return STATE_OFF @property def is_volume_muted(self): """Boolean if volume is currently muted.""" return self._muted @property def media_content_id(self): """Return the content ID of current playing media.""" return self._currentsong.get("file") @property def media_content_type(self): """Return the content type of current playing media.""" return MEDIA_TYPE_MUSIC @property def media_duration(self): """Return the duration of current playing media in seconds.""" # Time does not exist for streams return self._currentsong.get("time") @property def media_position(self): """Position of current playing media in seconds. This is returned as part of the mpd status rather than in the details of the current song. """ return self._media_position @property def media_position_updated_at(self): """Last valid time of media position.""" return self._media_position_updated_at @property def media_title(self): """Return the title of current playing media.""" name = self._currentsong.get("name", None) title = self._currentsong.get("title", None) file_name = self._currentsong.get("file", None) if name is None and title is None: if file_name is None: return "None" return os.path.basename(file_name) if name is None: return title if title is None: return name return f"{name}: {title}" @property def media_artist(self): """Return the artist of current playing media (Music track only).""" return self._currentsong.get("artist") @property def media_album_name(self): """Return the album of current playing media (Music track only).""" return self._currentsong.get("album") @property def media_image_hash(self): """Hash value for media image.""" file = self._currentsong.get("file") if file: return hashlib.sha256(file.encode("utf-8")).hexdigest()[:16] return None async def async_get_media_image(self): """Fetch media image of current playing track.""" file = self._currentsong.get("file") if not file: return None, None # not all MPD implementations and versions support the `albumart` and `fetchpicture` commands can_albumart = "albumart" in self._commands can_readpicture = "readpicture" in self._commands response = None # read artwork embedded into the media file if can_readpicture: try: response = await self._client.readpicture(file) except mpd.CommandError as error: _LOGGER.warning( "Retrieving artwork through `readpicture` command failed: %s", error, ) # read artwork contained in the media directory (cover.{jpg,png,tiff,bmp}) if none is embedded if can_albumart and not response: try: response = await self._client.albumart(file) except mpd.CommandError as error: _LOGGER.warning( "Retrieving artwork through `albumart` command failed: %s", error, ) if not response: return None, None image = bytes(response.get("binary")) mime = response.get( "type", "image/png") # readpicture has type, albumart does not return (image, mime) @property def volume_level(self): """Return the volume level.""" if "volume" in self._status: return int(self._status["volume"]) / 100 return None @property def supported_features(self): """Flag media player features that are supported.""" if self._status is None: return 0 supported = SUPPORT_MPD if "volume" in self._status: supported |= SUPPORT_VOLUME_SET | SUPPORT_VOLUME_STEP | SUPPORT_VOLUME_MUTE if self._playlists is not None: supported |= SUPPORT_SELECT_SOURCE return supported @property def source(self): """Name of the current input source.""" return self._currentplaylist @property def source_list(self): """Return the list of available input sources.""" return self._playlists async def async_select_source(self, source): """Choose a different available playlist and play it.""" await self.async_play_media(MEDIA_TYPE_PLAYLIST, source) @Throttle(PLAYLIST_UPDATE_INTERVAL) async def _update_playlists(self, **kwargs): """Update available MPD playlists.""" try: self._playlists = [] for playlist_data in await self._client.listplaylists(): self._playlists.append(playlist_data["playlist"]) except mpd.CommandError as error: self._playlists = None _LOGGER.warning("Playlists could not be updated: %s:", error) async def async_set_volume_level(self, volume): """Set volume of media player.""" if "volume" in self._status: await self._client.setvol(int(volume * 100)) async def async_volume_up(self): """Service to send the MPD the command for volume up.""" if "volume" in self._status: current_volume = int(self._status["volume"]) if current_volume <= 100: self._client.setvol(current_volume + 5) async def async_volume_down(self): """Service to send the MPD the command for volume down.""" if "volume" in self._status: current_volume = int(self._status["volume"]) if current_volume >= 0: await self._client.setvol(current_volume - 5) async def async_media_play(self): """Service to send the MPD the command for play/pause.""" if self._status["state"] == "pause": await self._client.pause(0) else: await self._client.play() async def async_media_pause(self): """Service to send the MPD the command for play/pause.""" await self._client.pause(1) async def async_media_stop(self): """Service to send the MPD the command for stop.""" await self._client.stop() async def async_media_next_track(self): """Service to send the MPD the command for next track.""" await self._client.next() async def async_media_previous_track(self): """Service to send the MPD the command for previous track.""" await self._client.previous() async def async_mute_volume(self, mute): """Mute. Emulated with set_volume_level.""" if "volume" in self._status: if mute: self._muted_volume = self.volume_level await self.async_set_volume_level(0) elif self._muted_volume is not None: await self.async_set_volume_level(self._muted_volume) self._muted = mute async def async_play_media(self, media_type, media_id, **kwargs): """Send the media player the command for playing a playlist.""" _LOGGER.debug("Playing playlist: %s", media_id) if media_type == MEDIA_TYPE_PLAYLIST: if media_id in self._playlists: self._currentplaylist = media_id else: self._currentplaylist = None _LOGGER.warning("Unknown playlist name %s", media_id) await self._client.clear() await self._client.load(media_id) await self._client.play() else: await self._client.clear() self._currentplaylist = None await self._client.add(media_id) await self._client.play() @property def repeat(self): """Return current repeat mode.""" if self._status["repeat"] == "1": if self._status["single"] == "1": return REPEAT_MODE_ONE return REPEAT_MODE_ALL return REPEAT_MODE_OFF async def async_set_repeat(self, repeat): """Set repeat mode.""" if repeat == REPEAT_MODE_OFF: await self._client.repeat(0) await self._client.single(0) else: await self._client.repeat(1) if repeat == REPEAT_MODE_ONE: await self._client.single(1) else: await self._client.single(0) @property def shuffle(self): """Boolean if shuffle is enabled.""" return bool(int(self._status["random"])) async def async_set_shuffle(self, shuffle): """Enable/disable shuffle mode.""" await self._client.random(int(shuffle)) async def async_turn_off(self): """Service to send the MPD the command to stop playing.""" await self._client.stop() async def async_turn_on(self): """Service to send the MPD the command to start playing.""" await self._client.play() await self._update_playlists(no_throttle=True) async def async_clear_playlist(self): """Clear players playlist.""" await self._client.clear() async def async_media_seek(self, position): """Send seek command.""" await self._client.seekcur(position)
import mpd from mpd.asyncio import MPDClient import json client = MPDClient() # Переподключение в случае отвала cоединения def reconnect(func): async def wrapper(*args): try: return await func(*args) except mpd.ConnectionError: await client.connect("localhost", 6600) return await func(*args) return wrapper ############## Базовое управление плеером @reconnect async def get_status(): return await client.status() @reconnect async def get_current_song(): return await client.currentsong() @reconnect async def get_start_status(): data = { 'song': await client.currentsong(),
class MpdSnapcastSyncer: def __init__(self, loop): self._logger = logging.getLogger(self.__class__.__name__) self._loop = loop self.mpd_outputs = {} async def setup(self, snapcast_server: str, mpd_server: str) -> None: snapcast_task = asyncio.create_task(self.setup_snapcast(snapcast_server)) mpd_task = asyncio.create_task(self.setup_mpd(mpd_server)) await snapcast_task await mpd_task def snapcast_client_changed(self, client: snapcast.control.client.Snapclient) -> None: self._loop.create_task(self.async_snapcast_client_changed(client)) async def async_snapcast_client_changed(self, client: snapcast.control.client.Snapclient) -> None: name = client.friendly_name try: output = self.mpd_outputs[name] except KeyError: # there is no output named like this Snapcast client, ignore event self._logger.debug('Ignoring change of snapcast client %s: No matching MPD output' % name) return if output.enabled != client.muted: # If the output is not enabled and the client is muted (or vice # versa), everything is fine. return self._logger.info('Turning %s MPD output %s' % ( 'off' if client.muted else 'on', name )) # determine which method to call actor = self.mpd.disableoutput if client.muted else self.mpd.enableoutput # fake stored state of the output to avoid calling # mpd_output_changed() from mpd_outputs_changed() when MPD notifies us # about our own change self.mpd_outputs[name].enabled = not self.mpd_outputs[name].enabled # call actual actor method await actor(output.id) async def mpd_outputs_changed(self) -> None: async for output in self.mpd.outputs(): output = MPDOutput(output) try: # find stored data about this output old_output = self.mpd_outputs[output.name] except KeyError: # the output didn't exist before, don't trigger any action pass else: if output.enabled != old_output.enabled: # the output's enabled state changed await self.mpd_output_changed(output) # update our stored copy of output data self.mpd_outputs[output.name] = output async def mpd_output_changed(self, output: dict) -> None: for client in self.snapcast.clients: if client.friendly_name != output.name: continue self._logger.info('%s snapcast client %s (%s)' % ( 'Unmuting' if output.enabled else 'Muting', output.name, client.identifier )) await client.set_muted(not output.enabled) return else: self._logger.debug('Ignoring change of MPD output %s: No matching snapcast client' % output.name) async def setup_snapcast(self, snapcast_server: str) -> None: self.snapcast = await snapcast.control.create_server( self._loop, snapcast_server, ) for client in self.snapcast.clients: client.set_callback(self.snapcast_client_changed) self._logger.debug('Set callback for snapcast client %s' % client.friendly_name) async def setup_mpd(self, mpd_server: str) -> None: self.mpd = MPDClient() await self.mpd.connect(mpd_server) # get initial state of outputs async for output in self.mpd.outputs(): output = MPDOutput(output) self.mpd_outputs[output.name] = output # add idle command to to event loop self._loop.create_task(self.listen_mpd()) async def listen_mpd(self) -> None: async for event in self.mpd.idle(['output']): await self.mpd_outputs_changed()
class StreamingComponent(AudioComponent): def __init__(self, sfavs, cpo): # Do Startup tasks super().__init__("Streaming", "Str", "Streaming Audio Services") # We have our own mpd client self.mpdc = MPDClient() self.loop = asyncio.get_event_loop() self.REQ_MET_MPD = False # Use a return value here to set up fitness? self.start_client() # Features self.enable_dirble = False # This is part of the Streaming hierarchy self.component = "streaming" # Config parser object self.cpo = cpo # Zero the client state on startup self.mpdc.clear() # Super favorites self.sfavs = sfavs # Set up the areas of the component self.favs_save_file = "saved/{}_favorites.json".format(self.component) self.favorites_node.menu_labels['comment'] = "Streaming Favorites" self.favorites_node.component = self.component self.load_favorites() self.custom_node = StreamingMenuList("Custom", "Cstm", "My custom stations") self.dirble_node = StreamingMenuList("Dirble", "Drbl", "Streaming Audio Directory") self.add_child(self.custom_node) if self.enable_dirble: self.add_child(self.dirble_node) # Reference Streaming menu in the superfavorites # Testing purposes, set up custom stations (backed from where?) custom_stations = [ StreamingOpus("AncientFM", "Early music.", "http://5.152.208.98:8058/", self.mpdc, genre="Classical", subgenre="Early"), StreamingOpus("Venice Classsic Radio", "Beautiful classical music.", "http://174.36.206.197:8000/stream", self.mpdc, genre="Classical"), StreamingOpus("Bartok Radio", "Hungarian classical radio.", "http://mr-stream.mediaconnect.hu/4741/mr3.mp3", self.mpdc, genre="Classical"), ] # Populate the custom menu for station in custom_stations: self.custom_node.add_child(station) # When we add an opus to a collection, we want to keep track of the genre/subgenre structure? def start_client(self): return self.loop.run_until_complete(self.__async__start_client()) async def __async__start_client(self): try: await self.mpdc.connect('localhost', 6600) except Exception as e: print("Connection failed:", e) self.REQ_MET_MPD = False self.mpdc.consume(0) def requirements_met(self): # req_met = True # # if not self.REQ_MET_MPD: # req_met = False return True def save_favorites(self): favs = [] for fav in self.favorites_node.children: favs.append(fav.opus_get_metadata()) json_favs = json.dumps(favs) with open(self.favs_save_file, mode='w', encoding='utf-8') as the_file: the_file.write(json_favs) def load_favorites(self): try: with open(self.favs_save_file, mode='r', encoding='utf-8') as the_file: favs = json.loads(the_file.read()) for fav in favs: this_opus = StreamingOpus(fav['name'], fav['comment'], fav['url'], self.mpdc, fav['genre'], fav['subgenre']) self.favorites_node.add_child(this_opus) self.sfavs.update_one_favorite_menu(self) except json.JSONDecodeError: pass except FileNotFoundError: pass
# Kill MPD if necessary? pass if __name__ == '__main__': parser = argparse.ArgumentParser(description="Server settings") parser.add_argument("--port", type=int) args = vars(parser.parse_args()) logger.info("Starting up..") cp = ConfigParser() cp.read('opuscule-config.ini') mpdc = MPDClient() op = OpusculeController(cp) connected_clients = [] startup() op_port = cp.get('main', 'port') if args['port']: op_port = args['port'] loop = asyncio.get_event_loop() start_mpd_client()
def __init__(self): self.host, self.port = settings.MPD_HOST, settings.MPD_PORT self.client = MPDClient()
def __init__(self, sfavs, cpo): # Do Startup tasks super().__init__("Streaming", "Str", "Streaming Audio Services") # We have our own mpd client self.mpdc = MPDClient() self.loop = asyncio.get_event_loop() self.REQ_MET_MPD = False # Use a return value here to set up fitness? self.start_client() # Features self.enable_dirble = False # This is part of the Streaming hierarchy self.component = "streaming" # Config parser object self.cpo = cpo # Zero the client state on startup self.mpdc.clear() # Super favorites self.sfavs = sfavs # Set up the areas of the component self.favs_save_file = "saved/{}_favorites.json".format(self.component) self.favorites_node.menu_labels['comment'] = "Streaming Favorites" self.favorites_node.component = self.component self.load_favorites() self.custom_node = StreamingMenuList("Custom", "Cstm", "My custom stations") self.dirble_node = StreamingMenuList("Dirble", "Drbl", "Streaming Audio Directory") self.add_child(self.custom_node) if self.enable_dirble: self.add_child(self.dirble_node) # Reference Streaming menu in the superfavorites # Testing purposes, set up custom stations (backed from where?) custom_stations = [ StreamingOpus("AncientFM", "Early music.", "http://5.152.208.98:8058/", self.mpdc, genre="Classical", subgenre="Early"), StreamingOpus("Venice Classsic Radio", "Beautiful classical music.", "http://174.36.206.197:8000/stream", self.mpdc, genre="Classical"), StreamingOpus("Bartok Radio", "Hungarian classical radio.", "http://mr-stream.mediaconnect.hu/4741/mr3.mp3", self.mpdc, genre="Classical"), ] # Populate the custom menu for station in custom_stations: self.custom_node.add_child(station)