From c7b2fbbb9689a180f220322b21e2b0bef798eb68 Mon Sep 17 00:00:00 2001 From: Kaliko Jack Date: Tue, 6 Feb 2024 18:43:45 +0100 Subject: [PATCH] Add examples --- doc/source/examples.rst | 44 ++++++++++++++ doc/source/examples/client.py | 94 +++++++++++++++++++++++++++++ doc/source/examples/connect.py | 30 +++++++++ doc/source/examples/connect_host.py | 17 ++++++ doc/source/examples/findadd.py | 8 +++ doc/source/examples/playback.py | 7 +++ doc/source/index.rst | 1 + doc/source/use.rst | 12 +++- 8 files changed, 210 insertions(+), 3 deletions(-) create mode 100644 doc/source/examples.rst create mode 100644 doc/source/examples/client.py create mode 100644 doc/source/examples/connect.py create mode 100644 doc/source/examples/connect_host.py create mode 100644 doc/source/examples/findadd.py create mode 100644 doc/source/examples/playback.py diff --git a/doc/source/examples.rst b/doc/source/examples.rst new file mode 100644 index 0000000..291344c --- /dev/null +++ b/doc/source/examples.rst @@ -0,0 +1,44 @@ +.. SPDX-FileCopyrightText: 2018-2023 kaliko +.. SPDX-License-Identifier: LGPL-3.0-or-later + +.. _examples: + +Examples +======== + +Plain examples +-------------- + +Connect, if playing, get currently playing track, the next one: + +.. literalinclude:: examples/connect.py + :language: python + :linenos: + +Connect a specific password protected host: + +.. literalinclude:: examples/connect_host.py + :language: python + :linenos: + +Start playing current queue and set the volume: + +.. literalinclude:: examples/playback.py + :language: python + :linenos: + +Clear the queue, search artist, queue what's found and play: + +.. literalinclude:: examples/findadd.py + :language: python + :linenos: + +Object Oriented example +----------------------- + +A plain client monitoring changes on MPD. + +.. literalinclude:: examples/client.py + :language: python + :linenos: + diff --git a/doc/source/examples/client.py b/doc/source/examples/client.py new file mode 100644 index 0000000..41ef1bd --- /dev/null +++ b/doc/source/examples/client.py @@ -0,0 +1,94 @@ +"""Plain client class +""" +import logging +import select +import sys + +import musicpd + + +class MyClient(musicpd.MPDClient): + """Plain client inheriting from MPDClient""" + + def __init__(self): + # Set logging to debug level + logging.basicConfig(level=logging.DEBUG, + format='%(levelname)-8s %(message)s') + self.log = logging.getLogger(__name__) + super().__init__() + # Set host/port/password after init to overrides defaults + # self.host = 'example.org' + # self.port = 4242 + # self.pwd = 'secret' + + def connect(self): + """Overriding explicitly MPDClient.connect()""" + try: + super().connect(host=self.host, port=self.port) + if hasattr(self, 'pwd') and self.pwd: + self.password(self.pwd) + except musicpd.ConnectionError as err: + # Catch socket error + self.log.error('Failed to connect: %s', err) + sys.exit(42) + + def _wait_for_changes(self, callback): + select_timeout = 10 # second + while True: + self.send_idle() # use send_ API to avoid blocking on read + _read, _, _ = select.select([self], [], [], select_timeout) + if _read: # tries to read response + ret = self.fetch_idle() + # do something + callback(ret) + else: # cancels idle + self.noidle() + + def callback(self, *args): + """Method launch on MPD event, cf. monitor method""" + self.log.info('%s', args) + + def monitor(self): + """Continuously monitor MPD activity. + Launch callback method on event. + """ + try: + self._wait_for_changes(self.callback) + except (OSError, musicpd.MPDError) as err: + self.log.error('%s: Something went wrong: %s', + type(err).__name__, err) + +if __name__ == '__main__': + cli = MyClient() + # You can overrides host here or in init + #cli.host = 'example.org' + # Connect MPD server + try: + cli.connect() + except musicpd.ConnectionError as err: + cli.log.error(err) + + # Monitor MPD changes, blocking/timeout idle approach + try: + cli.socket_timeout = 20 # seconds + ret = cli.idle() + cli.log.info('Leaving idle, got: %s', ret) + except TimeoutError as err: + cli.log.info('Nothing occured the last %ss', cli.socket_timeout) + + # Reset connection + try: + cli.socket_timeout = None + cli.disconnect() + cli.connect() + except musicpd.ConnectionError as err: + cli.log.error(err) + + # Monitor MPD changes, non blocking idle approach + try: + cli.monitor() + except KeyboardInterrupt as err: + cli.log.info(type(err).__name__) + cli.send_noidle() + cli.disconnect() + diff --git a/doc/source/examples/connect.py b/doc/source/examples/connect.py new file mode 100644 index 0000000..0c74448 --- /dev/null +++ b/doc/source/examples/connect.py @@ -0,0 +1,30 @@ +import musicpd +import logging + +import musicpd + +# Set logging to debug level +# it should log messages showing where defaults come from +logging.basicConfig(level=logging.DEBUG, format='%(levelname)-8s %(message)s') +log = logging.getLogger() + +client = musicpd.MPDClient() +# use MPD_HOST/MPD_PORT env var if set else +# test ${XDG_RUNTIME_DIR}/mpd/socket for existence +# fallback to localhost:6600 +# connect support host/port argument as well +client.connect() + +status = client.status() +if status.get('state') == 'play': + current_song_id = status.get('songid') + current_song = client.playlistid(current_song_id)[0] + log.info(f'Playing : {current_song.get("file")}') + next_song_id = status.get('nextsongid', None) + if next_song_id: + next_song = client.playlistid(next_song_id)[0] + log.info(f'Next song : {next_song.get("file")}') +else: + log.info('Not playing') + +client.disconnect() diff --git a/doc/source/examples/connect_host.py b/doc/source/examples/connect_host.py new file mode 100644 index 0000000..a7bdccc --- /dev/null +++ b/doc/source/examples/connect_host.py @@ -0,0 +1,17 @@ +import sys +import logging + +import musicpd + +# Set logging to debug level +logging.basicConfig(level=logging.DEBUG, format='%(levelname)-8s %(message)s') + +client = musicpd.MPDClient() +try: + client.connect(host='example.lan') + client.password('secret') + client.status() +except musicpd.MPDError as err: + print(f'An error occured: {err}') +finally: + client.disconnect() diff --git a/doc/source/examples/findadd.py b/doc/source/examples/findadd.py new file mode 100644 index 0000000..922cae1 --- /dev/null +++ b/doc/source/examples/findadd.py @@ -0,0 +1,8 @@ +import musicpd + +# Using a context manager +# (use env var if you need to override default host) +with musicpd.MPDClient() as client: + client.clear() + client.findadd("(artist == 'Monkey3')") + client.play() diff --git a/doc/source/examples/playback.py b/doc/source/examples/playback.py new file mode 100644 index 0000000..ce7eb78 --- /dev/null +++ b/doc/source/examples/playback.py @@ -0,0 +1,7 @@ +import musicpd + +# Using a context manager +# (use env var if you need to override default host) +with musicpd.MPDClient() as client: + client.play() + client.setvol('80') diff --git a/doc/source/index.rst b/doc/source/index.rst index 9b4b43d..d16644b 100644 --- a/doc/source/index.rst +++ b/doc/source/index.rst @@ -69,6 +69,7 @@ Contents use.rst doc.rst commands.rst + examples.rst contribute.rst diff --git a/doc/source/use.rst b/doc/source/use.rst index 106ce90..7029684 100644 --- a/doc/source/use.rst +++ b/doc/source/use.rst @@ -36,10 +36,16 @@ strings. In the example above, an integer can be used as argument for the written to the socket. To avoid confusion use regular string instead of relying on object string representation. -:py:class:`musicpd.MPDClient` methods returns different kinds of objects depending on the command. Could be :py:obj:`None`, a single object as a :py:obj:`str` or a :py:obj:`dict`, a list of :py:obj:`dict`. +:py:class:`musicpd.MPDClient` methods returns different kinds of objects +depending on the command. Could be :py:obj:`None`, a single object as a +:py:obj:`str` or a :py:obj:`dict`, a list of :py:obj:`dict`. -For more about the protocol and MPD commands see the `MPD protocol -documentation`_. +Then :py:class:`musicpd.MPDClient` **methods signatures** are not hard coded +within this module since the protocol is handled on the server side. Please +refer to the protocol and MPD commands in `MPD protocol documentation`_ to +learn how to call commands and what kind of arguments they expect. + +Some examples are provided for the most common cases, see :ref:`examples`. For a list of currently supported commands in this python module see :ref:`commands`. -- 2.39.5