--- /dev/null
+#!/usr/bin/python3
+import re
+import sys
+
+START = 'self._commands = {'
+END = '}'
+
+def find_start(fd):
+ line = fd.readline()
+ while START not in line:
+ line = fd.readline()
+ if not line:
+ break
+ if not line:
+ print('Reach end of file!', file=sys.stderr)
+ sys.exit(1)
+
+
+def main():
+ with open('mpdaio/client.py', 'r', encoding='utf-8') as fd:
+ # fast forward to find self._commands
+ find_start(fd)
+ cmd_patt = '"(?P<command>.*)":'
+ tit_patt = '# ?(?P<title>.*)'
+ cmd_regex = re.compile(cmd_patt)
+ tit_regex = re.compile(tit_patt)
+ # Now extract supported commands
+ line = 'foo'
+ while line and END not in line:
+ line = fd.readline()
+ cmd = cmd_regex.search(line)
+ tit = tit_regex.search(line)
+ if tit:
+ print(f'\n{tit[1]}')
+ print('^'*len(tit[1]))
+ if cmd:
+ print(f'* {cmd[1]}')
+
+
+if __name__ == '__main__':
+ main()
--- /dev/null
+@ECHO OFF\r
+\r
+pushd %~dp0\r
+\r
+REM Command file for Sphinx documentation\r
+\r
+if "%SPHINXBUILD%" == "" (\r
+ set SPHINXBUILD=sphinx-build\r
+)\r
+set SOURCEDIR=source\r
+set BUILDDIR=build\r
+\r
+%SPHINXBUILD% >NUL 2>NUL\r
+if errorlevel 9009 (\r
+ echo.\r
+ echo.The 'sphinx-build' command was not found. Make sure you have Sphinx\r
+ echo.installed, then set the SPHINXBUILD environment variable to point\r
+ echo.to the full path of the 'sphinx-build' executable. Alternatively you\r
+ echo.may add the Sphinx directory to PATH.\r
+ echo.\r
+ echo.If you don't have Sphinx installed, grab it from\r
+ echo.https://www.sphinx-doc.org/\r
+ exit /b 1\r
+)\r
+\r
+if "%1" == "" goto help\r
+\r
+%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%\r
+goto end\r
+\r
+:help\r
+%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%\r
+\r
+:end\r
+popd\r
--- /dev/null
+Changelog
+=========
+
+`Semantic Versioning`_. is used.
+
+Unreleased
+----------
+
+
+.. _Semantic Versioning: https://semver.org/spec/v2.0.0.html
--- /dev/null
+
+Status Commands
+^^^^^^^^^^^^^^^
+
+* clearerror
+* currentsong
+* idle
+* noidle
+* status
+* stats
+
+Playback Option Commands
+^^^^^^^^^^^^^^^^^^^^^^^^
+
+* consume
+* crossfade
+* mixrampdb
+* mixrampdelay
+* random
+* repeat
+* setvol
+* getvol
+* single
+* replay_gain_mode
+* replay_gain_status
+* volume
+
+Playback Control Commands
+^^^^^^^^^^^^^^^^^^^^^^^^^
+
+* next
+* pause
+* play
+* playid
+* previous
+* seek
+* seekid
+* seekcur
+* stop
+
+Queue Commands
+^^^^^^^^^^^^^^
+
+* add
+* addid
+* clear
+* delete
+* deleteid
+* move
+* moveid
+* playlist
+* playlistfind
+* playlistid
+* playlistinfo
+* playlistsearch
+* plchanges
+* plchangesposid
+* prio
+* prioid
+* rangeid
+* shuffle
+* swap
+* swapid
+* addtagid
+* cleartagid
+
+Stored Playlist Commands
+^^^^^^^^^^^^^^^^^^^^^^^^
+
+* listplaylist
+* listplaylistinfo
+* listplaylists
+* load
+* playlistadd
+* playlistclear
+* playlistdelete
+* playlistmove
+* rename
+* rm
+* save
+
+Database Commands
+^^^^^^^^^^^^^^^^^
+
+* albumart
+* count
+* getfingerprint
+* find
+* findadd
+* list
+* listall
+* listallinfo
+* listfiles
+* lsinfo
+* readcomments
+* readpicture
+* search
+* searchadd
+* searchaddpl
+* update
+* rescan
+
+Mounts and neighbors
+^^^^^^^^^^^^^^^^^^^^
+
+* mount
+* unmount
+* listmounts
+* listneighbors
+
+Sticker Commands
+^^^^^^^^^^^^^^^^
+
+* sticker get
+* sticker set
+* sticker delete
+* sticker list
+* sticker find
+
+Connection Commands
+^^^^^^^^^^^^^^^^^^^
+
+* close
+* kill
+* password
+* ping
+* binarylimit
+* tagtypes
+* tagtypes disable
+* tagtypes enable
+* tagtypes clear
+* tagtypes all
+
+Partition Commands
+^^^^^^^^^^^^^^^^^^
+
+* partition
+* listpartitions
+* newpartition
+* delpartition
+* moveoutput
+
+Audio Output Commands
+^^^^^^^^^^^^^^^^^^^^^
+
+* disableoutput
+* enableoutput
+* toggleoutput
+* outputs
+* outputset
+
+Reflection Commands
+^^^^^^^^^^^^^^^^^^^
+
+* config
+* commands
+* notcommands
+* urlhandlers
+* decoders
+
+Client to Client
+^^^^^^^^^^^^^^^^
+
+* subscribe
+* unsubscribe
+* channels
+* readmessages
+* sendmessage
----------------
* If you need a plain MPD client to manage you MPD server, then stick with
- no-ansynio module python-musicpd_
- * If you're building a interactive client, concurrent access to MPD or plugin
+ non-asyncio module python-musicpd_
+ * If you're building an interactive client, concurrent access to MPD or plugin
into another asyncio project then use musicpdaio.
Some examples are provided for the most common cases, see :ref:`tutorial`.
-For a list of currently supported commands in this python module see
-:ref:`commands`.
-
**musicpdaio** tries to come with sane defaults, then running
-:py:class:`mpdaio.MPDClient` with no explicit argument will try defaults values
-to connect to MPD. Cf. :ref:`reference` for more about defaults.
+:py:class:`mpdaio.MPDClient` with no explicit argument will try default values
+to connect to MPD. Cf. :ref:`reference` for more about
+:ref:`defaults<default_settings>`.
+
+.. _socket_connections:
+.. index:: pair: connection pool; socket
+
+Socket connection
+-----------------
+**musicpdaio** uses a connection pool internally to keep already opened socket
+and reuse it.
+
+When first instantiated :py:class:`mpdaio.MPDClient` comes with an empty pool,
+when the first MPD command is called a connection is opened, saved and
+potentially reused later. In case a concurrent MPD command is called while the
+connection is still in use a new connection is made and kept in the pool.
+
+.. code-block:: python
-.. _environment_variables:
+ client = mpdaio.MPDClient()
+ client.connections # Returns an empty list: []
+ client.version # Returns empty string: ''
+ await client.ping() # A connection is made and kept open
+ client.connections # Returns a list; [Connection<example.org:6600>]
.. _snake case: https://en.wikipedia.org/wiki/Snake_case
:maxdepth: 2
tutorial
+
explanations
+
reference
+ changelog
+
Indices and tables
Reference
=========
+.. _environment_variables:
+
Environment variables
---------------------
* else set host to ``localhost``
Default port:
- * use :envvar:`MPD_PORT` environment variable is set
+ * use :envvar:`MPD_PORT` environment variable if set
* else use ``6600``
Default timeout:
- * use :envvar:`MPD_TIMEOUT` is set
- * else use :py:obj:`mpdaio.CONNECTION_TIMEOUT`
+ * use :envvar:`MPD_TIMEOUT` if set
+ * else use :py:obj:`mpdaio.const.CONNECTION_TIMEOUT`
-Changelog
----------
+Supported commands
+------------------
-No release yet
+.. include:: commands.rst
Getting started
----------------
+.. index:: single: command; searchadd
+
+**Connect, clear the queue, add tracks**
+
+.. sourcecode:: python
+
+ import mpdaio
+
+ client = mpdaio.MPDClient()
+ await client.ping() # Plain connection test
+ print(client.version) # Prints MPD's protocol version
+ print(await client.clear()) # Clears the current queue
+ # Add all tracks from artist "Amon Tobin"
+ print(await client.searchadd('(Artist == "Amon Tobin")'))
+ await client.play()
+ await client.setvol(60)
+ print(await client.currentsong())
+ await client.close() # Finally close connection
+
+.. index:: single: command; password
+
+**Using a specific host, port and a password.**
+
+The password is sent when a connection is made, no need to explicitly send the
+password command.
+
+.. sourcecode:: python
+
+ client = mpdaio.MPDClient(host='example.org', port='6601', password='53(237')
+ await client.ping()
+
+**Wrapping some commands in a python script**
.. literalinclude:: tutorial/tutorial-00.py
+.. index:: single: command; albumart
+
+**Fetch album art for the given track**
+
+The logic is similar with `readpicture` command.
+
+.. sourcecode:: python
+
+ client = mpdaio.MPDClient()
+ # Looking for cover art in 'Tool/2001-Lateralus/'
+ track = 'Tool/2001-Lateralus/09-Tool - Lateralus.flac'
+ aart = await cli.albumart(track, 0)
+ received = int(aart.get('binary'))
+ size = int(aart.get('size'))
+ with open('/tmp/cover', 'wb') as cover:
+ # aart = {'size': 42, 'binary': 2051, data: bytes(...)}
+ cover.write(aart.get('data'))
+ while received < size:
+ aart = await cli.albumart(track, received)
+ cover.write(aart.get('data'))
+ received += int(aart.get('binary'))
+ if received != size:
+ print('something went wrong')
+ await cli.close()
+
+Cf. `MPD protocol documentation`_ for more binary responses.
+
Concurrency
-----------
.. literalinclude:: tutorial/tutorial-01.py
+.. _MPD protocol documentation: http://www.musicpd.org/doc/protocol/
.. vim: spell spelllang=en
+++ /dev/null
-../../../mpdaio
\ No newline at end of file
+# mpd-client.py
import asyncio
import logging
-from mpdaio.client import MPDClient
+from mpdaio import MPDClient
# Configure loggers
logging.basicConfig(level=logging.INFO, format='%(levelname)-8s %(message)s')
# The connection is kept open an reused for later commands
await client.ping()
+ # Get player status
status = await client.status()
if status.get('state') == 'play':
current_song_id = status.get('songid')
else:
log.info('Not playing')
+ # Add all songs form artist "The Doors"
+ await client.searchadd('(Artist == "The Doors")')
+ # Start playing
+ if (await client.status()).get('state') != 'play':
+ await client.play()
+
+
# Closes any remaining connections to MPD server
await client.close()
import asyncio
import logging
-from mpdaio.client import MPDClient
+from mpdaio import MPDClient
# Configure loggers
logging.basicConfig(level=logging.INFO, format='%(levelname)-8s %(message)s')
log.setLevel(logging.DEBUG)
+async def search(fltr):
+ # Look for and add
+ await client.searchadd(fltr)
+
+
async def run():
- # Use defaults to access MPD server
- client = MPDClient()
# Make an initial connection to MPD server
# The connection is kept open an reused for later commands
await client.ping()
-
+ await client.clear()
+ filters = [
+ '(Artist == "Neurosis")',
+ '(Artist == "Isis")',
+ '(Artist == "Cult of Luna")',
+ ]
# Each task gathered here will run with it's own connection
- await asyncio.gather(...)
+ await asyncio.gather(*map(search, filters))
# Closes all connections to MPD server
await client.close()
if __name__ == '__main__':
+ # Use defaults to access MPD server
+ client = MPDClient()
asyncio.run(run())