././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1789253948.1034708 mopidy-4.0.4/0000755000175100017510000000000015251354474012500 5ustar00runnerrunner././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1789253948.0555131 mopidy-4.0.4/.github/0000755000175100017510000000000015251354474014040 5ustar00runnerrunner././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1789253948.0561619 mopidy-4.0.4/.github/ISSUE_TEMPLATE/0000755000175100017510000000000015251354474016223 5ustar00runnerrunner././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1789253942.6397884 mopidy-4.0.4/.github/ISSUE_TEMPLATE/bug.md0000644000175100017510000000221215251354467017321 0ustar00runnerrunner--- name: Bug report about: Create a bug report to help us improve. title: '' labels: '' assignees: '' --- If you are not sure about anything below then please post on our [forum](https://discourse.mopidy.com/) first. **Describe the bug** A clear and concise description of what the bug is. Is this specific to a particular Backend (e.g. Mopidy-Spotify), Frontend (e.g. Mopidy-MPD) or client? **How to reproduce** Steps to reproduce the behaviour including any error messages that occur. **Expected behaviour** A clear and concise description of what you expected to happen. **Environment** Please complete the following information: - Operating system (e.g. Debian Buster, Ubuntu 20.04) - [Running Mopidy](https://docs.mopidy.com/en/latest/running/) as a service or in the terminal? - Your config (output of `sudo mopidyctl config`) - Software versions (output of `sudo mopidyctl deps`) - Any specific hardware (e.g. an exotic USB soundcard) **Additional context** Add any other context about the problem here. If appropriate, upload an [appropriately verbose](https://docs.mopidy.com/en/latest/troubleshooting/#debug-logging) log file showing the bug. ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1789253942.6399138 mopidy-4.0.4/.github/ISSUE_TEMPLATE/config.yml0000644000175100017510000000024315251354467020214 0ustar00runnerrunnerblank_issues_enabled: true contact_links: - name: Mopidy Discourse forum url: https://discourse.mopidy.com/ about: Please ask and answer questions here. ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1789253942.6399138 mopidy-4.0.4/.github/ISSUE_TEMPLATE/extension.md0000644000175100017510000000121315251354467020560 0ustar00runnerrunner--- name: Extension request about: Share your idea for a new Mopidy extension. title: '' labels: C-extension-request assignees: '' --- Please explain your idea and provide any information on how the extension might be implemented. See [this forum thread](https://discourse.mopidy.com/t/supporting-new-streaming-services-and-other-music-sources/22) for more information. Note: these issues are closed as they cannot be fixed in Mopidy itself. However, they will be added to our [Extension wishlist](https://github.com/mopidy/mopidy/issues?utf8=%E2%9C%93&q=label%3AC-extension-request+) label for potential authors/collaborators to find and discuss. ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1789253942.6399138 mopidy-4.0.4/.github/ISSUE_TEMPLATE/feature.md0000644000175100017510000000043715251354467020206 0ustar00runnerrunner--- name: Feature request about: Suggest an idea for this project. title: '' labels: '' assignees: '' --- Mopidy's GitHub Issues are reserved for well-defined feature requests. If this is a more general idea or question, please post on the [forum](https://discourse.mopidy.com) first. ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1789253942.6399138 mopidy-4.0.4/.github/codecov.yml0000644000175100017510000000011015251354467016177 0ustar00runnerrunnercoverage: status: project: default: threshold: 0.3% ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1789253948.056663 mopidy-4.0.4/.github/workflows/0000755000175100017510000000000015251354474016075 5ustar00runnerrunner././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1789253942.6399138 mopidy-4.0.4/.github/workflows/ci.yml0000644000175100017510000000527315251354467017224 0ustar00runnerrunnername: CI on: pull_request: push: branches: - main workflow_dispatch: # No permissions by default; each job requests what it needs. permissions: {} concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: ${{ github.event_name == 'pull_request' }} jobs: build: name: Build runs-on: ubuntu-24.04 permissions: contents: read # Check out the repository. steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false - uses: hynek/build-and-inspect-python-package@2abe76da66d0a6a4a227101f9348ee855797cfa5 # v3.0.1 main: strategy: fail-fast: false matrix: include: - name: "pytest (3.13-lowest)" python: "3.13" tox: "3.13-lowest" - name: "pytest (3.13)" python: "3.13" tox: "3.13" - name: "pytest (3.14)" python: "3.14" tox: "3.14" coverage: true - name: "pytest (3.15)" python: "3.15" tox: "3.15" - name: "pyright" python: "3.14" tox: "pyright" - name: "rumdl" python: "3.14" tox: "rumdl" - name: "ruff check" python: "3.14" tox: "ruff-check" - name: "ruff format" python: "3.14" tox: "ruff-format" - name: "ty" python: "3.14" tox: "ty" - name: "Docs" python: "3.13" tox: "docs" name: ${{ matrix.name }} runs-on: ubuntu-24.04 # Deliberately unpinned: this is our own image, and we always want the # latest version of it. container: ghcr.io/mopidy/ci:latest # zizmor: ignore[unpinned-images] permissions: contents: read # Check out the repository. env: PYTHON: ${{ matrix.python }} TOX_ENV: ${{ matrix.tox }} UV_PYTHON_PREFERENCE: only-managed steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false - uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1 with: enable-cache: true - run: uv python install "$PYTHON" - run: uvx --python "$PYTHON" --with tox-uv tox -e "$TOX_ENV" if: ${{ ! matrix.coverage }} - run: uvx --python "$PYTHON" --with tox-uv tox -e "$TOX_ENV" -- --cov-report=xml if: ${{ matrix.coverage }} - uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v7.0.0 if: ${{ matrix.coverage }} with: token: ${{ secrets.CODECOV_TOKEN }} ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1789253942.6401584 mopidy-4.0.4/.github/workflows/release.yml0000644000175100017510000000166615251354467020253 0ustar00runnerrunnername: Release on: release: types: [published] # No permissions by default; each job requests what it needs. permissions: {} jobs: release: name: Release runs-on: ubuntu-24.04 environment: name: pypi url: https://pypi.org/project/mopidy/ permissions: contents: read # Check out the repository. id-token: write # Authenticate with PyPI using trusted publishing. steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false - uses: hynek/build-and-inspect-python-package@2abe76da66d0a6a4a227101f9348ee855797cfa5 # v3.0.1 id: build - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: name: ${{ steps.build.outputs.artifact-name }} path: dist - uses: pypa/gh-action-pypi-publish@dc37677b2e1c63e2034f94d8a5b11f265b73ba33 # v1.14.2 ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1789253942.6401584 mopidy-4.0.4/.github/workflows/ubuntu-devel.yml0000644000175100017510000000230715251354467021243 0ustar00runnerrunnername: "Ubuntu devel" on: schedule: # Every Friday at 18:20 UTC, so we can fix the issue over the weekend - cron: "20 18 * * 5" workflow_dispatch: # No permissions by default; each job requests what it needs. permissions: {} jobs: main: name: "Test: Python 3.14" runs-on: ubuntu-24.04 # Deliberately unpinned: the container should be automatically updated # from time to time. container: ubuntu:devel # zizmor: ignore[unpinned-images] permissions: contents: read # Check out the repository. steps: - name: Install dependencies run: | apt-get update DEBIAN_FRONTEND=noninteractive apt-get install -y \ git \ gstreamer1.0-plugins-good \ gstreamer1.0-plugins-ugly \ libcairo2-dev \ libgirepository-2.0-dev \ libglib2.0-dev \ python3 \ python3-gst-1.0 \ tox - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false - name: Configure git safe directory run: git config --global --add safe.directory "$GITHUB_WORKSPACE" - run: tox -e py314 ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1789253942.6401584 mopidy-4.0.4/.gitignore0000644000175100017510000000015015251354467014466 0ustar00runnerrunner*.egg-info/ /*.lock /.*_cache/ /.coverage /.tox/ /.venv/ /build/ /dist/ /site/ __pycache__/ mopidy.log* ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1789253942.6401584 mopidy-4.0.4/.mailmap0000644000175100017510000000543015251354467014125 0ustar00runnerrunnerStein Magnus Jodal Stein Magnus Jodal Thomas Adamcik Thomas Adamcik Thomas Adamcik Thomas Adacmik Kristian Klette Johannes Knutsen Johannes Knutsen John Bäckstrand David Caruso Adam Rigg Ernst Bammer Alli Witheford Alexandre Petitjean Alexandre Petitjean Javier Domingo Cansino Lasse Bigum Nick Steel Nick Steel Nick Steel Nick Steel Janez Troha Janez Troha Luke Giuliani Colin Montgomerie Nathan Harper Ignasi Fosch Christopher Schirner Laura Barber John Cass Ronald Zielaznicki Kyle Heyne Tom Roth Eric Jahn Loïck Bonniot Jens Lütjen Jens Lütjen Daniel T Ismael Asensio Brendan Jones Marvin Preuss Bernhard Gehl Caysho Nick Aquina Jarryd Tilbrook Matthieu Melquiond Geoffroy Youri Berret Hugo van Kemenade Tobias Girstmair Jonathan Jefferies Matthew H. Flamm Keith Scroggs Jan Iversen Thomas Kern Benoit Hagenbourger ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1789253942.6401584 mopidy-4.0.4/.pre-commit-config.yaml0000644000175100017510000000113015251354467016756 0ustar00runnerrunner# Run `pre-commit install` to install the pre-commit hooks. # # Run `pre-commit autoupdate` to update all the plugins here. # # See https://pre-commit.com for more information. repos: - repo: https://github.com/pre-commit/pre-commit-hooks rev: v4.5.0 hooks: - id: trailing-whitespace - id: check-toml - repo: local hooks: - id: ruff-check name: ruff check entry: ruff check language: system types: [python] - id: ruff-format name: ruff format entry: ruff format language: system types: [python] ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1789253942.6401584 mopidy-4.0.4/.readthedocs.yml0000644000175100017510000000076715251354467015602 0ustar00runnerrunnerversion: 2 build: os: ubuntu-24.04 tools: python: latest apt_packages: # pyobject dependencies - libcairo2-dev - libgirepository-2.0-dev jobs: post_create_environment: - pip install uv post_install: - UV_PROJECT_ENVIRONMENT=$READTHEDOCS_VIRTUALENV_PATH uv sync --all-extras --group docs --link-mode copy build: html: - zensical build post_build: - mkdir -p $READTHEDOCS_OUTPUT/html/ - cp -r site/* $READTHEDOCS_OUTPUT/html/ ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1789253942.6401584 mopidy-4.0.4/AUTHORS0000644000175100017510000001407415251354467013560 0ustar00runnerrunner- Stein Magnus Jodal - Johannes Knutsen - Thomas Adamcik - Kristian Klette - Martins Grunskis - Henrik Olsson - Antoine Pierlot-Garcin - John Bäckstrand - Fred Hatfull - Erling Børresen - David Caruso - Christian Johansen - Matt Bray - Trygve Aaberge - Wouter van Wijk - Jeremy B. Merrill - Adam Rigg - Ernst Bammer - Nick Steel - Zan Dobersek - Thomas Refis - Janez Troha - Tobias Sauerwein - Alli Witheford - Alexandre Petitjean - Terje Larsen - Javier Domingo Cansino - Pavol Babincak - Javier Domingo - Lasse Bigum - David Eisner - PÃ¥l Ruud - Thomas Kemmer - Paul Connolley - Luke Giuliani - Colin Montgomerie - Simon de Bakker - Arnaud Barisain-Monrose - Nathan Harper - Pierpaolo Frasa - Thomas Scholtes - Sam Willcocks - Ignasi Fosch - Arjun Naik - Christopher Schirner - Dmitry Sandalov - Lukas Vogel - Thomas Amland - Deni Bertovic - Ali Ukani - Dirk Groenen - John Cass - Laura Barber - Jakab Kristóf - Ronald Zielaznicki - Wojciech WnÄ™trzak - Camilo Nova - Dražen LuÄ�anin - Naglis Jonaitis - Kyle Heyne - Tom Roth - Mark Greenwood - Stein Karlsen - Dejan Prokić - Eric Jahn - Mikhail Golubev - Danilo Bargen - Bjørnar Snoksrud - Giorgos Logiotatidis - Ben Evans - vrs01 - Cadel Watson - Loïck Bonniot - Gustaf Hallberg - kozec - Jelle van der Waa - Alex Malone - Daniel Hahler - Bryan Bennett - Jens Lütjen - Lina He - Daniel T - Lars Kruse - Benjamin Chrétien - SeppSTA - Ismael Asensio - Tom Parker - Nantas Nardelli - Naglis Jonaitis - Alexander Jaworowski - Don Armstrong - Nadav Tau - Aleksandar Benic - Tom Swirly - Piotr Dobrowolski - Tomas Susanka - James Barnsley - Caysho - Brendan Jones - Marvin Preuss - Bernhard Gehl - CL123123 - Piotr Dobrowolski - Nick Aquina - Marcus Götling - Dominique Tardif - Alexey Murz Korepov - Jarryd Tilbrook - Dan Brough - Jonathan Jefferies - Matthieu Melquiond - Damien Cassou - Leonid Bogdanov - Geoffroy Youri Berret - Dan Stowell - Gildas Le Nadan - Zvonimir Fras - Simon - Vivien Henry - Hugo van Kemenade - Tobias Girstmair - Jakub FijaÅ‚kowski - Jonathan Jefferies - Prajjwal Nijhara - Asmi Jafar <47150162+asmijafar20@users.noreply.github.com> - Saloni Gupta <60188408+salonigupta1@users.noreply.github.com> - Parth Verma - Matthew H. Flamm - Matthew Gamble - Keith Scroggs - Andrzej Rybczak - Tim Gates - Davis Mosenkovs - Jonathan - Kunal Attri - Archish Thakkar - grdorin <94456679+grdorin@users.noreply.github.com> - solo - lmdc45 <47163513+lmdc45@users.noreply.github.com> - Matthias Meulien - Jan Iversen - SandeshPyakurel <85491057+SandeshPyakurel@users.noreply.github.com> - ayushrakesh <115995339+ayushrakesh@users.noreply.github.com> - Rakesh Chatrath - Thomas Kern - Xavier Chevalier - Frank Anderson - Aarni Koskela - Fabio Manganiello - Raphael Nestler - Benoit Hagenbourger ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1789253942.6401584 mopidy-4.0.4/LICENSE0000644000175100017510000002613615251354467013517 0ustar00runnerrunner Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1789253948.103434 mopidy-4.0.4/PKG-INFO0000644000175100017510000000575515251354474013611 0ustar00runnerrunnerMetadata-Version: 2.4 Name: mopidy Version: 4.0.4 Summary: Mopidy is an extensible music server written in Python Author-email: Stein Magnus Jodal License: Apache-2.0 Project-URL: Homepage, https://mopidy.com/ Project-URL: Documentation, https://docs.mopidy.com/ Project-URL: Discourse forum, https://discourse.mopidy.com/ Project-URL: Zulip chat, https://mopidy.zulipchat.com/ Project-URL: Source, https://github.com/mopidy/mopidy Project-URL: Issues, https://github.com/mopidy/mopidy/issues Classifier: Development Status :: 5 - Production/Stable Classifier: Environment :: No Input/Output (Daemon) Classifier: Intended Audience :: End Users/Desktop Classifier: License :: OSI Approved :: Apache Software License Classifier: Operating System :: OS Independent Classifier: Topic :: Multimedia :: Sound/Audio :: Players Requires-Python: >=3.13 Description-Content-Type: text/markdown License-File: LICENSE License-File: AUTHORS Requires-Dist: cyclopts>=3.12 Requires-Dist: httpx>=0.28.1 Requires-Dist: platformdirs>=4.3 Requires-Dist: pydantic>=2.10 Requires-Dist: pygobject>=3.50 Requires-Dist: pykka>=4.1 Requires-Dist: rich>=13.9 Requires-Dist: tornado>=6.4.2 Dynamic: license-file # Mopidy [Mopidy](https://mopidy.com/) is an extensible music server written in Python. It plays music from local disk, Spotify, SoundCloud, internet radio, and more, controlled from any MPD or web client on your phone, tablet, or computer. [![Latest PyPI version](https://img.shields.io/pypi/v/mopidy)](https://pypi.org/project/mopidy/) [![CI build status](https://img.shields.io/github/actions/workflow/status/mopidy/mopidy/ci.yml)](https://github.com/mopidy/mopidy/actions/workflows/ci.yml) [![Read the Docs build status](https://img.shields.io/readthedocs/mopidy)](https://docs.mopidy.com/) [![Test coverage](https://img.shields.io/codecov/c/github/mopidy/mopidy)](https://codecov.io/gh/mopidy/mopidy) [![Packaging status](https://repology.org/badge/tiny-repos/mopidy.svg)](https://repology.org/project/mopidy/versions) [![Discourse forum](https://img.shields.io/badge/forum-discourse-brightgreen)](https://discourse.mopidy.com/) ## Install See the [installation docs](https://docs.mopidy.com/stable/installation/) for packages for Debian, Ubuntu, Raspberry Pi OS, Arch Linux, macOS, and manual installation. ## Links - [Website](https://mopidy.com/) — project pitch, extensions, and news - [Documentation](https://docs.mopidy.com/) — install, configure, and develop - [Discourse forum](https://discourse.mopidy.com/) — ask for help, discuss usage and development, and follow announcements - [Zulip chat](https://mopidy.zulipchat.com/) — real-time development chat - [Issue tracker](https://github.com/mopidy/mopidy/issues) — bugs and feature requests ## Contributing See the [contributing guide](https://docs.mopidy.com/stable/about/contributing/). We welcome help with bug fixing, testing, documentation, and supporting other users. ## License Mopidy is licensed under the [Apache License, Version 2.0](LICENSE). ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1789253942.6401584 mopidy-4.0.4/README.md0000644000175100017510000000346315251354467013767 0ustar00runnerrunner# Mopidy [Mopidy](https://mopidy.com/) is an extensible music server written in Python. It plays music from local disk, Spotify, SoundCloud, internet radio, and more, controlled from any MPD or web client on your phone, tablet, or computer. [![Latest PyPI version](https://img.shields.io/pypi/v/mopidy)](https://pypi.org/project/mopidy/) [![CI build status](https://img.shields.io/github/actions/workflow/status/mopidy/mopidy/ci.yml)](https://github.com/mopidy/mopidy/actions/workflows/ci.yml) [![Read the Docs build status](https://img.shields.io/readthedocs/mopidy)](https://docs.mopidy.com/) [![Test coverage](https://img.shields.io/codecov/c/github/mopidy/mopidy)](https://codecov.io/gh/mopidy/mopidy) [![Packaging status](https://repology.org/badge/tiny-repos/mopidy.svg)](https://repology.org/project/mopidy/versions) [![Discourse forum](https://img.shields.io/badge/forum-discourse-brightgreen)](https://discourse.mopidy.com/) ## Install See the [installation docs](https://docs.mopidy.com/stable/installation/) for packages for Debian, Ubuntu, Raspberry Pi OS, Arch Linux, macOS, and manual installation. ## Links - [Website](https://mopidy.com/) — project pitch, extensions, and news - [Documentation](https://docs.mopidy.com/) — install, configure, and develop - [Discourse forum](https://discourse.mopidy.com/) — ask for help, discuss usage and development, and follow announcements - [Zulip chat](https://mopidy.zulipchat.com/) — real-time development chat - [Issue tracker](https://github.com/mopidy/mopidy/issues) — bugs and feature requests ## Contributing See the [contributing guide](https://docs.mopidy.com/stable/about/contributing/). We welcome help with bug fixing, testing, documentation, and supporting other users. ## License Mopidy is licensed under the [Apache License, Version 2.0](LICENSE). ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1789253948.056816 mopidy-4.0.4/docs/0000755000175100017510000000000015251354474013430 5ustar00runnerrunner././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1789253948.0583394 mopidy-4.0.4/docs/about/0000755000175100017510000000000015251354474014542 5ustar00runnerrunner././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1789253942.6406038 mopidy-4.0.4/docs/about/authors-update.sh0000755000175100017510000000027115251354467020050 0ustar00runnerrunner#!/bin/bash set -euo pipefail IFS=$'\n\t' # Keep authors in the order of appearance and use awk to filter out dupes git log --format='- %aN <%aE>' --reverse | awk '!x[$0]++' > AUTHORS ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1789253942.6406589 mopidy-4.0.4/docs/about/authors.md0000644000175100017510000000134015251354467016551 0ustar00runnerrunner# Contributors Mopidy has been built by a community of contributors since 2009. Every bug report, patch, documentation fix, and kind word has helped make it what it is today. Thank you. Mopidy is copyright 2009-2026 Stein Magnus Jodal and contributors, and is licensed under the [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0). The names below are everyone who has contributed to the primary Mopidy Git repository, listed in order of first contribution. --8<-- "AUTHORS" Want to add your name to this list? The best way to contribute is through code, documentation, tests, or bug reports — but helping other users and spreading the word counts too. See [Contributing](contributing.md) for a head start. ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1789253942.6406589 mopidy-4.0.4/docs/about/codestyle.md0000644000175100017510000000066615251354467017071 0ustar00runnerrunner # Code style All projects in the Mopidy organization use [Ruff](https://github.com/astral-sh/ruff) for code style enforcement: - Automatically format all code, using the default configuration. - Automatically sort imports, using the default configuration. - As far as reasonable and possible, comply with the lint warnings. Strict adherence is enforced by our CI setup. Pull requests that do not pass these checks will not be merged. ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1789253942.6406589 mopidy-4.0.4/docs/about/contributing.md0000644000175100017510000001030115251354467017570 0ustar00runnerrunner # Contributing If you want to contribute to Mopidy, here are some tips to get you started. ## Asking questions Please get in touch with us in one of these ways when requesting help with Mopidy and its extensions: - Our Discourse forum: [discourse.mopidy.com](https://discourse.mopidy.com). - The `#mopidy-users` stream on Zulip chat: [mopidy.zulipchat.com](https://mopidy.zulipchat.com). Before asking for help, it might be worth your time to read the [troubleshooting](../usage/troubleshooting.md) page, both so you might find a solution to your problem but also to be able to provide useful details when asking for help. ## Helping users If you want to contribute to Mopidy, a great place to start is by helping other users in the discussion forum and the `#mopidy-users` Zulip stream. This is a contribution we value highly. As more people help with user support, new users get faster and better help. For your own benefit, you'll quickly learn what users find confusing, difficult or lacking, giving you some ideas for where you may contribute improvements, either to code or documentation. Lastly, this may also free up time for other contributors to spend more time on fixing bugs or implementing new features. ## Issue guidelines - If you need help, see [Asking questions](#asking-questions) above. The GitHub issue tracker is not a support forum. - If you are not sure if what you're experiencing is a bug or not, post in the [discussion forum](https://discourse.mopidy.com) first to verify that it's a bug. - If you are sure that you've found a bug or have a feature request, check if there's already an issue in the [issue tracker](https://github.com/mopidy/mopidy/issues). If there is, see if there is anything you can add to help reproduce or fix the issue. - If there is no existing issue matching your bug or feature request, create a [new issue](https://github.com/mopidy/mopidy/issues/new). Please include as much relevant information as possible. If it's a bug, include how to reproduce the bug and any relevant logs or error messages. ## Pull request guidelines - Before spending any time on making a pull request: - If it's a bug, [file an issue](#issue-guidelines). - If it's an enhancement, discuss it with other Mopidy developers first, either in a GitHub issue, on the discussion forum, or on Zulip chat. Making sure your ideas and solutions are aligned with other contributors greatly increases the odds of your pull request being quickly accepted. - Create a new branch, based on the `main` branch, for every feature or bug fix. Keep branches small and on topic, as that makes them far easier to review. - Follow the [code style](codestyle.md), especially make sure the `ruff` linter does not complain about anything. Our CI setup will check that your pull request is "ruff clean". See [code linting](devenv.md#style-checking-and-linting). - Include tests for any new feature or substantial bug fix. See [running tests](devenv.md#running-tests). - Include documentation for any new feature. See [writing docs](devenv.md#writing-documentation). - Feel free to include a changelog entry in your pull request. The changelog is in `docs/changelog/index.md`. - Write good commit messages. - Follow the template "topic: description" for the first line of the commit message, e.g. "mpd: Switch list command to using list_distinct". See the commit history for inspiration. - Use the rest of the commit message to explain anything you feel isn't obvious. It's better to have the details here than in the pull request description, since the commit message will live forever. - Write in the imperative, present tense: "add" not "added". For more inspiration, feel free to read these blog posts: - [Writing Git commit messages](https://365git.tumblr.com/post/3308646748/writing-git-commit-messages) - [A Note About Git Commit Messages](https://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html) - [On commit messages](https://who-t.blogspot.com/2009/12/on-commit-messages.html) - Send a pull request to the `main` branch. See the [GitHub pull request docs](https://help.github.com/en/articles/about-pull-requests) for help. ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1789253942.6406589 mopidy-4.0.4/docs/about/devenv.md0000644000175100017510000004004015251354467016353 0ustar00runnerrunner # Development environment This page describes a common development setup for working with Mopidy and Mopidy extensions. Of course, there may be other ways that work better for you and the tools you use, but here's one recommended way to do it. ## Initial setup The following steps help you get a good initial setup. They build on each other to some degree, so if you're not very familiar with Python development it might be wise to proceed in the order laid out here. ### Install Mopidy the regular way Install Mopidy the regular way. Mopidy has some non-Python dependencies which may be tricky to install. Thus we recommend to always start with a full regular Mopidy install, as described in installation. That is, if you're running e.g. Debian, start with installing Mopidy from Debian packages. ### Make a development workspace Make a directory to be used as a workspace for all your Mopidy development: ```console $ mkdir ~/mopidy-dev ``` It will contain all the Git repositories you'll check out when working on Mopidy and extensions. ### Make a virtualenv Make a Python virtualenv for Mopidy development. The virtualenv will wall off Mopidy and its dependencies from the rest of your system. All development and installation of Python dependencies, versions of Mopidy, and extensions are done inside the virtualenv. This way your regular Mopidy install, which you set up in the first step, is unaffected by your hacking and will always be working. To create a virtualenv in the Mopidy workspace directory, run: ```console $ python3 -m venv ~/mopidy-dev/.venv ``` Now, each time you open a terminal and want to activate the `mopidy` virtualenv, run: ```console $ . ~/mopidy-dev/.venv/bin/activate ``` There are lots of ways to set up your shell to automatically activate the virtualenv, e.g. when changing directory into `~/mopidy-dev/` or a subdirectory. As this is just convenience and not strictly required, it is left as an exercise for the reader. ### Clone the repo from GitHub Once inside the virtualenv, it's time to clone the `mopidy/mopidy` Git repo from GitHub: ```console $ cd ~/mopidy-dev/ $ git clone https://github.com/mopidy/mopidy.git ``` When you've cloned the `mopidy` Git repo, `cd` into it: ```console $ cd ~/mopidy-dev/mopidy/ ``` With a fresh clone of the Git repo, you should start out on the `main` branch. This is where all features for the next feature release land. To confirm that you're on the right branch, run: ```console $ git branch ``` ### Install Mopidy from the Git repo Next up, we'll want to run Mopidy from the Git repo. There's two reasons for this: first of all, it lets you easily change the source code, restart Mopidy, and see the change take effect. Second, it's a convenient way to keep at the bleeding edge, testing the latest developments in Mopidy itself or test some extension against the latest Mopidy changes. Assuming you're still inside the Git repo, use pip to install Mopidy from the Git repo in an "editable" form: ```console $ pip install --upgrade --editable . ``` When using the `--editable` flag, the source code is not copied into the virtualenv's `site-packages` directory, but instead creates a link there pointing to the Git repo. This way, you can change the source code in the Git repo and the changes will be visible inside the virtualenv without having to reinstall Mopidy. It will also create a `mopidy` executable inside the virtualenv that will always run the latest code from the Git repo: ```console $ cat ~/mopidy-dev/.venv/bin/mopidy ... ``` This file is on the path when the virtualenv is active, so you can run it from anywhere, simply by running: ```console $ mopidy ``` /// tip It is also possible to run Python apps directly, e.g. using `python3 src/mopidy` directly on the `/mopidy-dev/mopidy/src/mopidy/` Python package directory. However, if you don't run the install command above, the extensions bundled with Mopidy will not be registered and made available for use, making Mopidy quite useless. /// Third, the install command will register the bundled Mopidy extensions so that Mopidy may find them through `importlib`. The result of this can be seen in a file named `entry_points.txt` which can be found inside the virtualenv dir, e.g. `/mopidy-dev/.venv/lib/python3.13/site-packages/Mopidy-4.0.0.dist-info/entry_points.txt`. The `entry_points.txt` file is of special interest as it shows both how the above executable and the bundled extensions are connected to the Mopidy source code: ```ini title="entry_points.txt" [console_scripts] mopidy = mopidy._app.cli:app.meta [mopidy.ext] file = mopidy._exts.file:Extension http = mopidy._exts.http:Extension m3u = mopidy._exts.m3u:Extension softwaremixer = mopidy._exts.softwaremixer:Extension stream = mopidy._exts.stream:Extension ``` ### Install development tools Before continuing, you will probably want to install the development tools we use as well. These can be installed into the active virtualenv by running: ```console $ cd ~/mopidy-dev/mopidy/ $ pip install --upgrade --editable ".[dev]" ``` Note that this is the same command as you used to install Mopidy from the Git repo, with the addition of the `[dev]` suffix after `.`. This makes pip install the "dev" set of extra dependencies. Exactly what the "dev" set includes are defined in `pyproject.toml`. Since pip v25.1 you can instead do: ```console $ pip install --upgrade --group dev --editable . ``` To upgrade the development tools in the future, just rerun the exact same command. ## Running Mopidy from Git As long as the virtualenv is activated, you can start Mopidy from any directory. Simply run: ```console $ mopidy ``` To stop it again, press ++ctrl+c++. Every time you change code in Mopidy or an extension and want to see it live, you must restart Mopidy. If you want to iterate quickly while developing, it may sound a bit tedious to restart Mopidy for every minor change. Then it's useful to have tests to exercise your code... ## Running tests Mopidy has quite good test coverage, and we would like all new code going into Mopidy to come with tests. ### Test it all You need to know at least one command; the one that runs all the tests: ```console $ tox ``` This will run exactly the same tests as our CI setup runs for all our branches and pull requests. If this command turns green, you can be quite confident that your pull request will get the green flag from CI as well, which is a requirement for it to be merged. As this is the ultimate test command, it's also the one taking the most time to run; up to a minute, depending on your system. But, if you have patience, this is all you need to know. Always run this command before pushing your changes to GitHub. If you take a look at the tox config in `pyproject.toml`, you'll see that tox runs tests in multiple environments, including `ruff-check` and `ruff-format` environments that lint the source code for issues and a `docs` environment that tests that the documentation can be built. You can also limit tox to just test specific environments using the `-e` option, e.g. to run just unit tests on Python 3.13: ```console $ tox -e 3.13 ``` To learn more, see the [tox documentation](https://tox.readthedocs.io/) . Before submitting a pull request, we recommend running: ```console $ tox ``` This will locally run similar tests to what we use in our CI runs and help us to merge high-quality contributions. ### Running unit tests Under the hood, `tox -e 3.13` will use [pytest](https://docs.pytest.org/) as the test runner. We can also use it directly to run all tests: ```console $ pytest ``` pytest has lots of possibilities, so you'll have to dive into their docs and plugins to get full benefit from it. To get you interested, here are some examples. We can limit to just tests in a single directory to save time: ```console $ pytest tests/http/ ``` With the help of the `pytest-xdist` plugin, we can run tests with four Python processes in parallel, which usually cuts the test time in half or more: ```console $ pytest -n 4 ``` Another useful feature from `pytest-xdist`, is the possibility to stop on the first test failure, watch the file system for changes, and then rerun the tests. This makes for a very quick code-test cycle: ```console $ pytest -f # or --looponfail ``` With the help of the pytest-cov plugin, we can get a report on what parts of the given module are covered by the test suite: ```console $ pytest --cov ``` /// tip Up to date test coverage statistics can also be viewed online at [Codecov](https://codecov.io/gh/mopidy/mopidy). /// If we want to speed up the test suite, we can even get a list of the ten slowest tests: ```console $ pytest --durations=10 ``` By now, you should be convinced that running pytest directly during development can be very useful. ### Continuous integration Mopidy uses [GitHub Actions](https://github.com/mopidy/mopidy/actions) for automatically running the test suite when code is pushed to GitHub. This works both for the main Mopidy repo, but also for any forks. This way, any contributions to Mopidy through GitHub will automatically be tested, and the build status will be visible in the GitHub pull request interface, making it easier to evaluate the quality of pull requests. For each successful build, the CI setup submits code coverage data to [Codecov](https://codecov.io/gh/mopidy/mopidy). If you're out of work, Codecov might help you find areas in the code which could need better test coverage. ### Style checking and linting We're quite pedantic about codestyle and try hard to keep the Mopidy code base a very clean and nice place to work in. Luckily, you can get very far by using the [ruff](https://github.com/astral-sh/ruff) linter to check your code for issues before submitting a pull request. Mopidy's ruff rules are configured in `pyproject.toml`. You can either run the `ruff-check` tox environment, like our CI setup will do on your pull request: ```console $ tox -e ruff-check ``` Or you can run ruff directly: ```console $ ruff . ``` If successful, the command will not print anything at all. /// tip In some rare cases it doesn't make sense to listen to ruff's warnings. In those cases, ignore the check by appending `# noqa: ` to the source line that triggers the warning. The `# noqa` part will make ruff skip all checks on the line, while the warning code will help other developers lookup what you are ignoring. /// ## Writing documentation Documentation lives in the `docs/` directory and is written in Markdown. We use [Zensical](https://zensical.org/) to build the documentation site. To serve the documentation locally with live reloading, run: ```console $ zensical serve ``` To build the documentation without serving it: ```console $ zensical build ``` The documentation at is automatically updated when a change is pushed to the `mopidy/mopidy` repo at GitHub. ## Working on extensions Much of the above also applies to Mopidy extensions, though they're often a bit simpler. They don't have documentation sites and their test suites are either small and fast, or sadly missing entirely. Most of them use tox to run various linters, and pytest can be used to run their test suites. ### Installing extensions As always, the `mopidy` virtualenv should be active when working on extensions: ```console $ . ~/mopidy-dev/.venv/bin/activate ``` Just like with non-development Mopidy installations, you can install extensions using pip: ```console $ pip install mopidy-scrobbler ``` Installing an extension from its Git repo works the same way as with Mopidy itself. First, go to the Mopidy workspace: ```console $ cd ~/mopidy-dev/ ``` Clone the desired Mopidy extension: ```console $ git clone https://github.com/mopidy/mopidy-spotify.git ``` Change to the newly created extension directory: ```console $ cd ~/mopidy-dev/mopidy-spotify/ ``` Then, install the extension in "editable" mode, so that it can be imported from anywhere inside the virtualenv and the extension is registered and discoverable through `importlib`: ```console $ pip install --editable . ``` Every extension will have a `README.md` file. It may contain information about extra dependencies required, development process, etc. Extensions usually have a changelog in their GitHub Releases page. ### Upgrading extensions Extensions often have a much quicker life cycle than Mopidy itself, often with daily releases in periods of active development. To find outdated extensions in your virtualenv, you can run: ```console $ pip list --outdated ``` To upgrade an extension installed with pip, simply use pip: ```console $ pip install --upgrade mopidy-scrobbler ``` To upgrade an extension installed from a Git repo, it's usually enough to pull the new changes in: ```console $ cd ~/mopidy-dev/mopidy-spotify/ $ git pull ``` Of course, if you have local modifications, you'll need to stash these away on a branch or similar first. Depending on the changes to the extension, it may be necessary to update the metadata about the extension package by installing it in "editable" mode again: ```console $ pip install --editable . ``` ## Contribution workflow Before you being, make sure you've read the contributing page and the guidelines there. This section will focus more on the practical workflow. For the examples, we're making a change to Mopidy. Approximately the same workflow should work for most Mopidy extensions too. ### Setting up Git remotes Assuming we already have a local Git clone of the upstream Git repo in `/mopidy-dev/mopidy/`, we can run `git remote -v` to list the configured remotes of the repo: ```console $ git remote -v origin https://github.com/mopidy/mopidy.git (fetch) origin https://github.com/mopidy/mopidy.git (push) ``` For clarity, we can rename the `origin` remote to `upstream`: ```console $ git remote rename origin upstream $ git remote -v upstream https://github.com/mopidy/mopidy.git (fetch) upstream https://github.com/mopidy/mopidy.git (push) ``` If you haven't already, [fork the repository](https://help.github.com/en/articles/fork-a-repo) to your own GitHub account. Then, add the new fork as a remote to your local clone: ```console $ git remote add myuser git@github.com:myuser/mopidy.git ``` The end result is that you have both the upstream repo and your own fork as remotes: ```console $ git remote -v myuser git@github.com:myuser/mopidy.git (fetch) myuser git@github.com:myuser/mopidy.git (push) upstream https://github.com/mopidy/mopidy.git (fetch) upstream https://github.com/mopidy/mopidy.git (push) ``` ### Creating a branch Fetch the latest data from all remotes without affecting your working directory: ```console $ git remote update --prune ``` Now, we are ready to create and checkout a new branch off of the upstream `main` branch for our work: ```console $ git checkout -b fix-crash-on-foo upstream/main ``` Do the work, while remembering to adhere to code style, test the changes, make necessary updates to the documentation, and making small commits with good commit messages. All as described in contributing and elsewhere in the devenv guide. ### Creating a pull request When everything is done and committed, push the branch to your fork on GitHub: ```console $ git push myuser fix-crash-on-foo ``` Go to the repository on GitHub where you want the change merged, in this case , and [create a pull request](https://help.github.com/en/articles/creating-a-pull-request). ### Updating a pull request When the pull request is created, our CI setup will run all tests on it. If something fails, you'll usually get a notification from GitHub. You might as well just fix the issues right away, as we won't merge a pull request without all CI builds being green. See running-tests on how to run the same tests locally as our CI setup runs on your pull request. When you've fixed the issues, you can update the pull request simply by pushing more commits to the same branch in your fork: ```console $ git push myuser fix-crash-on-foo ``` Likewise, when you get review comments from other developers on your pull request, you're expected to create additional commits which addresses the comments. Push them to your branch so that the pull request is updated. ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1789253942.6406589 mopidy-4.0.4/docs/about/index.md0000644000175100017510000000177015251354467016202 0ustar00runnerrunner # About Mopidy is an open source project. Here is where to find us. ## Getting help The best place to ask for help is the [Discourse forum](https://discourse.mopidy.com/). You can also find users in the `#mopidy-users` stream on [Zulip chat](https://mopidy.zulipchat.com/). If you've found a bug or have a feature request, please create an issue in the [issue tracker](https://github.com/mopidy/mopidy/issues). If you're unsure whether it's a bug or not, ask in the forum first. ## Getting involved The best place to follow and participate in Mopidy development is the `#mopidy-dev` stream on [Zulip chat](https://mopidy.zulipchat.com/). Announcements also appear on the [Discourse forum](https://discourse.mopidy.com/). ## Source code Mopidy's source code is on [GitHub](https://github.com/mopidy/mopidy), along with many of the extensions maintained by the Mopidy organization. Mopidy has been in development since 2009, built by [many contributors](authors.md) and supported by [sponsors](sponsors.md). ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1789253942.6406589 mopidy-4.0.4/docs/about/releasing.md0000644000175100017510000000611115251354467017036 0ustar00runnerrunner # Release procedures Here we try to keep an up to date record of how Mopidy releases are made. This documentation serves both as a checklist, to reduce the project's dependency on key individuals, and as a stepping stone to more automation. ## Releasing extensions Extensions that are maintained in the Mopidy organization use a quite stream-lined release procedure. 1. Make sure that everything has been merged into the `main` branch on GitHub, and that all CI checks are green. 1. Perform any manual tests you feel are required. 1. Go to the GitHub repository's releases page, e.g. `https://github.com/mopidy/mopidy-foo/releases`. 1. In the "choose a tag" dropdown, create a new tag, e.g. `v0.1.0`. 1. Add a title, e.g. `v0.1.0`, and a description of the changes. 1. Decide if the release is a pre-release (alpha, beta, or release candidate) or should be marked as the latest release, and click "Publish release". 1. Once the release is created, the `release.yml` GitHub Actions workflow builds the package and uploads it to PyPI. This procedure has several benefits: - Everyone with commit access can make releases. - No one, except those with direct PyPI access, can make releases without also leaving the source code of what they released publicly available on GitHub, creating an audit log in case of any malicious actions. - The changelog can be amended post-release through the GitHub Releases UI. The primary drawback of this procedure is that there is no obvious way to maintain a changelog in-between releases. The preferred solution is to make releases often, so that writing up a changelog from the recent Git commits is done in a minute or two. ### How to setup this release workflow If you maintain a Mopidy extension, you're encouraged to adopt the same procedure. See the [extension project template](https://github.com/mopidy/mopidy-ext-template/) and specifically the generated project's README for further instructions. ## Releasing Mopidy itself Mopidy itself is a bit more complicated than extensions because the changelog is maintained in the Git repo. ### Preparations - Make sure that everything has been merged into the `main` branch on GitHub, and that all CI checks are green. - Make sure the changelog in the `docs/changelog/index.md` file includes all significant changes since the last release. Commit and push it. - Perform any manual tests you feel are required. ### Release 1. Select a version number in line with [our strategy](../about/versioning.md). 1. Update the release in `docs/changelog/index.md` with the right version number and release date. Commit and push it. 1. Release the new version exactly like you would do for an extension, with the addition of linking from the release to the changelog entry in the docs. ### Post-release - Make sure the new tag is built by [Read the Docs](https://app.readthedocs.org/projects/mopidy/), and that the ["stable" version](https://docs.mopidy.com/stable/) shows the newly released version. - Spread the word through an announcement post on the [Discourse forum](https://discourse.mopidy.com/). ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1789253942.6406589 mopidy-4.0.4/docs/about/sponsors.md0000644000175100017510000000056615251354467016763 0ustar00runnerrunner# Sponsors The Mopidy project would like to thank the following sponsors for supporting the project. ## Discourse [Discourse](https://www.discourse.org/) sponsors Mopidy with free hosting of our discussion forum at . ## Zulip [Zulip](https://zulip.com/) sponsors Mopidy with free hosting of our chat app at . ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1789253942.6406589 mopidy-4.0.4/docs/about/versioning.md0000644000175100017510000000272315251354467017255 0ustar00runnerrunner# Versioning Mopidy follows [Semantic Versioning](https://semver.org/). In summary this means that our version numbers have three parts, MAJOR.MINOR.PATCH, which change according to the following rules: - When we *make incompatible API changes*, we increase the MAJOR number. - When we *add features* in a backwards-compatible manner, we increase the MINOR number. - When we *fix bugs* in a backwards-compatible manner, we increase the PATCH number. The promise is that if you make a Mopidy extension for Mopidy 1.0, it should work unchanged with any Mopidy 1.x release, but probably not with 2.0. When a new major version is released, you must review the incompatible changes and update your extension accordingly. ## Release schedule We intend to have about one feature release every month in periods of active development. The features added is a mix of what we feel is most important/requested of the missing features, and features we develop just because we find them fun to make, even though they may be useful for very few users or for a limited use case. Bugfix releases will be released whenever we discover bugs that are too serious to wait for the next feature release. We will only release bugfix releases for the last feature release. E.g. when 1.2.0 is released, we will no longer provide bugfix releases for the 1.1.x series. In other words, there will be just a single supported release at any point in time. This is to not spread our limited resources too thin. ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1789253948.0593934 mopidy-4.0.4/docs/changelog/0000755000175100017510000000000015251354474015357 5ustar00runnerrunner././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1789253942.6406589 mopidy-4.0.4/docs/changelog/0.x.md0000644000175100017510000030502215251354467016312 0ustar00runnerrunner# Changelog 0.x series This is the changelog of Mopidy v0.1.0a0 through v0.19.5. For the latest releases, see changelog. ## v0.19.5 (2014-12-23) Today is Mopidy's five year anniversary. We're celebrating with a bugfix release and are looking forward to the next five years! - Config: Support UTF-8 in extension's default config. If an extension with non-ASCII characters in its default config was installed, and Mopidy didn't already have a config file, Mopidy would crashed when trying to create the initial config file based on the default config of all available extensions. ([#428](https://discourse.mopidy.com/t/428)) - Extensions: Fix crash when unpacking data from `pkg_resources.VersionConflict` created with a single argument. (#911) - Models: Hide empty collections from `repr()` representations. - Models: Field values are no longer stored on the model instance when the value matches the default value for the field. This makes two models equal when they have a field which in one case is implicitly set to the default value and in the other case explicitly set to the default value, but with otherwise equal fields. (#837) - Models: Changed the default value of `mopidy.models.Album.num_tracks`, `mopidy.models.Track.track_no`, and `mopidy.models.Track.last_modified` from `0` to `None`. - Core: When skipping to the next track in consume mode, remove the skipped track from the tracklist. This is consistent with the original MPD server's behavior. (#902) - Local: Fix scanning of modified files. (!904) - MPD: Re-enable browsing of empty directories. (!906) - MPD: Remove track comments from responses. They are not included by the original MPD server, and this works around #881. (!882) - HTTP: Errors while starting HTTP apps are logged instead of crashing the HTTP server. (#875) ## v0.19.4 (2014-09-01) Bug fix release. - Configuration: :option:`mopidy --config` now supports directories. - Logging: Fix that some loggers would be disabled if `logging/config_file` was set. (#740) - Quit process with exit code 1 when stopping because of a backend, frontend, or mixer initialization error. - Backend API: Update `mopidy.backend.LibraryProvider.browse` signature and docs to match how the core use the backend's browse method. (#833) - Local library API: Add `mopidy.local.Library.ROOT_DIRECTORY_URI` constant for use by implementors of `mopidy.local.Library.browse`. (#833) - HTTP frontend: Guard against double close of WebSocket, which causes an `AttributeError` on Tornado < 3.2. - MPD frontend: Make the `list` command return albums when sending 3 arguments. This was incorrectly returning artists after the MPD command changes in 0.19.0. (#817) - MPD frontend: Fix a race condition where two threads could try to free the same data simultaneously. (#781) ## v0.19.3 (2014-08-03) Bug fix release. - Audio: Fix negative track length for radio streams. (#662, !796) - Audio: Tell GStreamer to not pick Jack sink. (#604) - Zeroconf: Fix discovery by adding `.local` to the announced hostname. (!795) - Zeroconf: Fix intermittent DBus/Avahi exception. - Extensions: Fail early if trying to setup an extension which doesn't implement the `mopidy.ext.Extension.setup` method. (#813) ## v0.19.2 (2014-07-26) Bug fix release, directly from the Mopidy development sprint at EuroPython 2014 in Berlin. - Audio: Make `audio/mixer_volume` work on the software mixer again. This was broken with the mixer changes in 0.19.0. (#791) - HTTP frontend: When using Tornado 4.0, allow WebSocket requests from other hosts. (#788) - MPD frontend: Fix crash when MPD commands are called with the wrong number of arguments. This was broken with the MPD command changes in 0.19.0. (#789) ## v0.19.1 (2014-07-23) Bug fix release. - Dependencies: Mopidy now requires Tornado >= 2.3, instead of >= 3.1. This should make Mopidy continue to work on Debian/Raspbian stable, where Tornado 2.3 is the newest version available. - HTTP frontend: Add missing string interpolation placeholder. - Development: `mopidy --version` and `mopidy.core.Core.get_version` now returns the correct version when Mopidy is run from a Git repo other than Mopidy's own. (#706) ## v0.19.0 (2014-07-21) The focus of 0.19 have been on improving the MPD implementation, replacing GStreamer mixers with our own mixer API, and on making web clients installable with `pip`, like any other Mopidy extension. Since the release of 0.18, we've closed or merged 53 issues and pull requests through about 445 commits by 12 people, including five new guys. Thanks to everyone that has contributed! ### Dependencies - Mopidy now requires Tornado >= 3.1. - Mopidy no longer requires CherryPy or ws4py. Previously, these were optional dependencies required for the HTTP frontend to work. ### Backend API - *Breaking change:* Imports of the backend API from `mopidy.backends` no longer works. The new API introuced in v0.18 is now required. Most extensions already use the new API location. ### Commands - The `mopidy-convert-config` tool for migrating the `settings.py` configuration file used by Mopidy up until 0.14 to the new config file format has been removed after over a year of trusty service. If you still need to convert your old `settings.py` configuration file, do so using and older release, like Mopidy 0.18, or migrate the configuration to the new format by hand. ### Configuration - Add `optional=True` support to `mopidy.config.Boolean`. ### Logging - Fix proper decoding of exception messages that depends on the user's locale. - Colorize logs depending on log level. This can be turned off with the new `logging/color` configuration. (#772) ### Extension support - *Breaking change:* Removed the `mopidy.ext.Extension` methods that were deprecated in 0.18: `mopidy.ext.Extension.get_backend_classes`, `mopidy.ext.Extension.get_frontend_classes`, and `mopidy.ext.Extension.register_gstreamer_elements`. Use `mopidy.ext.Extension.setup` instead, as most extensions already do. ### Audio - *Breaking change:* Removed support for GStreamer mixers. GStreamer 1.x does not support volume control, so we changed to use software mixing by default in v0.17.0. Now, we're removing support for all other GStreamer mixers and are reintroducing mixers as something extensions can provide independently of GStreamer. (#665, !760) - *Breaking change:* Changed the `audio/mixer` config value to refer to Mopidy mixer extensions instead of GStreamer mixers. The default value, `software`, still has the same behavior. All other values will either no longer work or will at the very least require you to install an additional extension. - Changed the `audio/mixer_volume` config value behavior from affecting GStreamer mixers to affecting Mopidy mixer extensions instead. The end result should be the same without any changes to this config value. - Deprecated the `audio/mixer_track` config value. This config value is no longer in use. Mixer extensions that need additional configuration handle this themselves. - Use proxy-config when streaming media from the Internet. (Partly fixing #390) - Fix proper decoding of exception messages that depends on the user's locale. - Fix recognition of ASX and XSPF playlists with tags in all caps or with carriage return line endings. (#687) - Support simpler ASX playlist variant with `` elements without children. - Added `target_state` attribute to the audio layer's `mopidy.audio.AudioListener.state_changed` event. Currently, it is `None` except when we're paused because of buffering. Then the new field exposes our target state after buffering has completed. ### Mixers - Added new `mopidy.mixer.Mixer` API which can be implemented by extensions. - Created a bundled extension, ext-softwaremixer, for controlling volume in software in GStreamer's pipeline. This is Mopidy's default mixer. To use this mixer, set the `audio/mixer` config value to `software`. - Created an external extension, [Mopidy-ALSAMixer](https://github.com/mopidy/mopidy-alsamixer/), for controlling volume with hardware through ALSA. To use this mixer, install the extension, and set the `audio/mixer` config value to `alsamixer`. ### HTTP frontend - CherryPy and ws4py have been replaced with Tornado. This will hopefully reduce CPU usage on OS X (#445) and improve error handling in corner cases, like when returning from suspend (#718). - Added support for packaging web clients as Mopidy extensions and installing them using pip. See the http-server-api for details. (#440) - Added web page at `/mopidy/` which lists all web clients installed as Mopidy extensions. (#440) - Added support for extending the HTTP frontend with additional server side functionality. See http-server-api for details. - Exposed the core API using HTTP POST requests with JSON-RPC payloads at `/mopidy/rpc`. This is the same JSON-RPC interface as is exposed over the WebSocket at `/mopidy/ws`, so you can run any core API command. The HTTP POST interfaces does not give you access to events from Mopidy, like the WebSocket does. The WebSocket interface is still recommended for web clients. The HTTP POST interface may be easier to use for simpler programs, that just needs to query the currently playing track or similar. See http-post-api for details. - If Zeroconf is enabled, we now announce the `_mopidy-http._tcp` service in addition to `_http._tcp`. This is to make it easier to automatically find Mopidy's HTTP server among other Zeroconf-published HTTP servers on the local network. ### Mopidy.js client library This version has been released to npm as Mopidy.js v0.4.0. - Update Mopidy.js to use when.js 3. If you maintain a Mopidy client, you should review the [differences between when.js 2 and 3](https://github.com/cujojs/when/blob/master/docs/api.md#upgrading-to-30-from-2x) and the [when.js debugging guide](https://github.com/cujojs/when/blob/master/docs/api.md#debugging-promises). - All of Mopidy.js' promise rejection values are now of the Error type. This ensures that all JavaScript VMs will show a useful stack trace if a rejected promise's value is used to throw an exception. To allow catch clauses to handle different errors differently, server side errors are of the type `Mopidy.ServerError`, and connection related errors are of the type `Mopidy.ConnectionError`. - Add support for method calls with by-name arguments. The old calling convention, `by-position-only`, is still the default, but this will change in the future. A warning is logged to the console if you don't explicitly select a calling convention. See the mopidy-js docs for details. ### MPD frontend - Proper command tokenization for MPD requests. This replaces the old regex based system with an MPD protocol specific tokenizer responsible for breaking requests into pieces before the handlers have at them. (#591 and #592) - Updated command handler system. As part of the tokenizer cleanup we've updated how commands are registered and making it simpler to create new handlers. - Simplified a bunch of handlers. All the "browse" type commands now use a common browse helper under the hood for less repetition. Likewise the query handling of "search" commands has been somewhat simplified. - Adds placeholders for missing MPD commands, preparing the way for bumping the protocol version once they have been added. - Respond to all pending requests before closing connection. (!722) - Stop incorrectly catching `LookupError` in command handling. (#741) - Browse support for playlists and albums has been added. (!749, #754) - The `lsinfo` command now returns browse results before local playlists. This is helpful as not all clients sort the returned items. (!755) - Browse now supports different entries with identical names. (!762) - Search terms that are empty or consists of only whitespace are no longer included in the search query sent to backends. (!758) ### Local backend - The JSON local library backend now logs a friendly message telling you about `mopidy local scan` if you don't have a local library cache. (#711) - The `local scan` command now use multiple threads to walk the file system and check files' modification time. This speeds up scanning, escpecially when scanning remote file systems over e.g. NFS. - the `local scan` command now creates necessary folders if they don't already exist. Previously, this was only done by the Mopidy server, so doing a `local scan` before running the server the first time resulted in a crash. (#703) - Fix proper decoding of exception messages that depends on the user's locale. ### Stream backend - Add config value `stream/metadata_blacklist` to blacklist certain URIs we should not open to read metadata from before they are opened for playback. This is typically needed for services that invalidate URIs after a single use. (#660) ## v0.18.3 (2014-02-16) Bug fix release. - Fix documentation build. ## v0.18.2 (2014-02-16) Bug fix release. - We now log warnings for wrongly configured extensions, and clearly label them in `mopidy config`, but does no longer stop Mopidy from starting because of misconfigured extensions. (#682) - Fix a crash in the server side WebSocket handler caused by connection problems with clients. (#428, #571) - Fix the `time_position` field of the `track_playback_ended` event, which has been always 0 since v0.18.0. This made scrobbles by Mopidy-Scrobbler not be persisted by Last.fm, because Mopidy reported that you listened to 0 seconds of each track. (#674) - Fix the log setup so that it is possible to increase the amount of logging from a specific logger using the `loglevels` config section. (#684) - Serialization of `mopidy.models.Playlist` models with the `last_modified` field set to a `datetime.datetime` instance did not work. The type of `mopidy.models.Playlist.last_modified` has been redefined from a `datetime.datetime` instance to the number of milliseconds since Unix epoch as an integer. This makes serialization of the time stamp simpler. - Minor refactor of the MPD server context so that Mopidy's MPD protocol implementation can easier be reused. (#646) - Network and signal handling has been updated to play nice on Windows systems. ## v0.18.1 (2014-01-23) Bug fix release. - Disable extension instead of crashing if a dependency has the wrong version. (#657) - Make logging work to both console, debug log file, and any custom logging setup from `logging/config_file` at the same time. (#661) ## v0.18.0 (2014-01-19) The focus of 0.18 have been on two fronts: the local library and browsing. First, the local library's old tag cache file used for storing the track metadata scanned from your music collection has been replaced with a far simpler implementation using JSON as the storage format. At the same time, the local library have been made replaceable by extensions, so you can now create extensions that use your favorite database to store the metadata. Second, we've finally implemented the long awaited "file system" browsing feature that you know from MPD. It is supported by both the MPD frontend and the local and Spotify backends. It is also used by the new Mopidy-Dirble extension to provide you with a directory of Internet radio stations from all over the world. Since the release of 0.17, we've closed or merged 49 issues and pull requests through about 285 commits by 11 people, including six new guys. Thanks to everyone that has contributed! ### Core API - Add `mopidy.core.Core.version` for HTTP clients to manage compatibility between API versions. (#597) - Add `mopidy.models.Ref` class for use as a lightweight reference to other model types, containing just an URI, a name, and an object type. It is barely used for now, but its use will be extended over time. - Add `mopidy.core.LibraryController.browse` method for browsing a virtual file system of tracks. Backends can implement support for this by implementing `mopidy.backend.LibraryProvider.browse`. - Events emitted on play/stop, pause/resume, next/previous and on end of track has been cleaned up to work consistently. See the message of :commit:`1d108752f6` for the full details. (#629) ### Backend API - Move the backend API classes from `mopidy.backends.base` to `mopidy.backend` and remove the `Base` prefix from the class names: - From `mopidy.backends.base.Backend` to `mopidy.backend.Backend` - From `mopidy.backends.base.BaseLibraryProvider` to `mopidy.backend.LibraryProvider` - From `mopidy.backends.base.BasePlaybackProvider` to `mopidy.backend.PlaybackProvider` - From `mopidy.backends.base.BasePlaylistsProvider` to `mopidy.backend.PlaylistsProvider` - From `mopidy.backends.listener.BackendListener` to `mopidy.backend.BackendListener` Imports from the old locations still works, but are deprecated. - Add `mopidy.backend.LibraryProvider.browse`, which can be implemented by backends that wants to expose directories of tracks in Mopidy's virtual file system. ### Frontend API - The dummy backend used for testing many frontends have moved from `mopidy.backends.dummy` to `mopidy.backend.dummy`. (!984) ### Commands - Reduce amount of logging from dependencies when using :option:`mopidy -v`. (#593) - Add support for additional logging verbosity levels with `mopidy -vv` and `mopidy -vvv` which increases the amount of logging from dependencies. (#593) ### Configuration - The default for the :option:`mopidy --config` option has been updated to include `$XDG_CONFIG_DIRS` in addition to `$XDG_CONFIG_DIR`. (#431) - Added support for deprecating config values in order to allow for graceful removal of the no longer used config value `local/tag_cache_file`. ### Extension support - Switched to using a registry model for classes provided by extension. This allows extensions to be extended by other extensions, as needed by for example pluggable libraries for the local backend. See `mopidy.ext.Registry` for details. (#601) - Added the new method `mopidy.ext.Extension.setup`. This method replaces the now deprecated `mopidy.ext.Extension.get_backend_classes`, `mopidy.ext.Extension.get_frontend_classes`, and `mopidy.ext.Extension.register_gstreamer_elements`. ### Audio - Added `audio/mixer_volume` to set the initial volume of mixers. This is especially useful for setting the software mixer volume to something else than the default 100%. (#633) ### Local backend /// note After upgrading to Mopidy 0.18 you must run `mopidy local scan` to reindex your local music collection. This is due to the change of storage format. /// - Added support for browsing local directories in Mopidy's virtual file system. - Finished the work on creating pluggable libraries. Users can now reconfigure Mopidy to use alternate library providers of their choosing for local files. (issue #44, partially resolves #397, and causes a temporary regression of #527.) - Switched default local library provider from a "tag cache" file that closely resembled the one used by the original MPD server to a compressed JSON file. This greatly simplifies our library code and reuses our existing model serialization code, as used by the HTTP API and web clients. - Removed our outdated and bug-ridden "tag cache" local library implementation. - Added the config value `local/library` to select which library to use. It defaults to `json`, which is the only local library bundled with Mopidy. - Added the config value `local/data_dir` to have a common config for where to store local library data. This is intended to avoid every single local library provider having to have it's own config value for this. - Added the config value `local/scan_flush_threshold` to control how often to tell local libraries to store changes when scanning local music. ### Streaming backend - Add live lookup of URI metadata. (#540) - Add support for extended M3U playlist, meaning that basic track metadata stored in playlists will be used by Mopidy. ### HTTP frontend - Upgrade Mopidy.js dependencies and add support for using Mopidy.js with Browserify. This version has been released to npm as Mopidy.js v0.2.0. (#609) ### MPD frontend - Make the `lsinfo`, `listall`, and `listallinfo` commands support browsing of Mopidy's virtual file system. (#145) - Empty commands now return a `ACK [5@0] {} No command given` error instead of `OK`. This is consistent with the original MPD server implementation. ### Internal changes - Events from the audio actor, backends, and core actor are now emitted asynchronously through the GObject event loop. This should resolve the issue that has blocked the merge of the EOT-vs-EOS fix for a long time. ## v0.17.0 (2013-11-23) The focus of 0.17 has been on introducing subcommands to the `mopidy` command, making it possible for extensions to add subcommands of their own, and to improve the default config file when starting Mopidy the first time. In addition, we've grown support for Zeroconf publishing of the MPD and HTTP servers, and gotten a much faster scanner. The scanner now also scans some additional tags like composers and performers. Since the release of 0.16, we've closed or merged 22 issues and pull requests through about 200 commits by five people, including one new contributor. ### Commands - Switched to subcommands for the `mopidy` command , this implies the following changes: (#437) | Old command | New command | | -------------------- | ----------------- | | `mopidy --show-deps` | `mopidy deps` | | `mopidy --show-config` | `mopidy config` | | `mopidy-scan` | `mopidy local scan` | - Added hooks for extensions to create their own custom subcommands and converted `mopidy-scan` as a first user of the new API. (#436) ### Configuration - When `mopidy` is started for the first time we create an empty :file:`{$XDG_CONFIG_DIR}/mopidy/mopidy.conf` file. We now populate this file with the default config for all installed extensions so it'll be easier to set up Mopidy without looking through all the documentation for relevant config values. (#467) ### Core API - The `mopidy.models.Track` model has grown fields for `composers`, `performers`, `genre`, and `comment`. - The search field `track` has been renamed to `track_name` to avoid confusion with `track_no`. (#535) - The signature of the tracklist's `mopidy.core.TracklistController.filter` and `mopidy.core.TracklistController.remove` methods have changed. Previously, they expected e.g. `tracklist.filter(tlid=17)`. Now, the value must always be a list, e.g. `tracklist.filter(tlid=[17])`. This change allows you to get or remove multiple tracks with a single call, e.g. `tracklist.remove(tlid=[1, 2, 7])`. This is especially useful for web clients, as requests can be batched. This also brings the interface closer to the library's `mopidy.core.LibraryController.find_exact` and `mopidy.core.LibraryController.search` methods. ### Audio - Change default volume mixer from `autoaudiomixer` to `software`. GStreamer 1.x does not support volume control, so we're changing to use software mixing by default, as that may be the only thing we'll support in the future when we upgrade to GStreamer 1.x. ### Local backend - Library scanning has been switched back from GStreamer's discoverer to our custom implementation due to various issues with GStreamer 0.10's built in scanner. This also fixes the scanner slowdown. (#565) - When scanning, we no longer default the album artist to be the same as the track artist. Album artist is now only populated if the scanned file got an explicit album artist set. - The scanner will now extract multiple artists from files with multiple artist tags. - The scanner will now extract composers and performers, as well as genre, bitrate, and comments. (#577) - Fix scanner so that time of last modification is respected when deciding which files can be skipped when scanning the music collection for changes. - The scanner now ignores the capitalization of file extensions in `local/excluded_file_extensions`, so you no longer need to list both `.jpg` and `.JPG` to ignore JPEG files when scanning. (#525) - The scanner now by default ignores `*.nfo` and `*.html` files too. ### MPD frontend - The MPD service is now published as a Zeroconf service if avahi-daemon is running on the system. Some MPD clients will use this to present Mopidy as an available server on the local network without needing any configuration. See the `mpd/zeroconf` config value to change the service name or disable the service. (#39) - Add support for `composer`, `performer`, `comment`, `genre`, and `performer`. These tags can be used with `list ...`, `search ...`, and `find ...` and their variants, and are supported in the `any` tag also - The `bitrate` field in the `status` response is now always an integer. This follows the behavior of the original MPD server. (#577) ### HTTP frontend - The HTTP service is now published as a Zeroconf service if avahi-daemon is running on the system. Some browsers will present HTTP Zeroconf services on the local network as "local sites" bookmarks. See the `http/zeroconf` config value to change the service name or disable the service. (#39) ### DBUS/MPRIS - The `mopidy` process now registers it's GObject event loop as the default eventloop for dbus-python. (mopidy-mpris#2) ## v0.16.1 (2013-11-02) This is very small release to get Mopidy's Debian package ready for inclusion in Debian. ### Commands - Fix removal of last dir level in paths to dependencies in `mopidy --show-deps` output. - Add manpages for all commands. ### Local backend - Fix search filtering by track number that was added in 0.16.0. ### MPD frontend - Add support for `list "albumartist" ...` which was missed when `find` and `search` learned to handle `albumartist` in 0.16.0. (#553) ## v0.16.0 (2013-10-27) The goals for 0.16 were to add support for queuing playlists of e.g. radio streams directly to Mopidy, without manually extracting the stream URLs from the playlist first, and to move the Spotify, Last.fm, and MPRIS support out to independent Mopidy extensions, living outside the main Mopidy repo. In addition, we've seen some cleanup to the playback vs tracklist part of the core API, which will require some changes for users of the HTTP/JavaScript APIs, as well as the addition of audio muting to the core API. To speed up the development of new extensions, we've added a cookiecutter project to get the skeleton of a Mopidy extension up and running in a matter of minutes. Read below for all the details and for links to issues with even more details. Since the release of 0.15, we've closed or merged 31 issues and pull requests through about 200 commits by five people, including three new contributors. ### Dependencies Parts of Mopidy have been moved to their own external extensions. If you want Mopidy to continue to work like it used to, you may have to install one or more of the following extensions as well: - The Spotify backend has been moved to [Mopidy-Spotify](https://github.com/mopidy/mopidy-spotify). - The Last.fm scrobbler has been moved to [Mopidy-Scrobbler](https://github.com/mopidy/mopidy-scrobbler). - The MPRIS frontend has been moved to [Mopidy-MPRIS](https://github.com/mopidy/mopidy-mpris). ### Core - Parts of the functionality in `mopidy.core.PlaybackController` have been moved to `mopidy.core.TracklistController`: | Old location | New location | | ------------------------------------- | ------------------------------------ | | `playback.get_consume()` | `tracklist.get_consume()` | | `playback.set_consume(v)` | `tracklist.set_consume(v)` | | `playback.consume` | `tracklist.consume` | | `playback.get_random()` | `tracklist.get_random()` | | `playback.set_random(v)` | `tracklist.set_random(v)` | | `playback.random` | `tracklist.random` | | `playback.get_repeat()` | `tracklist.get_repeat()` | | `playback.set_repeat(v)` | `tracklist.set_repeat(v)` | | `playback.repeat` | `tracklist.repeat` | | `playback.get_single()` | `tracklist.get_single()` | | `playback.set_single(v)` | `tracklist.set_single(v)` | | `playback.single` | `tracklist.single` | | `playback.get_tracklist_position()` | `tracklist.index(tl_track)` | | `playback.tracklist_position` | `tracklist.index(tl_track)` | | `playback.get_tl_track_at_eot()` | `tracklist.eot_track(tl_track)` | | `playback.tl_track_at_eot` | `tracklist.eot_track(tl_track)` | | `playback.get_tl_track_at_next()` | `tracklist.next_track(tl_track)` | | `playback.tl_track_at_next` | `tracklist.next_track(tl_track)` | | `playback.get_tl_track_at_previous()` | `tracklist.previous_track(tl_track)` | | `playback.tl_track_at_previous` | `tracklist.previous_track(tl_track)` | The `tl_track` argument to the last four new functions are used as the reference `tl_track` in the tracklist to find e.g. the next track. Usually, this will be `mopidy.core.PlaybackController.current_tl_track`. - Added `mopidy.core.PlaybackController.mute` for muting and unmuting audio. (#186) - Added `mopidy.core.CoreListener.mute_changed` event that is triggered when the mute state changes. - In "random" mode, after a full playthrough of the tracklist, playback continued from the last track played to the end of the playlist in non-random order. It now stops when all tracks have been played once, unless "repeat" mode is enabled. (#453) - In "single" mode, after a track ended, playback continued with the next track in the tracklist. It now stops after playing a single track, unless "repeat" mode is enabled. (#496) ### Audio - Added support for parsing and playback of playlists in GStreamer. For end users this basically means that you can now add a radio playlist to Mopidy and we will automatically download it and play the stream inside it. Currently we support M3U, PLS, XSPF and ASX files. Also note that we can currently only play the first stream in the playlist. - We now handle the rare case where an audio track has max volume equal to min. This was causing divide by zero errors when scaling volumes to a zero to hundred scale. (#525) - Added support for muting audio without setting the volume to 0. This works both for the software and hardware mixers. (#186) ### Local backend - Replaced our custom media library scanner with GStreamer's builtin scanner. This should make scanning less error prone and faster as timeouts should be infrequent. (#198) - Media files with less than 100ms duration are now excluded from the library. - Media files with the file extensions `.jpeg`, `.jpg`, `.png`, `.txt`, and `.log` are now skipped by the media library scanner. You can change the list of excluded file extensions by setting the `local/excluded_file_extensions` config value. (#516) - Unknown URIs found in playlists are now made into track objects with the URI set instead of being ignored. This makes it possible to have playlists with e.g. HTTP radio streams and not just `local:track:...` URIs. This used to work, but was broken in Mopidy 0.15.0. (#527) - Fixed crash when playing `local:track:...` URIs which contained non-ASCII chars after uridecode. - Removed media files are now also removed from the in-memory media library when the media library is reloaded from disk. (#500) ### MPD frontend - Made the formerly unused commands `outputs`, `enableoutput`, and `disableoutput` mute/unmute audio. (#186) - The MPD command `list` now works with `"albumartist"` as its second argument, e.g. `list "album" "albumartist" "anartist"`. (#468) - The MPD commands `find` and `search` now accepts `albumartist` and `track` (this is the track number, not the track name) as field types to limit the search result with. - The MPD command `count` is now implemented. It accepts the same type of arguments as `find` and `search`, but returns the number of tracks and their total playtime instead. ### Extension support - A cookiecutter project for quickly creating new Mopidy extensions have been created. You can find it at [cookiecutter-mopidy-ext](https://github.com/mopidy/cookiecutter-mopidy-ext). (#522) ## v0.15.0 (2013-09-19) A release with a number of small and medium fixes, with no specific focus. ### Dependencies - Mopidy no longer supports Python 2.6. Currently, the only Python version supported by Mopidy is Python 2.7. We're continuously working towards running Mopidy on Python 3. (#344) ### Command line options - Converted from the optparse to the argparse library for handling command line options. - `mopidy --show-config` will now take into consideration any :option:`mopidy --option` arguments appearing later on the command line. This helps you see the effective configuration for runs with the same `mopidy --options` arguments. ### Audio - Added support for audio visualization. `audio/visualizer` can now be set to GStreamer visualizers. - Properly encode localized mixer names before logging. ### Local backend - An album's number of discs and a track's disc number are now extracted when scanning your music collection. - The scanner now gives up scanning a file after a second, and continues with the next file. This fixes some hangs on non-media files, like logs. (#476, #483) - Added support for pluggable library updaters. This allows extension writers to start providing their own custom libraries instead of being stuck with just our tag cache as the only option. - Converted local backend to use new `local:playlist:path` and `local:track:path` URI scheme. Also moves support of `file://` to streaming backend. ### Spotify backend - Prepend playlist folder names to the playlist name, so that the playlist hierarchy from your Spotify account is available in Mopidy. (#62) - Fix proxy config values that was broken with the config system change in 0.14. (#472) ### MPD frontend - Replace newline, carriage return and forward slash in playlist names. (#474, #480) - Accept `listall` and `listallinfo` commands without the URI parameter. The methods are still not implemented, but now the commands are accepted as valid. ### HTTP frontend - Fix too broad truth test that caused `mopidy.models.TlTrack` objects with `tlid` set to `0` to be sent to the HTTP client without the `tlid` field. (#501) - Upgrade Mopidy.js dependencies. This version has been released to npm as Mopidy.js v0.1.1. ### Extension support - `mopidy.config.Secret` is now deserialized to unicode instead of bytes. This may require modifications to extensions. ## v0.14.2 (2013-07-01) This is a maintenance release to make Mopidy 0.14 work with pyspotify 1.11. ### Dependencies - pyspotify >= 1.9, < 2 is now required for Spotify support. In other words, you're free to upgrade to pyspotify 1.11, but it isn't a requirement. ## v0.14.1 (2013-04-28) This release addresses an issue in v0.14.0 where the new `mopidy-convert-config` tool and the new :option:`mopidy --option` command line option was broken because some string operations inadvertently converted some byte strings to unicode. ## v0.14.0 (2013-04-28) The 0.14 release has a clear focus on two things: the new configuration system and extension support. Mopidy's documentation has also been greatly extended and improved. Since the last release a month ago, we've closed or merged 53 issues and pull requests. A total of seven authors have contributed, including one new. ### Dependencies - setuptools or distribute is now required. We've introduced this dependency to use setuptools' entry points functionality to find installed Mopidy extensions. ### New configuration system - Mopidy has a new configuration system based on ini-style files instead of a Python file. This makes configuration easier for users, and also makes it possible for Mopidy extensions to have their own config sections. As part of this change we have cleaned up the naming of our config values. To ease migration we've made a tool named `mopidy-convert-config` for automatically converting the old `settings.py` to a new `mopidy.conf` file. This tool takes care of all the renamed config values as well. See `mopidy-convert-config` for details on how to use it. - A long wanted feature: You can now enable or disable specific frontends or backends without having to redefine `mopidy.settings.FRONTENDS` or `mopidy.settings.BACKENDS` in your config. Those config values are gone completely. ### Extension support - Mopidy now supports extensions. This means that any developer now easily can create a Mopidy extension to add new control interfaces or music backends. This helps spread the maintenance burden across more developers, and also makes it possible to extend Mopidy with new backends the core developers are unable to create and/or maintain because of geo restrictions, etc. If you're interested in creating an extension for Mopidy, read up on extensiondev. - All of Mopidy's existing frontends and backends are now plugged into Mopidy as extensions, but they are still distributed together with Mopidy and are enabled by default. - The NAD mixer have been moved out of Mopidy core to its own project, Mopidy-NAD. - Janez Troha has made the first two external extensions for Mopidy: a backend for playing music from Soundcloud, and a backend for playing music from a Beets music library. ### Command line options - The command option `mopidy --list-settings` is now named `mopidy --show-config`. - The command option `mopidy --list-deps` is now named `mopidy --show-deps`. - What configuration files to use can now be specified through the command option :option:`mopidy --config`, multiple files can be specified using colon as a separator. - Configuration values can now be overridden through the command option `mopidy --option`. For example: `mopidy --option spotify/enabled=false`. - The GStreamer command line options, `mopidy --gst-*` and `mopidy --help-gst` are no longer supported. To set GStreamer debug flags, you can use environment variables such as `GST_DEBUG`. Refer to GStreamer's documentation for details. ### Spotify backend - Add support for starred playlists, both your own and those owned by other users. (#326) - Fix crash when a new playlist is added by another Spotify client. (#387, #425) ### MPD frontend - Playlists with identical names are now handled properly by the MPD frontend by suffixing the duplicate names with e.g. `[2]`. This is needed because MPD identify playlists by name only, while Mopidy and Spotify supports multiple playlists with the same name, and identify them using an URI. (#114) ### MPRIS frontend - The frontend is now disabled if the `DISPLAY` environment variable is unset. This avoids some harmless error messages, that have been known to confuse new users debugging other problems. ### Development - Developers running Mopidy from a Git clone now need to run `python setup.py develop` to register the bundled extensions. If you don't do this, Mopidy will not find any frontends or backends. Note that we highly recommend you do this in a virtualenv, not system wide. As a bonus, the command also gives you a `mopidy` executable in your search path. ## v0.13.0 (2013-03-31) The 0.13 release brings small improvements and bugfixes throughout Mopidy. There are no major new features, just incremental improvement of what we already have. ### Dependencies - Pykka >= 1.1 is now required. ### Core - Removed the `mopidy.settings.DEBUG_THREAD` setting and the `mopidy --debug-thread` command line option. Sending SIGUSR1 to the Mopidy process will now always make it log tracebacks for all alive threads. - Log a warning if a track isn't playable to make it more obvious that backend X needs backend Y to be present for playback to work. - `mopidy.core.TracklistController.add` now accepts an `uri` which it will lookup in the library and then add to the tracklist. This is helpful for e.g. web clients that doesn't want to transfer all track meta data back to the server just to add it to the tracklist when the server already got all the needed information easily available. (#325) - Change the following methods to accept an `uris` keyword argument: - `mopidy.core.LibraryController.find_exact` - `mopidy.core.LibraryController.search` Search queries will only be forwarded to backends handling the given URI roots, and the backends may use the URI roots to further limit what results are returned. For example, a search with `uris=['file:']` will only be processed by the local backend. A search with `uris=['file:///media/music']` will only be processed by the local backend, and, if such filtering is supported by the backend, will only return results with URIs within the given URI root. ### Audio sub-system - Make audio error logging handle log messages with non-ASCII chars. (#347) ### Local backend - Make `mopidy-scan` work with Ogg Vorbis files. (#275) - Fix playback of files with non-ASCII chars in their file path. (#353) ### Spotify backend - Let GStreamer handle time position tracking and seeks. (#191) - For all playlists owned by other Spotify users, we now append the owner's username to the playlist name. (Partial: #114) ### HTTP frontend - Mopidy.js now works both from browsers and from Node.js environments. This means that you now can make Mopidy clients in Node.js. Mopidy.js has been published to the [npm registry](https://www.npmjs.com/package/mopidy) for easy installation in Node.js projects. - Upgrade Mopidy.js' build system Grunt from 0.3 to 0.4. - Upgrade Mopidy.js' dependencies when.js from 1.6.1 to 2.0.0. - Expose `mopidy.core.Core.get_uri_schemes` to HTTP clients. It is available through Mopidy.js as `mopidy.getUriSchemes()`. ### MPRIS frontend - Publish album art URIs if available. - Publish disc number of track if available. ## v0.12.0 (2013-03-12) The 0.12 release has been delayed for a while because of some issues related some ongoing GStreamer cleanup we didn't invest enough time to finish. Finally, we've come to our senses and have now cherry-picked the good parts to bring you a new release, while postponing the GStreamer changes to 0.13. The release adds a new backend for playing audio streams, as well as various minor improvements throughout Mopidy. - Make Mopidy work on early Python 2.6 versions. (#302) - `optparse` fails if the first argument to `add_option` is a unicode string on Python < 2.6.2rc1. - `foo(**data)` fails if the keys in `data` is unicode strings on Python < 2.6.5rc1. ### Audio sub-system - Improve selection of mixer tracks for volume control. (#307) ### Local backend - Make `mopidy-scan` support symlinks. ### Stream backend We've added a new backend for playing audio streams, the :mod:`stream backend `. It is activated by default. The stream backend supports the intersection of what your GStreamer installation supports and what protocols are included in the `mopidy.settings.STREAM_PROTOCOLS` setting. Current limitations: - No metadata about the current track in the stream is available. - Playlists are not parsed, so you can't play e.g. a M3U or PLS file which contains stream URIs. You need to extract the stream URL from the playlist yourself. See #303 for progress on this. ### Core API - `mopidy.core.PlaylistsController.get_playlists` now accepts an argument `include_tracks`. This defaults to `True`, which has the same old behavior. If set to `False`, the tracks are stripped from the playlists before they are returned. This can be used to limit the amount of data returned if the response is to be passed out of the application, e.g. to a web client. (#297) ### Models - Add `mopidy.models.Album.images` field for including album art URIs. (Partial: #263) - Add `mopidy.models.Track.disc_no` field. (Partial: #286) - Add `mopidy.models.Album.num_discs` field. (Partial: #286) ## v0.11.1 (2012-12-24) Spotify search was broken in 0.11.0 for users of Python 2.6. This release fixes it. If you're using Python 2.7, v0.11.0 and v0.11.1 should be equivalent. ## v0.11.0 (2012-12-24) In celebration of Mopidy's three year anniversary December 23, we're releasing Mopidy 0.11. This release brings several improvements, most notably better search which now includes matching artists and albums from Spotify in the search results. ### Settings - The settings validator now complains if a setting which expects a tuple of values (e.g. `mopidy.settings.BACKENDS`, `mopidy.settings.FRONTENDS`) has a non-iterable value. This typically happens because the setting value contains a single value and one has forgotten to add a comma after the string, making the value a tuple. (#278) ### Spotify backend - Add `mopidy.settings.SPOTIFY_TIMEOUT` setting which allows you to control how long we should wait before giving up on Spotify searches, etc. - Add support for looking up albums, artists, and playlists by URI in addition to tracks. (#67) As an example of how this can be used, you can try the the following MPD commands which now all adds one or more tracks to your tracklist: ```text add "spotify:track:1mwt9hzaH7idmC5UCoOUkz" add "spotify:album:3gpHG5MGwnipnap32lFYvI" add "spotify:artist:5TgQ66WuWkoQ2xYxaSTnVP" add "spotify:user:p3.no:playlist:0XX6tamRiqEgh3t6FPFEkw" ``` - Increase max number of tracks returned by searches from 100 to 200, which seems to be Spotify's current max limit. ### Local backend - Load track dates from tag cache. - Add support for searching by track date. ### MPD frontend - Add `mopidy.settings.MPD_SERVER_CONNECTION_TIMEOUT` setting which controls how long an MPD client can stay inactive before the connection is closed by the server. - Add support for the `findadd` command. - Updated to match the MPD 0.17 protocol (#228): - Add support for `seekcur` command. - Add support for `config` command. - Add support for loading a range of tracks from a playlist to the `load` command. - Add support for `searchadd` command. - Add support for `searchaddpl` command. - Add empty stubs for channel commands for client to client communication. - Add support for search by date. - Make `seek` and `seekid` not restart the current track before seeking in it. - Include fake tracks representing albums and artists in the search results. When these are added to the tracklist, they expand to either all tracks in the album or all tracks by the artist. This makes it easy to play full albums in proper order, which is a feature that have been frequently requested. (#67, #148) ### Internal changes *Models:* - Specified that `mopidy.models.Playlist.last_modified` should be in UTC. - Added `mopidy.models.SearchResult` model to encapsulate search results consisting of more than just tracks. *Core API:* - Change the following methods to return `mopidy.models.SearchResult` objects which can include both track results and other results: - `mopidy.core.LibraryController.find_exact` - `mopidy.core.LibraryController.search` - Change the following methods to accept either a dict with filters or kwargs. Previously they only accepted kwargs, which made them impossible to use from the Mopidy.js through JSON-RPC, which doesn't support kwargs. - `mopidy.core.LibraryController.find_exact` - `mopidy.core.LibraryController.search` - `mopidy.core.PlaylistsController.filter` - `mopidy.core.TracklistController.filter` - `mopidy.core.TracklistController.remove` - Actually trigger the `mopidy.core.CoreListener.volume_changed` event. - Include the new volume level in the `mopidy.core.CoreListener.volume_changed` event. - The `track_playback_{paused,resumed,started,ended}` events now include a `mopidy.models.TlTrack` instead of a `mopidy.models.Track`. *Audio:* - Mixers with fewer than 100 volume levels could report another volume level than what you just set due to the conversion between Mopidy's 0-100 range and the mixer's range. Now Mopidy returns the recently set volume if the mixer reports a volume level that matches the recently set volume, otherwise the mixer's volume level is rescaled to the 1-100 range and returned. ## v0.10.0 (2012-12-12) We've added an HTTP frontend for those wanting to build web clients for Mopidy! ### Dependencies - pyspotify >= 1.9, < 1.11 is now required for Spotify support. In other words, you're free to upgrade to pyspotify 1.10, but it isn't a requirement. ### Documentation - Added installation instructions for Fedora. ### Spotify backend - Save a lot of memory by reusing artist, album, and track models. - Make sure the playlist loading hack only runs once. ### Local backend - Change log level from error to warning on messages emitted when the tag cache isn't found and a couple of similar cases. - Make `mopidy-scan` ignore invalid dates, e.g. dates in years outside the range 1-9999. - Make `mopidy-scan` accept `-q`/`--quiet` and `-v`/`--verbose` options to control the amount of logging output when scanning. - The scanner can now handle files with other encodings than UTF-8. Rebuild your tag cache with `mopidy-scan` to include tracks that may have been ignored previously. ### HTTP frontend - Added new optional HTTP frontend which exposes Mopidy's core API through JSON-RPC 2.0 messages over a WebSocket. See http-api for further details. - Added a JavaScript library, Mopidy.js, to make it easier to develop web based Mopidy clients using the new HTTP frontend. ### Bug fixes - Issue #256: Fix crash caused by non-ASCII characters in paths returned from `glib`. The bug can be worked around by overriding the settings that includes offending `$XDG_` variables. ## v0.9.0 (2012-11-21) Support for using the local and Spotify backends simultaneously have for a very long time been our most requested feature. Finally, it's here! ### Dependencies - pyspotify >= 1.9, < 1.10 is now required for Spotify support. ### Documentation - New installation guides, organized by OS and distribution so that you can follow one concise list of instructions instead of jumping around the docs to look for instructions for each dependency. - Moved raspberrypi-installation howto from the wiki to the docs. - Updated MPD clients overview. - Added mpris-clients and UPnP clients overview. ### Multiple backends support - Both the local backend and the Spotify backend are now turned on by default. The local backend is listed first in the `mopidy.settings.BACKENDS` setting, and are thus given the highest priority in e.g. search results, meaning that we're listing search hits from the local backend first. If you want to prioritize the backends in another way, simply set `BACKENDS` in your own settings file and reorder the backends. There are no other setting changes related to the local and Spotify backends. As always, see `mopidy.settings` for the full list of available settings. ### Spotify backend - The Spotify backend now includes release year and artist on albums. - Issue #233: The Spotify backend now returns the track if you search for the Spotify track URI. - Added support for connecting to the Spotify service through an HTTP or SOCKS proxy, which is supported by pyspotify >= 1.9. - Subscriptions to other Spotify user's "starred" playlists are ignored, as they currently isn't fully supported by pyspotify. ### Local backend - Issue #236: The `mopidy-scan` command failed to include tags from ALAC files (Apple lossless) because it didn't support multiple tag messages from GStreamer per track it scanned. - Added support for search by filename to local backend. ### MPD frontend - Issue #218: The MPD commands `listplaylist` and `listplaylistinfo` now accepts unquoted playlist names if they don't contain spaces. - Issue #246: The MPD command `list album artist ""` and similar `search`, `find`, and `list` commands with empty filter values caused a `LookupError`, but should have been ignored by the MPD server. - The MPD frontend no longer lowercases search queries. This broke e.g. search by URI, where casing may be essential. - The MPD command `plchanges` always returned the entire playlist. It now returns an empty response when the client has seen the latest version. - The MPD commands `search` and `find` now allows the key `file`, which is used by ncmpcpp instead of `filename`. - The MPD commands `search` and `find` now allow search query values to be empty strings. - The MPD command `listplaylists` will no longer return playlists without a name. This could crash ncmpcpp. - The MPD command `list` will no longer return artist names, album names, or dates that are blank. - The MPD command `decoders` will now return an empty response instead of a "not implemented" error to make the ncmpcpp browse view work the first time it is opened. ### MPRIS frontend - The MPRIS playlists interface is now supported by our MPRIS frontend. This means that you now can select playlists to queue and play from the Ubuntu Sound Menu. ### Audio mixers - Made the `NAD mixer ` responsive to interrupts during amplifier calibration. It will now quit immediately, while previously it completed the calibration first, and then quit, which could take more than 15 seconds. ### Developer support - Added optional background thread for debugging deadlocks. When the feature is enabled via the `mopidy --debug-thread` option or `mopidy.settings.DEBUG_THREAD` setting a `SIGUSR1` signal will dump the traceback for all running threads. - The settings validator will now allow any setting prefixed with `CUSTOM_` to exist in the settings file. ### Internal changes Internally, Mopidy have seen a lot of changes to pave the way for multiple backends and the future HTTP frontend. - A new layer and actor, "core", has been added to our stack, inbetween the frontends and the backends. The responsibility of the core layer and actor is to take requests from the frontends, pass them on to one or more backends, and combining the response from the backends into a single response to the requesting frontend. Frontends no longer know anything about the backends. They just use the core-api. - The dependency graph between the core controllers and the backend providers have been straightened out, so that we don't have any circular dependencies. The frontend, core, backend, and audio layers are now strictly separate. The frontend layer calls on the core layer, and the core layer calls on the backend layer. Both the core layer and the backends are allowed to call on the audio layer. Any data flow in the opposite direction is done by broadcasting of events to listeners, through e.g. `mopidy.core.CoreListener` and `mopidy.audio.AudioListener`. See concepts for more details and illustrations of all the relations. - All dependencies are now explicitly passed to the constructors of the frontends, core, and the backends. This makes testing each layer with dummy/mocked lower layers easier than with the old variant, where dependencies where looked up in Pykka's actor registry. - All properties in the core API now got getters, and setters if setting them is allowed. They are not explicitly listed in the docs as they have the same behavior as the documented properties, but they are available and may be used. This is useful for the future HTTP frontend. *Models:* - Added `mopidy.models.Album.date` attribute. It has the same format as the existing `mopidy.models.Track.date`. - Added `mopidy.models.ModelJSONEncoder` and `mopidy.models.model_json_decoder` for automatic JSON serialization and deserialization of data structures which contains Mopidy models. This is useful for the future HTTP frontend. *Library:* - `mopidy.core.LibraryController.find_exact` and `mopidy.core.LibraryController.search` now returns plain lists of tracks instead of playlist objects. - `mopidy.core.LibraryController.lookup` now returns a list of tracks instead of a single track. This makes it possible to support lookup of artist or album URIs which then can expand to a list of tracks. *Playback:* - The base playback provider has been updated with sane default behavior instead of empty functions. By default, the playback provider now lets GStreamer keep track of the current track's time position. The local backend simply uses the base playback provider without any changes. Any future backend that just feeds URIs to GStreamer to play can also use the base playback provider without any changes. - Removed `mopidy.core.PlaybackController.track_at_previous`. Use `mopidy.core.PlaybackController.tl_track_at_previous` instead. - Removed `mopidy.core.PlaybackController.track_at_next`. Use `mopidy.core.PlaybackController.tl_track_at_next` instead. - Removed `mopidy.core.PlaybackController.track_at_eot`. Use `mopidy.core.PlaybackController.tl_track_at_eot` instead. - Removed `mopidy.core.PlaybackController.current_tlid`. Use `mopidy.core.PlaybackController.current_tl_track` instead. *Playlists:* The playlists part of the core API has been revised to be more focused around the playlist URI, and some redundant functionality has been removed: - Renamed "stored playlists" to "playlists" everywhere, including the core API used by frontends. - `mopidy.core.PlaylistsController.playlists` no longer supports assignment to it. The `playlists` property on the backend layer still does, and all functionality is maintained by assigning to the playlists collections at the backend level. - `mopidy.core.PlaylistsController.delete` now accepts an URI, and not a playlist object. - `mopidy.core.PlaylistsController.save` now returns the saved playlist. The returned playlist may differ from the saved playlist, and should thus be used instead of the playlist passed to `mopidy.core.PlaylistsController.save`. - `mopidy.core.PlaylistsController.rename` has been removed, since renaming can be done with `mopidy.core.PlaylistsController.save`. - `mopidy.core.PlaylistsController.get` has been replaced by `mopidy.core.PlaylistsController.filter`. - The event `mopidy.core.CoreListener.playlist_changed` has been changed to include the playlist that was changed. *Tracklist:* - Renamed "current playlist" to "tracklist" everywhere, including the core API used by frontends. - Removed `mopidy.core.TracklistController.append`. Use `mopidy.core.TracklistController.add` instead, which is now capable of adding multiple tracks. - `mopidy.core.TracklistController.get` has been replaced by `mopidy.core.TracklistController.filter`. - `mopidy.core.TracklistController.remove` can now remove multiple tracks, and returns the tracks it removed. - When the tracklist is changed, we now trigger the new `mopidy.core.CoreListener.tracklist_changed` event. Previously we triggered `mopidy.core.CoreListener.playlist_changed`, which is intended for stored playlists, not the tracklist. *Towards Python 3 support:* - Make the entire code base use unicode strings by default, and only fall back to bytestrings where it is required. Another step closer to Python 3. ## v0.8.1 (2012-10-30) A small maintenance release to fix a bug introduced in 0.8.0 and update Mopidy to work with Pykka 1.0. ### Dependencies - Pykka >= 1.0 is now required. ### Bug fixes - Issue #213: Fix "streaming task paused, reason not-negotiated" errors observed by some users on some Spotify tracks due to a change introduced in 0.8.0. See the issue for a patch that applies to 0.8.0. - Issue #216: Volume returned by the MPD command `status` contained a floating point `.0` suffix. This bug was introduced with the large audio output and mixer changes in v0.8.0 and broke the MPDroid Android client. It now returns an integer again. ## v0.8.0 (2012-09-20) This release does not include any major new features. We've done a major cleanup of how audio outputs and audio mixers work, and on the way we've resolved a bunch of related issues. ### Audio output and mixer changes - Removed multiple outputs support. Having this feature currently seems to be more trouble than what it is worth. The `mopidy.settings.OUTPUTS` setting is no longer supported, and has been replaced with `mopidy.settings.OUTPUT` which is a GStreamer bin description string in the same format as `gst-launch` expects. Default value is `autoaudiosink`. (#81, #115, #121, #159) - Switch to pure GStreamer based mixing. This implies that users setup a GStreamer bin with a mixer in it in `mopidy.settings.MIXER`. The default value is `autoaudiomixer`, a custom mixer that attempts to find a mixer that will work on your system. If this picks the wrong mixer you can of course override it. Setting the mixer to `None` is also supported. MPD protocol support for volume has also been updated to return -1 when we have no mixer set. `software` can be used to force software mixing. - Removed the Denon hardware mixer, as it is not maintained. - Updated the NAD hardware mixer to work in the new GStreamer based mixing regime. Settings are now passed as GStreamer element properties. In practice that means that the following old-style config: ```python MIXER = u'mopidy.mixers.nad.NadMixer' MIXER_EXT_PORT = u'/dev/ttyUSB0' MIXER_EXT_SOURCE = u'Aux' MIXER_EXT_SPEAKERS_A = u'On' MIXER_EXT_SPEAKERS_B = u'Off' ``` Now is reduced to simply: ```python MIXER = u'nadmixer port=/dev/ttyUSB0 source=aux speakers-a=on speakers-b=off' ``` The `port` property defaults to `/dev/ttyUSB0`, and the rest of the properties may be left out if you don't want the mixer to adjust the settings on your NAD amplifier when Mopidy is started. ### Changes - When unknown settings are encountered, we now check if it's similar to a known setting, and suggests to the user what we think the setting should have been. - Added `mopidy --list-deps` option that lists required and optional dependencies, their current versions, and some other information useful for debugging. (#74) - Added `tools/debug-proxy.py` to tee client requests to two backends and diff responses. Intended as a developer tool for checking for MPD protocol changes and various client support. Requires gevent, which currently is not a dependency of Mopidy. - Support tracks with only release year, and not a full release date, like e.g. Spotify tracks. - Default value of `LOCAL_MUSIC_PATH` has been updated to be `$XDG_MUSIC_DIR`, which on most systems this is set to `$HOME`. Users of local backend that relied on the old default `~/music` need to update their settings. Note that the code responsible for finding this music now also ignores UNIX hidden files and folders. - File and path settings now support `$XDG_CACHE_DIR`, `$XDG_DATA_DIR` and `$XDG_MUSIC_DIR` substitution. Defaults for such settings have been updated to use this instead of hidden away defaults. - Playback is now done using `playbin2` from GStreamer instead of rolling our own. This is the first step towards resolving #171. ### Bug fixes - Issue #72: Created a Spotify track proxy that will switch to using loaded data as soon as it becomes available. - Issue #150: Fix bug which caused some clients to block Mopidy completely. The bug was caused by some clients sending `close` and then shutting down the connection right away. This triggered a situation in which the connection cleanup code would wait for an response that would never come inside the event loop, blocking everything else. - Issue #162: Fixed bug when the MPD command `playlistinfo` is used with a track position. Track position and CPID was intermixed, so it would cause a crash if a CPID matching the track position didn't exist. - Fixed crash on lookup of unknown path when using local backend. - Issue #189: `LOCAL_MUSIC_PATH` and path handling in rest of settings has been updated so all of the code now uses the correct value. - Fixed incorrect track URIs generated by M3U playlist parsing code. Generated tracks are now relative to `LOCAL_MUSIC_PATH`. - Issue #203: Re-add support for software mixing. ## v0.7.3 (2012-08-11) A small maintenance release to fix a crash affecting a few users, and a couple of small adjustments to the Spotify backend. - Fixed crash when logging `IOError` exceptions on systems using languages with non-ASCII characters, like French. - Move the default location of the Spotify cache from `~/.cache/mopidy` to `~/.cache/mopidy/spotify`. You can change this by setting `mopidy.settings.SPOTIFY_CACHE_PATH`. - Reduce time required to update the Spotify cache on startup. One one system/Spotify account, the time from clean cache to ready for use was reduced from 35s to 12s. ## v0.7.2 (2012-05-07) This is a maintenance release to make Mopidy 0.7 build on systems without all of Mopidy's runtime dependencies, like Launchpad PPAs. - Change from version tuple at `mopidy.VERSION` to :pep:`386` compliant version string at `mopidy.__version__` to conform to :pep:`396`. ## v0.7.1 (2012-04-22) This is a maintenance release to make Mopidy 0.7 work with pyspotify >= 1.7. - Don't override pyspotify's `notify_main_thread` callback. The default implementation is sensible, while our override did nothing. ## v0.7.0 (2012-02-25) Not a big release with regard to features, but this release got some performance improvements over v0.6, especially for slower Atom systems. It also fixes a couple of other bugs, including one which made Mopidy crash when using GStreamer from the prereleases of Ubuntu 12.04. - The MPD command `playlistinfo` is now faster, thanks to John Bäckstrand. - Added the method `mopidy.backends.base.CurrentPlaylistController.length()`, `mopidy.backends.base.CurrentPlaylistController.index()`, and `mopidy.backends.base.CurrentPlaylistController.slice()` to reduce the need for copying the entire current playlist from one thread to another. Thanks to John Bäckstrand for pinpointing the issue. - Fix crash on creation of config and cache directories if intermediate directories does not exist. This was especially the case on OS X, where `~/.config` doesn't exist for most users. - Fix `gst.LinkError` which appeared when using newer versions of GStreamer, e.g. on Ubuntu 12.04 Alpha. (#144) - Fix crash on mismatching quotation in `list` MPD queries. (#137) - Volume is now reported to be the same as the volume was set to, also when internal rounding have been done due to `mopidy.settings.MIXER_MAX_VOLUME` has been set to cap the volume. This should make it possible to manage capped volume from clients that only increase volume with one step at a time, like ncmpcpp does. ## v0.6.1 (2011-12-28) This is a maintenance release to make Mopidy 0.6 work with pyspotify >= 1.5, which Mopidy's develop branch have supported for a long time. This should also make the Debian packages work out of the box again. ### Important changes - pyspotify 1.5 or greater is required. ### Changes - Spotify playlist folder boundaries are now properly detected. In other words, if you use playlist folders, you will no longer get lots of log messages about bad playlists. ## v0.6.0 (2011-10-09) The development of Mopidy have been quite slow for the last couple of months, but we do have some goodies to release which have been idling in the develop branch since the warmer days of the summer. This release brings support for the MPD `idle` command, which makes it possible for a client wait for updates from the server instead of polling every second. Also, we've added support for the MPRIS standard, so that Mopidy can be controlled over D-Bus from e.g. the Ubuntu Sound Menu. Please note that 0.6.0 requires some updated dependencies, as listed under *Important changes* below. ### Important changes - Pykka 0.12.3 or greater is required. - pyspotify 1.4 or greater is required. - All config, data, and cache locations are now based on the XDG spec. - This means that your settings file will need to be moved from `~/.mopidy/settings.py` to `~/.config/mopidy/settings.py`. - Your Spotify cache will now be stored in `~/.cache/mopidy` instead of `~/.mopidy/spotify_cache`. - The local backend's `tag_cache` should now be in `~/.local/share/mopidy/tag_cache`, likewise your playlists will be in `~/.local/share/mopidy/playlists`. - The local client now tries to lookup where your music is via XDG, it will fall-back to `~/music` or use whatever setting you set manually. - The MPD command `idle` is now supported by Mopidy for the following subsystems: player, playlist, options, and mixer. (#32) - A new frontend `mopidy.frontends.mpris` have been added. It exposes Mopidy through the [MPRIS interface](http://specifications.freedesktop.org/mpris-spec/latest/) over D-Bus. In practice, this makes it possible to control Mopidy through the [Ubuntu Sound Menu](https://wiki.ubuntu.com/Sound#menu). ### Changes - Replace `mopidy.backends.base.Backend.uri_handlers` with `mopidy.backends.base.Backend.uri_schemes`, which just takes the part up to the colon of an URI, and not any prefix. - Add Listener API, `mopidy.listeners`, to be implemented by actors wanting to receive events from the backend. This is a formalization of the ad hoc events the Last.fm scrobbler has already been using for some time. - Replaced all of the MPD network code that was provided by asyncore with custom stack. This change was made to facilitate support for the `idle` command, and to reduce the number of event loops being used. - Fix metadata update in Shoutcast streaming. (#122) - Unescape all incoming MPD requests. (#113) - Increase the maximum number of results returned by Spotify searches from 32 to 100. - Send Spotify search queries to pyspotify as unicode objects, as required by pyspotify 1.4. (#129) - Add setting `mopidy.settings.MPD_SERVER_MAX_CONNECTIONS`. (#134) - Remove `destroy()` methods from backend controller and provider APIs, as it was not in use and actually not called by any code. Will reintroduce when needed. ## v0.5.0 (2011-06-15) Since last time we've added support for audio streaming to SHOUTcast servers and fixed the longstanding playlist loading issue in the Spotify backend. As always the release has a bunch of bug fixes and minor improvements. Please note that 0.5.0 requires some updated dependencies, as listed under *Important changes* below. ### Important changes - If you use the Spotify backend, you *must* upgrade to libspotify 0.0.8 and pyspotify 1.3. If you install from APT, libspotify and pyspotify will automatically be upgraded. If you are not installing from APT, follow the instructions at installation. - If you have explicitly set the `mopidy.settings.SPOTIFY_HIGH_BITRATE` setting, you must update your settings file. The new setting is named `mopidy.settings.SPOTIFY_BITRATE` and accepts the integer values 96, 160, and 320. - Mopidy now supports running with 1 to N outputs at the same time. This feature was mainly added to facilitate SHOUTcast support, which Mopidy has also gained. In its current state outputs can not be toggled during runtime. ### Changes - Local backend: - Fix local backend time query errors that where coming from stopped pipeline. (#87) - Spotify backend: - Thanks to Antoine Pierlot-Garcin's recent work on updating and improving pyspotify, stored playlists will again load when Mopidy starts. The workaround of searching and reconnecting to make the playlists appear are no longer necessary. (#59) - Track's that are no longer available in Spotify's archives are now "autolinked" to corresponding tracks in other albums, just like the official Spotify clients do. (#34) - MPD frontend: - Refactoring and cleanup. Most notably, all request handlers now get an instance of `mopidy.frontends.mpd.dispatcher.MpdContext` as the first argument. The new class contains reference to any object in Mopidy the MPD protocol implementation should need access to. - Close the client connection when the command `close` is received. - Do not allow access to the command `kill`. - `commands` and `notcommands` now have correct output if password authentication is turned on, but the connected user has not been authenticated yet. - Command line usage: - Support passing options to GStreamer. See `mopidy --help-gst` for a list of available options. (#95) - Improve `mopidy --list-settings` output. (#91) - Added `mopidy --interactive` for reading missing local settings from `stdin`. (#96) - Improve shutdown procedure at CTRL+C. Add signal handler for `SIGTERM`, which initiates the same shutdown procedure as CTRL+C does. - Tag cache generator: - Made it possible to abort `mopidy-scan` with CTRL+C. - Fixed bug regarding handling of bad dates. - Use `logging` instead of `print` statements. - Found and worked around strange WMA metadata behaviour. - Backend API: - Calling on `mopidy.backends.base.playback.PlaybackController.next` and `mopidy.backends.base.playback.PlaybackController.previous` no longer implies that playback should be started. The playback state--whether playing, paused or stopped--will now be kept. - The method `mopidy.backends.base.playback.PlaybackController.change_track` has been added. Like `next()`, and `prev()`, it changes the current track without changing the playback state. ## v0.4.1 (2011-05-06) This is a bug fix release fixing audio problems on older GStreamer and some minor bugs. - Fix broken audio on at least GStreamer 0.10.30, which affects Ubuntu 10.10. The GStreamer `appsrc` bin wasn't being linked due to lack of default caps. (#85) - Fix crash in `mopidy.mixers.nad` that occurs at startup when the `io` module is available. We used an `eol` keyword argument which is supported by `serial.FileLike.readline`, but not by `io.RawBaseIO.readline`. When the `io` module is available, it is used by PySerial instead of the `FileLike` implementation. - Fix UnicodeDecodeError in MPD frontend on non-english locale. Thanks to Antoine Pierlot-Garcin for the patch. (#88) - Do not create Pykka proxies that are not going to be used in `mopidy.core`. The underlying actor may already intentionally be dead, and thus the program may crash on creating a proxy it doesn't need. Combined with the Pykka 0.12.2 release this fixes a crash in the Last.fm frontend which may occur when all dependencies are installed, but the frontend isn't configured. (#84) ## v0.4.0 (2011-04-27) Mopidy 0.4.0 is another release without major feature additions. In 0.4.0 we've fixed a bunch of issues and bugs, with the help of several new contributors who are credited in the changelog below. The major change of 0.4.0 is an internal refactoring which clears way for future features, and which also make Mopidy work on Python 2.7. In other words, Mopidy 0.4.0 works on Ubuntu 11.04 and Arch Linux. Please note that 0.4.0 requires some updated dependencies, as listed under *Important changes* below. Also, the known bug in the Spotify playlist loading from Mopidy 0.3.0 is still present. /// warning | Known bug in Spotify playlist loading There is a known bug in the loading of Spotify playlists. To avoid the bug, follow the simple workaround described at #59. /// ### Important changes - Mopidy now depends on [Pykka](http://pykka.readthedocs.org/) >=0.12. If you install from APT, Pykka will automatically be installed. If you are not installing from APT, you may install Pykka from PyPI: ```text sudo pip install -U Pykka ``` - If you use the Spotify backend, you *should* upgrade to libspotify 0.0.7 and the latest pyspotify from the Mopidy developers. If you install from APT, libspotify and pyspotify will automatically be upgraded. If you are not installing from APT, follow the instructions at installation. ### Changes - Mopidy now use Pykka actors for thread management and inter-thread communication. The immediate advantage of this is that Mopidy now works on Python 2.7, which is the default on e.g. Ubuntu 11.04. (#66) - Spotify backend: - Fixed multiple segmentation faults due to bugs in Pyspotify. Thanks to Antoine Pierlot-Garcin and Jamie Kirkpatrick for patches to Pyspotify. - Better error messages on wrong login or network problems. Thanks to Antoine Pierlot-Garcin for patches to Mopidy and Pyspotify. (#77) - Reduce log level for trivial log messages from warning to info. (#71) - Pause playback on network connection errors. (#65) - Local backend: - Fix crash in `mopidy-scan` if a track has no artist name. Thanks to Martins Grunskis for test and patch and "octe" for patch. - Fix crash in `tag_cache` parsing if a track has no total number of tracks in the album. Thanks to Martins Grunskis for the patch. - MPD frontend: - Add support for "date" queries to both the `find` and `search` commands. This makes media library browsing in ncmpcpp work, though very slow due to all the meta data requests to Spotify. - Add support for `play "-1"` when in playing or paused state, which fixes resume and addition of tracks to the current playlist while playing for the MPoD client. - Fix bug where `status` returned `song: None`, which caused MPDroid to crash. (#69) - Gracefully fallback to IPv4 sockets on systems that supports IPv6, but has turned it off. (#75) - GStreamer output: - Use `uridecodebin` for playing audio from both Spotify and the local backend. This contributes to support for multiple backends simultaneously. - Settings: - Fix crash on `mopidy --list-settings` on clean installation. Thanks to Martins Grunskis for the bug report and patch. (#63) - Packaging: - Replace test data symlinks with real files to avoid symlink issues when installing with pip. (#68) - Debugging: - Include platform, architecture, Linux distribution, and Python version in the debug log, to ease debugging of issues with attached debug logs. ## v0.3.1 (2011-01-22) A couple of fixes to the 0.3.0 release is needed to get a smooth installation. - The Spotify application key was missing from the Python package. - Installation of the Python package as a normal user failed because it did not have permissions to install `mopidy.desktop`. The file is now only installed if the installation is executed as the root user. ## v0.3.0 (2011-01-22) Mopidy 0.3.0 brings a bunch of small changes all over the place, but no large changes. The main features are support for high bitrate audio from Spotify, and MPD password authentication. Regarding the docs, we've improved the :ref:`installation instructions ` and done a bit of testing of the available Android and iOS clients for MPD. Please note that 0.3.0 requires some updated dependencies, as listed under *Important changes* below. Also, there is a known bug in the Spotify playlist loading, as described below. As the bug will take some time to fix and has a known workaround, we did not want to delay the release while waiting for a fix to this problem. /// warning | Known bug in Spotify playlist loading There is a known bug in the loading of Spotify playlists. This bug affects both Mopidy 0.2.1 and 0.3.0, given that you use libspotify 0.0.6. To avoid the bug, either use Mopidy 0.2.1 with libspotify 0.0.4, or use either Mopidy version with libspotify 0.0.6 and follow the simple workaround described at #59. /// ### Important changes - If you use the Spotify backend, you need to upgrade to libspotify 0.0.6 and the latest pyspotify from the Mopidy developers. Follow the instructions at installation. - If you use the Last.fm frontend, you need to upgrade to pylast 0.5.7. Run `sudo pip install --upgrade pylast` or install Mopidy from APT. ### Changes - Spotify backend: - Support high bitrate (320k) audio. Set the new setting `mopidy.settings.SPOTIFY_HIGH_BITRATE` to `True` to switch to high bitrate audio. - Rename `mopidy.backends.libspotify` to `mopidy.backends.spotify`. If you have set `mopidy.settings.BACKENDS` explicitly, you may need to update the setting's value. - Catch and log error caused by playlist folder boundaries being threated as normal playlists. More permanent fix requires support for checking playlist types in pyspotify (see #62). - Fix crash on failed lookup of track by URI. (#60) - Local backend: - Add `mopidy-scan` command to generate `tag_cache` files without any help from the original MPD server. See "Generating a local library" for instructions on how to use it. - Fix support for UTF-8 encoding in tag caches. - MPD frontend: - Add support for password authentication. See `mopidy.settings.MPD_SERVER_PASSWORD` for details on how to use it. (#41) - Support `setvol 50` without quotes around the argument. volume control in Droid MPD. - Support `seek 1 120` without quotes around the arguments. seek in Droid MPD. - Last.fm frontend: - Update to use Last.fm's new Scrobbling 2.0 API, as the old Submissions Protocol 1.2.1 is deprecated. (#33) - Fix crash when track object does not contain all the expected meta data. - Fix crash when response from Last.fm cannot be decoded as UTF-8. (#37) - Fix crash when response from Last.fm contains invalid XML. - Fix crash when response from Last.fm has an invalid HTTP status line. - Mixers: - Support use of unicode strings for settings specific to `mopidy.mixers.nad`. - Settings: - Automatically expand the "~" character to the user's home directory and make the path absolute for settings with names ending in `_PATH` or `_FILE`. - Rename the following settings. The settings validator will warn you if you need to change your local settings. - `LOCAL_MUSIC_FOLDER` to `mopidy.settings.LOCAL_MUSIC_PATH` - `LOCAL_PLAYLIST_FOLDER` to `mopidy.settings.LOCAL_PLAYLIST_PATH` - `LOCAL_TAG_CACHE` to `mopidy.settings.LOCAL_TAG_CACHE_FILE` - `SPOTIFY_LIB_CACHE` to `mopidy.settings.SPOTIFY_CACHE_PATH` - Fix bug which made settings set to `None` or 0 cause a `mopidy.SettingsError` to be raised. - Packaging and distribution: - Setup APT repository and create Debian packages of Mopidy. See installation for instructions for how to install Mopidy, including all dependencies, from APT. - Install `mopidy.desktop` file that makes Mopidy available from e.g. Gnome application menus. - API: - Rename and generalize `Playlist._with(**kwargs)` to `mopidy.models.ImmutableObject.copy`. - Add `musicbrainz_id` field to `mopidy.models.Artist`, `mopidy.models.Album`, and `mopidy.models.Track`. - Prepare for multi-backend support (see #40) by introducing the provider concept. Split the backend API into a backend controller API (for frontend use) and a backend provider API (for backend implementation use), which includes the following changes: - Rename `BaseBackend` to `mopidy.backends.base.Backend`. - Rename `BaseCurrentPlaylistController` to `mopidy.backends.base.CurrentPlaylistController`. - Split `BaseLibraryController` to `mopidy.backends.base.LibraryController` and `mopidy.backends.base.BaseLibraryProvider`. - Split `BasePlaybackController` to `mopidy.backends.base.PlaybackController` and `mopidy.backends.base.BasePlaybackProvider`. - Split `BaseStoredPlaylistsController` to `mopidy.backends.base.StoredPlaylistsController` and `mopidy.backends.base.BaseStoredPlaylistsProvider`. - Move `BaseMixer` to `mopidy.mixers.base.BaseMixer`. - Add docs for the current non-stable output API, `mopidy.outputs.base.BaseOutput`. ## v0.2.1 (2011-01-07) This is a maintenance release without any new features. - Fix crash in `mopidy.frontends.lastfm` which occurred at playback if either `pylast` was not installed or the Last.fm scrobbling was not correctly configured. The scrobbling thread now shuts properly down at failure. ## v0.2.0 (2010-10-24) In Mopidy 0.2.0 we've added a [Last.fm](http://www.last.fm/) scrobbling support, which means that Mopidy now can submit meta data about the tracks you play to your Last.fm profile. See `mopidy.frontends.lastfm` for details on new dependencies and settings. If you use Mopidy's Last.fm support, please join the [Mopidy group at Last.fm](http://www.last.fm/group/Mopidy). With the exception of the work on the Last.fm scrobbler, there has been a couple of quiet months in the Mopidy camp. About the only thing going on, has been stabilization work and bug fixing. All bugs reported on GitHub, plus some, have been fixed in 0.2.0. Thus, we hope this will be a great release! We've worked a bit on OS X support, but not all issues are completely solved yet. #25 is the one that is currently blocking OS X support. Any help solving it will be greatly appreciated! Finally, please update your pyspotify installation when upgrading to Mopidy 0.2.0. The latest pyspotify got a fix for the segmentation fault that occurred when playing music and searching at the same time, thanks to Valentin David. ### Important changes - Added a Last.fm scrobbler. See `mopidy.frontends.lastfm` for details. ### Changes - Logging and command line options: - Simplify the default log format, `mopidy.settings.CONSOLE_LOG_FORMAT`. From a user's point of view: Less noise, more information. - Rename the `mopidy --dump` command line option to `mopidy --save-debug-log`. - Rename setting `mopidy.settings.DUMP_LOG_FORMAT` to `mopidy.settings.DEBUG_LOG_FORMAT` and use it for :option:`mopidy --verbose` too. - Rename setting `mopidy.settings.DUMP_LOG_FILENAME` to `mopidy.settings.DEBUG_LOG_FILENAME`. - MPD frontend: - MPD command `list` now supports queries by artist, album name, and date, as used by e.g. the Ario client. (#20) - MPD command `add ""` and `addid ""` now behaves as expected. (#16) - MPD command `playid "-1"` now correctly resumes playback if paused. - Random mode: - Fix wrong behavior on end of track and next after random mode has been used. (#18) - Fix infinite recursion loop crash on playback of non-playable tracks when in random mode. (#17) - Fix assertion error that happened if one removed tracks from the current playlist, while in random mode. (#22) - Switched from using subprocesses to threads. (#14) - `mopidy.outputs.gstreamer`: Set `caps` on the `appsrc` bin before use. This makes sound output work with GStreamer >= 0.10.29, which includes the versions used in Ubuntu 10.10 and on OS X if using Homebrew. (#21, #24, contributes to #14) - Improved handling of uncaught exceptions in threads. The entire process should now exit immediately. ## v0.1.0 (2010-08-23) After three weeks of long nights and sprints we're finally pleased enough with the state of Mopidy to remove the alpha label, and do a regular release. Mopidy 0.1.0 got important improvements in search functionality, working track position seeking, no known stability issues, and greatly improved MPD client support. There are lots of changes since 0.1.0a3, and we urge you to at least read the *important changes* below. This release does not support OS X. We're sorry about that, and are working on fixing the OS X issues for a future release. You can track the progress at #14 ### Important changes - License changed from GPLv2 to Apache License, version 2.0. - GStreamer is now a required dependency. See our :ref:`GStreamer installation docs `. - `mopidy.backends.libspotify` is now the default backend. `mopidy.backends.despotify` is no longer available. This means that you need to install the dependencies for libspotify. - If you used `mopidy.backends.libspotify` previously, pyspotify must be updated when updating to this release, to get working seek functionality. - `mopidy.settings.SERVER_HOSTNAME` and `mopidy.settings.SERVER_PORT` has been renamed to `mopidy.settings.MPD_SERVER_HOSTNAME` and `mopidy.settings.MPD_SERVER_PORT` to allow for multiple frontends in the future. ### Changes - Exit early if not Python >= 2.6, < 3. - Validate settings at startup and print useful error messages if the settings has not been updated or anything is misspelled. - Add command line option `mopidy --list-settings` to print the currently active settings. - Include Sphinx scripts for building docs, pylintrc, tests and test data in the packages created by `setup.py` for i.e. PyPI. - MPD frontend: - Search improvements, including support for multi-word search. - Fixed `play "-1"` and `playid "-1"` behaviour when playlist is empty or when a current track is set. - Support `plchanges "-1"` to work better with MPDroid. - Support `pause` without arguments to work better with MPDroid. - Support `plchanges`, `play`, `consume`, `random`, `repeat`, and `single` without quotes to work better with BitMPC. - Fixed deletion of the currently playing track from the current playlist, which crashed several clients. - Implement `seek` and `seekid`. - Fix `playlistfind` output so the correct song is played when playing songs directly from search results in GMPC. - Fix `load` so that one can append a playlist to the current playlist, and make it return the correct error message if the playlist is not found. - Support for single track repeat added. (#4) - Relocate from `mopidy.mpd` to `mopidy.frontends.mpd`. - Split gigantic protocol implementation into eleven modules. - Rename `mopidy.frontends.mpd.{serializer => translator}` to match naming in backends. - Remove setting `mopidy.settings.SERVER` and `mopidy.settings.FRONTEND` in favour of the new `mopidy.settings.FRONTENDS`. - Run MPD server in its own process. - Backends: - Rename `mopidy.backends.gstreamer` to `mopidy.backends.local`. - Remove `mopidy.backends.despotify`, as Despotify is little maintained and the Libspotify backend is working much better. (#9, #10, #13) - A Spotify application key is now bundled with the source. `mopidy.settings.SPOTIFY_LIB_APPKEY` is thus removed. - If failing to play a track, playback will skip to the next track. - Both `mopidy.backends.libspotify` and `mopidy.backends.local` have been rewritten to use the new common GStreamer audio output module, `mopidy.outputs.gstreamer`. - Mixers: - Added new `mopidy.mixers.gstreamer_software.GStreamerSoftwareMixer` which now is the default mixer on all platforms. - New setting `mopidy.settings.MIXER_MAX_VOLUME` for capping the maximum output volume. - Backend API: - Relocate from `mopidy.backends` to `mopidy.backends.base`. - The `id` field of `mopidy.models.Track` has been removed, as it is no longer needed after the CPID refactoring. - `mopidy.backends.base.BaseBackend()` now accepts an `output_queue` which it can use to send messages (i.e. audio data) to the output process. - `mopidy.backends.base.BaseLibraryController.find_exact()` now accepts keyword arguments of the form `find_exact(artist=['foo'], album=['bar'])`. - `mopidy.backends.base.BaseLibraryController.search()` now accepts keyword arguments of the form `search(artist=['foo', 'fighters'], album=['bar', 'grooves'])`. - `mopidy.backends.base.BaseCurrentPlaylistController.append()` replaces `mopidy.backends.base.BaseCurrentPlaylistController.load()`. Use `mopidy.backends.base.BaseCurrentPlaylistController.clear()` if you want to clear the current playlist. - The following fields in `mopidy.backends.base.BasePlaybackController` has been renamed to reflect their relation to methods called on the controller: - `next_track` to `track_at_next` - `next_cp_track` to `cp_track_at_next` - `previous_track` to `track_at_previous` - `previous_cp_track` to `cp_track_at_previous` - `mopidy.backends.base.BasePlaybackController.track_at_eot` and `mopidy.backends.base.BasePlaybackController.cp_track_at_eot` has been added to better handle the difference between the user pressing next and the current track ending. - Rename `mopidy.backends.base.BasePlaybackController.new_playlist_loaded_callback()` to `mopidy.backends.base.BasePlaybackController.on_current_playlist_change()`. - Rename `mopidy.backends.base.BasePlaybackController.end_of_track_callback()` to `mopidy.backends.base.BasePlaybackController.on_end_of_track()`. - Remove `mopidy.backends.base.BaseStoredPlaylistsController.search()` since it was barely used, untested, and we got no use case for non-exact search in stored playlists yet. Use `mopidy.backends.base.BaseStoredPlaylistsController.get()` instead. ## v0.1.0a3 (2010-08-03) In the last two months, Mopidy's MPD frontend has gotten lots of stability fixes and error handling improvements, proper support for having the same track multiple times in a playlist, and support for IPv6. We have also fixed the choppy playback on the libspotify backend. For the road ahead of us, we got an updated release roadmap with our goals for the 0.1 to 0.3 releases. Enjoy the best alpha release of Mopidy ever :-) - MPD frontend: - Support IPv6. - `addid` responds properly on errors instead of crashing. - `commands` support, which makes RelaXXPlayer work with Mopidy. (#6) - Does no longer crash on invalid data, i.e. non-UTF-8 data. - `ACK` error messages are now MPD-compliant, which should make clients handle errors from Mopidy better. - Requests to existing commands with wrong arguments are no longer reported as unknown commands. - `command_list_end` before `command_list_start` now returns unknown command error instead of crashing. - `list` accepts field argument without quotes and capitalized, to work with GMPC and ncmpc. - `noidle` command now returns `OK` instead of an error. Should make some clients work a bit better. - Having multiple identical tracks in a playlist is now working properly. (CPID refactoring) - Despotify backend: - Catch and log `spytify.SpytifyError`. (#11) - Libspotify backend: - Fix choppy playback using the Libspotify backend by using blocking ALSA mode. (#7) - Backend API: - A new data structure called `cp_track` is now used in the current playlist controller and the playback controller. A `cp_track` is a two-tuple of (CPID integer, `mopidy.models.Track`), identifying an instance of a track uniquely within the current playlist. - `mopidy.backends.BaseCurrentPlaylistController.load()` now accepts lists of `mopidy.models.Track` instead of `mopidy.models.Playlist`, as none of the other fields on the `Playlist` model was in use. - `mopidy.backends.BaseCurrentPlaylistController.add()` now returns the `cp_track` added to the current playlist. - `mopidy.backends.BaseCurrentPlaylistController.remove()` now takes criteria, just like `mopidy.backends.BaseCurrentPlaylistController.get()`. - `mopidy.backends.BaseCurrentPlaylistController.get()` now returns a `cp_track`. - `mopidy.backends.BaseCurrentPlaylistController.tracks` is now read-only. Use the methods to change its contents. - `mopidy.backends.BaseCurrentPlaylistController.cp_tracks` is a read-only list of `cp_track`. Use the methods to change its contents. - `mopidy.backends.BasePlaybackController.current_track` is now just for convenience and read-only. To set the current track, assign a `cp_track` to `mopidy.backends.BasePlaybackController.current_cp_track`. - `mopidy.backends.BasePlaybackController.current_cpid` is the read-only CPID of the current track. - `mopidy.backends.BasePlaybackController.next_cp_track` is the next `cp_track` in the playlist. - `mopidy.backends.BasePlaybackController.previous_cp_track` is the previous `cp_track` in the playlist. - `mopidy.backends.BasePlaybackController.play()` now takes a `cp_track`. ## v0.1.0a2 (2010-06-02) It has been a rather slow month for Mopidy, but we would like to keep up with the established pace of at least a release per month. - Improvements to MPD protocol handling, making Mopidy work much better with a group of clients, including ncmpc, MPoD, and Theremin. - New command line flag `mopidy --dump` for dumping debug log to `dump.log` in the current directory. - New setting `mopidy.settings.MIXER_ALSA_CONTROL` for forcing what ALSA control `mopidy.mixers.alsa.AlsaMixer` should use. ## v0.1.0a1 (2010-05-04) Since the previous release Mopidy has seen about 300 commits, more than 200 new tests, a libspotify release, and major feature additions to Spotify. The new releases from Spotify have lead to updates to our dependencies, and also to new bugs in Mopidy. Thus, this is primarily a bugfix release, even though the not yet finished work on a GStreamer backend have been merged. All users are recommended to upgrade to 0.1.0a1, and should at the same time ensure that they have the latest versions of our dependencies: Despotify r508 if you are using DespotifyBackend, and pyspotify 1.1 with libspotify 0.0.4 if you are using LibspotifyBackend. As always, report problems at our IRC channel or our issue tracker. Thanks! - Backend API changes: - Removed `backend.playback.volume` wrapper. Use `backend.mixer.volume` directly. - Renamed `backend.playback.playlist_position` to `current_playlist_position` to match naming of `current_track`. - Replaced `get_by_id()` with a more flexible `get(**criteria)`. - Merged the `gstreamer` branch from Thomas Adamcik: - More than 200 new tests, and thus several bug fixes to existing code. - Several new generic features, like shuffle, consume, and playlist repeat. (#3) - **[Work in Progress]** A new backend for playing music from a local music archive using the GStreamer library. - Made `mopidy.mixers.alsa.AlsaMixer` work on machines without a mixer named "Master". - Make `mopidy.backends.DespotifyBackend` ignore local files in playlists (feature added in Spotify 0.4.3). Reported by Richard Haugen Olsen. - And much more. ## v0.1.0a0 (2010-03-27) "*Release early. Release often. Listen to your customers.*" wrote Eric S. Raymond in *The Cathedral and the Bazaar*. Three months of development should be more than enough. We have more to do, but Mopidy is working and usable. 0.1.0a0 is an alpha release, which basically means we will still change APIs, add features, etc. before the final 0.1.0 release. But the software is usable as is, so we release it. Please give it a try and give us feedback, either at our IRC channel or through the [issue tracker](https://github.com/mopidy/mopidy/issues). Thanks! - Initial version. No changelog available. ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1789253942.6415482 mopidy-4.0.4/docs/changelog/1.x.md0000644000175100017510000007416715251354467016330 0ustar00runnerrunner# Changelog 1.x series This is the changelog of Mopidy v1.0.0 through v1.1.2. For the latest releases, see changelog. ## v1.1.2 (2016-01-18) Bug fix release. - Main: Catch errors when loading the `logging/config_file` file. (#1320) - Core: If changing to another track while the player is paused, the new track would not be added to the history or marked as currently playing. (#1352, !1356) - Core: Skips over unplayable tracks if the user attempts to change tracks while paused, like we already did if in playing state. (#1378, !1379) - Core: Make `mopidy.core.LibraryController.lookup` ignore tracks with empty URIs. (Partial: #1340, !1381) - Core: Fix crash if backends emits events with wrong names or arguments. (#1383) - Stream: If an URI is considered playable, don't consider it as a candidate for playlist parsing. Just looking at MIME type prefixes isn't enough, as for example Ogg Vorbis has the MIME type `application/ogg`. (#1299) - Local: If the scan or clear commands are used on a library that does not exist, exit with an error. (#1298) - MPD: Notify idling clients when a seek is performed. (#1331) - MPD: Don't return tracks with empty URIs. (Partial: #1340, !1343) - MPD: Add `volume` command that was reintroduced, though still as a deprecated command, in MPD 0.18 and is in use by some clients like mpc. (#1393, !1397) - Proxy: Handle case where `proxy/port` is either missing from config or set to an empty string. (!1371) ## v1.1.1 (2015-09-14) Bug fix release. - Dependencies: Specify that we need Requests >= 2.0, not just any version. This ensures that we fail earlier if Mopidy is used with a too old Requests. - Core: Make `mopidy.core.LibraryController.refresh` work for all backends with a library provider. Previously, it wrongly worked for all backends with a playlists provider. (#1257) - Core: Respect `core/cache_dir` and `core/data_dir` config values added in 1.1.0 when creating the dirs Mopidy need to store data. This should not change the behavior for desktop users running Mopidy. When running Mopidy as a system service installed from a package which sets the core dir configs properly (e.g. Debian and Arch packages), this fix avoids the creation of a couple of directories that should not be used, typically :file:`/var/lib/mopidy/.local` and :file:`/var/lib/mopidy/.cache`. (#1259, !1266) - Core: Fix error in `mopidy.core.TracklistController.get_eot_tlid` docstring. (#1269) - Audio: Add `timeout` parameter to `mopidy.audio.scan.Scanner.scan`. (#1250, !1281) - Extension support: Make `mopidy.ext.Extension.get_cache_dir`, `mopidy.ext.Extension.get_config_dir`, and `mopidy.ext.Extension.get_data_dir` class methods, so they can be used without creating an instance of the `mopidy.ext.Extension` class. (#1275) - Local: Deprecate `local/data_dir` and respect `core/data_dir` instead. This does not change the defaults for desktop users, only system services installed from packages that properly set `core/data_dir`, like the Debian and Arch packages. (#1259, !1266) - Local: Change default value of `local/scan_flush_threshold` from 1000 to 100 to shorten the time Mopidy-Local-SQLite blocks incoming requests while scanning the local library. - M3U: Changed default for the `m3u/playlists_dir` from `$XDG_DATA_DIR/mopidy/m3u` to unset, which now means the extension's data dir. This does not change the defaults for desktop users, only system services installed from packages that properly set `core/data_dir`, like the Debian and Arch packages. (#1259, !1266) - Stream: Expand nested playlists to find the stream URI. This used to work, but regressed in 1.1.0 with the extraction of stream playlist parsing from GStreamer to being handled by the Mopidy-Stream backend. (#1250, !1281) - Stream: If "file" is present in the `stream/protocols` config value and the ext-file extension is enabled, we exited with an error because two extensions claimed the same URI scheme. We now log a warning recommending to remove "file" from the `stream/protocols` config, and then proceed startup. (#1248, !1254) - Stream: Fix bug in new playlist parser. A non-ASCII char in an urilist comment would cause a crash while parsing due to comparison of a non-ASCII bytestring with a Unicode string. (#1265) - File: Adjust log levels when failing to expand `$XDG_MUSIC_DIR` into a real path. This usually happens when running Mopidy as a system service, and thus with a limited set of environment variables. (#1249, !1255) - File: When browsing files, we no longer scan the files to check if they're playable. This makes browsing of the file hierarchy instant for HTTP clients, which do no scanning of the files' metadata, and a bit faster for MPD clients, which no longer scan the files twice. (#1260, !1261) - File: Allow looking up metadata about any `file://` URI, just like we did in Mopidy 1.0.x, where Mopidy-Stream handled `file://` URIs. In Mopidy 1.1.0, Mopidy-File did not allow one to lookup files outside the directories listed in `file/media_dir`. This broke Mopidy-Local-SQLite when the `local/media_dir` directory was not within one of the `file/media_dirs` directories. For browsing of files, we still limit access to files inside the `file/media_dir` directories. For lookup, you can now read metadata for any file you know the path of. (#1268, !1273) - Audio: Fix timeout handling in scanner. This regression caused timeouts to expire before it should, causing scans to fail. - Audio: Update scanner to emit MIME type instead of an error when missing a plugin. ## v1.1.0 (2015-08-09) Mopidy 1.1 is here! Since the release of 1.0, we've closed or merged approximately 65 issues and pull requests through about 400 commits by a record high 20 extraordinary people, including 14 newcomers. That's less issues and commits than in the 1.0 release, but even more contributors, and a doubling of the number of newcomers. Thanks to everyone who has contributed, especially those that joined the sprint at EuroPython 2015 in Bilbao, Spain a couple of weeks ago! As we promised with the release of Mopidy 1.0, any extension working with Mopidy 1.0 should continue working with all Mopidy 1.x releases. However, this release brings a lot stronger enforcement of our documented APIs. If an extension doesn't use the APIs properly, it may no longer work. The advantage of this change is that Mopidy is now more robust against errors in extensions, and also provides vastly better error messages when extensions misbehave. This should make it easier to create quality extensions. The major features of Mopidy 1.1 are: - Validation of the arguments to all core API methods, as well as all responses from backends and all data model attributes. - New bundled backend, Mopidy-File. It is similar to Mopidy-Local, but allows you to browse and play music from local disk without running a scan to index the music first. The drawback is that it doesn't support searching. - The Mopidy-MPD server should now be up to date with the 0.19 version of the MPD protocol. ### Dependencies - Mopidy now requires Requests. - Heads up: Porting from GStreamer 0.10 to 1.x and support for running Mopidy with Python 3.4+ is not far off on our roadmap. ### Core API - **Deprecated:** Calling the following methods with `kwargs` is being deprecated. (!1090) - `mopidy.core.LibraryController.search` - `mopidy.core.PlaylistsController.filter` - `mopidy.core.TracklistController.filter` - `mopidy.core.TracklistController.remove` - Updated core controllers to handle backend exceptions in all calls that rely on multiple backends. (#667) - Update core methods to do strict input checking. (#700) - Add `tlid` alternatives to methods that take `tl_track` and also add `get_{eot,next,previous}_tlid` methods as light weight alternatives to the `tl_track` versions of the calls. (#1131, !1136, #1140) - Add `mopidy.core.PlaybackController.get_current_tlid`. (#1137) - Update core to handle backend crashes and bad data. (#1161) - Add `core/max_tracklist_length` config and limitation. (#997 !1225) - Added `playlist_deleted` event. (#996) ### Models - Added type checks and other sanity checks to model construction and serialization. (#865) - Memory usage for models has been greatly improved. We now have a lower overhead per instance by using slots, interned identifiers and automatically reuse instances. For the test data set this was developed against, a library of ~14.000 tracks, went from needing ~75MB to ~17MB. (#348) - Added `mopidy.models.Artist.sortname` field that is mapped to `musicbrainz-sortname` tag. (#940) ### Configuration - Add new configurations to set base directories to be used by Mopidy and Mopidy extensions: `core/cache_dir`, `core/config_dir`, and `core/data_dir`. (#843, !1232) ### Extension support - Add new methods to `mopidy.ext.Extension` class for getting cache, config and data directories specific to your extension: - `mopidy.ext.Extension.get_cache_dir` - `mopidy.ext.Extension.get_config_dir` - `mopidy.ext.Extension.get_data_dir` Extensions should use these methods so that the correct directories are used both when Mopidy is run by a regular user and when run as a system service. (#843, !1232) - Add `mopidy.httpclient.format_proxy` and `mopidy.httpclient.format_user_agent`. (#1156) - It is now possible to import `mopidy.backends` without having GObject or GStreamer installed. In other words, a lot of backend extensions should now be able to run tests in a virtualenv with global site-packages disabled. This removes a lot of potential error sources. (#1068, !1115) ### Local backend - Filter out `None` from `mopidy.backend.LibraryProvider.get_distinct` results. All returned results should be strings. (#1202) ### Stream backend - Move stream playlist parsing from GStreamer to the stream backend. (#671) ### File backend The Mopidy-File backend is a new bundled backend. It is similar to Mopidy-Local since it works with local files, but it differs in a few key ways: - Mopidy-File lets you browse your media files by their file hierarchy. - It supports multiple media directories, all exposed under the "Files" directory when you browse your library with e.g. an MPD client. - There is no index of the media files, like the JSON or SQLite files used by Mopidy-Local. Thus no need to scan the music collection before starting Mopidy. Everything is read from the file system when needed and changes to the file system is thus immediately visible in Mopidy clients. - Because there is no index, there is no support for search. Our long term plan is to keep this very simple file backend in Mopidy, as it has a well defined and limited scope, while splitting the more feature rich Mopidy-Local extension out to an independent project. (#1004, !1207) ### M3U backend - Support loading UTF-8 encoded M3U files with the `.m3u8` file extension. (!1193) ### MPD frontend - The MPD command `count` now ignores tracks with no length, which would previously cause a `TypeError`. (!1192) - Concatenate multiple artists, composers and performers using the "A;B" format instead of "A, B". This is a part of updating our protocol implementation to match MPD 0.19. (!1213) - Add "not implemented" skeletons of new commands in the MPD protocol version 0.19: - Current playlist: - `rangeid` - `addtagid` - `cleartagid` - Mounts and neighbors: - `mount` - `unmount` - `listmounts` - `listneighbors` - Music DB: - `listfiles` - Track data now include the `Last-Modified` field if set on the track model. (#1218, !1219) - Implement `tagtypes` MPD command. (!1235) - Exclude empty tags fields from metadata output. (#1045, !1235) - Implement protocol extensions to output Album URIs and Album Images when outputting track data to clients. (!1230) - The MPD commands `lsinfo` and `listplaylists` are now implemented using the `mopidy.core.PlaylistsController.as_list` method, which retrieves a lot less data and is thus much faster than the deprecated `mopidy.core.PlaylistsController.get_playlists`. The drawback is that the `Last-Modified` timestamp is not available through this method, and the timestamps in the MPD command responses are now always set to the current time. ### Internal changes - Tests have been cleaned up to stop using deprecated APIs where feasible. (Partial fix: #1083, !1090) ## v1.0.8 (2015-07-22) Bug fix release. - Fix reversal of `Title` and `Name` in MPD protocol (#1212 !1214) - Fix crash if an M3U file in the `m3u/playlist_dir` directory has a file name not decodable with the current file system encoding. (#1209) ## v1.0.7 (2015-06-26) Bug fix release. - Fix error in the MPD command `list title ...`. The error was introduced in v1.0.6. ## v1.0.6 (2015-06-25) Bug fix release. - Core/MPD/Local: Add support for `title` in `mopidy.core.LibraryController.get_distinct`. (#1181, !1183) - Core: Make sure track changes make it to audio while paused. (#1177, !1185) ## v1.0.5 (2015-05-19) Bug fix release. - Core: Add workaround for playlist providers that do not support creating playlists. (#1162, !1165) - M3U: Fix encoding error when saving playlists with non-ASCII track titles. (#1175, !1176) ## v1.0.4 (2015-04-30) Bug fix release. - Audio: Since all previous attempts at tweaking the queuing for #1097 seems to break things in subtle ways for different users. We are giving up on tweaking the defaults and just going to live with a bit more lag on software volume changes. (#1147) ## v1.0.3 (2015-04-28) Bug fix release. - HTTP: Another follow-up to the Tornado <3.0 fixing. Since the tests aren't run for Tornado 2.3 we didn't catch that our previous fix wasn't sufficient. (#1153, !1154) - Audio: Follow-up fix for #1097 still exhibits issues for certain setups. We are giving this get an other go by setting the buffer size to maximum 100ms instead of a fixed number of buffers. (#1147, !1154) ## v1.0.2 (2015-04-27) Bug fix release. - HTTP: Make event broadcasts work with Tornado 2.3 again. The threading fix in v1.0.1 broke this. - Audio: Fix for #1097 tuned down the buffer size in the queue. Turns out this can cause distortions in certain cases. Give this an other go with a more generous buffer size. (#1147, !1152) - Audio: Make sure mute events get emitted by software mixer. (#1146, !1152) ## v1.0.1 (2015-04-23) Bug fix release. - Core: Make the new history controller available for use. (mopidy.js#6) - Audio: Software volume control has been reworked to greatly reduce the delay between changing the volume and the change taking effect. (#1097, !1101) - Audio: As a side effect of the previous bug fix, software volume is no longer tied to the PulseAudio application volume when using `pulsesink`. This behavior was confusing for many users and doesn't work well with the plans for multiple outputs. - Audio: Update scanner to decode all media it finds. This should fix cases where the scanner hangs on non-audio files like video. The scanner will now also let us know if we found any decodeable audio. (#726, !1124) - HTTP: Fix threading bug that would cause duplicate delivery of WS messages. (!1127) - MPD: Fix case where a playlist that is present in both browse and as a listed playlist breaks the MPD frontend protocol output. (#1120, !1142) ## v1.0.0 (2015-03-25) Three months after our fifth anniversary, Mopidy 1.0 is finally here! Since the release of 0.19, we've closed or merged approximately 140 issues and pull requests through more than 600 commits by a record high 19 extraordinary people, including seven newcomers. Thanks to everyone who has contributed! For the longest time, the focus of Mopidy 1.0 was to be another incremental improvement, to be numbered 0.20. The result is still very much an incremental improvement, with lots of small and larger improvements across Mopidy's functionality. The major features of Mopidy 1.0 are: - Semantic Versioning. We promise to not break APIs before Mopidy 2.0. A Mopidy extension working with Mopidy 1.0 should continue to work with all Mopidy 1.x releases. - Preparation work to ease migration to a cleaned up and leaner core API in Mopidy 2.0, and to give us some of the benefits of the cleaned up core API right away. - Preparation work to enable gapless playback in an upcoming 1.x release. ### Dependencies Since the previous release there are no changes to Mopidy's dependencies. However, porting from GStreamer 0.10 to 1.x and support for running Mopidy with Python 3.4+ is not far off on our roadmap. ### Core API In the API used by all frontends and web extensions there is lots of methods and arguments that are now deprecated in preparation for the next major release. With the exception of some internals that leaked out in the playback controller, no core APIs have been removed in this release. In other words, most clients should continue to work unchanged when upgrading to Mopidy 1.0. Though, it is strongly encouraged to review any use of the deprecated parts of the API as those parts will be removed in Mopidy 2.0. - **Deprecated:** Deprecate all Python properties in the core API. The previously undocumented getter and setter methods are now the official API. This aligns the Python API with the WebSocket/JavaScript API. Python frontends needs to be updated. WebSocket/JavaScript API users are not affected. (#952) - Add `mopidy.core.HistoryController` which keeps track of what tracks have been played. (#423, #1056, !803, #1063) - Add `mopidy.core.MixerController` which keeps track of volume and mute. (#962) #### Core library controller - **Deprecated:** `mopidy.core.LibraryController.find_exact`. Use `mopidy.core.LibraryController.search` with the `exact` keyword argument set to `True`. - **Deprecated:** The `uri` argument to `mopidy.core.LibraryController.lookup`. Use new `uris` keyword argument instead. - Add `exact` keyword argument to `mopidy.core.LibraryController.search`. - Add `uris` keyword argument to `mopidy.core.LibraryController.lookup` which allows for simpler lookup of multiple URIs. (#1008, !1047) - Updated `mopidy.core.LibraryController.search` and `mopidy.core.LibraryController.find_exact` to normalize and warn about malformed queries from clients. (#1067, !1073) - Add `mopidy.core.LibraryController.get_distinct` for getting unique values for a given field. (#913, !1022) - Add `mopidy.core.LibraryController.get_images` for looking up images for any URI that is known to the backends. (#973, !981, !992, !1013) #### Core playlist controller - **Deprecated:** `mopidy.core.PlaylistsController.get_playlists`. Use `mopidy.core.PlaylistsController.as_list` and `mopidy.core.PlaylistsController.get_items` instead. (#1057, !1075) - **Deprecated:** `mopidy.core.PlaylistsController.filter`. Use `mopidy.core.PlaylistsController.as_list` and filter yourself. - Add `mopidy.core.PlaylistsController.as_list`. (#1057, !1075) - Add `mopidy.core.PlaylistsController.get_items`. (#1057, !1075) #### Core tracklist controller - **Removed:** The following methods were documented as internal. They are now fully private and unavailable outside the core actor. (#1058, !1062) - `mopidy.core.TracklistController.mark_played` - `mopidy.core.TracklistController.mark_playing` - `mopidy.core.TracklistController.mark_unplayable` - Add `uris` argument to `mopidy.core.TracklistController.add` which allows for simpler addition of multiple URIs to the tracklist. (#1060, !1065) #### Core playback controller - **Removed:** Remove several internal parts that were leaking into the public API and was never intended to be used externally. (#1070, !1076) - `mopidy.core.PlaybackController.change_track` is now internal. - Removed `on_error_step` keyword argument from `mopidy.core.PlaybackController.play` - Removed `clear_current_track` keyword argument to `mopidy.core.PlaybackController.stop`. - Made the following event triggers internal: - `mopidy.core.PlaybackController.on_end_of_track` - `mopidy.core.PlaybackController.on_stream_changed` - `mopidy.core.PlaybackController.on_tracklist_changed` - `mopidy.core.PlaybackController.set_current_tl_track` is now internal. - **Deprecated:** The old methods on `mopidy.core.PlaybackController` for volume and mute management have been deprecated. Use `mopidy.core.MixerController` instead. (#962) - When seeking while paused, we no longer change to playing. (#939, !1018) - Changed `mopidy.core.PlaybackController.play` to take the return value from `mopidy.backend.PlaybackProvider.change_track` into account when determining the success of the `mopidy.core.PlaybackController.play` call. (!1071) - Add `mopidy.core.Listener.stream_title_changed` and `mopidy.core.PlaybackController.get_stream_title` for letting clients know about the current title in streams. (!938, #1030) ### Backend API In the API implemented by all backends there have been way fewer but somewhat more drastic changes with some methods removed and new ones being required for certain functionality to continue working. Most backends were already updated to be compatible with Mopidy 1.0 before the release. New versions of the backends will be released shortly after Mopidy itself. #### Backend library providers - **Removed:** Remove `mopidy.backend.LibraryProvider.find_exact`. - Add an `exact` keyword argument to `mopidy.backend.LibraryProvider.search` to replace the old `mopidy.backend.LibraryProvider.find_exact` method. #### Backend playlist providers - **Removed:** Remove default implementation of `mopidy.backend.PlaylistsProvider.playlists`. This is potentially backwards incompatible. (!1046) - Changed the API for `mopidy.backend.PlaylistsProvider`. Note that this change is **not** backwards compatible. These changes are important to reduce the Mopidy startup time. (#1057, !1075) - Add `mopidy.backend.PlaylistsProvider.as_list`. - Add `mopidy.backend.PlaylistsProvider.get_items`. - Remove `mopidy.backend.PlaylistsProvider.playlists` property. #### Backend playback providers - Changed the API for `mopidy.backend.PlaybackProvider`. Note that this change is **not** backwards compatible for certain backends. These changes are crucial to adding gapless in one of the upcoming releases. (#1052, !1064) - `mopidy.backend.PlaybackProvider.translate_uri` has been added. It is strongly recommended that all backends migrate to using this API for translating "Mopidy URIs" to real ones for playback. - The semantics and signature of `mopidy.backend.PlaybackProvider.play` has changed. The method is now only used to set the playback state to playing, and no longer takes a track. Backends must migrate to `mopidy.backend.PlaybackProvider.translate_uri` or `mopidy.backend.PlaybackProvider.change_track` to continue working. - `mopidy.backend.PlaybackProvider.prepare_change` has been added. ### Models - Add `mopidy.models.Image` model to be returned by `mopidy.core.LibraryController.get_images`. (#973) - Change the semantics of `mopidy.models.Track.last_modified` to be milliseconds instead of seconds since Unix epoch, or a simple counter, depending on the source of the track. This makes it match the semantics of `mopidy.models.Playlist.last_modified`. (#678, !1036) ### Commands - Make the `mopidy` command print a friendly error message if the `gobject` Python module cannot be imported. (#836) - Add support for repeating the :option:`-v ` argument four times to set the log level for all loggers to the lowest possible value, including log records at levels lower than `DEBUG` too. - Add path to the current `mopidy` executable to the output of `mopidy deps`. This make it easier to see that a user is using pip-installed Mopidy instead of APT-installed Mopidy without asking for `which mopidy` output. ### Configuration - Add support for the log level value `all` to the loglevels configurations. This can be used to show absolutely all log records, including those at custom levels below `DEBUG`. - Add debug logging of unknown sections. (#694, !1002) ### Logging - Add custom log level `TRACE` (numerical level 5), which can be used by Mopidy and extensions to log at an even more detailed level than `DEBUG`. - Add support for per logger color overrides. (#808, !1005) ### Local backend - Improve error logging for scanner. (#856, !874) - Add symlink support with loop protection to file finder. (#858, !874) - Add `--force` option for `mopidy local scan` for forcing a full rescan of the library. (#910, !1010) - Stop ignoring `offset` and `limit` in searches when using the default JSON backed local library. (#917, !949) - Removed double triggering of `playlists_loaded` event. (#998, !999) - Cleanup and refactoring of local playlist code. Preserves playlist names better and fixes bug in deletion of playlists. (#937, !995 and rebased into #1000) - Sort local playlists by name. (#1026, !1028) - Moved playlist support out to a new extension, ext-m3u. - *Deprecated:* The config value `local/playlists_dir` is no longer in use and can be removed from your config. #### Local library API - Implementors of `mopidy.local.Library.lookup` should now return a list of `mopidy.models.Track` instead of a single track, just like the other `lookup()` methods in Mopidy. For now, returning a single track will continue to work. (!840) - Add support for giving local libraries direct access to tags and duration. (#967) - Add `mopidy.local.Library.get_images` for looking up images for local URIs. (#1031, !1032 and #1037) ### Stream backend - Add support for HTTP proxies when doing initial metadata lookup for a stream. (#390, !982) - Add basic tests for the stream library provider. ### M3U backend - Mopidy-M3U is a new bundled backend. It provides the same M3U support as was previously part of the local backend. (#1054, !1066) - In playlist names, replace "/", which are illegal in M3U file names, with "|". (!1084) ### MPD frontend - Add support for blacklisting MPD commands. This is used to prevent clients from using `listall` and `listallinfo` which recursively lookup the entire "database". If you insist on using a client that needs these commands change `mpd/command_blacklist`. - Start setting the `Name` field with the stream title when listening to radio streams. (#944, !1030) - Enable browsing of artist references, in addition to albums and playlists. (!884) - Switch the `list` command over to using the new method `mopidy.core.LibraryController.get_distinct` for increased performance. (#913) - In stored playlist names, replace "/", which are illegal, with "|" instead of a whitespace. Pipes are more similar to forward slash. - Share a single mapping between names and URIs across all MPD sessions. (#934, !968) - Add support for `toggleoutput` command. (!1015) - The `mixrampdb` and `mixrampdelay` commands are now known to Mopidy, but are not implemented. (!1015) - Fix crash on socket error when using a locale causing the exception's error message to contain characters not in ASCII. (#971, !1044) ### HTTP frontend - **Deprecated:** Deprecated the `http/static_dir` config. Please make your web clients pip-installable Mopidy extensions to make it easier to install for end users. - Prevent a race condition in WebSocket event broadcasting from crashing the web server. (!1020) ### Mixers - Add support for disabling volume control in Mopidy entirely by setting the configuration `audio/mixer` to `none`. (#936, !1015, !1035) ### Audio - **Removed:** Support for visualizers and the `audio/visualizer` config value. The feature was originally added as a workaround for all the people asking for ncmpcpp visualizer support, and since we could get it almost for free thanks to GStreamer. But, this feature did never make sense for a server such as Mopidy. - **Deprecated:** Deprecated `mopidy.audio.Audio.emit_end_of_stream`. Pass a `None` buffer to `mopidy.audio.Audio.emit_data` to end the stream. This should only affect Mopidy-Spotify. - Add `mopidy.audio.AudioListener.tags_changed`. Notifies core when new tags are found. - Add `mopidy.audio.Audio.get_current_tags` for looking up the current tags of the playing media. - Internal code cleanup within audio subsystem: - Started splitting audio code into smaller better defined pieces. - Improved GStreamer related debug logging. - Provide better error messages for missing plugins. - Add foundation for trying to re-add multiple output support. - Add internal helper for converting GStreamer data types to Python. - Reduce scope of audio scanner to just find tags and duration. Modification time, URI and minimum length handling are now outside of this class. - Update scanner to operate with milliseconds for duration. - Update scanner to use a custom source, typefind and decodebin. This allows us to detect playlists before we try to decode them. - Refactored scanner to create a new pipeline per track, this is needed as resetting decodebin is much slower than tearing it down and making a fresh one. - Move and rename helper for converting tags to tracks. - Ignore albums without a name when converting tags to tracks. - Support UTF-8 in M3U playlists. (#853) - Add workaround for volume not persisting across tracks on OS X. (#886, !958) - Improved missing plugin error reporting in scanner. (!1033) - Introduced a new return type for the scanner, a named tuple with `uri`, `tags`, `duration`, `seekable` and `mime`. (!1033) - Added support for checking if the media is seekable, and getting the initial MIME type guess. (!1033) ### Mopidy.js client library This version has been released to npm as Mopidy.js v0.5.0. - Reexport When.js library as `Mopidy.when`, to make it easily available to users of Mopidy.js. (mopidy.js#1) - Default to `wss://` as the WebSocket protocol if the page is hosted on `https://`. This has no effect if the `webSocketUrl` setting is specified. (mopidy.js!2) - Upgrade dependencies. ### Development - Add new contribution guidelines. - Add new development guide. - Speed up event emitting. - Changed test runner from nose to py.test. (!1024) ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1789253942.6415482 mopidy-4.0.4/docs/changelog/2.x.md0000644000175100017510000004201415251354467016313 0ustar00runnerrunner# Changelog 2.x series This is the changelog of Mopidy v2.0.0 through v2.3.1. For the latest releases, see changelog. ## v2.3.1 (2019-10-15) Bug fix release. - Dependencies: Lower requirement for Tornado from `>= 5, < 6` to `>= 4.4, < 6`. Our HTTP server implementation works with Tornado 4 as well, which is the latest version that is packaged on Ubuntu 18.04 LTS. ## v2.3.0 (2019-10-02) Mopidy 2.3.0 is mostly a bug fix release. Because we're requiring a new major version of Tornado, we're doing a minor version bump of Mopidy. - Dependencies: Support and require Tornado >= 5, < 6, as that is the latest version support Python 2.7 and currently the oldest version shipped by Debian and Arch. (#1798, !1796) - Fix `PkgResourcesDeprecationWarning` on startup when a recent release of setuptools is installed. (#1778, !1780) - Network: Close connection following an exception in the protocol handler. (#1762, !1765) - Network: Log client's connection details instead of server's. This fixed a regression introduced as part of !1629. (#1788, !1792) - Core: Trigger `mopidy.core.CoreListener.stream_title_changed` event on receiving a `title` audio tag that differs from the current track's `mopidy.models.Track.name`. (#1746, !1751) - Stream: Support playlists containing relative URIs. (#1785, !1802) - Stream: Fix crash when unwrapping stream without MIME type. (#1760, !1800) - MPD: Add support for seeking to time positions with float point precision. (#1756, !1801) - MPD: Handle URIs containing non-ASCII characters. (#1759, !1805, #1808) ## v2.2.3 (2019-06-20) Bug fix release. - Audio: Fix switching between tracks with different sample rates. (#1528, !1735) - Audio: Prevent buffering handling interfering with track changes. (#1722, !1740) - Local: Add `.pdf` and `.zip` to the default `local/excluded_file_extensions` config value. (!1737) - File: Synchronised the default `file/excluded_file_extensions` config values with `local/excluded_file_extensions`. (!1743) - Stream: Fix error when playing stream from `.pls` playlist with quoted URLs. (#1770, !1771) - Docs: Resize and compress images, reducing the release tarball size from 3.5 to 1.1 MB. - Docs: Fix broken links. ## v2.2.2 (2018-12-29) Bug fix release. - HTTP: Fix hang on exit due to change in Tornado v5.0 IOLoop. (#1715, !1716) - Files: Fix crash due to mix of text and bytes in paths that come from `$XDG_CONFIG_HOME/user-dirs.dirs`. (#1676, #1725) ## v2.2.1 (2018-10-15) Bug fix release. - HTTP: Stop blocking connections where the network location part of the `Origin` header is empty, such as WebSocket connections originating from local files. (#1711, !1712) - HTTP: Add new config value `http/csrf_protection` which enables all CSRF protections introduced in Mopidy 2.2.0. It is enabled by default and should only be disabled by those users who are unable to set a `Content-Type: application/json` request header or cannot utilise the `http/allowed_origins` config value. (#1713, !1714) ## v2.2.0 (2018-09-30) Mopidy 2.2.0, a feature release, is out. It is a quite small release, featuring mostly minor fixes and improvements. Most notably, this release introduces CSRF protection for both the HTTP and WebSocket RPC interfaces, and improves the file path checking in the M3U backend. The CSRF protection should stop attacks against local Mopidy servers from malicious websites, like what was demonstrated by Josef Gajdusek in #1659 Since the release of 2.1.0, we've closed approximately 21 issues and pull requests through 133 commits by 22 authors. - Dependencies: Drop support for Tornado < 4.4. Though strictly a breaking change, this shouldn't affect any supported systems as even Debian stable includes Tornado >= 4.4. - Core: Remove upper limit of 10000 tracks in tracklist. 10000 tracks is still the default limit as some MPD clients crash if the tracklist is longer, but it is now possible to set the `core/max_tracklist_length` config value as high as you want to. (#1600, !1666) - Core: Fix crash on `library.lookup(uris=[])`. (#1619, !1620) - Core: Define return value of `playlists.delete()` to be a bool, `True` on success, `False` otherwise. (!1702) - M3U: Ignore all attempts at accessing files outside the `m3u/playlist_dir`. (Partial: #1659, !1702) - File: Change default ordering to show directories first, then files. (!1595) - File: Fix extraneous encoding of path. (!1611) - HTTP: Protect RPC and WebSocket interfaces against CSRF by blocking requests that originate from servers other than those specified in the new config value `http/allowed_origins`. An artifact of this is that all JSON-RPC requests must now always set the header `Content-Type: application/json`. (Partial: #1659, !1668) - MPD: Added `idle` to the list of available commands. (#1593, !1597) - MPD: Added Unix domain sockets for binding MPD to. (#1531, !1629) - MPD: Lookup track metadata for MPD `load` and `listplaylistinfo`. (#1511, !1621) - Ensure that decoding of OS errors with unknown encoding never crashes, but instead replaces unknown bytes with a replacement marker. (#1599) - Set GLib program and application name, so that we show up as "Mopidy" in PulseAudio instead of "python ...". (!1626) ## v2.1.0 (2017-01-02) Mopidy 2.1.0, a feature release, is finally out! Since the release of 2.0.0, it has been quiet times in Mopidy circles. This is mainly caused by core developers moving from the enterprise to startups or into positions with more responsibility, and getting more kids. Of course, this has greatly decreased the amount of spare time available for open source work. But fear not, Mopidy is not dead. We've returned from year long periods with close to no activity before, and will hopefully do so again. Despite all, we've closed or merged approximately 18 issues and pull requests through about 170 commits since the release of v2.0.1 back in August. The major new feature in Mopidy 2.1 is support for restoring playback state and the current playlist after a restart. This feature was contributed by [Jens Lütjen](https://github.com/dublok). - Dependencies: Drop support for Tornado < 3.2. Though strictly a breaking change, this shouldn't have any effect on what systems we support, as Tornado 3.2 or newer is available from the distros that include GStreamer >= 1.2.3, which we already require. - Core: Mopidy restores its last state when started. Can be enabled by setting the config value `core/restore_state` to `true`. - Audio: Update scanner to handle sources such as RTSP. (#1479) - Audio: The scanner set the date to `mopidy.models.Track.date` and `mopidy.models.Album.date` (#1741) - File: Add new config value `file/excluded_file_extensions`. - Local: Skip hidden directories directly in `media_dir`. (#1559, !1555) - MPD: Fix MPD protocol for `replay_gain_status` command. The actual command remains unimplemented. (!1520) - MPD: Add `nextsong` and `nextsongid` to the response of MPD `status` command. (#1133, #1516, !1523) - MPD: Fix inconsistent playlist state after playlist is emptied with repeat and consume mode turned on. (#1512, !1549) - Audio: Improve handling of duration in scanning. VBR tracks should fail less frequently and MMS works again. (#1553, !1575, !1576, !1577) ## v2.0.1 (2016-08-16) Bug fix release. - Audio: Set `soft-volume` flag on GStreamer's playbin element. This is the playbin's default, but we managed to override it when configuring the playbin to only process audio. This should fix the "Volume/mute is not available" warning. - Audio: Fix buffer conversion. This fixes image extraction. (#1469, !1472) - Audio: Update scan logic to workaround GStreamer issue where tags and duration might only be available after we start playing. (#935, #1453, #1474, #1480, !1487) - Audio: Better handling of seek when position does not match the expected pending position. (#1462, #1505, !1496) - Audio: Handle bad date tags from audio, thanks to Mario Lang and Tom Parker who fixed this in parallel. (#1506, !1525, #1517) - Audio: Make sure scanner handles streams without a duration. (#1526) - Audio: Ensure audio tags are never `None`. (#1449) - Audio: Update `mopidy.audio.Audio.set_metadata` to postpone sending tags if there is a pending track change. (#1357, !1538) - Core: Avoid endless loop if all tracks in the tracklist are unplayable and consume mode is off. (#1221, #1454, !1455) - Core: Correctly record the last position of a track when switching to another one. Particularly relevant for Mopidy-Scrobbler users, as before it was essentially unusable. (#1456, !1534) - Models: Fix encoding error if `mopidy.models.fields.Identifier` fields, like the `musicbrainz_id` model fields, contained non-ASCII Unicode data. (#1508, !1546) - File: Ensure path comparison is done between bytestrings only. crash where a `file/media_dirs` path contained non-ASCII characters. (#1345, !1493) - Stream: Fix milliseconds vs seconds mistake in timeout handling. (#1521, !1522) - Docs: Fix the rendering of `mopidy.core.Core` and `mopidy.audio.Audio` docs. This should also contribute towards making the Mopidy Debian package build bit-by-bit reproducible. (#1500) ## v2.0.0 (2016-02-15) Mopidy 2.0 is here! Since the release of 1.1, we've closed or merged approximately 80 issues and pull requests through about 350 commits by 14 extraordinary people, including 10 newcomers. That's about the same amount of issues and commits as between 1.0 and 1.1. The number of contributors is a bit lower but we didn't have a real life sprint during this development cycle. Thanks to everyone who has contributed! With the release of Mopidy 1.0 we promised that any extension working with Mopidy 1.0 should continue working with all Mopidy 1.x releases. Mopidy 2.0 is quite a friendly major release and will only break a single extension that we know of: Mopidy-Spotify. To ensure that everything continues working, please upgrade to Mopidy 2.0 and Mopidy-Spotify 3.0 at the same time. No deprecated functionality has been removed in Mopidy 2.0. The major features of Mopidy 2.0 are: - Gapless playback has been mostly implemented. It works as long as you don't change tracks in the middle of a track or use previous and next. In a future release, previous and next will also become gapless. It is now quite easy to have Mopidy streaming audio over the network using Icecast. See the updated icecast docs for details of how to set it up and workarounds for the remaining issues. - Mopidy has upgraded from GStreamer 0.10 to 1.x. This has been in our backlog for more than three years. With this upgrade we're ridding ourselves of years of GStreamer bugs that have been fixed in newer releases, we can get into Debian testing again, and we've removed the last major roadblock for running Mopidy on Python 3. ### Dependencies - Mopidy now requires GStreamer >= 1.2.3, as we've finally ported from GStreamer 0.10. Since we're requiring a new major version of our major dependency, we're upping the major version of Mopidy too. (#225) ### Core API - Start `tlid` counting at 1 instead of 0 to keep in sync with MPD's `songid`. - `mopidy.core.PlaybackController.get_time_position` now returns the seek target while a seek is in progress. This gives better results than just failing the position query. (#312 !1346) - Add `mopidy.core.PlaylistsController.get_uri_schemes`. (!1362) - The `track_playback_ended` event now includes the correct `tl_track` reference when changing to the next track in consume mode. (#1402 !1403 !1406) ### Models - **Deprecated:** `mopidy.models.Album.images` is deprecated. Use `mopidy.core.LibraryController.get_images` instead. (#1325) ### Extension support - Log exception and continue if an extension crashes during setup. Previously, we let Mopidy crash if an extension's setup crashed. (!1337) ### Local backend - Made `local/data_dir` really deprecated. This change breaks older versions of Mopidy-Local-SQLite and Mopidy-Local-Images. ### M3U backend - Add `m3u/base_dir` for resolving relative paths in M3U files. (#1428, !1442) - Derive track name from file name for non-extended M3U playlists. (#1364, !1369) - Major refactoring of the M3U playlist extension. (#1370, !1386) - Add `m3u/default_encoding` and `m3u/default_extension` config values for improved text encoding support. - No longer scan playlist directory and parse playlists at startup or refresh. Similarly tothe file extension, this now happens on request. - Use `mopidy.models.Ref` instances when reading and writing playlists. Therefore, `Track.length` is no longer stored in extended M3U playlists and `#EXTINF` runtime is always set to -1. - Improve reliability of playlist updates using the core playlist API by applying the write-replace pattern for file updates. ### Stream backend - Make sure both lookup and playback correctly handle playlists and our blacklist support. (#1445, !1447) ### MPD frontend - Implemented commands for modifying stored playlists: - `playlistadd` - `playlistclear` - `playlistdelete` - `playlistmove` - `rename` - `rm` - `save` (#1014, !1187, #1308, #1322) - Start `songid` counting at 1 instead of 0 to match the original MPD server. - Idle events are now emitted on `seeked` events. This fix means that clients relying on `idle` events now get notified about seeks. (#1331, !1347) - Idle events are now emitted on `playlists_loaded` events. This fix means that clients relying on `idle` events now get notified about playlist loads. (#1331, !1347) - Event handler for `playlist_deleted` has been unbroken. This unreported bug would cause the MPD frontend to crash preventing any further communication via the MPD protocol. (!1347) ### Zeroconf - Require `stype` argument to `mopidy.zeroconf.Zeroconf`. - Use Avahi's interface selection by default. (#1283) - Use Avahi server's hostname instead of `socket.getfqdn()` in service display name. ### Cleanups - Removed warning if :file:`~/.mopidy` exists. We stopped using this location in 0.6, released in October 2011. - Removed warning if :file:`~/.config/mopidy/settings.py` exists. We stopped using this settings file in 0.14, released in April 2013. - The `on_event` handler in our listener helper now catches exceptions. This means that any errors in event handling won't crash the actor in question. - Catch errors when loading `logging/config_file`. (#1320) - **Breaking:** Removed unused internal `mopidy.internal.process.BaseThread`. This breaks Mopidy-Spotify 1.4.0. Versions < 1.4.0 was already broken by Mopidy 1.1, while versions >= 2.0 doesn't use this class. ### Audio - **Breaking:** The audio scanner now returns ISO-8601 formatted strings instead of `datetime.datetime` objects for dates found in tags. Because of this change, we can now return years without months or days, which matches the semantics of the date fields in our data models. - **Breaking:** `mopidy.audio.Audio.set_appsrc`'s `caps` argument has changed format due to the upgrade from GStreamer 0.10 to GStreamer 1. As far as we know, this is only used by Mopidy-Spotify. As an example, with GStreamer 0.10 the Mopidy-Spotify caps was: ```text audio/x-raw-int, endianness=(int)1234, channels=(int)2, width=(int)16, depth=(int)16, signed=(boolean)true, rate=(int)44100 ``` With GStreamer 1 this changes to: ```text audio/x-raw,format=S16LE,rate=44100,channels=2,layout=interleaved ``` If your Mopidy backend uses `set_appsrc()`, please refer to GStreamer documentation for details on the new caps string format. - **Breaking:** `mopidy.audio.utils.create_buffer`'s `capabilities` argument is no longer in use and has been removed. As far as we know, this was only used by Mopidy-Spotify. - Duplicate seek events getting to `appsrc` based backends is now fixed. This should prevent seeking in Mopidy-Spotify from glitching. (#1404) - Workaround crash caused by a race that does not seem to affect functionality. This should be fixed properly together with #1222. (#1430, !1438) - Add a new config option, `audio/buffer_time`, for setting the buffer time of the GStreamer queue. If you experience buffering before track changes, it may help to increase this. (#1409) - `tags_changed` events are only emitted for fields that have changed. Previous behavior was to emit this for all fields received from GStreamer. (!1439) ### Gapless - Add partial support for gapless playback. Gapless now works as long as you don't change tracks or use next/previous. (!1288) The icecast docs has been updated with the workarounds still needed to properly stream Mopidy audio through Icecast. - Core playback has been refactored to better handle gapless, and async state changes. - Tests have been updated to always use a core actor so async state changes don't trip us up. - Seek events are now triggered when the seek completes. Previously the event was emitted when the seek was requested, not when it completed. Further changes have been made to make seek work correctly for gapless related corner cases. (#1305 !1346) ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1789253942.6415482 mopidy-4.0.4/docs/changelog/3.x.md0000644000175100017510000003432515251354467016322 0ustar00runnerrunner# Changelog 3.x series This is the changelog of Mopidy v3.0.0 through v3.4.2. For the latest releases, see changelog. ## v3.4.2 (2023-11-01) - Deps: Python 3.11 and 3.12 are now included in the testing matrix. - M3U: Stop following symlinks when `file/follow_symlinks` is false. (!2094) - zeroconf: Fix exception on shutdown if `dbus` is not installed. - Docs: Fix crash when building docs on recent Sphinx versions. - Dev: Make stacktraces from deprecation warnings include the offending call site, to help upgrade API usage in extensions. - Dev: Upgrade CI workflows to fix Node.js 12 deprecation notices and avoid Codecov's bash uploader. - Dev: Make tests pass on macOS. (!2092) - Dev: Incease test coverage of Mopidy-File to 100%. (!2096) - Dev: Added `"tox -e ci`", to allow easy CI check before `git push`. ## v3.4.1 (2022-12-07) - HTTP: Fix non-optional `http/allowed_origins` config setting. (!2066) ## v3.4.0 (2022-11-28) - Config: Handle DBus "Algorithm plain is not supported" error. (!2061) - File: Fix uppercase `file/excluded_file_extensions`. (!2063) - Add `mopidy.backend.PlaybackProvider.on_source_setup` which can be implemented by Backend playback providers that want to set GStreamer source properties in the `source-setup` callback. (!2060) - HTTP: Improve handling of `http/allowed_origins` config setting. (!2054) ## v3.3.0 (2022-04-29) - Core: invalid verbosity logging levels. (#1947, !2021) - Core: Fix TypeError exception when playing track with unnamed artists. (#1991, !2012) - Core: Fix startup crash when loading invalid extensions. (!1990) - Core: Fix error-handling when fetching backend support info. (!1964) - Core: Align values supported by the `field` argument to `mopidy.core.LibraryController.get_distinct` with Mopidy search query fields, with the exception of 'any'. Deprecated field 'track' with the goal of removing it in the next major release, use 'track_name' instead. Backends should support both `track` and `track_name` until they require a version of Mopidy where `track` has been removed. (#1900, !1899) - Core: Add `musicbrainz_albumid`, `musicbrainz_artistid`, `musicbrainz_trackid`, and `disc_no` to the permitted search query fields. (#1900, !1899) - Audio: Fix TypeError when handling create output pipeline errors. (#1924, !2040) - Audio: Fix seek when stopped. (#2005, !2006) - Config: Fix support for inline comments, a regression introduced during our Python 3 migration. (#1868, !2041) - HTTP: Fix missing CORS headers on RPC response. (#2028, !2029) - HTTP: Improve CSRF protection Content-Type check. (!1997) - HTTP: Fix support for websocket clients connecting/disconnecting during broadcast. (!1993) - Add Python 3.10 to our test matrix. - Core: Added and improved configuration parsing code for extension developers. (!2010) ## v3.2.0 (2021-07-08) - Initial type annotations and mypy support. (!1842) - Move CI to GitHub Actions (!1951) - Fix logging during extension loading (#1958, !1960) - Fix appsrc track change after live-mode previously set. (#1969, !1971) ## v3.1.1 (2020-12-26) - Fix crash when extracting tags using gst-python >= 1.18. (!1948) ## v3.1.0 (2020-12-16) - Add Python 3.9 to our test matrix. - Add `mopidy.backend.PlaybackProvider.should_download` which can be implemented by playback providers that want to use GStreamer's download buffering strategy for their URIs. (!1888) - Audio: Fix memory leak when converting GStreamer `sample` type tags. (#1827, !1929) - Turn off strict parsing of `*.pls` playlist files. This was a regression that happened during the migration to Python 3. (!1923) - Make the systemd unit that ships with Mopidy wait for an Internet connection before starting Mopidy. When used by distribution packages, this can help avoid that extensions try to connect to cloud services before the machine's Internet connection is ready for use. (!1946) ## v3.0.2 (2020-04-02) Bugfix release. - Core: Reset stream title on receipt of any `title` audio tag change. (#1871, !1875) - Core: Hide the methods `mopidy.core.Core.setup` and `mopidy.core.Core.teardown` from other actors and JSON-RPC API clients. The methods have always been clearly documented as internal. (!1865) - Config: Log a warning if unknown config sections are found. (#1878, !1890) - Config: Fix crash when reading values from keyring. (!1887) - Various documentation updates. ## v3.0.1 (2019-12-22) Bugfix release. - Remove `mopidy.local` migration helper. (#1861, !1862) ## v3.0.0 (2019-12-22) The long-awaited Mopidy 3.0 is finally here, just in time for the Mopidy project's 10th anniversary on December 23rd! Mopidy 3.0 is a backward-incompatible release in a pretty significant way: Mopidy no longer runs on Python 2. **Mopidy 3.0 requires Python 3.7 or newer.** While extensions have been able to continue working without changes throughout the 1.x and 2.x series of Mopidy, this time is different: - All extensions must be updated to work on Python 3.7 and newer. - Some extensions need to replace their use of a few long-deprecated APIs that we've removed. See below for details. - Extension maintainers are also encouraged to update their project's setup to match our refreshed [extension cookiecutter](https://github.com/mopidy/cookiecutter-mopidy-ext). In parallel with the development of Mopidy 3.0, we've coordinated with a few extension maintainers and upgraded almost 20 of the most popular extensions. These will all be published shortly after the release of Mopidy 3.0. We've also built a new [extension registry](https://mopidy.com/ext/), where you can quickly track what extensions are ready for Python 3. In other news, the [Mopidy-MPD](https://mopidy.com/ext/mpd/) and [Mopidy-Local](https://mopidy.com/ext/local/) extensions have grown up and moved out to flourish as independent extension projects. After the move, Mopidy-Local merged with Mopidy-Local-SQLite and Mopidy-Local-Images, which are now both a part of the Mopidy-Local extension. ### Dependencies - Python >= 3.7 is now required. Python 2.7 is no longer supported. - GStreamer >= 1.14.0 is now required. - Pykka >= 2.0.1 is now required. - Tornado >= 4.4 is now required. The upper boundary (< 6) has been removed. - We now use a number of constants and functions from `GLib` instead of their deprecated equivalents in `GObject`. The exact version of PyGObject and GLib that makes these constants and functions available in the new location is not known, but is believed to have been released in 2015 or earlier. ### Logging - The command line option `mopidy --save-debug-log` and the configuration `logging/debug_file` have been removed. To save a debug log for sharing, run `mopidy -vvvv 2>&1 | tee mopidy.log` or equivalent. (#1452, !1783) - Replaced the configurations `logging/console_format` and `logging/debug_format` with the single configuration `logging/format`. It defaults to the same format as the old debug format. (#1452, !1783) - Added configuration `logging/verbosity` to be able to control logging verbosity from the configuration file, in addition to passing `-q` or `-v` on the command line. (#1452, !1783) ### Core API - Removed properties, methods, and arguments that have been deprecated since 1.0, released in 2015. Everything removed already has a replacement, that should be used instead. See below for a full list of removals and replacements. (#1083, #1461, !1768, #1769) #### Root object - Removed properties, use getter/setter instead: - `mopidy.core.Core.uri_schemes` - `mopidy.core.Core.version` #### Library controller - Removed methods: - `mopidy.core.LibraryController.find_exact`: Use `mopidy.core.LibraryController.search` with the keyword argument `exact=True` instead. - Removed the `uri` argument to `mopidy.core.LibraryController.lookup`. Use the `uris` argument instead. - Removed the support for passing the search query as keyword arguments to `mopidy.core.LibraryController.search`. Use the `query` argument instead. - `mopidy.core.LibraryController.search` now returns an empty result if there is no `query`. Previously, it returned the full music library. This is not feasible for online music services and has thus been deprecated since 1.0. #### Playback controller - Removed properties, use getter/setter instead: - `mopidy.core.PlaybackController.current_tl_track` - `mopidy.core.PlaybackController.current_track` - `mopidy.core.PlaybackController.state` - `mopidy.core.PlaybackController.time_position` - Moved to the mixer controller: - `mopidy.core.PlaybackController.get_mute`: Use `mopidy.core.MixerController.get_mute`. - `mopidy.core.PlaybackController.get_volume`: Use `mopidy.core.MixerController.get_volume`. - `mopidy.core.PlaybackController.set_mute`: Use `mopidy.core.MixerController.set_mute`. - `mopidy.core.PlaybackController.set_volume`: Use `mopidy.core.MixerController.set_volume`. - `mopidy.core.PlaybackController.mute`: Use `mopidy.core.MixerController.get_mute` and `mopidy.core.MixerController.set_mute`. - `mopidy.core.PlaybackController.volume`: Use `mopidy.core.MixerController.get_volume` and `mopidy.core.MixerController.set_volume`. - Deprecated the `tl_track` argument to `mopidy.core.PlaybackController.play`, with the goal of removing it in the next major release. Use the `tlid` argument instead. (#1773, !1786, #1854) #### Playlist controller - Removed properties, use getter/setter instead: - `mopidy.core.PlaylistController.playlists` - Removed methods: - `mopidy.core.PlaylistsController.filter`: Use `mopidy.core.PlaylistsController.as_list` and filter yourself. - `mopidy.core.PlaylistsController.get_playlists`: Use `mopidy.core.PlaylistsController.as_list` and `mopidy.core.PlaylistsController.get_items`. #### Tracklist controller - Removed properties, use getter/setter instead: - `mopidy.core.TracklistController.tl_tracks` - `mopidy.core.TracklistController.tracks` - `mopidy.core.TracklistController.length` - `mopidy.core.TracklistController.version` - `mopidy.core.TracklistController.consume` - `mopidy.core.TracklistController.random` - `mopidy.core.TracklistController.repeat` - `mopidy.core.TracklistController.single` - Removed the `uri` argument to `mopidy.core.TracklistController.add`. Use the `uris` argument instead. - Removed the support for passing filter criteria as keyword arguments to `mopidy.core.TracklistController.filter`. Use the `criteria` argument instead. - Removed the support for passing filter criteria as keyword arguments to `mopidy.core.TracklistController.remove`. Use the `criteria` argument instead. - Deprecated methods, with the goal of removing them in the next major release: (#1773, !1786, #1854) - `mopidy.core.TracklistController.eot_track`. Use `mopidy.core.TracklistController.get_eot_tlid` instead. - `mopidy.core.TracklistController.next_track`. Use `mopidy.core.TracklistController.get_next_tlid` instead. - `mopidy.core.TracklistController.previous_track`. Use `mopidy.core.TracklistController.get_previous_tlid` instead. - The `tracks` argument to `mopidy.core.TracklistController.add` has been deprecated since Mopidy 1.0. It is still deprecated, with the goal of removing it in the next major release. Use the `uris` argument instead. ### Backend API - Add `mopidy.backend.PlaybackProvider.is_live` which can be implemented by playback providers that want to mark their URIs as live streams that should not be buffered. (!1845) ### Models - Remove `.copy()` method on all model classes. Use the `.replace()` method instead. (#1464, !1774) - Remove `mopidy.models.Album.images`. Clients should use `mopidy.core.LibraryController.get_images` instead. Backends should implement `mopidy.backend.LibraryProvider.get_images`. (#1464, !1774) ### Extension support - The following methods now return `pathlib.Path` objects instead of strings: - `mopidy.ext.Extension.get_cache_dir` - `mopidy.ext.Extension.get_config_dir` - `mopidy.ext.Extension.get_data_dir` This makes it easier to support arbitrary encoding in file names. - The command `mopidy deps` no longer repeats the dependencies of Mopidy itself for every installed extension. This reduces the length of the command's output drastically. (!1846) ### HTTP frontend - Stop bundling Mopidy.js and serving it at `/mopidy/mopidy.js` and `/mopidy/mopidy.min.js`. All Mopidy web clients must use Mopidy.js from npm or vendor their own copy of the library. (#1083, #1460, !1708) - Remove support for serving arbitrary files over HTTP through the use of `http/static_dir`, which has been deprecated since 1.0. (#1463, !1706) - Add option `http/default_app` to redirect from web server root to a specific app instead of Mopidy's web app list. (!1791) - Add cookie secret to Tornado web server, allowing Tornado request handlers to call `get_secure_cookie()`, in an implementation of `get_current_user()`. (!1801) ### MPD frontend - The Mopidy-MPD frontend is no longer bundled with Mopidy, and has been moved to its own [Git repo](https://github.com/mopidy/mopidy-mpd) and [PyPI project](https://pypi.org/project/Mopidy-MPD). ### Local backend - The Mopidy-Local backend is no longer bundled with Mopidy, and has been moved to its own [Git repo](https://github.com/mopidy/mopidy-local) and [PyPI project](https://pypi.org/project/Mopidy-Local). (#1003) - Removed `mopidy.exceptions.FindError`, as it was only used by Mopidy-Local. (!1857) ### Audio - Remove the method `mopidy.audio.Audio.emit_end_of_stream`, which has been deprecated since 1.0. (#1465, !1705) - Add `live_stream` option to `mopidy.audio.Audio.set_uri` that disables buffering, which reduces latency before playback starts, and discards data when paused. (!1845) ### Internals - Format code with Black. (!1834) - Port test assertions from `unittest` methods to pytest `assert` statements. (!1838) - Switch all internal path handling to use `pathlib`. (#1744, !1814) - Remove `mopidy.compat` and all Python 2/3 compatibility code. (!1833, !1835) - Replace `requirements.txt` and `setup.py` with declarative config in `setup.cfg`. (!1839) - Refreshed and updated all of our end user-oriented documentation. ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1789253942.6415482 mopidy-4.0.4/docs/changelog/index.md0000644000175100017510000004127515251354467017023 0ustar00runnerrunner# Changelog This changelog is used to track all major changes to Mopidy. For older releases, see: [Changelog 3.x](3.x.md) · [Changelog 2.x](2.x.md) · [Changelog 1.x](1.x.md) · [Changelog 0.x](0.x.md) ## v4.0.4 (2026-09-13) - Deps: Fix support for Cyclopts 3.12, the version in Debian stable. Cyclopts divides by the token count of the inner type when it converts a tuple, and a `bool` has no tokens, so `mopidy --verbose` stopped with a `ZeroDivisionError`. The systemd service passes `--verbose`, so Mopidy could not start at all on Debian 13. The parameter is a list now, which works with all supported Cyclopts versions. (!2300) - Docs: Install Mopidy-Spotify from the APT repository in the Debian guide, now that the extension is packaged there. (!2299) - Packaging: Add `Keywords` to the desktop file and `Documentation` to the systemd service, as Lintian asks for. ## v4.0.3 (2026-09-06) - Config: Use `platformdirs` to find the XDG user directories that path config values expand to. Values set in `$XDG_CONFIG_HOME/user-dirs.dirs` are kept. Directories that are not set there, such as `$XDG_MUSIC_DIR`, now fall back to the standard location, like `~/Music`, instead of staying unexpanded. (!2272) - Audio: Fix scanning of valid audio files. `Gst.Structure` has a `__len__()` method, so an empty structure is falsy. The truthiness checks in the scanner were replaced by explicit `is not None` tests. (#2278, !2280) - HTTP extension: Fix missing web client files in the wheel builds. The `package-data` config was not updated when the bundled extensions moved to `mopidy._exts`. (#2292, !2293) - HTTP extension: Fix `core.describe()` so that it lists all params of a method. Keyword-only params, like `at_position` and `uris` on [`core.tracklist.add()`][mopidy.core.TracklistController.add], were left out. Deprecated methods were described with the `*args, **kwargs` of the wrapper added by the `deprecated` decorator instead of their own params. (#2284, !2296) - HTTP extension: Set the `Access-Control-Max-Age` header on CORS preflight responses. Browsers now cache the preflight result for two hours, instead of sending a preflight request before every cross-origin request. (#2043, !2285) - Stream extension: Fix crash when a PLS playlist has a missing or invalid `NumberOfEntries`. The parser now ignores the declared count and uses the `File` keys that the playlist has, in numeric order. Entries that a too low count left out are now included. (!2276) - Stream extension: Skip entries with an empty value in PLS and ASX reference playlists. An empty first entry resolved to the playlist URI itself, which stopped the unwrapping and lost the valid entries after it. (!2276) - Docs: Update the macOS install instructions for current Homebrew. (!2291) - Dev: Add Python 3.15 to the test matrix, in tox and in CI. (!2297) - Dev: Fix the scanner test for plain text files on GStreamer >= 1.28.5. That version types `.txt` files as `application/x-subtitle` by extension, so the scan no longer fails. The test now asserts that plain text is not playable. (!2294) ## v4.0.2 (2026-08-19) - Models: The `musicbrainz_id` fields on [`Album`][mopidy.models.Album], [`Artist`][mopidy.models.Artist], and [`Track`][mopidy.models.Track] are again typed as `str` instead of `UUID`, like in Mopidy < 4.0, to allow scanning collections using alternative identifiers, or multiple identifiers in the same field. (!2283) - Models: The `date` field on [`Album`][mopidy.models.Album] and [`Track`][mopidy.models.Track] now also accepts the `YYYY-MM` format, in addition to `YYYY` and `YYYY-MM-DD`. GStreamer emits `YYYY-MM` for files that only have a year and a month in their date tag, and such files were previously rejected. (!2283) - Types: Deprecated `mopidy.types.Date`, `mopidy.types.Year`, and `mopidy.types.DateOrYear`. They are replaced by a single [`mopidy.types.ReleaseDate`][mopidy.types.ReleaseDate] type, covering all three supported date formats. The old names remain importable, but they are now `str` subclasses instead of `NewType`s, so they are not assignable to `ReleaseDate` and using them will fail type checking. At runtime they still behave as strings, and instantiating them emits a `DeprecationWarning`. They will be removed in a future release. (!2283) - Audio: [`convert_tags_to_track()`][mopidy.audio.tags.convert_tags_to_track] now raises [`ScannerError`][mopidy.exceptions.ScannerError] if the tags can't be coerced into a valid [`Track`][mopidy.models.Track]. The `file` and `stream` backends catch this per URI and fall back to a track with just the URI, so that a single file with broken tags no longer fails the lookup of every other URI in the same batch. (!2283) - File extension: Strip whitespace from `local/media_dirs` config options. (#1965, !2275) - Stream extension: Support legacy ASX playlists. (#794, !2281) ## v4.0.1 (2026-05-16) - Deps: Fix support for Cyclopts >= 3.12, < 4.3. We accidentally relied on features not present in the minimum supported version, 3.12. (!2257, !2261) - Deps: Require Tornado >= 6.4.2, as this is the exact version in Debian stable and we rely on testing improvements that landed in 6.4.1. (!2260) - Core API type hints: Change `LibraryProvider` methods to accept `Iterable` instead of `list`. (!2255) - Dev: Add tox env for running tests on lowest supported versions of deps (!2259) ## v4.0.0 (2026-04-25) Mopidy 4.0 is a major release bringing very few functional changes, but mostly focusing on modernizations to Mopidy's tech stack to keep it maintainable going into the future. In Mopidy 4.0 we've: - dropped support for old versions of our dependencies, - rebuilt data models using Pydantic, so data is validated and errors are caught at the edges of the application instead of on use, - removed a few long-deprecated APIs, - rebuilt the app startup sequence, - made many modules explicitly private, - added type hints to most of the source code, and - rebuilt our docs using Zensical. ### Dependencies We require newer versions of all of our dependencies. The versions we require are all available in Debian 13 Trixie, which is the latest Debian stable release at the time of writing. - Python >= 3.13 - Support for 3.7-3.12 has been dropped. Python 3.13-3.14 has been added to the testing matrix. - setuptools is no longer a runtime dependency, as we've replaced the use of `pkg_resources` with `importlib.metadata` from Python's standard library. - GStreamer >= 1.26.2 - PyGObject >= 3.50 is now an explicit Python dependency. Previously we assumed that you would install this together with your GStreamer installation. - Added a workaround for GStreamer's `Gst.Structure().get_name()` regression for versions v1.26.1 to v1.26.2 inclusive. (!2094) - We also added a workaround to not crash when using PyGObject < 3.55.3 together with GLib >= 2.88. (!2248) - Pykka >= 4.1 - Tornado >= 6.4 - Pydantic >= 2.10 - This is a new dependency for Mopidy to replace our custom data models. - Cyclopts >= 3.12 - This is a new dependency for Mopidy to build the command line interfaces, replacing our use of `argparse`. - Rich >= 3.9 - This is a new dependency for Mopidy transitively via Cyclopts, which we've started to use directly as well to handle colorized log output. - HTTPX >= 0.28.1 - HTTPX has replaced Requests as our HTTP client. (!2249) ### CLI - Everything in the app startup from starting Mopidy to having a running server has been rebuilt. The code is now hopefully a lot easier to follow and maintain going forwards. (!2234) - Breaking change for extensions with their own CLI commands: The `mopidy.commands` module which extended on `argparse` to let extensions add their own CLI subcommands has been removed. We now use new dependency Cyclopts to build command line interfaces. The extensions maintained by the core team, including `mopidy-local` and `mopidy-spotify`, have been updated to use Cyclopts. The migration is pretty straight forward, but feel free to reach out for help with migrating your extension. (!2234) - Previously, different commands had different default logging levels. Now all commands, including `mopidy` itself, emit logs from warning level and higher by default. To get more detailed log output, add `-v` to the root command one or more times. The included `mopidy.service` systemd unit has been updated to include `--verbose` in its command, to continue logging on info level. (!2239) - Support for custom log colors via the `logcolors` config section has been removed. (!2241) - Log output to terminals are now colorized using Rich, which hopefully leads to a more pleasant and readable log output. Log colors can still be disabled by changing the `logging/colors` config. (!2241) - The command `mopidy deps` no longer repeats transitive dependencies that have already been listed. This reduces the length of the command's output drastically. (!2152) ### Data models Changes to the data models may affect any Mopidy extension or client. - The [`Track`][mopidy.models.Track] and [`Playlist`][mopidy.models.Playlist] models now require the `uri` field to always be set. (#2190, !2229) - The models are now based on Pydantic data classes, which means: - All model fields and the [`replace()`][mopidy.models.Album.replace] method should work as before, so unless your extension modifies or adds models, this should not affect you. - Models are now type-checked at runtime. This should help catch bugs early, where the models are instantiated with data, instead of when the data is used. This means that if your extension is instantiating models with incorrect data, you should see errors sooner than before. - Since we now use Pydantic to convert data models to and from JSON, the old model machinery has been removed. This includes the following: - `mopidy.models.ImmutableObject` - `mopidy.models.ValidatedImmutableObject` - `mopidy.models.ModelJSONEncoder` - `mopidy.models.model_json_decoder()` - `mopidy.models.fields.Collection` - `mopidy.models.fields.Date` - `mopidy.models.fields.Field` - `mopidy.models.fields.Identifier` - `mopidy.models.fields.Integer` - `mopidy.models.fields.String` - `mopidy.models.fields.URI` ### Core API Changes to the Core API affect Mopidy frontends and clients. #### Import paths - The [core API](../reference/core.md) is no longer exported from submodules, just from `mopidy.core`. Update your imports accordingly. (!2221) The removed modules are: - `mopidy.core.actor` - `mopidy.core.history` - `mopidy.core.library` - `mopidy.core.listener` - `mopidy.core.mixer` - `mopidy.core.playback` - `mopidy.core.playlists` - `mopidy.core.tracklist` - Moved `mopidy.core.PlaybackState` to [`mopidy.types.PlaybackState`][mopidy.types.PlaybackState], where it has been unified with `mopidy.audio.PlaybackState`. #### Root object - The [`Core`][mopidy.core.Core] class now requires the `config` argument to be present. As this argument is provided by Mopidy itself at runtime, this should only affect the setup of extension's test suites. #### Playback controller - [`PlaybackController.play()`][mopidy.core.PlaybackController.play] no longer accepts [`TlTrack`][mopidy.models.TlTrack] objects, which has been deprecated since Mopidy 3.0. Use tracklist IDs (`tlid`) instead. (#1855, !2150) There are a few APIs left that still support `TlTrack` objects where a simple tracklist ID should be used instead, but these have long been deprecated and will be removed in later releases, once our test suites have been updated to not rely so heavily on the use of `TlTrack` objects in API calls. ### Backend API Changes to the Backend API affect Mopidy backend extensions. - Document that [`Backend`][mopidy.backend.Backend] is instantiated with two keyword arguments, `config` and `audio`. (!2253) - Remove the attribute `Backend.audio` from the documentation. Mopidy core does not rely on a backend exposing this attribute, and the default backend constructor does not set this attribute. How the `config` and `audio` arguments are used is up to the backend implementation, but they are provided for backends to use if they need them. (!2253) - Added [`LibraryProvider.lookup_many()`][mopidy.backend.LibraryProvider.lookup_many] to take a list of URIs and return a mapping of URIs to tracks. If this method is not implemented then repeated calls to [`LibraryProvider.lookup()`][mopidy.backend.LibraryProvider.lookup] will be used as a fallback. (!2145) - Deprecated [`LibraryProvider.lookup()`][mopidy.backend.LibraryProvider.lookup]. Extensions should implement [`LibraryProvider.lookup_many()`][mopidy.backend.LibraryProvider.lookup_many] instead. (!2145) ### Audio API Changes to the Audio API only affect the few Mopidy backend extensions that interface with the audio layer themselves. - The audio API is no longer exported from submodules, just from [`mopidy.audio`][mopidy.audio]. Update your imports accordingly. The removed modules are: - `mopidy.audio.actor` - `mopidy.audio.listener` - `mopidy.audio.utils` - The old audio actor has been split into a [`Audio`][mopidy.audio.Audio] interface with the API used by core and backends, and a [`GstAudio`][mopidy.audio.GstAudio] implementation using GStreamer. The API is still very specific to GStreamer, but this split makes it a bit easier to mock out the audio layer in tests as it is clearer what is part of the required interface. (!2224) - Moved `mopidy.audio.PlaybackState` to [`mopidy.types.PlaybackState`][mopidy.types.PlaybackState], where it has been unified with `mopidy.core.PlaybackState`. - The [`convert_tags_to_track()`][mopidy.audio.tags.convert_tags_to_track] function now requires the track `uri` as an argument, so that it can construct valid [`Track`][mopidy.models.Track] objects. - Removed APIs only used by Mopidy-Spotify's bespoke audio delivery mechanism, which has not been used since Spotify shut down their libspotify APIs in May 2022. The removed functions/methods are: - `mopidy.audio.Audio.emit_data()` - `mopidy.audio.Audio.set_appsrc()` - `mopidy.audio.Audio.set_metadata()` - `mopidy.audio.calculate_duration()` - `mopidy.audio.create_buffer()` - `mopidy.audio.millisecond_to_clocktime()` ### Type hints - Added type hints to most of the source code to make it safer to do changes, to catch bugs earlier, and to make it faster to navigate the code base. Pykka actor proxies are fully supported, so you should be able to see what type of object you get back when you call an actor method, and what type of arguments the method expects. - We now have a public `mopidy.types` module with types that are useful both for Mopidy core and extensions. This module will probably see tweaks going forward, and should not be considered entirely stable yet. However, if you limit your use of this module in your extension to with `if TYPE_CHECKING:` code blocks, your extension should continue to work at runtime even if this module sees breaking changes. - Switched from mypy to pyright and ty for type checking. The jury is still out on which we'll use long-term. (!2226) ### Private modules - Moved bundled extensions to the private package `mopidy._exts`. (!2217) The removed modules are: - `mopidy.file` - `mopidy.http` - `mopidy.m3u` - `mopidy.softwaremixer` - `mopidy.stream` - Renamed modules to be explicitly private. (!2226) The removed modules are: - `mopidy.config.keyring` - `mopidy.internal.*` ### Development process - We've dropped the split between the `main` and `develop` branches in our development process. We now use `main` for all development, and have removed the `develop` branch. - All docstrings have been ported from reStructuredText to Markdown in Google style. All docs have been ported from reStructuredText to Markdown, and we now use Zensical to build our docs. During the process, there has been quite a bit of improvements to the docs, but there are clearly corners that still need dusting and updating. (!2247) - The [`mopidy-ext-template`](https://github.com/mopidy/mopidy-ext-template) extension template has been heavily modernized over the last year. All of the extensions maintained in the Mopidy GitHub organization have been updated to the latest version of the template, and we strongly recommend maintainers of other extensions to update their extensions to the latest version of the template as well. With the move from cookiecutter to Copier, it is now much easier to use the template to update existing extensions, not just create new ones. ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1789253948.0604467 mopidy-4.0.4/docs/ext/0000755000175100017510000000000015251354474014230 5ustar00runnerrunner././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1789253942.6415482 mopidy-4.0.4/docs/ext/file.md0000644000175100017510000000333315251354467015475 0ustar00runnerrunner# mopidy-file mopidy-file is an extension for playing music from your local music archive. It is bundled with Mopidy and enabled by default. It allows you to browse through your local file system. Only files that are considered playable will be shown. For large music collections and search functionality consider [mopidy-local](https://mopidy.com/ext/local/) instead. This backend handles URIs starting with `file:`. This backend does not currently provide images. ## Configuration See [Configuration](../usage/config.md) for general help on configuring Mopidy. ```ini title="mopidy.conf" [file] enabled = true media_dirs = $XDG_MUSIC_DIR|Music ~/|Home show_dotfiles = false excluded_file_extensions = .directory .html .jpeg .jpg .log .nfo .pdf .png .txt .zip follow_symlinks = false metadata_timeout = 1000 ``` ### file/enabled If the file extension should be enabled or not. ### file/media_dirs A list of directories to be browsable. Optionally the path can be followed by `|` and a name that will be shown for that path. ### file/show_dotfiles Whether to show hidden files and directories that start with a dot. Default is false. ### file/excluded_file_extensions File extensions to exclude when scanning the media directory. Values should be separated by either comma or newline. ### file/follow_symlinks Whether to follow symbolic links found in [`file/media_dirs`](#filemedia_dirs). Directories and files that are outside the configured directories will not be shown. Default is false. ### file/metadata_timeout Number of milliseconds before giving up scanning a file and moving on to the next file. Reducing the value might speed up the directory listing, but can lead to some tracks not being shown. ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1789253942.642117 mopidy-4.0.4/docs/ext/http.md0000644000175100017510000001056015251354467015535 0ustar00runnerrunner# mopidy-http mopidy-http is an extension that lets you control Mopidy through HTTP and WebSockets, for example from a web client. It is bundled with Mopidy and enabled by default. When it is enabled it starts a web server at the port specified by the [`http/port`](#httpport) config value. /// warning As a simple security measure, the web server is by default only available from localhost. To make it available from other computers, change the [`http/hostname`](#httphostname) config value. Before you do so, note that the HTTP extension does not feature any form of user authentication or authorization. Anyone able to access the web server can use the full core API of Mopidy. Thus, you probably only want to make the web server available from your local network or place it behind a web proxy which takes care of user authentication. You have been warned. /// ## Hosting web clients mopidy-http's web server can also host Tornado apps or any static files, for example the HTML, CSS, JavaScript, and images needed for a web based Mopidy client. See [HTTP server API](../reference/http-server.md) for how to make static files or server-side functionality from a Mopidy extension available through Mopidy's web server. If you're making a web based client and want to do server side development using some other technology than Tornado, you are of course free to run your own web server and just use Mopidy's web server to host the API endpoints. But, for clients implemented purely in JavaScript, letting Mopidy host the files is a simpler solution. See [HTTP API](../reference/http.md) for details on how to integrate with Mopidy over HTTP. If you're looking for a web based client for Mopidy, go check out [Clients](../usage/clients.md). ## Configuration See [Configuration](../usage/config.md) for general help on configuring Mopidy. ```ini title="mopidy.conf" [http] enabled = true hostname = 127.0.0.1 port = 6680 zeroconf = Mopidy HTTP server on $hostname allowed_origins = csrf_protection = true default_app = mopidy ``` ### http/enabled If the HTTP extension should be enabled or not. ### http/hostname Which address the HTTP server should bind to. - `127.0.0.1` — Listens only on the IPv4 loopback interface - `::1` — Listens only on the IPv6 loopback interface - `0.0.0.0` — Listens on all IPv4 interfaces - `::` — Listens on all interfaces, both IPv4 and IPv6 ### http/port Which TCP port the HTTP server should listen to. ### http/zeroconf Name of the HTTP service when published through Zeroconf. The variables `$hostname` and `$port` can be used in the name. If set, the Zeroconf services `_http._tcp` and `_mopidy-http._tcp` will be published. Set to an empty string to disable Zeroconf for HTTP. ### http/allowed_origins A list of domains allowed to perform Cross-Origin Resource Sharing (CORS) requests. This applies to both JSON-RPC and WebSocket requests. Values should be in the format `hostname:port`, should not specify any scheme and be separated by either a comma or newline. Additionally, the `port` should not be specified if it is the default (80 for http, 443 for https). Same-origin requests (i.e. requests from Mopidy's web server) are always allowed and so you don't need an entry for those. However, if your requests originate from a different web server, you will need to add an entry for that server in this list. For example, to allow requests from a web server at `http://my-web-client.example.com` you would specify the entry `my-web-client.example.com`. ### http/csrf_protection Enable the HTTP server's protection against Cross-Site Request Forgery (CSRF) from both JSON-RPC and WebSocket requests. Disabling this will remove the requirement to set a `Content-Type: application/json` header for JSON-RPC POST requests. It will also disable all same-origin checks, effectively ignoring the [`http/allowed_origins`](#httpallowed_origins) config since requests from any origin will be allowed. Lastly, all `Access-Control-Allow-*` response headers will be suppressed. This config should only be disabled if you understand the security implications and require the HTTP server's old behaviour. ### http/default_app Redirect from the web server root to a specific web app instead of Mopidy's default list of web apps. The value should be the name used by the extension when it registers its `http:static` or `http:app` extension points. By convention, this is the extension's `ext_name`. ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1789253942.642117 mopidy-4.0.4/docs/ext/index.md0000644000175100017510000000165315251354467015670 0ustar00runnerrunner # Extensions Mopidy's functionality can be extended with *extensions* — Python packages that add one or more backends, frontends, mixers, or web clients to Mopidy. Browse the [extension registry](https://mopidy.com/ext/) to find extensions for music services like Spotify, SoundCloud, and YouTube, as well as clients, scrobblers, and more. If you want to build your own extension, see [Extension development](../guides/extensiondev.md). ## Bundled extensions Mopidy ships with a small set of extensions included out of the box: - [mopidy-file](file.md) — play music from your local file system. - [mopidy-m3u](m3u.md) — read and write M3U playlists stored on disk. - [mopidy-stream](stream.md) — play streaming music and Internet radio. - [mopidy-http](http.md) — control Mopidy from a web client over HTTP and WebSockets. - [mopidy-softwaremixer](softwaremixer.md) — control audio volume in software through GStreamer. ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1789253942.642117 mopidy-4.0.4/docs/ext/m3u.md0000644000175100017510000000330615251354467015262 0ustar00runnerrunner# mopidy-m3u mopidy-m3u is an extension for reading and writing M3U playlists stored on disk. It is bundled with Mopidy and enabled by default. This backend handles URIs starting with `m3u:`. ## Editing playlists There is a core playlist API in place for editing playlists. This is supported by a few Mopidy clients, but not through Mopidy's MPD server yet. It is possible to edit playlists by editing the M3U files located in the `m3u/playlists_dir` directory by hand with a text editor. See [Wikipedia](https://en.wikipedia.org/wiki/M3U) for a short description of the quite simple M3U playlist format. If you run Mopidy manually in a terminal, the playlists are usually found in `~/.local/share/mopidy/m3u/`. If you run Mopidy as a system service, the playlists are usually found in `/var/lib/mopidy/m3u/`. ## Configuration See [Configuration](../usage/config.md) for general help on configuring Mopidy. ```ini title="mopidy.conf" [m3u] enabled = true playlists_dir = base_dir = $XDG_MUSIC_DIR default_encoding = latin-1 default_extension = .m3u8 ``` ### m3u/enabled If the M3U extension should be enabled or not. ### m3u/playlists_dir Path to directory with M3U files. Unset by default, in which case the extension's data dir is used to store playlists. ### m3u/base_dir Path to base directory for resolving relative paths in M3U files. If not set, relative paths are resolved based on the M3U file's location. ### m3u/default_encoding Text encoding used for files with extension `.m3u`. Default is `latin-1`. Note that files with extension `.m3u8` are always expected to be UTF-8 encoded. ### m3u/default_extension The file extension for M3U playlists created using the core playlist API. Default is `.m3u8`. ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1789253942.642117 mopidy-4.0.4/docs/ext/softwaremixer.md0000644000175100017510000000211415251354467017451 0ustar00runnerrunner# mopidy-softwaremixer mopidy-softwaremixer is an extension for controlling audio volume in software through GStreamer. It is the only mixer bundled with Mopidy and is enabled by default. If you use PulseAudio, the software mixer will control the per-application volume for Mopidy in PulseAudio, and any changes to the per-application volume done from outside Mopidy will be reflected by the software mixer. If you don't use PulseAudio, the mixer will adjust the volume internally in Mopidy's GStreamer pipeline. ## Configuration Multiple mixers can be installed and enabled at the same time, but only the mixer pointed to by the [`audio/mixer`](../usage/config.md#audiomixer) config value will actually be used. See [Configuration](../usage/config.md) for general help on configuring Mopidy. ```ini title="mopidy.conf" [softwaremixer] enabled = true ``` ### softwaremixer/enabled If the software mixer should be enabled or not. Usually you don't want to change this, but instead change the [`audio/mixer`](../usage/config.md#audiomixer) config value to decide which mixer is actually used. ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1789253942.642117 mopidy-4.0.4/docs/ext/stream.md0000644000175100017510000000340115251354467016045 0ustar00runnerrunner# mopidy-stream mopidy-stream is an extension for playing streaming music. It is bundled with Mopidy and enabled by default. This backend does not provide a library or playlist storage. It simply accepts any URI added to Mopidy's tracklist that matches any of the protocols in the [`stream/protocols`](#streamprotocols) config value. It then tries to retrieve metadata and play back the URI using GStreamer. For example, if you're using an MPD client, you'll just have to find your clients "add URI" interface, and provide it with the URI of a stream. In addition to playing streams, the extension also understands how to extract streams from a lot of playlist formats. This is convenient as most Internet radio stations links to playlists instead of directly to the radio streams. If you're having trouble playing back a stream, see [Troubleshooting](../usage/troubleshooting.md) for how to check if you have all relevant GStreamer plugins installed. ## Configuration See [Configuration](../usage/config.md) for general help on configuring Mopidy. ```ini title="mopidy.conf" [stream] enabled = true protocols = http https mms rtmp rtmps rtsp timeout = 5000 metadata_blacklist = ``` ### stream/enabled If the stream extension should be enabled or not. ### stream/protocols Whitelist of URI schemas to allow streaming from. Values should be separated by either comma or newline. ### stream/timeout Number of milliseconds before giving up looking up stream metadata. ### stream/metadata_blacklist List of URI globs to not fetch metadata from before playing. This feature is typically needed for play once URIs provided by certain streaming providers. Regular POSIX glob semantics apply, so `http://*.example.com/*` would match all example.com sub-domains. ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1789253948.0618968 mopidy-4.0.4/docs/guides/0000755000175100017510000000000015251354474014710 5ustar00runnerrunner././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1789253942.642117 mopidy-4.0.4/docs/guides/audiosinks.md0000644000175100017510000000253215251354467017407 0ustar00runnerrunner# Audio sinks Mopidy has very few [audio configurations](../reference/audio.md), but the ones we have are very powerful because they let you modify the GStreamer audio pipeline directly. If you have successfully installed GStreamer, and then run the `gst-inspect-1.0` command, you should see a long listing of installed plugins, ending in a summary line: ```console $ gst-inspect-1.0 ... long list of installed plugins ... Total count: 233 plugins, 1339 features ``` Next, you should be able to produce a audible tone by running: ```console $ gst-launch-1.0 audiotestsrc ! audioresample ! autoaudiosink ``` If you cannot hear any sound when running this command, you won't hear any sound from Mopidy either, as Mopidy by default uses GStreamer's `autoaudiosink` to play audio. Thus, make this work before you file a bug against Mopidy. If you for some reason want to use some other GStreamer audio sink than `autoaudiosink`, you can set the [`audio/output`](../usage/config.md#audiooutput) config value to a partial GStreamer pipeline description describing the GStreamer sink you want to use. Example `mopidy.conf` for using OSS4: ```ini title="mopidy.conf" [audio] output = oss4sink ``` Again, this is the equivalent of the following `gst-launch-1.0` command, so make this work first: ```console $ gst-launch-1.0 audiotestsrc ! audioresample ! oss4sink ``` ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1789253942.642382 mopidy-4.0.4/docs/guides/debian.md0000644000175100017510000003232215251354467016460 0ustar00runnerrunner# Spotify on Debian This guide describes how to set up Mopidy with Spotify support on a Debian-based system with Debian 13 Trixie or equivalent, like Ubuntu 25.10 or Raspberry Pi OS 2025-12-04, or newer. If you're using a Raspberry Pi, we recommend following the instructions in the [Raspberry Pi guide](../guides/raspberrypi.md) before following the instructions in this guide to install and configure Mopidy. In this guide, we'll show how to install Mopidy and both the mopidy-mpd and mopidy-spotify extensions from the APT repository. For any other extensions, please follow our [general installation instructions](../installation/index.md). The general instructions cover both installing from the APT repository and installing using pip, but we recommend using the APT repository when possible, as it makes it easier to keep Mopidy and its extensions up-to-date with the rest of the system. ## Preparations Before we begin, ensure that your system is fully up-to-date: ```console $ sudo apt update $ sudo apt upgrade -y ``` ## Install Mopidy Let's begin by installing Mopidy with the mopidy-mpd extension from Mopidy's own APT repository: ```console $ sudo wget -q -O /etc/apt/keyrings/mopidy-archive-keyring.gpg \ https://apt.mopidy.com/mopidy-archive-keyring.gpg $ sudo wget -q -O /etc/apt/sources.list.d/mopidy.sources \ https://apt.mopidy.com/trixie.sources $ sudo apt update $ sudo apt install mopidy mopidy-mpd ``` You can now run `mopidy deps` to check that everything is installed correctly: ```console $ mopidy deps Executable: /usr/bin/mopidy Platform: Linux-6.18.39+rpt-rpi-v8-aarch64-with-glibc2.41 Python: CPython 3.13.5 from /usr/lib/python3.13 mopidy: 4.0.3 from /usr/lib/python3/dist-packages cyclopts: 3.12.0 from /usr/lib/python3/dist-packages attrs: 25.3.0 from /usr/lib/python3/dist-packages docstring-parser: 0.16 from /usr/lib/python3/dist-packages importlib-metadata: not found rich: 13.9.4 from /usr/lib/python3/dist-packages markdown-it-py: 3.0.0 from /usr/lib/python3/dist-packages mdurl: 0.1.2 from /usr/lib/python3/dist-packages pygments: 2.18.0 from /usr/lib/python3/dist-packages typing-extensions: 4.13.2 from /usr/lib/python3/dist-packages rich-rst: 1.3.1 from /usr/lib/python3/dist-packages docutils: 0.21.2 from /usr/lib/python3/dist-packages httpx: 0.28.1 from /usr/lib/python3/dist-packages anyio: 4.8.0 from /usr/lib/python3/dist-packages exceptiongroup: not found idna: 3.10 from /usr/lib/python3/dist-packages sniffio: 1.3.1 from /usr/lib/python3/dist-packages typing_extensions: 4.13.2 from /usr/lib/python3/dist-packages certifi: 2025.1.31 from /usr/lib/python3/dist-packages httpcore: 1.0.7 from /usr/lib/python3/dist-packages h11: 0.14.0 from /usr/lib/python3/dist-packages platformdirs: 4.3.7 from /usr/lib/python3/dist-packages pydantic: 2.10.6 from /usr/lib/python3/dist-packages annotated-types: 0.7.0 from /usr/lib/python3/dist-packages pydantic-core: 2.27.2 from /usr/lib/python3/dist-packages pygobject: 3.50.0 from /usr/lib/python3/dist-packages pykka: 4.1.1 from /usr/lib/python3/dist-packages rich: 13.9.4 from /usr/lib/python3/dist-packages tornado: 6.4.2 from /usr/lib/python3/dist-packages mopidy-mpd: 4.0.1 from /usr/lib/python3/dist-packages mopidy: 4.0.3 from /usr/lib/python3/dist-packages pygobject: 3.50.0 from /usr/lib/python3/dist-packages pykka: 4.1.1 from /usr/lib/python3/dist-packages GStreamer: 1.26.2.0 Available elements: uridecodebin: 1.26.2 souphttpsrc: 1.26.2 alsasink: 1.26.2 osssink: 1.26.2 oss4sink: 1.26.2 pulsesink: 1.26.2 id3demux: 1.26.2 id3v2mux: 1.26.2 lamemp3enc: 1.26.2 mpegaudioparse: 1.26.2 mpg123audiodec: 1.26.2 vorbisdec: 1.26.2 vorbisenc: 1.26.2 vorbisparse: 1.26.2 oggdemux: 1.26.2 oggmux: 1.26.2 oggparse: 1.26.2 flacdec: 1.26.2 flacparse: 1.26.2 shout2send: 1.26.2 Missing elements: flump3dec mad ``` ## Enable and start the service Now we're ready to enable the Mopidy service, so that it starts at boot, and start the service: ```console $ sudo systemctl enable --now mopidy ``` We can check that the service is running with: ```console hl_lines="4" $ systemctl status mopidy â—� mopidy.service - Mopidy music server Loaded: loaded (/usr/lib/systemd/system/mopidy.service; enabled; preset: enabled) Active: active (running) since Sun 2026-04-12 17:55:21 CEST; 4min 57s ago Invocation: ed14f137cee44a68ad008eb877d3395a Process: 3813 ExecStartPre=/bin/mkdir -p /var/cache/mopidy (code=exited, status=0/SUCCESS) Process: 3815 ExecStartPre=/bin/chown mopidy:audio /var/cache/mopidy (code=exited, status=0/SUCCESS) Main PID: 3817 (mopidy) Tasks: 11 (limit: 764) CPU: 3.387s CGroup: /system.slice/mopidy.service └─3817 /usr/bin/python3 /usr/bin/mopidy --config /usr/share/mopidy/conf.d:/etc/mopidy/mopidy.conf Apr 12 17:55:25 hipup mopidy[3817]: INFO [MainThread] mopidy.commands Starting Mopidy mixer: SoftwareMixer Apr 12 17:55:25 hipup mopidy[3817]: INFO [MainThread] mopidy.commands Starting Mopidy audio Apr 12 17:55:25 hipup mopidy[3817]: INFO [MainThread] mopidy.commands Starting Mopidy backends: FileBackend, M3UBacken> Apr 12 17:55:25 hipup mopidy[3817]: INFO [Audio-2 (_actor_loop)] mopidy.audio.actor Audio output set to "autoaudiosink" Apr 12 17:55:25 hipup mopidy[3817]: INFO [MainThread] mopidy.commands Starting Mopidy core Apr 12 17:55:25 hipup mopidy[3817]: INFO [MainThread] mopidy.commands Starting Mopidy frontends: HttpFrontend, MpdFron> Apr 12 17:55:25 hipup mopidy[3817]: INFO [HttpFrontend-8 (_actor_loop)] mopidy.http.actor HTTP server running at [::ff> Apr 12 17:55:25 hipup mopidy[3817]: INFO [MainThread] mopidy_mpd.actor MPD server running at [::ffff:127.0.0.1]:6600 Apr 12 17:55:25 hipup mopidy[3817]: INFO [MainThread] mopidy.commands Starting GLib mainloop Apr 12 17:55:25 hipup mopidy[3817]: INFO [HttpServer] mopidy.internal.path Creating dir file:///var/lib/mopidy/http ``` To tail the logs in real time, you can run: ```console $ journalctl -u mopidy -f ``` Hit ++ctrl+c++ to stop tailing the logs. If we look closer at the logs, we can see that both the MPD and HTTP server have started, but are only listening for connections on the loopback interface, which means that we won't be able to connect to them from other devices on the network. ```console $ journalctl -u mopidy | grep "server running" Apr 12 17:55:25 hipup mopidy[3817]: INFO [HttpFrontend-8 (_actor_loop)] mopidy.http.actor HTTP server running at [::ffff:127.0.0.1]:6680 Apr 12 17:55:25 hipup mopidy[3817]: INFO [MainThread] mopidy_mpd.actor MPD server running at [::ffff:127.0.0.1]:6600 ``` We need to change the configuration to allow that. ## Enable network access to the HTTP and MPD servers To enable both the bundled [HTTP](../ext/http.md) server and the [MPD](https://mopidy.com/ext/mpd/) server we installed with the `mopidy-mpd` package to listen on all network interfaces, add the following lines to the `/etc/mopidy/mopidy.conf` configuration file: ```ini title="/etc/mopidy/mopidy.conf" [http] hostname = :: [mpd] hostname = :: ``` You can edit the configuration file using e.g. `nano`: ```console $ sudo nano /etc/mopidy/mopidy.conf ``` Press ++ctrl+o++ to save the file, and ++ctrl+x++ to exit the editor. You can check that the changes are correctly read by Mopidy by asking Mopidy to print its effective configuration, including all default values, with: ```console $ sudo mopidyctl config Running "/usr/bin/mopidy --config /usr/share/mopidy/conf.d:/etc/mopidy/mopidy.conf config" as user mopidy [core] cache_dir = /var/cache/mopidy config_dir = /etc/mopidy data_dir = /var/lib/mopidy max_tracklist_length = 10000 restore_state = false ... ``` Now, we need to restart the Mopidy service to apply the changes: ```console $ sudo systemctl restart mopidy ``` To confirm that the servers are listening on all interfaces we can look at the logs again, confirming that `::ffff:127.0.0.1` has been replaced with `::`, which means all IPv4 and IPv6 interfaces. ```console $ journalctl -u mopidy | grep "server running" Apr 12 18:14:34 hipup mopidy[3954]: INFO [HttpFrontend-10 (_actor_loop)] mopidy.http.actor HTTP server running at [::]:6680 Apr 12 18:14:34 hipup mopidy[3954]: INFO [MainThread] mopidy_mpd.actor MPD server running at [::]:6600 ``` You should now be able to connect to the HTTP and MPD servers from other devices on the network, using the Pi's IP address and the respective ports (6680 for HTTP and 6600 for MPD). For example, you can open `http://:6680/` in a web browser to access the HTTP server, or connect to the MPD server using a terminal-based MPD client like `ncmpcpp`: ```console $ sudo apt install ncmpcpp $ ncmpcpp -h ``` ## Install the Spotify extension The [mopidy-spotify](https://mopidy.com/ext/spotify/) extension is in the same APT repository as Mopidy itself: ```console $ sudo apt install mopidy-spotify ``` /// note | The GStreamer Spotify plugin The extension needs the `gst-plugin-spotify` GStreamer plugin for the playback. The `mopidy-spotify` package depends on that plugin, so `apt` installs it for you. Debian does not have the plugin, so we maintain [gst-plugins-rs-build](https://github.com/mopidy/gst-plugins-rs-build) to build it, and the APT repository has it for amd64, arm64, armhf and riscv64. On any other architecture, you must build the plugin yourself, as described in the [gst-plugins-rs-build README](https://github.com/mopidy/gst-plugins-rs-build#readme). /// You can now check that the plugin was installed correctly by running `gst-inspect-1.0 spotify`: ```console $ gst-inspect-1.0 spotify Plugin Details: Name spotify Description GStreamer Spotify Plugin Filename /usr/lib/aarch64-linux-gnu/gstreamer-1.0/libgstspotify.so Version 0.16.0-alpha-b9abdbc License MPL Source module gst-plugin-spotify Documentation https://gstreamer.freedesktop.org/documentation/spotify/ Source release date 2026-08-20 Binary package gst-plugin-spotify Origin URL https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs spotifyaudiosrc: Spotify source spotifylyricssrc: Spotify lyrics source 2 features: +-- 2 elements ``` Using `mopidy deps`, we can check that Mopidy has detected the mopidy-spotify extension: ```console hl_lines="7-15" $ mopidy deps Executable: /usr/bin/mopidy Platform: Linux-6.18.39+rpt-rpi-v8-aarch64-with-glibc2.41 Python: CPython 3.13.5 from /usr/lib/python3.13 mopidy: 4.0.3 from /usr/lib/python3/dist-packages ... mopidy-mpd: 4.0.1 from /usr/lib/python3/dist-packages ... mopidy-spotify: 5.0.1 from /usr/lib/python3/dist-packages mopidy: 4.0.3 from /usr/lib/python3/dist-packages pykka: 4.1.1 from /usr/lib/python3/dist-packages requests: 2.32.3 from /usr/lib/python3/dist-packages charset_normalizer: 3.4.2 from /usr/lib/python3/dist-packages urllib3: 2.3.0 from /usr/lib/python3/dist-packages GStreamer: 1.26.2.0 Available elements: ... spotifyaudiosrc: 0.16.0-alpha-b9abdbc ... Missing elements: ... ``` ## Authenticate with Spotify If we restart the Mopidy service now, we should see in the logs that the extension is there, but it is disabled because we haven't authenticated with Spotify yet: ```console $ journalctl -u mopidy | grep spotify Apr 12 18:07:53 hipup mopidy[3906]: INFO [MainThread] mopidy.__main__ Disabled extensions: spotify Apr 12 18:07:53 hipup mopidy[3906]: WARNING [MainThread] mopidy.__main__ Found spotify configuration errors. The extension has been automatically disabled: Apr 12 18:07:53 hipup mopidy[3906]: WARNING [MainThread] mopidy.__main__ spotify/client_id must be set. Apr 12 18:07:53 hipup mopidy[3906]: WARNING [MainThread] mopidy.__main__ spotify/client_secret must be set. ``` To authenticate the Mopidy extension with your Spotify account, go to and follow the instructions to authenticate with Spotify. Once you've logged in with Spotify and come back to the Mopidy website, the page will show the exact configuration you need to add to your `/etc/mopidy/mopidy.conf` file to enable Spotify playback. It should look something like this: ```ini title="/etc/mopidy/mopidy.conf" [spotify] client_id = your-oauth-client-id client_secret = your-oauth-client-secret ``` After editing `/etc/mopidy/mopidy.conf` again to add the Spotify config, restart the Mopidy service again: ```console $ sudo systemctl restart mopidy ``` ## Play music from Spotify If you have the very minimalistic `mpc` client installed, you can add a Spotify track to the tracklist and start playback with: ```console $ mpc -h add spotify:track:4mdsqeYQoEwCYl0gdnUCLC $ mpc -h play ``` But it might be easier to use a more full-featured MPD client, like `ncmpcpp`, which lets you browse the music library and playlists, and search for tracks and artists, directly from the client interface. Et voilà! You should now have a working Mopidy setup with Spotify support on your Debian-based system. You can now explore the various MPD clients available, or use the HTTP server to control playback from one of the many web clients. Happy listening! ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1789253942.642382 mopidy-4.0.4/docs/guides/extensiondev.md0000644000175100017510000005363315251354467017761 0ustar00runnerrunner # Extension development Mopidy started as simply an MPD server that could play music from Spotify. Early on, Mopidy got multiple "frontends" to expose Mopidy to more than just MPD clients, including: - the scrobbler frontend that scrobbles your listening history to your Last.fm account, - the MPRIS frontend that integrates Mopidy into the Ubuntu Sound Menu, and - the HTTP server and JavaScript player API making web based Mopidy clients possible. In Mopidy 0.9 we added support for multiple music sources without stopping and reconfiguring Mopidy, introducing: - the local backend for playing music from your disk, - the stream backend for playing Internet radio streams, and - the Spotify and SoundCloud backends, for playing music directly from those services. All of these are examples of what you can accomplish by creating a Mopidy extension. If you want to create your own Mopidy extension for something that does not exist yet, this guide to extension development will help you get your extension running in no time, and make it feel the way users would expect your extension to behave. ## Extension project template We've made a [Copier](https://copier.readthedocs.io/) project template for [creating new Mopidy extensions](https://github.com/mopidy/mopidy-ext-template). By running a single command, you're asked a few questions about the name of your extension, and then your answers are used to create a folder structure similar to the above, with all the needed files and most of the details filled in for you. See the readme of [mopidy-ext-template](https://github.com/mopidy/mopidy-ext-template) for further details. ## Anatomy of an extension Extensions are located in a Python package called `mopidy_something` where "something" is the name of the application, library or web service you want to integrate with Mopidy. So, for example, if you plan to add support for a service named Soundspot to Mopidy, you would name your extension's Python package `mopidy_soundspot`. The extension must be registered on [PyPI](https://pypi.org/). The name of the distribution on PyPI would be something like "mopidy-soundspot". This is the name users will use when they install your extension from PyPI. Mopidy extensions must be licensed under an Apache 2.0 (like Mopidy itself), BSD, MIT or more liberal license to be able to be enlisted in the Mopidy documentation. The license text should be included in the `LICENSE` file in the root of the extension's Git repo. Combining this together, we get the following folder structure for our extension, mopidy-soundspot: ```text mopidy-soundspot/ # The Git repo root pyproject.toml # Project metadata and build configuration LICENSE # The license text README.md # Document what it is and how to use it src/ mopidy_soundspot/ # Your code __init__.py ext.conf # Default config for the extension ... tests/ __init__.py test_extension.py # Tests for the extension ``` See the above project template for examples of each of the files. ## Extension registration For Mopidy to find your extension, the extension must register an [entry point](https://packaging.python.org/en/latest/specifications/entry-points/) of the `mopidy.ext` type. This is done by adding a section like this to your `pyproject.toml` file: ```toml title="mopidy-soundspot/pyproject.toml" [project.entry-points."mopidy.ext"] soundspot = "mopidy_soundspot:Extension" ``` In this example, the first part, `soundspot`, is your extensions "short name", which is used to identify your extension in the Mopidy configuration file. The second part, `mopidy_soundspot:Extension`, is the fully qualified name of your Python module and extension class. ## Extension class The root of your Python package should have a class named `Extension` which inherits from Mopidy's extension base class, `mopidy.ext.Extension`. This is the class referred to in the `project.entry-points` part of `pyproject.toml`. Any imports of other files in your extension, outside of Mopidy and its core requirements, should be kept inside methods. This ensures that this file can be imported without raising `ImportError` exceptions for missing dependencies, etc. The default configuration for the extension is defined by the `get_default_config()` method in the `Extension` class which returns a `ConfigParser` compatible config section. The config section's name must be the same as the extension's short name, as defined in the `project.entry-points` part of `pyproject.toml`, for example `soundspot`. All extensions must include an `enabled` config which normally should default to `true`. Provide good defaults for all config values so that as few users as possible will need to change them. The exception is if the config value has security implications; in that case you should default to the most secure configuration. Leave any configurations that don't have meaningful defaults blank, like `username` and `password`. In the example below, we've chosen to maintain the default config as a separate file named `ext.conf`. This makes it easy to include the default config in documentation without duplicating it. ```python title="mopidy-soundspot/src/mopidy_soundspot/__init__.py" import logging import pathlib from importlib.metadata import version import cyclopts from mopidy import config, exceptions, ext __version__ = version("mopidy-soundspot") # If you need to log, use loggers named after the current Python module logger = logging.getLogger(__name__) class Extension(ext.Extension): dist_name = "Mopidy-Soundspot" ext_name = "soundspot" version = __version__ def get_default_config(self) -> str: return config.read(pathlib.Path(__file__).parent / "ext.conf") def get_config_schema(self) -> config.ConfigSchema: schema = super().get_config_schema() schema["username"] = config.String() schema["password"] = config.Secret() return schema def get_command(self) -> cyclopts.App: # To extend the `mopidy` command line interface: from .commands import app # noqa: PLC0415 return app def validate_environment(self) -> None: # Any manual checks of the environment to fail early. # Dependencies described by pyproject.toml are checked by Mopidy, so # you should not check their presence here. pass def setup(self, registry: ext.Registry) -> None: # You will typically only do one of the following things in a # single extension. # Register a frontend from .frontend import SoundspotFrontend # noqa: PLC0415 registry.add("frontend", SoundspotFrontend) # Register a backend from .backend import SoundspotBackend # noqa: PLC0415 registry.add("backend", SoundspotBackend) # Or nothing to register e.g. command extension pass ``` And this is the config file: ```ini title="mopidy-soundspot/src/mopidy_soundspot/ext.conf" [soundspot] enabled = true username = password = ``` For more detailed documentation on the extension class, see the ext-api. ## Example frontend If you want to *use* Mopidy's core API from your extension, then you want to implement a frontend. The skeleton of a frontend would look like this. Notice that the frontend gets passed a reference to the core API when it's created. See the [Frontend API](../reference/frontend.md) for more details. ```python title="mopidy-soundspot/src/mopidy_soundspot/frontend.py" import pykka from mopidy import core class SoundspotFrontend(pykka.ThreadingActor, core.CoreListener): def __init__(self, config, core): super().__init__() self.config = config self.core = core # Your frontend implementation ``` ## Example backend If you want to extend Mopidy to support new music and playlist sources, you want to implement a backend. A backend does not have access to Mopidy's core API at all, but it does have a bunch of interfaces it can implement to extend Mopidy. The skeleton of a backend would look like this. See the [Backend API](../reference/backend.md) for more details. ```python title="mopidy-soundspot/src/mopidy_soundspot/backend.py" import pykka from mopidy import backend class SoundspotBackend(pykka.ThreadingActor, backend.Backend): def __init__(self, config, audio): super().__init__() self.config = config self.audio = audio # Your backend implementation ``` ## Example command If you want to extend the Mopidy with a new helper not run from the server, such as scanning for media, adding a command is the way to go. Your top level command name will always match your extension name, but you are free to add sub-commands with names of your choosing. The skeleton of a `mopidy soundspot reticulate` command would look like this. See the [Cyclopts documentation](https://cyclopts.readthedocs.io/) for more details. ```python title="mopidy-soundspot/src/mopidy_soundspot/commands.py" import cyclopts app = cyclopts.App(help="Some text that will show up in --help") @app.command(help="Reticulate the Soundspot library.") def reticulate(*, degrees: int) -> None: # Your command implementation print("Reticulating {degrees} degrees...") ``` To register your commands, make sure to return the `app` instance from your `mopidy.ext.Extension.get_command()` implementation. ## Example web application Extensions can use Mopidy's built-in web server to host static web clients as well as WSGI web applications. For several examples, see the [HTTP server API](../reference/http-server.md) docs or explore with the [mopidy-api-explorer](https://mopidy.com/ext/api-explorer) extension. ## Python conventions In general, it would be nice if Mopidy extensions followed the same codestyle as Mopidy itself, as they're part of the same ecosystem. ## Use of Mopidy APIs When writing an extension, you should only use APIs documented in the [Reference](../reference/index.md) section. Any package or module whose name starts with an underscore (e.g. `mopidy._internal`) is considered private, may change at any time, and should not be used by extensions. Mopidy performs type checking to help catch extension bugs. This applies to both frontend calls into core and return values from backends. Additionally, model fields always get validated to further guard against bad data. ## Logging in extensions For servers like Mopidy, logging is essential for understanding what's going on. We use the `logging` module from Python's standard library. When creating a logger, always namespace the logger using your Python package name as this will be visible in Mopidy's debug log: ```python import logging logger = logging.getLogger(__name__) ``` When logging at logging level `info` or higher (i.e. `warning`, `error`, and `critical`, but not `debug`) the log message will be displayed to all Mopidy users. Thus, the log messages at those levels should be well written and easy to understand. As the logger name is not included in Mopidy's default logging format, you should make it obvious from the log message who is the source of the log message. For example: ```text Loaded 17 Soundspot playlists ``` Is much better than: ```text Loaded 17 playlists ``` If you want to turn on debug logging for your own extension, but not for everything else due to the amount of noise, see the docs for the `loglevels/*` config section. ## Making HTTP requests from extensions Many Mopidy extensions need to make HTTP requests to use some web API. Here's a few recommendations to those extensions. ### Proxies If you make HTTP requests please make sure to respect the [proxy config](../usage/config.md#proxy-section), so that all the requests you make go through the proxy configured by the Mopidy user. To make this easier for extension developers, the helper function [mopidy.httpclient.format_proxy][] was added in Mopidy 1.1. This function returns the proxy settings [formatted the way Requests expects](https://requests.readthedocs.io/en/latest/user/advanced/#proxies). ### User-Agent strings When you make HTTP requests, it's helpful for debugging and usage analysis if the client identifies itself with a proper User-Agent string. In Mopidy 1.1, we added the helper function [mopidy.httpclient.format_user_agent][]. Here's an example of how to use it: ```python >>> from mopidy import httpclient >>> import mopidy_soundspot >>> httpclient.format_user_agent( ... f'{mopidy_soundspot.Extension.dist_name}/' ... f'{mopidy_soundspot.__version__}' ... ) 'Mopidy-SoundSpot/2.0.0 Mopidy/4.0.0 Python/3.13.2' ``` ### Example using HTTPX Most Mopidy extensions that make HTTP requests use either the [HTTPX](https://www.python-httpx.org/) or [Requests](https://requests.readthedocs.io/) library to do so. If you're using HTTPX, you can create a session object like this: ```python import httpx from mopidy import httpclient import mopidy_soundspot client = httpx.Client( proxy=httpclient.format_proxy(proxy_config), headers={ "user-agent": httpclient.format_user_agent( f"{mopidy_soundspot.Extension.dist_name}/{mopidy_soundspot.__version__}" ), }, ) response = client.get("https://example.com") ``` For further details, see HTTPX' docs on [clients](https://www.python-httpx.org/advanced/clients/). ### Example using Requests When using Requests, the most convenient way to make sure the proxy and User-Agent header is set properly is to create a Requests session object and use that object to make all your HTTP requests: ```python import requests from mopidy import httpclient import mopidy_soundspot def get_requests_session(proxy_config, user_agent): proxy = httpclient.format_proxy(proxy_config) full_user_agent = httpclient.format_user_agent(user_agent) session = requests.Session() session.proxies.update({"http": proxy, "https": proxy}) session.headers.update({"user-agent": full_user_agent}) return session # mopidy_config is the config object passed to your frontend/backend # constructor session = get_requests_session( proxy_config=mopidy_config["proxy"], user_agent=( f"{mopidy_soundspot.Extension.dist_name}/{mopidy_soundspot.__version__}" ), ) response = session.get("https://example.com") ``` For further details, see Requests' docs on [session objects](https://requests.readthedocs.io/en/latest/user/advanced/#proxies). ## Testing extensions Creating test cases for your extensions makes them much simpler to maintain over the long term. It can also make it easier for you to review and accept pull requests from other contributors knowing that they will not break the extension in some unanticipated way. Before getting started, it is important to familiarize yourself with the Python [mock library](https://docs.python.org/dev/library/unittest.mock.html). When it comes to running tests, Mopidy typically makes use of testing tools like [tox](https://tox.readthedocs.io/) and [pytest](https://docs.pytest.org/). ### Testing approach To a large extent the testing approach to follow depends on how your extension is structured, which parts of Mopidy it interacts with, and if it uses any third party APIs or makes any HTTP requests to the outside world. The sections that follow contain code extracts that highlight some of the key areas that should be tested. For more exhaustive examples, you may want to take a look at the test cases that ship with Mopidy itself which covers everything from instantiating various controllers, reading configuration files, and simulating events that your extension can listen to. In general your tests should cover the extension definition, the relevant Mopidy controllers, and the backend and/or frontend Pykka actors that form part of the extension. ### Testing the extension definition Test cases for checking the definition of the extension should ensure that: - the extension provides a `ext.conf` configuration file containing the relevant parameters with their default values, - that the config schema is fully defined, and - that the extension's actor(s) are added to the Mopidy registry on setup. An example of what these tests could look like is provided below: ```python title="mopidy-soundspot/tests/test_extension.py" def test_get_default_config(): ext = Extension() config = ext.get_default_config() assert "[my_extension]" in config assert "enabled = true" in config assert "param_1 = value_1" in config assert "param_2 = value_2" in config assert "param_n = value_n" in config def test_get_config_schema(): ext = Extension() schema = ext.get_config_schema() assert "enabled" in schema assert "param_1" in schema assert "param_2" in schema assert "param_n" in schema def test_setup(): registry = mock.Mock() ext = Extension() ext.setup(registry) calls = [ mock.call("frontend", frontend_lib.MyFrontend), mock.call("backend", backend_lib.MyBackend), ] registry.add.assert_has_calls(calls, any_order=True) ``` ### Testing backend actors Backends can usually be constructed with a small mockup of the configuration file, and mocking the audio actor: ```python @pytest.fixture def config(): return { "http": {"hostname": "127.0.0.1", "port": "6680"}, "proxy": {"hostname": "host_mock", "port": "port_mock"}, "my_extension": { "enabled": True, "param_1": "value_1", "param_2": "value_2", "param_n": "value_n", }, } def get_backend(config): return backend.MyBackend(config=config, audio=mock.Mock()) ``` The following libraries might be useful for mocking any HTTP requests that your extension makes: - [pytest-httpx](https://pypi.org/project/pytest-httpx/) - A pytest plugin for mocking HTTPX requests. - [responses](https://pypi.org/project/responses) - A utility library for mocking out the Requests Python library. - [vcrpy](https://pypi.org/project/vcrpy) - Automatically mock your HTTP interactions to simplify and speed up testing. At the very least, you'll probably want to mock out the API's that you use to avoid any unintended requests from being made by your backend during testing. Backend tests should also ensure that: - the backend provides a unique URI scheme, - that it sets up the various providers (e.g. library, playback, etc.) ```python title="mopidy-soundspot/tests/test_extension.py" def test_uri_schemes(config): backend = get_backend(config) assert "my_scheme" in backend.uri_schemes def test_init_sets_up_the_providers(config): backend = get_backend(config) assert isinstance(backend.library, library.MyLibraryProvider) assert isinstance(backend.playback, playback.MyPlaybackProvider) ``` Once you have a backend instance to work with, testing the various playback, library, and other providers is straight forward and should not require any special setup or processing. ### Testing libraries Library test cases should cover the implementations of the standard Mopidy API (e.g. `browse`, `lookup`, `refresh`, `get_images`, `search`, etc.) ### Testing playback controllers Testing `change_track` and `translate_uri` is probably the highest priority, since these methods are used to prepare the track and provide its audio URL to Mopidy's core for playback. ### Testing frontends Because most frontends will interact with the Mopidy core, it will most likely be necessary to have a full core running for testing purposes: ```python self.core = core.Core.start( config, backends=[get_backend(config)], ).proxy() ``` It may be advisable to take a quick look at the [Pykka API](https://pykka.readthedocs.io/) at this point to make sure that you are familiar with `ThreadingActor`, `ThreadingFuture`, and the "proxies" that allow you to access the attributes and methods of the actor directly. You'll also need a list of `mopidy.models.Track` and a list of URIs in order to populate the core with some simple tracks that can be used for testing: ```python tracks = [ models.Track(uri="my_scheme:track:id1", length=40000), # Regular track models.Track(uri="my_scheme:track:id2", length=None), # No duration ] uris = ["my_scheme:track:id1", "my_scheme:track:id2"] ``` In the `setup()` method of your test class, you will then probably need to monkey patch looking up tracks in the library (so that it will always use the lists that you defined), and then populate the core's tracklist: ```python def lookup(uris): result = {uri: [] for uri in uris} for track in self.tracks: if track.uri in result: result[track.uri].append(track) return result self.core.library.lookup = lookup self.tl_tracks = self.core.tracklist.add(uris=self.uris).get() ``` With all of that done you should finally be ready to instantiate your frontend: ```python self.frontend = frontend.MyFrontend.start(config(), self.core).proxy() ``` Keep in mind that the normal core and frontend methods will usually return `pykka.ThreadingFuture` objects, so you will need to add `.get()` at the end of most method calls in order to get to the actual return values. ### Triggering events There may be test case scenarios that require simulating certain event triggers that your extension's actors can listen for and respond on. An example for patching the listener to store these events, and then play them back for your actor, may look something like this: ```python self.events = [] self.patcher = mock.patch("mopidy.listener.send") self.send_mock = self.patcher.start() def send(cls, event, **kwargs): self.events.append((event, kwargs)) self.send_mock.side_effect = send ``` Once all of the events have been captured, a method like `replay_events()` can be called at the relevant points in the code to have the events fire: ```python def replay_events(self, my_actor, until=None): while self.events: if self.events[0][0] == until: break event, kwargs = self.events.pop(0) frontend.on_event(event, **kwargs).get() ``` For further details and examples, refer to the [/tests](https://github.com/mopidy/mopidy/tree/main/tests) directory in the Mopidy repo. ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1789253942.642382 mopidy-4.0.4/docs/guides/icecast.md0000644000175100017510000000655715251354467016664 0ustar00runnerrunner# Icecast If you want to play the audio on another computer than the one running Mopidy, you can stream the audio from Mopidy through an Icecast audio streaming server. Multiple media players can then be connected to the streaming server simultaneously. To use the Icecast output, do the following: /// warning The last known working version of libshout3 is v2.4.1, this is the version available in Debian Buster. Newer versions of this library, such as those found in Ubuntu 20.04 and Debian Bullseye are bugged and render GStreamer's shout2send sink [broken](https://gitlab.freedesktop.org/gstreamer/gst-plugins-good/-/issues/848) and unusable. You cannot stream from Mopidy via Icecast with the below method when using Ubuntu 20.04 or Debian Bullseye. Consider [Snapcast](https://github.com/badaix/snapcast) as an alternative solution. /// 1. Install, configure and start the Icecast server. It can be found in the `icecast2` package in Debian/Ubuntu. 2. Set the [`audio/output`](../usage/config.md#audiooutput) config value to encode the output audio to MP3 (`lamemp3enc`) or Ogg Vorbis (`audioresample ! audioconvert ! vorbisenc ! oggmux`) and send it to Icecast (`shout2send`). You might also need to change the `shout2send` default settings, run `gst-inspect-1.0 shout2send` to see the available settings. Most likely you want to change `ip`, `username`, `password`, and `mount`. Example for MP3 streaming: ```ini title="mopidy.conf" [audio] output = lamemp3enc ! shout2send async=false mount=mopidy ip=127.0.0.1 port=8000 password=hackme ``` Example for Ogg Vorbis streaming: ```ini title="mopidy.conf" [audio] output = audioresample ! audioconvert ! vorbisenc ! oggmux ! shout2send async=false mount=mopidy ip=127.0.0.1 port=8000 password=hackme ``` Example for MP3 streaming and local audio (multiple outputs): ```ini title="mopidy.conf" [audio] output = tee name=t ! queue ! audioresample ! autoaudiosink t. ! queue ! lamemp3enc ! shout2send async=false mount=mopidy ip=127.0.0.1 port=8000 password=hackme ``` Other advanced setups are also possible for outputs. Basically, anything you can use with the `gst-launch-1.0` command can be plugged into [`audio/output`](../usage/config.md#audiooutput). ## Known issues - **Previous/next:** The stream ends on previous and next. See [#1306](https://github.com/mopidy/mopidy/issues/1306) for details. This can be worked around using a fallback stream, as described below. - **Pause:** Pausing playback stops the stream. This is probably not something we're going to fix. This can be worked around using a fallback stream, as described below. - **Metadata:** Track metadata might be missing from the stream. For Spotify, this should mostly work as of Mopidy 2.0.1. For other extensions, [#866](https://github.com/mopidy/mopidy/issues/866) is the tracking issue. ## Fallback stream By using a *fallback stream* playing silence, you can somewhat mitigate the known issues above. Example Icecast configuration: ```xml title="icecast.xml" /mopidy /silence.mp3 1 ``` You can easily find MP3 files with just silence by searching the web. The `silence.mp3` file needs to be placed in the directory defined by `...` in the Icecast configuration. ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1789253942.642382 mopidy-4.0.4/docs/guides/index.md0000644000175100017510000000221115251354467016337 0ustar00runnerrunner # Guides Step-by-step guides for specific use cases beyond the basic setup. ## Setup guides - [Raspberry Pi](raspberrypi.md) — set up Raspberry Pi OS and Mopidy on a Raspberry Pi. - [Spotify on Debian](debian.md) — set up Mopidy with Spotify support on Debian-based systems, like Ubuntu and Raspberry Pi OS. ## Audio output - [Audio sinks](audiosinks.md) — configure GStreamer audio output for different hardware and software audio systems. - [Icecast](icecast.md) — stream audio from Mopidy through an Icecast server so multiple clients can listen simultaneously. - [PulseAudio](pulseaudio.md) — connect Mopidy running as a system service to a PulseAudio server running as your main user. - [PipeWire](pipewire.md) — connect Mopidy running as a system service to a PipeWire server running as your main user. ## Remote control - [UPnP](upnp.md) — make Mopidy available as a UPnP/DLNA media renderer or expose its music library to UPnP media players on your network. ## Development - [Extension development](extensiondev.md) — build your own Mopidy extension to add new music sources, frontends, or other functionality. ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1789253942.642382 mopidy-4.0.4/docs/guides/pipewire.md0000644000175100017510000000241015251354467017055 0ustar00runnerrunner# PipeWire When [running Mopidy as a service](../usage/service.md), it runs as the `mopidy` system user rather than your own user. PipeWire runs as your main user, so Mopidy can't access it directly. As with [PulseAudio](pulseaudio.md), you can configure PipeWire and Mopidy so that Mopidy sends audio over TCP to the PipeWire server already running as your main user. First make sure that `pipewire-pulse` is installed. It's PipeWire's PulseAudio replacement. Check whether a configuration file for `pipewire-pulse` is available (may depend on the Linux distribution but `/etc/pipewire/pipewire-pulse.conf` is standard). If not, copy from `/usr/share/pipewire/pipewire-pulse.conf`. Then modify that file for `pipewire-pulse` to accept sound over TCP from localhost (note the uncommented line with `"tcp:4713"`): ```text title="pipewire-pulse.conf" hl_lines="5" pulse.properties = { # the addresses this server listens on server.address = [ "unix:native" "tcp:4713" ] ``` Next, configure Mopidy to use this `pipewire-pulse` server: ```ini title="mopidy.conf" [audio] output = pulsesink server=127.0.0.1 ``` After this, restart both PipeWire and Mopidy: ```console $ systemctl --user restart pipewire pipewire-pulse $ sudo systemctl restart mopidy ``` ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1789253942.642382 mopidy-4.0.4/docs/guides/pulseaudio.md0000644000175100017510000000312615251354467017410 0ustar00runnerrunner# PulseAudio When [running Mopidy as a service](../usage/service.md), it runs as the `mopidy` system user rather than your own user. PulseAudio runs as your main user, so Mopidy can't access it directly. Running PulseAudio as a system-wide daemon is [discouraged by upstream](https://www.freedesktop.org/wiki/Software/PulseAudio/Documentation/User/WhatIsWrongWithSystemWide/). Instead, you can configure PulseAudio and Mopidy so that Mopidy sends audio over TCP to the PulseAudio server already running as your main user. First, configure PulseAudio to accept sound over TCP from localhost by uncommenting or adding the TCP module to `/etc/pulse/default.pa` or `$XDG_CONFIG_HOME/pulse/default.pa` (typically `~/.config/pulse/default.pa`): ```plain title="default.pa" ### Network access (may be configured with paprefs, so leave this commented ### here if you plan to use paprefs) #load-module module-esound-protocol-tcp load-module module-native-protocol-tcp auth-ip-acl=127.0.0.1 #load-module module-zeroconf-publish ``` Next, configure Mopidy to use this PulseAudio server: ```ini title="mopidy.conf" [audio] output = pulsesink server=127.0.0.1 ``` After this, restart both PulseAudio and Mopidy: ```console $ pulseaudio --kill $ start-pulseaudio-x11 $ sudo systemctl restart mopidy ``` If you are not running any X server, run `pulseaudio --start` instead of `start-pulseaudio-x11`. If you don't want to hard code the output in your Mopidy config, you can instead of adding any config to Mopidy add this to `~mopidy/.pulse/client.conf`: ```text title="~mopidy/.pulse/client.conf" default-server=127.0.0.1 ``` ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1789253942.642382 mopidy-4.0.4/docs/guides/raspberrypi.md0000644000175100017510000001325415251354467017603 0ustar00runnerrunner# Raspberry Pi This guide will show you the golden path to get Mopidy up and running on a Raspberry Pi, but there are many possible variations depending on what model you have, what audio output accessories you have, and what Linux distribution you run on your Raspberry Pi. We cannot cover all of those variations here, but we try to cover the most common setup. Mopidy runs on most versions of [Raspberry Pi](https://www.raspberrypi.org/), but we recommend using a Raspberry Pi 3 or later for the best experience. If anything here is unclear, please refer to Raspberry Pi's excellent [getting started documentation](https://www.raspberrypi.com/documentation/computers/getting-started.html). This guide was last tested using the Raspberry Pi OS (64-bit) Lite image, dated 2025-12-04, on a Raspberry Pi 3 Model B with 1GB of memory and a 16GB SD card, with audio output through the 3.5mm audio jack. The instructions should be similar for other models and configurations, but your mileage may vary. ## Prepare the SD card Install and run the [Raspberry Pi Imager](https://www.raspberrypi.com/software/), and follow these instructions to flash an OS image to your SD card: - **Device:** Select your type of device, typically Raspberry Pi 3, 4, or 5. - **OS:** When selecting the OS, you probably want to navigate into the "Raspberry Pi OS (other)" category to find the "Raspberry Pi OS (64-bit) Lite" variant, to save on both memory usage and disk space. - **Storage:** Select the SD card you want to write the OS onto. - **Customisation:** In the customization steps, you can set the Pi's hostname. We recommend that you enable SSH, and set up WiFi credentials so that the device can connect to the network on first boot, removing the need to connect a monitor and keyboard to the device for the initial setup. - **Writing:** Confirm the previous choices and wait for the OS image to be written to the SD card. This may take a few minutes. /// note | WiFi and WPA support If using a Raspberry Pi older than 3B+, e.g. 3B, remember that it doesn't support 5GHz WiFi or WPA3. Depending on Raspberry Pi model, you may need to set up a 2.4GHz WiFi network with WPA2 security for the Pi to be able to connect to the network. /// ## Connect via SSH Insert the SD card into the Raspberry Pi, connect a network cable if you're not using WiFi, and power it on. The Pi will boot and connect to the network, and you should be able to connect to it via SSH. To find the IP address of the Pi, you have two options: - Look in the client list on your Internet router's DHCP server. Look for a device with the hostname you set up in the previous step, or a device with the manufacturer "Raspberry Pi Foundation". - Connect the Pi to a monitor. It'll print its current IPv4 and IPv6 address to the console at the end of the boot sequence, just before the login prompt or shell appears. If you have a keyboard connected, you can run the command `hostname -I` to print the IP address at any time. When you have found the Pi's IP address, SSH to it, and login using the credentials you set up in the previous step: ```console $ ssh @ ``` ## Update the system Once connected through SSH, it's a good idea to update the system as the image may be a few months old and missing important security updates and bug fixes. You can do this by running the following commands: ```console $ sudo apt update $ sudo apt upgrade -y ``` ## Initial configuration Next, use the `raspi-config` tool to set up the basics of your Pi: ```console $ sudo raspi-config ``` The tool lets you change many of the choices you made when creating the SD card with Imager. You might also want to have a look at the following settings: - **System Options > S2 Audio:** Select if you want audio output via the HDMI port or the 3.5mm audio jack. /// tip | Contribute Using a USB audio card or a HAT for audio output? Please let us know how the setup differs and help us document this. /// - **System Options > S5 Boot:** If you didn't use the Lite image and have a full desktop environment on your Pi, you can select "B1 Console Text console" to save some memory and CPU resources by not starting the desktop environment on boot. - **System Options > S6 Auto Login:** For a bit better security, you can disable auto login and require a password to log in to the Pi when physically connecting with a monitor and a keyboard. - **Localisation Options > L1 Locale:** If you want to save some space and time during upgrades, you can unselect the `en_GB.UTF-8` locale. On the next screen, select `C.UTF-8` as the default locale instead of `None`. Explicitly selecting the Pi's locale fixes warnings about mismatching locales when you SSH in from a machine with a different locale. Once done, select "Finish". Depending on what you changed you may be asked if you want to restart your Pi, select "Yes" and then log back in again afterwards. To manually reboot the device, you can run: ```console $ sudo reboot ``` If you want to change any settings later, you can simply rerun `sudo raspi-config`. ## Testing sound output You can test sound output independent of Mopidy by running: ```console $ aplay /usr/share/sounds/alsa/Front_Center.wav ``` If you hear a voice saying "Front Center", then your sound is working. If you want to change your audio output setting, simply rerun `sudo raspi-config`. ## Install Mopidy with Spotify support Now we're done with everything specific to the Raspberry Pi, and we can follow the same instructions as for any other Debian-based system to install Mopidy and the Spotify extension. Next, follow the instructions in the [Spotify on Debian-based systems](debian.md) guide. ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1789253942.642382 mopidy-4.0.4/docs/guides/upnp.md0000644000175100017510000000422215251354467016216 0ustar00runnerrunner# UPnP [UPnP](https://en.wikipedia.org/wiki/Universal_Plug_and_Play) is a set of specifications for media sharing, playing, remote control, etc, across a home network. The specs are supported by a lot of consumer devices (like smartphones, TVs, Xbox, and PlayStation) that are often labeled as being [DLNA](https://en.wikipedia.org/wiki/DLNA) compatible or certified. ## UPnP MediaRenderer The DLNA guidelines and UPnP specifications defines several device roles, of which Mopidy may play the role of DLNA Digital Media Renderer (DMR), also known as an UPnP MediaRenderer. A MediaRenderer is asked by some remote controller to play some given media, typically served by a MediaServer. If Mopidy was a MediaRenderer, you could use e.g. your smartphone or tablet to make Mopidy play media. There are two ways Mopidy can be made available as an UPnP MediaRenderer: using mopidy-mpd and upmpdcli, or using mopidy-mpris and Rygel. ### mopidy-mpd and upmpdcli [upmpdcli](https://www.lesbonscomptes.com/upmpdcli/) is recommended, since it is easier to setup, and offers [OpenHome](http://wiki.openhome.org/wiki/OhMedia) compatibility. upmpdcli exposes a UPnP MediaRenderer to the network, while using the MPD protocol to control Mopidy. 1. Install upmpdcli. On Debian/Ubuntu: ```console $ sudo apt install upmpdcli ``` Alternatively, follow the instructions from the upmpdcli website. 2. The default settings of upmpdcli will work with the default settings of mopidy-mpd. Edit `/etc/upmpdcli.conf` if you want to use different ports, hosts, or other settings. 3. Start upmpdcli using the command: ```console $ upmpdcli ``` Or, run it in the background as a service: ```console $ sudo service upmpdcli start ``` 4. An UPnP renderer should be available now. ### mopidy-mpris and Rygel See the [mopidy-mpris](https://github.com/mopidy/mopidy-mpris) documentation for how to setup Rygel to make Mopidy an UPnP MediaRenderer. ## UPnP clients For a long list of UPnP clients for all possible platforms, see Wikipedia's [List of UPnP AV media servers and clients](https://en.wikipedia.org/wiki/List_of_UPnP_AV_media_servers_and_clients). ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1789253948.0620375 mopidy-4.0.4/docs/images/0000755000175100017510000000000015251354474014675 5ustar00runnerrunner././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1789253942.642382 mopidy-4.0.4/docs/images/logo.png0000644000175100017510000016753015251354467016361 0ustar00runnerrunner‰PNG  IHDR��€¿6Ì€IDATxœì½œdWuà}Î}ïÕ«\ÕÕ¹§'�f4Êe@6"˜  Æ`Œ±—ýÙëÝ5û}f�Y€�í]c ¼€ 2,ŒH �BH4 {B÷Ìtª\õÒ=ßïÝ{_‘[3]î4Ó©zÞ}uï;ù�F£Ñ¬-@4�F³"´Ñh4ÍŠÐD£Ñh4+B �F£Ñ¬-@4�F³"´Ñh4ÍŠÐD£Ñh4+B �F£Ñ¬-@4�F³"´Ñh4ÍŠÐD£Ñh4+B �F£Ñ¬-@4�F³"´Ñh4ÍŠÐD£Ñh4+B �F£Ñ¬-@4�F³"´Ñh4ÍŠÐD£Ñh4+B �F£Ñ¬-@4�F³"´Ñh4ÍŠÐD£Ñh4+B �F£Ñ¬-@4�F³"´Ñh4ÍŠÐD£Ñh4+B �F£Ñ¬-@4�F³"´Ñh4ÍŠÐD£Ñh4+B �F£Ñ¬-@4�F³"´Ñh4ÍŠÐD£Ñh4+B �F£Ñ¬-@4�F³"´Ñh4ÍŠÐD£Ñh4+B �F£Ñ¬-@4�F³"´Ñh4ÍŠÐD£Ñh4+B �F£Ñ¬-@4�F³"´Ñh4ÍŠÐD£Ñh4+B �F£Ñ¬-@4�F³"´Ñh4ÍŠÐD£Ñh4+B �F£Ñ¬-@4�F³"´Ñh4ÍŠÐD£Ñh4+B �F£Ñ¬-@4�F³"´Ñh4ÍŠÐD£Ñh4+B �F£Ñ¬-@4�F³"ÌA_€FR.—¡ÙlBµZιøxhh†‡Glß÷&\ÇYç~™#dÌ2M7›Íµ-óÀR½1???Ocë×£éy0;;Kƒ^�FóDòù<˜¦)öv.—ƒµÓkòž¬w]o,ð½|Àc�i=fZ‡2¦1;ôh7üù�ééiý£7%ÔÈ�‘ú€€3%B‘8},à‘_‹�úeê·¨ó'þdô:Í ßœûáâ‘C/k,/íàmÑœâlÚ²¥Üêzïó Ã/Þ°<×1C3XÉÑ^Žl ¹-ÃÏòpGDŸ‡û;4§Qìw¹ôe�З(ü^|8 ¥X1TÛ[|+ ÓÂ¥ý;!ÏÛïé,/þIciÑà­9#ÐäQ.—Á¶m03öÓz�ý {|ýUÅ¡aßn'’š—øY©c… ¡ë™À=ªÌ‡ó«ÔJ.¬ËLd=È"C‚øP ØÓÎÐBà@#‹�6m°2äl|q¬¤ü�‡VhyB1f„Ÿ@ïè¡ÀòÛïk=øÖf½Þô=Óœ„VG§Û5GƧþ€Ç~ŸJµuv&Ëyà1ñX±p.ƒá.dpè;�Ï`{¡SFóLÛ>Œ˜˜BYàÀ Ï\»:&Ô[ü ž…F`€i`[�6aÁ�´¾¥áÈ êwZ¼3»oWÙòßz`ïîÏøv�Öhr‚˜žžÆJuèwçûÿ ­ÝÜ÷„Àu3TQC…ÖAÏÍ@³iÃÛ/›ƒß8«NŬ�NÏ¿c€çJÈ×0â@DÕ™Ihd8ZŒ,Áçw•à­ßZ FP-»Ä9HO3 ®yÔ¤ê��Ç›3�6˜×¹fáðìýkšS�±É5–KÆWìémWŠ“¸'vj�RŠ8…85y¶Þ"øÃËçàEêàømƒ|7ÜÊá*Âw(â ¡‚Å3ÊäÌä]¨÷ ø§‡Gé=?ƒl±ùŒ^ }·òe‰½#ŒÓà�¹ƒ0 ýì|äÁßð-;mÑä83== õFcltí¦÷öò㿞/•�ðúrŠŒpÊ“ X®×­éÂó×¶à†‰y=}߃qáeFD.d�±©saô»¤™Á9�a˜…kØð¹Ù|awŽrƳ¶Ç8— !†ÿx(¤0ã`˜ä,Ì-cëÈ;—æ�¼§ÛnéØˆæ'RŸ¼Ù]÷÷™êĤeqžz†È}ZÉ­~¶e=¸y} ž½¶ërø} €abyÿXð/ÔvP¹¥€€Ç_G0CëÃðákGÊð¹ýþÕÙ<Ë\P¡¿èŸ—D$»3Þïu à4ªÏîyÕâüQ­ g´9ÎŒŒŽmrÕ{«›ÎË­l JÓ#|`r0¡ÞÈÀë65à¿^u ž�À0 _ ”y�†2)!"¡øÐ‚r ‡šß1#€ì¨K{¬Œï¼o”XÅ—G�âW ½M‹¼N+€Å™�ØõèëWû¾iN~¶=õ’Wíчjë6#y®°a¹ÜžÑ^ä^`0Þ4áŸ~ˆ_3ÑaÝe‹Ð qR[5±¨SÁqa« I*\ßÃ$b#>��çà—]ú½ÿØ€_]ÌA±è%¿Kü[½þ:Ž™lÐÙ¿³Õ:´óW{íæ}«}ßNg´9Ž �<µ0uÖì±uk¸ï‰[®‚á¤G¿oÀs×ôàMÛ`�@àê`EZ”<:Ä ‰a@”¹‡#ã“ø„7�S| …Ô’‘ xßâðÑ]Uö‘G*à¸È»Ê"¦~'"8ý>ï|äC~kñ ºÒW1ºfÓëÍñ�ïËU*÷•�Š2D&!dœ ýÁ¹Gñ5[ëàµÍx¿Æ›7µw“=íTTŸ�ú¹D޳Ǖu!O2yªgáo4 w×mÈç<â$ÒO¤¥ŽJ1ÝÅ#�uùà5÷}çž[VóÞ�΃¾€Ó…R¥\Ùø”[­áé­ÜwãsÅ=ÂSÐX*Àgnœ�W®é@>  €ÅÖ†üù8ÿJæ3Æ.ª$—Ôñc?Iú“ÊðÅc�gxšìà’’‹/:¿N_}¤FMa¬P”í«^OÜ0 „lù¢ÑJ‘úÆ ¿qš“ž+žvÕÕ‹ÜþtahØJ}¼cD߀‹Jw>žmpÏyX‘g „ ®>TÿSŸ$‡Iöà �±ºUnWx&BM)ð ³^¸­ç wá³»†1kÒû‹©‚2ÅrÆ ðœ ºj7›Á*߯Ó-@Ž…R¹T_ÿy¬M_N¨0ž²>À÷®ªxðÙ÷Á™çžÒÁâ ’˜È€t=ÇO±†–ŽÇúIJ�"q†l—áož·ˆV€ðõƒ%°lJ<ÐÂhf³Ðwý«jkFÑY^þ¦ïû«q5'![Î{ê wès¥‰õo'©`7ˆä+0á�¶/ÃÛΟ§nÝŠu)gŽEE&¢H<çÜ6ÞòÀ˜yOuLQ¹öDhd †Ïíël¿ 9;ó�n·»š·Us`ZöÕlhê …ñõ&ñ@½Â=G$g±ˆÿ~ón¸"ë K¯k¼QãDHmh¹7)V›¤Ä‘ÉÓ€$ê Õ¯`)WVÚOÂ)2HHœAž¼âìeht ø~#¦AÑkÃAŒ1Úéµê7f‹ÙèÖë¹·§ Z€’ *- ]².Égœúòâ·Wý†ŸhòK°n· ¥ré\·8ùï¥Ú˜A»nÅæl;YøäuûáêªKäc’Å©¸_”Æ‚ª�.ÆÉ*±e iKP졊â�Çx·b—r�§”¾§Z�†ð”_,Žœ„›ƒß¾doÛY…†üŒR+‰8°BeÈ ü› àì÷{ºÐð4drrJ#SïȬÝö Îdâ9ˆ]âƒM¾ÿrã ôZ™hç%›Wqlœ"NItSûŸâ¶ŸŒ'*NúÕJé ÅFô•¨�/Rê,Q¼“)•…BGš08níÑ¿íÛâ×S¡™Í³fÏF%k<Øn6[Íû}: È/A(<ÊCÕé|mÍ…é­%œÉ¬|\{ƒ?Û¾Ov!‘Â�Ià0EìPi(ÉwÒÞã@üÌ"`†AÄ Q{‹ÄQöÂä C*ë$勽 Û-òä¡ò ‡¿Àí˜tÓY üÒ¾ tÒY^*g Õa¿µTìµ_>Ñ÷Z³úä+µg•©÷dl›E|´}¶æøÜ³ö^Çž©ø¹){ã‰yW‘¢t X^(©†»†IÄL``PF�€gÖ1iX˜ÚÒ$¦r#m�TìesуѼw*¡i¥ëMråRà]î÷šŸò\·}Ânðiˆ ¿$“ë¶¼;»áü«�|+î¿ÃdõÞ5#}ø/ç.w˜Ø´Ç ÂÔ£œIýž''Cô·bòëÀ1ùŒ˜!¥S™,q•zâþ�ΠÔãð˜à;fäÆñ?f«�É‘«Z¼È0L£Ðë'†¾²pôèÁU¾õšÄèè(dsù--ÈÝ][w£ @Ùn]Ôqƒ˜ð²±<5«â!qÔ<Ê&ŒÙQ]}_m0ƒ%ÙVg`9äjÌ£Ÿ[È—÷áË3e¼s®÷.eá‡~ƇX;âG"™—¥NrS’roÅÆJ¬DQ‘+ñ¼’C”|hÙçAdC’lùk+åLà]ÓZžÿЀކS’OÒüT&Ö¬{ž1¾ù–l©Ê8‹MF`ö,øä a= ílø#•[•„ •[(�ÁÊ-‹`\¸ãHži®„?8œÃÝ= ‹ƒiú`0¥¡!‚÷< ƒq˜!\;Ն׵àúá>€c�b¨^DD ŸPYå6*H}~ld9üáýãpgÃŽ¾ÁUØ?r¼¹ÿ‘yoqîâfcYñ9Ň#GŽÀº³ÏûŒ5µý×ß…D¾ ¿Rsð//™ƒ o€ª‰ÌrS?±J)5÷#r=1‚v†à³{‹pÏá|w1ݸaÌʰpo“ì´ëù ¸Ÿò.éó«¦vóÚ:_cèö˜lå‡1Ri‹ œn”hG§µKÿíž¾Ïùæ*Ñ,+r 0†Üé÷©³ç�·ÔçÿÝÞŠS-@~A*CCOµÇÏú^yr­AÜ') ²•t£���_{.Ë{ÂôVn-©+EfˆÒT$Ñc<üÜ#ù¢ ÿðHð™ƒ9cw7CEˆ ÙoD¶Ó%©ÜÉpK Ãâ,¢¨‹Ãr/[r><{MÞ|ö8mK´ˆ—VF=‘|‹ÒÓ©Àq¬Æ àµ÷NÒ� ˜ŒdЦh¶%f8�{èñ/Ïí|øWú¦hž4¡õQý~eÝÿ439¡"D!€ë*üý‡ ßÊÈ/²$é#Ú€©rŠòn•™Áá¨Ïà“Jð±ÝUè¶A2t2üÛ�Š�òꚢꄠӲáµç,ÒK'Ú¸1ÇErJ*%QøÈR.­èê ÂD—i˜ÜÛÌÑïÜ;Ì¢PHÔTœÅ£‹‹»¾ÿ\§Û¹w`oÊ)„vaýlÞ¼<«ôÅòÚ³ÖçÊ2—Åq,xßå³ð´’+ãéh9æN� .«ð0“ÃNnÀk¿±¾²`³¾��µü¸��‘r¨‚™¡«”¬è¹šYƒC ¾·”…[vÕàœI¦‹.@€<úYuQxlˆSþõ%‚/@xɶ:~u¦ŒK±ûME×9�oå7 Ùf·¹¼¨ÇäžÂ”††žÑÏNüc¾ZCá;UO^€µŽ¾á@×Vö³AR{\mSõaà„v9à¹cÿ»c°£oŠù†Ì7Á$a“ ^9=-²ÛÅü�È:·3<Ú²ñ“;«P÷M¸h¢VÀÒI^ q$ÎöŠc�¡â7ir+»xç\YT«Ç¡C"0²…ãÞ¯ºíæ'8ôŒœŸƒ ?‡‰‰ ´ …?„‘�¯2 Ó£9 “à†!Þ¸­ÁÉ…cê70•¡+ˆR£â†UÌú—¹2¾á® ð­Â_/³K¢ŒÈ�-³ˆ±ðT1bbv‚L‰GL~£¸G�cr¸eWU ž~ÚTŸn<�}ÆéÊÆ$%?JÕw{\4Õåÿøè(äm?9˜ 1cçaaqþW*Yó£ÝN[—óž¢d‡Æ>QÝpÎ:â�Ü•*7·Û¶ñ+/Øùž!:<ÇÁi–tsNW™'‰ã f<5óüÚg·Ð=}3Y®ÒΑ’’±�¸Ì4Eü¹ÚûÊtgrˆ&`ânµ8ìè™të##ø[—Îï2©ð`bÓGõO±4LÇù”9rÁ¸wî«Àbd·Ä‚1;4\àÍeÞï¶¾6�7åB �ŸC6—ßÜ·*Ÿ-ŒN1’Óo Šw·yøÀ5¡à ÊƒJ'"¦š” <¡÷f9üí#CøžÇ‡ ”÷¢ïóXCáoöú�6ó:M?‡ÞÎäÁ{ÄÜY·×1ÚíV1<ݦ�“Ý~£#­*z­Œ÷.åðp#CW¯k!z ÕÈžc®ã dZ³D`AÍ(>»w)�†‘TqÈUF œ¶•5ñ‹ºÕɩǦí羊*Óo2,ƒa”O.k�ð5Ûp}ÉKí�´§ ã:r•Ư™0È_qÇzhfÅ„BŽIZ ÜÛŒñ€sê5–½~«dÒ}6¹äïBÏYt:­|¿Û·ÁÌ€iYœÔÔAˆ,dìšûÁ]8ÝÇIó‰}ã .JõK‰¾"ܰ�cà Î[„y´>#•Í* @8ŠÏõ;õOäs¹fTû¥ùqt äçP�úÛá ®39ÝЮgÊ&†N?ï½ôÝPõ0ÊæPµèª[nìJüVáþ Là��×à“³Y0#%~¸‘#'Ã2¡qèV™ûín·ý^ît>=;;+N‰ðY °oß>¸ð‚ òíNÿu]´ß ùÊhul’õúNJ8pH8ßæðñgî…N½ ®�Žin—¤Ö¨+æRq"(ŒypÓ­aNDd"_·0uxóÈ~�Ïذ´päðê¿;š•ëUYitqlÛEÈ}OçÉj¼ÞBzé£Àz–b#,`éø]$�{ïˆ(íå Ÿ~ÛéÉÙjJ!‰< “¼^;è.ÍÁxÑú€igþÏ�¾{Ï£?éϽüÊÍízó �^ðæòô&Û²sŒÄ€ôd  1^ˆ¯þú ÷ÀçIa{d}Ä>¥ºELáqäÈ?5Ÿcñø0¨R.9Þ�pciÿ{~ôý_Y•7åE �ŸA¾P˜Ú|ÁìÚÄ�(¬RÓ@çf>víAò:fäûI¦'DË!9d–À»ƒ�Ìä)kª¦\¦d…b¤ß\òìÎÒ-CÅÌ»øþýü"×yÅWྙýÏ ¬üÛíñ�—frE5>7�È=ß`W—=øû+‚ß3“ ÿÔ5G…]ø„Z”�oÖmøý‡Æ¹m„Z%K4Q|éÑû¿é·—nh·u ý©Àôô4rÃú­uþ)SùÞr¶Gßµàÿ^}ˆ.¶=!5RÎTÀ<.8OlFp€¼â«kÁÍ•-Š_#üUAÐZjugwÿ™ßïüCcy©÷‹\kmxl®ø� !Ôʳ[oÚSÄ9Àd±!¤’Fâ+>fª åIŽÃ3oÛD�œÇM0vÌ å#³ÜjÍ=ãèÁ=Öà§ ]X?ƒÍgŸûQ>´æ¦:MEuJ­NþæŠ#PóeóÄØÌð˜¾S˜Š—ÈÚ„�*Á{÷T — qË wîzøwÍìþ‡çæ~a�þàÁƒÀîY>:÷�[ÖNY=35‹s+åo7‡>B»Ÿ�§M¶|ÆXòHÀDÅŒe ÊeApÖhnxZ¶ÊYQ¾fàq\›gþÝ­fcï‰z/4ÇÛ¶� ­ù´]©æ)àqB…e]˜óð¶.÷Y<¢¢ü¨è')Ù(Ñ—ê6§+?s6dK’°qU-"™ ïÌîkœÅ›Íìù”ÓïýÂóz½NÇ€à.ô> ½æ Vuj\xÅ¢¤ôð£Œ_ÞU 7]²€^×DYš•ºFAzB{ 1éÓE¸nË"~dG l;> ¢ÍO¾:ýåùj·±ø¯ÇýM8M`ƒ¾€“•éõ.^pðy¦aÈ{$“~e¤çû²ãh¨Å�r]¥M’iær@Xœå þîá!°­dg8G”N§éõ¿ãÈÜÜ?­äz›Í¦pMÚ·ó¯òË3ÿ»ÛX�Éõ*7žTÿ#3eúâÑ  ª‚b�,Ê«OÚdCœOéµ2ð�_›Á~Ûf¢SžjÌEA@•ɵèböMOþ®kV–+ÿ)j#•zÞèØø–sÀéQ«¨$­&c&Å’ÐJ±Kýá]S8<Üt1,PFÓ‰sλGö=â/ºf×ã�­¨ßçœFK#˜ë_ÚŸß·Ÿ1“Ç�{u�õ Ã~qb6û–ÇnZ5:W¶…T­¡¸vQ¥nÜ4Õ\é‰\œeî9ÀŠCÏ®ŒŒ]tnûi‰¶@~ …‘ñwm¾à<ßqåcX=j'C·?wºM[Žª86ë `wÆE–Œ¯¿u#äË�£ØˆH£~¯ï�ñÆÛ|à{ÿóÉ\s·Û…N§ÓŸ?zøK'GXÌk,; ‘i„ŸÞUƒžµe™› ±Ã� Î^¡8Í2j=D˜#äû{îsÇrªD8zmËÝ�ö{=Ýlñ$fã–-ņ‡·W&7‰!0„ÀHy_ˆ¼ù¼E|¦ŒéÈ_•ÔW$½q¤yme8üÕÃãxÛ‚ FMu„{‰ƒ•áÞ‘=;©=ñüì¡Ù•^³ã8°°pš�%Ê£ÿ•fýèJë÷yì’ ÷øQB\.r¥Þ˜ŒµJB|”ñÊ–Àg°e´ÿôØ·3�2N¤Õ’)U�ù½«Z‹óï[ù]?}ÑÈO`|zí:( ¿0p{bc©,“Wmj¢·œUóÔ#4*øV¯'W—#ÂLÞÁ·~gŠFFPu�,j¦ë¶Ûl¨?÷�»{øÇs Ë Gß6Ü_øÛ~§¥Ò �+¼°ÑjŸþË=S�-yÂ,QµXr ¢ãâ1®nÕ3ÃÁ~çìeh4…­ÏäÒIü—G#Wþëã¹Íñgq¹ñ†Òº³�§&‹ÂnÇ‚¿ºñ 8Sü¥þD 6Sé{¡å}ˆ÷í,‚i¥ç €aAçÀcõþò‘vëõã6o£ÙlîØ2=qqwvÏ,3 ž¾Ìp#hwW^dŠ/=n7‡QÌc«*TøÎ)zp}¥Ç8ÅM¯UŽQ)Úœé\"X6xó½k Ÿq•5•È€×÷<4?”co9QkÙW¯SŽñßïì~`оtbì "Ø¢¿Ü5Œ;º6WSxé˜ÊâÔÒ8 ãªåƒ×Cü‹9õAu)`C¶›¹êÐkNÔz4+ǶmX¿iË Ê>…¾˜ï ú¹ár7Ë_»e‰¼‹“ëb-\ ŽQåq/v3À‡¥‡D�‘óp_ûžç³ö»Ýñð½óóóÇuÑï;4³çse^ÿk§Û !ûŒ9B·Âñ}� ¥/–‡Qð˜y# â<¢ø–³¾ÁŸ=ÑâÈ¢–ñâû,p]#°J/ظù¬á㺘Ó-@ž@¾T}åÐú­ÜSGˆA@ÈÈ1ØŸ_{|Ç�¢ ׸ì.šOJ_—ÝC?øØ0Ø'³Y_>á­ òùQ°óæ[–íAdLe «V& t :NÞzéAð»L6ÁŠ&Ba”(&˪Þòuv>€>^åùœ›z# iZ”óÛ?ܽë±UX3;;K% nó}�™&¦Û—Œà»ÊP(:ÒYÔc*¢µVy�èê©L� ­ 竬ÙH¹lá²Õ^£æ§X¹qâ�rð0ñ`¬/eøs§[h°¨ÿ™rï`l‹Äï”Dñ`# ´3 êðb�Êéö –Çw·[ÍUïFÐXZòƒÖÂÛÝ^_Bb2bÇ øýKz¤“�Ø)' #é‘>Å=Þ S7ØóÖµ˜ÆÉ–ÜwÑ3²×�LNiíH¡ˆ¢PÝ2´fƒìE­TjD—”zpíh—sJšØ©qhQ+®z˜„�qøü¡"ÝݰRMB92<¼ëaªZîëFGGW}}¡VøÅ{î œúüëö=Ú7L3PÆ»x4˜Oðíi°lž(f<]£´d BÛ»Kxóùu¹À@ÚhH¾gdòÅ7 b�šgbýæ?É˦˜e>U)Ç5àÏ/Y`?%PõO‹êk‘£Ç.:ðçŽ@µàÉn!bÎ%�ig86羺÷‘‡ß6¨56�|Ì�Ýñ™€{ᆎE±\>À_ûâz¨•……,ŽÛ%Ë”Uè²|„!òßܲÈ ùÐ*Ãx €Q�fK‹�ÿ<>>>¨¥žTh¢(M>‹¼>‹½¾„à _²¾ ýV†Å äéÒóøCÙç$üÛÌðŸïYÙL�lOD\/Ø:Qú·ƒû}µMü4�fóàsÞÞëuTˆG'f ~æ` ޏI0’¿dÁò1ù9Œ<ºIõfž &R„¥»Žs2JÕ r�ÉÄÄ:ÿ¯²ÕŽJ7"îðº§,FütÄc'0Õ¶DŽ`žq,z¨aƒÊ¥ÅCh//QÕ† v�›‘\ç·z󳋭‹ðdUûpëþ ð@ÞqEaôj•¬Ÿ$i±Œo±×Ÿ=Ï´KÌ„Bá\æ„ÁŒkÀP¹„Ý% ¤3*£�Í�CL¼z¸Ã™¤Æ…}ª[v ˜iÝ4ÐÅž$œñ$´rÕÚó•¡(T(vJ§gÁ›.9 n×P �7†Jk`Òá#][3u ïY,çQ7Α4ŽÌ¡ÛilÐk�ét`íÔè'÷>Ì4¢¼+ñßQŽtëÎÆËñŽ”<‰»DÊ€+cÀ†-€mG6ü%RˆöÐÚd—+gt­g0�NÊk6<ÈMSE;èµ³píd 9F:ÿSÅu˜Ì‘‰“^wûª”=Ù¢PéP†a‚á´gÈÀ÷®F=Ó/÷‚¯™ÿ}NbŽ¡\Ëçú_ßž;$}z)™ Û\jÂBøÝ^ÝàÏXßCŸG‹ôújÃ�-Õt:¯ ç_tÉÆ¶ç¤ê¡Ä&Ù–óhc! 4âGiôôTý¡Q5­–iõ!|úÀ0d²ñTZ‘¶Ë °3ßèw;·z­÷íØA¬ç>¶®hÞæ{n€©ïå³ìBŒ[Õ R· i×BqߣnÝ¢&»B»‹j%‰8Ô¦6R._¼vÕ¨Œ­ß8ÙíO¥ �颉øsj*21Ä5(¡¤3%cÎDZïQЦïÇ¢&´7ygþÐgšóóǵ�Û“¡ÝnPÖd¯ï./Äzcˆ f÷-ØÊšPRSµ¬J�ìѧ š&Á�“-l/çã6ð=æZY=©P €ƒ‡fŸU]»‚ eÚáãß2€®™êa¿nÆ-eÓŽtY¤)¥� “ó¡Ã=0™¯Ú=ˆ^éÐn,ñªmþ·“mføŽ™:ztîÿé-U­ñ(ª ¡;vçÀÌóXj²¸,† ò—GéhÊgŽOoA»mÊdP·€e-ƒù›ºÐ3ÏñþS¦<n]#šùÑé˜ìµ—, ×g"å:ªaR)Ý×D¨âòðëù!Ÿþíñªœ ‡« s°”Í|zÐë|"s{ç(ü!Ó4¹ª‚«+”øìž ø²E¦$±Þh0:‹kÔÙ¨ðËk}.ƪ¡œú «Pµ7Ÿ}Îot¡'g¼Éä«7äŠeŒ 4¯ïA*Ê¢*mõBe÷*‹—8røØã#ÀãùÿÒçÌ»uÇíµ>x² æït:päÈÑŽÛø ´m• ”QöÃVî[ÌR�îÑKÇ4"¥�I9#~áHû®E& oW(?|Çϰoݰ¹Ö3‘ÑÑQXlt/“Ë3Œc"/™WLvSµ¡*ª…”rOÆ.Z>k÷v ÜÝfÑ i©3ƒ›nk÷Ìž]÷ z­Odl¬nãð¿z®OÊ÷,]œ W$0dUz2*�1¾ Ù¾Èïšì βžk%=^� _Æ~ßýï^êÀ9£ÈÐÐ(ÅÊõ¾ÓSsfLF€]ž6Ú•uså^¥zEÅJ2¨ÎÀ/ª€‘ww�p˜dôÛŸÚ·oär¹A/÷'2w`ß­¾(À’SLÂõŽ–»ð…ý4¢üJúx�JHšËÌÐÀ1á�ç,£ë³¸Xèrf&Ãú�A¯óLÃ'¼”JÃ9¢€s�E쪱ô–¬¨B¶f—Ã3xœY¨÷‚�õàÝ=¹$5Ü ¨Û\†b!3°º�ŸExÞ²†ñ饻€É^;ñ²vÍ@®âð€ÎÄ´/Î>½ZàYk»�çœWAt•ÀÜ ŒuãSë¶ p©çŒ ¹rù×îQÜÈ ¼jKÀGHu\P~“?¯Ì+Ç዇ P´xÜ„!òöÒ!^ÿ6½ˆzXáϧ˜Ïܲ8ó8¢)‡S2Bð¾t  ùš#§ÞF»£Ã§5+ý�|à�žz˜zŽ *Ž.þÈ—«X{í`Wyæaå‹¿RžD ˆ…»ð½pϘìPàaðõ= žÅu?‡ø\ø�ߥ‡l¼ZÖ-§uøà'¹ÆŸÅÜ¡å=ãåâ—¸ÿ$£ow5³pÿ|–…šâ �ç¬r™Ugma�õ[¿|´'b"qe~`yj#ïõ{Wt¡æŒ •jåÕÙÊ�L¤Rîü~Ï„Wo[Ïa<ét@*0®òâ•yµ;7rî>š‡@†J¤ƒÕ2€7æ»õåÅ/z�? ?à‡7VíÛßO¦!B¾| ˆr<¯êâér©,¥É"cyƒðì¼G"ƒRLÆ œ;º|MM—¥¯*™BùJÃdLu3oS£�Bƒ¥f¨ù|÷@ãQ…àËs%*U½X+­M42è6oŸ?‰‚çO¤V³Ásêïê.UîW*ä\x×}S�É2ÌÃŽ­ >¦B]õ”t»\;ÞX…S›Áó•!fÊgt0ýŒ CµšuàhãfÓ2A†|…£†Æ Ÿ69{|cË–”ÒI£9�}¤>üx Ožüý@ù °{÷nX\Z¾+þN†Oõ‡zí™D±d]0cçq\3àq‚kÇ:ÔuL„TÓBµŽG× h‰gáû¹óÀìóÉÊÙ©¤\è{¼ìÜEðºf2$\´}¢TÅ9&ÕØ [òá{‡ Ì'FQöz¸³»ƒ^§ýµ}ûö z¹?‘ÐØ-—Ëâï ßú<3-žôOD¨•ûðµ¹øÜˆ Fm&âºtŒ¤É†¢•ÀƒÅUÅb4´ëóÑm_¶u�ë$g¬á¦uyqdL:AI‚ês¤ë&<îx REç)׎xiÜ=î•> ¯¨,3ÆÀBÀ‹Ê=U&5YòX³Ó¿xzó–¡Á­ôÌ ›/fY|±egŒ(©Š#ð󊸾a0ÆcÃä˜�–j:«4ÃÈqö¡ÇÀÎñÄÚðÇ�VÝè6ëÐumä}SNœŒ BI±ÒöG¸‰ ƒàó3E,ä\¸W?ùŽ�Ù½Ÿàò~išÍ& oØpoÆ4îE4¯!T­9ÁWæ‹T÷K‘Äâön2]��‹Ü[<@¼j¤Ÿ)±R†G}Ä0[*Ób¯=|pÐë]mÎ( $dîðãà‘åç¦� -‡íµ.äD'HbŒc×TT�®æ ‘ãðùG‡!g«0à´—Ééuß_­V½Ô_š¥V+Ƚ;ÍŒY*6G@E´˜hÉÒ%Ì”ž© «ï;=·¾tpÏ=²xFh¸o‹Å.Þ5W„¨ÖR3Ó�iŸŠqg¸l¤ Ü )‘é/~÷crã¶ ¶ÀrÆ �õ›6]`«ú.K2 ¶�tÁmg¸ b¬Š`IOBé.gpË–3Tý*3M¨æíY¨×ëƒ^ê/M­VcA¯ùmÏõ"p"ª8pïÑ"€AdÛGý0Õâ%)ÜGØVëƒÁÔ³‰Ërµ|e˜5Û½UíÎXÙ+çöPiÝ9—c&ÇÈs Ñ5Á@OqeŒCÂpO°ð�žgåËUÓœØòöÒš�_+×FŒÕ¼î•P(U ;#…6…ÛRtß%Ï¿g¤žŒQ?ޏhãδÀCà;»EÝd(…AÜe-cnÐëüe ­·]ÿVëÈ!†±4ȘHß8šç¦ùf)Êp&5ó=ºeJoÊpØ^ë§³€SÀZ}ïì /¹,;èµ®6gœYX\zJylMø@‹Ûm0Dè;p}†”N �2«ÔðWTç‰s¤»ç Tp‰S•$DXœ=pÏ`W¸röíÛÇÏ>kËÍ#‡”a&SÑ,äðÍùX_Þ‚X“�~GCpe‚cW�ôyßWÅZ2”<ðÙØ†³ž²Zë) ]¥±[ªk·0ò]Õ~ )L2Ž˜ø"&VLåΦ ~aìºòô†“¶mG„eÛ7e ¥d<…Ê1ÀËÖ·EU¹rÄDûŽé¥††AðÑ�Ufeƒ(c[Lèì5–Ð ç_N¶¦ ¿(Ãcc_ë/ò��ë€þhÉfÙ’Ç)’â”A’}M´¿ÏàëÚjÓpPÅìÜÈøìÜáß8Ó¨�qÄñè*;›�’ô„¬° «ÇăOi̱•/'³aX“Z9~¯•ƒ’í«Y„Rät› °€N9?̓¶vÝ¥'Ë`­Õ⌠6oU…å Ðõ‰_>Ü\¶ŠÃ($ äÉãì§Ç�0D»kŠS½ö"wzíï z�+%|å ž7‚ ã€"†K>ƒÇê"ŠÍŽTyYÜh/Ž­o­º`ù&Æ’EÞÃL!ë3ó¥'ZS«ML¿±Þ£§Í T~¿ˆþûÂðÒ©¼ã¬xÏùKðß·Ôášj¬.—F’ ˜!:zÙy£e~trú¤eÝÇêðØö “ÏHQt�Ï€ëÇ[¢yyjqÜ?bq#­�vÀà‡=KZ,qsE‚êðÈþùÙƒË;vìÜbŸ33;hyaþ–^c‘«ÑXâ63üóîa0YT÷—ÀÆ—§c íƒçûï;F’ À3V†œ]|ùÄÄ©)`WÊ'@š]ï Î=Fqsj‚^‡±+Ö6SS£ÝCŠ }]˜ºvÞƒûòĹòy(‰496~`yyé$™{¾2œ¥%Øqÿw‚€ÉI²íny´Š†Å1•¹+rç1ÝÅU½†|^²®AQÖ¼|@qÈVj41=}å‰ÔÔÆ&×XÙ‘éwd‹Ådp�Šm5;9øÝÍmøòÍ»é?M·à™£Üúè°| Ò™ Xjw‹×aäÃþNök“mæ»F">B300í"fÌu]ã*sF �§œwþEÜ0 5ö\n@p\CôºIè(4¬Ã( ¹šÅÿÈrú̈f”I;ß4ùÒáýÿ÷T®H�ŸŸÇ4áºk.>ØoÖ™27ÄŸ–Áá_w×Ę̀~ŠqúAÊ BIüÈs xÖYKØï±S9<�ÙlöìÚóÒé 1³ùg†Æ*È©ÖáÕêYð·ÏÂÏZ佺�ÌI¶‰!³”ør>pÝ!øÏàû†êùÍE×–ÀóÀ±‹çn}Ê…¿yÂ.~8Žìê\µç…Ë6 €�µx]+V«)êUmpLœÿþ¨‘…¢Å Y$r ˜eÑÑý»áTÉFûiÞ·���Öî&51Tt%„Ú8^´…S©Í‘»–P…ÌdôƱ”Y4%8êC¬T潎{Ñ—¸êœQdï¾϶²E#Ê$’F§ç�ùäËJkT-®“r]LŠvCÍ-“áxÛÎ*ØY?iý ~Ýì®ÇÜS¥B÷§j´{vîûgNŽÏä-A¦šÐÕ‰h/£FÂ'¯‰èñ3IeóÜ8Õäžkª8 ó-ð=ƒgË#š'$22±fks¯`Ì4‚ Ï"âݾÉÿæ©‹ü¦ñ.ú2"¥“E�P µw$ðÛ¼aû2ß�ú¹f¢(ÈÈWGÙ¡£Kï¯Ôj'MÆMøž�mØvø^&Vž  ë\]ë0)ÿ@ÕºLILõ´r><\·Å�0Šf3Æ‘¨íÍ©L«ÙØ!{¸âlsb�ËypߢMœ�RsýezÊs�bâ–Ü׌.¨ö áãäQ÷8£8 åJqӠ׸šœQdó–­ÚùBT$þßõL¸hý2úÏezgR`¥,[�‘ àÖ}e̘¢‡P¸{ú­V*ÅöÀw90{ØÍâ}hXJ“·¬PvÙ÷rÀÕ°­8ô%`ÔŒ‹bÌ®›� *}q¹rpq*ŽL"Æ ©ÉŠ¿9¼~ç\ ÷’ýÙ #}¼yý2p—1H7�¢Èe©²Y¥GœŽÉ>ñì½Ø“¾nŠnwû¬²á+cN+¤Z1ê�ÞU¸âóÈë86œ7Ôƒq IH51¡Ä* eg¶àÃýó9„'JÂè5–yp×Àxñ»­o²ŒIRq§J[²‘§ÅªôXÇ.ªhÏ„¯é6-\;â¡%õO†(*žÐ2ö9»`Ðk\MÎ(²w~ùYh0RJÄS#p½ls]ôý�”QÂ$A5Î6Rjšã3ØãfÈ5%V8'·ÓÆs6oú—�.ð8273s?c–L:S±"۠஥¬JaLj­�Ež,TÎw&ºQ:M“mr⌄"²Ärå!rûΉ™äVz <#:÷ˆË-›Þò”r:¦LÓUEÜ#0öÈ%mÄ3¤™áçæ|éëˆ~òå V'¦_|B®”‡ÇnDÓ¶x4¦ž,ƒDÊéÚ¢ Éœ—T뫨­[d=r„}Nº`Èñ¶Jk ÍÏÎâ Åot‘ljZÁöÏWÇ£ÚAáÆjdÅü“XÂbäÄ"Õ½cW-¿¤ä@70åÜFñ0 …HÀJ#gÔˆÛÓ^€DY*Ók×m`™\ŽÒ­äl䬔¥h^Rz¶¾ºAr„Zí}‹Y(ä�¨M„Ø�œT°àÈ=ßûÞì—zÜ…¢Åîév±VÞ?ã±£6äʲ@º‹£´çÈ‘'4x&«Ï]Qí‹boˆ$ŠJ11WÝ|¼}êãë6>ßC{œãq%>ÜPíá¶¼‡"Iδ�}Œ”kMÎÃO "#_œa{ñ–x¾ªÒV e�×Çåž{í%—]qRt¶òùë¬B™3Q)Š!¼|] |—AôôÃÔú¢þ‘h žŸÚQËôd×åhN:c¼d±�÷í>õýW0³ÿÀro~.Œ,Ê< 5¤ÇYÈž>õÍ›7C¾\ý£ly(4ˆb©×ÏÀ»®›·Ï0iC›KDÉ!Hz±F]¡^²¡õå,O|ãRNÚÅ6Z�7·< ú=÷¥v> z»9 nÚº�c@OU?©Ö&qß§lÿþX•g­h\ˆÔ޼^—¬ÿŸ½7 ¶ìºÎÃÖZûL÷ÞwßüzÑèÆ(�˜€$8ФQ25Q¶•¥Š¥*Nœ¤"WJÎW©ìR+®8eÇVÆRlI¤8I–%R¢� €H‚˜=ϯß|ç3ì½RgOç¼&8`h<ôm,Ýý†{ïöÙkúÖ÷�úäÖžákgwÞûà×úk‹LDVÞ¬‰3Þ­ºfž[E–�Ûüª�ùß<•Á`(X f¨T®> Æó{\7}�±w `³�;wîo´g¥]¥± €m¤Ý�,PÏ1–ûƒý1C)x|5н7pËMÃíÃÎÑk¥²ÉHž³Á ’PUšÅ >s¶méÜm¡OA jÄ€õµ!ÆÛf†* LUKY 0ŠÆmwÝ{óku¸ý,ß·>”o#Br��2(¸£9‚��²U)ÏEï{WÚ*Rfk•#$ ð±Cë$%*®Ø+ 15Üåµ:þWjóÛw´×Ù>�²ë²¼ÂEð�]=Å–€k`'ÎŒµ²Ì #utûn;Z¦¬tÐ�8oh]ÿ—có¥?L%ѳZYÊ¢óÊÿ‹Pñgδ‘°ÏGUehLUsı1i Ë�Ay5`Fa—Î�ýñ­>Ï×Ë® 2;; ËëÝ{£F³ªo—¡1Ü9™ƒ’›ze®÷ZUm¸4 8·Ñ€BYš»ÖºëË„À��JÅYšJH0ÿýl8ðãåå5ˆb†¯œšЬhŠ‘’t|`Ý :ñhAï›êSùº2PÐlÓ©cÇ~úµ:ÞÁàл'vì÷2ßå ‚ìð cZ"ŽvÆX%б©fé~jaùPÀoÜs‰ÃÈHN*G¨©¨—Ó¡ÝûléäXšåïlÍmC•SÆ7· �@„µöŽ ¯Ù3�Xp*ƒúÆJ‚Ív�¬ªë£SKYGŸ~êo·ò_k H~:ômšj–qKøú±)³®]&Z_Œ–hÕ|SeÚ7„LšM¶ÉIØh¾ƒG®(ïØ;�={ö€ößxóî2éðu_è�ØÑ*<…ÔIÊ©šÎu˜£ Tð|?PõT „¬×JG·ò<_k+�a³™üyÚ]c¨æ¨@ „Ïœn*!L°«Òä(]�)¨@ jDðÀÁ.¤9)ƒÒ2Í٨ن]û½ïµ:Þ¸}ñCQÒd£fJÛ+ë ¼cWÑSr�Ÿ÷V±ÔTCõuµ Øž(Þª>ý"ó&¶ïÁÞ`ø¶×ê^‰µgæŽ4g¶¹¼BÇ4„ Î�wCF+ß6Ù!V_§-Šéx'æ‰F^ÃU3ð’ TþÕn›š~¹$QøY­¢æÕëñs'8ªBæùŸš{L"€Ìܾ»£²L°KÅËkD ¸xñÂO½î'¶E6öäܹsÐjµhµ;¸O…û6*"¾cnÂm�gca«äúävC|ôrC)2èpp•~°öyš>³•çy5,KÓ/ ™v˜„rO“R¢UÐÓ«1;^>dÇOç*]¶CÄ )1þî�k�§‚e¢2Xz¦ «�wMNO¿jN“…0™Ùþ™gäŠÊxñö™!üÈLV¤´ê°+Þ8x¦+MQ�¿ o¸у;ûDU£Lÿrsz†·mßõ®W{ü¯Ær Þa4gäÒ!Ü?7‚b–³ÝRÉ"úº‹¤ YÅ �­5Í^à<'ö—tcùÉS§žå-;ÁרÊý`aÛüスK^H‹¥CJ€§{ŽçÊ‘b±ŸÆ÷ø 4°ôïîS‘kWëh‘˜e�E�LïÛð °¸Ú6ödûöíÀÞ"1ðƒ_:9G„¶dÜÖ(jÏÔ�íÛ‘‚/Ÿ¥8*l#Í@[•”|㮉?^[[S[uŽWËŽ9‚1È/EÂåôæaj73øÆJ e”WUñs˜««„0×”°7’< ÚÉZ³Û Ë嫦wO‚èý™âíÈ~0P÷=ß} Ù€€xóÔ#û£­�–«da…xu"ŬÞµ­ƒÔ œÜü¡Ê TÍ©_íñ¿‹[“÷Ë<%öUÆþ „›gLA-³rG,¹€€�âVO.ÅZÂa("ô–•̳¯7›cÔÛ€gž|RÆ~…‚@ÕŠyÐjeðôZ¢ŒÞ [èÀ–Ž_‡'z†]�:Ô”äû¦Œ¨Xb2³ ³,»KOòu²±w J)8söÔÆMXÕ5 @ø‰Ý#0e¨ü4VÿYè…Ž=D¤àKE“”o–º|üØéooñi^;zô(Ëþúc¹”6X·Uc$xb5æ(.<–ƒçšW:1í©AvC¸inAå_ô5f`Ûöí¯ZKzßž}ï “ ïÊcÊöcP’*ê.�Ñ­ B…Dª8»\ÐiVE_Þ¿0€~× õ ”HÉBô‡ù­÷¿ã/{*ýÖ[oõÿ~¥à‹�»÷ÆÃLn–úâ[\ GÿÈT†n^‡k�9Ös1I$º Ü/.s@ù¢˜h&� ãÓÛ‹Ìì®\>AˆPè6"ÉO­7С"Xc7į_oeæ±:ØÎ@P¥õLOÎ@<ÑÞ½ÕçùzØØ;�¥¥%¸åæ[öG­–eq0½Ý, àÈΩФ«\¹pMsÛþЙ«,Žö"Ç“ÃÊÖŠaîßû©­=Ë«c§N�‚<¾Ð_]æú8¾T/®Ç¶ ¨ò�ªYë\ Øè> ¸ejd £¾ Á†!�ÿè«9ÆÆä$¼xæÌß â¤*ÌèÏŒà= ­å)åk‰ˆ…ùWõ+ÜäZ6ÏJ@3TpS3·µprh.¦0‚³ç/þÐ5ïF³Ç͉ïæê×·í=ø[åñäÌ?8|˭›{YÏ#‹àïqb´X=òáöÉ ÕÇ<,W@mòÞý°Üüþðø„aõ‹ˆd¤Òµ³§NŒÅlÓ•|#OS�F¥ñl7Ä !Ë眛ëæK \ §•¤©€=“F¹ÚÊ`3ŠÞØØ¸.écï@:—?Ɖ49¨‰”UNðÁ½]�²ªy;fó� ^Ú™~Id’NÀT¶¶ºrv«ÎïjÛþ}û¾ž ûHA°)z=5�¡rä¼^'Ä’ß3×ö*†›'3èg��ÔL3WMÎmUdnvaG/…ÝàXøí`XOßµ·g¹îجY¹ Ý”©„¥<ŠÌ×6T™ øÀÁ.乨ôVA« Üø#?è8Ë,㎻ïý/âù½fnºçOqáð¿l¹÷7&ßûáîÛþy/Ùþ¥pjçÅÃwÝó‘öÜ÷î¹é�¨9UE>€�Äïßß9"—o€ƒ™ÚHåìËD ž>=]Fß@Nß²áöîÞMéÁ¿[__[uVurfË–¤áÄzA[Ù~ð¦æ™ù˃)n›AÁd3CvBÈOÍÏoÝÙ½~6ödqqVûÙ~ÅN¹Ü8)šÈªÂ +_ë´²–~s)#î£ÝˆÃ$³Ñ,;D O6¢'N�:56MÆ+í‘Ç[jý‹L‚+Jp†0ÊáON:Ö¦ÞsMáÏPážÙ z¡Eÿ˜%+‰�aqðÆ#GZ¯äØ:„ÃÁàmÍÙíå{yfàH(¸ov¤@’B�®(+ÀCt7gþëZ?ÄkÔÂOé@–‰úÐQŒ§Ïœý¾›þÄÔt»µm÷§åäïÎÞxÇT£=C$D(³,Ry1p”LL‰ö�Ûú8ùéC·¿õwfç¾oYlÏž=pæü±�…ͦ%ÜM9_ä„ï?Ð52(®ClÅÏ]Æ….ŒF!|þr£¼) ±°Ì†<{öÅÇ_É}¹lfròÑÁÚe ²´ìöžÊ BÉ,Ù!ÀG•žlšÍ&žnj44å¿2a)C“"Ï`bzþðÖžáëccï@æçfD£I¨ ¿Y2ïŽ2PÂԷͪÜü¼ˆ‹5/ Bh…쨺a,Bî­-�möáìðžÖÊ,gô1-BKõÔ±6ˆXzÆ{vÀ6Cw†xÊŒOÌ7 ¤R±oêêšqœàFoøŸ¿’ã:~ü8‹8¾«1=§'4°¦Q{xzH£~è‘û6óqCÀu¨.¸¯«Z·/‡ù~ñ�SCP@%«½ mdê–oº)|©cœœ™›hNÏ?³>Ò˜œD.2Qî=U2‡Îo!çÆS”Íø‡³Û÷ü³ïwî&3ƒæt“Ç]K�/E¸wv`#"n0Ê˾ U5yÔÅ~9ú  ŒWe)üJî˵`Ï�^Åt¸DÒ°º›‹ À“k‘…õ蟵9–V´TpûdNù0P.«6Fao8|͆dßÈ6ödemý®hbÊÈ{[R›òÿ‡§3Èûמ㪱ŠXÚC½Pžé†XB´Ò¶,³ÁXͼ”;yù[Ù c��¨�Ú÷þñÅ ¤P™«bµMÀksz7¸†F ôöw)wp; ݘ˜’�fó­¯T¡0iM݆;b¼ÒiwNg •®f!ƒón6ù ç|—Ù>ø¶oSo™Ö`Éy†ê¶é€Øëe+)!lµa}£{çK_³=ù÷{o?b1Âfy)7œ µéK!¢aMÿƒ›ßrÏ÷t¬kËK÷%³Û %�ڹˠî˜Î “T®PªiVϹs6*' éé�’FAZ…Jo¯IÀ "¸´xáµ­Mðl÷î�Ÿ—y–"Í(PF }¶A(ñÊñR×_s  ˆe`4’:fñPMÇÞ}ßÛ¶oÙɽN6ödÏÞ½Ûâ‰$›ç† YE s–ŠÒ(æÊYT"d.ãg¥6F‘ß�n#1ôŸÛês¼Ú(õ2²¡p1CSAŠ�aÓ,öµ6ˆÉÄEð�û78Í„açµ…0N`˜?÷J ï¸ã¤Fûö2 @Gˆ£^À÷. ”@WºÉ!Ú‰;›�(»Óz˜+V (ÖJs²â¾É! ëɰa€ˆ'xÛŽߥD·}ï^Jfvü¦¡¢rõtWÄÓM&UîäÈàgh€™âé¹àüjçn8´÷Ê÷]XX€Ù»w%SÛ¨¼¸VjµÜ÷yg3…¼–‹]™–.ruÞî̆)™Ô©a­Dú õû¬c5}þRÖ_½ü|:ìûÀ§Œ�’€á‰ÓS¥#©iýÖG �«”Qóqª'R]Îr• ÍÇpþÒ⇷òü^{Òë÷oŠ’„ÀÐ7—y½¦w8Ð.°(HhÂêM:n~m›‚¦¤6bK…†PŒ6–AJyq‹OñªÛ™ Šv$žA!4ÿ‹¡ "Lš¿¹Zj/¤ .ˆG¬ðÎe´ö‘}}Ô¨7åªñšá˜2 föÜpãÜË=®œÚI?-n`%‰-¥Š@†D!noDäkM~0Ì—ù½‡­…_…—ŽQ¸†¸±G›ï˜Ê4¤ÎìºiÎÌ©ÕÕÕ[®<>Åô'Ekv{M'Ùedˆi÷4 øÑ‰`£‘¨mZ²€™·„�øRçÞÙØ¸/j&ÈÚQ˜·WðGçR(rMoIžµÉØûÉ}>qRзWBM±ï؈õueVrÔ?þrïǵfI³ù¥b80À˜Zô+kP™ÄÕ›¦5f<”?ôÞݪ�hP¿újQÜ⩹Ùý[}ŽWÛÆÞ�t:ƒE˜"¶ r…x8’ž‡Ü�u¡Ö$¶ãCz³ àB&ÌÞâhÕ`cæçfŸØÂÓ{Ýl°¾òQÃC3RðÌzäÛæìÊî¡ÝÁ5]$‚ygœƒãÑQ½”ؘœãÕ¥¥^î1õ{«?‰aT:/i#{íä?²¿£dJ6Ïp*W¬wIUfKìR€�¾r)� 6äîUÊÁ–,³Tö·2è�b7eQFìAˆAor ­©éwâôŽ£Ñu\Êp5GØ–|õÃ'Ô¿}Ûeø×w_§õÛ°�ÙJ?‚_�HˆYÐøµ{õ÷^ZZ‚oÞ�Æq#Xiæþá@ÛDÃPÓµ¨‚hŽÎ¾ü#jdp|­‰ZýÞÊ»–¿;ÚX(Š'_ö"¹Æl½ßy’‹\¡ß”�Qi.Ôf`·ªÔ±ÂhªaßB†Y�õ)#7› @¼ê§7º�½Yص³ÉJé®.³–ƒþ0À¹f"(SvU1•úHÕĉnäù�X XE›üëA2ƬŸ^¾¼Øßês¼Ú699 "ïCåiéQu©¨Ül ðÜF¢$W€èi¯�•ùžÞ¬”¡›Ëú!Þ:Ÿ2¢TVzZ× »pfaáe£VRoK&gµÔ¬ib2ŒR¡îÞß§4#£÷Qa‰�²=3±MC4ïI"Õ¿|z;›Bx…¼«'«©R·L§� …"�¦V~†*DszûÁM×lnçßmï¸X*Òj3vm½m2ƒ/þÌIúDù0ÏG'Ûü‰÷žUj=F¿9dÄÖls”åßÅúÛPEÆ„®«§8KÝ!/ß�ž|˜­ðªC¢åm!€c}²y7ë’œ¨«‹ ‚Ær8¶nK§Nq»¼@ Y"Äa®žé¬ùÛÐ9Üß¿m¦*È%Áv÷Q¢  ”.ó*E]Z^ûàVŸãÕ¶±w "žØ² ´ša„Œ2àÐDV|Ìõá×sRm¦® _Zl– ÇVd!yç¶©Ç_IíþZ3}ŽJ½ÐY½L^׳|Ráñµ’‰C•Îy$“eV´X¨Ñ�àÖù …IHP'ûȯ®õî~¹ÇÕMù—Ã0ò #öDô‘Ý=ˆUáõÑg¸ `×ÍÎö�°Gºê#Ï ¿TžÇÞf½T�¨Uëd‘co8| ØþÄö]{ClÏþ’ʇzhDsq!ÀÊÊüÓû/‚܈ª>¾y_œQD¿óàŽ¨ �S£= �ÎÎúyßõÀÛ·åZZFLT^Ó& ´ÅfLuÕ_¯Š1VçûK—Z,"'M`³óp#¿|áì /÷~\‹ÖÛXy(Ð}<´ýÐ8)à|/a¥¼žœ1ÏÈYõùÊ-äp’saúŽµÚ€”{ÚõÆT¤xá…Ǿ�îìâÅK«3B³^×Ó’RXð'�·9 ”G^ÕĶ7Mê…ã}³}è§18}Ý·ÈsêtûŽ={‚ú€Døödf[`èÆM¤ø¶© ä�,�‘iyP-—\¨¾¾ÚÄF¬`T�ç@#‹Èñƒw™Å J"¿wG— E>«)/IÐh‰sç¾ÜxG*øGaÜ,Ó+á¡£~ýŽU”šâ�{òȵcú1¾s_Ÿ#ãŠË݈”TxöÄ‹ïØ}ðÆ#ÐÌ QÔþHsrÚ¼·2‚xi¨{öm [¬Ë¢Õ|‰]劂HòçNNªXØ_Ñ):ªlЃ�Ê¿y%„u­tö“sÓ�qfÐÊäA¥‚c=2%n#‘V­¨(}Àf�YN|¸•û,ÎÀ±%6¦·ÁÊòòXk¤�­)än·{#ŠÀ‰—ú?ï�Î=‚Ƽزõþ*Bóÿ°¼Xåõ,¿pÇ Sa 4 �_ØhQ7UÿÃÎ]»~`kvv)nH]gË ÛihÝŽ˜l`^5À©…Û ‘$|â¹ Ú —‡¡v.x¯eº—Î'•^oW#gd©«æV'‚¹( «’Ûnÿ•åB#þI ‚õÕ&|hoáD¿¤*j>ý}Ið«‡Ö1—ƒÀˆRRÉOýÌÏ}4*Šâ�æì|ÁŠ}¡*ŠïÚ6‚ÑJä®4Zô›‡ðÖ‰"±`x|¹ SIŽv»3?¨Ò�•/Ž•¶ÿ05J/È~H4Ù2¥",¥¤@ ”lƒG?DTä2«\1ò['3ÈUy»ØmºL»¶Þû�D›×²�µÙØØ¸!Š\‡ 0ðÖ…¡°`ùà{¥±Úƒ ãÊ eyBJŸßê€TœDÁØS˜\i­V &'š_aèúG<•d|¬ÓRÕaŽ&åwêè›PÀ÷/ôË=Ñïsº¢ŠF;È uÏ:Ž…]{Þ"1h±Æ¹š™ˆawP@3´l ˆÀZg¿Õ‘⯛†8’Š�ù™N`tÜI¹3ôµo¥#1f9áÍ“©í•˜�B±¢æÌl0Êò}õ®u¡>vÃ:€¦q3GP5·aóüQ‘ÿÔí«œg¡ÐÞÃf R­§¾óì=©„'&§7yéBÞ7—‚T–«Bq Ø b†gÖCpMcw¬ÄPP|{œ´ý�­®.«v«yH;|Ó×C!€^ì…à´lÝz@Go½2Ú€r*Î!Í ¡Vg–°ÿ–[_¶V̵dcí@.®l<¨‘uU¤ U×î˜Lõ×&{¯¦¢+>¥Šž|e@¨ªRþ„pòè Ý­;³­±~¿‹§� ªŠ±¡çÖBB uñ?Fe39ô=�rI‚;Û)z�i­Û-¡5·K�¸éG#Ž2j¶•×f†L ø™C�)�ò<ˆè)Ùq3Q;`øçËåÃO¥ZËBÝ4Ë i ›U ÜýO—±F!ìhç5Ö£½¨¤B BðŸÆ Ãa¿xÛeȆ=NMg¤¶ÔŒ#EÆûçFJf…äw£¤Õæ Œ>°ëð­;Ó,5­o›ÅôG!j�DÅ¥€ž—Ì¢˜=E$ƒ˜,àÄj…rï`^Õ_[Ùp8vÒÌßÏJg9¿cáaå)ÅLVI�‚G/7ʤ¬†È³/"‡ä³_#ãl(!ËBÞ–Z°sß8gtcí@ÖÖ»;táÊÕåÑL–GÂU�ýº`®†Àé!œ Í1T)êꈚjÅcOõp¥éÈTÊGä¨'m¨«˜2Epz#†p}Ùl$¬Ø×ùmT�­G|p6å0`¬"`ÆÖô´è¥üîïw åÃ8,øÁ Ñ¾w…YÀo\…<3u%í›Ð5’+'‚¶¸ð�Õˆ£¨3ë¨`0 S¬pT]è5Òm}œ ×`š03!AçÉÏ›x³]`Û¼e[V¡4,Ž Yüš³¯/?q4p÷Ljê\6KA(²xòÃIÍ€”à_†\:'¸w&eªƒàjÜÉŽ®Qƒ˜ôlÓ±4ÖØ/"Ôäf$î¬.ÃääÄÃWký¼Q-ív�EnHÞ,Á@ œYNJGÂud€Š‹ÙÅJ·s( ¡ì3a7½~÷¦qÎèÆÖ�Üyç]-ˆ²þ!j‡ÊÔ•±VjA×'«…È .ŽBðu.û£l8„ÉVëØ–žàí,^§ëêÓÏu„˜éÙ bŒ¹Š)0ÃÛf†ØÏT<ðyž‰=‡_l&‘,®÷Þ�!@uÑ5[l…)ÀÜY†šÈÂÖ*¨ÆRê”#ª¼1Q0Ê(å -‰5ì¬âé3gÆžÂä¥liñâSƒ�%F!,°Ç<0üõÅ[†ZU@9BB—™¾unÃnƒªî³’¬uw8pà{>pGŽÜô`ÔžÃòwÝ�Fù™÷N¥œ�Œf¹Ù�±Bù´¨Ü#‚Pñ#KM�Ãn,!.¤zc¢‚¿ÚŒFÓ}€š�$s#�U)bMp !Dàá(†"5Ø>ßFc¬]º)GÃÆÁÝ;›rZ�Õ�×¼Xº4'¢&ùÒ d’à'®B‘iå�GÕÙS.[ bÅŸ‚8VfÆŠ¨EÆ7îÞõ¹×t±\#6·0÷%3×¶cw ŸØˆ‘2M®²�=䪞1ÎRQ9»±Pñî}ÇV}lȉS§v‡� ËjC€mÍxD6Ùàªteÿàê  PÁå� u=i„bЃ™Ù™±’z)k4ppÛÌd!õüƒ‹Î±‚/ž˜Á ’¬ÜxB‰vb,¶Œt �A�3*¨ùEq“r Ÿý^ŸúÂÅ3 ÒS“Cˆ ̤˜öHoˆd¿ï£zö úEÑDN'×b(¤ë!£¢¼×T%, <8ÇzŠ™·3Ìu+¢ÚQª Ö[ˆ·‡™nÈֈѣ�ÍïéŸQísÁpÓä�F½Ð^:ôC®V»ËŸ{6ð‘# rûñ5Z}í´¬‚�;y)üüÙ&„Fц˜LV’÷;|öÌ©ë‚ôJ;}ìÅe™¥\²i³ÉnÄl”âšç×f¡Ý²@xûtæHzÁý–"\[YyçV�ÛÕ¶±t 0 š"i¸A3Ý #bˆUÞlX¯ÖWKÂQ¬–‹ Y�ÕÚÒFƒL-”óÑ’$¹îz ¥…açÏ�þò¨»�L:áÐW™�üщ6ˆØSàf$“ãƒ0å%f• øèÁ ,üœ…Þ&)iMÂŽ]ûÞõ½>_ÉÛâ8©Ð×eFÈo›ïƒTHF¯ªêf¡+-ÙL¢|ØC¡àÛÝØ‹–èJ|y©©ˆÀ놰ý[¿ÔÉA¡iúïoç< NÌÎ÷=l³š}ï;´Xm;•pÚ¬Ýà>ûžEùõ‘©´|.¯+{H€CE¸2AÌ nžÊ«q�š€‰Ÿ”‡Š7eT ,ff�+&(Ø·o÷'¯âÒyÃÚÚÒ‡Dž—ÒÎqD‚a=³ ¾Úý©¡ïØ!±T�pÃT¹¡ªñuP cØX]=°ÕçxµllÈî={“0JœPŽ�%˜�¥Ž<}˜ÆìÁ—î”b’±[�&ž°üIŠq‘¦rqñâõ1Aø6;3ý{ù [æöÒ%l…B ‚•AˆŽ6íµå Dmˆ¦uäü ·]£'í "0H8}~ñc­Vë¥ËX�öý²È43»‹Ãû1Ü>›B@µ´ÒÅêvã4ãzF/äS§§ ²Cb®$‚N,5H„ª‹²ÙAU×;�Ç  ¹š@­ _@¬ Èn�Ê*>-æŠÜL©ûì¨ÞkÑh, ø±ù.Öú7&‹B‹ÔNMÂÝsí¨ #—Åž×£dt0£íÿøRC5^°×œž h%tî�=ºþ/•kÆšÍäY3¶Yø�„“£Pkµ”i5×îƒ#CF‚ÒgpA09=RE�¾ÿ�:�xû�ƒ�­>¿«ecé@ž}öY(¤Ú&‰®þB Æ»»!AJöJè®ãJ®ê™ZÖFIWÐÑÓ*•ë¦Ùëýë„êáJët:0?7·ÒŽè$aÇ0Ì5l6rxl5iëê:jg®�¸ˆ[¿îžm)’¼×þE¤âÖäÔôôwÁyï¼ûî¹TÑ  2£`»ÕÆ’`o#ß0ð¥'‡š©•¾ü 7’ÂBùÍmŒ€c—šº—Sáñ\PQ‰K¹Ì¤Ìiß¿3]÷ ªkï¶æB ø¥›º�ŽD­ûµü®Ï»ÿ—ß·Þ¸ YîȪ,FËzD­ˆ{§ ÈûBé0¹®Â\Ÿ�´âhÀ'º M$¶Eņà=#™uÖ½ZkæZ°$Ä%M¹íç:‚HÂÒ(¨ª’X�iÈ”5 O¾þ�í“¡"ò¼| ‚€šÍÖ¸ByÇÒ�”6ŽŠ0ªò~=4�0H£EjÉ­*Œ¿ÃÿØJ€z¦1+[u6ó×2‡íÓÓ×e­ØY¿ßWýÕÅo`à ~ú7“žZmy´’}ÁtZ}�Fƒ�ï]‚Ð�\&}eæ‡ó»(ø®�Â¥Õõ_ Ä« /~å¦5P™$�}# BÍøçž�H©Ï_j¢Ù_�—sYÈ×Ö@¡.ÊïõP›âf·�¤öM !/@•‡›ziÒA¿vû ËŒÀ1Ý×EËÌ®DõV•Zú‚à­C–¹æxõZ LgƒBÀ}3)H¶êº ëïâOÜÉ÷†àë¡¿2öÛ( *ë?=®›Üc'ž}&E"žs! d²ôˆCX aTuÍ;›ÝÝ15*ÿv}ËD`¯Ó»s\¡¼cë@úi1‹›ôä@å…€„$ˆzY¸Ò]­Eʤ%-ŸÝH…†Û…¢d�ss“ßÚÂSÛr»pá¶bñ·£Á€ÝŒ…C½¸ž@<‘ûÞ�™¼s‘±/è¶pÞ'¸caäzä’ÿ¨Ù¤N¯ÿÞúg–›#ýJÜj{¿T&„é(„_{ëe(2a†å°6ê�W¶ ]>Ð�/'D ¥X9Z Û¥‚À§P+EÀ}SF.$Á‘�](rAU…¾w¢WÒÁVÛËHßUK]ktaºR¡z\¥Ïd æC€H ¨dšªÏ2"�z!¼ev�c–öjXf Wýt³+†Í Ž­7Uá¡êæ5Ý•%\_Yzz\7¹Æ’8Z±Biž¥$™]JÀì]K­\YÏ®oh)%%’ªðÚ(HðF§sÛŸÞU³±t åf³ÿàÁ¸¢gPȤr£ƒ`¹Kj£@XÉ>ئké@–×Cݶ¨®Dy§Nœ¼.!¼ÎÖ××¹™$ÏŒV�2‘ªã[�•›Hg7ÐæEç}(‡&óC¾w¶ƒT SîF&�yOl¢€‚ðP§oDU˜v¶EgïN ž ÙRײýAG½íÐuÀ %Á3-˜lääèn·PeÄN,ç-¸Ö�ßìIXsp½ËÒ‡(t’#f‹/³{¶ a´xœ8×ú$\Cw)Àš³?ŸTz–|$ø-³)h()W( 2'.Sޤ@Ø3Qx¬Šä¯Ös²]æHD LJD]ŠP–/r’$�¼.‹ç j�$>¥Š¼æ�Íú97 Ì(²Ó‰ò dU¹ìUn’ ۀɲÍ�d‘Jˆvþ`Ž·kÑÆÒ�´Z-ã «ç¶®râÙ¤PD¬¸./VKúÙb°@0w»�†ˆúß@-ð#{½^o+Îë�d.^ø›tå"# ‹P13ž\k˜ë¨jµ£z Ù‚�1>8?„N?V¦ÒŒŠQ³TGÞú£78p@åßQ£ñÞ齇XIfPZŠB¡øŽÙ!¤�H�z¬í¡ôf_ÖÕǰ!á‘åP j ’£û0�ó0”ðL?2³CÚ-Öˆ«°Vʲþr¾Y€’ õȱÛÜ‘aXxûܰÜDtACAÕ–Ù¼ñX�ÖJMžä0ÀCÓºQo$ƒ]¥Õ–^nŸenùêqsï=ý¸�'”÷æOÎM0 Wè²·6)¿´¾ºzéj¯™7²5É…¢ÈÌŒ§% -íb¦'÷UE¤èäçÌÚp0uSW* Mô VPÙpî÷ï?ÐÜÒ¼J6–dÇŽX(1¯˜øÒÔHðîØB°Ø3£@ÅVj+ZH‚ùD'dAdc¤ãdžÑÂÂÜ3Û·oßÒsÜj[_]-öïÚñ§Š+Ï@eŸdôÙçf€¥–C‚:$h“\:¸†7ª, ÀŒk±#´£Ë—ïÄ=KKK0�0lM¾?ˆÇ•¢KF¹D|çö*'(¨¼/³Ÿ¨ñNߣfß^N¸ü}ÍKâ:và®Ð„ZBß¼ƒÕt‡*ðÚ†˜+à�‰ò=r´:ë½Nl”Ù«>N–¯Jq«(¶¦Ûn9È]¨òÚÞ==„n�Ñ—g[tB¦1üøÍK(õ!¢ƒgA­æK) Ÿ˜Á(’µœ ! ¸ÝjüÁë°dÞÐvúäñQÚï±ë‹¹Î^š,M¯µ×ªÒD ºb˜ƒ ù@¢�;CCƒ¡”cXY]�Ûês¼6–ä‘Ga)y�¥t0,@P0â\ ”«Dx:"ÏóO&¶+SÁtb˜�Ä—bP)–05=õÍÅÅÅ­>Í-µC‡ÁòÒåÏŽz몞ÁEÈüïON{b¿Š9ja·y�òÚæCA¿~×%Œâò”AqØœøU°Tò}Ž>ÈyFU Î°ÑkÁ{¶÷™H:TlÕO`¨+ÇéÎùTÀå<ÀªaP+ûhúŠó}BëÌù¡'1ó@háÉE*ðP+«4o-šìíÓ9LGÒTÍÝø oí¡çê³ó¢^¨¾q(¸}f£~ EÍ#æÂ{vöAy”[UVWLcÛ ÑŽ Ÿ<ÝAöa°«:ënpž¾~äÈ‘«±L®kµ&zù OèºRÌP(ä,' PQmey曑(+v¬gAv6TUÙ “úa+Ëã9 2–drrzi6 Jý@ëº9c€ß„T]G�_1àÌ|. ¡ªxêñ7’Yk««×_ЕvüøqÈÝ?†t´¡›ºfˆŽ X9‰G@92tÀª×!²Ê»óö¹ìRÙØYCšS³8*įîß`òÎ{îý†�6 KËhËwµú0̵æB}\£VÎVQ¢à_=9¯’$ƒÍɦÝämDy¼aB�xqSŽÐóª1í8Ç�ªç¨?࿺ëƒÀЄT�ìMׯÀ"¸éeG faÐ `_Sòá©‘®žzeM*à¾ÙŒ†! ¬®eu¬à� ÚJí‰N"*C®ÈÝ‘dº±±±ñù£G¯;u‚M65»óùtÐww×Ô±°“ áØÈtâˆ\hD:ódPR’š räbÀJQ�´8 ¦·ö ¯Ž�¥ét: ª& Mv¸�ìH¡ †IC)-›�Ó‚ ±ŽTP†œ\‹äXËã/¼�oÝÙ½ql}mm$û+_¦0V^+æ'3^ û�1T•ûº1‘|y;†ë±úèÁuB·” µ-+Ì&v(€[ï þIsvβ㚇w0ü¾ï£e7\ cîN'ÄŸ>Ð1x$Ãs¥ªÚ¯uýŸô.5sø›Ë ”5Ú*ÍfÕ�¡, ðÉ•˜+¼TÕ�wóCæsìž(XæA™kQ¯—à=ó]-Fä·t—¹´DÕz)ìuÉ­ã1…aÐ%†÷íì‰"zR_ Â;ZÄ�ÅZá&þDÇ:l�¤©¾¶Ú™:Ó×�|´¤Â I¨ÞÊ%œh6¾úú­–7®í:p4âàœ²ð~wQôG„K7× #˜­wň³�ô,úŒ¦îMB@–e»¶òü®–�¥É 5M÷b¦Ê-h!RµB´e[e¦ô�Íòù]Í`¨ª2†�'ˆ�ÎuË£ø]vñÂ…âæöý»l8ä¦ .ä!œèDž<k‘<ÕÑRhg%Àß¹¡ÍB‘Íke#ÿ1ñÁ@ÂቌQ#*ŸƒM:è`î¥HZ…žÿP^À Ýa–diòÊð¾ÜÔ)Tðåó-M̈¾› ÖéޤñƉœ Iº·¶/P¼£eµQÍĸeèeå{ÄI]U~ú¡G¨¨_˜°B˜—Kòö]] i¼ï üsG–Yר"ë�Ï€ìÊrÉDFQ²‰ RŸÝ¶vðÄòòòêÕZ×’mŸib£™<¯×ÑæX ËŽ�Ì¡ö,Ïc�,DzO ³œüM-×¾4JÓ±¬{�¥é†2�‚oIDAT{1ŒmÛ›I?gJáD(m•™lRcô2bh`@£œÌz ¡S.ŠA·7ÜâÓ{CÙÎ�;±»¾ü»ÃµËÞC˜™Š þÅãs*h(¯1^¥ü¾Ä®ÕM„0X‰ÕÏè±ʧ l ý–9»£ëíT1¼;0z±UóÙ~Û¾/ß M>hÛ<ðÈþùÿy8è1!ÛHž ‰^¸0"‘5}|ó–ä£{åKYa™ d@YNüK·,a1 ì²³§P+£:iÔ0‘üñS„„ ëŽÒ3Û×|§*3â»çš:…ꣷô8+ìÅó%9ÞÔø×Ç‹†uá«Ü‹B‹U>ìs6üÉÕ[×–=õÔS'ÉEÖm�ŠŸ Ü Wò@_hª3jjÌ–‰@)n¬eƒëÔåö#¢­>Ç«acé@FY¾i3rR1BÔPfTÚ”ŠkÐGÞ `% Q‘åÍ[Ââ çN¯ÿ½qMÁý^÷Ù‰N! ÷ÀèòÎ'ÏN É5¥jÆmÐÆª·Ü,vW7"¢El™×0<¸}ˆU=}‰ß k%�°YÀÇ¿½I,½FIùìçéPýúCÿ›, ƒ¹¬ sáRSgBªÖßv2›„”ß¼¥�+•øË·­hDpìÍ\a8êâV�¬&xq¨ÕÉ�d xæhŸSXÉšáz„;g È8J1�Rëš×ûíˆQÆu§U:+Ÿfv&ßIYffþðª/�kÈÚ;J)?õSnˆ ÎB@2|þ^Ù�V�BÕTê\ÀT¦›níÙ]K’¥Y½P¢ØJÞ›Ôßàs±ÆL •x~΄zŒÐEv5 (Ûêó{#Ù¹sç ÝnCÖ]ýê]Íìä’&Û)<|©…ªæ ™ÙëÜúÉ^«“Á馩îJR+ßÁ~ØÎ { ~þ†u5\ Ë»G›«Ð›1þ樓à©Nâû  ³Ó }õìÙ³2$>…H»ƒëä¢G�^�3¾Ôé°` ~df„D•¸{í@ë#Ìv/"¥þô¹9µšF bƒžÕ~CUÞ•ÁO0êçÁýS}}‘î]HU–êˆX£º¸6BÖQ» O—YÃ'ÏLÔ&f€7^|êÉ'®KQ´ïe�<ú˜”yîçpÚêòˆª<ØuÖ½ÀdÅß[®×2ÿ”5¨¶�!pûŽÝoÂx¯¦Ã@á09:UL%€F÷r�÷Úáïë�0³PúƧIWI†a0غ3{ãY§Ó�§Ÿ~ZÉ|ðW½Õ©ÕŒN!7›ü§Kmˆ™t㶪Ñ;ðt­]¡k‡i_À¿ùÀ%èuBünÊ@¬xu9†¿xƒ��@•0?0U ÒE……"xèü$ÌÌŒÀý0”7P.ž�dª@¥·¶ÇT/KA­‹ÌF6xFIú¥ÃJ‘ÉË�q” þço¿”V�KÏÕȵ4ÓP�è·m´ øëÅD÷°&¢>謳€â/Ê/N}n�„póvú¢$§ó½È&;Ÿ/`ÔÊL†¾"j¼µ=2ˆ³ÒçpÜ­�wt„z±OÔK¹P—û‚W¨@¿)9³ö„†àë­3Cµ±ÜR‡gRCcÑ•c°õ@dU–’à›ëMhµ ¬Íœ@Ö@1èüÍë¶8®‹â2õ•XÏzËÿÒL(›L®ÌUQæëû ˆÊ}ÂOtú–+hqõñ³±t J™ 5µ«-=3ÂT¬ õ¸.gVMMG©ácMDH»áy/ƒ ¤Ä0 ßÌ@^–——7ŽìÝöoò4µ€"ƒ¼ZU¨¾p.±Õ™×:;�)Wð[KXdîž‘ïKñ¾PáÇn^×”}åži Cܤô§"Å_lƒ�¢A!B¬^ÄɉÖÞýîwãÔäÄ7‹Q¦\¶PîÁ�@ÂÉ~X¦­Ê±*Ä�+ÄyÛßR°Ð,L†Â^½Ž÷´à „o,MÀÄdKúÔñÔð][×ͯM³•_L@X½efh˜$}0\/ßY€²½q;‡§.'`Ymï¡{ñ4’׵ᕶ°°q÷53WÎV@^èœwS&ÉP㳨¾&2%DÏOm+@í-=Á«dcé@X*§LIÝÈ-BW['¨ÇdþßèQ6}e¨6 '�yÅ áuOåþ½ìÔ‰cÿ÷pcY�猦ß~d-Y“vutW<€V»¼|?¸­§n.�I!ÔF7äü–%.Gˆ\ˆ¯@İk¶˜ýý“ß™Aޤ™³ýÅ,§czbme¹ûÐCqwcíܨ³†þþÛÞËs!‰®wôU$ƒ:R¸|ÝûÛC¾o:3´zªjJXOþ(HÁ#k1´‚ZqÿéØ,„±4­Ó!¯�Šˆ^3ÍNv`j`´½¸ìÑ>î¼^—>ŽpBÁ#ˉeޱ‡PÃõÅ¥K¯kUÍ+­×ëAFËÒ*= Ï5TŽ›˜ü%÷éŽ"F›�Z”²ÇzJÅoN¢_+¦´ú Z˜6ú†ä„PºUõ;êdè9šÊö}…Ý´QôzQu·ðÔÞж|éâ7§#z0°O–¹öÇ9ÂïlDàxèüfo¿r€¹J¾AäDŸûðIš øõ|p!UàÕg«L‘kÍJ§¥Qº ò“TI,}Pþ|ØY§ÑÆòÇã8Ö߈£øhÑ[ur~æ—ñån Ñ„´íãì®,`YuFtHÕ‚6gækãŒÎü�åäÊø‰'» d–PIÇ;T÷5¹3FP£þòC'¡HCv "o/W}�Ð}þä$(QiÐ#� »j"iþîUY×° ‡C­jYͪë¥/\ž¬e¸Ð— «¹&t×f’ž_O,’ ›°þ�Šk¥:&ø`P¿r %4 øûG§8J”™ÄGPíF¦ž\n�R®zç¹Áª«âz/5¸¹›h‡§–ClÙ+«–ûDOvC¯"\“"5Ïœ;sæØU^ ל-,,€ˆÂ¾©Yzrd}‰Lä—ÏÿêÂ& Pž�µ–º–ŸR�­<¿«ecé@vï>@FÂÇ——õíl�YÎé¸ Êãxw äµ¹‡ QÌ8ìϪ-:­kÂ.�;}¦ÉOëÓî©z<)à¿yx'D±òBVhºŠÛ7Euèkɸ‰/Šý†êµí¹‹)oqÈü™SS0=9Rèßa¸¾LýÕËŸ½ò˜Ã�þ£’² &!Œ•ú³cÓì#M`OŠæøãÝæQŸ@q€-¨%Hú˜bÅŸ{n“¨PÕQfTôÔJCÅÒ¾Û°‡«F<^/¶’Ù];#ƒuA)ƒÌ*ß÷//·Õd;só f* I­ž?ñWã¾_ë–¦)B¬k¸�¥bvÎwXvýŒ iàåKÝ?]hƒnœÇù]Ý|…u­X’*ws˯CôƒXŸms7½^R6ôͶ¬áÑBŠÏžÿú›äûØââ"L6âÿ.¥žvL_û€áÏ—ê›ë‘™³±¥}‡Àr>ÝÝ sù=XÒòøz²wï5Üæézçš’�‘>{~ )ÈÌû!`2o,Ÿ%æÏ]yÌr4üRÚïTU´2Ø$ýÙñi«Å^ÏDù¸“|FPIZb•�¸ÄrA®ä„ß„ÌJ·KÀpo¡úÆJ *”8XãAtP\‡È…:Qå&óˆë~™8iê�Njý-ÇÊ«5šWϼHJÿájÜ÷kÝâ86�½Ÿ4k¯Œp”r·ßö>Èm V°Ë·Õ-»™¹¿µÈBÕºuãecé@”D[›¶´Þ¶)l´æ÷“Ÿèv»«o:†Ö ª*¤.]�^â)~Óêö³Ï^àÎâóB‹IÛÍ—&›ý÷�ï„Æt¦5Ü TÚ·>À\|»�ÚámôX‡Ôª¦½«-Ô6o(þ/ÿü L42§;©s”ÁÚ’œŸlü£õµÕïëu7þR¦ÃÌ—ƒ�uñLO”“E˺8ƒýðBm¤ �7tyØZ9�æFR_jÀädªƒ[egaÊç/jHúßßNaleÓëMqðgË�qÅT×@g7;­ð¹ „çzõq"9EÙãkK‹o꼄•Hй&…\zü\ÃqÌð¦bÞ\^t«Ã ·�<©Æ0h)Ï}c,C!]ê�µRBjFu�æW5zŸzøÁ6_šõ�u$¢±Œ$^k“£õ›åúÑ3õF� aþÛ‡÷@˜^ê–+i¨lÞæu„ºëœW–j†ïØ4ö›…æ>³5h$A*VÃO?ùØ'_êX×––Ôd>‡ÂHÍ'„¤™�ˆ•†P°%bôàš©þ]ª�¾jÉ™�&Œ úúJ "’¶^n¿Ë‹#ˆáÿxnÂ(Wlq:ˆªž3°€9±ÿD㫜Z!DM ÿìÑ}˜4 £>h�u¸¶$†½µq5îõ8X§ÓÍBž‚ŒÝ?”™-4Z+iႈ*~@ÍÑê²n{ûŒ¬ C½T;N6–ä‰'ŸRÎÍ~ Œ2ì›G�º²Cm°Ð|!6Cõ ýr'BÅêÍ ÖcƒÕåÿwtîèyQÅÌŽð—ð…Ë VJ)W"¬#Z,ËTÅ1TClT¥*W>Ò=h ПTø‰£3 Àj݇æyŽ«çŽüûïÚ¥³Ïˆ ò*·åû¶›9,õ…á+o}]e꥾Y D킟YJ ÷HN›\=HÚüíR‹Œ„¿K&*¢–M îÅÎ=ºÂ‰ýì3#‚‡7BDEìÁ†¥,Ò “Íæ½‚[yݘB)·Ä�>ãÅ:A'"à±AX+ÖÕœ&L7%üÙ¹¶žJ¯¸·ø 4ïæ²–»(Ö›°m�ð§OÏÂütêǤË+EB¨îÅŸß¹sç›k÷û‚ðÓ>®e^^ß8pú–5 5¸ëïjX滹–qv̘–I �ë®1–ɀ؇~ŽnHÜTD·V=3ô8ͱæ)ª`’¥c"œl·Ç2}­mii FÝ�/«õ‹€HªŠ¢� —Í>ô¹a-Ô³ÛU¿ªßóêÃßõîæ÷Héè�E$áyb.Å©SE ͬ‹½•EØ53ñ±‹ÎßÐQ¿²·¶dŸó¼’à;k‘ï¯@ ±‡Ž; í�E}cçjQ…¡‚O±*@Ä® RÄ:ÎØ¾sçXòú_ +�Èι™²tü; Â@a ÚRþ«×Ìùï}q4fú¬T5¤‡®!Rõ1L˜]á%ÍÃI~u1–ÿ׉i�NFÐ1§Qƒ¹»üÐóßùÖg~б¶&&¾Ö½|‘BvRsåû<½Úà°ia¶n}X…ES†ªG¤TËR fK4$|ü¹ynÊs½×_cËìØÉÿîÅ™ò|`SͰr^TñcÕ'ð Â@©õô,,¹Ý�É^*«g�c>üíkvcÇÐA)N,Èße º[SÍsƒ÷çWt½liµ\å´˜sÍ×H5–Ècé@Â(ìi•{äZòϰ<–ÇÁÈeÛ[í„¡+L Iõ(ÑÔ’%HÏkvµì[O=yÃÍùœIW éþ»’Žú•½as|ô »èw^œ‚‰Vnæ}ìjF*�°qù·äÇ~˜cLÓæ&“ßÏúk®ƒ¢7z*øäñ)­è–ZÎêNÅ|ªù2焾µs£�)×d…š¤„ÿ}Òü[Cõ©³Óúìõ¬�•ªOÃ{êFgXÛÇ�ÎÂôl6wjzK) õ_¿·qü�e¢éw¹Î@€@o³_ f7F¬° T%ÆJJ¡Gl»ÏgÕaŒ¥ ÄX:�f«yJIåZåzCTnYJ¹Í�m„ñ]UvÒMUÎÙQh˜¥D1y>µUçu­Úúúºâ<ý-ùÿ³÷&@–egyàÿŸs—·çžY{euUw£îÖæVK-c„Äj<801vŒ aÛ„Œ=žð`DÀŒ†160ÈÆà „ l4 ‘„’º[RK­®ª®5+«²*÷Ì·¿»œó;îÙîM ­Õ�¯_åý:º*ëåËåžwßùÏÿÿßÿ}k/ý¦ˆcY4NRþØhÆð¯¯Ná7¼o~áÖ »–øõxU ¯ ð+±rð{1bð³×§áëß{® ¸/²Ï Àü ÈÝÛ·Ú›k¿òUœ¼qq~éÿJ]Jq5Há=/,€WÍÇG° _ü3oãè§)B¥‘Rè –“@óšãQȵþ®Ifgì×.NÎE®cOLoeº›�1æü,„»!ÁoÞlA"½ÂoÀ‚¼¤ýg½Ýí?¿¿WðÁÇòò2¤B4ÿÛäÈÌX*¾¡U3kæOÆ'&/y6!¦F6‡ò[Bñ²9Âpœ×÷Já�¯¯V+wvGò@TàH”ŽT„9>(C7À&!—çÙó}BÕÞÕEu}³0„­ñ\ÕäÂlâñôôôßîn\kxg^÷×�R£)…NÆÝóSÚÂ߸Sƒ_¼<iálUÂRÁ�‘wâÂA«:á§Em_ ™mš¬_ú4.Íøß;è÷V¿Šß‘®]½¼Óœ;ýqéß c'Ÿìù°=äдó*Ùf.h;ª€üÀ‘}¥ç<·_ÃTZ�Eõ7îÁ©ÿ'·†ø³•jUãEúÛå ð}±÷~× Å®fÈ]¶PS’Ò’§RøÞÿçÐt¤Ë.ÝÎÖ]Á†�Ÿ/ËW_ý~üÆtö¿ìüW*„aj¨Ò 3`VœF–@Äõ=,㔪º?T0ÉËBÒi…ý@f œù1jB…:Ãé)t@x>RP¨Î4¦u‚��Ð lsv9ƒ8�€UAB¥‹7žï1!!óåM$²MìêÕ«”vw$½wé×Ũ›r†ÂH’Ëìo¦mÀÔoºžÀÂR£)·ªÒ€¥ÅZÕ#ö"üôóK¶Rõ½=®ê³µa†¶ì!@eFÀ�}ä8Ä­ÄPA#0³®ݽúÇë·oYA Űßçà+KAÊÅVÁôl 'Øf ¶=;È` „u Õ–„ÊtA]ÈJ• “s×™“²?è~�ƒ€2ÙÙ¸³#ëǦØü§]ž¿Þ‚?Y«ÁÊ€yL "J«ñ§N$Ô©­�$S„öüR×ÿ°€;­Ù泤}k}{øÏZ‹'„H4rî@£‡¥z-\ Ù•¾è ÛW)dí]~feüŽHˆË_ëï úÜÙXûñæüSãˆ(Ÿé?ߨâ�>´Ã>wÂy*+•h¬eÑ 1f›O¶‡ÿÞÍ´ !ÔU‘lógñh�6}úÕ饓2¨5þæ�µÕÏÖçÏ’DÛóð¹ ÿûN/®ŸÂÿ]·IvLS» *U ›¡€|ïC´ T5zÝaFÉWwׇµ€ÿYx™±¼¼ "ŒGq+Íœ±M¾ðS]‚ŸDO) HÔÓè"û?á ¤„aLo[ŠØkN÷áMÒ“:SÍ?¤{kwÈòÀÎ4,>öÖQ}a)Ì)wqŠŠœåÒœ˜™jÖ¬,0üys%ëþ¦ë#¢^7™Ü«ˆÁö7Dòþýí­ýq_ï$âÄ™³ß±úÏWN¾6¨·$©N2åÂ�föð`ÚªMéò¢L% w7RÙ^û7q¿ûÏ:û{÷eøUk4XcþÔíÆC¯=i¤ù=†ÞóMwàu\·Ú¿è­ƒîsA5†¯ï#0˜JœT ðlcߖǃôìõ—^¼Ûjµ ­Oý«`áÂ;9cù$¾‚A ªƒo?Ó…7NDz¶#>¾[‡oÔ!¨Ä MÍ]ÿ>Œ#ÅÃ>Ä÷®½ssõÆ/ÜÏZ;sîmI ß5þ±'÷öÚ¥6;çkoeýy"[¦!<13JF"….iF)S¯C5Ì0�!„µ†¸õ‰üúpçîßïÕ¾üxàÈéGA�ß’Tçÿ l´|iÍ‘Â:&X{1¥n‡hèxÌÔãõ÷*Nœ:^>3u{ÎiØë´7÷§k•ŸÞºsíÊZóW�ÖÂ?qòníõ~"\Xn6ææ%$ Ra­ì‘e&.éP†÷ Ñ“�í�´‘ì?çìï_{á³/›Hàìâ±ÿ±rúñ_¬4YÄ”A2H·Bxþo½¢€TÍ3[LAÐâO»üÃçg�qf'–É÷}�{rí™�¼Ã]ÿÔtµ¾tòSµ“�?B2fnà…òn g‰Ô¾*_ËŸh’®­Çkê1e»•Œâ�>þ¡Áþη½\ëñ â±'Ÿú–½~úk²2sº1;ÇT¶+,ÏZäÚh}fôßÒ ìäyd'­ÌZUMßAô’�õO îžù¶Íµÿð@%…L™;vê Œ{?ØýÎÝõµÿ³øó‚°rnñ¡Ç?œ¸p†Ò˜;՗σ$<¨F yÉ ‘)Û4±uã½��;cgç�Ô^º_pÏ?¾¸üðÿ!k³­>‚“LI/lþæ/ÎÙXŸ‡¼xJn4É*$#æÒ'”¿*9—²Fਲ਼›ø2þð`óÝ»k·Yý"€<òäS?´ÛÇ_ž>sžQ’ÉÔŒ’q/¶“_önÐ,=KÆÏ=ÒuA™H8ý¼9ên=grõ­ˆ¾díÍg6¯îöw·HÙ‚W�ãÇq.èÖ­[pfùüÂ0‰þj4Í«Vký©ééO¤£øE£þÊÊÊ+ú»,œ8ûÍGßô.™ ?—�—Ðï†ð›ß|‹þR]`2ô€qµO«{KHÕé!ýÀûÏ‹)GÆÔqTMÆóÀë—? ØÛZè¶÷÷Š?kzzNžyÝfgøÜ̹×2Û "7íž«!P!%6…q™Æ#J7V?ÖÛ[{{gw·¼ÿ¾N<òø»*gžx—L’l ùC`ÌËÝv­Ñ¾nÈ®<¹b«í‡YOûbèG´™Š2º%T ¿¿+ÙÞ�ÿ¸víâß9Ô‹0±ä¡ X§Ëÿ©¿4÷�j³Ç§$É<Ãp5¨‚»y±¥ Ó�{TB*Uð8÷´ï¨©cK™�8³ç&�Æ }HF0�ƒçû¹#å2(Ì!Ù¸ñþÕž-ËŒ……èìØ™‡>K�¼™DjŽ ¨õ€a-Fø†S=xËl5?‘‘`ì#ÛuøÀí`ÕÈÉÛùDÙßß–|ÿöwݽµòþ/ù³�Ÿü;•…3ÿ+Uš§«­)&…äJ–©˜: Îâ8†xwcÈþ{¶nßúçÃ~¯´ªý"hµZ0wê¡ÕßÖ«J4¹}"‰#à<¿âC£Ù? �³ìýîç†ÃH"¤” ÒÒ$V{B4A*U®äžïÊä`K de’òí9§þöùÑþ¿ë¬¯þP§ÓßâÜ&6€œí“?—4Žýc/ ˜Õè—Ö釬R‘¢_QE#¬·š0wâL-,€ïùêEOâ¤(Hèï¤ê:ã æyTB–ÝL{[›pçê5 JÉü•f¨Ý­õtÎ�^ÿÒgž¿8Îõ)qÈ‚ˆd¼U9ûº½°RSuP¢¼¥†j“Ý9±ð%g‚UœIL»Wž{ogóÛǼD_&2€œyäÑ‹'þE­5ÍÑ9±¡ÓÊþ‹F#ð}æ�‡éùyhÎL©a{·�Rš&™Ñ]*˜ÂDÒÈ%0EM^† ‚0„~gn_½Èr}­ "�b±qýÝk×_úÑ1.Q‰— Ë�¿ñ½8{ö{�1� Ør¦ÌϘhôp¬�”´uqàœÉνÕQ0ØzÓÚêÊ¥¯öç_xôëŽíïí?57?WeŒÃÍ•›ñôÔô‹�½öÕÁ £NÖ“zz=LÌŸ8ý·+g_û+^zLJ«—©2ŒÓ_ô|qâv÷V«©´ï²�C�öÚ]ô8˵ñlQÜø÷gy�§håýNúí6´wvT¦’íT ò¨a]î1è'rëúOܹyýŸ�{�¾ZL\Y8q扤6ó™ùÓ“�‚´öÔÕd7AœÄôø“o¥ǡ·ß6/ËÞ̰µ»¯­ô / ;PNÙ¥\´�„]¡ñŽº¤Ùhµèù�~}?È?ïy¢sõÓýíÛ×JÉ“�V«Õ˜YúHíÂG‘r]uÔ½\‚Þœ6´°ºi©"ƒ¸·7[ßrùâ‹÷µU,//Ã^?ùw o|ÛßLGCf¤0è÷àuù¯@<™=„ŒÒ2Àt³©†¿”€9 ììµ! à”ôóCC.rf{(ê�Õz“ }¸ñÂg¥—{8è÷ËYŒW8ç'ñ2V$³ ù$%x^hAsÆ9ʾ®40S—†Dá†ÝD@‘íYÜ,˜£Ö¸�!@ pâÂÃpñ™O€øöÇ3‘ ‚ Ò<}á‘ÖíkW&¦©5qD¤2×¢BT¥«Ó΃HRû�¢ß¿Àâ`�nj’�“kk›ŠÅ™�ÜãÚü üc7¥®¥ ¸ç�”¸Çì·bÌhksýT@Ll­ß}²ÿߣþ½zÝ}îæþæ8µ®_¿ÇŸX\ ¨lßöCâ(çíaJTÊs_ê!Ps¸”¨B¸1btÚ:æÐ‰V›W'"Ʀ (õo[è£A_Ñ„£hdEDI’{ ަ`bÈD•°�4ØüQíØÜ¥¡¦N•ë:þ< V8qØ›à€7Q~Ò°.d·B{£e߇û\Í–ØF<‘æû4 k‡¿B%J”ø‹�¦i•lÏÂXÖª†¶ë^ £,ë°RÏà&ÿ�⾓õDtÌY€Â¬±õ=˯v“Ú�§ ncWvö0w(Ü ŽõEäª9cŠîFÌÀ +,‰Òj–1•(Qb|¨×C8vò\�1Õ'cV‹˜¿Ÿ •�˜-�‘µR&Kç×&3)TµuY˘âöæ�¹��v$)SU­°ÔoB”"MDÔëy‰î @Ÿoƒ‘^'íUëj‰ú• EÝ•R~Þ ¬Lqz­b®5¢®d¹ï5Ø …ò“YŒ{º^jUc= NâéR�·D‰ñbkëÄñè,çJ”[{š“î]ê ¡8˜¡x0vÚœô;[©µ+­+#MaJä�Áæ",oªÃÁ¦ºq(°ÂšªO›Ó¶Œ•šL6î®NñbâGÜBä¹6�²—¶³7y#C¨iSý(R¾ógIÊÉxÆ¢”Èꪺ™B§c²�aÀŠ¥Ë¿¿2á÷Côº�´ÎŸ?Œ%)Q¢ÄA«Õ‚^§÷rÏIQ‚z�zÊKÆU'Àú�’î·|ܼ¨]W…¢ª¢Ë0ŠÏ[Ý�ŽÀéfJr$!ŸËŒ–Q5vqi^L\ñls'…h8rG4»–�PÁØÃÜ"Òãœ$É\Í”ì æÓçMžh§Ó³B¥ºÌöDTÂq0ˆg;ׯÊš”(Qâ‹£ÓÞ?‰Æç×fÜ @ˆÔ<ãv†F©"Ûg¬æ¶””g&‹AÄüT‰Öê,ßìײ|@})ó¸%Ðëz¼)MMOîðWçþ0q¤ÓëôÑiÛVš$ʳ֞²¿S!•tIAûH°Uy+c7¹ºßL¡RžØæ©&¹ò6ÕTËÜ”}<Šâù±,P‰%`wçÍÜ÷òdðü¤,ö9)'Ù*×6© BuÛîV™1ÿOë2±½|/²sf¤Ï´Y8á·S&j£qÄRíþêÜ&.€„•꺈bR‡Ó´bŒ+£'£L¨T/c§ïo² õEßi°ð@“ÝM£¦Dó� ô+ÛLϲ�jµ¦² €Ü2]<õô;øá®N‰%>ýAtšikZ%Ç+‰Àó9äm›•èÿ¥6Ÿ·=VÔ‡öc ",î 9 Xæ=(tQÁcÍG ‘¡ÕÞRZ¿DÔlÍÜ8Ìuy90q$ðƒ]‘&L›ë43Ë>°H–ÒÝlˆ’X)aûsÓî 𹯂 È4ó:¦mÒÚy£‰ÃòLÕ­;e'�Çš©›CKÛif½,yÇT/Ô¡•]ÍÎàúTPð–F UÿŸ¦©›5A#»Diµfëj«Õ×2}M˜¸"d<ˆG'”¨4m|~P«JU¢eÝ ´[ðOÓò G W 5Iª²–!C’°DnrJ8yæBšSNÆkÀ>Ge@I|ì°×§D‰9Î^øº r%mä„ µø){Ò3\LÒîù-uC]Ne¶ÍKÓï�VÈÈÔ£læQ´8µ|§ÆÔŸi’äOS2XR¤õO­Lš»äÄ�ÝÍÍTކ„È$™´“q$rfœ}ÉR)Áƒ�QUÎþáqn�]7…ÜŸn$Ý=Q ðª%³Mu5¶Šàù¾âq`�ËÅ|íö‡¾@%J”pˆ“ä÷ƒâ � ð<ϱ£œÏƒÑÁRŠºªÂ‘÷ZIZ%Ö”PÙ Ä�sí‹l‡÷a¿}ö°×§D‰9â8:å…Õœ€ ¤dD²C'³‘À•¬”8R>®®?«ÅY [Ó”À];_«~UèŸ"ÁMVë_4êòš‡.Áó0&ÊÊÖbâ »)âH ‹2̧ȅH!ײs•æŒ2Æ·¯¸SÑ%7˜Jùg\®Qð±­vÛl³u®œÚ­I}Ù‰ ,.VXoÿuÍñ,R‰%2Œƒ9 j¹î B*-¬|:< Îö,(O0#O¤¬ ¬WÞ֡õÔ�·Ùù�ùF»ÜRì´ûÌó‡Ð÷7}q^L\ Âp+�²BJÊpE’JEÞëÊNIªK[®j™åDÈÕäz. æø6ƒ1/>Z]Êï,2“ƒªE–Ý�¾çX\Ù7ô}_¼þÍic,‹T¢D �Àã~¥fD†Ô»]ÛD1y [¿·UÇ�v•‚tìL‚¢ù™9s²•-BWÖ&û•ñV¨‘£’^r�)éâœÝ×Ý&.€œX:=Lã¡›ü¶<í$Ž³Ê²(82JS­wc³L4%§,€èL"7·�­Q¾ÉJ%æËD¶'‚–¹�¦<î þ"ãH2}x KT¢D �ÜÉ‹ØMß™Ð1c÷ã<Ì™Ë"œWˆµ¬µò»Ž¿K¹À"ÚèRøQØDQe8Éph[­º'O ©;®5ºL\ùÄŸý‰ô�uØG5š¥—2M 3åæO¥{¥(Öú‹íü(Ii‹>Çú)è´üm€A—mØ2—ã{ËcÌ÷ˆ­!2¶¹¹õÚq­S‰%¦ZÓ5î‡y-Ú�n §=Wy°=LKïG2Ʀd>(šÊƒŽî¡ºC-8Õ^2½y²~!*ó‰’Ø�zŒv2í·'®�“@@™ÄTwIJ‰éêÜ4M‹úúdÅIh)0Ù¨M%ñÀìˆûm«Ì ‚ã}“û,¸Ò•0Õ<Ï÷œ@£jë3„ýv§¤ñ–(1FÄi|’{aβT¬I((j[� 4›=�™Þ�TxæzF¶â-Áiœ¸ tÄ‚² C7®˜!;ìºAEÃöݼ·�k�î@¦Z�ç¤̉ Pö¢$%Ùõ”8ŠThŠ­ñ56™¨ ²P£´UM;Dæ.È{fÒ Y¬–3áÌ7ÏGŒcY %J(DÃèdÞçÔA Þ³yº ËRJÚ„¹âµ-"ʇA Ítr¦"®¤e¶ B7?f>gl&rN±® ´Z­»ãZ£ûÁD�FP»$EJhãD–�(AEÖõØ~R¨Í�¹¶¸Me•æ�"wK7ÖQtG.²*r�w‚|6ÝÞG”ôK{_iÉ”,u>ûÈk¼ñ¬R‰%F£¨™ëØé÷~ ¤‡rO üÍLÄtA)VØAC!¤M+tJb†‹ Ù œ@¯“0‘à8öBnÏ£B*ú‘$X©V¯Žu¡¾FLdyáù�DœØWRŸö“DµØAq´µ-XûctÒÄ#!�6§aàšaÄ×â@dA'^à¦@ÈR÷<œ–:jpÏã"M§Æ³J%J”¨´š‘d–M)HB¥ZÕ^ ŽŠ_�ij= ÇÀ%my–ÒuÌ!'ob�©‚�íØÛQÃÕ"U | ùŒ³ˆF4ˆí±,Ð}b"HXo …H”´¥±–¢$M€sÏ1Ô+É(gf(º„Üã¨elçœCÃÅd…Bf®qãhîÛ�esªQ÷Œ”Ìê÷{¥¯m‰cÂüÉ庚ᰠÚB@X­™áAýr«‚—ya:]+¯âA #ý9µæ@_¤ ÖëFME$M�ƒ©VÑÈ>›F#Ø[_Ÿ(/t‹‰ Õj­é5J"pþYdO5B(];ÜŽý¡½ÇT»Ë’ÁÌ ¢ãdë뿈d¡-_PµA-šhÌn�yjÂÕ8"’$Öd·Ý.é%JŒ�=öÄ£Ñ"š)sÕ—Âj%¯CÞÓPÄnÜÍ0r*…n§»ƒdqXÄN°›“£›+«°å*eDšãfÛ5{GõÁ÷ƒtkt¿˜¸2ajvö9¡¨pÚER  ¡r8 :¢§<ã$V¦0h»îˆ`UÝsS) âÏˉÁÅ ½Ûó`¾§ •M�ÃñªuYŸj•Ö¶%JŒ/^„(ŠÎI©KËh‰A¥…ô#—áF �û‡Ú'‡9—9*nEV/0Z-,:ÐêÐûTšªJI> GCjLM­òò¼,˜¸’!éµo’NÖL×+µ !8�Lpå¤$IU9Êu´HÓ|Õ4z>v^,Ré,ç—œRŽ~ªº#d±Ö ¾ïƒ‰=�è&÷ÙÌÔ\õpW§D‰I*§I‰ª¢kfzž_àYÙ^&�Ǹ*W‘9*ywõ=DQY�ò·-:¨ŠªŽÑiTw¥‘rG,Ô5ƒd4daµ:q^ 0©dåúÕ(Õc êTüm‘ĪF¥ë•ä b’T�ÓOÄ<õ|ŠE¶n™›M’vKvcf„(¯|¢TºŽÙ©‚ yJö±ø”¤QéLX¢Äpüøq$!¼¢ ó¸.!1ÓútÏÖLJéÞÿ$Í@°­n€ 1~ ÈóS¥Ûsì¿1*² ) ó�õ�D“ôØîá®Î˃‰ |îíÙ.”É?a45G¿;)1§{£O|�j¡X(kæÙ†DÇá5Ï—ãªDÆŽqtm6ÓáœÓp0<~ˆËR¢D ƒù³ËSÈ=rcâD*ûpU+,8�fŸcÌeÙŽb·ÝÑéJNÎÕ!ÆõO?Ÿì Ÿ FΉР±g0Z¿r¥l¢&‚6§tñ``4nŒ”eÝ)7¹:¤ì |¿ ¢h,jõGy‹ŠÎé…VCñÕw #„ ¨ä_k~©áh´tè‹S¢D öF�q߇"_×ËÞó"Íy2¦¥a„u]JšiãÜpÖTTÇ =Q+r”÷Öé ˜7ZéW&©.Z¸ç5µ­C\–—@Z­øˆw‘ÛM]3(†Ã��$EÈM]TÚ¨žhíj�t³Ï=Ç´×ÛͰ²ÉÍÁjgNÍ£¼÷bèàW÷}ô�…±J£Yö@J”zÝÎk”î•�Tn¤�ê•ê ©‚´)Ï•îÆõ.¬ˆªé��5‚¢B”0gV(Ä+,e5 +¯zhºf½Õºx¨ ó2b"H§Ó|€ÈqŠ!B4çŽ%*Qâ(ã±ÇƒQ·wž¾ÌòkÍ s é˜Sù̆QÞFBIÎSJª)bá„© ƒ�¬;v óíÄ;éÁf*�sd*ìL€ö ÌÌÍoŽk�î“@ ¬Õî¦I|€ý€f"� 4.UU�3ã™n”ÑTzÊX>Lä4óyfÓÊc +tâ­Xovï ’¦Áf¥QÔ�Êž~ÛÛJY÷%/^„~øDO™9BMvÈVf¦ÿÁ@GÍ|†–ÇcRJm‹9×ѱr9%,’½P"؉tr-c1áÆTP¦ZSÏŒs­î@ªß—’ìﯪ–Ì㪦ˆR¿°Òö;HëÙ 02¥Gåd˜¤)x< † m¥˜sûZÐý�<$SÛœ¾¥‚Q§E2Ÿ I¬3HyyJ”8òèõ‡Çg¬H�ÊâZ×¢mš‘�q­Ü-B•̳“\/g`šààr ÊI¾DÄÐM¤£Ë|Ò41ßCf[ŒÌö‘ý@‚‰œB‡I ·nÞ¥£¡ÌC‚Ç=Ý1Ù…™ÇPÙ¤P~!ÌuÓÐŒ•[�$ç?Hy'Äfúæ`öV"–LA•ìy)¥Å›†T�”qô:O�wµJ”8ZXXX€8I*º˜”Ï…KINhÕ¾�•¬2² ”OfaD3̤¢ÄUÞËÀ\Éþ›L%­n7Ä£ˆPw˜ÞNŒšEowc"íl3L¬Ùѱ“'×ûÃTê5Ý¿Ê^ßS³`Ã|fdÝ%z8èõjã^¯%ŽÚ{{SÌ+ ¡;¦6†‚y¹mps~½¡vöA§ íímhïíðß×s#ÙI”sôB‚J SMhLO«�³}  MyŒåýWX„ÉDFýž®x¨MC϶×*“éDh1±Äg¸†JÑ @Sã²€�e F\Ľ�ª'âqI±½½‡íÝ]hï쪹‘ìDÀ¹¯¾–g{žQlV‰'“R@¯Ó†ÎÞŽ BÙó«�Ô§¦¡57«D“(V=’  )Q'éù>.�Y®Ýºøü¸—¬D‰#�……ð8?T*h†¿4Áó8ØyA%¬½ý=Ø]¿ ½ý}ˆ£HIšpÏæ1pŒ‡ö‰K »èïïÁÝë×Õ›Ü|¨6ZPm6ÔAS2ÍSê‚ófûº®!ã\¢”)ãn1±¤×ÞO$¯K5 (rÅ€D$¤é¹úßDHµé&¬_¿Á®>ÿ)¨ÔŽ×*_§D‚ªé.èÓ�~ù¤.XrOÍydO̲�½� X_¹ Õf οö HâüJ¨¤ LñL}P«TãR•(q¤°µµÝÞp©yáuši©Þ�JÓ¼ PïΰZ�µk×`wc‚ZM[;paX!�¨† IÖ„.oªgaÀË‚ Ó V"z0ô³ ^%„óO<¡t·¢Q¤X›œqû¨)Æ'÷}ؽ7×÷zÝ&6€d7ÉÉG–.’��Ž HGf/"S)ãæê*&q¤Na­®£‚õáLÝPY–��:Ôd:‘란¥áYþxš‚‚Ò8AI)Õªzìʧ>¥žsæ‘ À}Žy£�™'Ûíöǽ^%Jœ?ú*èWõð833@Ä<Ž7/}N1®8÷”½-¥ Æ8ya€žï÷ ÑpžïÙ4Â�ˆ‡µJ`MÑlY’&,MÝd—Âhp*¨’'ãœqdˆœKîyÈ=ŽŠÙçyP©Õuã] ×§s7ˆÎ�Õ÷J’4X8q·îÞ¥±.X‰G­V Fƒá™© i<òl!û›s;@®] ýÀ‡ ¬H0ÂzÙ†‘Æ1J¡« Ùž Îœ†e¥È5œëC#WûAvhTŸ¬7E$ÃQAã„ Z¯ÂKŸüùAˆ¹„7°ìðÎ/݃›WÆ»`÷�‰ ëׯõç_Ô‰¥¦]©G‰6%'ª©NµÆ”z<Ë-†ý> ƒ,s@´Ì ›lä¿ìEV< Š�iE_€J¥¢SQªï‘2 ŸVð™ô ³ïŸ’b4çR•(qd ëuä¾´jm+G�"­oAè÷MõG‚á0ÊÞË,ÛKÔS•ÞÓœ\Ì‹5RH’R"&) ³“ ª¨OMeÙú¾ƒnÏü, °5¹é¡š]IfHçöµ‰R„I/aÍÌ/\¦$ÒS€tÀ+L>³Ç%©Ãj½ B$0ìö`g‡¢ÑH•ªÀH·;{(GåÎå͸‡9u0ˆã:;;Ðo·U$ C3ºH֫Њq©œuqnæ3»»»eöQ¢Ä! wïUµµ¸ßSÿÎŵí;�TÓ<{ƒfïßh4„~»I¬ýœÖ1=Vhæ­P7-udd¼ÀW¥óöÖöö÷í·>Õ2”Ý‚Ÿé½ê_Y4è`: Ç·R÷�‰Î@¦§f?²“ÆEý}7ݧ ˜”møˆžçC¿Û‘R ÄìLÂ\J«ÙथÑxvZñŒ´‰~¡!MRSýB3Xj¨ÁL[ä**°ê�p":àz ¤üÃq¯U‰G U„+{8}ì´$PiDqÎO SC*„•¹RºuEaEÎQ2ÏcÖ›0‹’D*U Ì ƒö¥R†#ª2èu³|CÍŒ Iã9G�'AˆÈ�’~ê­Öí~goÜËõ5c¢ÈÚêÍý`iYH"޹:�V%Qû¼T³|£a_íø˜ ñZM+ÔV¸#ôƒóç—aqaZ•¢4�½Î.]º{Ý„ͪ’ö%'Á‰Zä_Ë(«Ê÷çïò”(q¤QT·M£ÑåéZå©áÆõ[Y Èó²ŠÛf2\7Ã=ÏWÚvÜã*ƒPAÇH¼37¡¿ž!“Ìç2�F¨ž–¹)u q–ÎB7Ëw²ObÚÞþ,Dƒßç:½˜è’a!êí¬oGÒMóåG›¬ÚØ"Í¿% +p÷Öšž,5JÿèZ%ÎYßf,L©ŽÎ5ÐJþ« &û�‡�l¯\Ç[ßwïÆå1ž)Q¢D<|voåÊۻן! :wk$À¥%8Õ¬Â(N5�—�4>„hØ·JfÕÔÁÔ,úh1¯RQƒ�Z�9Gk ]òVß^{qà¾ô)y×ÖÖÖ8—åeÁÄØÚ‚äuܽóÛÃî¾È2ÊëKj0œrß[ÛáfÌ÷`gG*‰4Vê…ô6¿ôýÀ9CBù”¡ÖìÍ~@4èSw3ܺôŸaØ~ãóþÿÿÓá/D‰%þ"dÉÈã«ULßè%{?=Ú»�zm•]�#¿(i˜šjÀ0ŽÌÛœŒ /‚”2§`šŠ†�¢~‚Z�™£ª³’r‡XÔºW¦ì-Ó(�kŸüÐÆê�‚Ú?ùF{{0ع÷Mü\ûÖÕ„�o*æ®Ê dn Oêâ{œîmlbÙÍ¡FQ™0"¥ÀI¶ib²ÓÙVµ�¡¨`#dÉdëܯ¤Û+o¹{õ…ïÞ¾·viÜëQ¢D‰ÏC§·nÝ¢›Ÿ~æ§©}w6îþ“ÞΦDÛþ0ܘéF†ƒ¡ZWû2mw›]ë…)tº}ðn¶ñÉÉ4Z؈!p?”ƒÍ;‚í¯þÒÎíë +ó� `Ôyׯ_þ ŸFoÞüìï ·Ö†ÃξĔ>æ7ŠQ&ð8^¹y�*�oD ¤ê} gF­W}…²�ÌâIÇpþÌ‚r´V•@À‡½®89wéÇvÖï~jÜkP¢D‰/�õÛ·‡2üj2Xï@µñ3Æ Z©Áp8’Ê|Ьú8ŸÝBÕå �sØÞïÛêµÍ6ã’qD‰þö½ttïêð¤óŽ[—?÷?�óÚ_nLô áçà ê½0;;û×iÈš³Mÿ‡ÛíµoÚÙí~kmñ¤Ö”¥¥„HÙͱ²¶Åxà)™gnŠW\%¤ÎgYζŒ"X>s’n~ö:³ÄY,G§&׿D‰£†4K¼êAöÖgª„ŇFµ‰ œ‚ôæi‚ú1ÐŒ^„ `0  Ê™¦Èµ皨üsÓõÊï{uü×Ï?ûéÝ1_ò+‚*€€ "&�toÝ€Ÿ€ŸoµZP[œÿ d�·‘%"•½’H…Ò¼Ñ_­­'™RX´.SºFšÄ)œyè,ÆÏ^‚ ZÉÎ-L¹Šyžìõ÷.H‰“ƒN»ýp­y";LªÆ(©v¦Ä¹Yå¤Ä ñÊx‚H+ ¯'0Õ¬C»Û&e®—Å=ظô)š©Â©;—nlÞ÷…¾Âx`JX_ Y@!‘l!ó�®š$ •J€ÝþPQuS$øž–bEL×¾„ôðÃË “Èšì/Vpcmõuc½À%J|UX<öpÍoN*öŒfj¦I ÍF <�‘£g!’òDE¶Q­v;:<ÕjÀ½Í=ÕL×ÌÌóHŽ:éÚÍ›ã¾ÆÃÀ‘ î­\Û7f ¤z öÛ=”ª°)µë ø¾Ÿ3'LÈÉÎ(ssÓÚ7Äú2p/„Ç_ÿö©1^Z‰%¾Z`¿VjÆöA‘kX4QP©gZ M×4MS“}€õ%T½Ð0 `·Ý5Þ£Zk+Û/õæ½±^Û!â� ­V Þþö·c£^»*¥Ðó=V÷¦Rƒn§+×Êø>·SAîÆaˆP }’ DËú%í^F½å±^d‰%¾b,//à×?îùžõ¯V1Éætƒ”Í�PJˆˆ2IS©ö¬7�ÐVu’PJ`ù<‰ˆcX¬Þ\†J`QE)P& _yÈÎ�ûK”(ñå±°°Q�%! ]NÇfš(„4jZÌD*Y#Õ-˜ÓI¨Vª°r{|¥¥g=q³çËtÔÙÛ÷5ŽLéïoïA÷Ñó�̤ ¤½v¦›u�B `¡^.é3s‹ ! ÕH**8™þˆä!ܽ»vrÜ×X¢D‰/�­­-EÑ"™r¶¢S §OÌ+¿sý°¦geïym†mÚä¦�x\»½¡Ø™Ì²5ßã£+/Êq_ãaáÈ�ì¦iÖ«ky:ªãÃÆ^OÖj)dZh�©–é�kg#ë ­z�d*´Ä�Igý°J¡„ã¼¾%J|e˜�=†i’øÚJû˜g�âôÉc4Š#p”*°> GÍðp¶ApÎ`·ÝƒÜ�”€q­zõH¹�™’annêÏ% k„¬nˆa1îq&dv7¡ÉL�¸¼ÒÂ'†fAÈ9Të5E±8u“ùÕÍ,-–=�%&Ë_÷h ¹g˜SŠ� i”ÐâÉ%Œ£¬Ì„¶ƒ�Vw‚l´Èþ!Ò8#’¦Aª•x91Ä#%it¤2º“F#‰J=͈'Ao0Êî¦ÛÆ+ •3«êN€ÂZÒá�m«˜çá¨?|d¼WW¢D‰¯½nû5œ{„ÆPJÙÕ¦ .›‡Ä•°t¨P=ýofLB”:w§7€ ÐR}Á¸‘n¬\í�ûúG*€Üºr}7 •Å‹ÕíçœÓ½�]“�æN†QêLV de·C­é†£‘~–)xqæA081Îk+Q¢ÄW†ÝÝ�'™[8#N",-ÌJ×Q> (¥¢ø[$-Ä*¥ v·,ÉŽ±kö&“ÉhØïÕ.ŽT™?vüã2¡­m*ºnâÊõÛJÊ ö÷ �ô4·�Ô@H«Qg"IÌ“tg„s¤©Ù…²R¢Ä€þ ÷+zxµÉœ¦§Œ¡k~ª ÄI‘Õ¥©Àao^­fÇÒ�PfY 4›õkc¾¼CÅ‘ õZåb𤯏V "ú¡wVï@àsý¸5;Æ\ÎDJT„ V­@¥€œ9^ŸÀš³ ã¾¾%J|yœ9u~–A^s0ÓæÕ00ªÜfdP¹C)ùR:G¦T•J‚Q¯A%P[„4YH<AµZ[óå*ŽTùÌ'þ¬ $„É4T ÔãL^½yj• ’=n¢1&Ôy†fð)¦/È u¨1ñEÈ!.ŒûúJ”(ñåÑë¶å�oµµ/:÷='³J¤úúóÒÈVP^àf a¿Ûî1²"'Ù¾÷ÚpëÆÕ²„õ £V o#·Ìm­KpåÖ¡ŸÝ'z=0Kd #K'´Ìæ¯q"عSó¤|­ä»„Þ`87Þ++Q¢ÄW‚Ýí½'¸ÉaB˜™jÀhk #ã,(‘£òªIsýï0ða{¿çœ ÕÃŒ±¸ßÆÙÅÅ•q_ßaâÈ�Ðó¯"ãV­]e£Áµ€"9�+å}˘iŽ) $ £a_wþ$¦©NVTa‹ˆ àþÓÿí÷ÕÇ{u%J”øR¨×[ØO’9T¶¤h³˜ŸiB¯?0^?ú¹‰H�}µSYULµš°»×™ ÒY‹r1•ñh€A¥rd”xá(�Õ—>·Ç¸vò%3:õC)µn�fî ÷¸r¯,Ž¢ˆŽ-H)RpT^à~Àº[·㥕(QâË`ùÑGêèžÒH-Ä…FµƒQDv^HJ¡22ån0¡dºÕ¤n·+‰¤‘«}Ø©înÇ{…‡‹#@*aØ!£¬‰F¢Ä ØÛïfÙˆŽ ¨ßS.YHa¦Xi*qéÔIQ¤)àÆ`„ûžÜØØx󸯯D‰_ƒþà¸T‘”`høŒ!„•�†Q„dØVYT‘©´²FF—[7Ü+•7w:¨ ZzËD43Õ\ÝÚÚ÷%*ŽTyê©§°Ùj¼‡¦L¥›¬Âúú& i†ˆH7ÒçNûFÛP)±48yr ÉQyõSxPÅÎþþì8¯¯D‰_Û›gYXÕçBÐý�ì½=]¯`’Jcë ëÖ©”Nm"7 ­loØéì)T ) ­fóÈȸ[©òì³Ï’ïáÝxØ7æ:,Tª!noî@–²J«Þ$¹6–ÒI )Ûcus-̶HuÑÉ6ѽ €$ŽqÜ×X¢D‰/Ž~·7å…5N L�@ ‚f=4CædJØd,ìб­8ìïw$J¡m¤Èèó¦) ºÝËKKKã¾ÄC…7î_àБ&Ýt4�T­2m…�àq[Û»h$Ú•=¾Òåe®}F(KYÛV«HÔÉÅ�¶”Œ��tÀá¬D‰¯6È4aAµ&��I3.(ÒjµŠö’”¨F†I§"èŒJíÜ0\»y‡yùÑ[š$âÞݕް{dÌŽT’¹5éwAÓ+ #Km÷»Ú#Äx ©!¥*[e' Î9A( [�šÌ�.%!cØ‘[Î%& ­©xaE镨©á8I²Ç©R �3`©Ùc¨ôU¥Pdy6r}u Àó­ ¯ò ‘£‘œ[<~¹Õj�ûG.I9ÜH:íKh(xZÿ†‘’¸ œ›ª³™©†bclì´aíÞ6ìîîQ¿ÓÃ$NäK/^‡j­Ê"I[eÄã§ÏÉ{7/�÷K”(ñEVjJv]¨t¼À§Ë7Ö`{{OT*Ul4lf¶I'ç Õj €”ûí>ÛÞmC"RÙßïz>C3–í!iãÒÂâ‡î\?Zïÿ#@n_º”ÖN§È%"WYªDߣKŸ»Â7÷zrs{nÝÝ‚n�˜ y}�“Ò8! �[A_Òš9$ÙòëßÐ(H‰¯^lï·©¥ÜÁÍwx ñ#Ÿ¼BJ.³C¡@B ¥‚ÔjUvfqŽ-ÍÓñù&ëõzàWGáW9ˆLéæ•«{㽺ÃÇ‘ Zõê ì5HÂÍ‚ÜÜØfwvÚ@ˆL¦)`èA3h©R—}GDÒ³§(ÈØ…˜"(“AòRÒ½D‰W1æOœBÊåÚ­­(x¡ž«( H-Æ­�@`­Óƒõá?}CB4AàûêûIC�ïîìî¹&è‘*Ú/,,@µZ…¹…Åç”Ûi?%H@ ÒT@š¤†\•=ÎÕ×1%å®»(U‘z sÁwØÛï”s %J¼Š13¿4‹JþPñ­¬£­R¤°&sYX`„š¶Kè&e’‚Lø^VO=ƒùT«†«ã¾¶qàH�­­-h4Шί%ƒ¾$×ú†ÑG̘�[ê…ºÑì:‘ÑV“æŸdGDŸ«Ûë.,”¢¼%J¼j!€IJfõ®l+Ô\a–�¨z‚'ë ¢ÿ"(›˜ uU¤À:Ç#eekqäJX½^n\ÿäÎ�ƒ RÕʉæ0�Ö_ ¹šN�£’8ˆcH…àßîqÆŒ‚§]€t8ðkÍÙ lm�Æ}�%J”øBô»½SµÚ«wqÉÖÌô•aggÜ—wè8r$C𫲛"ÙÔ�IH¢ž~âa|òõßgN-¨D¤ÓëÃhÃÆÖžÜÜÞC®dàUÍ‹�R"ŠrUE”Qœœ€£%ˆS¢Ä„ 1]›í“Dka-S Õjˆ×n¬JÎyvTõ«4M¥H%?sê¼æÑeøïð»¡7ˆ`ee ~é7Þ�©–³Ä•Ñk•ÆÍq_Û8p$Hog+ K1 TlôÔ›^‹>ØÝï©Ó…PÞSžÇY÷@ôR³YCǤ»ºÇ=Ÿu;�ãðéq_c‰%¾I4<ƒÕ*Z‰")ÌÎN‘D†2#ßNŒqà Œ¥º½!v{CÕ9~|Ó^—hº‰Öê!tåÆêÕÞ¸¯m8RMt‹••hÔ+ñüP‚Ѹá•*]~ñ:UÂHJee)M¡T7ÖÌì*j‡²f½ 2hÜqµªo­%O�9WK•(ñ*ÅÞ^÷ŒÑº3ä‚z=Æ8Rö�.K)Y"«¾«Y˜BìníoM¡¡Ú"ƒxÔçÍ©éõq_Û8p$H†[/]Þ@®m²ûªÕ*ûì W°Õ¨»FZ¦8ŸdCÍ’„Ðé �…žcd0 j5–&qs¬V¢D‰/ŠJ­ÚG+£‹<áÒ —µ:H0æç`5Ž˜ %ÌP}%¶w÷ 9eÞâš‚¥þªÕŸ÷µ�G6€ÌÎN=#ÒX¢:mhÒÞÕÕ;°ßîæÄ^M ×Y "‡gžù ´æçœO²V‡VRÏÉØ.ªD‰_Ífó"A>òTøÿƒFÑJuÆuIB“2A«)êlåüCgèýyVɹƒq»F†¤ìݾq¥?îkŽliïí\ëmoH”*•DÓK ôËï~œ>± ¼j9÷Ì)CÝ)ŠðK’`ùÌ xÿŸ~R©x¢E%!¨·qOÞ]»]6ÐK”x•b{kýŠ”B’öþQÔÜÖ±EøÓ>£FAÐ8Ÿ£É.è!â…ùú©Ÿþ%Ü‹Õ×êŠvöõòÄ|óÆ}]ã‘ 2Ž> ûû;Ƚ,·ÐîRRâÎ` ÿí/ÿL5ëĬަñNÎn¸F½ ïþ÷ï… ^ÑÓ鯀Èj54[Í?÷µ•(Qâ/†Ÿ >À=ßÔf'I |æ3òìé¥,¤f?’í�dÇg?ñ\ï÷ˆ¹¢·nŒö÷¶±³×þ�q_׸pdȰߋ�MÏþÓa{O�4HW $ó<ö¹[òÊ¥ë˜ aÖô Z«ÀûþàƒÀÃÐ)q�äž�2êüÛí{wË�%^¥¨†þͽÛ×MYÁ ’à~䣟†Å®'‘$ §O‡÷üî+EM¤! 2N&×V¯|î}㽪ñáÈ�V««]v6?Æ<.Ôm#‰e™FX¯°}ìyœ�ÑÍ2f½Ï áÎÝMhÌN+étŠ:£Þ$ÝvIß-QâUŒ«—^üÄL£ú!†Ì¬ÔÑóñöí5ø¾b[éÒ5b£Vƒ_ùÕß‚P5έþ¶’ÂÃ� Švîþo㾦qâÈ�N§õzš~ëG7.’9Q+5)(àÊê&ÌNksB�–�åû\ù3+ºfÎ$Y¤üí±^T‰%¾$Μ=×é ¾‘ˆ˜¢èeÅtzùŒŒã$K;@Óxšõ*|ìSW€Øœ%Û ÒÑ«^ü_¶îÜz÷¸¯iœ8²$C¿ß‡k—þìųç.|ȉဎŒ£$DãGf?³87Çf̪‘sV<� ¬tûÝ<¾«)Q¢Ä—Aå;¦N?LBHiyVÙ{¹‚€o}êq–J©††Q[ ªþÈ\«ª:Öÿ# ,bØI½ö÷�ûzÆ >î_`œX\\„æÌ“íˆ~Öo´Œ8;rçgá±G‚$Mͬ þlo0ÀïþÎo‚ßûý€_­¸f÷|˜^8öz9Øÿ…Ñ` Ç|i%J”ø<,//Ÿ9ñ* Œ¡ÖÍÞÛßó�O‘çûÀ²Oè95LÜŒàÌÉ%xö³/)­<;L�FÐyÛÉǃÁ¸/ml8ÒÈÊÊ Ä©ü[S§Ï+ƒKÒÜ=us<ý¦'¨Ýd70ÎA’I„ˆ öÛøÖ¯= cf>] ñv§?ÏüÊ_÷u•(Qâ ‘†µI€ŸÇ¢Ž;"Ôãž~ëë³÷9){kS¯–Rª`ñæ7?A�n~�lÌ/ag¯óƒÇ}]ãÄ‘ Ê`jþø[dš2ͨ’*(t×wà/½ñqô´x¢v±Š®þÅ)}û·½voÝQ .£ê Íù%¬O/~߸¯«D‰_ˆA»ÿ®ÚÌÂ� ¿·³?÷s?Û{RÐUˆ4kumÞöäãÀ=ÏÊ›0 bx'ë3S�í‚^8Ò$Ã0¯“R¢ö­Eˆ†#øÁÿîÐë÷Aëâè!sŵ’RÑ6²»,;�|û7? "ÎÙLŠRî¿í¿²wg?r\UÇÏ­¥»§gmÏLÆñ² ±XŠDH‘ñ‘‘ò€à�Ä?€< ¼�€°)R6ILbG@”Å&NX<Ž{lÏ’Ùzzz¦·éîêºuUµíXNB®�Ê¢¿)ŠfÒÕ�ªúWuëžsÓ€·Ûý�kwÔƒp{R¾ý㺎ܲÿÙ(W£©«èÕ‡1*™Qè^å’4¥P7Ý|½lKI9˜éþw�)HÐ >ÒÏ›Èõu€Lí».�S½ÕJ$¨×Íí·ßfšÍ–ŽàÄÿ�Õ›«’hãäJ­.wÞùEiV6ãÇ’¨]«F³½ýcŸ¾u4í±¸ Z©Ü00wÝMö¬Ž¶°Þÿá}f£Z‹çLÔÞD‹râf&ÉJ¥”ÞµsJ·jué­ãí>�dò#jlr*_,öo󉾣Å5&¹çHa…F©Œïªèe‡ŠkÄuŒ©VëQ[çhϱóO¼q5a¯vµûÙv'”l&¯ãHÚ;»™œÙÜØØ‘òÐ\Äu½@kݽ]tzYѽƒTÉn¢êÂÎÔÒÛ½7+¡Ä‘ *O‡N¼n?þ:ˆêÁŒ öîÝ›æÐRÕ×ò¯—_Öƒ9çXt%o22ùAõÔÁÔP~ {ÒE¿×ɉÖÛT¿7êx®4›[qEzÒ�Ó´56±íTÚcpÁ®�;^5åúÞù�½ü€4ªu鄺Wx7MMöé‡RŽ„aGffdhr¼-ÝϘve]­®.-�;w.íᥦ¯¤kmnæ5ãúa¯$5“ÏÉãO>+ÙlÆ&Ó[&^²}"j|=˜ÌÍ­J¼Ím<&Ž‘NÐ2›ëGR€‹œ˜þw1£:×õ’ÕúJrù¬ÌÎ/‹#މC%žePN¯¿DÔ¯È ääð«Ç$72œLc+Q®g¥eq]õRÚcKSßÈàÐС­òšNV⊵vï�ïüSuÂ0š M^®ÅçWüÈ« #CòðÃOH¶{RõJ’O†ò¹™âÂ…„Àdrr—\wýµ[ÍÞ¢I -ÇNž‘=WOªó×v²t7¾[tœ0Ôª06&ÿ˜>%Aœïx¤u¨¯ž˜xz£´ÖI{liêûÉ*s ºð¦QŽ/çkS]×|þˆ|pïÎè†#ºC‘^xˆŽ~ÖF^ymZ\×5ñþfޏž¯WçÎü=í1x»bqAJÅåkë+ÑÏñrF––KÒ6a´Ï�ã8Z)Õ›hˆ¦«ÆÇ†åÞß/ÞȰxŽÓû�öæ†SoTîKyX©ëûY˜=[ûm§Óަ7ãÓǨJ³-‡žþ›ä2¾ %*T×½NœAБ£G§¥°kG´*+þKFêå’3œÏ¾�ò�\FiqñÅŒ«¥÷n\‰ä·�É=?ú�Ll‹VÞ;Ýk¿ÕvÅ•è륲LÏ.œß(y0×óõÒÖi�)m} ]ÍêÚwjÅÅÞJÝèÌÊ æå—�vЉ÷ºÔq‰j7-öî¾JýùÅ׌ӻ[‰( êâyÎ3iŽÀå•Ëe š�ƒŽã\Xq刜^\“•b) Œh9�Žï §Æ òèþ"Ã…¸LÅñ”ëImeþ÷•ÊBÊ#JÒ½3Y^ÞÊûê!m$TÉv•J™¸f§<òèA¹j|4Z¢Ñ=‰|ϕï—õZCI²z«›#®ç†¾è­³3si�ÀåÕK‹Ïj¥´£�x�•ŒLäɧž—ÂÈPò¢ST.—•£G�ËÉÅU1aÜŠ7™�ÐõåùÎVmãgi�åJ@€$Þxý�Žë½ïˆëmäÔÒš<~àYÎGŸËærrïÏ•ìà@tò%oÐu'h©…cG^N{ÞÙÞݻΕçfL´\7ù�1FŽÍ,Èâꚸ®£ÛA[Ú[-¹ï‘?™�Á|Tß•<²h×÷Ä [÷5k–ê÷{;÷‹ �6ÃvëîÌØD.©F�V캞§NÎÌKÞuåS·|T~ñ«ßIÓ÷L\“j’¾b¶J+�ÁŒóízes-í±¸¼ÚVc>ë¹·9C…}ÊuIJˆ½�¬<÷Üa¹õ7ËàРºç'ˆ7< ˜ä6Q)i”×$¬X¯”O¤=–+A_·"¾Ô5×ßøÙvfð™la‡£$ í+£DÂv'ª<÷}/Ù@¢b##Ê(ß ³Ó¿^:uü›i�À»¿jjÊ)\=?8µÏï^â*© vGj›µhÊjxÛˆh£µ˜h÷óî…®•ï›ÊéןY›=ý…´Çp¥à ä"›¥â¹‘Ba·;<þñóïÓ%~Äp7wö+¡Çö\³çЙ“oé^ÿ•J%íCàÿ ï@V€`…X!@V€`…X!@V€`…X!@V€`…X!@V€`…X!@V€`…X!@V€`…X!@V€`…X!@V€`…X!@V€`…X!@V€`…X!@V€`…X!@V€`…X!@V€`…X!@V€`…X!@V€`…X!@V€`å?ÿÿljÆOÍYIEND®B`‚././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1789253942.642517 mopidy-4.0.4/docs/index.md0000644000175100017510000000254115251354467015065 0ustar00runnerrunner# Mopidy [Mopidy](https://mopidy.com/) is an extensible music server written in Python. These are the user and developer docs. For the project pitch, screenshots, and news, see [mopidy.com](https://mopidy.com/). ## Getting started - [Installation](installation/index.md) — install Mopidy on Linux, macOS, or Raspberry Pi. - [Usage](usage/index.md) — run the server, configure it, and connect a client. - [Extensions](ext/index.md) — add music sources, frontends, and more. ## Going further - [Guides](guides/index.md) — setup walkthroughs, audio output, and extension development. - [Reference](reference/index.md) — command line, config, APIs, and glossary. - [About](about/index.md) — contributing, changelog, authors, and sponsors. ## Getting help If you get stuck, ask for help on the [Discourse forum](https://discourse.mopidy.com/). If you stumble into a bug or have a feature request, please create an issue in the [issue tracker](https://github.com/mopidy/mopidy/issues). If you're unsure if it's a bug or not, ask on the forum first. The [source code](https://github.com/mopidy/mopidy) may also be of help. ## Getting involved To follow and discuss Mopidy development, see the [Development category](https://discourse.mopidy.com/c/dev/10) on the Discourse forum, or the `#mopidy-dev` stream on [Zulip chat](https://mopidy.zulipchat.com/). ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1789253948.062509 mopidy-4.0.4/docs/installation/0000755000175100017510000000000015251354474016131 5ustar00runnerrunner././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1789253942.642517 mopidy-4.0.4/docs/installation/index.md0000644000175100017510000000067215251354467017571 0ustar00runnerrunner# Installation - [Install from system packages](packages.md) — install from your operating system's package manager for Debian/Ubuntu, Arch Linux, Fedora, or macOS. - [Manual install](manual.md) — install from PyPI using `pip`, for systems not covered above. If you want to contribute to Mopidy, install it the regular way first, then read [Contributing](../about/contributing.md) and [Development environment](../about/devenv.md). ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1789253942.6431894 mopidy-4.0.4/docs/installation/manual.md0000644000175100017510000000745615251354467017746 0ustar00runnerrunner# Manual install If you are on Linux or macOS, but can't install from a package manager, you can install Mopidy from PyPI using `pip`. ## Python You need Python 3.13 or newer. Check if you have Python and what version by running: ```console $ python3 --version ``` ## pip and build tools You need `pip`, the Python package installer. You'll also need a C compiler and the Python development headers to install some Mopidy extensions. /// tab | Debian/Ubuntu ```console $ sudo apt install build-essential python3-dev python3-pip ``` /// /// tab | Arch Linux ```console $ sudo pacman -S base-devel python-pip ``` /// /// tab | Fedora ```console $ sudo dnf install -y gcc python3-devel python3-pip ``` /// /// tab | Gentoo ```console $ emerge -av dev-python/pip ``` /// /// tab | macOS pip is included with Python when installed via Homebrew or python.org. /// ## GStreamer You'll need GStreamer >= 1.26.2. GStreamer is packaged for most popular Linux distributions. Search for GStreamer in your package manager and make sure to install the "good" and "ugly" plugin sets, as well as the Python bindings. To be able to build the Python bindings from source, also install the development headers for `libcairo2` and `libgirepository-2.0`. /// tab | Debian/Ubuntu ```console $ sudo apt install \ gir1.2-gst-plugins-base-1.0 \ gir1.2-gstreamer-1.0 \ gstreamer1.0-plugins-good \ gstreamer1.0-plugins-ugly \ gstreamer1.0-tools \ libcairo2-dev \ libgirepository-2.0-dev \ python3-gst-1.0 ``` /// /// tab | Arch Linux ```console $ sudo pacman -S \ cairo \ gobject-introspection \ gst-python \ gst-plugins-good \ gst-plugins-ugly ``` /// /// tab | Fedora ```console $ sudo dnf install -y \ cairo-devel \ gobject-introspection-devel \ python3-gstreamer1 \ gstreamer1-plugins-good \ gstreamer1-plugins-ugly-free ``` /// /// tab | Gentoo ```console $ emerge -av \ dev-libs/gobject-introspection \ dev-python/gst-python \ media-plugins/gst-plugins-meta \ x11-libs/cairo ``` `gst-plugins-meta` is the one that actually pulls in the plugins you want, so pay attention to the USE flags, e.g. `alsa`, `mp3`, etc. /// /// tab | macOS Homebrew bundles all GStreamer plugin sets and the Python bindings in the single `gstreamer` formula. The `cairo` and `gobject-introspection` formulas are needed to build PyGObject and pycairo from source: ```console $ brew install gstreamer cairo gobject-introspection ``` /// ## Mopidy You are now ready to install the latest release of Mopidy. If you're installing Mopidy inside a Python virtual environment, activate the virtualenv and run: ```console $ python3 -m pip install --upgrade mopidy ``` If you want to install Mopidy globally on your system, you can run: ```console $ sudo python3 -m pip install --upgrade --break-system-packages mopidy ``` This will use `pip` to install the latest release of [Mopidy from PyPI](https://pypi.org/project/Mopidy). To upgrade Mopidy in the future, just rerun the same command. Now, you're ready to [run Mopidy](../usage/index.md). ## Extensions If you want to use any Mopidy extensions, like Spotify support or MPD client access, you need to install them separately. You can install any Mopidy extension directly from PyPI with `pip`. Browse the [extension registry](https://mopidy.com/ext/) to find available extensions. To install one, e.g. `mopidy-mpd`, inside a virtualenv, run: ```console $ python3 -m pip install mopidy-mpd ``` To install the same package globally on your system, run: ```console $ sudo python3 -m pip install --break-system-packages mopidy-mpd ``` Note that extensions installed with `pip` will only install Python dependencies. Please refer to the extension's documentation for information about any other requirements needed for the extension to work properly. ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1789253942.6431894 mopidy-4.0.4/docs/installation/packages.md0000644000175100017510000001100015251354467020223 0ustar00runnerrunner# Install from system packages Install Mopidy from your operating system's package manager to get automatic updates alongside the rest of your system. If your OS or distribution is not listed below, see [Manual install](manual.md). //// tab | Debian/Ubuntu If you run a Debian based Linux distribution, like Ubuntu or Raspberry Pi OS, install from the [Mopidy APT archive](https://apt.mopidy.com/). The packages here are built from the same source as the packages in Debian proper, just updated more often than the Debian release cycle. If you're setting up a Raspberry Pi from scratch, see the [Raspberry Pi guide](../guides/raspberrypi.md) first. Add the archive's GPG key: ```console $ sudo mkdir -p /etc/apt/keyrings $ sudo wget -q -O /etc/apt/keyrings/mopidy-archive-keyring.gpg \ https://apt.mopidy.com/mopidy-archive-keyring.gpg ``` Add the APT repo to your package sources: /// tab | Debian 13 (Trixie) Also works on Ubuntu 25.10 and Raspberry Pi OS 2025-10-01 or newer. ```console $ sudo wget -q -O /etc/apt/sources.list.d/mopidy.sources \ https://apt.mopidy.com/trixie.sources ``` /// /// tab | Debian 12 (Bookworm) Also works on Ubuntu 23.10 and Raspberry Pi OS 2023-10-10 or newer. ```console $ sudo wget -q -O /etc/apt/sources.list.d/mopidy.sources \ https://apt.mopidy.com/bookworm.sources ``` /// Install Mopidy and all dependencies: ```console $ sudo apt update $ sudo apt install mopidy ``` //// //// tab | Arch Linux Install from the [Extra repository](https://www.archlinux.org/packages/extra/any/mopidy/): ```console $ pacman -S mopidy ``` //// //// tab | Fedora Install from the standard Fedora repositories: ```console $ sudo dnf install mopidy ``` This will automatically install mopidy-mpd as a weak dependency as well. Some extensions are packaged in RPMFusion. To [install this repository](https://rpmfusion.org/Configuration), run: ```console $ sudo dnf install \ https://download1.rpmfusion.org/free/fedora/rpmfusion-free-release-$(rpm -E %fedora).noarch.rpm \ https://download1.rpmfusion.org/nonfree/fedora/rpmfusion-nonfree-release-$(rpm -E %fedora).noarch.rpm ``` //// //// tab | macOS Install using [Homebrew](https://brew.sh/). First, enable the [Mopidy Homebrew tap](https://github.com/mopidy/homebrew-mopidy): ```console $ brew tap mopidy/mopidy ``` Then install Mopidy: ```console $ brew install mopidy ``` This will take some time, as it also installs GStreamer and its media codecs. //// Now, you're ready to [run Mopidy](../usage/index.md). ## Upgrade Mopidy /// tab | Debian/Ubuntu ```console $ sudo apt update $ sudo apt upgrade ``` /// /// tab | Arch Linux ```console $ pacman -Syu ``` /// /// tab | Fedora ```console $ sudo dnf upgrade ``` /// /// tab | macOS ```console $ brew upgrade ``` /// ## Install extensions /// tab | Debian/Ubuntu To list extensions available from apt.mopidy.com: ```console $ apt search mopidy ``` To install one, e.g. `mopidy-mpd`: ```console $ sudo apt install mopidy-mpd ``` If the extension you want is not in APT, install it from PyPI using `pip`: ```console $ sudo apt install python3-pip $ sudo python3 -m pip install --break-system-packages ... ``` /// /// tab | Arch Linux AUR has packages for many [Mopidy extensions](https://aur.archlinux.org/packages/?K=mopidy). To install one, e.g. `mopidy-mpd`: ```console $ yay -S mopidy-mpd ``` If the extension you want is not in AUR, install it from PyPI using `pip`: ```console $ sudo python3 -m pip install --break-system-packages ... ``` /// /// tab | Fedora As of February 2020, only mopidy-mpd (automatically installed) and mopidy-spotify (RPMFusion-nonfree) are packaged. To install `mopidy-spotify` from RPMFusion-nonfree: ```console $ sudo dnf install mopidy-spotify ``` If the extension you want is not in the repositories, install it from PyPI using `pip`: ```console $ sudo python3 -m pip install ... ``` /// /// tab | macOS The Homebrew tap has formulas for several Mopidy extensions. Extensions installed from Homebrew come complete with all dependencies, both Python and non-Python. To list available extensions: ```console $ brew search mopidy ``` If the extension you want is not in Homebrew, install it from PyPI into the `mopidy` formula's virtualenv: ```console $ $(brew --prefix mopidy)/libexec/bin/python -m pip install ... ``` Note that extensions installed this way are not managed by Homebrew, and may need to be reinstalled after an upgrade of the `mopidy` formula. /// For a comprehensive index of available extensions, see the [Mopidy extension registry](https://mopidy.com/ext/). ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1789253948.0654502 mopidy-4.0.4/docs/reference/0000755000175100017510000000000015251354474015366 5ustar00runnerrunner././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1789253942.6431894 mopidy-4.0.4/docs/reference/architecture.md0000644000175100017510000000715715251354467020406 0ustar00runnerrunner# Architecture The overall architecture of Mopidy is organized around multiple frontends and backends. The frontends use the core API. The core actor makes multiple backends work as one. The backends connect to various music sources. The core actor use the mixer actor to control volume, while the backends use the audio actor to play audio. ```mermaid graph LR frontend[Multiple frontends] --> core[Core]; core --> backend[Multiple backends]; core --> mixer[Mixer]; backend --> audio[Audio]; ``` ## Frontends Frontends expose Mopidy to the external world. They can implement servers for protocols like HTTP, MPD and MPRIS, and they can be used to update other services when something happens in Mopidy, like the Last.fm scrobbler frontend does. See [Frontend API](frontend.md) for more details. ```mermaid graph LR http[HTTP frontend] --> core[Core]; mpd[MPD frontend] --> core; mpris[MPRIS frontend] --> core; scrobbler[Scrobbler frontend] --> core; ``` ## Core The core is organized as a set of controllers with responsibility for separate sets of functionality. The core is the single actor that the frontends send their requests to. For every request from a frontend it calls out to one or more backends which does the real work, and when the backends respond, the core actor is responsible for combining the responses into a single response to the requesting frontend. The core actor also keeps track of the tracklist, since it doesn't belong to a specific backend. See [Core API](core.md) for more details. ```mermaid graph LR core[Core] --> tracklist{{Tracklist controller}}; core --> library{{Library controller}}; core --> playback{{Playback controller}}; core --> playlists{{Playlists controller}}; core --> history{{History controller}}; library --> local[Local backend]; library --> spotify[Spotify backend]; playback --> local; playback --> spotify; playback --> audio[Audio]; playlists --> local; playlists --> spotify; ``` ## Backends The backends are organized as a set of providers with responsibility for separate sets of functionality, similar to the core actor. Anything specific to i.e. Spotify integration or local storage is contained in the backends. To integrate with new music sources, you just add a new backend. See [Backend API](backend.md) for more details. ```mermaid graph LR local[Local backend] --> local_library{{Local library provider}}; local_library --> local_disk[(Local disk)]; local --> local_playback{{Local playback provider}}; local_playback --> local_disk; local_playback --> audio[Audio]; local --> local_playlists{{Local playlists provider}}; local_playlists --> local_disk; spotify[Spotify backend] --> spotify_library{{Spotify library provider}}; spotify_library --> spotify_service[(Spotify service)]; spotify --> spotify_playback{{Spotify playback provider}}; spotify_playback --> spotify_service; spotify_playback --> audio; spotify --> spotify_playlists{{Spotify playlists provider}}; spotify_playlists --> spotify_service; ``` ## Audio The audio actor is a thin wrapper around the parts of the GStreamer library we use. If you implement an advanced backend, you may need to implement your own playback provider using the [Audio API](audio.md), but most backends can use the default playback provider without any changes. ## Mixer The mixer actor is responsible for volume control and muting. The default mixer use the audio actor to control volume in software. The alternative implementations are typically independent of the audio actor, but instead use some third party Python library or a serial interface to control other forms of volume controls. ././@PaxHeader0000000000000000000000000000003200000000000010210 xustar0026 mtime=1789253942.64336 mopidy-4.0.4/docs/reference/audio.md0000644000175100017510000000134415251354467017015 0ustar00runnerrunner# Audio API The audio API is the interface we have built around GStreamer to support our specific use cases. Most backends should be able to get by with simply setting the URI of the resource they want to play, for these cases the default playback provider should be used. For more advanced cases such as when the raw audio data is delivered outside of GStreamer or the backend needs to add metadata to the currently playing resource, developers should sub-class the base playback provider and implement the extra behaviour that is needed through the following API: ::: mopidy.audio options: heading_level: 2 ::: mopidy.audio.scan options: heading_level: 2 ::: mopidy.audio.tags options: heading_level: 2 ././@PaxHeader0000000000000000000000000000003200000000000010210 xustar0026 mtime=1789253942.64336 mopidy-4.0.4/docs/reference/backend.md0000644000175100017510000000577515251354467017317 0ustar00runnerrunner# Backend API The backend API is the interface that must be implemented when you create a backend. If you are working on a frontend and need to access the backends, see the [Core API](core.md) instead. ## URIs and routing of requests to the backend When Mopidy's core layer is processing a client request, it routes the request to one or more appropriate backends based on the URIs of the objects the request touches on. The objects' URIs are compared with the backends' [mopidy.backend.Backend.uri_schemes][] to select the relevant backends. An often used pattern when implementing Mopidy backends is to create your own URI scheme which you use for all tracks, playlists, etc. related to your backend. In most cases the Mopidy URI is translated to an actual URI that GStreamer knows how to play right before playback. For example: - Spotify already has its own URI scheme (`spotify:track:...`, `spotify:playlist:...`, etc.) used throughout their applications, and thus mopidy-spotify simply uses the same URI scheme. - mopidy-soundcloud created it's own URI scheme, after the model of Spotify, and uses URIs of the following forms: `soundcloud:search`, `soundcloud:user-...`, `soundcloud:exp-...`, and `soundcloud:set-...`. Playback is handled by converting the custom `soundcloud:..` URIs to `http://` URIs immediately before they are passed on to GStreamer for playback. - Mopidy differentiates between `file://...` URIs handled by [mopidy-stream](../ext/stream.md) and `local:...` URIs handled by mopidy-local. [mopidy-stream](../ext/stream.md) can play `file://...` URIs pointing to tracks and playlists located anywhere on your system, but it doesn't know a thing about the object before you play it. On the other hand, mopidy-local scans a predefined `local/media_dir` to build a meta data library of all known tracks. It is thus limited to playing tracks residing in the media library, but can provide additional features like directory browsing and search. In other words, we have two different ways of playing local music, handled by two different backends, and have thus created two different URI schemes to separate their handling. The `local:...` URIs are converted to `file://...` URIs immediately before they are passed on to GStreamer for playback. If there isn't an existing URI scheme that fits for your backend's purpose, you should create your own, and name it after your extension's `mopidy.ext.Extension.ext_name`. Care should be taken not to conflict with already in use URI schemes. It is also recommended to design the format such that tracks, playlists and other entities can be distinguished easily. However, it's important to note that outside of the backend that created them, URIs are opaque values that neither Mopidy's core layer or Mopidy frontends should attempt to derive any meaning from. The only valid exception to this is checking the scheme. ## Backend implementations See the [extension registry](https://mopidy.com/ext/). ::: mopidy.backend options: heading_level: 2 ././@PaxHeader0000000000000000000000000000003200000000000010210 xustar0026 mtime=1789253942.64336 mopidy-4.0.4/docs/reference/command.md0000644000175100017510000000535115251354467017334 0ustar00runnerrunner# mopidy command ## Synopsis ```text mopidy [-h] [--version] [-q] [-v] [--config CONFIG_FILES] [-o CONFIG_OVERRIDES] [COMMAND] ... ``` ## Description Mopidy is a music server which can play music both from multiple sources, like your local hard drive, radio streams, and from Spotify and SoundCloud. Searches combines results from all music sources, and you can mix tracks from all sources in your play queue. Your playlists from Spotify or SoundCloud are also available for use. The `mopidy` command is used to start the server. ## Options **`--help`, `-h`** Show help message and exit. **`--version`** Show Mopidy's version number and exit. **`--quiet`, `-q`** Show less output: warning level and higher. **`--verbose`, `-v`** Show more output. Repeat up to four times for even more. **`--config `** Specify config files and directories to use. To use multiple config files or directories, separate them with a colon. The later files override the earlier ones if there's a conflict. When specifying a directory, all files ending in .conf in the directory are used. **`--option