pax_global_header00006660000000000000000000000064147232547740014531gustar00rootroot0000000000000052 comment=01f1fafc888457a440a22a41320f02e9014e07c4 beetbox-beets-01f1faf/000077500000000000000000000000001472325477400147475ustar00rootroot00000000000000beetbox-beets-01f1faf/.git-blame-ignore-revs000066400000000000000000000023571472325477400210560ustar00rootroot00000000000000# 2014 # flake8-cleanliness in missing e21c04e9125a28ae0452374acf03d93315eb4381 # 2016 # Removed unicode_literals from library, logging and mediafile 43572f50b0eb3522239d94149d91223e67d9a009 # Removed unicode_literals from plugins 53d2c8d9db87be4d4750ad879bf46176537be73f # reformat flake8 errors 1db46dfeb6607c164afb247d8da82443677795c1 # 2021 # pyupgrade root e26276658052947e9464d9726b703335304c7c13 # pyupgrade beets dir 6d1316f463cb7c9390f85bf35b220e250a35004a # pyupgrade autotag dir f8b8938fd8bbe91898d0982552bc75d35703d3ef # pyupgrade dbcore dir d288f872903c79a7ee7c5a7c9cc690809441196e # pyupgrade ui directory 432fa557258d9ff01e23ed750f9a86a96239599e # pyupgrade util dir af102c3e2f1c7a49e99839e2825906fe01780eec # fix unused import and flake8 910354a6c617ed5aa643cff666205b43e1557373 # pyupgrade beetsplug and tests 1ec87a3bdd737abe46c6e614051bf9e314db4619 # 2022 # Reformat flake8 config comments abc3dfbf429b179fac25bd1dff72d577cd4d04c7 # 2023 # Apply formatting tools to all files a6e5201ff3fad4c69bf24d17bace2ef744b9f51b # 2024 # Reformat the codebase 85a17ee5039628a6f3cdcb7a03d7d1bd530fbe89 # Fix lint issues f36bc497c8c8f89004f3f6879908d3f0b25123e1 # Remove some lint exclusions and fix the issues 5f78d1b82b2292d5ce0c99623ba0ec444b80d24c beetbox-beets-01f1faf/.github/000077500000000000000000000000001472325477400163075ustar00rootroot00000000000000beetbox-beets-01f1faf/.github/ISSUE_TEMPLATE/000077500000000000000000000000001472325477400204725ustar00rootroot00000000000000beetbox-beets-01f1faf/.github/ISSUE_TEMPLATE/bug-report.md000066400000000000000000000014471472325477400231100ustar00rootroot00000000000000--- name: "\U0001F41B Bug report" about: Report a problem with beets --- ### Problem Running this command in verbose (`-vv`) mode: ```sh $ beet -vv (... paste here ...) ``` Led to this problem: ``` (paste here) ``` Here's a link to the music files that trigger the bug (if relevant): ### Setup * OS: * Python version: * beets version: * Turning off plugins made problem go away (yes/no): My configuration (output of `beet config`) is: ```yaml (paste here) ``` beetbox-beets-01f1faf/.github/ISSUE_TEMPLATE/config.yml000066400000000000000000000005341472325477400224640ustar00rootroot00000000000000blank_issues_enabled: false contact_links: - name: đź’ˇ Have an idea for a new feature? url: https://github.com/beetbox/beets/discussions about: Create a new idea discussion! - name: 🙇 Need help with beets? url: https://github.com/beetbox/beets/discussions about: Create a new help discussion if it hasn't been asked before! beetbox-beets-01f1faf/.github/ISSUE_TEMPLATE/feature-request.md000066400000000000000000000012031472325477400241310ustar00rootroot00000000000000--- name: "\U0001F680 Feature request" about: "Formalize a feature request from GitHub Discussions" --- ### Proposed solution ### Objective #### Goals #### Non-goals #### Anti-goals beetbox-beets-01f1faf/.github/pull_request_template.md000066400000000000000000000020761472325477400232550ustar00rootroot00000000000000## Description Fixes #X. (...) ## To Do - [ ] Documentation. (If you've added a new command-line flag, for example, find the appropriate page under `docs/` to describe it.) - [ ] Changelog. (Add an entry to `docs/changelog.rst` to the bottom of one of the lists near the top of the document.) - [ ] Tests. (Very much encouraged but not strictly required.) beetbox-beets-01f1faf/.github/sphinx-problem-matcher.json000066400000000000000000000003551472325477400235750ustar00rootroot00000000000000{ "problemMatcher": [ { "owner": "sphinx", "pattern": [ { "regexp": "^([^:]+):(\\d+): (WARNING: )?(.+)$", "file": 1, "line": 2, "message": 4 } ] } ] } beetbox-beets-01f1faf/.github/stale.yml000066400000000000000000000015101472325477400201370ustar00rootroot00000000000000# Configuration for probot-stale - https://github.com/probot/stale daysUntilClose: 7 staleLabel: stale issues: daysUntilStale: 60 onlyLabels: - needinfo markComment: > Is this still relevant? If so, what is blocking it? Is there anything you can do to help move it forward? This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions. pulls: daysUntilStale: 120 markComment: > Is this still relevant? If so, what is blocking it? Is there anything you can do to help move it forward? This pull request has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions. beetbox-beets-01f1faf/.github/workflows/000077500000000000000000000000001472325477400203445ustar00rootroot00000000000000beetbox-beets-01f1faf/.github/workflows/changelog_reminder.yaml000066400000000000000000000017611472325477400250510ustar00rootroot00000000000000name: Verify changelog updated on: pull_request_target: types: - opened - ready_for_review jobs: check_changes: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Get all updated Python files id: changed-python-files uses: tj-actions/changed-files@v44 with: files: | **.py - name: Check for the changelog update id: changelog-update uses: tj-actions/changed-files@v44 with: files: docs/changelog.rst - name: Comment under the PR with a reminder if: steps.changed-python-files.outputs.any_changed == 'true' && steps.changelog-update.outputs.any_changed == 'false' uses: thollander/actions-comment-pull-request@v2 with: message: 'Thank you for the PR! The changelog has not been updated, so here is a friendly reminder to check if you need to add an entry.' GITHUB_TOKEN: '${{ secrets.GITHUB_TOKEN }}' beetbox-beets-01f1faf/.github/workflows/ci.yaml000066400000000000000000000043121472325477400216230ustar00rootroot00000000000000name: Test on: pull_request: push: branches: - master env: PY_COLORS: 1 jobs: test: name: Run tests strategy: fail-fast: false matrix: platform: [ubuntu-latest, windows-latest] python-version: ["3.8", "3.9"] runs-on: ${{ matrix.platform }} env: IS_MAIN_PYTHON: ${{ matrix.python-version == '3.8' && matrix.platform == 'ubuntu-latest' }} steps: - uses: actions/checkout@v4 - name: Install Python tools uses: BrandonLWhite/pipx-install-action@v0.1.1 - name: Setup Python with poetry caching # poetry cache requires poetry to already be installed, weirdly uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} cache: poetry - name: Install PyGobject dependencies on Ubuntu if: matrix.platform == 'ubuntu-latest' run: | sudo apt update sudo apt install ffmpeg gobject-introspection libgirepository1.0-dev poetry install --extras=replaygain --extras=reflink - name: Install Python dependencies run: poetry install --only=main,test --extras=autobpm - if: ${{ env.IS_MAIN_PYTHON != 'true' }} name: Test without coverage run: poe test - if: ${{ env.IS_MAIN_PYTHON == 'true' }} name: Test with coverage uses: liskin/gh-problem-matcher-wrap@v3 with: linters: pytest run: poe test-with-coverage - if: ${{ env.IS_MAIN_PYTHON == 'true' }} name: Store the coverage report uses: actions/upload-artifact@v4 with: name: coverage-report path: .reports/coverage.xml upload-coverage: name: Upload coverage report needs: test runs-on: ubuntu-latest permissions: id-token: write steps: - uses: actions/checkout@v4 - name: Get the coverage report uses: actions/download-artifact@v4 with: name: coverage-report - name: Upload code coverage uses: codecov/codecov-action@v4 with: files: ./coverage.xml use_oidc: ${{ !(github.event_name == 'pull_request' && github.event.pull_request.head.repo.fork) }} beetbox-beets-01f1faf/.github/workflows/integration_test.yaml000066400000000000000000000024761472325477400246230ustar00rootroot00000000000000name: integration tests on: workflow_dispatch: schedule: - cron: "0 0 * * SUN" # run every Sunday at midnight jobs: test_integration: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Install Python tools uses: BrandonLWhite/pipx-install-action@v0.1.1 - uses: actions/setup-python@v5 with: python-version: 3.8 cache: poetry - name: Install dependencies run: poetry install - name: Test env: INTEGRATION_TEST: 1 run: poe test - name: Check external links in docs run: poe check-docs-links - name: Notify on failure if: ${{ failure() }} env: ZULIP_BOT_CREDENTIALS: ${{ secrets.ZULIP_BOT_CREDENTIALS }} run: | if [ -z "${ZULIP_BOT_CREDENTIALS}" ]; then echo "Skipping notify, ZULIP_BOT_CREDENTIALS is unset" exit 0 fi curl -X POST https://beets.zulipchat.com/api/v1/messages \ -u "${ZULIP_BOT_CREDENTIALS}" \ -d "type=stream" \ -d "to=github" \ -d "subject=${GITHUB_WORKFLOW} - $(date -u +%Y-%m-%d)" \ -d "content=[${GITHUB_WORKFLOW}#${GITHUB_RUN_NUMBER}](${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID}) failed." beetbox-beets-01f1faf/.github/workflows/lint.yml000066400000000000000000000102031472325477400220310ustar00rootroot00000000000000name: Lint check run-name: Lint code on: pull_request: push: branches: - master env: PYTHON_VERSION: 3.8 jobs: changed-files: runs-on: ubuntu-latest name: Get changed files outputs: any_docs_changed: ${{ steps.changed-doc-files.outputs.any_changed }} any_python_changed: ${{ steps.raw-changed-python-files.outputs.any_changed }} changed_doc_files: ${{ steps.changed-doc-files.outputs.all_changed_files }} changed_python_files: ${{ steps.changed-python-files.outputs.all_changed_files }} steps: - uses: actions/checkout@v4 - name: Get changed docs files id: changed-doc-files uses: tj-actions/changed-files@v44 with: files: | docs/** - name: Get changed python files id: raw-changed-python-files uses: tj-actions/changed-files@v44 with: files: | **.py poetry.lock - name: Check changed python files id: changed-python-files env: CHANGED_PYTHON_FILES: ${{ steps.raw-changed-python-files.outputs.all_changed_files }} run: | if [[ " $CHANGED_PYTHON_FILES " == *" poetry.lock "* ]]; then # if poetry.lock is changed, we need to check everything CHANGED_PYTHON_FILES="." fi echo "all_changed_files=$CHANGED_PYTHON_FILES" >> "$GITHUB_OUTPUT" format: if: needs.changed-files.outputs.any_python_changed == 'true' runs-on: ubuntu-latest name: Check formatting needs: changed-files steps: - uses: actions/checkout@v4 - name: Install Python tools uses: BrandonLWhite/pipx-install-action@v0.1.1 - uses: actions/setup-python@v5 with: python-version: ${{ env.PYTHON_VERSION }} cache: poetry - name: Install dependencies run: poetry install --only=lint - name: Check code formatting # the job output will contain colored diffs with what needs adjusting run: poe check-format lint: if: needs.changed-files.outputs.any_python_changed == 'true' runs-on: ubuntu-latest name: Check linting needs: changed-files steps: - uses: actions/checkout@v4 - name: Install Python tools uses: BrandonLWhite/pipx-install-action@v0.1.1 - uses: actions/setup-python@v5 with: python-version: ${{ env.PYTHON_VERSION }} cache: poetry - name: Install dependencies run: poetry install --only=lint - name: Lint code run: poe lint --output-format=github ${{ needs.changed-files.outputs.changed_python_files }} mypy: if: needs.changed-files.outputs.any_python_changed == 'true' runs-on: ubuntu-latest name: Check types with mypy needs: changed-files steps: - uses: actions/checkout@v4 - name: Install Python tools uses: BrandonLWhite/pipx-install-action@v0.1.1 - uses: actions/setup-python@v5 with: python-version: ${{ env.PYTHON_VERSION }} cache: poetry - name: Install dependencies run: poetry install --only=typing - name: Type check code uses: liskin/gh-problem-matcher-wrap@v3 continue-on-error: true with: linters: mypy run: poe check-types --show-column-numbers --no-error-summary ${{ needs.changed-files.outputs.changed_python_files }} docs: if: needs.changed-files.outputs.any_docs_changed == 'true' runs-on: ubuntu-latest name: Check docs needs: changed-files steps: - uses: actions/checkout@v4 - name: Install Python tools uses: BrandonLWhite/pipx-install-action@v0.1.1 - uses: actions/setup-python@v5 with: python-version: ${{ env.PYTHON_VERSION }} cache: poetry - name: Install dependencies run: poetry install --extras=docs - name: Add Sphinx problem matcher run: echo "::add-matcher::.github/sphinx-problem-matcher.json" - name: Build docs run: | poe docs |& tee /tmp/output # fail the job if there are issues grep -q " WARNING:" /tmp/output && exit 1 || exit 0 beetbox-beets-01f1faf/.github/workflows/make_release.yaml000066400000000000000000000076161472325477400236570ustar00rootroot00000000000000name: Make a Beets Release on: workflow_dispatch: inputs: version: description: 'Version of the new release, just as a number with no prepended "v"' required: true env: PYTHON_VERSION: 3.8 NEW_VERSION: ${{ inputs.version }} jobs: increment-version: name: Bump project version and commit it runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Install Python tools uses: BrandonLWhite/pipx-install-action@v0.1.1 - uses: actions/setup-python@v5 with: python-version: ${{ env.PYTHON_VERSION }} cache: poetry - name: Install dependencies run: poetry install --only=release - name: Bump project version id: script run: poe bump "${{ env.NEW_VERSION }}" - uses: EndBug/add-and-commit@v9 name: Commit the changes with: message: "Increment version to ${{ env.NEW_VERSION }}" build: name: Get changelog and build the distribution package runs-on: ubuntu-latest needs: increment-version outputs: changelog: ${{ steps.generate_changelog.outputs.changelog }} steps: - uses: actions/checkout@v4 with: ref: master - name: Install Python tools uses: BrandonLWhite/pipx-install-action@v0.1.1 - uses: actions/setup-python@v5 with: python-version: ${{ env.PYTHON_VERSION }} cache: poetry - name: Install dependencies run: poetry install --with=release --extras=docs - name: Install pandoc run: sudo apt update && sudo apt install pandoc -y - name: Obtain the changelog id: generate_changelog run: | { echo 'changelog<> "$GITHUB_OUTPUT" - name: Build a binary wheel and a source tarball run: poe build - name: Store the distribution packages uses: actions/upload-artifact@v4 with: name: python-package-distributions path: dist/ publish-to-pypi: name: Publish distribution 📦 to PyPI runs-on: ubuntu-latest needs: build environment: name: pypi url: https://pypi.org/p/beets permissions: id-token: write steps: - name: Download all the dists uses: actions/download-artifact@v4 with: name: python-package-distributions path: dist/ - name: Publish distribution 📦 to PyPI uses: pypa/gh-action-pypi-publish@release/v1 make-github-release: name: Create GitHub release runs-on: ubuntu-latest needs: [build, publish-to-pypi] env: CHANGELOG: ${{ needs.build.outputs.changelog }} steps: - uses: actions/checkout@v4 with: ref: master - name: Tag the commit id: tag_version uses: mathieudutour/github-tag-action@v6.2 with: github_token: ${{ secrets.GITHUB_TOKEN }} custom_tag: ${{ env.NEW_VERSION }} - name: Download all the dists uses: actions/download-artifact@v4 with: name: python-package-distributions path: dist/ - name: Create a GitHub release id: make_release uses: ncipollo/release-action@v1 env: NEW_TAG: ${{ steps.tag_version.outputs.new_tag }} with: tag: ${{ env.NEW_TAG }} name: Release ${{ env.NEW_TAG }} body: ${{ env.CHANGELOG }} artifacts: dist/* - name: Send release toot to Fosstodon uses: cbrgm/mastodon-github-action@v2 continue-on-error: true with: access-token: ${{ secrets.MASTODON_ACCESS_TOKEN }} url: ${{ secrets.MASTODON_URL }} message: "Version ${{ steps.tag_version.outputs.new_tag }} of beets has been released! Check out all of the new changes at ${{ steps.create_release.outputs.html_url }}" beetbox-beets-01f1faf/.gitignore000066400000000000000000000022061472325477400167370ustar00rootroot00000000000000# general hidden files/directories .DS_Store .idea # file patterns *~ # Project Specific patterns man # The rest is from https://www.gitignore.io/api/python ### Python ### # Byte-compiled / optimized / DLL files __pycache__/ *.py[cod] *$py.class # C extensions *.so # Distribution / packaging .Python env/ build/ develop-eggs/ dist/ downloads/ eggs/ .eggs/ lib/ lib64 parts/ sdist/ var/ *.egg-info/ .installed.cfg *.egg # PyInstaller # Usually these files are written by a python script from a template # before PyInstaller builds the exe, so as to inject date/other infos into it. *.manifest *.spec # Installer logs pip-log.txt pip-delete-this-directory.txt # Unit test / coverage reports htmlcov/ .tox/ .coverage .coverage.* .cache coverage.xml *,cover .hypothesis/ .reports # Flask stuff: instance/ .webassets-cache # Sphinx documentation docs/_build/ # PyBuilder target/ # pyenv .python-version # dotenv .env # virtualenv env/ venv/ .venv/ ENV/ # Spyder project settings .spyderproject # Rope project settings .ropeproject # PyDev and Eclipse project settings /.project /.pydevproject /.settings .vscode # pyright pyrightconfig.json beetbox-beets-01f1faf/.pre-commit-config.yaml000066400000000000000000000003331472325477400212270ustar00rootroot00000000000000# See https://pre-commit.com for more information # See https://pre-commit.com/hooks.html for more hooks repos: - repo: https://github.com/astral-sh/ruff-pre-commit rev: v0.8.0 hooks: - id: ruff-format beetbox-beets-01f1faf/.readthedocs.yaml000066400000000000000000000003061472325477400201750ustar00rootroot00000000000000version: 2 build: os: ubuntu-22.04 tools: python: "3.11" sphinx: configuration: docs/conf.py python: install: - method: pip path: . extra_requirements: - docs beetbox-beets-01f1faf/CODE_OF_CONDUCT.rst000066400000000000000000000123701472325477400177610ustar00rootroot00000000000000#################################### Contributor Covenant Code of Conduct #################################### Our Pledge ========== We as members, contributors, and leaders pledge to make participation in our community a harassment-free experience for everyone, regardless of age, body size, visible or invisible disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, caste, color, religion, or sexual identity and orientation. We pledge to act and interact in ways that contribute to an open, welcoming, diverse, inclusive, and healthy community. Our Standards ============= Examples of behavior that contributes to a positive environment for our community include: - Demonstrating empathy and kindness toward other people - Being respectful of differing opinions, viewpoints, and experiences - Giving and gracefully accepting constructive feedback - Accepting responsibility and apologizing to those affected by our mistakes, and learning from the experience - Focusing on what is best not just for us as individuals, but for the overall community Examples of unacceptable behavior include: - The use of sexualized language or imagery, and sexual attention or advances of any kind - Trolling, insulting or derogatory comments, and personal or political attacks - Public or private harassment - Publishing others' private information, such as a physical or email address, without their explicit permission - Other conduct which could reasonably be considered inappropriate in a professional setting Enforcement Responsibilities ============================ Community leaders are responsible for clarifying and enforcing our standards of acceptable behavior and will take appropriate and fair corrective action in response to any behavior that they deem inappropriate, threatening, offensive, or harmful. Community leaders have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, and will communicate reasons for moderation decisions when appropriate. Scope ===== This Code of Conduct applies within all community spaces, and also applies when an individual is officially representing the community in public spaces. Examples of representing our community include using an official e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Enforcement =========== Instances of abusive, harassing, or otherwise unacceptable behavior may be reported to the community leaders responsible for enforcement at here on Github. All complaints will be reviewed and investigated promptly and fairly. All community leaders are obligated to respect the privacy and security of the reporter of any incident. Enforcement Guidelines ====================== Community leaders will follow these Community Impact Guidelines in determining the consequences for any action they deem in violation of this Code of Conduct: 1. Correction ------------- **Community Impact**: Use of inappropriate language or other behavior deemed unprofessional or unwelcome in the community. **Consequence**: A private, written warning from community leaders, providing clarity around the nature of the violation and an explanation of why the behavior was inappropriate. A public apology may be requested. 2. Warning ---------- **Community Impact**: A violation through a single incident or series of actions. **Consequence**: A warning with consequences for continued behavior. No interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, for a specified period of time. This includes avoiding interactions in community spaces as well as external channels like social media. Violating these terms may lead to a temporary or permanent ban. 3. Temporary Ban ---------------- **Community Impact**: A serious violation of community standards, including sustained inappropriate behavior. **Consequence**: A temporary ban from any sort of interaction or public communication with the community for a specified period of time. No public or private interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, is allowed during this period. Violating these terms may lead to a permanent ban. 4. Permanent Ban ---------------- **Community Impact**: Demonstrating a pattern of violation of community standards, including sustained inappropriate behavior, harassment of an individual, or aggression toward or disparagement of classes of individuals. **Consequence**: A permanent ban from any sort of public interaction within the community. Attribution =========== This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 2.1, available `here `_. Community Impact Guidelines were inspired by Mozilla's code of conduct enforcement ladder. For answers to common questions about this code of conduct, see the `FAQ `_. Translations are available at `translations `_. beetbox-beets-01f1faf/CONTRIBUTING.rst000066400000000000000000000373451472325477400174240ustar00rootroot00000000000000############ Contributing ############ .. contents:: :depth: 3 Thank you! ========== First off, thank you for considering contributing to beets! It’s people like you that make beets continue to succeed. These guidelines describe how you can help most effectively. By following these guidelines, you can make life easier for the development team as it indicates you respect the maintainers’ time; in return, the maintainers will reciprocate by helping to address your issue, review changes, and finalize pull requests. Types of Contributions ====================== We love to get contributions from our community—you! There are many ways to contribute, whether you’re a programmer or not. The first thing to do, regardless of how you'd like to contribute to the project, is to check out our :doc:`Code of Conduct ` and to keep that in mind while interacting with other contributors and users. Non-Programming --------------- - Promote beets! Help get the word out by telling your friends, writing a blog post, or discussing it on a forum you frequent. - Improve the `documentation`_. It’s incredibly easy to contribute here: just find a page you want to modify and hit the “Edit on GitHub” button in the upper-right. You can automatically send us a pull request for your changes. - GUI design. For the time being, beets is a command-line-only affair. But that’s mostly because we don’t have any great ideas for what a good GUI should look like. If you have those great ideas, please get in touch. - Benchmarks. We’d like to have a consistent way of measuring speed improvements in beets’ tagger and other functionality as well as a way of comparing beets’ performance to other tools. You can help by compiling a library of freely-licensed music files (preferably with incorrect metadata) for testing and measurement. - Think you have a nice config or cool use-case for beets? We’d love to hear about it! Submit a post to our `discussion board `__ under the “Show and Tell” category for a chance to get featured in `the docs `__. - Consider helping out fellow users by by `responding to support requests `__ . Programming ----------- - As a programmer (even if you’re just a beginner!), you have a ton of opportunities to get your feet wet with beets. - For developing plugins, or hacking away at beets, there’s some good information in the `“For Developers” section of the docs `__. .. _development-tools: Development Tools ^^^^^^^^^^^^^^^^^ In order to develop beets, you will need a few tools installed: - `poetry`_ for packaging, virtual environment and dependency management - `poethepoet`_ to run tasks, such as linting, formatting, testing Python community recommends using `pipx`_ to install stand-alone command-line applications such as above. `pipx`_ installs each application in an isolated virtual environment, where its dependencies will not interfere with your system and other CLI tools. If you do not have `pipx`_ installed in your system, follow `pipx-installation-instructions`_ or .. code-block:: sh $ python3 -m pip install --user pipx Install `poetry`_ and `poethepoet`_ using `pipx`_:: $ pipx install poetry poethepoet .. _pipx: https://pipx.pypa.io/stable .. _pipx-installation-instructions: https://pipx.pypa.io/stable/installation/ .. _getting-the-source: Getting the Source ^^^^^^^^^^^^^^^^^^ The easiest way to get started with the latest beets source is to clone the repository and install ``beets`` in a local virtual environment using `poetry`_. This can be done with: .. code-block:: bash $ git clone https://github.com/beetbox/beets.git $ cd beets $ poetry install This will install ``beets`` and all development dependencies into its own virtual environment in your ``$POETRY_CACHE_DIR``. See ``poetry install --help`` for installation options, including installing ``extra`` dependencies for plugins. In order to run something within this virtual environment, start the command with ``poetry run`` to them, for example ``poetry run pytest``. On the other hand, it may get tedious to type ``poetry run`` before every command. Instead, you can activate the virtual environment in your shell with:: $ poetry shell You should see ``(beets-py38)`` prefix in your shell prompt. Now you can run commands directly, for example:: $ (beets-py38) pytest Additionally, `poethepoet`_ task runner assists us with the most common operations. Formatting, linting, testing are defined as ``poe`` tasks in `pyproject.toml`_. Run:: $ poe to see all available tasks. They can be used like this, for example .. code-block:: sh $ poe lint # check code style $ poe format # fix formatting issues $ poe test # run tests # ... fix failing tests $ poe test --lf # re-run failing tests (note the additional pytest option) $ poe check-types --pretty # check types with an extra option for mypy Code Contribution Ideas ^^^^^^^^^^^^^^^^^^^^^^^ - We maintain a set of `issues marked as “bite-sized” `__. These are issues that would serve as a good introduction to the codebase. Claim one and start exploring! - Like testing? Our `test coverage `__ is somewhat low. You can help out by finding low-coverage modules or checking out other `testing-related issues `__. - There are several ways to improve the tests in general (see :ref:`testing` and some places to think about performance optimization (see `Optimization `__). - Not all of our code is up to our coding conventions. In particular, the `library API documentation `__ are currently quite sparse. You can help by adding to the docstrings in the code and to the documentation pages themselves. beets follows `PEP-257 `__ for docstrings and in some places, we also sometimes use `ReST autodoc syntax for Sphinx `__ to, for example, refer to a class name. .. _poethepoet: https://poethepoet.natn.io/index.html .. _poetry: https://python-poetry.org/docs/ Your First Contribution ======================= If this is your first time contributing to an open source project, welcome! If you are confused at all about how to contribute or what to contribute, take a look at `this great tutorial `__, or stop by our `discussion board `__ if you have any questions. We maintain a list of issues we reserved for those new to open source labeled `“first timers only” `__. Since the goal of these issues is to get users comfortable with contributing to an open source project, please do not hesitate to ask any questions. How to Submit Your Work ======================= Do you have a great bug fix, new feature, or documentation expansion you’d like to contribute? Follow these steps to create a GitHub pull request and your code will ship in no time. 1. Fork the beets repository and clone it (see above) to create a workspace. 2. Install pre-commit, following the instructions `here `_. 3. Make your changes. 4. Add tests. If you’ve fixed a bug, write a test to ensure that you’ve actually fixed it. If there’s a new feature or plugin, please contribute tests that show that your code does what it says. 5. Add documentation. If you’ve added a new command flag, for example, find the appropriate page under ``docs/`` where it needs to be listed. 6. Add a changelog entry to ``docs/changelog.rst`` near the top of the document. 7. Run the tests and style checker, see :ref:`testing`. 8. Push to your fork and open a pull request! We’ll be in touch shortly. 9. If you add commits to a pull request, please add a comment or re-request a review after you push them since GitHub doesn’t automatically notify us when commits are added. Remember, code contributions have four parts: the code, the tests, the documentation, and the changelog entry. Thank you for contributing! The Code ======== The documentation has a section on the `library API `__ that serves as an introduction to beets’ design. Coding Conventions ================== General ------- There are a few coding conventions we use in beets: - Whenever you access the library database, do so through the provided Library methods or via a Transaction object. Never call ``lib.conn.*`` directly. For example, do this: .. code-block:: python with g.lib.transaction() as tx: rows = tx.query("SELECT DISTINCT '{0}' FROM '{1}' ORDER BY '{2}'" .format(field, model._table, sort_field)) To fetch Item objects from the database, use lib.items(…) and supply a query as an argument. Resist the urge to write raw SQL for your query. If you must use lower-level queries into the database, do this: .. code-block:: python with lib.transaction() as tx: rows = tx.query("SELECT …") Transaction objects help control concurrent access to the database and assist in debugging conflicting accesses. - Always use the `future imports `__ ``print_function``, ``division``, and ``absolute_import``, but *not* ``unicode_literals``. These help keep your code modern and will help in the eventual move to Python 3. - ``str.format()`` should be used instead of the ``%`` operator - Never ``print`` informational messages; use the `logging `__ module instead. In particular, we have our own logging shim, so you’ll see ``from beets import logging`` in most files. - The loggers use `str.format `__-style logging instead of ``%``-style, so you can type ``log.debug("{0}", obj)`` to do your formatting. - Exception handlers must use ``except A as B:`` instead of ``except A, B:``. Style ----- We use `ruff`_ to format and lint the codebase. Run ``poe check-format`` and ``poe lint`` to check your code for style and linting errors. Running ``poe format`` will automatically format your code according to the specifications required by the project. .. _ruff: https://docs.astral.sh/ruff/ Handling Paths -------------- A great deal of convention deals with the handling of **paths**. Paths are stored internally—in the database, for instance—as byte strings (i.e., ``bytes`` instead of ``str`` in Python 3). This is because POSIX operating systems’ path names are only reliably usable as byte strings—operating systems typically recommend but do not require that filenames use a given encoding, so violations of any reported encoding are inevitable. On Windows, the strings are always encoded with UTF-8; on Unix, the encoding is controlled by the filesystem. Here are some guidelines to follow: - If you have a Unicode path or you’re not sure whether something is Unicode or not, pass it through ``bytestring_path`` function in the ``beets.util`` module to convert it to bytes. - Pass every path name through the ``syspath`` function (also in ``beets.util``) before sending it to any *operating system* file operation (``open``, for example). This is necessary to use long filenames (which, maddeningly, must be Unicode) on Windows. This allows us to consistently store bytes in the database but use the native encoding rule on both POSIX and Windows. - Similarly, the ``displayable_path`` utility function converts bytestring paths to a Unicode string for displaying to the user. Every time you want to print out a string to the terminal or log it with the ``logging`` module, feed it through this function. Editor Settings --------------- Personally, I work on beets with `vim`_. Here are some ``.vimrc`` lines that might help with PEP 8-compliant Python coding:: filetype indent on autocmd FileType python setlocal shiftwidth=4 tabstop=4 softtabstop=4 expandtab shiftround autoindent Consider installing `this alternative Python indentation plugin `__. I also like `neomake `__ with its flake8 checker. .. _testing: Testing ======= Running the Tests ----------------- Use ``poe`` to run tests:: $ poe test [pytest options] You can disable a hand-selected set of "slow" tests by setting the environment variable ``SKIP_SLOW_TESTS``, for example:: $ SKIP_SLOW_TESTS=1 poe test Coverage ^^^^^^^^ The ``test`` command does not include coverage as it slows down testing. In order to measure it, use the ``test-with-coverage`` task $ poe test-with-coverage [pytest options] You are welcome to explore coverage by opening the HTML report in ``.reports/html/index.html``. Note that for each covered line the report shows **which tests cover it** (expand the list on the right-hand side of the affected line). You can find project coverage status on `Codecov`_. Red Flags ^^^^^^^^^ The `pytest-random`_ plugin makes it easy to randomize the order of tests. ``poe test --random`` will occasionally turn up failing tests that reveal ordering dependencies—which are bad news! Test Dependencies ^^^^^^^^^^^^^^^^^ The tests have a few more dependencies than beets itself. (The additional dependencies consist of testing utilities and dependencies of non-default plugins exercised by the test suite.) The dependencies are listed under the ``tool.poetry.group.test.dependencies`` section in `pyproject.toml`_. Writing Tests ------------- Writing tests is done by adding or modifying files in folder `test`_. Take a look at `https://github.com/beetbox/beets/blob/master/test/test_template.py#L224`_ to get a basic view on how tests are written. Since we are currently migrating the tests from `unittest`_ to `pytest`_, new tests should be written using `pytest`_. Contributions migrating existing tests are welcome! External API requests under test should be mocked with `requests_mock`_, However, we still want to know whether external APIs are up and that they return expected responses, therefore we test them weekly with our `integration test`_ suite. In order to add such a test, mark your test with the ``integration_test`` marker .. code-block:: python @pytest.mark.integration_test def test_external_api_call(): ... This way, the test will be run only in the integration test suite. .. _Codecov: https://codecov.io/github/beetbox/beets .. _pytest-random: https://github.com/klrmn/pytest-random .. _pytest: https://docs.pytest.org/en/stable/ .. _pyproject.toml: https://github.com/beetbox/beets/tree/master/pyproject.toml .. _test: https://github.com/beetbox/beets/tree/master/test .. _`https://github.com/beetbox/beets/blob/master/test/test_template.py#L224`: https://github.com/beetbox/beets/blob/master/test/test_template.py#L224 .. _unittest: https://docs.python.org/3/library/unittest.html .. _integration test: https://github.com/beetbox/beets/actions?query=workflow%3A%22integration+tests%22 .. _requests-mock: https://requests-mock.readthedocs.io/en/latest/response.html .. _documentation: https://beets.readthedocs.io/en/stable/ .. _vim: https://www.vim.org/ beetbox-beets-01f1faf/LICENSE000066400000000000000000000020701472325477400157530ustar00rootroot00000000000000The MIT License Copyright (c) 2010-2016 Adrian Sampson Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. beetbox-beets-01f1faf/MANIFEST.in000066400000000000000000000015311472325477400165050ustar00rootroot00000000000000# Include tests (but avoid including *.pyc, etc.) prune test recursive-include test/rsrc * recursive-exclude test/rsrc *.pyc recursive-exclude test/rsrc *.pyo include test/*.py # Include relevant text files. include LICENSE README.rst # And generated manpages. include man/beet.1 include man/beetsconfig.5 # Include the Sphinx documentation. recursive-include docs *.rst *.py Makefile *.png prune docs/_build # Resources for web plugin. recursive-include beetsplug/web/templates * recursive-include beetsplug/web/static * # And for the lastgenre plugin. include beetsplug/lastgenre/genres.txt include beetsplug/lastgenre/genres-tree.yaml # Exclude junk. global-exclude .DS_Store # Include default config include beets/config_default.yaml # Shell completion template include beets/ui/completion_base.sh # Include extra bits recursive-include extra * beetbox-beets-01f1faf/README.rst000066400000000000000000000122771472325477400164470ustar00rootroot00000000000000.. image:: https://img.shields.io/pypi/v/beets.svg :target: https://pypi.python.org/pypi/beets .. image:: https://img.shields.io/codecov/c/github/beetbox/beets.svg :target: https://codecov.io/github/beetbox/beets .. image:: https://github.com/beetbox/beets/workflows/ci/badge.svg?branch=master :target: https://github.com/beetbox/beets/actions .. image:: https://repology.org/badge/tiny-repos/beets.svg :target: https://repology.org/project/beets/versions beets ===== Beets is the media library management system for obsessive music geeks. The purpose of beets is to get your music collection right once and for all. It catalogs your collection, automatically improving its metadata as it goes. It then provides a bouquet of tools for manipulating and accessing your music. Here's an example of beets' brainy tag corrector doing its thing:: $ beet import ~/music/ladytron Tagging: Ladytron - Witching Hour (Similarity: 98.4%) * Last One Standing -> The Last One Standing * Beauty -> Beauty*2 * White Light Generation -> Whitelightgenerator * All the Way -> All the Way... Because beets is designed as a library, it can do almost anything you can imagine for your music collection. Via `plugins`_, beets becomes a panacea: - Fetch or calculate all the metadata you could possibly need: `album art`_, `lyrics`_, `genres`_, `tempos`_, `ReplayGain`_ levels, or `acoustic fingerprints`_. - Get metadata from `MusicBrainz`_, `Discogs`_, and `Beatport`_. Or guess metadata using songs' filenames or their acoustic fingerprints. - `Transcode audio`_ to any format you like. - Check your library for `duplicate tracks and albums`_ or for `albums that are missing tracks`_. - Clean up crufty tags left behind by other, less-awesome tools. - Embed and extract album art from files' metadata. - Browse your music library graphically through a Web browser and play it in any browser that supports `HTML5 Audio`_. - Analyze music files' metadata from the command line. - Listen to your library with a music player that speaks the `MPD`_ protocol and works with a staggering variety of interfaces. If beets doesn't do what you want yet, `writing your own plugin`_ is shockingly simple if you know a little Python. .. _plugins: https://beets.readthedocs.org/page/plugins/ .. _MPD: https://www.musicpd.org/ .. _MusicBrainz music collection: https://musicbrainz.org/doc/Collections/ .. _writing your own plugin: https://beets.readthedocs.org/page/dev/plugins.html .. _HTML5 Audio: https://html.spec.whatwg.org/multipage/media.html#the-audio-element .. _albums that are missing tracks: https://beets.readthedocs.org/page/plugins/missing.html .. _duplicate tracks and albums: https://beets.readthedocs.org/page/plugins/duplicates.html .. _Transcode audio: https://beets.readthedocs.org/page/plugins/convert.html .. _Discogs: https://www.discogs.com/ .. _acoustic fingerprints: https://beets.readthedocs.org/page/plugins/chroma.html .. _ReplayGain: https://beets.readthedocs.org/page/plugins/replaygain.html .. _tempos: https://beets.readthedocs.org/page/plugins/acousticbrainz.html .. _genres: https://beets.readthedocs.org/page/plugins/lastgenre.html .. _album art: https://beets.readthedocs.org/page/plugins/fetchart.html .. _lyrics: https://beets.readthedocs.org/page/plugins/lyrics.html .. _MusicBrainz: https://musicbrainz.org/ .. _Beatport: https://www.beatport.com Install ------- You can install beets by typing ``pip install beets`` or directly from Github (see details `here`_). Beets has also been packaged in the `software repositories`_ of several distributions. Check out the `Getting Started`_ guide for more information. .. _here: https://beets.readthedocs.io/en/latest/faq.html#run-the-latest-source-version-of-beets .. _Getting Started: https://beets.readthedocs.org/page/guides/main.html .. _software repositories: https://repology.org/project/beets/versions Contribute ---------- Thank you for considering contributing to ``beets``! Whether you're a programmer or not, you should be able to find all the info you need at `CONTRIBUTING.rst`_. .. _CONTRIBUTING.rst: https://github.com/beetbox/beets/blob/master/CONTRIBUTING.rst Read More --------- Learn more about beets at `its Web site`_. Follow `@b33ts`_ on Mastodon for news and updates. .. _its Web site: https://beets.io/ .. _@b33ts: https://fosstodon.org/@beets Contact ------- * Encountered a bug you'd like to report? Check out our `issue tracker`_! * If your issue hasn't already been reported, please `open a new ticket`_ and we'll be in touch with you shortly. * If you'd like to vote on a feature/bug, simply give a :+1: on issues you'd like to see prioritized over others. * Need help/support, would like to start a discussion, have an idea for a new feature, or would just like to introduce yourself to the team? Check out `GitHub Discussions`_! .. _GitHub Discussions: https://github.com/beetbox/beets/discussions .. _issue tracker: https://github.com/beetbox/beets/issues .. _open a new ticket: https://github.com/beetbox/beets/issues/new/choose Authors ------- Beets is by `Adrian Sampson`_ with a supporting cast of thousands. .. _Adrian Sampson: https://www.cs.cornell.edu/~asampson/ beetbox-beets-01f1faf/README_kr.rst000066400000000000000000000113161472325477400171340ustar00rootroot00000000000000.. image:: https://img.shields.io/pypi/v/beets.svg :target: https://pypi.python.org/pypi/beets .. image:: https://img.shields.io/codecov/c/github/beetbox/beets.svg :target: https://codecov.io/github/beetbox/beets .. image:: https://travis-ci.org/beetbox/beets.svg?branch=master :target: https://travis-ci.org/beetbox/beets beets ===== Beets는 ę°•ë°•ě ěť¸ 음악을 듣는 사람들을 위한 미디어 라이브러리 관리 시스템이다. Beetsěť ëŞ©ě ěť€ 음악들을 한ë˛ě— 다 받는 ę˛ěť´ë‹¤. 음악들을 ěą´í로그화 í•ęł , ěžëŹ™ěśĽëˇś ë©”í€ ëŤ°ěť´í„°ëĄĽ 개선한다. 그리고 ěťŚě•…ě— ě ‘ę·Ľí•ęł  조작할 ě ěžëŠ” 도구들을 제공한다. 다음은 Beetsěť brainy tag correctorę°€ 한 ěťĽěť ě시이다. $ beet import ~/music/ladytron Tagging: Ladytron - Witching Hour (Similarity: 98.4%) * Last One Standing -> The Last One Standing * Beauty -> Beauty*2 * White Light Generation -> Whitelightgenerator * All the Way -> All the Way... Beets는 라이브러리로 ë””ěžěť¸ ëě—기 때문ě—, 당신이 ěťŚě•…ë“¤ě— ëŚ€í•´ ěěí•는 모든 ę˛ěť„ í•  ě ěžë‹¤. `plugins`_ ěť„ 통해서 모든 ę˛ěť„ í•  ě ěžëŠ” ę˛ěť´ë‹¤! - í•„ěš”í•는 ë©”í€ ëŤ°ěť´í„°ëĄĽ 계산í•ę±°ë‚ íŚ¨ěą í•  때: `album art`_, `lyrics`_, `genres`_, `tempos`_, `ReplayGain`_ levels, or `acoustic fingerprints`_. - `MusicBrainz`_, `Discogs`_,`Beatport`_로부터 ë©”í€ëŤ°ěť´í„°ëĄĽ 가져ě¤ę±°ë‚, ë…¸ëž ě śëŞ©ěť´ë‚ ěťŚí–Ą 특징으로 ë©”í€ëŤ°ěť´í„°ëĄĽ 추측한다 - `Transcode audio`_ 당신이 좋아í•는 ě–´ë–¤ 포맷으로든 변경한다. - ë‹ąě‹ ěť ëťĽěť´ë¸Śëź¬ë¦¬ě—서 `duplicate tracks and albums`_ ěť´ë‚ `albums that are missing tracks`_ 를 검사한다. - 남이 남기거ë‚, 좋지 않은 도구로 남긴 잡다한 íśę·¸ë“¤ěť„ 지운다. - íŚŚěťĽěť ë©”í€ëŤ°ěť´í„°ě—서 앨범 아트를 ě‚˝ěž…ěť´ë‚ ě¶”ě¶śí•śë‹¤. - ë‹ąě‹ ěť ěťŚě•…ë“¤ěť„ `HTML5 Audio`_ 를 ě§€ě›í•는 ě–´ë–¤ 브라우저든 재ěťí•  ě ěžęł , 웹 ë¸ŚëťĽěš°ě €ě— í‘śě‹ś í•  ě ěžë‹¤. - 명령어로부터 음악 íŚŚěťĽěť ë©”í€ëŤ°ěť´í„°ëĄĽ 분석할 ě ěžë‹¤. - `MPD`_ 프로토콜을 사용í•ě—¬ 음악 플ë ěť´ě–´ëˇś 음악을 들으면, 엄청ë‚게 다양한 인터íŽěť´ěŠ¤ëˇś 작동한다. ë§Śě•˝ Beetsě— ë‹ąě‹ ěť´ ě›í•는게 ě•„ě§ ě—†ë‹¤ë©´, 당신이 pythoněť„ ě•다면 `writing your own plugin`_ _은 놀라울정도로 간단í•다. .. _plugins: https://beets.readthedocs.org/page/plugins/ .. _MPD: https://www.musicpd.org/ .. _MusicBrainz music collection: https://musicbrainz.org/doc/Collections/ .. _writing your own plugin: https://beets.readthedocs.org/page/dev/plugins.html .. _HTML5 Audio: https://html.spec.whatwg.org/multipage/media.html#the-audio-element .. _albums that are missing tracks: https://beets.readthedocs.org/page/plugins/missing.html .. _duplicate tracks and albums: https://beets.readthedocs.org/page/plugins/duplicates.html .. _Transcode audio: https://beets.readthedocs.org/page/plugins/convert.html .. _Discogs: https://www.discogs.com/ .. _acoustic fingerprints: https://beets.readthedocs.org/page/plugins/chroma.html .. _ReplayGain: https://beets.readthedocs.org/page/plugins/replaygain.html .. _tempos: https://beets.readthedocs.org/page/plugins/acousticbrainz.html .. _genres: https://beets.readthedocs.org/page/plugins/lastgenre.html .. _album art: https://beets.readthedocs.org/page/plugins/fetchart.html .. _lyrics: https://beets.readthedocs.org/page/plugins/lyrics.html .. _MusicBrainz: https://musicbrainz.org/ .. _Beatport: https://www.beatport.com ě„¤ěą ------- 당신은 ``pip install beets`` ěť„ 사용해서 Beets를 설ěąí•  ě ěžë‹¤. 그리고 `Getting Started`_ 가이드를 확인할 ě ěžë‹¤. .. _Getting Started: https://beets.readthedocs.org/page/guides/main.html ě»¨íŠ¸ë¦¬ë·°ě… ---------- 어떻게 도우려는지 알고싶다면 `Hacking`_ 위키íŽěť´ě§€ëĄĽ 확인í•라. 당신은 docs ě•ě— `For Developers`_ ě—도 관심이 ěžěť„ě ěžë‹¤. .. _Hacking: https://github.com/beetbox/beets/wiki/Hacking .. _For Developers: https://beets.readthedocs.io/en/stable/dev/ Read More --------- `its Web site`_ ě—서 Beetsě— ëŚ€í•´ ěˇ°ę¸ ëŤ” 알아볼 ě ěžë‹¤. 트위터ě—서 `@b33ts`_ 를 팔로우í•ë©´ ě 소식을 볼 ě ěžë‹¤. .. _its Web site: https://beets.io/ .. _@b33ts: https://twitter.com/b33ts/ ě €ěžë“¤ ------- `Adrian Sampson`_ 와 많은 ě‚¬ëžŚë“¤ěť ě§€ě§€ëĄĽ 받아 Beets를 만들ě—다. 돕고 싶다면 `forum`_.를 방문í•ë©´ ëśë‹¤. .. _forum: https://github.com/beetbox/beets/discussions/ .. _Adrian Sampson: https://www.cs.cornell.edu/~asampson/ beetbox-beets-01f1faf/SECURITY.md000066400000000000000000000004441472325477400165420ustar00rootroot00000000000000# Security Policy ## Supported Versions We currently support only the latest release of beets. ## Reporting a Vulnerability To report a security vulnerability, please send email to [our Zulip team][z]. [z]: mailto:email.218c36e48d78cf125c0a6219a6c2a417.show-sender@streams.zulipchat.com beetbox-beets-01f1faf/beets/000077500000000000000000000000001472325477400160515ustar00rootroot00000000000000beetbox-beets-01f1faf/beets/__init__.py000066400000000000000000000025141472325477400201640ustar00rootroot00000000000000# This file is part of beets. # Copyright 2016, Adrian Sampson. # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the # "Software"), to deal in the Software without restriction, including # without limitation the rights to use, copy, modify, merge, publish, # distribute, sublicense, and/or sell copies of the Software, and to # permit persons to whom the Software is furnished to do so, subject to # the following conditions: # # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. from sys import stderr import confuse __version__ = "2.1.0" __author__ = "Adrian Sampson " class IncludeLazyConfig(confuse.LazyConfig): """A version of Confuse's LazyConfig that also merges in data from YAML files specified in an `include` setting. """ def read(self, user=True, defaults=True): super().read(user, defaults) try: for view in self["include"]: self.set_file(view.as_filename()) except confuse.NotFoundError: pass except confuse.ConfigReadError as err: stderr.write("configuration `import` failed: {}".format(err.reason)) config = IncludeLazyConfig("beets", __name__) beetbox-beets-01f1faf/beets/__main__.py000066400000000000000000000014711472325477400201460ustar00rootroot00000000000000# This file is part of beets. # Copyright 2017, Adrian Sampson. # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the # "Software"), to deal in the Software without restriction, including # without limitation the rights to use, copy, modify, merge, publish, # distribute, sublicense, and/or sell copies of the Software, and to # permit persons to whom the Software is furnished to do so, subject to # the following conditions: # # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. """The __main__ module lets you run the beets CLI interface by typing `python -m beets`. """ import sys from .ui import main if __name__ == "__main__": main(sys.argv[1:]) beetbox-beets-01f1faf/beets/art.py000066400000000000000000000136121472325477400172140ustar00rootroot00000000000000# This file is part of beets. # Copyright 2016, Adrian Sampson. # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the # "Software"), to deal in the Software without restriction, including # without limitation the rights to use, copy, modify, merge, publish, # distribute, sublicense, and/or sell copies of the Software, and to # permit persons to whom the Software is furnished to do so, subject to # the following conditions: # # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. """High-level utilities for manipulating image files associated with music and items' embedded album art. """ import os from tempfile import NamedTemporaryFile import mediafile from beets.util import bytestring_path, displayable_path, syspath from beets.util.artresizer import ArtResizer def mediafile_image(image_path, maxwidth=None): """Return a `mediafile.Image` object for the path.""" with open(syspath(image_path), "rb") as f: data = f.read() return mediafile.Image(data, type=mediafile.ImageType.front) def get_art(log, item): # Extract the art. try: mf = mediafile.MediaFile(syspath(item.path)) except mediafile.UnreadableFileError as exc: log.warning( "Could not extract art from {0}: {1}", displayable_path(item.path), exc, ) return return mf.art def embed_item( log, item, imagepath, maxwidth=None, itempath=None, compare_threshold=0, ifempty=False, as_album=False, id3v23=None, quality=0, ): """Embed an image into the item's media file.""" # Conditions. if compare_threshold: is_similar = check_art_similarity( log, item, imagepath, compare_threshold ) if is_similar is None: log.warning("Error while checking art similarity; skipping.") return elif not is_similar: log.info("Image not similar; skipping.") return if ifempty and get_art(log, item): log.info("media file already contained art") return # Filters. if maxwidth and not as_album: imagepath = resize_image(log, imagepath, maxwidth, quality) # Get the `Image` object from the file. try: log.debug("embedding {0}", displayable_path(imagepath)) image = mediafile_image(imagepath, maxwidth) except OSError as exc: log.warning("could not read image file: {0}", exc) return # Make sure the image kind is safe (some formats only support PNG # and JPEG). if image.mime_type not in ("image/jpeg", "image/png"): log.info("not embedding image of unsupported type: {}", image.mime_type) return item.try_write(path=itempath, tags={"images": [image]}, id3v23=id3v23) def embed_album( log, album, maxwidth=None, quiet=False, compare_threshold=0, ifempty=False, quality=0, ): """Embed album art into all of the album's items.""" imagepath = album.artpath if not imagepath: log.info("No album art present for {0}", album) return if not os.path.isfile(syspath(imagepath)): log.info( "Album art not found at {0} for {1}", displayable_path(imagepath), album, ) return if maxwidth: imagepath = resize_image(log, imagepath, maxwidth, quality) log.info("Embedding album art into {0}", album) for item in album.items(): embed_item( log, item, imagepath, maxwidth, None, compare_threshold, ifempty, as_album=True, quality=quality, ) def resize_image(log, imagepath, maxwidth, quality): """Returns path to an image resized to maxwidth and encoded with the specified quality level. """ log.debug( "Resizing album art to {0} pixels wide and encoding at quality \ level {1}", maxwidth, quality, ) imagepath = ArtResizer.shared.resize( maxwidth, syspath(imagepath), quality=quality ) return imagepath def check_art_similarity( log, item, imagepath, compare_threshold, artresizer=None, ): """A boolean indicating if an image is similar to embedded item art. If no embedded art exists, always return `True`. If the comparison fails for some reason, the return value is `None`. This must only be called if `ArtResizer.shared.can_compare` is `True`. """ with NamedTemporaryFile(delete=True) as f: art = extract(log, f.name, item) if not art: return True if artresizer is None: artresizer = ArtResizer.shared return artresizer.compare(art, imagepath, compare_threshold) def extract(log, outpath, item): art = get_art(log, item) outpath = bytestring_path(outpath) if not art: log.info("No album art present in {0}, skipping.", item) return # Add an extension to the filename. ext = mediafile.image_extension(art) if not ext: log.warning("Unknown image type in {0}.", displayable_path(item.path)) return outpath += bytestring_path("." + ext) log.info( "Extracting album art from: {0} to: {1}", item, displayable_path(outpath), ) with open(syspath(outpath), "wb") as f: f.write(art) return outpath def extract_first(log, outpath, items): for item in items: real_path = extract(log, outpath, item) if real_path: return real_path def clear(log, lib, query): items = lib.items(query) log.info("Clearing album art from {0} items", len(items)) for item in items: log.debug("Clearing art for {0}", item) item.try_write(tags={"images": None}) beetbox-beets-01f1faf/beets/autotag/000077500000000000000000000000001472325477400175155ustar00rootroot00000000000000beetbox-beets-01f1faf/beets/autotag/__init__.py000066400000000000000000000205071472325477400216320ustar00rootroot00000000000000# This file is part of beets. # Copyright 2016, Adrian Sampson. # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the # "Software"), to deal in the Software without restriction, including # without limitation the rights to use, copy, modify, merge, publish, # distribute, sublicense, and/or sell copies of the Software, and to # permit persons to whom the Software is furnished to do so, subject to # the following conditions: # # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. """Facilities for automatically determining files' correct metadata.""" from typing import Mapping, Sequence, Union from beets import config, logging from beets.library import Album, Item # Parts of external interface. from .hooks import AlbumInfo, AlbumMatch, Distance, TrackInfo, TrackMatch from .match import ( Proposal, Recommendation, current_metadata, tag_album, tag_item, ) __all__ = [ "AlbumInfo", "AlbumMatch", "Distance", "TrackInfo", "TrackMatch", "Proposal", "Recommendation", "apply_album_metadata", "apply_item_metadata", "apply_metadata", "current_metadata", "tag_album", "tag_item", ] # Global logger. log = logging.getLogger("beets") # Metadata fields that are already hardcoded, or where the tag name changes. SPECIAL_FIELDS = { "album": ( "va", "releasegroup_id", "artist_id", "artists_ids", "album_id", "mediums", "tracks", "year", "month", "day", "artist", "artists", "artist_credit", "artists_credit", "artist_sort", "artists_sort", "data_url", ), "track": ( "track_alt", "artist_id", "artists_ids", "release_track_id", "medium", "index", "medium_index", "title", "artist_credit", "artists_credit", "artist_sort", "artists_sort", "artist", "artists", "track_id", "medium_total", "data_url", "length", ), } # Additional utilities for the main interface. def _apply_metadata( info: Union[AlbumInfo, TrackInfo], db_obj: Union[Album, Item], nullable_fields: Sequence[str] = [], ): """Set the db_obj's metadata to match the info.""" special_fields = SPECIAL_FIELDS[ "album" if isinstance(info, AlbumInfo) else "track" ] for field, value in info.items(): # We only overwrite fields that are not already hardcoded. if field in special_fields: continue # Don't overwrite fields with empty values unless the # field is explicitly allowed to be overwritten. if value is None and field not in nullable_fields: continue db_obj[field] = value def apply_item_metadata(item: Item, track_info: TrackInfo): """Set an item's metadata from its matched TrackInfo object.""" item.artist = track_info.artist item.artists = track_info.artists item.artist_sort = track_info.artist_sort item.artists_sort = track_info.artists_sort item.artist_credit = track_info.artist_credit item.artists_credit = track_info.artists_credit item.title = track_info.title item.mb_trackid = track_info.track_id item.mb_releasetrackid = track_info.release_track_id if track_info.artist_id: item.mb_artistid = track_info.artist_id if track_info.artists_ids: item.mb_artistids = track_info.artists_ids _apply_metadata(track_info, item) # At the moment, the other metadata is left intact (including album # and track number). Perhaps these should be emptied? def apply_album_metadata(album_info: AlbumInfo, album: Album): """Set the album's metadata to match the AlbumInfo object.""" _apply_metadata(album_info, album) def apply_metadata(album_info: AlbumInfo, mapping: Mapping[Item, TrackInfo]): """Set the items' metadata to match an AlbumInfo object using a mapping from Items to TrackInfo objects. """ for item, track_info in mapping.items(): # Artist or artist credit. if config["artist_credit"]: item.artist = ( track_info.artist_credit or track_info.artist or album_info.artist_credit or album_info.artist ) item.artists = ( track_info.artists_credit or track_info.artists or album_info.artists_credit or album_info.artists ) item.albumartist = album_info.artist_credit or album_info.artist item.albumartists = album_info.artists_credit or album_info.artists else: item.artist = track_info.artist or album_info.artist item.artists = track_info.artists or album_info.artists item.albumartist = album_info.artist item.albumartists = album_info.artists # Album. item.album = album_info.album # Artist sort and credit names. item.artist_sort = track_info.artist_sort or album_info.artist_sort item.artists_sort = track_info.artists_sort or album_info.artists_sort item.artist_credit = ( track_info.artist_credit or album_info.artist_credit ) item.artists_credit = ( track_info.artists_credit or album_info.artists_credit ) item.albumartist_sort = album_info.artist_sort item.albumartists_sort = album_info.artists_sort item.albumartist_credit = album_info.artist_credit item.albumartists_credit = album_info.artists_credit # Release date. for prefix in "", "original_": if config["original_date"] and not prefix: # Ignore specific release date. continue for suffix in "year", "month", "day": key = prefix + suffix value = getattr(album_info, key) or 0 # If we don't even have a year, apply nothing. if suffix == "year" and not value: break # Otherwise, set the fetched value (or 0 for the month # and day if not available). item[key] = value # If we're using original release date for both fields, # also set item.year = info.original_year, etc. if config["original_date"]: item[suffix] = value # Title. item.title = track_info.title if config["per_disc_numbering"]: # We want to let the track number be zero, but if the medium index # is not provided we need to fall back to the overall index. if track_info.medium_index is not None: item.track = track_info.medium_index else: item.track = track_info.index item.tracktotal = track_info.medium_total or len(album_info.tracks) else: item.track = track_info.index item.tracktotal = len(album_info.tracks) # Disc and disc count. item.disc = track_info.medium item.disctotal = album_info.mediums # MusicBrainz IDs. item.mb_trackid = track_info.track_id item.mb_releasetrackid = track_info.release_track_id item.mb_albumid = album_info.album_id if track_info.artist_id: item.mb_artistid = track_info.artist_id else: item.mb_artistid = album_info.artist_id if track_info.artists_ids: item.mb_artistids = track_info.artists_ids else: item.mb_artistids = album_info.artists_ids item.mb_albumartistid = album_info.artist_id item.mb_albumartistids = album_info.artists_ids item.mb_releasegroupid = album_info.releasegroup_id # Compilation flag. item.comp = album_info.va # Track alt. item.track_alt = track_info.track_alt _apply_metadata( album_info, item, nullable_fields=config["overwrite_null"]["album"].as_str_seq(), ) _apply_metadata( track_info, item, nullable_fields=config["overwrite_null"]["track"].as_str_seq(), ) beetbox-beets-01f1faf/beets/autotag/hooks.py000066400000000000000000000556451472325477400212310ustar00rootroot00000000000000# This file is part of beets. # Copyright 2016, Adrian Sampson. # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the # "Software"), to deal in the Software without restriction, including # without limitation the rights to use, copy, modify, merge, publish, # distribute, sublicense, and/or sell copies of the Software, and to # permit persons to whom the Software is furnished to do so, subject to # the following conditions: # # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. """Glue between metadata sources and the matching logic.""" from __future__ import annotations import re from functools import total_ordering from typing import ( Any, Callable, Dict, Iterable, Iterator, List, NamedTuple, Optional, Tuple, TypeVar, Union, cast, ) from jellyfish import levenshtein_distance from unidecode import unidecode from beets import config, logging, plugins from beets.autotag import mb from beets.library import Item from beets.util import as_string, cached_classproperty log = logging.getLogger("beets") V = TypeVar("V") # Classes used to represent candidate options. class AttrDict(Dict[str, V]): """A dictionary that supports attribute ("dot") access, so `d.field` is equivalent to `d['field']`. """ def __getattr__(self, attr: str) -> V: if attr in self: return self[attr] else: raise AttributeError def __setattr__(self, key: str, value: V): self.__setitem__(key, value) def __hash__(self): return id(self) class AlbumInfo(AttrDict): """Describes a canonical release that may be used to match a release in the library. Consists of these data members: - ``album``: the release title - ``album_id``: MusicBrainz ID; UUID fragment only - ``artist``: name of the release's primary artist - ``artist_id`` - ``tracks``: list of TrackInfo objects making up the release ``mediums`` along with the fields up through ``tracks`` are required. The others are optional and may be None. """ # TYPING: are all of these correct? I've assumed optional strings def __init__( self, tracks: List[TrackInfo], album: Optional[str] = None, album_id: Optional[str] = None, artist: Optional[str] = None, artist_id: Optional[str] = None, artists: Optional[List[str]] = None, artists_ids: Optional[List[str]] = None, asin: Optional[str] = None, albumtype: Optional[str] = None, albumtypes: Optional[List[str]] = None, va: bool = False, year: Optional[int] = None, month: Optional[int] = None, day: Optional[int] = None, label: Optional[str] = None, barcode: Optional[str] = None, mediums: Optional[int] = None, artist_sort: Optional[str] = None, artists_sort: Optional[List[str]] = None, releasegroup_id: Optional[str] = None, release_group_title: Optional[str] = None, catalognum: Optional[str] = None, script: Optional[str] = None, language: Optional[str] = None, country: Optional[str] = None, style: Optional[str] = None, genre: Optional[str] = None, albumstatus: Optional[str] = None, media: Optional[str] = None, albumdisambig: Optional[str] = None, releasegroupdisambig: Optional[str] = None, artist_credit: Optional[str] = None, artists_credit: Optional[List[str]] = None, original_year: Optional[int] = None, original_month: Optional[int] = None, original_day: Optional[int] = None, data_source: Optional[str] = None, data_url: Optional[str] = None, discogs_albumid: Optional[str] = None, discogs_labelid: Optional[str] = None, discogs_artistid: Optional[str] = None, **kwargs, ): self.album = album self.album_id = album_id self.artist = artist self.artist_id = artist_id self.artists = artists or [] self.artists_ids = artists_ids or [] self.tracks = tracks self.asin = asin self.albumtype = albumtype self.albumtypes = albumtypes or [] self.va = va self.year = year self.month = month self.day = day self.label = label self.barcode = barcode self.mediums = mediums self.artist_sort = artist_sort self.artists_sort = artists_sort or [] self.releasegroup_id = releasegroup_id self.release_group_title = release_group_title self.catalognum = catalognum self.script = script self.language = language self.country = country self.style = style self.genre = genre self.albumstatus = albumstatus self.media = media self.albumdisambig = albumdisambig self.releasegroupdisambig = releasegroupdisambig self.artist_credit = artist_credit self.artists_credit = artists_credit or [] self.original_year = original_year self.original_month = original_month self.original_day = original_day self.data_source = data_source self.data_url = data_url self.discogs_albumid = discogs_albumid self.discogs_labelid = discogs_labelid self.discogs_artistid = discogs_artistid self.update(kwargs) def copy(self) -> AlbumInfo: dupe = AlbumInfo([]) dupe.update(self) dupe.tracks = [track.copy() for track in self.tracks] return dupe class TrackInfo(AttrDict): """Describes a canonical track present on a release. Appears as part of an AlbumInfo's ``tracks`` list. Consists of these data members: - ``title``: name of the track - ``track_id``: MusicBrainz ID; UUID fragment only Only ``title`` and ``track_id`` are required. The rest of the fields may be None. The indices ``index``, ``medium``, and ``medium_index`` are all 1-based. """ # TYPING: are all of these correct? I've assumed optional strings def __init__( self, title: Optional[str] = None, track_id: Optional[str] = None, release_track_id: Optional[str] = None, artist: Optional[str] = None, artist_id: Optional[str] = None, artists: Optional[List[str]] = None, artists_ids: Optional[List[str]] = None, length: Optional[float] = None, index: Optional[int] = None, medium: Optional[int] = None, medium_index: Optional[int] = None, medium_total: Optional[int] = None, artist_sort: Optional[str] = None, artists_sort: Optional[List[str]] = None, disctitle: Optional[str] = None, artist_credit: Optional[str] = None, artists_credit: Optional[List[str]] = None, data_source: Optional[str] = None, data_url: Optional[str] = None, media: Optional[str] = None, lyricist: Optional[str] = None, composer: Optional[str] = None, composer_sort: Optional[str] = None, arranger: Optional[str] = None, track_alt: Optional[str] = None, work: Optional[str] = None, mb_workid: Optional[str] = None, work_disambig: Optional[str] = None, bpm: Optional[str] = None, initial_key: Optional[str] = None, genre: Optional[str] = None, album: Optional[str] = None, **kwargs, ): self.title = title self.track_id = track_id self.release_track_id = release_track_id self.artist = artist self.artist_id = artist_id self.artists = artists or [] self.artists_ids = artists_ids or [] self.length = length self.index = index self.media = media self.medium = medium self.medium_index = medium_index self.medium_total = medium_total self.artist_sort = artist_sort self.artists_sort = artists_sort or [] self.disctitle = disctitle self.artist_credit = artist_credit self.artists_credit = artists_credit or [] self.data_source = data_source self.data_url = data_url self.lyricist = lyricist self.composer = composer self.composer_sort = composer_sort self.arranger = arranger self.track_alt = track_alt self.work = work self.mb_workid = mb_workid self.work_disambig = work_disambig self.bpm = bpm self.initial_key = initial_key self.genre = genre self.album = album self.update(kwargs) def copy(self) -> TrackInfo: dupe = TrackInfo() dupe.update(self) return dupe # Candidate distance scoring. # Parameters for string distance function. # Words that can be moved to the end of a string using a comma. SD_END_WORDS = ["the", "a", "an"] # Reduced weights for certain portions of the string. SD_PATTERNS = [ (r"^the ", 0.1), (r"[\[\(]?(ep|single)[\]\)]?", 0.0), (r"[\[\(]?(featuring|feat|ft)[\. :].+", 0.1), (r"\(.*?\)", 0.3), (r"\[.*?\]", 0.3), (r"(, )?(pt\.|part) .+", 0.2), ] # Replacements to use before testing distance. SD_REPLACE = [ (r"&", "and"), ] def _string_dist_basic(str1: str, str2: str) -> float: """Basic edit distance between two strings, ignoring non-alphanumeric characters and case. Comparisons are based on a transliteration/lowering to ASCII characters. Normalized by string length. """ assert isinstance(str1, str) assert isinstance(str2, str) str1 = as_string(unidecode(str1)) str2 = as_string(unidecode(str2)) str1 = re.sub(r"[^a-z0-9]", "", str1.lower()) str2 = re.sub(r"[^a-z0-9]", "", str2.lower()) if not str1 and not str2: return 0.0 return levenshtein_distance(str1, str2) / float(max(len(str1), len(str2))) def string_dist(str1: Optional[str], str2: Optional[str]) -> float: """Gives an "intuitive" edit distance between two strings. This is an edit distance, normalized by the string length, with a number of tweaks that reflect intuition about text. """ if str1 is None and str2 is None: return 0.0 if str1 is None or str2 is None: return 1.0 str1 = str1.lower() str2 = str2.lower() # Don't penalize strings that move certain words to the end. For # example, "the something" should be considered equal to # "something, the". for word in SD_END_WORDS: if str1.endswith(", %s" % word): str1 = "{} {}".format(word, str1[: -len(word) - 2]) if str2.endswith(", %s" % word): str2 = "{} {}".format(word, str2[: -len(word) - 2]) # Perform a couple of basic normalizing substitutions. for pat, repl in SD_REPLACE: str1 = re.sub(pat, repl, str1) str2 = re.sub(pat, repl, str2) # Change the weight for certain string portions matched by a set # of regular expressions. We gradually change the strings and build # up penalties associated with parts of the string that were # deleted. base_dist = _string_dist_basic(str1, str2) penalty = 0.0 for pat, weight in SD_PATTERNS: # Get strings that drop the pattern. case_str1 = re.sub(pat, "", str1) case_str2 = re.sub(pat, "", str2) if case_str1 != str1 or case_str2 != str2: # If the pattern was present (i.e., it is deleted in the # the current case), recalculate the distances for the # modified strings. case_dist = _string_dist_basic(case_str1, case_str2) case_delta = max(0.0, base_dist - case_dist) if case_delta == 0.0: continue # Shift our baseline strings down (to avoid rematching the # same part of the string) and add a scaled distance # amount to the penalties. str1 = case_str1 str2 = case_str2 base_dist = case_dist penalty += weight * case_delta return base_dist + penalty @total_ordering class Distance: """Keeps track of multiple distance penalties. Provides a single weighted distance for all penalties as well as a weighted distance for each individual penalty. """ def __init__(self): self._penalties = {} self.tracks: Dict[TrackInfo, Distance] = {} @cached_classproperty def _weights(cls) -> Dict[str, float]: """A dictionary from keys to floating-point weights.""" weights_view = config["match"]["distance_weights"] weights = {} for key in weights_view.keys(): weights[key] = weights_view[key].as_number() return weights # Access the components and their aggregates. @property def distance(self) -> float: """Return a weighted and normalized distance across all penalties. """ dist_max = self.max_distance if dist_max: return self.raw_distance / self.max_distance return 0.0 @property def max_distance(self) -> float: """Return the maximum distance penalty (normalization factor).""" dist_max = 0.0 for key, penalty in self._penalties.items(): dist_max += len(penalty) * self._weights[key] return dist_max @property def raw_distance(self) -> float: """Return the raw (denormalized) distance.""" dist_raw = 0.0 for key, penalty in self._penalties.items(): dist_raw += sum(penalty) * self._weights[key] return dist_raw def items(self) -> List[Tuple[str, float]]: """Return a list of (key, dist) pairs, with `dist` being the weighted distance, sorted from highest to lowest. Does not include penalties with a zero value. """ list_ = [] for key in self._penalties: dist = self[key] if dist: list_.append((key, dist)) # Convert distance into a negative float we can sort items in # ascending order (for keys, when the penalty is equal) and # still get the items with the biggest distance first. return sorted( list_, key=lambda key_and_dist: (-key_and_dist[1], key_and_dist[0]) ) def __hash__(self) -> int: return id(self) def __eq__(self, other) -> bool: return self.distance == other # Behave like a float. def __lt__(self, other) -> bool: return self.distance < other def __float__(self) -> float: return self.distance def __sub__(self, other) -> float: return self.distance - other def __rsub__(self, other) -> float: return other - self.distance def __str__(self) -> str: return f"{self.distance:.2f}" # Behave like a dict. def __getitem__(self, key) -> float: """Returns the weighted distance for a named penalty.""" dist = sum(self._penalties[key]) * self._weights[key] dist_max = self.max_distance if dist_max: return dist / dist_max return 0.0 def __iter__(self) -> Iterator[Tuple[str, float]]: return iter(self.items()) def __len__(self) -> int: return len(self.items()) def keys(self) -> List[str]: return [key for key, _ in self.items()] def update(self, dist: "Distance"): """Adds all the distance penalties from `dist`.""" if not isinstance(dist, Distance): raise ValueError( "`dist` must be a Distance object, not {}".format(type(dist)) ) for key, penalties in dist._penalties.items(): self._penalties.setdefault(key, []).extend(penalties) # Adding components. def _eq(self, value1: Union[re.Pattern[str], Any], value2: Any) -> bool: """Returns True if `value1` is equal to `value2`. `value1` may be a compiled regular expression, in which case it will be matched against `value2`. """ if isinstance(value1, re.Pattern): value2 = cast(str, value2) return bool(value1.match(value2)) return value1 == value2 def add(self, key: str, dist: float): """Adds a distance penalty. `key` must correspond with a configured weight setting. `dist` must be a float between 0.0 and 1.0, and will be added to any existing distance penalties for the same key. """ if not 0.0 <= dist <= 1.0: raise ValueError(f"`dist` must be between 0.0 and 1.0, not {dist}") self._penalties.setdefault(key, []).append(dist) def add_equality( self, key: str, value: Any, options: Union[List[Any], Tuple[Any, ...], Any], ): """Adds a distance penalty of 1.0 if `value` doesn't match any of the values in `options`. If an option is a compiled regular expression, it will be considered equal if it matches against `value`. """ if not isinstance(options, (list, tuple)): options = [options] for opt in options: if self._eq(opt, value): dist = 0.0 break else: dist = 1.0 self.add(key, dist) def add_expr(self, key: str, expr: bool): """Adds a distance penalty of 1.0 if `expr` evaluates to True, or 0.0. """ if expr: self.add(key, 1.0) else: self.add(key, 0.0) def add_number(self, key: str, number1: int, number2: int): """Adds a distance penalty of 1.0 for each number of difference between `number1` and `number2`, or 0.0 when there is no difference. Use this when there is no upper limit on the difference between the two numbers. """ diff = abs(number1 - number2) if diff: for i in range(diff): self.add(key, 1.0) else: self.add(key, 0.0) def add_priority( self, key: str, value: Any, options: Union[List[Any], Tuple[Any, ...], Any], ): """Adds a distance penalty that corresponds to the position at which `value` appears in `options`. A distance penalty of 0.0 for the first option, or 1.0 if there is no matching option. If an option is a compiled regular expression, it will be considered equal if it matches against `value`. """ if not isinstance(options, (list, tuple)): options = [options] unit = 1.0 / (len(options) or 1) for i, opt in enumerate(options): if self._eq(opt, value): dist = i * unit break else: dist = 1.0 self.add(key, dist) def add_ratio( self, key: str, number1: Union[int, float], number2: Union[int, float], ): """Adds a distance penalty for `number1` as a ratio of `number2`. `number1` is bound at 0 and `number2`. """ number = float(max(min(number1, number2), 0)) if number2: dist = number / number2 else: dist = 0.0 self.add(key, dist) def add_string(self, key: str, str1: Optional[str], str2: Optional[str]): """Adds a distance penalty based on the edit distance between `str1` and `str2`. """ dist = string_dist(str1, str2) self.add(key, dist) # Structures that compose all the information for a candidate match. class AlbumMatch(NamedTuple): distance: Distance info: AlbumInfo mapping: Dict[Item, TrackInfo] extra_items: List[Item] extra_tracks: List[TrackInfo] class TrackMatch(NamedTuple): distance: Distance info: TrackInfo # Aggregation of sources. def album_for_mbid(release_id: str) -> Optional[AlbumInfo]: """Get an AlbumInfo object for a MusicBrainz release ID. Return None if the ID is not found. """ try: album = mb.album_for_id(release_id) if album: plugins.send("albuminfo_received", info=album) return album except mb.MusicBrainzAPIError as exc: exc.log(log) return None def track_for_mbid(recording_id: str) -> Optional[TrackInfo]: """Get a TrackInfo object for a MusicBrainz recording ID. Return None if the ID is not found. """ try: track = mb.track_for_id(recording_id) if track: plugins.send("trackinfo_received", info=track) return track except mb.MusicBrainzAPIError as exc: exc.log(log) return None def albums_for_id(album_id: str) -> Iterable[AlbumInfo]: """Get a list of albums for an ID.""" a = album_for_mbid(album_id) if a: yield a for a in plugins.album_for_id(album_id): if a: plugins.send("albuminfo_received", info=a) yield a def tracks_for_id(track_id: str) -> Iterable[TrackInfo]: """Get a list of tracks for an ID.""" t = track_for_mbid(track_id) if t: yield t for t in plugins.track_for_id(track_id): if t: plugins.send("trackinfo_received", info=t) yield t def invoke_mb(call_func: Callable, *args): try: return call_func(*args) except mb.MusicBrainzAPIError as exc: exc.log(log) return () @plugins.notify_info_yielded("albuminfo_received") def album_candidates( items: List[Item], artist: str, album: str, va_likely: bool, extra_tags: Dict, ) -> Iterable[Tuple]: """Search for album matches. ``items`` is a list of Item objects that make up the album. ``artist`` and ``album`` are the respective names (strings), which may be derived from the item list or may be entered by the user. ``va_likely`` is a boolean indicating whether the album is likely to be a "various artists" release. ``extra_tags`` is an optional dictionary of additional tags used to further constrain the search. """ if config["musicbrainz"]["enabled"]: # Base candidates if we have album and artist to match. if artist and album: yield from invoke_mb( mb.match_album, artist, album, len(items), extra_tags ) # Also add VA matches from MusicBrainz where appropriate. if va_likely and album: yield from invoke_mb( mb.match_album, None, album, len(items), extra_tags ) # Candidates from plugins. yield from plugins.candidates(items, artist, album, va_likely, extra_tags) @plugins.notify_info_yielded("trackinfo_received") def item_candidates(item: Item, artist: str, title: str) -> Iterable[Tuple]: """Search for item matches. ``item`` is the Item to be matched. ``artist`` and ``title`` are strings and either reflect the item or are specified by the user. """ # MusicBrainz candidates. if config["musicbrainz"]["enabled"] and artist and title: yield from invoke_mb(mb.match_track, artist, title) # Plugin candidates. yield from plugins.item_candidates(item, artist, title) beetbox-beets-01f1faf/beets/autotag/match.py000066400000000000000000000522061472325477400211700ustar00rootroot00000000000000# This file is part of beets. # Copyright 2016, Adrian Sampson. # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the # "Software"), to deal in the Software without restriction, including # without limitation the rights to use, copy, modify, merge, publish, # distribute, sublicense, and/or sell copies of the Software, and to # permit persons to whom the Software is furnished to do so, subject to # the following conditions: # # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. """Matches existing metadata with canonical information to identify releases and tracks. """ from __future__ import annotations import datetime import re from enum import IntEnum from typing import ( Any, Dict, Iterable, List, NamedTuple, Optional, Sequence, Tuple, TypeVar, Union, cast, ) from munkres import Munkres from beets import config, logging, plugins from beets.autotag import ( AlbumInfo, AlbumMatch, Distance, TrackInfo, TrackMatch, hooks, ) from beets.library import Item from beets.util import plurality # Artist signals that indicate "various artists". These are used at the # album level to determine whether a given release is likely a VA # release and also on the track level to to remove the penalty for # differing artists. VA_ARTISTS = ("", "various artists", "various", "va", "unknown") # Global logger. log = logging.getLogger("beets") # Recommendation enumeration. class Recommendation(IntEnum): """Indicates a qualitative suggestion to the user about what should be done with a given match. """ none = 0 low = 1 medium = 2 strong = 3 # A structure for holding a set of possible matches to choose between. This # consists of a list of possible candidates (i.e., AlbumInfo or TrackInfo # objects) and a recommendation value. class Proposal(NamedTuple): candidates: Sequence[AlbumMatch | TrackMatch] recommendation: Recommendation # Primary matching functionality. def current_metadata( items: Iterable[Item], ) -> Tuple[Dict[str, Any], Dict[str, Any]]: """Extract the likely current metadata for an album given a list of its items. Return two dictionaries: - The most common value for each field. - Whether each field's value was unanimous (values are booleans). """ assert items # Must be nonempty. likelies = {} consensus = {} fields = [ "artist", "album", "albumartist", "year", "disctotal", "mb_albumid", "label", "barcode", "catalognum", "country", "media", "albumdisambig", ] for field in fields: values = [item[field] for item in items if item] likelies[field], freq = plurality(values) consensus[field] = freq == len(values) # If there's an album artist consensus, use this for the artist. if consensus["albumartist"] and likelies["albumartist"]: likelies["artist"] = likelies["albumartist"] return likelies, consensus def assign_items( items: Sequence[Item], tracks: Sequence[TrackInfo], ) -> Tuple[Dict[Item, TrackInfo], List[Item], List[TrackInfo]]: """Given a list of Items and a list of TrackInfo objects, find the best mapping between them. Returns a mapping from Items to TrackInfo objects, a set of extra Items, and a set of extra TrackInfo objects. These "extra" objects occur when there is an unequal number of objects of the two types. """ # Construct the cost matrix. costs: List[List[Distance]] = [] for item in items: row = [] for track in tracks: row.append(track_distance(item, track)) costs.append(row) # Find a minimum-cost bipartite matching. log.debug("Computing track assignment...") matching = Munkres().compute(costs) log.debug("...done.") # Produce the output matching. mapping = {items[i]: tracks[j] for (i, j) in matching} extra_items = list(set(items) - set(mapping.keys())) extra_items.sort(key=lambda i: (i.disc, i.track, i.title)) extra_tracks = list(set(tracks) - set(mapping.values())) extra_tracks.sort(key=lambda t: (t.index, t.title)) return mapping, extra_items, extra_tracks def track_index_changed(item: Item, track_info: TrackInfo) -> bool: """Returns True if the item and track info index is different. Tolerates per disc and per release numbering. """ return item.track not in (track_info.medium_index, track_info.index) def track_distance( item: Item, track_info: TrackInfo, incl_artist: bool = False, ) -> Distance: """Determines the significance of a track metadata change. Returns a Distance object. `incl_artist` indicates that a distance component should be included for the track artist (i.e., for various-artist releases). """ dist = hooks.Distance() # Length. if track_info.length: item_length = cast(float, item.length) track_length_grace = cast( Union[float, int], config["match"]["track_length_grace"].as_number(), ) track_length_max = cast( Union[float, int], config["match"]["track_length_max"].as_number(), ) diff = abs(item_length - track_info.length) - track_length_grace dist.add_ratio("track_length", diff, track_length_max) # Title. dist.add_string("track_title", item.title, track_info.title) # Artist. Only check if there is actually an artist in the track data. if ( incl_artist and track_info.artist and item.artist.lower() not in VA_ARTISTS ): dist.add_string("track_artist", item.artist, track_info.artist) # Track index. if track_info.index and item.track: dist.add_expr("track_index", track_index_changed(item, track_info)) # Track ID. if item.mb_trackid: dist.add_expr("track_id", item.mb_trackid != track_info.track_id) # Penalize mismatching disc numbers. if track_info.medium and item.disc: dist.add_expr("medium", item.disc != track_info.medium) # Plugins. dist.update(plugins.track_distance(item, track_info)) return dist def distance( items: Sequence[Item], album_info: AlbumInfo, mapping: Dict[Item, TrackInfo], ) -> Distance: """Determines how "significant" an album metadata change would be. Returns a Distance object. `album_info` is an AlbumInfo object reflecting the album to be compared. `items` is a sequence of all Item objects that will be matched (order is not important). `mapping` is a dictionary mapping Items to TrackInfo objects; the keys are a subset of `items` and the values are a subset of `album_info.tracks`. """ likelies, _ = current_metadata(items) dist = hooks.Distance() # Artist, if not various. if not album_info.va: dist.add_string("artist", likelies["artist"], album_info.artist) # Album. dist.add_string("album", likelies["album"], album_info.album) # Current or preferred media. if album_info.media: # Preferred media options. patterns = config["match"]["preferred"]["media"].as_str_seq() patterns = cast(Sequence[str], patterns) options = [re.compile(r"(\d+x)?(%s)" % pat, re.I) for pat in patterns] if options: dist.add_priority("media", album_info.media, options) # Current media. elif likelies["media"]: dist.add_equality("media", album_info.media, likelies["media"]) # Mediums. if likelies["disctotal"] and album_info.mediums: dist.add_number("mediums", likelies["disctotal"], album_info.mediums) # Prefer earliest release. if album_info.year and config["match"]["preferred"]["original_year"]: # Assume 1889 (earliest first gramophone discs) if we don't know the # original year. original = album_info.original_year or 1889 diff = abs(album_info.year - original) diff_max = abs(datetime.date.today().year - original) dist.add_ratio("year", diff, diff_max) # Year. elif likelies["year"] and album_info.year: if likelies["year"] in (album_info.year, album_info.original_year): # No penalty for matching release or original year. dist.add("year", 0.0) elif album_info.original_year: # Prefer matchest closest to the release year. diff = abs(likelies["year"] - album_info.year) diff_max = abs( datetime.date.today().year - album_info.original_year ) dist.add_ratio("year", diff, diff_max) else: # Full penalty when there is no original year. dist.add("year", 1.0) # Preferred countries. patterns = config["match"]["preferred"]["countries"].as_str_seq() patterns = cast(Sequence[str], patterns) options = [re.compile(pat, re.I) for pat in patterns] if album_info.country and options: dist.add_priority("country", album_info.country, options) # Country. elif likelies["country"] and album_info.country: dist.add_string("country", likelies["country"], album_info.country) # Label. if likelies["label"] and album_info.label: dist.add_string("label", likelies["label"], album_info.label) # Catalog number. if likelies["catalognum"] and album_info.catalognum: dist.add_string( "catalognum", likelies["catalognum"], album_info.catalognum ) # Disambiguation. if likelies["albumdisambig"] and album_info.albumdisambig: dist.add_string( "albumdisambig", likelies["albumdisambig"], album_info.albumdisambig ) # Album ID. if likelies["mb_albumid"]: dist.add_equality( "album_id", likelies["mb_albumid"], album_info.album_id ) # Tracks. dist.tracks = {} for item, track in mapping.items(): dist.tracks[track] = track_distance(item, track, album_info.va) dist.add("tracks", dist.tracks[track].distance) # Missing tracks. for _ in range(len(album_info.tracks) - len(mapping)): dist.add("missing_tracks", 1.0) # Unmatched tracks. for _ in range(len(items) - len(mapping)): dist.add("unmatched_tracks", 1.0) # Plugins. dist.update(plugins.album_distance(items, album_info, mapping)) return dist def match_by_id(items: Iterable[Item]): """If the items are tagged with a MusicBrainz album ID, returns an AlbumInfo object for the corresponding album. Otherwise, returns None. """ albumids = (item.mb_albumid for item in items if item.mb_albumid) # Did any of the items have an MB album ID? try: first = next(albumids) except StopIteration: log.debug("No album ID found.") return None # Is there a consensus on the MB album ID? for other in albumids: if other != first: log.debug("No album ID consensus.") return None # If all album IDs are equal, look up the album. log.debug("Searching for discovered album ID: {0}", first) return hooks.album_for_mbid(first) def _recommendation( results: Sequence[AlbumMatch | TrackMatch], ) -> Recommendation: """Given a sorted list of AlbumMatch or TrackMatch objects, return a recommendation based on the results' distances. If the recommendation is higher than the configured maximum for an applied penalty, the recommendation will be downgraded to the configured maximum for that penalty. """ if not results: # No candidates: no recommendation. return Recommendation.none # Basic distance thresholding. min_dist = results[0].distance if min_dist < config["match"]["strong_rec_thresh"].as_number(): # Strong recommendation level. rec = Recommendation.strong elif min_dist <= config["match"]["medium_rec_thresh"].as_number(): # Medium recommendation level. rec = Recommendation.medium elif len(results) == 1: # Only a single candidate. rec = Recommendation.low elif ( results[1].distance - min_dist >= config["match"]["rec_gap_thresh"].as_number() ): # Gap between first two candidates is large. rec = Recommendation.low else: # No conclusion. Return immediately. Can't be downgraded any further. return Recommendation.none # Downgrade to the max rec if it is lower than the current rec for an # applied penalty. keys = set(min_dist.keys()) if isinstance(results[0], hooks.AlbumMatch): for track_dist in min_dist.tracks.values(): keys.update(list(track_dist.keys())) max_rec_view = config["match"]["max_rec"] for key in keys: if key in list(max_rec_view.keys()): max_rec = max_rec_view[key].as_choice( { "strong": Recommendation.strong, "medium": Recommendation.medium, "low": Recommendation.low, "none": Recommendation.none, } ) rec = min(rec, max_rec) return rec AnyMatch = TypeVar("AnyMatch", TrackMatch, AlbumMatch) def _sort_candidates(candidates: Iterable[AnyMatch]) -> Sequence[AnyMatch]: """Sort candidates by distance.""" return sorted(candidates, key=lambda match: match.distance) def _add_candidate( items: Sequence[Item], results: Dict[Any, AlbumMatch], info: AlbumInfo, ): """Given a candidate AlbumInfo object, attempt to add the candidate to the output dictionary of AlbumMatch objects. This involves checking the track count, ordering the items, checking for duplicates, and calculating the distance. """ log.debug( "Candidate: {0} - {1} ({2})", info.artist, info.album, info.album_id ) # Discard albums with zero tracks. if not info.tracks: log.debug("No tracks.") return # Prevent duplicates. if info.album_id and info.album_id in results: log.debug("Duplicate.") return # Discard matches without required tags. for req_tag in cast( Sequence[str], config["match"]["required"].as_str_seq() ): if getattr(info, req_tag) is None: log.debug("Ignored. Missing required tag: {0}", req_tag) return # Find mapping between the items and the track info. mapping, extra_items, extra_tracks = assign_items(items, info.tracks) # Get the change distance. dist = distance(items, info, mapping) # Skip matches with ignored penalties. penalties = [key for key, _ in dist] ignored = cast(Sequence[str], config["match"]["ignored"].as_str_seq()) for penalty in ignored: if penalty in penalties: log.debug("Ignored. Penalty: {0}", penalty) return log.debug("Success. Distance: {0}", dist) results[info.album_id] = hooks.AlbumMatch( dist, info, mapping, extra_items, extra_tracks ) def tag_album( items, search_artist: Optional[str] = None, search_album: Optional[str] = None, search_ids: List[str] = [], ) -> Tuple[str, str, Proposal]: """Return a tuple of the current artist name, the current album name, and a `Proposal` containing `AlbumMatch` candidates. The artist and album are the most common values of these fields among `items`. The `AlbumMatch` objects are generated by searching the metadata backends. By default, the metadata of the items is used for the search. This can be customized by setting the parameters. `search_ids` is a list of metadata backend IDs: if specified, it will restrict the candidates to those IDs, ignoring `search_artist` and `search album`. The `mapping` field of the album has the matched `items` as keys. The recommendation is calculated from the match quality of the candidates. """ # Get current metadata. likelies, consensus = current_metadata(items) cur_artist = cast(str, likelies["artist"]) cur_album = cast(str, likelies["album"]) log.debug("Tagging {0} - {1}", cur_artist, cur_album) # The output result, keys are the MB album ID. candidates: Dict[Any, AlbumMatch] = {} # Search by explicit ID. if search_ids: for search_id in search_ids: log.debug("Searching for album ID: {0}", search_id) for album_info_for_id in hooks.albums_for_id(search_id): _add_candidate(items, candidates, album_info_for_id) # Use existing metadata or text search. else: # Try search based on current ID. id_info = match_by_id(items) if id_info: _add_candidate(items, candidates, id_info) rec = _recommendation(list(candidates.values())) log.debug("Album ID match recommendation is {0}", rec) if candidates and not config["import"]["timid"]: # If we have a very good MBID match, return immediately. # Otherwise, this match will compete against metadata-based # matches. if rec == Recommendation.strong: log.debug("ID match.") return ( cur_artist, cur_album, Proposal(list(candidates.values()), rec), ) # Search terms. if not (search_artist and search_album): # No explicit search terms -- use current metadata. search_artist, search_album = cur_artist, cur_album log.debug("Search terms: {0} - {1}", search_artist, search_album) extra_tags = None if config["musicbrainz"]["extra_tags"]: tag_list = config["musicbrainz"]["extra_tags"].get() extra_tags = {k: v for (k, v) in likelies.items() if k in tag_list} log.debug("Additional search terms: {0}", extra_tags) # Is this album likely to be a "various artist" release? va_likely = ( (not consensus["artist"]) or (search_artist.lower() in VA_ARTISTS) or any(item.comp for item in items) ) log.debug("Album might be VA: {0}", va_likely) # Get the results from the data sources. for matched_candidate in hooks.album_candidates( items, search_artist, search_album, va_likely, extra_tags ): _add_candidate(items, candidates, matched_candidate) log.debug("Evaluating {0} candidates.", len(candidates)) # Sort and get the recommendation. candidates_sorted = _sort_candidates(candidates.values()) rec = _recommendation(candidates_sorted) return cur_artist, cur_album, Proposal(candidates_sorted, rec) def tag_item( item, search_artist: Optional[str] = None, search_title: Optional[str] = None, search_ids: Optional[List[str]] = None, ) -> Proposal: """Find metadata for a single track. Return a `Proposal` consisting of `TrackMatch` objects. `search_artist` and `search_title` may be used to override the current metadata for the purposes of the MusicBrainz title. `search_ids` may be used for restricting the search to a list of metadata backend IDs. """ # Holds candidates found so far: keys are MBIDs; values are # (distance, TrackInfo) pairs. candidates = {} rec: Optional[Recommendation] = None # First, try matching by MusicBrainz ID. trackids = search_ids or [t for t in [item.mb_trackid] if t] if trackids: for trackid in trackids: log.debug("Searching for track ID: {0}", trackid) for track_info in hooks.tracks_for_id(trackid): dist = track_distance(item, track_info, incl_artist=True) candidates[track_info.track_id] = hooks.TrackMatch( dist, track_info ) # If this is a good match, then don't keep searching. rec = _recommendation(_sort_candidates(candidates.values())) if ( rec == Recommendation.strong and not config["import"]["timid"] ): log.debug("Track ID match.") return Proposal(_sort_candidates(candidates.values()), rec) # If we're searching by ID, don't proceed. if search_ids: if candidates: assert rec is not None return Proposal(_sort_candidates(candidates.values()), rec) else: return Proposal([], Recommendation.none) # Search terms. if not (search_artist and search_title): search_artist, search_title = item.artist, item.title log.debug("Item search terms: {0} - {1}", search_artist, search_title) # Get and evaluate candidate metadata. for track_info in hooks.item_candidates(item, search_artist, search_title): dist = track_distance(item, track_info, incl_artist=True) candidates[track_info.track_id] = hooks.TrackMatch(dist, track_info) # Sort by distance and return with recommendation. log.debug("Found {0} candidates.", len(candidates)) candidates_sorted = _sort_candidates(candidates.values()) rec = _recommendation(candidates_sorted) return Proposal(candidates_sorted, rec) beetbox-beets-01f1faf/beets/autotag/mb.py000066400000000000000000000714021472325477400204710ustar00rootroot00000000000000# This file is part of beets. # Copyright 2016, Adrian Sampson. # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the # "Software"), to deal in the Software without restriction, including # without limitation the rights to use, copy, modify, merge, publish, # distribute, sublicense, and/or sell copies of the Software, and to # permit persons to whom the Software is furnished to do so, subject to # the following conditions: # # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. """Searches for albums in the MusicBrainz database.""" from __future__ import annotations import re import traceback from collections import Counter from itertools import product from typing import Any, Dict, Iterator, List, Optional, Sequence, Tuple, cast from urllib.parse import urljoin import musicbrainzngs import beets import beets.autotag.hooks from beets import config, logging, plugins, util from beets.plugins import MetadataSourcePlugin from beets.util.id_extractors import ( beatport_id_regex, deezer_id_regex, extract_discogs_id_regex, spotify_id_regex, ) VARIOUS_ARTISTS_ID = "89ad4ac3-39f7-470e-963a-56509c546377" BASE_URL = "https://musicbrainz.org/" SKIPPED_TRACKS = ["[data track]"] FIELDS_TO_MB_KEYS = { "catalognum": "catno", "country": "country", "label": "label", "barcode": "barcode", "media": "format", "year": "date", } musicbrainzngs.set_useragent("beets", beets.__version__, "https://beets.io/") class MusicBrainzAPIError(util.HumanReadableError): """An error while talking to MusicBrainz. The `query` field is the parameter to the action and may have any type. """ def __init__(self, reason, verb, query, tb=None): self.query = query if isinstance(reason, musicbrainzngs.WebServiceError): reason = "MusicBrainz not reachable" super().__init__(reason, verb, tb) def get_message(self): return "{} in {} with query {}".format( self._reasonstr(), self.verb, repr(self.query) ) log = logging.getLogger("beets") RELEASE_INCLUDES = [ "artists", "media", "recordings", "release-groups", "labels", "artist-credits", "aliases", "recording-level-rels", "work-rels", "work-level-rels", "artist-rels", "isrcs", "url-rels", "release-rels", ] BROWSE_INCLUDES = [ "artist-credits", "work-rels", "artist-rels", "recording-rels", "release-rels", ] if "work-level-rels" in musicbrainzngs.VALID_BROWSE_INCLUDES["recording"]: BROWSE_INCLUDES.append("work-level-rels") BROWSE_CHUNKSIZE = 100 BROWSE_MAXTRACKS = 500 TRACK_INCLUDES = ["artists", "aliases", "isrcs"] if "work-level-rels" in musicbrainzngs.VALID_INCLUDES["recording"]: TRACK_INCLUDES += ["work-level-rels", "artist-rels"] if "genres" in musicbrainzngs.VALID_INCLUDES["recording"]: RELEASE_INCLUDES += ["genres"] def track_url(trackid: str) -> str: return urljoin(BASE_URL, "recording/" + trackid) def album_url(albumid: str) -> str: return urljoin(BASE_URL, "release/" + albumid) def configure(): """Set up the python-musicbrainz-ngs module according to settings from the beets configuration. This should be called at startup. """ hostname = config["musicbrainz"]["host"].as_str() https = config["musicbrainz"]["https"].get(bool) # Only call set_hostname when a custom server is configured. Since # musicbrainz-ngs connects to musicbrainz.org with HTTPS by default if hostname != "musicbrainz.org": musicbrainzngs.set_hostname(hostname, https) musicbrainzngs.set_rate_limit( config["musicbrainz"]["ratelimit_interval"].as_number(), config["musicbrainz"]["ratelimit"].get(int), ) def _preferred_alias(aliases: List): """Given an list of alias structures for an artist credit, select and return the user's preferred alias alias or None if no matching alias is found. """ if not aliases: return # Only consider aliases that have locales set. aliases = [a for a in aliases if "locale" in a] # Get any ignored alias types and lower case them to prevent case issues ignored_alias_types = config["import"]["ignored_alias_types"].as_str_seq() ignored_alias_types = [a.lower() for a in ignored_alias_types] # Search configured locales in order. for locale in config["import"]["languages"].as_str_seq(): # Find matching primary aliases for this locale that are not # being ignored matches = [] for a in aliases: if ( a["locale"] == locale and "primary" in a and a.get("type", "").lower() not in ignored_alias_types ): matches.append(a) # Skip to the next locale if we have no matches if not matches: continue return matches[0] def _preferred_release_event(release: Dict[str, Any]) -> Tuple[str, str]: """Given a release, select and return the user's preferred release event as a tuple of (country, release_date). Fall back to the default release event if a preferred event is not found. """ countries = config["match"]["preferred"]["countries"].as_str_seq() countries = cast(Sequence, countries) for country in countries: for event in release.get("release-event-list", {}): try: if country in event["area"]["iso-3166-1-code-list"]: return country, event["date"] except KeyError: pass return (cast(str, release.get("country")), cast(str, release.get("date"))) def _multi_artist_credit( credit: List[Dict], include_join_phrase: bool ) -> Tuple[List[str], List[str], List[str]]: """Given a list representing an ``artist-credit`` block, accumulate data into a triple of joined artist name lists: canonical, sort, and credit. """ artist_parts = [] artist_sort_parts = [] artist_credit_parts = [] for el in credit: if isinstance(el, str): # Join phrase. if include_join_phrase: artist_parts.append(el) artist_credit_parts.append(el) artist_sort_parts.append(el) else: alias = _preferred_alias(el["artist"].get("alias-list", ())) # An artist. if alias: cur_artist_name = alias["alias"] else: cur_artist_name = el["artist"]["name"] artist_parts.append(cur_artist_name) # Artist sort name. if alias: artist_sort_parts.append(alias["sort-name"]) elif "sort-name" in el["artist"]: artist_sort_parts.append(el["artist"]["sort-name"]) else: artist_sort_parts.append(cur_artist_name) # Artist credit. if "name" in el: artist_credit_parts.append(el["name"]) else: artist_credit_parts.append(cur_artist_name) return ( artist_parts, artist_sort_parts, artist_credit_parts, ) def _flatten_artist_credit(credit: List[Dict]) -> Tuple[str, str, str]: """Given a list representing an ``artist-credit`` block, flatten the data into a triple of joined artist name strings: canonical, sort, and credit. """ artist_parts, artist_sort_parts, artist_credit_parts = _multi_artist_credit( credit, include_join_phrase=True ) return ( "".join(artist_parts), "".join(artist_sort_parts), "".join(artist_credit_parts), ) def _artist_ids(credit: List[Dict]) -> List[str]: """ Given a list representing an ``artist-credit``, return a list of artist IDs """ artist_ids: List[str] = [] for el in credit: if isinstance(el, dict): artist_ids.append(el["artist"]["id"]) return artist_ids def _get_related_artist_names(relations, relation_type): """Given a list representing the artist relationships extract the names of the remixers and concatenate them. """ related_artists = [] for relation in relations: if relation["type"] == relation_type: related_artists.append(relation["artist"]["name"]) return ", ".join(related_artists) def track_info( recording: Dict, index: Optional[int] = None, medium: Optional[int] = None, medium_index: Optional[int] = None, medium_total: Optional[int] = None, ) -> beets.autotag.hooks.TrackInfo: """Translates a MusicBrainz recording result dictionary into a beets ``TrackInfo`` object. Three parameters are optional and are used only for tracks that appear on releases (non-singletons): ``index``, the overall track number; ``medium``, the disc number; ``medium_index``, the track's index on its medium; ``medium_total``, the number of tracks on the medium. Each number is a 1-based index. """ info = beets.autotag.hooks.TrackInfo( title=recording["title"], track_id=recording["id"], index=index, medium=medium, medium_index=medium_index, medium_total=medium_total, data_source="MusicBrainz", data_url=track_url(recording["id"]), ) if recording.get("artist-credit"): # Get the artist names. ( info.artist, info.artist_sort, info.artist_credit, ) = _flatten_artist_credit(recording["artist-credit"]) ( info.artists, info.artists_sort, info.artists_credit, ) = _multi_artist_credit( recording["artist-credit"], include_join_phrase=False ) info.artists_ids = _artist_ids(recording["artist-credit"]) info.artist_id = info.artists_ids[0] if recording.get("artist-relation-list"): info.remixer = _get_related_artist_names( recording["artist-relation-list"], relation_type="remixer" ) if recording.get("length"): info.length = int(recording["length"]) / 1000.0 info.trackdisambig = recording.get("disambiguation") if recording.get("isrc-list"): info.isrc = ";".join(recording["isrc-list"]) lyricist = [] composer = [] composer_sort = [] for work_relation in recording.get("work-relation-list", ()): if work_relation["type"] != "performance": continue info.work = work_relation["work"]["title"] info.mb_workid = work_relation["work"]["id"] if "disambiguation" in work_relation["work"]: info.work_disambig = work_relation["work"]["disambiguation"] for artist_relation in work_relation["work"].get( "artist-relation-list", () ): if "type" in artist_relation: type = artist_relation["type"] if type == "lyricist": lyricist.append(artist_relation["artist"]["name"]) elif type == "composer": composer.append(artist_relation["artist"]["name"]) composer_sort.append(artist_relation["artist"]["sort-name"]) if lyricist: info.lyricist = ", ".join(lyricist) if composer: info.composer = ", ".join(composer) info.composer_sort = ", ".join(composer_sort) arranger = [] for artist_relation in recording.get("artist-relation-list", ()): if "type" in artist_relation: type = artist_relation["type"] if type == "arranger": arranger.append(artist_relation["artist"]["name"]) if arranger: info.arranger = ", ".join(arranger) # Supplementary fields provided by plugins extra_trackdatas = plugins.send("mb_track_extract", data=recording) for extra_trackdata in extra_trackdatas: info.update(extra_trackdata) return info def _set_date_str( info: beets.autotag.hooks.AlbumInfo, date_str: str, original: bool = False, ): """Given a (possibly partial) YYYY-MM-DD string and an AlbumInfo object, set the object's release date fields appropriately. If `original`, then set the original_year, etc., fields. """ if date_str: date_parts = date_str.split("-") for key in ("year", "month", "day"): if date_parts: date_part = date_parts.pop(0) try: date_num = int(date_part) except ValueError: continue if original: key = "original_" + key setattr(info, key, date_num) def album_info(release: Dict) -> beets.autotag.hooks.AlbumInfo: """Takes a MusicBrainz release result dictionary and returns a beets AlbumInfo object containing the interesting data about that release. """ # Get artist name using join phrases. artist_name, artist_sort_name, artist_credit_name = _flatten_artist_credit( release["artist-credit"] ) ( artists_names, artists_sort_names, artists_credit_names, ) = _multi_artist_credit( release["artist-credit"], include_join_phrase=False ) ntracks = sum(len(m["track-list"]) for m in release["medium-list"]) # The MusicBrainz API omits 'artist-relation-list' and 'work-relation-list' # when the release has more than 500 tracks. So we use browse_recordings # on chunks of tracks to recover the same information in this case. if ntracks > BROWSE_MAXTRACKS: log.debug("Album {} has too many tracks", release["id"]) recording_list = [] for i in range(0, ntracks, BROWSE_CHUNKSIZE): log.debug("Retrieving tracks starting at {}", i) recording_list.extend( musicbrainzngs.browse_recordings( release=release["id"], limit=BROWSE_CHUNKSIZE, includes=BROWSE_INCLUDES, offset=i, )["recording-list"] ) track_map = {r["id"]: r for r in recording_list} for medium in release["medium-list"]: for recording in medium["track-list"]: recording_info = track_map[recording["recording"]["id"]] recording["recording"] = recording_info # Basic info. track_infos = [] index = 0 for medium in release["medium-list"]: disctitle = medium.get("title") format = medium.get("format") if format in config["match"]["ignored_media"].as_str_seq(): continue all_tracks = medium["track-list"] if ( "data-track-list" in medium and not config["match"]["ignore_data_tracks"] ): all_tracks += medium["data-track-list"] track_count = len(all_tracks) if "pregap" in medium: all_tracks.insert(0, medium["pregap"]) for track in all_tracks: if ( "title" in track["recording"] and track["recording"]["title"] in SKIPPED_TRACKS ): continue if ( "video" in track["recording"] and track["recording"]["video"] == "true" and config["match"]["ignore_video_tracks"] ): continue # Basic information from the recording. index += 1 ti = track_info( track["recording"], index, int(medium["position"]), int(track["position"]), track_count, ) ti.release_track_id = track["id"] ti.disctitle = disctitle ti.media = format ti.track_alt = track["number"] # Prefer track data, where present, over recording data. if track.get("title"): ti.title = track["title"] if track.get("artist-credit"): # Get the artist names. ( ti.artist, ti.artist_sort, ti.artist_credit, ) = _flatten_artist_credit(track["artist-credit"]) ( ti.artists, ti.artists_sort, ti.artists_credit, ) = _multi_artist_credit( track["artist-credit"], include_join_phrase=False ) ti.artists_ids = _artist_ids(track["artist-credit"]) ti.artist_id = ti.artists_ids[0] if track.get("length"): ti.length = int(track["length"]) / (1000.0) track_infos.append(ti) album_artist_ids = _artist_ids(release["artist-credit"]) info = beets.autotag.hooks.AlbumInfo( album=release["title"], album_id=release["id"], artist=artist_name, artist_id=album_artist_ids[0], artists=artists_names, artists_ids=album_artist_ids, tracks=track_infos, mediums=len(release["medium-list"]), artist_sort=artist_sort_name, artists_sort=artists_sort_names, artist_credit=artist_credit_name, artists_credit=artists_credit_names, data_source="MusicBrainz", data_url=album_url(release["id"]), barcode=release.get("barcode"), ) info.va = info.artist_id == VARIOUS_ARTISTS_ID if info.va: info.artist = config["va_name"].as_str() info.asin = release.get("asin") info.releasegroup_id = release["release-group"]["id"] info.albumstatus = release.get("status") if release["release-group"].get("title"): info.release_group_title = release["release-group"].get("title") # Get the disambiguation strings at the release and release group level. if release["release-group"].get("disambiguation"): info.releasegroupdisambig = release["release-group"].get( "disambiguation" ) if release.get("disambiguation"): info.albumdisambig = release.get("disambiguation") # Get the "classic" Release type. This data comes from a legacy API # feature before MusicBrainz supported multiple release types. if "type" in release["release-group"]: reltype = release["release-group"]["type"] if reltype: info.albumtype = reltype.lower() # Set the new-style "primary" and "secondary" release types. albumtypes = [] if "primary-type" in release["release-group"]: rel_primarytype = release["release-group"]["primary-type"] if rel_primarytype: albumtypes.append(rel_primarytype.lower()) if "secondary-type-list" in release["release-group"]: if release["release-group"]["secondary-type-list"]: for sec_type in release["release-group"]["secondary-type-list"]: albumtypes.append(sec_type.lower()) info.albumtypes = albumtypes # Release events. info.country, release_date = _preferred_release_event(release) release_group_date = release["release-group"].get("first-release-date") if not release_date: # Fall back if release-specific date is not available. release_date = release_group_date _set_date_str(info, release_date, False) _set_date_str(info, release_group_date, True) # Label name. if release.get("label-info-list"): label_info = release["label-info-list"][0] if label_info.get("label"): label = label_info["label"]["name"] if label != "[no label]": info.label = label info.catalognum = label_info.get("catalog-number") # Text representation data. if release.get("text-representation"): rep = release["text-representation"] info.script = rep.get("script") info.language = rep.get("language") # Media (format). if release["medium-list"]: # If all media are the same, use that medium name if len({m.get("format") for m in release["medium-list"]}) == 1: info.media = release["medium-list"][0].get("format") # Otherwise, let's just call it "Media" else: info.media = "Media" if config["musicbrainz"]["genres"]: sources = [ release["release-group"].get("genre-list", []), release.get("genre-list", []), ] genres: Counter[str] = Counter() for source in sources: for genreitem in source: genres[genreitem["name"]] += int(genreitem["count"]) info.genre = "; ".join( genre for genre, _count in sorted(genres.items(), key=lambda g: -g[1]) ) # We might find links to external sources (Discogs, Bandcamp, ...) external_ids = config["musicbrainz"]["external_ids"].get() wanted_sources = {site for site, wanted in external_ids.items() if wanted} if wanted_sources and (url_rels := release.get("url-relation-list")): urls = {} for source, url in product(wanted_sources, url_rels): if f"{source}.com" in (target := url["target"]): urls[source] = target log.debug( "Found link to {} release via MusicBrainz", source.capitalize(), ) if "discogs" in urls: info.discogs_albumid = extract_discogs_id_regex(urls["discogs"]) if "bandcamp" in urls: info.bandcamp_album_id = urls["bandcamp"] if "spotify" in urls: info.spotify_album_id = MetadataSourcePlugin._get_id( "album", urls["spotify"], spotify_id_regex ) if "deezer" in urls: info.deezer_album_id = MetadataSourcePlugin._get_id( "album", urls["deezer"], deezer_id_regex ) if "beatport" in urls: info.beatport_album_id = MetadataSourcePlugin._get_id( "album", urls["beatport"], beatport_id_regex ) if "tidal" in urls: info.tidal_album_id = urls["tidal"].split("/")[-1] extra_albumdatas = plugins.send("mb_album_extract", data=release) for extra_albumdata in extra_albumdatas: info.update(extra_albumdata) return info def match_album( artist: str, album: str, tracks: Optional[int] = None, extra_tags: Optional[Dict[str, Any]] = None, ) -> Iterator[beets.autotag.hooks.AlbumInfo]: """Searches for a single album ("release" in MusicBrainz parlance) and returns an iterator over AlbumInfo objects. May raise a MusicBrainzAPIError. The query consists of an artist name, an album name, and, optionally, a number of tracks on the album and any other extra tags. """ # Build search criteria. criteria = {"release": album.lower().strip()} if artist is not None: criteria["artist"] = artist.lower().strip() else: # Various Artists search. criteria["arid"] = VARIOUS_ARTISTS_ID if tracks is not None: criteria["tracks"] = str(tracks) # Additional search cues from existing metadata. if extra_tags: for tag, value in extra_tags.items(): key = FIELDS_TO_MB_KEYS[tag] value = str(value).lower().strip() if key == "catno": value = value.replace(" ", "") if value: criteria[key] = value # Abort if we have no search terms. if not any(criteria.values()): return try: log.debug("Searching for MusicBrainz releases with: {!r}", criteria) res = musicbrainzngs.search_releases( limit=config["musicbrainz"]["searchlimit"].get(int), **criteria ) except musicbrainzngs.MusicBrainzError as exc: raise MusicBrainzAPIError( exc, "release search", criteria, traceback.format_exc() ) for release in res["release-list"]: # The search result is missing some data (namely, the tracks), # so we just use the ID and fetch the rest of the information. albuminfo = album_for_id(release["id"]) if albuminfo is not None: yield albuminfo def match_track( artist: str, title: str, ) -> Iterator[beets.autotag.hooks.TrackInfo]: """Searches for a single track and returns an iterable of TrackInfo objects. May raise a MusicBrainzAPIError. """ criteria = { "artist": artist.lower().strip(), "recording": title.lower().strip(), } if not any(criteria.values()): return try: res = musicbrainzngs.search_recordings( limit=config["musicbrainz"]["searchlimit"].get(int), **criteria ) except musicbrainzngs.MusicBrainzError as exc: raise MusicBrainzAPIError( exc, "recording search", criteria, traceback.format_exc() ) for recording in res["recording-list"]: yield track_info(recording) def _parse_id(s: str) -> Optional[str]: """Search for a MusicBrainz ID in the given string and return it. If no ID can be found, return None. """ # Find the first thing that looks like a UUID/MBID. match = re.search("[a-f0-9]{8}(-[a-f0-9]{4}){3}-[a-f0-9]{12}", s) if match is not None: return match.group() if match else None return None def _is_translation(r): _trans_key = "transl-tracklisting" return r["type"] == _trans_key and r["direction"] == "backward" def _find_actual_release_from_pseudo_release( pseudo_rel: Dict, ) -> Optional[Dict]: try: relations = pseudo_rel["release"]["release-relation-list"] except KeyError: return None # currently we only support trans(liter)ation's translations = [r for r in relations if _is_translation(r)] if not translations: return None actual_id = translations[0]["target"] return musicbrainzngs.get_release_by_id(actual_id, RELEASE_INCLUDES) def _merge_pseudo_and_actual_album( pseudo: beets.autotag.hooks.AlbumInfo, actual: beets.autotag.hooks.AlbumInfo ) -> Optional[beets.autotag.hooks.AlbumInfo]: """ Merges a pseudo release with its actual release. This implementation is naive, it doesn't overwrite fields, like status or ids. According to the ticket PICARD-145, the main release id should be used. But the ticket has been in limbo since over a decade now. It also suggests the introduction of the tag `musicbrainz_pseudoreleaseid`, but as of this field can't be found in any official Picard docs, hence why we did not implement that for now. """ merged = pseudo.copy() from_actual = { k: actual[k] for k in [ "media", "mediums", "country", "catalognum", "year", "month", "day", "original_year", "original_month", "original_day", "label", "barcode", "asin", "style", "genre", ] } merged.update(from_actual) return merged def album_for_id(releaseid: str) -> Optional[beets.autotag.hooks.AlbumInfo]: """Fetches an album by its MusicBrainz ID and returns an AlbumInfo object or None if the album is not found. May raise a MusicBrainzAPIError. """ log.debug("Requesting MusicBrainz release {}", releaseid) albumid = _parse_id(releaseid) if not albumid: log.debug("Invalid MBID ({0}).", releaseid) return None try: res = musicbrainzngs.get_release_by_id(albumid, RELEASE_INCLUDES) # resolve linked release relations actual_res = None if res["release"].get("status") == "Pseudo-Release": actual_res = _find_actual_release_from_pseudo_release(res) except musicbrainzngs.ResponseError: log.debug("Album ID match failed.") return None except musicbrainzngs.MusicBrainzError as exc: raise MusicBrainzAPIError( exc, "get release by ID", albumid, traceback.format_exc() ) # release is potentially a pseudo release release = album_info(res["release"]) # should be None unless we're dealing with a pseudo release if actual_res is not None: actual_release = album_info(actual_res["release"]) return _merge_pseudo_and_actual_album(release, actual_release) else: return release def track_for_id(releaseid: str) -> Optional[beets.autotag.hooks.TrackInfo]: """Fetches a track by its MusicBrainz ID. Returns a TrackInfo object or None if no track is found. May raise a MusicBrainzAPIError. """ trackid = _parse_id(releaseid) if not trackid: log.debug("Invalid MBID ({0}).", releaseid) return None try: res = musicbrainzngs.get_recording_by_id(trackid, TRACK_INCLUDES) except musicbrainzngs.ResponseError: log.debug("Track ID match failed.") return None except musicbrainzngs.MusicBrainzError as exc: raise MusicBrainzAPIError( exc, "get recording by ID", trackid, traceback.format_exc() ) return track_info(res["recording"]) beetbox-beets-01f1faf/beets/config_default.yaml000066400000000000000000000120251472325477400217060ustar00rootroot00000000000000# --------------- Main --------------- library: library.db directory: ~/Music statefile: state.pickle # --------------- Plugins --------------- plugins: [] pluginpath: [] # --------------- Import --------------- clutter: ["Thumbs.DB", ".DS_Store"] ignore: [".*", "*~", "System Volume Information", "lost+found"] ignore_hidden: yes import: # common options write: yes copy: yes move: no timid: no quiet: no log: # other options default_action: apply languages: [] quiet_fallback: skip none_rec_action: ask # rare options link: no hardlink: no reflink: no delete: no resume: ask incremental: no incremental_skip_later: no from_scratch: no autotag: yes singletons: no detail: no flat: no group_albums: no pretend: false search_ids: [] duplicate_keys: album: albumartist album item: artist title duplicate_action: ask duplicate_verbose_prompt: no bell: no set_fields: {} ignored_alias_types: [] singleton_album_disambig: yes # --------------- Paths --------------- path_sep_replace: _ drive_sep_replace: _ asciify_paths: false art_filename: cover max_filename_length: 0 replace: # Replace bad characters with _ # prohibited in many filesystem paths '[<>:\?\*\|]': _ # double quotation mark " '\"': _ # path separators: \ or / '[\\/]': _ # starting and closing periods '^\.': _ '\.$': _ # control characters '[\x00-\x1f]': _ # dash at the start of a filename (causes command line ambiguity) '^-': _ # Replace bad characters with nothing # starting and closing whitespace '\s+$': '' '^\s+': '' aunique: keys: albumartist album disambiguators: albumtype year label catalognum albumdisambig releasegroupdisambig bracket: '[]' sunique: keys: artist title disambiguators: year trackdisambig bracket: '[]' # --------------- Tagging --------------- per_disc_numbering: no original_date: no artist_credit: no id3v23: no va_name: "Various Artists" paths: default: $albumartist/$album%aunique{}/$track $title singleton: Non-Album/$artist/$title comp: Compilations/$album%aunique{}/$track $title # --------------- Performance --------------- threaded: yes timeout: 5.0 # --------------- UI --------------- verbose: 0 terminal_encoding: ui: terminal_width: 80 length_diff_thresh: 10.0 color: yes colors: text_success: ['bold', 'green'] text_warning: ['bold', 'yellow'] text_error: ['bold', 'red'] text_highlight: ['bold', 'red'] text_highlight_minor: ['white'] action_default: ['bold', 'cyan'] action: ['bold', 'cyan'] # New Colors text: ['normal'] text_faint: ['faint'] import_path: ['bold', 'blue'] import_path_items: ['bold', 'blue'] added: ['green'] removed: ['red'] changed: ['yellow'] added_highlight: ['bold', 'green'] removed_highlight: ['bold', 'red'] changed_highlight: ['bold', 'yellow'] text_diff_added: ['bold', 'red'] text_diff_removed: ['bold', 'red'] text_diff_changed: ['bold', 'red'] action_description: ['white'] import: indentation: match_header: 2 match_details: 2 match_tracklist: 5 layout: column # --------------- Search --------------- format_item: $artist - $album - $title format_album: $albumartist - $album time_format: '%Y-%m-%d %H:%M:%S' format_raw_length: no sort_album: albumartist+ album+ sort_item: artist+ album+ disc+ track+ sort_case_insensitive: yes # --------------- Autotagger --------------- overwrite_null: album: [] track: [] musicbrainz: enabled: yes host: musicbrainz.org https: no ratelimit: 1 ratelimit_interval: 1.0 searchlimit: 5 extra_tags: [] genres: no external_ids: discogs: no bandcamp: no spotify: no deezer: no beatport: no tidal: no match: strong_rec_thresh: 0.04 medium_rec_thresh: 0.25 rec_gap_thresh: 0.25 max_rec: missing_tracks: medium unmatched_tracks: medium distance_weights: source: 2.0 artist: 3.0 album: 3.0 media: 1.0 mediums: 1.0 year: 1.0 country: 0.5 label: 0.5 catalognum: 0.5 albumdisambig: 0.5 album_id: 5.0 tracks: 2.0 missing_tracks: 0.9 unmatched_tracks: 0.6 track_title: 3.0 track_artist: 2.0 track_index: 1.0 track_length: 2.0 track_id: 5.0 medium: 1.0 preferred: countries: [] media: [] original_year: no ignored: [] required: [] ignored_media: [] ignore_data_tracks: yes ignore_video_tracks: yes track_length_grace: 10 track_length_max: 30 album_disambig_fields: data_source media year country label catalognum albumdisambig singleton_disambig_fields: data_source index track_alt album beetbox-beets-01f1faf/beets/dbcore/000077500000000000000000000000001472325477400173075ustar00rootroot00000000000000beetbox-beets-01f1faf/beets/dbcore/__init__.py000066400000000000000000000023641472325477400214250ustar00rootroot00000000000000# This file is part of beets. # Copyright 2016, Adrian Sampson. # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the # "Software"), to deal in the Software without restriction, including # without limitation the rights to use, copy, modify, merge, publish, # distribute, sublicense, and/or sell copies of the Software, and to # permit persons to whom the Software is furnished to do so, subject to # the following conditions: # # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. """DBCore is an abstract database package that forms the basis for beets' Library. """ from .db import Database, Model, Results from .query import ( AndQuery, FieldQuery, InvalidQueryError, MatchQuery, OrQuery, Query, ) from .queryparse import ( parse_sorted_query, query_from_strings, sort_from_strings, ) from .types import Type __all__ = [ "AndQuery", "Database", "FieldQuery", "InvalidQueryError", "MatchQuery", "Model", "OrQuery", "Query", "Results", "Type", "parse_sorted_query", "query_from_strings", "sort_from_strings", ] beetbox-beets-01f1faf/beets/dbcore/db.py000077500000000000000000001263711472325477400202630ustar00rootroot00000000000000# This file is part of beets. # Copyright 2016, Adrian Sampson. # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the # "Software"), to deal in the Software without restriction, including # without limitation the rights to use, copy, modify, merge, publish, # distribute, sublicense, and/or sell copies of the Software, and to # permit persons to whom the Software is furnished to do so, subject to # the following conditions: # # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. """The central Model and Database constructs for DBCore.""" from __future__ import annotations import contextlib import os import re import sqlite3 import threading import time from abc import ABC from collections import defaultdict from sqlite3 import Connection from types import TracebackType from typing import ( Any, AnyStr, Callable, DefaultDict, Dict, Generator, Generic, Iterable, Iterator, List, Mapping, Optional, Sequence, Tuple, Type, TypeVar, Union, cast, ) from unidecode import unidecode import beets from ..util import cached_classproperty, functemplate from . import types from .query import ( AndQuery, FieldQuery, MatchQuery, NullSort, Query, Sort, TrueQuery, ) class DBAccessError(Exception): """The SQLite database became inaccessible. This can happen when trying to read or write the database when, for example, the database file is deleted or otherwise disappears. There is probably no way to recover from this error. """ class FormattedMapping(Mapping[str, str]): """A `dict`-like formatted view of a model. The accessor `mapping[key]` returns the formatted version of `model[key]` as a unicode string. The `included_keys` parameter allows filtering the fields that are returned. By default all fields are returned. Limiting to specific keys can avoid expensive per-item database queries. If `for_path` is true, all path separators in the formatted values are replaced. """ ALL_KEYS = "*" def __init__( self, model: Model, included_keys: str = ALL_KEYS, for_path: bool = False, ): self.for_path = for_path self.model = model if included_keys == self.ALL_KEYS: # Performance note: this triggers a database query. self.model_keys = self.model.keys(True) else: self.model_keys = included_keys def __getitem__(self, key: str) -> str: if key in self.model_keys: return self._get_formatted(self.model, key) else: raise KeyError(key) def __iter__(self) -> Iterator[str]: return iter(self.model_keys) def __len__(self) -> int: return len(self.model_keys) # The following signature is incompatible with `Mapping[str, str]`, since # the return type doesn't include `None` (but `default` can be `None`). def get( # type: ignore self, key: str, default: Optional[str] = None, ) -> str: """Similar to Mapping.get(key, default), but always formats to str.""" if default is None: default = self.model._type(key).format(None) return super().get(key, default) def _get_formatted(self, model: Model, key: str) -> str: value = model._type(key).format(model.get(key)) if isinstance(value, bytes): value = value.decode("utf-8", "ignore") if self.for_path: sep_repl = cast(str, beets.config["path_sep_replace"].as_str()) sep_drive = cast(str, beets.config["drive_sep_replace"].as_str()) if re.match(r"^\w:", value): value = re.sub(r"(?<=^\w):", sep_drive, value) for sep in (os.path.sep, os.path.altsep): if sep: value = value.replace(sep, sep_repl) return value # NOTE: This seems like it should be a `Mapping`, i.e. # ``` # class LazyConvertDict(Mapping[str, Any]) # ``` # but there are some conflicts with the `Mapping` protocol such that we # can't do this without changing behaviour: In particular, iterators returned # by some methods build intermediate lists, such that modification of the # `LazyConvertDict` becomes safe during iteration. Some code does in fact rely # on this. class LazyConvertDict: """Lazily convert types for attributes fetched from the database""" def __init__(self, model_cls: "Model"): """Initialize the object empty""" # FIXME: Dict[str, SQLiteType] self._data: Dict[str, Any] = {} self.model_cls = model_cls self._converted: Dict[str, Any] = {} def init(self, data: Dict[str, Any]): """Set the base data that should be lazily converted""" self._data = data def _convert(self, key: str, value: Any): """Convert the attribute type according to the SQL type""" return self.model_cls._type(key).from_sql(value) def __setitem__(self, key: str, value: Any): """Set an attribute value, assume it's already converted""" self._converted[key] = value def __getitem__(self, key: str) -> Any: """Get an attribute value, converting the type on demand if needed """ if key in self._converted: return self._converted[key] elif key in self._data: value = self._convert(key, self._data[key]) self._converted[key] = value return value def __delitem__(self, key: str): """Delete both converted and base data""" if key in self._converted: del self._converted[key] if key in self._data: del self._data[key] def keys(self) -> List[str]: """Get a list of available field names for this object.""" return list(self._converted.keys()) + list(self._data.keys()) def copy(self) -> LazyConvertDict: """Create a copy of the object.""" new = self.__class__(self.model_cls) new._data = self._data.copy() new._converted = self._converted.copy() return new # Act like a dictionary. def update(self, values: Mapping[str, Any]): """Assign all values in the given dict.""" for key, value in values.items(): self[key] = value def items(self) -> Iterable[Tuple[str, Any]]: """Iterate over (key, value) pairs that this object contains. Computed fields are not included. """ for key in self: yield key, self[key] def get(self, key: str, default: Optional[Any] = None): """Get the value for a given key or `default` if it does not exist. """ if key in self: return self[key] else: return default def __contains__(self, key: Any) -> bool: """Determine whether `key` is an attribute on this object.""" return key in self._converted or key in self._data def __iter__(self) -> Iterator[str]: """Iterate over the available field names (excluding computed fields). """ # NOTE: It would be nice to use the following: # yield from self._converted # yield from self._data # but that won't work since some code relies on modifying `self` # during iteration. return iter(self.keys()) def __len__(self) -> int: # FIXME: This is incorrect due to duplication of keys return len(self._converted) + len(self._data) # Abstract base for model classes. class Model(ABC): """An abstract object representing an object in the database. Model objects act like dictionaries (i.e., they allow subscript access like ``obj['field']``). The same field set is available via attribute access as a shortcut (i.e., ``obj.field``). Three kinds of attributes are available: * **Fixed attributes** come from a predetermined list of field names. These fields correspond to SQLite table columns and are thus fast to read, write, and query. * **Flexible attributes** are free-form and do not need to be listed ahead of time. * **Computed attributes** are read-only fields computed by a getter function provided by a plugin. Access to all three field types is uniform: ``obj.field`` works the same regardless of whether ``field`` is fixed, flexible, or computed. Model objects can optionally be associated with a `Library` object, in which case they can be loaded and stored from the database. Dirty flags are used to track which fields need to be stored. """ # Abstract components (to be provided by subclasses). _table: str """The main SQLite table name. """ _flex_table: str """The flex field SQLite table name. """ _fields: Dict[str, types.Type] = {} """A mapping indicating available "fixed" fields on this type. The keys are field names and the values are `Type` objects. """ _search_fields: Sequence[str] = () """The fields that should be queried by default by unqualified query terms. """ _types: Dict[str, types.Type] = {} """Optional Types for non-fixed (i.e., flexible and computed) fields. """ _sorts: Dict[str, Type[Sort]] = {} """Optional named sort criteria. The keys are strings and the values are subclasses of `Sort`. """ _queries: Dict[str, Type[FieldQuery]] = {} """Named queries that use a field-like `name:value` syntax but which do not relate to any specific field. """ _always_dirty = False """By default, fields only become "dirty" when their value actually changes. Enabling this flag marks fields as dirty even when the new value is the same as the old value (e.g., `o.f = o.f`). """ _revision = -1 """A revision number from when the model was loaded from or written to the database. """ @cached_classproperty def _relation(cls) -> type[Model]: """The model that this model is closely related to.""" return cls @cached_classproperty def relation_join(cls) -> str: """Return the join required to include the related table in the query. This is intended to be used as a FROM clause in the SQL query. """ return "" @cached_classproperty def all_db_fields(cls) -> set[str]: return cls._fields.keys() | cls._relation._fields.keys() @cached_classproperty def shared_db_fields(cls) -> set[str]: return cls._fields.keys() & cls._relation._fields.keys() @cached_classproperty def other_db_fields(cls) -> set[str]: """Fields in the related table.""" return cls._relation._fields.keys() - cls.shared_db_fields @classmethod def _getters(cls: Type["Model"]): """Return a mapping from field names to getter functions.""" # We could cache this if it becomes a performance problem to # gather the getter mapping every time. raise NotImplementedError() def _template_funcs(self) -> Mapping[str, Callable[[str], str]]: """Return a mapping from function names to text-transformer functions. """ # As above: we could consider caching this result. raise NotImplementedError() # Basic operation. def __init__(self, db: Optional[Database] = None, **values): """Create a new object with an optional Database association and initial field values. """ self._db = db self._dirty: set[str] = set() self._values_fixed = LazyConvertDict(self) self._values_flex = LazyConvertDict(self) # Initial contents. self.update(values) self.clear_dirty() @classmethod def _awaken( cls: Type[AnyModel], db: Optional[Database] = None, fixed_values: Dict[str, Any] = {}, flex_values: Dict[str, Any] = {}, ) -> AnyModel: """Create an object with values drawn from the database. This is a performance optimization: the checks involved with ordinary construction are bypassed. """ obj = cls(db) obj._values_fixed.init(fixed_values) obj._values_flex.init(flex_values) return obj def __repr__(self) -> str: return "{}({})".format( type(self).__name__, ", ".join(f"{k}={v!r}" for k, v in dict(self).items()), ) def clear_dirty(self): """Mark all fields as *clean* (i.e., not needing to be stored to the database). Also update the revision. """ self._dirty = set() if self._db: self._revision = self._db.revision def _check_db(self, need_id: bool = True) -> Database: """Ensure that this object is associated with a database row: it has a reference to a database (`_db`) and an id. A ValueError exception is raised otherwise. """ if not self._db: raise ValueError("{} has no database".format(type(self).__name__)) if need_id and not self.id: raise ValueError("{} has no id".format(type(self).__name__)) return self._db def copy(self) -> "Model": """Create a copy of the model object. The field values and other state is duplicated, but the new copy remains associated with the same database as the old object. (A simple `copy.deepcopy` will not work because it would try to duplicate the SQLite connection.) """ new = self.__class__() new._db = self._db new._values_fixed = self._values_fixed.copy() new._values_flex = self._values_flex.copy() new._dirty = self._dirty.copy() return new # Essential field accessors. @classmethod def _type(cls, key) -> types.Type: """Get the type of a field, a `Type` instance. If the field has no explicit type, it is given the base `Type`, which does no conversion. """ return cls._fields.get(key) or cls._types.get(key) or types.DEFAULT def _get(self, key, default: Any = None, raise_: bool = False): """Get the value for a field, or `default`. Alternatively, raise a KeyError if the field is not available. """ getters = self._getters() if key in getters: # Computed. return getters[key](self) elif key in self._fields: # Fixed. if key in self._values_fixed: return self._values_fixed[key] else: return self._type(key).null elif key in self._values_flex: # Flexible. return self._values_flex[key] elif raise_: raise KeyError(key) else: return default get = _get def __getitem__(self, key): """Get the value for a field. Raise a KeyError if the field is not available. """ return self._get(key, raise_=True) def _setitem(self, key, value): """Assign the value for a field, return whether new and old value differ. """ # Choose where to place the value. if key in self._fields: source = self._values_fixed else: source = self._values_flex # If the field has a type, filter the value. value = self._type(key).normalize(value) # Assign value and possibly mark as dirty. old_value = source.get(key) source[key] = value changed = old_value != value if self._always_dirty or changed: self._dirty.add(key) return changed def __setitem__(self, key, value): """Assign the value for a field.""" self._setitem(key, value) def __delitem__(self, key): """Remove a flexible attribute from the model.""" if key in self._values_flex: # Flexible. del self._values_flex[key] self._dirty.add(key) # Mark for dropping on store. elif key in self._fields: # Fixed setattr(self, key, self._type(key).null) elif key in self._getters(): # Computed. raise KeyError(f"computed field {key} cannot be deleted") else: raise KeyError(f"no such field {key}") def keys(self, computed: bool = False): """Get a list of available field names for this object. The `computed` parameter controls whether computed (plugin-provided) fields are included in the key list. """ base_keys = list(self._fields) + list(self._values_flex.keys()) if computed: return base_keys + list(self._getters().keys()) else: return base_keys @classmethod def all_keys(cls): """Get a list of available keys for objects of this type. Includes fixed and computed fields. """ return list(cls._fields) + list(cls._getters().keys()) # Act like a dictionary. def update(self, values): """Assign all values in the given dict.""" for key, value in values.items(): self[key] = value def items(self) -> Iterator[Tuple[str, Any]]: """Iterate over (key, value) pairs that this object contains. Computed fields are not included. """ for key in self: yield key, self[key] def __contains__(self, key) -> bool: """Determine whether `key` is an attribute on this object.""" return key in self.keys(computed=True) def __iter__(self) -> Iterator[str]: """Iterate over the available field names (excluding computed fields). """ return iter(self.keys()) # Convenient attribute access. def __getattr__(self, key): if key.startswith("_"): raise AttributeError(f"model has no attribute {key!r}") else: try: return self[key] except KeyError: raise AttributeError(f"no such field {key!r}") def __setattr__(self, key, value): if key.startswith("_"): super().__setattr__(key, value) else: self[key] = value def __delattr__(self, key): if key.startswith("_"): super().__delattr__(key) else: del self[key] # Database interaction (CRUD methods). def store(self, fields: Optional[Iterable[str]] = None): """Save the object's metadata into the library database. :param fields: the fields to be stored. If not specified, all fields will be. """ if fields is None: fields = self._fields db = self._check_db() # Build assignments for query. assignments = [] subvars = [] for key in fields: if key != "id" and key in self._dirty: self._dirty.remove(key) assignments.append(key + "=?") value = self._type(key).to_sql(self[key]) subvars.append(value) with db.transaction() as tx: # Main table update. if assignments: query = "UPDATE {} SET {} WHERE id=?".format( self._table, ",".join(assignments) ) subvars.append(self.id) tx.mutate(query, subvars) # Modified/added flexible attributes. for key, value in self._values_flex.items(): if key in self._dirty: self._dirty.remove(key) tx.mutate( "INSERT INTO {} " "(entity_id, key, value) " "VALUES (?, ?, ?);".format(self._flex_table), (self.id, key, value), ) # Deleted flexible attributes. for key in self._dirty: tx.mutate( f"DELETE FROM {self._flex_table} WHERE entity_id=? AND key=?", (self.id, key), ) self.clear_dirty() def load(self): """Refresh the object's metadata from the library database. If check_revision is true, the database is only queried loaded when a transaction has been committed since the item was last loaded. """ db = self._check_db() if not self._dirty and db.revision == self._revision: # Exit early return stored_obj = db._get(type(self), self.id) assert stored_obj is not None, f"object {self.id} not in DB" self._values_fixed = LazyConvertDict(self) self._values_flex = LazyConvertDict(self) self.update(dict(stored_obj)) self.clear_dirty() def remove(self): """Remove the object's associated rows from the database.""" db = self._check_db() with db.transaction() as tx: tx.mutate(f"DELETE FROM {self._table} WHERE id=?", (self.id,)) tx.mutate( f"DELETE FROM {self._flex_table} WHERE entity_id=?", (self.id,) ) def add(self, db: Optional["Database"] = None): """Add the object to the library database. This object must be associated with a database; you can provide one via the `db` parameter or use the currently associated database. The object's `id` and `added` fields are set along with any current field values. """ if db: self._db = db db = self._check_db(False) with db.transaction() as tx: new_id = tx.mutate(f"INSERT INTO {self._table} DEFAULT VALUES") self.id = new_id self.added = time.time() # Mark every non-null field as dirty and store. for key in self: if self[key] is not None: self._dirty.add(key) self.store() # Formatting and templating. _formatter = FormattedMapping def formatted( self, included_keys: str = _formatter.ALL_KEYS, for_path: bool = False, ): """Get a mapping containing all values on this object formatted as human-readable unicode strings. """ return self._formatter(self, included_keys, for_path) def evaluate_template( self, template: Union[str, functemplate.Template], for_path: bool = False, ) -> str: """Evaluate a template (a string or a `Template` object) using the object's fields. If `for_path` is true, then no new path separators will be added to the template. """ # Perform substitution. if isinstance(template, str): t = functemplate.template(template) else: # Help out mypy t = template return t.substitute( self.formatted(for_path=for_path), self._template_funcs() ) # Parsing. @classmethod def _parse(cls, key, string: str) -> Any: """Parse a string as a value for the given key.""" if not isinstance(string, str): raise TypeError("_parse() argument must be a string") return cls._type(key).parse(string) def set_parse(self, key, string: str): """Set the object's key to a value represented by a string.""" self[key] = self._parse(key, string) # Convenient queries. @classmethod def field_query( cls, field, pattern, query_cls: Type[FieldQuery] = MatchQuery, ) -> FieldQuery: """Get a `FieldQuery` for this model.""" return query_cls(field, pattern, field in cls._fields) @classmethod def all_fields_query( cls: Type["Model"], pats: Mapping, query_cls: Type[FieldQuery] = MatchQuery, ): """Get a query that matches many fields with different patterns. `pats` should be a mapping from field names to patterns. The resulting query is a conjunction ("and") of per-field queries for all of these field/pattern pairs. """ subqueries = [cls.field_query(k, v, query_cls) for k, v in pats.items()] return AndQuery(subqueries) # Database controller and supporting interfaces. AnyModel = TypeVar("AnyModel", bound=Model) class Results(Generic[AnyModel]): """An item query result set. Iterating over the collection lazily constructs Model objects that reflect database rows. """ def __init__( self, model_class: Type[AnyModel], rows: List[Mapping], db: "Database", flex_rows, query: Optional[Query] = None, sort=None, ): """Create a result set that will construct objects of type `model_class`. `model_class` is a subclass of `Model` that will be constructed. `rows` is a query result: a list of mappings. The new objects will be associated with the database `db`. If `query` is provided, it is used as a predicate to filter the results for a "slow query" that cannot be evaluated by the database directly. If `sort` is provided, it is used to sort the full list of results before returning. This means it is a "slow sort" and all objects must be built before returning the first one. """ self.model_class = model_class self.rows = rows self.db = db self.query = query self.sort = sort self.flex_rows = flex_rows # We keep a queue of rows we haven't yet consumed for # materialization. We preserve the original total number of # rows. self._rows = rows self._row_count = len(rows) # The materialized objects corresponding to rows that have been # consumed. self._objects: List[AnyModel] = [] def _get_objects(self) -> Iterator[AnyModel]: """Construct and generate Model objects for they query. The objects are returned in the order emitted from the database; no slow sort is applied. For performance, this generator caches materialized objects to avoid constructing them more than once. This way, iterating over a `Results` object a second time should be much faster than the first. """ # Index flexible attributes by the item ID, so we have easier access flex_attrs = self._get_indexed_flex_attrs() index = 0 # Position in the materialized objects. while index < len(self._objects) or self._rows: # Are there previously-materialized objects to produce? if index < len(self._objects): yield self._objects[index] index += 1 # Otherwise, we consume another row, materialize its object # and produce it. else: while self._rows: row = self._rows.pop(0) obj = self._make_model(row, flex_attrs.get(row["id"], {})) # If there is a slow-query predicate, ensurer that the # object passes it. if not self.query or self.query.match(obj): self._objects.append(obj) index += 1 yield obj break def __iter__(self) -> Iterator[AnyModel]: """Construct and generate Model objects for all matching objects, in sorted order. """ if self.sort: # Slow sort. Must build the full list first. objects = self.sort.sort(list(self._get_objects())) return iter(objects) else: # Objects are pre-sorted (i.e., by the database). return self._get_objects() def _get_indexed_flex_attrs(self) -> Mapping: """Index flexible attributes by the entity id they belong to""" flex_values: Dict[int, Dict[str, Any]] = {} for row in self.flex_rows: if row["entity_id"] not in flex_values: flex_values[row["entity_id"]] = {} flex_values[row["entity_id"]][row["key"]] = row["value"] return flex_values def _make_model(self, row, flex_values: Dict = {}) -> AnyModel: """Create a Model object for the given row""" cols = dict(row) values = {k: v for (k, v) in cols.items() if not k[:4] == "flex"} # Construct the Python object obj = self.model_class._awaken(self.db, values, flex_values) return obj def __len__(self) -> int: """Get the number of matching objects.""" if not self._rows: # Fully materialized. Just count the objects. return len(self._objects) elif self.query: # A slow query. Fall back to testing every object. count = 0 for obj in self: count += 1 return count else: # A fast query. Just count the rows. return self._row_count def __nonzero__(self) -> bool: """Does this result contain any objects?""" return self.__bool__() def __bool__(self) -> bool: """Does this result contain any objects?""" return bool(len(self)) def __getitem__(self, n): """Get the nth item in this result set. This is inefficient: all items up to n are materialized and thrown away. """ if not self._rows and not self.sort: # Fully materialized and already in order. Just look up the # object. return self._objects[n] it = iter(self) try: for i in range(n): next(it) return next(it) except StopIteration: raise IndexError(f"result index {n} out of range") def get(self) -> Optional[AnyModel]: """Return the first matching object, or None if no objects match. """ it = iter(self) try: return next(it) except StopIteration: return None class Transaction: """A context manager for safe, concurrent access to the database. All SQL commands should be executed through a transaction. """ _mutated = False """A flag storing whether a mutation has been executed in the current transaction. """ def __init__(self, db: "Database"): self.db = db def __enter__(self) -> "Transaction": """Begin a transaction. This transaction may be created while another is active in a different thread. """ with self.db._tx_stack() as stack: first = not stack stack.append(self) if first: # Beginning a "root" transaction, which corresponds to an # SQLite transaction. self.db._db_lock.acquire() return self def __exit__( self, exc_type: Type[Exception], exc_value: Exception, traceback: TracebackType, ): """Complete a transaction. This must be the most recently entered but not yet exited transaction. If it is the last active transaction, the database updates are committed. """ # Beware of races; currently secured by db._db_lock self.db.revision += self._mutated with self.db._tx_stack() as stack: assert stack.pop() is self empty = not stack if empty: # Ending a "root" transaction. End the SQLite transaction. self.db._connection().commit() self._mutated = False self.db._db_lock.release() def query(self, statement: str, subvals: Sequence = ()) -> List: """Execute an SQL statement with substitution values and return a list of rows from the database. """ cursor = self.db._connection().execute(statement, subvals) return cursor.fetchall() def mutate(self, statement: str, subvals: Sequence = ()) -> Any: """Execute an SQL statement with substitution values and return the row ID of the last affected row. """ try: cursor = self.db._connection().execute(statement, subvals) except sqlite3.OperationalError as e: # In two specific cases, SQLite reports an error while accessing # the underlying database file. We surface these exceptions as # DBAccessError so the application can abort. if e.args[0] in ( "attempt to write a readonly database", "unable to open database file", ): raise DBAccessError(e.args[0]) else: raise else: self._mutated = True return cursor.lastrowid def script(self, statements: str): """Execute a string containing multiple SQL statements.""" # We don't know whether this mutates, but quite likely it does. self._mutated = True self.db._connection().executescript(statements) class Database: """A container for Model objects that wraps an SQLite database as the backend. """ _models: Sequence[Type[Model]] = () """The Model subclasses representing tables in this database. """ supports_extensions = hasattr(sqlite3.Connection, "enable_load_extension") """Whether or not the current version of SQLite supports extensions""" revision = 0 """The current revision of the database. To be increased whenever data is written in a transaction. """ def __init__(self, path, timeout: float = 5.0): if sqlite3.threadsafety == 0: raise RuntimeError( "sqlite3 must be compiled with multi-threading support" ) self.path = path self.timeout = timeout self._connections: Dict[int, sqlite3.Connection] = {} self._tx_stacks: DefaultDict[int, List[Transaction]] = defaultdict(list) self._extensions: List[str] = [] # A lock to protect the _connections and _tx_stacks maps, which # both map thread IDs to private resources. self._shared_map_lock = threading.Lock() # A lock to protect access to the database itself. SQLite does # allow multiple threads to access the database at the same # time, but many users were experiencing crashes related to this # capability: where SQLite was compiled without HAVE_USLEEP, its # backoff algorithm in the case of contention was causing # whole-second sleeps (!) that would trigger its internal # timeout. Using this lock ensures only one SQLite transaction # is active at a time. self._db_lock = threading.Lock() # Set up database schema. for model_cls in self._models: self._make_table(model_cls._table, model_cls._fields) self._make_attribute_table(model_cls._flex_table) # Primitive access control: connections and transactions. def _connection(self) -> Connection: """Get a SQLite connection object to the underlying database. One connection object is created per thread. """ thread_id = threading.current_thread().ident # Help the type checker: ident can only be None if the thread has not # been started yet; but since this results from current_thread(), that # can't happen assert thread_id is not None with self._shared_map_lock: if thread_id in self._connections: return self._connections[thread_id] else: conn = self._create_connection() self._connections[thread_id] = conn return conn def _create_connection(self) -> Connection: """Create a SQLite connection to the underlying database. Makes a new connection every time. If you need to configure the connection settings (e.g., add custom functions), override this method. """ # Make a new connection. The `sqlite3` module can't use # bytestring paths here on Python 3, so we need to # provide a `str` using `os.fsdecode`. conn = sqlite3.connect( os.fsdecode(self.path), timeout=self.timeout, # We have our own same-thread checks in _connection(), but need to # call conn.close() in _close() check_same_thread=False, ) self.add_functions(conn) if self.supports_extensions: conn.enable_load_extension(True) # Load any extension that are already loaded for other connections. for path in self._extensions: conn.load_extension(path) # Access SELECT results like dictionaries. conn.row_factory = sqlite3.Row return conn def add_functions(self, conn): def regexp(value, pattern): if isinstance(value, bytes): value = value.decode() return re.search(pattern, str(value)) is not None def bytelower(bytestring: Optional[AnyStr]) -> Optional[AnyStr]: """A custom ``bytelower`` sqlite function so we can compare bytestrings in a semi case insensitive fashion. This is to work around sqlite builds are that compiled with ``-DSQLITE_LIKE_DOESNT_MATCH_BLOBS``. See ``https://github.com/beetbox/beets/issues/2172`` for details. """ if bytestring is not None: return bytestring.lower() return bytestring conn.create_function("regexp", 2, regexp) conn.create_function("unidecode", 1, unidecode) conn.create_function("bytelower", 1, bytelower) def _close(self): """Close the all connections to the underlying SQLite database from all threads. This does not render the database object unusable; new connections can still be opened on demand. """ with self._shared_map_lock: while self._connections: _thread_id, conn = self._connections.popitem() conn.close() @contextlib.contextmanager def _tx_stack(self) -> Generator[List, None, None]: """A context manager providing access to the current thread's transaction stack. The context manager synchronizes access to the stack map. Transactions should never migrate across threads. """ thread_id = threading.current_thread().ident # Help the type checker: ident can only be None if the thread has not # been started yet; but since this results from current_thread(), that # can't happen assert thread_id is not None with self._shared_map_lock: yield self._tx_stacks[thread_id] def transaction(self) -> Transaction: """Get a :class:`Transaction` object for interacting directly with the underlying SQLite database. """ return Transaction(self) def load_extension(self, path: str): """Load an SQLite extension into all open connections.""" if not self.supports_extensions: raise ValueError( "this sqlite3 installation does not support extensions" ) self._extensions.append(path) # Load the extension into every open connection. for conn in self._connections.values(): conn.load_extension(path) # Schema setup and migration. def _make_table(self, table: str, fields: Mapping[str, types.Type]): """Set up the schema of the database. `fields` is a mapping from field names to `Type`s. Columns are added if necessary. """ # Get current schema. with self.transaction() as tx: rows = tx.query("PRAGMA table_info(%s)" % table) current_fields = {row[1] for row in rows} field_names = set(fields.keys()) if current_fields.issuperset(field_names): # Table exists and has all the required columns. return if not current_fields: # No table exists. columns = [] for name, typ in fields.items(): columns.append(f"{name} {typ.sql}") setup_sql = "CREATE TABLE {} ({});\n".format( table, ", ".join(columns) ) else: # Table exists does not match the field set. setup_sql = "" for name, typ in fields.items(): if name in current_fields: continue setup_sql += "ALTER TABLE {} ADD COLUMN {} {};\n".format( table, name, typ.sql ) with self.transaction() as tx: tx.script(setup_sql) def _make_attribute_table(self, flex_table: str): """Create a table and associated index for flexible attributes for the given entity (if they don't exist). """ with self.transaction() as tx: tx.script( """ CREATE TABLE IF NOT EXISTS {0} ( id INTEGER PRIMARY KEY, entity_id INTEGER, key TEXT, value TEXT, UNIQUE(entity_id, key) ON CONFLICT REPLACE); CREATE INDEX IF NOT EXISTS {0}_by_entity ON {0} (entity_id); """.format(flex_table) ) # Querying. def _fetch( self, model_cls: Type[AnyModel], query: Optional[Query] = None, sort: Optional[Sort] = None, ) -> Results[AnyModel]: """Fetch the objects of type `model_cls` matching the given query. The query may be given as a string, string sequence, a Query object, or None (to fetch everything). `sort` is an `Sort` object. """ query = query or TrueQuery() # A null query. sort = sort or NullSort() # Unsorted. where, subvals = query.clause() order_by = sort.order_clause() table = model_cls._table _from = table if query.field_names & model_cls.other_db_fields: _from += f" {model_cls.relation_join}" # group by id to avoid duplicates when joining with the relation sql = ( f"SELECT {table}.* " f"FROM ({_from}) " f"WHERE {where or 1} " f"GROUP BY {table}.id" ) # Fetch flexible attributes for items matching the main query. # Doing the per-item filtering in python is faster than issuing # one query per item to sqlite. flex_sql = ( "SELECT * " f"FROM {model_cls._flex_table} " f"WHERE entity_id IN (SELECT id FROM ({sql}))" ) if order_by: # the sort field may exist in both 'items' and 'albums' tables # (when they are joined), causing ambiguous column OperationalError # if we try to order directly. # Since the join is required only for filtering, we can filter in # a subquery and order the result, which returns unique fields. sql = f"SELECT * FROM ({sql}) ORDER BY {order_by}" with self.transaction() as tx: rows = tx.query(sql, subvals) flex_rows = tx.query(flex_sql, subvals) return Results( model_cls, rows, self, flex_rows, None if where else query, # Slow query component. sort if sort.is_slow() else None, # Slow sort component. ) def _get( self, model_cls: Type[AnyModel], id, ) -> Optional[AnyModel]: """Get a Model object by its id or None if the id does not exist. """ return self._fetch(model_cls, MatchQuery("id", id)).get() beetbox-beets-01f1faf/beets/dbcore/query.py000066400000000000000000001020001472325477400210170ustar00rootroot00000000000000# This file is part of beets. # Copyright 2016, Adrian Sampson. # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the # "Software"), to deal in the Software without restriction, including # without limitation the rights to use, copy, modify, merge, publish, # distribute, sublicense, and/or sell copies of the Software, and to # permit persons to whom the Software is furnished to do so, subject to # the following conditions: # # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. """The Query type hierarchy for DBCore.""" from __future__ import annotations import re import unicodedata from abc import ABC, abstractmethod from datetime import datetime, timedelta from functools import reduce from operator import mul, or_ from typing import ( TYPE_CHECKING, Any, Collection, Generic, Iterator, List, MutableSequence, Optional, Pattern, Sequence, Set, Tuple, Type, TypeVar, Union, ) from beets import util if TYPE_CHECKING: from beets.dbcore import Model class ParsingError(ValueError): """Abstract class for any unparsable user-requested album/query specification. """ class InvalidQueryError(ParsingError): """Represent any kind of invalid query. The query should be a unicode string or a list, which will be space-joined. """ def __init__(self, query, explanation): if isinstance(query, list): query = " ".join(query) message = f"'{query}': {explanation}" super().__init__(message) class InvalidQueryArgumentValueError(ParsingError): """Represent a query argument that could not be converted as expected. It exists to be caught in upper stack levels so a meaningful (i.e. with the query) InvalidQueryError can be raised. """ def __init__(self, what, expected, detail=None): message = f"'{what}' is not {expected}" if detail: message = f"{message}: {detail}" super().__init__(message) class Query(ABC): """An abstract class representing a query into the database.""" @property def field_names(self) -> Set[str]: """Return a set with field names that this query operates on.""" return set() def clause(self) -> Tuple[Optional[str], Sequence[Any]]: """Generate an SQLite expression implementing the query. Return (clause, subvals) where clause is a valid sqlite WHERE clause implementing the query and subvals is a list of items to be substituted for ?s in the clause. The default implementation returns None, falling back to a slow query using `match()`. """ return None, () @abstractmethod def match(self, obj: Model): """Check whether this query matches a given Model. Can be used to perform queries on arbitrary sets of Model. """ ... def __repr__(self) -> str: return f"{self.__class__.__name__}()" def __eq__(self, other) -> bool: return type(self) is type(other) def __hash__(self) -> int: """Minimalistic default implementation of a hash. Given the implementation if __eq__ above, this is certainly correct. """ return hash(type(self)) P = TypeVar("P") SQLiteType = Union[str, bytes, float, int, memoryview] AnySQLiteType = TypeVar("AnySQLiteType", bound=SQLiteType) class FieldQuery(Query, Generic[P]): """An abstract query that searches in a specific field for a pattern. Subclasses must provide a `value_match` class method, which determines whether a certain pattern string matches a certain value string. Subclasses may also provide `col_clause` to implement the same matching functionality in SQLite. """ @property def field(self) -> str: return ( f"{self.table}.{self.field_name}" if self.table else self.field_name ) @property def field_names(self) -> Set[str]: """Return a set with field names that this query operates on.""" return {self.field_name} def __init__(self, field_name: str, pattern: P, fast: bool = True): self.table, _, self.field_name = field_name.rpartition(".") self.pattern = pattern self.fast = fast def col_clause(self) -> Tuple[str, Sequence[SQLiteType]]: return self.field, () def clause(self) -> Tuple[Optional[str], Sequence[SQLiteType]]: if self.fast: return self.col_clause() else: # Matching a flexattr. This is a slow query. return None, () @classmethod def value_match(cls, pattern: P, value: Any): """Determine whether the value matches the pattern.""" raise NotImplementedError() def match(self, obj: Model) -> bool: return self.value_match(self.pattern, obj.get(self.field_name)) def __repr__(self) -> str: return ( f"{self.__class__.__name__}({self.field_name!r}, {self.pattern!r}, " f"fast={self.fast})" ) def __eq__(self, other) -> bool: return ( super().__eq__(other) and self.field_name == other.field_name and self.pattern == other.pattern ) def __hash__(self) -> int: return hash((self.field_name, hash(self.pattern))) class MatchQuery(FieldQuery[AnySQLiteType]): """A query that looks for exact matches in an Model field.""" def col_clause(self) -> Tuple[str, Sequence[SQLiteType]]: return self.field + " = ?", [self.pattern] @classmethod def value_match(cls, pattern: AnySQLiteType, value: Any) -> bool: return pattern == value class NoneQuery(FieldQuery[None]): """A query that checks whether a field is null.""" def __init__(self, field, fast: bool = True): super().__init__(field, None, fast) def col_clause(self) -> Tuple[str, Sequence[SQLiteType]]: return self.field + " IS NULL", () def match(self, obj: Model) -> bool: return obj.get(self.field_name) is None def __repr__(self) -> str: return f"{self.__class__.__name__}({self.field_name!r}, {self.fast})" class StringFieldQuery(FieldQuery[P]): """A FieldQuery that converts values to strings before matching them. """ @classmethod def value_match(cls, pattern: P, value: Any): """Determine whether the value matches the pattern. The value may have any type. """ return cls.string_match(pattern, util.as_string(value)) @classmethod def string_match( cls, pattern: P, value: str, ) -> bool: """Determine whether the value matches the pattern. Both arguments are strings. Subclasses implement this method. """ raise NotImplementedError() class StringQuery(StringFieldQuery[str]): """A query that matches a whole string in a specific Model field.""" def col_clause(self) -> Tuple[str, Sequence[SQLiteType]]: search = ( self.pattern.replace("\\", "\\\\") .replace("%", "\\%") .replace("_", "\\_") ) clause = self.field + " like ? escape '\\'" subvals = [search] return clause, subvals @classmethod def string_match(cls, pattern: str, value: str) -> bool: return pattern.lower() == value.lower() class SubstringQuery(StringFieldQuery[str]): """A query that matches a substring in a specific Model field.""" def col_clause(self) -> Tuple[str, Sequence[SQLiteType]]: pattern = ( self.pattern.replace("\\", "\\\\") .replace("%", "\\%") .replace("_", "\\_") ) search = "%" + pattern + "%" clause = self.field + " like ? escape '\\'" subvals = [search] return clause, subvals @classmethod def string_match(cls, pattern: str, value: str) -> bool: return pattern.lower() in value.lower() class RegexpQuery(StringFieldQuery[Pattern[str]]): """A query that matches a regular expression in a specific Model field. Raises InvalidQueryError when the pattern is not a valid regular expression. """ def __init__(self, field_name: str, pattern: str, fast: bool = True): pattern = self._normalize(pattern) try: pattern_re = re.compile(pattern) except re.error as exc: # Invalid regular expression. raise InvalidQueryArgumentValueError( pattern, "a regular expression", format(exc) ) super().__init__(field_name, pattern_re, fast) def col_clause(self) -> Tuple[str, Sequence[SQLiteType]]: return f" regexp({self.field}, ?)", [self.pattern.pattern] @staticmethod def _normalize(s: str) -> str: """Normalize a Unicode string's representation (used on both patterns and matched values). """ return unicodedata.normalize("NFC", s) @classmethod def string_match(cls, pattern: Pattern, value: str) -> bool: return pattern.search(cls._normalize(value)) is not None class BooleanQuery(MatchQuery[int]): """Matches a boolean field. Pattern should either be a boolean or a string reflecting a boolean. """ def __init__( self, field_name: str, pattern: bool, fast: bool = True, ): if isinstance(pattern, str): pattern = util.str2bool(pattern) pattern_int = int(pattern) super().__init__(field_name, pattern_int, fast) class BytesQuery(FieldQuery[bytes]): """Match a raw bytes field (i.e., a path). This is a necessary hack to work around the `sqlite3` module's desire to treat `bytes` and `unicode` equivalently in Python 2. Always use this query instead of `MatchQuery` when matching on BLOB values. """ def __init__(self, field_name: str, pattern: Union[bytes, str, memoryview]): # Use a buffer/memoryview representation of the pattern for SQLite # matching. This instructs SQLite to treat the blob as binary # rather than encoded Unicode. if isinstance(pattern, (str, bytes)): if isinstance(pattern, str): bytes_pattern = pattern.encode("utf-8") else: bytes_pattern = pattern self.buf_pattern = memoryview(bytes_pattern) elif isinstance(pattern, memoryview): self.buf_pattern = pattern bytes_pattern = bytes(pattern) else: raise ValueError("pattern must be bytes, str, or memoryview") super().__init__(field_name, bytes_pattern) def col_clause(self) -> Tuple[str, Sequence[SQLiteType]]: return self.field + " = ?", [self.buf_pattern] @classmethod def value_match(cls, pattern: bytes, value: Any) -> bool: return pattern == value class NumericQuery(FieldQuery[str]): """Matches numeric fields. A syntax using Ruby-style range ellipses (``..``) lets users specify one- or two-sided ranges. For example, ``year:2001..`` finds music released since the turn of the century. Raises InvalidQueryError when the pattern does not represent an int or a float. """ def _convert(self, s: str) -> Union[float, int, None]: """Convert a string to a numeric type (float or int). Return None if `s` is empty. Raise an InvalidQueryError if the string cannot be converted. """ # This is really just a bit of fun premature optimization. if not s: return None try: return int(s) except ValueError: try: return float(s) except ValueError: raise InvalidQueryArgumentValueError(s, "an int or a float") def __init__(self, field_name: str, pattern: str, fast: bool = True): super().__init__(field_name, pattern, fast) parts = pattern.split("..", 1) if len(parts) == 1: # No range. self.point = self._convert(parts[0]) self.rangemin = None self.rangemax = None else: # One- or two-sided range. self.point = None self.rangemin = self._convert(parts[0]) self.rangemax = self._convert(parts[1]) def match(self, obj: Model) -> bool: if self.field_name not in obj: return False value = obj[self.field_name] if isinstance(value, str): value = self._convert(value) if self.point is not None: return value == self.point else: if self.rangemin is not None and value < self.rangemin: return False if self.rangemax is not None and value > self.rangemax: return False return True def col_clause(self) -> Tuple[str, Sequence[SQLiteType]]: if self.point is not None: return self.field + "=?", (self.point,) else: if self.rangemin is not None and self.rangemax is not None: return ( "{0} >= ? AND {0} <= ?".format(self.field), (self.rangemin, self.rangemax), ) elif self.rangemin is not None: return f"{self.field} >= ?", (self.rangemin,) elif self.rangemax is not None: return f"{self.field} <= ?", (self.rangemax,) else: return "1", () class InQuery(Generic[AnySQLiteType], FieldQuery[Sequence[AnySQLiteType]]): """Query which matches values in the given set.""" field_name: str pattern: Sequence[AnySQLiteType] fast: bool = True @property def subvals(self) -> Sequence[SQLiteType]: return self.pattern def col_clause(self) -> Tuple[str, Sequence[SQLiteType]]: placeholders = ", ".join(["?"] * len(self.subvals)) return f"{self.field_name} IN ({placeholders})", self.subvals @classmethod def value_match( cls, pattern: Sequence[AnySQLiteType], value: AnySQLiteType ) -> bool: return value in pattern class CollectionQuery(Query): """An abstract query class that aggregates other queries. Can be indexed like a list to access the sub-queries. """ @property def field_names(self) -> Set[str]: """Return a set with field names that this query operates on.""" return reduce(or_, (sq.field_names for sq in self.subqueries)) def __init__(self, subqueries: Sequence = ()): self.subqueries = subqueries # Act like a sequence. def __len__(self) -> int: return len(self.subqueries) def __getitem__(self, key): return self.subqueries[key] def __iter__(self) -> Iterator: return iter(self.subqueries) def __contains__(self, subq) -> bool: return subq in self.subqueries def clause_with_joiner( self, joiner: str, ) -> Tuple[Optional[str], Sequence[SQLiteType]]: """Return a clause created by joining together the clauses of all subqueries with the string joiner (padded by spaces). """ clause_parts = [] subvals = [] for subq in self.subqueries: subq_clause, subq_subvals = subq.clause() if not subq_clause: # Fall back to slow query. return None, () clause_parts.append("(" + subq_clause + ")") subvals += subq_subvals clause = (" " + joiner + " ").join(clause_parts) return clause, subvals def __repr__(self) -> str: return f"{self.__class__.__name__}({self.subqueries!r})" def __eq__(self, other) -> bool: return super().__eq__(other) and self.subqueries == other.subqueries def __hash__(self) -> int: """Since subqueries are mutable, this object should not be hashable. However and for conveniences purposes, it can be hashed. """ return reduce(mul, map(hash, self.subqueries), 1) class AnyFieldQuery(CollectionQuery): """A query that matches if a given FieldQuery subclass matches in any field. The individual field query class is provided to the constructor. """ @property def field_names(self) -> Set[str]: """Return a set with field names that this query operates on.""" return set(self.fields) def __init__(self, pattern, fields, cls: Type[FieldQuery]): self.pattern = pattern self.fields = fields self.query_class = cls subqueries = [] for field in self.fields: subqueries.append(cls(field, pattern, True)) # TYPING ERROR super().__init__(subqueries) def clause(self) -> Tuple[Optional[str], Sequence[SQLiteType]]: return self.clause_with_joiner("or") def match(self, obj: Model) -> bool: for subq in self.subqueries: if subq.match(obj): return True return False def __repr__(self) -> str: return ( f"{self.__class__.__name__}({self.pattern!r}, {self.fields!r}, " f"{self.query_class.__name__})" ) def __eq__(self, other) -> bool: return super().__eq__(other) and self.query_class == other.query_class def __hash__(self) -> int: return hash((self.pattern, tuple(self.fields), self.query_class)) class MutableCollectionQuery(CollectionQuery): """A collection query whose subqueries may be modified after the query is initialized. """ subqueries: MutableSequence def __setitem__(self, key, value): self.subqueries[key] = value def __delitem__(self, key): del self.subqueries[key] class AndQuery(MutableCollectionQuery): """A conjunction of a list of other queries.""" def clause(self) -> Tuple[Optional[str], Sequence[SQLiteType]]: return self.clause_with_joiner("and") def match(self, obj: Model) -> bool: return all(q.match(obj) for q in self.subqueries) class OrQuery(MutableCollectionQuery): """A conjunction of a list of other queries.""" def clause(self) -> Tuple[Optional[str], Sequence[SQLiteType]]: return self.clause_with_joiner("or") def match(self, obj: Model) -> bool: return any(q.match(obj) for q in self.subqueries) class NotQuery(Query): """A query that matches the negation of its `subquery`, as a shortcut for performing `not(subquery)` without using regular expressions. """ @property def field_names(self) -> Set[str]: """Return a set with field names that this query operates on.""" return self.subquery.field_names def __init__(self, subquery): self.subquery = subquery def clause(self) -> Tuple[Optional[str], Sequence[SQLiteType]]: clause, subvals = self.subquery.clause() if clause: return f"not ({clause})", subvals else: # If there is no clause, there is nothing to negate. All the logic # is handled by match() for slow queries. return clause, subvals def match(self, obj: Model) -> bool: return not self.subquery.match(obj) def __repr__(self) -> str: return f"{self.__class__.__name__}({self.subquery!r})" def __eq__(self, other) -> bool: return super().__eq__(other) and self.subquery == other.subquery def __hash__(self) -> int: return hash(("not", hash(self.subquery))) class TrueQuery(Query): """A query that always matches.""" def clause(self) -> Tuple[str, Sequence[SQLiteType]]: return "1", () def match(self, obj: Model) -> bool: return True class FalseQuery(Query): """A query that never matches.""" def clause(self) -> Tuple[str, Sequence[SQLiteType]]: return "0", () def match(self, obj: Model) -> bool: return False # Time/date queries. def _parse_periods(pattern: str) -> Tuple[Optional[Period], Optional[Period]]: """Parse a string containing two dates separated by two dots (..). Return a pair of `Period` objects. """ parts = pattern.split("..", 1) if len(parts) == 1: instant = Period.parse(parts[0]) return (instant, instant) else: start = Period.parse(parts[0]) end = Period.parse(parts[1]) return (start, end) class Period: """A period of time given by a date, time and precision. Example: 2014-01-01 10:50:30 with precision 'month' represents all instants of time during January 2014. """ precisions = ("year", "month", "day", "hour", "minute", "second") date_formats = ( ("%Y",), # year ("%Y-%m",), # month ("%Y-%m-%d",), # day ("%Y-%m-%dT%H", "%Y-%m-%d %H"), # hour ("%Y-%m-%dT%H:%M", "%Y-%m-%d %H:%M"), # minute ("%Y-%m-%dT%H:%M:%S", "%Y-%m-%d %H:%M:%S"), # second ) relative_units = {"y": 365, "m": 30, "w": 7, "d": 1} relative_re = ( "(?P[+|-]?)(?P[0-9]+)" + "(?P[y|m|w|d])" ) def __init__(self, date: datetime, precision: str): """Create a period with the given date (a `datetime` object) and precision (a string, one of "year", "month", "day", "hour", "minute", or "second"). """ if precision not in Period.precisions: raise ValueError(f"Invalid precision {precision}") self.date = date self.precision = precision @classmethod def parse(cls: Type["Period"], string: str) -> Optional["Period"]: """Parse a date and return a `Period` object or `None` if the string is empty, or raise an InvalidQueryArgumentValueError if the string cannot be parsed to a date. The date may be absolute or relative. Absolute dates look like `YYYY`, or `YYYY-MM-DD`, or `YYYY-MM-DD HH:MM:SS`, etc. Relative dates have three parts: - Optionally, a ``+`` or ``-`` sign indicating the future or the past. The default is the future. - A number: how much to add or subtract. - A letter indicating the unit: days, weeks, months or years (``d``, ``w``, ``m`` or ``y``). A "month" is exactly 30 days and a "year" is exactly 365 days. """ def find_date_and_format( string: str, ) -> Union[Tuple[None, None], Tuple[datetime, int]]: for ord, format in enumerate(cls.date_formats): for format_option in format: try: date = datetime.strptime(string, format_option) return date, ord except ValueError: # Parsing failed. pass return (None, None) if not string: return None date: Optional[datetime] # Check for a relative date. match_dq = re.match(cls.relative_re, string) if match_dq: sign = match_dq.group("sign") quantity = match_dq.group("quantity") timespan = match_dq.group("timespan") # Add or subtract the given amount of time from the current # date. multiplier = -1 if sign == "-" else 1 days = cls.relative_units[timespan] date = ( datetime.now() + timedelta(days=int(quantity) * days) * multiplier ) return cls(date, cls.precisions[5]) # Check for an absolute date. date, ordinal = find_date_and_format(string) if date is None or ordinal is None: raise InvalidQueryArgumentValueError( string, "a valid date/time string" ) precision = cls.precisions[ordinal] return cls(date, precision) def open_right_endpoint(self) -> datetime: """Based on the precision, convert the period to a precise `datetime` for use as a right endpoint in a right-open interval. """ precision = self.precision date = self.date if "year" == self.precision: return date.replace(year=date.year + 1, month=1) elif "month" == precision: if date.month < 12: return date.replace(month=date.month + 1) else: return date.replace(year=date.year + 1, month=1) elif "day" == precision: return date + timedelta(days=1) elif "hour" == precision: return date + timedelta(hours=1) elif "minute" == precision: return date + timedelta(minutes=1) elif "second" == precision: return date + timedelta(seconds=1) else: raise ValueError(f"unhandled precision {precision}") class DateInterval: """A closed-open interval of dates. A left endpoint of None means since the beginning of time. A right endpoint of None means towards infinity. """ def __init__(self, start: Optional[datetime], end: Optional[datetime]): if start is not None and end is not None and not start < end: raise ValueError( "start date {} is not before end date {}".format(start, end) ) self.start = start self.end = end @classmethod def from_periods( cls, start: Optional[Period], end: Optional[Period], ) -> DateInterval: """Create an interval with two Periods as the endpoints.""" end_date = end.open_right_endpoint() if end is not None else None start_date = start.date if start is not None else None return cls(start_date, end_date) def contains(self, date: datetime) -> bool: if self.start is not None and date < self.start: return False if self.end is not None and date >= self.end: return False return True def __str__(self) -> str: return f"[{self.start}, {self.end})" class DateQuery(FieldQuery[str]): """Matches date fields stored as seconds since Unix epoch time. Dates can be specified as ``year-month-day`` strings where only year is mandatory. The value of a date field can be matched against a date interval by using an ellipsis interval syntax similar to that of NumericQuery. """ def __init__(self, field_name: str, pattern: str, fast: bool = True): super().__init__(field_name, pattern, fast) start, end = _parse_periods(pattern) self.interval = DateInterval.from_periods(start, end) def match(self, obj: Model) -> bool: if self.field_name not in obj: return False timestamp = float(obj[self.field_name]) date = datetime.fromtimestamp(timestamp) return self.interval.contains(date) _clause_tmpl = "{0} {1} ?" def col_clause(self) -> Tuple[str, Sequence[SQLiteType]]: clause_parts = [] subvals = [] # Convert the `datetime` objects to an integer number of seconds since # the (local) Unix epoch using `datetime.timestamp()`. if self.interval.start: clause_parts.append(self._clause_tmpl.format(self.field, ">=")) subvals.append(int(self.interval.start.timestamp())) if self.interval.end: clause_parts.append(self._clause_tmpl.format(self.field, "<")) subvals.append(int(self.interval.end.timestamp())) if clause_parts: # One- or two-sided interval. clause = " AND ".join(clause_parts) else: # Match any date. clause = "1" return clause, subvals class DurationQuery(NumericQuery): """NumericQuery that allow human-friendly (M:SS) time interval formats. Converts the range(s) to a float value, and delegates on NumericQuery. Raises InvalidQueryError when the pattern does not represent an int, float or M:SS time interval. """ def _convert(self, s: str) -> Optional[float]: """Convert a M:SS or numeric string to a float. Return None if `s` is empty. Raise an InvalidQueryError if the string cannot be converted. """ if not s: return None try: return util.raw_seconds_short(s) except ValueError: try: return float(s) except ValueError: raise InvalidQueryArgumentValueError( s, "a M:SS string or a float" ) # Sorting. class Sort: """An abstract class representing a sort operation for a query into the database. """ def order_clause(self) -> Optional[str]: """Generates a SQL fragment to be used in a ORDER BY clause, or None if no fragment is used (i.e., this is a slow sort). """ return None def sort(self, items: List) -> List: """Sort the list of objects and return a list.""" return sorted(items) def is_slow(self) -> bool: """Indicate whether this query is *slow*, meaning that it cannot be executed in SQL and must be executed in Python. """ return False def __hash__(self) -> int: return 0 def __eq__(self, other) -> bool: return type(self) is type(other) def __repr__(self): return f"{self.__class__.__name__}()" class MultipleSort(Sort): """Sort that encapsulates multiple sub-sorts.""" def __init__(self, sorts: Optional[List[Sort]] = None): self.sorts = sorts or [] def add_sort(self, sort: Sort): self.sorts.append(sort) def order_clause(self) -> str: """Return the list SQL clauses for those sub-sorts for which we can be (at least partially) fast. A contiguous suffix of fast (SQL-capable) sub-sorts are executable in SQL. The remaining, even if they are fast independently, must be executed slowly. """ order_strings = [] for sort in reversed(self.sorts): clause = sort.order_clause() if clause is None: break order_strings.append(clause) order_strings.reverse() return ", ".join(order_strings) def is_slow(self) -> bool: for sort in self.sorts: if sort.is_slow(): return True return False def sort(self, items): slow_sorts = [] switch_slow = False for sort in reversed(self.sorts): if switch_slow: slow_sorts.append(sort) elif sort.order_clause() is None: switch_slow = True slow_sorts.append(sort) else: pass for sort in slow_sorts: items = sort.sort(items) return items def __repr__(self): return f"{self.__class__.__name__}({self.sorts!r})" def __hash__(self): return hash(tuple(self.sorts)) def __eq__(self, other): return super().__eq__(other) and self.sorts == other.sorts class FieldSort(Sort): """An abstract sort criterion that orders by a specific field (of any kind). """ def __init__( self, field, ascending: bool = True, case_insensitive: bool = True, ): self.field = field self.ascending = ascending self.case_insensitive = case_insensitive def sort(self, objs: Collection): # TODO: Conversion and null-detection here. In Python 3, # comparisons with None fail. We should also support flexible # attributes with different types without falling over. def key(obj: Model) -> Any: field_val = obj.get(self.field, "") if self.case_insensitive and isinstance(field_val, str): field_val = field_val.lower() return field_val return sorted(objs, key=key, reverse=not self.ascending) def __repr__(self) -> str: return ( f"{self.__class__.__name__}" f"({self.field!r}, ascending={self.ascending!r})" ) def __hash__(self) -> int: return hash((self.field, self.ascending)) def __eq__(self, other) -> bool: return ( super().__eq__(other) and self.field == other.field and self.ascending == other.ascending ) class FixedFieldSort(FieldSort): """Sort object to sort on a fixed field.""" def order_clause(self) -> str: order = "ASC" if self.ascending else "DESC" if self.case_insensitive: field = ( "(CASE " "WHEN TYPEOF({0})='text' THEN LOWER({0}) " "WHEN TYPEOF({0})='blob' THEN LOWER({0}) " "ELSE {0} END)".format(self.field) ) else: field = self.field return f"{field} {order}" class SlowFieldSort(FieldSort): """A sort criterion by some model field other than a fixed field: i.e., a computed or flexible field. """ def is_slow(self) -> bool: return True class NullSort(Sort): """No sorting. Leave results unsorted.""" def sort(self, items: List) -> List: return items def __nonzero__(self) -> bool: return self.__bool__() def __bool__(self) -> bool: return False def __eq__(self, other) -> bool: return type(self) is type(other) or other is None def __hash__(self) -> int: return 0 beetbox-beets-01f1faf/beets/dbcore/queryparse.py000066400000000000000000000240641472325477400220670ustar00rootroot00000000000000# This file is part of beets. # Copyright 2016, Adrian Sampson. # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the # "Software"), to deal in the Software without restriction, including # without limitation the rights to use, copy, modify, merge, publish, # distribute, sublicense, and/or sell copies of the Software, and to # permit persons to whom the Software is furnished to do so, subject to # the following conditions: # # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. """Parsing of strings into DBCore queries.""" import itertools import re from typing import Collection, Dict, List, Optional, Sequence, Tuple, Type from . import Model, query from .query import Sort PARSE_QUERY_PART_REGEX = re.compile( # Non-capturing optional segment for the keyword. r"(-|\^)?" # Negation prefixes. r"(?:" r"(\S+?)" # The field key. r"(? Tuple[Optional[str], str, Type[query.FieldQuery], bool]: """Parse a single *query part*, which is a chunk of a complete query string representing a single criterion. A query part is a string consisting of: - A *pattern*: the value to look for. - Optionally, a *field name* preceding the pattern, separated by a colon. So in `foo:bar`, `foo` is the field name and `bar` is the pattern. - Optionally, a *query prefix* just before the pattern (and after the optional colon) indicating the type of query that should be used. For example, in `~foo`, `~` might be a prefix. (The set of prefixes to look for is given in the `prefixes` parameter.) - Optionally, a negation indicator, `-` or `^`, at the very beginning. Both prefixes and the separating `:` character may be escaped with a backslash to avoid their normal meaning. The function returns a tuple consisting of: - The field name: a string or None if it's not present. - The pattern, a string. - The query class to use, which inherits from the base :class:`Query` type. - A negation flag, a bool. The three optional parameters determine which query class is used (i.e., the third return value). They are: - `query_classes`, which maps field names to query classes. These are used when no explicit prefix is present. - `prefixes`, which maps prefix strings to query classes. - `default_class`, the fallback when neither the field nor a prefix indicates a query class. So the precedence for determining which query class to return is: prefix, followed by field, and finally the default. For example, assuming the `:` prefix is used for `RegexpQuery`: - `'stapler'` -> `(None, 'stapler', SubstringQuery, False)` - `'color:red'` -> `('color', 'red', SubstringQuery, False)` - `':^Quiet'` -> `(None, '^Quiet', RegexpQuery, False)`, because the `^` follows the `:` - `'color::b..e'` -> `('color', 'b..e', RegexpQuery, False)` - `'-color:red'` -> `('color', 'red', SubstringQuery, True)` """ # Apply the regular expression and extract the components. part = part.strip() match = PARSE_QUERY_PART_REGEX.match(part) assert match # Regex should always match negate = bool(match.group(1)) key = match.group(2) term = match.group(3).replace("\\:", ":") # Check whether there's a prefix in the query and use the # corresponding query type. for pre, query_class in prefixes.items(): if term.startswith(pre): return key, term[len(pre) :], query_class, negate # No matching prefix, so use either the query class determined by # the field or the default as a fallback. query_class = query_classes.get(key, default_class) return key, term, query_class, negate def construct_query_part( model_cls: Type[Model], prefixes: Dict, query_part: str, ) -> query.Query: """Parse a *query part* string and return a :class:`Query` object. :param model_cls: The :class:`Model` class that this is a query for. This is used to determine the appropriate query types for the model's fields. :param prefixes: A map from prefix strings to :class:`Query` types. :param query_part: The string to parse. See the documentation for `parse_query_part` for more information on query part syntax. """ # A shortcut for empty query parts. if not query_part: return query.TrueQuery() out_query: query.Query # Use `model_cls` to build up a map from field (or query) names to # `Query` classes. query_classes: Dict[str, Type[query.FieldQuery]] = {} for k, t in itertools.chain( model_cls._fields.items(), model_cls._types.items() ): query_classes[k] = t.query query_classes.update(model_cls._queries) # Non-field queries. # Parse the string. key, pattern, query_class, negate = parse_query_part( query_part, query_classes, prefixes ) # If there's no key (field name) specified, this is a "match # anything" query. if key is None: # The query type matches a specific field, but none was # specified. So we use a version of the query that matches # any field. out_query = query.AnyFieldQuery( pattern, model_cls._search_fields, query_class ) # Field queries get constructed according to the name of the field # they are querying. else: field = table = key.lower() if field in model_cls.shared_db_fields: # This field exists in both tables, so SQLite will encounter # an OperationalError if we try to query it in a join. # Using an explicit table name resolves this. table = f"{model_cls._table}.{field}" field_in_db = field in model_cls.all_db_fields out_query = query_class(table, pattern, field_in_db) # Apply negation. if negate: return query.NotQuery(out_query) else: return out_query # TYPING ERROR def query_from_strings( query_cls: Type[query.CollectionQuery], model_cls: Type[Model], prefixes: Dict, query_parts: Collection[str], ) -> query.Query: """Creates a collection query of type `query_cls` from a list of strings in the format used by parse_query_part. `model_cls` determines how queries are constructed from strings. """ subqueries = [] for part in query_parts: subqueries.append(construct_query_part(model_cls, prefixes, part)) if not subqueries: # No terms in query. subqueries = [query.TrueQuery()] return query_cls(subqueries) def construct_sort_part( model_cls: Type[Model], part: str, case_insensitive: bool = True, ) -> Sort: """Create a `Sort` from a single string criterion. `model_cls` is the `Model` being queried. `part` is a single string ending in ``+`` or ``-`` indicating the sort. `case_insensitive` indicates whether or not the sort should be performed in a case sensitive manner. """ assert part, "part must be a field name and + or -" field = part[:-1] assert field, "field is missing" direction = part[-1] assert direction in ("+", "-"), "part must end with + or -" is_ascending = direction == "+" if field in model_cls._sorts: sort = model_cls._sorts[field]( model_cls, is_ascending, case_insensitive ) elif field in model_cls._fields: sort = query.FixedFieldSort(field, is_ascending, case_insensitive) else: # Flexible or computed. sort = query.SlowFieldSort(field, is_ascending, case_insensitive) return sort def sort_from_strings( model_cls: Type[Model], sort_parts: Sequence[str], case_insensitive: bool = True, ) -> Sort: """Create a `Sort` from a list of sort criteria (strings).""" if not sort_parts: return query.NullSort() elif len(sort_parts) == 1: return construct_sort_part(model_cls, sort_parts[0], case_insensitive) else: sort = query.MultipleSort() for part in sort_parts: sort.add_sort( construct_sort_part(model_cls, part, case_insensitive) ) return sort def parse_sorted_query( model_cls: Type[Model], parts: List[str], prefixes: Dict = {}, case_insensitive: bool = True, ) -> Tuple[query.Query, Sort]: """Given a list of strings, create the `Query` and `Sort` that they represent. """ # Separate query token and sort token. query_parts = [] sort_parts = [] # Split up query in to comma-separated subqueries, each representing # an AndQuery, which need to be joined together in one OrQuery subquery_parts = [] for part in parts + [","]: if part.endswith(","): # Ensure we can catch "foo, bar" as well as "foo , bar" last_subquery_part = part[:-1] if last_subquery_part: subquery_parts.append(last_subquery_part) # Parse the subquery in to a single AndQuery # TODO: Avoid needlessly wrapping AndQueries containing 1 subquery? query_parts.append( query_from_strings( query.AndQuery, model_cls, prefixes, subquery_parts ) ) del subquery_parts[:] else: # Sort parts (1) end in + or -, (2) don't have a field, and # (3) consist of more than just the + or -. if part.endswith(("+", "-")) and ":" not in part and len(part) > 1: sort_parts.append(part) else: subquery_parts.append(part) # Avoid needlessly wrapping single statements in an OR q = query.OrQuery(query_parts) if len(query_parts) > 1 else query_parts[0] s = sort_from_strings(model_cls, sort_parts, case_insensitive) return q, s beetbox-beets-01f1faf/beets/dbcore/types.py000066400000000000000000000221141472325477400210250ustar00rootroot00000000000000# This file is part of beets. # Copyright 2016, Adrian Sampson. # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the # "Software"), to deal in the Software without restriction, including # without limitation the rights to use, copy, modify, merge, publish, # distribute, sublicense, and/or sell copies of the Software, and to # permit persons to whom the Software is furnished to do so, subject to # the following conditions: # # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. """Representation of type information for DBCore model fields.""" import typing from abc import ABC from typing import Any, Generic, List, TypeVar, Union, cast from beets.util import str2bool from .query import BooleanQuery, FieldQuery, NumericQuery, SubstringQuery class ModelType(typing.Protocol): """Protocol that specifies the required constructor for model types, i.e. a function that takes any argument and attempts to parse it to the given type. """ def __init__(self, value: Any = None): ... # Generic type variables, used for the value type T and null type N (if # nullable, else T and N are set to the same type for the concrete subclasses # of Type). N = TypeVar("N") T = TypeVar("T", bound=ModelType) class Type(ABC, Generic[T, N]): """An object encapsulating the type of a model field. Includes information about how to store, query, format, and parse a given field. """ sql: str = "TEXT" """The SQLite column type for the value. """ query: typing.Type[FieldQuery] = SubstringQuery """The `Query` subclass to be used when querying the field. """ model_type: typing.Type[T] """The Python type that is used to represent the value in the model. The model is guaranteed to return a value of this type if the field is accessed. To this end, the constructor is used by the `normalize` and `from_sql` methods and the `default` property. """ @property def null(self) -> N: """The value to be exposed when the underlying value is None.""" # Note that this default implementation only makes sense for T = N. # It would be better to implement `null()` only in subclasses, or # have a field null_type similar to `model_type` and use that here. return cast(N, self.model_type()) def format(self, value: Union[N, T]) -> str: """Given a value of this type, produce a Unicode string representing the value. This is used in template evaluation. """ if value is None: value = self.null # `self.null` might be `None` if value is None: return "" elif isinstance(value, bytes): return value.decode("utf-8", "ignore") else: return str(value) def parse(self, string: str) -> Union[T, N]: """Parse a (possibly human-written) string and return the indicated value of this type. """ try: return self.model_type(string) except ValueError: return self.null def normalize(self, value: Any) -> Union[T, N]: """Given a value that will be assigned into a field of this type, normalize the value to have the appropriate type. This base implementation only reinterprets `None`. """ # TYPING ERROR if value is None: return self.null else: # TODO This should eventually be replaced by # `self.model_type(value)` return cast(T, value) def from_sql( self, sql_value: Union[None, int, float, str, bytes], ) -> Union[T, N]: """Receives the value stored in the SQL backend and return the value to be stored in the model. For fixed fields the type of `value` is determined by the column type affinity given in the `sql` property and the SQL to Python mapping of the database adapter. For more information see: https://www.sqlite.org/datatype3.html https://docs.python.org/2/library/sqlite3.html#sqlite-and-python-types Flexible fields have the type affinity `TEXT`. This means the `sql_value` is either a `memoryview` or a `unicode` object` and the method must handle these in addition. """ if isinstance(sql_value, memoryview): sql_value = bytes(sql_value).decode("utf-8", "ignore") if isinstance(sql_value, str): return self.parse(sql_value) else: return self.normalize(sql_value) def to_sql(self, model_value: Any) -> Union[None, int, float, str, bytes]: """Convert a value as stored in the model object to a value used by the database adapter. """ return model_value # Reusable types. class Default(Type[str, None]): model_type = str @property def null(self): return None class BaseInteger(Type[int, N]): """A basic integer type.""" sql = "INTEGER" query = NumericQuery model_type = int def normalize(self, value: Any) -> Union[int, N]: try: return self.model_type(round(float(value))) except ValueError: return self.null except TypeError: return self.null class Integer(BaseInteger[int]): @property def null(self) -> int: return 0 class NullInteger(BaseInteger[None]): @property def null(self) -> None: return None class BasePaddedInt(BaseInteger[N]): """An integer field that is formatted with a given number of digits, padded with zeroes. """ def __init__(self, digits: int): self.digits = digits def format(self, value: Union[int, N]) -> str: return "{0:0{1}d}".format(value or 0, self.digits) class PaddedInt(BasePaddedInt[int]): pass class NullPaddedInt(BasePaddedInt[None]): """Same as `PaddedInt`, but does not normalize `None` to `0`.""" @property def null(self) -> None: return None class ScaledInt(Integer): """An integer whose formatting operation scales the number by a constant and adds a suffix. Good for units with large magnitudes. """ def __init__(self, unit: int, suffix: str = ""): self.unit = unit self.suffix = suffix def format(self, value: int) -> str: return "{}{}".format((value or 0) // self.unit, self.suffix) class Id(NullInteger): """An integer used as the row id or a foreign key in a SQLite table. This type is nullable: None values are not translated to zero. """ @property def null(self) -> None: return None def __init__(self, primary: bool = True): if primary: self.sql = "INTEGER PRIMARY KEY" class BaseFloat(Type[float, N]): """A basic floating-point type. The `digits` parameter specifies how many decimal places to use in the human-readable representation. """ sql = "REAL" query: typing.Type[FieldQuery[Any]] = NumericQuery model_type = float def __init__(self, digits: int = 1): self.digits = digits def format(self, value: Union[float, N]) -> str: return "{0:.{1}f}".format(value or 0, self.digits) class Float(BaseFloat[float]): """Floating-point type that normalizes `None` to `0.0`.""" @property def null(self) -> float: return 0.0 class NullFloat(BaseFloat[None]): """Same as `Float`, but does not normalize `None` to `0.0`.""" @property def null(self) -> None: return None class BaseString(Type[T, N]): """A Unicode string type.""" sql = "TEXT" query = SubstringQuery def normalize(self, value: Any) -> Union[T, N]: if value is None: return self.null else: return self.model_type(value) class String(BaseString[str, Any]): """A Unicode string type.""" model_type = str class DelimitedString(BaseString[List[str], List[str]]): """A list of Unicode strings, represented in-database by a single string containing delimiter-separated values. """ model_type = list def __init__(self, delimiter: str): self.delimiter = delimiter def format(self, value: List[str]): return self.delimiter.join(value) def parse(self, string: str): if not string: return [] return string.split(self.delimiter) def to_sql(self, model_value: List[str]): return self.delimiter.join(model_value) class Boolean(Type): """A boolean type.""" sql = "INTEGER" query = BooleanQuery model_type = bool def format(self, value: bool) -> str: return str(bool(value)) def parse(self, string: str) -> bool: return str2bool(string) # Shared instances of common types. DEFAULT = Default() INTEGER = Integer() PRIMARY_ID = Id(True) FOREIGN_ID = Id(False) FLOAT = Float() NULL_FLOAT = NullFloat() STRING = String() BOOLEAN = Boolean() SEMICOLON_SPACE_DSV = DelimitedString(delimiter="; ") # Will set the proper null char in mediafile MULTI_VALUE_DSV = DelimitedString(delimiter="\\â€") beetbox-beets-01f1faf/beets/importer.py000066400000000000000000002004151472325477400202660ustar00rootroot00000000000000# This file is part of beets. # Copyright 2016, Adrian Sampson. # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the # "Software"), to deal in the Software without restriction, including # without limitation the rights to use, copy, modify, merge, publish, # distribute, sublicense, and/or sell copies of the Software, and to # permit persons to whom the Software is furnished to do so, subject to # the following conditions: # # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. """Provides the basic, interface-agnostic workflow for importing and autotagging music files. """ import itertools import os import pickle import re import shutil import time from bisect import bisect_left, insort from collections import defaultdict from contextlib import contextmanager from enum import Enum from tempfile import mkdtemp import mediafile from beets import autotag, config, dbcore, library, logging, plugins, util from beets.util import ( MoveOperation, ancestry, displayable_path, normpath, pipeline, sorted_walk, syspath, ) action = Enum("action", ["SKIP", "ASIS", "TRACKS", "APPLY", "ALBUMS", "RETAG"]) # The RETAG action represents "don't apply any match, but do record # new metadata". It's not reachable via the standard command prompt but # can be used by plugins. QUEUE_SIZE = 128 SINGLE_ARTIST_THRESH = 0.25 PROGRESS_KEY = "tagprogress" HISTORY_KEY = "taghistory" # Usually flexible attributes are preserved (i.e., not updated) during # reimports. The following two lists (globally) change this behaviour for # certain fields. To alter these lists only when a specific plugin is in use, # something like this can be used within that plugin's code: # # from beets import importer # def extend_reimport_fresh_fields_item(): # importer.REIMPORT_FRESH_FIELDS_ITEM.extend(['tidal_track_popularity'] # ) REIMPORT_FRESH_FIELDS_ALBUM = [ "data_source", "bandcamp_album_id", "spotify_album_id", "deezer_album_id", "beatport_album_id", "tidal_album_id", ] REIMPORT_FRESH_FIELDS_ITEM = list(REIMPORT_FRESH_FIELDS_ALBUM) # Global logger. log = logging.getLogger("beets") class ImportAbortError(Exception): """Raised when the user aborts the tagging operation.""" pass # Utilities. def _open_state(): """Reads the state file, returning a dictionary.""" try: with open(config["statefile"].as_filename(), "rb") as f: return pickle.load(f) except Exception as exc: # The `pickle` module can emit all sorts of exceptions during # unpickling, including ImportError. We use a catch-all # exception to avoid enumerating them all (the docs don't even have a # full list!). log.debug("state file could not be read: {0}", exc) return {} def _save_state(state): """Writes the state dictionary out to disk.""" try: with open(config["statefile"].as_filename(), "wb") as f: pickle.dump(state, f) except OSError as exc: log.error("state file could not be written: {0}", exc) # Utilities for reading and writing the beets progress file, which # allows long tagging tasks to be resumed when they pause (or crash). def progress_read(): state = _open_state() return state.setdefault(PROGRESS_KEY, {}) @contextmanager def progress_write(): state = _open_state() progress = state.setdefault(PROGRESS_KEY, {}) yield progress _save_state(state) def progress_add(toppath, *paths): """Record that the files under all of the `paths` have been imported under `toppath`. """ with progress_write() as state: imported = state.setdefault(toppath, []) for path in paths: # Normally `progress_add` will be called with the path # argument increasing. This is because of the ordering in # `albums_in_dir`. We take advantage of that to make the # code faster if imported and imported[len(imported) - 1] <= path: imported.append(path) else: insort(imported, path) def progress_element(toppath, path): """Return whether `path` has been imported in `toppath`.""" state = progress_read() if toppath not in state: return False imported = state[toppath] i = bisect_left(imported, path) return i != len(imported) and imported[i] == path def has_progress(toppath): """Return `True` if there exist paths that have already been imported under `toppath`. """ state = progress_read() return toppath in state def progress_reset(toppath): with progress_write() as state: if toppath in state: del state[toppath] # Similarly, utilities for manipulating the "incremental" import log. # This keeps track of all directories that were ever imported, which # allows the importer to only import new stuff. def history_add(paths): """Indicate that the import of the album in `paths` is completed and should not be repeated in incremental imports. """ state = _open_state() if HISTORY_KEY not in state: state[HISTORY_KEY] = set() state[HISTORY_KEY].add(tuple(paths)) _save_state(state) def history_get(): """Get the set of completed path tuples in incremental imports.""" state = _open_state() if HISTORY_KEY not in state: return set() return state[HISTORY_KEY] # Abstract session class. class ImportSession: """Controls an import action. Subclasses should implement methods to communicate with the user or otherwise make decisions. """ def __init__(self, lib, loghandler, paths, query): """Create a session. `lib` is a Library object. `loghandler` is a logging.Handler. Either `paths` or `query` is non-null and indicates the source of files to be imported. """ self.lib = lib self.logger = self._setup_logging(loghandler) self.paths = paths self.query = query self._is_resuming = {} self._merged_items = set() self._merged_dirs = set() # Normalize the paths. if self.paths: self.paths = list(map(normpath, self.paths)) def _setup_logging(self, loghandler): logger = logging.getLogger(__name__) logger.propagate = False if not loghandler: loghandler = logging.NullHandler() logger.handlers = [loghandler] return logger def set_config(self, config): """Set `config` property from global import config and make implied changes. """ # FIXME: Maybe this function should not exist and should instead # provide "decision wrappers" like "should_resume()", etc. iconfig = dict(config) self.config = iconfig # Incremental and progress are mutually exclusive. if iconfig["incremental"]: iconfig["resume"] = False # When based on a query instead of directories, never # save progress or try to resume. if self.query is not None: iconfig["resume"] = False iconfig["incremental"] = False if iconfig["reflink"]: iconfig["reflink"] = iconfig["reflink"].as_choice( ["auto", True, False] ) # Copy, move, reflink, link, and hardlink are mutually exclusive. if iconfig["move"]: iconfig["copy"] = False iconfig["link"] = False iconfig["hardlink"] = False iconfig["reflink"] = False elif iconfig["link"]: iconfig["copy"] = False iconfig["move"] = False iconfig["hardlink"] = False iconfig["reflink"] = False elif iconfig["hardlink"]: iconfig["copy"] = False iconfig["move"] = False iconfig["link"] = False iconfig["reflink"] = False elif iconfig["reflink"]: iconfig["copy"] = False iconfig["move"] = False iconfig["link"] = False iconfig["hardlink"] = False # Only delete when copying. if not iconfig["copy"]: iconfig["delete"] = False self.want_resume = config["resume"].as_choice([True, False, "ask"]) def tag_log(self, status, paths): """Log a message about a given album to the importer log. The status should reflect the reason the album couldn't be tagged. """ self.logger.info("{0} {1}", status, displayable_path(paths)) def log_choice(self, task, duplicate=False): """Logs the task's current choice if it should be logged. If ``duplicate``, then this is a secondary choice after a duplicate was detected and a decision was made. """ paths = task.paths if duplicate: # Duplicate: log all three choices (skip, keep both, and trump). if task.should_remove_duplicates: self.tag_log("duplicate-replace", paths) elif task.choice_flag in (action.ASIS, action.APPLY): self.tag_log("duplicate-keep", paths) elif task.choice_flag is (action.SKIP): self.tag_log("duplicate-skip", paths) else: # Non-duplicate: log "skip" and "asis" choices. if task.choice_flag is action.ASIS: self.tag_log("asis", paths) elif task.choice_flag is action.SKIP: self.tag_log("skip", paths) def should_resume(self, path): raise NotImplementedError def choose_match(self, task): raise NotImplementedError def resolve_duplicate(self, task, found_duplicates): raise NotImplementedError def choose_item(self, task): raise NotImplementedError def run(self): """Run the import task.""" self.logger.info("import started {0}", time.asctime()) self.set_config(config["import"]) # Set up the pipeline. if self.query is None: stages = [read_tasks(self)] else: stages = [query_tasks(self)] # In pretend mode, just log what would otherwise be imported. if self.config["pretend"]: stages += [log_files(self)] else: if self.config["group_albums"] and not self.config["singletons"]: # Split directory tasks into one task for each album. stages += [group_albums(self)] # These stages either talk to the user to get a decision or, # in the case of a non-autotagged import, just choose to # import everything as-is. In *both* cases, these stages # also add the music to the library database, so later # stages need to read and write data from there. if self.config["autotag"]: stages += [lookup_candidates(self), user_query(self)] else: stages += [import_asis(self)] # Plugin stages. for stage_func in plugins.early_import_stages(): stages.append(plugin_stage(self, stage_func)) for stage_func in plugins.import_stages(): stages.append(plugin_stage(self, stage_func)) stages += [manipulate_files(self)] pl = pipeline.Pipeline(stages) # Run the pipeline. plugins.send("import_begin", session=self) try: if config["threaded"]: pl.run_parallel(QUEUE_SIZE) else: pl.run_sequential() except ImportAbortError: # User aborted operation. Silently stop. pass # Incremental and resumed imports def already_imported(self, toppath, paths): """Returns true if the files belonging to this task have already been imported in a previous session. """ if self.is_resuming(toppath) and all( [progress_element(toppath, p) for p in paths] ): return True if self.config["incremental"] and tuple(paths) in self.history_dirs: return True return False @property def history_dirs(self): if not hasattr(self, "_history_dirs"): self._history_dirs = history_get() return self._history_dirs def already_merged(self, paths): """Returns true if all the paths being imported were part of a merge during previous tasks. """ for path in paths: if path not in self._merged_items and path not in self._merged_dirs: return False return True def mark_merged(self, paths): """Mark paths and directories as merged for future reimport tasks.""" self._merged_items.update(paths) dirs = { os.path.dirname(path) if os.path.isfile(syspath(path)) else path for path in paths } self._merged_dirs.update(dirs) def is_resuming(self, toppath): """Return `True` if user wants to resume import of this path. You have to call `ask_resume` first to determine the return value. """ return self._is_resuming.get(toppath, False) def ask_resume(self, toppath): """If import of `toppath` was aborted in an earlier session, ask user if they want to resume the import. Determines the return value of `is_resuming(toppath)`. """ if self.want_resume and has_progress(toppath): # Either accept immediately or prompt for input to decide. if self.want_resume is True or self.should_resume(toppath): log.warning( "Resuming interrupted import of {0}", util.displayable_path(toppath), ) self._is_resuming[toppath] = True else: # Clear progress; we're starting from the top. progress_reset(toppath) # The importer task class. class BaseImportTask: """An abstract base class for importer tasks. Tasks flow through the importer pipeline. Each stage can update them.""" def __init__(self, toppath, paths, items): """Create a task. The primary fields that define a task are: * `toppath`: The user-specified base directory that contains the music for this task. If the task has *no* user-specified base (for example, when importing based on an -L query), this can be None. This is used for tracking progress and history. * `paths`: A list of *specific* paths where the music for this task came from. These paths can be directories, when their entire contents are being imported, or files, when the task comprises individual tracks. This is used for progress/history tracking and for displaying the task to the user. * `items`: A list of `Item` objects representing the music being imported. These fields should not change after initialization. """ self.toppath = toppath self.paths = paths self.items = items class ImportTask(BaseImportTask): """Represents a single set of items to be imported along with its intermediate state. May represent an album or a single item. The import session and stages call the following methods in the given order. * `lookup_candidates()` Sets the `common_artist`, `common_album`, `candidates`, and `rec` attributes. `candidates` is a list of `AlbumMatch` objects. * `choose_match()` Uses the session to set the `match` attribute from the `candidates` list. * `find_duplicates()` Returns a list of albums from `lib` with the same artist and album name as the task. * `apply_metadata()` Sets the attributes of the items from the task's `match` attribute. * `add()` Add the imported items and album to the database. * `manipulate_files()` Copy, move, and write files depending on the session configuration. * `set_fields()` Sets the fields given at CLI or configuration to the specified values. * `finalize()` Update the import progress and cleanup the file system. """ def __init__(self, toppath, paths, items): super().__init__(toppath, paths, items) self.choice_flag = None self.cur_album = None self.cur_artist = None self.candidates = [] self.rec = None self.should_remove_duplicates = False self.should_merge_duplicates = False self.is_album = True self.search_ids = [] # user-supplied candidate IDs. def set_choice(self, choice): """Given an AlbumMatch or TrackMatch object or an action constant, indicates that an action has been selected for this task. """ # Not part of the task structure: assert choice != action.APPLY # Only used internally. if choice in ( action.SKIP, action.ASIS, action.TRACKS, action.ALBUMS, action.RETAG, ): self.choice_flag = choice self.match = None else: self.choice_flag = action.APPLY # Implicit choice. self.match = choice def save_progress(self): """Updates the progress state to indicate that this album has finished. """ if self.toppath: progress_add(self.toppath, *self.paths) def save_history(self): """Save the directory in the history for incremental imports.""" if self.paths: history_add(self.paths) # Logical decisions. @property def apply(self): return self.choice_flag == action.APPLY @property def skip(self): return self.choice_flag == action.SKIP # Convenient data. def chosen_info(self): """Return a dictionary of metadata about the current choice. May only be called when the choice flag is ASIS or RETAG (in which case the data comes from the files' current metadata) or APPLY (in which case the data comes from the choice). """ if self.choice_flag in (action.ASIS, action.RETAG): likelies, consensus = autotag.current_metadata(self.items) return likelies elif self.choice_flag is action.APPLY: return self.match.info.copy() assert False def imported_items(self): """Return a list of Items that should be added to the library. If the tasks applies an album match the method only returns the matched items. """ if self.choice_flag in (action.ASIS, action.RETAG): return list(self.items) elif self.choice_flag == action.APPLY: return list(self.match.mapping.keys()) else: assert False def apply_metadata(self): """Copy metadata from match info to the items.""" if config["import"]["from_scratch"]: for item in self.match.mapping: item.clear() autotag.apply_metadata(self.match.info, self.match.mapping) def duplicate_items(self, lib): duplicate_items = [] for album in self.find_duplicates(lib): duplicate_items += album.items() return duplicate_items def remove_duplicates(self, lib): duplicate_items = self.duplicate_items(lib) log.debug("removing {0} old duplicated items", len(duplicate_items)) for item in duplicate_items: item.remove() if lib.directory in util.ancestry(item.path): log.debug( "deleting duplicate {0}", util.displayable_path(item.path) ) util.remove(item.path) util.prune_dirs(os.path.dirname(item.path), lib.directory) def set_fields(self, lib): """Sets the fields given at CLI or configuration to the specified values, for both the album and all its items. """ items = self.imported_items() for field, view in config["import"]["set_fields"].items(): value = str(view.get()) log.debug( "Set field {1}={2} for {0}", displayable_path(self.paths), field, value, ) self.album.set_parse(field, format(self.album, value)) for item in items: item.set_parse(field, format(item, value)) with lib.transaction(): for item in items: item.store() self.album.store() def finalize(self, session): """Save progress, clean up files, and emit plugin event.""" # Update progress. if session.want_resume: self.save_progress() if session.config["incremental"] and not ( # Should we skip recording to incremental list? self.skip and session.config["incremental_skip_later"] ): self.save_history() self.cleanup( copy=session.config["copy"], delete=session.config["delete"], move=session.config["move"], ) if not self.skip: self._emit_imported(session.lib) def cleanup(self, copy=False, delete=False, move=False): """Remove and prune imported paths.""" # Do not delete any files or prune directories when skipping. if self.skip: return items = self.imported_items() # When copying and deleting originals, delete old files. if copy and delete: new_paths = [os.path.realpath(item.path) for item in items] for old_path in self.old_paths: # Only delete files that were actually copied. if old_path not in new_paths: util.remove(syspath(old_path), False) self.prune(old_path) # When moving, prune empty directories containing the original files. elif move: for old_path in self.old_paths: self.prune(old_path) def _emit_imported(self, lib): plugins.send("album_imported", lib=lib, album=self.album) def handle_created(self, session): """Send the `import_task_created` event for this task. Return a list of tasks that should continue through the pipeline. By default, this is a list containing only the task itself, but plugins can replace the task with new ones. """ tasks = plugins.send("import_task_created", session=session, task=self) if not tasks: tasks = [self] else: # The plugins gave us a list of lists of tasks. Flatten it. tasks = [t for inner in tasks for t in inner] return tasks def lookup_candidates(self): """Retrieve and store candidates for this album. User-specified candidate IDs are stored in self.search_ids: if present, the initial lookup is restricted to only those IDs. """ artist, album, prop = autotag.tag_album( self.items, search_ids=self.search_ids ) self.cur_artist = artist self.cur_album = album self.candidates = prop.candidates self.rec = prop.recommendation def find_duplicates(self, lib): """Return a list of albums from `lib` with the same artist and album name as the task. """ info = self.chosen_info() info["albumartist"] = info["artist"] if info["artist"] is None: # As-is import with no artist. Skip check. return [] # Construct a query to find duplicates with this metadata. We # use a temporary Album object to generate any computed fields. tmp_album = library.Album(lib, **info) keys = config["import"]["duplicate_keys"]["album"].as_str_seq() dup_query = library.Album.all_fields_query( {key: tmp_album.get(key) for key in keys} ) # Don't count albums with the same files as duplicates. task_paths = {i.path for i in self.items if i} duplicates = [] for album in lib.albums(dup_query): # Check whether the album paths are all present in the task # i.e. album is being completely re-imported by the task, # in which case it is not a duplicate (will be replaced). album_paths = {i.path for i in album.items()} if not (album_paths <= task_paths): duplicates.append(album) return duplicates def align_album_level_fields(self): """Make some album fields equal across `self.items`. For the RETAG action, we assume that the responsible for returning it (ie. a plugin) always ensures that the first item contains valid data on the relevant fields. """ changes = {} if self.choice_flag == action.ASIS: # Taking metadata "as-is". Guess whether this album is VA. plur_albumartist, freq = util.plurality( [i.albumartist or i.artist for i in self.items] ) if freq == len(self.items) or ( freq > 1 and float(freq) / len(self.items) >= SINGLE_ARTIST_THRESH ): # Single-artist album. changes["albumartist"] = plur_albumartist changes["comp"] = False else: # VA. changes["albumartist"] = config["va_name"].as_str() changes["comp"] = True elif self.choice_flag in (action.APPLY, action.RETAG): # Applying autotagged metadata. Just get AA from the first # item. if not self.items[0].albumartist: changes["albumartist"] = self.items[0].artist if not self.items[0].albumartists: changes["albumartists"] = self.items[0].artists if not self.items[0].mb_albumartistid: changes["mb_albumartistid"] = self.items[0].mb_artistid if not self.items[0].mb_albumartistids: changes["mb_albumartistids"] = self.items[0].mb_artistids # Apply new metadata. for item in self.items: item.update(changes) def manipulate_files(self, operation=None, write=False, session=None): """Copy, move, link, hardlink or reflink (depending on `operation`) the files as well as write metadata. `operation` should be an instance of `util.MoveOperation`. If `write` is `True` metadata is written to the files. """ items = self.imported_items() # Save the original paths of all items for deletion and pruning # in the next step (finalization). self.old_paths = [item.path for item in items] for item in items: if operation is not None: # In copy and link modes, treat re-imports specially: # move in-library files. (Out-of-library files are # copied/moved as usual). old_path = item.path if ( operation != MoveOperation.MOVE and self.replaced_items[item] and session.lib.directory in util.ancestry(old_path) ): item.move() # We moved the item, so remove the # now-nonexistent file from old_paths. self.old_paths.remove(old_path) else: # A normal import. Just copy files and keep track of # old paths. item.move(operation) if write and (self.apply or self.choice_flag == action.RETAG): item.try_write() with session.lib.transaction(): for item in self.imported_items(): item.store() plugins.send("import_task_files", session=session, task=self) def add(self, lib): """Add the items as an album to the library and remove replaced items.""" self.align_album_level_fields() with lib.transaction(): self.record_replaced(lib) self.remove_replaced(lib) self.album = lib.add_album(self.imported_items()) if self.choice_flag == action.APPLY: # Copy album flexible fields to the DB # TODO: change the flow so we create the `Album` object earlier, # and we can move this into `self.apply_metadata`, just like # is done for tracks. autotag.apply_album_metadata(self.match.info, self.album) self.album.store() self.reimport_metadata(lib) def record_replaced(self, lib): """Records the replaced items and albums in the `replaced_items` and `replaced_albums` dictionaries. """ self.replaced_items = defaultdict(list) self.replaced_albums = defaultdict(list) replaced_album_ids = set() for item in self.imported_items(): dup_items = list( lib.items(dbcore.query.BytesQuery("path", item.path)) ) self.replaced_items[item] = dup_items for dup_item in dup_items: if ( not dup_item.album_id or dup_item.album_id in replaced_album_ids ): continue replaced_album = dup_item._cached_album if replaced_album: replaced_album_ids.add(dup_item.album_id) self.replaced_albums[replaced_album.path] = replaced_album def reimport_metadata(self, lib): """For reimports, preserves metadata for reimported items and albums. """ def _reduce_and_log(new_obj, existing_fields, overwrite_keys): """Some flexible attributes should be overwritten (rather than preserved) on reimports; Copies existing_fields, logs and removes entries that should not be preserved and returns a dict containing those fields left to actually be preserved. """ noun = "album" if isinstance(new_obj, library.Album) else "item" existing_fields = dict(existing_fields) overwritten_fields = [ k for k in existing_fields if k in overwrite_keys and new_obj.get(k) and existing_fields.get(k) != new_obj.get(k) ] if overwritten_fields: log.debug( "Reimported {} {}. Not preserving flexible attributes {}. " "Path: {}", noun, new_obj.id, overwritten_fields, displayable_path(new_obj.path), ) for key in overwritten_fields: del existing_fields[key] return existing_fields if self.is_album: replaced_album = self.replaced_albums.get(self.album.path) if replaced_album: album_fields = _reduce_and_log( self.album, replaced_album._values_flex, REIMPORT_FRESH_FIELDS_ALBUM, ) self.album.added = replaced_album.added self.album.update(album_fields) self.album.artpath = replaced_album.artpath self.album.store() log.debug( "Reimported album {}. Preserving attribute ['added']. " "Path: {}", self.album.id, displayable_path(self.album.path), ) log.debug( "Reimported album {}. Preserving flexible attributes {}. " "Path: {}", self.album.id, list(album_fields.keys()), displayable_path(self.album.path), ) for item in self.imported_items(): dup_items = self.replaced_items[item] for dup_item in dup_items: if dup_item.added and dup_item.added != item.added: item.added = dup_item.added log.debug( "Reimported item {}. Preserving attribute ['added']. " "Path: {}", item.id, displayable_path(item.path), ) item_fields = _reduce_and_log( item, dup_item._values_flex, REIMPORT_FRESH_FIELDS_ITEM ) item.update(item_fields) log.debug( "Reimported item {}. Preserving flexible attributes {}. " "Path: {}", item.id, list(item_fields.keys()), displayable_path(item.path), ) item.store() def remove_replaced(self, lib): """Removes all the items from the library that have the same path as an item from this task. """ for item in self.imported_items(): for dup_item in self.replaced_items[item]: log.debug( "Replacing item {0}: {1}", dup_item.id, displayable_path(item.path), ) dup_item.remove() log.debug( "{0} of {1} items replaced", sum(bool(v) for v in self.replaced_items.values()), len(self.imported_items()), ) def choose_match(self, session): """Ask the session which match should apply and apply it.""" choice = session.choose_match(self) self.set_choice(choice) session.log_choice(self) def reload(self): """Reload albums and items from the database.""" for item in self.imported_items(): item.load() self.album.load() # Utilities. def prune(self, filename): """Prune any empty directories above the given file. If this task has no `toppath` or the file path provided is not within the `toppath`, then this function has no effect. Similarly, if the file still exists, no pruning is performed, so it's safe to call when the file in question may not have been removed. """ if self.toppath and not os.path.exists(syspath(filename)): util.prune_dirs( os.path.dirname(filename), self.toppath, clutter=config["clutter"].as_str_seq(), ) class SingletonImportTask(ImportTask): """ImportTask for a single track that is not associated to an album.""" def __init__(self, toppath, item): super().__init__(toppath, [item.path], [item]) self.item = item self.is_album = False self.paths = [item.path] def chosen_info(self): """Return a dictionary of metadata about the current choice. May only be called when the choice flag is ASIS or RETAG (in which case the data comes from the files' current metadata) or APPLY (in which case the data comes from the choice). """ assert self.choice_flag in (action.ASIS, action.RETAG, action.APPLY) if self.choice_flag in (action.ASIS, action.RETAG): return dict(self.item) elif self.choice_flag is action.APPLY: return self.match.info.copy() def imported_items(self): return [self.item] def apply_metadata(self): autotag.apply_item_metadata(self.item, self.match.info) def _emit_imported(self, lib): for item in self.imported_items(): plugins.send("item_imported", lib=lib, item=item) def lookup_candidates(self): prop = autotag.tag_item(self.item, search_ids=self.search_ids) self.candidates = prop.candidates self.rec = prop.recommendation def find_duplicates(self, lib): """Return a list of items from `lib` that have the same artist and title as the task. """ info = self.chosen_info() # Query for existing items using the same metadata. We use a # temporary `Item` object to generate any computed fields. tmp_item = library.Item(lib, **info) keys = config["import"]["duplicate_keys"]["item"].as_str_seq() dup_query = library.Album.all_fields_query( {key: tmp_item.get(key) for key in keys} ) found_items = [] for other_item in lib.items(dup_query): # Existing items not considered duplicates. if other_item.path != self.item.path: found_items.append(other_item) return found_items duplicate_items = find_duplicates def add(self, lib): with lib.transaction(): self.record_replaced(lib) self.remove_replaced(lib) lib.add(self.item) self.reimport_metadata(lib) def infer_album_fields(self): raise NotImplementedError def choose_match(self, session): """Ask the session which match should apply and apply it.""" choice = session.choose_item(self) self.set_choice(choice) session.log_choice(self) def reload(self): self.item.load() def set_fields(self, lib): """Sets the fields given at CLI or configuration to the specified values, for the singleton item. """ for field, view in config["import"]["set_fields"].items(): value = str(view.get()) log.debug( "Set field {1}={2} for {0}", displayable_path(self.paths), field, value, ) self.item.set_parse(field, format(self.item, value)) self.item.store() # FIXME The inheritance relationships are inverted. This is why there # are so many methods which pass. More responsibility should be delegated to # the BaseImportTask class. class SentinelImportTask(ImportTask): """A sentinel task marks the progress of an import and does not import any items itself. If only `toppath` is set the task indicates the end of a top-level directory import. If the `paths` argument is also given, the task indicates the progress in the `toppath` import. """ def __init__(self, toppath, paths): super().__init__(toppath, paths, ()) # TODO Remove the remaining attributes eventually self.should_remove_duplicates = False self.is_album = True self.choice_flag = None def save_history(self): pass def save_progress(self): if self.paths is None: # "Done" sentinel. progress_reset(self.toppath) else: # "Directory progress" sentinel for singletons progress_add(self.toppath, *self.paths) def skip(self): return True def set_choice(self, choice): raise NotImplementedError def cleanup(self, **kwargs): pass def _emit_imported(self, session): pass class ArchiveImportTask(SentinelImportTask): """An import task that represents the processing of an archive. `toppath` must be a `zip`, `tar`, or `rar` archive. Archive tasks serve two purposes: - First, it will unarchive the files to a temporary directory and return it. The client should read tasks from the resulting directory and send them through the pipeline. - Second, it will clean up the temporary directory when it proceeds through the pipeline. The client should send the archive task after sending the rest of the music tasks to make this work. """ def __init__(self, toppath): super().__init__(toppath, ()) self.extracted = False @classmethod def is_archive(cls, path): """Returns true if the given path points to an archive that can be handled. """ if not os.path.isfile(path): return False for path_test, _ in cls.handlers(): if path_test(os.fsdecode(path)): return True return False @classmethod def handlers(cls): """Returns a list of archive handlers. Each handler is a `(path_test, ArchiveClass)` tuple. `path_test` is a function that returns `True` if the given path can be handled by `ArchiveClass`. `ArchiveClass` is a class that implements the same interface as `tarfile.TarFile`. """ if not hasattr(cls, "_handlers"): cls._handlers = [] from zipfile import ZipFile, is_zipfile cls._handlers.append((is_zipfile, ZipFile)) import tarfile cls._handlers.append((tarfile.is_tarfile, tarfile.open)) try: from rarfile import RarFile, is_rarfile except ImportError: pass else: cls._handlers.append((is_rarfile, RarFile)) try: from py7zr import SevenZipFile, is_7zfile except ImportError: pass else: cls._handlers.append((is_7zfile, SevenZipFile)) return cls._handlers def cleanup(self, **kwargs): """Removes the temporary directory the archive was extracted to.""" if self.extracted: log.debug( "Removing extracted directory: {0}", displayable_path(self.toppath), ) shutil.rmtree(syspath(self.toppath)) def extract(self): """Extracts the archive to a temporary directory and sets `toppath` to that directory. """ for path_test, handler_class in self.handlers(): if path_test(os.fsdecode(self.toppath)): break extract_to = mkdtemp() archive = handler_class(os.fsdecode(self.toppath), mode="r") try: archive.extractall(extract_to) # Adjust the files' mtimes to match the information from the # archive. Inspired by: https://stackoverflow.com/q/9813243 for f in archive.infolist(): # The date_time will need to adjusted otherwise # the item will have the current date_time of extraction. # The (0, 0, -1) is added to date_time because the # function time.mktime expects a 9-element tuple. # The -1 indicates that the DST flag is unknown. date_time = time.mktime(f.date_time + (0, 0, -1)) fullpath = os.path.join(extract_to, f.filename) os.utime(fullpath, (date_time, date_time)) finally: archive.close() self.extracted = True self.toppath = extract_to class ImportTaskFactory: """Generate album and singleton import tasks for all media files indicated by a path. """ def __init__(self, toppath, session): """Create a new task factory. `toppath` is the user-specified path to search for music to import. `session` is the `ImportSession`, which controls how tasks are read from the directory. """ self.toppath = toppath self.session = session self.skipped = 0 # Skipped due to incremental/resume. self.imported = 0 # "Real" tasks created. self.is_archive = ArchiveImportTask.is_archive(syspath(toppath)) def tasks(self): """Yield all import tasks for music found in the user-specified path `self.toppath`. Any necessary sentinel tasks are also produced. During generation, update `self.skipped` and `self.imported` with the number of tasks that were not produced (due to incremental mode or resumed imports) and the number of concrete tasks actually produced, respectively. If `self.toppath` is an archive, it is adjusted to point to the extracted data. """ # Check whether this is an archive. if self.is_archive: archive_task = self.unarchive() if not archive_task: return # Search for music in the directory. for dirs, paths in self.paths(): if self.session.config["singletons"]: for path in paths: tasks = self._create(self.singleton(path)) yield from tasks yield self.sentinel(dirs) else: tasks = self._create(self.album(paths, dirs)) yield from tasks # Produce the final sentinel for this toppath to indicate that # it is finished. This is usually just a SentinelImportTask, but # for archive imports, send the archive task instead (to remove # the extracted directory). if self.is_archive: yield archive_task else: yield self.sentinel() def _create(self, task): """Handle a new task to be emitted by the factory. Emit the `import_task_created` event and increment the `imported` count if the task is not skipped. Return the same task. If `task` is None, do nothing. """ if task: tasks = task.handle_created(self.session) self.imported += len(tasks) return tasks return [] def paths(self): """Walk `self.toppath` and yield `(dirs, files)` pairs where `files` are individual music files and `dirs` the set of containing directories where the music was found. This can either be a recursive search in the ordinary case, a single track when `toppath` is a file, a single directory in `flat` mode. """ if not os.path.isdir(syspath(self.toppath)): yield [self.toppath], [self.toppath] elif self.session.config["flat"]: paths = [] for dirs, paths_in_dir in albums_in_dir(self.toppath): paths += paths_in_dir yield [self.toppath], paths else: for dirs, paths in albums_in_dir(self.toppath): yield dirs, paths def singleton(self, path): """Return a `SingletonImportTask` for the music file.""" if self.session.already_imported(self.toppath, [path]): log.debug( "Skipping previously-imported path: {0}", displayable_path(path) ) self.skipped += 1 return None item = self.read_item(path) if item: return SingletonImportTask(self.toppath, item) else: return None def album(self, paths, dirs=None): """Return a `ImportTask` with all media files from paths. `dirs` is a list of parent directories used to record already imported albums. """ if not paths: return None if dirs is None: dirs = list({os.path.dirname(p) for p in paths}) if self.session.already_imported(self.toppath, dirs): log.debug( "Skipping previously-imported path: {0}", displayable_path(dirs) ) self.skipped += 1 return None items = map(self.read_item, paths) items = [item for item in items if item] if items: return ImportTask(self.toppath, dirs, items) else: return None def sentinel(self, paths=None): """Return a `SentinelImportTask` indicating the end of a top-level directory import. """ return SentinelImportTask(self.toppath, paths) def unarchive(self): """Extract the archive for this `toppath`. Extract the archive to a new directory, adjust `toppath` to point to the extracted directory, and return an `ArchiveImportTask`. If extraction fails, return None. """ assert self.is_archive if not (self.session.config["move"] or self.session.config["copy"]): log.warning( "Archive importing requires either " "'copy' or 'move' to be enabled." ) return log.debug("Extracting archive: {0}", displayable_path(self.toppath)) archive_task = ArchiveImportTask(self.toppath) try: archive_task.extract() except Exception as exc: log.error("extraction failed: {0}", exc) return # Now read albums from the extracted directory. self.toppath = archive_task.toppath log.debug("Archive extracted to: {0}", self.toppath) return archive_task def read_item(self, path): """Return an `Item` read from the path. If an item cannot be read, return `None` instead and log an error. """ try: return library.Item.from_path(path) except library.ReadError as exc: if isinstance(exc.reason, mediafile.FileTypeError): # Silently ignore non-music files. pass elif isinstance(exc.reason, mediafile.UnreadableFileError): log.warning("unreadable file: {0}", displayable_path(path)) else: log.error("error reading {0}: {1}", displayable_path(path), exc) # Pipeline utilities def _freshen_items(items): # Clear IDs from re-tagged items so they appear "fresh" when # we add them back to the library. for item in items: item.id = None item.album_id = None def _extend_pipeline(tasks, *stages): # Return pipeline extension for stages with list of tasks if isinstance(tasks, list): task_iter = iter(tasks) else: task_iter = tasks ipl = pipeline.Pipeline([task_iter] + list(stages)) return pipeline.multiple(ipl.pull()) # Full-album pipeline stages. def read_tasks(session): """A generator yielding all the albums (as ImportTask objects) found in the user-specified list of paths. In the case of a singleton import, yields single-item tasks instead. """ skipped = 0 for toppath in session.paths: # Check whether we need to resume the import. session.ask_resume(toppath) # Generate tasks. task_factory = ImportTaskFactory(toppath, session) yield from task_factory.tasks() skipped += task_factory.skipped if not task_factory.imported: log.warning("No files imported from {0}", displayable_path(toppath)) # Show skipped directories (due to incremental/resume). if skipped: log.info("Skipped {0} paths.", skipped) def query_tasks(session): """A generator that works as a drop-in-replacement for read_tasks. Instead of finding files from the filesystem, a query is used to match items from the library. """ if session.config["singletons"]: # Search for items. for item in session.lib.items(session.query): task = SingletonImportTask(None, item) for task in task.handle_created(session): yield task else: # Search for albums. for album in session.lib.albums(session.query): log.debug( "yielding album {0}: {1} - {2}", album.id, album.albumartist, album.album, ) items = list(album.items()) _freshen_items(items) task = ImportTask(None, [album.item_dir()], items) for task in task.handle_created(session): yield task @pipeline.mutator_stage def lookup_candidates(session, task): """A coroutine for performing the initial MusicBrainz lookup for an album. It accepts lists of Items and yields (items, cur_artist, cur_album, candidates, rec) tuples. If no match is found, all of the yielded parameters (except items) are None. """ if task.skip: # FIXME This gets duplicated a lot. We need a better # abstraction. return plugins.send("import_task_start", session=session, task=task) log.debug("Looking up: {0}", displayable_path(task.paths)) # Restrict the initial lookup to IDs specified by the user via the -m # option. Currently all the IDs are passed onto the tasks directly. task.search_ids = session.config["search_ids"].as_str_seq() task.lookup_candidates() @pipeline.stage def user_query(session, task): """A coroutine for interfacing with the user about the tagging process. The coroutine accepts an ImportTask objects. It uses the session's `choose_match` method to determine the `action` for this task. Depending on the action additional stages are executed and the processed task is yielded. It emits the ``import_task_choice`` event for plugins. Plugins have access to the choice via the ``task.choice_flag`` property and may choose to change it. """ if task.skip: return task if session.already_merged(task.paths): return pipeline.BUBBLE # Ask the user for a choice. task.choose_match(session) plugins.send("import_task_choice", session=session, task=task) # As-tracks: transition to singleton workflow. if task.choice_flag is action.TRACKS: # Set up a little pipeline for dealing with the singletons. def emitter(task): for item in task.items: task = SingletonImportTask(task.toppath, item) yield from task.handle_created(session) yield SentinelImportTask(task.toppath, task.paths) return _extend_pipeline( emitter(task), lookup_candidates(session), user_query(session) ) # As albums: group items by albums and create task for each album if task.choice_flag is action.ALBUMS: return _extend_pipeline( [task], group_albums(session), lookup_candidates(session), user_query(session), ) resolve_duplicates(session, task) if task.should_merge_duplicates: # Create a new task for tagging the current items # and duplicates together duplicate_items = task.duplicate_items(session.lib) # Duplicates would be reimported so make them look "fresh" _freshen_items(duplicate_items) duplicate_paths = [item.path for item in duplicate_items] # Record merged paths in the session so they are not reimported session.mark_merged(duplicate_paths) merged_task = ImportTask( None, task.paths + duplicate_paths, task.items + duplicate_items ) return _extend_pipeline( [merged_task], lookup_candidates(session), user_query(session) ) apply_choice(session, task) return task def resolve_duplicates(session, task): """Check if a task conflicts with items or albums already imported and ask the session to resolve this. """ if task.choice_flag in (action.ASIS, action.APPLY, action.RETAG): found_duplicates = task.find_duplicates(session.lib) if found_duplicates: log.debug( "found duplicates: {}".format([o.id for o in found_duplicates]) ) # Get the default action to follow from config. duplicate_action = config["import"]["duplicate_action"].as_choice( { "skip": "s", "keep": "k", "remove": "r", "merge": "m", "ask": "a", } ) log.debug("default action for duplicates: {0}", duplicate_action) if duplicate_action == "s": # Skip new. task.set_choice(action.SKIP) elif duplicate_action == "k": # Keep both. Do nothing; leave the choice intact. pass elif duplicate_action == "r": # Remove old. task.should_remove_duplicates = True elif duplicate_action == "m": # Merge duplicates together task.should_merge_duplicates = True else: # No default action set; ask the session. session.resolve_duplicate(task, found_duplicates) session.log_choice(task, True) @pipeline.mutator_stage def import_asis(session, task): """Select the `action.ASIS` choice for all tasks. This stage replaces the initial_lookup and user_query stages when the importer is run without autotagging. """ if task.skip: return log.info("{}", displayable_path(task.paths)) task.set_choice(action.ASIS) apply_choice(session, task) def apply_choice(session, task): """Apply the task's choice to the Album or Item it contains and add it to the library. """ if task.skip: return # Change metadata. if task.apply: task.apply_metadata() plugins.send("import_task_apply", session=session, task=task) task.add(session.lib) # If ``set_fields`` is set, set those fields to the # configured values. # NOTE: This cannot be done before the ``task.add()`` call above, # because then the ``ImportTask`` won't have an `album` for which # it can set the fields. if config["import"]["set_fields"]: task.set_fields(session.lib) @pipeline.mutator_stage def plugin_stage(session, func, task): """A coroutine (pipeline stage) that calls the given function with each non-skipped import task. These stages occur between applying metadata changes and moving/copying/writing files. """ if task.skip: return func(session, task) # Stage may modify DB, so re-load cached item data. # FIXME Importer plugins should not modify the database but instead # the albums and items attached to tasks. task.reload() @pipeline.stage def manipulate_files(session, task): """A coroutine (pipeline stage) that performs necessary file manipulations *after* items have been added to the library and finalizes each task. """ if not task.skip: if task.should_remove_duplicates: task.remove_duplicates(session.lib) if session.config["move"]: operation = MoveOperation.MOVE elif session.config["copy"]: operation = MoveOperation.COPY elif session.config["link"]: operation = MoveOperation.LINK elif session.config["hardlink"]: operation = MoveOperation.HARDLINK elif session.config["reflink"] == "auto": operation = MoveOperation.REFLINK_AUTO elif session.config["reflink"]: operation = MoveOperation.REFLINK else: operation = None task.manipulate_files( operation, write=session.config["write"], session=session, ) # Progress, cleanup, and event. task.finalize(session) @pipeline.stage def log_files(session, task): """A coroutine (pipeline stage) to log each file to be imported.""" if isinstance(task, SingletonImportTask): log.info("Singleton: {0}", displayable_path(task.item["path"])) elif task.items: log.info("Album: {0}", displayable_path(task.paths[0])) for item in task.items: log.info(" {0}", displayable_path(item["path"])) def group_albums(session): """A pipeline stage that groups the items of each task into albums using their metadata. Groups are identified using their artist and album fields. The pipeline stage emits new album tasks for each discovered group. """ def group(item): return (item.albumartist or item.artist, item.album) task = None while True: task = yield task if task.skip: continue tasks = [] sorted_items = sorted(task.items, key=group) for _, items in itertools.groupby(sorted_items, group): items = list(items) task = ImportTask(task.toppath, [i.path for i in items], items) tasks += task.handle_created(session) tasks.append(SentinelImportTask(task.toppath, task.paths)) task = pipeline.multiple(tasks) MULTIDISC_MARKERS = (rb"dis[ck]", rb"cd") MULTIDISC_PAT_FMT = rb"^(.*%s[\W_]*)\d" def is_subdir_of_any_in_list(path, dirs): """Returns True if path os a subdirectory of any directory in dirs (a list). In other case, returns False. """ ancestors = ancestry(path) return any(d in ancestors for d in dirs) def albums_in_dir(path): """Recursively searches the given directory and returns an iterable of (paths, items) where paths is a list of directories and items is a list of Items that is probably an album. Specifically, any folder containing any media files is an album. """ collapse_pat = collapse_paths = collapse_items = None ignore = config["ignore"].as_str_seq() ignore_hidden = config["ignore_hidden"].get(bool) for root, dirs, files in sorted_walk( path, ignore=ignore, ignore_hidden=ignore_hidden, logger=log ): items = [os.path.join(root, f) for f in files] # If we're currently collapsing the constituent directories in a # multi-disc album, check whether we should continue collapsing # and add the current directory. If so, just add the directory # and move on to the next directory. If not, stop collapsing. if collapse_paths: if (is_subdir_of_any_in_list(root, collapse_paths)) or ( collapse_pat and collapse_pat.match(os.path.basename(root)) ): # Still collapsing. collapse_paths.append(root) collapse_items += items continue else: # Collapse finished. Yield the collapsed directory and # proceed to process the current one. if collapse_items: yield collapse_paths, collapse_items collapse_pat = collapse_paths = collapse_items = None # Check whether this directory looks like the *first* directory # in a multi-disc sequence. There are two indicators: the file # is named like part of a multi-disc sequence (e.g., "Title Disc # 1") or it contains no items but only directories that are # named in this way. start_collapsing = False for marker in MULTIDISC_MARKERS: # We're using replace on %s due to lack of .format() on bytestrings p = MULTIDISC_PAT_FMT.replace(b"%s", marker) marker_pat = re.compile(p, re.I) match = marker_pat.match(os.path.basename(root)) # Is this directory the root of a nested multi-disc album? if dirs and not items: # Check whether all subdirectories have the same prefix. start_collapsing = True subdir_pat = None for subdir in dirs: subdir = util.bytestring_path(subdir) # The first directory dictates the pattern for # the remaining directories. if not subdir_pat: match = marker_pat.match(subdir) if match: match_group = re.escape(match.group(1)) subdir_pat = re.compile( b"".join([b"^", match_group, rb"\d"]), re.I ) else: start_collapsing = False break # Subsequent directories must match the pattern. elif not subdir_pat.match(subdir): start_collapsing = False break # If all subdirectories match, don't check other # markers. if start_collapsing: break # Is this directory the first in a flattened multi-disc album? elif match: start_collapsing = True # Set the current pattern to match directories with the same # prefix as this one, followed by a digit. collapse_pat = re.compile( b"".join([b"^", re.escape(match.group(1)), rb"\d"]), re.I ) break # If either of the above heuristics indicated that this is the # beginning of a multi-disc album, initialize the collapsed # directory and item lists and check the next directory. if start_collapsing: # Start collapsing; continue to the next iteration. collapse_paths = [root] collapse_items = items continue # If it's nonempty, yield it. if items: yield [root], items # Clear out any unfinished collapse. if collapse_paths and collapse_items: yield collapse_paths, collapse_items beetbox-beets-01f1faf/beets/library.py000066400000000000000000002017211472325477400200720ustar00rootroot00000000000000# This file is part of beets. # Copyright 2016, Adrian Sampson. # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the # "Software"), to deal in the Software without restriction, including # without limitation the rights to use, copy, modify, merge, publish, # distribute, sublicense, and/or sell copies of the Software, and to # permit persons to whom the Software is furnished to do so, subject to # the following conditions: # # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. """The core data store and collection logic for beets.""" from __future__ import annotations import os import re import shlex import string import sys import time import unicodedata from functools import cached_property from pathlib import Path import platformdirs from mediafile import MediaFile, UnreadableFileError import beets from beets import dbcore, logging, plugins, util from beets.dbcore import Results, types from beets.util import ( MoveOperation, bytestring_path, cached_classproperty, normpath, samefile, syspath, ) from beets.util.functemplate import Template, template # To use the SQLite "blob" type, it doesn't suffice to provide a byte # string; SQLite treats that as encoded text. Wrapping it in a # `memoryview` tells it that we actually mean non-text data. BLOB_TYPE = memoryview log = logging.getLogger("beets") # Library-specific query types. class SingletonQuery(dbcore.FieldQuery[str]): """This query is responsible for the 'singleton' lookup. It is based on the FieldQuery and constructs a SQL clause 'album_id is NULL' which yields the same result as the previous filter in Python but is more performant since it's done in SQL. Using util.str2bool ensures that lookups like singleton:true, singleton:1 and singleton:false, singleton:0 are handled consistently. """ def __new__(cls, field: str, value: str, *args, **kwargs): query = dbcore.query.NoneQuery("album_id") if util.str2bool(value): return query return dbcore.query.NotQuery(query) class PathQuery(dbcore.FieldQuery[bytes]): """A query that matches all items under a given path. Matching can either be case-insensitive or case-sensitive. By default, the behavior depends on the OS: case-insensitive on Windows and case-sensitive otherwise. """ # For tests force_implicit_query_detection = False def __init__(self, field, pattern, fast=True, case_sensitive=None): """Create a path query. `pattern` must be a path, either to a file or a directory. `case_sensitive` can be a bool or `None`, indicating that the behavior should depend on the filesystem. """ super().__init__(field, pattern, fast) path = util.normpath(pattern) # By default, the case sensitivity depends on the filesystem # that the query path is located on. if case_sensitive is None: case_sensitive = util.case_sensitive(path) self.case_sensitive = case_sensitive # Use a normalized-case pattern for case-insensitive matches. if not case_sensitive: # We need to lowercase the entire path, not just the pattern. # In particular, on Windows, the drive letter is otherwise not # lowercased. # This also ensures that the `match()` method below and the SQL # from `col_clause()` do the same thing. path = path.lower() # Match the path as a single file. self.file_path = path # As a directory (prefix). self.dir_path = os.path.join(path, b"") @classmethod def is_path_query(cls, query_part): """Try to guess whether a unicode query part is a path query. Condition: separator precedes colon and the file exists. """ colon = query_part.find(":") if colon != -1: query_part = query_part[:colon] # Test both `sep` and `altsep` (i.e., both slash and backslash on # Windows). if not ( os.sep in query_part or (os.altsep and os.altsep in query_part) ): return False if cls.force_implicit_query_detection: return True return os.path.exists(syspath(normpath(query_part))) def match(self, item): path = item.path if self.case_sensitive else item.path.lower() return (path == self.file_path) or path.startswith(self.dir_path) def col_clause(self): file_blob = BLOB_TYPE(self.file_path) dir_blob = BLOB_TYPE(self.dir_path) if self.case_sensitive: query_part = "({0} = ?) || (substr({0}, 1, ?) = ?)" else: query_part = "(BYTELOWER({0}) = BYTELOWER(?)) || \ (substr(BYTELOWER({0}), 1, ?) = BYTELOWER(?))" return query_part.format(self.field), ( file_blob, len(dir_blob), dir_blob, ) def __repr__(self) -> str: return ( f"{self.__class__.__name__}({self.field!r}, {self.pattern!r}, " f"fast={self.fast}, case_sensitive={self.case_sensitive})" ) # Library-specific field types. class DateType(types.Float): # TODO representation should be `datetime` object # TODO distinguish between date and time types query = dbcore.query.DateQuery def format(self, value): return time.strftime( beets.config["time_format"].as_str(), time.localtime(value or 0) ) def parse(self, string): try: # Try a formatted date string. return time.mktime( time.strptime(string, beets.config["time_format"].as_str()) ) except ValueError: # Fall back to a plain timestamp number. try: return float(string) except ValueError: return self.null class PathType(types.Type[bytes, bytes]): """A dbcore type for filesystem paths. These are represented as `bytes` objects, in keeping with the Unix filesystem abstraction. """ sql = "BLOB" query = PathQuery model_type = bytes def __init__(self, nullable=False): """Create a path type object. `nullable` controls whether the type may be missing, i.e., None. """ self.nullable = nullable @property def null(self): if self.nullable: return None else: return b"" def format(self, value): return util.displayable_path(value) def parse(self, string): return normpath(bytestring_path(string)) def normalize(self, value): if isinstance(value, str): # Paths stored internally as encoded bytes. return bytestring_path(value) elif isinstance(value, BLOB_TYPE): # We unwrap buffers to bytes. return bytes(value) else: return value def from_sql(self, sql_value): return self.normalize(sql_value) def to_sql(self, value): if isinstance(value, bytes): value = BLOB_TYPE(value) return value class MusicalKey(types.String): """String representing the musical key of a song. The standard format is C, Cm, C#, C#m, etc. """ ENHARMONIC = { r"db": "c#", r"eb": "d#", r"gb": "f#", r"ab": "g#", r"bb": "a#", } null = None def parse(self, key): key = key.lower() for flat, sharp in self.ENHARMONIC.items(): key = re.sub(flat, sharp, key) key = re.sub(r"[\W\s]+minor", "m", key) key = re.sub(r"[\W\s]+major", "", key) return key.capitalize() def normalize(self, key): if key is None: return None else: return self.parse(key) class DurationType(types.Float): """Human-friendly (M:SS) representation of a time interval.""" query = dbcore.query.DurationQuery def format(self, value): if not beets.config["format_raw_length"].get(bool): return beets.ui.human_seconds_short(value or 0.0) else: return value def parse(self, string): try: # Try to format back hh:ss to seconds. return util.raw_seconds_short(string) except ValueError: # Fall back to a plain float. try: return float(string) except ValueError: return self.null # Library-specific sort types. class SmartArtistSort(dbcore.query.Sort): """Sort by artist (either album artist or track artist), prioritizing the sort field over the raw field. """ def __init__(self, model_cls, ascending=True, case_insensitive=True): self.album = model_cls is Album self.ascending = ascending self.case_insensitive = case_insensitive def order_clause(self): order = "ASC" if self.ascending else "DESC" field = "albumartist" if self.album else "artist" collate = "COLLATE NOCASE" if self.case_insensitive else "" return f"COALESCE(NULLIF({field}_sort, ''), {field}) {collate} {order}" def sort(self, objs): if self.album: def field(a): return a.albumartist_sort or a.albumartist else: def field(i): return i.artist_sort or i.artist if self.case_insensitive: def key(x): return field(x).lower() else: key = field return sorted(objs, key=key, reverse=not self.ascending) # Special path format key. PF_KEY_DEFAULT = "default" # Exceptions. class FileOperationError(Exception): """Indicate an error when interacting with a file on disk. Possibilities include an unsupported media type, a permissions error, and an unhandled Mutagen exception. """ def __init__(self, path, reason): """Create an exception describing an operation on the file at `path` with the underlying (chained) exception `reason`. """ super().__init__(path, reason) self.path = path self.reason = reason def __str__(self): """Get a string representing the error. Describe both the underlying reason and the file path in question. """ return f"{util.displayable_path(self.path)}: {self.reason}" class ReadError(FileOperationError): """An error while reading a file (i.e. in `Item.read`).""" def __str__(self): return "error reading " + str(super()) class WriteError(FileOperationError): """An error while writing a file (i.e. in `Item.write`).""" def __str__(self): return "error writing " + str(super()) # Item and Album model classes. class LibModel(dbcore.Model): """Shared concrete functionality for Items and Albums.""" # Config key that specifies how an instance should be formatted. _format_config_key: str def _template_funcs(self): funcs = DefaultTemplateFunctions(self, self._db).functions() funcs.update(plugins.template_funcs()) return funcs def store(self, fields=None): super().store(fields) plugins.send("database_change", lib=self._db, model=self) def remove(self): super().remove() plugins.send("database_change", lib=self._db, model=self) def add(self, lib=None): super().add(lib) plugins.send("database_change", lib=self._db, model=self) def __format__(self, spec): if not spec: spec = beets.config[self._format_config_key].as_str() assert isinstance(spec, str) return self.evaluate_template(spec) def __str__(self): return format(self) def __bytes__(self): return self.__str__().encode("utf-8") class FormattedItemMapping(dbcore.db.FormattedMapping): """Add lookup for album-level fields. Album-level fields take precedence if `for_path` is true. """ ALL_KEYS = "*" def __init__(self, item, included_keys=ALL_KEYS, for_path=False): # We treat album and item keys specially here, # so exclude transitive album keys from the model's keys. super().__init__(item, included_keys=[], for_path=for_path) self.included_keys = included_keys if included_keys == self.ALL_KEYS: # Performance note: this triggers a database query. self.model_keys = item.keys(computed=True, with_album=False) else: self.model_keys = included_keys self.item = item @cached_property def all_keys(self): return set(self.model_keys).union(self.album_keys) @cached_property def album_keys(self): album_keys = [] if self.album: if self.included_keys == self.ALL_KEYS: # Performance note: this triggers a database query. for key in self.album.keys(computed=True): if ( key in Album.item_keys or key not in self.item._fields.keys() ): album_keys.append(key) else: album_keys = self.included_keys return album_keys @property def album(self): return self.item._cached_album def _get(self, key): """Get the value for a key, either from the album or the item. Raise a KeyError for invalid keys. """ if self.for_path and key in self.album_keys: return self._get_formatted(self.album, key) elif key in self.model_keys: return self._get_formatted(self.model, key) elif key in self.album_keys: return self._get_formatted(self.album, key) else: raise KeyError(key) def __getitem__(self, key): """Get the value for a key. `artist` and `albumartist` are fallback values for each other when not set. """ value = self._get(key) # `artist` and `albumartist` fields fall back to one another. # This is helpful in path formats when the album artist is unset # on as-is imports. try: if key == "artist" and not value: return self._get("albumartist") elif key == "albumartist" and not value: return self._get("artist") except KeyError: pass return value def __iter__(self): return iter(self.all_keys) def __len__(self): return len(self.all_keys) class Item(LibModel): """Represent a song or track.""" _table = "items" _flex_table = "item_attributes" _fields = { "id": types.PRIMARY_ID, "path": PathType(), "album_id": types.FOREIGN_ID, "title": types.STRING, "artist": types.STRING, "artists": types.MULTI_VALUE_DSV, "artists_ids": types.MULTI_VALUE_DSV, "artist_sort": types.STRING, "artists_sort": types.MULTI_VALUE_DSV, "artist_credit": types.STRING, "artists_credit": types.MULTI_VALUE_DSV, "remixer": types.STRING, "album": types.STRING, "albumartist": types.STRING, "albumartists": types.MULTI_VALUE_DSV, "albumartist_sort": types.STRING, "albumartists_sort": types.MULTI_VALUE_DSV, "albumartist_credit": types.STRING, "albumartists_credit": types.MULTI_VALUE_DSV, "genre": types.STRING, "style": types.STRING, "discogs_albumid": types.INTEGER, "discogs_artistid": types.INTEGER, "discogs_labelid": types.INTEGER, "lyricist": types.STRING, "composer": types.STRING, "composer_sort": types.STRING, "work": types.STRING, "mb_workid": types.STRING, "work_disambig": types.STRING, "arranger": types.STRING, "grouping": types.STRING, "year": types.PaddedInt(4), "month": types.PaddedInt(2), "day": types.PaddedInt(2), "track": types.PaddedInt(2), "tracktotal": types.PaddedInt(2), "disc": types.PaddedInt(2), "disctotal": types.PaddedInt(2), "lyrics": types.STRING, "comments": types.STRING, "bpm": types.INTEGER, "comp": types.BOOLEAN, "mb_trackid": types.STRING, "mb_albumid": types.STRING, "mb_artistid": types.STRING, "mb_artistids": types.MULTI_VALUE_DSV, "mb_albumartistid": types.STRING, "mb_albumartistids": types.MULTI_VALUE_DSV, "mb_releasetrackid": types.STRING, "trackdisambig": types.STRING, "albumtype": types.STRING, "albumtypes": types.SEMICOLON_SPACE_DSV, "label": types.STRING, "barcode": types.STRING, "acoustid_fingerprint": types.STRING, "acoustid_id": types.STRING, "mb_releasegroupid": types.STRING, "release_group_title": types.STRING, "asin": types.STRING, "isrc": types.STRING, "catalognum": types.STRING, "script": types.STRING, "language": types.STRING, "country": types.STRING, "albumstatus": types.STRING, "media": types.STRING, "albumdisambig": types.STRING, "releasegroupdisambig": types.STRING, "disctitle": types.STRING, "encoder": types.STRING, "rg_track_gain": types.NULL_FLOAT, "rg_track_peak": types.NULL_FLOAT, "rg_album_gain": types.NULL_FLOAT, "rg_album_peak": types.NULL_FLOAT, "r128_track_gain": types.NULL_FLOAT, "r128_album_gain": types.NULL_FLOAT, "original_year": types.PaddedInt(4), "original_month": types.PaddedInt(2), "original_day": types.PaddedInt(2), "initial_key": MusicalKey(), "length": DurationType(), "bitrate": types.ScaledInt(1000, "kbps"), "bitrate_mode": types.STRING, "encoder_info": types.STRING, "encoder_settings": types.STRING, "format": types.STRING, "samplerate": types.ScaledInt(1000, "kHz"), "bitdepth": types.INTEGER, "channels": types.INTEGER, "mtime": DateType(), "added": DateType(), } _search_fields = ( "artist", "title", "comments", "album", "albumartist", "genre", ) _types = { "data_source": types.STRING, } # Set of item fields that are backed by `MediaFile` fields. # Any kind of field (fixed, flexible, and computed) may be a media # field. Only these fields are read from disk in `read` and written in # `write`. _media_fields = set(MediaFile.readable_fields()).intersection( _fields.keys() ) # Set of item fields that are backed by *writable* `MediaFile` tag # fields. # This excludes fields that represent audio data, such as `bitrate` or # `length`. _media_tag_fields = set(MediaFile.fields()).intersection(_fields.keys()) _formatter = FormattedItemMapping _sorts = {"artist": SmartArtistSort} _queries = {"singleton": SingletonQuery} _format_config_key = "format_item" # Cached album object. Read-only. __album = None @cached_classproperty def _relation(cls) -> type[Album]: return Album @cached_classproperty def relation_join(cls) -> str: """Return the FROM clause which includes related albums. We need to use a LEFT JOIN here, otherwise items that are not part of an album (e.g. singletons) would be left out. """ return ( f"LEFT JOIN {cls._relation._table} " f"ON {cls._table}.album_id = {cls._relation._table}.id" ) @property def filepath(self) -> Path | None: """The path to the item's file as pathlib.Path.""" return Path(os.fsdecode(self.path)) if self.path else self.path @property def _cached_album(self): """The Album object that this item belongs to, if any, or None if the item is a singleton or is not associated with a library. The instance is cached and refreshed on access. DO NOT MODIFY! If you want a copy to modify, use :meth:`get_album`. """ if not self.__album and self._db: self.__album = self._db.get_album(self) elif self.__album: self.__album.load() return self.__album @_cached_album.setter def _cached_album(self, album): self.__album = album @classmethod def _getters(cls): getters = plugins.item_field_getters() getters["singleton"] = lambda i: i.album_id is None getters["filesize"] = Item.try_filesize # In bytes. return getters @classmethod def from_path(cls, path): """Create a new item from the media file at the specified path.""" # Initiate with values that aren't read from files. i = cls(album_id=None) i.read(path) i.mtime = i.current_mtime() # Initial mtime. return i def __setitem__(self, key, value): """Set the item's value for a standard field or a flexattr.""" # Encode unicode paths and read buffers. if key == "path": if isinstance(value, str): value = bytestring_path(value) elif isinstance(value, BLOB_TYPE): value = bytes(value) elif key == "album_id": self._cached_album = None changed = super()._setitem(key, value) if changed and key in MediaFile.fields(): self.mtime = 0 # Reset mtime on dirty. def __getitem__(self, key): """Get the value for a field, falling back to the album if necessary. Raise a KeyError if the field is not available. """ try: return super().__getitem__(key) except KeyError: if self._cached_album: return self._cached_album[key] raise def __repr__(self): # This must not use `with_album=True`, because that might access # the database. When debugging, that is not guaranteed to succeed, and # can even deadlock due to the database lock. return "{}({})".format( type(self).__name__, ", ".join( "{}={!r}".format(k, self[k]) for k in self.keys(with_album=False) ), ) def keys(self, computed=False, with_album=True): """Get a list of available field names. `with_album` controls whether the album's fields are included. """ keys = super().keys(computed=computed) if with_album and self._cached_album: keys = set(keys) keys.update(self._cached_album.keys(computed=computed)) keys = list(keys) return keys def get(self, key, default=None, with_album=True): """Get the value for a given key or `default` if it does not exist. Set `with_album` to false to skip album fallback. """ try: return self._get(key, default, raise_=with_album) except KeyError: if self._cached_album: return self._cached_album.get(key, default) return default def update(self, values): """Set all key/value pairs in the mapping. If mtime is specified, it is not reset (as it might otherwise be). """ super().update(values) if self.mtime == 0 and "mtime" in values: self.mtime = values["mtime"] def clear(self): """Set all key/value pairs to None.""" for key in self._media_tag_fields: setattr(self, key, None) def get_album(self): """Get the Album object that this item belongs to, if any, or None if the item is a singleton or is not associated with a library. """ if not self._db: return None return self._db.get_album(self) # Interaction with file metadata. def read(self, read_path=None): """Read the metadata from the associated file. If `read_path` is specified, read metadata from that file instead. Update all the properties in `_media_fields` from the media file. Raise a `ReadError` if the file could not be read. """ if read_path is None: read_path = self.path else: read_path = normpath(read_path) try: mediafile = MediaFile(syspath(read_path)) except UnreadableFileError as exc: raise ReadError(read_path, exc) for key in self._media_fields: value = getattr(mediafile, key) if isinstance(value, int): if value.bit_length() > 63: value = 0 self[key] = value # Database's mtime should now reflect the on-disk value. if read_path == self.path: self.mtime = self.current_mtime() self.path = read_path def write(self, path=None, tags=None, id3v23=None): """Write the item's metadata to a media file. All fields in `_media_fields` are written to disk according to the values on this object. `path` is the path of the mediafile to write the data to. It defaults to the item's path. `tags` is a dictionary of additional metadata the should be written to the file. (These tags need not be in `_media_fields`.) `id3v23` will override the global `id3v23` config option if it is set to something other than `None`. Can raise either a `ReadError` or a `WriteError`. """ if path is None: path = self.path else: path = normpath(path) if id3v23 is None: id3v23 = beets.config["id3v23"].get(bool) # Get the data to write to the file. item_tags = dict(self) item_tags = { k: v for k, v in item_tags.items() if k in self._media_fields } # Only write media fields. if tags is not None: item_tags.update(tags) plugins.send("write", item=self, path=path, tags=item_tags) # Open the file. try: mediafile = MediaFile(syspath(path), id3v23=id3v23) except UnreadableFileError as exc: raise ReadError(path, exc) # Write the tags to the file. mediafile.update(item_tags) try: mediafile.save() except UnreadableFileError as exc: raise WriteError(self.path, exc) # The file has a new mtime. if path == self.path: self.mtime = self.current_mtime() plugins.send("after_write", item=self, path=path) def try_write(self, *args, **kwargs): """Call `write()` but catch and log `FileOperationError` exceptions. Return `False` an exception was caught and `True` otherwise. """ try: self.write(*args, **kwargs) return True except FileOperationError as exc: log.error("{0}", exc) return False def try_sync(self, write, move, with_album=True): """Synchronize the item with the database and, possibly, update its tags on disk and its path (by moving the file). `write` indicates whether to write new tags into the file. Similarly, `move` controls whether the path should be updated. In the latter case, files are *only* moved when they are inside their library's directory (if any). Similar to calling :meth:`write`, :meth:`move`, and :meth:`store` (conditionally). """ if write: self.try_write() if move: # Check whether this file is inside the library directory. if self._db and self._db.directory in util.ancestry(self.path): log.debug( "moving {0} to synchronize path", util.displayable_path(self.path), ) self.move(with_album=with_album) self.store() # Files themselves. def move_file(self, dest, operation=MoveOperation.MOVE): """Move, copy, link or hardlink the item depending on `operation`, updating the path value if the move succeeds. If a file exists at `dest`, then it is slightly modified to be unique. `operation` should be an instance of `util.MoveOperation`. """ if not util.samefile(self.path, dest): dest = util.unique_path(dest) if operation == MoveOperation.MOVE: plugins.send( "before_item_moved", item=self, source=self.path, destination=dest, ) util.move(self.path, dest) plugins.send( "item_moved", item=self, source=self.path, destination=dest ) elif operation == MoveOperation.COPY: util.copy(self.path, dest) plugins.send( "item_copied", item=self, source=self.path, destination=dest ) elif operation == MoveOperation.LINK: util.link(self.path, dest) plugins.send( "item_linked", item=self, source=self.path, destination=dest ) elif operation == MoveOperation.HARDLINK: util.hardlink(self.path, dest) plugins.send( "item_hardlinked", item=self, source=self.path, destination=dest ) elif operation == MoveOperation.REFLINK: util.reflink(self.path, dest, fallback=False) plugins.send( "item_reflinked", item=self, source=self.path, destination=dest ) elif operation == MoveOperation.REFLINK_AUTO: util.reflink(self.path, dest, fallback=True) plugins.send( "item_reflinked", item=self, source=self.path, destination=dest ) else: assert False, "unknown MoveOperation" # Either copying or moving succeeded, so update the stored path. self.path = dest def current_mtime(self): """Return the current mtime of the file, rounded to the nearest integer. """ return int(os.path.getmtime(syspath(self.path))) def try_filesize(self): """Get the size of the underlying file in bytes. If the file is missing, return 0 (and log a warning). """ try: return os.path.getsize(syspath(self.path)) except (OSError, Exception) as exc: log.warning("could not get filesize: {0}", exc) return 0 # Model methods. def remove(self, delete=False, with_album=True): """Remove the item. If `delete`, then the associated file is removed from disk. If `with_album`, then the item's album (if any) is removed if the item was the last in the album. """ super().remove() # Remove the album if it is empty. if with_album: album = self.get_album() if album and not album.items(): album.remove(delete, False) # Send a 'item_removed' signal to plugins plugins.send("item_removed", item=self) # Delete the associated file. if delete: util.remove(self.path) util.prune_dirs(os.path.dirname(self.path), self._db.directory) self._db._memotable = {} def move( self, operation=MoveOperation.MOVE, basedir=None, with_album=True, store=True, ): """Move the item to its designated location within the library directory (provided by destination()). Subdirectories are created as needed. If the operation succeeds, the item's path field is updated to reflect the new location. Instead of moving the item it can also be copied, linked or hardlinked depending on `operation` which should be an instance of `util.MoveOperation`. `basedir` overrides the library base directory for the destination. If the item is in an album and `with_album` is `True`, the album is given an opportunity to move its art. By default, the item is stored to the database if it is in the database, so any dirty fields prior to the move() call will be written as a side effect. If `store` is `False` however, the item won't be stored and it will have to be manually stored after invoking this method. """ self._check_db() dest = self.destination(basedir=basedir) # Create necessary ancestry for the move. util.mkdirall(dest) # Perform the move and store the change. old_path = self.path self.move_file(dest, operation) if store: self.store() # If this item is in an album, move its art. if with_album: album = self.get_album() if album: album.move_art(operation) if store: album.store() # Prune vacated directory. if operation == MoveOperation.MOVE: util.prune_dirs(os.path.dirname(old_path), self._db.directory) # Templating. def destination( self, fragment=False, basedir=None, platform=None, path_formats=None, replacements=None, ): """Return the path in the library directory designated for the item (i.e., where the file ought to be). fragment makes this method return just the path fragment underneath the root library directory; the path is also returned as Unicode instead of encoded as a bytestring. basedir can override the library's base directory for the destination. """ self._check_db() platform = platform or sys.platform basedir = basedir or self._db.directory path_formats = path_formats or self._db.path_formats if replacements is None: replacements = self._db.replacements # Use a path format based on a query, falling back on the # default. for query, path_format in path_formats: if query == PF_KEY_DEFAULT: continue query, _ = parse_query_string(query, type(self)) if query.match(self): # The query matches the item! Use the corresponding path # format. break else: # No query matched; fall back to default. for query, path_format in path_formats: if query == PF_KEY_DEFAULT: break else: assert False, "no default path format" if isinstance(path_format, Template): subpath_tmpl = path_format else: subpath_tmpl = template(path_format) # Evaluate the selected template. subpath = self.evaluate_template(subpath_tmpl, True) # Prepare path for output: normalize Unicode characters. if platform == "darwin": subpath = unicodedata.normalize("NFD", subpath) else: subpath = unicodedata.normalize("NFC", subpath) if beets.config["asciify_paths"]: subpath = util.asciify_path( subpath, beets.config["path_sep_replace"].as_str() ) maxlen = beets.config["max_filename_length"].get(int) if not maxlen: # When zero, try to determine from filesystem. maxlen = util.max_filename_length(self._db.directory) subpath, fellback = util.legalize_path( subpath, replacements, maxlen, os.path.splitext(self.path)[1], fragment, ) if fellback: # Print an error message if legalization fell back to # default replacements because of the maximum length. log.warning( "Fell back to default replacements when naming " "file {}. Configure replacements to avoid lengthening " "the filename.", subpath, ) if fragment: return util.as_string(subpath) else: return normpath(os.path.join(basedir, subpath)) class Album(LibModel): """Provide access to information about albums stored in a library. Reflects the library's "albums" table, including album art. """ _table = "albums" _flex_table = "album_attributes" _always_dirty = True _fields = { "id": types.PRIMARY_ID, "artpath": PathType(True), "added": DateType(), "albumartist": types.STRING, "albumartist_sort": types.STRING, "albumartist_credit": types.STRING, "albumartists": types.MULTI_VALUE_DSV, "albumartists_sort": types.MULTI_VALUE_DSV, "albumartists_credit": types.MULTI_VALUE_DSV, "album": types.STRING, "genre": types.STRING, "style": types.STRING, "discogs_albumid": types.INTEGER, "discogs_artistid": types.INTEGER, "discogs_labelid": types.INTEGER, "year": types.PaddedInt(4), "month": types.PaddedInt(2), "day": types.PaddedInt(2), "disctotal": types.PaddedInt(2), "comp": types.BOOLEAN, "mb_albumid": types.STRING, "mb_albumartistid": types.STRING, "albumtype": types.STRING, "albumtypes": types.SEMICOLON_SPACE_DSV, "label": types.STRING, "barcode": types.STRING, "mb_releasegroupid": types.STRING, "release_group_title": types.STRING, "asin": types.STRING, "catalognum": types.STRING, "script": types.STRING, "language": types.STRING, "country": types.STRING, "albumstatus": types.STRING, "albumdisambig": types.STRING, "releasegroupdisambig": types.STRING, "rg_album_gain": types.NULL_FLOAT, "rg_album_peak": types.NULL_FLOAT, "r128_album_gain": types.NULL_FLOAT, "original_year": types.PaddedInt(4), "original_month": types.PaddedInt(2), "original_day": types.PaddedInt(2), } _search_fields = ("album", "albumartist", "genre") _types = { "path": PathType(), "data_source": types.STRING, } _sorts = { "albumartist": SmartArtistSort, "artist": SmartArtistSort, } # List of keys that are set on an album's items. item_keys = [ "added", "albumartist", "albumartists", "albumartist_sort", "albumartists_sort", "albumartist_credit", "albumartists_credit", "album", "genre", "style", "discogs_albumid", "discogs_artistid", "discogs_labelid", "year", "month", "day", "disctotal", "comp", "mb_albumid", "mb_albumartistid", "albumtype", "albumtypes", "label", "barcode", "mb_releasegroupid", "asin", "catalognum", "script", "language", "country", "albumstatus", "albumdisambig", "releasegroupdisambig", "release_group_title", "rg_album_gain", "rg_album_peak", "r128_album_gain", "original_year", "original_month", "original_day", ] _format_config_key = "format_album" @cached_classproperty def _relation(cls) -> type[Item]: return Item @cached_classproperty def relation_join(cls) -> str: """Return FROM clause which joins on related album items. Use LEFT join to select all albums, including those that do not have any items. """ return ( f"LEFT JOIN {cls._relation._table} " f"ON {cls._table}.id = {cls._relation._table}.album_id" ) @classmethod def _getters(cls): # In addition to plugin-provided computed fields, also expose # the album's directory as `path`. getters = plugins.album_field_getters() getters["path"] = Album.item_dir getters["albumtotal"] = Album._albumtotal return getters def items(self): """Return an iterable over the items associated with this album. This method conflicts with :meth:`LibModel.items`, which is inherited from :meth:`beets.dbcore.Model.items`. Since :meth:`Album.items` predates these methods, and is likely to be used by plugins, we keep this interface as-is. """ return self._db.items(dbcore.MatchQuery("album_id", self.id)) def remove(self, delete=False, with_items=True): """Remove this album and all its associated items from the library. If delete, then the items' files are also deleted from disk, along with any album art. The directories containing the album are also removed (recursively) if empty. Set with_items to False to avoid removing the album's items. """ super().remove() # Send a 'album_removed' signal to plugins plugins.send("album_removed", album=self) # Delete art file. if delete: artpath = self.artpath if artpath: util.remove(artpath) # Remove (and possibly delete) the constituent items. if with_items: for item in self.items(): item.remove(delete, False) def move_art(self, operation=MoveOperation.MOVE): """Move, copy, link or hardlink (depending on `operation`) any existing album art so that it remains in the same directory as the items. `operation` should be an instance of `util.MoveOperation`. """ old_art = self.artpath if not old_art: return if not os.path.exists(syspath(old_art)): log.error( "removing reference to missing album art file {}", util.displayable_path(old_art), ) self.artpath = None return new_art = self.art_destination(old_art) if new_art == old_art: return new_art = util.unique_path(new_art) log.debug( "moving album art {0} to {1}", util.displayable_path(old_art), util.displayable_path(new_art), ) if operation == MoveOperation.MOVE: util.move(old_art, new_art) util.prune_dirs(os.path.dirname(old_art), self._db.directory) elif operation == MoveOperation.COPY: util.copy(old_art, new_art) elif operation == MoveOperation.LINK: util.link(old_art, new_art) elif operation == MoveOperation.HARDLINK: util.hardlink(old_art, new_art) elif operation == MoveOperation.REFLINK: util.reflink(old_art, new_art, fallback=False) elif operation == MoveOperation.REFLINK_AUTO: util.reflink(old_art, new_art, fallback=True) else: assert False, "unknown MoveOperation" self.artpath = new_art def move(self, operation=MoveOperation.MOVE, basedir=None, store=True): """Move, copy, link or hardlink (depending on `operation`) all items to their destination. Any album art moves along with them. `basedir` overrides the library base directory for the destination. `operation` should be an instance of `util.MoveOperation`. By default, the album is stored to the database, persisting any modifications to its metadata. If `store` is `False` however, the album is not stored automatically, and it will have to be manually stored after invoking this method. """ basedir = basedir or self._db.directory # Ensure new metadata is available to items for destination # computation. if store: self.store() # Move items. items = list(self.items()) for item in items: item.move(operation, basedir=basedir, with_album=False, store=store) # Move art. self.move_art(operation) if store: self.store() def item_dir(self): """Return the directory containing the album's first item, provided that such an item exists. """ item = self.items().get() if not item: raise ValueError("empty album for album id %d" % self.id) return os.path.dirname(item.path) def _albumtotal(self): """Return the total number of tracks on all discs on the album.""" if self.disctotal == 1 or not beets.config["per_disc_numbering"]: return self.items()[0].tracktotal counted = [] total = 0 for item in self.items(): if item.disc in counted: continue total += item.tracktotal counted.append(item.disc) if len(counted) == self.disctotal: break return total def art_destination(self, image, item_dir=None): """Return a path to the destination for the album art image for the album. `image` is the path of the image that will be moved there (used for its extension). The path construction uses the existing path of the album's items, so the album must contain at least one item or item_dir must be provided. """ image = bytestring_path(image) item_dir = item_dir or self.item_dir() filename_tmpl = template(beets.config["art_filename"].as_str()) subpath = self.evaluate_template(filename_tmpl, True) if beets.config["asciify_paths"]: subpath = util.asciify_path( subpath, beets.config["path_sep_replace"].as_str() ) subpath = util.sanitize_path( subpath, replacements=self._db.replacements ) subpath = bytestring_path(subpath) _, ext = os.path.splitext(image) dest = os.path.join(item_dir, subpath + ext) return bytestring_path(dest) def set_art(self, path, copy=True): """Set the album's cover art to the image at the given path. The image is copied (or moved) into place, replacing any existing art. Send an 'art_set' event with `self` as the sole argument. """ path = bytestring_path(path) oldart = self.artpath artdest = self.art_destination(path) if oldart and samefile(path, oldart): # Art already set. return elif samefile(path, artdest): # Art already in place. self.artpath = path return # Normal operation. if oldart == artdest: util.remove(oldart) artdest = util.unique_path(artdest) if copy: util.copy(path, artdest) else: util.move(path, artdest) self.artpath = artdest plugins.send("art_set", album=self) def store(self, fields=None, inherit=True): """Update the database with the album information. `fields` represents the fields to be stored. If not specified, all fields will be. The album's tracks are also updated when the `inherit` flag is enabled. This applies to fixed attributes as well as flexible ones. The `id` attribute of the album will never be inherited. """ # Get modified track fields. track_updates = {} track_deletes = set() for key in self._dirty: if inherit: if key in self.item_keys: # is a fixed attribute track_updates[key] = self[key] elif key not in self: # is a fixed or a flexible attribute track_deletes.add(key) elif key != "id": # is a flexible attribute track_updates[key] = self[key] with self._db.transaction(): super().store(fields) if track_updates: for item in self.items(): for key, value in track_updates.items(): item[key] = value item.store() if track_deletes: for item in self.items(): for key in track_deletes: if key in item: del item[key] item.store() def try_sync(self, write, move, inherit=True): """Synchronize the album and its items with the database. Optionally, also write any new tags into the files and update their paths. `write` indicates whether to write tags to the item files, and `move` controls whether files (both audio and album art) are moved. """ self.store(inherit=inherit) for item in self.items(): item.try_sync(write, move) # Query construction helpers. def parse_query_parts(parts, model_cls): """Given a beets query string as a list of components, return the `Query` and `Sort` they represent. Like `dbcore.parse_sorted_query`, with beets query prefixes and ensuring that implicit path queries are made explicit with 'path::' """ # Get query types and their prefix characters. prefixes = { ":": dbcore.query.RegexpQuery, "=~": dbcore.query.StringQuery, "=": dbcore.query.MatchQuery, } prefixes.update(plugins.queries()) # Special-case path-like queries, which are non-field queries # containing path separators (/). parts = [f"path:{s}" if PathQuery.is_path_query(s) else s for s in parts] case_insensitive = beets.config["sort_case_insensitive"].get(bool) query, sort = dbcore.parse_sorted_query( model_cls, parts, prefixes, case_insensitive ) log.debug("Parsed query: {!r}", query) log.debug("Parsed sort: {!r}", sort) return query, sort def parse_query_string(s, model_cls): """Given a beets query string, return the `Query` and `Sort` they represent. The string is split into components using shell-like syntax. """ message = f"Query is not unicode: {s!r}" assert isinstance(s, str), message try: parts = shlex.split(s) except ValueError as exc: raise dbcore.InvalidQueryError(s, exc) return parse_query_parts(parts, model_cls) # The Library: interface to the database. class Library(dbcore.Database): """A database of music containing songs and albums.""" _models = (Item, Album) def __init__( self, path="library.blb", directory: str | None = None, path_formats=((PF_KEY_DEFAULT, "$artist/$album/$track $title"),), replacements=None, ): timeout = beets.config["timeout"].as_number() super().__init__(path, timeout=timeout) self.directory = normpath(directory or platformdirs.user_music_path()) self.path_formats = path_formats self.replacements = replacements self._memotable = {} # Used for template substitution performance. # Adding objects to the database. def add(self, obj): """Add the :class:`Item` or :class:`Album` object to the library database. Return the object's new id. """ obj.add(self) self._memotable = {} return obj.id def add_album(self, items): """Create a new album consisting of a list of items. The items are added to the database if they don't yet have an ID. Return a new :class:`Album` object. The list items must not be empty. """ if not items: raise ValueError("need at least one item") # Create the album structure using metadata from the first item. values = {key: items[0][key] for key in Album.item_keys} album = Album(self, **values) # Add the album structure and set the items' album_id fields. # Store or add the items. with self.transaction(): album.add(self) for item in items: item.album_id = album.id if item.id is None: item.add(self) else: item.store() return album # Querying. def _fetch(self, model_cls, query, sort=None): """Parse a query and fetch. If an order specification is present in the query string the `sort` argument is ignored. """ # Parse the query, if necessary. try: parsed_sort = None if isinstance(query, str): query, parsed_sort = parse_query_string(query, model_cls) elif isinstance(query, (list, tuple)): query, parsed_sort = parse_query_parts(query, model_cls) except dbcore.query.InvalidQueryArgumentValueError as exc: raise dbcore.InvalidQueryError(query, exc) # Any non-null sort specified by the parsed query overrides the # provided sort. if parsed_sort and not isinstance(parsed_sort, dbcore.query.NullSort): sort = parsed_sort return super()._fetch(model_cls, query, sort) @staticmethod def get_default_album_sort(): """Get a :class:`Sort` object for albums from the config option.""" return dbcore.sort_from_strings( Album, beets.config["sort_album"].as_str_seq() ) @staticmethod def get_default_item_sort(): """Get a :class:`Sort` object for items from the config option.""" return dbcore.sort_from_strings( Item, beets.config["sort_item"].as_str_seq() ) def albums(self, query=None, sort=None) -> Results[Album]: """Get :class:`Album` objects matching the query.""" return self._fetch(Album, query, sort or self.get_default_album_sort()) def items(self, query=None, sort=None) -> Results[Item]: """Get :class:`Item` objects matching the query.""" return self._fetch(Item, query, sort or self.get_default_item_sort()) # Convenience accessors. def get_item(self, id): """Fetch a :class:`Item` by its ID. Return `None` if no match is found. """ return self._get(Item, id) def get_album(self, item_or_id): """Given an album ID or an item associated with an album, return a :class:`Album` object for the album. If no such album exists, return `None`. """ if isinstance(item_or_id, int): album_id = item_or_id else: album_id = item_or_id.album_id if album_id is None: return None return self._get(Album, album_id) # Default path template resources. def _int_arg(s): """Convert a string argument to an integer for use in a template function. May raise a ValueError. """ return int(s.strip()) class DefaultTemplateFunctions: """A container class for the default functions provided to path templates. These functions are contained in an object to provide additional context to the functions -- specifically, the Item being evaluated. """ _prefix = "tmpl_" @cached_classproperty def _func_names(cls) -> list[str]: """Names of tmpl_* functions in this class.""" return [s for s in dir(cls) if s.startswith(cls._prefix)] def __init__(self, item=None, lib=None): """Parametrize the functions. If `item` or `lib` is None, then some functions (namely, ``aunique``) will always evaluate to the empty string. """ self.item = item self.lib = lib def functions(self): """Return a dictionary containing the functions defined in this object. The keys are function names (as exposed in templates) and the values are Python functions. """ out = {} for key in self._func_names: out[key[len(self._prefix) :]] = getattr(self, key) return out @staticmethod def tmpl_lower(s): """Convert a string to lower case.""" return s.lower() @staticmethod def tmpl_upper(s): """Convert a string to upper case.""" return s.upper() @staticmethod def tmpl_capitalize(s): """Converts to a capitalized string.""" return s.capitalize() @staticmethod def tmpl_title(s): """Convert a string to title case.""" return string.capwords(s) @staticmethod def tmpl_left(s, chars): """Get the leftmost characters of a string.""" return s[0 : _int_arg(chars)] @staticmethod def tmpl_right(s, chars): """Get the rightmost characters of a string.""" return s[-_int_arg(chars) :] @staticmethod def tmpl_if(condition, trueval, falseval=""): """If ``condition`` is nonempty and nonzero, emit ``trueval``; otherwise, emit ``falseval`` (if provided). """ try: int_condition = _int_arg(condition) except ValueError: if condition.lower() == "false": return falseval else: condition = int_condition if condition: return trueval else: return falseval @staticmethod def tmpl_asciify(s): """Translate non-ASCII characters to their ASCII equivalents.""" return util.asciify_path(s, beets.config["path_sep_replace"].as_str()) @staticmethod def tmpl_time(s, fmt): """Format a time value using `strftime`.""" cur_fmt = beets.config["time_format"].as_str() return time.strftime(fmt, time.strptime(s, cur_fmt)) def tmpl_aunique(self, keys=None, disam=None, bracket=None): """Generate a string that is guaranteed to be unique among all albums in the library who share the same set of keys. A fields from "disam" is used in the string if one is sufficient to disambiguate the albums. Otherwise, a fallback opaque value is used. Both "keys" and "disam" should be given as whitespace-separated lists of field names, while "bracket" is a pair of characters to be used as brackets surrounding the disambiguator or empty to have no brackets. """ # Fast paths: no album, no item or library, or memoized value. if not self.item or not self.lib: return "" if isinstance(self.item, Item): album_id = self.item.album_id elif isinstance(self.item, Album): album_id = self.item.id if album_id is None: return "" memokey = self._tmpl_unique_memokey("aunique", keys, disam, album_id) memoval = self.lib._memotable.get(memokey) if memoval is not None: return memoval album = self.lib.get_album(album_id) return self._tmpl_unique( "aunique", keys, disam, bracket, album_id, album, album.item_keys, # Do nothing for singletons. lambda a: a is None, ) def tmpl_sunique(self, keys=None, disam=None, bracket=None): """Generate a string that is guaranteed to be unique among all singletons in the library who share the same set of keys. A fields from "disam" is used in the string if one is sufficient to disambiguate the albums. Otherwise, a fallback opaque value is used. Both "keys" and "disam" should be given as whitespace-separated lists of field names, while "bracket" is a pair of characters to be used as brackets surrounding the disambiguator or empty to have no brackets. """ # Fast paths: no album, no item or library, or memoized value. if not self.item or not self.lib: return "" if isinstance(self.item, Item): item_id = self.item.id else: raise NotImplementedError("sunique is only implemented for items") if item_id is None: return "" return self._tmpl_unique( "sunique", keys, disam, bracket, item_id, self.item, Item.all_keys(), # Do nothing for non singletons. lambda i: i.album_id is not None, initial_subqueries=[dbcore.query.NoneQuery("album_id", True)], ) def _tmpl_unique_memokey(self, name, keys, disam, item_id): """Get the memokey for the unique template named "name" for the specific parameters. """ return (name, keys, disam, item_id) def _tmpl_unique( self, name, keys, disam, bracket, item_id, db_item, item_keys, skip_item, initial_subqueries=None, ): """Generate a string that is guaranteed to be unique among all items of the same type as "db_item" who share the same set of keys. A field from "disam" is used in the string if one is sufficient to disambiguate the items. Otherwise, a fallback opaque value is used. Both "keys" and "disam" should be given as whitespace-separated lists of field names, while "bracket" is a pair of characters to be used as brackets surrounding the disambiguator or empty to have no brackets. "name" is the name of the templates. It is also the name of the configuration section where the default values of the parameters are stored. "skip_item" is a function that must return True when the template should return an empty string. "initial_subqueries" is a list of subqueries that should be included in the query to find the ambiguous items. """ memokey = self._tmpl_unique_memokey(name, keys, disam, item_id) memoval = self.lib._memotable.get(memokey) if memoval is not None: return memoval if skip_item(db_item): self.lib._memotable[memokey] = "" return "" keys = keys or beets.config[name]["keys"].as_str() disam = disam or beets.config[name]["disambiguators"].as_str() if bracket is None: bracket = beets.config[name]["bracket"].as_str() keys = keys.split() disam = disam.split() # Assign a left and right bracket or leave blank if argument is empty. if len(bracket) == 2: bracket_l = bracket[0] bracket_r = bracket[1] else: bracket_l = "" bracket_r = "" # Find matching items to disambiguate with. subqueries = [] if initial_subqueries is not None: subqueries.extend(initial_subqueries) for key in keys: value = db_item.get(key, "") # Use slow queries for flexible attributes. fast = key in item_keys subqueries.append(dbcore.MatchQuery(key, value, fast)) query = dbcore.AndQuery(subqueries) ambigous_items = ( self.lib.items(query) if isinstance(db_item, Item) else self.lib.albums(query) ) # If there's only one item to matching these details, then do # nothing. if len(ambigous_items) == 1: self.lib._memotable[memokey] = "" return "" # Find the first disambiguator that distinguishes the items. for disambiguator in disam: # Get the value for each item for the current field. disam_values = {s.get(disambiguator, "") for s in ambigous_items} # If the set of unique values is equal to the number of # items in the disambiguation set, we're done -- this is # sufficient disambiguation. if len(disam_values) == len(ambigous_items): break else: # No disambiguator distinguished all fields. res = f" {bracket_l}{item_id}{bracket_r}" self.lib._memotable[memokey] = res return res # Flatten disambiguation value into a string. disam_value = db_item.formatted(for_path=True).get(disambiguator) # Return empty string if disambiguator is empty. if disam_value: res = f" {bracket_l}{disam_value}{bracket_r}" else: res = "" self.lib._memotable[memokey] = res return res @staticmethod def tmpl_first(s, count=1, skip=0, sep="; ", join_str="; "): """Get the item(s) from x to y in a string separated by something and join then with something. Args: s: the string count: The number of items included skip: The number of items skipped sep: the separator. Usually is '; ' (default) or '/ ' join_str: the string which will join the items, default '; '. """ skip = int(skip) count = skip + int(count) return join_str.join(s.split(sep)[skip:count]) def tmpl_ifdef(self, field, trueval="", falseval=""): """If field exists return trueval or the field (default) otherwise, emit return falseval (if provided). Args: field: The name of the field trueval: The string if the condition is true falseval: The string if the condition is false Returns: The string, based on condition. """ if field in self.item: return trueval if trueval else self.item.formatted().get(field) else: return falseval beetbox-beets-01f1faf/beets/logging.py000066400000000000000000000112401472325477400200470ustar00rootroot00000000000000# This file is part of beets. # Copyright 2016, Adrian Sampson. # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the # "Software"), to deal in the Software without restriction, including # without limitation the rights to use, copy, modify, merge, publish, # distribute, sublicense, and/or sell copies of the Software, and to # permit persons to whom the Software is furnished to do so, subject to # the following conditions: # # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. """A drop-in replacement for the standard-library `logging` module. Provides everything the "logging" module does. In addition, beets' logger (as obtained by `getLogger(name)`) supports thread-local levels, and messages use {}-style formatting and can interpolate keywords arguments to the logging calls (`debug`, `info`, etc). """ import threading from copy import copy from logging import ( DEBUG, INFO, NOTSET, WARNING, FileHandler, Filter, Handler, Logger, NullHandler, StreamHandler, ) __all__ = [ "DEBUG", "INFO", "NOTSET", "WARNING", "FileHandler", "Filter", "Handler", "Logger", "NullHandler", "StreamHandler", "getLogger", ] def logsafe(val): """Coerce `bytes` to `str` to avoid crashes solely due to logging. This is particularly relevant for bytestring paths. Much of our code explicitly uses `displayable_path` for them, but better be safe and prevent any crashes that are solely due to log formatting. """ # Bytestring: Needs decoding to be safe for substitution in format strings. if isinstance(val, bytes): # Blindly convert with UTF-8. Eventually, it would be nice to # (a) only do this for paths, if they can be given a distinct # type, and (b) warn the developer if they do this for other # bytestrings. return val.decode("utf-8", "replace") # Other objects are used as-is so field access, etc., still works in # the format string. Relies on a working __str__ implementation. return val class StrFormatLogger(Logger): """A version of `Logger` that uses `str.format`-style formatting instead of %-style formatting and supports keyword arguments. We cannot easily get rid of this even in the Python 3 era: This custom formatting supports substitution from `kwargs` into the message, which the default `logging.Logger._log()` implementation does not. Remark by @sampsyo: https://stackoverflow.com/a/24683360 might be a way to achieve this with less code. """ class _LogMessage: def __init__(self, msg, args, kwargs): self.msg = msg self.args = args self.kwargs = kwargs def __str__(self): args = [logsafe(a) for a in self.args] kwargs = {k: logsafe(v) for (k, v) in self.kwargs.items()} return self.msg.format(*args, **kwargs) def _log( self, level, msg, args, exc_info=None, extra=None, stack_info=False, **kwargs, ): """Log msg.format(*args, **kwargs)""" m = self._LogMessage(msg, args, kwargs) stacklevel = kwargs.pop("stacklevel", 1) stacklevel = {"stacklevel": stacklevel} return super()._log( level, m, (), exc_info=exc_info, extra=extra, stack_info=stack_info, **stacklevel, ) class ThreadLocalLevelLogger(Logger): """A version of `Logger` whose level is thread-local instead of shared.""" def __init__(self, name, level=NOTSET): self._thread_level = threading.local() self.default_level = NOTSET super().__init__(name, level) @property def level(self): try: return self._thread_level.level except AttributeError: self._thread_level.level = self.default_level return self.level @level.setter def level(self, value): self._thread_level.level = value def set_global_level(self, level): """Set the level on the current thread + the default value for all threads. """ self.default_level = level self.setLevel(level) class BeetsLogger(ThreadLocalLevelLogger, StrFormatLogger): pass my_manager = copy(Logger.manager) my_manager.loggerClass = BeetsLogger # Override the `getLogger` to use our machinery. def getLogger(name=None): # noqa if name: return my_manager.getLogger(name) else: return Logger.root beetbox-beets-01f1faf/beets/mediafile.py000066400000000000000000000022431472325477400203430ustar00rootroot00000000000000# This file is part of beets. # Copyright 2016, Adrian Sampson. # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the # "Software"), to deal in the Software without restriction, including # without limitation the rights to use, copy, modify, merge, publish, # distribute, sublicense, and/or sell copies of the Software, and to # permit persons to whom the Software is furnished to do so, subject to # the following conditions: # # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. import warnings import mediafile warnings.warn( "beets.mediafile is deprecated; use mediafile instead", # Show the location of the `import mediafile` statement as the warning's # source, rather than this file, such that the offending module can be # identified easily. stacklevel=2, ) # Import everything from the mediafile module into this module. for key, value in mediafile.__dict__.items(): if key not in ["__name__"]: globals()[key] = value # Cleanup namespace. del key, value, warnings, mediafile beetbox-beets-01f1faf/beets/plugins.py000066400000000000000000000637751472325477400201260ustar00rootroot00000000000000# This file is part of beets. # Copyright 2016, Adrian Sampson. # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the # "Software"), to deal in the Software without restriction, including # without limitation the rights to use, copy, modify, merge, publish, # distribute, sublicense, and/or sell copies of the Software, and to # permit persons to whom the Software is furnished to do so, subject to # the following conditions: # # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. """Support for beets plugins.""" import abc import inspect import re import traceback from collections import defaultdict from functools import wraps import mediafile import beets from beets import logging PLUGIN_NAMESPACE = "beetsplug" # Plugins using the Last.fm API can share the same API key. LASTFM_KEY = "2dc3914abf35f0d9c92d97d8f8e42b43" # Global logger. log = logging.getLogger("beets") class PluginConflictError(Exception): """Indicates that the services provided by one plugin conflict with those of another. For example two plugins may define different types for flexible fields. """ class PluginLogFilter(logging.Filter): """A logging filter that identifies the plugin that emitted a log message. """ def __init__(self, plugin): self.prefix = f"{plugin.name}: " def filter(self, record): if hasattr(record.msg, "msg") and isinstance(record.msg.msg, str): # A _LogMessage from our hacked-up Logging replacement. record.msg.msg = self.prefix + record.msg.msg elif isinstance(record.msg, str): record.msg = self.prefix + record.msg return True # Managing the plugins themselves. class BeetsPlugin: """The base class for all beets plugins. Plugins provide functionality by defining a subclass of BeetsPlugin and overriding the abstract methods defined here. """ def __init__(self, name=None): """Perform one-time plugin setup.""" self.name = name or self.__module__.split(".")[-1] self.config = beets.config[self.name] if not self.template_funcs: self.template_funcs = {} if not self.template_fields: self.template_fields = {} if not self.album_template_fields: self.album_template_fields = {} self.early_import_stages = [] self.import_stages = [] self._log = log.getChild(self.name) self._log.setLevel(logging.NOTSET) # Use `beets` logger level. if not any(isinstance(f, PluginLogFilter) for f in self._log.filters): self._log.addFilter(PluginLogFilter(self)) def commands(self): """Should return a list of beets.ui.Subcommand objects for commands that should be added to beets' CLI. """ return () def _set_stage_log_level(self, stages): """Adjust all the stages in `stages` to WARNING logging level.""" return [ self._set_log_level_and_params(logging.WARNING, stage) for stage in stages ] def get_early_import_stages(self): """Return a list of functions that should be called as importer pipelines stages early in the pipeline. The callables are wrapped versions of the functions in `self.early_import_stages`. Wrapping provides some bookkeeping for the plugin: specifically, the logging level is adjusted to WARNING. """ return self._set_stage_log_level(self.early_import_stages) def get_import_stages(self): """Return a list of functions that should be called as importer pipelines stages. The callables are wrapped versions of the functions in `self.import_stages`. Wrapping provides some bookkeeping for the plugin: specifically, the logging level is adjusted to WARNING. """ return self._set_stage_log_level(self.import_stages) def _set_log_level_and_params(self, base_log_level, func): """Wrap `func` to temporarily set this plugin's logger level to `base_log_level` + config options (and restore it to its previous value after the function returns). Also determines which params may not be sent for backwards-compatibility. """ argspec = inspect.getfullargspec(func) @wraps(func) def wrapper(*args, **kwargs): assert self._log.level == logging.NOTSET verbosity = beets.config["verbose"].get(int) log_level = max(logging.DEBUG, base_log_level - 10 * verbosity) self._log.setLevel(log_level) if argspec.varkw is None: kwargs = {k: v for k, v in kwargs.items() if k in argspec.args} try: return func(*args, **kwargs) finally: self._log.setLevel(logging.NOTSET) return wrapper def queries(self): """Return a dict mapping prefixes to Query subclasses.""" return {} def track_distance(self, item, info): """Should return a Distance object to be added to the distance for every track comparison. """ return beets.autotag.hooks.Distance() def album_distance(self, items, album_info, mapping): """Should return a Distance object to be added to the distance for every album-level comparison. """ return beets.autotag.hooks.Distance() def candidates(self, items, artist, album, va_likely, extra_tags=None): """Should return a sequence of AlbumInfo objects that match the album whose items are provided. """ return () def item_candidates(self, item, artist, title): """Should return a sequence of TrackInfo objects that match the item provided. """ return () def album_for_id(self, album_id): """Return an AlbumInfo object or None if no matching release was found. """ return None def track_for_id(self, track_id): """Return a TrackInfo object or None if no matching release was found. """ return None def add_media_field(self, name, descriptor): """Add a field that is synchronized between media files and items. When a media field is added ``item.write()`` will set the name property of the item's MediaFile to ``item[name]`` and save the changes. Similarly ``item.read()`` will set ``item[name]`` to the value of the name property of the media file. ``descriptor`` must be an instance of ``mediafile.MediaField``. """ # Defer import to prevent circular dependency from beets import library mediafile.MediaFile.add_field(name, descriptor) library.Item._media_fields.add(name) _raw_listeners = None listeners = None def register_listener(self, event, func): """Add a function as a listener for the specified event.""" wrapped_func = self._set_log_level_and_params(logging.WARNING, func) cls = self.__class__ if cls.listeners is None or cls._raw_listeners is None: cls._raw_listeners = defaultdict(list) cls.listeners = defaultdict(list) if func not in cls._raw_listeners[event]: cls._raw_listeners[event].append(func) cls.listeners[event].append(wrapped_func) template_funcs = None template_fields = None album_template_fields = None @classmethod def template_func(cls, name): """Decorator that registers a path template function. The function will be invoked as ``%name{}`` from path format strings. """ def helper(func): if cls.template_funcs is None: cls.template_funcs = {} cls.template_funcs[name] = func return func return helper @classmethod def template_field(cls, name): """Decorator that registers a path template field computation. The value will be referenced as ``$name`` from path format strings. The function must accept a single parameter, the Item being formatted. """ def helper(func): if cls.template_fields is None: cls.template_fields = {} cls.template_fields[name] = func return func return helper _classes = set() def load_plugins(names=()): """Imports the modules for a sequence of plugin names. Each name must be the name of a Python module under the "beetsplug" namespace package in sys.path; the module indicated should contain the BeetsPlugin subclasses desired. """ for name in names: modname = f"{PLUGIN_NAMESPACE}.{name}" try: try: namespace = __import__(modname, None, None) except ImportError as exc: # Again, this is hacky: if exc.args[0].endswith(" " + name): log.warning("** plugin {0} not found", name) else: raise else: for obj in getattr(namespace, name).__dict__.values(): if ( isinstance(obj, type) and issubclass(obj, BeetsPlugin) and obj != BeetsPlugin and obj not in _classes ): _classes.add(obj) except Exception: log.warning( "** error loading plugin {}:\n{}", name, traceback.format_exc(), ) _instances = {} def find_plugins(): """Returns a list of BeetsPlugin subclass instances from all currently loaded beets plugins. Loads the default plugin set first. """ if _instances: # After the first call, use cached instances for performance reasons. # See https://github.com/beetbox/beets/pull/3810 return list(_instances.values()) load_plugins() plugins = [] for cls in _classes: # Only instantiate each plugin class once. if cls not in _instances: _instances[cls] = cls() plugins.append(_instances[cls]) return plugins # Communication with plugins. def commands(): """Returns a list of Subcommand objects from all loaded plugins.""" out = [] for plugin in find_plugins(): out += plugin.commands() return out def queries(): """Returns a dict mapping prefix strings to Query subclasses all loaded plugins. """ out = {} for plugin in find_plugins(): out.update(plugin.queries()) return out def types(model_cls): # Gives us `item_types` and `album_types` attr_name = f"{model_cls.__name__.lower()}_types" types = {} for plugin in find_plugins(): plugin_types = getattr(plugin, attr_name, {}) for field in plugin_types: if field in types and plugin_types[field] != types[field]: raise PluginConflictError( "Plugin {} defines flexible field {} " "which has already been defined with " "another type.".format(plugin.name, field) ) types.update(plugin_types) return types def named_queries(model_cls): # Gather `item_queries` and `album_queries` from the plugins. attr_name = f"{model_cls.__name__.lower()}_queries" queries = {} for plugin in find_plugins(): plugin_queries = getattr(plugin, attr_name, {}) queries.update(plugin_queries) return queries def track_distance(item, info): """Gets the track distance calculated by all loaded plugins. Returns a Distance object. """ from beets.autotag.hooks import Distance dist = Distance() for plugin in find_plugins(): dist.update(plugin.track_distance(item, info)) return dist def album_distance(items, album_info, mapping): """Returns the album distance calculated by plugins.""" from beets.autotag.hooks import Distance dist = Distance() for plugin in find_plugins(): dist.update(plugin.album_distance(items, album_info, mapping)) return dist def candidates(items, artist, album, va_likely, extra_tags=None): """Gets MusicBrainz candidates for an album from each plugin.""" for plugin in find_plugins(): yield from plugin.candidates( items, artist, album, va_likely, extra_tags ) def item_candidates(item, artist, title): """Gets MusicBrainz candidates for an item from the plugins.""" for plugin in find_plugins(): yield from plugin.item_candidates(item, artist, title) def album_for_id(album_id): """Get AlbumInfo objects for a given ID string.""" for plugin in find_plugins(): album = plugin.album_for_id(album_id) if album: yield album def track_for_id(track_id): """Get TrackInfo objects for a given ID string.""" for plugin in find_plugins(): track = plugin.track_for_id(track_id) if track: yield track def template_funcs(): """Get all the template functions declared by plugins as a dictionary. """ funcs = {} for plugin in find_plugins(): if plugin.template_funcs: funcs.update(plugin.template_funcs) return funcs def early_import_stages(): """Get a list of early import stage functions defined by plugins.""" stages = [] for plugin in find_plugins(): stages += plugin.get_early_import_stages() return stages def import_stages(): """Get a list of import stage functions defined by plugins.""" stages = [] for plugin in find_plugins(): stages += plugin.get_import_stages() return stages # New-style (lazy) plugin-provided fields. def _check_conflicts_and_merge(plugin, plugin_funcs, funcs): """Check the provided template functions for conflicts and merge into funcs. Raises a `PluginConflictError` if a plugin defines template functions for fields that another plugin has already defined template functions for. """ if plugin_funcs: if not plugin_funcs.keys().isdisjoint(funcs.keys()): conflicted_fields = ", ".join(plugin_funcs.keys() & funcs.keys()) raise PluginConflictError( f"Plugin {plugin.name} defines template functions for " f"{conflicted_fields} that conflict with another plugin." ) funcs.update(plugin_funcs) def item_field_getters(): """Get a dictionary mapping field names to unary functions that compute the field's value. """ funcs = {} for plugin in find_plugins(): _check_conflicts_and_merge(plugin, plugin.template_fields, funcs) return funcs def album_field_getters(): """As above, for album fields.""" funcs = {} for plugin in find_plugins(): _check_conflicts_and_merge(plugin, plugin.album_template_fields, funcs) return funcs # Event dispatch. def event_handlers(): """Find all event handlers from plugins as a dictionary mapping event names to sequences of callables. """ all_handlers = defaultdict(list) for plugin in find_plugins(): if plugin.listeners: for event, handlers in plugin.listeners.items(): all_handlers[event] += handlers return all_handlers def send(event, **arguments): """Send an event to all assigned event listeners. `event` is the name of the event to send, all other named arguments are passed along to the handlers. Return a list of non-None values returned from the handlers. """ log.debug("Sending event: {0}", event) results = [] for handler in event_handlers()[event]: result = handler(**arguments) if result is not None: results.append(result) return results def feat_tokens(for_artist=True): """Return a regular expression that matches phrases like "featuring" that separate a main artist or a song title from secondary artists. The `for_artist` option determines whether the regex should be suitable for matching artist fields (the default) or title fields. """ feat_words = ["ft", "featuring", "feat", "feat.", "ft."] if for_artist: feat_words += ["with", "vs", "and", "con", "&"] return r"(?<=[\s(\[])(?:{})(?=\s)".format( "|".join(re.escape(x) for x in feat_words) ) def sanitize_choices(choices, choices_all): """Clean up a stringlist configuration attribute: keep only choices elements present in choices_all, remove duplicate elements, expand '*' wildcard while keeping original stringlist order. """ seen = set() others = [x for x in choices_all if x not in choices] res = [] for s in choices: if s not in seen: if s in list(choices_all): res.append(s) elif s == "*": res.extend(others) seen.add(s) return res def sanitize_pairs(pairs, pairs_all): """Clean up a single-element mapping configuration attribute as returned by Confuse's `Pairs` template: keep only two-element tuples present in pairs_all, remove duplicate elements, expand ('str', '*') and ('*', '*') wildcards while keeping the original order. Note that ('*', '*') and ('*', 'whatever') have the same effect. For example, >>> sanitize_pairs( ... [('foo', 'baz bar'), ('key', '*'), ('*', '*')], ... [('foo', 'bar'), ('foo', 'baz'), ('foo', 'foobar'), ... ('key', 'value')] ... ) [('foo', 'baz'), ('foo', 'bar'), ('key', 'value'), ('foo', 'foobar')] """ pairs_all = list(pairs_all) seen = set() others = [x for x in pairs_all if x not in pairs] res = [] for k, values in pairs: for v in values.split(): x = (k, v) if x in pairs_all: if x not in seen: seen.add(x) res.append(x) elif k == "*": new = [o for o in others if o not in seen] seen.update(new) res.extend(new) elif v == "*": new = [o for o in others if o not in seen and o[0] == k] seen.update(new) res.extend(new) return res def notify_info_yielded(event): """Makes a generator send the event 'event' every time it yields. This decorator is supposed to decorate a generator, but any function returning an iterable should work. Each yielded value is passed to plugins using the 'info' parameter of 'send'. """ def decorator(generator): def decorated(*args, **kwargs): for v in generator(*args, **kwargs): send(event, info=v) yield v return decorated return decorator def get_distance(config, data_source, info): """Returns the ``data_source`` weight and the maximum source weight for albums or individual tracks. """ dist = beets.autotag.Distance() if info.data_source == data_source: dist.add("source", config["source_weight"].as_number()) return dist def apply_item_changes(lib, item, move, pretend, write): """Store, move, and write the item according to the arguments. :param lib: beets library. :type lib: beets.library.Library :param item: Item whose changes to apply. :type item: beets.library.Item :param move: Move the item if it's in the library. :type move: bool :param pretend: Return without moving, writing, or storing the item's metadata. :type pretend: bool :param write: Write the item's metadata to its media file. :type write: bool """ if pretend: return from beets import util # Move the item if it's in the library. if move and lib.directory in util.ancestry(item.path): item.move(with_album=False) if write: item.try_write() item.store() class MetadataSourcePlugin(metaclass=abc.ABCMeta): def __init__(self): super().__init__() self.config.add({"source_weight": 0.5}) @abc.abstractproperty def id_regex(self): raise NotImplementedError @abc.abstractproperty def data_source(self): raise NotImplementedError @abc.abstractproperty def search_url(self): raise NotImplementedError @abc.abstractproperty def album_url(self): raise NotImplementedError @abc.abstractproperty def track_url(self): raise NotImplementedError @abc.abstractmethod def _search_api(self, query_type, filters, keywords=""): raise NotImplementedError @abc.abstractmethod def album_for_id(self, album_id): raise NotImplementedError @abc.abstractmethod def track_for_id(self, track_id=None, track_data=None): raise NotImplementedError @staticmethod def get_artist(artists, id_key="id", name_key="name", join_key=None): """Returns an artist string (all artists) and an artist_id (the main artist) for a list of artist object dicts. For each artist, this function moves articles (such as 'a', 'an', and 'the') to the front and strips trailing disambiguation numbers. It returns a tuple containing the comma-separated string of all normalized artists and the ``id`` of the main/first artist. Alternatively a keyword can be used to combine artists together into a single string by passing the join_key argument. :param artists: Iterable of artist dicts or lists returned by API. :type artists: list[dict] or list[list] :param id_key: Key or index corresponding to the value of ``id`` for the main/first artist. Defaults to 'id'. :type id_key: str or int :param name_key: Key or index corresponding to values of names to concatenate for the artist string (containing all artists). Defaults to 'name'. :type name_key: str or int :param join_key: Key or index corresponding to a field containing a keyword to use for combining artists into a single string, for example "Feat.", "Vs.", "And" or similar. The default is None which keeps the default behaviour (comma-separated). :type join_key: str or int :return: Normalized artist string. :rtype: str """ artist_id = None artist_string = "" artists = list(artists) # In case a generator was passed. total = len(artists) for idx, artist in enumerate(artists): if not artist_id: artist_id = artist[id_key] name = artist[name_key] # Strip disambiguation number. name = re.sub(r" \(\d+\)$", "", name) # Move articles to the front. name = re.sub(r"^(.*?), (a|an|the)$", r"\2 \1", name, flags=re.I) # Use a join keyword if requested and available. if idx < (total - 1): # Skip joining on last. if join_key and artist.get(join_key, None): name += f" {artist[join_key]} " else: name += ", " artist_string += name return artist_string, artist_id @staticmethod def _get_id(url_type, id_, id_regex): """Parse an ID from its URL if necessary. :param url_type: Type of URL. Either 'album' or 'track'. :type url_type: str :param id_: Album/track ID or URL. :type id_: str :param id_regex: A dictionary containing a regular expression extracting an ID from an URL (if it's not an ID already) in 'pattern' and the number of the match group in 'match_group'. :type id_regex: dict :return: Album/track ID. :rtype: str """ log.debug("Extracting {} ID from '{}'", url_type, id_) match = re.search(id_regex["pattern"].format(url_type), str(id_)) if match: id_ = match.group(id_regex["match_group"]) if id_: return id_ return None def candidates(self, items, artist, album, va_likely, extra_tags=None): """Returns a list of AlbumInfo objects for Search API results matching an ``album`` and ``artist`` (if not various). :param items: List of items comprised by an album to be matched. :type items: list[beets.library.Item] :param artist: The artist of the album to be matched. :type artist: str :param album: The name of the album to be matched. :type album: str :param va_likely: True if the album to be matched likely has Various Artists. :type va_likely: bool :return: Candidate AlbumInfo objects. :rtype: list[beets.autotag.hooks.AlbumInfo] """ query_filters = {"album": album} if not va_likely: query_filters["artist"] = artist results = self._search_api(query_type="album", filters=query_filters) albums = [self.album_for_id(album_id=r["id"]) for r in results] return [a for a in albums if a is not None] def item_candidates(self, item, artist, title): """Returns a list of TrackInfo objects for Search API results matching ``title`` and ``artist``. :param item: Singleton item to be matched. :type item: beets.library.Item :param artist: The artist of the track to be matched. :type artist: str :param title: The title of the track to be matched. :type title: str :return: Candidate TrackInfo objects. :rtype: list[beets.autotag.hooks.TrackInfo] """ tracks = self._search_api( query_type="track", keywords=title, filters={"artist": artist} ) return [self.track_for_id(track_data=track) for track in tracks] def album_distance(self, items, album_info, mapping): return get_distance( data_source=self.data_source, info=album_info, config=self.config ) def track_distance(self, item, track_info): return get_distance( data_source=self.data_source, info=track_info, config=self.config ) beetbox-beets-01f1faf/beets/random.py000066400000000000000000000071141472325477400177060ustar00rootroot00000000000000# This file is part of beets. # Copyright 2016, Philippe Mongeau. # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the # "Software"), to deal in the Software without restriction, including # without limitation the rights to use, copy, modify, merge, publish, # distribute, sublicense, and/or sell copies of the Software, and to # permit persons to whom the Software is furnished to do so, subject to # the following conditions: # # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. """Get a random song or album from the library.""" import random from itertools import groupby from operator import attrgetter def _length(obj, album): """Get the duration of an item or album.""" if album: return sum(i.length for i in obj.items()) else: return obj.length def _equal_chance_permutation(objs, field="albumartist", random_gen=None): """Generate (lazily) a permutation of the objects where every group with equal values for `field` have an equal chance of appearing in any given position. """ rand = random_gen or random # Group the objects by artist so we can sample from them. key = attrgetter(field) objs.sort(key=key) objs_by_artists = {} for artist, v in groupby(objs, key): objs_by_artists[artist] = list(v) # While we still have artists with music to choose from, pick one # randomly and pick a track from that artist. while objs_by_artists: # Choose an artist and an object for that artist, removing # this choice from the pool. artist = rand.choice(list(objs_by_artists.keys())) objs_from_artist = objs_by_artists[artist] i = rand.randint(0, len(objs_from_artist) - 1) yield objs_from_artist.pop(i) # Remove the artist if we've used up all of its objects. if not objs_from_artist: del objs_by_artists[artist] def _take(iter, num): """Return a list containing the first `num` values in `iter` (or fewer, if the iterable ends early). """ out = [] for val in iter: out.append(val) num -= 1 if num <= 0: break return out def _take_time(iter, secs, album): """Return a list containing the first values in `iter`, which should be Item or Album objects, that add up to the given amount of time in seconds. """ out = [] total_time = 0.0 for obj in iter: length = _length(obj, album) if total_time + length <= secs: out.append(obj) total_time += length return out def random_objs( objs, album, number=1, time=None, equal_chance=False, random_gen=None ): """Get a random subset of the provided `objs`. If `number` is provided, produce that many matches. Otherwise, if `time` is provided, instead select a list whose total time is close to that number of minutes. If `equal_chance` is true, give each artist an equal chance of being included so that artists with more songs are not represented disproportionately. """ rand = random_gen or random # Permute the objects either in a straightforward way or an # artist-balanced way. if equal_chance: perm = _equal_chance_permutation(objs) else: perm = objs rand.shuffle(perm) # N.B. This shuffles the original list. # Select objects by time our count. if time: return _take_time(perm, time * 60, album) else: return _take(perm, number) beetbox-beets-01f1faf/beets/test/000077500000000000000000000000001472325477400170305ustar00rootroot00000000000000beetbox-beets-01f1faf/beets/test/__init__.py000066400000000000000000000016151472325477400211440ustar00rootroot00000000000000# This file is part of beets. # Copyright 2024, Lars Kruse # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the # "Software"), to deal in the Software without restriction, including # without limitation the rights to use, copy, modify, merge, publish, # distribute, sublicense, and/or sell copies of the Software, and to # permit persons to whom the Software is furnished to do so, subject to # the following conditions: # # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. """This module contains components of beets' test environment, which may be of use for testing procedures of external libraries or programs. For example the 'TestHelper' class may be useful for creating an in-memory beets library filled with a few example items. """ beetbox-beets-01f1faf/beets/test/_common.py000066400000000000000000000147671472325477400210500ustar00rootroot00000000000000# This file is part of beets. # Copyright 2016, Adrian Sampson. # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the # "Software"), to deal in the Software without restriction, including # without limitation the rights to use, copy, modify, merge, publish, # distribute, sublicense, and/or sell copies of the Software, and to # permit persons to whom the Software is furnished to do so, subject to # the following conditions: # # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. """Some common functionality for beets' test cases.""" import os import sys import unittest from contextlib import contextmanager import beets import beets.library # Make sure the development versions of the plugins are used import beetsplug from beets import importer, logging, util from beets.ui import commands from beets.util import syspath beetsplug.__path__ = [ os.path.abspath( os.path.join( os.path.dirname(__file__), os.path.pardir, os.path.pardir, "beetsplug", ) ) ] # Test resources path. RSRC = util.bytestring_path( os.path.abspath( os.path.join( os.path.dirname(__file__), os.path.pardir, os.path.pardir, "test", "rsrc", ) ) ) PLUGINPATH = os.path.join(RSRC.decode(), "beetsplug") # Propagate to root logger so the test runner can capture it log = logging.getLogger("beets") log.propagate = True log.setLevel(logging.DEBUG) # OS feature test. HAVE_SYMLINK = sys.platform != "win32" HAVE_HARDLINK = sys.platform != "win32" def item(lib=None): i = beets.library.Item( title="the title", artist="the artist", albumartist="the album artist", album="the album", genre="the genre", lyricist="the lyricist", composer="the composer", arranger="the arranger", grouping="the grouping", work="the work title", mb_workid="the work musicbrainz id", work_disambig="the work disambiguation", year=1, month=2, day=3, track=4, tracktotal=5, disc=6, disctotal=7, lyrics="the lyrics", comments="the comments", bpm=8, comp=True, length=60.0, bitrate=128000, format="FLAC", mb_trackid="someID-1", mb_albumid="someID-2", mb_artistid="someID-3", mb_albumartistid="someID-4", mb_releasetrackid="someID-5", album_id=None, mtime=12345, ) if lib: lib.add(i) return i # Dummy import session. def import_session(lib=None, loghandler=None, paths=[], query=[], cli=False): cls = commands.TerminalImportSession if cli else importer.ImportSession return cls(lib, loghandler, paths, query) class Assertions: """A mixin with additional unit test assertions.""" def assertExists(self, path): assert os.path.exists(syspath(path)), f"file does not exist: {path!r}" def assertNotExists(self, path): assert not os.path.exists(syspath(path)), f"file exists: {path!r}" def assertIsFile(self, path): self.assertExists(path) assert os.path.isfile( syspath(path) ), "path exists, but is not a regular file: {!r}".format(path) def assertIsDir(self, path): self.assertExists(path) assert os.path.isdir( syspath(path) ), "path exists, but is not a directory: {!r}".format(path) def assert_equal_path(self, a, b): """Check that two paths are equal.""" a_bytes, b_bytes = util.normpath(a), util.normpath(b) assert a_bytes == b_bytes, f"{a_bytes=} != {b_bytes=}" # Mock I/O. class InputError(Exception): def __init__(self, output=None): self.output = output def __str__(self): msg = "Attempt to read with no input provided." if self.output is not None: msg += f" Output: {self.output!r}" return msg class DummyOut: encoding = "utf-8" def __init__(self): self.buf = [] def write(self, s): self.buf.append(s) def get(self): return "".join(self.buf) def flush(self): self.clear() def clear(self): self.buf = [] class DummyIn: encoding = "utf-8" def __init__(self, out=None): self.buf = [] self.reads = 0 self.out = out def add(self, s): self.buf.append(s + "\n") def close(self): pass def readline(self): if not self.buf: if self.out: raise InputError(self.out.get()) else: raise InputError() self.reads += 1 return self.buf.pop(0) class DummyIO: """Mocks input and output streams for testing UI code.""" def __init__(self): self.stdout = DummyOut() self.stdin = DummyIn(self.stdout) def addinput(self, s): self.stdin.add(s) def getoutput(self): res = self.stdout.get() self.stdout.clear() return res def readcount(self): return self.stdin.reads def install(self): sys.stdin = self.stdin sys.stdout = self.stdout def restore(self): sys.stdin = sys.__stdin__ sys.stdout = sys.__stdout__ # Utility. def touch(path): open(syspath(path), "a").close() class Bag: """An object that exposes a set of fields given as keyword arguments. Any field not found in the dictionary appears to be None. Used for mocking Album objects and the like. """ def __init__(self, **fields): self.fields = fields def __getattr__(self, key): return self.fields.get(key) # Platform mocking. @contextmanager def platform_windows(): import ntpath old_path = os.path try: os.path = ntpath yield finally: os.path = old_path @contextmanager def platform_posix(): import posixpath old_path = os.path try: os.path = posixpath yield finally: os.path = old_path @contextmanager def system_mock(name): import platform old_system = platform.system platform.system = lambda: name try: yield finally: platform.system = old_system def slow_test(unused=None): def _id(obj): return obj if "SKIP_SLOW_TESTS" in os.environ: return unittest.skip("test is slow") return _id beetbox-beets-01f1faf/beets/test/helper.py000066400000000000000000000675671472325477400207060ustar00rootroot00000000000000# This file is part of beets. # Copyright 2016, Thomas Scholtes. # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the # "Software"), to deal in the Software without restriction, including # without limitation the rights to use, copy, modify, merge, publish, # distribute, sublicense, and/or sell copies of the Software, and to # permit persons to whom the Software is furnished to do so, subject to # the following conditions: # # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. """This module includes various helpers that provide fixtures, capture information or mock the environment. - The `control_stdin` and `capture_stdout` context managers allow one to interact with the user interface. - `has_program` checks the presence of a command on the system. - The `ImportSessionFixture` allows one to run importer code while controlling the interactions through code. - The `TestHelper` class encapsulates various fixtures that can be set up. """ from __future__ import annotations import os import os.path import shutil import subprocess import sys import unittest from contextlib import contextmanager from enum import Enum from functools import cached_property from io import StringIO from pathlib import Path from tempfile import gettempdir, mkdtemp, mkstemp from typing import Any, ClassVar from unittest.mock import patch import responses from mediafile import Image, MediaFile import beets import beets.plugins from beets import autotag, config, importer, logging, util from beets.autotag.hooks import AlbumInfo, TrackInfo from beets.importer import ImportSession from beets.library import Album, Item, Library from beets.test import _common from beets.ui.commands import TerminalImportSession from beets.util import ( MoveOperation, bytestring_path, clean_module_tempdir, syspath, ) class LogCapture(logging.Handler): def __init__(self): logging.Handler.__init__(self) self.messages = [] def emit(self, record): self.messages.append(str(record.msg)) @contextmanager def capture_log(logger="beets"): capture = LogCapture() log = logging.getLogger(logger) log.addHandler(capture) try: yield capture.messages finally: log.removeHandler(capture) @contextmanager def control_stdin(input=None): """Sends ``input`` to stdin. >>> with control_stdin('yes'): ... input() 'yes' """ org = sys.stdin sys.stdin = StringIO(input) try: yield sys.stdin finally: sys.stdin = org @contextmanager def capture_stdout(): """Save stdout in a StringIO. >>> with capture_stdout() as output: ... print('spam') ... >>> output.getvalue() 'spam' """ org = sys.stdout sys.stdout = capture = StringIO() try: yield sys.stdout finally: sys.stdout = org print(capture.getvalue()) def _convert_args(args): """Convert args to bytestrings for Python 2 and convert them to strings on Python 3. """ for i, elem in enumerate(args): if isinstance(elem, bytes): args[i] = elem.decode(util.arg_encoding()) return args def has_program(cmd, args=["--version"]): """Returns `True` if `cmd` can be executed.""" full_cmd = _convert_args([cmd] + args) try: with open(os.devnull, "wb") as devnull: subprocess.check_call( full_cmd, stderr=devnull, stdout=devnull, stdin=devnull ) except OSError: return False except subprocess.CalledProcessError: return False else: return True def check_reflink_support(path: str) -> bool: try: import reflink except ImportError: return False return reflink.supported_at(path) NEEDS_REFLINK = unittest.skipUnless( check_reflink_support(gettempdir()), "no reflink support for libdir" ) class TestHelper(_common.Assertions): """Helper mixin for high-level cli and plugin tests. This mixin provides methods to isolate beets' global state provide fixtures. """ db_on_disk: ClassVar[bool] = False # TODO automate teardown through hook registration def setup_beets(self): """Setup pristine global configuration and library for testing. Sets ``beets.config`` so we can safely use any functionality that uses the global configuration. All paths used are contained in a temporary directory Sets the following properties on itself. - ``temp_dir`` Path to a temporary directory containing all files specific to beets - ``libdir`` Path to a subfolder of ``temp_dir``, containing the library's media files. Same as ``config['directory']``. - ``config`` The global configuration used by beets. - ``lib`` Library instance created with the settings from ``config``. Make sure you call ``teardown_beets()`` afterwards. """ self.create_temp_dir() temp_dir_str = os.fsdecode(self.temp_dir) self.env_patcher = patch.dict( "os.environ", { "BEETSDIR": temp_dir_str, "HOME": temp_dir_str, # used by Confuse to create directories. }, ) self.env_patcher.start() self.config = beets.config self.config.sources = [] self.config.read(user=False, defaults=True) self.config["plugins"] = [] self.config["verbose"] = 1 self.config["ui"]["color"] = False self.config["threaded"] = False self.libdir = os.path.join(self.temp_dir, b"libdir") os.mkdir(syspath(self.libdir)) self.config["directory"] = os.fsdecode(self.libdir) if self.db_on_disk: dbpath = util.bytestring_path(self.config["library"].as_filename()) else: dbpath = ":memory:" self.lib = Library(dbpath, self.libdir) # Initialize, but don't install, a DummyIO. self.io = _common.DummyIO() def teardown_beets(self): self.env_patcher.stop() self.io.restore() self.lib._close() self.remove_temp_dir() beets.config.clear() beets.config._materialized = False # Library fixtures methods def create_item(self, **values): """Return an `Item` instance with sensible default values. The item receives its attributes from `**values` paratmeter. The `title`, `artist`, `album`, `track`, `format` and `path` attributes have defaults if they are not given as parameters. The `title` attribute is formatted with a running item count to prevent duplicates. The default for the `path` attribute respects the `format` value. The item is attached to the database from `self.lib`. """ values_ = { "title": "t\u00eftle {0}", "artist": "the \u00e4rtist", "album": "the \u00e4lbum", "track": 1, "format": "MP3", } values_.update(values) values_["title"] = values_["title"].format(1) values_["db"] = self.lib item = Item(**values_) if "path" not in values: item["path"] = "audio." + item["format"].lower() # mtime needs to be set last since other assignments reset it. item.mtime = 12345 return item def add_item(self, **values): """Add an item to the library and return it. Creates the item by passing the parameters to `create_item()`. If `path` is not set in `values` it is set to `item.destination()`. """ # When specifying a path, store it normalized (as beets does # ordinarily). if "path" in values: values["path"] = util.normpath(values["path"]) item = self.create_item(**values) item.add(self.lib) # Ensure every item has a path. if "path" not in values: item["path"] = item.destination() item.store() return item def add_item_fixture(self, **values): """Add an item with an actual audio file to the library.""" item = self.create_item(**values) extension = item["format"].lower() item["path"] = os.path.join( _common.RSRC, util.bytestring_path("min." + extension) ) item.add(self.lib) item.move(operation=MoveOperation.COPY) item.store() return item def add_album(self, **values): item = self.add_item(**values) return self.lib.add_album([item]) def add_item_fixtures(self, ext="mp3", count=1): """Add a number of items with files to the database.""" # TODO base this on `add_item()` items = [] path = os.path.join(_common.RSRC, util.bytestring_path("full." + ext)) for i in range(count): item = Item.from_path(path) item.album = f"\u00e4lbum {i}" # Check unicode paths item.title = f"t\u00eftle {i}" # mtime needs to be set last since other assignments reset it. item.mtime = 12345 item.add(self.lib) item.move(operation=MoveOperation.COPY) item.store() items.append(item) return items def add_album_fixture( self, track_count=1, fname="full", ext="mp3", disc_count=1, ): """Add an album with files to the database.""" items = [] path = os.path.join( _common.RSRC, util.bytestring_path(f"{fname}.{ext}"), ) for discnumber in range(1, disc_count + 1): for i in range(track_count): item = Item.from_path(path) item.album = "\u00e4lbum" # Check unicode paths item.title = f"t\u00eftle {i}" item.disc = discnumber # mtime needs to be set last since other assignments reset it. item.mtime = 12345 item.add(self.lib) item.move(operation=MoveOperation.COPY) item.store() items.append(item) return self.lib.add_album(items) def create_mediafile_fixture(self, ext="mp3", images=[]): """Copy a fixture mediafile with the extension to `temp_dir`. `images` is a subset of 'png', 'jpg', and 'tiff'. For each specified extension a cover art image is added to the media file. """ src = os.path.join(_common.RSRC, util.bytestring_path("full." + ext)) handle, path = mkstemp(dir=self.temp_dir) path = bytestring_path(path) os.close(handle) shutil.copyfile(syspath(src), syspath(path)) if images: mediafile = MediaFile(path) imgs = [] for img_ext in images: file = util.bytestring_path(f"image-2x3.{img_ext}") img_path = os.path.join(_common.RSRC, file) with open(img_path, "rb") as f: imgs.append(Image(f.read())) mediafile.images = imgs mediafile.save() return path # Running beets commands def run_command(self, *args, **kwargs): """Run a beets command with an arbitrary amount of arguments. The Library` defaults to `self.lib`, but can be overridden with the keyword argument `lib`. """ sys.argv = ["beet"] # avoid leakage from test suite args lib = None if hasattr(self, "lib"): lib = self.lib lib = kwargs.get("lib", lib) beets.ui._raw_main(_convert_args(list(args)), lib) def run_with_output(self, *args): with capture_stdout() as out: self.run_command(*args) return out.getvalue() # Safe file operations def create_temp_dir(self, **kwargs): """Create a temporary directory and assign it into `self.temp_dir`. Call `remove_temp_dir` later to delete it. """ temp_dir = mkdtemp(**kwargs) self.temp_dir = util.bytestring_path(temp_dir) def remove_temp_dir(self): """Delete the temporary directory created by `create_temp_dir`.""" shutil.rmtree(syspath(self.temp_dir)) def touch(self, path, dir=None, content=""): """Create a file at `path` with given content. If `dir` is given, it is prepended to `path`. After that, if the path is relative, it is resolved with respect to `self.temp_dir`. """ if dir: path = os.path.join(dir, path) if not os.path.isabs(path): path = os.path.join(self.temp_dir, path) parent = os.path.dirname(path) if not os.path.isdir(syspath(parent)): os.makedirs(syspath(parent)) with open(syspath(path), "a+") as f: f.write(content) return path # A test harness for all beets tests. # Provides temporary, isolated configuration. class BeetsTestCase(unittest.TestCase, TestHelper): """A unittest.TestCase subclass that saves and restores beets' global configuration. This allows tests to make temporary modifications that will then be automatically removed when the test completes. Also provides some additional assertion methods, a temporary directory, and a DummyIO. """ def setUp(self): self.setup_beets() def tearDown(self): self.teardown_beets() class ItemInDBTestCase(BeetsTestCase): """A test case that includes an in-memory library object (`lib`) and an item added to the library (`i`). """ def setUp(self): super().setUp() self.i = _common.item(self.lib) class PluginMixin: plugin: ClassVar[str] preload_plugin: ClassVar[bool] = True def setup_beets(self): super().setup_beets() if self.preload_plugin: self.load_plugins() def teardown_beets(self): super().teardown_beets() self.unload_plugins() def load_plugins(self, *plugins: str) -> None: """Load and initialize plugins by names. Similar setting a list of plugins in the configuration. Make sure you call ``unload_plugins()`` afterwards. """ # FIXME this should eventually be handled by a plugin manager plugins = (self.plugin,) if hasattr(self, "plugin") else plugins beets.config["plugins"] = plugins beets.plugins.load_plugins(plugins) beets.plugins.find_plugins() # Take a backup of the original _types and _queries to restore # when unloading. Item._original_types = dict(Item._types) Album._original_types = dict(Album._types) Item._types.update(beets.plugins.types(Item)) Album._types.update(beets.plugins.types(Album)) Item._original_queries = dict(Item._queries) Album._original_queries = dict(Album._queries) Item._queries.update(beets.plugins.named_queries(Item)) Album._queries.update(beets.plugins.named_queries(Album)) def unload_plugins(self) -> None: """Unload all plugins and remove them from the configuration.""" # FIXME this should eventually be handled by a plugin manager for plugin_class in beets.plugins._instances: plugin_class.listeners = None beets.config["plugins"] = [] beets.plugins._classes = set() beets.plugins._instances = {} Item._types = getattr(Item, "_original_types", {}) Album._types = getattr(Album, "_original_types", {}) Item._queries = getattr(Item, "_original_queries", {}) Album._queries = getattr(Album, "_original_queries", {}) @contextmanager def configure_plugin(self, config: Any): beets.config[self.plugin].set(config) self.load_plugins(self.plugin) yield self.unload_plugins() class PluginTestCase(PluginMixin, BeetsTestCase): pass class ImportHelper(TestHelper): """Provides tools to setup a library, a directory containing files that are to be imported and an import session. The class also provides stubs for the autotagging library and several assertions for the library. """ resource_path = syspath(os.path.join(_common.RSRC, b"full.mp3")) default_import_config = { "autotag": True, "copy": True, "hardlink": False, "link": False, "move": False, "resume": False, "singletons": False, "timid": True, } lib: Library importer: ImportSession @cached_property def import_path(self) -> Path: import_path = Path(os.fsdecode(self.temp_dir)) / "import" import_path.mkdir(exist_ok=True) return import_path @cached_property def import_dir(self) -> bytes: return bytestring_path(self.import_path) def setUp(self): super().setUp() self.import_media = [] self.lib.path_formats = [ ("default", os.path.join("$artist", "$album", "$title")), ("singleton:true", os.path.join("singletons", "$title")), ("comp:true", os.path.join("compilations", "$album", "$title")), ] def prepare_track_for_import( self, track_id: int, album_path: Path, album_id: int | None = None, ) -> Path: track_path = album_path / f"track_{track_id}.mp3" shutil.copy(self.resource_path, track_path) medium = MediaFile(track_path) medium.update( { "album": "Tag Album" + (f" {album_id}" if album_id else ""), "albumartist": None, "mb_albumid": None, "comp": None, "artist": "Tag Artist", "title": f"Tag Track {track_id}", "track": track_id, "mb_trackid": None, } ) medium.save() self.import_media.append(medium) return track_path def prepare_album_for_import( self, item_count: int, album_id: int | None = None, album_path: Path | None = None, ) -> list[Path]: """Create an album directory with media files to import. The directory has following layout album/ track_1.mp3 track_2.mp3 track_3.mp3 """ if not album_path: album_dir = f"album_{album_id}" if album_id else "album" album_path = self.import_path / album_dir album_path.mkdir(exist_ok=True) return [ self.prepare_track_for_import(tid, album_path, album_id=album_id) for tid in range(1, item_count + 1) ] def prepare_albums_for_import(self, count: int = 1) -> None: album_dirs = Path(os.fsdecode(self.import_dir)).glob("album_*") base_idx = int(str(max(album_dirs, default="0")).split("_")[-1]) + 1 for album_id in range(base_idx, count + base_idx): self.prepare_album_for_import(1, album_id=album_id) def _get_import_session(self, import_dir: bytes) -> ImportSession: return ImportSessionFixture( self.lib, loghandler=None, query=None, paths=[import_dir], ) def setup_importer( self, import_dir: bytes | None = None, **kwargs ) -> ImportSession: config["import"].set_args({**self.default_import_config, **kwargs}) self.importer = self._get_import_session(import_dir or self.import_dir) return self.importer def setup_singleton_importer(self, **kwargs) -> ImportSession: return self.setup_importer(singletons=True, **kwargs) def assert_file_in_lib(self, *segments): """Join the ``segments`` and assert that this path exists in the library directory. """ self.assertExists(os.path.join(self.libdir, *segments)) def assert_file_not_in_lib(self, *segments): """Join the ``segments`` and assert that this path does not exist in the library directory. """ self.assertNotExists(os.path.join(self.libdir, *segments)) def assert_lib_dir_empty(self): assert not os.listdir(syspath(self.libdir)) class AsIsImporterMixin: def setUp(self): super().setUp() self.prepare_album_for_import(1) def run_asis_importer(self, **kwargs): importer = self.setup_importer(autotag=False, **kwargs) importer.run() return importer class ImportTestCase(ImportHelper, BeetsTestCase): pass class ImportSessionFixture(ImportSession): """ImportSession that can be controlled programaticaly. >>> lib = Library(':memory:') >>> importer = ImportSessionFixture(lib, paths=['/path/to/import']) >>> importer.add_choice(importer.action.SKIP) >>> importer.add_choice(importer.action.ASIS) >>> importer.default_choice = importer.action.APPLY >>> importer.run() This imports ``/path/to/import`` into `lib`. It skips the first album and imports thesecond one with metadata from the tags. For the remaining albums, the metadata from the autotagger will be applied. """ def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self._choices = [] self._resolutions = [] default_choice = importer.action.APPLY def add_choice(self, choice): self._choices.append(choice) def clear_choices(self): self._choices = [] def choose_match(self, task): try: choice = self._choices.pop(0) except IndexError: choice = self.default_choice if choice == importer.action.APPLY: return task.candidates[0] elif isinstance(choice, int): return task.candidates[choice - 1] else: return choice choose_item = choose_match Resolution = Enum("Resolution", "REMOVE SKIP KEEPBOTH MERGE") default_resolution = "REMOVE" def resolve_duplicate(self, task, found_duplicates): try: res = self._resolutions.pop(0) except IndexError: res = self.default_resolution if res == self.Resolution.SKIP: task.set_choice(importer.action.SKIP) elif res == self.Resolution.REMOVE: task.should_remove_duplicates = True elif res == self.Resolution.MERGE: task.should_merge_duplicates = True class TerminalImportSessionFixture(TerminalImportSession): def __init__(self, *args, **kwargs): self.io = kwargs.pop("io") super().__init__(*args, **kwargs) self._choices = [] default_choice = importer.action.APPLY def add_choice(self, choice): self._choices.append(choice) def clear_choices(self): self._choices = [] def choose_match(self, task): self._add_choice_input() return super().choose_match(task) def choose_item(self, task): self._add_choice_input() return super().choose_item(task) def _add_choice_input(self): try: choice = self._choices.pop(0) except IndexError: choice = self.default_choice if choice == importer.action.APPLY: self.io.addinput("A") elif choice == importer.action.ASIS: self.io.addinput("U") elif choice == importer.action.ALBUMS: self.io.addinput("G") elif choice == importer.action.TRACKS: self.io.addinput("T") elif choice == importer.action.SKIP: self.io.addinput("S") else: self.io.addinput("M") self.io.addinput(str(choice)) self._add_choice_input() class TerminalImportMixin(ImportHelper): """Provides_a terminal importer for the import session.""" io: _common.DummyIO def _get_import_session(self, import_dir: bytes) -> importer.ImportSession: self.io.install() return TerminalImportSessionFixture( self.lib, loghandler=None, query=None, io=self.io, paths=[import_dir], ) class AutotagStub: """Stub out MusicBrainz album and track matcher and control what the autotagger returns. """ NONE = "NONE" IDENT = "IDENT" GOOD = "GOOD" BAD = "BAD" MISSING = "MISSING" """Generate an album match for all but one track """ length = 2 matching = IDENT def install(self): self.mb_match_album = autotag.mb.match_album self.mb_match_track = autotag.mb.match_track self.mb_album_for_id = autotag.mb.album_for_id self.mb_track_for_id = autotag.mb.track_for_id autotag.mb.match_album = self.match_album autotag.mb.match_track = self.match_track autotag.mb.album_for_id = self.album_for_id autotag.mb.track_for_id = self.track_for_id return self def restore(self): autotag.mb.match_album = self.mb_match_album autotag.mb.match_track = self.mb_match_track autotag.mb.album_for_id = self.mb_album_for_id autotag.mb.track_for_id = self.mb_track_for_id def match_album(self, albumartist, album, tracks, extra_tags): if self.matching == self.IDENT: yield self._make_album_match(albumartist, album, tracks) elif self.matching == self.GOOD: for i in range(self.length): yield self._make_album_match(albumartist, album, tracks, i) elif self.matching == self.BAD: for i in range(self.length): yield self._make_album_match(albumartist, album, tracks, i + 1) elif self.matching == self.MISSING: yield self._make_album_match(albumartist, album, tracks, missing=1) def match_track(self, artist, title): yield TrackInfo( title=title.replace("Tag", "Applied"), track_id="trackid", artist=artist.replace("Tag", "Applied"), artist_id="artistid", length=1, index=0, ) def album_for_id(self, mbid): return None def track_for_id(self, mbid): return None def _make_track_match(self, artist, album, number): return TrackInfo( title="Applied Track %d" % number, track_id="match %d" % number, artist=artist, length=1, index=0, ) def _make_album_match(self, artist, album, tracks, distance=0, missing=0): if distance: id = " " + "M" * distance else: id = "" if artist is None: artist = "Various Artists" else: artist = artist.replace("Tag", "Applied") + id album = album.replace("Tag", "Applied") + id track_infos = [] for i in range(tracks - missing): track_infos.append(self._make_track_match(artist, album, i + 1)) return AlbumInfo( artist=artist, album=album, tracks=track_infos, va=False, album_id="albumid" + id, artist_id="artistid" + id, albumtype="soundtrack", data_source="match_source", bandcamp_album_id="bc_url", ) class FetchImageHelper: """Helper mixin for mocking requests when fetching images with remote art sources. """ @responses.activate def run(self, *args, **kwargs): super().run(*args, **kwargs) IMAGEHEADER = { "image/jpeg": b"\xff\xd8\xff" + b"\x00" * 3 + b"JFIF", "image/png": b"\211PNG\r\n\032\n", } def mock_response(self, url, content_type="image/jpeg", file_type=None): if file_type is None: file_type = content_type responses.add( responses.GET, url, content_type=content_type, # imghdr reads 32 bytes body=self.IMAGEHEADER.get(file_type, b"").ljust(32, b"\x00"), ) class CleanupModulesMixin: modules: ClassVar[tuple[str, ...]] @classmethod def tearDownClass(cls) -> None: """Remove files created by the plugin.""" for module in cls.modules: clean_module_tempdir(module) beetbox-beets-01f1faf/beets/ui/000077500000000000000000000000001472325477400164665ustar00rootroot00000000000000beetbox-beets-01f1faf/beets/ui/__init__.py000066400000000000000000001745211472325477400206110ustar00rootroot00000000000000# This file is part of beets. # Copyright 2016, Adrian Sampson. # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the # "Software"), to deal in the Software without restriction, including # without limitation the rights to use, copy, modify, merge, publish, # distribute, sublicense, and/or sell copies of the Software, and to # permit persons to whom the Software is furnished to do so, subject to # the following conditions: # # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. """This module contains all of the core logic for beets' command-line interface. To invoke the CLI, just call beets.ui.main(). The actual CLI commands are implemented in the ui.commands module. """ import errno import optparse import os.path import re import sqlite3 import struct import sys import textwrap import traceback from difflib import SequenceMatcher from typing import Any, Callable, List import confuse from beets import config, library, logging, plugins, util from beets.autotag import mb from beets.dbcore import db from beets.dbcore import query as db_query from beets.util import as_string from beets.util.functemplate import template # On Windows platforms, use colorama to support "ANSI" terminal colors. if sys.platform == "win32": try: import colorama except ImportError: pass else: colorama.init() log = logging.getLogger("beets") if not log.handlers: log.addHandler(logging.StreamHandler()) log.propagate = False # Don't propagate to root handler. PF_KEY_QUERIES = { "comp": "comp:true", "singleton": "singleton:true", } class UserError(Exception): """UI exception. Commands should throw this in order to display nonrecoverable errors to the user. """ # Encoding utilities. def _in_encoding(): """Get the encoding to use for *inputting* strings from the console.""" return _stream_encoding(sys.stdin) def _out_encoding(): """Get the encoding to use for *outputting* strings to the console.""" return _stream_encoding(sys.stdout) def _stream_encoding(stream, default="utf-8"): """A helper for `_in_encoding` and `_out_encoding`: get the stream's preferred encoding, using a configured override or a default fallback if neither is not specified. """ # Configured override? encoding = config["terminal_encoding"].get() if encoding: return encoding # For testing: When sys.stdout or sys.stdin is a StringIO under the # test harness, it doesn't have an `encoding` attribute. Just use # UTF-8. if not hasattr(stream, "encoding"): return default # Python's guessed output stream encoding, or UTF-8 as a fallback # (e.g., when piped to a file). return stream.encoding or default def decargs(arglist): """Given a list of command-line argument bytestrings, attempts to decode them to Unicode strings when running under Python 2. """ return arglist def print_(*strings, **kwargs): """Like print, but rather than raising an error when a character is not in the terminal's encoding's character set, just silently replaces it. The arguments must be Unicode strings: `unicode` on Python 2; `str` on Python 3. The `end` keyword argument behaves similarly to the built-in `print` (it defaults to a newline). """ if not strings: strings = [""] assert isinstance(strings[0], str) txt = " ".join(strings) txt += kwargs.get("end", "\n") # Encode the string and write it to stdout. # On Python 3, sys.stdout expects text strings and uses the # exception-throwing encoding error policy. To avoid throwing # errors and use our configurable encoding override, we use the # underlying bytes buffer instead. if hasattr(sys.stdout, "buffer"): out = txt.encode(_out_encoding(), "replace") sys.stdout.buffer.write(out) sys.stdout.buffer.flush() else: # In our test harnesses (e.g., DummyOut), sys.stdout.buffer # does not exist. We instead just record the text string. sys.stdout.write(txt) # Configuration wrappers. def _bool_fallback(a, b): """Given a boolean or None, return the original value or a fallback.""" if a is None: assert isinstance(b, bool) return b else: assert isinstance(a, bool) return a def should_write(write_opt=None): """Decide whether a command that updates metadata should also write tags, using the importer configuration as the default. """ return _bool_fallback(write_opt, config["import"]["write"].get(bool)) def should_move(move_opt=None): """Decide whether a command that updates metadata should also move files when they're inside the library, using the importer configuration as the default. Specifically, commands should move files after metadata updates only when the importer is configured *either* to move *or* to copy files. They should avoid moving files when the importer is configured not to touch any filenames. """ return _bool_fallback( move_opt, config["import"]["move"].get(bool) or config["import"]["copy"].get(bool), ) # Input prompts. def indent(count): """Returns a string with `count` many spaces.""" return " " * count def input_(prompt=None): """Like `input`, but decodes the result to a Unicode string. Raises a UserError if stdin is not available. The prompt is sent to stdout rather than stderr. A printed between the prompt and the input cursor. """ # raw_input incorrectly sends prompts to stderr, not stdout, so we # use print_() explicitly to display prompts. # https://bugs.python.org/issue1927 if prompt: print_(prompt, end=" ") try: resp = input() except EOFError: raise UserError("stdin stream ended while input required") return resp def input_options( options, require=False, prompt=None, fallback_prompt=None, numrange=None, default=None, max_width=72, ): """Prompts a user for input. The sequence of `options` defines the choices the user has. A single-letter shortcut is inferred for each option; the user's choice is returned as that single, lower-case letter. The options should be provided as lower-case strings unless a particular shortcut is desired; in that case, only that letter should be capitalized. By default, the first option is the default. `default` can be provided to override this. If `require` is provided, then there is no default. The prompt and fallback prompt are also inferred but can be overridden. If numrange is provided, it is a pair of `(high, low)` (both ints) indicating that, in addition to `options`, the user may enter an integer in that inclusive range. `max_width` specifies the maximum number of columns in the automatically generated prompt string. """ # Assign single letters to each option. Also capitalize the options # to indicate the letter. letters = {} display_letters = [] capitalized = [] first = True for option in options: # Is a letter already capitalized? for letter in option: if letter.isalpha() and letter.upper() == letter: found_letter = letter break else: # Infer a letter. for letter in option: if not letter.isalpha(): continue # Don't use punctuation. if letter not in letters: found_letter = letter break else: raise ValueError("no unambiguous lettering found") letters[found_letter.lower()] = option index = option.index(found_letter) # Mark the option's shortcut letter for display. if not require and ( (default is None and not numrange and first) or ( isinstance(default, str) and found_letter.lower() == default.lower() ) ): # The first option is the default; mark it. show_letter = "[%s]" % found_letter.upper() is_default = True else: show_letter = found_letter.upper() is_default = False # Colorize the letter shortcut. show_letter = colorize( "action_default" if is_default else "action", show_letter ) # Insert the highlighted letter back into the word. descr_color = "action_default" if is_default else "action_description" capitalized.append( colorize(descr_color, option[:index]) + show_letter + colorize(descr_color, option[index + 1 :]) ) display_letters.append(found_letter.upper()) first = False # The default is just the first option if unspecified. if require: default = None elif default is None: if numrange: default = numrange[0] else: default = display_letters[0].lower() # Make a prompt if one is not provided. if not prompt: prompt_parts = [] prompt_part_lengths = [] if numrange: if isinstance(default, int): default_name = str(default) default_name = colorize("action_default", default_name) tmpl = "# selection (default %s)" prompt_parts.append(tmpl % default_name) prompt_part_lengths.append(len(tmpl % str(default))) else: prompt_parts.append("# selection") prompt_part_lengths.append(len(prompt_parts[-1])) prompt_parts += capitalized prompt_part_lengths += [len(s) for s in options] # Wrap the query text. # Start prompt with U+279C: Heavy Round-Tipped Rightwards Arrow prompt = colorize("action", "\u279c ") line_length = 0 for i, (part, length) in enumerate( zip(prompt_parts, prompt_part_lengths) ): # Add punctuation. if i == len(prompt_parts) - 1: part += colorize("action_description", "?") else: part += colorize("action_description", ",") length += 1 # Choose either the current line or the beginning of the next. if line_length + length + 1 > max_width: prompt += "\n" line_length = 0 if line_length != 0: # Not the beginning of the line; need a space. part = " " + part length += 1 prompt += part line_length += length # Make a fallback prompt too. This is displayed if the user enters # something that is not recognized. if not fallback_prompt: fallback_prompt = "Enter one of " if numrange: fallback_prompt += "%i-%i, " % numrange fallback_prompt += ", ".join(display_letters) + ":" resp = input_(prompt) while True: resp = resp.strip().lower() # Try default option. if default is not None and not resp: resp = default # Try an integer input if available. if numrange: try: resp = int(resp) except ValueError: pass else: low, high = numrange if low <= resp <= high: return resp else: resp = None # Try a normal letter input. if resp: resp = resp[0] if resp in letters: return resp # Prompt for new input. resp = input_(fallback_prompt) def input_yn(prompt, require=False): """Prompts the user for a "yes" or "no" response. The default is "yes" unless `require` is `True`, in which case there is no default. """ # Start prompt with U+279C: Heavy Round-Tipped Rightwards Arrow yesno = colorize("action", "\u279c ") + colorize( "action_description", "Enter Y or N:" ) sel = input_options(("y", "n"), require, prompt, yesno) return sel == "y" def input_select_objects(prompt, objs, rep, prompt_all=None): """Prompt to user to choose all, none, or some of the given objects. Return the list of selected objects. `prompt` is the prompt string to use for each question (it should be phrased as an imperative verb). If `prompt_all` is given, it is used instead of `prompt` for the first (yes(/no/select) question. `rep` is a function to call on each object to print it out when confirming objects individually. """ choice = input_options( ("y", "n", "s"), False, "%s? (Yes/no/select)" % (prompt_all or prompt) ) print() # Blank line. if choice == "y": # Yes. return objs elif choice == "s": # Select. out = [] for obj in objs: rep(obj) answer = input_options( ("y", "n", "q"), True, "%s? (yes/no/quit)" % prompt, "Enter Y or N:", ) if answer == "y": out.append(obj) elif answer == "q": return out return out else: # No. return [] # Human output formatting. def human_bytes(size): """Formats size, a number of bytes, in a human-readable way.""" powers = ["", "K", "M", "G", "T", "P", "E", "Z", "Y", "H"] unit = "B" for power in powers: if size < 1024: return f"{size:3.1f} {power}{unit}" size /= 1024.0 unit = "iB" return "big" def human_seconds(interval): """Formats interval, a number of seconds, as a human-readable time interval using English words. """ units = [ (1, "second"), (60, "minute"), (60, "hour"), (24, "day"), (7, "week"), (52, "year"), (10, "decade"), ] for i in range(len(units) - 1): increment, suffix = units[i] next_increment, _ = units[i + 1] interval /= float(increment) if interval < next_increment: break else: # Last unit. increment, suffix = units[-1] interval /= float(increment) return f"{interval:3.1f} {suffix}s" def human_seconds_short(interval): """Formats a number of seconds as a short human-readable M:SS string. """ interval = int(interval) return "%i:%02i" % (interval // 60, interval % 60) # Colorization. # ANSI terminal colorization code heavily inspired by pygments: # https://bitbucket.org/birkenfeld/pygments-main/src/default/pygments/console.py # (pygments is by Tim Hatch, Armin Ronacher, et al.) COLOR_ESCAPE = "\x1b[" LEGACY_COLORS = { "black": ["black"], "darkred": ["red"], "darkgreen": ["green"], "brown": ["yellow"], "darkyellow": ["yellow"], "darkblue": ["blue"], "purple": ["magenta"], "darkmagenta": ["magenta"], "teal": ["cyan"], "darkcyan": ["cyan"], "lightgray": ["white"], "darkgray": ["bold", "black"], "red": ["bold", "red"], "green": ["bold", "green"], "yellow": ["bold", "yellow"], "blue": ["bold", "blue"], "fuchsia": ["bold", "magenta"], "magenta": ["bold", "magenta"], "turquoise": ["bold", "cyan"], "cyan": ["bold", "cyan"], "white": ["bold", "white"], } # All ANSI Colors. ANSI_CODES = { # Styles. "normal": 0, "bold": 1, "faint": 2, # "italic": 3, "underline": 4, # "blink_slow": 5, # "blink_rapid": 6, "inverse": 7, # "conceal": 8, # "crossed_out": 9 # Text colors. "black": 30, "red": 31, "green": 32, "yellow": 33, "blue": 34, "magenta": 35, "cyan": 36, "white": 37, # Background colors. "bg_black": 40, "bg_red": 41, "bg_green": 42, "bg_yellow": 43, "bg_blue": 44, "bg_magenta": 45, "bg_cyan": 46, "bg_white": 47, } RESET_COLOR = COLOR_ESCAPE + "39;49;00m" # These abstract COLOR_NAMES are lazily mapped on to the actual color in COLORS # as they are defined in the configuration files, see function: colorize COLOR_NAMES = [ "text_success", "text_warning", "text_error", "text_highlight", "text_highlight_minor", "action_default", "action", # New Colors "text", "text_faint", "import_path", "import_path_items", "action_description", "added", "removed", "changed", "added_highlight", "removed_highlight", "changed_highlight", "text_diff_added", "text_diff_removed", "text_diff_changed", ] COLORS = None def _colorize(color, text): """Returns a string that prints the given text in the given color in a terminal that is ANSI color-aware. The color must be a list of strings from ANSI_CODES. """ # Construct escape sequence to be put before the text by iterating # over all "ANSI codes" in `color`. escape = "" for code in color: escape = escape + COLOR_ESCAPE + "%im" % ANSI_CODES[code] return escape + text + RESET_COLOR def colorize(color_name, text): """Colorize text if colored output is enabled. (Like _colorize but conditional.) """ if config["ui"]["color"] and "NO_COLOR" not in os.environ: global COLORS if not COLORS: # Read all color configurations and set global variable COLORS. COLORS = dict() for name in COLOR_NAMES: # Convert legacy color definitions (strings) into the new # list-based color definitions. Do this by trying to read the # color definition from the configuration as unicode - if this # is successful, the color definition is a legacy definition # and has to be converted. try: color_def = config["ui"]["colors"][name].get(str) except (confuse.ConfigTypeError, NameError): # Normal color definition (type: list of unicode). color_def = config["ui"]["colors"][name].get(list) else: # Legacy color definition (type: unicode). Convert. if color_def in LEGACY_COLORS: color_def = LEGACY_COLORS[color_def] else: raise UserError("no such color %s", color_def) for code in color_def: if code not in ANSI_CODES.keys(): raise ValueError("no such ANSI code %s", code) COLORS[name] = color_def # In case a 3rd party plugin is still passing the actual color ('red') # instead of the abstract color name ('text_error') color = COLORS.get(color_name) if not color: log.debug("Invalid color_name: {0}", color_name) color = color_name return _colorize(color, text) else: return text def uncolorize(colored_text): """Remove colors from a string.""" # Define a regular expression to match ANSI codes. # See: http://stackoverflow.com/a/2187024/1382707 # Explanation of regular expression: # \x1b - matches ESC character # \[ - matches opening square bracket # [;\d]* - matches a sequence consisting of one or more digits or # semicola # [A-Za-z] - matches a letter ansi_code_regex = re.compile(r"\x1b\[[;\d]*[A-Za-z]", re.VERBOSE) # Strip ANSI codes from `colored_text` using the regular expression. text = ansi_code_regex.sub("", colored_text) return text def color_split(colored_text, index): ansi_code_regex = re.compile(r"(\x1b\[[;\d]*[A-Za-z])", re.VERBOSE) length = 0 pre_split = "" post_split = "" found_color_code = None found_split = False for part in ansi_code_regex.split(colored_text): # Count how many real letters we have passed length += color_len(part) if found_split: post_split += part else: if ansi_code_regex.match(part): # This is a color code if part == RESET_COLOR: found_color_code = None else: found_color_code = part pre_split += part else: if index < length: # Found part with our split in. split_index = index - (length - color_len(part)) found_split = True if found_color_code: pre_split += part[:split_index] + RESET_COLOR post_split += found_color_code + part[split_index:] else: pre_split += part[:split_index] post_split += part[split_index:] else: # Not found, add this part to the pre split pre_split += part return pre_split, post_split def color_len(colored_text): """Measure the length of a string while excluding ANSI codes from the measurement. The standard `len(my_string)` method also counts ANSI codes to the string length, which is counterproductive when layouting a Terminal interface. """ # Return the length of the uncolored string. return len(uncolorize(colored_text)) def _colordiff(a, b): """Given two values, return the same pair of strings except with their differences highlighted in the specified color. Strings are highlighted intelligently to show differences; other values are stringified and highlighted in their entirety. """ # First, convert paths to readable format if isinstance(a, bytes) or isinstance(b, bytes): # A path field. a = util.displayable_path(a) b = util.displayable_path(b) if not isinstance(a, str) or not isinstance(b, str): # Non-strings: use ordinary equality. if a == b: return str(a), str(b) else: return ( colorize("text_diff_removed", str(a)), colorize("text_diff_added", str(b)), ) a_out = [] b_out = [] matcher = SequenceMatcher(lambda x: False, a, b) for op, a_start, a_end, b_start, b_end in matcher.get_opcodes(): if op == "equal": # In both strings. a_out.append(a[a_start:a_end]) b_out.append(b[b_start:b_end]) elif op == "insert": # Right only. b_out.append(colorize("text_diff_added", b[b_start:b_end])) elif op == "delete": # Left only. a_out.append(colorize("text_diff_removed", a[a_start:a_end])) elif op == "replace": # Right and left differ. Colorise with second highlight if # it's just a case change. if a[a_start:a_end].lower() != b[b_start:b_end].lower(): a_color = "text_diff_removed" b_color = "text_diff_added" else: a_color = b_color = "text_highlight_minor" a_out.append(colorize(a_color, a[a_start:a_end])) b_out.append(colorize(b_color, b[b_start:b_end])) else: assert False return "".join(a_out), "".join(b_out) def colordiff(a, b): """Colorize differences between two values if color is enabled. (Like _colordiff but conditional.) """ if config["ui"]["color"]: return _colordiff(a, b) else: return str(a), str(b) def get_path_formats(subview=None): """Get the configuration's path formats as a list of query/template pairs. """ path_formats = [] subview = subview or config["paths"] for query, view in subview.items(): query = PF_KEY_QUERIES.get(query, query) # Expand common queries. path_formats.append((query, template(view.as_str()))) return path_formats def get_replacements(): """Confuse validation function that reads regex/string pairs.""" replacements = [] for pattern, repl in config["replace"].get(dict).items(): repl = repl or "" try: replacements.append((re.compile(pattern), repl)) except re.error: raise UserError( "malformed regular expression in replace: {}".format(pattern) ) return replacements def term_width(): """Get the width (columns) of the terminal.""" fallback = config["ui"]["terminal_width"].get(int) # The fcntl and termios modules are not available on non-Unix # platforms, so we fall back to a constant. try: import fcntl import termios except ImportError: return fallback try: buf = fcntl.ioctl(0, termios.TIOCGWINSZ, " " * 4) except OSError: return fallback try: height, width = struct.unpack("hh", buf) except struct.error: return fallback return width def split_into_lines(string, width_tuple): """Splits string into a list of substrings at whitespace. `width_tuple` is a 3-tuple of `(first_width, last_width, middle_width)`. The first substring has a length not longer than `first_width`, the last substring has a length not longer than `last_width`, and all other substrings have a length not longer than `middle_width`. `string` may contain ANSI codes at word borders. """ first_width, middle_width, last_width = width_tuple words = [] esc_text = re.compile( r"""(?P[^\x1b]*) (?P(?:\x1b\[[;\d]*[A-Za-z])+) (?P[^\x1b]+)(?P\x1b\[39;49;00m) (?P[^\x1b]*)""", re.VERBOSE, ) if uncolorize(string) == string: # No colors in string words = string.split() else: # Use a regex to find escapes and the text within them. for m in esc_text.finditer(string): # m contains four groups: # pretext - any text before escape sequence # esc - intitial escape sequence # text - text, no escape sequence, may contain spaces # reset - ASCII colour reset space_before_text = False if m.group("pretext") != "": # Some pretext found, let's handle it # Add any words in the pretext words += m.group("pretext").split() if m.group("pretext")[-1] == " ": # Pretext ended on a space space_before_text = True else: # Pretext ended mid-word, ensure next word pass else: # pretext empty, treat as if there is a space before space_before_text = True if m.group("text")[0] == " ": # First character of the text is a space space_before_text = True # Now, handle the words in the main text: raw_words = m.group("text").split() if space_before_text: # Colorize each word with pre/post escapes # Reconstruct colored words words += [ m.group("esc") + raw_word + RESET_COLOR for raw_word in raw_words ] elif raw_words: # Pretext stops mid-word if m.group("esc") != RESET_COLOR: # Add the rest of the current word, with a reset after it words[-1] += m.group("esc") + raw_words[0] + RESET_COLOR # Add the subsequent colored words: words += [ m.group("esc") + raw_word + RESET_COLOR for raw_word in raw_words[1:] ] else: # Caught a mid-word escape sequence words[-1] += raw_words[0] words += raw_words[1:] if ( m.group("text")[-1] != " " and m.group("posttext") != "" and m.group("posttext")[0] != " " ): # reset falls mid-word post_text = m.group("posttext").split() words[-1] += post_text[0] words += post_text[1:] else: # Add any words after escape sequence words += m.group("posttext").split() result = [] next_substr = "" # Iterate over all words. previous_fit = False for i in range(len(words)): if i == 0: pot_substr = words[i] else: # (optimistically) add the next word to check the fit pot_substr = " ".join([next_substr, words[i]]) # Find out if the pot(ential)_substr fits into the next substring. fits_first = len(result) == 0 and color_len(pot_substr) <= first_width fits_middle = len(result) != 0 and color_len(pot_substr) <= middle_width if fits_first or fits_middle: # Fitted(!) let's try and add another word before appending next_substr = pot_substr previous_fit = True elif not fits_first and not fits_middle and previous_fit: # Extra word didn't fit, append what we have result.append(next_substr) next_substr = words[i] previous_fit = color_len(next_substr) <= middle_width else: # Didn't fit anywhere if uncolorize(pot_substr) == pot_substr: # Simple uncolored string, append a cropped word if len(result) == 0: # Crop word by the first_width for the first line result.append(pot_substr[:first_width]) # add rest of word to next line next_substr = pot_substr[first_width:] else: result.append(pot_substr[:middle_width]) next_substr = pot_substr[middle_width:] else: # Colored strings if len(result) == 0: this_line, next_line = color_split(pot_substr, first_width) result.append(this_line) next_substr = next_line else: this_line, next_line = color_split(pot_substr, middle_width) result.append(this_line) next_substr = next_line previous_fit = color_len(next_substr) <= middle_width # We finished constructing the substrings, but the last substring # has not yet been added to the result. result.append(next_substr) # Also, the length of the last substring was only checked against # `middle_width`. Append an empty substring as the new last substring if # the last substring is too long. if not color_len(next_substr) <= last_width: result.append("") return result def print_column_layout( indent_str, left, right, separator=" -> ", max_width=term_width() ): """Print left & right data, with separator inbetween 'left' and 'right' have a structure of: {'prefix':u'','contents':u'','suffix':u'','width':0} In a column layout the printing will be: {indent_str}{lhs0}{separator}{rhs0} {lhs1 / padding }{rhs1} ... The first line of each column (i.e. {lhs0} or {rhs0}) is: {prefix}{part of contents}{suffix} With subsequent lines (i.e. {lhs1}, {rhs1} onwards) being the rest of contents, wrapped if the width would be otherwise exceeded. """ if right["prefix"] + right["contents"] + right["suffix"] == "": # No right hand information, so we don't need a separator. separator = "" first_line_no_wrap = ( indent_str + left["prefix"] + left["contents"] + left["suffix"] + separator + right["prefix"] + right["contents"] + right["suffix"] ) if color_len(first_line_no_wrap) < max_width: # Everything fits, print out line. print_(first_line_no_wrap) else: # Wrap into columns if "width" not in left or "width" not in right: # If widths have not been defined, set to share space. left["width"] = ( max_width - len(indent_str) - color_len(separator) ) // 2 right["width"] = ( max_width - len(indent_str) - color_len(separator) ) // 2 # On the first line, account for suffix as well as prefix left_width_tuple = ( left["width"] - color_len(left["prefix"]) - color_len(left["suffix"]), left["width"] - color_len(left["prefix"]), left["width"] - color_len(left["prefix"]), ) left_split = split_into_lines(left["contents"], left_width_tuple) right_width_tuple = ( right["width"] - color_len(right["prefix"]) - color_len(right["suffix"]), right["width"] - color_len(right["prefix"]), right["width"] - color_len(right["prefix"]), ) right_split = split_into_lines(right["contents"], right_width_tuple) max_line_count = max(len(left_split), len(right_split)) out = "" for i in range(max_line_count): # indentation out += indent_str # Prefix or indent_str for line if i == 0: out += left["prefix"] else: out += indent(color_len(left["prefix"])) # Line i of left hand side contents. if i < len(left_split): out += left_split[i] left_part_len = color_len(left_split[i]) else: left_part_len = 0 # Padding until end of column. # Note: differs from original # column calcs in not -1 afterwards for space # in track number as that is included in 'prefix' padding = left["width"] - color_len(left["prefix"]) - left_part_len # Remove some padding on the first line to display # length if i == 0: padding -= color_len(left["suffix"]) out += indent(padding) if i == 0: out += left["suffix"] # Separator between columns. if i == 0: out += separator else: out += indent(color_len(separator)) # Right prefix, contents, padding, suffix if i == 0: out += right["prefix"] else: out += indent(color_len(right["prefix"])) # Line i of right hand side. if i < len(right_split): out += right_split[i] right_part_len = color_len(right_split[i]) else: right_part_len = 0 # Padding until end of column padding = ( right["width"] - color_len(right["prefix"]) - right_part_len ) # Remove some padding on the first line to display # length if i == 0: padding -= color_len(right["suffix"]) out += indent(padding) # Length in first line if i == 0: out += right["suffix"] # Linebreak, except in the last line. if i < max_line_count - 1: out += "\n" # Constructed all of the columns, now print print_(out) def print_newline_layout( indent_str, left, right, separator=" -> ", max_width=term_width() ): """Prints using a newline separator between left & right if they go over their allocated widths. The datastructures are shared with the column layout. In contrast to the column layout, the prefix and suffix are printed at the beginning and end of the contents. If no wrapping is required (i.e. everything fits) the first line will look exactly the same as the column layout: {indent}{lhs0}{separator}{rhs0} However if this would go over the width given, the layout now becomes: {indent}{lhs0} {indent}{separator}{rhs0} If {lhs0} would go over the maximum width, the subsequent lines are indented a second time for ease of reading. """ if right["prefix"] + right["contents"] + right["suffix"] == "": # No right hand information, so we don't need a separator. separator = "" first_line_no_wrap = ( indent_str + left["prefix"] + left["contents"] + left["suffix"] + separator + right["prefix"] + right["contents"] + right["suffix"] ) if color_len(first_line_no_wrap) < max_width: # Everything fits, print out line. print_(first_line_no_wrap) else: # Newline separation, with wrapping empty_space = max_width - len(indent_str) # On lower lines we will double the indent for clarity left_width_tuple = ( empty_space, empty_space - len(indent_str), empty_space - len(indent_str), ) left_str = left["prefix"] + left["contents"] + left["suffix"] left_split = split_into_lines(left_str, left_width_tuple) # Repeat calculations for rhs, including separator on first line right_width_tuple = ( empty_space - color_len(separator), empty_space - len(indent_str), empty_space - len(indent_str), ) right_str = right["prefix"] + right["contents"] + right["suffix"] right_split = split_into_lines(right_str, right_width_tuple) for i, line in enumerate(left_split): if i == 0: print_(indent_str + line) elif line != "": # Ignore empty lines print_(indent_str * 2 + line) for i, line in enumerate(right_split): if i == 0: print_(indent_str + separator + line) elif line != "": print_(indent_str * 2 + line) FLOAT_EPSILON = 0.01 def _field_diff(field, old, old_fmt, new, new_fmt): """Given two Model objects and their formatted views, format their values for `field` and highlight changes among them. Return a human-readable string. If the value has not changed, return None instead. """ oldval = old.get(field) newval = new.get(field) # If no change, abort. if ( isinstance(oldval, float) and isinstance(newval, float) and abs(oldval - newval) < FLOAT_EPSILON ): return None elif oldval == newval: return None # Get formatted values for output. oldstr = old_fmt.get(field, "") newstr = new_fmt.get(field, "") # For strings, highlight changes. For others, colorize the whole # thing. if isinstance(oldval, str): oldstr, newstr = colordiff(oldval, newstr) else: oldstr = colorize("text_error", oldstr) newstr = colorize("text_error", newstr) return f"{oldstr} -> {newstr}" def show_model_changes(new, old=None, fields=None, always=False): """Given a Model object, print a list of changes from its pristine version stored in the database. Return a boolean indicating whether any changes were found. `old` may be the "original" object to avoid using the pristine version from the database. `fields` may be a list of fields to restrict the detection to. `always` indicates whether the object is always identified, regardless of whether any changes are present. """ old = old or new._db._get(type(new), new.id) # Keep the formatted views around instead of re-creating them in each # iteration step old_fmt = old.formatted() new_fmt = new.formatted() # Build up lines showing changed fields. changes = [] for field in old: # Subset of the fields. Never show mtime. if field == "mtime" or (fields and field not in fields): continue # Detect and show difference for this field. line = _field_diff(field, old, old_fmt, new, new_fmt) if line: changes.append(f" {field}: {line}") # New fields. for field in set(new) - set(old): if fields and field not in fields: continue changes.append( " {}: {}".format(field, colorize("text_highlight", new_fmt[field])) ) # Print changes. if changes or always: print_(format(old)) if changes: print_("\n".join(changes)) return bool(changes) def show_path_changes(path_changes): """Given a list of tuples (source, destination) that indicate the path changes, log the changes as INFO-level output to the beets log. The output is guaranteed to be unicode. Every pair is shown on a single line if the terminal width permits it, else it is split over two lines. E.g., Source -> Destination vs. Source -> Destination """ sources, destinations = zip(*path_changes) # Ensure unicode output sources = list(map(util.displayable_path, sources)) destinations = list(map(util.displayable_path, destinations)) # Calculate widths for terminal split col_width = (term_width() - len(" -> ")) // 2 max_width = len(max(sources + destinations, key=len)) if max_width > col_width: # Print every change over two lines for source, dest in zip(sources, destinations): color_source, color_dest = colordiff(source, dest) print_("{0} \n -> {1}".format(color_source, color_dest)) else: # Print every change on a single line, and add a header title_pad = max_width - len("Source ") + len(" -> ") print_("Source {0} Destination".format(" " * title_pad)) for source, dest in zip(sources, destinations): pad = max_width - len(source) color_source, color_dest = colordiff(source, dest) print_( "{0} {1} -> {2}".format( color_source, " " * pad, color_dest, ) ) # Helper functions for option parsing. def _store_dict(option, opt_str, value, parser): """Custom action callback to parse options which have ``key=value`` pairs as values. All such pairs passed for this option are aggregated into a dictionary. """ dest = option.dest option_values = getattr(parser.values, dest, None) if option_values is None: # This is the first supplied ``key=value`` pair of option. # Initialize empty dictionary and get a reference to it. setattr(parser.values, dest, {}) option_values = getattr(parser.values, dest) try: key, value = value.split("=", 1) if not (key and value): raise ValueError except ValueError: raise UserError( "supplied argument `{}' is not of the form `key=value'".format( value ) ) option_values[key] = value class CommonOptionsParser(optparse.OptionParser): """Offers a simple way to add common formatting options. Options available include: - matching albums instead of tracks: add_album_option() - showing paths instead of items/albums: add_path_option() - changing the format of displayed items/albums: add_format_option() The last one can have several behaviors: - against a special target - with a certain format - autodetected target with the album option Each method is fully documented in the related method. """ def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self._album_flags = False # this serves both as an indicator that we offer the feature AND allows # us to check whether it has been specified on the CLI - bypassing the # fact that arguments may be in any order def add_album_option(self, flags=("-a", "--album")): """Add a -a/--album option to match albums instead of tracks. If used then the format option can auto-detect whether we're setting the format for items or albums. Sets the album property on the options extracted from the CLI. """ album = optparse.Option( *flags, action="store_true", help="match albums instead of tracks" ) self.add_option(album) self._album_flags = set(flags) def _set_format( self, option, opt_str, value, parser, target=None, fmt=None, store_true=False, ): """Internal callback that sets the correct format while parsing CLI arguments. """ if store_true: setattr(parser.values, option.dest, True) # Use the explicitly specified format, or the string from the option. if fmt: value = fmt elif value: (value,) = decargs([value]) else: value = "" parser.values.format = value if target: config[target._format_config_key].set(value) else: if self._album_flags: if parser.values.album: target = library.Album else: # the option is either missing either not parsed yet if self._album_flags & set(parser.rargs): target = library.Album else: target = library.Item config[target._format_config_key].set(value) else: config[library.Item._format_config_key].set(value) config[library.Album._format_config_key].set(value) def add_path_option(self, flags=("-p", "--path")): """Add a -p/--path option to display the path instead of the default format. By default this affects both items and albums. If add_album_option() is used then the target will be autodetected. Sets the format property to '$path' on the options extracted from the CLI. """ path = optparse.Option( *flags, nargs=0, action="callback", callback=self._set_format, callback_kwargs={"fmt": "$path", "store_true": True}, help="print paths for matched items or albums", ) self.add_option(path) def add_format_option(self, flags=("-f", "--format"), target=None): """Add -f/--format option to print some LibModel instances with a custom format. `target` is optional and can be one of ``library.Item``, 'item', ``library.Album`` and 'album'. Several behaviors are available: - if `target` is given then the format is only applied to that LibModel - if the album option is used then the target will be autodetected - otherwise the format is applied to both items and albums. Sets the format property on the options extracted from the CLI. """ kwargs = {} if target: if isinstance(target, str): target = {"item": library.Item, "album": library.Album}[target] kwargs["target"] = target opt = optparse.Option( *flags, action="callback", callback=self._set_format, callback_kwargs=kwargs, help="print with custom format", ) self.add_option(opt) def add_all_common_options(self): """Add album, path and format options.""" self.add_album_option() self.add_path_option() self.add_format_option() # Subcommand parsing infrastructure. # # This is a fairly generic subcommand parser for optparse. It is # maintained externally here: # https://gist.github.com/462717 # There you will also find a better description of the code and a more # succinct example program. class Subcommand: """A subcommand of a root command-line application that may be invoked by a SubcommandOptionParser. """ func: Callable[[library.Library, optparse.Values, List[str]], Any] def __init__(self, name, parser=None, help="", aliases=(), hide=False): """Creates a new subcommand. name is the primary way to invoke the subcommand; aliases are alternate names. parser is an OptionParser responsible for parsing the subcommand's options. help is a short description of the command. If no parser is given, it defaults to a new, empty CommonOptionsParser. """ self.name = name self.parser = parser or CommonOptionsParser() self.aliases = aliases self.help = help self.hide = hide self._root_parser = None def print_help(self): self.parser.print_help() def parse_args(self, args): return self.parser.parse_args(args) @property def root_parser(self): return self._root_parser @root_parser.setter def root_parser(self, root_parser): self._root_parser = root_parser self.parser.prog = "{} {}".format( as_string(root_parser.get_prog_name()), self.name ) class SubcommandsOptionParser(CommonOptionsParser): """A variant of OptionParser that parses subcommands and their arguments. """ def __init__(self, *args, **kwargs): """Create a new subcommand-aware option parser. All of the options to OptionParser.__init__ are supported in addition to subcommands, a sequence of Subcommand objects. """ # A more helpful default usage. if "usage" not in kwargs: kwargs["usage"] = """ %prog COMMAND [ARGS...] %prog help COMMAND""" kwargs["add_help_option"] = False # Super constructor. super().__init__(*args, **kwargs) # Our root parser needs to stop on the first unrecognized argument. self.disable_interspersed_args() self.subcommands = [] def add_subcommand(self, *cmds): """Adds a Subcommand object to the parser's list of commands.""" for cmd in cmds: cmd.root_parser = self self.subcommands.append(cmd) # Add the list of subcommands to the help message. def format_help(self, formatter=None): # Get the original help message, to which we will append. out = super().format_help(formatter) if formatter is None: formatter = self.formatter # Subcommands header. result = ["\n"] result.append(formatter.format_heading("Commands")) formatter.indent() # Generate the display names (including aliases). # Also determine the help position. disp_names = [] help_position = 0 subcommands = [c for c in self.subcommands if not c.hide] subcommands.sort(key=lambda c: c.name) for subcommand in subcommands: name = subcommand.name if subcommand.aliases: name += " (%s)" % ", ".join(subcommand.aliases) disp_names.append(name) # Set the help position based on the max width. proposed_help_position = len(name) + formatter.current_indent + 2 if proposed_help_position <= formatter.max_help_position: help_position = max(help_position, proposed_help_position) # Add each subcommand to the output. for subcommand, name in zip(subcommands, disp_names): # Lifted directly from optparse.py. name_width = help_position - formatter.current_indent - 2 if len(name) > name_width: name = "%*s%s\n" % (formatter.current_indent, "", name) indent_first = help_position else: name = "%*s%-*s " % ( formatter.current_indent, "", name_width, name, ) indent_first = 0 result.append(name) help_width = formatter.width - help_position help_lines = textwrap.wrap(subcommand.help, help_width) help_line = help_lines[0] if help_lines else "" result.append("%*s%s\n" % (indent_first, "", help_line)) result.extend( [ "%*s%s\n" % (help_position, "", line) for line in help_lines[1:] ] ) formatter.dedent() # Concatenate the original help message with the subcommand # list. return out + "".join(result) def _subcommand_for_name(self, name): """Return the subcommand in self.subcommands matching the given name. The name may either be the name of a subcommand or an alias. If no subcommand matches, returns None. """ for subcommand in self.subcommands: if name == subcommand.name or name in subcommand.aliases: return subcommand return None def parse_global_options(self, args): """Parse options up to the subcommand argument. Returns a tuple of the options object and the remaining arguments. """ options, subargs = self.parse_args(args) # Force the help command if options.help: subargs = ["help"] elif options.version: subargs = ["version"] return options, subargs def parse_subcommand(self, args): """Given the `args` left unused by a `parse_global_options`, return the invoked subcommand, the subcommand options, and the subcommand arguments. """ # Help is default command if not args: args = ["help"] cmdname = args.pop(0) subcommand = self._subcommand_for_name(cmdname) if not subcommand: raise UserError(f"unknown command '{cmdname}'") suboptions, subargs = subcommand.parse_args(args) return subcommand, suboptions, subargs optparse.Option.ALWAYS_TYPED_ACTIONS += ("callback",) # The main entry point and bootstrapping. def _load_plugins(options, config): """Load the plugins specified on the command line or in the configuration.""" paths = config["pluginpath"].as_str_seq(split=False) paths = [util.normpath(p) for p in paths] log.debug("plugin paths: {0}", util.displayable_path(paths)) # On Python 3, the search paths need to be unicode. paths = [os.fsdecode(p) for p in paths] # Extend the `beetsplug` package to include the plugin paths. import beetsplug beetsplug.__path__ = paths + list(beetsplug.__path__) # For backwards compatibility, also support plugin paths that # *contain* a `beetsplug` package. sys.path += paths # If we were given any plugins on the command line, use those. if options.plugins is not None: plugin_list = ( options.plugins.split(",") if len(options.plugins) > 0 else [] ) else: plugin_list = config["plugins"].as_str_seq() # Exclude any plugins that were specified on the command line if options.exclude is not None: plugin_list = [ p for p in plugin_list if p not in options.exclude.split(",") ] plugins.load_plugins(plugin_list) return plugins def _setup(options, lib=None): """Prepare and global state and updates it with command line options. Returns a list of subcommands, a list of plugins, and a library instance. """ # Configure the MusicBrainz API. mb.configure() config = _configure(options) plugins = _load_plugins(options, config) # Add types and queries defined by plugins. plugin_types_album = plugins.types(library.Album) library.Album._types.update(plugin_types_album) item_types = plugin_types_album.copy() item_types.update(library.Item._types) item_types.update(plugins.types(library.Item)) library.Item._types = item_types library.Item._queries.update(plugins.named_queries(library.Item)) library.Album._queries.update(plugins.named_queries(library.Album)) plugins.send("pluginload") # Get the default subcommands. from beets.ui.commands import default_commands subcommands = list(default_commands) subcommands.extend(plugins.commands()) if lib is None: lib = _open_library(config) plugins.send("library_opened", lib=lib) return subcommands, plugins, lib def _configure(options): """Amend the global configuration object with command line options.""" # Add any additional config files specified with --config. This # special handling lets specified plugins get loaded before we # finish parsing the command line. if getattr(options, "config", None) is not None: overlay_path = options.config del options.config config.set_file(overlay_path) else: overlay_path = None config.set_args(options) # Configure the logger. if config["verbose"].get(int): log.set_global_level(logging.DEBUG) else: log.set_global_level(logging.INFO) if overlay_path: log.debug( "overlaying configuration: {0}", util.displayable_path(overlay_path) ) config_path = config.user_config_path() if os.path.isfile(config_path): log.debug("user configuration: {0}", util.displayable_path(config_path)) else: log.debug( "no user configuration found at {0}", util.displayable_path(config_path), ) log.debug("data directory: {0}", util.displayable_path(config.config_dir())) return config def _ensure_db_directory_exists(path): if path == b":memory:": # in memory db return newpath = os.path.dirname(path) if not os.path.isdir(newpath): if input_yn( "The database directory {} does not \ exist. Create it (Y/n)?".format( util.displayable_path(newpath) ) ): os.makedirs(newpath) def _open_library(config): """Create a new library instance from the configuration.""" dbpath = util.bytestring_path(config["library"].as_filename()) _ensure_db_directory_exists(dbpath) try: lib = library.Library( dbpath, config["directory"].as_filename(), get_path_formats(), get_replacements(), ) lib.get_item(0) # Test database connection. except (sqlite3.OperationalError, sqlite3.DatabaseError) as db_error: log.debug("{}", traceback.format_exc()) raise UserError( "database file {} cannot not be opened: {}".format( util.displayable_path(dbpath), db_error ) ) log.debug( "library database: {0}\n" "library directory: {1}", util.displayable_path(lib.path), util.displayable_path(lib.directory), ) return lib def _raw_main(args, lib=None): """A helper function for `main` without top-level exception handling. """ parser = SubcommandsOptionParser() parser.add_format_option(flags=("--format-item",), target=library.Item) parser.add_format_option(flags=("--format-album",), target=library.Album) parser.add_option( "-l", "--library", dest="library", help="library database file to use" ) parser.add_option( "-d", "--directory", dest="directory", help="destination music directory", ) parser.add_option( "-v", "--verbose", dest="verbose", action="count", help="log more details (use twice for even more)", ) parser.add_option( "-c", "--config", dest="config", help="path to configuration file" ) parser.add_option( "-p", "--plugins", dest="plugins", help="a comma-separated list of plugins to load", ) parser.add_option( "-P", "--disable-plugins", dest="exclude", help="a comma-separated list of plugins to disable", ) parser.add_option( "-h", "--help", dest="help", action="store_true", help="show this help message and exit", ) parser.add_option( "--version", dest="version", action="store_true", help=optparse.SUPPRESS_HELP, ) options, subargs = parser.parse_global_options(args) # Special case for the `config --edit` command: bypass _setup so # that an invalid configuration does not prevent the editor from # starting. if ( subargs and subargs[0] == "config" and ("-e" in subargs or "--edit" in subargs) ): from beets.ui.commands import config_edit return config_edit() test_lib = bool(lib) subcommands, plugins, lib = _setup(options, lib) parser.add_subcommand(*subcommands) subcommand, suboptions, subargs = parser.parse_subcommand(subargs) subcommand.func(lib, suboptions, subargs) plugins.send("cli_exit", lib=lib) if not test_lib: # Clean up the library unless it came from the test harness. lib._close() def main(args=None): """Run the main command-line interface for beets. Includes top-level exception handlers that print friendly error messages. """ if "AppData\\Local\\Microsoft\\WindowsApps" in sys.exec_prefix: log.error( "error: beets is unable to use the Microsoft Store version of " "Python. Please install Python from https://python.org.\n" "error: More details can be found here " "https://beets.readthedocs.io/en/stable/guides/main.html" ) sys.exit(1) try: _raw_main(args) except UserError as exc: message = exc.args[0] if exc.args else None log.error("error: {0}", message) sys.exit(1) except util.HumanReadableError as exc: exc.log(log) sys.exit(1) except library.FileOperationError as exc: # These errors have reasonable human-readable descriptions, but # we still want to log their tracebacks for debugging. log.debug("{}", traceback.format_exc()) log.error("{}", exc) sys.exit(1) except confuse.ConfigError as exc: log.error("configuration error: {0}", exc) sys.exit(1) except db_query.InvalidQueryError as exc: log.error("invalid query: {0}", exc) sys.exit(1) except OSError as exc: if exc.errno == errno.EPIPE: # "Broken pipe". End silently. sys.stderr.close() else: raise except KeyboardInterrupt: # Silently ignore ^C except in verbose mode. log.debug("{}", traceback.format_exc()) except db.DBAccessError as exc: log.error( "database access error: {0}\n" "the library file might have a permissions problem", exc, ) sys.exit(1) beetbox-beets-01f1faf/beets/ui/commands.py000077500000000000000000002354021472325477400206520ustar00rootroot00000000000000# This file is part of beets. # Copyright 2016, Adrian Sampson. # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the # "Software"), to deal in the Software without restriction, including # without limitation the rights to use, copy, modify, merge, publish, # distribute, sublicense, and/or sell copies of the Software, and to # permit persons to whom the Software is furnished to do so, subject to # the following conditions: # # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. """This module provides the default commands for beets' command-line interface. """ import os import re from collections import Counter from itertools import chain from platform import python_version from typing import Any, NamedTuple, Sequence import beets from beets import autotag, config, importer, library, logging, plugins, ui, util from beets.autotag import Recommendation, hooks from beets.ui import ( decargs, input_, print_, print_column_layout, print_newline_layout, show_path_changes, ) from beets.util import ( MoveOperation, ancestry, displayable_path, functemplate, normpath, syspath, ) from . import _store_dict VARIOUS_ARTISTS = "Various Artists" # Global logger. log = logging.getLogger("beets") # The list of default subcommands. This is populated with Subcommand # objects that can be fed to a SubcommandsOptionParser. default_commands = [] # Utilities. def _do_query(lib, query, album, also_items=True): """For commands that operate on matched items, performs a query and returns a list of matching items and a list of matching albums. (The latter is only nonempty when album is True.) Raises a UserError if no items match. also_items controls whether, when fetching albums, the associated items should be fetched also. """ if album: albums = list(lib.albums(query)) items = [] if also_items: for al in albums: items += al.items() else: albums = [] items = list(lib.items(query)) if album and not albums: raise ui.UserError("No matching albums found.") elif not album and not items: raise ui.UserError("No matching items found.") return items, albums def _paths_from_logfile(path): """Parse the logfile and yield skipped paths to pass to the `import` command. """ with open(path, encoding="utf-8") as fp: for i, line in enumerate(fp, start=1): verb, sep, paths = line.rstrip("\n").partition(" ") if not sep: raise ValueError(f"line {i} is invalid") # Ignore informational lines that don't need to be re-imported. if verb in {"import", "duplicate-keep", "duplicate-replace"}: continue if verb not in {"asis", "skip", "duplicate-skip"}: raise ValueError(f"line {i} contains unknown verb {verb}") yield os.path.commonpath(paths.split("; ")) def _parse_logfiles(logfiles): """Parse all `logfiles` and yield paths from it.""" for logfile in logfiles: try: yield from _paths_from_logfile(syspath(normpath(logfile))) except ValueError as err: raise ui.UserError( "malformed logfile {}: {}".format( util.displayable_path(logfile), str(err) ) ) from err except OSError as err: raise ui.UserError( "unreadable logfile {}: {}".format( util.displayable_path(logfile), str(err) ) ) from err # fields: Shows a list of available fields for queries and format strings. def _print_keys(query): """Given a SQLite query result, print the `key` field of each returned row, with indentation of 2 spaces. """ for row in query: print_(" " * 2 + row["key"]) def fields_func(lib, opts, args): def _print_rows(names): names.sort() print_(" " + "\n ".join(names)) print_("Item fields:") _print_rows(library.Item.all_keys()) print_("Album fields:") _print_rows(library.Album.all_keys()) with lib.transaction() as tx: # The SQL uses the DISTINCT to get unique values from the query unique_fields = "SELECT DISTINCT key FROM (%s)" print_("Item flexible attributes:") _print_keys(tx.query(unique_fields % library.Item._flex_table)) print_("Album flexible attributes:") _print_keys(tx.query(unique_fields % library.Album._flex_table)) fields_cmd = ui.Subcommand( "fields", help="show fields available for queries and format strings" ) fields_cmd.func = fields_func default_commands.append(fields_cmd) # help: Print help text for commands class HelpCommand(ui.Subcommand): def __init__(self): super().__init__( "help", aliases=("?",), help="give detailed help on a specific sub-command", ) def func(self, lib, opts, args): if args: cmdname = args[0] helpcommand = self.root_parser._subcommand_for_name(cmdname) if not helpcommand: raise ui.UserError(f"unknown command '{cmdname}'") helpcommand.print_help() else: self.root_parser.print_help() default_commands.append(HelpCommand()) # import: Autotagger and importer. # Importer utilities and support. def disambig_string(info): """Generate a string for an AlbumInfo or TrackInfo object that provides context that helps disambiguate similar-looking albums and tracks. """ if isinstance(info, hooks.AlbumInfo): disambig = get_album_disambig_fields(info) elif isinstance(info, hooks.TrackInfo): disambig = get_singleton_disambig_fields(info) else: return "" return ", ".join(disambig) def get_singleton_disambig_fields(info: hooks.TrackInfo) -> Sequence[str]: out = [] chosen_fields = config["match"]["singleton_disambig_fields"].as_str_seq() calculated_values = { "index": "Index {}".format(str(info.index)), "track_alt": "Track {}".format(info.track_alt), "album": ( "[{}]".format(info.album) if ( config["import"]["singleton_album_disambig"].get() and info.get("album") ) else "" ), } for field in chosen_fields: if field in calculated_values: out.append(str(calculated_values[field])) else: try: out.append(str(info[field])) except (AttributeError, KeyError): print(f"Disambiguation string key {field} does not exist.") return out def get_album_disambig_fields(info: hooks.AlbumInfo) -> Sequence[str]: out = [] chosen_fields = config["match"]["album_disambig_fields"].as_str_seq() calculated_values = { "media": ( "{}x{}".format(info.mediums, info.media) if (info.mediums and info.mediums > 1) else info.media ), } for field in chosen_fields: if field in calculated_values: out.append(str(calculated_values[field])) else: try: out.append(str(info[field])) except (AttributeError, KeyError): print(f"Disambiguation string key {field} does not exist.") return out def dist_colorize(string, dist): """Formats a string as a colorized similarity string according to a distance. """ if dist <= config["match"]["strong_rec_thresh"].as_number(): string = ui.colorize("text_success", string) elif dist <= config["match"]["medium_rec_thresh"].as_number(): string = ui.colorize("text_warning", string) else: string = ui.colorize("text_error", string) return string def dist_string(dist): """Formats a distance (a float) as a colorized similarity percentage string. """ string = "{:.1f}%".format(((1 - dist) * 100)) return dist_colorize(string, dist) def penalty_string(distance, limit=None): """Returns a colorized string that indicates all the penalties applied to a distance object. """ penalties = [] for key in distance.keys(): key = key.replace("album_", "") key = key.replace("track_", "") key = key.replace("_", " ") penalties.append(key) if penalties: if limit and len(penalties) > limit: penalties = penalties[:limit] + ["..."] # Prefix penalty string with U+2260: Not Equal To penalty_string = "\u2260 {}".format(", ".join(penalties)) return ui.colorize("changed", penalty_string) class ChangeRepresentation: """Keeps track of all information needed to generate a (colored) text representation of the changes that will be made if an album or singleton's tags are changed according to `match`, which must be an AlbumMatch or TrackMatch object, accordingly. """ cur_artist = None # cur_album set if album, cur_title set if singleton cur_album = None cur_title = None match = None indent_header = "" indent_detail = "" def __init__(self): # Read match header indentation width from config. match_header_indent_width = config["ui"]["import"]["indentation"][ "match_header" ].as_number() self.indent_header = ui.indent(match_header_indent_width) # Read match detail indentation width from config. match_detail_indent_width = config["ui"]["import"]["indentation"][ "match_details" ].as_number() self.indent_detail = ui.indent(match_detail_indent_width) # Read match tracklist indentation width from config match_tracklist_indent_width = config["ui"]["import"]["indentation"][ "match_tracklist" ].as_number() self.indent_tracklist = ui.indent(match_tracklist_indent_width) self.layout = config["ui"]["import"]["layout"].as_choice( { "column": 0, "newline": 1, } ) def print_layout( self, indent, left, right, separator=" -> ", max_width=None ): if not max_width: # If no max_width provided, use terminal width max_width = ui.term_width() if self.layout == 0: print_column_layout(indent, left, right, separator, max_width) else: print_newline_layout(indent, left, right, separator, max_width) def show_match_header(self): """Print out a 'header' identifying the suggested match (album name, artist name,...) and summarizing the changes that would be made should the user accept the match. """ # Print newline at beginning of change block. print_("") # 'Match' line and similarity. print_( self.indent_header + f"Match ({dist_string(self.match.distance)}):" ) if self.match.info.get("album"): # Matching an album - print that artist_album_str = ( f"{self.match.info.artist}" + f" - {self.match.info.album}" ) else: # Matching a single track artist_album_str = ( f"{self.match.info.artist}" + f" - {self.match.info.title}" ) print_( self.indent_header + dist_colorize(artist_album_str, self.match.distance) ) # Penalties. penalties = penalty_string(self.match.distance) if penalties: print_(self.indent_header + penalties) # Disambiguation. disambig = disambig_string(self.match.info) if disambig: print_(self.indent_header + disambig) # Data URL. if self.match.info.data_url: url = ui.colorize("text_faint", f"{self.match.info.data_url}") print_(self.indent_header + url) def show_match_details(self): """Print out the details of the match, including changes in album name and artist name. """ # Artist. artist_l, artist_r = self.cur_artist or "", self.match.info.artist if artist_r == VARIOUS_ARTISTS: # Hide artists for VA releases. artist_l, artist_r = "", "" if artist_l != artist_r: artist_l, artist_r = ui.colordiff(artist_l, artist_r) # Prefix with U+2260: Not Equal To left = { "prefix": ui.colorize("changed", "\u2260") + " Artist: ", "contents": artist_l, "suffix": "", } right = {"prefix": "", "contents": artist_r, "suffix": ""} self.print_layout(self.indent_detail, left, right) else: print_(self.indent_detail + "*", "Artist:", artist_r) if self.cur_album: # Album album_l, album_r = self.cur_album or "", self.match.info.album if ( self.cur_album != self.match.info.album and self.match.info.album != VARIOUS_ARTISTS ): album_l, album_r = ui.colordiff(album_l, album_r) # Prefix with U+2260: Not Equal To left = { "prefix": ui.colorize("changed", "\u2260") + " Album: ", "contents": album_l, "suffix": "", } right = {"prefix": "", "contents": album_r, "suffix": ""} self.print_layout(self.indent_detail, left, right) else: print_(self.indent_detail + "*", "Album:", album_r) elif self.cur_title: # Title - for singletons title_l, title_r = self.cur_title or "", self.match.info.title if self.cur_title != self.match.info.title: title_l, title_r = ui.colordiff(title_l, title_r) # Prefix with U+2260: Not Equal To left = { "prefix": ui.colorize("changed", "\u2260") + " Title: ", "contents": title_l, "suffix": "", } right = {"prefix": "", "contents": title_r, "suffix": ""} self.print_layout(self.indent_detail, left, right) else: print_(self.indent_detail + "*", "Title:", title_r) def make_medium_info_line(self, track_info): """Construct a line with the current medium's info.""" track_media = track_info.get("media", "Media") # Build output string. if self.match.info.mediums > 1 and track_info.disctitle: return ( f"* {track_media} {track_info.medium}: {track_info.disctitle}" ) elif self.match.info.mediums > 1: return f"* {track_media} {track_info.medium}" elif track_info.disctitle: return f"* {track_media}: {track_info.disctitle}" else: return "" def format_index(self, track_info): """Return a string representing the track index of the given TrackInfo or Item object. """ if isinstance(track_info, hooks.TrackInfo): index = track_info.index medium_index = track_info.medium_index medium = track_info.medium mediums = self.match.info.mediums else: index = medium_index = track_info.track medium = track_info.disc mediums = track_info.disctotal if config["per_disc_numbering"]: if mediums and mediums > 1: return f"{medium}-{medium_index}" else: return str(medium_index if medium_index is not None else index) else: return str(index) def make_track_numbers(self, item, track_info): """Format colored track indices.""" cur_track = self.format_index(item) new_track = self.format_index(track_info) templ = "(#{})" changed = False # Choose color based on change. if cur_track != new_track: changed = True if item.track in (track_info.index, track_info.medium_index): highlight_color = "text_highlight_minor" else: highlight_color = "text_highlight" else: highlight_color = "text_faint" cur_track = templ.format(cur_track) new_track = templ.format(new_track) lhs_track = ui.colorize(highlight_color, cur_track) rhs_track = ui.colorize(highlight_color, new_track) return lhs_track, rhs_track, changed @staticmethod def make_track_titles(item, track_info): """Format colored track titles.""" new_title = track_info.title if not item.title.strip(): # If there's no title, we use the filename. Don't colordiff. cur_title = displayable_path(os.path.basename(item.path)) return cur_title, new_title, True else: # If there is a title, highlight differences. cur_title = item.title.strip() cur_col, new_col = ui.colordiff(cur_title, new_title) return cur_col, new_col, cur_title != new_title @staticmethod def make_track_lengths(item, track_info): """Format colored track lengths.""" changed = False if ( item.length and track_info.length and abs(item.length - track_info.length) >= config["ui"]["length_diff_thresh"].as_number() ): highlight_color = "text_highlight" changed = True else: highlight_color = "text_highlight_minor" # Handle nonetype lengths by setting to 0 cur_length0 = item.length if item.length else 0 new_length0 = track_info.length if track_info.length else 0 # format into string cur_length = f"({ui.human_seconds_short(cur_length0)})" new_length = f"({ui.human_seconds_short(new_length0)})" # colorize lhs_length = ui.colorize(highlight_color, cur_length) rhs_length = ui.colorize(highlight_color, new_length) return lhs_length, rhs_length, changed def make_line(self, item, track_info): """Extract changes from item -> new TrackInfo object, and colorize appropriately. Returns (lhs, rhs) for column printing. """ # Track titles. lhs_title, rhs_title, diff_title = self.make_track_titles( item, track_info ) # Track number change. lhs_track, rhs_track, diff_track = self.make_track_numbers( item, track_info ) # Length change. lhs_length, rhs_length, diff_length = self.make_track_lengths( item, track_info ) changed = diff_title or diff_track or diff_length # Construct lhs and rhs dicts. # Previously, we printed the penalties, however this is no longer # the case, thus the 'info' dictionary is unneeded. # penalties = penalty_string(self.match.distance.tracks[track_info]) prefix = ui.colorize("changed", "\u2260 ") if changed else "* " lhs = { "prefix": prefix + lhs_track + " ", "contents": lhs_title, "suffix": " " + lhs_length, } rhs = {"prefix": "", "contents": "", "suffix": ""} if not changed: # Only return the left side, as nothing changed. return (lhs, rhs) else: # Construct a dictionary for the "changed to" side rhs = { "prefix": rhs_track + " ", "contents": rhs_title, "suffix": " " + rhs_length, } return (lhs, rhs) def print_tracklist(self, lines): """Calculates column widths for tracks stored as line tuples: (left, right). Then prints each line of tracklist. """ if len(lines) == 0: # If no lines provided, e.g. details not required, do nothing. return def get_width(side): """Return the width of left or right in uncolorized characters.""" try: return len( ui.uncolorize( " ".join( [side["prefix"], side["contents"], side["suffix"]] ) ) ) except KeyError: # An empty dictionary -> Nothing to report return 0 # Check how to fit content into terminal window indent_width = len(self.indent_tracklist) terminal_width = ui.term_width() joiner_width = len("".join(["* ", " -> "])) col_width = (terminal_width - indent_width - joiner_width) // 2 max_width_l = max(get_width(line_tuple[0]) for line_tuple in lines) max_width_r = max(get_width(line_tuple[1]) for line_tuple in lines) if ( (max_width_l <= col_width) and (max_width_r <= col_width) or ( ((max_width_l > col_width) or (max_width_r > col_width)) and ((max_width_l + max_width_r) <= col_width * 2) ) ): # All content fits. Either both maximum widths are below column # widths, or one of the columns is larger than allowed but the # other is smaller than allowed. # In this case we can afford to shrink the columns to fit their # largest string col_width_l = max_width_l col_width_r = max_width_r else: # Not all content fits - stick with original half/half split col_width_l = col_width col_width_r = col_width # Print out each line, using the calculated width from above. for left, right in lines: left["width"] = col_width_l right["width"] = col_width_r self.print_layout(self.indent_tracklist, left, right) class AlbumChange(ChangeRepresentation): """Album change representation, setting cur_album""" def __init__(self, cur_artist, cur_album, match): super().__init__() self.cur_artist = cur_artist self.cur_album = cur_album self.match = match def show_match_tracks(self): """Print out the tracks of the match, summarizing changes the match suggests for them. """ # Tracks. # match is an AlbumMatch NamedTuple, mapping is a dict # Sort the pairs by the track_info index (at index 1 of the NamedTuple) pairs = list(self.match.mapping.items()) pairs.sort(key=lambda item_and_track_info: item_and_track_info[1].index) # Build up LHS and RHS for track difference display. The `lines` list # contains `(left, right)` tuples. lines = [] medium = disctitle = None for item, track_info in pairs: # If the track is the first on a new medium, show medium # number and title. if medium != track_info.medium or disctitle != track_info.disctitle: # Create header for new medium header = self.make_medium_info_line(track_info) if header != "": # Print tracks from previous medium self.print_tracklist(lines) lines = [] print_(self.indent_detail + header) # Save new medium details for future comparison. medium, disctitle = track_info.medium, track_info.disctitle # Construct the line tuple for the track. left, right = self.make_line(item, track_info) if right["contents"] != "": lines.append((left, right)) else: if config["import"]["detail"]: lines.append((left, right)) self.print_tracklist(lines) # Missing and unmatched tracks. if self.match.extra_tracks: print_( "Missing tracks ({0}/{1} - {2:.1%}):".format( len(self.match.extra_tracks), len(self.match.info.tracks), len(self.match.extra_tracks) / len(self.match.info.tracks), ) ) for track_info in self.match.extra_tracks: line = f" ! {track_info.title} (#{self.format_index(track_info)})" if track_info.length: line += f" ({ui.human_seconds_short(track_info.length)})" print_(ui.colorize("text_warning", line)) if self.match.extra_items: print_(f"Unmatched tracks ({len(self.match.extra_items)}):") for item in self.match.extra_items: line = " ! {} (#{})".format(item.title, self.format_index(item)) if item.length: line += " ({})".format(ui.human_seconds_short(item.length)) print_(ui.colorize("text_warning", line)) class TrackChange(ChangeRepresentation): """Track change representation, comparing item with match.""" def __init__(self, cur_artist, cur_title, match): super().__init__() self.cur_artist = cur_artist self.cur_title = cur_title self.match = match def show_change(cur_artist, cur_album, match): """Print out a representation of the changes that will be made if an album's tags are changed according to `match`, which must be an AlbumMatch object. """ change = AlbumChange( cur_artist=cur_artist, cur_album=cur_album, match=match ) # Print the match header. change.show_match_header() # Print the match details. change.show_match_details() # Print the match tracks. change.show_match_tracks() def show_item_change(item, match): """Print out the change that would occur by tagging `item` with the metadata from `match`, a TrackMatch object. """ change = TrackChange( cur_artist=item.artist, cur_title=item.title, match=match ) # Print the match header. change.show_match_header() # Print the match details. change.show_match_details() def summarize_items(items, singleton): """Produces a brief summary line describing a set of items. Used for manually resolving duplicates during import. `items` is a list of `Item` objects. `singleton` indicates whether this is an album or single-item import (if the latter, them `items` should only have one element). """ summary_parts = [] if not singleton: summary_parts.append("{} items".format(len(items))) format_counts = {} for item in items: format_counts[item.format] = format_counts.get(item.format, 0) + 1 if len(format_counts) == 1: # A single format. summary_parts.append(items[0].format) else: # Enumerate all the formats by decreasing frequencies: for fmt, count in sorted( format_counts.items(), key=lambda fmt_and_count: (-fmt_and_count[1], fmt_and_count[0]), ): summary_parts.append(f"{fmt} {count}") if items: average_bitrate = sum([item.bitrate for item in items]) / len(items) total_duration = sum([item.length for item in items]) total_filesize = sum([item.filesize for item in items]) summary_parts.append("{}kbps".format(int(average_bitrate / 1000))) if items[0].format == "FLAC": sample_bits = "{}kHz/{} bit".format( round(int(items[0].samplerate) / 1000, 1), items[0].bitdepth ) summary_parts.append(sample_bits) summary_parts.append(ui.human_seconds_short(total_duration)) summary_parts.append(ui.human_bytes(total_filesize)) return ", ".join(summary_parts) def _summary_judgment(rec): """Determines whether a decision should be made without even asking the user. This occurs in quiet mode and when an action is chosen for NONE recommendations. Return None if the user should be queried. Otherwise, returns an action. May also print to the console if a summary judgment is made. """ if config["import"]["quiet"]: if rec == Recommendation.strong: return importer.action.APPLY else: action = config["import"]["quiet_fallback"].as_choice( { "skip": importer.action.SKIP, "asis": importer.action.ASIS, } ) elif config["import"]["timid"]: return None elif rec == Recommendation.none: action = config["import"]["none_rec_action"].as_choice( { "skip": importer.action.SKIP, "asis": importer.action.ASIS, "ask": None, } ) else: return None if action == importer.action.SKIP: print_("Skipping.") elif action == importer.action.ASIS: print_("Importing as-is.") return action class PromptChoice(NamedTuple): short: str long: str callback: Any def choose_candidate( candidates, singleton, rec, cur_artist=None, cur_album=None, item=None, itemcount=None, choices=[], ): """Given a sorted list of candidates, ask the user for a selection of which candidate to use. Applies to both full albums and singletons (tracks). Candidates are either AlbumMatch or TrackMatch objects depending on `singleton`. for albums, `cur_artist`, `cur_album`, and `itemcount` must be provided. For singletons, `item` must be provided. `choices` is a list of `PromptChoice`s to be used in each prompt. Returns one of the following: * the result of the choice, which may be SKIP or ASIS * a candidate (an AlbumMatch/TrackMatch object) * a chosen `PromptChoice` from `choices` """ # Sanity check. if singleton: assert item is not None else: assert cur_artist is not None assert cur_album is not None # Build helper variables for the prompt choices. choice_opts = tuple(c.long for c in choices) choice_actions = {c.short: c for c in choices} # Zero candidates. if not candidates: if singleton: print_("No matching recordings found.") else: print_("No matching release found for {} tracks.".format(itemcount)) print_( "For help, see: " "https://beets.readthedocs.org/en/latest/faq.html#nomatch" ) sel = ui.input_options(choice_opts) if sel in choice_actions: return choice_actions[sel] else: assert False # Is the change good enough? bypass_candidates = False if rec != Recommendation.none: match = candidates[0] bypass_candidates = True while True: # Display and choose from candidates. require = rec <= Recommendation.low if not bypass_candidates: # Display list of candidates. print_("") print_( 'Finding tags for {} "{} - {}".'.format( "track" if singleton else "album", item.artist if singleton else cur_artist, item.title if singleton else cur_album, ) ) print_(ui.indent(2) + "Candidates:") for i, match in enumerate(candidates): # Index, metadata, and distance. index0 = "{0}.".format(i + 1) index = dist_colorize(index0, match.distance) dist = "({:.1f}%)".format((1 - match.distance) * 100) distance = dist_colorize(dist, match.distance) metadata = "{0} - {1}".format( match.info.artist, match.info.title if singleton else match.info.album, ) if i == 0: metadata = dist_colorize(metadata, match.distance) else: metadata = ui.colorize("text_highlight_minor", metadata) line1 = [index, distance, metadata] print_(ui.indent(2) + " ".join(line1)) # Penalties. penalties = penalty_string(match.distance, 3) if penalties: print_(ui.indent(13) + penalties) # Disambiguation disambig = disambig_string(match.info) if disambig: print_(ui.indent(13) + disambig) # Ask the user for a choice. sel = ui.input_options(choice_opts, numrange=(1, len(candidates))) if sel == "m": pass elif sel in choice_actions: return choice_actions[sel] else: # Numerical selection. match = candidates[sel - 1] if sel != 1: # When choosing anything but the first match, # disable the default action. require = True bypass_candidates = False # Show what we're about to do. if singleton: show_item_change(item, match) else: show_change(cur_artist, cur_album, match) # Exact match => tag automatically if we're not in timid mode. if rec == Recommendation.strong and not config["import"]["timid"]: return match # Ask for confirmation. default = config["import"]["default_action"].as_choice( { "apply": "a", "skip": "s", "asis": "u", "none": None, } ) if default is None: require = True # Bell ring when user interaction is needed. if config["import"]["bell"]: ui.print_("\a", end="") sel = ui.input_options( ("Apply", "More candidates") + choice_opts, require=require, default=default, ) if sel == "a": return match elif sel in choice_actions: return choice_actions[sel] def manual_search(session, task): """Get a new `Proposal` using manual search criteria. Input either an artist and album (for full albums) or artist and track name (for singletons) for manual search. """ artist = input_("Artist:").strip() name = input_("Album:" if task.is_album else "Track:").strip() if task.is_album: _, _, prop = autotag.tag_album(task.items, artist, name) return prop else: return autotag.tag_item(task.item, artist, name) def manual_id(session, task): """Get a new `Proposal` using a manually-entered ID. Input an ID, either for an album ("release") or a track ("recording"). """ prompt = "Enter {} ID:".format("release" if task.is_album else "recording") search_id = input_(prompt).strip() if task.is_album: _, _, prop = autotag.tag_album(task.items, search_ids=search_id.split()) return prop else: return autotag.tag_item(task.item, search_ids=search_id.split()) def abort_action(session, task): """A prompt choice callback that aborts the importer.""" raise importer.ImportAbortError() class TerminalImportSession(importer.ImportSession): """An import session that runs in a terminal.""" def choose_match(self, task): """Given an initial autotagging of items, go through an interactive dance with the user to ask for a choice of metadata. Returns an AlbumMatch object, ASIS, or SKIP. """ # Show what we're tagging. print_() path_str0 = displayable_path(task.paths, "\n") path_str = ui.colorize("import_path", path_str0) items_str0 = "({} items)".format(len(task.items)) items_str = ui.colorize("import_path_items", items_str0) print_(" ".join([path_str, items_str])) # Let plugins display info or prompt the user before we go through the # process of selecting candidate. results = plugins.send( "import_task_before_choice", session=self, task=task ) actions = [action for action in results if action] if len(actions) == 1: return actions[0] elif len(actions) > 1: raise plugins.PluginConflictError( "Only one handler for `import_task_before_choice` may return " "an action." ) # Take immediate action if appropriate. action = _summary_judgment(task.rec) if action == importer.action.APPLY: match = task.candidates[0] show_change(task.cur_artist, task.cur_album, match) return match elif action is not None: return action # Loop until we have a choice. while True: # Ask for a choice from the user. The result of # `choose_candidate` may be an `importer.action`, an # `AlbumMatch` object for a specific selection, or a # `PromptChoice`. choices = self._get_choices(task) choice = choose_candidate( task.candidates, False, task.rec, task.cur_artist, task.cur_album, itemcount=len(task.items), choices=choices, ) # Basic choices that require no more action here. if choice in (importer.action.SKIP, importer.action.ASIS): # Pass selection to main control flow. return choice # Plugin-provided choices. We invoke the associated callback # function. elif choice in choices: post_choice = choice.callback(self, task) if isinstance(post_choice, importer.action): return post_choice elif isinstance(post_choice, autotag.Proposal): # Use the new candidates and continue around the loop. task.candidates = post_choice.candidates task.rec = post_choice.recommendation # Otherwise, we have a specific match selection. else: # We have a candidate! Finish tagging. Here, choice is an # AlbumMatch object. assert isinstance(choice, autotag.AlbumMatch) return choice def choose_item(self, task): """Ask the user for a choice about tagging a single item. Returns either an action constant or a TrackMatch object. """ print_() print_(displayable_path(task.item.path)) candidates, rec = task.candidates, task.rec # Take immediate action if appropriate. action = _summary_judgment(task.rec) if action == importer.action.APPLY: match = candidates[0] show_item_change(task.item, match) return match elif action is not None: return action while True: # Ask for a choice. choices = self._get_choices(task) choice = choose_candidate( candidates, True, rec, item=task.item, choices=choices ) if choice in (importer.action.SKIP, importer.action.ASIS): return choice elif choice in choices: post_choice = choice.callback(self, task) if isinstance(post_choice, importer.action): return post_choice elif isinstance(post_choice, autotag.Proposal): candidates = post_choice.candidates rec = post_choice.recommendation else: # Chose a candidate. assert isinstance(choice, autotag.TrackMatch) return choice def resolve_duplicate(self, task, found_duplicates): """Decide what to do when a new album or item seems similar to one that's already in the library. """ log.warning( "This {0} is already in the library!", ("album" if task.is_album else "item"), ) if config["import"]["quiet"]: # In quiet mode, don't prompt -- just skip. log.info("Skipping.") sel = "s" else: # Print some detail about the existing and new items so the # user can make an informed decision. for duplicate in found_duplicates: print_( "Old: " + summarize_items( ( list(duplicate.items()) if task.is_album else [duplicate] ), not task.is_album, ) ) if config["import"]["duplicate_verbose_prompt"]: if task.is_album: for dup in duplicate.items(): print(f" {dup}") else: print(f" {duplicate}") print_( "New: " + summarize_items( task.imported_items(), not task.is_album, ) ) if config["import"]["duplicate_verbose_prompt"]: for item in task.imported_items(): print(f" {item}") sel = ui.input_options( ("Skip new", "Keep all", "Remove old", "Merge all") ) if sel == "s": # Skip new. task.set_choice(importer.action.SKIP) elif sel == "k": # Keep both. Do nothing; leave the choice intact. pass elif sel == "r": # Remove old. task.should_remove_duplicates = True elif sel == "m": task.should_merge_duplicates = True else: assert False def should_resume(self, path): return ui.input_yn( "Import of the directory:\n{}\n" "was interrupted. Resume (Y/n)?".format(displayable_path(path)) ) def _get_choices(self, task): """Get the list of prompt choices that should be presented to the user. This consists of both built-in choices and ones provided by plugins. The `before_choose_candidate` event is sent to the plugins, with session and task as its parameters. Plugins are responsible for checking the right conditions and returning a list of `PromptChoice`s, which is flattened and checked for conflicts. If two or more choices have the same short letter, a warning is emitted and all but one choices are discarded, giving preference to the default importer choices. Returns a list of `PromptChoice`s. """ # Standard, built-in choices. choices = [ PromptChoice("s", "Skip", lambda s, t: importer.action.SKIP), PromptChoice("u", "Use as-is", lambda s, t: importer.action.ASIS), ] if task.is_album: choices += [ PromptChoice( "t", "as Tracks", lambda s, t: importer.action.TRACKS ), PromptChoice( "g", "Group albums", lambda s, t: importer.action.ALBUMS ), ] choices += [ PromptChoice("e", "Enter search", manual_search), PromptChoice("i", "enter Id", manual_id), PromptChoice("b", "aBort", abort_action), ] # Send the before_choose_candidate event and flatten list. extra_choices = list( chain( *plugins.send( "before_choose_candidate", session=self, task=task ) ) ) # Add a "dummy" choice for the other baked-in option, for # duplicate checking. all_choices = ( [ PromptChoice("a", "Apply", None), ] + choices + extra_choices ) # Check for conflicts. short_letters = [c.short for c in all_choices] if len(short_letters) != len(set(short_letters)): # Duplicate short letter has been found. duplicates = [ i for i, count in Counter(short_letters).items() if count > 1 ] for short in duplicates: # Keep the first of the choices, removing the rest. dup_choices = [c for c in all_choices if c.short == short] for c in dup_choices[1:]: log.warning( "Prompt choice '{0}' removed due to conflict " "with '{1}' (short letter: '{2}')", c.long, dup_choices[0].long, c.short, ) extra_choices.remove(c) return choices + extra_choices # The import command. def import_files(lib, paths, query): """Import the files in the given list of paths or matching the query. """ # Check parameter consistency. if config["import"]["quiet"] and config["import"]["timid"]: raise ui.UserError("can't be both quiet and timid") # Open the log. if config["import"]["log"].get() is not None: logpath = syspath(config["import"]["log"].as_filename()) try: loghandler = logging.FileHandler(logpath, encoding="utf-8") except OSError: raise ui.UserError( f"Could not open log file for writing: {displayable_path(logpath)}" ) else: loghandler = None # Never ask for input in quiet mode. if config["import"]["resume"].get() == "ask" and config["import"]["quiet"]: config["import"]["resume"] = False session = TerminalImportSession(lib, loghandler, paths, query) session.run() # Emit event. plugins.send("import", lib=lib, paths=paths) def import_func(lib, opts, args): config["import"].set_args(opts) # Special case: --copy flag suppresses import_move (which would # otherwise take precedence). if opts.copy: config["import"]["move"] = False if opts.library: query = decargs(args) paths = [] else: query = None paths = args # The paths from the logfiles go into a separate list to allow handling # errors differently from user-specified paths. paths_from_logfiles = list(_parse_logfiles(opts.from_logfiles or [])) if not paths and not paths_from_logfiles: raise ui.UserError("no path specified") # On Python 2, we used to get filenames as raw bytes, which is # what we need. On Python 3, we need to undo the "helpful" # conversion to Unicode strings to get the real bytestring # filename. paths = [ p.encode(util.arg_encoding(), "surrogateescape") for p in paths ] paths_from_logfiles = [ p.encode(util.arg_encoding(), "surrogateescape") for p in paths_from_logfiles ] # Check the user-specified directories. for path in paths: if not os.path.exists(syspath(normpath(path))): raise ui.UserError( "no such file or directory: {}".format( displayable_path(path) ) ) # Check the directories from the logfiles, but don't throw an error in # case those paths don't exist. Maybe some of those paths have already # been imported and moved separately, so logging a warning should # suffice. for path in paths_from_logfiles: if not os.path.exists(syspath(normpath(path))): log.warning( "No such file or directory: {}".format( displayable_path(path) ) ) continue paths.append(path) # If all paths were read from a logfile, and none of them exist, throw # an error if not paths: raise ui.UserError("none of the paths are importable") import_files(lib, paths, query) import_cmd = ui.Subcommand( "import", help="import new music", aliases=("imp", "im") ) import_cmd.parser.add_option( "-c", "--copy", action="store_true", default=None, help="copy tracks into library directory (default)", ) import_cmd.parser.add_option( "-C", "--nocopy", action="store_false", dest="copy", help="don't copy tracks (opposite of -c)", ) import_cmd.parser.add_option( "-m", "--move", action="store_true", dest="move", help="move tracks into the library (overrides -c)", ) import_cmd.parser.add_option( "-w", "--write", action="store_true", default=None, help="write new metadata to files' tags (default)", ) import_cmd.parser.add_option( "-W", "--nowrite", action="store_false", dest="write", help="don't write metadata (opposite of -w)", ) import_cmd.parser.add_option( "-a", "--autotag", action="store_true", dest="autotag", help="infer tags for imported files (default)", ) import_cmd.parser.add_option( "-A", "--noautotag", action="store_false", dest="autotag", help="don't infer tags for imported files (opposite of -a)", ) import_cmd.parser.add_option( "-p", "--resume", action="store_true", default=None, help="resume importing if interrupted", ) import_cmd.parser.add_option( "-P", "--noresume", action="store_false", dest="resume", help="do not try to resume importing", ) import_cmd.parser.add_option( "-q", "--quiet", action="store_true", dest="quiet", help="never prompt for input: skip albums instead", ) import_cmd.parser.add_option( "--quiet-fallback", type="string", dest="quiet_fallback", help="decision in quiet mode when no strong match: skip or asis", ) import_cmd.parser.add_option( "-l", "--log", dest="log", help="file to log untaggable albums for later review", ) import_cmd.parser.add_option( "-s", "--singletons", action="store_true", help="import individual tracks instead of full albums", ) import_cmd.parser.add_option( "-t", "--timid", dest="timid", action="store_true", help="always confirm all actions", ) import_cmd.parser.add_option( "-L", "--library", dest="library", action="store_true", help="retag items matching a query", ) import_cmd.parser.add_option( "-i", "--incremental", dest="incremental", action="store_true", help="skip already-imported directories", ) import_cmd.parser.add_option( "-I", "--noincremental", dest="incremental", action="store_false", help="do not skip already-imported directories", ) import_cmd.parser.add_option( "-R", "--incremental-skip-later", action="store_true", dest="incremental_skip_later", help="do not record skipped files during incremental import", ) import_cmd.parser.add_option( "-r", "--noincremental-skip-later", action="store_false", dest="incremental_skip_later", help="record skipped files during incremental import", ) import_cmd.parser.add_option( "--from-scratch", dest="from_scratch", action="store_true", help="erase existing metadata before applying new metadata", ) import_cmd.parser.add_option( "--flat", dest="flat", action="store_true", help="import an entire tree as a single album", ) import_cmd.parser.add_option( "-g", "--group-albums", dest="group_albums", action="store_true", help="group tracks in a folder into separate albums", ) import_cmd.parser.add_option( "--pretend", dest="pretend", action="store_true", help="just print the files to import", ) import_cmd.parser.add_option( "-S", "--search-id", dest="search_ids", action="append", metavar="ID", help="restrict matching to a specific metadata backend ID", ) import_cmd.parser.add_option( "--from-logfile", dest="from_logfiles", action="append", metavar="PATH", help="read skipped paths from an existing logfile", ) import_cmd.parser.add_option( "--set", dest="set_fields", action="callback", callback=_store_dict, metavar="FIELD=VALUE", help="set the given fields to the supplied values", ) import_cmd.func = import_func default_commands.append(import_cmd) # list: Query and show library contents. def list_items(lib, query, album, fmt=""): """Print out items in lib matching query. If album, then search for albums instead of single items. """ if album: for album in lib.albums(query): ui.print_(format(album, fmt)) else: for item in lib.items(query): ui.print_(format(item, fmt)) def list_func(lib, opts, args): list_items(lib, decargs(args), opts.album) list_cmd = ui.Subcommand("list", help="query the library", aliases=("ls",)) list_cmd.parser.usage += ( "\n" "Example: %prog -f '$album: $title' artist:beatles" ) list_cmd.parser.add_all_common_options() list_cmd.func = list_func default_commands.append(list_cmd) # update: Update library contents according to on-disk tags. def update_items(lib, query, album, move, pretend, fields, exclude_fields=None): """For all the items matched by the query, update the library to reflect the item's embedded tags. :param fields: The fields to be stored. If not specified, all fields will be. :param exclude_fields: The fields to not be stored. If not specified, all fields will be. """ with lib.transaction(): items, _ = _do_query(lib, query, album) if move and fields is not None and "path" not in fields: # Special case: if an item needs to be moved, the path field has to # updated; otherwise the new path will not be reflected in the # database. fields.append("path") if fields is None: # no fields were provided, update all media fields item_fields = fields or library.Item._media_fields if move and "path" not in item_fields: # move is enabled, add 'path' to the list of fields to update item_fields.add("path") else: # fields was provided, just update those item_fields = fields # get all the album fields to update album_fields = fields or library.Album._fields.keys() if exclude_fields: # remove any excluded fields from the item and album sets item_fields = [f for f in item_fields if f not in exclude_fields] album_fields = [f for f in album_fields if f not in exclude_fields] # Walk through the items and pick up their changes. affected_albums = set() for item in items: # Item deleted? if not item.path or not os.path.exists(syspath(item.path)): ui.print_(format(item)) ui.print_(ui.colorize("text_error", " deleted")) if not pretend: item.remove(True) affected_albums.add(item.album_id) continue # Did the item change since last checked? if item.current_mtime() <= item.mtime: log.debug( "skipping {0} because mtime is up to date ({1})", displayable_path(item.path), item.mtime, ) continue # Read new data. try: item.read() except library.ReadError as exc: log.error( "error reading {0}: {1}", displayable_path(item.path), exc ) continue # Special-case album artist when it matches track artist. (Hacky # but necessary for preserving album-level metadata for non- # autotagged imports.) if not item.albumartist: old_item = lib.get_item(item.id) if old_item.albumartist == old_item.artist == item.artist: item.albumartist = old_item.albumartist item._dirty.discard("albumartist") # Check for and display changes. changed = ui.show_model_changes(item, fields=item_fields) # Save changes. if not pretend: if changed: # Move the item if it's in the library. if move and lib.directory in ancestry(item.path): item.move(store=False) item.store(fields=item_fields) affected_albums.add(item.album_id) else: # The file's mtime was different, but there were no # changes to the metadata. Store the new mtime, # which is set in the call to read(), so we don't # check this again in the future. item.store(fields=item_fields) # Skip album changes while pretending. if pretend: return # Modify affected albums to reflect changes in their items. for album_id in affected_albums: if album_id is None: # Singletons. continue album = lib.get_album(album_id) if not album: # Empty albums have already been removed. log.debug("emptied album {0}", album_id) continue first_item = album.items().get() # Update album structure to reflect an item in it. for key in library.Album.item_keys: album[key] = first_item[key] album.store(fields=album_fields) # Move album art (and any inconsistent items). if move and lib.directory in ancestry(first_item.path): log.debug("moving album {0}", album_id) # Manually moving and storing the album. items = list(album.items()) for item in items: item.move(store=False, with_album=False) item.store(fields=item_fields) album.move(store=False) album.store(fields=album_fields) def update_func(lib, opts, args): # Verify that the library folder exists to prevent accidental wipes. if not os.path.isdir(syspath(lib.directory)): ui.print_("Library path is unavailable or does not exist.") ui.print_(lib.directory) if not ui.input_yn("Are you sure you want to continue (y/n)?", True): return update_items( lib, decargs(args), opts.album, ui.should_move(opts.move), opts.pretend, opts.fields, opts.exclude_fields, ) update_cmd = ui.Subcommand( "update", help="update the library", aliases=( "upd", "up", ), ) update_cmd.parser.add_album_option() update_cmd.parser.add_format_option() update_cmd.parser.add_option( "-m", "--move", action="store_true", dest="move", help="move files in the library directory", ) update_cmd.parser.add_option( "-M", "--nomove", action="store_false", dest="move", help="don't move files in library", ) update_cmd.parser.add_option( "-p", "--pretend", action="store_true", help="show all changes but do nothing", ) update_cmd.parser.add_option( "-F", "--field", default=None, action="append", dest="fields", help="list of fields to update", ) update_cmd.parser.add_option( "-e", "--exclude-field", default=None, action="append", dest="exclude_fields", help="list of fields to exclude from updates", ) update_cmd.func = update_func default_commands.append(update_cmd) # remove: Remove items from library, delete files. def remove_items(lib, query, album, delete, force): """Remove items matching query from lib. If album, then match and remove whole albums. If delete, also remove files from disk. """ # Get the matching items. items, albums = _do_query(lib, query, album) objs = albums if album else items # Confirm file removal if not forcing removal. if not force: # Prepare confirmation with user. album_str = ( " in {} album{}".format(len(albums), "s" if len(albums) > 1 else "") if album else "" ) if delete: fmt = "$path - $title" prompt = "Really DELETE" prompt_all = "Really DELETE {} file{}{}".format( len(items), "s" if len(items) > 1 else "", album_str ) else: fmt = "" prompt = "Really remove from the library?" prompt_all = "Really remove {} item{}{} from the library?".format( len(items), "s" if len(items) > 1 else "", album_str ) # Helpers for printing affected items def fmt_track(t): ui.print_(format(t, fmt)) def fmt_album(a): ui.print_() for i in a.items(): fmt_track(i) fmt_obj = fmt_album if album else fmt_track # Show all the items. for o in objs: fmt_obj(o) # Confirm with user. objs = ui.input_select_objects( prompt, objs, fmt_obj, prompt_all=prompt_all ) if not objs: return # Remove (and possibly delete) items. with lib.transaction(): for obj in objs: obj.remove(delete) def remove_func(lib, opts, args): remove_items(lib, decargs(args), opts.album, opts.delete, opts.force) remove_cmd = ui.Subcommand( "remove", help="remove matching items from the library", aliases=("rm",) ) remove_cmd.parser.add_option( "-d", "--delete", action="store_true", help="also remove files from disk" ) remove_cmd.parser.add_option( "-f", "--force", action="store_true", help="do not ask when removing items" ) remove_cmd.parser.add_album_option() remove_cmd.func = remove_func default_commands.append(remove_cmd) # stats: Show library/query statistics. def show_stats(lib, query, exact): """Shows some statistics about the matched items.""" items = lib.items(query) total_size = 0 total_time = 0.0 total_items = 0 artists = set() albums = set() album_artists = set() for item in items: if exact: try: total_size += os.path.getsize(syspath(item.path)) except OSError as exc: log.info("could not get size of {}: {}", item.path, exc) else: total_size += int(item.length * item.bitrate / 8) total_time += item.length total_items += 1 artists.add(item.artist) album_artists.add(item.albumartist) if item.album_id: albums.add(item.album_id) size_str = "" + ui.human_bytes(total_size) if exact: size_str += f" ({total_size} bytes)" print_( """Tracks: {} Total time: {}{} {}: {} Artists: {} Albums: {} Album artists: {}""".format( total_items, ui.human_seconds(total_time), f" ({total_time:.2f} seconds)" if exact else "", "Total size" if exact else "Approximate total size", size_str, len(artists), len(albums), len(album_artists), ), ) def stats_func(lib, opts, args): show_stats(lib, decargs(args), opts.exact) stats_cmd = ui.Subcommand( "stats", help="show statistics about the library or a query" ) stats_cmd.parser.add_option( "-e", "--exact", action="store_true", help="exact size and time" ) stats_cmd.func = stats_func default_commands.append(stats_cmd) # version: Show current beets version. def show_version(lib, opts, args): print_("beets version %s" % beets.__version__) print_(f"Python version {python_version()}") # Show plugins. names = sorted(p.name for p in plugins.find_plugins()) if names: print_("plugins:", ", ".join(names)) else: print_("no plugins loaded") version_cmd = ui.Subcommand("version", help="output version information") version_cmd.func = show_version default_commands.append(version_cmd) # modify: Declaratively change metadata. def modify_items(lib, mods, dels, query, write, move, album, confirm, inherit): """Modifies matching items according to user-specified assignments and deletions. `mods` is a dictionary of field and value pairse indicating assignments. `dels` is a list of fields to be deleted. """ # Parse key=value specifications into a dictionary. model_cls = library.Album if album else library.Item # Get the items to modify. items, albums = _do_query(lib, query, album, False) objs = albums if album else items # Apply changes *temporarily*, preview them, and collect modified # objects. print_("Modifying {} {}s.".format(len(objs), "album" if album else "item")) changed = [] templates = { key: functemplate.template(value) for key, value in mods.items() } for obj in objs: obj_mods = { key: model_cls._parse(key, obj.evaluate_template(templates[key])) for key in mods.keys() } if print_and_modify(obj, obj_mods, dels) and obj not in changed: changed.append(obj) # Still something to do? if not changed: print_("No changes to make.") return # Confirm action. if confirm: if write and move: extra = ", move and write tags" elif write: extra = " and write tags" elif move: extra = " and move" else: extra = "" changed = ui.input_select_objects( "Really modify%s" % extra, changed, lambda o: print_and_modify(o, mods, dels), ) # Apply changes to database and files with lib.transaction(): for obj in changed: obj.try_sync(write, move, inherit) def print_and_modify(obj, mods, dels): """Print the modifications to an item and return a bool indicating whether any changes were made. `mods` is a dictionary of fields and values to update on the object; `dels` is a sequence of fields to delete. """ obj.update(mods) for field in dels: try: del obj[field] except KeyError: pass return ui.show_model_changes(obj) def modify_parse_args(args): """Split the arguments for the modify subcommand into query parts, assignments (field=value), and deletions (field!). Returns the result as a three-tuple in that order. """ mods = {} dels = [] query = [] for arg in args: if arg.endswith("!") and "=" not in arg and ":" not in arg: dels.append(arg[:-1]) # Strip trailing !. elif "=" in arg and ":" not in arg.split("=", 1)[0]: key, val = arg.split("=", 1) mods[key] = val else: query.append(arg) return query, mods, dels def modify_func(lib, opts, args): query, mods, dels = modify_parse_args(decargs(args)) if not mods and not dels: raise ui.UserError("no modifications specified") modify_items( lib, mods, dels, query, ui.should_write(opts.write), ui.should_move(opts.move), opts.album, not opts.yes, opts.inherit, ) modify_cmd = ui.Subcommand( "modify", help="change metadata fields", aliases=("mod",) ) modify_cmd.parser.add_option( "-m", "--move", action="store_true", dest="move", help="move files in the library directory", ) modify_cmd.parser.add_option( "-M", "--nomove", action="store_false", dest="move", help="don't move files in library", ) modify_cmd.parser.add_option( "-w", "--write", action="store_true", default=None, help="write new metadata to files' tags (default)", ) modify_cmd.parser.add_option( "-W", "--nowrite", action="store_false", dest="write", help="don't write metadata (opposite of -w)", ) modify_cmd.parser.add_album_option() modify_cmd.parser.add_format_option(target="item") modify_cmd.parser.add_option( "-y", "--yes", action="store_true", help="skip confirmation" ) modify_cmd.parser.add_option( "-I", "--noinherit", action="store_false", dest="inherit", default=True, help="when modifying albums, don't also change item data", ) modify_cmd.func = modify_func default_commands.append(modify_cmd) # move: Move/copy files to the library or a new base directory. def move_items( lib, dest, query, copy, album, pretend, confirm=False, export=False ): """Moves or copies items to a new base directory, given by dest. If dest is None, then the library's base directory is used, making the command "consolidate" files. """ items, albums = _do_query(lib, query, album, False) objs = albums if album else items num_objs = len(objs) # Filter out files that don't need to be moved. def isitemmoved(item): return item.path != item.destination(basedir=dest) def isalbummoved(album): return any(isitemmoved(i) for i in album.items()) objs = [o for o in objs if (isalbummoved if album else isitemmoved)(o)] num_unmoved = num_objs - len(objs) # Report unmoved files that match the query. unmoved_msg = "" if num_unmoved > 0: unmoved_msg = f" ({num_unmoved} already in place)" copy = copy or export # Exporting always copies. action = "Copying" if copy else "Moving" act = "copy" if copy else "move" entity = "album" if album else "item" log.info( "{0} {1} {2}{3}{4}.", action, len(objs), entity, "s" if len(objs) != 1 else "", unmoved_msg, ) if not objs: return if pretend: if album: show_path_changes( [ (item.path, item.destination(basedir=dest)) for obj in objs for item in obj.items() ] ) else: show_path_changes( [(obj.path, obj.destination(basedir=dest)) for obj in objs] ) else: if confirm: objs = ui.input_select_objects( "Really %s" % act, objs, lambda o: show_path_changes( [(o.path, o.destination(basedir=dest))] ), ) for obj in objs: log.debug("moving: {0}", util.displayable_path(obj.path)) if export: # Copy without affecting the database. obj.move( operation=MoveOperation.COPY, basedir=dest, store=False ) else: # Ordinary move/copy: store the new path. if copy: obj.move(operation=MoveOperation.COPY, basedir=dest) else: obj.move(operation=MoveOperation.MOVE, basedir=dest) def move_func(lib, opts, args): dest = opts.dest if dest is not None: dest = normpath(dest) if not os.path.isdir(syspath(dest)): raise ui.UserError( "no such directory: {}".format(displayable_path(dest)) ) move_items( lib, dest, decargs(args), opts.copy, opts.album, opts.pretend, opts.timid, opts.export, ) move_cmd = ui.Subcommand("move", help="move or copy items", aliases=("mv",)) move_cmd.parser.add_option( "-d", "--dest", metavar="DIR", dest="dest", help="destination directory" ) move_cmd.parser.add_option( "-c", "--copy", default=False, action="store_true", help="copy instead of moving", ) move_cmd.parser.add_option( "-p", "--pretend", default=False, action="store_true", help="show how files would be moved, but don't touch anything", ) move_cmd.parser.add_option( "-t", "--timid", dest="timid", action="store_true", help="always confirm all actions", ) move_cmd.parser.add_option( "-e", "--export", default=False, action="store_true", help="copy without changing the database path", ) move_cmd.parser.add_album_option() move_cmd.func = move_func default_commands.append(move_cmd) # write: Write tags into files. def write_items(lib, query, pretend, force): """Write tag information from the database to the respective files in the filesystem. """ items, albums = _do_query(lib, query, False, False) for item in items: # Item deleted? if not os.path.exists(syspath(item.path)): log.info("missing file: {0}", util.displayable_path(item.path)) continue # Get an Item object reflecting the "clean" (on-disk) state. try: clean_item = library.Item.from_path(item.path) except library.ReadError as exc: log.error( "error reading {0}: {1}", displayable_path(item.path), exc ) continue # Check for and display changes. changed = ui.show_model_changes( item, clean_item, library.Item._media_tag_fields, force ) if (changed or force) and not pretend: # We use `try_sync` here to keep the mtime up to date in the # database. item.try_sync(True, False) def write_func(lib, opts, args): write_items(lib, decargs(args), opts.pretend, opts.force) write_cmd = ui.Subcommand("write", help="write tag information to files") write_cmd.parser.add_option( "-p", "--pretend", action="store_true", help="show all changes but do nothing", ) write_cmd.parser.add_option( "-f", "--force", action="store_true", help="write tags even if the existing tags match the database", ) write_cmd.func = write_func default_commands.append(write_cmd) # config: Show and edit user configuration. def config_func(lib, opts, args): # Make sure lazy configuration is loaded config.resolve() # Print paths. if opts.paths: filenames = [] for source in config.sources: if not opts.defaults and source.default: continue if source.filename: filenames.append(source.filename) # In case the user config file does not exist, prepend it to the # list. user_path = config.user_config_path() if user_path not in filenames: filenames.insert(0, user_path) for filename in filenames: print_(displayable_path(filename)) # Open in editor. elif opts.edit: config_edit() # Dump configuration. else: config_out = config.dump(full=opts.defaults, redact=opts.redact) if config_out.strip() != "{}": print_(config_out) else: print("Empty configuration") def config_edit(): """Open a program to edit the user configuration. An empty config file is created if no existing config file exists. """ path = config.user_config_path() editor = util.editor_command() try: if not os.path.isfile(path): open(path, "w+").close() util.interactive_open([path], editor) except OSError as exc: message = f"Could not edit configuration: {exc}" if not editor: message += ( ". Please set the VISUAL (or EDITOR) environment variable" ) raise ui.UserError(message) config_cmd = ui.Subcommand("config", help="show or edit the user configuration") config_cmd.parser.add_option( "-p", "--paths", action="store_true", help="show files that configuration was loaded from", ) config_cmd.parser.add_option( "-e", "--edit", action="store_true", help="edit user configuration with $VISUAL (or $EDITOR)", ) config_cmd.parser.add_option( "-d", "--defaults", action="store_true", help="include the default configuration", ) config_cmd.parser.add_option( "-c", "--clear", action="store_false", dest="redact", default=True, help="do not redact sensitive fields", ) config_cmd.func = config_func default_commands.append(config_cmd) # completion: print completion script def print_completion(*args): for line in completion_script(default_commands + plugins.commands()): print_(line, end="") if not any(os.path.isfile(syspath(p)) for p in BASH_COMPLETION_PATHS): log.warning( "Warning: Unable to find the bash-completion package. " "Command line completion might not work." ) BASH_COMPLETION_PATHS = [ b"/etc/bash_completion", b"/usr/share/bash-completion/bash_completion", b"/usr/local/share/bash-completion/bash_completion", # SmartOS b"/opt/local/share/bash-completion/bash_completion", # Homebrew (before bash-completion2) b"/usr/local/etc/bash_completion", ] def completion_script(commands): """Yield the full completion shell script as strings. ``commands`` is alist of ``ui.Subcommand`` instances to generate completion data for. """ base_script = os.path.join(os.path.dirname(__file__), "completion_base.sh") with open(base_script) as base_script: yield base_script.read() options = {} aliases = {} command_names = [] # Collect subcommands for cmd in commands: name = cmd.name command_names.append(name) for alias in cmd.aliases: if re.match(r"^\w+$", alias): aliases[alias] = name options[name] = {"flags": [], "opts": []} for opts in cmd.parser._get_all_options()[1:]: if opts.action in ("store_true", "store_false"): option_type = "flags" else: option_type = "opts" options[name][option_type].extend( opts._short_opts + opts._long_opts ) # Add global options options["_global"] = { "flags": ["-v", "--verbose"], "opts": "-l --library -c --config -d --directory -h --help".split(" "), } # Add flags common to all commands options["_common"] = {"flags": ["-h", "--help"]} # Start generating the script yield "_beet() {\n" # Command names yield " local commands='%s'\n" % " ".join(command_names) yield "\n" # Command aliases yield " local aliases='%s'\n" % " ".join(aliases.keys()) for alias, cmd in aliases.items(): yield " local alias__{}={}\n".format(alias.replace("-", "_"), cmd) yield "\n" # Fields yield " fields='%s'\n" % " ".join( set( list(library.Item._fields.keys()) + list(library.Album._fields.keys()) ) ) # Command options for cmd, opts in options.items(): for option_type, option_list in opts.items(): if option_list: option_list = " ".join(option_list) yield " local {}__{}='{}'\n".format( option_type, cmd.replace("-", "_"), option_list ) yield " _beet_dispatch\n" yield "}\n" completion_cmd = ui.Subcommand( "completion", help="print shell script that provides command line completion", ) completion_cmd.func = print_completion completion_cmd.hide = True default_commands.append(completion_cmd) beetbox-beets-01f1faf/beets/ui/completion_base.sh000066400000000000000000000122571472325477400221740ustar00rootroot00000000000000# This file is part of beets. # Copyright (c) 2014, Thomas Scholtes. # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the # "Software"), to deal in the Software without restriction, including # without limitation the rights to use, copy, modify, merge, publish, # distribute, sublicense, and/or sell copies of the Software, and to # permit persons to whom the Software is furnished to do so, subject to # the following conditions: # # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. # Completion for the `beet` command # ================================= # # Load this script to complete beets subcommands, options, and # queries. # # If a beets command is found on the command line it completes filenames and # the subcommand's options. Otherwise it will complete global options and # subcommands. If the previous option on the command line expects an argument, # it also completes filenames or directories. Options are only # completed if '-' has already been typed on the command line. # # Note that completion of plugin commands only works for those plugins # that were enabled when running `beet completion`. It does not check # plugins dynamically # # Currently, only Bash 3.2 and newer is supported and the # `bash-completion` package (v2.8 or newer) is required. # # TODO # ---- # # * There are some issues with arguments that are quoted on the command line. # # * Complete arguments for the `--format` option by expanding field variables. # # beet ls -f "$tit[TAB] # beet ls -f "$title # # * Support long options with `=`, e.g. `--config=file`. Debian's bash # completion package can handle this. # # Note that 'bash-completion' v2.8 is a part of Debian 10, which is part of # LTS until 2024-06-30. After this date, the minimum version requirement can # be changed, and newer features can be used unconditionally. See PR#5301. # if [[ ${BASH_COMPLETION_VERSINFO[0]} -ne 2 \ || ${BASH_COMPLETION_VERSINFO[1]} -lt 8 ]]; then echo "Incompatible version of 'bash-completion'!" return 1 fi # The later code relies on 'bash-completion' version 2.12, but older versions # are still supported. Here, we provide implementations of the newer functions # in terms of older ones, if 'bash-completion' is too old to have them. if [[ ${BASH_COMPLETION_VERSINFO[1]} -lt 12 ]]; then _comp_get_words() { _get_comp_words_by_ref "$@" } _comp_compgen_filedir() { _filedir "$@" } fi # Determines the beets subcommand and dispatches the completion # accordingly. _beet_dispatch() { local cur prev cmd= COMPREPLY=() _comp_get_words -n : cur prev # Look for the beets subcommand local arg for (( i=1; i < COMP_CWORD; i++ )); do arg="${COMP_WORDS[i]}" if _list_include_item "${opts___global}" $arg; then ((i++)) elif [[ "$arg" != -* ]]; then cmd="$arg" break fi done # Replace command shortcuts if [[ -n $cmd ]] && _list_include_item "$aliases" "$cmd"; then eval "cmd=\$alias__${cmd//-/_}" fi case $cmd in help) COMPREPLY+=( $(compgen -W "$commands" -- $cur) ) ;; list|remove|move|update|write|stats) _beet_complete_query ;; "") _beet_complete_global ;; *) _beet_complete ;; esac } # Adds option and file completion to COMPREPLY for the subcommand $cmd _beet_complete() { if [[ $cur == -* ]]; then local opts flags completions eval "opts=\$opts__${cmd//-/_}" eval "flags=\$flags__${cmd//-/_}" completions="${flags___common} ${opts} ${flags}" COMPREPLY+=( $(compgen -W "$completions" -- $cur) ) else _comp_compgen_filedir fi } # Add global options and subcommands to the completion _beet_complete_global() { case $prev in -h|--help) # Complete commands COMPREPLY+=( $(compgen -W "$commands" -- $cur) ) return ;; -l|--library|-c|--config) # Filename completion _comp_compgen_filedir return ;; -d|--directory) # Directory completion _comp_compgen_filedir -d return ;; esac if [[ $cur == -* ]]; then local completions="$opts___global $flags___global" COMPREPLY+=( $(compgen -W "$completions" -- $cur) ) elif [[ -n $cur ]] && _list_include_item "$aliases" "$cur"; then local cmd eval "cmd=\$alias__${cur//-/_}" COMPREPLY+=( "$cmd" ) else COMPREPLY+=( $(compgen -W "$commands" -- $cur) ) fi } _beet_complete_query() { local opts eval "opts=\$opts__${cmd//-/_}" if [[ $cur == -* ]] || _list_include_item "$opts" "$prev"; then _beet_complete elif [[ $cur != \'* && $cur != \"* && $cur != *:* ]]; then # Do not complete quoted queries or those who already have a field # set. compopt -o nospace COMPREPLY+=( $(compgen -S : -W "$fields" -- $cur) ) return 0 fi } # Returns true if the space separated list $1 includes $2 _list_include_item() { [[ " $1 " == *[[:space:]]$2[[:space:]]* ]] } # This is where beets dynamically adds the _beet function. This # function sets the variables $flags, $opts, $commands, and $aliases. complete -o filenames -F _beet beet beetbox-beets-01f1faf/beets/util/000077500000000000000000000000001472325477400170265ustar00rootroot00000000000000beetbox-beets-01f1faf/beets/util/__init__.py000066400000000000000000001077211472325477400211470ustar00rootroot00000000000000# This file is part of beets. # Copyright 2016, Adrian Sampson. # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the # "Software"), to deal in the Software without restriction, including # without limitation the rights to use, copy, modify, merge, publish, # distribute, sublicense, and/or sell copies of the Software, and to # permit persons to whom the Software is furnished to do so, subject to # the following conditions: # # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. """Miscellaneous utility functions.""" from __future__ import annotations import errno import fnmatch import os import platform import re import shlex import shutil import subprocess import sys import tempfile import traceback from collections import Counter from contextlib import suppress from enum import Enum from importlib import import_module from multiprocessing.pool import ThreadPool from pathlib import Path from typing import ( TYPE_CHECKING, Any, AnyStr, Callable, Iterator, NamedTuple, Pattern, Sequence, TypeVar, Union, ) from unidecode import unidecode from beets.util import hidden if TYPE_CHECKING: from logging import Logger if sys.version_info >= (3, 10): from typing import TypeAlias else: from typing_extensions import TypeAlias MAX_FILENAME_LENGTH = 200 WINDOWS_MAGIC_PREFIX = "\\\\?\\" T = TypeVar("T") BytesOrStr = Union[str, bytes] PathLike = Union[BytesOrStr, Path] Replacements: TypeAlias = "Sequence[tuple[Pattern[str], str]]" class HumanReadableError(Exception): """An Exception that can include a human-readable error message to be logged without a traceback. Can preserve a traceback for debugging purposes as well. Has at least two fields: `reason`, the underlying exception or a string describing the problem; and `verb`, the action being performed during the error. If `tb` is provided, it is a string containing a traceback for the associated exception. (Note that this is not necessary in Python 3.x and should be removed when we make the transition.) """ error_kind = "Error" # Human-readable description of error type. def __init__(self, reason, verb, tb=None): self.reason = reason self.verb = verb self.tb = tb super().__init__(self.get_message()) def _gerund(self): """Generate a (likely) gerund form of the English verb.""" if " " in self.verb: return self.verb gerund = self.verb[:-1] if self.verb.endswith("e") else self.verb gerund += "ing" return gerund def _reasonstr(self): """Get the reason as a string.""" if isinstance(self.reason, str): return self.reason elif isinstance(self.reason, bytes): return self.reason.decode("utf-8", "ignore") elif hasattr(self.reason, "strerror"): # i.e., EnvironmentError return self.reason.strerror else: return '"{}"'.format(str(self.reason)) def get_message(self): """Create the human-readable description of the error, sans introduction. """ raise NotImplementedError def log(self, logger): """Log to the provided `logger` a human-readable message as an error and a verbose traceback as a debug message. """ if self.tb: logger.debug(self.tb) logger.error("{0}: {1}", self.error_kind, self.args[0]) class FilesystemError(HumanReadableError): """An error that occurred while performing a filesystem manipulation via a function in this module. The `paths` field is a sequence of pathnames involved in the operation. """ def __init__(self, reason, verb, paths, tb=None): self.paths = paths super().__init__(reason, verb, tb) def get_message(self): # Use a nicer English phrasing for some specific verbs. if self.verb in ("move", "copy", "rename"): clause = "while {} {} to {}".format( self._gerund(), displayable_path(self.paths[0]), displayable_path(self.paths[1]), ) elif self.verb in ("delete", "write", "create", "read"): clause = "while {} {}".format( self._gerund(), displayable_path(self.paths[0]) ) else: clause = "during {} of paths {}".format( self.verb, ", ".join(displayable_path(p) for p in self.paths) ) return f"{self._reasonstr()} {clause}" class MoveOperation(Enum): """The file operations that e.g. various move functions can carry out.""" MOVE = 0 COPY = 1 LINK = 2 HARDLINK = 3 REFLINK = 4 REFLINK_AUTO = 5 def normpath(path: bytes) -> bytes: """Provide the canonical form of the path suitable for storing in the database. """ str_path = syspath(path, prefix=False) str_path = os.path.normpath(os.path.abspath(os.path.expanduser(str_path))) return bytestring_path(str_path) def ancestry(path: AnyStr) -> list[AnyStr]: """Return a list consisting of path's parent directory, its grandparent, and so on. For instance: >>> ancestry(b'/a/b/c') ['/', '/a', '/a/b'] The argument should *not* be the result of a call to `syspath`. """ out: list[AnyStr] = [] last_path = None while path: path = os.path.dirname(path) if path == last_path: break last_path = path if path: # don't yield '' out.insert(0, path) return out def sorted_walk( path: AnyStr, ignore: Sequence[bytes] = (), ignore_hidden: bool = False, logger: Logger | None = None, ) -> Iterator[tuple[bytes, Sequence[bytes], Sequence[bytes]]]: """Like `os.walk`, but yields things in case-insensitive sorted, breadth-first order. Directory and file names matching any glob pattern in `ignore` are skipped. If `logger` is provided, then warning messages are logged there when a directory cannot be listed. """ # Make sure the paths aren't Unicode strings. bytes_path = bytestring_path(path) ignore = [bytestring_path(i) for i in ignore] # Get all the directories and files at this level. try: contents = os.listdir(syspath(bytes_path)) except OSError as exc: if logger: logger.warning( "could not list directory {}: {}".format( displayable_path(bytes_path), exc.strerror ) ) return dirs = [] files = [] for str_base in contents: base = bytestring_path(str_base) # Skip ignored filenames. skip = False for pat in ignore: if fnmatch.fnmatch(base, pat): if logger: logger.debug( "ignoring '{}' due to ignore rule '{}'", base, pat ) skip = True break if skip: continue # Add to output as either a file or a directory. cur = os.path.join(bytes_path, base) if (ignore_hidden and not hidden.is_hidden(cur)) or not ignore_hidden: if os.path.isdir(syspath(cur)): dirs.append(base) else: files.append(base) # Sort lists (case-insensitive) and yield the current level. dirs.sort(key=bytes.lower) files.sort(key=bytes.lower) yield (bytes_path, dirs, files) # Recurse into directories. for base in dirs: cur = os.path.join(bytes_path, base) yield from sorted_walk(cur, ignore, ignore_hidden, logger) def path_as_posix(path: bytes) -> bytes: """Return the string representation of the path with forward (/) slashes. """ return path.replace(b"\\", b"/") def mkdirall(path: bytes): """Make all the enclosing directories of path (like mkdir -p on the parent). """ for ancestor in ancestry(path): if not os.path.isdir(syspath(ancestor)): try: os.mkdir(syspath(ancestor)) except OSError as exc: raise FilesystemError( exc, "create", (ancestor,), traceback.format_exc() ) def fnmatch_all(names: Sequence[bytes], patterns: Sequence[bytes]) -> bool: """Determine whether all strings in `names` match at least one of the `patterns`, which should be shell glob expressions. """ for name in names: matches = False for pattern in patterns: matches = fnmatch.fnmatch(name, pattern) if matches: break if not matches: return False return True def prune_dirs( path: bytes, root: bytes | None = None, clutter: Sequence[str] = (".DS_Store", "Thumbs.db"), ): """If path is an empty directory, then remove it. Recursively remove path's ancestry up to root (which is never removed) where there are empty directories. If path is not contained in root, then nothing is removed. Glob patterns in clutter are ignored when determining emptiness. If root is not provided, then only path may be removed (i.e., no recursive removal). """ path = normpath(path) root = normpath(root) if root else None ancestors = ancestry(path) if root is None: # Only remove the top directory. ancestors = [] elif root in ancestors: # Only remove directories below the root_bytes. ancestors = ancestors[ancestors.index(root) + 1 :] else: # Remove nothing. return bytes_clutter = [bytestring_path(c) for c in clutter] # Traverse upward from path. ancestors.append(path) ancestors.reverse() for directory in ancestors: str_directory = syspath(directory) if not os.path.exists(directory): # Directory gone already. continue match_paths = [bytestring_path(d) for d in os.listdir(str_directory)] try: if fnmatch_all(match_paths, bytes_clutter): # Directory contains only clutter (or nothing). shutil.rmtree(str_directory) else: break except OSError: break def components(path: AnyStr) -> list[AnyStr]: """Return a list of the path components in path. For instance: >>> components(b'/a/b/c') ['a', 'b', 'c'] The argument should *not* be the result of a call to `syspath`. """ comps = [] ances = ancestry(path) for anc in ances: comp = os.path.basename(anc) if comp: comps.append(comp) else: # root comps.append(anc) last = os.path.basename(path) if last: comps.append(last) return comps def arg_encoding() -> str: """Get the encoding for command-line arguments (and other OS locale-sensitive strings). """ return sys.getfilesystemencoding() def _fsencoding() -> str: """Get the system's filesystem encoding. On Windows, this is always UTF-8 (not MBCS). """ encoding = sys.getfilesystemencoding() or sys.getdefaultencoding() if encoding == "mbcs": # On Windows, a broken encoding known to Python as "MBCS" is # used for the filesystem. However, we only use the Unicode API # for Windows paths, so the encoding is actually immaterial so # we can avoid dealing with this nastiness. We arbitrarily # choose UTF-8. encoding = "utf-8" return encoding def bytestring_path(path: PathLike) -> bytes: """Given a path, which is either a bytes or a unicode, returns a str path (ensuring that we never deal with Unicode pathnames). Path should be bytes but has safeguards for strings to be converted. """ # Pass through bytestrings. if isinstance(path, bytes): return path str_path = str(path) # On Windows, remove the magic prefix added by `syspath`. This makes # ``bytestring_path(syspath(X)) == X``, i.e., we can safely # round-trip through `syspath`. if os.path.__name__ == "ntpath" and str_path.startswith( WINDOWS_MAGIC_PREFIX ): str_path = str_path[len(WINDOWS_MAGIC_PREFIX) :] # Try to encode with default encodings, but fall back to utf-8. try: return str_path.encode(_fsencoding()) except (UnicodeError, LookupError): return str_path.encode("utf-8") PATH_SEP: bytes = bytestring_path(os.sep) def displayable_path( path: BytesOrStr | tuple[BytesOrStr, ...], separator: str = "; " ) -> str: """Attempts to decode a bytestring path to a unicode object for the purpose of displaying it to the user. If the `path` argument is a list or a tuple, the elements are joined with `separator`. """ if isinstance(path, (list, tuple)): return separator.join(displayable_path(p) for p in path) elif isinstance(path, str): return path elif not isinstance(path, bytes): # A non-string object: just get its unicode representation. return str(path) try: return path.decode(_fsencoding(), "ignore") except (UnicodeError, LookupError): return path.decode("utf-8", "ignore") def syspath(path: PathLike, prefix: bool = True) -> str: """Convert a path for use by the operating system. In particular, paths on Windows must receive a magic prefix and must be converted to Unicode before they are sent to the OS. To disable the magic prefix on Windows, set `prefix` to False---but only do this if you *really* know what you're doing. """ str_path = os.fsdecode(path) # Don't do anything if we're not on windows if os.path.__name__ != "ntpath": return str_path # Add the magic prefix if it isn't already there. # https://msdn.microsoft.com/en-us/library/windows/desktop/aa365247.aspx if prefix and not str_path.startswith(WINDOWS_MAGIC_PREFIX): if str_path.startswith("\\\\"): # UNC path. Final path should look like \\?\UNC\... str_path = "UNC" + str_path[1:] str_path = WINDOWS_MAGIC_PREFIX + str_path return str_path def samefile(p1: bytes, p2: bytes) -> bool: """Safer equality for paths.""" if p1 == p2: return True with suppress(OSError): return os.path.samefile(syspath(p1), syspath(p2)) return False def remove(path: bytes, soft: bool = True): """Remove the file. If `soft`, then no error will be raised if the file does not exist. """ str_path = syspath(path) if not str_path or (soft and not os.path.exists(str_path)): return try: os.remove(str_path) except OSError as exc: raise FilesystemError( exc, "delete", (str_path,), traceback.format_exc() ) def copy(path: bytes, dest: bytes, replace: bool = False): """Copy a plain file. Permissions are not copied. If `dest` already exists, raises a FilesystemError unless `replace` is True. Has no effect if `path` is the same as `dest`. Paths are translated to system paths before the syscall. """ if samefile(path, dest): return str_path = syspath(path) str_dest = syspath(dest) if not replace and os.path.exists(str_dest): raise FilesystemError("file exists", "copy", (str_path, str_dest)) try: shutil.copyfile(str_path, str_dest) except OSError as exc: raise FilesystemError( exc, "copy", (str_path, str_dest), traceback.format_exc() ) def move(path: bytes, dest: bytes, replace: bool = False): """Rename a file. `dest` may not be a directory. If `dest` already exists, raises an OSError unless `replace` is True. Has no effect if `path` is the same as `dest`. If the paths are on different filesystems (or the rename otherwise fails), a copy is attempted instead, in which case metadata will *not* be preserved. Paths are translated to system paths. """ if os.path.isdir(syspath(path)): raise FilesystemError("source is directory", "move", (path, dest)) if os.path.isdir(syspath(dest)): raise FilesystemError("destination is directory", "move", (path, dest)) if samefile(path, dest): return if os.path.exists(syspath(dest)) and not replace: raise FilesystemError("file exists", "rename", (path, dest)) # First, try renaming the file. try: os.replace(syspath(path), syspath(dest)) except OSError: # Copy the file to a temporary destination. basename = os.path.basename(bytestring_path(dest)) dirname = os.path.dirname(bytestring_path(dest)) tmp = tempfile.NamedTemporaryFile( suffix=syspath(b".beets", prefix=False), prefix=syspath(b"." + basename + b".", prefix=False), dir=syspath(dirname), delete=False, ) try: with open(syspath(path), "rb") as f: # mypy bug: # - https://github.com/python/mypy/issues/15031 # - https://github.com/python/mypy/issues/14943 # Fix not yet released: # - https://github.com/python/mypy/pull/14975 shutil.copyfileobj(f, tmp) # type: ignore[misc] finally: tmp.close() # Move the copied file into place. tmp_filename = tmp.name try: os.replace(tmp_filename, syspath(dest)) tmp_filename = "" os.remove(syspath(path)) except OSError as exc: raise FilesystemError( exc, "move", (path, dest), traceback.format_exc() ) finally: if tmp_filename: os.remove(tmp_filename) def link(path: bytes, dest: bytes, replace: bool = False): """Create a symbolic link from path to `dest`. Raises an OSError if `dest` already exists, unless `replace` is True. Does nothing if `path` == `dest`. """ if samefile(path, dest): return if os.path.exists(syspath(dest)) and not replace: raise FilesystemError("file exists", "rename", (path, dest)) try: os.symlink(syspath(path), syspath(dest)) except NotImplementedError: # raised on python >= 3.2 and Windows versions before Vista raise FilesystemError( "OS does not support symbolic links." "link", (path, dest), traceback.format_exc(), ) except OSError as exc: raise FilesystemError(exc, "link", (path, dest), traceback.format_exc()) def hardlink(path: bytes, dest: bytes, replace: bool = False): """Create a hard link from path to `dest`. Raises an OSError if `dest` already exists, unless `replace` is True. Does nothing if `path` == `dest`. """ if samefile(path, dest): return if os.path.exists(syspath(dest)) and not replace: raise FilesystemError("file exists", "rename", (path, dest)) try: os.link(syspath(path), syspath(dest)) except NotImplementedError: raise FilesystemError( "OS does not support hard links." "link", (path, dest), traceback.format_exc(), ) except OSError as exc: if exc.errno == errno.EXDEV: raise FilesystemError( "Cannot hard link across devices." "link", (path, dest), traceback.format_exc(), ) else: raise FilesystemError( exc, "link", (path, dest), traceback.format_exc() ) def reflink( path: bytes, dest: bytes, replace: bool = False, fallback: bool = False, ): """Create a reflink from `dest` to `path`. Raise an `OSError` if `dest` already exists, unless `replace` is True. If `path` == `dest`, then do nothing. If `fallback` is enabled, ignore errors and copy the file instead. Otherwise, errors are re-raised as FilesystemError with an explanation. """ if samefile(path, dest): return if os.path.exists(syspath(dest)) and not replace: raise FilesystemError("target exists", "rename", (path, dest)) if fallback: with suppress(Exception): return import_module("reflink").reflink(path, dest) return copy(path, dest, replace) try: import_module("reflink").reflink(path, dest) except (ImportError, OSError): raise except Exception as exc: msg = { "EXDEV": "Cannot reflink across devices", "EOPNOTSUPP": "Device does not support reflinks", }.get(str(exc), "OS does not support reflinks") raise FilesystemError( msg, "reflink", (path, dest), traceback.format_exc() ) from exc def unique_path(path: bytes) -> bytes: """Returns a version of ``path`` that does not exist on the filesystem. Specifically, if ``path` itself already exists, then something unique is appended to the path. """ if not os.path.exists(syspath(path)): return path base, ext = os.path.splitext(path) match = re.search(rb"\.(\d)+$", base) if match: num = int(match.group(1)) base = base[: match.start()] else: num = 0 while True: num += 1 suffix = f".{num}".encode() + ext new_path = base + suffix if not os.path.exists(new_path): return new_path # Note: The Windows "reserved characters" are, of course, allowed on # Unix. They are forbidden here because they cause problems on Samba # shares, which are sufficiently common as to cause frequent problems. # https://msdn.microsoft.com/en-us/library/windows/desktop/aa365247.aspx CHAR_REPLACE = [ (re.compile(r"[\\/]"), "_"), # / and \ -- forbidden everywhere. (re.compile(r"^\."), "_"), # Leading dot (hidden files on Unix). (re.compile(r"[\x00-\x1f]"), ""), # Control characters. (re.compile(r'[<>:"\?\*\|]'), "_"), # Windows "reserved characters". (re.compile(r"\.$"), "_"), # Trailing dots. (re.compile(r"\s+$"), ""), # Trailing whitespace. ] def sanitize_path(path: str, replacements: Replacements | None = None) -> str: """Takes a path (as a Unicode string) and makes sure that it is legal. Returns a new path. Only works with fragments; won't work reliably on Windows when a path begins with a drive letter. Path separators (including altsep!) should already be cleaned from the path components. If replacements is specified, it is used *instead* of the default set of replacements; it must be a list of (compiled regex, replacement string) pairs. """ replacements = replacements or CHAR_REPLACE comps = components(path) if not comps: return "" for i, comp in enumerate(comps): for regex, repl in replacements: comp = regex.sub(repl, comp) comps[i] = comp return os.path.join(*comps) def truncate_path(path: AnyStr, length: int = MAX_FILENAME_LENGTH) -> AnyStr: """Given a bytestring path or a Unicode path fragment, truncate the components to a legal length. In the last component, the extension is preserved. """ comps = components(path) out = [c[:length] for c in comps] base, ext = os.path.splitext(comps[-1]) if ext: # Last component has an extension. base = base[: length - len(ext)] out[-1] = base + ext return os.path.join(*out) def _legalize_stage( path: str, replacements: Replacements | None, length: int, extension: str, fragment: bool, ) -> tuple[BytesOrStr, bool]: """Perform a single round of path legalization steps (sanitation/replacement, encoding from Unicode to bytes, extension-appending, and truncation). Return the path (Unicode if `fragment` is set, `bytes` otherwise) and whether truncation was required. """ # Perform an initial sanitization including user replacements. path = sanitize_path(path, replacements) # Encode for the filesystem. if not fragment: path = bytestring_path(path) # type: ignore # Preserve extension. path += extension.lower() # Truncate too-long components. pre_truncate_path = path path = truncate_path(path, length) return path, path != pre_truncate_path def legalize_path( path: str, replacements: Replacements | None, length: int, extension: bytes, fragment: bool, ) -> tuple[BytesOrStr, bool]: """Given a path-like Unicode string, produce a legal path. Return the path and a flag indicating whether some replacements had to be ignored (see below). The legalization process (see `_legalize_stage`) consists of applying the sanitation rules in `replacements`, encoding the string to bytes (unless `fragment` is set), truncating components to `length`, appending the `extension`. This function performs up to three calls to `_legalize_stage` in case truncation conflicts with replacements (as can happen when truncation creates whitespace at the end of the string, for example). The limited number of iterations iterations avoids the possibility of an infinite loop of sanitation and truncation operations, which could be caused by replacement rules that make the string longer. The flag returned from this function indicates that the path has to be truncated twice (indicating that replacements made the string longer again after it was truncated); the application should probably log some sort of warning. """ if fragment: # Outputting Unicode. extension = extension.decode("utf-8", "ignore") first_stage_path, _ = _legalize_stage( path, replacements, length, extension, fragment ) # Convert back to Unicode with extension removed. first_stage_path, _ = os.path.splitext(displayable_path(first_stage_path)) # Re-sanitize following truncation (including user replacements). second_stage_path, retruncated = _legalize_stage( first_stage_path, replacements, length, extension, fragment ) # If the path was once again truncated, discard user replacements # and run through one last legalization stage. if retruncated: second_stage_path, _ = _legalize_stage( first_stage_path, None, length, extension, fragment ) return second_stage_path, retruncated def str2bool(value: str) -> bool: """Returns a boolean reflecting a human-entered string.""" return value.lower() in ("yes", "1", "true", "t", "y") def as_string(value: Any) -> str: """Convert a value to a Unicode object for matching with a query. None becomes the empty string. Bytestrings are silently decoded. """ if value is None: return "" elif isinstance(value, memoryview): return bytes(value).decode("utf-8", "ignore") elif isinstance(value, bytes): return value.decode("utf-8", "ignore") else: return str(value) def plurality(objs: Sequence[T]) -> tuple[T, int]: """Given a sequence of hashble objects, returns the object that is most common in the set and the its number of appearance. The sequence must contain at least one object. """ c = Counter(objs) if not c: raise ValueError("sequence must be non-empty") return c.most_common(1)[0] def convert_command_args(args: list[BytesOrStr]) -> list[str]: """Convert command arguments, which may either be `bytes` or `str` objects, to uniformly surrogate-escaped strings.""" assert isinstance(args, list) def convert(arg) -> str: if isinstance(arg, bytes): return os.fsdecode(arg) return arg return [convert(a) for a in args] # stdout and stderr as bytes class CommandOutput(NamedTuple): stdout: bytes stderr: bytes def command_output(cmd: list[BytesOrStr], shell: bool = False) -> CommandOutput: """Runs the command and returns its output after it has exited. Returns a CommandOutput. The attributes ``stdout`` and ``stderr`` contain byte strings of the respective output streams. ``cmd`` is a list of arguments starting with the command names. The arguments are bytes on Unix and strings on Windows. If ``shell`` is true, ``cmd`` is assumed to be a string and passed to a shell to execute. If the process exits with a non-zero return code ``subprocess.CalledProcessError`` is raised. May also raise ``OSError``. This replaces `subprocess.check_output` which can have problems if lots of output is sent to stderr. """ converted_cmd = convert_command_args(cmd) devnull = subprocess.DEVNULL proc = subprocess.Popen( cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, stdin=devnull, close_fds=platform.system() != "Windows", shell=shell, ) stdout, stderr = proc.communicate() if proc.returncode: raise subprocess.CalledProcessError( returncode=proc.returncode, cmd=" ".join(converted_cmd), output=stdout + stderr, ) return CommandOutput(stdout, stderr) def max_filename_length(path: BytesOrStr, limit=MAX_FILENAME_LENGTH) -> int: """Attempt to determine the maximum filename length for the filesystem containing `path`. If the value is greater than `limit`, then `limit` is used instead (to prevent errors when a filesystem misreports its capacity). If it cannot be determined (e.g., on Windows), return `limit`. """ if hasattr(os, "statvfs"): try: res = os.statvfs(path) except OSError: return limit return min(res[9], limit) else: return limit def open_anything() -> str: """Return the system command that dispatches execution to the correct program. """ sys_name = platform.system() if sys_name == "Darwin": base_cmd = "open" elif sys_name == "Windows": base_cmd = "start" else: # Assume Unix base_cmd = "xdg-open" return base_cmd def editor_command() -> str: """Get a command for opening a text file. First try environment variable `VISUAL` followed by `EDITOR`. As last resort fall back to `open_anything()`, the platform-specific tool for opening files in general. """ return ( os.environ.get("VISUAL") or os.environ.get("EDITOR") or open_anything() ) def interactive_open(targets: Sequence[str], command: str): """Open the files in `targets` by `exec`ing a new `command`, given as a Unicode string. (The new program takes over, and Python execution ends: this does not fork a subprocess.) Can raise `OSError`. """ assert command # Split the command string into its arguments. try: args = shlex.split(command) except ValueError: # Malformed shell tokens. args = [command] args.insert(0, args[0]) # for argv[0] args += targets return os.execlp(*args) def case_sensitive(path: bytes) -> bool: """Check whether the filesystem at the given path is case sensitive. To work best, the path should point to a file or a directory. If the path does not exist, assume a case sensitive file system on every platform except Windows. Currently only used for absolute paths by beets; may have a trailing path separator. """ # Look at parent paths until we find a path that actually exists, or # reach the root. while True: head, tail = os.path.split(path) if head == path: # We have reached the root of the file system. # By default, the case sensitivity depends on the platform. return platform.system() != "Windows" # Trailing path separator, or path does not exist. if not tail or not os.path.exists(path): path = head continue upper_tail = tail.upper() lower_tail = tail.lower() # In case we can't tell from the given path name, look at the # parent directory. if upper_tail == lower_tail: path = head continue upper_sys = syspath(os.path.join(head, upper_tail)) lower_sys = syspath(os.path.join(head, lower_tail)) # If either the upper-cased or lower-cased path does not exist, the # filesystem must be case-sensitive. # (Otherwise, we have more work to do.) if not os.path.exists(upper_sys) or not os.path.exists(lower_sys): return True # Original and both upper- and lower-cased versions of the path # exist on the file system. Check whether they refer to different # files by their inodes (or an alternative method on Windows). return not os.path.samefile(lower_sys, upper_sys) def raw_seconds_short(string: str) -> float: """Formats a human-readable M:SS string as a float (number of seconds). Raises ValueError if the conversion cannot take place due to `string` not being in the right format. """ match = re.match(r"^(\d+):([0-5]\d)$", string) if not match: raise ValueError("String not in M:SS format") minutes, seconds = map(int, match.groups()) return float(minutes * 60 + seconds) def asciify_path(path: str, sep_replace: str) -> str: """Decodes all unicode characters in a path into ASCII equivalents. Substitutions are provided by the unidecode module. Path separators in the input are preserved. Keyword arguments: path -- The path to be asciified. sep_replace -- the string to be used to replace extraneous path separators. """ # if this platform has an os.altsep, change it to os.sep. if os.altsep: path = path.replace(os.altsep, os.sep) path_components: list[str] = path.split(os.sep) for index, item in enumerate(path_components): path_components[index] = unidecode(item).replace(os.sep, sep_replace) if os.altsep: path_components[index] = unidecode(item).replace( os.altsep, sep_replace ) return os.sep.join(path_components) def par_map(transform: Callable[[T], Any], items: Sequence[T]) -> None: """Apply the function `transform` to all the elements in the iterable `items`, like `map(transform, items)` but with no return value. The parallelism uses threads (not processes), so this is only useful for IO-bound `transform`s. """ pool = ThreadPool() pool.map(transform, items) pool.close() pool.join() class cached_classproperty: """A decorator implementing a read-only property that is *lazy* in the sense that the getter is only invoked once. Subsequent accesses through *any* instance use the cached result. """ def __init__(self, getter): self.getter = getter self.cache = {} def __get__(self, instance, owner): if owner not in self.cache: self.cache[owner] = self.getter(owner) return self.cache[owner] def get_module_tempdir(module: str) -> Path: """Return the temporary directory for the given module. The directory is created within the `/tmp/beets/` directory on Linux (or the equivalent temporary directory on other systems). Dots in the module name are replaced by underscores. """ module = module.replace("beets.", "").replace(".", "_") return Path(tempfile.gettempdir()) / "beets" / module def clean_module_tempdir(module: str) -> None: """Clean the temporary directory for the given module.""" tempdir = get_module_tempdir(module) shutil.rmtree(tempdir, ignore_errors=True) with suppress(OSError): # remove parent (/tmp/beets) directory if it is empty tempdir.parent.rmdir() def get_temp_filename( module: str, prefix: str = "", path: PathLike | None = None, suffix: str = "", ) -> bytes: """Return temporary filename for the given module and prefix. The filename starts with the given `prefix`. If 'suffix' is given, it is used a the file extension. If 'path' is given, we use the same suffix. """ if not suffix and path: suffix = Path(os.fsdecode(path)).suffix tempdir = get_module_tempdir(module) tempdir.mkdir(parents=True, exist_ok=True) _, filename = tempfile.mkstemp(dir=tempdir, prefix=prefix, suffix=suffix) return bytestring_path(filename) beetbox-beets-01f1faf/beets/util/artresizer.py000066400000000000000000000535531472325477400216050ustar00rootroot00000000000000# This file is part of beets. # Copyright 2016, Fabrice Laporte # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the # "Software"), to deal in the Software without restriction, including # without limitation the rights to use, copy, modify, merge, publish, # distribute, sublicense, and/or sell copies of the Software, and to # permit persons to whom the Software is furnished to do so, subject to # the following conditions: # # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. """Abstraction layer to resize images using PIL, ImageMagick, or a public resizing proxy if neither is available. """ import os import os.path import platform import re import subprocess from itertools import chain from urllib.parse import urlencode from beets import logging, util from beets.util import displayable_path, get_temp_filename, syspath PROXY_URL = "https://images.weserv.nl/" log = logging.getLogger("beets") def resize_url(url, maxwidth, quality=0): """Return a proxied image URL that resizes the original image to maxwidth (preserving aspect ratio). """ params = { "url": url.replace("http://", ""), "w": maxwidth, } if quality > 0: params["q"] = quality return "{}?{}".format(PROXY_URL, urlencode(params)) class LocalBackendNotAvailableError(Exception): pass _NOT_AVAILABLE = object() class LocalBackend: @classmethod def available(cls): try: cls.version() return True except LocalBackendNotAvailableError: return False class IMBackend(LocalBackend): NAME = "ImageMagick" # These fields are used as a cache for `version()`. `_legacy` indicates # whether the modern `magick` binary is available or whether to fall back # to the old-style `convert`, `identify`, etc. commands. _version = None _legacy = None @classmethod def version(cls): """Obtain and cache ImageMagick version. Raises `LocalBackendNotAvailableError` if not available. """ if cls._version is None: for cmd_name, legacy in (("magick", False), ("convert", True)): try: out = util.command_output([cmd_name, "--version"]).stdout except (subprocess.CalledProcessError, OSError) as exc: log.debug("ImageMagick version check failed: {}", exc) cls._version = _NOT_AVAILABLE else: if b"imagemagick" in out.lower(): pattern = rb".+ (\d+)\.(\d+)\.(\d+).*" match = re.search(pattern, out) if match: cls._version = ( int(match.group(1)), int(match.group(2)), int(match.group(3)), ) cls._legacy = legacy if cls._version is _NOT_AVAILABLE: raise LocalBackendNotAvailableError() else: return cls._version def __init__(self): """Initialize a wrapper around ImageMagick for local image operations. Stores the ImageMagick version and legacy flag. If ImageMagick is not available, raise an Exception. """ self.version() # Use ImageMagick's magick binary when it's available. # If it's not, fall back to the older, separate convert # and identify commands. if self._legacy: self.convert_cmd = ["convert"] self.identify_cmd = ["identify"] self.compare_cmd = ["compare"] else: self.convert_cmd = ["magick"] self.identify_cmd = ["magick", "identify"] self.compare_cmd = ["magick", "compare"] def resize( self, maxwidth, path_in, path_out=None, quality=0, max_filesize=0 ): """Resize using ImageMagick. Use the ``magick`` program or ``convert`` on older versions. Return the output path of resized image. """ if not path_out: path_out = get_temp_filename(__name__, "resize_IM_", path_in) log.debug( "artresizer: ImageMagick resizing {0} to {1}", displayable_path(path_in), displayable_path(path_out), ) # "-resize WIDTHx>" shrinks images with the width larger # than the given width while maintaining the aspect ratio # with regards to the height. # ImageMagick already seems to default to no interlace, but we include # it here for the sake of explicitness. cmd = self.convert_cmd + [ syspath(path_in, prefix=False), "-resize", f"{maxwidth}x>", "-interlace", "none", ] if quality > 0: cmd += ["-quality", f"{quality}"] # "-define jpeg:extent=SIZEb" sets the target filesize for imagemagick # to SIZE in bytes. if max_filesize > 0: cmd += ["-define", f"jpeg:extent={max_filesize}b"] cmd.append(syspath(path_out, prefix=False)) try: util.command_output(cmd) except subprocess.CalledProcessError: log.warning( "artresizer: IM convert failed for {0}", displayable_path(path_in), ) return path_in return path_out def get_size(self, path_in): cmd = self.identify_cmd + [ "-format", "%w %h", syspath(path_in, prefix=False), ] try: out = util.command_output(cmd).stdout except subprocess.CalledProcessError as exc: log.warning("ImageMagick size query failed") log.debug( "`convert` exited with (status {}) when " "getting size with command {}:\n{}", exc.returncode, cmd, exc.output.strip(), ) return None try: return tuple(map(int, out.split(b" "))) except IndexError: log.warning("Could not understand IM output: {0!r}", out) return None def deinterlace(self, path_in, path_out=None): if not path_out: path_out = get_temp_filename(__name__, "deinterlace_IM_", path_in) cmd = self.convert_cmd + [ syspath(path_in, prefix=False), "-interlace", "none", syspath(path_out, prefix=False), ] try: util.command_output(cmd) return path_out except subprocess.CalledProcessError: # FIXME: Should probably issue a warning? return path_in def get_format(self, filepath): cmd = self.identify_cmd + ["-format", "%[magick]", syspath(filepath)] try: return util.command_output(cmd).stdout except subprocess.CalledProcessError: # FIXME: Should probably issue a warning? return None def convert_format(self, source, target, deinterlaced): cmd = self.convert_cmd + [ syspath(source), *(["-interlace", "none"] if deinterlaced else []), syspath(target), ] try: subprocess.check_call( cmd, stderr=subprocess.DEVNULL, stdout=subprocess.DEVNULL ) return target except subprocess.CalledProcessError: # FIXME: Should probably issue a warning? return source @property def can_compare(self): return self.version() > (6, 8, 7) def compare(self, im1, im2, compare_threshold): is_windows = platform.system() == "Windows" # Converting images to grayscale tends to minimize the weight # of colors in the diff score. So we first convert both images # to grayscale and then pipe them into the `compare` command. # On Windows, ImageMagick doesn't support the magic \\?\ prefix # on paths, so we pass `prefix=False` to `syspath`. convert_cmd = self.convert_cmd + [ syspath(im2, prefix=False), syspath(im1, prefix=False), "-colorspace", "gray", "MIFF:-", ] compare_cmd = self.compare_cmd + [ "-define", "phash:colorspaces=sRGB,HCLp", "-metric", "PHASH", "-", "null:", ] log.debug( "comparing images with pipeline {} | {}", convert_cmd, compare_cmd ) convert_proc = subprocess.Popen( convert_cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, close_fds=not is_windows, ) compare_proc = subprocess.Popen( compare_cmd, stdin=convert_proc.stdout, stdout=subprocess.PIPE, stderr=subprocess.PIPE, close_fds=not is_windows, ) # Check the convert output. We're not interested in the # standard output; that gets piped to the next stage. convert_proc.stdout.close() convert_stderr = convert_proc.stderr.read() convert_proc.stderr.close() convert_proc.wait() if convert_proc.returncode: log.debug( "ImageMagick convert failed with status {}: {!r}", convert_proc.returncode, convert_stderr, ) return None # Check the compare output. stdout, stderr = compare_proc.communicate() if compare_proc.returncode: if compare_proc.returncode != 1: log.debug( "ImageMagick compare failed: {0}, {1}", displayable_path(im2), displayable_path(im1), ) return None out_str = stderr else: out_str = stdout try: phash_diff = float(out_str) except ValueError: log.debug("IM output is not a number: {0!r}", out_str) return None log.debug("ImageMagick compare score: {0}", phash_diff) return phash_diff <= compare_threshold @property def can_write_metadata(self): return True def write_metadata(self, file, metadata): assignments = list( chain.from_iterable(("-set", k, v) for k, v in metadata.items()) ) command = self.convert_cmd + [file, *assignments, file] util.command_output(command) class PILBackend(LocalBackend): NAME = "PIL" @classmethod def version(cls): try: __import__("PIL", fromlist=["Image"]) except ImportError: raise LocalBackendNotAvailableError() def __init__(self): """Initialize a wrapper around PIL for local image operations. If PIL is not available, raise an Exception. """ self.version() def resize( self, maxwidth, path_in, path_out=None, quality=0, max_filesize=0 ): """Resize using Python Imaging Library (PIL). Return the output path of resized image. """ if not path_out: path_out = get_temp_filename(__name__, "resize_PIL_", path_in) from PIL import Image log.debug( "artresizer: PIL resizing {0} to {1}", displayable_path(path_in), displayable_path(path_out), ) try: im = Image.open(syspath(path_in)) size = maxwidth, maxwidth im.thumbnail(size, Image.Resampling.LANCZOS) if quality == 0: # Use PIL's default quality. quality = -1 # progressive=False only affects JPEGs and is the default, # but we include it here for explicitness. im.save(os.fsdecode(path_out), quality=quality, progressive=False) if max_filesize > 0: # If maximum filesize is set, we attempt to lower the quality # of jpeg conversion by a proportional amount, up to 3 attempts # First, set the maximum quality to either provided, or 95 if quality > 0: lower_qual = quality else: lower_qual = 95 for i in range(5): # 5 attempts is an arbitrary choice filesize = os.stat(syspath(path_out)).st_size log.debug("PIL Pass {0} : Output size: {1}B", i, filesize) if filesize <= max_filesize: return path_out # The relationship between filesize & quality will be # image dependent. lower_qual -= 10 # Restrict quality dropping below 10 if lower_qual < 10: lower_qual = 10 # Use optimize flag to improve filesize decrease im.save( os.fsdecode(path_out), quality=lower_qual, optimize=True, progressive=False, ) log.warning( "PIL Failed to resize file to below {0}B", max_filesize ) return path_out else: return path_out except OSError: log.error( "PIL cannot create thumbnail for '{0}'", displayable_path(path_in), ) return path_in def get_size(self, path_in): from PIL import Image try: im = Image.open(syspath(path_in)) return im.size except OSError as exc: log.error( "PIL could not read file {}: {}", displayable_path(path_in), exc ) return None def deinterlace(self, path_in, path_out=None): if not path_out: path_out = get_temp_filename(__name__, "deinterlace_PIL_", path_in) from PIL import Image try: im = Image.open(syspath(path_in)) im.save(os.fsdecode(path_out), progressive=False) return path_out except OSError: # FIXME: Should probably issue a warning? return path_in def get_format(self, filepath): from PIL import Image, UnidentifiedImageError try: with Image.open(syspath(filepath)) as im: return im.format except ( ValueError, TypeError, UnidentifiedImageError, FileNotFoundError, ): log.exception("failed to detect image format for {}", filepath) return None def convert_format(self, source, target, deinterlaced): from PIL import Image, UnidentifiedImageError try: with Image.open(syspath(source)) as im: im.save(os.fsdecode(target), progressive=not deinterlaced) return target except ( ValueError, TypeError, UnidentifiedImageError, FileNotFoundError, OSError, ): log.exception("failed to convert image {} -> {}", source, target) return source @property def can_compare(self): return False def compare(self, im1, im2, compare_threshold): # It is an error to call this when ArtResizer.can_compare is not True. raise NotImplementedError() @property def can_write_metadata(self): return True def write_metadata(self, file, metadata): from PIL import Image, PngImagePlugin # FIXME: Detect and handle other file types (currently, the only user # is the thumbnails plugin, which generates PNG images). im = Image.open(syspath(file)) meta = PngImagePlugin.PngInfo() for k, v in metadata.items(): meta.add_text(k, v, 0) im.save(os.fsdecode(file), "PNG", pnginfo=meta) class Shareable(type): """A pseudo-singleton metaclass that allows both shared and non-shared instances. The ``MyClass.shared`` property holds a lazily-created shared instance of ``MyClass`` while calling ``MyClass()`` to construct a new object works as usual. """ def __init__(cls, name, bases, dict): super().__init__(name, bases, dict) cls._instance = None @property def shared(cls): if cls._instance is None: cls._instance = cls() return cls._instance BACKEND_CLASSES = [ IMBackend, PILBackend, ] class ArtResizer(metaclass=Shareable): """A singleton class that performs image resizes.""" def __init__(self): """Create a resizer object with an inferred method.""" # Check if a local backend is available, and store an instance of the # backend class. Otherwise, fallback to the web proxy. for backend_cls in BACKEND_CLASSES: try: self.local_method = backend_cls() log.debug(f"artresizer: method is {self.local_method.NAME}") break except LocalBackendNotAvailableError: continue else: log.debug("artresizer: method is WEBPROXY") self.local_method = None @property def method(self): if self.local: return self.local_method.NAME else: return "WEBPROXY" def resize( self, maxwidth, path_in, path_out=None, quality=0, max_filesize=0 ): """Manipulate an image file according to the method, returning a new path. For PIL or IMAGEMAGIC methods, resizes the image to a temporary file and encodes with the specified quality level. For WEBPROXY, returns `path_in` unmodified. """ if self.local: return self.local_method.resize( maxwidth, path_in, path_out, quality=quality, max_filesize=max_filesize, ) else: # Handled by `proxy_url` already. return path_in def deinterlace(self, path_in, path_out=None): """Deinterlace an image. Only available locally. """ if self.local: return self.local_method.deinterlace(path_in, path_out) else: # FIXME: Should probably issue a warning? return path_in def proxy_url(self, maxwidth, url, quality=0): """Modifies an image URL according the method, returning a new URL. For WEBPROXY, a URL on the proxy server is returned. Otherwise, the URL is returned unmodified. """ if self.local: # Going to be handled by `resize()`. return url else: return resize_url(url, maxwidth, quality) @property def local(self): """A boolean indicating whether the resizing method is performed locally (i.e., PIL or ImageMagick). """ return self.local_method is not None def get_size(self, path_in): """Return the size of an image file as an int couple (width, height) in pixels. Only available locally. """ if self.local: return self.local_method.get_size(path_in) else: # FIXME: Should probably issue a warning? return path_in def get_format(self, path_in): """Returns the format of the image as a string. Only available locally. """ if self.local: return self.local_method.get_format(path_in) else: # FIXME: Should probably issue a warning? return None def reformat(self, path_in, new_format, deinterlaced=True): """Converts image to desired format, updating its extension, but keeping the same filename. Only available locally. """ if not self.local: # FIXME: Should probably issue a warning? return path_in new_format = new_format.lower() # A nonexhaustive map of image "types" to extensions overrides new_format = { "jpeg": "jpg", }.get(new_format, new_format) fname, ext = os.path.splitext(path_in) path_new = fname + b"." + new_format.encode("utf8") # allows the exception to propagate, while still making sure a changed # file path was removed result_path = path_in try: result_path = self.local_method.convert_format( path_in, path_new, deinterlaced ) finally: if result_path != path_in: os.unlink(path_in) return result_path @property def can_compare(self): """A boolean indicating whether image comparison is available""" if self.local: return self.local_method.can_compare else: return False def compare(self, im1, im2, compare_threshold): """Return a boolean indicating whether two images are similar. Only available locally. """ if self.local: return self.local_method.compare(im1, im2, compare_threshold) else: # FIXME: Should probably issue a warning? return None @property def can_write_metadata(self): """A boolean indicating whether writing image metadata is supported.""" if self.local: return self.local_method.can_write_metadata else: return False def write_metadata(self, file, metadata): """Write key-value metadata to the image file. Only available locally. Currently, expects the image to be a PNG file. """ if self.local: self.local_method.write_metadata(file, metadata) else: # FIXME: Should probably issue a warning? pass beetbox-beets-01f1faf/beets/util/bluelet.py000066400000000000000000000467251472325477400210520ustar00rootroot00000000000000"""Extremely simple pure-Python implementation of coroutine-style asynchronous socket I/O. Inspired by, but inferior to, Eventlet. Bluelet can also be thought of as a less-terrible replacement for asyncore. Bluelet: easy concurrency without all the messy parallelism. """ import collections import errno import select import socket import sys import time import traceback import types # Basic events used for thread scheduling. class Event: """Just a base class identifying Bluelet events. An event is an object yielded from a Bluelet thread coroutine to suspend operation and communicate with the scheduler. """ pass class WaitableEvent(Event): """A waitable event is one encapsulating an action that can be waited for using a select() call. That is, it's an event with an associated file descriptor. """ def waitables(self): """Return "waitable" objects to pass to select(). Should return three iterables for input readiness, output readiness, and exceptional conditions (i.e., the three lists passed to select()). """ return (), (), () def fire(self): """Called when an associated file descriptor becomes ready (i.e., is returned from a select() call). """ pass class ValueEvent(Event): """An event that does nothing but return a fixed value.""" def __init__(self, value): self.value = value class ExceptionEvent(Event): """Raise an exception at the yield point. Used internally.""" def __init__(self, exc_info): self.exc_info = exc_info class SpawnEvent(Event): """Add a new coroutine thread to the scheduler.""" def __init__(self, coro): self.spawned = coro class JoinEvent(Event): """Suspend the thread until the specified child thread has completed. """ def __init__(self, child): self.child = child class KillEvent(Event): """Unschedule a child thread.""" def __init__(self, child): self.child = child class DelegationEvent(Event): """Suspend execution of the current thread, start a new thread and, once the child thread finished, return control to the parent thread. """ def __init__(self, coro): self.spawned = coro class ReturnEvent(Event): """Return a value the current thread's delegator at the point of delegation. Ends the current (delegate) thread. """ def __init__(self, value): self.value = value class SleepEvent(WaitableEvent): """Suspend the thread for a given duration.""" def __init__(self, duration): self.wakeup_time = time.time() + duration def time_left(self): return max(self.wakeup_time - time.time(), 0.0) class ReadEvent(WaitableEvent): """Reads from a file-like object.""" def __init__(self, fd, bufsize): self.fd = fd self.bufsize = bufsize def waitables(self): return (self.fd,), (), () def fire(self): return self.fd.read(self.bufsize) class WriteEvent(WaitableEvent): """Writes to a file-like object.""" def __init__(self, fd, data): self.fd = fd self.data = data def waitable(self): return (), (self.fd,), () def fire(self): self.fd.write(self.data) # Core logic for executing and scheduling threads. def _event_select(events): """Perform a select() over all the Events provided, returning the ones ready to be fired. Only WaitableEvents (including SleepEvents) matter here; all other events are ignored (and thus postponed). """ # Gather waitables and wakeup times. waitable_to_event = {} rlist, wlist, xlist = [], [], [] earliest_wakeup = None for event in events: if isinstance(event, SleepEvent): if not earliest_wakeup: earliest_wakeup = event.wakeup_time else: earliest_wakeup = min(earliest_wakeup, event.wakeup_time) elif isinstance(event, WaitableEvent): r, w, x = event.waitables() rlist += r wlist += w xlist += x for waitable in r: waitable_to_event[("r", waitable)] = event for waitable in w: waitable_to_event[("w", waitable)] = event for waitable in x: waitable_to_event[("x", waitable)] = event # If we have a any sleeping threads, determine how long to sleep. if earliest_wakeup: timeout = max(earliest_wakeup - time.time(), 0.0) else: timeout = None # Perform select() if we have any waitables. if rlist or wlist or xlist: rready, wready, xready = select.select(rlist, wlist, xlist, timeout) else: rready, wready, xready = (), (), () if timeout: time.sleep(timeout) # Gather ready events corresponding to the ready waitables. ready_events = set() for ready in rready: ready_events.add(waitable_to_event[("r", ready)]) for ready in wready: ready_events.add(waitable_to_event[("w", ready)]) for ready in xready: ready_events.add(waitable_to_event[("x", ready)]) # Gather any finished sleeps. for event in events: if isinstance(event, SleepEvent) and event.time_left() == 0.0: ready_events.add(event) return ready_events class ThreadError(Exception): def __init__(self, coro, exc_info): self.coro = coro self.exc_info = exc_info def reraise(self): raise self.exc_info[1].with_traceback(self.exc_info[2]) SUSPENDED = Event() # Special sentinel placeholder for suspended threads. class Delegated(Event): """Placeholder indicating that a thread has delegated execution to a different thread. """ def __init__(self, child): self.child = child def run(root_coro): """Schedules a coroutine, running it to completion. This encapsulates the Bluelet scheduler, which the root coroutine can add to by spawning new coroutines. """ # The "threads" dictionary keeps track of all the currently- # executing and suspended coroutines. It maps coroutines to their # currently "blocking" event. The event value may be SUSPENDED if # the coroutine is waiting on some other condition: namely, a # delegated coroutine or a joined coroutine. In this case, the # coroutine should *also* appear as a value in one of the below # dictionaries `delegators` or `joiners`. threads = {root_coro: ValueEvent(None)} # Maps child coroutines to delegating parents. delegators = {} # Maps child coroutines to joining (exit-waiting) parents. joiners = collections.defaultdict(list) def complete_thread(coro, return_value): """Remove a coroutine from the scheduling pool, awaking delegators and joiners as necessary and returning the specified value to any delegating parent. """ del threads[coro] # Resume delegator. if coro in delegators: threads[delegators[coro]] = ValueEvent(return_value) del delegators[coro] # Resume joiners. if coro in joiners: for parent in joiners[coro]: threads[parent] = ValueEvent(None) del joiners[coro] def advance_thread(coro, value, is_exc=False): """After an event is fired, run a given coroutine associated with it in the threads dict until it yields again. If the coroutine exits, then the thread is removed from the pool. If the coroutine raises an exception, it is reraised in a ThreadError. If is_exc is True, then the value must be an exc_info tuple and the exception is thrown into the coroutine. """ try: if is_exc: next_event = coro.throw(*value) else: next_event = coro.send(value) except StopIteration: # Thread is done. complete_thread(coro, None) except BaseException: # Thread raised some other exception. del threads[coro] raise ThreadError(coro, sys.exc_info()) else: if isinstance(next_event, types.GeneratorType): # Automatically invoke sub-coroutines. (Shorthand for # explicit bluelet.call().) next_event = DelegationEvent(next_event) threads[coro] = next_event def kill_thread(coro): """Unschedule this thread and its (recursive) delegates.""" # Collect all coroutines in the delegation stack. coros = [coro] while isinstance(threads[coro], Delegated): coro = threads[coro].child coros.append(coro) # Complete each coroutine from the top to the bottom of the # stack. for coro in reversed(coros): complete_thread(coro, None) # Continue advancing threads until root thread exits. exit_te = None while threads: try: # Look for events that can be run immediately. Continue # running immediate events until nothing is ready. while True: have_ready = False for coro, event in list(threads.items()): if isinstance(event, SpawnEvent): threads[event.spawned] = ValueEvent(None) # Spawn. advance_thread(coro, None) have_ready = True elif isinstance(event, ValueEvent): advance_thread(coro, event.value) have_ready = True elif isinstance(event, ExceptionEvent): advance_thread(coro, event.exc_info, True) have_ready = True elif isinstance(event, DelegationEvent): threads[coro] = Delegated(event.spawned) # Suspend. threads[event.spawned] = ValueEvent(None) # Spawn. delegators[event.spawned] = coro have_ready = True elif isinstance(event, ReturnEvent): # Thread is done. complete_thread(coro, event.value) have_ready = True elif isinstance(event, JoinEvent): threads[coro] = SUSPENDED # Suspend. joiners[event.child].append(coro) have_ready = True elif isinstance(event, KillEvent): threads[coro] = ValueEvent(None) kill_thread(event.child) have_ready = True # Only start the select when nothing else is ready. if not have_ready: break # Wait and fire. event2coro = {v: k for k, v in threads.items()} for event in _event_select(threads.values()): # Run the IO operation, but catch socket errors. try: value = event.fire() except OSError as exc: if ( isinstance(exc.args, tuple) and exc.args[0] == errno.EPIPE ): # Broken pipe. Remote host disconnected. pass elif ( isinstance(exc.args, tuple) and exc.args[0] == errno.ECONNRESET ): # Connection was reset by peer. pass else: traceback.print_exc() # Abort the coroutine. threads[event2coro[event]] = ReturnEvent(None) else: advance_thread(event2coro[event], value) except ThreadError as te: # Exception raised from inside a thread. event = ExceptionEvent(te.exc_info) if te.coro in delegators: # The thread is a delegate. Raise exception in its # delegator. threads[delegators[te.coro]] = event del delegators[te.coro] else: # The thread is root-level. Raise in client code. exit_te = te break except BaseException: # For instance, KeyboardInterrupt during select(). Raise # into root thread and terminate others. threads = {root_coro: ExceptionEvent(sys.exc_info())} # If any threads still remain, kill them. for coro in threads: coro.close() # If we're exiting with an exception, raise it in the client. if exit_te: exit_te.reraise() # Sockets and their associated events. class SocketClosedError(Exception): pass class Listener: """A socket wrapper object for listening sockets.""" def __init__(self, host, port): """Create a listening socket on the given hostname and port.""" self._closed = False self.host = host self.port = port self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) self.sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) self.sock.bind((host, port)) self.sock.listen(5) def accept(self): """An event that waits for a connection on the listening socket. When a connection is made, the event returns a Connection object. """ if self._closed: raise SocketClosedError() return AcceptEvent(self) def close(self): """Immediately close the listening socket. (Not an event.)""" self._closed = True self.sock.close() class Connection: """A socket wrapper object for connected sockets.""" def __init__(self, sock, addr): self.sock = sock self.addr = addr self._buf = b"" self._closed = False def close(self): """Close the connection.""" self._closed = True self.sock.close() def recv(self, size): """Read at most size bytes of data from the socket.""" if self._closed: raise SocketClosedError() if self._buf: # We already have data read previously. out = self._buf[:size] self._buf = self._buf[size:] return ValueEvent(out) else: return ReceiveEvent(self, size) def send(self, data): """Sends data on the socket, returning the number of bytes successfully sent. """ if self._closed: raise SocketClosedError() return SendEvent(self, data) def sendall(self, data): """Send all of data on the socket.""" if self._closed: raise SocketClosedError() return SendEvent(self, data, True) def readline(self, terminator=b"\n", bufsize=1024): """Reads a line (delimited by terminator) from the socket.""" if self._closed: raise SocketClosedError() while True: if terminator in self._buf: line, self._buf = self._buf.split(terminator, 1) line += terminator yield ReturnEvent(line) break data = yield ReceiveEvent(self, bufsize) if data: self._buf += data else: line = self._buf self._buf = b"" yield ReturnEvent(line) break class AcceptEvent(WaitableEvent): """An event for Listener objects (listening sockets) that suspends execution until the socket gets a connection. """ def __init__(self, listener): self.listener = listener def waitables(self): return (self.listener.sock,), (), () def fire(self): sock, addr = self.listener.sock.accept() return Connection(sock, addr) class ReceiveEvent(WaitableEvent): """An event for Connection objects (connected sockets) for asynchronously reading data. """ def __init__(self, conn, bufsize): self.conn = conn self.bufsize = bufsize def waitables(self): return (self.conn.sock,), (), () def fire(self): return self.conn.sock.recv(self.bufsize) class SendEvent(WaitableEvent): """An event for Connection objects (connected sockets) for asynchronously writing data. """ def __init__(self, conn, data, sendall=False): self.conn = conn self.data = data self.sendall = sendall def waitables(self): return (), (self.conn.sock,), () def fire(self): if self.sendall: return self.conn.sock.sendall(self.data) else: return self.conn.sock.send(self.data) # Public interface for threads; each returns an event object that # can immediately be "yield"ed. def null(): """Event: yield to the scheduler without doing anything special.""" return ValueEvent(None) def spawn(coro): """Event: add another coroutine to the scheduler. Both the parent and child coroutines run concurrently. """ if not isinstance(coro, types.GeneratorType): raise ValueError("%s is not a coroutine" % coro) return SpawnEvent(coro) def call(coro): """Event: delegate to another coroutine. The current coroutine is resumed once the sub-coroutine finishes. If the sub-coroutine returns a value using end(), then this event returns that value. """ if not isinstance(coro, types.GeneratorType): raise ValueError("%s is not a coroutine" % coro) return DelegationEvent(coro) def end(value=None): """Event: ends the coroutine and returns a value to its delegator. """ return ReturnEvent(value) def read(fd, bufsize=None): """Event: read from a file descriptor asynchronously.""" if bufsize is None: # Read all. def reader(): buf = [] while True: data = yield read(fd, 1024) if not data: break buf.append(data) yield ReturnEvent("".join(buf)) return DelegationEvent(reader()) else: return ReadEvent(fd, bufsize) def write(fd, data): """Event: write to a file descriptor asynchronously.""" return WriteEvent(fd, data) def connect(host, port): """Event: connect to a network address and return a Connection object for communicating on the socket. """ addr = (host, port) sock = socket.create_connection(addr) return ValueEvent(Connection(sock, addr)) def sleep(duration): """Event: suspend the thread for ``duration`` seconds.""" return SleepEvent(duration) def join(coro): """Suspend the thread until another, previously `spawn`ed thread completes. """ return JoinEvent(coro) def kill(coro): """Halt the execution of a different `spawn`ed thread.""" return KillEvent(coro) # Convenience function for running socket servers. def server(host, port, func): """A coroutine that runs a network server. Host and port specify the listening address. func should be a coroutine that takes a single parameter, a Connection object. The coroutine is invoked for every incoming connection on the listening socket. """ def handler(conn): try: yield func(conn) finally: conn.close() listener = Listener(host, port) try: while True: conn = yield listener.accept() yield spawn(handler(conn)) except KeyboardInterrupt: pass finally: listener.close() beetbox-beets-01f1faf/beets/util/functemplate.py000066400000000000000000000463531472325477400221020ustar00rootroot00000000000000# This file is part of beets. # Copyright 2016, Adrian Sampson. # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the # "Software"), to deal in the Software without restriction, including # without limitation the rights to use, copy, modify, merge, publish, # distribute, sublicense, and/or sell copies of the Software, and to # permit persons to whom the Software is furnished to do so, subject to # the following conditions: # # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. """This module implements a string formatter based on the standard PEP 292 string.Template class extended with function calls. Variables, as with string.Template, are indicated with $ and functions are delimited with %. This module assumes that everything is Unicode: the template and the substitution values. Bytestrings are not supported. Also, the templates always behave like the ``safe_substitute`` method in the standard library: unknown symbols are left intact. This is sort of like a tiny, horrible degeneration of a real templating engine like Jinja2 or Mustache. """ import ast import dis import functools import re import types SYMBOL_DELIM = "$" FUNC_DELIM = "%" GROUP_OPEN = "{" GROUP_CLOSE = "}" ARG_SEP = "," ESCAPE_CHAR = "$" VARIABLE_PREFIX = "__var_" FUNCTION_PREFIX = "__func_" class Environment: """Contains the values and functions to be substituted into a template. """ def __init__(self, values, functions): self.values = values self.functions = functions # Code generation helpers. def ex_rvalue(name): """A variable store expression.""" return ast.Name(name, ast.Load()) def ex_literal(val): """An int, float, long, bool, string, or None literal with the given value. """ return ast.Constant(val) def ex_call(func, args): """A function-call expression with only positional parameters. The function may be an expression or the name of a function. Each argument may be an expression or a value to be used as a literal. """ if isinstance(func, str): func = ex_rvalue(func) args = list(args) for i in range(len(args)): if not isinstance(args[i], ast.expr): args[i] = ex_literal(args[i]) return ast.Call(func, args, []) def compile_func(arg_names, statements, name="_the_func", debug=False): """Compile a list of statements as the body of a function and return the resulting Python function. If `debug`, then print out the bytecode of the compiled function. """ args_fields = { "args": [ast.arg(arg=n, annotation=None) for n in arg_names], "kwonlyargs": [], "kw_defaults": [], "defaults": [ex_literal(None) for _ in arg_names], } args_fields["posonlyargs"] = [] args = ast.arguments(**args_fields) func_def = ast.FunctionDef( name=name, args=args, body=statements, decorator_list=[], ) # The ast.Module signature changed in 3.8 to accept a list of types to # ignore. mod = ast.Module([func_def], []) ast.fix_missing_locations(mod) prog = compile(mod, "", "exec") # Debug: show bytecode. if debug: dis.dis(prog) for const in prog.co_consts: if isinstance(const, types.CodeType): dis.dis(const) the_locals = {} exec(prog, {}, the_locals) return the_locals[name] # AST nodes for the template language. class Symbol: """A variable-substitution symbol in a template.""" def __init__(self, ident, original): self.ident = ident self.original = original def __repr__(self): return "Symbol(%s)" % repr(self.ident) def evaluate(self, env): """Evaluate the symbol in the environment, returning a Unicode string. """ if self.ident in env.values: # Substitute for a value. return env.values[self.ident] else: # Keep original text. return self.original def translate(self): """Compile the variable lookup.""" ident = self.ident expr = ex_rvalue(VARIABLE_PREFIX + ident) return [expr], {ident}, set() class Call: """A function call in a template.""" def __init__(self, ident, args, original): self.ident = ident self.args = args self.original = original def __repr__(self): return "Call({}, {}, {})".format( repr(self.ident), repr(self.args), repr(self.original) ) def evaluate(self, env): """Evaluate the function call in the environment, returning a Unicode string. """ if self.ident in env.functions: arg_vals = [expr.evaluate(env) for expr in self.args] try: out = env.functions[self.ident](*arg_vals) except Exception as exc: # Function raised exception! Maybe inlining the name of # the exception will help debug. return "<%s>" % str(exc) return str(out) else: return self.original def translate(self): """Compile the function call.""" varnames = set() funcnames = {self.ident} arg_exprs = [] for arg in self.args: subexprs, subvars, subfuncs = arg.translate() varnames.update(subvars) funcnames.update(subfuncs) # Create a subexpression that joins the result components of # the arguments. arg_exprs.append( ex_call( ast.Attribute(ex_literal(""), "join", ast.Load()), [ ex_call( "map", [ ex_rvalue(str.__name__), ast.List(subexprs, ast.Load()), ], ) ], ) ) subexpr_call = ex_call(FUNCTION_PREFIX + self.ident, arg_exprs) return [subexpr_call], varnames, funcnames class Expression: """Top-level template construct: contains a list of text blobs, Symbols, and Calls. """ def __init__(self, parts): self.parts = parts def __repr__(self): return "Expression(%s)" % (repr(self.parts)) def evaluate(self, env): """Evaluate the entire expression in the environment, returning a Unicode string. """ out = [] for part in self.parts: if isinstance(part, str): out.append(part) else: out.append(part.evaluate(env)) return "".join(map(str, out)) def translate(self): """Compile the expression to a list of Python AST expressions, a set of variable names used, and a set of function names. """ expressions = [] varnames = set() funcnames = set() for part in self.parts: if isinstance(part, str): expressions.append(ex_literal(part)) else: e, v, f = part.translate() expressions.extend(e) varnames.update(v) funcnames.update(f) return expressions, varnames, funcnames # Parser. class ParseError(Exception): pass class Parser: """Parses a template expression string. Instantiate the class with the template source and call ``parse_expression``. The ``pos`` field will indicate the character after the expression finished and ``parts`` will contain a list of Unicode strings, Symbols, and Calls reflecting the concatenated portions of the expression. This is a terrible, ad-hoc parser implementation based on a left-to-right scan with no lexing step to speak of; it's probably both inefficient and incorrect. Maybe this should eventually be replaced with a real, accepted parsing technique (PEG, parser generator, etc.). """ def __init__(self, string, in_argument=False): """Create a new parser. :param in_arguments: boolean that indicates the parser is to be used for parsing function arguments, ie. considering commas (`ARG_SEP`) a special character """ self.string = string self.in_argument = in_argument self.pos = 0 self.parts = [] # Common parsing resources. special_chars = ( SYMBOL_DELIM, FUNC_DELIM, GROUP_OPEN, GROUP_CLOSE, ESCAPE_CHAR, ) special_char_re = re.compile( r"[%s]|\Z" % "".join(re.escape(c) for c in special_chars) ) escapable_chars = (SYMBOL_DELIM, FUNC_DELIM, GROUP_CLOSE, ARG_SEP) terminator_chars = (GROUP_CLOSE,) def parse_expression(self): """Parse a template expression starting at ``pos``. Resulting components (Unicode strings, Symbols, and Calls) are added to the ``parts`` field, a list. The ``pos`` field is updated to be the next character after the expression. """ # Append comma (ARG_SEP) to the list of special characters only when # parsing function arguments. extra_special_chars = () special_char_re = self.special_char_re if self.in_argument: extra_special_chars = (ARG_SEP,) special_char_re = re.compile( r"[%s]|\Z" % "".join( re.escape(c) for c in self.special_chars + extra_special_chars ) ) text_parts = [] while self.pos < len(self.string): char = self.string[self.pos] if char not in self.special_chars + extra_special_chars: # A non-special character. Skip to the next special # character, treating the interstice as literal text. next_pos = ( special_char_re.search(self.string[self.pos :]).start() + self.pos ) text_parts.append(self.string[self.pos : next_pos]) self.pos = next_pos continue if self.pos == len(self.string) - 1: # The last character can never begin a structure, so we # just interpret it as a literal character (unless it # terminates the expression, as with , and }). if char not in self.terminator_chars + extra_special_chars: text_parts.append(char) self.pos += 1 break next_char = self.string[self.pos + 1] if char == ESCAPE_CHAR and next_char in ( self.escapable_chars + extra_special_chars ): # An escaped special character ($$, $}, etc.). Note that # ${ is not an escape sequence: this is ambiguous with # the start of a symbol and it's not necessary (just # using { suffices in all cases). text_parts.append(next_char) self.pos += 2 # Skip the next character. continue # Shift all characters collected so far into a single string. if text_parts: self.parts.append("".join(text_parts)) text_parts = [] if char == SYMBOL_DELIM: # Parse a symbol. self.parse_symbol() elif char == FUNC_DELIM: # Parse a function call. self.parse_call() elif char in self.terminator_chars + extra_special_chars: # Template terminated. break elif char == GROUP_OPEN: # Start of a group has no meaning hear; just pass # through the character. text_parts.append(char) self.pos += 1 else: assert False # If any parsed characters remain, shift them into a string. if text_parts: self.parts.append("".join(text_parts)) def parse_symbol(self): """Parse a variable reference (like ``$foo`` or ``${foo}``) starting at ``pos``. Possibly appends a Symbol object (or, failing that, text) to the ``parts`` field and updates ``pos``. The character at ``pos`` must, as a precondition, be ``$``. """ assert self.pos < len(self.string) assert self.string[self.pos] == SYMBOL_DELIM if self.pos == len(self.string) - 1: # Last character. self.parts.append(SYMBOL_DELIM) self.pos += 1 return next_char = self.string[self.pos + 1] start_pos = self.pos self.pos += 1 if next_char == GROUP_OPEN: # A symbol like ${this}. self.pos += 1 # Skip opening. closer = self.string.find(GROUP_CLOSE, self.pos) if closer == -1 or closer == self.pos: # No closing brace found or identifier is empty. self.parts.append(self.string[start_pos : self.pos]) else: # Closer found. ident = self.string[self.pos : closer] self.pos = closer + 1 self.parts.append( Symbol(ident, self.string[start_pos : self.pos]) ) else: # A bare-word symbol. ident = self._parse_ident() if ident: # Found a real symbol. self.parts.append( Symbol(ident, self.string[start_pos : self.pos]) ) else: # A standalone $. self.parts.append(SYMBOL_DELIM) def parse_call(self): """Parse a function call (like ``%foo{bar,baz}``) starting at ``pos``. Possibly appends a Call object to ``parts`` and update ``pos``. The character at ``pos`` must be ``%``. """ assert self.pos < len(self.string) assert self.string[self.pos] == FUNC_DELIM start_pos = self.pos self.pos += 1 ident = self._parse_ident() if not ident: # No function name. self.parts.append(FUNC_DELIM) return if self.pos >= len(self.string): # Identifier terminates string. self.parts.append(self.string[start_pos : self.pos]) return if self.string[self.pos] != GROUP_OPEN: # Argument list not opened. self.parts.append(self.string[start_pos : self.pos]) return # Skip past opening brace and try to parse an argument list. self.pos += 1 args = self.parse_argument_list() if self.pos >= len(self.string) or self.string[self.pos] != GROUP_CLOSE: # Arguments unclosed. self.parts.append(self.string[start_pos : self.pos]) return self.pos += 1 # Move past closing brace. self.parts.append(Call(ident, args, self.string[start_pos : self.pos])) def parse_argument_list(self): """Parse a list of arguments starting at ``pos``, returning a list of Expression objects. Does not modify ``parts``. Should leave ``pos`` pointing to a } character or the end of the string. """ # Try to parse a subexpression in a subparser. expressions = [] while self.pos < len(self.string): subparser = Parser(self.string[self.pos :], in_argument=True) subparser.parse_expression() # Extract and advance past the parsed expression. expressions.append(Expression(subparser.parts)) self.pos += subparser.pos if ( self.pos >= len(self.string) or self.string[self.pos] == GROUP_CLOSE ): # Argument list terminated by EOF or closing brace. break # Only other way to terminate an expression is with ,. # Continue to the next argument. assert self.string[self.pos] == ARG_SEP self.pos += 1 return expressions def _parse_ident(self): """Parse an identifier and return it (possibly an empty string). Updates ``pos``. """ remainder = self.string[self.pos :] ident = re.match(r"\w*", remainder).group(0) self.pos += len(ident) return ident def _parse(template): """Parse a top-level template string Expression. Any extraneous text is considered literal text. """ parser = Parser(template) parser.parse_expression() parts = parser.parts remainder = parser.string[parser.pos :] if remainder: parts.append(remainder) return Expression(parts) @functools.lru_cache(maxsize=128) def template(fmt): return Template(fmt) # External interface. class Template: """A string template, including text, Symbols, and Calls.""" def __init__(self, template): self.expr = _parse(template) self.original = template self.compiled = self.translate() def __eq__(self, other): return self.original == other.original def interpret(self, values={}, functions={}): """Like `substitute`, but forces the interpreter (rather than the compiled version) to be used. The interpreter includes exception-handling code for missing variables and buggy template functions but is much slower. """ return self.expr.evaluate(Environment(values, functions)) def substitute(self, values={}, functions={}): """Evaluate the template given the values and functions.""" try: res = self.compiled(values, functions) except Exception: # Handle any exceptions thrown by compiled version. res = self.interpret(values, functions) return res def translate(self): """Compile the template to a Python function.""" expressions, varnames, funcnames = self.expr.translate() argnames = [] for varname in varnames: argnames.append(VARIABLE_PREFIX + varname) for funcname in funcnames: argnames.append(FUNCTION_PREFIX + funcname) func = compile_func( argnames, [ast.Return(ast.List(expressions, ast.Load()))], ) def wrapper_func(values={}, functions={}): args = {} for varname in varnames: args[VARIABLE_PREFIX + varname] = values[varname] for funcname in funcnames: args[FUNCTION_PREFIX + funcname] = functions[funcname] parts = func(**args) return "".join(parts) return wrapper_func # Performance tests. if __name__ == "__main__": import timeit _tmpl = Template("foo $bar %baz{foozle $bar barzle} $bar") _vars = {"bar": "qux"} _funcs = {"baz": str.upper} interp_time = timeit.timeit( "_tmpl.interpret(_vars, _funcs)", "from __main__ import _tmpl, _vars, _funcs", number=10000, ) print(interp_time) comp_time = timeit.timeit( "_tmpl.substitute(_vars, _funcs)", "from __main__ import _tmpl, _vars, _funcs", number=10000, ) print(comp_time) print("Speedup:", interp_time / comp_time) beetbox-beets-01f1faf/beets/util/hidden.py000066400000000000000000000040761472325477400206420ustar00rootroot00000000000000# This file is part of beets. # Copyright 2016, Adrian Sampson. # Copyright 2024, Arav K. # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the # "Software"), to deal in the Software without restriction, including # without limitation the rights to use, copy, modify, merge, publish, # distribute, sublicense, and/or sell copies of the Software, and to # permit persons to whom the Software is furnished to do so, subject to # the following conditions: # # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. """Simple library to work out if a file is hidden on different platforms.""" import ctypes import os import stat import sys from pathlib import Path from typing import Union def is_hidden(path: Union[bytes, Path]) -> bool: """ Determine whether the given path is treated as a 'hidden file' by the OS. """ if isinstance(path, bytes): path = Path(os.fsdecode(path)) # TODO: Avoid doing a platform check on every invocation of the function. # TODO: Stop supporting 'bytes' inputs once 'pathlib' is fully integrated. if sys.platform == "win32": # On Windows, we check for an FS-provided attribute. # FILE_ATTRIBUTE_HIDDEN = 2 (0x2) from GetFileAttributes documentation. hidden_mask = 2 # Retrieve the attributes for the file. attrs = ctypes.windll.kernel32.GetFileAttributesW(str(path)) # Ensure the attribute mask is valid. if attrs < 0: return False # Check for the hidden attribute. return attrs & hidden_mask # On OS X, we check for an FS-provided attribute. if sys.platform == "darwin": if hasattr(os.stat_result, "st_flags") and hasattr(stat, "UF_HIDDEN"): if path.lstat().st_flags & stat.UF_HIDDEN: return True # On all non-Windows platforms, we check for a '.'-prefixed file name. if path.name.startswith("."): return True return False beetbox-beets-01f1faf/beets/util/id_extractors.py000066400000000000000000000047241472325477400222610ustar00rootroot00000000000000# This file is part of beets. # Copyright 2016, Adrian Sampson. # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the # "Software"), to deal in the Software without restriction, including # without limitation the rights to use, copy, modify, merge, publish, # distribute, sublicense, and/or sell copies of the Software, and to # permit persons to whom the Software is furnished to do so, subject to # the following conditions: # # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. """Helpers around the extraction of album/track ID's from metadata sources.""" import re # Spotify IDs consist of 22 alphanumeric characters # (zero-left-padded base62 representation of randomly generated UUID4) spotify_id_regex = { "pattern": r"(^|open\.spotify\.com/{}/)([0-9A-Za-z]{{22}})", "match_group": 2, } deezer_id_regex = { "pattern": r"(^|deezer\.com/)([a-z]*/)?({}/)?(\d+)", "match_group": 4, } beatport_id_regex = { "pattern": r"(^|beatport\.com/release/.+/)(\d+)$", "match_group": 2, } # A note on Bandcamp: There is no such thing as a Bandcamp album or artist ID, # the URL can be used as the identifier. The Bandcamp metadata source plugin # works that way - https://github.com/snejus/beetcamp. Bandcamp album # URLs usually look like: https://nameofartist.bandcamp.com/album/nameofalbum def extract_discogs_id_regex(album_id): """Returns the Discogs_id or None.""" # Discogs-IDs are simple integers. In order to avoid confusion with # other metadata plugins, we only look for very specific formats of the # input string: # - plain integer, optionally wrapped in brackets and prefixed by an # 'r', as this is how discogs displays the release ID on its webpage. # - legacy url format: discogs.com//release/ # - legacy url short format: discogs.com/release/ # - current url format: discogs.com/release/- # See #291, #4080 and #4085 for the discussions leading up to these # patterns. # Regex has been tested here https://regex101.com/r/TOu7kw/1 for pattern in [ r"^\[?r?(?P\d+)\]?$", r"discogs\.com/release/(?P\d+)-?", r"discogs\.com/[^/]+/release/(?P\d+)", ]: match = re.search(pattern, album_id) if match: return int(match.group("id")) return None beetbox-beets-01f1faf/beets/util/m3u.py000066400000000000000000000070631472325477400201120ustar00rootroot00000000000000# This file is part of beets. # Copyright 2022, J0J0 Todos. # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the # "Software"), to deal in the Software without restriction, including # without limitation the rights to use, copy, modify, merge, publish, # distribute, sublicense, and/or sell copies of the Software, and to # permit persons to whom the Software is furnished to do so, subject to # the following conditions: # # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. """Provides utilities to read, write and manipulate m3u playlist files.""" import traceback from beets.util import FilesystemError, mkdirall, normpath, syspath class EmptyPlaylistError(Exception): """Raised when a playlist file without media files is saved or loaded.""" pass class M3UFile: """Reads and writes m3u or m3u8 playlist files.""" def __init__(self, path): """``path`` is the absolute path to the playlist file. The playlist file type, m3u or m3u8 is determined by 1) the ending being m3u8 and 2) the file paths contained in the list being utf-8 encoded. Since the list is passed from the outside, this is currently out of control of this class. """ self.path = path self.extm3u = False self.media_list = [] def load(self): """Reads the m3u file from disk and sets the object's attributes.""" pl_normpath = normpath(self.path) try: with open(syspath(pl_normpath), "rb") as pl_file: raw_contents = pl_file.readlines() except OSError as exc: raise FilesystemError( exc, "read", (pl_normpath,), traceback.format_exc() ) self.extm3u = True if raw_contents[0].rstrip() == b"#EXTM3U" else False for line in raw_contents[1:]: if line.startswith(b"#"): # Support for specific EXTM3U comments could be added here. continue self.media_list.append(normpath(line.rstrip())) if not self.media_list: raise EmptyPlaylistError def set_contents(self, media_list, extm3u=True): """Sets self.media_list to a list of media file paths. Also sets additional flags, changing the final m3u-file's format. ``media_list`` is a list of paths to media files that should be added to the playlist (relative or absolute paths, that's the responsibility of the caller). By default the ``extm3u`` flag is set, to ensure a save-operation writes an m3u-extended playlist (comment "#EXTM3U" at the top of the file). """ self.media_list = media_list self.extm3u = extm3u def write(self): """Writes the m3u file to disk. Handles the creation of potential parent directories. """ header = [b"#EXTM3U"] if self.extm3u else [] if not self.media_list: raise EmptyPlaylistError contents = header + self.media_list pl_normpath = normpath(self.path) mkdirall(pl_normpath) try: with open(syspath(pl_normpath), "wb") as pl_file: for line in contents: pl_file.write(line + b"\n") pl_file.write(b"\n") # Final linefeed to prevent noeol file. except OSError as exc: raise FilesystemError( exc, "create", (pl_normpath,), traceback.format_exc() ) beetbox-beets-01f1faf/beets/util/pipeline.py000066400000000000000000000376771472325477400212310ustar00rootroot00000000000000# This file is part of beets. # Copyright 2016, Adrian Sampson. # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the # "Software"), to deal in the Software without restriction, including # without limitation the rights to use, copy, modify, merge, publish, # distribute, sublicense, and/or sell copies of the Software, and to # permit persons to whom the Software is furnished to do so, subject to # the following conditions: # # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. """Simple but robust implementation of generator/coroutine-based pipelines in Python. The pipelines may be run either sequentially (single-threaded) or in parallel (one thread per pipeline stage). This implementation supports pipeline bubbles (indications that the processing for a certain item should abort). To use them, yield the BUBBLE constant from any stage coroutine except the last. In the parallel case, the implementation transparently handles thread shutdown when the processing is complete and when a stage raises an exception. KeyboardInterrupts (^C) are also handled. When running a parallel pipeline, it is also possible to use multiple coroutines for the same pipeline stage; this lets you speed up a bottleneck stage by dividing its work among multiple threads. To do so, pass an iterable of coroutines to the Pipeline constructor in place of any single coroutine. """ import queue import sys from threading import Lock, Thread BUBBLE = "__PIPELINE_BUBBLE__" POISON = "__PIPELINE_POISON__" DEFAULT_QUEUE_SIZE = 16 def _invalidate_queue(q, val=None, sync=True): """Breaks a Queue such that it never blocks, always has size 1, and has no maximum size. get()ing from the queue returns `val`, which defaults to None. `sync` controls whether a lock is required (because it's not reentrant!). """ def _qsize(len=len): return 1 def _put(item): pass def _get(): return val if sync: q.mutex.acquire() try: # Originally, we set `maxsize` to 0 here, which is supposed to mean # an unlimited queue size. However, there is a race condition since # Python 3.2 when this attribute is changed while another thread is # waiting in put()/get() due to a full/empty queue. # Setting it to 2 is still hacky because Python does not give any # guarantee what happens if Queue methods/attributes are overwritten # when it is already in use. However, because of our dummy _put() # and _get() methods, it provides a workaround to let the queue appear # to be never empty or full. # See issue https://github.com/beetbox/beets/issues/2078 q.maxsize = 2 q._qsize = _qsize q._put = _put q._get = _get q.not_empty.notify_all() q.not_full.notify_all() finally: if sync: q.mutex.release() class CountedQueue(queue.Queue): """A queue that keeps track of the number of threads that are still feeding into it. The queue is poisoned when all threads are finished with the queue. """ def __init__(self, maxsize=0): queue.Queue.__init__(self, maxsize) self.nthreads = 0 self.poisoned = False def acquire(self): """Indicate that a thread will start putting into this queue. Should not be called after the queue is already poisoned. """ with self.mutex: assert not self.poisoned assert self.nthreads >= 0 self.nthreads += 1 def release(self): """Indicate that a thread that was putting into this queue has exited. If this is the last thread using the queue, the queue is poisoned. """ with self.mutex: self.nthreads -= 1 assert self.nthreads >= 0 if self.nthreads == 0: # All threads are done adding to this queue. Poison it # when it becomes empty. self.poisoned = True # Replacement _get invalidates when no items remain. _old_get = self._get def _get(): out = _old_get() if not self.queue: _invalidate_queue(self, POISON, False) return out if self.queue: # Items remain. self._get = _get else: # No items. Invalidate immediately. _invalidate_queue(self, POISON, False) class MultiMessage: """A message yielded by a pipeline stage encapsulating multiple values to be sent to the next stage. """ def __init__(self, messages): self.messages = messages def multiple(messages): """Yield multiple([message, ..]) from a pipeline stage to send multiple values to the next pipeline stage. """ return MultiMessage(messages) def stage(func): """Decorate a function to become a simple stage. >>> @stage ... def add(n, i): ... return i + n >>> pipe = Pipeline([ ... iter([1, 2, 3]), ... add(2), ... ]) >>> list(pipe.pull()) [3, 4, 5] """ def coro(*args): task = None while True: task = yield task task = func(*(args + (task,))) return coro def mutator_stage(func): """Decorate a function that manipulates items in a coroutine to become a simple stage. >>> @mutator_stage ... def setkey(key, item): ... item[key] = True >>> pipe = Pipeline([ ... iter([{'x': False}, {'a': False}]), ... setkey('x'), ... ]) >>> list(pipe.pull()) [{'x': True}, {'a': False, 'x': True}] """ def coro(*args): task = None while True: task = yield task func(*(args + (task,))) return coro def _allmsgs(obj): """Returns a list of all the messages encapsulated in obj. If obj is a MultiMessage, returns its enclosed messages. If obj is BUBBLE, returns an empty list. Otherwise, returns a list containing obj. """ if isinstance(obj, MultiMessage): return obj.messages elif obj == BUBBLE: return [] else: return [obj] class PipelineThread(Thread): """Abstract base class for pipeline-stage threads.""" def __init__(self, all_threads): super().__init__() self.abort_lock = Lock() self.abort_flag = False self.all_threads = all_threads self.exc_info = None def abort(self): """Shut down the thread at the next chance possible.""" with self.abort_lock: self.abort_flag = True # Ensure that we are not blocking on a queue read or write. if hasattr(self, "in_queue"): _invalidate_queue(self.in_queue, POISON) if hasattr(self, "out_queue"): _invalidate_queue(self.out_queue, POISON) def abort_all(self, exc_info): """Abort all other threads in the system for an exception.""" self.exc_info = exc_info for thread in self.all_threads: thread.abort() class FirstPipelineThread(PipelineThread): """The thread running the first stage in a parallel pipeline setup. The coroutine should just be a generator. """ def __init__(self, coro, out_queue, all_threads): super().__init__(all_threads) self.coro = coro self.out_queue = out_queue self.out_queue.acquire() def run(self): try: while True: with self.abort_lock: if self.abort_flag: return # Get the value from the generator. try: msg = next(self.coro) except StopIteration: break # Send messages to the next stage. for msg in _allmsgs(msg): with self.abort_lock: if self.abort_flag: return self.out_queue.put(msg) except BaseException: self.abort_all(sys.exc_info()) return # Generator finished; shut down the pipeline. self.out_queue.release() class MiddlePipelineThread(PipelineThread): """A thread running any stage in the pipeline except the first or last. """ def __init__(self, coro, in_queue, out_queue, all_threads): super().__init__(all_threads) self.coro = coro self.in_queue = in_queue self.out_queue = out_queue self.out_queue.acquire() def run(self): try: # Prime the coroutine. next(self.coro) while True: with self.abort_lock: if self.abort_flag: return # Get the message from the previous stage. msg = self.in_queue.get() if msg is POISON: break with self.abort_lock: if self.abort_flag: return # Invoke the current stage. out = self.coro.send(msg) # Send messages to next stage. for msg in _allmsgs(out): with self.abort_lock: if self.abort_flag: return self.out_queue.put(msg) except BaseException: self.abort_all(sys.exc_info()) return # Pipeline is shutting down normally. self.out_queue.release() class LastPipelineThread(PipelineThread): """A thread running the last stage in a pipeline. The coroutine should yield nothing. """ def __init__(self, coro, in_queue, all_threads): super().__init__(all_threads) self.coro = coro self.in_queue = in_queue def run(self): # Prime the coroutine. next(self.coro) try: while True: with self.abort_lock: if self.abort_flag: return # Get the message from the previous stage. msg = self.in_queue.get() if msg is POISON: break with self.abort_lock: if self.abort_flag: return # Send to consumer. self.coro.send(msg) except BaseException: self.abort_all(sys.exc_info()) return class Pipeline: """Represents a staged pattern of work. Each stage in the pipeline is a coroutine that receives messages from the previous stage and yields messages to be sent to the next stage. """ def __init__(self, stages): """Makes a new pipeline from a list of coroutines. There must be at least two stages. """ if len(stages) < 2: raise ValueError("pipeline must have at least two stages") self.stages = [] for stage in stages: if isinstance(stage, (list, tuple)): self.stages.append(stage) else: # Default to one thread per stage. self.stages.append((stage,)) def run_sequential(self): """Run the pipeline sequentially in the current thread. The stages are run one after the other. Only the first coroutine in each stage is used. """ list(self.pull()) def run_parallel(self, queue_size=DEFAULT_QUEUE_SIZE): """Run the pipeline in parallel using one thread per stage. The messages between the stages are stored in queues of the given size. """ queue_count = len(self.stages) - 1 queues = [CountedQueue(queue_size) for i in range(queue_count)] threads = [] # Set up first stage. for coro in self.stages[0]: threads.append(FirstPipelineThread(coro, queues[0], threads)) # Middle stages. for i in range(1, queue_count): for coro in self.stages[i]: threads.append( MiddlePipelineThread( coro, queues[i - 1], queues[i], threads ) ) # Last stage. for coro in self.stages[-1]: threads.append(LastPipelineThread(coro, queues[-1], threads)) # Start threads. for thread in threads: thread.start() # Wait for termination. The final thread lasts the longest. try: # Using a timeout allows us to receive KeyboardInterrupt # exceptions during the join(). while threads[-1].is_alive(): threads[-1].join(1) except BaseException: # Stop all the threads immediately. for thread in threads: thread.abort() raise finally: # Make completely sure that all the threads have finished # before we return. They should already be either finished, # in normal operation, or aborted, in case of an exception. for thread in threads[:-1]: thread.join() for thread in threads: exc_info = thread.exc_info if exc_info: # Make the exception appear as it was raised originally. raise exc_info[1].with_traceback(exc_info[2]) def pull(self): """Yield elements from the end of the pipeline. Runs the stages sequentially until the last yields some messages. Each of the messages is then yielded by ``pulled.next()``. If the pipeline has a consumer, that is the last stage does not yield any messages, then pull will not yield any messages. Only the first coroutine in each stage is used """ coros = [stage[0] for stage in self.stages] # "Prime" the coroutines. for coro in coros[1:]: next(coro) # Begin the pipeline. for out in coros[0]: msgs = _allmsgs(out) for coro in coros[1:]: next_msgs = [] for msg in msgs: out = coro.send(msg) next_msgs.extend(_allmsgs(out)) msgs = next_msgs for msg in msgs: yield msg # Smoke test. if __name__ == "__main__": import time # Test a normally-terminating pipeline both in sequence and # in parallel. def produce(): for i in range(5): print("generating %i" % i) time.sleep(1) yield i def work(): num = yield while True: print("processing %i" % num) time.sleep(2) num = yield num * 2 def consume(): while True: num = yield time.sleep(1) print("received %i" % num) ts_start = time.time() Pipeline([produce(), work(), consume()]).run_sequential() ts_seq = time.time() Pipeline([produce(), work(), consume()]).run_parallel() ts_par = time.time() Pipeline([produce(), (work(), work()), consume()]).run_parallel() ts_end = time.time() print("Sequential time:", ts_seq - ts_start) print("Parallel time:", ts_par - ts_seq) print("Multiply-parallel time:", ts_end - ts_par) print() # Test a pipeline that raises an exception. def exc_produce(): for i in range(10): print("generating %i" % i) time.sleep(1) yield i def exc_work(): num = yield while True: print("processing %i" % num) time.sleep(3) if num == 3: raise Exception() num = yield num * 2 def exc_consume(): while True: num = yield print("received %i" % num) Pipeline([exc_produce(), exc_work(), exc_consume()]).run_parallel(1) beetbox-beets-01f1faf/beets/vfs.py000066400000000000000000000033741472325477400172300ustar00rootroot00000000000000# This file is part of beets. # Copyright 2016, Adrian Sampson. # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the # "Software"), to deal in the Software without restriction, including # without limitation the rights to use, copy, modify, merge, publish, # distribute, sublicense, and/or sell copies of the Software, and to # permit persons to whom the Software is furnished to do so, subject to # the following conditions: # # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. """A simple utility for constructing filesystem-like trees from beets libraries. """ from typing import Any, Dict, NamedTuple from beets import util class Node(NamedTuple): files: Dict[str, Any] dirs: Dict[str, Any] def _insert(node, path, itemid): """Insert an item into a virtual filesystem node.""" if len(path) == 1: # Last component. Insert file. node.files[path[0]] = itemid else: # In a directory. dirname = path[0] rest = path[1:] if dirname not in node.dirs: node.dirs[dirname] = Node({}, {}) _insert(node.dirs[dirname], rest, itemid) def libtree(lib): """Generates a filesystem-like directory tree for the files contained in `lib`. Filesystem nodes are (files, dirs) named tuples in which both components are dictionaries. The first maps filenames to Item ids. The second maps directory names to child node tuples. """ root = Node({}, {}) for item in lib.items(): dest = item.destination(fragment=True) parts = util.components(dest) _insert(root, parts, item.id) return root beetbox-beets-01f1faf/beetsplug/000077500000000000000000000000001472325477400167415ustar00rootroot00000000000000beetbox-beets-01f1faf/beetsplug/__init__.py000066400000000000000000000014421472325477400210530ustar00rootroot00000000000000# This file is part of beets. # Copyright 2016, Adrian Sampson. # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the # "Software"), to deal in the Software without restriction, including # without limitation the rights to use, copy, modify, merge, publish, # distribute, sublicense, and/or sell copies of the Software, and to # permit persons to whom the Software is furnished to do so, subject to # the following conditions: # # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. """A namespace package for beets plugins.""" # Make this a namespace package. from pkgutil import extend_path __path__ = extend_path(__path__, __name__) beetbox-beets-01f1faf/beetsplug/absubmit.py000066400000000000000000000171371472325477400211320ustar00rootroot00000000000000# This file is part of beets. # Copyright 2016, Pieter Mulder. # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the # "Software"), to deal in the Software without restriction, including # without limitation the rights to use, copy, modify, merge, publish, # distribute, sublicense, and/or sell copies of the Software, and to # permit persons to whom the Software is furnished to do so, subject to # the following conditions: # # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. """Calculate acoustic information and submit to AcousticBrainz.""" import errno import hashlib import json import os import subprocess import tempfile from distutils.spawn import find_executable import requests from beets import plugins, ui, util # We use this field to check whether AcousticBrainz info is present. PROBE_FIELD = "mood_acoustic" class ABSubmitError(Exception): """Raised when failing to analyse file with extractor.""" def call(args): """Execute the command and return its output. Raise a AnalysisABSubmitError on failure. """ try: return util.command_output(args).stdout except subprocess.CalledProcessError as e: raise ABSubmitError( "{} exited with status {}".format(args[0], e.returncode) ) class AcousticBrainzSubmitPlugin(plugins.BeetsPlugin): def __init__(self): super().__init__() self._log.warning("This plugin is deprecated.") self.config.add( {"extractor": "", "force": False, "pretend": False, "base_url": ""} ) self.extractor = self.config["extractor"].as_str() if self.extractor: self.extractor = util.normpath(self.extractor) # Explicit path to extractor if not os.path.isfile(self.extractor): raise ui.UserError( "Extractor command does not exist: {0}.".format( self.extractor ) ) else: # Implicit path to extractor, search for it in path self.extractor = "streaming_extractor_music" try: call([self.extractor]) except OSError: raise ui.UserError( "No extractor command found: please install the extractor" " binary from https://essentia.upf.edu/" ) except ABSubmitError: # Extractor found, will exit with an error if not called with # the correct amount of arguments. pass # Get the executable location on the system, which we need # to calculate the SHA-1 hash. self.extractor = find_executable(self.extractor) # Calculate extractor hash. self.extractor_sha = hashlib.sha1() with open(self.extractor, "rb") as extractor: self.extractor_sha.update(extractor.read()) self.extractor_sha = self.extractor_sha.hexdigest() self.url = "" base_url = self.config["base_url"].as_str() if base_url: if not base_url.startswith("http"): raise ui.UserError( "AcousticBrainz server base URL must start " "with an HTTP scheme" ) elif base_url[-1] != "/": base_url = base_url + "/" self.url = base_url + "{mbid}/low-level" def commands(self): cmd = ui.Subcommand( "absubmit", help="calculate and submit AcousticBrainz analysis" ) cmd.parser.add_option( "-f", "--force", dest="force_refetch", action="store_true", default=False, help="re-download data when already present", ) cmd.parser.add_option( "-p", "--pretend", dest="pretend_fetch", action="store_true", default=False, help="pretend to perform action, but show \ only files which would be processed", ) cmd.func = self.command return [cmd] def command(self, lib, opts, args): if not self.url: raise ui.UserError( "This plugin is deprecated since AcousticBrainz no longer " "accepts new submissions. See the base_url configuration " "option." ) else: # Get items from arguments items = lib.items(ui.decargs(args)) self.opts = opts util.par_map(self.analyze_submit, items) def analyze_submit(self, item): analysis = self._get_analysis(item) if analysis: self._submit_data(item, analysis) def _get_analysis(self, item): mbid = item["mb_trackid"] # Avoid re-analyzing files that already have AB data. if not self.opts.force_refetch and not self.config["force"]: if item.get(PROBE_FIELD): return None # If file has no MBID, skip it. if not mbid: self._log.info( "Not analysing {}, missing " "musicbrainz track id.", item ) return None if self.opts.pretend_fetch or self.config["pretend"]: self._log.info("pretend action - extract item: {}", item) return None # Temporary file to save extractor output to, extractor only works # if an output file is given. Here we use a temporary file to copy # the data into a python object and then remove the file from the # system. tmp_file, filename = tempfile.mkstemp(suffix=".json") try: # Close the file, so the extractor can overwrite it. os.close(tmp_file) try: call([self.extractor, util.syspath(item.path), filename]) except ABSubmitError as e: self._log.warning( "Failed to analyse {item} for AcousticBrainz: {error}", item=item, error=e, ) return None with open(filename) as tmp_file: analysis = json.load(tmp_file) # Add the hash to the output. analysis["metadata"]["version"]["essentia_build_sha"] = ( self.extractor_sha ) return analysis finally: try: os.remove(filename) except OSError as e: # ENOENT means file does not exist, just ignore this error. if e.errno != errno.ENOENT: raise def _submit_data(self, item, data): mbid = item["mb_trackid"] headers = {"Content-Type": "application/json"} response = requests.post( self.url.format(mbid=mbid), json=data, headers=headers, timeout=10, ) # Test that request was successful and raise an error on failure. if response.status_code != 200: try: message = response.json()["message"] except (ValueError, KeyError) as e: message = f"unable to get error message: {e}" self._log.error( "Failed to submit AcousticBrainz analysis of {item}: " "{message}).", item=item, message=message, ) else: self._log.debug( "Successfully submitted AcousticBrainz analysis " "for {}.", item, ) beetbox-beets-01f1faf/beetsplug/acousticbrainz.py000066400000000000000000000267261472325477400223500ustar00rootroot00000000000000# This file is part of beets. # Copyright 2015-2016, Ohm Patel. # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the # "Software"), to deal in the Software without restriction, including # without limitation the rights to use, copy, modify, merge, publish, # distribute, sublicense, and/or sell copies of the Software, and to # permit persons to whom the Software is furnished to do so, subject to # the following conditions: # # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. """Fetch various AcousticBrainz metadata using MBID.""" from collections import defaultdict import requests from beets import plugins, ui from beets.dbcore import types LEVELS = ["/low-level", "/high-level"] ABSCHEME = { "highlevel": { "danceability": {"all": {"danceable": "danceable"}}, "gender": {"value": "gender"}, "genre_rosamerica": {"value": "genre_rosamerica"}, "mood_acoustic": {"all": {"acoustic": "mood_acoustic"}}, "mood_aggressive": {"all": {"aggressive": "mood_aggressive"}}, "mood_electronic": {"all": {"electronic": "mood_electronic"}}, "mood_happy": {"all": {"happy": "mood_happy"}}, "mood_party": {"all": {"party": "mood_party"}}, "mood_relaxed": {"all": {"relaxed": "mood_relaxed"}}, "mood_sad": {"all": {"sad": "mood_sad"}}, "moods_mirex": {"value": "moods_mirex"}, "ismir04_rhythm": {"value": "rhythm"}, "tonal_atonal": {"all": {"tonal": "tonal"}}, "timbre": {"value": "timbre"}, "voice_instrumental": {"value": "voice_instrumental"}, }, "lowlevel": {"average_loudness": "average_loudness"}, "rhythm": {"bpm": "bpm"}, "tonal": { "chords_changes_rate": "chords_changes_rate", "chords_key": "chords_key", "chords_number_rate": "chords_number_rate", "chords_scale": "chords_scale", "key_key": ("initial_key", 0), "key_scale": ("initial_key", 1), "key_strength": "key_strength", }, } class AcousticPlugin(plugins.BeetsPlugin): item_types = { "average_loudness": types.Float(6), "chords_changes_rate": types.Float(6), "chords_key": types.STRING, "chords_number_rate": types.Float(6), "chords_scale": types.STRING, "danceable": types.Float(6), "gender": types.STRING, "genre_rosamerica": types.STRING, "initial_key": types.STRING, "key_strength": types.Float(6), "mood_acoustic": types.Float(6), "mood_aggressive": types.Float(6), "mood_electronic": types.Float(6), "mood_happy": types.Float(6), "mood_party": types.Float(6), "mood_relaxed": types.Float(6), "mood_sad": types.Float(6), "moods_mirex": types.STRING, "rhythm": types.Float(6), "timbre": types.STRING, "tonal": types.Float(6), "voice_instrumental": types.STRING, } def __init__(self): super().__init__() self._log.warning("This plugin is deprecated.") self.config.add( {"auto": True, "force": False, "tags": [], "base_url": ""} ) self.base_url = self.config["base_url"].as_str() if self.base_url: if not self.base_url.startswith("http"): raise ui.UserError( "AcousticBrainz server base URL must start " "with an HTTP scheme" ) elif self.base_url[-1] != "/": self.base_url = self.base_url + "/" if self.config["auto"]: self.register_listener("import_task_files", self.import_task_files) def commands(self): cmd = ui.Subcommand( "acousticbrainz", help="fetch metadata from AcousticBrainz" ) cmd.parser.add_option( "-f", "--force", dest="force_refetch", action="store_true", default=False, help="re-download data when already present", ) def func(lib, opts, args): items = lib.items(ui.decargs(args)) self._fetch_info( items, ui.should_write(), opts.force_refetch or self.config["force"], ) cmd.func = func return [cmd] def import_task_files(self, session, task): """Function is called upon beet import.""" self._fetch_info(task.imported_items(), False, True) def _get_data(self, mbid): if not self.base_url: raise ui.UserError( "This plugin is deprecated since AcousticBrainz has shut " "down. See the base_url configuration option." ) data = {} for url in _generate_urls(self.base_url, mbid): self._log.debug("fetching URL: {}", url) try: res = requests.get(url, timeout=10) except requests.RequestException as exc: self._log.info("request error: {}", exc) return {} if res.status_code == 404: self._log.info("recording ID {} not found", mbid) return {} try: data.update(res.json()) except ValueError: self._log.debug("Invalid Response: {}", res.text) return {} return data def _fetch_info(self, items, write, force): """Fetch additional information from AcousticBrainz for the `item`s.""" tags = self.config["tags"].as_str_seq() for item in items: # If we're not forcing re-downloading for all tracks, check # whether the data is already present. We use one # representative field name to check for previously fetched # data. if not force: mood_str = item.get("mood_acoustic", "") if mood_str: self._log.info("data already present for: {}", item) continue # We can only fetch data for tracks with MBIDs. if not item.mb_trackid: continue self._log.info("getting data for: {}", item) data = self._get_data(item.mb_trackid) if data: for attr, val in self._map_data_to_scheme(data, ABSCHEME): if not tags or attr in tags: self._log.debug( "attribute {} of {} set to {}", attr, item, val ) setattr(item, attr, val) else: self._log.debug( "skipping attribute {} of {}" " (value {}) due to config", attr, item, val, ) item.store() if write: item.try_write() def _map_data_to_scheme(self, data, scheme): """Given `data` as a structure of nested dictionaries, and `scheme` as a structure of nested dictionaries , `yield` tuples `(attr, val)` where `attr` and `val` are corresponding leaf nodes in `scheme` and `data`. As its name indicates, `scheme` defines how the data is structured, so this function tries to find leaf nodes in `data` that correspond to the leafs nodes of `scheme`, and not the other way around. Leaf nodes of `data` that do not exist in the `scheme` do not matter. If a leaf node of `scheme` is not present in `data`, no value is yielded for that attribute and a simple warning is issued. Finally, to account for attributes of which the value is split between several leaf nodes in `data`, leaf nodes of `scheme` can be tuples `(attr, order)` where `attr` is the attribute to which the leaf node belongs, and `order` is the place at which it should appear in the value. The different `value`s belonging to the same `attr` are simply joined with `' '`. This is hardcoded and not very flexible, but it gets the job done. For example: >>> scheme = { 'key1': 'attribute', 'key group': { 'subkey1': 'subattribute', 'subkey2': ('composite attribute', 0) }, 'key2': ('composite attribute', 1) } >>> data = { 'key1': 'value', 'key group': { 'subkey1': 'subvalue', 'subkey2': 'part 1 of composite attr' }, 'key2': 'part 2' } >>> print(list(_map_data_to_scheme(data, scheme))) [('subattribute', 'subvalue'), ('attribute', 'value'), ('composite attribute', 'part 1 of composite attr part 2')] """ # First, we traverse `scheme` and `data`, `yield`ing all the non # composites attributes straight away and populating the dictionary # `composites` with the composite attributes. # When we are finished traversing `scheme`, `composites` should # map each composite attribute to an ordered list of the values # belonging to the attribute, for example: # `composites = {'initial_key': ['B', 'minor']}`. # The recursive traversal. composites = defaultdict(list) yield from self._data_to_scheme_child(data, scheme, composites) # When composites has been populated, yield the composite attributes # by joining their parts. for composite_attr, value_parts in composites.items(): yield composite_attr, " ".join(value_parts) def _data_to_scheme_child(self, subdata, subscheme, composites): """The recursive business logic of :meth:`_map_data_to_scheme`: Traverse two structures of nested dictionaries in parallel and `yield` tuples of corresponding leaf nodes. If a leaf node belongs to a composite attribute (is a `tuple`), populate `composites` rather than yielding straight away. All the child functions for a single traversal share the same `composites` instance, which is passed along. """ for k, v in subscheme.items(): if k in subdata: if isinstance(v, dict): yield from self._data_to_scheme_child( subdata[k], v, composites ) elif isinstance(v, tuple): composite_attribute, part_number = v attribute_parts = composites[composite_attribute] # Parts are not guaranteed to be inserted in order while len(attribute_parts) <= part_number: attribute_parts.append("") attribute_parts[part_number] = subdata[k] else: yield v, subdata[k] else: self._log.warning( "Acousticbrainz did not provide info " "about {}", k ) self._log.debug( "Data {} could not be mapped to scheme {} " "because key {} was not found", subdata, v, k, ) def _generate_urls(base_url, mbid): """Generates AcousticBrainz end point urls for given `mbid`.""" for level in LEVELS: yield base_url + mbid + level beetbox-beets-01f1faf/beetsplug/advancedrewrite.py000066400000000000000000000154771472325477400225000ustar00rootroot00000000000000# This file is part of beets. # Copyright 2023, Max Rumpf. # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the # "Software"), to deal in the Software without restriction, including # without limitation the rights to use, copy, modify, merge, publish, # distribute, sublicense, and/or sell copies of the Software, and to # permit persons to whom the Software is furnished to do so, subject to # the following conditions: # # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. """Plugin to rewrite fields based on a given query.""" import re import shlex from collections import defaultdict import confuse from beets.dbcore import AndQuery, query_from_strings from beets.dbcore.types import MULTI_VALUE_DSV from beets.library import Album, Item from beets.plugins import BeetsPlugin from beets.ui import UserError def rewriter(field, simple_rules, advanced_rules): """Template field function factory. Create a template field function that rewrites the given field with the given rewriting rules. ``simple_rules`` must be a list of (pattern, replacement) pairs. ``advanced_rules`` must be a list of (query, replacement) pairs. """ def fieldfunc(item): value = item._values_fixed[field] for pattern, replacement in simple_rules: if pattern.match(value.lower()): # Rewrite activated. return replacement for query, replacement in advanced_rules: if query.match(item): # Rewrite activated. return replacement # Not activated; return original value. return value return fieldfunc class AdvancedRewritePlugin(BeetsPlugin): """Plugin to rewrite fields based on a given query.""" def __init__(self): """Parse configuration and register template fields for rewriting.""" super().__init__() template = confuse.Sequence( confuse.OneOf( [ confuse.MappingValues(str), { "match": str, "replacements": confuse.MappingValues( confuse.OneOf([str, confuse.Sequence(str)]), ), }, ] ) ) # Used to apply the same rewrite to the corresponding album field. corresponding_album_fields = { "artist": "albumartist", "artists": "albumartists", "artist_sort": "albumartist_sort", "artists_sort": "albumartists_sort", } # Gather all the rewrite rules for each field. class RulesContainer: def __init__(self): self.simple = [] self.advanced = [] rules = defaultdict(RulesContainer) for rule in self.config.get(template): if "match" not in rule: # Simple syntax if len(rule) != 1: raise UserError( "Simple rewrites must have only one rule, " "but found multiple entries. " "Did you forget to prepend a dash (-)?" ) key, value = next(iter(rule.items())) try: fieldname, pattern = key.split(None, 1) except ValueError: raise UserError( f"Invalid simple rewrite specification {key}" ) if fieldname not in Item._fields: raise UserError( f"invalid field name {fieldname} in rewriter" ) self._log.debug( f"adding simple rewrite '{pattern}' → '{value}' " f"for field {fieldname}" ) pattern = re.compile(pattern.lower()) rules[fieldname].simple.append((pattern, value)) # Apply the same rewrite to the corresponding album field. if fieldname in corresponding_album_fields: album_fieldname = corresponding_album_fields[fieldname] rules[album_fieldname].simple.append((pattern, value)) else: # Advanced syntax match = rule["match"] replacements = rule["replacements"] if len(replacements) == 0: raise UserError( "Advanced rewrites must have at least one replacement" ) query = query_from_strings( AndQuery, Item, prefixes={}, query_parts=shlex.split(match), ) for fieldname, replacement in replacements.items(): if fieldname not in Item._fields: raise UserError( f"Invalid field name {fieldname} in rewriter" ) self._log.debug( f"adding advanced rewrite to '{replacement}' " f"for field {fieldname}" ) if isinstance(replacement, list): if Item._fields[fieldname] is not MULTI_VALUE_DSV: raise UserError( f"Field {fieldname} is not a multi-valued field " f"but a list was given: {', '.join(replacement)}" ) elif isinstance(replacement, str): if Item._fields[fieldname] is MULTI_VALUE_DSV: replacement = [replacement] else: raise UserError( f"Invalid type of replacement {replacement} " f"for field {fieldname}" ) rules[fieldname].advanced.append((query, replacement)) # Apply the same rewrite to the corresponding album field. if fieldname in corresponding_album_fields: album_fieldname = corresponding_album_fields[fieldname] rules[album_fieldname].advanced.append( (query, replacement) ) # Replace each template field with the new rewriter function. for fieldname, fieldrules in rules.items(): getter = rewriter(fieldname, fieldrules.simple, fieldrules.advanced) self.template_fields[fieldname] = getter if fieldname in Album._fields: self.album_template_fields[fieldname] = getter beetbox-beets-01f1faf/beetsplug/albumtypes.py000066400000000000000000000044741472325477400215110ustar00rootroot00000000000000# This file is part of beets. # Copyright 2021, Edgars Supe. # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the # "Software"), to deal in the Software without restriction, including # without limitation the rights to use, copy, modify, merge, publish, # distribute, sublicense, and/or sell copies of the Software, and to # permit persons to whom the Software is furnished to do so, subject to # the following conditions: # # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. """Adds an album template field for formatted album types.""" from beets.autotag.mb import VARIOUS_ARTISTS_ID from beets.library import Album from beets.plugins import BeetsPlugin class AlbumTypesPlugin(BeetsPlugin): """Adds an album template field for formatted album types.""" def __init__(self): """Init AlbumTypesPlugin.""" super().__init__() self.album_template_fields["atypes"] = self._atypes self.config.add( { "types": [ ("ep", "EP"), ("single", "Single"), ("soundtrack", "OST"), ("live", "Live"), ("compilation", "Anthology"), ("remix", "Remix"), ], "ignore_va": ["compilation"], "bracket": "[]", } ) def _atypes(self, item: Album): """Returns a formatted string based on album's types.""" types = self.config["types"].as_pairs() ignore_va = self.config["ignore_va"].as_str_seq() bracket = self.config["bracket"].as_str() # Assign a left and right bracket or leave blank if argument is empty. if len(bracket) == 2: bracket_l = bracket[0] bracket_r = bracket[1] else: bracket_l = "" bracket_r = "" res = "" albumtypes = item.albumtypes is_va = item.mb_albumartistid == VARIOUS_ARTISTS_ID for type in types: if type[0] in albumtypes and type[1]: if not is_va or (type[0] not in ignore_va and is_va): res += f"{bracket_l}{type[1]}{bracket_r}" return res beetbox-beets-01f1faf/beetsplug/aura.py000066400000000000000000001011731472325477400202460ustar00rootroot00000000000000# This file is part of beets. # Copyright 2020, Callum Brown. # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the # "Software"), to deal in the Software without restriction, including # without limitation the rights to use, copy, modify, merge, publish, # distribute, sublicense, and/or sell copies of the Software, and to # permit persons to whom the Software is furnished to do so, subject to # the following conditions: # # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. """An AURA server using Flask.""" import os import re import sys from dataclasses import dataclass from mimetypes import guess_type from typing import ClassVar, Mapping, Type from flask import ( Blueprint, Flask, current_app, make_response, request, send_file, ) if sys.version_info >= (3, 11): from typing import Self else: from typing_extensions import Self from beets import config from beets.dbcore.query import ( AndQuery, FixedFieldSort, MatchQuery, MultipleSort, NotQuery, RegexpQuery, SlowFieldSort, SQLiteType, ) from beets.library import Album, Item, LibModel, Library from beets.plugins import BeetsPlugin from beets.ui import Subcommand, _open_library # Constants # AURA server information # TODO: Add version information SERVER_INFO = { "aura-version": "0", "server": "beets-aura", "server-version": "0.1", "auth-required": False, "features": ["albums", "artists", "images"], } # Maps AURA Track attribute to beets Item attribute TRACK_ATTR_MAP = { # Required "title": "title", "artist": "artist", # Optional "album": "album", "track": "track", # Track number on album "tracktotal": "tracktotal", "disc": "disc", "disctotal": "disctotal", "year": "year", "month": "month", "day": "day", "bpm": "bpm", "genre": "genre", "recording-mbid": "mb_trackid", # beets trackid is MB recording "track-mbid": "mb_releasetrackid", "composer": "composer", "albumartist": "albumartist", "comments": "comments", # Optional for Audio Metadata # TODO: Support the mimetype attribute, format != mime type # "mimetype": track.format, "duration": "length", "framerate": "samplerate", # I don't think beets has a framecount field # "framecount": ???, "channels": "channels", "bitrate": "bitrate", "bitdepth": "bitdepth", "size": "filesize", } # Maps AURA Album attribute to beets Album attribute ALBUM_ATTR_MAP = { # Required "title": "album", "artist": "albumartist", # Optional "tracktotal": "albumtotal", "disctotal": "disctotal", "year": "year", "month": "month", "day": "day", "genre": "genre", "release-mbid": "mb_albumid", "release-group-mbid": "mb_releasegroupid", } # Maps AURA Artist attribute to beets Item field # Artists are not first-class in beets, so information is extracted from # beets Items. ARTIST_ATTR_MAP = { # Required "name": "artist", # Optional "artist-mbid": "mb_artistid", } @dataclass class AURADocument: """Base class for building AURA documents.""" model_cls: ClassVar[Type[LibModel]] lib: Library args: Mapping[str, str] @classmethod def from_app(cls) -> Self: """Initialise the document using the global app and request.""" return cls(current_app.config["lib"], request.args) @staticmethod def error(status, title, detail): """Make a response for an error following the JSON:API spec. Args: status: An HTTP status code string, e.g. "404 Not Found". title: A short, human-readable summary of the problem. detail: A human-readable explanation specific to this occurrence of the problem. """ document = { "errors": [{"status": status, "title": title, "detail": detail}] } return make_response(document, status) @classmethod def get_attribute_converter(cls, beets_attr: str) -> Type[SQLiteType]: """Work out what data type an attribute should be for beets. Args: beets_attr: The name of the beets attribute, e.g. "title". """ try: # Look for field in list of Album fields # and get python type of database type. # See beets.library.Album and beets.dbcore.types return cls.model_cls._fields[beets_attr].model_type except KeyError: # Fall back to string (NOTE: probably not good) return str def translate_filters(self): """Translate filters from request arguments to a beets Query.""" # The format of each filter key in the request parameter is: # filter[]. This regex extracts . pattern = re.compile(r"filter\[(?P[a-zA-Z0-9_-]+)\]") queries = [] for key, value in self.args.items(): match = pattern.match(key) if match: # Extract attribute name from key aura_attr = match.group("attribute") # Get the beets version of the attribute name beets_attr = self.attribute_map.get(aura_attr, aura_attr) converter = self.get_attribute_converter(beets_attr) value = converter(value) # Add exact match query to list # Use a slow query so it works with all fields queries.append(MatchQuery(beets_attr, value, fast=False)) # NOTE: AURA doesn't officially support multiple queries return AndQuery(queries) def translate_sorts(self, sort_arg): """Translate an AURA sort parameter into a beets Sort. Args: sort_arg: The value of the 'sort' query parameter; a comma separated list of fields to sort by, in order. E.g. "-year,title". """ # Change HTTP query parameter to a list aura_sorts = sort_arg.strip(",").split(",") sorts = [] for aura_attr in aura_sorts: if aura_attr[0] == "-": ascending = False # Remove leading "-" aura_attr = aura_attr[1:] else: # JSON:API default ascending = True # Get the beets version of the attribute name beets_attr = self.attribute_map.get(aura_attr, aura_attr) # Use slow sort so it works with all fields (inc. computed) sorts.append(SlowFieldSort(beets_attr, ascending=ascending)) return MultipleSort(sorts) def paginate(self, collection): """Get a page of the collection and the URL to the next page. Args: collection: The raw data from which resource objects can be built. Could be an sqlite3.Cursor object (tracks and albums) or a list of strings (artists). """ # Pages start from zero page = self.args.get("page", 0, int) # Use page limit defined in config by default. default_limit = config["aura"]["page_limit"].get(int) limit = self.args.get("limit", default_limit, int) # start = offset of first item to return start = page * limit # end = offset of last item + 1 end = start + limit if end > len(collection): end = len(collection) next_url = None else: # Not the last page so work out links.next url if not self.args: # No existing arguments, so current page is 0 next_url = request.url + "?page=1" elif not self.args.get("page", None): # No existing page argument, so add one to the end next_url = request.url + "&page=1" else: # Increment page token by 1 next_url = request.url.replace( f"page={page}", "page={}".format(page + 1) ) # Get only the items in the page range data = [ self.get_resource_object(self.lib, collection[i]) for i in range(start, end) ] return data, next_url def get_included(self, data, include_str): """Build a list of resource objects for inclusion. Args: data: An array of dicts in the form of resource objects. include_str: A comma separated list of resource types to include. E.g. "tracks,images". """ # Change HTTP query parameter to a list to_include = include_str.strip(",").split(",") # Build a list of unique type and id combinations # For each resource object in the primary data, iterate over it's # relationships. If a relationship matches one of the types # requested for inclusion (e.g. "albums") then add each type-id pair # under the "data" key to unique_identifiers, checking first that # it has not already been added. This ensures that no resources are # included more than once. unique_identifiers = [] for res_obj in data: for rel_name, rel_obj in res_obj["relationships"].items(): if rel_name in to_include: # NOTE: Assumes relationship is to-many for identifier in rel_obj["data"]: if identifier not in unique_identifiers: unique_identifiers.append(identifier) # TODO: I think this could be improved included = [] for identifier in unique_identifiers: res_type = identifier["type"] if res_type == "track": track_id = int(identifier["id"]) track = self.lib.get_item(track_id) included.append( TrackDocument.get_resource_object(self.lib, track) ) elif res_type == "album": album_id = int(identifier["id"]) album = self.lib.get_album(album_id) included.append( AlbumDocument.get_resource_object(self.lib, album) ) elif res_type == "artist": artist_id = identifier["id"] included.append( ArtistDocument.get_resource_object(self.lib, artist_id) ) elif res_type == "image": image_id = identifier["id"] included.append( ImageDocument.get_resource_object(self.lib, image_id) ) else: raise ValueError(f"Invalid resource type: {res_type}") return included def all_resources(self): """Build document for /tracks, /albums or /artists.""" query = self.translate_filters() sort_arg = self.args.get("sort", None) if sort_arg: sort = self.translate_sorts(sort_arg) # For each sort field add a query which ensures all results # have a non-empty, non-zero value for that field. for s in sort.sorts: query.subqueries.append( NotQuery( # Match empty fields (^$) or zero fields, (^0$) RegexpQuery(s.field, "(^$|^0$)", fast=False) ) ) else: sort = None # Get information from the library collection = self.get_collection(query=query, sort=sort) # Convert info to AURA form and paginate it data, next_url = self.paginate(collection) document = {"data": data} # If there are more pages then provide a way to access them if next_url: document["links"] = {"next": next_url} # Include related resources for each element in "data" include_str = self.args.get("include", None) if include_str: document["included"] = self.get_included(data, include_str) return document def single_resource_document(self, resource_object): """Build document for a specific requested resource. Args: resource_object: A dictionary in the form of a JSON:API resource object. """ document = {"data": resource_object} include_str = self.args.get("include", None) if include_str: # [document["data"]] is because arg needs to be list document["included"] = self.get_included( [document["data"]], include_str ) return document class TrackDocument(AURADocument): """Class for building documents for /tracks endpoints.""" model_cls = Item attribute_map = TRACK_ATTR_MAP def get_collection(self, query=None, sort=None): """Get Item objects from the library. Args: query: A beets Query object or a beets query string. sort: A beets Sort object. """ return self.lib.items(query, sort) @classmethod def get_attribute_converter(cls, beets_attr: str) -> Type[SQLiteType]: """Work out what data type an attribute should be for beets. Args: beets_attr: The name of the beets attribute, e.g. "title". """ # filesize is a special field (read from disk not db?) if beets_attr == "filesize": return int return super().get_attribute_converter(beets_attr) @staticmethod def get_resource_object(lib: Library, track): """Construct a JSON:API resource object from a beets Item. Args: track: A beets Item object. """ attributes = {} # Use aura => beets attribute map, e.g. size => filesize for aura_attr, beets_attr in TRACK_ATTR_MAP.items(): a = getattr(track, beets_attr) # Only set attribute if it's not None, 0, "", etc. # NOTE: This could result in required attributes not being set if a: attributes[aura_attr] = a # JSON:API one-to-many relationship to parent album relationships = { "artists": {"data": [{"type": "artist", "id": track.artist}]} } # Only add album relationship if not singleton if not track.singleton: relationships["albums"] = { "data": [{"type": "album", "id": str(track.album_id)}] } return { "type": "track", "id": str(track.id), "attributes": attributes, "relationships": relationships, } def single_resource(self, track_id): """Get track from the library and build a document. Args: track_id: The beets id of the track (integer). """ track = self.lib.get_item(track_id) if not track: return self.error( "404 Not Found", "No track with the requested id.", "There is no track with an id of {} in the library.".format( track_id ), ) return self.single_resource_document( self.get_resource_object(self.lib, track) ) class AlbumDocument(AURADocument): """Class for building documents for /albums endpoints.""" model_cls = Album attribute_map = ALBUM_ATTR_MAP def get_collection(self, query=None, sort=None): """Get Album objects from the library. Args: query: A beets Query object or a beets query string. sort: A beets Sort object. """ return self.lib.albums(query, sort) @staticmethod def get_resource_object(lib: Library, album): """Construct a JSON:API resource object from a beets Album. Args: album: A beets Album object. """ attributes = {} # Use aura => beets attribute name map for aura_attr, beets_attr in ALBUM_ATTR_MAP.items(): a = getattr(album, beets_attr) # Only set attribute if it's not None, 0, "", etc. # NOTE: This could mean required attributes are not set if a: attributes[aura_attr] = a # Get beets Item objects for all tracks in the album sorted by # track number. Sorting is not required but it's nice. query = MatchQuery("album_id", album.id) sort = FixedFieldSort("track", ascending=True) tracks = lib.items(query, sort) # JSON:API one-to-many relationship to tracks on the album relationships = { "tracks": { "data": [{"type": "track", "id": str(t.id)} for t in tracks] } } # Add images relationship if album has associated images if album.artpath: path = os.fsdecode(album.artpath) filename = path.split("/")[-1] image_id = f"album-{album.id}-{filename}" relationships["images"] = { "data": [{"type": "image", "id": image_id}] } # Add artist relationship if artist name is same on tracks # Tracks are used to define artists so don't albumartist # Check for all tracks in case some have featured artists if album.albumartist in [t.artist for t in tracks]: relationships["artists"] = { "data": [{"type": "artist", "id": album.albumartist}] } return { "type": "album", "id": str(album.id), "attributes": attributes, "relationships": relationships, } def single_resource(self, album_id): """Get album from the library and build a document. Args: album_id: The beets id of the album (integer). """ album = self.lib.get_album(album_id) if not album: return self.error( "404 Not Found", "No album with the requested id.", "There is no album with an id of {} in the library.".format( album_id ), ) return self.single_resource_document( self.get_resource_object(self.lib, album) ) class ArtistDocument(AURADocument): """Class for building documents for /artists endpoints.""" model_cls = Item attribute_map = ARTIST_ATTR_MAP def get_collection(self, query=None, sort=None): """Get a list of artist names from the library. Args: query: A beets Query object or a beets query string. sort: A beets Sort object. """ # Gets only tracks with matching artist information tracks = self.lib.items(query, sort) collection = [] for track in tracks: # Do not add duplicates if track.artist not in collection: collection.append(track.artist) return collection @staticmethod def get_resource_object(lib: Library, artist_id): """Construct a JSON:API resource object for the given artist. Args: artist_id: A string which is the artist's name. """ # Get tracks where artist field exactly matches artist_id query = MatchQuery("artist", artist_id) tracks = lib.items(query) if not tracks: return None # Get artist information from the first track # NOTE: It could be that the first track doesn't have a # MusicBrainz id but later tracks do, which isn't ideal. attributes = {} # Use aura => beets attribute map, e.g. artist => name for aura_attr, beets_attr in ARTIST_ATTR_MAP.items(): a = getattr(tracks[0], beets_attr) # Only set attribute if it's not None, 0, "", etc. # NOTE: This could mean required attributes are not set if a: attributes[aura_attr] = a relationships = { "tracks": { "data": [{"type": "track", "id": str(t.id)} for t in tracks] } } album_query = MatchQuery("albumartist", artist_id) albums = lib.albums(query=album_query) if len(albums) != 0: relationships["albums"] = { "data": [{"type": "album", "id": str(a.id)} for a in albums] } return { "type": "artist", "id": artist_id, "attributes": attributes, "relationships": relationships, } def single_resource(self, artist_id): """Get info for the requested artist and build a document. Args: artist_id: A string which is the artist's name. """ artist_resource = self.get_resource_object(self.lib, artist_id) if not artist_resource: return self.error( "404 Not Found", "No artist with the requested id.", "There is no artist with an id of {} in the library.".format( artist_id ), ) return self.single_resource_document(artist_resource) def safe_filename(fn): """Check whether a string is a simple (non-path) filename. For example, `foo.txt` is safe because it is a "plain" filename. But `foo/bar.txt` and `../foo.txt` and `.` are all non-safe because they can traverse to other directories other than the current one. """ # Rule out any directories. if os.path.basename(fn) != fn: return False # In single names, rule out Unix directory traversal names. if fn in (".", ".."): return False return True class ImageDocument(AURADocument): """Class for building documents for /images/(id) endpoints.""" model_cls = Album @staticmethod def get_image_path(lib: Library, image_id): """Works out the full path to the image with the given id. Returns None if there is no such image. Args: image_id: A string in the form "--". """ # Split image_id into its constituent parts id_split = image_id.split("-") if len(id_split) < 3: # image_id is not in the required format return None parent_type = id_split[0] parent_id = id_split[1] img_filename = "-".join(id_split[2:]) if not safe_filename(img_filename): return None # Get the path to the directory parent's images are in if parent_type == "album": album = lib.get_album(int(parent_id)) if not album or not album.artpath: return None # Cut the filename off of artpath # This is in preparation for supporting images in the same # directory that are not tracked by beets. artpath = os.fsdecode(album.artpath) dir_path = "/".join(artpath.split("/")[:-1]) else: # Images for other resource types are not supported return None img_path = os.path.join(dir_path, img_filename) # Check the image actually exists if os.path.isfile(img_path): return img_path else: return None @staticmethod def get_resource_object(lib: Library, image_id): """Construct a JSON:API resource object for the given image. Args: image_id: A string in the form "--". """ # Could be called as a static method, so can't use # self.get_image_path() image_path = ImageDocument.get_image_path(lib, image_id) if not image_path: return None attributes = { "role": "cover", "mimetype": guess_type(image_path)[0], "size": os.path.getsize(image_path), } try: from PIL import Image except ImportError: pass else: im = Image.open(image_path) attributes["width"] = im.width attributes["height"] = im.height relationships = {} # Split id into [parent_type, parent_id, filename] id_split = image_id.split("-") relationships[id_split[0] + "s"] = { "data": [{"type": id_split[0], "id": id_split[1]}] } return { "id": image_id, "type": "image", # Remove attributes that are None, 0, "", etc. "attributes": {k: v for k, v in attributes.items() if v}, "relationships": relationships, } def single_resource(self, image_id): """Get info for the requested image and build a document. Args: image_id: A string in the form "--". """ image_resource = self.get_resource_object(self.lib, image_id) if not image_resource: return self.error( "404 Not Found", "No image with the requested id.", "There is no image with an id of {} in the library.".format( image_id ), ) return self.single_resource_document(image_resource) # Initialise flask blueprint aura_bp = Blueprint("aura_bp", __name__) @aura_bp.route("/server") def server_info(): """Respond with info about the server.""" return {"data": {"type": "server", "id": "0", "attributes": SERVER_INFO}} # Track endpoints @aura_bp.route("/tracks") def all_tracks(): """Respond with a list of all tracks and related information.""" return TrackDocument.from_app().all_resources() @aura_bp.route("/tracks/") def single_track(track_id): """Respond with info about the specified track. Args: track_id: The id of the track provided in the URL (integer). """ return TrackDocument.from_app().single_resource(track_id) @aura_bp.route("/tracks//audio") def audio_file(track_id): """Supply an audio file for the specified track. Args: track_id: The id of the track provided in the URL (integer). """ track = current_app.config["lib"].get_item(track_id) if not track: return AURADocument.error( "404 Not Found", "No track with the requested id.", "There is no track with an id of {} in the library.".format( track_id ), ) path = os.fsdecode(track.path) if not os.path.isfile(path): return AURADocument.error( "404 Not Found", "No audio file for the requested track.", ( "There is no audio file for track {} at the expected location" ).format(track_id), ) file_mimetype = guess_type(path)[0] if not file_mimetype: return AURADocument.error( "500 Internal Server Error", "Requested audio file has an unknown mimetype.", ( "The audio file for track {} has an unknown mimetype. " "Its file extension is {}." ).format(track_id, path.split(".")[-1]), ) # Check that the Accept header contains the file's mimetype # Takes into account */* and audio/* # Adding support for the bitrate parameter would require some effort so I # left it out. This means the client could be sent an error even if the # audio doesn't need transcoding. if not request.accept_mimetypes.best_match([file_mimetype]): return AURADocument.error( "406 Not Acceptable", "Unsupported MIME type or bitrate parameter in Accept header.", ( "The audio file for track {} is only available as {} and " "bitrate parameters are not supported." ).format(track_id, file_mimetype), ) return send_file( path, mimetype=file_mimetype, # Handles filename in Content-Disposition header as_attachment=True, # Tries to upgrade the stream to support range requests conditional=True, ) # Album endpoints @aura_bp.route("/albums") def all_albums(): """Respond with a list of all albums and related information.""" return AlbumDocument.from_app().all_resources() @aura_bp.route("/albums/") def single_album(album_id): """Respond with info about the specified album. Args: album_id: The id of the album provided in the URL (integer). """ return AlbumDocument.from_app().single_resource(album_id) # Artist endpoints # Artist ids are their names @aura_bp.route("/artists") def all_artists(): """Respond with a list of all artists and related information.""" return ArtistDocument.from_app().all_resources() # Using the path converter allows slashes in artist_id @aura_bp.route("/artists/") def single_artist(artist_id): """Respond with info about the specified artist. Args: artist_id: The id of the artist provided in the URL. A string which is the artist's name. """ return ArtistDocument.from_app().single_resource(artist_id) # Image endpoints # Image ids are in the form -- # For example: album-13-cover.jpg @aura_bp.route("/images/") def single_image(image_id): """Respond with info about the specified image. Args: image_id: The id of the image provided in the URL. A string in the form "--". """ return ImageDocument.from_app().single_resource(image_id) @aura_bp.route("/images//file") def image_file(image_id): """Supply an image file for the specified image. Args: image_id: The id of the image provided in the URL. A string in the form "--". """ img_path = ImageDocument.get_image_path(current_app.config["lib"], image_id) if not img_path: return AURADocument.error( "404 Not Found", "No image with the requested id.", "There is no image with an id of {} in the library".format( image_id ), ) return send_file(img_path) # WSGI app def create_app(): """An application factory for use by a WSGI server.""" config["aura"].add( { "host": "127.0.0.1", "port": 8337, "cors": [], "cors_supports_credentials": False, "page_limit": 500, } ) app = Flask(__name__) # Register AURA blueprint view functions under a URL prefix app.register_blueprint(aura_bp, url_prefix="/aura") # AURA specifies mimetype MUST be this app.config["JSONIFY_MIMETYPE"] = "application/vnd.api+json" # Disable auto-sorting of JSON keys app.config["JSON_SORT_KEYS"] = False # Provide a way to access the beets library # The normal method of using the Library and config provided in the # command function is not used because create_app() could be called # by an external WSGI server. # NOTE: this uses a 'private' function from beets.ui.__init__ app.config["lib"] = _open_library(config) # Enable CORS if required cors = config["aura"]["cors"].as_str_seq(list) if cors: from flask_cors import CORS # "Accept" is the only header clients use app.config["CORS_ALLOW_HEADERS"] = "Accept" app.config["CORS_RESOURCES"] = {r"/aura/*": {"origins": cors}} app.config["CORS_SUPPORTS_CREDENTIALS"] = config["aura"][ "cors_supports_credentials" ].get(bool) CORS(app) return app # Beets Plugin Hook class AURAPlugin(BeetsPlugin): """The BeetsPlugin subclass for the AURA server plugin.""" def __init__(self): """Add configuration options for the AURA plugin.""" super().__init__() def commands(self): """Add subcommand used to run the AURA server.""" def run_aura(lib, opts, args): """Run the application using Flask's built in-server. Args: lib: A beets Library object (not used). opts: Command line options. An optparse.Values object. args: The list of arguments to process (not used). """ app = create_app() # Start the built-in server (not intended for production) app.run( host=self.config["host"].get(str), port=self.config["port"].get(int), debug=opts.debug, threaded=True, ) run_aura_cmd = Subcommand("aura", help="run an AURA server") run_aura_cmd.parser.add_option( "-d", "--debug", action="store_true", default=False, help="use Flask debug mode", ) run_aura_cmd.func = run_aura return [run_aura_cmd] beetbox-beets-01f1faf/beetsplug/autobpm.py000066400000000000000000000053571472325477400207740ustar00rootroot00000000000000# This file is part of beets. # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the # "Software"), to deal in the Software without restriction, including # without limitation the rights to use, copy, modify, merge, publish, # distribute, sublicense, and/or sell copies of the Software, and to # permit persons to whom the Software is furnished to do so, subject to # the following conditions: # # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. """Uses Librosa to calculate the `bpm` field.""" from __future__ import annotations from typing import Iterable import librosa from beets.importer import ImportTask from beets.library import Item, Library from beets.plugins import BeetsPlugin from beets.ui import Subcommand, should_write class AutoBPMPlugin(BeetsPlugin): def __init__(self) -> None: super().__init__() self.config.add( { "auto": True, "overwrite": False, "beat_track_kwargs": {}, } ) if self.config["auto"]: self.import_stages = [self.imported] def commands(self) -> list[Subcommand]: cmd = Subcommand( "autobpm", help="detect and add bpm from audio using Librosa" ) cmd.func = self.command return [cmd] def command(self, lib: Library, _, args: list[str]) -> None: self.calculate_bpm(list(lib.items(args)), write=should_write()) def imported(self, _, task: ImportTask) -> None: self.calculate_bpm(task.imported_items()) def calculate_bpm(self, items: list[Item], write: bool = False) -> None: for item in items: path = item.filepath if bpm := item.bpm: self._log.info("BPM for {} already exists: {}", path, bpm) if not self.config["overwrite"]: continue try: y, sr = librosa.load(item.filepath, res_type="kaiser_fast") except Exception as exc: self._log.error("Failed to load {}: {}", path, exc) continue kwargs = self.config["beat_track_kwargs"].flatten() try: tempo, _ = librosa.beat.beat_track(y=y, sr=sr, **kwargs) except Exception as exc: self._log.error("Failed to measure BPM for {}: {}", path, exc) continue bpm = round(tempo[0] if isinstance(tempo, Iterable) else tempo) item["bpm"] = bpm self._log.info("Computed BPM for {}: {}", path, bpm) if write: item.try_write() item.store() beetbox-beets-01f1faf/beetsplug/badfiles.py000066400000000000000000000165111472325477400210700ustar00rootroot00000000000000# This file is part of beets. # Copyright 2016, François-Xavier Thomas. # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the # "Software"), to deal in the Software without restriction, including # without limitation the rights to use, copy, modify, merge, publish, # distribute, sublicense, and/or sell copies of the Software, and to # permit persons to whom the Software is furnished to do so, subject to # the following conditions: # # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. """Use command-line tools to check for audio file corruption.""" import errno import os import shlex import sys from subprocess import STDOUT, CalledProcessError, check_output, list2cmdline import confuse from beets import importer, ui from beets.plugins import BeetsPlugin from beets.ui import Subcommand from beets.util import displayable_path, par_map class CheckerCommandError(Exception): """Raised when running a checker failed. Attributes: checker: Checker command name. path: Path to the file being validated. errno: Error number from the checker execution error. msg: Message from the checker execution error. """ def __init__(self, cmd, oserror): self.checker = cmd[0] self.path = cmd[-1] self.errno = oserror.errno self.msg = str(oserror) class BadFiles(BeetsPlugin): def __init__(self): super().__init__() self.verbose = False self.register_listener("import_task_start", self.on_import_task_start) self.register_listener( "import_task_before_choice", self.on_import_task_before_choice ) def run_command(self, cmd): self._log.debug( "running command: {}", displayable_path(list2cmdline(cmd)) ) try: output = check_output(cmd, stderr=STDOUT) errors = 0 status = 0 except CalledProcessError as e: output = e.output errors = 1 status = e.returncode except OSError as e: raise CheckerCommandError(cmd, e) output = output.decode(sys.getdefaultencoding(), "replace") return status, errors, [line for line in output.split("\n") if line] def check_mp3val(self, path): status, errors, output = self.run_command(["mp3val", path]) if status == 0: output = [line for line in output if line.startswith("WARNING:")] errors = len(output) return status, errors, output def check_flac(self, path): return self.run_command(["flac", "-wst", path]) def check_custom(self, command): def checker(path): cmd = shlex.split(command) cmd.append(path) return self.run_command(cmd) return checker def get_checker(self, ext): ext = ext.lower() try: command = self.config["commands"].get(dict).get(ext) except confuse.NotFoundError: command = None if command: return self.check_custom(command) if ext == "mp3": return self.check_mp3val if ext == "flac": return self.check_flac def check_item(self, item): # First, check whether the path exists. If not, the user # should probably run `beet update` to cleanup your library. dpath = displayable_path(item.path) self._log.debug("checking path: {}", dpath) if not os.path.exists(item.path): ui.print_( "{}: file does not exist".format( ui.colorize("text_error", dpath) ) ) # Run the checker against the file if one is found ext = os.path.splitext(item.path)[1][1:].decode("utf8", "ignore") checker = self.get_checker(ext) if not checker: self._log.error("no checker specified in the config for {}", ext) return [] path = item.path if not isinstance(path, str): path = item.path.decode(sys.getfilesystemencoding()) try: status, errors, output = checker(path) except CheckerCommandError as e: if e.errno == errno.ENOENT: self._log.error( "command not found: {} when validating file: {}", e.checker, e.path, ) else: self._log.error("error invoking {}: {}", e.checker, e.msg) return [] error_lines = [] if status > 0: error_lines.append( "{}: checker exited with status {}".format( ui.colorize("text_error", dpath), status ) ) for line in output: error_lines.append(f" {line}") elif errors > 0: error_lines.append( "{}: checker found {} errors or warnings".format( ui.colorize("text_warning", dpath), errors ) ) for line in output: error_lines.append(f" {line}") elif self.verbose: error_lines.append( "{}: ok".format(ui.colorize("text_success", dpath)) ) return error_lines def on_import_task_start(self, task, session): if not self.config["check_on_import"].get(False): return checks_failed = [] for item in task.items: error_lines = self.check_item(item) if error_lines: checks_failed.append(error_lines) if checks_failed: task._badfiles_checks_failed = checks_failed def on_import_task_before_choice(self, task, session): if hasattr(task, "_badfiles_checks_failed"): ui.print_( "{} one or more files failed checks:".format( ui.colorize("text_warning", "BAD") ) ) for error in task._badfiles_checks_failed: for error_line in error: ui.print_(error_line) ui.print_() ui.print_("What would you like to do?") sel = ui.input_options(["aBort", "skip", "continue"]) if sel == "s": return importer.action.SKIP elif sel == "c": return None elif sel == "b": raise importer.ImportAbortError() else: raise Exception(f"Unexpected selection: {sel}") def command(self, lib, opts, args): # Get items from arguments items = lib.items(ui.decargs(args)) self.verbose = opts.verbose def check_and_print(item): for error_line in self.check_item(item): ui.print_(error_line) par_map(check_and_print, items) def commands(self): bad_command = Subcommand( "bad", help="check for corrupt or missing files" ) bad_command.parser.add_option( "-v", "--verbose", action="store_true", default=False, dest="verbose", help="view results for both the bad and uncorrupted files", ) bad_command.func = self.command return [bad_command] beetbox-beets-01f1faf/beetsplug/bareasc.py000066400000000000000000000061601472325477400207160ustar00rootroot00000000000000# This file is part of beets. # Copyright 2016, Philippe Mongeau. # Copyright 2021, Graham R. Cobb. # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and ascociated documentation files (the # "Software"), to deal in the Software without restriction, including # without limitation the rights to use, copy, modify, merge, publish, # distribute, sublicense, and/or sell copies of the Software, and to # permit persons to whom the Software is furnished to do so, subject to # the following conditions: # # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. # # This module is adapted from Fuzzy in accordance to the licence of # that module """Provides a bare-ASCII matching query.""" from unidecode import unidecode from beets import ui from beets.dbcore.query import StringFieldQuery from beets.plugins import BeetsPlugin from beets.ui import decargs, print_ class BareascQuery(StringFieldQuery[str]): """Compare items using bare ASCII, without accents etc.""" @classmethod def string_match(cls, pattern, val): """Convert both pattern and string to plain ASCII before matching. If pattern is all lower case, also convert string to lower case so match is also case insensitive """ # smartcase if pattern.islower(): val = val.lower() pattern = unidecode(pattern) val = unidecode(val) return pattern in val def col_clause(self): """Compare ascii version of the pattern.""" clause = f"unidecode({self.field})" if self.pattern.islower(): clause = f"lower({clause})" return rf"{clause} LIKE ? ESCAPE '\'", [f"%{unidecode(self.pattern)}%"] class BareascPlugin(BeetsPlugin): """Plugin to provide bare-ASCII option for beets matching.""" def __init__(self): """Default prefix for selecting bare-ASCII matching is #.""" super().__init__() self.config.add( { "prefix": "#", } ) def queries(self): """Register bare-ASCII matching.""" prefix = self.config["prefix"].as_str() return {prefix: BareascQuery} def commands(self): """Add bareasc command as unidecode version of 'list'.""" cmd = ui.Subcommand( "bareasc", help="unidecode version of beet list command" ) cmd.parser.usage += ( "\n" "Example: %prog -f '$album: $title' artist:beatles" ) cmd.parser.add_all_common_options() cmd.func = self.unidecode_list return [cmd] def unidecode_list(self, lib, opts, args): """Emulate normal 'list' command but with unidecode output.""" query = decargs(args) album = opts.album # Copied from commands.py - list_items if album: for album in lib.albums(query): bare = unidecode(str(album)) print_(bare) else: for item in lib.items(query): bare = unidecode(str(item)) print_(bare) beetbox-beets-01f1faf/beetsplug/beatport.py000066400000000000000000000441651472325477400211450ustar00rootroot00000000000000# This file is part of beets. # Copyright 2016, Adrian Sampson. # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the # "Software"), to deal in the Software without restriction, including # without limitation the rights to use, copy, modify, merge, publish, # distribute, sublicense, and/or sell copies of the Software, and to # permit persons to whom the Software is furnished to do so, subject to # the following conditions: # # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. """Adds Beatport release and track search support to the autotagger""" import json import re from datetime import datetime, timedelta import confuse from requests_oauthlib import OAuth1Session from requests_oauthlib.oauth1_session import ( TokenMissing, TokenRequestDenied, VerifierMissing, ) import beets import beets.ui from beets.autotag.hooks import AlbumInfo, TrackInfo from beets.plugins import BeetsPlugin, MetadataSourcePlugin, get_distance from beets.util.id_extractors import beatport_id_regex AUTH_ERRORS = (TokenRequestDenied, TokenMissing, VerifierMissing) USER_AGENT = f"beets/{beets.__version__} +https://beets.io/" class BeatportAPIError(Exception): pass class BeatportObject: def __init__(self, data): self.beatport_id = data["id"] self.name = str(data["name"]) if "releaseDate" in data: self.release_date = datetime.strptime( data["releaseDate"], "%Y-%m-%d" ) if "artists" in data: self.artists = [(x["id"], str(x["name"])) for x in data["artists"]] if "genres" in data: self.genres = [str(x["name"]) for x in data["genres"]] class BeatportClient: _api_base = "https://oauth-api.beatport.com" def __init__(self, c_key, c_secret, auth_key=None, auth_secret=None): """Initiate the client with OAuth information. For the initial authentication with the backend `auth_key` and `auth_secret` can be `None`. Use `get_authorize_url` and `get_access_token` to obtain them for subsequent uses of the API. :param c_key: OAuth1 client key :param c_secret: OAuth1 client secret :param auth_key: OAuth1 resource owner key :param auth_secret: OAuth1 resource owner secret """ self.api = OAuth1Session( client_key=c_key, client_secret=c_secret, resource_owner_key=auth_key, resource_owner_secret=auth_secret, callback_uri="oob", ) self.api.headers = {"User-Agent": USER_AGENT} def get_authorize_url(self): """Generate the URL for the user to authorize the application. Retrieves a request token from the Beatport API and returns the corresponding authorization URL on their end that the user has to visit. This is the first step of the initial authorization process with the API. Once the user has visited the URL, call :py:method:`get_access_token` with the displayed data to complete the process. :returns: Authorization URL for the user to visit :rtype: unicode """ self.api.fetch_request_token( self._make_url("/identity/1/oauth/request-token") ) return self.api.authorization_url( self._make_url("/identity/1/oauth/authorize") ) def get_access_token(self, auth_data): """Obtain the final access token and secret for the API. :param auth_data: URL-encoded authorization data as displayed at the authorization url (obtained via :py:meth:`get_authorize_url`) after signing in :type auth_data: unicode :returns: OAuth resource owner key and secret :rtype: (unicode, unicode) tuple """ self.api.parse_authorization_response( "https://beets.io/auth?" + auth_data ) access_data = self.api.fetch_access_token( self._make_url("/identity/1/oauth/access-token") ) return access_data["oauth_token"], access_data["oauth_token_secret"] def search(self, query, release_type="release", details=True): """Perform a search of the Beatport catalogue. :param query: Query string :param release_type: Type of releases to search for, can be 'release' or 'track' :param details: Retrieve additional information about the search results. Currently this will fetch the tracklist for releases and do nothing for tracks :returns: Search results :rtype: generator that yields py:class:`BeatportRelease` or :py:class:`BeatportTrack` """ response = self._get( "catalog/3/search", query=query, perPage=5, facets=[f"fieldType:{release_type}"], ) for item in response: if release_type == "release": if details: release = self.get_release(item["id"]) else: release = BeatportRelease(item) yield release elif release_type == "track": yield BeatportTrack(item) def get_release(self, beatport_id): """Get information about a single release. :param beatport_id: Beatport ID of the release :returns: The matching release :rtype: :py:class:`BeatportRelease` """ response = self._get("/catalog/3/releases", id=beatport_id) if response: release = BeatportRelease(response[0]) release.tracks = self.get_release_tracks(beatport_id) return release return None def get_release_tracks(self, beatport_id): """Get all tracks for a given release. :param beatport_id: Beatport ID of the release :returns: Tracks in the matching release :rtype: list of :py:class:`BeatportTrack` """ response = self._get( "/catalog/3/tracks", releaseId=beatport_id, perPage=100 ) return [BeatportTrack(t) for t in response] def get_track(self, beatport_id): """Get information about a single track. :param beatport_id: Beatport ID of the track :returns: The matching track :rtype: :py:class:`BeatportTrack` """ response = self._get("/catalog/3/tracks", id=beatport_id) return BeatportTrack(response[0]) def _make_url(self, endpoint): """Get complete URL for a given API endpoint.""" if not endpoint.startswith("/"): endpoint = "/" + endpoint return self._api_base + endpoint def _get(self, endpoint, **kwargs): """Perform a GET request on a given API endpoint. Automatically extracts result data from the response and converts HTTP exceptions into :py:class:`BeatportAPIError` objects. """ try: response = self.api.get(self._make_url(endpoint), params=kwargs) except Exception as e: raise BeatportAPIError( "Error connecting to Beatport API: {}".format(e) ) if not response: raise BeatportAPIError( "Error {0.status_code} for '{0.request.path_url}".format( response ) ) return response.json()["results"] class BeatportRelease(BeatportObject): def __str__(self): if len(self.artists) < 4: artist_str = ", ".join(x[1] for x in self.artists) else: artist_str = "Various Artists" return "".format( artist_str, self.name, self.catalog_number, ) def __repr__(self): return str(self).encode("utf-8") def __init__(self, data): BeatportObject.__init__(self, data) if "catalogNumber" in data: self.catalog_number = data["catalogNumber"] if "label" in data: self.label_name = data["label"]["name"] if "category" in data: self.category = data["category"] if "slug" in data: self.url = "https://beatport.com/release/{}/{}".format( data["slug"], data["id"] ) self.genre = data.get("genre") class BeatportTrack(BeatportObject): def __str__(self): artist_str = ", ".join(x[1] for x in self.artists) return "".format( artist_str, self.name, self.mix_name ) def __repr__(self): return str(self).encode("utf-8") def __init__(self, data): BeatportObject.__init__(self, data) if "title" in data: self.title = str(data["title"]) if "mixName" in data: self.mix_name = str(data["mixName"]) self.length = timedelta(milliseconds=data.get("lengthMs", 0) or 0) if not self.length: try: min, sec = data.get("length", "0:0").split(":") self.length = timedelta(minutes=int(min), seconds=int(sec)) except ValueError: pass if "slug" in data: self.url = "https://beatport.com/track/{}/{}".format( data["slug"], data["id"] ) self.track_number = data.get("trackNumber") self.bpm = data.get("bpm") self.initial_key = str((data.get("key") or {}).get("shortName")) # Use 'subgenre' and if not present, 'genre' as a fallback. if data.get("subGenres"): self.genre = str(data["subGenres"][0].get("name")) elif data.get("genres"): self.genre = str(data["genres"][0].get("name")) class BeatportPlugin(BeetsPlugin): data_source = "Beatport" id_regex = beatport_id_regex def __init__(self): super().__init__() self.config.add( { "apikey": "57713c3906af6f5def151b33601389176b37b429", "apisecret": "b3fe08c93c80aefd749fe871a16cd2bb32e2b954", "tokenfile": "beatport_token.json", "source_weight": 0.5, } ) self.config["apikey"].redact = True self.config["apisecret"].redact = True self.client = None self.register_listener("import_begin", self.setup) def setup(self, session=None): c_key = self.config["apikey"].as_str() c_secret = self.config["apisecret"].as_str() # Get the OAuth token from a file or log in. try: with open(self._tokenfile()) as f: tokendata = json.load(f) except OSError: # No token yet. Generate one. token, secret = self.authenticate(c_key, c_secret) else: token = tokendata["token"] secret = tokendata["secret"] self.client = BeatportClient(c_key, c_secret, token, secret) def authenticate(self, c_key, c_secret): # Get the link for the OAuth page. auth_client = BeatportClient(c_key, c_secret) try: url = auth_client.get_authorize_url() except AUTH_ERRORS as e: self._log.debug("authentication error: {0}", e) raise beets.ui.UserError("communication with Beatport failed") beets.ui.print_("To authenticate with Beatport, visit:") beets.ui.print_(url) # Ask for the verifier data and validate it. data = beets.ui.input_("Enter the string displayed in your browser:") try: token, secret = auth_client.get_access_token(data) except AUTH_ERRORS as e: self._log.debug("authentication error: {0}", e) raise beets.ui.UserError("Beatport token request failed") # Save the token for later use. self._log.debug("Beatport token {0}, secret {1}", token, secret) with open(self._tokenfile(), "w") as f: json.dump({"token": token, "secret": secret}, f) return token, secret def _tokenfile(self): """Get the path to the JSON file for storing the OAuth token.""" return self.config["tokenfile"].get(confuse.Filename(in_app_dir=True)) def album_distance(self, items, album_info, mapping): """Returns the Beatport source weight and the maximum source weight for albums. """ return get_distance( data_source=self.data_source, info=album_info, config=self.config ) def track_distance(self, item, track_info): """Returns the Beatport source weight and the maximum source weight for individual tracks. """ return get_distance( data_source=self.data_source, info=track_info, config=self.config ) def candidates(self, items, artist, release, va_likely, extra_tags=None): """Returns a list of AlbumInfo objects for beatport search results matching release and artist (if not various). """ if va_likely: query = release else: query = f"{artist} {release}" try: return self._get_releases(query) except BeatportAPIError as e: self._log.debug("API Error: {0} (query: {1})", e, query) return [] def item_candidates(self, item, artist, title): """Returns a list of TrackInfo objects for beatport search results matching title and artist. """ query = f"{artist} {title}" try: return self._get_tracks(query) except BeatportAPIError as e: self._log.debug("API Error: {0} (query: {1})", e, query) return [] def album_for_id(self, release_id): """Fetches a release by its Beatport ID and returns an AlbumInfo object or None if the query is not a valid ID or release is not found. """ self._log.debug("Searching for release {0}", release_id) release_id = self._get_id("album", release_id, self.id_regex) if release_id is None: self._log.debug("Not a valid Beatport release ID.") return None release = self.client.get_release(release_id) if release: return self._get_album_info(release) return None def track_for_id(self, track_id): """Fetches a track by its Beatport ID and returns a TrackInfo object or None if the track is not a valid Beatport ID or track is not found. """ self._log.debug("Searching for track {0}", track_id) match = re.search(r"(^|beatport\.com/track/.+/)(\d+)$", track_id) if not match: self._log.debug("Not a valid Beatport track ID.") return None bp_track = self.client.get_track(match.group(2)) if bp_track is not None: return self._get_track_info(bp_track) return None def _get_releases(self, query): """Returns a list of AlbumInfo objects for a beatport search query.""" # Strip non-word characters from query. Things like "!" and "-" can # cause a query to return no results, even if they match the artist or # album title. Use `re.UNICODE` flag to avoid stripping non-english # word characters. query = re.sub(r"\W+", " ", query, flags=re.UNICODE) # Strip medium information from query, Things like "CD1" and "disk 1" # can also negate an otherwise positive result. query = re.sub(r"\b(CD|disc)\s*\d+", "", query, flags=re.I) albums = [self._get_album_info(x) for x in self.client.search(query)] return albums def _get_album_info(self, release): """Returns an AlbumInfo object for a Beatport Release object.""" va = len(release.artists) > 3 artist, artist_id = self._get_artist(release.artists) if va: artist = "Various Artists" tracks = [self._get_track_info(x) for x in release.tracks] return AlbumInfo( album=release.name, album_id=release.beatport_id, beatport_album_id=release.beatport_id, artist=artist, artist_id=artist_id, tracks=tracks, albumtype=release.category, va=va, year=release.release_date.year, month=release.release_date.month, day=release.release_date.day, label=release.label_name, catalognum=release.catalog_number, media="Digital", data_source=self.data_source, data_url=release.url, genre=release.genre, ) def _get_track_info(self, track): """Returns a TrackInfo object for a Beatport Track object.""" title = track.name if track.mix_name != "Original Mix": title += f" ({track.mix_name})" artist, artist_id = self._get_artist(track.artists) length = track.length.total_seconds() return TrackInfo( title=title, track_id=track.beatport_id, artist=artist, artist_id=artist_id, length=length, index=track.track_number, medium_index=track.track_number, data_source=self.data_source, data_url=track.url, bpm=track.bpm, initial_key=track.initial_key, genre=track.genre, ) def _get_artist(self, artists): """Returns an artist string (all artists) and an artist_id (the main artist) for a list of Beatport release or track artists. """ return MetadataSourcePlugin.get_artist( artists=artists, id_key=0, name_key=1 ) def _get_tracks(self, query): """Returns a list of TrackInfo objects for a Beatport query.""" bp_tracks = self.client.search(query, release_type="track") tracks = [self._get_track_info(x) for x in bp_tracks] return tracks beetbox-beets-01f1faf/beetsplug/bench.py000066400000000000000000000077641472325477400204100ustar00rootroot00000000000000# This file is part of beets. # Copyright 2016, Adrian Sampson. # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the # "Software"), to deal in the Software without restriction, including # without limitation the rights to use, copy, modify, merge, publish, # distribute, sublicense, and/or sell copies of the Software, and to # permit persons to whom the Software is furnished to do so, subject to # the following conditions: # # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. """Some simple performance benchmarks for beets.""" import cProfile import timeit from beets import importer, library, plugins, ui, vfs from beets.autotag import match from beets.plugins import BeetsPlugin from beets.util.functemplate import Template def aunique_benchmark(lib, prof): def _build_tree(): vfs.libtree(lib) # Measure path generation performance with %aunique{} included. lib.path_formats = [ ( library.PF_KEY_DEFAULT, Template("$albumartist/$album%aunique{}/$track $title"), ), ] if prof: cProfile.runctx( "_build_tree()", {}, {"_build_tree": _build_tree}, "paths.withaunique.prof", ) else: interval = timeit.timeit(_build_tree, number=1) print("With %aunique:", interval) # And with %aunique replaceed with a "cheap" no-op function. lib.path_formats = [ ( library.PF_KEY_DEFAULT, Template("$albumartist/$album%lower{}/$track $title"), ), ] if prof: cProfile.runctx( "_build_tree()", {}, {"_build_tree": _build_tree}, "paths.withoutaunique.prof", ) else: interval = timeit.timeit(_build_tree, number=1) print("Without %aunique:", interval) def match_benchmark(lib, prof, query=None, album_id=None): # If no album ID is provided, we'll match against a suitably huge # album. if not album_id: album_id = "9c5c043e-bc69-4edb-81a4-1aaf9c81e6dc" # Get an album from the library to use as the source for the match. items = lib.albums(query).get().items() # Ensure fingerprinting is invoked (if enabled). plugins.send( "import_task_start", task=importer.ImportTask(None, None, items), session=importer.ImportSession(lib, None, None, None), ) # Run the match. def _run_match(): match.tag_album(items, search_ids=[album_id]) if prof: cProfile.runctx( "_run_match()", {}, {"_run_match": _run_match}, "match.prof" ) else: interval = timeit.timeit(_run_match, number=1) print("match duration:", interval) class BenchmarkPlugin(BeetsPlugin): """A plugin for performing some simple performance benchmarks.""" def commands(self): aunique_bench_cmd = ui.Subcommand( "bench_aunique", help="benchmark for %aunique{}" ) aunique_bench_cmd.parser.add_option( "-p", "--profile", action="store_true", default=False, help="performance profiling", ) aunique_bench_cmd.func = lambda lib, opts, args: aunique_benchmark( lib, opts.profile ) match_bench_cmd = ui.Subcommand( "bench_match", help="benchmark for track matching" ) match_bench_cmd.parser.add_option( "-p", "--profile", action="store_true", default=False, help="performance profiling", ) match_bench_cmd.parser.add_option( "-i", "--id", default=None, help="album ID to match against" ) match_bench_cmd.func = lambda lib, opts, args: match_benchmark( lib, opts.profile, ui.decargs(args), opts.id ) return [aunique_bench_cmd, match_bench_cmd] beetbox-beets-01f1faf/beetsplug/bpd/000077500000000000000000000000001472325477400175065ustar00rootroot00000000000000beetbox-beets-01f1faf/beetsplug/bpd/__init__.py000066400000000000000000001576531472325477400216400ustar00rootroot00000000000000# This file is part of beets. # Copyright 2016, Adrian Sampson. # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the # "Software"), to deal in the Software without restriction, including # without limitation the rights to use, copy, modify, merge, publish, # distribute, sublicense, and/or sell copies of the Software, and to # permit persons to whom the Software is furnished to do so, subject to # the following conditions: # # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. """A clone of the Music Player Daemon (MPD) that plays music from a Beets library. Attempts to implement a compatible protocol to allow use of the wide range of MPD clients. """ import inspect import math import random import re import socket import sys import time import traceback from string import Template from typing import List from mediafile import MediaFile import beets import beets.ui from beets import dbcore, vfs from beets.library import Item from beets.plugins import BeetsPlugin from beets.util import bluelet PROTOCOL_VERSION = "0.16.0" BUFSIZE = 1024 HELLO = "OK MPD %s" % PROTOCOL_VERSION CLIST_BEGIN = "command_list_begin" CLIST_VERBOSE_BEGIN = "command_list_ok_begin" CLIST_END = "command_list_end" RESP_OK = "OK" RESP_CLIST_VERBOSE = "list_OK" RESP_ERR = "ACK" NEWLINE = "\n" ERROR_NOT_LIST = 1 ERROR_ARG = 2 ERROR_PASSWORD = 3 ERROR_PERMISSION = 4 ERROR_UNKNOWN = 5 ERROR_NO_EXIST = 50 ERROR_PLAYLIST_MAX = 51 ERROR_SYSTEM = 52 ERROR_PLAYLIST_LOAD = 53 ERROR_UPDATE_ALREADY = 54 ERROR_PLAYER_SYNC = 55 ERROR_EXIST = 56 VOLUME_MIN = 0 VOLUME_MAX = 100 SAFE_COMMANDS = ( # Commands that are available when unauthenticated. "close", "commands", "notcommands", "password", "ping", ) # List of subsystems/events used by the `idle` command. SUBSYSTEMS = [ "update", "player", "mixer", "options", "playlist", "database", # Related to unsupported commands: "stored_playlist", "output", "subscription", "sticker", "message", "partition", ] ITEM_KEYS_WRITABLE = set(MediaFile.fields()).intersection(Item._fields.keys()) # Gstreamer import error. class NoGstreamerError(Exception): pass # Error-handling, exceptions, parameter parsing. class BPDError(Exception): """An error that should be exposed to the client to the BPD server. """ def __init__(self, code, message, cmd_name="", index=0): self.code = code self.message = message self.cmd_name = cmd_name self.index = index template = Template("$resp [$code@$index] {$cmd_name} $message") def response(self): """Returns a string to be used as the response code for the erring command. """ return self.template.substitute( { "resp": RESP_ERR, "code": self.code, "index": self.index, "cmd_name": self.cmd_name, "message": self.message, } ) def make_bpd_error(s_code, s_message): """Create a BPDError subclass for a static code and message.""" class NewBPDError(BPDError): code = s_code message = s_message cmd_name = "" index = 0 def __init__(self): pass return NewBPDError ArgumentTypeError = make_bpd_error(ERROR_ARG, "invalid type for argument") ArgumentIndexError = make_bpd_error(ERROR_ARG, "argument out of range") ArgumentNotFoundError = make_bpd_error(ERROR_NO_EXIST, "argument not found") def cast_arg(t, val): """Attempts to call t on val, raising a ArgumentTypeError on ValueError. If 't' is the special string 'intbool', attempts to cast first to an int and then to a bool (i.e., 1=True, 0=False). """ if t == "intbool": return cast_arg(bool, cast_arg(int, val)) else: try: return t(val) except ValueError: raise ArgumentTypeError() class BPDCloseError(Exception): """Raised by a command invocation to indicate that the connection should be closed. """ class BPDIdleError(Exception): """Raised by a command to indicate the client wants to enter the idle state and should be notified when a relevant event happens. """ def __init__(self, subsystems): super().__init__() self.subsystems = set(subsystems) # Generic server infrastructure, implementing the basic protocol. class BaseServer: """A MPD-compatible music player server. The functions with the `cmd_` prefix are invoked in response to client commands. For instance, if the client says `status`, `cmd_status` will be invoked. The arguments to the client's commands are used as function arguments following the connection issuing the command. The functions may send data on the connection. They may also raise BPDError exceptions to report errors. This is a generic superclass and doesn't support many commands. """ def __init__(self, host, port, password, ctrl_port, log, ctrl_host=None): """Create a new server bound to address `host` and listening on port `port`. If `password` is given, it is required to do anything significant on the server. A separate control socket is established listening to `ctrl_host` on port `ctrl_port` which is used to forward notifications from the player and can be sent debug commands (e.g. using netcat). """ self.host, self.port, self.password = host, port, password self.ctrl_host, self.ctrl_port = ctrl_host or host, ctrl_port self.ctrl_sock = None self._log = log # Default server values. self.random = False self.repeat = False self.consume = False self.single = False self.volume = VOLUME_MAX self.crossfade = 0 self.mixrampdb = 0.0 self.mixrampdelay = float("nan") self.replay_gain_mode = "off" self.playlist = [] self.playlist_version = 0 self.current_index = -1 self.paused = False self.error = None # Current connections self.connections = set() # Object for random numbers generation self.random_obj = random.Random() def connect(self, conn): """A new client has connected.""" self.connections.add(conn) def disconnect(self, conn): """Client has disconnected; clean up residual state.""" self.connections.remove(conn) def run(self): """Block and start listening for connections from clients. An interrupt (^C) closes the server. """ self.startup_time = time.time() def start(): yield bluelet.spawn( bluelet.server( self.ctrl_host, self.ctrl_port, ControlConnection.handler(self), ) ) yield bluelet.server( self.host, self.port, MPDConnection.handler(self) ) bluelet.run(start()) def dispatch_events(self): """If any clients have idle events ready, send them.""" # We need a copy of `self.connections` here since clients might # disconnect once we try and send to them, changing `self.connections`. for conn in list(self.connections): yield bluelet.spawn(conn.send_notifications()) def _ctrl_send(self, message): """Send some data over the control socket. If it's our first time, open the socket. The message should be a string without a terminal newline. """ if not self.ctrl_sock: self.ctrl_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) self.ctrl_sock.connect((self.ctrl_host, self.ctrl_port)) self.ctrl_sock.sendall((message + "\n").encode("utf-8")) def _send_event(self, event): """Notify subscribed connections of an event.""" for conn in self.connections: conn.notify(event) def _item_info(self, item): """An abstract method that should response lines containing a single song's metadata. """ raise NotImplementedError def _item_id(self, item): """An abstract method returning the integer id for an item.""" raise NotImplementedError def _id_to_index(self, track_id): """Searches the playlist for a song with the given id and returns its index in the playlist. """ track_id = cast_arg(int, track_id) for index, track in enumerate(self.playlist): if self._item_id(track) == track_id: return index # Loop finished with no track found. raise ArgumentNotFoundError() def _random_idx(self): """Returns a random index different from the current one. If there are no songs in the playlist it returns -1. If there is only one song in the playlist it returns 0. """ if len(self.playlist) < 2: return len(self.playlist) - 1 new_index = self.random_obj.randint(0, len(self.playlist) - 1) while new_index == self.current_index: new_index = self.random_obj.randint(0, len(self.playlist) - 1) return new_index def _succ_idx(self): """Returns the index for the next song to play. It also considers random, single and repeat flags. No boundaries are checked. """ if self.repeat and self.single: return self.current_index if self.random: return self._random_idx() return self.current_index + 1 def _prev_idx(self): """Returns the index for the previous song to play. It also considers random and repeat flags. No boundaries are checked. """ if self.repeat and self.single: return self.current_index if self.random: return self._random_idx() return self.current_index - 1 def cmd_ping(self, conn): """Succeeds.""" pass def cmd_idle(self, conn, *subsystems): subsystems = subsystems or SUBSYSTEMS for system in subsystems: if system not in SUBSYSTEMS: raise BPDError(ERROR_ARG, f"Unrecognised idle event: {system}") raise BPDIdleError(subsystems) # put the connection into idle mode def cmd_kill(self, conn): """Exits the server process.""" sys.exit(0) def cmd_close(self, conn): """Closes the connection.""" raise BPDCloseError() def cmd_password(self, conn, password): """Attempts password authentication.""" if password == self.password: conn.authenticated = True else: conn.authenticated = False raise BPDError(ERROR_PASSWORD, "incorrect password") def cmd_commands(self, conn): """Lists the commands available to the user.""" if self.password and not conn.authenticated: # Not authenticated. Show limited list of commands. for cmd in SAFE_COMMANDS: yield "command: " + cmd else: # Authenticated. Show all commands. for func in dir(self): if func.startswith("cmd_"): yield "command: " + func[4:] def cmd_notcommands(self, conn): """Lists all unavailable commands.""" if self.password and not conn.authenticated: # Not authenticated. Show privileged commands. for func in dir(self): if func.startswith("cmd_"): cmd = func[4:] if cmd not in SAFE_COMMANDS: yield "command: " + cmd else: # Authenticated. No commands are unavailable. pass def cmd_status(self, conn): """Returns some status information for use with an implementation of cmd_status. Gives a list of response-lines for: volume, repeat, random, playlist, playlistlength, and xfade. """ yield ( "repeat: " + str(int(self.repeat)), "random: " + str(int(self.random)), "consume: " + str(int(self.consume)), "single: " + str(int(self.single)), "playlist: " + str(self.playlist_version), "playlistlength: " + str(len(self.playlist)), "mixrampdb: " + str(self.mixrampdb), ) if self.volume > 0: yield "volume: " + str(self.volume) if not math.isnan(self.mixrampdelay): yield "mixrampdelay: " + str(self.mixrampdelay) if self.crossfade > 0: yield "xfade: " + str(self.crossfade) if self.current_index == -1: state = "stop" elif self.paused: state = "pause" else: state = "play" yield "state: " + state if self.current_index != -1: # i.e., paused or playing current_id = self._item_id(self.playlist[self.current_index]) yield "song: " + str(self.current_index) yield "songid: " + str(current_id) if len(self.playlist) > self.current_index + 1: # If there's a next song, report its index too. next_id = self._item_id(self.playlist[self.current_index + 1]) yield "nextsong: " + str(self.current_index + 1) yield "nextsongid: " + str(next_id) if self.error: yield "error: " + self.error def cmd_clearerror(self, conn): """Removes the persistent error state of the server. This error is set when a problem arises not in response to a command (for instance, when playing a file). """ self.error = None def cmd_random(self, conn, state): """Set or unset random (shuffle) mode.""" self.random = cast_arg("intbool", state) self._send_event("options") def cmd_repeat(self, conn, state): """Set or unset repeat mode.""" self.repeat = cast_arg("intbool", state) self._send_event("options") def cmd_consume(self, conn, state): """Set or unset consume mode.""" self.consume = cast_arg("intbool", state) self._send_event("options") def cmd_single(self, conn, state): """Set or unset single mode.""" # TODO support oneshot in addition to 0 and 1 [MPD 0.20] self.single = cast_arg("intbool", state) self._send_event("options") def cmd_setvol(self, conn, vol): """Set the player's volume level (0-100).""" vol = cast_arg(int, vol) if vol < VOLUME_MIN or vol > VOLUME_MAX: raise BPDError(ERROR_ARG, "volume out of range") self.volume = vol self._send_event("mixer") def cmd_volume(self, conn, vol_delta): """Deprecated command to change the volume by a relative amount.""" vol_delta = cast_arg(int, vol_delta) return self.cmd_setvol(conn, self.volume + vol_delta) def cmd_crossfade(self, conn, crossfade): """Set the number of seconds of crossfading.""" crossfade = cast_arg(int, crossfade) if crossfade < 0: raise BPDError(ERROR_ARG, "crossfade time must be nonnegative") self._log.warning("crossfade is not implemented in bpd") self.crossfade = crossfade self._send_event("options") def cmd_mixrampdb(self, conn, db): """Set the mixramp normalised max volume in dB.""" db = cast_arg(float, db) if db > 0: raise BPDError(ERROR_ARG, "mixrampdb time must be negative") self._log.warning("mixramp is not implemented in bpd") self.mixrampdb = db self._send_event("options") def cmd_mixrampdelay(self, conn, delay): """Set the mixramp delay in seconds.""" delay = cast_arg(float, delay) if delay < 0: raise BPDError(ERROR_ARG, "mixrampdelay time must be nonnegative") self._log.warning("mixramp is not implemented in bpd") self.mixrampdelay = delay self._send_event("options") def cmd_replay_gain_mode(self, conn, mode): """Set the replay gain mode.""" if mode not in ["off", "track", "album", "auto"]: raise BPDError(ERROR_ARG, "Unrecognised replay gain mode") self._log.warning("replay gain is not implemented in bpd") self.replay_gain_mode = mode self._send_event("options") def cmd_replay_gain_status(self, conn): """Get the replaygain mode.""" yield "replay_gain_mode: " + str(self.replay_gain_mode) def cmd_clear(self, conn): """Clear the playlist.""" self.playlist = [] self.playlist_version += 1 self.cmd_stop(conn) self._send_event("playlist") def cmd_delete(self, conn, index): """Remove the song at index from the playlist.""" index = cast_arg(int, index) try: del self.playlist[index] except IndexError: raise ArgumentIndexError() self.playlist_version += 1 if self.current_index == index: # Deleted playing song. self.cmd_stop(conn) elif index < self.current_index: # Deleted before playing. # Shift playing index down. self.current_index -= 1 self._send_event("playlist") def cmd_deleteid(self, conn, track_id): self.cmd_delete(conn, self._id_to_index(track_id)) def cmd_move(self, conn, idx_from, idx_to): """Move a track in the playlist.""" idx_from = cast_arg(int, idx_from) idx_to = cast_arg(int, idx_to) try: track = self.playlist.pop(idx_from) self.playlist.insert(idx_to, track) except IndexError: raise ArgumentIndexError() # Update currently-playing song. if idx_from == self.current_index: self.current_index = idx_to elif idx_from < self.current_index <= idx_to: self.current_index -= 1 elif idx_from > self.current_index >= idx_to: self.current_index += 1 self.playlist_version += 1 self._send_event("playlist") def cmd_moveid(self, conn, idx_from, idx_to): idx_from = self._id_to_index(idx_from) return self.cmd_move(conn, idx_from, idx_to) def cmd_swap(self, conn, i, j): """Swaps two tracks in the playlist.""" i = cast_arg(int, i) j = cast_arg(int, j) try: track_i = self.playlist[i] track_j = self.playlist[j] except IndexError: raise ArgumentIndexError() self.playlist[j] = track_i self.playlist[i] = track_j # Update currently-playing song. if self.current_index == i: self.current_index = j elif self.current_index == j: self.current_index = i self.playlist_version += 1 self._send_event("playlist") def cmd_swapid(self, conn, i_id, j_id): i = self._id_to_index(i_id) j = self._id_to_index(j_id) return self.cmd_swap(conn, i, j) def cmd_urlhandlers(self, conn): """Indicates supported URL schemes. None by default.""" pass def cmd_playlistinfo(self, conn, index=None): """Gives metadata information about the entire playlist or a single track, given by its index. """ if index is None: for track in self.playlist: yield self._item_info(track) else: indices = self._parse_range(index, accept_single_number=True) try: tracks = [self.playlist[i] for i in indices] except IndexError: raise ArgumentIndexError() for track in tracks: yield self._item_info(track) def cmd_playlistid(self, conn, track_id=None): if track_id is not None: track_id = cast_arg(int, track_id) track_id = self._id_to_index(track_id) return self.cmd_playlistinfo(conn, track_id) def cmd_plchanges(self, conn, version): """Sends playlist changes since the given version. This is a "fake" implementation that ignores the version and just returns the entire playlist (rather like version=0). This seems to satisfy many clients. """ return self.cmd_playlistinfo(conn) def cmd_plchangesposid(self, conn, version): """Like plchanges, but only sends position and id. Also a dummy implementation. """ for idx, track in enumerate(self.playlist): yield "cpos: " + str(idx) yield "Id: " + str(track.id) def cmd_currentsong(self, conn): """Sends information about the currently-playing song.""" if self.current_index != -1: # -1 means stopped. track = self.playlist[self.current_index] yield self._item_info(track) def cmd_next(self, conn): """Advance to the next song in the playlist.""" old_index = self.current_index self.current_index = self._succ_idx() if self.consume: # TODO how does consume interact with single+repeat? self.playlist.pop(old_index) if self.current_index > old_index: self.current_index -= 1 self.playlist_version += 1 self._send_event("playlist") if self.current_index >= len(self.playlist): # Fallen off the end. Move to stopped state or loop. if self.repeat: self.current_index = -1 return self.cmd_play(conn) return self.cmd_stop(conn) elif self.single and not self.repeat: return self.cmd_stop(conn) else: return self.cmd_play(conn) def cmd_previous(self, conn): """Step back to the last song.""" old_index = self.current_index self.current_index = self._prev_idx() if self.consume: self.playlist.pop(old_index) if self.current_index < 0: if self.repeat: self.current_index = len(self.playlist) - 1 else: self.current_index = 0 return self.cmd_play(conn) def cmd_pause(self, conn, state=None): """Set the pause state playback.""" if state is None: self.paused = not self.paused # Toggle. else: self.paused = cast_arg("intbool", state) self._send_event("player") def cmd_play(self, conn, index=-1): """Begin playback, possibly at a specified playlist index.""" index = cast_arg(int, index) if index < -1 or index >= len(self.playlist): raise ArgumentIndexError() if index == -1: # No index specified: start where we are. if not self.playlist: # Empty playlist: stop immediately. return self.cmd_stop(conn) if self.current_index == -1: # No current song. self.current_index = 0 # Start at the beginning. # If we have a current song, just stay there. else: # Start with the specified index. self.current_index = index self.paused = False self._send_event("player") def cmd_playid(self, conn, track_id=0): track_id = cast_arg(int, track_id) if track_id == -1: index = -1 else: index = self._id_to_index(track_id) return self.cmd_play(conn, index) def cmd_stop(self, conn): """Stop playback.""" self.current_index = -1 self.paused = False self._send_event("player") def cmd_seek(self, conn, index, pos): """Seek to a specified point in a specified song.""" index = cast_arg(int, index) if index < 0 or index >= len(self.playlist): raise ArgumentIndexError() self.current_index = index self._send_event("player") def cmd_seekid(self, conn, track_id, pos): index = self._id_to_index(track_id) return self.cmd_seek(conn, index, pos) # Additions to the MPD protocol. def cmd_crash(self, conn): """Deliberately trigger a TypeError for testing purposes. We want to test that the server properly responds with ERROR_SYSTEM without crashing, and that this is not treated as ERROR_ARG (since it is caused by a programming error, not a protocol error). """ raise TypeError class Connection: """A connection between a client and the server.""" def __init__(self, server, sock): """Create a new connection for the accepted socket `client`.""" self.server = server self.sock = sock self.address = "{}:{}".format(*sock.sock.getpeername()) def debug(self, message, kind=" "): """Log a debug message about this connection.""" self.server._log.debug("{}[{}]: {}", kind, self.address, message) def run(self): pass def send(self, lines): """Send lines, which which is either a single string or an iterable consisting of strings, to the client. A newline is added after every string. Returns a Bluelet event that sends the data. """ if isinstance(lines, str): lines = [lines] out = NEWLINE.join(lines) + NEWLINE for line in out.split(NEWLINE)[:-1]: self.debug(line, kind=">") if isinstance(out, str): out = out.encode("utf-8") return self.sock.sendall(out) @classmethod def handler(cls, server): def _handle(sock): """Creates a new `Connection` and runs it.""" return cls(server, sock).run() return _handle class MPDConnection(Connection): """A connection that receives commands from an MPD-compatible client.""" def __init__(self, server, sock): """Create a new connection for the accepted socket `client`.""" super().__init__(server, sock) self.authenticated = False self.notifications = set() self.idle_subscriptions = set() def do_command(self, command): """A coroutine that runs the given command and sends an appropriate response.""" try: yield bluelet.call(command.run(self)) except BPDError as e: # Send the error. yield self.send(e.response()) else: # Send success code. yield self.send(RESP_OK) def disconnect(self): """The connection has closed for any reason.""" self.server.disconnect(self) self.debug("disconnected", kind="*") def notify(self, event): """Queue up an event for sending to this client.""" self.notifications.add(event) def send_notifications(self, force_close_idle=False): """Send the client any queued events now.""" pending = self.notifications.intersection(self.idle_subscriptions) try: for event in pending: yield self.send(f"changed: {event}") if pending or force_close_idle: self.idle_subscriptions = set() self.notifications = self.notifications.difference(pending) yield self.send(RESP_OK) except bluelet.SocketClosedError: self.disconnect() # Client disappeared. def run(self): """Send a greeting to the client and begin processing commands as they arrive. """ self.debug("connected", kind="*") self.server.connect(self) yield self.send(HELLO) clist = None # Initially, no command list is being constructed. while True: line = yield self.sock.readline() if not line: self.disconnect() # Client disappeared. break line = line.strip() if not line: err = BPDError(ERROR_UNKNOWN, "No command given") yield self.send(err.response()) self.disconnect() # Client sent a blank line. break line = line.decode("utf8") # MPD protocol uses UTF-8. for line in line.split(NEWLINE): self.debug(line, kind="<") if self.idle_subscriptions: # The connection is in idle mode. if line == "noidle": yield bluelet.call(self.send_notifications(True)) else: err = BPDError( ERROR_UNKNOWN, f"Got command while idle: {line}" ) yield self.send(err.response()) break continue if line == "noidle": # When not in idle, this command sends no response. continue if clist is not None: # Command list already opened. if line == CLIST_END: yield bluelet.call(self.do_command(clist)) clist = None # Clear the command list. yield bluelet.call(self.server.dispatch_events()) else: clist.append(Command(line)) elif line == CLIST_BEGIN or line == CLIST_VERBOSE_BEGIN: # Begin a command list. clist = CommandList([], line == CLIST_VERBOSE_BEGIN) else: # Ordinary command. try: yield bluelet.call(self.do_command(Command(line))) except BPDCloseError: # Command indicates that the conn should close. self.sock.close() self.disconnect() # Client explicitly closed. return except BPDIdleError as e: self.idle_subscriptions = e.subsystems self.debug( "awaiting: {}".format(" ".join(e.subsystems)), kind="z" ) yield bluelet.call(self.server.dispatch_events()) class ControlConnection(Connection): """A connection used to control BPD for debugging and internal events.""" def __init__(self, server, sock): """Create a new connection for the accepted socket `client`.""" super().__init__(server, sock) def debug(self, message, kind=" "): self.server._log.debug("CTRL {}[{}]: {}", kind, self.address, message) def run(self): """Listen for control commands and delegate to `ctrl_*` methods.""" self.debug("connected", kind="*") while True: line = yield self.sock.readline() if not line: break # Client disappeared. line = line.strip() if not line: break # Client sent a blank line. line = line.decode("utf8") # Protocol uses UTF-8. for line in line.split(NEWLINE): self.debug(line, kind="<") command = Command(line) try: func = command.delegate("ctrl_", self) yield bluelet.call(func(*command.args)) except (AttributeError, TypeError) as e: yield self.send("ERROR: {}".format(e.args[0])) except Exception: yield self.send( ["ERROR: server error", traceback.format_exc().rstrip()] ) def ctrl_play_finished(self): """Callback from the player signalling a song finished playing.""" yield bluelet.call(self.server.dispatch_events()) def ctrl_profile(self): """Memory profiling for debugging.""" from guppy import hpy heap = hpy().heap() yield self.send(heap) def ctrl_nickname(self, oldlabel, newlabel): """Rename a client in the log messages.""" for c in self.server.connections: if c.address == oldlabel: c.address = newlabel break else: yield self.send(f"ERROR: no such client: {oldlabel}") class Command: """A command issued by the client for processing by the server.""" command_re = re.compile(r"^([^ \t]+)[ \t]*") arg_re = re.compile(r'"((?:\\"|[^"])+)"|([^ \t"]+)') def __init__(self, s): """Creates a new `Command` from the given string, `s`, parsing the string for command name and arguments. """ command_match = self.command_re.match(s) self.name = command_match.group(1) self.args = [] arg_matches = self.arg_re.findall(s[command_match.end() :]) for match in arg_matches: if match[0]: # Quoted argument. arg = match[0] arg = arg.replace('\\"', '"').replace("\\\\", "\\") else: # Unquoted argument. arg = match[1] self.args.append(arg) def delegate(self, prefix, target, extra_args=0): """Get the target method that corresponds to this command. The `prefix` is prepended to the command name and then the resulting name is used to search `target` for a method with a compatible number of arguments. """ # Attempt to get correct command function. func_name = prefix + self.name if not hasattr(target, func_name): raise AttributeError(f'unknown command "{self.name}"') func = getattr(target, func_name) argspec = inspect.getfullargspec(func) # Check that `func` is able to handle the number of arguments sent # by the client (so we can raise ERROR_ARG instead of ERROR_SYSTEM). # Maximum accepted arguments: argspec includes "self". max_args = len(argspec.args) - 1 - extra_args # Minimum accepted arguments: some arguments might be optional. min_args = max_args if argspec.defaults: min_args -= len(argspec.defaults) wrong_num = (len(self.args) > max_args) or (len(self.args) < min_args) # If the command accepts a variable number of arguments skip the check. if wrong_num and not argspec.varargs: raise TypeError( 'wrong number of arguments for "{}"'.format(self.name), self.name, ) return func def run(self, conn): """A coroutine that executes the command on the given connection. """ try: # `conn` is an extra argument to all cmd handlers. func = self.delegate("cmd_", conn.server, extra_args=1) except AttributeError as e: raise BPDError(ERROR_UNKNOWN, e.args[0]) except TypeError as e: raise BPDError(ERROR_ARG, e.args[0], self.name) # Ensure we have permission for this command. if ( conn.server.password and not conn.authenticated and self.name not in SAFE_COMMANDS ): raise BPDError(ERROR_PERMISSION, "insufficient privileges") try: args = [conn] + self.args results = func(*args) if results: for data in results: yield conn.send(data) except BPDError as e: # An exposed error. Set the command name and then let # the Connection handle it. e.cmd_name = self.name raise e except BPDCloseError: # An indication that the connection should close. Send # it on the Connection. raise except BPDIdleError: raise except Exception: # An "unintentional" error. Hide it from the client. conn.server._log.error("{}", traceback.format_exc()) raise BPDError(ERROR_SYSTEM, "server error", self.name) class CommandList(List[Command]): """A list of commands issued by the client for processing by the server. May be verbose, in which case the response is delimited, or not. Should be a list of `Command` objects. """ def __init__(self, sequence=None, verbose=False): """Create a new `CommandList` from the given sequence of `Command`s. If `verbose`, this is a verbose command list. """ if sequence: for item in sequence: self.append(item) self.verbose = verbose def run(self, conn): """Coroutine executing all the commands in this list.""" for i, command in enumerate(self): try: yield bluelet.call(command.run(conn)) except BPDError as e: # If the command failed, stop executing. e.index = i # Give the error the correct index. raise e # Otherwise, possibly send the output delimiter if we're in a # verbose ("OK") command list. if self.verbose: yield conn.send(RESP_CLIST_VERBOSE) # A subclass of the basic, protocol-handling server that actually plays # music. class Server(BaseServer): """An MPD-compatible server using GStreamer to play audio and beets to store its library. """ def __init__(self, library, host, port, password, ctrl_port, log): try: from beetsplug.bpd import gstplayer except ImportError as e: # This is a little hacky, but it's the best I know for now. if e.args[0].endswith(" gst"): raise NoGstreamerError() else: raise log.info("Starting server...") super().__init__(host, port, password, ctrl_port, log) self.lib = library self.player = gstplayer.GstPlayer(self.play_finished) self.cmd_update(None) log.info("Server ready and listening on {}:{}".format(host, port)) log.debug( "Listening for control signals on {}:{}".format(host, ctrl_port) ) def run(self): self.player.run() super().run() def play_finished(self): """A callback invoked every time our player finishes a track.""" self.cmd_next(None) self._ctrl_send("play_finished") # Metadata helper functions. def _item_info(self, item): info_lines = [ "file: " + item.destination(fragment=True), "Time: " + str(int(item.length)), "duration: " + f"{item.length:.3f}", "Id: " + str(item.id), ] try: pos = self._id_to_index(item.id) info_lines.append("Pos: " + str(pos)) except ArgumentNotFoundError: # Don't include position if not in playlist. pass for tagtype, field in self.tagtype_map.items(): info_lines.append( "{}: {}".format(tagtype, str(getattr(item, field))) ) return info_lines def _parse_range(self, items, accept_single_number=False): """Convert a range of positions to a list of item info. MPD specifies ranges as START:STOP (endpoint excluded) for some commands. Sometimes a single number can be provided instead. """ try: start, stop = str(items).split(":", 1) except ValueError: if accept_single_number: return [cast_arg(int, items)] raise BPDError(ERROR_ARG, "bad range syntax") start = cast_arg(int, start) stop = cast_arg(int, stop) return range(start, stop) def _item_id(self, item): return item.id # Database updating. def cmd_update(self, conn, path="/"): """Updates the catalog to reflect the current database state.""" # Path is ignored. Also, the real MPD does this asynchronously; # this is done inline. self._log.debug("Building directory tree...") self.tree = vfs.libtree(self.lib) self._log.debug("Finished building directory tree.") self.updated_time = time.time() self._send_event("update") self._send_event("database") # Path (directory tree) browsing. def _resolve_path(self, path): """Returns a VFS node or an item ID located at the path given. If the path does not exist, raises a """ components = path.split("/") node = self.tree for component in components: if not component: continue if isinstance(node, int): # We're trying to descend into a file node. raise ArgumentNotFoundError() if component in node.files: node = node.files[component] elif component in node.dirs: node = node.dirs[component] else: raise ArgumentNotFoundError() return node def _path_join(self, p1, p2): """Smashes together two BPD paths.""" out = p1 + "/" + p2 return out.replace("//", "/").replace("//", "/") def cmd_lsinfo(self, conn, path="/"): """Sends info on all the items in the path.""" node = self._resolve_path(path) if isinstance(node, int): # Trying to list a track. raise BPDError(ERROR_ARG, "this is not a directory") else: for name, itemid in iter(sorted(node.files.items())): item = self.lib.get_item(itemid) yield self._item_info(item) for name, _ in iter(sorted(node.dirs.items())): dirpath = self._path_join(path, name) if dirpath.startswith("/"): # Strip leading slash (libmpc rejects this). dirpath = dirpath[1:] yield "directory: %s" % dirpath def _listall(self, basepath, node, info=False): """Helper function for recursive listing. If info, show tracks' complete info; otherwise, just show items' paths. """ if isinstance(node, int): # List a single file. if info: item = self.lib.get_item(node) yield self._item_info(item) else: yield "file: " + basepath else: # List a directory. Recurse into both directories and files. for name, itemid in sorted(node.files.items()): newpath = self._path_join(basepath, name) # "yield from" yield from self._listall(newpath, itemid, info) for name, subdir in sorted(node.dirs.items()): newpath = self._path_join(basepath, name) yield "directory: " + newpath yield from self._listall(newpath, subdir, info) def cmd_listall(self, conn, path="/"): """Send the paths all items in the directory, recursively.""" return self._listall(path, self._resolve_path(path), False) def cmd_listallinfo(self, conn, path="/"): """Send info on all the items in the directory, recursively.""" return self._listall(path, self._resolve_path(path), True) # Playlist manipulation. def _all_items(self, node): """Generator yielding all items under a VFS node.""" if isinstance(node, int): # Could be more efficient if we built up all the IDs and # then issued a single SELECT. yield self.lib.get_item(node) else: # Recurse into a directory. for name, itemid in sorted(node.files.items()): # "yield from" yield from self._all_items(itemid) for name, subdir in sorted(node.dirs.items()): yield from self._all_items(subdir) def _add(self, path, send_id=False): """Adds a track or directory to the playlist, specified by the path. If `send_id`, write each item's id to the client. """ for item in self._all_items(self._resolve_path(path)): self.playlist.append(item) if send_id: yield "Id: " + str(item.id) self.playlist_version += 1 self._send_event("playlist") def cmd_add(self, conn, path): """Adds a track or directory to the playlist, specified by a path. """ return self._add(path, False) def cmd_addid(self, conn, path): """Same as `cmd_add` but sends an id back to the client.""" return self._add(path, True) # Server info. def cmd_status(self, conn): yield from super().cmd_status(conn) if self.current_index > -1: item = self.playlist[self.current_index] yield ( "bitrate: " + str(item.bitrate / 1000), "audio: {}:{}:{}".format( str(item.samplerate), str(item.bitdepth), str(item.channels), ), ) (pos, total) = self.player.time() yield ( "time: {}:{}".format( str(int(pos)), str(int(total)), ), "elapsed: " + f"{pos:.3f}", "duration: " + f"{total:.3f}", ) # Also missing 'updating_db'. def cmd_stats(self, conn): """Sends some statistics about the library.""" with self.lib.transaction() as tx: statement = ( "SELECT COUNT(DISTINCT artist), " "COUNT(DISTINCT album), " "COUNT(id), " "SUM(length) " "FROM items" ) artists, albums, songs, totaltime = tx.query(statement)[0] yield ( "artists: " + str(artists), "albums: " + str(albums), "songs: " + str(songs), "uptime: " + str(int(time.time() - self.startup_time)), "playtime: " + "0", # Missing. "db_playtime: " + str(int(totaltime)), "db_update: " + str(int(self.updated_time)), ) def cmd_decoders(self, conn): """Send list of supported decoders and formats.""" decoders = self.player.get_decoders() for name, (mimes, exts) in decoders.items(): yield f"plugin: {name}" for ext in exts: yield f"suffix: {ext}" for mime in mimes: yield f"mime_type: {mime}" # Searching. tagtype_map = { "Artist": "artist", "ArtistSort": "artist_sort", "Album": "album", "Title": "title", "Track": "track", "AlbumArtist": "albumartist", "AlbumArtistSort": "albumartist_sort", "Label": "label", "Genre": "genre", "Date": "year", "OriginalDate": "original_year", "Composer": "composer", "Disc": "disc", "Comment": "comments", "MUSICBRAINZ_TRACKID": "mb_trackid", "MUSICBRAINZ_ALBUMID": "mb_albumid", "MUSICBRAINZ_ARTISTID": "mb_artistid", "MUSICBRAINZ_ALBUMARTISTID": "mb_albumartistid", "MUSICBRAINZ_RELEASETRACKID": "mb_releasetrackid", } def cmd_tagtypes(self, conn): """Returns a list of the metadata (tag) fields available for searching. """ for tag in self.tagtype_map: yield "tagtype: " + tag def _tagtype_lookup(self, tag): """Uses `tagtype_map` to look up the beets column name for an MPD tagtype (or throw an appropriate exception). Returns both the canonical name of the MPD tagtype and the beets column name. """ for test_tag, key in self.tagtype_map.items(): # Match case-insensitively. if test_tag.lower() == tag.lower(): return test_tag, key raise BPDError(ERROR_UNKNOWN, "no such tagtype") def _metadata_query(self, query_type, any_query_type, kv): """Helper function returns a query object that will find items according to the library query type provided and the key-value pairs specified. The any_query_type is used for queries of type "any"; if None, then an error is thrown. """ if kv: # At least one key-value pair. queries = [] # Iterate pairwise over the arguments. it = iter(kv) for tag, value in zip(it, it): if tag.lower() == "any": if any_query_type: queries.append( any_query_type( value, ITEM_KEYS_WRITABLE, query_type ) ) else: raise BPDError(ERROR_UNKNOWN, "no such tagtype") else: _, key = self._tagtype_lookup(tag) queries.append(query_type(key, value)) return dbcore.query.AndQuery(queries) else: # No key-value pairs. return dbcore.query.TrueQuery() def cmd_search(self, conn, *kv): """Perform a substring match for items.""" query = self._metadata_query( dbcore.query.SubstringQuery, dbcore.query.AnyFieldQuery, kv ) for item in self.lib.items(query): yield self._item_info(item) def cmd_find(self, conn, *kv): """Perform an exact match for items.""" query = self._metadata_query(dbcore.query.MatchQuery, None, kv) for item in self.lib.items(query): yield self._item_info(item) def cmd_list(self, conn, show_tag, *kv): """List distinct metadata values for show_tag, possibly filtered by matching match_tag to match_term. """ show_tag_canon, show_key = self._tagtype_lookup(show_tag) if len(kv) == 1: if show_tag_canon == "Album": # If no tag was given, assume artist. This is because MPD # supports a short version of this command for fetching the # albums belonging to a particular artist, and some clients # rely on this behaviour (e.g. MPDroid, M.A.L.P.). kv = ("Artist", kv[0]) else: raise BPDError(ERROR_ARG, 'should be "Album" for 3 arguments') elif len(kv) % 2 != 0: raise BPDError(ERROR_ARG, "Incorrect number of filter arguments") query = self._metadata_query(dbcore.query.MatchQuery, None, kv) clause, subvals = query.clause() statement = ( "SELECT DISTINCT " + show_key + " FROM items WHERE " + clause + " ORDER BY " + show_key ) self._log.debug(statement) with self.lib.transaction() as tx: rows = tx.query(statement, subvals) for row in rows: if not row[0]: # Skip any empty values of the field. continue yield show_tag_canon + ": " + str(row[0]) def cmd_count(self, conn, tag, value): """Returns the number and total time of songs matching the tag/value query. """ _, key = self._tagtype_lookup(tag) songs = 0 playtime = 0.0 for item in self.lib.items(dbcore.query.MatchQuery(key, value)): songs += 1 playtime += item.length yield "songs: " + str(songs) yield "playtime: " + str(int(playtime)) # Persistent playlist manipulation. In MPD this is an optional feature so # these dummy implementations match MPD's behaviour with the feature off. def cmd_listplaylist(self, conn, playlist): raise BPDError(ERROR_NO_EXIST, "No such playlist") def cmd_listplaylistinfo(self, conn, playlist): raise BPDError(ERROR_NO_EXIST, "No such playlist") def cmd_listplaylists(self, conn): raise BPDError(ERROR_UNKNOWN, "Stored playlists are disabled") def cmd_load(self, conn, playlist): raise BPDError(ERROR_NO_EXIST, "Stored playlists are disabled") def cmd_playlistadd(self, conn, playlist, uri): raise BPDError(ERROR_UNKNOWN, "Stored playlists are disabled") def cmd_playlistclear(self, conn, playlist): raise BPDError(ERROR_UNKNOWN, "Stored playlists are disabled") def cmd_playlistdelete(self, conn, playlist, index): raise BPDError(ERROR_UNKNOWN, "Stored playlists are disabled") def cmd_playlistmove(self, conn, playlist, from_index, to_index): raise BPDError(ERROR_UNKNOWN, "Stored playlists are disabled") def cmd_rename(self, conn, playlist, new_name): raise BPDError(ERROR_UNKNOWN, "Stored playlists are disabled") def cmd_rm(self, conn, playlist): raise BPDError(ERROR_UNKNOWN, "Stored playlists are disabled") def cmd_save(self, conn, playlist): raise BPDError(ERROR_UNKNOWN, "Stored playlists are disabled") # "Outputs." Just a dummy implementation because we don't control # any outputs. def cmd_outputs(self, conn): """List the available outputs.""" yield ( "outputid: 0", "outputname: gstreamer", "outputenabled: 1", ) def cmd_enableoutput(self, conn, output_id): output_id = cast_arg(int, output_id) if output_id != 0: raise ArgumentIndexError() def cmd_disableoutput(self, conn, output_id): output_id = cast_arg(int, output_id) if output_id == 0: raise BPDError(ERROR_ARG, "cannot disable this output") else: raise ArgumentIndexError() # Playback control. The functions below hook into the # half-implementations provided by the base class. Together, they're # enough to implement all normal playback functionality. def cmd_play(self, conn, index=-1): new_index = index != -1 and index != self.current_index was_paused = self.paused super().cmd_play(conn, index) if self.current_index > -1: # Not stopped. if was_paused and not new_index: # Just unpause. self.player.play() else: self.player.play_file(self.playlist[self.current_index].path) def cmd_pause(self, conn, state=None): super().cmd_pause(conn, state) if self.paused: self.player.pause() elif self.player.playing: self.player.play() def cmd_stop(self, conn): super().cmd_stop(conn) self.player.stop() def cmd_seek(self, conn, index, pos): """Seeks to the specified position in the specified song.""" index = cast_arg(int, index) pos = cast_arg(float, pos) super().cmd_seek(conn, index, pos) self.player.seek(pos) # Volume control. def cmd_setvol(self, conn, vol): vol = cast_arg(int, vol) super().cmd_setvol(conn, vol) self.player.volume = float(vol) / 100 # Beets plugin hooks. class BPDPlugin(BeetsPlugin): """Provides the "beet bpd" command for running a music player server. """ def __init__(self): super().__init__() self.config.add( { "host": "", "port": 6600, "control_port": 6601, "password": "", "volume": VOLUME_MAX, } ) self.config["password"].redact = True def start_bpd(self, lib, host, port, password, volume, ctrl_port): """Starts a BPD server.""" try: server = Server(lib, host, port, password, ctrl_port, self._log) server.cmd_setvol(None, volume) server.run() except NoGstreamerError: self._log.error("Gstreamer Python bindings not found.") self._log.error( 'Install "gstreamer1.0" and "python-gi"' "or similar package to use BPD." ) def commands(self): cmd = beets.ui.Subcommand( "bpd", help="run an MPD-compatible music player server" ) def func(lib, opts, args): host = self.config["host"].as_str() host = args.pop(0) if args else host port = args.pop(0) if args else self.config["port"].get(int) if args: ctrl_port = args.pop(0) else: ctrl_port = self.config["control_port"].get(int) if args: raise beets.ui.UserError("too many arguments") password = self.config["password"].as_str() volume = self.config["volume"].get(int) self.start_bpd( lib, host, int(port), password, volume, int(ctrl_port) ) cmd.func = func return [cmd] beetbox-beets-01f1faf/beetsplug/bpd/gstplayer.py000066400000000000000000000232341472325477400220760ustar00rootroot00000000000000# This file is part of beets. # Copyright 2016, Adrian Sampson. # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the # "Software"), to deal in the Software without restriction, including # without limitation the rights to use, copy, modify, merge, publish, # distribute, sublicense, and/or sell copies of the Software, and to # permit persons to whom the Software is furnished to do so, subject to # the following conditions: # # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. """A wrapper for the GStreamer Python bindings that exposes a simple music player. """ import _thread import copy import os import sys import time import urllib import gi from beets import ui gi.require_version("Gst", "1.0") from gi.repository import GLib, Gst # noqa: E402 Gst.init(None) class QueryError(Exception): pass class GstPlayer: """A music player abstracting GStreamer's Playbin element. Create a player object, then call run() to start a thread with a runloop. Then call play_file to play music. Use player.playing to check whether music is currently playing. A basic play queue is also implemented (just a Python list, player.queue, whose last element is next to play). To use it, just call enqueue() and then play(). When a track finishes and another is available on the queue, it is played automatically. """ def __init__(self, finished_callback=None): """Initialize a player. If a finished_callback is provided, it is called every time a track started with play_file finishes. Once the player has been created, call run() to begin the main runloop in a separate thread. """ # Set up the Gstreamer player. From the pygst tutorial: # https://pygstdocs.berlios.de/pygst-tutorial/playbin.html (gone) # https://brettviren.github.io/pygst-tutorial-org/pygst-tutorial.html #### # Updated to GStreamer 1.0 with: # https://wiki.ubuntu.com/Novacut/GStreamer1.0 self.player = Gst.ElementFactory.make("playbin", "player") if self.player is None: raise ui.UserError("Could not create playbin") fakesink = Gst.ElementFactory.make("fakesink", "fakesink") if fakesink is None: raise ui.UserError("Could not create fakesink") self.player.set_property("video-sink", fakesink) bus = self.player.get_bus() bus.add_signal_watch() bus.connect("message", self._handle_message) # Set up our own stuff. self.playing = False self.finished_callback = finished_callback self.cached_time = None self._volume = 1.0 def _get_state(self): """Returns the current state flag of the playbin.""" # gst's get_state function returns a 3-tuple; we just want the # status flag in position 1. return self.player.get_state(Gst.CLOCK_TIME_NONE)[1] def _handle_message(self, bus, message): """Callback for status updates from GStreamer.""" if message.type == Gst.MessageType.EOS: # file finished playing self.player.set_state(Gst.State.NULL) self.playing = False self.cached_time = None if self.finished_callback: self.finished_callback() elif message.type == Gst.MessageType.ERROR: # error self.player.set_state(Gst.State.NULL) err, debug = message.parse_error() print(f"Error: {err}") self.playing = False def _set_volume(self, volume): """Set the volume level to a value in the range [0, 1.5].""" # And the volume for the playbin. self._volume = volume self.player.set_property("volume", volume) def _get_volume(self): """Get the volume as a float in the range [0, 1.5].""" return self._volume volume = property(_get_volume, _set_volume) def play_file(self, path): """Immediately begin playing the audio file at the given path. """ self.player.set_state(Gst.State.NULL) if isinstance(path, str): path = path.encode("utf-8") uri = "file://" + urllib.parse.quote(path) self.player.set_property("uri", uri) self.player.set_state(Gst.State.PLAYING) self.playing = True def play(self): """If paused, resume playback.""" if self._get_state() == Gst.State.PAUSED: self.player.set_state(Gst.State.PLAYING) self.playing = True def pause(self): """Pause playback.""" self.player.set_state(Gst.State.PAUSED) def stop(self): """Halt playback.""" self.player.set_state(Gst.State.NULL) self.playing = False self.cached_time = None def run(self): """Start a new thread for the player. Call this function before trying to play any music with play_file() or play(). """ # If we don't use the MainLoop, messages are never sent. def start(): loop = GLib.MainLoop() loop.run() _thread.start_new_thread(start, ()) def time(self): """Returns a tuple containing (position, length) where both values are integers in seconds. If no stream is available, returns (0, 0). """ fmt = Gst.Format(Gst.Format.TIME) try: posq = self.player.query_position(fmt) if not posq[0]: raise QueryError("query_position failed") pos = posq[1] / (10**9) lengthq = self.player.query_duration(fmt) if not lengthq[0]: raise QueryError("query_duration failed") length = lengthq[1] / (10**9) self.cached_time = (pos, length) return (pos, length) except QueryError: # Stream not ready. For small gaps of time, for instance # after seeking, the time values are unavailable. For this # reason, we cache recent. if self.playing and self.cached_time: return self.cached_time else: return (0, 0) def seek(self, position): """Seeks to position (in seconds).""" cur_pos, cur_len = self.time() if position > cur_len: self.stop() return fmt = Gst.Format(Gst.Format.TIME) ns = position * 10**9 # convert to nanoseconds self.player.seek_simple(fmt, Gst.SeekFlags.FLUSH, ns) # save new cached time self.cached_time = (position, cur_len) def block(self): """Block until playing finishes.""" while self.playing: time.sleep(1) def get_decoders(self): return get_decoders() def get_decoders(): """Get supported audio decoders from GStreamer. Returns a dict mapping decoder element names to the associated media types and file extensions. """ # We only care about audio decoder elements. filt = ( Gst.ELEMENT_FACTORY_TYPE_DEPAYLOADER | Gst.ELEMENT_FACTORY_TYPE_DEMUXER | Gst.ELEMENT_FACTORY_TYPE_PARSER | Gst.ELEMENT_FACTORY_TYPE_DECODER | Gst.ELEMENT_FACTORY_TYPE_MEDIA_AUDIO ) decoders = {} mime_types = set() for f in Gst.ElementFactory.list_get_elements(filt, Gst.Rank.NONE): for pad in f.get_static_pad_templates(): if pad.direction == Gst.PadDirection.SINK: caps = pad.static_caps.get() mimes = set() for i in range(caps.get_size()): struct = caps.get_structure(i) mime = struct.get_name() if mime == "unknown/unknown": continue mimes.add(mime) mime_types.add(mime) if mimes: decoders[f.get_name()] = (mimes, set()) # Check all the TypeFindFactory plugin features form the registry. If they # are associated with an audio media type that we found above, get the list # of corresponding file extensions. mime_extensions = {mime: set() for mime in mime_types} for feat in Gst.Registry.get().get_feature_list(Gst.TypeFindFactory): caps = feat.get_caps() if caps: for i in range(caps.get_size()): struct = caps.get_structure(i) mime = struct.get_name() if mime in mime_types: mime_extensions[mime].update(feat.get_extensions()) # Fill in the slot we left for file extensions. for name, (mimes, exts) in decoders.items(): for mime in mimes: exts.update(mime_extensions[mime]) return decoders def play_simple(paths): """Play the files in paths in a straightforward way, without using the player's callback function. """ p = GstPlayer() p.run() for path in paths: p.play_file(path) p.block() def play_complicated(paths): """Play the files in the path one after the other by using the callback function to advance to the next song. """ my_paths = copy.copy(paths) def next_song(): my_paths.pop(0) p.play_file(my_paths[0]) p = GstPlayer(next_song) p.run() p.play_file(my_paths[0]) while my_paths: time.sleep(1) if __name__ == "__main__": # A very simple command-line player. Just give it names of audio # files on the command line; these are all played in sequence. paths = [os.path.abspath(os.path.expanduser(p)) for p in sys.argv[1:]] # play_simple(paths) play_complicated(paths) beetbox-beets-01f1faf/beetsplug/bpm.py000066400000000000000000000051561472325477400201000ustar00rootroot00000000000000# This file is part of beets. # Copyright 2016, aroquen # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the # "Software"), to deal in the Software without restriction, including # without limitation the rights to use, copy, modify, merge, publish, # distribute, sublicense, and/or sell copies of the Software, and to # permit persons to whom the Software is furnished to do so, subject to # the following conditions: # # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. """Determine BPM by pressing a key to the rhythm.""" import time from beets import ui from beets.plugins import BeetsPlugin def bpm(max_strokes): """Returns average BPM (possibly of a playing song) listening to Enter keystrokes. """ t0 = None dt = [] for i in range(max_strokes): # Press enter to the rhythm... s = input() if s == "": t1 = time.time() # Only start measuring at the second stroke if t0: dt.append(t1 - t0) t0 = t1 else: break # Return average BPM # bpm = (max_strokes-1) / sum(dt) * 60 ave = sum([1.0 / dti * 60 for dti in dt]) / len(dt) return ave class BPMPlugin(BeetsPlugin): def __init__(self): super().__init__() self.config.add( { "max_strokes": 3, "overwrite": True, } ) def commands(self): cmd = ui.Subcommand( "bpm", help="determine bpm of a song by pressing " "a key to the rhythm", ) cmd.func = self.command return [cmd] def command(self, lib, opts, args): items = lib.items(ui.decargs(args)) write = ui.should_write() self.get_bpm(items, write) def get_bpm(self, items, write=False): overwrite = self.config["overwrite"].get(bool) if len(items) > 1: raise ValueError("Can only get bpm of one song at time") item = items[0] if item["bpm"]: self._log.info("Found bpm {0}", item["bpm"]) if not overwrite: return self._log.info( "Press Enter {0} times to the rhythm or Ctrl-D " "to exit", self.config["max_strokes"].get(int), ) new_bpm = bpm(self.config["max_strokes"].get(int)) item["bpm"] = int(new_bpm) if write: item.try_write() item.store() self._log.info("Added new bpm {0}", item["bpm"]) beetbox-beets-01f1faf/beetsplug/bpsync.py000066400000000000000000000146651472325477400206250ustar00rootroot00000000000000# This file is part of beets. # Copyright 2019, Rahul Ahuja. # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the # "Software"), to deal in the Software without restriction, including # without limitation the rights to use, copy, modify, merge, publish, # distribute, sublicense, and/or sell copies of the Software, and to # permit persons to whom the Software is furnished to do so, subject to # the following conditions: # # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. """Update library's tags using Beatport.""" from beets import autotag, library, ui, util from beets.plugins import BeetsPlugin, apply_item_changes from .beatport import BeatportPlugin class BPSyncPlugin(BeetsPlugin): def __init__(self): super().__init__() self.beatport_plugin = BeatportPlugin() self.beatport_plugin.setup() def commands(self): cmd = ui.Subcommand("bpsync", help="update metadata from Beatport") cmd.parser.add_option( "-p", "--pretend", action="store_true", help="show all changes but do nothing", ) cmd.parser.add_option( "-m", "--move", action="store_true", dest="move", help="move files in the library directory", ) cmd.parser.add_option( "-M", "--nomove", action="store_false", dest="move", help="don't move files in library", ) cmd.parser.add_option( "-W", "--nowrite", action="store_false", default=None, dest="write", help="don't write updated metadata to files", ) cmd.parser.add_format_option() cmd.func = self.func return [cmd] def func(self, lib, opts, args): """Command handler for the bpsync function.""" move = ui.should_move(opts.move) pretend = opts.pretend write = ui.should_write(opts.write) query = ui.decargs(args) self.singletons(lib, query, move, pretend, write) self.albums(lib, query, move, pretend, write) def singletons(self, lib, query, move, pretend, write): """Retrieve and apply info from the autotagger for items matched by query. """ for item in lib.items(query + ["singleton:true"]): if not item.mb_trackid: self._log.info( "Skipping singleton with no mb_trackid: {}", item ) continue if not self.is_beatport_track(item): self._log.info( "Skipping non-{} singleton: {}", self.beatport_plugin.data_source, item, ) continue # Apply. trackinfo = self.beatport_plugin.track_for_id(item.mb_trackid) with lib.transaction(): autotag.apply_item_metadata(item, trackinfo) apply_item_changes(lib, item, move, pretend, write) @staticmethod def is_beatport_track(item): return ( item.get("data_source") == BeatportPlugin.data_source and item.mb_trackid.isnumeric() ) def get_album_tracks(self, album): if not album.mb_albumid: self._log.info("Skipping album with no mb_albumid: {}", album) return False if not album.mb_albumid.isnumeric(): self._log.info( "Skipping album with invalid {} ID: {}", self.beatport_plugin.data_source, album, ) return False items = list(album.items()) if album.get("data_source") == self.beatport_plugin.data_source: return items if not all(self.is_beatport_track(item) for item in items): self._log.info( "Skipping non-{} release: {}", self.beatport_plugin.data_source, album, ) return False return items def albums(self, lib, query, move, pretend, write): """Retrieve and apply info from the autotagger for albums matched by query and their items. """ # Process matching albums. for album in lib.albums(query): # Do we have a valid Beatport album? items = self.get_album_tracks(album) if not items: continue # Get the Beatport album information. albuminfo = self.beatport_plugin.album_for_id(album.mb_albumid) if not albuminfo: self._log.info( "Release ID {} not found for album {}", album.mb_albumid, album, ) continue beatport_trackid_to_trackinfo = { track.track_id: track for track in albuminfo.tracks } library_trackid_to_item = { int(item.mb_trackid): item for item in items } item_to_trackinfo = { item: beatport_trackid_to_trackinfo[track_id] for track_id, item in library_trackid_to_item.items() } self._log.info("applying changes to {}", album) with lib.transaction(): autotag.apply_metadata(albuminfo, item_to_trackinfo) changed = False # Find any changed item to apply Beatport changes to album. any_changed_item = items[0] for item in items: item_changed = ui.show_model_changes(item) changed |= item_changed if item_changed: any_changed_item = item apply_item_changes(lib, item, move, pretend, write) if pretend or not changed: continue # Update album structure to reflect an item in it. for key in library.Album.item_keys: album[key] = any_changed_item[key] album.store() # Move album art (and any inconsistent items). if move and lib.directory in util.ancestry(items[0].path): self._log.debug("moving album {}", album) album.move() beetbox-beets-01f1faf/beetsplug/bucket.py000066400000000000000000000175451472325477400206040ustar00rootroot00000000000000# This file is part of beets. # Copyright 2016, Fabrice Laporte. # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the # "Software"), to deal in the Software without restriction, including # without limitation the rights to use, copy, modify, merge, publish, # distribute, sublicense, and/or sell copies of the Software, and to # permit persons to whom the Software is furnished to do so, subject to # the following conditions: # # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. """Provides the %bucket{} function for path formatting.""" import re import string from datetime import datetime from itertools import tee from beets import plugins, ui ASCII_DIGITS = string.digits + string.ascii_lowercase class BucketError(Exception): pass def pairwise(iterable): "s -> (s0,s1), (s1,s2), (s2, s3), ..." a, b = tee(iterable) next(b, None) return zip(a, b) def span_from_str(span_str): """Build a span dict from the span string representation.""" def normalize_year(d, yearfrom): """Convert string to a 4 digits year""" if yearfrom < 100: raise BucketError("%d must be expressed on 4 digits" % yearfrom) # if two digits only, pick closest year that ends by these two # digits starting from yearfrom if d < 100: if (d % 100) < (yearfrom % 100): d = (yearfrom - yearfrom % 100) + 100 + d else: d = (yearfrom - yearfrom % 100) + d return d years = [int(x) for x in re.findall(r"\d+", span_str)] if not years: raise ui.UserError( "invalid range defined for year bucket '%s': no " "year found" % span_str ) try: years = [normalize_year(x, years[0]) for x in years] except BucketError as exc: raise ui.UserError( "invalid range defined for year bucket '%s': %s" % (span_str, exc) ) res = {"from": years[0], "str": span_str} if len(years) > 1: res["to"] = years[-1] return res def complete_year_spans(spans): """Set the `to` value of spans if empty and sort them chronologically.""" spans.sort(key=lambda x: x["from"]) for x, y in pairwise(spans): if "to" not in x: x["to"] = y["from"] - 1 if spans and "to" not in spans[-1]: spans[-1]["to"] = datetime.now().year def extend_year_spans(spans, spanlen, start=1900, end=2014): """Add new spans to given spans list so that every year of [start,end] belongs to a span. """ extended_spans = spans[:] for x, y in pairwise(spans): # if a gap between two spans, fill the gap with as much spans of # spanlen length as necessary for span_from in range(x["to"] + 1, y["from"], spanlen): extended_spans.append({"from": span_from}) # Create spans prior to declared ones for span_from in range(spans[0]["from"] - spanlen, start, -spanlen): extended_spans.append({"from": span_from}) # Create spans after the declared ones for span_from in range(spans[-1]["to"] + 1, end, spanlen): extended_spans.append({"from": span_from}) complete_year_spans(extended_spans) return extended_spans def build_year_spans(year_spans_str): """Build a chronologically ordered list of spans dict from unordered spans stringlist. """ spans = [] for elem in year_spans_str: spans.append(span_from_str(elem)) complete_year_spans(spans) return spans def str2fmt(s): """Deduces formatting syntax from a span string.""" regex = re.compile( r"(?P\D*)(?P\d+)(?P\D*)" r"(?P\d*)(?P\D*)" ) m = re.match(regex, s) res = { "fromnchars": len(m.group("fromyear")), "tonchars": len(m.group("toyear")), } res["fmt"] = "{}%s{}{}{}".format( m.group("bef"), m.group("sep"), "%s" if res["tonchars"] else "", m.group("after"), ) return res def format_span(fmt, yearfrom, yearto, fromnchars, tonchars): """Return a span string representation.""" args = str(yearfrom)[-fromnchars:] if tonchars: args = (str(yearfrom)[-fromnchars:], str(yearto)[-tonchars:]) return fmt % args def extract_modes(spans): """Extract the most common spans lengths and representation formats""" rangelen = sorted([x["to"] - x["from"] + 1 for x in spans]) deflen = sorted(rangelen, key=rangelen.count)[-1] reprs = [str2fmt(x["str"]) for x in spans] deffmt = sorted(reprs, key=reprs.count)[-1] return deflen, deffmt def build_alpha_spans(alpha_spans_str, alpha_regexs): """Extract alphanumerics from string and return sorted list of chars [from...to] """ spans = [] for elem in alpha_spans_str: if elem in alpha_regexs: spans.append(re.compile(alpha_regexs[elem])) else: bucket = sorted([x for x in elem.lower() if x.isalnum()]) if bucket: begin_index = ASCII_DIGITS.index(bucket[0]) end_index = ASCII_DIGITS.index(bucket[-1]) else: raise ui.UserError( "invalid range defined for alpha bucket " "'%s': no alphanumeric character found" % elem ) spans.append( re.compile( "^[" + ASCII_DIGITS[begin_index : end_index + 1] + ASCII_DIGITS[begin_index : end_index + 1].upper() + "]" ) ) return spans class BucketPlugin(plugins.BeetsPlugin): def __init__(self): super().__init__() self.template_funcs["bucket"] = self._tmpl_bucket self.config.add( { "bucket_year": [], "bucket_alpha": [], "bucket_alpha_regex": {}, "extrapolate": False, } ) self.setup() def setup(self): """Setup plugin from config options""" self.year_spans = build_year_spans(self.config["bucket_year"].get()) if self.year_spans and self.config["extrapolate"]: [self.ys_len_mode, self.ys_repr_mode] = extract_modes( self.year_spans ) self.year_spans = extend_year_spans( self.year_spans, self.ys_len_mode ) self.alpha_spans = build_alpha_spans( self.config["bucket_alpha"].get(), self.config["bucket_alpha_regex"].get(), ) def find_bucket_year(self, year): """Return bucket that matches given year or return the year if no matching bucket. """ for ys in self.year_spans: if ys["from"] <= int(year) <= ys["to"]: if "str" in ys: return ys["str"] else: return format_span( self.ys_repr_mode["fmt"], ys["from"], ys["to"], self.ys_repr_mode["fromnchars"], self.ys_repr_mode["tonchars"], ) return year def find_bucket_alpha(self, s): """Return alpha-range bucket that matches given string or return the string initial if no matching bucket. """ for i, span in enumerate(self.alpha_spans): if span.match(s): return self.config["bucket_alpha"].get()[i] return s[0].upper() def _tmpl_bucket(self, text, field=None): if not field and len(text) == 4 and text.isdigit(): field = "year" if field == "year": func = self.find_bucket_year else: func = self.find_bucket_alpha return func(text) beetbox-beets-01f1faf/beetsplug/chroma.py000066400000000000000000000271641472325477400205760ustar00rootroot00000000000000# This file is part of beets. # Copyright 2016, Adrian Sampson. # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the # "Software"), to deal in the Software without restriction, including # without limitation the rights to use, copy, modify, merge, publish, # distribute, sublicense, and/or sell copies of the Software, and to # permit persons to whom the Software is furnished to do so, subject to # the following conditions: # # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. """Adds Chromaprint/Acoustid acoustic fingerprinting support to the autotagger. Requires the pyacoustid library. """ import re from collections import defaultdict from functools import partial import acoustid import confuse from beets import config, plugins, ui, util from beets.autotag import hooks API_KEY = "1vOwZtEn" SCORE_THRESH = 0.5 TRACK_ID_WEIGHT = 10.0 COMMON_REL_THRESH = 0.6 # How many tracks must have an album in common? MAX_RECORDINGS = 5 MAX_RELEASES = 5 # Stores the Acoustid match information for each track. This is # populated when an import task begins and then used when searching for # candidates. It maps audio file paths to (recording_ids, release_ids) # pairs. If a given path is not present in the mapping, then no match # was found. _matches = {} # Stores the fingerprint and Acoustid ID for each track. This is stored # as metadata for each track for later use but is not relevant for # autotagging. _fingerprints = {} _acoustids = {} def prefix(it, count): """Truncate an iterable to at most `count` items.""" for i, v in enumerate(it): if i >= count: break yield v def releases_key(release, countries, original_year): """Used as a key to sort releases by date then preferred country""" date = release.get("date") if date and original_year: year = date.get("year", 9999) month = date.get("month", 99) day = date.get("day", 99) else: year = 9999 month = 99 day = 99 # Uses index of preferred countries to sort country_key = 99 if release.get("country"): for i, country in enumerate(countries): if country.match(release["country"]): country_key = i break return (year, month, day, country_key) def acoustid_match(log, path): """Gets metadata for a file from Acoustid and populates the _matches, _fingerprints, and _acoustids dictionaries accordingly. """ try: duration, fp = acoustid.fingerprint_file(util.syspath(path)) except acoustid.FingerprintGenerationError as exc: log.error( "fingerprinting of {0} failed: {1}", util.displayable_path(repr(path)), exc, ) return None fp = fp.decode() _fingerprints[path] = fp try: res = acoustid.lookup(API_KEY, fp, duration, meta="recordings releases") except acoustid.AcoustidError as exc: log.debug( "fingerprint matching {0} failed: {1}", util.displayable_path(repr(path)), exc, ) return None log.debug("chroma: fingerprinted {0}", util.displayable_path(repr(path))) # Ensure the response is usable and parse it. if res["status"] != "ok" or not res.get("results"): log.debug("no match found") return None result = res["results"][0] # Best match. if result["score"] < SCORE_THRESH: log.debug("no results above threshold") return None _acoustids[path] = result["id"] # Get recording and releases from the result if not result.get("recordings"): log.debug("no recordings found") return None recording_ids = [] releases = [] for recording in result["recordings"]: recording_ids.append(recording["id"]) if "releases" in recording: releases.extend(recording["releases"]) # The releases list is essentially in random order from the Acoustid lookup # so we optionally sort it using the match.preferred configuration options. # 'original_year' to sort the earliest first and # 'countries' to then sort preferred countries first. country_patterns = config["match"]["preferred"]["countries"].as_str_seq() countries = [re.compile(pat, re.I) for pat in country_patterns] original_year = config["match"]["preferred"]["original_year"] releases.sort( key=partial( releases_key, countries=countries, original_year=original_year ) ) release_ids = [rel["id"] for rel in releases] log.debug( "matched recordings {0} on releases {1}", recording_ids, release_ids ) _matches[path] = recording_ids, release_ids # Plugin structure and autotagging logic. def _all_releases(items): """Given an iterable of Items, determines (according to Acoustid) which releases the items have in common. Generates release IDs. """ # Count the number of "hits" for each release. relcounts = defaultdict(int) for item in items: if item.path not in _matches: continue _, release_ids = _matches[item.path] for release_id in release_ids: relcounts[release_id] += 1 for release_id, count in relcounts.items(): if float(count) / len(items) > COMMON_REL_THRESH: yield release_id class AcoustidPlugin(plugins.BeetsPlugin): def __init__(self): super().__init__() self.config.add( { "auto": True, } ) config["acoustid"]["apikey"].redact = True if self.config["auto"]: self.register_listener("import_task_start", self.fingerprint_task) self.register_listener("import_task_apply", apply_acoustid_metadata) def fingerprint_task(self, task, session): return fingerprint_task(self._log, task, session) def track_distance(self, item, info): dist = hooks.Distance() if item.path not in _matches or not info.track_id: # Match failed or no track ID. return dist recording_ids, _ = _matches[item.path] dist.add_expr("track_id", info.track_id not in recording_ids) return dist def candidates(self, items, artist, album, va_likely, extra_tags=None): albums = [] for relid in prefix(_all_releases(items), MAX_RELEASES): album = hooks.album_for_mbid(relid) if album: albums.append(album) self._log.debug("acoustid album candidates: {0}", len(albums)) return albums def item_candidates(self, item, artist, title): if item.path not in _matches: return [] recording_ids, _ = _matches[item.path] tracks = [] for recording_id in prefix(recording_ids, MAX_RECORDINGS): track = hooks.track_for_mbid(recording_id) if track: tracks.append(track) self._log.debug("acoustid item candidates: {0}", len(tracks)) return tracks def commands(self): submit_cmd = ui.Subcommand( "submit", help="submit Acoustid fingerprints" ) def submit_cmd_func(lib, opts, args): try: apikey = config["acoustid"]["apikey"].as_str() except confuse.NotFoundError: raise ui.UserError("no Acoustid user API key provided") submit_items(self._log, apikey, lib.items(ui.decargs(args))) submit_cmd.func = submit_cmd_func fingerprint_cmd = ui.Subcommand( "fingerprint", help="generate fingerprints for items without them" ) def fingerprint_cmd_func(lib, opts, args): for item in lib.items(ui.decargs(args)): fingerprint_item(self._log, item, write=ui.should_write()) fingerprint_cmd.func = fingerprint_cmd_func return [submit_cmd, fingerprint_cmd] # Hooks into import process. def fingerprint_task(log, task, session): """Fingerprint each item in the task for later use during the autotagging candidate search. """ items = task.items if task.is_album else [task.item] for item in items: acoustid_match(log, item.path) def apply_acoustid_metadata(task, session): """Apply Acoustid metadata (fingerprint and ID) to the task's items.""" for item in task.imported_items(): if item.path in _fingerprints: item.acoustid_fingerprint = _fingerprints[item.path] if item.path in _acoustids: item.acoustid_id = _acoustids[item.path] # UI commands. def submit_items(log, userkey, items, chunksize=64): """Submit fingerprints for the items to the Acoustid server.""" data = [] # The running list of dictionaries to submit. def submit_chunk(): """Submit the current accumulated fingerprint data.""" log.info("submitting {0} fingerprints", len(data)) try: acoustid.submit(API_KEY, userkey, data) except acoustid.AcoustidError as exc: log.warning("acoustid submission error: {0}", exc) del data[:] for item in items: fp = fingerprint_item(log, item, write=ui.should_write()) # Construct a submission dictionary for this item. item_data = { "duration": int(item.length), "fingerprint": fp, } if item.mb_trackid: item_data["mbid"] = item.mb_trackid log.debug("submitting MBID") else: item_data.update( { "track": item.title, "artist": item.artist, "album": item.album, "albumartist": item.albumartist, "year": item.year, "trackno": item.track, "discno": item.disc, } ) log.debug("submitting textual metadata") data.append(item_data) # If we have enough data, submit a chunk. if len(data) >= chunksize: submit_chunk() # Submit remaining data in a final chunk. if data: submit_chunk() def fingerprint_item(log, item, write=False): """Get the fingerprint for an Item. If the item already has a fingerprint, it is not regenerated. If fingerprint generation fails, return None. If the items are associated with a library, they are saved to the database. If `write` is set, then the new fingerprints are also written to files' metadata. """ # Get a fingerprint and length for this track. if not item.length: log.info("{0}: no duration available", util.displayable_path(item.path)) elif item.acoustid_fingerprint: if write: log.info( "{0}: fingerprint exists, skipping", util.displayable_path(item.path), ) else: log.info( "{0}: using existing fingerprint", util.displayable_path(item.path), ) return item.acoustid_fingerprint else: log.info("{0}: fingerprinting", util.displayable_path(item.path)) try: _, fp = acoustid.fingerprint_file(util.syspath(item.path)) item.acoustid_fingerprint = fp.decode() if write: log.info( "{0}: writing fingerprint", util.displayable_path(item.path) ) item.try_write() if item._db: item.store() return item.acoustid_fingerprint except acoustid.FingerprintGenerationError as exc: log.info("fingerprint generation failed: {0}", exc) beetbox-beets-01f1faf/beetsplug/convert.py000066400000000000000000000623311472325477400210000ustar00rootroot00000000000000# This file is part of beets. # Copyright 2016, Jakob Schnitzer. # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the # "Software"), to deal in the Software without restriction, including # without limitation the rights to use, copy, modify, merge, publish, # distribute, sublicense, and/or sell copies of the Software, and to # permit persons to whom the Software is furnished to do so, subject to # the following conditions: # # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. """Converts tracks or albums to external directory""" import logging import os import shlex import subprocess import tempfile import threading from string import Template from confuse import ConfigTypeError, Optional from beets import art, config, plugins, ui, util from beets.library import Item, parse_query_string from beets.plugins import BeetsPlugin from beets.util import arg_encoding, par_map from beets.util.artresizer import ArtResizer from beets.util.m3u import M3UFile _fs_lock = threading.Lock() _temp_files = [] # Keep track of temporary transcoded files for deletion. # Some convenient alternate names for formats. ALIASES = { "windows media": "wma", "vorbis": "ogg", } LOSSLESS_FORMATS = ["ape", "flac", "alac", "wave", "aiff"] def replace_ext(path, ext): """Return the path with its extension replaced by `ext`. The new extension must not contain a leading dot. """ ext_dot = b"." + ext return os.path.splitext(path)[0] + ext_dot def get_format(fmt=None): """Return the command template and the extension from the config.""" if not fmt: fmt = config["convert"]["format"].as_str().lower() fmt = ALIASES.get(fmt, fmt) try: format_info = config["convert"]["formats"][fmt].get(dict) command = format_info["command"] extension = format_info.get("extension", fmt) except KeyError: raise ui.UserError( 'convert: format {} needs the "command" field'.format(fmt) ) except ConfigTypeError: command = config["convert"]["formats"][fmt].get(str) extension = fmt # Convenience and backwards-compatibility shortcuts. keys = config["convert"].keys() if "command" in keys: command = config["convert"]["command"].as_str() elif "opts" in keys: # Undocumented option for backwards compatibility with < 1.3.1. command = "ffmpeg -i $source -y {} $dest".format( config["convert"]["opts"].as_str() ) if "extension" in keys: extension = config["convert"]["extension"].as_str() return (command.encode("utf-8"), extension.encode("utf-8")) def in_no_convert(item: Item) -> bool: no_convert_query = config["convert"]["no_convert"].as_str() if no_convert_query: query, _ = parse_query_string(no_convert_query, Item) return query.match(item) else: return False def should_transcode(item, fmt): """Determine whether the item should be transcoded as part of conversion (i.e., its bitrate is high or it has the wrong format). """ if in_no_convert(item) or ( config["convert"]["never_convert_lossy_files"] and item.format.lower() not in LOSSLESS_FORMATS ): return False maxbr = config["convert"]["max_bitrate"].get(Optional(int)) if maxbr is not None and item.bitrate >= 1000 * maxbr: return True return fmt.lower() != item.format.lower() class ConvertPlugin(BeetsPlugin): def __init__(self): super().__init__() self.config.add( { "dest": None, "pretend": False, "link": False, "hardlink": False, "threads": os.cpu_count(), "format": "mp3", "id3v23": "inherit", "formats": { "aac": { "command": "ffmpeg -i $source -y -vn -acodec aac " "-aq 1 $dest", "extension": "m4a", }, "alac": { "command": "ffmpeg -i $source -y -vn -acodec alac $dest", "extension": "m4a", }, "flac": "ffmpeg -i $source -y -vn -acodec flac $dest", "mp3": "ffmpeg -i $source -y -vn -aq 2 $dest", "opus": "ffmpeg -i $source -y -vn -acodec libopus -ab 96k $dest", "ogg": "ffmpeg -i $source -y -vn -acodec libvorbis -aq 3 $dest", "wma": "ffmpeg -i $source -y -vn -acodec wmav2 -vn $dest", }, "max_bitrate": None, "auto": False, "auto_keep": False, "tmpdir": None, "quiet": False, "embed": True, "paths": {}, "no_convert": "", "never_convert_lossy_files": False, "copy_album_art": False, "album_art_maxwidth": 0, "delete_originals": False, "playlist": None, } ) self.early_import_stages = [self.auto_convert, self.auto_convert_keep] self.register_listener("import_task_files", self._cleanup) def commands(self): cmd = ui.Subcommand("convert", help="convert to external location") cmd.parser.add_option( "-p", "--pretend", action="store_true", help="show actions but do nothing", ) cmd.parser.add_option( "-t", "--threads", action="store", type="int", help="change the number of threads, \ defaults to maximum available processors", ) cmd.parser.add_option( "-k", "--keep-new", action="store_true", dest="keep_new", help="keep only the converted \ and move the old files", ) cmd.parser.add_option( "-d", "--dest", action="store", help="set the destination directory" ) cmd.parser.add_option( "-f", "--format", action="store", dest="format", help="set the target format of the tracks", ) cmd.parser.add_option( "-y", "--yes", action="store_true", dest="yes", help="do not ask for confirmation", ) cmd.parser.add_option( "-l", "--link", action="store_true", dest="link", help="symlink files that do not \ need transcoding.", ) cmd.parser.add_option( "-H", "--hardlink", action="store_true", dest="hardlink", help="hardlink files that do not \ need transcoding. Overrides --link.", ) cmd.parser.add_option( "-m", "--playlist", action="store", help="""create an m3u8 playlist file containing the converted files. The playlist file will be saved below the destination directory, thus PLAYLIST could be a file name or a relative path. To ensure a working playlist when transferred to a different computer, or opened from an external drive, relative paths pointing to media files will be used.""", ) cmd.parser.add_album_option() cmd.func = self.convert_func return [cmd] def auto_convert(self, config, task): if self.config["auto"]: par_map( lambda item: self.convert_on_import(config.lib, item), task.imported_items(), ) def auto_convert_keep(self, config, task): if self.config["auto_keep"]: empty_opts = self.commands()[0].parser.get_default_values() ( dest, threads, path_formats, fmt, pretend, hardlink, link, playlist, ) = self._get_opts_and_config(empty_opts) items = task.imported_items() self._parallel_convert( dest, False, path_formats, fmt, pretend, link, hardlink, threads, items, ) # Utilities converted from functions to methods on logging overhaul def encode(self, command, source, dest, pretend=False): """Encode `source` to `dest` using command template `command`. Raises `subprocess.CalledProcessError` if the command exited with a non-zero status code. """ # The paths and arguments must be bytes. assert isinstance(command, bytes) assert isinstance(source, bytes) assert isinstance(dest, bytes) quiet = self.config["quiet"].get(bool) if not quiet and not pretend: self._log.info("Encoding {0}", util.displayable_path(source)) command = command.decode(arg_encoding(), "surrogateescape") source = os.fsdecode(source) dest = os.fsdecode(dest) # Substitute $source and $dest in the argument list. args = shlex.split(command) encode_cmd = [] for i, arg in enumerate(args): args[i] = Template(arg).safe_substitute( { "source": source, "dest": dest, } ) encode_cmd.append(args[i].encode(util.arg_encoding())) if pretend: self._log.info("{0}", " ".join(ui.decargs(args))) return try: util.command_output(encode_cmd) except subprocess.CalledProcessError as exc: # Something went wrong (probably Ctrl+C), remove temporary files self._log.info( "Encoding {0} failed. Cleaning up...", util.displayable_path(source), ) self._log.debug( "Command {0} exited with status {1}: {2}", args, exc.returncode, exc.output, ) util.remove(dest) util.prune_dirs(os.path.dirname(dest)) raise except OSError as exc: raise ui.UserError( "convert: couldn't invoke '{}': {}".format( " ".join(ui.decargs(args)), exc ) ) if not quiet and not pretend: self._log.info( "Finished encoding {0}", util.displayable_path(source) ) def convert_item( self, dest_dir, keep_new, path_formats, fmt, pretend=False, link=False, hardlink=False, ): """A pipeline thread that converts `Item` objects from a library. """ command, ext = get_format(fmt) item, original, converted = None, None, None while True: item = yield (item, original, converted) dest = item.destination(basedir=dest_dir, path_formats=path_formats) # When keeping the new file in the library, we first move the # current (pristine) file to the destination. We'll then copy it # back to its old path or transcode it to a new path. if keep_new: original = dest converted = item.path if should_transcode(item, fmt): converted = replace_ext(converted, ext) else: original = item.path if should_transcode(item, fmt): dest = replace_ext(dest, ext) converted = dest # Ensure that only one thread tries to create directories at a # time. (The existence check is not atomic with the directory # creation inside this function.) if not pretend: with _fs_lock: util.mkdirall(dest) if os.path.exists(util.syspath(dest)): self._log.info( "Skipping {0} (target file exists)", util.displayable_path(item.path), ) continue if keep_new: if pretend: self._log.info( "mv {0} {1}", util.displayable_path(item.path), util.displayable_path(original), ) else: self._log.info( "Moving to {0}", util.displayable_path(original) ) util.move(item.path, original) if should_transcode(item, fmt): linked = False try: self.encode(command, original, converted, pretend) except subprocess.CalledProcessError: continue else: linked = link or hardlink if pretend: msg = "ln" if hardlink else ("ln -s" if link else "cp") self._log.info( "{2} {0} {1}", util.displayable_path(original), util.displayable_path(converted), msg, ) else: # No transcoding necessary. msg = ( "Hardlinking" if hardlink else ("Linking" if link else "Copying") ) self._log.info( "{1} {0}", util.displayable_path(item.path), msg ) if hardlink: util.hardlink(original, converted) elif link: util.link(original, converted) else: util.copy(original, converted) if pretend: continue id3v23 = self.config["id3v23"].as_choice([True, False, "inherit"]) if id3v23 == "inherit": id3v23 = None # Write tags from the database to the converted file. item.try_write(path=converted, id3v23=id3v23) if keep_new: # If we're keeping the transcoded file, read it again (after # writing) to get new bitrate, duration, etc. item.path = converted item.read() item.store() # Store new path and audio data. if self.config["embed"] and not linked: album = item._cached_album if album and album.artpath: maxwidth = self._get_art_resize(album.artpath) self._log.debug( "embedding album art from {}", util.displayable_path(album.artpath), ) art.embed_item( self._log, item, album.artpath, maxwidth, itempath=converted, id3v23=id3v23, ) if keep_new: plugins.send( "after_convert", item=item, dest=dest, keepnew=True ) else: plugins.send( "after_convert", item=item, dest=converted, keepnew=False ) def copy_album_art( self, album, dest_dir, path_formats, pretend=False, link=False, hardlink=False, ): """Copies or converts the associated cover art of the album. Album must have at least one track. """ if not album or not album.artpath: return album_item = album.items().get() # Album shouldn't be empty. if not album_item: return # Get the destination of the first item (track) of the album, we use # this function to format the path accordingly to path_formats. dest = album_item.destination( basedir=dest_dir, path_formats=path_formats ) # Remove item from the path. dest = os.path.join(*util.components(dest)[:-1]) dest = album.art_destination(album.artpath, item_dir=dest) if album.artpath == dest: return if not pretend: util.mkdirall(dest) if os.path.exists(util.syspath(dest)): self._log.info( "Skipping {0} (target file exists)", util.displayable_path(album.artpath), ) return # Decide whether we need to resize the cover-art image. maxwidth = self._get_art_resize(album.artpath) # Either copy or resize (while copying) the image. if maxwidth is not None: self._log.info( "Resizing cover art from {0} to {1}", util.displayable_path(album.artpath), util.displayable_path(dest), ) if not pretend: ArtResizer.shared.resize(maxwidth, album.artpath, dest) else: if pretend: msg = "ln" if hardlink else ("ln -s" if link else "cp") self._log.info( "{2} {0} {1}", util.displayable_path(album.artpath), util.displayable_path(dest), msg, ) else: msg = ( "Hardlinking" if hardlink else ("Linking" if link else "Copying") ) self._log.info( "{2} cover art from {0} to {1}", util.displayable_path(album.artpath), util.displayable_path(dest), msg, ) if hardlink: util.hardlink(album.artpath, dest) elif link: util.link(album.artpath, dest) else: util.copy(album.artpath, dest) def convert_func(self, lib, opts, args): ( dest, threads, path_formats, fmt, pretend, hardlink, link, playlist, ) = self._get_opts_and_config(opts) if opts.album: albums = lib.albums(ui.decargs(args)) items = [i for a in albums for i in a.items()] if not pretend: for a in albums: ui.print_(format(a, "")) else: items = list(lib.items(ui.decargs(args))) if not pretend: for i in items: ui.print_(format(i, "")) if not items: self._log.error("Empty query result.") return if not (pretend or opts.yes or ui.input_yn("Convert? (Y/n)")): return if opts.album and self.config["copy_album_art"]: for album in albums: self.copy_album_art( album, dest, path_formats, pretend, link, hardlink ) self._parallel_convert( dest, opts.keep_new, path_formats, fmt, pretend, link, hardlink, threads, items, ) if playlist: # Playlist paths are understood as relative to the dest directory. pl_normpath = util.normpath(playlist) pl_dir = os.path.dirname(pl_normpath) self._log.info("Creating playlist file {0}", pl_normpath) # Generates a list of paths to media files, ensures the paths are # relative to the playlist's location and translates the unicode # strings we get from item.destination to bytes. items_paths = [ os.path.relpath( util.bytestring_path( item.destination( basedir=dest, path_formats=path_formats, fragment=False, ) ), pl_dir, ) for item in items ] if not pretend: m3ufile = M3UFile(playlist) m3ufile.set_contents(items_paths) m3ufile.write() def convert_on_import(self, lib, item): """Transcode a file automatically after it is imported into the library. """ fmt = self.config["format"].as_str().lower() if should_transcode(item, fmt): command, ext = get_format() # Create a temporary file for the conversion. tmpdir = self.config["tmpdir"].get() if tmpdir: tmpdir = os.fsdecode(util.bytestring_path(tmpdir)) fd, dest = tempfile.mkstemp(os.fsdecode(b"." + ext), dir=tmpdir) os.close(fd) dest = util.bytestring_path(dest) _temp_files.append(dest) # Delete the transcode later. # Convert. try: self.encode(command, item.path, dest) except subprocess.CalledProcessError: return # Change the newly-imported database entry to point to the # converted file. source_path = item.path item.path = dest item.write() item.read() # Load new audio information data. item.store() if self.config["delete_originals"]: self._log.log( logging.DEBUG if self.config["quiet"] else logging.INFO, "Removing original file {0}", source_path, ) util.remove(source_path, False) def _get_art_resize(self, artpath): """For a given piece of album art, determine whether or not it needs to be resized according to the user's settings. If so, returns the new size. If not, returns None. """ newwidth = None if self.config["album_art_maxwidth"]: maxwidth = self.config["album_art_maxwidth"].get(int) size = ArtResizer.shared.get_size(artpath) self._log.debug("image size: {}", size) if size: if size[0] > maxwidth: newwidth = maxwidth else: self._log.warning( "Could not get size of image (please see " "documentation for dependencies)." ) return newwidth def _cleanup(self, task, session): for path in task.old_paths: if path in _temp_files: if os.path.isfile(util.syspath(path)): util.remove(path) _temp_files.remove(path) def _get_opts_and_config(self, opts): """Returns parameters needed for convert function. Get parameters from command line if available, default to config if not available. """ dest = opts.dest or self.config["dest"].get() if not dest: raise ui.UserError("no convert destination set") dest = util.bytestring_path(dest) threads = opts.threads or self.config["threads"].get(int) path_formats = ui.get_path_formats(self.config["paths"] or None) fmt = opts.format or self.config["format"].as_str().lower() playlist = opts.playlist or self.config["playlist"].get() if playlist is not None: playlist = os.path.join(dest, util.bytestring_path(playlist)) if opts.pretend is not None: pretend = opts.pretend else: pretend = self.config["pretend"].get(bool) if opts.hardlink is not None: hardlink = opts.hardlink link = False elif opts.link is not None: hardlink = False link = opts.link else: hardlink = self.config["hardlink"].get(bool) link = self.config["link"].get(bool) return ( dest, threads, path_formats, fmt, pretend, hardlink, link, playlist, ) def _parallel_convert( self, dest, keep_new, path_formats, fmt, pretend, link, hardlink, threads, items, ): """Run the convert_item function for every items on as many thread as defined in threads """ convert = [ self.convert_item( dest, keep_new, path_formats, fmt, pretend, link, hardlink ) for _ in range(threads) ] pipe = util.pipeline.Pipeline([iter(items), convert]) pipe.run_parallel() beetbox-beets-01f1faf/beetsplug/deezer.py000066400000000000000000000275601472325477400206030ustar00rootroot00000000000000# This file is part of beets. # Copyright 2019, Rahul Ahuja. # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the # "Software"), to deal in the Software without restriction, including # without limitation the rights to use, copy, modify, merge, publish, # distribute, sublicense, and/or sell copies of the Software, and to # permit persons to whom the Software is furnished to do so, subject to # the following conditions: # # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. """Adds Deezer release and track search support to the autotagger""" import collections import time import requests import unidecode from beets import ui from beets.autotag import AlbumInfo, TrackInfo from beets.dbcore import types from beets.library import DateType from beets.plugins import BeetsPlugin, MetadataSourcePlugin from beets.util.id_extractors import deezer_id_regex class DeezerPlugin(MetadataSourcePlugin, BeetsPlugin): data_source = "Deezer" item_types = { "deezer_track_rank": types.INTEGER, "deezer_track_id": types.INTEGER, "deezer_updated": DateType(), } # Base URLs for the Deezer API # Documentation: https://developers.deezer.com/api/ search_url = "https://api.deezer.com/search/" album_url = "https://api.deezer.com/album/" track_url = "https://api.deezer.com/track/" id_regex = deezer_id_regex def __init__(self): super().__init__() def commands(self): """Add beet UI commands to interact with Deezer.""" deezer_update_cmd = ui.Subcommand( "deezerupdate", help=f"Update {self.data_source} rank" ) def func(lib, opts, args): items = lib.items(ui.decargs(args)) self.deezerupdate(items, ui.should_write()) deezer_update_cmd.func = func return [deezer_update_cmd] def fetch_data(self, url): try: response = requests.get(url, timeout=10) response.raise_for_status() data = response.json() except requests.exceptions.RequestException as e: self._log.error("Error fetching data from {}\n Error: {}", url, e) return None if "error" in data: self._log.error("Deezer API error: {}", data["error"]["message"]) return None return data def album_for_id(self, album_id): """Fetch an album by its Deezer ID or URL and return an AlbumInfo object or None if the album is not found. :param album_id: Deezer ID or URL for the album. :type album_id: str :return: AlbumInfo object for album. :rtype: beets.autotag.hooks.AlbumInfo or None """ deezer_id = self._get_id("album", album_id, self.id_regex) if deezer_id is None: return None album_data = self.fetch_data(self.album_url + deezer_id) if album_data is None: return None contributors = album_data.get("contributors") if contributors is not None: artist, artist_id = self.get_artist(contributors) else: artist, artist_id = None, None release_date = album_data["release_date"] date_parts = [int(part) for part in release_date.split("-")] num_date_parts = len(date_parts) if num_date_parts == 3: year, month, day = date_parts elif num_date_parts == 2: year, month = date_parts day = None elif num_date_parts == 1: year = date_parts[0] month = None day = None else: raise ui.UserError( f"Invalid `release_date` returned by {self.data_source} API: " f"{release_date!r}" ) tracks_obj = self.fetch_data(self.album_url + deezer_id + "/tracks") if tracks_obj is None: return None try: tracks_data = tracks_obj["data"] except KeyError: self._log.debug("Error fetching album tracks for {}", deezer_id) tracks_data = None if not tracks_data: return None while "next" in tracks_obj: tracks_obj = requests.get( tracks_obj["next"], timeout=10, ).json() tracks_data.extend(tracks_obj["data"]) tracks = [] medium_totals = collections.defaultdict(int) for i, track_data in enumerate(tracks_data, start=1): track = self._get_track(track_data) track.index = i medium_totals[track.medium] += 1 tracks.append(track) for track in tracks: track.medium_total = medium_totals[track.medium] return AlbumInfo( album=album_data["title"], album_id=deezer_id, deezer_album_id=deezer_id, artist=artist, artist_credit=self.get_artist([album_data["artist"]])[0], artist_id=artist_id, tracks=tracks, albumtype=album_data["record_type"], va=len(album_data["contributors"]) == 1 and artist.lower() == "various artists", year=year, month=month, day=day, label=album_data["label"], mediums=max(medium_totals.keys()), data_source=self.data_source, data_url=album_data["link"], cover_art_url=album_data.get("cover_xl"), ) def _get_track(self, track_data): """Convert a Deezer track object dict to a TrackInfo object. :param track_data: Deezer Track object dict :type track_data: dict :return: TrackInfo object for track :rtype: beets.autotag.hooks.TrackInfo """ artist, artist_id = self.get_artist( track_data.get("contributors", [track_data["artist"]]) ) return TrackInfo( title=track_data["title"], track_id=track_data["id"], deezer_track_id=track_data["id"], isrc=track_data.get("isrc"), artist=artist, artist_id=artist_id, length=track_data["duration"], index=track_data.get("track_position"), medium=track_data.get("disk_number"), deezer_track_rank=track_data.get("rank"), medium_index=track_data.get("track_position"), data_source=self.data_source, data_url=track_data["link"], deezer_updated=time.time(), ) def track_for_id(self, track_id=None, track_data=None): """Fetch a track by its Deezer ID or URL and return a TrackInfo object or None if the track is not found. :param track_id: (Optional) Deezer ID or URL for the track. Either ``track_id`` or ``track_data`` must be provided. :type track_id: str :param track_data: (Optional) Simplified track object dict. May be provided instead of ``track_id`` to avoid unnecessary API calls. :type track_data: dict :return: TrackInfo object for track :rtype: beets.autotag.hooks.TrackInfo or None """ if track_data is None: deezer_id = self._get_id("track", track_id, self.id_regex) if deezer_id is None: return None track_data = self.fetch_data(self.track_url + deezer_id) if track_data is None: return None track = self._get_track(track_data) # Get album's tracks to set `track.index` (position on the entire # release) and `track.medium_total` (total number of tracks on # the track's disc). album_tracks_obj = self.fetch_data( self.album_url + str(track_data["album"]["id"]) + "/tracks" ) if album_tracks_obj is None: return None try: album_tracks_data = album_tracks_obj["data"] except KeyError: self._log.debug( "Error fetching album tracks for {}", track_data["album"]["id"] ) return None medium_total = 0 for i, track_data in enumerate(album_tracks_data, start=1): if track_data["disk_number"] == track.medium: medium_total += 1 if track_data["id"] == track.track_id: track.index = i track.medium_total = medium_total return track @staticmethod def _construct_search_query(filters=None, keywords=""): """Construct a query string with the specified filters and keywords to be provided to the Deezer Search API (https://developers.deezer.com/api/search). :param filters: (Optional) Field filters to apply. :type filters: dict :param keywords: (Optional) Query keywords to use. :type keywords: str :return: Query string to be provided to the Search API. :rtype: str """ query_components = [ keywords, " ".join(f'{k}:"{v}"' for k, v in filters.items()), ] query = " ".join([q for q in query_components if q]) if not isinstance(query, str): query = query.decode("utf8") return unidecode.unidecode(query) def _search_api(self, query_type, filters=None, keywords=""): """Query the Deezer Search API for the specified ``keywords``, applying the provided ``filters``. :param query_type: The Deezer Search API method to use. Valid types are: 'album', 'artist', 'history', 'playlist', 'podcast', 'radio', 'track', 'user', and 'track'. :type query_type: str :param filters: (Optional) Field filters to apply. :type filters: dict :param keywords: (Optional) Query keywords to use. :type keywords: str :return: JSON data for the class:`Response ` object or None if no search results are returned. :rtype: dict or None """ query = self._construct_search_query(keywords=keywords, filters=filters) if not query: return None self._log.debug(f"Searching {self.data_source} for '{query}'") try: response = requests.get( self.search_url + query_type, params={"q": query}, timeout=10, ) response.raise_for_status() except requests.exceptions.RequestException as e: self._log.error( "Error fetching data from {} API\n Error: {}", self.data_source, e, ) return None response_data = response.json().get("data", []) self._log.debug( "Found {} result(s) from {} for '{}'", len(response_data), self.data_source, query, ) return response_data def deezerupdate(self, items, write): """Obtain rank information from Deezer.""" for index, item in enumerate(items, start=1): self._log.info( "Processing {}/{} tracks - {} ", index, len(items), item ) try: deezer_track_id = item.deezer_track_id except AttributeError: self._log.debug("No deezer_track_id present for: {}", item) continue try: rank = self.fetch_data( f"{self.track_url}{deezer_track_id}" ).get("rank") self._log.debug( "Deezer track: {} has {} rank", deezer_track_id, rank ) except Exception as e: self._log.debug("Invalid Deezer track_id: {}", e) continue item.deezer_track_rank = int(rank) item.store() item.deezer_updated = time.time() if write: item.try_write() beetbox-beets-01f1faf/beetsplug/discogs.py000066400000000000000000000723551472325477400207620ustar00rootroot00000000000000# This file is part of beets. # Copyright 2016, Adrian Sampson. # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the # "Software"), to deal in the Software without restriction, including # without limitation the rights to use, copy, modify, merge, publish, # distribute, sublicense, and/or sell copies of the Software, and to # permit persons to whom the Software is furnished to do so, subject to # the following conditions: # # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. """Adds Discogs album search support to the autotagger. Requires the python3-discogs-client library. """ from __future__ import annotations import http.client import json import os import re import socket import time import traceback from string import ascii_lowercase import confuse from discogs_client import Client, Master, Release from discogs_client import __version__ as dc_string from discogs_client.exceptions import DiscogsAPIError from requests.exceptions import ConnectionError from typing_extensions import TypedDict import beets import beets.ui from beets import config from beets.autotag.hooks import AlbumInfo, TrackInfo, string_dist from beets.plugins import BeetsPlugin, MetadataSourcePlugin, get_distance from beets.util.id_extractors import extract_discogs_id_regex USER_AGENT = f"beets/{beets.__version__} +https://beets.io/" API_KEY = "rAzVUQYRaoFjeBjyWuWZ" API_SECRET = "plxtUTqoCzwxZpqdPysCwGuBSmZNdZVy" # Exceptions that discogs_client should really handle but does not. CONNECTION_ERRORS = ( ConnectionError, socket.error, http.client.HTTPException, ValueError, # JSON decoding raises a ValueError. DiscogsAPIError, ) class ReleaseFormat(TypedDict): name: str qty: int descriptions: list[str] | None class DiscogsPlugin(BeetsPlugin): def __init__(self): super().__init__() self.check_discogs_client() self.config.add( { "apikey": API_KEY, "apisecret": API_SECRET, "tokenfile": "discogs_token.json", "source_weight": 0.5, "user_token": "", "separator": ", ", "index_tracks": False, "append_style_genre": False, } ) self.config["apikey"].redact = True self.config["apisecret"].redact = True self.config["user_token"].redact = True self.discogs_client = None self.register_listener("import_begin", self.setup) def check_discogs_client(self): """Ensure python3-discogs-client version >= 2.3.15""" dc_min_version = [2, 3, 15] dc_version = [int(elem) for elem in dc_string.split(".")] min_len = min(len(dc_version), len(dc_min_version)) gt_min = [ (elem > elem_min) for elem, elem_min in zip( dc_version[:min_len], dc_min_version[:min_len] ) ] if True not in gt_min: self._log.warning( "python3-discogs-client version should be >= 2.3.15" ) def setup(self, session=None): """Create the `discogs_client` field. Authenticate if necessary.""" c_key = self.config["apikey"].as_str() c_secret = self.config["apisecret"].as_str() # Try using a configured user token (bypassing OAuth login). user_token = self.config["user_token"].as_str() if user_token: # The rate limit for authenticated users goes up to 60 # requests per minute. self.discogs_client = Client(USER_AGENT, user_token=user_token) return # Get the OAuth token from a file or log in. try: with open(self._tokenfile()) as f: tokendata = json.load(f) except OSError: # No token yet. Generate one. token, secret = self.authenticate(c_key, c_secret) else: token = tokendata["token"] secret = tokendata["secret"] self.discogs_client = Client(USER_AGENT, c_key, c_secret, token, secret) def reset_auth(self): """Delete token file & redo the auth steps.""" os.remove(self._tokenfile()) self.setup() def _tokenfile(self): """Get the path to the JSON file for storing the OAuth token.""" return self.config["tokenfile"].get(confuse.Filename(in_app_dir=True)) def authenticate(self, c_key, c_secret): # Get the link for the OAuth page. auth_client = Client(USER_AGENT, c_key, c_secret) try: _, _, url = auth_client.get_authorize_url() except CONNECTION_ERRORS as e: self._log.debug("connection error: {0}", e) raise beets.ui.UserError("communication with Discogs failed") beets.ui.print_("To authenticate with Discogs, visit:") beets.ui.print_(url) # Ask for the code and validate it. code = beets.ui.input_("Enter the code:") try: token, secret = auth_client.get_access_token(code) except DiscogsAPIError: raise beets.ui.UserError("Discogs authorization failed") except CONNECTION_ERRORS as e: self._log.debug("connection error: {0}", e) raise beets.ui.UserError("Discogs token request failed") # Save the token for later use. self._log.debug("Discogs token {0}, secret {1}", token, secret) with open(self._tokenfile(), "w") as f: json.dump({"token": token, "secret": secret}, f) return token, secret def album_distance(self, items, album_info, mapping): """Returns the album distance.""" return get_distance( data_source="Discogs", info=album_info, config=self.config ) def track_distance(self, item, track_info): """Returns the track distance.""" return get_distance( data_source="Discogs", info=track_info, config=self.config ) def candidates(self, items, artist, album, va_likely, extra_tags=None): """Returns a list of AlbumInfo objects for discogs search results matching an album and artist (if not various). """ if not self.discogs_client: return if not album and not artist: self._log.debug( "Skipping Discogs query. Files missing album and " "artist tags." ) return [] if va_likely: query = album else: query = f"{artist} {album}" try: return self.get_albums(query) except DiscogsAPIError as e: self._log.debug("API Error: {0} (query: {1})", e, query) if e.status_code == 401: self.reset_auth() return self.candidates(items, artist, album, va_likely) else: return [] except CONNECTION_ERRORS: self._log.debug("Connection error in album search", exc_info=True) return [] def get_track_from_album_by_title( self, album_info, title, dist_threshold=0.3 ): def compare_func(track_info): track_title = getattr(track_info, "title", None) dist = string_dist(track_title, title) return track_title and dist < dist_threshold return self.get_track_from_album(album_info, compare_func) def get_track_from_album(self, album_info, compare_func): """Return the first track of the release where `compare_func` returns true. :return: TrackInfo object. :rtype: beets.autotag.hooks.TrackInfo """ if not album_info: return None for track_info in album_info.tracks: # check for matching position if not compare_func(track_info): continue # attach artist info if not provided if not track_info["artist"]: track_info["artist"] = album_info.artist track_info["artist_id"] = album_info.artist_id # attach album info track_info["album"] = album_info.album return track_info return None def item_candidates(self, item, artist, title): """Returns a list of TrackInfo objects for Search API results matching ``title`` and ``artist``. :param item: Singleton item to be matched. :type item: beets.library.Item :param artist: The artist of the track to be matched. :type artist: str :param title: The title of the track to be matched. :type title: str :return: Candidate TrackInfo objects. :rtype: list[beets.autotag.hooks.TrackInfo] """ if not self.discogs_client: return [] if not artist and not title: self._log.debug( "Skipping Discogs query. File missing artist and " "title tags." ) return [] query = f"{artist} {title}" try: albums = self.get_albums(query) except DiscogsAPIError as e: self._log.debug("API Error: {0} (query: {1})", e, query) if e.status_code == 401: self.reset_auth() return self.item_candidates(item, artist, title) else: return [] except CONNECTION_ERRORS: self._log.debug("Connection error in track search", exc_info=True) candidates = [] for album_cur in albums: self._log.debug("searching within album {0}", album_cur.album) track_result = self.get_track_from_album_by_title( album_cur, item["title"] ) if track_result: candidates.append(track_result) # first 10 results, don't overwhelm with options return candidates[:10] def album_for_id(self, album_id): """Fetches an album by its Discogs ID and returns an AlbumInfo object or None if the album is not found. """ if not self.discogs_client: return self._log.debug("Searching for release {0}", album_id) discogs_id = extract_discogs_id_regex(album_id) if not discogs_id: return None result = Release(self.discogs_client, {"id": discogs_id}) # Try to obtain title to verify that we indeed have a valid Release try: getattr(result, "title") except DiscogsAPIError as e: if e.status_code != 404: self._log.debug( "API Error: {0} (query: {1})", e, result.data["resource_url"], ) if e.status_code == 401: self.reset_auth() return self.album_for_id(album_id) return None except CONNECTION_ERRORS: self._log.debug("Connection error in album lookup", exc_info=True) return None return self.get_album_info(result) def get_albums(self, query): """Returns a list of AlbumInfo objects for a discogs search query.""" # Strip non-word characters from query. Things like "!" and "-" can # cause a query to return no results, even if they match the artist or # album title. Use `re.UNICODE` flag to avoid stripping non-english # word characters. query = re.sub(r"(?u)\W+", " ", query) # Strip medium information from query, Things like "CD1" and "disk 1" # can also negate an otherwise positive result. query = re.sub(r"(?i)\b(CD|disc|vinyl)\s*\d+", "", query) try: releases = self.discogs_client.search(query, type="release").page(1) except CONNECTION_ERRORS: self._log.debug( "Communication error while searching for {0!r}", query, exc_info=True, ) return [] return [ album for album in map(self.get_album_info, releases[:5]) if album ] def get_master_year(self, master_id): """Fetches a master release given its Discogs ID and returns its year or None if the master release is not found. """ self._log.debug("Searching for master release {0}", master_id) result = Master(self.discogs_client, {"id": master_id}) try: year = result.fetch("year") return year except DiscogsAPIError as e: if e.status_code != 404: self._log.debug( "API Error: {0} (query: {1})", e, result.data["resource_url"], ) if e.status_code == 401: self.reset_auth() return self.get_master_year(master_id) return None except CONNECTION_ERRORS: self._log.debug( "Connection error in master release lookup", exc_info=True ) return None @staticmethod def get_media_and_albumtype( formats: list[ReleaseFormat] | None, ) -> tuple[str | None, str | None]: media = albumtype = None if formats and (first_format := formats[0]): if descriptions := first_format["descriptions"]: albumtype = ", ".join(descriptions) media = first_format["name"] return media, albumtype def get_album_info(self, result): """Returns an AlbumInfo object for a discogs Release object.""" # Explicitly reload the `Release` fields, as they might not be yet # present if the result is from a `discogs_client.search()`. if not result.data.get("artists"): result.refresh() # Sanity check for required fields. The list of required fields is # defined at Guideline 1.3.1.a, but in practice some releases might be # lacking some of these fields. This function expects at least: # `artists` (>0), `title`, `id`, `tracklist` (>0) # https://www.discogs.com/help/doc/submission-guidelines-general-rules if not all( [ result.data.get(k) for k in ["artists", "title", "id", "tracklist"] ] ): self._log.warning("Release does not contain the required fields") return None artist, artist_id = MetadataSourcePlugin.get_artist( [a.data for a in result.artists], join_key="join" ) album = re.sub(r" +", " ", result.title) album_id = result.data["id"] # Use `.data` to access the tracklist directly instead of the # convenient `.tracklist` property, which will strip out useful artist # information and leave us with skeleton `Artist` objects that will # each make an API call just to get the same data back. tracks = self.get_tracks(result.data["tracklist"]) # Extract information for the optional AlbumInfo fields, if possible. va = result.data["artists"][0].get("name", "").lower() == "various" year = result.data.get("year") mediums = [t.medium for t in tracks] country = result.data.get("country") data_url = result.data.get("uri") style = self.format(result.data.get("styles")) base_genre = self.format(result.data.get("genres")) if self.config["append_style_genre"] and style: genre = self.config["separator"].as_str().join([base_genre, style]) else: genre = base_genre discogs_albumid = extract_discogs_id_regex(result.data.get("uri")) # Extract information for the optional AlbumInfo fields that are # contained on nested discogs fields. media, albumtype = self.get_media_and_albumtype( result.data.get("formats") ) label = catalogno = labelid = None if result.data.get("labels"): label = result.data["labels"][0].get("name") catalogno = result.data["labels"][0].get("catno") labelid = result.data["labels"][0].get("id") cover_art_url = self.select_cover_art(result) # Additional cleanups (various artists name, catalog number, media). if va: artist = config["va_name"].as_str() if catalogno == "none": catalogno = None # Explicitly set the `media` for the tracks, since it is expected by # `autotag.apply_metadata`, and set `medium_total`. for track in tracks: track.media = media track.medium_total = mediums.count(track.medium) if not track.artist: # get_track_info often fails to find artist track.artist = artist if not track.artist_id: track.artist_id = artist_id # Discogs does not have track IDs. Invent our own IDs as proposed # in #2336. track.track_id = str(album_id) + "-" + track.track_alt track.data_url = data_url track.data_source = "Discogs" # Retrieve master release id (returns None if there isn't one). master_id = result.data.get("master_id") # Assume `original_year` is equal to `year` for releases without # a master release, otherwise fetch the master release. original_year = self.get_master_year(master_id) if master_id else year return AlbumInfo( album=album, album_id=album_id, artist=artist, artist_id=artist_id, tracks=tracks, albumtype=albumtype, va=va, year=year, label=label, mediums=len(set(mediums)), releasegroup_id=master_id, catalognum=catalogno, country=country, style=style, genre=genre, media=media, original_year=original_year, data_source="Discogs", data_url=data_url, discogs_albumid=discogs_albumid, discogs_labelid=labelid, discogs_artistid=artist_id, cover_art_url=cover_art_url, ) def select_cover_art(self, result): """Returns the best candidate image, if any, from a Discogs `Release` object.""" if result.data.get("images") and len(result.data.get("images")) > 0: # The first image in this list appears to be the one displayed first # on the release page - even if it is not flagged as `type: "primary"` - and # so it is the best candidate for the cover art. return result.data.get("images")[0].get("uri") return None def format(self, classification): if classification: return ( self.config["separator"].as_str().join(sorted(classification)) ) else: return None def get_tracks(self, tracklist): """Returns a list of TrackInfo objects for a discogs tracklist.""" try: clean_tracklist = self.coalesce_tracks(tracklist) except Exception as exc: # FIXME: this is an extra precaution for making sure there are no # side effects after #2222. It should be removed after further # testing. self._log.debug("{}", traceback.format_exc()) self._log.error("uncaught exception in coalesce_tracks: {}", exc) clean_tracklist = tracklist tracks = [] index_tracks = {} index = 0 # Distinct works and intra-work divisions, as defined by index tracks. divisions, next_divisions = [], [] for track in clean_tracklist: # Only real tracks have `position`. Otherwise, it's an index track. if track["position"]: index += 1 if next_divisions: # End of a block of index tracks: update the current # divisions. divisions += next_divisions del next_divisions[:] track_info = self.get_track_info(track, index, divisions) track_info.track_alt = track["position"] tracks.append(track_info) else: next_divisions.append(track["title"]) # We expect new levels of division at the beginning of the # tracklist (and possibly elsewhere). try: divisions.pop() except IndexError: pass index_tracks[index + 1] = track["title"] # Fix up medium and medium_index for each track. Discogs position is # unreliable, but tracks are in order. medium = None medium_count, index_count, side_count = 0, 0, 0 sides_per_medium = 1 # If a medium has two sides (ie. vinyl or cassette), each pair of # consecutive sides should belong to the same medium. if all([track.medium is not None for track in tracks]): m = sorted({track.medium.lower() for track in tracks}) # If all track.medium are single consecutive letters, assume it is # a 2-sided medium. if "".join(m) in ascii_lowercase: sides_per_medium = 2 for track in tracks: # Handle special case where a different medium does not indicate a # new disc, when there is no medium_index and the ordinal of medium # is not sequential. For example, I, II, III, IV, V. Assume these # are the track index, not the medium. # side_count is the number of mediums or medium sides (in the case # of two-sided mediums) that were seen before. medium_is_index = ( track.medium and not track.medium_index and ( len(track.medium) != 1 or # Not within standard incremental medium values (A, B, C, ...). ord(track.medium) - 64 != side_count + 1 ) ) if not medium_is_index and medium != track.medium: side_count += 1 if sides_per_medium == 2: if side_count % sides_per_medium: # Two-sided medium changed. Reset index_count. index_count = 0 medium_count += 1 else: # Medium changed. Reset index_count. medium_count += 1 index_count = 0 medium = track.medium index_count += 1 medium_count = 1 if medium_count == 0 else medium_count track.medium, track.medium_index = medium_count, index_count # Get `disctitle` from Discogs index tracks. Assume that an index track # before the first track of each medium is a disc title. for track in tracks: if track.medium_index == 1: if track.index in index_tracks: disctitle = index_tracks[track.index] else: disctitle = None track.disctitle = disctitle return tracks def coalesce_tracks(self, raw_tracklist): """Pre-process a tracklist, merging subtracks into a single track. The title for the merged track is the one from the previous index track, if present; otherwise it is a combination of the subtracks titles. """ def add_merged_subtracks(tracklist, subtracks): """Modify `tracklist` in place, merging a list of `subtracks` into a single track into `tracklist`.""" # Calculate position based on first subtrack, without subindex. idx, medium_idx, sub_idx = self.get_track_index( subtracks[0]["position"] ) position = "{}{}".format(idx or "", medium_idx or "") if tracklist and not tracklist[-1]["position"]: # Assume the previous index track contains the track title. if sub_idx: # "Convert" the track title to a real track, discarding the # subtracks assuming they are logical divisions of a # physical track (12.2.9 Subtracks). tracklist[-1]["position"] = position else: # Promote the subtracks to real tracks, discarding the # index track, assuming the subtracks are physical tracks. index_track = tracklist.pop() # Fix artists when they are specified on the index track. if index_track.get("artists"): for subtrack in subtracks: if not subtrack.get("artists"): subtrack["artists"] = index_track["artists"] # Concatenate index with track title when index_tracks # option is set if self.config["index_tracks"]: for subtrack in subtracks: subtrack["title"] = "{}: {}".format( index_track["title"], subtrack["title"] ) tracklist.extend(subtracks) else: # Merge the subtracks, pick a title, and append the new track. track = subtracks[0].copy() track["title"] = " / ".join([t["title"] for t in subtracks]) tracklist.append(track) # Pre-process the tracklist, trying to identify subtracks. subtracks = [] tracklist = [] prev_subindex = "" for track in raw_tracklist: # Regular subtrack (track with subindex). if track["position"]: _, _, subindex = self.get_track_index(track["position"]) if subindex: if subindex.rjust(len(raw_tracklist)) > prev_subindex: # Subtrack still part of the current main track. subtracks.append(track) else: # Subtrack part of a new group (..., 1.3, *2.1*, ...). add_merged_subtracks(tracklist, subtracks) subtracks = [track] prev_subindex = subindex.rjust(len(raw_tracklist)) continue # Index track with nested sub_tracks. if not track["position"] and "sub_tracks" in track: # Append the index track, assuming it contains the track title. tracklist.append(track) add_merged_subtracks(tracklist, track["sub_tracks"]) continue # Regular track or index track without nested sub_tracks. if subtracks: add_merged_subtracks(tracklist, subtracks) subtracks = [] prev_subindex = "" tracklist.append(track) # Merge and add the remaining subtracks, if any. if subtracks: add_merged_subtracks(tracklist, subtracks) return tracklist def get_track_info(self, track, index, divisions): """Returns a TrackInfo object for a discogs track.""" title = track["title"] if self.config["index_tracks"]: prefix = ", ".join(divisions) if prefix: title = f"{prefix}: {title}" track_id = None medium, medium_index, _ = self.get_track_index(track["position"]) artist, artist_id = MetadataSourcePlugin.get_artist( track.get("artists", []), join_key="join" ) length = self.get_track_length(track["duration"]) return TrackInfo( title=title, track_id=track_id, artist=artist, artist_id=artist_id, length=length, index=index, medium=medium, medium_index=medium_index, ) def get_track_index(self, position): """Returns the medium, medium index and subtrack index for a discogs track position.""" # Match the standard Discogs positions (12.2.9), which can have several # forms (1, 1-1, A1, A1.1, A1a, ...). match = re.match( r"^(.*?)" # medium: everything before medium_index. r"(\d*?)" # medium_index: a number at the end of # `position`, except if followed by a subtrack # index. # subtrack_index: can only be matched if medium # or medium_index have been matched, and can be r"((?<=\w)\.[\w]+" # - a dot followed by a string (A.1, 2.A) r"|(?<=\d)[A-Z]+" # - a string that follows a number (1A, B2a) r")?" r"$", position.upper(), ) if match: medium, index, subindex = match.groups() if subindex and subindex.startswith("."): subindex = subindex[1:] else: self._log.debug("Invalid position: {0}", position) medium = index = subindex = None return medium or None, index or None, subindex or None def get_track_length(self, duration): """Returns the track length in seconds for a discogs duration.""" try: length = time.strptime(duration, "%M:%S") except ValueError: return None return length.tm_min * 60 + length.tm_sec beetbox-beets-01f1faf/beetsplug/duplicates.py000066400000000000000000000323161472325477400214550ustar00rootroot00000000000000# This file is part of beets. # Copyright 2016, Pedro Silva. # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the # "Software"), to deal in the Software without restriction, including # without limitation the rights to use, copy, modify, merge, publish, # distribute, sublicense, and/or sell copies of the Software, and to # permit persons to whom the Software is furnished to do so, subject to # the following conditions: # # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. """List duplicate tracks or albums.""" import os import shlex from beets.library import Album, Item from beets.plugins import BeetsPlugin from beets.ui import Subcommand, UserError, decargs, print_ from beets.util import ( MoveOperation, bytestring_path, command_output, displayable_path, subprocess, ) PLUGIN = "duplicates" class DuplicatesPlugin(BeetsPlugin): """List duplicate tracks or albums""" def __init__(self): super().__init__() self.config.add( { "album": False, "checksum": "", "copy": "", "count": False, "delete": False, "format": "", "full": False, "keys": [], "merge": False, "move": "", "path": False, "tiebreak": {}, "strict": False, "tag": "", } ) self._command = Subcommand("duplicates", help=__doc__, aliases=["dup"]) self._command.parser.add_option( "-c", "--count", dest="count", action="store_true", help="show duplicate counts", ) self._command.parser.add_option( "-C", "--checksum", dest="checksum", action="store", metavar="PROG", help="report duplicates based on arbitrary command", ) self._command.parser.add_option( "-d", "--delete", dest="delete", action="store_true", help="delete items from library and disk", ) self._command.parser.add_option( "-F", "--full", dest="full", action="store_true", help="show all versions of duplicate tracks or albums", ) self._command.parser.add_option( "-s", "--strict", dest="strict", action="store_true", help="report duplicates only if all attributes are set", ) self._command.parser.add_option( "-k", "--key", dest="keys", action="append", metavar="KEY", help="report duplicates based on keys (use multiple times)", ) self._command.parser.add_option( "-M", "--merge", dest="merge", action="store_true", help="merge duplicate items", ) self._command.parser.add_option( "-m", "--move", dest="move", action="store", metavar="DEST", help="move items to dest", ) self._command.parser.add_option( "-o", "--copy", dest="copy", action="store", metavar="DEST", help="copy items to dest", ) self._command.parser.add_option( "-t", "--tag", dest="tag", action="store", help="tag matched items with 'k=v' attribute", ) self._command.parser.add_all_common_options() def commands(self): def _dup(lib, opts, args): self.config.set_args(opts) album = self.config["album"].get(bool) checksum = self.config["checksum"].get(str) copy = bytestring_path(self.config["copy"].as_str()) count = self.config["count"].get(bool) delete = self.config["delete"].get(bool) fmt = self.config["format"].get(str) full = self.config["full"].get(bool) keys = self.config["keys"].as_str_seq() merge = self.config["merge"].get(bool) move = bytestring_path(self.config["move"].as_str()) path = self.config["path"].get(bool) tiebreak = self.config["tiebreak"].get(dict) strict = self.config["strict"].get(bool) tag = self.config["tag"].get(str) if album: if not keys: keys = ["mb_albumid"] items = lib.albums(decargs(args)) else: if not keys: keys = ["mb_trackid", "mb_albumid"] items = lib.items(decargs(args)) # If there's nothing to do, return early. The code below assumes # `items` to be non-empty. if not items: return if path: fmt = "$path" # Default format string for count mode. if count and not fmt: if album: fmt = "$albumartist - $album" else: fmt = "$albumartist - $album - $title" fmt += ": {0}" if checksum: for i in items: k, _ = self._checksum(i, checksum) keys = [k] for obj_id, obj_count, objs in self._duplicates( items, keys=keys, full=full, strict=strict, tiebreak=tiebreak, merge=merge, ): if obj_id: # Skip empty IDs. for o in objs: self._process_item( o, copy=copy, move=move, delete=delete, tag=tag, fmt=fmt.format(obj_count), ) self._command.func = _dup return [self._command] def _process_item( self, item, copy=False, move=False, delete=False, tag=False, fmt="" ): """Process Item `item`.""" print_(format(item, fmt)) if copy: item.move(basedir=copy, operation=MoveOperation.COPY) item.store() if move: item.move(basedir=move) item.store() if delete: item.remove(delete=True) if tag: try: k, v = tag.split("=") except Exception: raise UserError(f"{PLUGIN}: can't parse k=v tag: {tag}") setattr(item, k, v) item.store() def _checksum(self, item, prog): """Run external `prog` on file path associated with `item`, cache output as flexattr on a key that is the name of the program, and return the key, checksum tuple. """ args = [ p.format(file=os.fsdecode(item.path)) for p in shlex.split(prog) ] key = args[0] checksum = getattr(item, key, False) if not checksum: self._log.debug( "key {0} on item {1} not cached:" "computing checksum", key, displayable_path(item.path), ) try: checksum = command_output(args).stdout setattr(item, key, checksum) item.store() self._log.debug( "computed checksum for {0} using {1}", item.title, key ) except subprocess.CalledProcessError as e: self._log.debug( "failed to checksum {0}: {1}", displayable_path(item.path), e, ) else: self._log.debug( "key {0} on item {1} cached:" "not computing checksum", key, displayable_path(item.path), ) return key, checksum def _group_by(self, objs, keys, strict): """Return a dictionary with keys arbitrary concatenations of attributes and values lists of objects (Albums or Items) with those keys. If strict, all attributes must be defined for a duplicate match. """ import collections counts = collections.defaultdict(list) for obj in objs: values = [getattr(obj, k, None) for k in keys] values = [v for v in values if v not in (None, "")] if strict and len(values) < len(keys): self._log.debug( "some keys {0} on item {1} are null or empty:" " skipping", keys, displayable_path(obj.path), ) elif not strict and not len(values): self._log.debug( "all keys {0} on item {1} are null or empty:" " skipping", keys, displayable_path(obj.path), ) else: key = tuple(values) counts[key].append(obj) return counts def _order(self, objs, tiebreak=None): """Return the objects (Items or Albums) sorted by descending order of priority. If provided, the `tiebreak` dict indicates the field to use to prioritize the objects. Otherwise, Items are placed in order of "completeness" (objects with more non-null fields come first) and Albums are ordered by their track count. """ kind = "items" if all(isinstance(o, Item) for o in objs) else "albums" if tiebreak and kind in tiebreak.keys(): def key(x): return tuple(getattr(x, k) for k in tiebreak[kind]) else: if kind == "items": def truthy(v): # Avoid a Unicode warning by avoiding comparison # between a bytes object and the empty Unicode # string ''. return v is not None and ( v != "" if isinstance(v, str) else True ) fields = Item.all_keys() def key(x): return sum(1 for f in fields if truthy(getattr(x, f))) else: def key(x): return len(x.items()) return sorted(objs, key=key, reverse=True) def _merge_items(self, objs): """Merge Item objs by copying missing fields from items in the tail to the head item. Return same number of items, with the head item modified. """ fields = Item.all_keys() for f in fields: for o in objs[1:]: if getattr(objs[0], f, None) in (None, ""): value = getattr(o, f, None) if value: self._log.debug( "key {0} on item {1} is null " "or empty: setting from item {2}", f, displayable_path(objs[0].path), displayable_path(o.path), ) setattr(objs[0], f, value) objs[0].store() break return objs def _merge_albums(self, objs): """Merge Album objs by copying missing items from albums in the tail to the head album. Return same number of albums, with the head album modified.""" ids = [i.mb_trackid for i in objs[0].items()] for o in objs[1:]: for i in o.items(): if i.mb_trackid not in ids: missing = Item.from_path(i.path) missing.album_id = objs[0].id missing.add(i._db) self._log.debug( "item {0} missing from album {1}:" " merging from {2} into {3}", missing, objs[0], displayable_path(o.path), displayable_path(missing.destination()), ) missing.move(operation=MoveOperation.COPY) return objs def _merge(self, objs): """Merge duplicate items. See ``_merge_items`` and ``_merge_albums`` for the relevant strategies. """ kind = Item if all(isinstance(o, Item) for o in objs) else Album if kind is Item: objs = self._merge_items(objs) else: objs = self._merge_albums(objs) return objs def _duplicates(self, objs, keys, full, strict, tiebreak, merge): """Generate triples of keys, duplicate counts, and constituent objects.""" offset = 0 if full else 1 for k, objs in self._group_by(objs, keys, strict).items(): if len(objs) > 1: objs = self._order(objs, tiebreak) if merge: objs = self._merge(objs) yield (k, len(objs) - offset, objs[offset:]) beetbox-beets-01f1faf/beetsplug/edit.py000066400000000000000000000331101472325477400202360ustar00rootroot00000000000000# This file is part of beets. # Copyright 2016 # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the # "Software"), to deal in the Software without restriction, including # without limitation the rights to use, copy, modify, merge, publish, # distribute, sublicense, and/or sell copies of the Software, and to # permit persons to whom the Software is furnished to do so, subject to # the following conditions: # # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. """Open metadata information in a text editor to let the user edit it.""" import codecs import os import shlex import subprocess from tempfile import NamedTemporaryFile import yaml from beets import plugins, ui, util from beets.dbcore import types from beets.importer import action from beets.ui.commands import PromptChoice, _do_query # These "safe" types can avoid the format/parse cycle that most fields go # through: they are safe to edit with native YAML types. SAFE_TYPES = (types.BaseFloat, types.BaseInteger, types.Boolean) class ParseError(Exception): """The modified file is unreadable. The user should be offered a chance to fix the error. """ def edit(filename, log): """Open `filename` in a text editor.""" cmd = shlex.split(util.editor_command()) cmd.append(filename) log.debug("invoking editor command: {!r}", cmd) try: subprocess.call(cmd) except OSError as exc: raise ui.UserError( "could not run editor command {!r}: {}".format(cmd[0], exc) ) def dump(arg): """Dump a sequence of dictionaries as YAML for editing.""" return yaml.safe_dump_all( arg, allow_unicode=True, default_flow_style=False, ) def load(s): """Read a sequence of YAML documents back to a list of dictionaries with string keys. Can raise a `ParseError`. """ try: out = [] for d in yaml.safe_load_all(s): if not isinstance(d, dict): raise ParseError( "each entry must be a dictionary; found {}".format( type(d).__name__ ) ) # Convert all keys to strings. They started out as strings, # but the user may have inadvertently messed this up. out.append({str(k): v for k, v in d.items()}) except yaml.YAMLError as e: raise ParseError(f"invalid YAML: {e}") return out def _safe_value(obj, key, value): """Check whether the `value` is safe to represent in YAML and trust as returned from parsed YAML. This ensures that values do not change their type when the user edits their YAML representation. """ typ = obj._type(key) return isinstance(typ, SAFE_TYPES) and isinstance(value, typ.model_type) def flatten(obj, fields): """Represent `obj`, a `dbcore.Model` object, as a dictionary for serialization. Only include the given `fields` if provided; otherwise, include everything. The resulting dictionary's keys are strings and the values are safely YAML-serializable types. """ # Format each value. d = {} for key in obj.keys(): value = obj[key] if _safe_value(obj, key, value): # A safe value that is faithfully representable in YAML. d[key] = value else: # A value that should be edited as a string. d[key] = obj.formatted()[key] # Possibly filter field names. if fields: return {k: v for k, v in d.items() if k in fields} else: return d def apply_(obj, data): """Set the fields of a `dbcore.Model` object according to a dictionary. This is the opposite of `flatten`. The `data` dictionary should have strings as values. """ for key, value in data.items(): if _safe_value(obj, key, value): # A safe value *stayed* represented as a safe type. Assign it # directly. obj[key] = value else: # Either the field was stringified originally or the user changed # it from a safe type to an unsafe one. Parse it as a string. obj.set_parse(key, str(value)) class EditPlugin(plugins.BeetsPlugin): def __init__(self): super().__init__() self.config.add( { # The default fields to edit. "albumfields": "album albumartist", "itemfields": "track title artist album", # Silently ignore any changes to these fields. "ignore_fields": "id path", } ) self.register_listener( "before_choose_candidate", self.before_choose_candidate_listener ) def commands(self): edit_command = ui.Subcommand("edit", help="interactively edit metadata") edit_command.parser.add_option( "-f", "--field", metavar="FIELD", action="append", help="edit this field also", ) edit_command.parser.add_option( "--all", action="store_true", dest="all", help="edit all fields", ) edit_command.parser.add_album_option() edit_command.func = self._edit_command return [edit_command] def _edit_command(self, lib, opts, args): """The CLI command function for the `beet edit` command.""" # Get the objects to edit. query = ui.decargs(args) items, albums = _do_query(lib, query, opts.album, False) objs = albums if opts.album else items if not objs: ui.print_("Nothing to edit.") return # Get the fields to edit. if opts.all: fields = None else: fields = self._get_fields(opts.album, opts.field) self.edit(opts.album, objs, fields) def _get_fields(self, album, extra): """Get the set of fields to edit.""" # Start with the configured base fields. if album: fields = self.config["albumfields"].as_str_seq() else: fields = self.config["itemfields"].as_str_seq() # Add the requested extra fields. if extra: fields += extra # Ensure we always have the `id` field for identification. fields.append("id") return set(fields) def edit(self, album, objs, fields): """The core editor function. - `album`: A flag indicating whether we're editing Items or Albums. - `objs`: The `Item`s or `Album`s to edit. - `fields`: The set of field names to edit (or None to edit everything). """ # Present the YAML to the user and let them change it. success = self.edit_objects(objs, fields) # Save the new data. if success: self.save_changes(objs) def edit_objects(self, objs, fields): """Dump a set of Model objects to a file as text, ask the user to edit it, and apply any changes to the objects. Return a boolean indicating whether the edit succeeded. """ # Get the content to edit as raw data structures. old_data = [flatten(o, fields) for o in objs] # Set up a temporary file with the initial data for editing. new = NamedTemporaryFile( mode="w", suffix=".yaml", delete=False, encoding="utf-8" ) old_str = dump(old_data) new.write(old_str) new.close() # Loop until we have parseable data and the user confirms. try: while True: # Ask the user to edit the data. edit(new.name, self._log) # Read the data back after editing and check whether anything # changed. with codecs.open(new.name, encoding="utf-8") as f: new_str = f.read() if new_str == old_str: ui.print_("No changes; aborting.") return False # Parse the updated data. try: new_data = load(new_str) except ParseError as e: ui.print_(f"Could not read data: {e}") if ui.input_yn("Edit again to fix? (Y/n)", True): continue else: return False # Show the changes. # If the objects are not on the DB yet, we need a copy of their # original state for show_model_changes. objs_old = [obj.copy() if obj.id < 0 else None for obj in objs] self.apply_data(objs, old_data, new_data) changed = False for obj, obj_old in zip(objs, objs_old): changed |= ui.show_model_changes(obj, obj_old) if not changed: ui.print_("No changes to apply.") return False # Confirm the changes. choice = ui.input_options( ("continue Editing", "apply", "cancel") ) if choice == "a": # Apply. return True elif choice == "c": # Cancel. return False elif choice == "e": # Keep editing. # Reset the temporary changes to the objects. I we have a # copy from above, use that, else reload from the database. objs = [ (old_obj or obj) for old_obj, obj in zip(objs_old, objs) ] for obj in objs: if not obj.id < 0: obj.load() continue # Remove the temporary file before returning. finally: os.remove(new.name) def apply_data(self, objs, old_data, new_data): """Take potentially-updated data and apply it to a set of Model objects. The objects are not written back to the database, so the changes are temporary. """ if len(old_data) != len(new_data): self._log.warning( "number of objects changed from {} to {}", len(old_data), len(new_data), ) obj_by_id = {o.id: o for o in objs} ignore_fields = self.config["ignore_fields"].as_str_seq() for old_dict, new_dict in zip(old_data, new_data): # Prohibit any changes to forbidden fields to avoid # clobbering `id` and such by mistake. forbidden = False for key in ignore_fields: if old_dict.get(key) != new_dict.get(key): self._log.warning("ignoring object whose {} changed", key) forbidden = True break if forbidden: continue id_ = int(old_dict["id"]) apply_(obj_by_id[id_], new_dict) def save_changes(self, objs): """Save a list of updated Model objects to the database.""" # Save to the database and possibly write tags. for ob in objs: if ob._dirty: self._log.debug("saving changes to {}", ob) ob.try_sync(ui.should_write(), ui.should_move()) # Methods for interactive importer execution. def before_choose_candidate_listener(self, session, task): """Append an "Edit" choice and an "edit Candidates" choice (if there are candidates) to the interactive importer prompt. """ choices = [PromptChoice("d", "eDit", self.importer_edit)] if task.candidates: choices.append( PromptChoice( "c", "edit Candidates", self.importer_edit_candidate ) ) return choices def importer_edit(self, session, task): """Callback for invoking the functionality during an interactive import session on the *original* item tags. """ # Assign negative temporary ids to Items that are not in the database # yet. By using negative values, no clash with items in the database # can occur. for i, obj in enumerate(task.items, start=1): # The importer may set the id to None when re-importing albums. if not obj._db or obj.id is None: obj.id = -i # Present the YAML to the user and let them change it. fields = self._get_fields(album=False, extra=[]) success = self.edit_objects(task.items, fields) # Remove temporary ids. for obj in task.items: if obj.id < 0: obj.id = None # Save the new data. if success: # Return action.RETAG, which makes the importer write the tags # to the files if needed without re-applying metadata. return action.RETAG else: # Edit cancelled / no edits made. Revert changes. for obj in task.items: obj.read() def importer_edit_candidate(self, session, task): """Callback for invoking the functionality during an interactive import session on a *candidate*. The candidate's metadata is applied to the original items. """ # Prompt the user for a candidate. sel = ui.input_options([], numrange=(1, len(task.candidates))) # Force applying the candidate on the items. task.match = task.candidates[sel - 1] task.apply_metadata() return self.importer_edit(session, task) beetbox-beets-01f1faf/beetsplug/embedart.py000066400000000000000000000230371472325477400211030ustar00rootroot00000000000000# This file is part of beets. # Copyright 2016, Adrian Sampson. # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the # "Software"), to deal in the Software without restriction, including # without limitation the rights to use, copy, modify, merge, publish, # distribute, sublicense, and/or sell copies of the Software, and to # permit persons to whom the Software is furnished to do so, subject to # the following conditions: # # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. """Allows beets to embed album art into file metadata.""" import os.path import tempfile from mimetypes import guess_extension import requests from beets import art, config, ui from beets.plugins import BeetsPlugin from beets.ui import decargs, print_ from beets.util import bytestring_path, displayable_path, normpath, syspath from beets.util.artresizer import ArtResizer def _confirm(objs, album): """Show the list of affected objects (items or albums) and confirm that the user wants to modify their artwork. `album` is a Boolean indicating whether these are albums (as opposed to items). """ noun = "album" if album else "file" prompt = "Modify artwork for {} {}{} (Y/n)?".format( len(objs), noun, "s" if len(objs) > 1 else "" ) # Show all the items or albums. for obj in objs: print_(format(obj)) # Confirm with user. return ui.input_yn(prompt) class EmbedCoverArtPlugin(BeetsPlugin): """Allows albumart to be embedded into the actual files.""" def __init__(self): super().__init__() self.config.add( { "maxwidth": 0, "auto": True, "compare_threshold": 0, "ifempty": False, "remove_art_file": False, "quality": 0, } ) if self.config["maxwidth"].get(int) and not ArtResizer.shared.local: self.config["maxwidth"] = 0 self._log.warning( "ImageMagick or PIL not found; " "'maxwidth' option ignored" ) if ( self.config["compare_threshold"].get(int) and not ArtResizer.shared.can_compare ): self.config["compare_threshold"] = 0 self._log.warning( "ImageMagick 6.8.7 or higher not installed; " "'compare_threshold' option ignored" ) self.register_listener("art_set", self.process_album) def commands(self): # Embed command. embed_cmd = ui.Subcommand( "embedart", help="embed image files into file metadata" ) embed_cmd.parser.add_option( "-f", "--file", metavar="PATH", help="the image file to embed" ) embed_cmd.parser.add_option( "-y", "--yes", action="store_true", help="skip confirmation" ) embed_cmd.parser.add_option( "-u", "--url", metavar="URL", help="the URL of the image file to embed", ) maxwidth = self.config["maxwidth"].get(int) quality = self.config["quality"].get(int) compare_threshold = self.config["compare_threshold"].get(int) ifempty = self.config["ifempty"].get(bool) def embed_func(lib, opts, args): if opts.file: imagepath = normpath(opts.file) if not os.path.isfile(syspath(imagepath)): raise ui.UserError( "image file {} not found".format( displayable_path(imagepath) ) ) items = lib.items(decargs(args)) # Confirm with user. if not opts.yes and not _confirm(items, not opts.file): return for item in items: art.embed_item( self._log, item, imagepath, maxwidth, None, compare_threshold, ifempty, quality=quality, ) elif opts.url: try: response = requests.get(opts.url, timeout=5) response.raise_for_status() except requests.exceptions.RequestException as e: self._log.error("{}".format(e)) return extension = guess_extension(response.headers["Content-Type"]) if extension is None: self._log.error("Invalid image file") return file = f"image{extension}" tempimg = os.path.join(tempfile.gettempdir(), file) try: with open(tempimg, "wb") as f: f.write(response.content) except Exception as e: self._log.error("Unable to save image: {}".format(e)) return items = lib.items(decargs(args)) # Confirm with user. if not opts.yes and not _confirm(items, not opts.url): os.remove(tempimg) return for item in items: art.embed_item( self._log, item, tempimg, maxwidth, None, compare_threshold, ifempty, quality=quality, ) os.remove(tempimg) else: albums = lib.albums(decargs(args)) # Confirm with user. if not opts.yes and not _confirm(albums, not opts.file): return for album in albums: art.embed_album( self._log, album, maxwidth, False, compare_threshold, ifempty, quality=quality, ) self.remove_artfile(album) embed_cmd.func = embed_func # Extract command. extract_cmd = ui.Subcommand( "extractart", help="extract an image from file metadata", ) extract_cmd.parser.add_option( "-o", dest="outpath", help="image output file", ) extract_cmd.parser.add_option( "-n", dest="filename", help="image filename to create for all matched albums", ) extract_cmd.parser.add_option( "-a", dest="associate", action="store_true", help="associate the extracted images with the album", ) def extract_func(lib, opts, args): if opts.outpath: art.extract_first( self._log, normpath(opts.outpath), lib.items(decargs(args)) ) else: filename = bytestring_path( opts.filename or config["art_filename"].get() ) if os.path.dirname(filename) != b"": self._log.error( "Only specify a name rather than a path for -n" ) return for album in lib.albums(decargs(args)): artpath = normpath(os.path.join(album.path, filename)) artpath = art.extract_first( self._log, artpath, album.items() ) if artpath and opts.associate: album.set_art(artpath) album.store() extract_cmd.func = extract_func # Clear command. clear_cmd = ui.Subcommand( "clearart", help="remove images from file metadata", ) clear_cmd.parser.add_option( "-y", "--yes", action="store_true", help="skip confirmation" ) def clear_func(lib, opts, args): items = lib.items(decargs(args)) # Confirm with user. if not opts.yes and not _confirm(items, False): return art.clear(self._log, lib, decargs(args)) clear_cmd.func = clear_func return [embed_cmd, extract_cmd, clear_cmd] def process_album(self, album): """Automatically embed art after art has been set""" if self.config["auto"] and ui.should_write(): max_width = self.config["maxwidth"].get(int) art.embed_album( self._log, album, max_width, True, self.config["compare_threshold"].get(int), self.config["ifempty"].get(bool), ) self.remove_artfile(album) def remove_artfile(self, album): """Possibly delete the album art file for an album (if the appropriate configuration option is enabled). """ if self.config["remove_art_file"] and album.artpath: if os.path.isfile(syspath(album.artpath)): self._log.debug("Removing album art file for {0}", album) os.remove(syspath(album.artpath)) album.artpath = None album.store() beetbox-beets-01f1faf/beetsplug/embyupdate.py000066400000000000000000000135371472325477400214630ustar00rootroot00000000000000"""Updates the Emby Library whenever the beets library is changed. emby: host: localhost port: 8096 username: user apikey: apikey password: password """ import hashlib from urllib.parse import parse_qs, urlencode, urljoin, urlsplit, urlunsplit import requests from beets import config from beets.plugins import BeetsPlugin def api_url(host, port, endpoint): """Returns a joined url. Takes host, port and endpoint and generates a valid emby API url. :param host: Hostname of the emby server :param port: Portnumber of the emby server :param endpoint: API endpoint :type host: str :type port: int :type endpoint: str :returns: Full API url :rtype: str """ # check if http or https is defined as host and create hostname hostname_list = [host] if host.startswith("http://") or host.startswith("https://"): hostname = "".join(hostname_list) else: hostname_list.insert(0, "http://") hostname = "".join(hostname_list) joined = urljoin( "{hostname}:{port}".format(hostname=hostname, port=port), endpoint ) scheme, netloc, path, query_string, fragment = urlsplit(joined) query_params = parse_qs(query_string) query_params["format"] = ["json"] new_query_string = urlencode(query_params, doseq=True) return urlunsplit((scheme, netloc, path, new_query_string, fragment)) def password_data(username, password): """Returns a dict with username and its encoded password. :param username: Emby username :param password: Emby password :type username: str :type password: str :returns: Dictionary with username and encoded password :rtype: dict """ return { "username": username, "password": hashlib.sha1(password.encode("utf-8")).hexdigest(), "passwordMd5": hashlib.md5(password.encode("utf-8")).hexdigest(), } def create_headers(user_id, token=None): """Return header dict that is needed to talk to the Emby API. :param user_id: Emby user ID :param token: Authentication token for Emby :type user_id: str :type token: str :returns: Headers for requests :rtype: dict """ headers = {} authorization = ( 'MediaBrowser UserId="{user_id}", ' 'Client="other", ' 'Device="beets", ' 'DeviceId="beets", ' 'Version="0.0.0"' ).format(user_id=user_id) headers["x-emby-authorization"] = authorization if token: headers["x-mediabrowser-token"] = token return headers def get_token(host, port, headers, auth_data): """Return token for a user. :param host: Emby host :param port: Emby port :param headers: Headers for requests :param auth_data: Username and encoded password for authentication :type host: str :type port: int :type headers: dict :type auth_data: dict :returns: Access Token :rtype: str """ url = api_url(host, port, "/Users/AuthenticateByName") r = requests.post( url, headers=headers, data=auth_data, timeout=10, ) return r.json().get("AccessToken") def get_user(host, port, username): """Return user dict from server or None if there is no user. :param host: Emby host :param port: Emby port :username: Username :type host: str :type port: int :type username: str :returns: Matched Users :rtype: list """ url = api_url(host, port, "/Users/Public") r = requests.get(url, timeout=10) user = [i for i in r.json() if i["Name"] == username] return user class EmbyUpdate(BeetsPlugin): def __init__(self): super().__init__() # Adding defaults. config["emby"].add( { "host": "http://localhost", "port": 8096, "apikey": None, "password": None, } ) self.register_listener("database_change", self.listen_for_db_change) def listen_for_db_change(self, lib, model): """Listens for beets db change and register the update for the end.""" self.register_listener("cli_exit", self.update) def update(self, lib): """When the client exists try to send refresh request to Emby.""" self._log.info("Updating Emby library...") host = config["emby"]["host"].get() port = config["emby"]["port"].get() username = config["emby"]["username"].get() password = config["emby"]["password"].get() userid = config["emby"]["userid"].get() token = config["emby"]["apikey"].get() # Check if at least a apikey or password is given. if not any([password, token]): self._log.warning("Provide at least Emby password or apikey.") return if not userid: # Get user information from the Emby API. user = get_user(host, port, username) if not user: self._log.warning(f"User {username} could not be found.") return userid = user[0]["Id"] if not token: # Create Authentication data and headers. auth_data = password_data(username, password) headers = create_headers(userid) # Get authentication token. token = get_token(host, port, headers, auth_data) if not token: self._log.warning("Could not get token for user {0}", username) return # Recreate headers with a token. headers = create_headers(userid, token=token) # Trigger the Update. url = api_url(host, port, "/Library/Refresh") r = requests.post( url, headers=headers, timeout=10, ) if r.status_code != 204: self._log.warning("Update could not be triggered") else: self._log.info("Update triggered.") beetbox-beets-01f1faf/beetsplug/export.py000066400000000000000000000175271472325477400206500ustar00rootroot00000000000000# This file is part of beets. # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the # "Software"), to deal in the Software without restriction, including # without limitation the rights to use, copy, modify, merge, publish, # distribute, sublicense, and/or sell copies of the Software, and to # permit persons to whom the Software is furnished to do so, subject to # the following conditions: # # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. """Exports data from beets""" import codecs import csv import json import sys from datetime import date, datetime from xml.etree import ElementTree import mediafile from beets import ui, util from beets.plugins import BeetsPlugin from beetsplug.info import library_data, tag_data class ExportEncoder(json.JSONEncoder): """Deals with dates because JSON doesn't have a standard""" def default(self, o): if isinstance(o, (datetime, date)): return o.isoformat() return json.JSONEncoder.default(self, o) class ExportPlugin(BeetsPlugin): def __init__(self): super().__init__() self.config.add( { "default_format": "json", "json": { # JSON module formatting options. "formatting": { "ensure_ascii": False, "indent": 4, "separators": (",", ": "), "sort_keys": True, } }, "jsonlines": { # JSON Lines formatting options. "formatting": { "ensure_ascii": False, "separators": (",", ": "), "sort_keys": True, } }, "csv": { # CSV module formatting options. "formatting": { # The delimiter used to separate columns. "delimiter": ",", # The dialect to use when formatting the file output. "dialect": "excel", } }, "xml": { # XML module formatting options. "formatting": {} }, # TODO: Use something like the edit plugin # 'item_fields': [] } ) def commands(self): cmd = ui.Subcommand("export", help="export data from beets") cmd.func = self.run cmd.parser.add_option( "-l", "--library", action="store_true", help="show library fields instead of tags", ) cmd.parser.add_option( "-a", "--album", action="store_true", help='show album fields instead of tracks (implies "--library")', ) cmd.parser.add_option( "--append", action="store_true", default=False, help="if should append data to the file", ) cmd.parser.add_option( "-i", "--include-keys", default=[], action="append", dest="included_keys", help="comma separated list of keys to show", ) cmd.parser.add_option( "-o", "--output", help="path for the output file. If not given, will print the data", ) cmd.parser.add_option( "-f", "--format", default="json", help="the output format: json (default), jsonlines, csv, or xml", ) return [cmd] def run(self, lib, opts, args): file_path = opts.output file_mode = "a" if opts.append else "w" file_format = opts.format or self.config["default_format"].get(str) file_format_is_line_based = file_format == "jsonlines" format_options = self.config[file_format]["formatting"].get(dict) export_format = ExportFormat.factory( file_type=file_format, **{"file_path": file_path, "file_mode": file_mode}, ) if opts.library or opts.album: data_collector = library_data else: data_collector = tag_data included_keys = [] for keys in opts.included_keys: included_keys.extend(keys.split(",")) items = [] for data_emitter in data_collector( lib, ui.decargs(args), album=opts.album, ): try: data, item = data_emitter(included_keys or "*") except (mediafile.UnreadableFileError, OSError) as ex: self._log.error("cannot read file: {0}", ex) continue for key, value in data.items(): if isinstance(value, bytes): data[key] = util.displayable_path(value) if file_format_is_line_based: export_format.export(data, **format_options) else: items += [data] if not file_format_is_line_based: export_format.export(items, **format_options) class ExportFormat: """The output format type""" def __init__(self, file_path, file_mode="w", encoding="utf-8"): self.path = file_path self.mode = file_mode self.encoding = encoding # creates a file object to write/append or sets to stdout self.out_stream = ( codecs.open(self.path, self.mode, self.encoding) if self.path else sys.stdout ) @classmethod def factory(cls, file_type, **kwargs): if file_type in ["json", "jsonlines"]: return JsonFormat(**kwargs) elif file_type == "csv": return CSVFormat(**kwargs) elif file_type == "xml": return XMLFormat(**kwargs) else: raise NotImplementedError() def export(self, data, **kwargs): raise NotImplementedError() class JsonFormat(ExportFormat): """Saves in a json file""" def __init__(self, file_path, file_mode="w", encoding="utf-8"): super().__init__(file_path, file_mode, encoding) def export(self, data, **kwargs): json.dump(data, self.out_stream, cls=ExportEncoder, **kwargs) self.out_stream.write("\n") class CSVFormat(ExportFormat): """Saves in a csv file""" def __init__(self, file_path, file_mode="w", encoding="utf-8"): super().__init__(file_path, file_mode, encoding) def export(self, data, **kwargs): header = list(data[0].keys()) if data else [] writer = csv.DictWriter(self.out_stream, fieldnames=header, **kwargs) writer.writeheader() writer.writerows(data) class XMLFormat(ExportFormat): """Saves in a xml file""" def __init__(self, file_path, file_mode="w", encoding="utf-8"): super().__init__(file_path, file_mode, encoding) def export(self, data, **kwargs): # Creates the XML file structure. library = ElementTree.Element("library") tracks = ElementTree.SubElement(library, "tracks") if data and isinstance(data[0], dict): for index, item in enumerate(data): track = ElementTree.SubElement(tracks, "track") for key, value in item.items(): track_details = ElementTree.SubElement(track, key) track_details.text = value # Depending on the version of python the encoding needs to change try: data = ElementTree.tostring(library, encoding="unicode", **kwargs) except LookupError: data = ElementTree.tostring(library, encoding="utf-8", **kwargs) self.out_stream.write(data) beetbox-beets-01f1faf/beetsplug/fetchart.py000066400000000000000000001442061472325477400211220ustar00rootroot00000000000000# This file is part of beets. # Copyright 2016, Adrian Sampson. # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the # "Software"), to deal in the Software without restriction, including # without limitation the rights to use, copy, modify, merge, publish, # distribute, sublicense, and/or sell copies of the Software, and to # permit persons to whom the Software is furnished to do so, subject to # the following conditions: # # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. """Fetches album art.""" import os import re from collections import OrderedDict from contextlib import closing import confuse import requests from mediafile import image_mime_type from beets import config, importer, plugins, ui, util from beets.util import bytestring_path, get_temp_filename, sorted_walk, syspath from beets.util.artresizer import ArtResizer try: from bs4 import BeautifulSoup HAS_BEAUTIFUL_SOUP = True except ImportError: HAS_BEAUTIFUL_SOUP = False CONTENT_TYPES = {"image/jpeg": [b"jpg", b"jpeg"], "image/png": [b"png"]} IMAGE_EXTENSIONS = [ext for exts in CONTENT_TYPES.values() for ext in exts] class Candidate: """Holds information about a matching artwork, deals with validation of dimension restrictions and resizing. """ CANDIDATE_BAD = 0 CANDIDATE_EXACT = 1 CANDIDATE_DOWNSCALE = 2 CANDIDATE_DOWNSIZE = 3 CANDIDATE_DEINTERLACE = 4 CANDIDATE_REFORMAT = 5 MATCH_EXACT = 0 MATCH_FALLBACK = 1 def __init__( self, log, path=None, url=None, source="", match=None, size=None ): self._log = log self.path = path self.url = url self.source = source self.check = None self.match = match self.size = size def _validate(self, plugin, skip_check_for=None): """Determine whether the candidate artwork is valid based on its dimensions (width and ratio). `skip_check_for` is a check or list of checks to skip. This is used to avoid redundant checks when the candidate has already been validated for a particular operation without changing plugin configuration. Return `CANDIDATE_BAD` if the file is unusable. Return `CANDIDATE_EXACT` if the file is usable as-is. Return `CANDIDATE_DOWNSCALE` if the file must be rescaled. Return `CANDIDATE_DOWNSIZE` if the file must be resized, and possibly also rescaled. Return `CANDIDATE_DEINTERLACE` if the file must be deinterlaced. Return `CANDIDATE_REFORMAT` if the file has to be converted. """ if not self.path: return self.CANDIDATE_BAD if skip_check_for is None: skip_check_for = [] if isinstance(skip_check_for, int): skip_check_for = [skip_check_for] if not ( plugin.enforce_ratio or plugin.minwidth or plugin.maxwidth or plugin.max_filesize or plugin.deinterlace or plugin.cover_format ): return self.CANDIDATE_EXACT # get_size returns None if no local imaging backend is available if not self.size: self.size = ArtResizer.shared.get_size(self.path) self._log.debug("image size: {}", self.size) if not self.size: self._log.warning( "Could not get size of image (please see " "documentation for dependencies). " "The configuration options `minwidth`, " "`enforce_ratio` and `max_filesize` " "may be violated." ) return self.CANDIDATE_EXACT short_edge = min(self.size) long_edge = max(self.size) # Check minimum dimension. if plugin.minwidth and self.size[0] < plugin.minwidth: self._log.debug( "image too small ({} < {})", self.size[0], plugin.minwidth ) return self.CANDIDATE_BAD # Check aspect ratio. edge_diff = long_edge - short_edge if plugin.enforce_ratio: if plugin.margin_px: if edge_diff > plugin.margin_px: self._log.debug( "image is not close enough to being " "square, ({} - {} > {})", long_edge, short_edge, plugin.margin_px, ) return self.CANDIDATE_BAD elif plugin.margin_percent: margin_px = plugin.margin_percent * long_edge if edge_diff > margin_px: self._log.debug( "image is not close enough to being " "square, ({} - {} > {})", long_edge, short_edge, margin_px, ) return self.CANDIDATE_BAD elif edge_diff: # also reached for margin_px == 0 and margin_percent == 0.0 self._log.debug( "image is not square ({} != {})", self.size[0], self.size[1] ) return self.CANDIDATE_BAD # Check maximum dimension. downscale = False if plugin.maxwidth and self.size[0] > plugin.maxwidth: self._log.debug( "image needs rescaling ({} > {})", self.size[0], plugin.maxwidth ) downscale = True # Check filesize. downsize = False if plugin.max_filesize: filesize = os.stat(syspath(self.path)).st_size if filesize > plugin.max_filesize: self._log.debug( "image needs resizing ({}B > {}B)", filesize, plugin.max_filesize, ) downsize = True # Check image format reformat = False if plugin.cover_format: fmt = ArtResizer.shared.get_format(self.path) reformat = fmt != plugin.cover_format if reformat: self._log.debug( "image needs reformatting: {} -> {}", fmt, plugin.cover_format, ) if downscale and (self.CANDIDATE_DOWNSCALE not in skip_check_for): return self.CANDIDATE_DOWNSCALE if reformat and (self.CANDIDATE_REFORMAT not in skip_check_for): return self.CANDIDATE_REFORMAT if plugin.deinterlace and ( self.CANDIDATE_DEINTERLACE not in skip_check_for ): return self.CANDIDATE_DEINTERLACE if downsize and (self.CANDIDATE_DOWNSIZE not in skip_check_for): return self.CANDIDATE_DOWNSIZE return self.CANDIDATE_EXACT def validate(self, plugin, skip_check_for=None): self.check = self._validate(plugin, skip_check_for) return self.check def resize(self, plugin): """Resize the candidate artwork according to the plugin's configuration until it is valid or no further resizing is possible. """ # validate the candidate in case it hasn't been done yet current_check = self.validate(plugin) checks_performed = [] # we don't want to resize the image if it's valid or bad while current_check not in [self.CANDIDATE_BAD, self.CANDIDATE_EXACT]: self._resize(plugin, current_check) checks_performed.append(current_check) current_check = self.validate( plugin, skip_check_for=checks_performed ) def _resize(self, plugin, check=None): """Resize the candidate artwork according to the plugin's configuration and the specified check. """ if check == self.CANDIDATE_DOWNSCALE: self.path = ArtResizer.shared.resize( plugin.maxwidth, self.path, quality=plugin.quality, max_filesize=plugin.max_filesize, ) elif check == self.CANDIDATE_DOWNSIZE: # dimensions are correct, so maxwidth is set to maximum dimension self.path = ArtResizer.shared.resize( max(self.size), self.path, quality=plugin.quality, max_filesize=plugin.max_filesize, ) elif check == self.CANDIDATE_DEINTERLACE: self.path = ArtResizer.shared.deinterlace(self.path) elif check == self.CANDIDATE_REFORMAT: self.path = ArtResizer.shared.reformat( self.path, plugin.cover_format, deinterlaced=plugin.deinterlace, ) def _logged_get(log, *args, **kwargs): """Like `requests.get`, but logs the effective URL to the specified `log` at the `DEBUG` level. Use the optional `message` parameter to specify what to log before the URL. By default, the string is "getting URL". Also sets the User-Agent header to indicate beets. """ # Use some arguments with the `send` call but most with the # `Request` construction. This is a cheap, magic-filled way to # emulate `requests.get` or, more pertinently, # `requests.Session.request`. req_kwargs = kwargs send_kwargs = {} for arg in ("stream", "verify", "proxies", "cert", "timeout"): if arg in kwargs: send_kwargs[arg] = req_kwargs.pop(arg) if "timeout" not in send_kwargs: send_kwargs["timeout"] = 10 # Our special logging message parameter. if "message" in kwargs: message = kwargs.pop("message") else: message = "getting URL" req = requests.Request("GET", *args, **req_kwargs) with requests.Session() as s: s.headers = {"User-Agent": "beets"} prepped = s.prepare_request(req) settings = s.merge_environment_settings( prepped.url, {}, None, None, None ) send_kwargs.update(settings) log.debug("{}: {}", message, prepped.url) return s.send(prepped, **send_kwargs) class RequestMixin: """Adds a Requests wrapper to the class that uses the logger, which must be named `self._log`. """ def request(self, *args, **kwargs): """Like `requests.get`, but uses the logger `self._log`. See also `_logged_get`. """ return _logged_get(self._log, *args, **kwargs) # ART SOURCES ################################################################ class ArtSource(RequestMixin): VALID_MATCHING_CRITERIA = ["default"] def __init__(self, log, config, match_by=None): self._log = log self._config = config self.match_by = match_by or self.VALID_MATCHING_CRITERIA @staticmethod def add_default_config(config): pass @classmethod def available(cls, log, config): """Return whether or not all dependencies are met and the art source is in fact usable. """ return True def get(self, album, plugin, paths): raise NotImplementedError() def _candidate(self, **kwargs): return Candidate(source=self, log=self._log, **kwargs) def fetch_image(self, candidate, plugin): raise NotImplementedError() def cleanup(self, candidate): pass class LocalArtSource(ArtSource): IS_LOCAL = True LOC_STR = "local" def fetch_image(self, candidate, plugin): pass class RemoteArtSource(ArtSource): IS_LOCAL = False LOC_STR = "remote" def fetch_image(self, candidate, plugin): """Downloads an image from a URL and checks whether it seems to actually be an image. If so, returns a path to the downloaded image. Otherwise, returns None. """ if plugin.maxwidth: candidate.url = ArtResizer.shared.proxy_url( plugin.maxwidth, candidate.url ) try: with closing( self.request( candidate.url, stream=True, message="downloading image" ) ) as resp: ct = resp.headers.get("Content-Type", None) # Download the image to a temporary file. As some servers # (notably fanart.tv) have proven to return wrong Content-Types # when images were uploaded with a bad file extension, do not # rely on it. Instead validate the type using the file magic # and only then determine the extension. data = resp.iter_content(chunk_size=1024) header = b"" for chunk in data: header += chunk if len(header) >= 32: # The imghdr module will only read 32 bytes, and our # own additions in mediafile even less. break else: # server didn't return enough data, i.e. corrupt image return real_ct = image_mime_type(header) if real_ct is None: # detection by file magic failed, fall back to the # server-supplied Content-Type # Is our type detection failsafe enough to drop this? real_ct = ct if real_ct not in CONTENT_TYPES: self._log.debug( "not a supported image: {}", real_ct or "unknown content type", ) return ext = b"." + CONTENT_TYPES[real_ct][0] if real_ct != ct: self._log.warning( "Server specified {}, but returned a " "{} image. Correcting the extension " "to {}", ct, real_ct, ext, ) filename = get_temp_filename(__name__, suffix=ext.decode()) with open(filename, "wb") as fh: # write the first already loaded part of the image fh.write(header) # download the remaining part of the image for chunk in data: fh.write(chunk) self._log.debug( "downloaded art to: {0}", util.displayable_path(filename) ) candidate.path = util.bytestring_path(filename) return except (OSError, requests.RequestException, TypeError) as exc: # Handling TypeError works around a urllib3 bug: # https://github.com/shazow/urllib3/issues/556 self._log.debug("error fetching art: {}", exc) return def cleanup(self, candidate): if candidate.path: try: util.remove(path=candidate.path) except util.FilesystemError as exc: self._log.debug("error cleaning up tmp art: {}", exc) class CoverArtArchive(RemoteArtSource): NAME = "Cover Art Archive" VALID_MATCHING_CRITERIA = ["release", "releasegroup"] VALID_THUMBNAIL_SIZES = [250, 500, 1200] URL = "https://coverartarchive.org/release/{mbid}" GROUP_URL = "https://coverartarchive.org/release-group/{mbid}" def get(self, album, plugin, paths): """Return the Cover Art Archive and Cover Art Archive release group URLs using album MusicBrainz release ID and release group ID. """ def get_image_urls(url, preferred_width=None): try: response = self.request(url) except requests.RequestException: self._log.debug( "{}: error receiving response".format(self.NAME) ) return try: data = response.json() except ValueError: self._log.debug( "{}: error loading response: {}".format( self.NAME, response.text ) ) return for item in data.get("images", []): try: if "Front" not in item["types"]: continue # If there is a pre-sized thumbnail of the desired size # we select it. Otherwise, we return the raw image. image_url: str = item["image"] if preferred_width is not None: if isinstance(item.get("thumbnails"), dict): image_url = item["thumbnails"].get( preferred_width, image_url ) yield image_url except KeyError: pass release_url = self.URL.format(mbid=album.mb_albumid) release_group_url = self.GROUP_URL.format(mbid=album.mb_releasegroupid) # Cover Art Archive API offers pre-resized thumbnails at several sizes. # If the maxwidth config matches one of the already available sizes # fetch it directly instead of fetching the full sized image and # resizing it. preferred_width = None if plugin.maxwidth in self.VALID_THUMBNAIL_SIZES: preferred_width = str(plugin.maxwidth) if "release" in self.match_by and album.mb_albumid: for url in get_image_urls(release_url, preferred_width): yield self._candidate(url=url, match=Candidate.MATCH_EXACT) if "releasegroup" in self.match_by and album.mb_releasegroupid: for url in get_image_urls(release_group_url, preferred_width): yield self._candidate(url=url, match=Candidate.MATCH_FALLBACK) class Amazon(RemoteArtSource): NAME = "Amazon" URL = "https://images.amazon.com/images/P/%s.%02i.LZZZZZZZ.jpg" INDICES = (1, 2) def get(self, album, plugin, paths): """Generate URLs using Amazon ID (ASIN) string.""" if album.asin: for index in self.INDICES: yield self._candidate( url=self.URL % (album.asin, index), match=Candidate.MATCH_EXACT, ) class AlbumArtOrg(RemoteArtSource): NAME = "AlbumArt.org scraper" URL = "https://www.albumart.org/index_detail.php" PAT = r'href\s*=\s*"([^>"]*)"[^>]*title\s*=\s*"View larger image"' def get(self, album, plugin, paths): """Return art URL from AlbumArt.org using album ASIN.""" if not album.asin: return # Get the page from albumart.org. try: resp = self.request(self.URL, params={"asin": album.asin}) self._log.debug("scraped art URL: {0}", resp.url) except requests.RequestException: self._log.debug("error scraping art page") return # Search the page for the image URL. m = re.search(self.PAT, resp.text) if m: image_url = m.group(1) yield self._candidate(url=image_url, match=Candidate.MATCH_EXACT) else: self._log.debug("no image found on page") class GoogleImages(RemoteArtSource): NAME = "Google Images" URL = "https://www.googleapis.com/customsearch/v1" def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.key = (self._config["google_key"].get(),) self.cx = (self._config["google_engine"].get(),) @staticmethod def add_default_config(config): config.add( { "google_key": None, "google_engine": "001442825323518660753:hrh5ch1gjzm", } ) config["google_key"].redact = True @classmethod def available(cls, log, config): has_key = bool(config["google_key"].get()) if not has_key: log.debug("google: Disabling art source due to missing key") return has_key def get(self, album, plugin, paths): """Return art URL from google custom search engine given an album title and interpreter. """ if not (album.albumartist and album.album): return search_string = (album.albumartist + "," + album.album).encode("utf-8") try: response = self.request( self.URL, params={ "key": self.key, "cx": self.cx, "q": search_string, "searchType": "image", }, ) except requests.RequestException: self._log.debug("google: error receiving response") return # Get results using JSON. try: data = response.json() except ValueError: self._log.debug( "google: error loading response: {}".format(response.text) ) return if "error" in data: reason = data["error"]["errors"][0]["reason"] self._log.debug("google fetchart error: {0}", reason) return if "items" in data.keys(): for item in data["items"]: yield self._candidate( url=item["link"], match=Candidate.MATCH_EXACT ) class FanartTV(RemoteArtSource): """Art from fanart.tv requested using their API""" NAME = "fanart.tv" API_URL = "https://webservice.fanart.tv/v3/" API_ALBUMS = API_URL + "music/albums/" PROJECT_KEY = "61a7d0ab4e67162b7a0c7c35915cd48e" def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.client_key = self._config["fanarttv_key"].get() @staticmethod def add_default_config(config): config.add( { "fanarttv_key": None, } ) config["fanarttv_key"].redact = True def get(self, album, plugin, paths): if not album.mb_releasegroupid: return try: response = self.request( self.API_ALBUMS + album.mb_releasegroupid, headers={ "api-key": self.PROJECT_KEY, "client-key": self.client_key, }, ) except requests.RequestException: self._log.debug("fanart.tv: error receiving response") return try: data = response.json() except ValueError: self._log.debug( "fanart.tv: error loading response: {}", response.text ) return if "status" in data and data["status"] == "error": if "not found" in data["error message"].lower(): self._log.debug("fanart.tv: no image found") elif "api key" in data["error message"].lower(): self._log.warning( "fanart.tv: Invalid API key given, please " "enter a valid one in your config file." ) else: self._log.debug( "fanart.tv: error on request: {}", data["error message"] ) return matches = [] # can there be more than one releasegroupid per response? for mbid, art in data.get("albums", {}).items(): # there might be more art referenced, e.g. cdart, and an albumcover # might not be present, even if the request was successful if album.mb_releasegroupid == mbid and "albumcover" in art: matches.extend(art["albumcover"]) # can this actually occur? else: self._log.debug( "fanart.tv: unexpected mb_releasegroupid in " "response!" ) matches.sort(key=lambda x: int(x["likes"]), reverse=True) for item in matches: # fanart.tv has a strict size requirement for album art to be # uploaded yield self._candidate( url=item["url"], match=Candidate.MATCH_EXACT, size=(1000, 1000) ) class ITunesStore(RemoteArtSource): NAME = "iTunes Store" API_URL = "https://itunes.apple.com/search" def get(self, album, plugin, paths): """Return art URL from iTunes Store given an album title.""" if not (album.albumartist and album.album): return payload = { "term": album.albumartist + " " + album.album, "entity": "album", "media": "music", "limit": 200, } try: r = self.request(self.API_URL, params=payload) r.raise_for_status() except requests.RequestException as e: self._log.debug("iTunes search failed: {0}", e) return try: candidates = r.json()["results"] except ValueError as e: self._log.debug("Could not decode json response: {0}", e) return except KeyError as e: self._log.debug( "{} not found in json. Fields are {} ", e, list(r.json().keys()) ) return if not candidates: self._log.debug( "iTunes search for {!r} got no results", payload["term"] ) return if self._config["high_resolution"]: image_suffix = "100000x100000-999" else: image_suffix = "1200x1200bb" for c in candidates: try: if ( c["artistName"] == album.albumartist and c["collectionName"] == album.album ): art_url = c["artworkUrl100"] art_url = art_url.replace("100x100bb", image_suffix) yield self._candidate( url=art_url, match=Candidate.MATCH_EXACT ) except KeyError as e: self._log.debug( "Malformed itunes candidate: {} not found in {}", # NOQA E501 e, list(c.keys()), ) try: fallback_art_url = candidates[0]["artworkUrl100"] fallback_art_url = fallback_art_url.replace( "100x100bb", image_suffix ) yield self._candidate( url=fallback_art_url, match=Candidate.MATCH_FALLBACK ) except KeyError as e: self._log.debug( "Malformed itunes candidate: {} not found in {}", e, list(c.keys()), ) class Wikipedia(RemoteArtSource): NAME = "Wikipedia (queried through DBpedia)" DBPEDIA_URL = "https://dbpedia.org/sparql" WIKIPEDIA_URL = "https://en.wikipedia.org/w/api.php" SPARQL_QUERY = """PREFIX rdf: PREFIX dbpprop: PREFIX owl: PREFIX rdfs: PREFIX foaf: SELECT DISTINCT ?pageId ?coverFilename WHERE {{ ?subject owl:wikiPageID ?pageId . ?subject dbpprop:name ?name . ?subject rdfs:label ?label . {{ ?subject dbpprop:artist ?artist }} UNION {{ ?subject owl:artist ?artist }} {{ ?artist foaf:name "{artist}"@en }} UNION {{ ?artist dbpprop:name "{artist}"@en }} ?subject rdf:type . ?subject dbpprop:cover ?coverFilename . FILTER ( regex(?name, "{album}", "i") ) }} Limit 1""" def get(self, album, plugin, paths): if not (album.albumartist and album.album): return # Find the name of the cover art filename on DBpedia cover_filename, page_id = None, None try: dbpedia_response = self.request( self.DBPEDIA_URL, params={ "format": "application/sparql-results+json", "timeout": 2500, "query": self.SPARQL_QUERY.format( artist=album.albumartist.title(), album=album.album ), }, headers={"content-type": "application/json"}, ) except requests.RequestException: self._log.debug("dbpedia: error receiving response") return try: data = dbpedia_response.json() results = data["results"]["bindings"] if results: cover_filename = "File:" + results[0]["coverFilename"]["value"] page_id = results[0]["pageId"]["value"] else: self._log.debug("wikipedia: album not found on dbpedia") except (ValueError, KeyError, IndexError): self._log.debug( "wikipedia: error scraping dbpedia response: {}", dbpedia_response.text, ) # Ensure we have a filename before attempting to query wikipedia if not (cover_filename and page_id): return # DBPedia sometimes provides an incomplete cover_filename, indicated # by the filename having a space before the extension, e.g., 'foo .bar' # An additional Wikipedia call can help to find the real filename. # This may be removed once the DBPedia issue is resolved, see: # https://github.com/dbpedia/extraction-framework/issues/396 if " ." in cover_filename and "." not in cover_filename.split(" .")[-1]: self._log.debug( "wikipedia: dbpedia provided incomplete cover_filename" ) lpart, rpart = cover_filename.rsplit(" .", 1) # Query all the images in the page try: wikipedia_response = self.request( self.WIKIPEDIA_URL, params={ "format": "json", "action": "query", "continue": "", "prop": "images", "pageids": page_id, }, headers={"content-type": "application/json"}, ) except requests.RequestException: self._log.debug("wikipedia: error receiving response") return # Try to see if one of the images on the pages matches our # incomplete cover_filename try: data = wikipedia_response.json() results = data["query"]["pages"][page_id]["images"] for result in results: if re.match( re.escape(lpart) + r".*?\." + re.escape(rpart), result["title"], ): cover_filename = result["title"] break except (ValueError, KeyError): self._log.debug( "wikipedia: failed to retrieve a cover_filename" ) return # Find the absolute url of the cover art on Wikipedia try: wikipedia_response = self.request( self.WIKIPEDIA_URL, params={ "format": "json", "action": "query", "continue": "", "prop": "imageinfo", "iiprop": "url", "titles": cover_filename.encode("utf-8"), }, headers={"content-type": "application/json"}, ) except requests.RequestException: self._log.debug("wikipedia: error receiving response") return try: data = wikipedia_response.json() results = data["query"]["pages"] for _, result in results.items(): image_url = result["imageinfo"][0]["url"] yield self._candidate( url=image_url, match=Candidate.MATCH_EXACT ) except (ValueError, KeyError, IndexError): self._log.debug("wikipedia: error scraping imageinfo") return class FileSystem(LocalArtSource): NAME = "Filesystem" @staticmethod def filename_priority(filename, cover_names): """Sort order for image names. Return indexes of cover names found in the image filename. This means that images with lower-numbered and more keywords will have higher priority. """ return [idx for (idx, x) in enumerate(cover_names) if x in filename] def get(self, album, plugin, paths): """Look for album art files in the specified directories.""" if not paths: return cover_names = list(map(util.bytestring_path, plugin.cover_names)) cover_names_str = b"|".join(cover_names) cover_pat = rb"".join([rb"(\b|_)(", cover_names_str, rb")(\b|_)"]) for path in paths: if not os.path.isdir(syspath(path)): continue # Find all files that look like images in the directory. images = [] ignore = config["ignore"].as_str_seq() ignore_hidden = config["ignore_hidden"].get(bool) for _, _, files in sorted_walk( path, ignore=ignore, ignore_hidden=ignore_hidden ): for fn in files: fn = bytestring_path(fn) for ext in IMAGE_EXTENSIONS: if fn.lower().endswith(b"." + ext) and os.path.isfile( syspath(os.path.join(path, fn)) ): images.append(fn) # Look for "preferred" filenames. images = sorted( images, key=lambda x: self.filename_priority(x, cover_names) ) remaining = [] for fn in images: if re.search(cover_pat, os.path.splitext(fn)[0], re.I): self._log.debug( "using well-named art file {0}", util.displayable_path(fn), ) yield self._candidate( path=os.path.join(path, fn), match=Candidate.MATCH_EXACT ) else: remaining.append(fn) # Fall back to any image in the folder. if remaining and not plugin.cautious: self._log.debug( "using fallback art file {0}", util.displayable_path(remaining[0]), ) yield self._candidate( path=os.path.join(path, remaining[0]), match=Candidate.MATCH_FALLBACK, ) class LastFM(RemoteArtSource): NAME = "Last.fm" # Sizes in priority order. SIZES = OrderedDict( [ ("mega", (300, 300)), ("extralarge", (300, 300)), ("large", (174, 174)), ("medium", (64, 64)), ("small", (34, 34)), ] ) API_URL = "https://ws.audioscrobbler.com/2.0" def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.key = (self._config["lastfm_key"].get(),) @staticmethod def add_default_config(config): config.add( { "lastfm_key": None, } ) config["lastfm_key"].redact = True @classmethod def available(cls, log, config): has_key = bool(config["lastfm_key"].get()) if not has_key: log.debug("lastfm: Disabling art source due to missing key") return has_key def get(self, album, plugin, paths): if not album.mb_albumid: return try: response = self.request( self.API_URL, params={ "method": "album.getinfo", "api_key": self.key, "mbid": album.mb_albumid, "format": "json", }, ) except requests.RequestException: self._log.debug("lastfm: error receiving response") return try: data = response.json() if "error" in data: if data["error"] == 6: self._log.debug( "lastfm: no results for {}", album.mb_albumid ) else: self._log.error( "lastfm: failed to get album info: {} ({})", data["message"], data["error"], ) else: images = { image["size"]: image["#text"] for image in data["album"]["image"] } # Provide candidates in order of size. for size in self.SIZES.keys(): if size in images: yield self._candidate( url=images[size], size=self.SIZES[size] ) except ValueError: self._log.debug( "lastfm: error loading response: {}".format(response.text) ) return class Spotify(RemoteArtSource): NAME = "Spotify" SPOTIFY_ALBUM_URL = "https://open.spotify.com/album/" @classmethod def available(cls, log, config): if not HAS_BEAUTIFUL_SOUP: log.debug( "To use Spotify as an album art source, " "you must install the beautifulsoup4 module. See " "the documentation for further details." ) return HAS_BEAUTIFUL_SOUP def get(self, album, plugin, paths): try: url = self.SPOTIFY_ALBUM_URL + album.items().get().spotify_album_id except AttributeError: self._log.debug("Fetchart: no Spotify album ID found") return try: response = requests.get(url, timeout=10) response.raise_for_status() except requests.RequestException as e: self._log.debug("Error: " + str(e)) return try: html = response.text soup = BeautifulSoup(html, "html.parser") image_url = soup.find("meta", attrs={"property": "og:image"})[ "content" ] yield self._candidate(url=image_url, match=Candidate.MATCH_EXACT) except ValueError: self._log.debug( "Spotify: error loading response: {}".format(response.text) ) return class CoverArtUrl(RemoteArtSource): # This source is intended to be used with a plugin that sets the # cover_art_url field on albums or tracks. Users can also manually update # the cover_art_url field using the "set" command. This source will then # use that URL to fetch the image. NAME = "Cover Art URL" def get(self, album, plugin, paths): image_url = None try: # look for cover_art_url on album or first track if album.get("cover_art_url"): image_url = album.cover_art_url else: image_url = album.items().get().cover_art_url self._log.debug(f"Cover art URL {image_url} found for {album}") except (AttributeError, TypeError): self._log.debug(f"Cover art URL not found for {album}") return if image_url: yield self._candidate(url=image_url, match=Candidate.MATCH_EXACT) else: self._log.debug(f"Cover art URL not found for {album}") return # Try each source in turn. # Note that SOURCES_ALL is redundant (and presently unused). However, we keep # it around nn order not break plugins that "register" (a.k.a. monkey-patch) # their own fetchart sources. SOURCES_ALL = [ "filesystem", "coverart", "itunes", "amazon", "albumart", "wikipedia", "google", "fanarttv", "lastfm", "spotify", ] ART_SOURCES = { "filesystem": FileSystem, "coverart": CoverArtArchive, "itunes": ITunesStore, "albumart": AlbumArtOrg, "amazon": Amazon, "wikipedia": Wikipedia, "google": GoogleImages, "fanarttv": FanartTV, "lastfm": LastFM, "spotify": Spotify, "cover_art_url": CoverArtUrl, } SOURCE_NAMES = {v: k for k, v in ART_SOURCES.items()} # PLUGIN LOGIC ############################################################### class FetchArtPlugin(plugins.BeetsPlugin, RequestMixin): PAT_PX = r"(0|[1-9][0-9]*)px" PAT_PERCENT = r"(100(\.00?)?|[1-9]?[0-9](\.[0-9]{1,2})?)%" def __init__(self): super().__init__() # Holds candidates corresponding to downloaded images between # fetching them and placing them in the filesystem. self.art_candidates = {} self.config.add( { "auto": True, "minwidth": 0, "maxwidth": 0, "quality": 0, "max_filesize": 0, "enforce_ratio": False, "cautious": False, "cover_names": ["cover", "front", "art", "album", "folder"], "sources": [ "filesystem", "coverart", "itunes", "amazon", "albumart", "cover_art_url", ], "store_source": False, "high_resolution": False, "deinterlace": False, "cover_format": None, } ) for source in ART_SOURCES.values(): source.add_default_config(self.config) self.minwidth = self.config["minwidth"].get(int) self.maxwidth = self.config["maxwidth"].get(int) self.max_filesize = self.config["max_filesize"].get(int) self.quality = self.config["quality"].get(int) # allow both pixel and percentage-based margin specifications self.enforce_ratio = self.config["enforce_ratio"].get( confuse.OneOf( [ bool, confuse.String(pattern=self.PAT_PX), confuse.String(pattern=self.PAT_PERCENT), ] ) ) self.margin_px = None self.margin_percent = None self.deinterlace = self.config["deinterlace"].get(bool) if type(self.enforce_ratio) is str: if self.enforce_ratio[-1] == "%": self.margin_percent = float(self.enforce_ratio[:-1]) / 100 elif self.enforce_ratio[-2:] == "px": self.margin_px = int(self.enforce_ratio[:-2]) else: # shouldn't happen raise confuse.ConfigValueError() self.enforce_ratio = True cover_names = self.config["cover_names"].as_str_seq() self.cover_names = list(map(util.bytestring_path, cover_names)) self.cautious = self.config["cautious"].get(bool) self.store_source = self.config["store_source"].get(bool) self.cover_format = self.config["cover_format"].get( confuse.Optional(str) ) if self.config["auto"]: # Enable two import hooks when fetching is enabled. self.import_stages = [self.fetch_art] self.register_listener("import_task_files", self.assign_art) available_sources = [ (s_name, c) for (s_name, s_cls) in ART_SOURCES.items() if s_cls.available(self._log, self.config) for c in s_cls.VALID_MATCHING_CRITERIA ] sources = plugins.sanitize_pairs( self.config["sources"].as_pairs(default_value="*"), available_sources, ) if "remote_priority" in self.config: self._log.warning( "The `fetch_art.remote_priority` configuration option has " "been deprecated. Instead, place `filesystem` at the end of " "your `sources` list." ) if self.config["remote_priority"].get(bool): fs = [] others = [] for s, c in sources: if s == "filesystem": fs.append((s, c)) else: others.append((s, c)) sources = others + fs self.sources = [ ART_SOURCES[s](self._log, self.config, match_by=[c]) for s, c in sources ] @staticmethod def _is_source_file_removal_enabled(): return config["import"]["delete"] or config["import"]["move"] # Asynchronous; after music is added to the library. def fetch_art(self, session, task): """Find art for the album being imported.""" if task.is_album: # Only fetch art for full albums. if task.album.artpath and os.path.isfile( syspath(task.album.artpath) ): # Album already has art (probably a re-import); skip it. return if task.choice_flag == importer.action.ASIS: # For as-is imports, don't search Web sources for art. local = True elif task.choice_flag in ( importer.action.APPLY, importer.action.RETAG, ): # Search everywhere for art. local = False else: # For any other choices (e.g., TRACKS), do nothing. return candidate = self.art_for_album(task.album, task.paths, local) if candidate: self.art_candidates[task] = candidate def _set_art(self, album, candidate, delete=False): album.set_art(candidate.path, delete) if self.store_source: # store the source of the chosen artwork in a flexible field self._log.debug( "Storing art_source for {0.albumartist} - {0.album}", album ) album.art_source = SOURCE_NAMES[type(candidate.source)] album.store() # Synchronous; after music files are put in place. def assign_art(self, session, task): """Place the discovered art in the filesystem.""" if task in self.art_candidates: candidate = self.art_candidates.pop(task) removal_enabled = FetchArtPlugin._is_source_file_removal_enabled() self._set_art(task.album, candidate, not removal_enabled) if removal_enabled: task.prune(candidate.path) # Manual album art fetching. def commands(self): cmd = ui.Subcommand("fetchart", help="download album art") cmd.parser.add_option( "-f", "--force", dest="force", action="store_true", default=False, help="re-download art when already present", ) cmd.parser.add_option( "-q", "--quiet", dest="quiet", action="store_true", default=False, help="quiet mode: do not output albums that already have artwork", ) def func(lib, opts, args): self.batch_fetch_art( lib, lib.albums(ui.decargs(args)), opts.force, opts.quiet ) cmd.func = func return [cmd] # Utilities converted from functions to methods on logging overhaul def art_for_album(self, album, paths, local_only=False): """Given an Album object, returns a path to downloaded art for the album (or None if no art is found). If `maxwidth`, then images are resized to this maximum pixel size. If `quality` then resized images are saved at the specified quality level. If `local_only`, then only local image files from the filesystem are returned; no network requests are made. """ out = None for source in self.sources: if source.IS_LOCAL or not local_only: self._log.debug( "trying source {0} for album {1.albumartist} - {1.album}", SOURCE_NAMES[type(source)], album, ) # URLs might be invalid at this point, or the image may not # fulfill the requirements for candidate in source.get(album, self, paths): source.fetch_image(candidate, self) if candidate.validate(self): out = candidate self._log.debug( "using {0.LOC_STR} image {1}".format( source, util.displayable_path(out.path) ) ) break # Remove temporary files for invalid candidates. source.cleanup(candidate) if out: break if out: out.resize(self) return out def batch_fetch_art(self, lib, albums, force, quiet): """Fetch album art for each of the albums. This implements the manual fetchart CLI command. """ for album in albums: if ( album.artpath and not force and os.path.isfile(syspath(album.artpath)) ): if not quiet: message = ui.colorize( "text_highlight_minor", "has album art" ) self._log.info("{0}: {1}", album, message) else: # In ordinary invocations, look for images on the # filesystem. When forcing, however, always go to the Web # sources. local_paths = None if force else [album.path] candidate = self.art_for_album(album, local_paths) if candidate: self._set_art(album, candidate) message = ui.colorize("text_success", "found album art") else: message = ui.colorize("text_error", "no art found") self._log.info("{0}: {1}", album, message) beetbox-beets-01f1faf/beetsplug/filefilter.py000066400000000000000000000055121472325477400214430ustar00rootroot00000000000000# This file is part of beets. # Copyright 2016, Malte Ried. # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the # "Software"), to deal in the Software without restriction, including # without limitation the rights to use, copy, modify, merge, publish, # distribute, sublicense, and/or sell copies of the Software, and to # permit persons to whom the Software is furnished to do so, subject to # the following conditions: # # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. """Filter imported files using a regular expression.""" import re from beets import config from beets.importer import SingletonImportTask from beets.plugins import BeetsPlugin from beets.util import bytestring_path class FileFilterPlugin(BeetsPlugin): def __init__(self): super().__init__() self.register_listener( "import_task_created", self.import_task_created_event ) self.config.add({"path": ".*"}) self.path_album_regex = self.path_singleton_regex = re.compile( bytestring_path(self.config["path"].get()) ) if "album_path" in self.config: self.path_album_regex = re.compile( bytestring_path(self.config["album_path"].get()) ) if "singleton_path" in self.config: self.path_singleton_regex = re.compile( bytestring_path(self.config["singleton_path"].get()) ) def import_task_created_event(self, session, task): if task.items and len(task.items) > 0: items_to_import = [] for item in task.items: if self.file_filter(item["path"]): items_to_import.append(item) if len(items_to_import) > 0: task.items = items_to_import else: # Returning an empty list of tasks from the handler # drops the task from the rest of the importer pipeline. return [] elif isinstance(task, SingletonImportTask): if not self.file_filter(task.item["path"]): return [] # If not filtered, return the original task unchanged. return [task] def file_filter(self, full_path): """Checks if the configured regular expressions allow the import of the file given in full_path. """ import_config = dict(config["import"]) full_path = bytestring_path(full_path) if "singletons" not in import_config or not import_config["singletons"]: # Album return self.path_album_regex.match(full_path) is not None else: # Singleton return self.path_singleton_regex.match(full_path) is not None beetbox-beets-01f1faf/beetsplug/fish.py000066400000000000000000000256301472325477400202520ustar00rootroot00000000000000# This file is part of beets. # Copyright 2015, winters jean-marie. # Copyright 2020, Justin Mayer # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the # "Software"), to deal in the Software without restriction, including # without limitation the rights to use, copy, modify, merge, publish, # distribute, sublicense, and/or sell copies of the Software, and to # permit persons to whom the Software is furnished to do so, subject to # the following conditions: # # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. """This plugin generates tab completions for Beets commands for the Fish shell , including completions for Beets commands, plugin commands, and option flags. Also generated are completions for all the album and track fields, suggesting for example `genre:` or `album:` when querying the Beets database. Completions for the *values* of those fields are not generated by default but can be added via the `-e` / `--extravalues` flag. For example: `beet fish -e genre -e albumartist` """ import os from operator import attrgetter from beets import library, ui from beets.plugins import BeetsPlugin from beets.ui import commands BL_NEED2 = """complete -c beet -n '__fish_beet_needs_command' {} {}\n""" BL_USE3 = """complete -c beet -n '__fish_beet_using_command {}' {} {}\n""" BL_SUBS = """complete -c beet -n '__fish_at_level {} ""' {} {}\n""" BL_EXTRA3 = """complete -c beet -n '__fish_beet_use_extra {}' {} {}\n""" HEAD = """ function __fish_beet_needs_command set cmd (commandline -opc) if test (count $cmd) -eq 1 return 0 end return 1 end function __fish_beet_using_command set cmd (commandline -opc) set needle (count $cmd) if test $needle -gt 1 if begin test $argv[1] = $cmd[2]; and not contains -- $cmd[$needle] $FIELDS; end return 0 end end return 1 end function __fish_beet_use_extra set cmd (commandline -opc) set needle (count $cmd) if test $argv[2] = $cmd[$needle] return 0 end return 1 end """ class FishPlugin(BeetsPlugin): def commands(self): cmd = ui.Subcommand("fish", help="generate Fish shell tab completions") cmd.func = self.run cmd.parser.add_option( "-f", "--noFields", action="store_true", default=False, help="omit album/track field completions", ) cmd.parser.add_option( "-e", "--extravalues", action="append", type="choice", choices=library.Item.all_keys() + library.Album.all_keys(), help="include specified field *values* in completions", ) cmd.parser.add_option( "-o", "--output", default="~/.config/fish/completions/beet.fish", help="where to save the script. default: " "~/.config/fish/completions", ) return [cmd] def run(self, lib, opts, args): # Gather the commands from Beets core and its plugins. # Collect the album and track fields. # If specified, also collect the values for these fields. # Make a giant string of all the above, formatted in a way that # allows Fish to do tab completion for the `beet` command. completion_file_path = os.path.expanduser(opts.output) completion_dir = os.path.dirname(completion_file_path) if completion_dir != "": os.makedirs(completion_dir, exist_ok=True) nobasicfields = opts.noFields # Do not complete for album/track fields extravalues = opts.extravalues # e.g., Also complete artists names beetcmds = sorted( (commands.default_commands + commands.plugins.commands()), key=attrgetter("name"), ) fields = sorted(set(library.Album.all_keys() + library.Item.all_keys())) # Collect commands, their aliases, and their help text cmd_names_help = [] for cmd in beetcmds: names = list(cmd.aliases) names.append(cmd.name) for name in names: cmd_names_help.append((name, cmd.help)) # Concatenate the string totstring = HEAD + "\n" totstring += get_cmds_list([name[0] for name in cmd_names_help]) totstring += "" if nobasicfields else get_standard_fields(fields) totstring += get_extravalues(lib, extravalues) if extravalues else "" totstring += ( "\n" + "# ====== {} =====".format("setup basic beet completion") + "\n" * 2 ) totstring += get_basic_beet_options() totstring += ( "\n" + "# ====== {} =====".format( "setup field completion for subcommands" ) + "\n" ) totstring += get_subcommands(cmd_names_help, nobasicfields, extravalues) # Set up completion for all the command options totstring += get_all_commands(beetcmds) with open(completion_file_path, "w") as fish_file: fish_file.write(totstring) def _escape(name): # Escape ? in fish if name == "?": name = "\\" + name return name def get_cmds_list(cmds_names): # Make a list of all Beets core & plugin commands substr = "" substr += "set CMDS " + " ".join(cmds_names) + ("\n" * 2) return substr def get_standard_fields(fields): # Make a list of album/track fields and append with ':' fields = (field + ":" for field in fields) substr = "" substr += "set FIELDS " + " ".join(fields) + ("\n" * 2) return substr def get_extravalues(lib, extravalues): # Make a list of all values from an album/track field. # 'beet ls albumartist: ' yields completions for ABBA, Beatles, etc. word = "" values_set = get_set_of_values_for_field(lib, extravalues) for fld in extravalues: extraname = fld.upper() + "S" word += ( "set " + extraname + " " + " ".join(sorted(values_set[fld])) + ("\n" * 2) ) return word def get_set_of_values_for_field(lib, fields): # Get unique values from a specified album/track field fields_dict = {} for each in fields: fields_dict[each] = set() for item in lib.items(): for field in fields: fields_dict[field].add(wrap(item[field])) return fields_dict def get_basic_beet_options(): word = ( BL_NEED2.format("-l format-item", "-f -d 'print with custom format'") + BL_NEED2.format("-l format-album", "-f -d 'print with custom format'") + BL_NEED2.format( "-s l -l library", "-f -r -d 'library database file to use'" ) + BL_NEED2.format( "-s d -l directory", "-f -r -d 'destination music directory'" ) + BL_NEED2.format( "-s v -l verbose", "-f -d 'print debugging information'" ) + BL_NEED2.format( "-s c -l config", "-f -r -d 'path to configuration file'" ) + BL_NEED2.format( "-s h -l help", "-f -d 'print this help message and exit'" ) ) return word def get_subcommands(cmd_name_and_help, nobasicfields, extravalues): # Formatting for Fish to complete our fields/values word = "" for cmdname, cmdhelp in cmd_name_and_help: cmdname = _escape(cmdname) word += ( "\n" + "# ------ {} -------".format("fieldsetups for " + cmdname) + "\n" ) word += BL_NEED2.format( ("-a " + cmdname), ("-f " + "-d " + wrap(clean_whitespace(cmdhelp))) ) if nobasicfields is False: word += BL_USE3.format( cmdname, ("-a " + wrap("$FIELDS")), ("-f " + "-d " + wrap("fieldname")), ) if extravalues: for f in extravalues: setvar = wrap("$" + f.upper() + "S") word += ( " ".join( BL_EXTRA3.format( (cmdname + " " + f + ":"), ("-f " + "-A " + "-a " + setvar), ("-d " + wrap(f)), ).split() ) + "\n" ) return word def get_all_commands(beetcmds): # Formatting for Fish to complete command options word = "" for cmd in beetcmds: names = list(cmd.aliases) names.append(cmd.name) for name in names: name = _escape(name) word += "\n" word += ( ("\n" * 2) + "# ====== {} =====".format("completions for " + name) + "\n" ) for option in cmd.parser._get_all_options()[1:]: cmd_l = ( (" -l " + option._long_opts[0].replace("--", "")) if option._long_opts else "" ) cmd_s = ( (" -s " + option._short_opts[0].replace("-", "")) if option._short_opts else "" ) cmd_need_arg = " -r " if option.nargs in [1] else "" cmd_helpstr = ( (" -d " + wrap(" ".join(option.help.split()))) if option.help else "" ) cmd_arglist = ( (" -a " + wrap(" ".join(option.choices))) if option.choices else "" ) word += ( " ".join( BL_USE3.format( name, ( cmd_need_arg + cmd_s + cmd_l + " -f " + cmd_arglist ), cmd_helpstr, ).split() ) + "\n" ) word = word + " ".join( BL_USE3.format( name, ("-s " + "h " + "-l " + "help" + " -f "), ("-d " + wrap("print help") + "\n"), ).split() ) return word def clean_whitespace(word): # Remove excess whitespace and tabs in a string return " ".join(word.split()) def wrap(word): # Need " or ' around strings but watch out if they're in the string sptoken = '"' if ('"') in word and ("'") in word: word.replace('"', sptoken) return '"' + word + '"' tok = '"' if "'" in word else "'" return tok + word + tok beetbox-beets-01f1faf/beetsplug/freedesktop.py000066400000000000000000000026061472325477400216320ustar00rootroot00000000000000# This file is part of beets. # Copyright 2016, Matt Lichtenberg. # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the # "Software"), to deal in the Software without restriction, including # without limitation the rights to use, copy, modify, merge, publish, # distribute, sublicense, and/or sell copies of the Software, and to # permit persons to whom the Software is furnished to do so, subject to # the following conditions: # # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. """Creates freedesktop.org-compliant .directory files on an album level.""" from beets import ui from beets.plugins import BeetsPlugin class FreedesktopPlugin(BeetsPlugin): def commands(self): deprecated = ui.Subcommand( "freedesktop", help="Print a message to redirect to thumbnails --dolphin", ) deprecated.func = self.deprecation_message return [deprecated] def deprecation_message(self, lib, opts, args): ui.print_( "This plugin is deprecated. Its functionality is " "superseded by the 'thumbnails' plugin" ) ui.print_( "'thumbnails --dolphin' replaces freedesktop. See doc & " "changelog for more information" ) beetbox-beets-01f1faf/beetsplug/fromfilename.py000066400000000000000000000127161472325477400217660ustar00rootroot00000000000000# This file is part of beets. # Copyright 2016, Jan-Erik Dahlin # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the # "Software"), to deal in the Software without restriction, including # without limitation the rights to use, copy, modify, merge, publish, # distribute, sublicense, and/or sell copies of the Software, and to # permit persons to whom the Software is furnished to do so, subject to # the following conditions: # # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. """If the title is empty, try to extract track and title from the filename. """ import os import re from beets import plugins from beets.util import displayable_path # Filename field extraction patterns. PATTERNS = [ # Useful patterns. r"^(?P.+)[\-_](?P.+)[\-_](?P<tag>.*)$", r"^(?P<track>\d+)[\s.\-_]+(?P<artist>.+)[\-_](?P<title>.+)[\-_](?P<tag>.*)$", r"^(?P<artist>.+)[\-_](?P<title>.+)$", r"^(?P<track>\d+)[\s.\-_]+(?P<artist>.+)[\-_](?P<title>.+)$", r"^(?P<track>\d+)[\s.\-_]+(?P<title>.+)$", r"^(?P<track>\d+)\s+(?P<title>.+)$", r"^(?P<title>.+) by (?P<artist>.+)$", r"^(?P<track>\d+).*$", r"^(?P<title>.+)$", ] # Titles considered "empty" and in need of replacement. BAD_TITLE_PATTERNS = [ r"^$", ] def equal(seq): """Determine whether a sequence holds identical elements.""" return len(set(seq)) <= 1 def equal_fields(matchdict, field): """Do all items in `matchdict`, whose values are dictionaries, have the same value for `field`? (If they do, the field is probably not the title.) """ return equal(m[field] for m in matchdict.values()) def all_matches(names, pattern): """If all the filenames in the item/filename mapping match the pattern, return a dictionary mapping the items to dictionaries giving the value for each named subpattern in the match. Otherwise, return None. """ matches = {} for item, name in names.items(): m = re.match(pattern, name, re.IGNORECASE) if m and m.groupdict(): # Only yield a match when the regex applies *and* has # capture groups. Otherwise, no information can be extracted # from the filename. matches[item] = m.groupdict() else: return None return matches def bad_title(title): """Determine whether a given title is "bad" (empty or otherwise meaningless) and in need of replacement. """ for pat in BAD_TITLE_PATTERNS: if re.match(pat, title, re.IGNORECASE): return True return False def apply_matches(d, log): """Given a mapping from items to field dicts, apply the fields to the objects. """ some_map = list(d.values())[0] keys = some_map.keys() # Only proceed if the "tag" field is equal across all filenames. if "tag" in keys and not equal_fields(d, "tag"): return # Given both an "artist" and "title" field, assume that one is # *actually* the artist, which must be uniform, and use the other # for the title. This, of course, won't work for VA albums. if "artist" in keys: if equal_fields(d, "artist"): artist = some_map["artist"] title_field = "title" elif equal_fields(d, "title"): artist = some_map["title"] title_field = "artist" else: # Both vary. Abort. return for item in d: if not item.artist: item.artist = artist log.info("Artist replaced with: {}".format(item.artist)) # No artist field: remaining field is the title. else: title_field = "title" # Apply the title and track. for item in d: if bad_title(item.title): item.title = str(d[item][title_field]) log.info("Title replaced with: {}".format(item.title)) if "track" in d[item] and item.track == 0: item.track = int(d[item]["track"]) log.info("Track replaced with: {}".format(item.track)) # Plugin structure and hook into import process. class FromFilenamePlugin(plugins.BeetsPlugin): def __init__(self): super().__init__() self.register_listener("import_task_start", self.filename_task) def filename_task(self, task, session): """Examine each item in the task to see if we can extract a title from the filename. Try to match all filenames to a number of regexps, starting with the most complex patterns and successively trying less complex patterns. As soon as all filenames match the same regex we can make an educated guess of which part of the regex that contains the title. """ items = task.items if task.is_album else [task.item] # Look for suspicious (empty or meaningless) titles. missing_titles = sum(bad_title(i.title) for i in items) if missing_titles: # Get the base filenames (no path or extension). names = {} for item in items: path = displayable_path(item.path) name, _ = os.path.splitext(os.path.basename(path)) names[item] = name # Look for useful information in the filenames. for pattern in PATTERNS: d = all_matches(names, pattern) if d: apply_matches(d, self._log) ��������������������������������������������������beetbox-beets-01f1faf/beetsplug/ftintitle.py��������������������������������������������������������0000664�0000000�0000000�00000015055�14723254774�0021323�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# This file is part of beets. # Copyright 2016, Verrus, <github.com/Verrus/beets-plugin-featInTitle> # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the # "Software"), to deal in the Software without restriction, including # without limitation the rights to use, copy, modify, merge, publish, # distribute, sublicense, and/or sell copies of the Software, and to # permit persons to whom the Software is furnished to do so, subject to # the following conditions: # # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. """Moves "featured" artists to the title from the artist field.""" import re from beets import plugins, ui from beets.util import displayable_path def split_on_feat(artist): """Given an artist string, split the "main" artist from any artist on the right-hand side of a string like "feat". Return the main artist, which is always a string, and the featuring artist, which may be a string or None if none is present. """ # split on the first "feat". regex = re.compile(plugins.feat_tokens(), re.IGNORECASE) parts = [s.strip() for s in regex.split(artist, 1)] if len(parts) == 1: return parts[0], None else: return tuple(parts) def contains_feat(title): """Determine whether the title contains a "featured" marker.""" return bool( re.search( plugins.feat_tokens(for_artist=False), title, flags=re.IGNORECASE, ) ) def find_feat_part(artist, albumartist): """Attempt to find featured artists in the item's artist fields and return the results. Returns None if no featured artist found. """ # Look for the album artist in the artist field. If it's not # present, give up. albumartist_split = artist.split(albumartist, 1) if len(albumartist_split) <= 1: return None # If the last element of the split (the right-hand side of the # album artist) is nonempty, then it probably contains the # featured artist. elif albumartist_split[1] != "": # Extract the featured artist from the right-hand side. _, feat_part = split_on_feat(albumartist_split[1]) return feat_part # Otherwise, if there's nothing on the right-hand side, look for a # featuring artist on the left-hand side. else: lhs, rhs = split_on_feat(albumartist_split[0]) if lhs: return lhs return None class FtInTitlePlugin(plugins.BeetsPlugin): def __init__(self): super().__init__() self.config.add( { "auto": True, "drop": False, "format": "feat. {0}", "keep_in_artist": False, } ) self._command = ui.Subcommand( "ftintitle", help="move featured artists to the title field" ) self._command.parser.add_option( "-d", "--drop", dest="drop", action="store_true", default=None, help="drop featuring from artists and ignore title update", ) if self.config["auto"]: self.import_stages = [self.imported] def commands(self): def func(lib, opts, args): self.config.set_args(opts) drop_feat = self.config["drop"].get(bool) keep_in_artist_field = self.config["keep_in_artist"].get(bool) write = ui.should_write() for item in lib.items(ui.decargs(args)): self.ft_in_title(item, drop_feat, keep_in_artist_field) item.store() if write: item.try_write() self._command.func = func return [self._command] def imported(self, session, task): """Import hook for moving featuring artist automatically.""" drop_feat = self.config["drop"].get(bool) keep_in_artist_field = self.config["keep_in_artist"].get(bool) for item in task.imported_items(): self.ft_in_title(item, drop_feat, keep_in_artist_field) item.store() def update_metadata(self, item, feat_part, drop_feat, keep_in_artist_field): """Choose how to add new artists to the title and set the new metadata. Also, print out messages about any changes that are made. If `drop_feat` is set, then do not add the artist to the title; just remove it from the artist field. """ # In case the artist is kept, do not update the artist fields. if keep_in_artist_field: self._log.info( "artist: {0} (Not changing due to keep_in_artist)", item.artist ) else: self._log.info("artist: {0} -> {1}", item.artist, item.albumartist) item.artist = item.albumartist if item.artist_sort: # Just strip the featured artist from the sort name. item.artist_sort, _ = split_on_feat(item.artist_sort) # Only update the title if it does not already contain a featured # artist and if we do not drop featuring information. if not drop_feat and not contains_feat(item.title): feat_format = self.config["format"].as_str() new_format = feat_format.format(feat_part) new_title = f"{item.title} {new_format}" self._log.info("title: {0} -> {1}", item.title, new_title) item.title = new_title def ft_in_title(self, item, drop_feat, keep_in_artist_field): """Look for featured artists in the item's artist fields and move them to the title. """ artist = item.artist.strip() albumartist = item.albumartist.strip() # Check whether there is a featured artist on this track and the # artist field does not exactly match the album artist field. In # that case, we attempt to move the featured artist to the title. _, featured = split_on_feat(artist) if featured and albumartist != artist and albumartist: self._log.info("{}", displayable_path(item.path)) feat_part = None # Attempt to find the featured artist. feat_part = find_feat_part(artist, albumartist) # If we have a featuring artist, move it to the title. if feat_part: self.update_metadata( item, feat_part, drop_feat, keep_in_artist_field ) else: self._log.info("no featuring artists found") �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������beetbox-beets-01f1faf/beetsplug/fuzzy.py������������������������������������������������������������0000664�0000000�0000000�00000002751�14723254774�0020507�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# This file is part of beets. # Copyright 2016, Philippe Mongeau. # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the # "Software"), to deal in the Software without restriction, including # without limitation the rights to use, copy, modify, merge, publish, # distribute, sublicense, and/or sell copies of the Software, and to # permit persons to whom the Software is furnished to do so, subject to # the following conditions: # # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. """Provides a fuzzy matching query.""" import difflib from beets import config from beets.dbcore.query import StringFieldQuery from beets.plugins import BeetsPlugin class FuzzyQuery(StringFieldQuery[str]): @classmethod def string_match(cls, pattern: str, val: str): # smartcase if pattern.islower(): val = val.lower() query_matcher = difflib.SequenceMatcher(None, pattern, val) threshold = config["fuzzy"]["threshold"].as_number() return query_matcher.quick_ratio() >= threshold class FuzzyPlugin(BeetsPlugin): def __init__(self): super().__init__() self.config.add( { "prefix": "~", "threshold": 0.7, } ) def queries(self): prefix = self.config["prefix"].as_str() return {prefix: FuzzyQuery} �����������������������beetbox-beets-01f1faf/beetsplug/gmusic.py�����������������������������������������������������������0000664�0000000�0000000�00000001774�14723254774�0020613�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# This file is part of beets. # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the # "Software"), to deal in the Software without restriction, including # without limitation the rights to use, copy, modify, merge, publish, # distribute, sublicense, and/or sell copies of the Software, and to # permit persons to whom the Software is furnished to do so, subject to # the following conditions: # # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. """Deprecation warning for the removed gmusic plugin.""" from beets.plugins import BeetsPlugin class Gmusic(BeetsPlugin): def __init__(self): super().__init__() self._log.warning( "The 'gmusic' plugin has been removed following the" " shutdown of Google Play Music. Remove the plugin" " from your configuration to silence this warning." ) ����beetbox-beets-01f1faf/beetsplug/hook.py�������������������������������������������������������������0000664�0000000�0000000�00000006326�14723254774�0020262�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# This file is part of beets. # Copyright 2015, Adrian Sampson. # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the # "Software"), to deal in the Software without restriction, including # without limitation the rights to use, copy, modify, merge, publish, # distribute, sublicense, and/or sell copies of the Software, and to # permit persons to whom the Software is furnished to do so, subject to # the following conditions: # # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. """Allows custom commands to be run when an event is emitted by beets""" import shlex import string import subprocess from beets.plugins import BeetsPlugin from beets.util import arg_encoding class CodingFormatter(string.Formatter): """A variant of `string.Formatter` that converts everything to `unicode` strings. This was necessary on Python 2, in needs to be kept for backwards compatibility. """ def __init__(self, coding): """Creates a new coding formatter with the provided coding.""" self._coding = coding def convert_field(self, value, conversion): """Converts the provided value given a conversion type. This method decodes the converted value using the formatter's coding. """ converted = super().convert_field(value, conversion) if isinstance(converted, bytes): return converted.decode(self._coding) return converted class HookPlugin(BeetsPlugin): """Allows custom commands to be run when an event is emitted by beets""" def __init__(self): super().__init__() self.config.add({"hooks": []}) hooks = self.config["hooks"].get(list) for hook_index in range(len(hooks)): hook = self.config["hooks"][hook_index] hook_event = hook["event"].as_str() hook_command = hook["command"].as_str() self.create_and_register_hook(hook_event, hook_command) def create_and_register_hook(self, event, command): def hook_function(**kwargs): if command is None or len(command) == 0: self._log.error('invalid command "{0}"', command) return # For backwards compatibility, use a string formatter that decodes # bytes (in particular, paths) to unicode strings. formatter = CodingFormatter(arg_encoding()) command_pieces = [ formatter.format(piece, event=event, **kwargs) for piece in shlex.split(command) ] self._log.debug( 'running command "{0}" for event {1}', " ".join(command_pieces), event, ) try: subprocess.check_call(command_pieces) except subprocess.CalledProcessError as exc: self._log.error( "hook for {0} exited with status {1}", event, exc.returncode ) except OSError as exc: self._log.error("hook for {0} failed: {1}", event, exc) self.register_listener(event, hook_function) ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������beetbox-beets-01f1faf/beetsplug/ihate.py������������������������������������������������������������0000664�0000000�0000000�00000005443�14723254774�0020413�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# This file is part of beets. # Copyright 2016, Blemjhoo Tezoulbr <baobab@heresiarch.info>. # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the # "Software"), to deal in the Software without restriction, including # without limitation the rights to use, copy, modify, merge, publish, # distribute, sublicense, and/or sell copies of the Software, and to # permit persons to whom the Software is furnished to do so, subject to # the following conditions: # # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. """Warns you about things you hate (or even blocks import).""" from beets.importer import action from beets.library import Album, Item, parse_query_string from beets.plugins import BeetsPlugin __author__ = "baobab@heresiarch.info" __version__ = "2.0" def summary(task): """Given an ImportTask, produce a short string identifying the object. """ if task.is_album: return f"{task.cur_artist} - {task.cur_album}" else: return f"{task.item.artist} - {task.item.title}" class IHatePlugin(BeetsPlugin): def __init__(self): super().__init__() self.register_listener( "import_task_choice", self.import_task_choice_event ) self.config.add( { "warn": [], "skip": [], } ) @classmethod def do_i_hate_this(cls, task, action_patterns): """Process group of patterns (warn or skip) and returns True if task is hated and not whitelisted. """ if action_patterns: for query_string in action_patterns: query, _ = parse_query_string( query_string, Album if task.is_album else Item, ) if any(query.match(item) for item in task.imported_items()): return True return False def import_task_choice_event(self, session, task): skip_queries = self.config["skip"].as_str_seq() warn_queries = self.config["warn"].as_str_seq() if task.choice_flag == action.APPLY: if skip_queries or warn_queries: self._log.debug("processing your hate") if self.do_i_hate_this(task, skip_queries): task.choice_flag = action.SKIP self._log.info("skipped: {0}", summary(task)) return if self.do_i_hate_this(task, warn_queries): self._log.info("you may hate this: {0}", summary(task)) else: self._log.debug("nothing to do") else: self._log.debug("user made a decision, nothing to do") �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������beetbox-beets-01f1faf/beetsplug/importadded.py������������������������������������������������������0000664�0000000�0000000�00000013404�14723254774�0021611�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������"""Populate an item's `added` and `mtime` fields by using the file modification time (mtime) of the item's source file before import. Reimported albums and items are skipped. """ import os from beets import importer, util from beets.plugins import BeetsPlugin class ImportAddedPlugin(BeetsPlugin): def __init__(self): super().__init__() self.config.add( { "preserve_mtimes": False, "preserve_write_mtimes": False, } ) # item.id for new items that were reimported self.reimported_item_ids = None # album.path for old albums that were replaced by a reimported album self.replaced_album_paths = None # item path in the library to the mtime of the source file self.item_mtime = {} register = self.register_listener register("import_task_created", self.check_config) register("import_task_created", self.record_if_inplace) register("import_task_files", self.record_reimported) register("before_item_moved", self.record_import_mtime) register("item_copied", self.record_import_mtime) register("item_linked", self.record_import_mtime) register("item_hardlinked", self.record_import_mtime) register("item_reflinked", self.record_import_mtime) register("album_imported", self.update_album_times) register("item_imported", self.update_item_times) register("after_write", self.update_after_write_time) def check_config(self, task, session): self.config["preserve_mtimes"].get(bool) def reimported_item(self, item): return item.id in self.reimported_item_ids def reimported_album(self, album): return album.path in self.replaced_album_paths def record_if_inplace(self, task, session): if not ( session.config["copy"] or session.config["move"] or session.config["link"] or session.config["hardlink"] or session.config["reflink"] ): self._log.debug( "In place import detected, recording mtimes from " "source paths" ) items = ( [task.item] if isinstance(task, importer.SingletonImportTask) else task.items ) for item in items: self.record_import_mtime(item, item.path, item.path) def record_reimported(self, task, session): self.reimported_item_ids = { item.id for item, replaced_items in task.replaced_items.items() if replaced_items } self.replaced_album_paths = set(task.replaced_albums.keys()) def write_file_mtime(self, path, mtime): """Write the given mtime to the destination path.""" stat = os.stat(util.syspath(path)) os.utime(util.syspath(path), (stat.st_atime, mtime)) def write_item_mtime(self, item, mtime): """Write the given mtime to an item's `mtime` field and to the mtime of the item's file. """ # The file's mtime on disk must be in sync with the item's mtime self.write_file_mtime(util.syspath(item.path), mtime) item.mtime = mtime def record_import_mtime(self, item, source, destination): """Record the file mtime of an item's path before its import.""" mtime = os.stat(util.syspath(source)).st_mtime self.item_mtime[destination] = mtime self._log.debug( "Recorded mtime {0} for item '{1}' imported from " "'{2}'", mtime, util.displayable_path(destination), util.displayable_path(source), ) def update_album_times(self, lib, album): if self.reimported_album(album): self._log.debug( "Album '{0}' is reimported, skipping import of " "added dates for the album and its items.", util.displayable_path(album.path), ) return album_mtimes = [] for item in album.items(): mtime = self.item_mtime.pop(item.path, None) if mtime: album_mtimes.append(mtime) if self.config["preserve_mtimes"].get(bool): self.write_item_mtime(item, mtime) item.store() album.added = min(album_mtimes) self._log.debug( "Import of album '{0}', selected album.added={1} " "from item file mtimes.", album.album, album.added, ) album.store() def update_item_times(self, lib, item): if self.reimported_item(item): self._log.debug( "Item '{0}' is reimported, skipping import of " "added date.", util.displayable_path(item.path), ) return mtime = self.item_mtime.pop(item.path, None) if mtime: item.added = mtime if self.config["preserve_mtimes"].get(bool): self.write_item_mtime(item, mtime) self._log.debug( "Import of item '{0}', selected item.added={1}", util.displayable_path(item.path), item.added, ) item.store() def update_after_write_time(self, item, path): """Update the mtime of the item's file with the item.added value after each write of the item if `preserve_write_mtimes` is enabled. """ if item.added: if self.config["preserve_write_mtimes"].get(bool): self.write_item_mtime(item, item.added) self._log.debug( "Write of item '{0}', selected item.added={1}", util.displayable_path(item.path), item.added, ) ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������beetbox-beets-01f1faf/beetsplug/importfeeds.py������������������������������������������������������0000664�0000000�0000000�00000012264�14723254774�0021641�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# This file is part of beets. # Copyright 2016, Fabrice Laporte. # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the # "Software"), to deal in the Software without restriction, including # without limitation the rights to use, copy, modify, merge, publish, # distribute, sublicense, and/or sell copies of the Software, and to # permit persons to whom the Software is furnished to do so, subject to # the following conditions: # # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. """Write paths of imported files in various formats to ease later import in a music player. Also allow printing the new file locations to stdout in case one wants to manually add music to a player by its path. """ import datetime import os import re from beets import config from beets.plugins import BeetsPlugin from beets.util import bytestring_path, link, mkdirall, normpath, syspath M3U_DEFAULT_NAME = "imported.m3u" def _build_m3u_session_filename(basename): """Builds unique m3u filename by putting current date between given basename and file ending.""" date = datetime.datetime.now().strftime("%Y%m%d_%Hh%M") basename = re.sub(r"(\.m3u|\.M3U)", "", basename) path = normpath( os.path.join( config["importfeeds"]["dir"].as_filename(), f"{basename}_{date}.m3u" ) ) return path def _build_m3u_filename(basename): """Builds unique m3u filename by appending given basename to current date.""" basename = re.sub(r"[\s,/\\'\"]", "_", basename) date = datetime.datetime.now().strftime("%Y%m%d_%Hh%M") path = normpath( os.path.join( config["importfeeds"]["dir"].as_filename(), date + "_" + basename + ".m3u", ) ) return path def _write_m3u(m3u_path, items_paths): """Append relative paths to items into m3u file.""" mkdirall(m3u_path) with open(syspath(m3u_path), "ab") as f: for path in items_paths: f.write(path + b"\n") class ImportFeedsPlugin(BeetsPlugin): def __init__(self): super().__init__() self.config.add( { "formats": [], "m3u_name": "imported.m3u", "dir": None, "relative_to": None, "absolute_path": False, } ) relative_to = self.config["relative_to"].get() if relative_to: self.config["relative_to"] = normpath(relative_to) else: self.config["relative_to"] = self.get_feeds_dir() self.register_listener("album_imported", self.album_imported) self.register_listener("item_imported", self.item_imported) self.register_listener("import_begin", self.import_begin) def get_feeds_dir(self): feeds_dir = self.config["dir"].get() if feeds_dir: return os.path.expanduser(bytestring_path(feeds_dir)) return config["directory"].as_filename() def _record_items(self, lib, basename, items): """Records relative paths to the given items for each feed format""" feedsdir = bytestring_path(self.get_feeds_dir()) formats = self.config["formats"].as_str_seq() relative_to = self.config["relative_to"].get() or self.get_feeds_dir() relative_to = bytestring_path(relative_to) paths = [] for item in items: if self.config["absolute_path"]: paths.append(item.path) else: try: relpath = os.path.relpath(item.path, relative_to) except ValueError: # On Windows, it is sometimes not possible to construct a # relative path (if the files are on different disks). relpath = item.path paths.append(relpath) if "m3u" in formats: m3u_basename = bytestring_path(self.config["m3u_name"].as_str()) m3u_path = os.path.join(feedsdir, m3u_basename) _write_m3u(m3u_path, paths) if "m3u_session" in formats: m3u_path = os.path.join(feedsdir, self.m3u_session) _write_m3u(m3u_path, paths) if "m3u_multi" in formats: m3u_path = _build_m3u_filename(basename) _write_m3u(m3u_path, paths) if "link" in formats: for path in paths: dest = os.path.join(feedsdir, os.path.basename(path)) if not os.path.exists(syspath(dest)): link(path, dest) if "echo" in formats: self._log.info("Location of imported music:") for path in paths: self._log.info(" {0}", path) def album_imported(self, lib, album): self._record_items(lib, album.album, album.items()) def item_imported(self, lib, item): self._record_items(lib, item.title, [item]) def import_begin(self, session): formats = self.config["formats"].as_str_seq() if "m3u_session" in formats: self.m3u_session = _build_m3u_session_filename( self.config["m3u_name"].as_str() ) ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������beetbox-beets-01f1faf/beetsplug/info.py�������������������������������������������������������������0000664�0000000�0000000�00000016056�14723254774�0020256�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# This file is part of beets. # Copyright 2016, Adrian Sampson. # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the # "Software"), to deal in the Software without restriction, including # without limitation the rights to use, copy, modify, merge, publish, # distribute, sublicense, and/or sell copies of the Software, and to # permit persons to whom the Software is furnished to do so, subject to # the following conditions: # # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. """Shows file metadata.""" import os import mediafile from beets import ui from beets.library import Item from beets.plugins import BeetsPlugin from beets.util import displayable_path, normpath, syspath def tag_data(lib, args, album=False): query = [] for arg in args: path = normpath(arg) if os.path.isfile(syspath(path)): yield tag_data_emitter(path) else: query.append(arg) if query: for item in lib.items(query): yield tag_data_emitter(item.path) def tag_fields(): fields = set(mediafile.MediaFile.readable_fields()) fields.add("art") return fields def tag_data_emitter(path): def emitter(included_keys): if included_keys == "*": fields = tag_fields() else: fields = included_keys if "images" in fields: # We can't serialize the image data. fields.remove("images") mf = mediafile.MediaFile(syspath(path)) tags = {} for field in fields: if field == "art": tags[field] = mf.art is not None else: tags[field] = getattr(mf, field, None) # create a temporary Item to take advantage of __format__ item = Item.from_path(syspath(path)) return tags, item return emitter def library_data(lib, args, album=False): for item in lib.albums(args) if album else lib.items(args): yield library_data_emitter(item) def library_data_emitter(item): def emitter(included_keys): data = dict(item.formatted(included_keys=included_keys)) return data, item return emitter def update_summary(summary, tags): for key, value in tags.items(): if key not in summary: summary[key] = value elif summary[key] != value: summary[key] = "[various]" return summary def print_data(data, item=None, fmt=None): """Print, with optional formatting, the fields of a single element. If no format string `fmt` is passed, the entries on `data` are printed one in each line, with the format 'field: value'. If `fmt` is not `None`, the `item` is printed according to `fmt`, using the `Item.__format__` machinery. """ if fmt: # use fmt specified by the user ui.print_(format(item, fmt)) return path = displayable_path(item.path) if item else None formatted = {} for key, value in data.items(): if isinstance(value, list): formatted[key] = "; ".join(value) if value is not None: formatted[key] = value if len(formatted) == 0: return maxwidth = max(len(key) for key in formatted) lineformat = f"{{0:>{maxwidth}}}: {{1}}" if path: ui.print_(displayable_path(path)) for field in sorted(formatted): value = formatted[field] if isinstance(value, list): value = "; ".join(value) ui.print_(lineformat.format(field, value)) def print_data_keys(data, item=None): """Print only the keys (field names) for an item.""" path = displayable_path(item.path) if item else None formatted = [] for key, value in data.items(): formatted.append(key) if len(formatted) == 0: return line_format = "{0}{{0}}".format(" " * 4) if path: ui.print_(displayable_path(path)) for field in sorted(formatted): ui.print_(line_format.format(field)) class InfoPlugin(BeetsPlugin): def commands(self): cmd = ui.Subcommand("info", help="show file metadata") cmd.func = self.run cmd.parser.add_option( "-l", "--library", action="store_true", help="show library fields instead of tags", ) cmd.parser.add_option( "-a", "--album", action="store_true", help='show album fields instead of tracks (implies "--library")', ) cmd.parser.add_option( "-s", "--summarize", action="store_true", help="summarize the tags of all files", ) cmd.parser.add_option( "-i", "--include-keys", default=[], action="append", dest="included_keys", help="comma separated list of keys to show", ) cmd.parser.add_option( "-k", "--keys-only", action="store_true", help="show only the keys", ) cmd.parser.add_format_option(target="item") return [cmd] def run(self, lib, opts, args): """Print tag info or library data for each file referenced by args. Main entry point for the `beet info ARGS...` command. If an argument is a path pointing to an existing file, then the tags of that file are printed. All other arguments are considered queries, and for each item matching all those queries the tags from the file are printed. If `opts.summarize` is true, the function merges all tags into one dictionary and only prints that. If two files have different values for the same tag, the value is set to '[various]' """ if opts.library or opts.album: data_collector = library_data else: data_collector = tag_data included_keys = [] for keys in opts.included_keys: included_keys.extend(keys.split(",")) # Drop path even if user provides it multiple times included_keys = [k for k in included_keys if k != "path"] first = True summary = {} for data_emitter in data_collector( lib, ui.decargs(args), album=opts.album, ): try: data, item = data_emitter(included_keys or "*") except (mediafile.UnreadableFileError, OSError) as ex: self._log.error("cannot read file: {0}", ex) continue if opts.summarize: update_summary(summary, data) else: if not first: ui.print_() if opts.keys_only: print_data_keys(data, item) else: fmt = ui.decargs([opts.format])[0] if opts.format else None print_data(data, item, fmt) first = False if opts.summarize: print_data(summary) ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������beetbox-beets-01f1faf/beetsplug/inline.py�����������������������������������������������������������0000664�0000000�0000000�00000010433�14723254774�0020572�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# This file is part of beets. # Copyright 2016, Adrian Sampson. # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the # "Software"), to deal in the Software without restriction, including # without limitation the rights to use, copy, modify, merge, publish, # distribute, sublicense, and/or sell copies of the Software, and to # permit persons to whom the Software is furnished to do so, subject to # the following conditions: # # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. """Allows inline path template customization code in the config file.""" import itertools import traceback from beets import config from beets.plugins import BeetsPlugin FUNC_NAME = "__INLINE_FUNC__" class InlineError(Exception): """Raised when a runtime error occurs in an inline expression.""" def __init__(self, code, exc): super().__init__( ("error in inline path field code:\n" "%s\n%s: %s") % (code, type(exc).__name__, str(exc)) ) def _compile_func(body): """Given Python code for a function body, return a compiled callable that invokes that code. """ body = "def {}():\n {}".format(FUNC_NAME, body.replace("\n", "\n ")) code = compile(body, "inline", "exec") env = {} eval(code, env) return env[FUNC_NAME] class InlinePlugin(BeetsPlugin): def __init__(self): super().__init__() config.add( { "pathfields": {}, # Legacy name. "item_fields": {}, "album_fields": {}, } ) # Item fields. for key, view in itertools.chain( config["item_fields"].items(), config["pathfields"].items() ): self._log.debug("adding item field {0}", key) func = self.compile_inline(view.as_str(), False) if func is not None: self.template_fields[key] = func # Album fields. for key, view in config["album_fields"].items(): self._log.debug("adding album field {0}", key) func = self.compile_inline(view.as_str(), True) if func is not None: self.album_template_fields[key] = func def compile_inline(self, python_code, album): """Given a Python expression or function body, compile it as a path field function. The returned function takes a single argument, an Item, and returns a Unicode string. If the expression cannot be compiled, then an error is logged and this function returns None. """ # First, try compiling as a single function. try: code = compile(f"({python_code})", "inline", "eval") except SyntaxError: # Fall back to a function body. try: func = _compile_func(python_code) except SyntaxError: self._log.error( "syntax error in inline field definition:\n" "{0}", traceback.format_exc(), ) return else: is_expr = False else: is_expr = True def _dict_for(obj): out = dict(obj) if album: out["items"] = list(obj.items()) return out if is_expr: # For expressions, just evaluate and return the result. def _expr_func(obj): values = _dict_for(obj) try: return eval(code, values) except Exception as exc: raise InlineError(python_code, exc) return _expr_func else: # For function bodies, invoke the function with values as global # variables. def _func_func(obj): old_globals = dict(func.__globals__) func.__globals__.update(_dict_for(obj)) try: return func() except Exception as exc: raise InlineError(python_code, exc) finally: func.__globals__.clear() func.__globals__.update(old_globals) return _func_func �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������beetbox-beets-01f1faf/beetsplug/ipfs.py�������������������������������������������������������������0000664�0000000�0000000�00000024422�14723254774�0020260�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# This file is part of beets. # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the # "Software"), to deal in the Software without restriction, including # without limitation the rights to use, copy, modify, merge, publish, # distribute, sublicense, and/or sell copies of the Software, and to # permit persons to whom the Software is furnished to do so, subject to # the following conditions: # # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. """Adds support for ipfs. Requires go-ipfs and a running ipfs daemon""" import os import shutil import subprocess import tempfile from beets import config, library, ui, util from beets.plugins import BeetsPlugin from beets.util import syspath class IPFSPlugin(BeetsPlugin): def __init__(self): super().__init__() self.config.add( { "auto": True, "nocopy": False, } ) if self.config["auto"]: self.import_stages = [self.auto_add] def commands(self): cmd = ui.Subcommand("ipfs", help="interact with ipfs") cmd.parser.add_option( "-a", "--add", dest="add", action="store_true", help="Add to ipfs" ) cmd.parser.add_option( "-g", "--get", dest="get", action="store_true", help="Get from ipfs" ) cmd.parser.add_option( "-p", "--publish", dest="publish", action="store_true", help="Publish local library to ipfs", ) cmd.parser.add_option( "-i", "--import", dest="_import", action="store_true", help="Import remote library from ipfs", ) cmd.parser.add_option( "-l", "--list", dest="_list", action="store_true", help="Query imported libraries", ) cmd.parser.add_option( "-m", "--play", dest="play", action="store_true", help="Play music from remote libraries", ) def func(lib, opts, args): if opts.add: for album in lib.albums(ui.decargs(args)): if len(album.items()) == 0: self._log.info( "{0} does not contain items, aborting", album ) self.ipfs_add(album) album.store() if opts.get: self.ipfs_get(lib, ui.decargs(args)) if opts.publish: self.ipfs_publish(lib) if opts._import: self.ipfs_import(lib, ui.decargs(args)) if opts._list: self.ipfs_list(lib, ui.decargs(args)) if opts.play: self.ipfs_play(lib, opts, ui.decargs(args)) cmd.func = func return [cmd] def auto_add(self, session, task): if task.is_album: if self.ipfs_add(task.album): task.album.store() def ipfs_play(self, lib, opts, args): from beetsplug.play import PlayPlugin jlib = self.get_remote_lib(lib) player = PlayPlugin() config["play"]["relative_to"] = None player.album = True player.play_music(jlib, player, args) def ipfs_add(self, album): try: album_dir = album.item_dir() except AttributeError: return False try: if album.ipfs: self._log.debug("{0} already added", album_dir) # Already added to ipfs return False except AttributeError: pass self._log.info("Adding {0} to ipfs", album_dir) if self.config["nocopy"]: cmd = "ipfs add --nocopy -q -r".split() else: cmd = "ipfs add -q -r".split() cmd.append(album_dir) try: output = util.command_output(cmd).stdout.split() except (OSError, subprocess.CalledProcessError) as exc: self._log.error("Failed to add {0}, error: {1}", album_dir, exc) return False length = len(output) for linenr, line in enumerate(output): line = line.strip() if linenr == length - 1: # last printed line is the album hash self._log.info("album: {0}", line) album.ipfs = line else: try: item = album.items()[linenr] self._log.info("item: {0}", line) item.ipfs = line item.store() except IndexError: # if there's non music files in the to-add folder they'll # get ignored here pass return True def ipfs_get(self, lib, query): query = query[0] # Check if query is a hash # TODO: generalize to other hashes; probably use a multihash # implementation if query.startswith("Qm") and len(query) == 46: self.ipfs_get_from_hash(lib, query) else: albums = self.query(lib, query) for album in albums: self.ipfs_get_from_hash(lib, album.ipfs) def ipfs_get_from_hash(self, lib, _hash): try: cmd = "ipfs get".split() cmd.append(_hash) util.command_output(cmd) except (OSError, subprocess.CalledProcessError) as err: self._log.error( "Failed to get {0} from ipfs.\n{1}", _hash, err.output ) return False self._log.info("Getting {0} from ipfs", _hash) imp = ui.commands.TerminalImportSession( lib, loghandler=None, query=None, paths=[_hash] ) imp.run() # This uses a relative path, hence we cannot use util.syspath(_hash, # prefix=True). However, that should be fine since the hash will not # exceed MAX_PATH. shutil.rmtree(syspath(_hash, prefix=False)) def ipfs_publish(self, lib): with tempfile.NamedTemporaryFile() as tmp: self.ipfs_added_albums(lib, tmp.name) try: if self.config["nocopy"]: cmd = "ipfs add --nocopy -q ".split() else: cmd = "ipfs add -q ".split() cmd.append(tmp.name) output = util.command_output(cmd).stdout except (OSError, subprocess.CalledProcessError) as err: msg = f"Failed to publish library. Error: {err}" self._log.error(msg) return False self._log.info("hash of library: {0}", output) def ipfs_import(self, lib, args): _hash = args[0] if len(args) > 1: lib_name = args[1] else: lib_name = _hash lib_root = os.path.dirname(lib.path) remote_libs = os.path.join(lib_root, b"remotes") if not os.path.exists(remote_libs): try: os.makedirs(remote_libs) except OSError as e: msg = f"Could not create {remote_libs}. Error: {e}" self._log.error(msg) return False path = os.path.join(remote_libs, lib_name.encode() + b".db") if not os.path.exists(path): cmd = f"ipfs get {_hash} -o".split() cmd.append(path) try: util.command_output(cmd) except (OSError, subprocess.CalledProcessError): self._log.error(f"Could not import {_hash}") return False # add all albums from remotes into a combined library jpath = os.path.join(remote_libs, b"joined.db") jlib = library.Library(jpath) nlib = library.Library(path) for album in nlib.albums(): if not self.already_added(album, jlib): new_album = [] for item in album.items(): item.id = None new_album.append(item) added_album = jlib.add_album(new_album) added_album.ipfs = album.ipfs added_album.store() def already_added(self, check, jlib): for jalbum in jlib.albums(): if jalbum.mb_albumid == check.mb_albumid: return True return False def ipfs_list(self, lib, args): fmt = config["format_album"].get() try: albums = self.query(lib, args) except OSError: ui.print_("No imported libraries yet.") return for album in albums: ui.print_(format(album, fmt), " : ", album.ipfs.decode()) def query(self, lib, args): rlib = self.get_remote_lib(lib) albums = rlib.albums(args) return albums def get_remote_lib(self, lib): lib_root = os.path.dirname(lib.path) remote_libs = os.path.join(lib_root, b"remotes") path = os.path.join(remote_libs, b"joined.db") if not os.path.isfile(path): raise OSError return library.Library(path) def ipfs_added_albums(self, rlib, tmpname): """Returns a new library with only albums/items added to ipfs""" tmplib = library.Library(tmpname) for album in rlib.albums(): try: if album.ipfs: self.create_new_album(album, tmplib) except AttributeError: pass return tmplib def create_new_album(self, album, tmplib): items = [] for item in album.items(): try: if not item.ipfs: break except AttributeError: pass item_path = os.path.basename(item.path).decode( util._fsencoding(), "ignore" ) # Clear current path from item item.path = f"/ipfs/{album.ipfs}/{item_path}" item.id = None items.append(item) if len(items) < 1: return False self._log.info("Adding '{0}' to temporary library", album) new_album = tmplib.add_album(items) new_album.ipfs = album.ipfs new_album.store(inherit=False) ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������beetbox-beets-01f1faf/beetsplug/keyfinder.py��������������������������������������������������������0000664�0000000�0000000�00000006163�14723254774�0021301�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# This file is part of beets. # Copyright 2016, Thomas Scholtes. # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the # "Software"), to deal in the Software without restriction, including # without limitation the rights to use, copy, modify, merge, publish, # distribute, sublicense, and/or sell copies of the Software, and to # permit persons to whom the Software is furnished to do so, subject to # the following conditions: # # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. """Uses the `KeyFinder` program to add the `initial_key` field.""" import os.path import subprocess from beets import ui, util from beets.plugins import BeetsPlugin class KeyFinderPlugin(BeetsPlugin): def __init__(self): super().__init__() self.config.add( { "bin": "KeyFinder", "auto": True, "overwrite": False, } ) if self.config["auto"].get(bool): self.import_stages = [self.imported] def commands(self): cmd = ui.Subcommand( "keyfinder", help="detect and add initial key from audio" ) cmd.func = self.command return [cmd] def command(self, lib, opts, args): self.find_key(lib.items(ui.decargs(args)), write=ui.should_write()) def imported(self, session, task): self.find_key(task.imported_items()) def find_key(self, items, write=False): overwrite = self.config["overwrite"].get(bool) command = [self.config["bin"].as_str()] # The KeyFinder GUI program needs the -f flag before the path. # keyfinder-cli is similar, but just wants the path with no flag. if "keyfinder-cli" not in os.path.basename(command[0]).lower(): command.append("-f") for item in items: if item["initial_key"] and not overwrite: continue try: output = util.command_output( command + [util.syspath(item.path)] ).stdout except (subprocess.CalledProcessError, OSError) as exc: self._log.error("execution failed: {0}", exc) continue try: key_raw = output.rsplit(None, 1)[-1] except IndexError: # Sometimes keyfinder-cli returns 0 but with no key, usually # when the file is silent or corrupt, so we log and skip. self._log.error("no key returned for path: {0}", item.path) continue try: key = key_raw.decode("utf-8") except UnicodeDecodeError: self._log.error("output is invalid UTF-8") continue item["initial_key"] = key self._log.info( "added computed initial key {0} for {1}", key, util.displayable_path(item.path), ) if write: item.try_write() item.store() �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������beetbox-beets-01f1faf/beetsplug/kodiupdate.py�������������������������������������������������������0000664�0000000�0000000�00000006454�14723254774�0021455�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# This file is part of beets. # Copyright 2017, Pauli Kettunen. # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the # "Software"), to deal in the Software without restriction, including # without limitation the rights to use, copy, modify, merge, publish, # distribute, sublicense, and/or sell copies of the Software, and to # permit persons to whom the Software is furnished to do so, subject to # the following conditions: # # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. """Updates a Kodi library whenever the beets library is changed. This is based on the Plex Update plugin. Put something like the following in your config.yaml to configure: kodi: host: localhost port: 8080 user: user pwd: secret """ import requests from beets import config from beets.plugins import BeetsPlugin def update_kodi(host, port, user, password): """Sends request to the Kodi api to start a library refresh.""" url = f"http://{host}:{port}/jsonrpc" """Content-Type: application/json is mandatory according to the kodi jsonrpc documentation""" headers = {"Content-Type": "application/json"} # Create the payload. Id seems to be mandatory. payload = {"jsonrpc": "2.0", "method": "AudioLibrary.Scan", "id": 1} r = requests.post( url, auth=(user, password), json=payload, headers=headers, timeout=10, ) return r class KodiUpdate(BeetsPlugin): def __init__(self): super().__init__() # Adding defaults. config["kodi"].add( [{"host": "localhost", "port": 8080, "user": "kodi", "pwd": "kodi"}] ) config["kodi"]["pwd"].redact = True self.register_listener("database_change", self.listen_for_db_change) def listen_for_db_change(self, lib, model): """Listens for beets db change and register the update""" self.register_listener("cli_exit", self.update) def update(self, lib): """When the client exists try to send refresh request to Kodi server.""" self._log.info("Requesting a Kodi library update...") kodi = config["kodi"].get() # Backwards compatibility in case not configured as an array if not isinstance(kodi, list): kodi = [kodi] for instance in kodi: # Try to send update request. try: r = update_kodi( instance["host"], instance["port"], instance["user"], instance["pwd"], ) r.raise_for_status() json = r.json() if json.get("result") != "OK": self._log.warning( "Kodi update failed: JSON response was {0!r}", json ) continue self._log.info( "Kodi update triggered for {0}:{1}", instance["host"], instance["port"], ) except requests.exceptions.RequestException as e: self._log.warning("Kodi update failed: {0}", str(e)) continue ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������beetbox-beets-01f1faf/beetsplug/lastgenre/����������������������������������������������������������0000775�0000000�0000000�00000000000�14723254774�0020725�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������beetbox-beets-01f1faf/beetsplug/lastgenre/__init__.py�����������������������������������������������0000664�0000000�0000000�00000041335�14723254774�0023044�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# This file is part of beets. # Copyright 2016, Adrian Sampson. # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the # "Software"), to deal in the Software without restriction, including # without limitation the rights to use, copy, modify, merge, publish, # distribute, sublicense, and/or sell copies of the Software, and to # permit persons to whom the Software is furnished to do so, subject to # the following conditions: # # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. """Gets genres for imported music based on Last.fm tags. Uses a provided whitelist file to determine which tags are valid genres. The included (default) genre list was originally produced by scraping Wikipedia and has been edited to remove some questionable entries. The scraper script used is available here: https://gist.github.com/1241307 """ import codecs import os import traceback import pylast import yaml from beets import config, library, plugins, ui from beets.util import normpath, plurality LASTFM = pylast.LastFMNetwork(api_key=plugins.LASTFM_KEY) PYLAST_EXCEPTIONS = ( pylast.WSError, pylast.MalformedResponseError, pylast.NetworkError, ) REPLACE = { "\u2010": "-", } def deduplicate(seq): """Remove duplicates from sequence while preserving order.""" seen = set() return [x for x in seq if x not in seen and not seen.add(x)] # Canonicalization tree processing. def flatten_tree(elem, path, branches): """Flatten nested lists/dictionaries into lists of strings (branches). """ if not path: path = [] if isinstance(elem, dict): for k, v in elem.items(): flatten_tree(v, path + [k], branches) elif isinstance(elem, list): for sub in elem: flatten_tree(sub, path, branches) else: branches.append(path + [str(elem)]) def find_parents(candidate, branches): """Find parents genre of a given genre, ordered from the closest to the further parent. """ for branch in branches: try: idx = branch.index(candidate.lower()) return list(reversed(branch[: idx + 1])) except ValueError: continue return [candidate] # Main plugin logic. WHITELIST = os.path.join(os.path.dirname(__file__), "genres.txt") C14N_TREE = os.path.join(os.path.dirname(__file__), "genres-tree.yaml") class LastGenrePlugin(plugins.BeetsPlugin): def __init__(self): super().__init__() self.config.add( { "whitelist": True, "min_weight": 10, "count": 1, "fallback": None, "canonical": False, "source": "album", "force": True, "auto": True, "separator": ", ", "prefer_specific": False, "title_case": True, } ) self.setup() def setup(self): """Setup plugin from config options""" if self.config["auto"]: self.import_stages = [self.imported] self._genre_cache = {} # Read the whitelist file if enabled. self.whitelist = set() wl_filename = self.config["whitelist"].get() if wl_filename in (True, ""): # Indicates the default whitelist. wl_filename = WHITELIST if wl_filename: wl_filename = normpath(wl_filename) with open(wl_filename, "rb") as f: for line in f: line = line.decode("utf-8").strip().lower() if line and not line.startswith("#"): self.whitelist.add(line) # Read the genres tree for canonicalization if enabled. self.c14n_branches = [] c14n_filename = self.config["canonical"].get() self.canonicalize = c14n_filename is not False # Default tree if c14n_filename in (True, ""): c14n_filename = C14N_TREE elif not self.canonicalize and self.config["prefer_specific"].get(): # prefer_specific requires a tree, load default tree c14n_filename = C14N_TREE # Read the tree if c14n_filename: self._log.debug("Loading canonicalization tree {0}", c14n_filename) c14n_filename = normpath(c14n_filename) with codecs.open(c14n_filename, "r", encoding="utf-8") as f: genres_tree = yaml.safe_load(f) flatten_tree(genres_tree, [], self.c14n_branches) @property def sources(self): """A tuple of allowed genre sources. May contain 'track', 'album', or 'artist.' """ source = self.config["source"].as_choice(("track", "album", "artist")) if source == "track": return "track", "album", "artist" elif source == "album": return "album", "artist" elif source == "artist": return ("artist",) def _get_depth(self, tag): """Find the depth of a tag in the genres tree.""" depth = None for key, value in enumerate(self.c14n_branches): if tag in value: depth = value.index(tag) break return depth def _sort_by_depth(self, tags): """Given a list of tags, sort the tags by their depths in the genre tree. """ depth_tag_pairs = [(self._get_depth(t), t) for t in tags] depth_tag_pairs = [e for e in depth_tag_pairs if e[0] is not None] depth_tag_pairs.sort(reverse=True) return [p[1] for p in depth_tag_pairs] def _resolve_genres(self, tags): """Given a list of strings, return a genre by joining them into a single string and (optionally) canonicalizing each. """ if not tags: return None count = self.config["count"].get(int) if self.canonicalize: # Extend the list to consider tags parents in the c14n tree tags_all = [] for tag in tags: # Add parents that are in the whitelist, or add the oldest # ancestor if no whitelist if self.whitelist: parents = [ x for x in find_parents(tag, self.c14n_branches) if self._is_allowed(x) ] else: parents = [find_parents(tag, self.c14n_branches)[-1]] tags_all += parents # Stop if we have enough tags already, unless we need to find # the most specific tag (instead of the most popular). if ( not self.config["prefer_specific"] and len(tags_all) >= count ): break tags = tags_all tags = deduplicate(tags) # Sort the tags by specificity. if self.config["prefer_specific"]: tags = self._sort_by_depth(tags) # c14n only adds allowed genres but we may have had forbidden genres in # the original tags list tags = [self._format_tag(x) for x in tags if self._is_allowed(x)] return ( self.config["separator"] .as_str() .join(tags[: self.config["count"].get(int)]) ) def _format_tag(self, tag): if self.config["title_case"]: return tag.title() return tag def fetch_genre(self, lastfm_obj): """Return the genre for a pylast entity or None if no suitable genre can be found. Ex. 'Electronic, House, Dance' """ min_weight = self.config["min_weight"].get(int) return self._resolve_genres(self._tags_for(lastfm_obj, min_weight)) def _is_allowed(self, genre): """Determine whether the genre is present in the whitelist, returning a boolean. """ if genre is None: return False if not self.whitelist or genre in self.whitelist: return True return False # Cached entity lookups. def _last_lookup(self, entity, method, *args): """Get a genre based on the named entity using the callable `method` whose arguments are given in the sequence `args`. The genre lookup is cached based on the entity name and the arguments. Before the lookup, each argument is has some Unicode characters replaced with rough ASCII equivalents in order to return better results from the Last.fm database. """ # Shortcut if we're missing metadata. if any(not s for s in args): return None key = "{}.{}".format(entity, "-".join(str(a) for a in args)) if key in self._genre_cache: return self._genre_cache[key] else: args_replaced = [] for arg in args: for k, v in REPLACE.items(): arg = arg.replace(k, v) args_replaced.append(arg) genre = self.fetch_genre(method(*args_replaced)) self._genre_cache[key] = genre return genre def fetch_album_genre(self, obj): """Return the album genre for this Item or Album.""" return self._last_lookup( "album", LASTFM.get_album, obj.albumartist, obj.album ) def fetch_album_artist_genre(self, obj): """Return the album artist genre for this Item or Album.""" return self._last_lookup("artist", LASTFM.get_artist, obj.albumartist) def fetch_artist_genre(self, item): """Returns the track artist genre for this Item.""" return self._last_lookup("artist", LASTFM.get_artist, item.artist) def fetch_track_genre(self, obj): """Returns the track genre for this Item.""" return self._last_lookup( "track", LASTFM.get_track, obj.artist, obj.title ) def _get_genre(self, obj): """Get the genre string for an Album or Item object based on self.sources. Return a `(genre, source)` pair. The prioritization order is: - track (for Items only) - album - artist - original - fallback - None """ # Shortcut to existing genre if not forcing. if not self.config["force"] and self._is_allowed(obj.genre): return obj.genre, "keep" # Track genre (for Items only). if isinstance(obj, library.Item): if "track" in self.sources: result = self.fetch_track_genre(obj) if result: return result, "track" # Album genre. if "album" in self.sources: result = self.fetch_album_genre(obj) if result: return result, "album" # Artist (or album artist) genre. if "artist" in self.sources: result = None if isinstance(obj, library.Item): result = self.fetch_artist_genre(obj) elif obj.albumartist != config["va_name"].as_str(): result = self.fetch_album_artist_genre(obj) else: # For "Various Artists", pick the most popular track genre. item_genres = [] for item in obj.items(): item_genre = None if "track" in self.sources: item_genre = self.fetch_track_genre(item) if not item_genre: item_genre = self.fetch_artist_genre(item) if item_genre: item_genres.append(item_genre) if item_genres: result, _ = plurality(item_genres) if result: return result, "artist" # Filter the existing genre. if obj.genre: result = self._resolve_genres([obj.genre]) if result: return result, "original" # Fallback string. fallback = self.config["fallback"].get() if fallback: return fallback, "fallback" return None, None def commands(self): lastgenre_cmd = ui.Subcommand("lastgenre", help="fetch genres") lastgenre_cmd.parser.add_option( "-f", "--force", dest="force", action="store_true", help="re-download genre when already present", ) lastgenre_cmd.parser.add_option( "-s", "--source", dest="source", type="string", help="genre source: artist, album, or track", ) lastgenre_cmd.parser.add_option( "-A", "--items", action="store_false", dest="album", help="match items instead of albums", ) lastgenre_cmd.parser.add_option( "-a", "--albums", action="store_true", dest="album", help="match albums instead of items", ) lastgenre_cmd.parser.set_defaults(album=True) def lastgenre_func(lib, opts, args): write = ui.should_write() self.config.set_args(opts) if opts.album: # Fetch genres for whole albums for album in lib.albums(ui.decargs(args)): album.genre, src = self._get_genre(album) self._log.info( "genre for album {0} ({1}): {0.genre}", album, src ) album.store() for item in album.items(): # If we're using track-level sources, also look up each # track on the album. if "track" in self.sources: item.genre, src = self._get_genre(item) item.store() self._log.info( "genre for track {0} ({1}): {0.genre}", item, src, ) if write: item.try_write() else: # Just query singletons, i.e. items that are not part of # an album for item in lib.items(ui.decargs(args)): item.genre, src = self._get_genre(item) self._log.debug( "added last.fm item genre ({0}): {1}", src, item.genre ) item.store() lastgenre_cmd.func = lastgenre_func return [lastgenre_cmd] def imported(self, session, task): """Event hook called when an import task finishes.""" if task.is_album: album = task.album album.genre, src = self._get_genre(album) self._log.debug( "added last.fm album genre ({0}): {1}", src, album.genre ) album.store() if "track" in self.sources: for item in album.items(): item.genre, src = self._get_genre(item) self._log.debug( "added last.fm item genre ({0}): {1}", src, item.genre ) item.store() else: item = task.item item.genre, src = self._get_genre(item) self._log.debug( "added last.fm item genre ({0}): {1}", src, item.genre ) item.store() def _tags_for(self, obj, min_weight=None): """Core genre identification routine. Given a pylast entity (album or track), return a list of tag names for that entity. Return an empty list if the entity is not found or another error occurs. If `min_weight` is specified, tags are filtered by weight. """ # Work around an inconsistency in pylast where # Album.get_top_tags() does not return TopItem instances. # https://github.com/pylast/pylast/issues/86 if isinstance(obj, pylast.Album): obj = super(pylast.Album, obj) try: res = obj.get_top_tags() except PYLAST_EXCEPTIONS as exc: self._log.debug("last.fm error: {0}", exc) return [] except Exception as exc: # Isolate bugs in pylast. self._log.debug("{}", traceback.format_exc()) self._log.error("error in pylast library: {0}", exc) return [] # Filter by weight (optionally). if min_weight: res = [el for el in res if (int(el.weight or 0)) >= min_weight] # Get strings from tags. res = [el.item.get_name().lower() for el in res] return res ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������beetbox-beets-01f1faf/beetsplug/lastgenre/genres-tree.yaml������������������������������������������0000664�0000000�0000000�00000036510�14723254774�0024036�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������- african: - african heavy metal - african hip hop - afrobeat - apala - benga - bikutsi - bongo flava - cape jazz - chimurenga - coupĂ©-dĂ©calĂ© - fuji music - genge - highlife - hiplife - isicathamiya - jit - jĂąjĂş - kapuka - kizomba - kuduro - kwaito - kwela - makossa - maloya - marrabenta - mbalax - mbaqanga - mbube - morna - museve - palm-wine - raĂŻ - sakara - sega - seggae - semba - soukous - taarab - zouglou - asian: - east asian: - anison - c-pop - cantopop - enka - hong kong english pop - j-pop - k-pop - kayĹŤkyoku - korean pop - mandopop - onkyokei - taiwanese pop - fann at-tanbura - fijiri - khaliji - liwa - sawt - south and southeast asian: - baila - bhangra - bhojpuri - dangdut - filmi - indian pop - lavani - luk thung: - luk krung - manila sound - morlam - pinoy pop - pop sunda - ragini - thai pop - avant-garde: - experimental music - lo-fi - musique concrète - blues: - african blues - blues rock - blues shouter - british blues - canadian blues - chicago blues - classic female blues - contemporary r&b - country blues - delta blues - detroit blues - electric blues - gospel blues - hill country blues - hokum blues - jazz blues - jump blues - kansas city blues - louisiana blues - memphis blues - piano blues - piedmont blues - punk blues - soul blues - st. louis blues - swamp blues - texas blues - west coast blues - caribbean and latin american: - bachata - baithak gana - bolero - brazilian: - axĂ© - bossa nova - brazilian rock - brega - choro - forrĂł - frevo - funk carioca - lambada - maracatu - mĂşsica popular brasileira - mĂşsica sertaneja - pagode - samba - samba rock - tecnobrega - tropicalia - zouk-lambada - calypso - chutney - chutney soca - compas - mambo - merengue - mĂ©ringue - other latin: - chicha - criolla - cumbia - huayno - mariachi - ranchera - tejano - punta - punta rock - rasin - reggaeton - salsa - soca - son - timba - twoubadou - zouk - classical: - ballet - baroque: - baroque music - cantata - chamber music: - string quartet - classical music - concerto: - concerto grosso - contemporary classical - modern classical - opera - oratorio - orchestra: - orchestral - symphonic - symphony - organum - mass: - requiem - sacred music: - cantique - gregorian chant - sonata - comedy: - comedy music - comedy rock - humor - parody music - stand-up - country: - alternative country: - cowpunk - americana - australian country music - bakersfield sound - bluegrass: - progressive bluegrass - reactionary bluegrass - blues country - cajun: - cajun fiddle tunes - christian country music - classic country - close harmony - country pop - country rap - country rock - country soul - cowboy/western music - dansband music - franco-country - gulf and western - hellbilly music - hokum - honky tonk - instrumental country - lubbock sound - nashville sound - neotraditional country - outlaw country - progressive country - psychobilly/punkabilly - red dirt - rockabilly - sertanejo - texas country - traditional country music - truck-driving country - western swing - zydeco - easy listening: - background music - beautiful music - elevator music - furniture music - lounge music - middle of the road - new-age music - electronic: - ambient: - ambient dub - ambient house - ambient techno - dark ambient - drone music - illbient - isolationism - lowercase - asian underground - breakbeat: - 4-beat - acid breaks - baltimore club - big beat - breakbeat hardcore - broken beat - florida breaks - nu skool breaks - chiptune: - bitpop - game boy music - nintendocore - video game music - yorkshire bleeps and bass - disco: - cosmic disco - disco polo - euro disco - italo disco - nu-disco - space disco - downtempo: - acid jazz - balearic beat - chill out - dub music - dubtronica - ethnic electronica - moombahton - nu jazz - trip hop - drum and bass: - darkcore - darkstep - drumfunk - drumstep - hardstep - intelligent drum and bass - jump-up - liquid funk - neurofunk - oldschool jungle: - darkside jungle - ragga jungle - raggacore - sambass - techstep - electro: - crunk - electro backbeat - electro-grime - electropop - electroacoustic: - acousmatic music - computer music - electroacoustic improvisation - field recording - live coding - live electronics - soundscape composition - tape music - electronic rock: - alternative dance: - baggy - madchester - dance-punk - dance-rock - dark wave - electroclash - electronicore - electropunk - ethereal wave - indietronica - new rave - space rock - synthpop - synthpunk - electronica: - berlin school - chillwave - electronic art music - electronic dance music - folktronica - freestyle music - glitch - idm - laptronica - skweee - sound art - synthcore - eurodance: - bubblegum dance - italo dance - turbofolk - hardcore: - bouncy house - bouncy techno - breakcore - digital hardcore - doomcore - dubstyle - gabber - happy hardcore - hardstyle - jumpstyle - makina - speedcore - terrorcore - uk hardcore - hi-nrg: - eurobeat - hard nrg - new beat - house: - acid house - chicago house - deep house - diva house - dutch house - electro house - freestyle house - french house - funky house - ghetto house - hardbag - hip house - italo house - latin house - minimal house - progressive house - rave music - swing house - tech house - tribal house - uk hard house - us garage - vocal house - industrial: - aggrotech - coldwave - cybergrind - dark electro - death industrial - electro-industrial - electronic body music: - futurepop - industrial metal: - neue deutsche härte - industrial rock - noise: - japanoise - power electronics - power noise - witch house - post-disco: - boogie - dance-pop - progressive: - progressive house/trance: - disco house - dream house - space house - progressive breaks - progressive drum & bass - progressive techno - techno: - acid techno - detroit techno - free tekno - ghettotech - minimal - nortec - schranz - techno-dnb - technopop - tecno brega - toytown techno - trance: - acid trance - classic trance - dream trance - goa trance: - dark psytrance - full on - psybreaks - psyprog - suomisaundi - hard trance - tech trance - uplifting trance: - orchestral uplifting - vocal trance - uk garage: - 2-step - 4x4 - bassline - breakstep - dubstep - funky - grime - speed garage - trap - folk: - american folk revival - anti-folk - british folk revival - celtic music - contemporary folk - filk music - freak folk - indie folk - industrial folk - neofolk - progressive folk - psychedelic folk - sung poetry - techno-folk - hip hop: - alternative hip hop - avant-garde hip hop - chap hop - christian hip hop - conscious hip hop - country-rap - crunkcore - cumbia rap - east coast hip hop: - brick city club - hardcore hip hop - mafioso rap - new jersey hip hop - electro music - freestyle rap - g-funk - gangsta rap - golden age hip hop - hip hop soul - hip pop - hyphy - industrial hip hop - instrumental hip hop - jazz rap - low bap - lyrical hip hop - merenrap - midwest hip hop: - chicago hip hop - detroit hip hop - horrorcore - st. louis hip hop - twin cities hip hop - motswako - nerdcore - new jack swing - new school hip hop - old school hip hop - political hip hop - rap opera - rap rock: - rap metal - rapcore - songo-salsa - southern hip hop: - atlanta hip hop: - snap music - bounce music - houston hip hop: - chopped and screwed - miami bass - turntablism - underground hip hop - urban pasifika - west coast hip hop: - chicano rap - jerkin' - jazz: - asian american jazz - avant-garde jazz - bebop - boogie-woogie - british dance band - chamber jazz - continental jazz - cool jazz - crossover jazz - cubop - dixieland - ethno jazz - european free jazz - free funk - free improvisation - free jazz - gypsy jazz - hard bop - jazz fusion - jazz rock - jazz-funk - kansas city jazz - latin jazz - livetronica - m-base - mainstream jazz - modal jazz - neo-bop jazz - neo-swing - novelty ragtime - orchestral jazz - post-bop - punk jazz - ragtime - shibuya-kei - ska jazz - smooth jazz - soul jazz - straight-ahead jazz - stride jazz - swing - third stream - trad jazz - vocal jazz - west coast gypsy jazz - west coast jazz - other: - worldbeat - pop: - adult contemporary - arab pop - baroque pop - bubblegum pop - chanson - christian pop - classical crossover - europop: - austropop - balkan pop - french pop - latin pop - laĂŻkĂł - nederpop - russian pop - iranian pop - jangle pop - latin ballad - levenslied - louisiana swamp pop - mexican pop - motorpop - new romanticism - pop rap - popera - psychedelic pop - schlager - soft rock - sophisti-pop - space age pop - sunshine pop - surf pop - teen pop - traditional pop music - turkish pop - vispop - wonky pop - rhythm and blues: - funk: - deep funk - go-go - p-funk - soul: - blue-eyed soul - neo soul - northern soul - rock: - alternative rock: - britpop: - post-britpop - dream pop - grunge: - post-grunge - indie pop: - dunedin sound - twee pop - indie rock - noise pop - nu metal - post-punk revival - post-rock: - post-metal - sadcore - shoegaze - slowcore - art rock - beat music - chinese rock - christian rock - dark cabaret - desert rock - experimental rock - folk rock - garage rock - glam rock - hard rock - heavy metal: - alternative metal: - funk metal - black metal: - viking metal - christian metal - death metal: - death/doom - goregrind - melodic death metal - technical death metal - doom metal: - epic doom metal - funeral doom - drone metal - epic metal - folk metal: - celtic metal - medieval metal - pagan metal - funk metal - glam metal - gothic metal - industrial metal: - industrial death metal - metalcore: - deathcore - mathcore: - djent - synthcore - neoclassical metal - post-metal - power metal: - progressive power metal - progressive metal - sludge metal - speed metal - stoner rock: - stoner metal - symphonic metal - thrash metal: - crossover thrash - groove metal - progressive thrash metal - teutonic thrash metal - traditional heavy metal - math rock - new wave: - world fusion - paisley underground - pop rock - post-punk: - gothic rock - no wave - noise rock - power pop - progressive rock: - canterbury scene - krautrock - new prog - rock in opposition - psychedelic rock: - acid rock - freakbeat - neo-psychedelia - raga rock - punk rock: - anarcho punk: - crust punk: - d-beat - art punk - christian punk - deathrock - folk punk: - celtic punk - gypsy punk - garage punk - grindcore: - crustgrind - noisegrind - hardcore punk: - post-hardcore: - emo: - screamo - powerviolence - street punk - thrashcore - horror punk - oi! - pop punk - psychobilly - riot grrrl - ska punk: - ska-core - skate punk - rock and roll - southern rock - sufi rock - surf rock - visual kei: - nagoya kei - reggae: - roots reggae - reggae fusion - reggae en español: - spanish reggae - reggae 110 - reggae bultrĂłn - romantic flow - lovers rock - raggamuffin: - ragga - dancehall - ska: - 2 tone - dub - rocksteady ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������beetbox-beets-01f1faf/beetsplug/lastgenre/genres.txt������������������������������������������������0000664�0000000�0000000�00000041631�14723254774�0022756�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������2 tone 2-step garage 4-beat 4x4 garage 8-bit acapella acid acid breaks acid house acid jazz acid rock acoustic music acousticana adult contemporary music african popular music african rumba afrobeat aleatoric music alternative country alternative dance alternative hip hop alternative metal alternative rock ambient ambient house ambient music americana anarcho punk anti-folk apala ape haters arab pop arabesque arabic pop argentine rock ars antiqua ars nova art punk art rock ashiq asian american jazz australian country music australian hip hop australian pub rock austropop avant-garde avant-garde jazz avant-garde metal avant-garde music axĂ© bac-bal bachata baggy baila baile funk baisha xiyue baithak gana baiĂŁo bajourou bakersfield sound bakou bakshy bal-musette balakadri balinese gamelan balkan pop ballad ballata ballet bamboo band bambuco banda bangsawan bantowbol barbershop music barndance baroque baroque music baroque pop bass music batcave batucada batuco batá-rumba beach music beat beatboxing beautiful music bebop beiguan bel canto bend-skin benga berlin school of electronic music bhajan bhangra bhangra-wine bhangragga bhangramuffin big band big band music big beat biguine bihu bikutsi biomusic bitcore bitpop black metal blackened death metal blue-eyed soul bluegrass blues blues ballad blues-rock boogie boogie woogie boogie-woogie bossa nova brass band brazilian funk brazilian jazz breakbeat breakbeat hardcore breakcore breton music brill building pop britfunk british blues british invasion britpop broken beat brown-eyed soul brukdown brutal death metal bubblegum dance bubblegum pop bulerias bumba-meu-boi bunraku burger-highlife burgundian school byzantine chant ca din tulnic ca pe lunca ca trĂą cabaret cadence cadence rampa cadence-lypso cafĂ©-aman cai luong cajun music cakewalk calenda calentanos calgia calypso calypso jazz calypso-style baila campursari canatronic candombe canon canrock cantata cante chico cante jondo canterbury scene cantiga cantique cantiñas canto livre canto nuevo canto popular cantopop canzone napoletana cape jazz capoeira music caracoles carceleras cardas cardiowave carimbĂł cariso carnatic music carol cartageneras cassette culture cassĂ©y-co cavacha caveman caña celempungan cello rock celtic celtic fusion celtic metal celtic punk celtic reggae celtic rock cha-cha-cha chakacha chalga chamamĂ© chamber jazz chamber music chamber pop champeta changuĂ­ chanson chant charanga charanga-vallenata charikawi chastushki chau van chemical breaks chicago blues chicago house chicago soul chicano rap chicha chicken scratch children's music chillout chillwave chimurenga chinese music chinese pop chinese rock chip music cho-kantrum chongak chopera chorinho choro chouval bwa chowtal christian alternative christian black metal christian electronic music christian hardcore christian hip hop christian industrial christian metal christian music christian punk christian r&b christian rock christian ska christmas carol christmas music chumba chut-kai-pang chutney chutney soca chutney-bhangra chutney-hip hop chutney-soca chylandyk chzalni chèo cigányzene classic classic country classic female blues classic rock classical classical music classical music era clicks n cuts close harmony club music cocobale coimbra fado coladeira colombianas combined rhythm comedy comedy rap comedy rock comic opera comparsa compas direct compas meringue concert overture concerto concerto grosso congo conjunto contemporary christian contemporary christian music contemporary classical contemporary r&b contonbley contradanza cool jazz corrido corsican polyphonic song cothoza mfana country country blues country gospel country music country pop country r&b country rock country-rap countrypolitan couple de sonneurs coupĂ©-dĂ©calĂ© cowpunk cretan music crossover jazz crossover music crossover thrash crossover thrash metal crunk crunk&b crunkcore crust punk csárdás cuarteto cuban rumba cuddlecore cueca cumbia cumbia villera cybergrind dabka dadra daina dalauna dance dance music dance-pop dance-punk dance-rock dancehall dangdut danger music dansband danza danzĂłn dark ambient dark cabaret dark pop darkcore darkstep darkwave de ascultat la servici de codru de dragoste de jale de pahar death industrial death metal death rock death/doom deathcore deathgrind deathrock deep funk deep house deep soul degung delta blues dementia desert rock desi detroit blues detroit techno dhamar dhimotiká dhrupad dhun digital hardcore dirge dirty dutch dirty rap dirty rap/pornocore dirty south disco disco house disco polo disney disney hardcore disney pop diva house divine rock dixieland dixieland jazz djambadon djent dodompa doina dombola dondang sayang donegal fiddle tradition dongjing doo wop doom metal doomcore downtempo drag dream pop drone doom drone metal drone music dronology drum and bass dub dub house dubanguthu dubstep dubtronica dunedin sound dunun dutch jazz dĂ©cima early music east coast blues east coast hip hop easy listening electric blues electric folk electro electro backbeat electro hop electro house electro punk electro-industrial electro-swing electroclash electrofunk electronic electronic art music electronic body music electronic dance electronic luk thung electronic music electronic rock electronica electropop elevator music emo emo pop emo rap emocore emotronic enka epic doom metal epic metal eremwu eu ethereal pop ethereal wave euro euro disco eurobeat eurodance europop eurotrance eurourban exotica experimental music experimental noise experimental pop experimental rock extreme metal ezengileer fado falak fandango farruca fife and drum blues filk film score filmi filmi-ghazal finger-style fjatpangarri flamenco flamenco rumba flower power foaie verde fofa folk hop folk metal folk music folk pop folk punk folk rock folktronica forrĂł franco-country freak-folk freakbeat free improvisation free jazz free music freestyle freestyle house freetekno french pop frenchcore frevo fricote fuji fuji music fulia full on funaná funeral doom funk funk metal funk rock funkcore funky house furniture music fusion jazz g-funk gaana gabba gabber gagaku gaikyoku gaita galant gamad gambang kromong gamelan gamelan angklung gamelan bang gamelan bebonangan gamelan buh gamelan degung gamelan gede gamelan kebyar gamelan salendro gamelan selunding gamelan semar pegulingan gamewave gammeldans gandrung gangsta rap gar garage rock garrotin gavotte gelugpa chanting gender wayang gending german folk music gharbi gharnati ghazal ghazal-song ghetto house ghettotech girl group glam metal glam punk glam rock glitch gnawa go-go goa goa trance gong-chime music goombay goregrind goshu ondo gospel music gothic metal gothic rock granadinas grebo gregorian chant grime grindcore groove metal group sounds grunge grupera guaguanbo guajira guasca guitarra baiana guitarradas gumbe gunchei gunka guoyue gwo ka gwo ka moderne gypsy jazz gypsy punk gypsybilly gyu ke habanera hajnali hakka halling hambo hands up hapa haole happy hardcore haqibah hard hard bop hard house hard rock hard trance hardcore hip hop hardcore metal hardcore punk hardcore techno hardstyle harepa harmonica blues hasaposĂ©rviko heart attack heartland rock heavy beat heavy metal hesher hi-nrg highlands highlife highlife fusion hillybilly music hindustani classical music hip hop hip hop & rap hip hop soul hip house hiplife hiragasy hiva usu hong kong and cantonese pop hong kong english pop honky tonk honkyoku hora lunga hornpipe horror punk horrorcore horrorcore rap house house music hua'er huasteco huayno hula humor humppa hunguhungu hyangak hymn hyphy hát chau van hát chèo hát cĂŁi luong hát tuồng ibiza music icaro idm igbo music ijexá ilahije illbient impressionist music improvisational incidental music indian pop indie folk indie music indie pop indie rock indietronica indo jazz indo rock indonesian pop indoyĂ­ftika industrial death metal industrial hip-hop industrial metal industrial music industrial musical industrial rock instrumental rock intelligent dance music international latin inuit music iranian pop irish folk irish rebel music iscathamiya isicathamiya isikhwela jo island isolationist italo dance italo disco italo house itsmeños izvorna bosanska muzika j'ouvert j-fusion j-pop j-rock jaipongan jaliscienses jam band jam rock jamana kura jamrieng samai jangle pop japanese pop jarana jariang jarochos jawaiian jazz jazz blues jazz fusion jazz metal jazz rap jazz-funk jazz-rock jegog jenkka jesus music jibaro jig jig punk jing ping jingle jit jitterbug jive joged joged bumbung joik jonnycore joropo jota jtek jug band jujitsu juju juke joint blues jump blues jumpstyle jungle junkanoo jurĂ© jĂąjĂş k-pop kaba kabuki kachÄshÄ« kadans kagok kagyupa chanting kaiso kalamatianĂł kalattuut kalinda kamba pop kan ha diskan kansas city blues kantrum kantádhes kargyraa karma kaseko katajjaq kawachi ondo kayĹŤkyoku ke-kwe kebyar kecak kecapi suling kertok khaleeji khap khelimaski djili khene khoomei khorovodi khplam wai khrung sai khyal kilapanda kinko kirtan kiwi rock kizomba klape klasik klezmer kliningan klĂ©ftiko kochare kolomyjka komagaku kompa konpa korean pop koumpaneia kpanlogo krakowiak krautrock kriti kroncong krump krzesany kuduro kulintang kulning kumina kun-borrk kundere kundiman kussundĂ© kutumba wake kveding kvæði kwaito kwassa kwassa kwela käng kĂ©lĂ© kÄ©kĹ©yĹ© pop la la latin american latin jazz latin pop latin rap lavway laĂŻko laĂŻkĂł le leagan legĂ©nyes lelio letkajenkka levenslied lhamo lieder light music light rock likanos liquid drum&bass liquid funk liquindi llanera llanto lo-fi lo-fi music loki djili long-song louisiana blues louisiana swamp pop lounge music lovers rock lowercase lubbock sound lucknavi thumri luhya omutibo luk grung lullaby lundu lundum m-base madchester madrigal mafioso rap maglaal magnificat mahori mainstream jazz makossa makossa-soukous malagueñas malawian jazz malhun maloya maluf maluka mambo manaschi mandarin pop manding swing mango mangue bit mangulina manikay manila sound manouche manzuma mapouka mapouka-serrĂ© marabi maracatu marga mariachi marimba marinera marrabenta martial industrial martinetes maskanda mass matamuerte math rock mathcore matt bello maxixe mazurka mbalax mbaqanga mbube mbumba medh medieval folk rock medieval metal medieval music meditation mejorana melhoun melhĂ»n melodic black metal melodic death metal melodic hardcore melodic metalcore melodic music melodic trance memphis blues memphis rap memphis soul mento merengue merengue tĂ­pico moderno merengue-bomba meringue merseybeat metal metalcore metallic hardcore mexican pop mexican rock mexican son meykhana mezwed miami bass microhouse middle of the road midwest hip hop milonga min'yo mineras mini compas mini-jazz minimal techno minimalist music minimalist trance minneapolis sound minstrel show minuet mirolĂłyia modal jazz modern classical modern classical music modern laika modern rock modinha mohabelo montuno monumental dance mor lam mor lam sing morna motorpop motown mozambique mpb mugam multicultural murga musette museve mushroom jazz music drama music hall musiqi-e assil musique concrète mutuashi muwashshah muzak mĂ©ringue mĂşsica campesina mĂşsica criolla mĂşsica de la interior mĂşsica llanera mĂşsica nordestina mĂşsica popular brasileira mĂşsica tropical nagauta nakasi nangma nanguan narcocorrido nardcore narodna muzika nasheed nashville sound nashville sound/countrypolitan national socialist black metal naturalismo nederpop neo soul neo-classical metal neo-medieval neo-prog neo-psychedelia neoclassical neoclassical metal neoclassical music neofolk neotraditional country nerdcore neue deutsche härte neue deutsche welle new age music new beat new instrumental new jack swing new orleans blues new orleans jazz new pop new prog new rave new romantic new school hip hop new taiwanese song new wave new wave of british heavy metal new wave of new wave new weird america new york blues new york house newgrass nganja nightcore nintendocore nisiĂłtika no wave noh noise music noise pop noise rock nongak norae undong nordic folk dance music nordic folk music nortec norteño northern soul nota nu breaks nu jazz nu metal nu soul nueva canciĂłn nyatiti nĂ©o kĂ˝ma obscuro oi! old school hip hop old-time oldies olonkho oltului ondo opera operatic pop oratorio orchestra orchestral organ trio organic ambient organum orgel oriental metal ottava rima outlaw country outsider music p-funk pagan metal pagan rock pagode paisley underground palm wine palm-wine pambiche panambih panchai baja panchavadyam pansori paranda parang parody parranda partido alto pasillo patriotic peace punk pelimanni music petenera peyote song philadelphia soul piano blues piano rock piedmont blues pimba pinoy pop pinoy rock pinpeat orchestra piphat piyyutim plainchant plena pleng phua cheewit pleng thai sakorn political hip hop polka polo polonaise pols polska pong lang pop pop folk pop music pop punk pop rap pop rock pop sunda pornocore porro post disco post-britpop post-disco post-grunge post-hardcore post-industrial post-metal post-minimalism post-punk post-rock post-romanticism pow-wow power electronics power metal power noise power pop powerviolence ppongtchak praise song program symphony progressive bluegrass progressive country progressive death metal progressive electronic progressive electronic music progressive folk progressive folk music progressive house progressive metal progressive power metal progressive rock progressive trance progressive thrash metal protopunk psych folk psychedelic music psychedelic pop psychedelic rock psychedelic trance psychobilly punk blues punk cabaret punk jazz punk rock punta punta rock qasidah qasidah modern qawwali quadrille quan ho queercore quiet storm rada raga raga rock ragga ragga jungle raggamuffin ragtime rai rake-and-scrape ramkbach ramvong ranchera rap rap metal rap rock rapcore rara rare groove rasiya rave raw rock raĂŻ rebetiko red dirt reel reggae reggae 110 reggae bultrĂłn reggae en español reggae fusion reggae highlife reggaefusion reggaeton rekilaulu relax music religious rembetiko renaissance music requiem rhapsody rhyming spiritual rhythm & blues rhythm and blues ricercar riot grrrl rock rock and roll rock en español rock opera rockabilly rocksteady rococo romantic flow romantic period in music rondeaux ronggeng roots reggae roots rock roots rock reggae rumba russian pop rĂ­mur sabar sacred harp sacred music sadcore saibara sakara salegy salsa salsa erotica salsa romantica saltarello samba samba-canção samba-reggae samba-rock sambai sanjo sato kagura sawt saya scat schlager schottisch schranz scottish baroque music screamo scrumpy and western sea shanty sean nĂłs second viennese school sega music seggae seis semba sephardic music serialism set dance sevdalinka sevillana shabab shabad shalako shan'ge shango shape note shibuya-kei shidaiqu shima uta shock rock shoegaze shoegazer shoka shomyo show tune sica siguiriyas silat sinawi situational ska ska punk skacore skald skate punk skiffle slack-key guitar slide slowcore sludge metal slängpolska smooth jazz soca soft rock son son montuno son-batá sonata songo songo-salsa sophisti-pop soukous soul soul blues soul jazz soul music southern gospel southern harmony southern hip hop southern metal southern rock southern soul space age pop space music space rock spectralism speed garage speed metal speedcore spirituals spouge sprechgesang square dance squee st. louis blues stand-up steelband stoner metal stoner rock straight edge strathspeys stride string string quartet sufi music suite sunshine pop suomirock super eurobeat surf ballad surf instrumental surf music surf pop surf rock swamp blues swamp pop swamp rock swing swing music swingbeat sygyt symphonic symphonic black metal symphonic metal symphonic poem symphonic rock symphony synthcore synthpop synthpunk t'ong guitar taarab tai tu taiwanese pop tala talempong tambu tamburitza tamil christian keerthanai tango tanguk tappa tarana tarantella taranto tech tech house tech trance technical death metal technical metal techno technoid technopop techstep techtonik teen pop tejano tejano music tekno tembang sunda teutonic thrash metal texas blues thai pop thillana thrash metal thrashcore thumri tibetan pop tiento timbila tin pan alley tinga tinku toeshey togaku trad jazz traditional bluegrass traditional heavy metal traditional pop music trallalero trance tribal house trikitixa trip hop trip rock tropicalia tropicalismo tropipop truck-driving country tumba turbo-folk turkish music turkish pop turntablism tuvan throat-singing twee pop twist two tone táncház uk garage uk pub rock unblack metal underground music uplifting uplifting trance urban cowboy urban folk urban jazz vallenato vaudeville venezuela verbunkos verismo viking metal villanella virelai vispop visual kei visual music vocal vocal house vocal jazz vocal music volksmusik waila waltz wangga warabe uta wassoulou weld were music west coast hip hop west coast jazz western western blues western swing witch house wizard rock women's music wong shadow wonky pop wood work song world fusion world fusion music world music worldbeat xhosa music xoomii yo-pop yodeling yukar yĂ©-yĂ© zajal zapin zarzuela zeibekiko zeuhl ziglibithy zouglou zouk zouk chouv zouklove zulu music zydeco �������������������������������������������������������������������������������������������������������beetbox-beets-01f1faf/beetsplug/lastimport.py�������������������������������������������������������0000664�0000000�0000000�00000022276�14723254774�0021522�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# This file is part of beets. # Copyright 2016, Rafael Bodill https://github.com/rafi # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the # "Software"), to deal in the Software without restriction, including # without limitation the rights to use, copy, modify, merge, publish, # distribute, sublicense, and/or sell copies of the Software, and to # permit persons to whom the Software is furnished to do so, subject to # the following conditions: # # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. import pylast from pylast import TopItem, _extract, _number from beets import config, dbcore, plugins, ui from beets.dbcore import types API_URL = "https://ws.audioscrobbler.com/2.0/" class LastImportPlugin(plugins.BeetsPlugin): def __init__(self): super().__init__() config["lastfm"].add( { "user": "", "api_key": plugins.LASTFM_KEY, } ) config["lastfm"]["api_key"].redact = True self.config.add( { "per_page": 500, "retry_limit": 3, } ) self.item_types = { "play_count": types.INTEGER, } def commands(self): cmd = ui.Subcommand("lastimport", help="import last.fm play-count") def func(lib, opts, args): import_lastfm(lib, self._log) cmd.func = func return [cmd] class CustomUser(pylast.User): """Custom user class derived from pylast.User, and overriding the _get_things method to return MBID and album. Also introduces new get_top_tracks_by_page method to allow access to more than one page of top tracks. """ def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) def _get_things( self, method, thing, thing_type, params=None, cacheable=True ): """Returns a list of the most played thing_types by this thing, in a tuple with the total number of pages of results. Includes an MBID, if found. """ doc = self._request(self.ws_prefix + "." + method, cacheable, params) toptracks_node = doc.getElementsByTagName("toptracks")[0] total_pages = int(toptracks_node.getAttribute("totalPages")) seq = [] for node in doc.getElementsByTagName(thing): title = _extract(node, "name") artist = _extract(node, "name", 1) mbid = _extract(node, "mbid") playcount = _number(_extract(node, "playcount")) thing = thing_type(artist, title, self.network) thing.mbid = mbid seq.append(TopItem(thing, playcount)) return seq, total_pages def get_top_tracks_by_page( self, period=pylast.PERIOD_OVERALL, limit=None, page=1, cacheable=True ): """Returns the top tracks played by a user, in a tuple with the total number of pages of results. * period: The period of time. Possible values: o PERIOD_OVERALL o PERIOD_7DAYS o PERIOD_1MONTH o PERIOD_3MONTHS o PERIOD_6MONTHS o PERIOD_12MONTHS """ params = self._get_params() params["period"] = period params["page"] = page if limit: params["limit"] = limit return self._get_things( "getTopTracks", "track", pylast.Track, params, cacheable ) def import_lastfm(lib, log): user = config["lastfm"]["user"].as_str() per_page = config["lastimport"]["per_page"].get(int) if not user: raise ui.UserError("You must specify a user name for lastimport") log.info("Fetching last.fm library for @{0}", user) page_total = 1 page_current = 0 found_total = 0 unknown_total = 0 retry_limit = config["lastimport"]["retry_limit"].get(int) # Iterate through a yet to be known page total count while page_current < page_total: log.info( "Querying page #{0}{1}...", page_current + 1, f"/{page_total}" if page_total > 1 else "", ) for retry in range(0, retry_limit): tracks, page_total = fetch_tracks(user, page_current + 1, per_page) if page_total < 1: # It means nothing to us! raise ui.UserError("Last.fm reported no data.") if tracks: found, unknown = process_tracks(lib, tracks, log) found_total += found unknown_total += unknown break else: log.error("ERROR: unable to read page #{0}", page_current + 1) if retry < retry_limit: log.info( "Retrying page #{0}... ({1}/{2} retry)", page_current + 1, retry + 1, retry_limit, ) else: log.error( "FAIL: unable to fetch page #{0}, ", "tried {1} times", page_current, retry + 1, ) page_current += 1 log.info("... done!") log.info("finished processing {0} song pages", page_total) log.info("{0} unknown play-counts", unknown_total) log.info("{0} play-counts imported", found_total) def fetch_tracks(user, page, limit): """JSON format: [ { "mbid": "...", "artist": "...", "title": "...", "playcount": "..." } ] """ network = pylast.LastFMNetwork(api_key=config["lastfm"]["api_key"]) user_obj = CustomUser(user, network) results, total_pages = user_obj.get_top_tracks_by_page( limit=limit, page=page ) return [ { "mbid": track.item.mbid if track.item.mbid else "", "artist": {"name": track.item.artist.name}, "name": track.item.title, "playcount": track.weight, } for track in results ], total_pages def process_tracks(lib, tracks, log): total = len(tracks) total_found = 0 total_fails = 0 log.info("Received {0} tracks in this page, processing...", total) for num in range(0, total): song = None trackid = tracks[num]["mbid"].strip() if tracks[num]["mbid"] else None artist = ( tracks[num]["artist"].get("name", "").strip() if tracks[num]["artist"].get("name", "") else None ) title = tracks[num]["name"].strip() if tracks[num]["name"] else None album = "" if "album" in tracks[num]: album = ( tracks[num]["album"].get("name", "").strip() if tracks[num]["album"] else None ) log.debug("query: {0} - {1} ({2})", artist, title, album) # First try to query by musicbrainz's trackid if trackid: song = lib.items( dbcore.query.MatchQuery("mb_trackid", trackid) ).get() # If not, try just album/title if song is None: log.debug( "no album match, trying by album/title: {0} - {1}", album, title ) query = dbcore.AndQuery( [ dbcore.query.SubstringQuery("album", album), dbcore.query.SubstringQuery("title", title), ] ) song = lib.items(query).get() # If not, try just artist/title if song is None: log.debug("no album match, trying by artist/title") query = dbcore.AndQuery( [ dbcore.query.SubstringQuery("artist", artist), dbcore.query.SubstringQuery("title", title), ] ) song = lib.items(query).get() # Last resort, try just replacing to utf-8 quote if song is None: title = title.replace("'", "\u2019") log.debug("no title match, trying utf-8 single quote") query = dbcore.AndQuery( [ dbcore.query.SubstringQuery("artist", artist), dbcore.query.SubstringQuery("title", title), ] ) song = lib.items(query).get() if song is not None: count = int(song.get("play_count", 0)) new_count = int(tracks[num].get("playcount", 1)) log.debug( "match: {0} - {1} ({2}) " "updating: play_count {3} => {4}", song.artist, song.title, song.album, count, new_count, ) song["play_count"] = new_count song.store() total_found += 1 else: total_fails += 1 log.info(" - No match: {0} - {1} ({2})", artist, title, album) if total_fails > 0: log.info( "Acquired {0}/{1} play-counts ({2} unknown)", total_found, total, total_fails, ) return total_found, total_fails ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������beetbox-beets-01f1faf/beetsplug/limit.py������������������������������������������������������������0000664�0000000�0000000�00000005735�14723254774�0020443�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# This file is part of beets. # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the # "Software"), to deal in the Software without restriction, including # without limitation the rights to use, copy, modify, merge, publish, # distribute, sublicense, and/or sell copies of the Software, and to # permit persons to whom the Software is furnished to do so, subject to # the following conditions: # # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. """Adds head/tail functionality to list/ls. 1. Implemented as `lslimit` command with `--head` and `--tail` options. This is the idiomatic way to use this plugin. 2. Implemented as query prefix `<` for head functionality only. This is the composable way to use the plugin (plays nicely with anything that uses the query language). """ from collections import deque from itertools import islice from beets.dbcore import FieldQuery from beets.plugins import BeetsPlugin from beets.ui import Subcommand, decargs, print_ def lslimit(lib, opts, args): """Query command with head/tail.""" if (opts.head is not None) and (opts.tail is not None): raise ValueError("Only use one of --head and --tail") if (opts.head or opts.tail or 0) < 0: raise ValueError("Limit value must be non-negative") query = decargs(args) if opts.album: objs = lib.albums(query) else: objs = lib.items(query) if opts.head is not None: objs = islice(objs, opts.head) elif opts.tail is not None: objs = deque(objs, opts.tail) for obj in objs: print_(format(obj)) lslimit_cmd = Subcommand("lslimit", help="query with optional head or tail") lslimit_cmd.parser.add_option( "--head", action="store", type="int", default=None ) lslimit_cmd.parser.add_option( "--tail", action="store", type="int", default=None ) lslimit_cmd.parser.add_all_common_options() lslimit_cmd.func = lslimit class LimitPlugin(BeetsPlugin): """Query limit functionality via command and query prefix.""" def commands(self): """Expose `lslimit` subcommand.""" return [lslimit_cmd] def queries(self): class HeadQuery(FieldQuery): """This inner class pattern allows the query to track state.""" n = 0 N = None def __init__(self, *args, **kwargs) -> None: """Force the query to be slow so that 'value_match' is called.""" super().__init__(*args, **kwargs) self.fast = False @classmethod def value_match(cls, pattern, value): if cls.N is None: cls.N = int(pattern) if cls.N < 0: raise ValueError("Limit value must be non-negative") cls.n += 1 return cls.n <= cls.N return {"<": HeadQuery} �����������������������������������beetbox-beets-01f1faf/beetsplug/listenbrainz.py�����������������������������������������������������0000664�0000000�0000000�00000023737�14723254774�0022033�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������"""Adds Listenbrainz support to Beets.""" import datetime import musicbrainzngs import requests from beets import config, ui from beets.plugins import BeetsPlugin from beetsplug.lastimport import process_tracks class ListenBrainzPlugin(BeetsPlugin): """A Beets plugin for interacting with ListenBrainz.""" data_source = "ListenBrainz" ROOT = "http://api.listenbrainz.org/1/" def __init__(self): """Initialize the plugin.""" super().__init__() self.token = self.config["token"].get() self.username = self.config["username"].get() self.AUTH_HEADER = {"Authorization": f"Token {self.token}"} config["listenbrainz"]["token"].redact = True def commands(self): """Add beet UI commands to interact with ListenBrainz.""" lbupdate_cmd = ui.Subcommand( "lbimport", help=f"Import {self.data_source} history" ) def func(lib, opts, args): self._lbupdate(lib, self._log) lbupdate_cmd.func = func return [lbupdate_cmd] def _lbupdate(self, lib, log): """Obtain view count from Listenbrainz.""" found_total = 0 unknown_total = 0 ls = self.get_listens() tracks = self.get_tracks_from_listens(ls) log.info(f"Found {len(ls)} listens") if tracks: found, unknown = process_tracks(lib, tracks, log) found_total += found unknown_total += unknown log.info("... done!") log.info("{0} unknown play-counts", unknown_total) log.info("{0} play-counts imported", found_total) def _make_request(self, url, params=None): """Makes a request to the ListenBrainz API.""" try: response = requests.get( url=url, headers=self.AUTH_HEADER, timeout=10, params=params, ) response.raise_for_status() return response.json() except requests.exceptions.RequestException as e: self._log.debug(f"Invalid Search Error: {e}") return None def get_listens(self, min_ts=None, max_ts=None, count=None): """Gets the listen history of a given user. Args: username: User to get listen history of. min_ts: History before this timestamp will not be returned. DO NOT USE WITH max_ts. max_ts: History after this timestamp will not be returned. DO NOT USE WITH min_ts. count: How many listens to return. If not specified, uses a default from the server. Returns: A list of listen info dictionaries if there's an OK status. Raises: An HTTPError if there's a failure. A ValueError if the JSON in the response is invalid. An IndexError if the JSON is not structured as expected. """ url = f"{self.ROOT}/user/{self.username}/listens" params = { k: v for k, v in { "min_ts": min_ts, "max_ts": max_ts, "count": count, }.items() if v is not None } response = self._make_request(url, params) if response is not None: return response["payload"]["listens"] else: return None def get_tracks_from_listens(self, listens): """Returns a list of tracks from a list of listens.""" tracks = [] for track in listens: if track["track_metadata"].get("release_name") is None: continue mbid_mapping = track["track_metadata"].get("mbid_mapping", {}) # print(json.dumps(track, indent=4, sort_keys=True)) if mbid_mapping.get("recording_mbid") is None: # search for the track using title and release mbid = self.get_mb_recording_id(track) tracks.append( { "album": { "name": track["track_metadata"].get("release_name") }, "name": track["track_metadata"].get("track_name"), "artist": { "name": track["track_metadata"].get("artist_name") }, "mbid": mbid, "release_mbid": mbid_mapping.get("release_mbid"), "listened_at": track.get("listened_at"), } ) return tracks def get_mb_recording_id(self, track): """Returns the MusicBrainz recording ID for a track.""" resp = musicbrainzngs.search_recordings( query=track["track_metadata"].get("track_name"), release=track["track_metadata"].get("release_name"), strict=True, ) if resp.get("recording-count") == "1": return resp.get("recording-list")[0].get("id") else: return None def get_playlists_createdfor(self, username): """Returns a list of playlists created by a user.""" url = f"{self.ROOT}/user/{username}/playlists/createdfor" return self._make_request(url) def get_listenbrainz_playlists(self): resp = self.get_playlists_createdfor(self.username) playlists = resp.get("playlists") listenbrainz_playlists = [] for playlist in playlists: playlist_info = playlist.get("playlist") if playlist_info.get("creator") == "listenbrainz": title = playlist_info.get("title") self._log.debug(f"Playlist title: {title}") playlist_type = ( "Exploration" if "Exploration" in title else "Jams" ) if "week of" in title: date_str = title.split("week of ")[1].split(" ")[0] date = datetime.datetime.strptime( date_str, "%Y-%m-%d" ).date() else: continue identifier = playlist_info.get("identifier") id = identifier.split("/")[-1] listenbrainz_playlists.append( {"type": playlist_type, "date": date, "identifier": id} ) listenbrainz_playlists = sorted( listenbrainz_playlists, key=lambda x: x["type"] ) listenbrainz_playlists = sorted( listenbrainz_playlists, key=lambda x: x["date"], reverse=True ) for playlist in listenbrainz_playlists: self._log.debug( f'Playlist: {playlist["type"]} - {playlist["date"]}' ) return listenbrainz_playlists def get_playlist(self, identifier): """Returns a playlist.""" url = f"{self.ROOT}/playlist/{identifier}" return self._make_request(url) def get_tracks_from_playlist(self, playlist): """This function returns a list of tracks in the playlist.""" tracks = [] for track in playlist.get("playlist").get("track"): identifier = track.get("identifier") if isinstance(identifier, list): identifier = identifier[0] tracks.append( { "artist": track.get("creator", "Unknown artist"), "identifier": identifier.split("/")[-1], "title": track.get("title"), } ) return self.get_track_info(tracks) def get_track_info(self, tracks): track_info = [] for track in tracks: identifier = track.get("identifier") resp = musicbrainzngs.get_recording_by_id( identifier, includes=["releases", "artist-credits"] ) recording = resp.get("recording") title = recording.get("title") artist_credit = recording.get("artist-credit", []) if artist_credit: artist = artist_credit[0].get("artist", {}).get("name") else: artist = None releases = recording.get("release-list", []) if releases: album = releases[0].get("title") date = releases[0].get("date") year = date.split("-")[0] if date else None else: album = None year = None track_info.append( { "identifier": identifier, "title": title, "artist": artist, "album": album, "year": year, } ) return track_info def get_weekly_playlist(self, playlist_type, most_recent=True): # Fetch all playlists playlists = self.get_listenbrainz_playlists() # Filter playlists by type filtered_playlists = [ p for p in playlists if p["type"] == playlist_type ] # Sort playlists by date in descending order sorted_playlists = sorted( filtered_playlists, key=lambda x: x["date"], reverse=True ) # Select the most recent or older playlist based on the most_recent flag selected_playlist = ( sorted_playlists[0] if most_recent else sorted_playlists[1] ) self._log.debug( f"Selected playlist: {selected_playlist['type']} " f"- {selected_playlist['date']}" ) # Fetch and return tracks from the selected playlist playlist = self.get_playlist(selected_playlist.get("identifier")) return self.get_tracks_from_playlist(playlist) def get_weekly_exploration(self): return self.get_weekly_playlist("Exploration", most_recent=True) def get_weekly_jams(self): return self.get_weekly_playlist("Jams", most_recent=True) def get_last_weekly_exploration(self): return self.get_weekly_playlist("Exploration", most_recent=False) def get_last_weekly_jams(self): return self.get_weekly_playlist("Jams", most_recent=False) ���������������������������������beetbox-beets-01f1faf/beetsplug/loadext.py����������������������������������������������������������0000664�0000000�0000000�00000002752�14723254774�0020761�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# This file is part of beets. # Copyright 2019, Jack Wilsdon <jack.wilsdon@gmail.com> # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the # "Software"), to deal in the Software without restriction, including # without limitation the rights to use, copy, modify, merge, publish, # distribute, sublicense, and/or sell copies of the Software, and to # permit persons to whom the Software is furnished to do so, subject to # the following conditions: # # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. """Load SQLite extensions.""" import sqlite3 from beets.dbcore import Database from beets.plugins import BeetsPlugin class LoadExtPlugin(BeetsPlugin): def __init__(self): super().__init__() if not Database.supports_extensions: self._log.warn( "loadext is enabled but the current SQLite " "installation does not support extensions" ) return self.register_listener("library_opened", self.library_opened) def library_opened(self, lib): for v in self.config: ext = v.as_filename() self._log.debug("loading extension {}", ext) try: lib.load_extension(ext) except sqlite3.OperationalError as e: self._log.error("failed to load extension {}: {}", ext, e) ����������������������beetbox-beets-01f1faf/beetsplug/lyrics.py�����������������������������������������������������������0000664�0000000�0000000�00000105165�14723254774�0020630�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# This file is part of beets. # Copyright 2016, Adrian Sampson. # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the # "Software"), to deal in the Software without restriction, including # without limitation the rights to use, copy, modify, merge, publish, # distribute, sublicense, and/or sell copies of the Software, and to # permit persons to whom the Software is furnished to do so, subject to # the following conditions: # # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. """Fetches, embeds, and displays lyrics.""" from __future__ import annotations import difflib import errno import itertools import json import os.path import re import struct import unicodedata import warnings from functools import partial from typing import ClassVar from urllib.parse import quote, urlencode import requests from unidecode import unidecode try: import bs4 from bs4 import SoupStrainer HAS_BEAUTIFUL_SOUP = True except ImportError: HAS_BEAUTIFUL_SOUP = False try: import langdetect HAS_LANGDETECT = True except ImportError: HAS_LANGDETECT = False import beets from beets import plugins, ui DIV_RE = re.compile(r"<(/?)div>?", re.I) COMMENT_RE = re.compile(r"<!--.*-->", re.S) TAG_RE = re.compile(r"<[^>]*>") BREAK_RE = re.compile(r"\n?\s*<br([\s|/][^>]*)*>\s*\n?", re.I) USER_AGENT = f"beets/{beets.__version__}" # The content for the base index.rst generated in ReST mode. REST_INDEX_TEMPLATE = """Lyrics ====== * :ref:`Song index <genindex>` * :ref:`search` Artist index: .. toctree:: :maxdepth: 1 :glob: artists/* """ # The content for the base conf.py generated. REST_CONF_TEMPLATE = """# -*- coding: utf-8 -*- master_doc = 'index' project = 'Lyrics' copyright = 'none' author = 'Various Authors' latex_documents = [ (master_doc, 'Lyrics.tex', project, author, 'manual'), ] epub_title = project epub_author = author epub_publisher = author epub_copyright = copyright epub_exclude_files = ['search.html'] epub_tocdepth = 1 epub_tocdup = False """ # Utilities. def unichar(i): try: return chr(i) except ValueError: return struct.pack("i", i).decode("utf-32") def unescape(text): """Resolve &#xxx; HTML entities (and some others).""" if isinstance(text, bytes): text = text.decode("utf-8", "ignore") out = text.replace(" ", " ") def replchar(m): num = m.group(1) return unichar(int(num)) out = re.sub("&#(\\d+);", replchar, out) return out def extract_text_between(html, start_marker, end_marker): try: _, html = html.split(start_marker, 1) html, _ = html.split(end_marker, 1) except ValueError: return "" return html def search_pairs(item): """Yield a pairs of artists and titles to search for. The first item in the pair is the name of the artist, the second item is a list of song names. In addition to the artist and title obtained from the `item` the method tries to strip extra information like paranthesized suffixes and featured artists from the strings and add them as candidates. The artist sort name is added as a fallback candidate to help in cases where artist name includes special characters or is in a non-latin script. The method also tries to split multiple titles separated with `/`. """ def generate_alternatives(string, patterns): """Generate string alternatives by extracting first matching group for each given pattern. """ alternatives = [string] for pattern in patterns: match = re.search(pattern, string, re.IGNORECASE) if match: alternatives.append(match.group(1)) return alternatives title, artist, artist_sort = item.title, item.artist, item.artist_sort patterns = [ # Remove any featuring artists from the artists name rf"(.*?) {plugins.feat_tokens()}" ] artists = generate_alternatives(artist, patterns) # Use the artist_sort as fallback only if it differs from artist to avoid # repeated remote requests with the same search terms if artist != artist_sort: artists.append(artist_sort) patterns = [ # Remove a parenthesized suffix from a title string. Common # examples include (live), (remix), and (acoustic). r"(.+?)\s+[(].*[)]$", # Remove any featuring artists from the title r"(.*?) {}".format(plugins.feat_tokens(for_artist=False)), # Remove part of title after colon ':' for songs with subtitles r"(.+?)\s*:.*", ] titles = generate_alternatives(title, patterns) # Check for a dual song (e.g. Pink Floyd - Speak to Me / Breathe) # and each of them. multi_titles = [] for title in titles: multi_titles.append([title]) if "/" in title: multi_titles.append([x.strip() for x in title.split("/")]) return itertools.product(artists, multi_titles) def slug(text): """Make a URL-safe, human-readable version of the given text This will do the following: 1. decode unicode characters into ASCII 2. shift everything to lowercase 3. strip whitespace 4. replace other non-word characters with dashes 5. strip extra dashes This somewhat duplicates the :func:`Google.slugify` function but slugify is not as generic as this one, which can be reused elsewhere. """ return re.sub(r"\W+", "-", unidecode(text).lower().strip()).strip("-") if HAS_BEAUTIFUL_SOUP: def try_parse_html(html, **kwargs): return bs4.BeautifulSoup(html, "html.parser", **kwargs) else: def try_parse_html(html, **kwargs): return None class Backend: REQUIRES_BS = False def __init__(self, config, log): self._log = log self.config = config def fetch_url(self, url): """Retrieve the content at a given URL, or return None if the source is unreachable. """ try: # Disable the InsecureRequestWarning that comes from using # `verify=false`. # https://github.com/kennethreitz/requests/issues/2214 # We're not overly worried about the NSA MITMing our lyrics scraper with warnings.catch_warnings(): warnings.simplefilter("ignore") r = requests.get( url, verify=False, headers={ "User-Agent": USER_AGENT, }, timeout=10, ) except requests.RequestException as exc: self._log.debug("lyrics request failed: {0}", exc) return if r.status_code == requests.codes.ok: return r.text else: self._log.debug("failed to fetch: {0} ({1})", url, r.status_code) return None def fetch(self, artist, title, album=None, length=None): raise NotImplementedError() class LRCLib(Backend): base_url = "https://lrclib.net/api/get" def fetch(self, artist, title, album=None, length=None): params = { "artist_name": artist, "track_name": title, "album_name": album, "duration": length, } try: response = requests.get( self.base_url, params=params, timeout=10, ) data = response.json() except (requests.RequestException, json.decoder.JSONDecodeError) as exc: self._log.debug("LRCLib API request failed: {0}", exc) return None if self.config["synced"]: return data.get("syncedLyrics") return data.get("plainLyrics") class DirectBackend(Backend): """A backend for fetching lyrics directly.""" URL_TEMPLATE: ClassVar[str] #: May include formatting placeholders @classmethod def encode(cls, text: str) -> str: """Encode the string for inclusion in a URL.""" raise NotImplementedError @classmethod def build_url(cls, *args: str) -> str: return cls.URL_TEMPLATE.format(*map(cls.encode, args)) class MusiXmatch(DirectBackend): URL_TEMPLATE = "https://www.musixmatch.com/lyrics/{}/{}" REPLACEMENTS = { r"\s+": "-", "<": "Less_Than", ">": "Greater_Than", "#": "Number_", r"[\[\{]": "(", r"[\]\}]": ")", } @classmethod def encode(cls, text: str) -> str: for old, new in cls.REPLACEMENTS.items(): text = re.sub(old, new, text) return quote(unidecode(text)) def fetch(self, artist, title, album=None, length=None): url = self.build_url(artist, title) html = self.fetch_url(url) if not html: return None if "We detected that your IP is blocked" in html: self._log.warning( "we are blocked at MusixMatch: url %s failed" % url ) return None html_parts = html.split('<p class="mxm-lyrics__content') # Sometimes lyrics come in 2 or more parts lyrics_parts = [] for html_part in html_parts: lyrics_parts.append(extract_text_between(html_part, ">", "</p>")) lyrics = "\n".join(lyrics_parts) lyrics = lyrics.strip(',"').replace("\\n", "\n") # another odd case: sometimes only that string remains, for # missing songs. this seems to happen after being blocked # above, when filling in the CAPTCHA. if "Instant lyrics for all your music." in lyrics: return None # sometimes there are non-existent lyrics with some content if "Lyrics | Musixmatch" in lyrics: return None return lyrics class Genius(Backend): """Fetch lyrics from Genius via genius-api. Simply adapted from bigishdata.com/2016/09/27/getting-song-lyrics-from-geniuss-api-scraping/ """ REQUIRES_BS = True base_url = "https://api.genius.com" def __init__(self, config, log): super().__init__(config, log) self.api_key = config["genius_api_key"].as_str() self.headers = { "Authorization": "Bearer %s" % self.api_key, "User-Agent": USER_AGENT, } def fetch(self, artist, title, album=None, length=None): """Fetch lyrics from genius.com Because genius doesn't allow accessing lyrics via the api, we first query the api for a url matching our artist & title, then attempt to scrape that url for the lyrics. """ json = self._search(artist, title) if not json: self._log.debug("Genius API request returned invalid JSON") return None # find a matching artist in the json for hit in json["response"]["hits"]: hit_artist = hit["result"]["primary_artist"]["name"] if slug(hit_artist) == slug(artist): html = self.fetch_url(hit["result"]["url"]) if not html: return None return self._scrape_lyrics_from_html(html) self._log.debug( "Genius failed to find a matching artist for '{0}'", artist ) return None def _search(self, artist, title): """Searches the genius api for a given artist and title https://docs.genius.com/#search-h2 :returns: json response """ search_url = self.base_url + "/search" data = {"q": title + " " + artist.lower()} try: response = requests.get( search_url, params=data, headers=self.headers, timeout=10, ) except requests.RequestException as exc: self._log.debug("Genius API request failed: {0}", exc) return None try: return response.json() except ValueError: return None def replace_br(self, lyrics_div): for br in lyrics_div.find_all("br"): br.replace_with("\n") def _scrape_lyrics_from_html(self, html): """Scrape lyrics from a given genius.com html""" soup = try_parse_html(html) if not soup: return # Remove script tags that they put in the middle of the lyrics. [h.extract() for h in soup("script")] # Most of the time, the page contains a div with class="lyrics" where # all of the lyrics can be found already correctly formatted # Sometimes, though, it packages the lyrics into separate divs, most # likely for easier ad placement lyrics_divs = soup.find_all("div", {"data-lyrics-container": True}) if not lyrics_divs: self._log.debug("Received unusual song page html") return self._try_extracting_lyrics_from_non_data_lyrics_container( soup ) lyrics = "" for lyrics_div in lyrics_divs: self.replace_br(lyrics_div) lyrics += lyrics_div.get_text() + "\n\n" while lyrics[-1] == "\n": lyrics = lyrics[:-1] return lyrics def _try_extracting_lyrics_from_non_data_lyrics_container(self, soup): """Extract lyrics from a div without attribute data-lyrics-container This is the second most common layout on genius.com """ verse_div = soup.find("div", class_=re.compile("Lyrics__Container")) if not verse_div: if soup.find( "div", class_=re.compile("LyricsPlaceholder__Message"), string="This song is an instrumental", ): self._log.debug("Detected instrumental") return "[Instrumental]" else: self._log.debug("Couldn't scrape page using known layouts") return None lyrics_div = verse_div.parent self.replace_br(lyrics_div) ads = lyrics_div.find_all( "div", class_=re.compile("InreadAd__Container") ) for ad in ads: ad.replace_with("\n") footers = lyrics_div.find_all( "div", class_=re.compile("Lyrics__Footer") ) for footer in footers: footer.replace_with("") return lyrics_div.get_text() class Tekstowo(DirectBackend): """Fetch lyrics from Tekstowo.pl.""" REQUIRES_BS = True URL_TEMPLATE = "https://www.tekstowo.pl/piosenka,{},{}.html" non_alpha_to_underscore = partial(re.compile(r"\W").sub, "_") @classmethod def encode(cls, text: str) -> str: return cls.non_alpha_to_underscore(unidecode(text.lower())) def fetch(self, artist, title, album=None, length=None): if html := self.fetch_url(self.build_url(artist, title)): return self.extract_lyrics(html) return None def extract_lyrics(self, html: str) -> str | None: html = _scrape_strip_cruft(html) html = _scrape_merge_paragraphs(html) soup = try_parse_html(html) if lyrics_div := soup.select_one("div.song-text > div.inner-text"): return lyrics_div.get_text() return None def remove_credits(text): """Remove first/last line of text if it contains the word 'lyrics' eg 'Lyrics by songsdatabase.com' """ textlines = text.split("\n") credits = None for i in (0, -1): if textlines and "lyrics" in textlines[i].lower(): credits = textlines.pop(i) if credits: text = "\n".join(textlines) return text def _scrape_strip_cruft(html, plain_text_out=False): """Clean up HTML""" html = unescape(html) html = html.replace("\r", "\n") # Normalize EOL. html = re.sub(r" +", " ", html) # Whitespaces collapse. html = BREAK_RE.sub("\n", html) # <br> eats up surrounding '\n'. html = re.sub(r"(?s)<(script).*?</\1>", "", html) # Strip script tags. html = re.sub("\u2005", " ", html) # replace unicode with regular space if plain_text_out: # Strip remaining HTML tags html = COMMENT_RE.sub("", html) html = TAG_RE.sub("", html) html = "\n".join([x.strip() for x in html.strip().split("\n")]) html = re.sub(r"\n{3,}", r"\n\n", html) return html def _scrape_merge_paragraphs(html): html = re.sub(r"</p>\s*<p(\s*[^>]*)>", "\n", html) return re.sub(r"<div .*>\s*</div>", "\n", html) def scrape_lyrics_from_html(html): """Scrape lyrics from a URL. If no lyrics can be found, return None instead. """ def is_text_notcode(text): if not text: return False length = len(text) return ( length > 20 and text.count(" ") > length / 25 and (text.find("{") == -1 or text.find(";") == -1) ) html = _scrape_strip_cruft(html) html = _scrape_merge_paragraphs(html) # extract all long text blocks that are not code soup = try_parse_html(html, parse_only=SoupStrainer(string=is_text_notcode)) if not soup: return None # Get the longest text element (if any). strings = sorted(soup.stripped_strings, key=len, reverse=True) if strings: return strings[0] else: return None class Google(Backend): """Fetch lyrics from Google search results.""" REQUIRES_BS = True def __init__(self, config, log): super().__init__(config, log) self.api_key = config["google_API_key"].as_str() self.engine_id = config["google_engine_ID"].as_str() def is_lyrics(self, text, artist=None): """Determine whether the text seems to be valid lyrics.""" if not text: return False bad_triggers_occ = [] nb_lines = text.count("\n") if nb_lines <= 1: self._log.debug("Ignoring too short lyrics '{0}'", text) return False elif nb_lines < 5: bad_triggers_occ.append("too_short") else: # Lyrics look legit, remove credits to avoid being penalized # further down text = remove_credits(text) bad_triggers = ["lyrics", "copyright", "property", "links"] if artist: bad_triggers += [artist] for item in bad_triggers: bad_triggers_occ += [item] * len( re.findall(r"\W%s\W" % item, text, re.I) ) if bad_triggers_occ: self._log.debug("Bad triggers detected: {0}", bad_triggers_occ) return len(bad_triggers_occ) < 2 def slugify(self, text): """Normalize a string and remove non-alphanumeric characters.""" text = re.sub(r"[-'_\s]", "_", text) text = re.sub(r"_+", "_", text).strip("_") pat = r"([^,\(]*)\((.*?)\)" # Remove content within parentheses text = re.sub(pat, r"\g<1>", text).strip() try: text = unicodedata.normalize("NFKD", text).encode("ascii", "ignore") text = str(re.sub(r"[-\s]+", " ", text.decode("utf-8"))) except UnicodeDecodeError: self._log.exception("Failing to normalize '{0}'", text) return text BY_TRANS = ["by", "par", "de", "von"] LYRICS_TRANS = ["lyrics", "paroles", "letras", "liedtexte"] def is_page_candidate(self, url_link, url_title, title, artist): """Return True if the URL title makes it a good candidate to be a page that contains lyrics of title by artist. """ title = self.slugify(title.lower()) artist = self.slugify(artist.lower()) sitename = re.search( "//([^/]+)/.*", self.slugify(url_link.lower()) ).group(1) url_title = self.slugify(url_title.lower()) # Check if URL title contains song title (exact match) if url_title.find(title) != -1: return True # or try extracting song title from URL title and check if # they are close enough tokens = ( [by + "_" + artist for by in self.BY_TRANS] + [artist, sitename, sitename.replace("www.", "")] + self.LYRICS_TRANS ) tokens = [re.escape(t) for t in tokens] song_title = re.sub("(%s)" % "|".join(tokens), "", url_title) song_title = song_title.strip("_|") typo_ratio = 0.9 ratio = difflib.SequenceMatcher(None, song_title, title).ratio() return ratio >= typo_ratio def fetch(self, artist, title, album=None, length=None): query = f"{artist} {title}" url = "https://www.googleapis.com/customsearch/v1?key=%s&cx=%s&q=%s" % ( self.api_key, self.engine_id, quote(query.encode("utf-8")), ) data = self.fetch_url(url) if not data: self._log.debug("google backend returned no data") return None try: data = json.loads(data) except ValueError as exc: self._log.debug("google backend returned malformed JSON: {}", exc) if "error" in data: reason = data["error"]["errors"][0]["reason"] self._log.debug("google backend error: {0}", reason) return None if "items" in data.keys(): for item in data["items"]: url_link = item["link"] url_title = item.get("title", "") if not self.is_page_candidate( url_link, url_title, title, artist ): continue html = self.fetch_url(url_link) if not html: continue lyrics = scrape_lyrics_from_html(html) if not lyrics: continue if self.is_lyrics(lyrics, artist): self._log.debug("got lyrics from {0}", item["displayLink"]) return lyrics return None class LyricsPlugin(plugins.BeetsPlugin): SOURCES = ["google", "musixmatch", "genius", "tekstowo", "lrclib"] SOURCE_BACKENDS = { "google": Google, "musixmatch": MusiXmatch, "genius": Genius, "tekstowo": Tekstowo, "lrclib": LRCLib, } def __init__(self): super().__init__() self.import_stages = [self.imported] self.config.add( { "auto": True, "bing_client_secret": None, "bing_lang_from": [], "bing_lang_to": None, "google_API_key": None, "google_engine_ID": "009217259823014548361:lndtuqkycfu", "genius_api_key": "Ryq93pUGm8bM6eUWwD_M3NOFFDAtp2yEE7W" "76V-uFL5jks5dNvcGCdarqFjDhP9c", "fallback": None, "force": False, "local": False, "synced": False, # Musixmatch is disabled by default as they are currently blocking # requests with the beets user agent. "sources": [s for s in self.SOURCES if s != "musixmatch"], "dist_thresh": 0.1, } ) self.config["bing_client_secret"].redact = True self.config["google_API_key"].redact = True self.config["google_engine_ID"].redact = True self.config["genius_api_key"].redact = True # State information for the ReST writer. # First, the current artist we're writing. self.artist = "Unknown artist" # The current album: False means no album yet. self.album = False # The current rest file content. None means the file is not # open yet. self.rest = None available_sources = list(self.SOURCES) sources = plugins.sanitize_choices( self.config["sources"].as_str_seq(), available_sources ) if not HAS_BEAUTIFUL_SOUP: sources = self.sanitize_bs_sources(sources) if "google" in sources: if not self.config["google_API_key"].get(): # We log a *debug* message here because the default # configuration includes `google`. This way, the source # is silent by default but can be enabled just by # setting an API key. self._log.debug( "Disabling google source: " "no API key configured." ) sources.remove("google") self.config["bing_lang_from"] = [ x.lower() for x in self.config["bing_lang_from"].as_str_seq() ] self.bing_auth_token = None if not HAS_LANGDETECT and self.config["bing_client_secret"].get(): self._log.warning( "To use bing translations, you need to " "install the langdetect module. See the " "documentation for further details." ) self.backends = [ self.SOURCE_BACKENDS[source](self.config, self._log) for source in sources ] def sanitize_bs_sources(self, sources): enabled_sources = [] for source in sources: if self.SOURCE_BACKENDS[source].REQUIRES_BS: self._log.debug( "To use the %s lyrics source, you must " "install the beautifulsoup4 module. See " "the documentation for further details." % source ) else: enabled_sources.append(source) return enabled_sources def get_bing_access_token(self): params = { "client_id": "beets", "client_secret": self.config["bing_client_secret"], "scope": "https://api.microsofttranslator.com", "grant_type": "client_credentials", } oauth_url = "https://datamarket.accesscontrol.windows.net/v2/OAuth2-13" oauth_token = json.loads( requests.post( oauth_url, data=urlencode(params), timeout=10, ).content ) if "access_token" in oauth_token: return "Bearer " + oauth_token["access_token"] else: self._log.warning( "Could not get Bing Translate API access token." ' Check your "bing_client_secret" password' ) def commands(self): cmd = ui.Subcommand("lyrics", help="fetch song lyrics") cmd.parser.add_option( "-p", "--print", dest="printlyr", action="store_true", default=False, help="print lyrics to console", ) cmd.parser.add_option( "-r", "--write-rest", dest="writerest", action="store", default=None, metavar="dir", help="write lyrics to given directory as ReST files", ) cmd.parser.add_option( "-f", "--force", dest="force_refetch", action="store_true", default=False, help="always re-download lyrics", ) cmd.parser.add_option( "-l", "--local", dest="local_only", action="store_true", default=False, help="do not fetch missing lyrics", ) def func(lib, opts, args): # The "write to files" option corresponds to the # import_write config value. write = ui.should_write() if opts.writerest: self.writerest_indexes(opts.writerest) items = lib.items(ui.decargs(args)) for item in items: if not opts.local_only and not self.config["local"]: self.fetch_item_lyrics( lib, item, write, opts.force_refetch or self.config["force"], ) if item.lyrics: if opts.printlyr: ui.print_(item.lyrics) if opts.writerest: self.appendrest(opts.writerest, item) if opts.writerest and items: # flush last artist & write to ReST self.writerest(opts.writerest) ui.print_("ReST files generated. to build, use one of:") ui.print_( " sphinx-build -b html %s _build/html" % opts.writerest ) ui.print_( " sphinx-build -b epub %s _build/epub" % opts.writerest ) ui.print_( ( " sphinx-build -b latex %s _build/latex " "&& make -C _build/latex all-pdf" ) % opts.writerest ) cmd.func = func return [cmd] def appendrest(self, directory, item): """Append the item to an ReST file This will keep state (in the `rest` variable) in order to avoid writing continuously to the same files. """ if slug(self.artist) != slug(item.albumartist): # Write current file and start a new one ~ item.albumartist self.writerest(directory) self.artist = item.albumartist.strip() self.rest = "%s\n%s\n\n.. contents::\n :local:\n\n" % ( self.artist, "=" * len(self.artist), ) if self.album != item.album: tmpalbum = self.album = item.album.strip() if self.album == "": tmpalbum = "Unknown album" self.rest += "{}\n{}\n\n".format(tmpalbum, "-" * len(tmpalbum)) title_str = ":index:`%s`" % item.title.strip() block = "| " + item.lyrics.replace("\n", "\n| ") self.rest += "{}\n{}\n\n{}\n\n".format( title_str, "~" * len(title_str), block ) def writerest(self, directory): """Write self.rest to a ReST file""" if self.rest is not None and self.artist is not None: path = os.path.join( directory, "artists", slug(self.artist) + ".rst" ) with open(path, "wb") as output: output.write(self.rest.encode("utf-8")) def writerest_indexes(self, directory): """Write conf.py and index.rst files necessary for Sphinx We write minimal configurations that are necessary for Sphinx to operate. We do not overwrite existing files so that customizations are respected.""" try: os.makedirs(os.path.join(directory, "artists")) except OSError as e: if e.errno == errno.EEXIST: pass else: raise indexfile = os.path.join(directory, "index.rst") if not os.path.exists(indexfile): with open(indexfile, "w") as output: output.write(REST_INDEX_TEMPLATE) conffile = os.path.join(directory, "conf.py") if not os.path.exists(conffile): with open(conffile, "w") as output: output.write(REST_CONF_TEMPLATE) def imported(self, session, task): """Import hook for fetching lyrics automatically.""" if self.config["auto"]: for item in task.imported_items(): self.fetch_item_lyrics( session.lib, item, False, self.config["force"] ) def fetch_item_lyrics(self, lib, item, write, force): """Fetch and store lyrics for a single item. If ``write``, then the lyrics will also be written to the file itself. """ # Skip if the item already has lyrics. if not force and item.lyrics: self._log.info("lyrics already present: {0}", item) return lyrics = None album = item.album length = round(item.length) for artist, titles in search_pairs(item): lyrics = [ self.get_lyrics(artist, title, album=album, length=length) for title in titles ] if any(lyrics): break lyrics = "\n\n---\n\n".join(filter(None, lyrics)) if lyrics: self._log.info("fetched lyrics: {0}", item) if HAS_LANGDETECT and self.config["bing_client_secret"].get(): lang_from = langdetect.detect(lyrics) if self.config["bing_lang_to"].get() != lang_from and ( not self.config["bing_lang_from"] or (lang_from in self.config["bing_lang_from"].as_str_seq()) ): lyrics = self.append_translation( lyrics, self.config["bing_lang_to"] ) else: self._log.info("lyrics not found: {0}", item) fallback = self.config["fallback"].get() if fallback: lyrics = fallback else: return item.lyrics = lyrics if write: item.try_write() item.store() def get_lyrics(self, artist, title, album=None, length=None): """Fetch lyrics, trying each source in turn. Return a string or None if no lyrics were found. """ for backend in self.backends: lyrics = backend.fetch(artist, title, album=album, length=length) if lyrics: self._log.debug( "got lyrics from backend: {0}", backend.__class__.__name__ ) return _scrape_strip_cruft(lyrics, True) def append_translation(self, text, to_lang): from xml.etree import ElementTree if not self.bing_auth_token: self.bing_auth_token = self.get_bing_access_token() if self.bing_auth_token: # Extract unique lines to limit API request size per song text_lines = set(text.split("\n")) url = ( "https://api.microsofttranslator.com/v2/Http.svc/" "Translate?text=%s&to=%s" % ("|".join(text_lines), to_lang) ) r = requests.get( url, headers={"Authorization ": self.bing_auth_token}, timeout=10, ) if r.status_code != 200: self._log.debug( "translation API error {}: {}", r.status_code, r.text ) if "token has expired" in r.text: self.bing_auth_token = None return self.append_translation(text, to_lang) return text lines_translated = ElementTree.fromstring( r.text.encode("utf-8") ).text # Use a translation mapping dict to build resulting lyrics translations = dict(zip(text_lines, lines_translated.split("|"))) result = "" for line in text.split("\n"): result += "{} / {}\n".format(line, translations[line]) return result �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������beetbox-beets-01f1faf/beetsplug/mbcollection.py�����������������������������������������������������0000664�0000000�0000000�00000013624�14723254774�0021773�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# This file is part of beets. # Copyright (c) 2011, Jeffrey Aylesworth <mail@jeffrey.red> # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the # "Software"), to deal in the Software without restriction, including # without limitation the rights to use, copy, modify, merge, publish, # distribute, sublicense, and/or sell copies of the Software, and to # permit persons to whom the Software is furnished to do so, subject to # the following conditions: # # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. import re import musicbrainzngs from beets import config, ui from beets.plugins import BeetsPlugin from beets.ui import Subcommand SUBMISSION_CHUNK_SIZE = 200 FETCH_CHUNK_SIZE = 100 UUID_REGEX = r"^[a-f0-9]{8}(-[a-f0-9]{4}){3}-[a-f0-9]{12}$" def mb_call(func, *args, **kwargs): """Call a MusicBrainz API function and catch exceptions.""" try: return func(*args, **kwargs) except musicbrainzngs.AuthenticationError: raise ui.UserError("authentication with MusicBrainz failed") except (musicbrainzngs.ResponseError, musicbrainzngs.NetworkError) as exc: raise ui.UserError(f"MusicBrainz API error: {exc}") except musicbrainzngs.UsageError: raise ui.UserError("MusicBrainz credentials missing") def submit_albums(collection_id, release_ids): """Add all of the release IDs to the indicated collection. Multiple requests are made if there are many release IDs to submit. """ for i in range(0, len(release_ids), SUBMISSION_CHUNK_SIZE): chunk = release_ids[i : i + SUBMISSION_CHUNK_SIZE] mb_call(musicbrainzngs.add_releases_to_collection, collection_id, chunk) class MusicBrainzCollectionPlugin(BeetsPlugin): def __init__(self): super().__init__() config["musicbrainz"]["pass"].redact = True musicbrainzngs.auth( config["musicbrainz"]["user"].as_str(), config["musicbrainz"]["pass"].as_str(), ) self.config.add( { "auto": False, "collection": "", "remove": False, } ) if self.config["auto"]: self.import_stages = [self.imported] def _get_collection(self): collections = mb_call(musicbrainzngs.get_collections) if not collections["collection-list"]: raise ui.UserError("no collections exist for user") # Get all collection IDs, avoiding event collections collection_ids = [x["id"] for x in collections["collection-list"]] if not collection_ids: raise ui.UserError("No collection found.") # Check that the collection exists so we can present a nice error collection = self.config["collection"].as_str() if collection: if collection not in collection_ids: raise ui.UserError( "invalid collection ID: {}".format(collection) ) return collection # No specified collection. Just return the first collection ID return collection_ids[0] def _get_albums_in_collection(self, id): def _fetch(offset): res = mb_call( musicbrainzngs.get_releases_in_collection, id, limit=FETCH_CHUNK_SIZE, offset=offset, )["collection"] return [x["id"] for x in res["release-list"]], res["release-count"] offset = 0 albums_in_collection, release_count = _fetch(offset) for i in range(0, release_count, FETCH_CHUNK_SIZE): albums_in_collection += _fetch(offset)[0] offset += FETCH_CHUNK_SIZE return albums_in_collection def commands(self): mbupdate = Subcommand("mbupdate", help="Update MusicBrainz collection") mbupdate.parser.add_option( "-r", "--remove", action="store_true", default=None, dest="remove", help="Remove albums not in beets library", ) mbupdate.func = self.update_collection return [mbupdate] def remove_missing(self, collection_id, lib_albums): lib_ids = {x.mb_albumid for x in lib_albums} albums_in_collection = self._get_albums_in_collection(collection_id) remove_me = list(set(albums_in_collection) - lib_ids) for i in range(0, len(remove_me), FETCH_CHUNK_SIZE): chunk = remove_me[i : i + FETCH_CHUNK_SIZE] mb_call( musicbrainzngs.remove_releases_from_collection, collection_id, chunk, ) def update_collection(self, lib, opts, args): self.config.set_args(opts) remove_missing = self.config["remove"].get(bool) self.update_album_list(lib, lib.albums(), remove_missing) def imported(self, session, task): """Add each imported album to the collection.""" if task.is_album: self.update_album_list(session.lib, [task.album]) def update_album_list(self, lib, album_list, remove_missing=False): """Update the MusicBrainz collection from a list of Beets albums""" collection_id = self._get_collection() # Get a list of all the album IDs. album_ids = [] for album in album_list: aid = album.mb_albumid if aid: if re.match(UUID_REGEX, aid): album_ids.append(aid) else: self._log.info("skipping invalid MBID: {0}", aid) # Submit to MusicBrainz. self._log.info("Updating MusicBrainz collection {0}...", collection_id) submit_albums(collection_id, album_ids) if remove_missing: self.remove_missing(collection_id, lib.albums()) self._log.info("...MusicBrainz collection updated.") ������������������������������������������������������������������������������������������������������������beetbox-beets-01f1faf/beetsplug/mbsubmit.py���������������������������������������������������������0000664�0000000�0000000�00000006532�14723254774�0021143�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# This file is part of beets. # Copyright 2016, Adrian Sampson and Diego Moreda. # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the # "Software"), to deal in the Software without restriction, including # without limitation the rights to use, copy, modify, merge, publish, # distribute, sublicense, and/or sell copies of the Software, and to # permit persons to whom the Software is furnished to do so, subject to # the following conditions: # # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. """Aid in submitting information to MusicBrainz. This plugin allows the user to print track information in a format that is parseable by the MusicBrainz track parser [1]. Programmatic submitting is not implemented by MusicBrainz yet. [1] https://wiki.musicbrainz.org/History:How_To_Parse_Track_Listings """ import subprocess from beets import ui from beets.autotag import Recommendation from beets.plugins import BeetsPlugin from beets.ui.commands import PromptChoice from beets.util import displayable_path from beetsplug.info import print_data class MBSubmitPlugin(BeetsPlugin): def __init__(self): super().__init__() self.config.add( { "format": "$track. $title - $artist ($length)", "threshold": "medium", "picard_path": "picard", } ) # Validate and store threshold. self.threshold = self.config["threshold"].as_choice( { "none": Recommendation.none, "low": Recommendation.low, "medium": Recommendation.medium, "strong": Recommendation.strong, } ) self.register_listener( "before_choose_candidate", self.before_choose_candidate_event ) def before_choose_candidate_event(self, session, task): if task.rec <= self.threshold: return [ PromptChoice("p", "Print tracks", self.print_tracks), PromptChoice("o", "Open files with Picard", self.picard), ] def picard(self, session, task): paths = [] for p in task.paths: paths.append(displayable_path(p)) try: picard_path = self.config["picard_path"].as_str() subprocess.Popen([picard_path] + paths) self._log.info("launched picard from\n{}", picard_path) except OSError as exc: self._log.error(f"Could not open picard, got error:\n{exc}") def print_tracks(self, session, task): for i in sorted(task.items, key=lambda i: i.track): print_data(None, i, self.config["format"].as_str()) def commands(self): """Add beet UI commands for mbsubmit.""" mbsubmit_cmd = ui.Subcommand( "mbsubmit", help="Submit Tracks to MusicBrainz" ) def func(lib, opts, args): items = lib.items(ui.decargs(args)) self._mbsubmit(items) mbsubmit_cmd.func = func return [mbsubmit_cmd] def _mbsubmit(self, items): """Print track information to be submitted to MusicBrainz.""" for i in sorted(items, key=lambda i: i.track): print_data(None, i, self.config["format"].as_str()) ����������������������������������������������������������������������������������������������������������������������������������������������������������������������beetbox-beets-01f1faf/beetsplug/mbsync.py�����������������������������������������������������������0000664�0000000�0000000�00000017107�14723254774�0020614�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# This file is part of beets. # Copyright 2016, Jakob Schnitzer. # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the # "Software"), to deal in the Software without restriction, including # without limitation the rights to use, copy, modify, merge, publish, # distribute, sublicense, and/or sell copies of the Software, and to # permit persons to whom the Software is furnished to do so, subject to # the following conditions: # # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. """Update library's tags using MusicBrainz.""" import re from collections import defaultdict from beets import autotag, library, ui, util from beets.autotag import hooks from beets.plugins import BeetsPlugin, apply_item_changes MBID_REGEX = r"(\d|\w){8}-(\d|\w){4}-(\d|\w){4}-(\d|\w){4}-(\d|\w){12}" class MBSyncPlugin(BeetsPlugin): def __init__(self): super().__init__() def commands(self): cmd = ui.Subcommand("mbsync", help="update metadata from musicbrainz") cmd.parser.add_option( "-p", "--pretend", action="store_true", help="show all changes but do nothing", ) cmd.parser.add_option( "-m", "--move", action="store_true", dest="move", help="move files in the library directory", ) cmd.parser.add_option( "-M", "--nomove", action="store_false", dest="move", help="don't move files in library", ) cmd.parser.add_option( "-W", "--nowrite", action="store_false", default=None, dest="write", help="don't write updated metadata to files", ) cmd.parser.add_format_option() cmd.func = self.func return [cmd] def func(self, lib, opts, args): """Command handler for the mbsync function.""" move = ui.should_move(opts.move) pretend = opts.pretend write = ui.should_write(opts.write) query = ui.decargs(args) self.singletons(lib, query, move, pretend, write) self.albums(lib, query, move, pretend, write) def singletons(self, lib, query, move, pretend, write): """Retrieve and apply info from the autotagger for items matched by query. """ for item in lib.items(query + ["singleton:true"]): item_formatted = format(item) if not item.mb_trackid: self._log.info( "Skipping singleton with no mb_trackid: {0}", item_formatted ) continue # Do we have a valid MusicBrainz track ID? if not re.match(MBID_REGEX, item.mb_trackid): self._log.info( "Skipping singleton with invalid mb_trackid:" + " {0}", item_formatted, ) continue # Get the MusicBrainz recording info. track_info = hooks.track_for_mbid(item.mb_trackid) if not track_info: self._log.info( "Recording ID not found: {0} for track {0}", item.mb_trackid, item_formatted, ) continue # Apply. with lib.transaction(): autotag.apply_item_metadata(item, track_info) apply_item_changes(lib, item, move, pretend, write) def albums(self, lib, query, move, pretend, write): """Retrieve and apply info from the autotagger for albums matched by query and their items. """ # Process matching albums. for a in lib.albums(query): album_formatted = format(a) if not a.mb_albumid: self._log.info( "Skipping album with no mb_albumid: {0}", album_formatted ) continue items = list(a.items()) # Do we have a valid MusicBrainz album ID? if not re.match(MBID_REGEX, a.mb_albumid): self._log.info( "Skipping album with invalid mb_albumid: {0}", album_formatted, ) continue # Get the MusicBrainz album information. album_info = hooks.album_for_mbid(a.mb_albumid) if not album_info: self._log.info( "Release ID {0} not found for album {1}", a.mb_albumid, album_formatted, ) continue # Map release track and recording MBIDs to their information. # Recordings can appear multiple times on a release, so each MBID # maps to a list of TrackInfo objects. releasetrack_index = {} track_index = defaultdict(list) for track_info in album_info.tracks: releasetrack_index[track_info.release_track_id] = track_info track_index[track_info.track_id].append(track_info) # Construct a track mapping according to MBIDs (release track MBIDs # first, if available, and recording MBIDs otherwise). This should # work for albums that have missing or extra tracks. mapping = {} for item in items: if ( item.mb_releasetrackid and item.mb_releasetrackid in releasetrack_index ): mapping[item] = releasetrack_index[item.mb_releasetrackid] else: candidates = track_index[item.mb_trackid] if len(candidates) == 1: mapping[item] = candidates[0] else: # If there are multiple copies of a recording, they are # disambiguated using their disc and track number. for c in candidates: if ( c.medium_index == item.track and c.medium == item.disc ): mapping[item] = c break # Apply. self._log.debug("applying changes to {}", album_formatted) with lib.transaction(): autotag.apply_metadata(album_info, mapping) changed = False # Find any changed item to apply MusicBrainz changes to album. any_changed_item = items[0] for item in items: item_changed = ui.show_model_changes(item) changed |= item_changed if item_changed: any_changed_item = item apply_item_changes(lib, item, move, pretend, write) if not changed: # No change to any item. continue if not pretend: # Update album structure to reflect an item in it. for key in library.Album.item_keys: a[key] = any_changed_item[key] a.store() # Move album art (and any inconsistent items). if move and lib.directory in util.ancestry(items[0].path): self._log.debug("moving album {0}", album_formatted) a.move() ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������beetbox-beets-01f1faf/beetsplug/metasync/�����������������������������������������������������������0000775�0000000�0000000�00000000000�14723254774�0020564�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������beetbox-beets-01f1faf/beetsplug/metasync/__init__.py������������������������������������������������0000664�0000000�0000000�00000010264�14723254774�0022700�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# This file is part of beets. # Copyright 2016, Heinz Wiesinger. # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the # "Software"), to deal in the Software without restriction, including # without limitation the rights to use, copy, modify, merge, publish, # distribute, sublicense, and/or sell copies of the Software, and to # permit persons to whom the Software is furnished to do so, subject to # the following conditions: # # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. """Synchronize information from music player libraries""" from abc import ABCMeta, abstractmethod from importlib import import_module from confuse import ConfigValueError from beets import ui from beets.plugins import BeetsPlugin METASYNC_MODULE = "beetsplug.metasync" # Dictionary to map the MODULE and the CLASS NAME of meta sources SOURCES = { "amarok": "Amarok", "itunes": "Itunes", } class MetaSource(metaclass=ABCMeta): def __init__(self, config, log): self.item_types = {} self.config = config self._log = log @abstractmethod def sync_from_source(self, item): pass def load_meta_sources(): """Returns a dictionary of all the MetaSources E.g., {'itunes': Itunes} with isinstance(Itunes, MetaSource) true """ meta_sources = {} for module_path, class_name in SOURCES.items(): module = import_module(METASYNC_MODULE + "." + module_path) meta_sources[class_name.lower()] = getattr(module, class_name) return meta_sources META_SOURCES = load_meta_sources() def load_item_types(): """Returns a dictionary containing the item_types of all the MetaSources""" item_types = {} for meta_source in META_SOURCES.values(): item_types.update(meta_source.item_types) return item_types class MetaSyncPlugin(BeetsPlugin): item_types = load_item_types() def __init__(self): super().__init__() def commands(self): cmd = ui.Subcommand( "metasync", help="update metadata from music player libraries" ) cmd.parser.add_option( "-p", "--pretend", action="store_true", help="show all changes but do nothing", ) cmd.parser.add_option( "-s", "--source", default=[], action="append", dest="sources", help="comma-separated list of sources to sync", ) cmd.parser.add_format_option() cmd.func = self.func return [cmd] def func(self, lib, opts, args): """Command handler for the metasync function.""" pretend = opts.pretend query = ui.decargs(args) sources = [] for source in opts.sources: sources.extend(source.split(",")) sources = sources or self.config["source"].as_str_seq() meta_source_instances = {} items = lib.items(query) # Avoid needlessly instantiating meta sources (can be expensive) if not items: self._log.info("No items found matching query") return # Instantiate the meta sources for player in sources: try: cls = META_SOURCES[player] except KeyError: self._log.error("Unknown metadata source '{}'".format(player)) try: meta_source_instances[player] = cls(self.config, self._log) except (ImportError, ConfigValueError) as e: self._log.error( f"Failed to instantiate metadata source {player!r}: {e}" ) # Avoid needlessly iterating over items if not meta_source_instances: self._log.error("No valid metadata sources found") return # Sync the items with all of the meta sources for item in items: for meta_source in meta_source_instances.values(): meta_source.sync_from_source(item) changed = ui.show_model_changes(item) if changed and not pretend: item.store() ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������beetbox-beets-01f1faf/beetsplug/metasync/amarok.py��������������������������������������������������0000664�0000000�0000000�00000007657�14723254774�0022427�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# This file is part of beets. # Copyright 2016, Heinz Wiesinger. # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the # "Software"), to deal in the Software without restriction, including # without limitation the rights to use, copy, modify, merge, publish, # distribute, sublicense, and/or sell copies of the Software, and to # permit persons to whom the Software is furnished to do so, subject to # the following conditions: # # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. """Synchronize information from amarok's library via dbus""" from datetime import datetime from os.path import basename from time import mktime from xml.sax.saxutils import quoteattr from beets.dbcore import types from beets.library import DateType from beets.util import displayable_path from beetsplug.metasync import MetaSource def import_dbus(): try: return __import__("dbus") except ImportError: return None dbus = import_dbus() class Amarok(MetaSource): item_types = { "amarok_rating": types.INTEGER, "amarok_score": types.FLOAT, "amarok_uid": types.STRING, "amarok_playcount": types.INTEGER, "amarok_firstplayed": DateType(), "amarok_lastplayed": DateType(), } query_xml = '<query version="1.0"> \ <filters> \ <and><include field="filename" value=%s /></and> \ </filters> \ </query>' def __init__(self, config, log): super().__init__(config, log) if not dbus: raise ImportError("failed to import dbus") self.collection = dbus.SessionBus().get_object( "org.kde.amarok", "/Collection" ) def sync_from_source(self, item): path = displayable_path(item.path) # amarok unfortunately doesn't allow searching for the full path, only # for the patch relative to the mount point. But the full path is part # of the result set. So query for the filename and then try to match # the correct item from the results we get back results = self.collection.Query( self.query_xml % quoteattr(basename(path)) ) for result in results: if result["xesam:url"] != path: continue item.amarok_rating = result["xesam:userRating"] item.amarok_score = result["xesam:autoRating"] item.amarok_playcount = result["xesam:useCount"] item.amarok_uid = result["xesam:id"].replace( "amarok-sqltrackuid://", "" ) if result["xesam:firstUsed"][0][0] != 0: # These dates are stored as timestamps in amarok's db, but # exposed over dbus as fixed integers in the current timezone. first_played = datetime( result["xesam:firstUsed"][0][0], result["xesam:firstUsed"][0][1], result["xesam:firstUsed"][0][2], result["xesam:firstUsed"][1][0], result["xesam:firstUsed"][1][1], result["xesam:firstUsed"][1][2], ) if result["xesam:lastUsed"][0][0] != 0: last_played = datetime( result["xesam:lastUsed"][0][0], result["xesam:lastUsed"][0][1], result["xesam:lastUsed"][0][2], result["xesam:lastUsed"][1][0], result["xesam:lastUsed"][1][1], result["xesam:lastUsed"][1][2], ) else: last_played = first_played item.amarok_firstplayed = mktime(first_played.timetuple()) item.amarok_lastplayed = mktime(last_played.timetuple()) ���������������������������������������������������������������������������������beetbox-beets-01f1faf/beetsplug/metasync/itunes.py��������������������������������������������������0000664�0000000�0000000�00000010733�14723254774�0022451�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# This file is part of beets. # Copyright 2016, Tom Jaspers. # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the # "Software"), to deal in the Software without restriction, including # without limitation the rights to use, copy, modify, merge, publish, # distribute, sublicense, and/or sell copies of the Software, and to # permit persons to whom the Software is furnished to do so, subject to # the following conditions: # # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. """Synchronize information from iTunes's library""" import os import plistlib import shutil import tempfile from contextlib import contextmanager from time import mktime from urllib.parse import unquote, urlparse from confuse import ConfigValueError from beets import util from beets.dbcore import types from beets.library import DateType from beets.util import bytestring_path, syspath from beetsplug.metasync import MetaSource @contextmanager def create_temporary_copy(path): temp_dir = bytestring_path(tempfile.mkdtemp()) temp_path = os.path.join(temp_dir, b"temp_itunes_lib") shutil.copyfile(syspath(path), syspath(temp_path)) try: yield temp_path finally: shutil.rmtree(syspath(temp_dir)) def _norm_itunes_path(path): # Itunes prepends the location with 'file://' on posix systems, # and with 'file://localhost/' on Windows systems. # The actual path to the file is always saved as posix form # E.g., 'file://Users/Music/bar' or 'file://localhost/G:/Music/bar' # The entire path will also be capitalized (e.g., '/Music/Alt-J') # Note that this means the path will always have a leading separator, # which is unwanted in the case of Windows systems. # E.g., '\\G:\\Music\\bar' needs to be stripped to 'G:\\Music\\bar' return util.bytestring_path( os.path.normpath(unquote(urlparse(path).path)).lstrip("\\") ).lower() class Itunes(MetaSource): item_types = { "itunes_rating": types.INTEGER, # 0..100 scale "itunes_playcount": types.INTEGER, "itunes_skipcount": types.INTEGER, "itunes_lastplayed": DateType(), "itunes_lastskipped": DateType(), "itunes_dateadded": DateType(), } def __init__(self, config, log): super().__init__(config, log) config.add({"itunes": {"library": "~/Music/iTunes/iTunes Library.xml"}}) # Load the iTunes library, which has to be the .xml one (not the .itl) library_path = config["itunes"]["library"].as_filename() try: self._log.debug(f"loading iTunes library from {library_path}") with create_temporary_copy(library_path) as library_copy: with open(library_copy, "rb") as library_copy_f: raw_library = plistlib.load(library_copy_f) except OSError as e: raise ConfigValueError("invalid iTunes library: " + e.strerror) except Exception: # It's likely the user configured their '.itl' library (<> xml) if os.path.splitext(library_path)[1].lower() != ".xml": hint = ( ": please ensure that the configured path" " points to the .XML library" ) else: hint = "" raise ConfigValueError("invalid iTunes library" + hint) # Make the iTunes library queryable using the path self.collection = { _norm_itunes_path(track["Location"]): track for track in raw_library["Tracks"].values() if "Location" in track } def sync_from_source(self, item): result = self.collection.get(util.bytestring_path(item.path).lower()) if not result: self._log.warning(f"no iTunes match found for {item}") return item.itunes_rating = result.get("Rating") item.itunes_playcount = result.get("Play Count") item.itunes_skipcount = result.get("Skip Count") if result.get("Play Date UTC"): item.itunes_lastplayed = mktime( result.get("Play Date UTC").timetuple() ) if result.get("Skip Date"): item.itunes_lastskipped = mktime( result.get("Skip Date").timetuple() ) if result.get("Date Added"): item.itunes_dateadded = mktime(result.get("Date Added").timetuple()) �������������������������������������beetbox-beets-01f1faf/beetsplug/missing.py����������������������������������������������������������0000664�0000000�0000000�00000017564�14723254774�0021001�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# This file is part of beets. # Copyright 2016, Pedro Silva. # Copyright 2017, Quentin Young. # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the # "Software"), to deal in the Software without restriction, including # without limitation the rights to use, copy, modify, merge, publish, # distribute, sublicense, and/or sell copies of the Software, and to # permit persons to whom the Software is furnished to do so, subject to # the following conditions: # # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. """List missing tracks.""" from collections import defaultdict import musicbrainzngs from musicbrainzngs.musicbrainz import MusicBrainzError from beets import config from beets.autotag import hooks from beets.dbcore import types from beets.library import Item from beets.plugins import BeetsPlugin from beets.ui import Subcommand, decargs, print_ def _missing_count(album): """Return number of missing items in `album`.""" return (album.albumtotal or 0) - len(album.items()) def _item(track_info, album_info, album_id): """Build and return `item` from `track_info` and `album info` objects. `item` is missing what fields cannot be obtained from MusicBrainz alone (encoder, rg_track_gain, rg_track_peak, rg_album_gain, rg_album_peak, original_year, original_month, original_day, length, bitrate, format, samplerate, bitdepth, channels, mtime.) """ t = track_info a = album_info return Item( **{ "album_id": album_id, "album": a.album, "albumartist": a.artist, "albumartist_credit": a.artist_credit, "albumartist_sort": a.artist_sort, "albumdisambig": a.albumdisambig, "albumstatus": a.albumstatus, "albumtype": a.albumtype, "artist": t.artist, "artist_credit": t.artist_credit, "artist_sort": t.artist_sort, "asin": a.asin, "catalognum": a.catalognum, "comp": a.va, "country": a.country, "day": a.day, "disc": t.medium, "disctitle": t.disctitle, "disctotal": a.mediums, "label": a.label, "language": a.language, "length": t.length, "mb_albumid": a.album_id, "mb_artistid": t.artist_id, "mb_releasegroupid": a.releasegroup_id, "mb_trackid": t.track_id, "media": t.media, "month": a.month, "script": a.script, "title": t.title, "track": t.index, "tracktotal": len(a.tracks), "year": a.year, } ) class MissingPlugin(BeetsPlugin): """List missing tracks""" album_types = { "missing": types.INTEGER, } def __init__(self): super().__init__() self.config.add( { "count": False, "total": False, "album": False, } ) self.album_template_fields["missing"] = _missing_count self._command = Subcommand("missing", help=__doc__, aliases=["miss"]) self._command.parser.add_option( "-c", "--count", dest="count", action="store_true", help="count missing tracks per album", ) self._command.parser.add_option( "-t", "--total", dest="total", action="store_true", help="count total of missing tracks", ) self._command.parser.add_option( "-a", "--album", dest="album", action="store_true", help="show missing albums for artist instead of tracks", ) self._command.parser.add_format_option() def commands(self): def _miss(lib, opts, args): self.config.set_args(opts) albms = self.config["album"].get() helper = self._missing_albums if albms else self._missing_tracks helper(lib, decargs(args)) self._command.func = _miss return [self._command] def _missing_tracks(self, lib, query): """Print a listing of tracks missing from each album in the library matching query. """ albums = lib.albums(query) count = self.config["count"].get() total = self.config["total"].get() fmt = config["format_album" if count else "format_item"].get() if total: print(sum([_missing_count(a) for a in albums])) return # Default format string for count mode. if count: fmt += ": $missing" for album in albums: if count: if _missing_count(album): print_(format(album, fmt)) else: for item in self._missing(album): print_(format(item, fmt)) def _missing_albums(self, lib, query): """Print a listing of albums missing from each artist in the library matching query. """ total = self.config["total"].get() albums = lib.albums(query) # build dict mapping artist to list of their albums in library albums_by_artist = defaultdict(list) for alb in albums: artist = (alb["albumartist"], alb["mb_albumartistid"]) albums_by_artist[artist].append(alb) total_missing = 0 # build dict mapping artist to list of all albums for artist, albums in albums_by_artist.items(): if artist[1] is None or artist[1] == "": albs_no_mbid = ["'" + a["album"] + "'" for a in albums] self._log.info( "No musicbrainz ID for artist '{}' found in album(s) {}; " "skipping", artist[0], ", ".join(albs_no_mbid), ) continue try: resp = musicbrainzngs.browse_release_groups(artist=artist[1]) release_groups = resp["release-group-list"] except MusicBrainzError as err: self._log.info( "Couldn't fetch info for artist '{}' ({}) - '{}'", artist[0], artist[1], err, ) continue missing = [] present = [] for rg in release_groups: missing.append(rg) for alb in albums: if alb["mb_releasegroupid"] == rg["id"]: missing.remove(rg) present.append(rg) break total_missing += len(missing) if total: continue missing_titles = {rg["title"] for rg in missing} for release_title in missing_titles: print_("{} - {}".format(artist[0], release_title)) if total: print(total_missing) def _missing(self, album): """Query MusicBrainz to determine items missing from `album`.""" item_mbids = [x.mb_trackid for x in album.items()] if len(list(album.items())) < album.albumtotal: # fetch missing items # TODO: Implement caching that without breaking other stuff album_info = hooks.album_for_mbid(album.mb_albumid) for track_info in getattr(album_info, "tracks", []): if track_info.track_id not in item_mbids: item = _item(track_info, album_info, album.id) self._log.debug( "track {0} in album {1}", track_info.track_id, album_info.album_id, ) yield item ��������������������������������������������������������������������������������������������������������������������������������������������beetbox-beets-01f1faf/beetsplug/mpdstats.py���������������������������������������������������������0000664�0000000�0000000�00000030000�14723254774�0021143�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# This file is part of beets. # Copyright 2016, Peter Schnebel and Johann Klähn. # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the # "Software"), to deal in the Software without restriction, including # without limitation the rights to use, copy, modify, merge, publish, # distribute, sublicense, and/or sell copies of the Software, and to # permit persons to whom the Software is furnished to do so, subject to # the following conditions: # # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. import os import time import mpd from beets import config, library, plugins, ui from beets.dbcore import types from beets.util import displayable_path # If we lose the connection, how many times do we want to retry and how # much time should we wait between retries? RETRIES = 10 RETRY_INTERVAL = 5 mpd_config = config["mpd"] def is_url(path): """Try to determine if the path is an URL.""" if isinstance(path, bytes): # if it's bytes, then it's a path return False return path.split("://", 1)[0] in ["http", "https"] class MPDClientWrapper: def __init__(self, log): self._log = log self.music_directory = mpd_config["music_directory"].as_str() self.strip_path = mpd_config["strip_path"].as_str() # Ensure strip_path end with '/' if not self.strip_path.endswith("/"): self.strip_path += "/" self._log.debug("music_directory: {0}", self.music_directory) self._log.debug("strip_path: {0}", self.strip_path) self.client = mpd.MPDClient() def connect(self): """Connect to the MPD.""" host = mpd_config["host"].as_str() port = mpd_config["port"].get(int) if host[0] in ["/", "~"]: host = os.path.expanduser(host) self._log.info("connecting to {0}:{1}", host, port) try: self.client.connect(host, port) except OSError as e: raise ui.UserError(f"could not connect to MPD: {e}") password = mpd_config["password"].as_str() if password: try: self.client.password(password) except mpd.CommandError as e: raise ui.UserError(f"could not authenticate to MPD: {e}") def disconnect(self): """Disconnect from the MPD.""" self.client.close() self.client.disconnect() def get(self, command, retries=RETRIES): """Wrapper for requests to the MPD server. Tries to re-connect if the connection was lost (f.ex. during MPD's library refresh). """ try: return getattr(self.client, command)() except (OSError, mpd.ConnectionError) as err: self._log.error("{0}", err) if retries <= 0: # if we exited without breaking, we couldn't reconnect in time :( raise ui.UserError("communication with MPD server failed") time.sleep(RETRY_INTERVAL) try: self.disconnect() except mpd.ConnectionError: pass self.connect() return self.get(command, retries=retries - 1) def currentsong(self): """Return the path to the currently playing song, along with its songid. Prefixes paths with the music_directory, to get the absolute path. In some cases, we need to remove the local path from MPD server, we replace 'strip_path' with ''. `strip_path` defaults to ''. """ result = None entry = self.get("currentsong") if "file" in entry: if not is_url(entry["file"]): file = entry["file"] if file.startswith(self.strip_path): file = file[len(self.strip_path) :] result = os.path.join(self.music_directory, file) else: result = entry["file"] self._log.debug("returning: {0}", result) return result, entry.get("id") def status(self): """Return the current status of the MPD.""" return self.get("status") def events(self): """Return list of events. This may block a long time while waiting for an answer from MPD. """ return self.get("idle") class MPDStats: def __init__(self, lib, log): self.lib = lib self._log = log self.do_rating = mpd_config["rating"].get(bool) self.rating_mix = mpd_config["rating_mix"].get(float) self.time_threshold = 10.0 # TODO: maybe add config option? self.now_playing = None self.mpd = MPDClientWrapper(log) def rating(self, play_count, skip_count, rating, skipped): """Calculate a new rating for a song based on play count, skip count, old rating and the fact if it was skipped or not. """ if skipped: rolling = rating - rating / 2.0 else: rolling = rating + (1.0 - rating) / 2.0 stable = (play_count + 1.0) / (play_count + skip_count + 2.0) return self.rating_mix * stable + (1.0 - self.rating_mix) * rolling def get_item(self, path): """Return the beets item related to path.""" query = library.PathQuery("path", path) item = self.lib.items(query).get() if item: return item else: self._log.info("item not found: {0}", displayable_path(path)) def update_item(self, item, attribute, value=None, increment=None): """Update the beets item. Set attribute to value or increment the value of attribute. If the increment argument is used the value is cast to the corresponding type. """ if item is None: return if increment is not None: item.load() value = type(increment)(item.get(attribute, 0)) + increment if value is not None: item[attribute] = value item.store() self._log.debug( "updated: {0} = {1} [{2}]", attribute, item[attribute], displayable_path(item.path), ) def update_rating(self, item, skipped): """Update the rating for a beets item. The `item` can either be a beets `Item` or None. If the item is None, nothing changes. """ if item is None: return item.load() rating = self.rating( int(item.get("play_count", 0)), int(item.get("skip_count", 0)), float(item.get("rating", 0.5)), skipped, ) self.update_item(item, "rating", rating) def handle_song_change(self, song): """Determine if a song was skipped or not and update its attributes. To this end the difference between the song's supposed end time and the current time is calculated. If it's greater than a threshold, the song is considered skipped. Returns whether the change was manual (skipped previous song or not) """ diff = abs(song["remaining"] - (time.time() - song["started"])) skipped = diff >= self.time_threshold if skipped: self.handle_skipped(song) else: self.handle_played(song) if self.do_rating: self.update_rating(song["beets_item"], skipped) return skipped def handle_played(self, song): """Updates the play count of a song.""" self.update_item(song["beets_item"], "play_count", increment=1) self._log.info("played {0}", displayable_path(song["path"])) def handle_skipped(self, song): """Updates the skip count of a song.""" self.update_item(song["beets_item"], "skip_count", increment=1) self._log.info("skipped {0}", displayable_path(song["path"])) def on_stop(self, status): self._log.info("stop") # if the current song stays the same it means that we stopped on the # current track and should not record a skip. if self.now_playing and self.now_playing["id"] != status.get("songid"): self.handle_song_change(self.now_playing) self.now_playing = None def on_pause(self, status): self._log.info("pause") self.now_playing = None def on_play(self, status): path, songid = self.mpd.currentsong() if not path: return played, duration = map(int, status["time"].split(":", 1)) remaining = duration - played if self.now_playing: if self.now_playing["path"] != path: self.handle_song_change(self.now_playing) else: # In case we got mpd play event with same song playing # multiple times, # assume low diff means redundant second play event # after natural song start. diff = abs(time.time() - self.now_playing["started"]) if diff <= self.time_threshold: return if self.now_playing["path"] == path and played == 0: self.handle_song_change(self.now_playing) if is_url(path): self._log.info("playing stream {0}", displayable_path(path)) self.now_playing = None return self._log.info("playing {0}", displayable_path(path)) self.now_playing = { "started": time.time(), "remaining": remaining, "path": path, "id": songid, "beets_item": self.get_item(path), } self.update_item( self.now_playing["beets_item"], "last_played", value=int(time.time()), ) def run(self): self.mpd.connect() events = ["player"] while True: if "player" in events: status = self.mpd.status() handler = getattr(self, "on_" + status["state"], None) if handler: handler(status) else: self._log.debug('unhandled status "{0}"', status) events = self.mpd.events() class MPDStatsPlugin(plugins.BeetsPlugin): item_types = { "play_count": types.INTEGER, "skip_count": types.INTEGER, "last_played": library.DateType(), "rating": types.FLOAT, } def __init__(self): super().__init__() mpd_config.add( { "music_directory": config["directory"].as_filename(), "strip_path": "", "rating": True, "rating_mix": 0.75, "host": os.environ.get("MPD_HOST", "localhost"), "port": int(os.environ.get("MPD_PORT", 6600)), "password": "", } ) mpd_config["password"].redact = True def commands(self): cmd = ui.Subcommand( "mpdstats", help="run a MPD client to gather play statistics" ) cmd.parser.add_option( "--host", dest="host", type="string", help="set the hostname of the server to connect to", ) cmd.parser.add_option( "--port", dest="port", type="int", help="set the port of the MPD server to connect to", ) cmd.parser.add_option( "--password", dest="password", type="string", help="set the password of the MPD server to connect to", ) def func(lib, opts, args): mpd_config.set_args(opts) # Overrides for MPD settings. if opts.host: mpd_config["host"] = opts.host.decode("utf-8") if opts.port: mpd_config["host"] = int(opts.port) if opts.password: mpd_config["password"] = opts.password.decode("utf-8") try: MPDStats(lib, self._log).run() except KeyboardInterrupt: pass cmd.func = func return [cmd] beetbox-beets-01f1faf/beetsplug/mpdupdate.py��������������������������������������������������������0000664�0000000�0000000�00000010033�14723254774�0021273�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# This file is part of beets. # Copyright 2016, Adrian Sampson. # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the # "Software"), to deal in the Software without restriction, including # without limitation the rights to use, copy, modify, merge, publish, # distribute, sublicense, and/or sell copies of the Software, and to # permit persons to whom the Software is furnished to do so, subject to # the following conditions: # # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. """Updates an MPD index whenever the library is changed. Put something like the following in your config.yaml to configure: mpd: host: localhost port: 6600 password: seekrit """ import os import socket from beets import config from beets.plugins import BeetsPlugin # No need to introduce a dependency on an MPD library for such a # simple use case. Here's a simple socket abstraction to make things # easier. class BufferedSocket: """Socket abstraction that allows reading by line.""" def __init__(self, host, port, sep=b"\n"): if host[0] in ["/", "~"]: self.sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) self.sock.connect(os.path.expanduser(host)) else: self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) self.sock.connect((host, port)) self.buf = b"" self.sep = sep def readline(self): while self.sep not in self.buf: data = self.sock.recv(1024) if not data: break self.buf += data if self.sep in self.buf: res, self.buf = self.buf.split(self.sep, 1) return res + self.sep else: return b"" def send(self, data): self.sock.send(data) def close(self): self.sock.close() class MPDUpdatePlugin(BeetsPlugin): def __init__(self): super().__init__() config["mpd"].add( { "host": os.environ.get("MPD_HOST", "localhost"), "port": int(os.environ.get("MPD_PORT", 6600)), "password": "", } ) config["mpd"]["password"].redact = True # For backwards compatibility, use any values from the # plugin-specific "mpdupdate" section. for key in config["mpd"].keys(): if self.config[key].exists(): config["mpd"][key] = self.config[key].get() self.register_listener("database_change", self.db_change) def db_change(self, lib, model): self.register_listener("cli_exit", self.update) def update(self, lib): self.update_mpd( config["mpd"]["host"].as_str(), config["mpd"]["port"].get(int), config["mpd"]["password"].as_str(), ) def update_mpd(self, host="localhost", port=6600, password=None): """Sends the "update" command to the MPD server indicated, possibly authenticating with a password first. """ self._log.info("Updating MPD database...") try: s = BufferedSocket(host, port) except OSError as e: self._log.warning("MPD connection failed: {0}", str(e.strerror)) return resp = s.readline() if b"OK MPD" not in resp: self._log.warning("MPD connection failed: {0!r}", resp) return if password: s.send(b'password "%s"\n' % password.encode("utf8")) resp = s.readline() if b"OK" not in resp: self._log.warning("Authentication failed: {0!r}", resp) s.send(b"close\n") s.close() return s.send(b"update\n") resp = s.readline() if b"updating_db" not in resp: self._log.warning("Update failed: {0!r}", resp) s.send(b"close\n") s.close() self._log.info("Database updated.") �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������beetbox-beets-01f1faf/beetsplug/parentwork.py�������������������������������������������������������0000664�0000000�0000000�00000017716�14723254774�0021523�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# This file is part of beets. # Copyright 2017, Dorian Soergel. # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the # "Software"), to deal in the Software without restriction, including # without limitation the rights to use, copy, modify, merge, publish, # distribute, sublicense, and/or sell copies of the Software, and to # permit persons to whom the Software is furnished to do so, subject to # the following conditions: # # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. """Gets parent work, its disambiguation and id, composer, composer sort name and work composition date """ import musicbrainzngs from beets import ui from beets.plugins import BeetsPlugin def direct_parent_id(mb_workid, work_date=None): """Given a Musicbrainz work id, find the id one of the works the work is part of and the first composition date it encounters. """ work_info = musicbrainzngs.get_work_by_id( mb_workid, includes=["work-rels", "artist-rels"] ) if "artist-relation-list" in work_info["work"] and work_date is None: for artist in work_info["work"]["artist-relation-list"]: if artist["type"] == "composer": if "end" in artist.keys(): work_date = artist["end"] if "work-relation-list" in work_info["work"]: for direct_parent in work_info["work"]["work-relation-list"]: if ( direct_parent["type"] == "parts" and direct_parent.get("direction") == "backward" ): direct_id = direct_parent["work"]["id"] return direct_id, work_date return None, work_date def work_parent_id(mb_workid): """Find the parent work id and composition date of a work given its id.""" work_date = None while True: new_mb_workid, work_date = direct_parent_id(mb_workid, work_date) if not new_mb_workid: return mb_workid, work_date mb_workid = new_mb_workid return mb_workid, work_date def find_parentwork_info(mb_workid): """Get the MusicBrainz information dict about a parent work, including the artist relations, and the composition date for a work's parent work. """ parent_id, work_date = work_parent_id(mb_workid) work_info = musicbrainzngs.get_work_by_id( parent_id, includes=["artist-rels"] ) return work_info, work_date class ParentWorkPlugin(BeetsPlugin): def __init__(self): super().__init__() self.config.add( { "auto": False, "force": False, } ) if self.config["auto"]: self.import_stages = [self.imported] def commands(self): def func(lib, opts, args): self.config.set_args(opts) force_parent = self.config["force"].get(bool) write = ui.should_write() for item in lib.items(ui.decargs(args)): changed = self.find_work(item, force_parent) if changed: item.store() if write: item.try_write() command = ui.Subcommand( "parentwork", help="fetch parent works, composers and dates" ) command.parser.add_option( "-f", "--force", dest="force", action="store_true", default=None, help="re-fetch when parent work is already present", ) command.func = func return [command] def imported(self, session, task): """Import hook for fetching parent works automatically.""" force_parent = self.config["force"].get(bool) for item in task.imported_items(): self.find_work(item, force_parent) item.store() def get_info(self, item, work_info): """Given the parent work info dict, fetch parent_composer, parent_composer_sort, parentwork, parentwork_disambig, mb_workid and composer_ids. """ parent_composer = [] parent_composer_sort = [] parentwork_info = {} composer_exists = False if "artist-relation-list" in work_info["work"]: for artist in work_info["work"]["artist-relation-list"]: if artist["type"] == "composer": composer_exists = True parent_composer.append(artist["artist"]["name"]) parent_composer_sort.append(artist["artist"]["sort-name"]) if "end" in artist.keys(): parentwork_info["parentwork_date"] = artist["end"] parentwork_info["parent_composer"] = ", ".join(parent_composer) parentwork_info["parent_composer_sort"] = ", ".join( parent_composer_sort ) if not composer_exists: self._log.debug( "no composer for {}; add one at " "https://musicbrainz.org/work/{}", item, work_info["work"]["id"], ) parentwork_info["parentwork"] = work_info["work"]["title"] parentwork_info["mb_parentworkid"] = work_info["work"]["id"] if "disambiguation" in work_info["work"]: parentwork_info["parentwork_disambig"] = work_info["work"][ "disambiguation" ] else: parentwork_info["parentwork_disambig"] = None return parentwork_info def find_work(self, item, force): """Finds the parent work of a recording and populates the tags accordingly. The parent work is found recursively, by finding the direct parent repeatedly until there are no more links in the chain. We return the final, topmost work in the chain. Namely, the tags parentwork, parentwork_disambig, mb_parentworkid, parent_composer, parent_composer_sort and work_date are populated. """ if not item.mb_workid: self._log.info( "No work for {}, \ add one at https://musicbrainz.org/recording/{}", item, item.mb_trackid, ) return hasparent = hasattr(item, "parentwork") work_changed = True if hasattr(item, "parentwork_workid_current"): work_changed = item.parentwork_workid_current != item.mb_workid if force or not hasparent or work_changed: try: work_info, work_date = find_parentwork_info(item.mb_workid) except musicbrainzngs.musicbrainz.WebServiceError as e: self._log.debug("error fetching work: {}", e) return parent_info = self.get_info(item, work_info) parent_info["parentwork_workid_current"] = item.mb_workid if "parent_composer" in parent_info: self._log.debug( "Work fetched: {} - {}", parent_info["parentwork"], parent_info["parent_composer"], ) else: self._log.debug( "Work fetched: {} - no parent composer", parent_info["parentwork"], ) elif hasparent: self._log.debug("{}: Work present, skipping", item) return # apply all non-null values to the item for key, value in parent_info.items(): if value: item[key] = value if work_date: item["work_date"] = work_date return ui.show_model_changes( item, fields=[ "parentwork", "parentwork_disambig", "mb_parentworkid", "parent_composer", "parent_composer_sort", "work_date", "parentwork_workid_current", "parentwork_date", ], ) ��������������������������������������������������beetbox-beets-01f1faf/beetsplug/permissions.py������������������������������������������������������0000664�0000000�0000000�00000010145�14723254774�0021667�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������"""Fixes file permissions after the file gets written on import. Put something like the following in your config.yaml to configure: permissions: file: 644 dir: 755 """ import os import stat from beets import config from beets.plugins import BeetsPlugin from beets.util import ancestry, displayable_path, syspath def convert_perm(perm): """Convert a string to an integer, interpreting the text as octal. Or, if `perm` is an integer, reinterpret it as an octal number that has been "misinterpreted" as decimal. """ if isinstance(perm, int): perm = str(perm) return int(perm, 8) def check_permissions(path, permission): """Check whether the file's permissions equal the given vector. Return a boolean. """ return oct(stat.S_IMODE(os.stat(syspath(path)).st_mode)) == oct(permission) def assert_permissions(path, permission, log): """Check whether the file's permissions are as expected, otherwise, log a warning message. Return a boolean indicating the match, like `check_permissions`. """ if not check_permissions(path, permission): log.warning("could not set permissions on {}", displayable_path(path)) log.debug( "set permissions to {}, but permissions are now {}", permission, os.stat(syspath(path)).st_mode & 0o777, ) def dirs_in_library(library, item): """Creates a list of ancestor directories in the beets library path.""" return [ ancestor for ancestor in ancestry(item) if ancestor.startswith(library) ][1:] class Permissions(BeetsPlugin): def __init__(self): super().__init__() # Adding defaults. self.config.add( { "file": "644", "dir": "755", } ) self.register_listener("item_imported", self.fix) self.register_listener("album_imported", self.fix) self.register_listener("art_set", self.fix_art) def fix(self, lib, item=None, album=None): """Fix the permissions for an imported Item or Album.""" files = [] dirs = set() if item: files.append(item.path) dirs.update(dirs_in_library(lib.directory, item.path)) elif album: for album_item in album.items(): files.append(album_item.path) dirs.update(dirs_in_library(lib.directory, album_item.path)) self.set_permissions(files=files, dirs=dirs) def fix_art(self, album): """Fix the permission for Album art file.""" if album.artpath: self.set_permissions(files=[album.artpath]) def set_permissions(self, files=[], dirs=[]): # Get the configured permissions. The user can specify this either a # string (in YAML quotes) or, for convenience, as an integer so the # quotes can be omitted. In the latter case, we need to reinterpret the # integer as octal, not decimal. file_perm = config["permissions"]["file"].get() dir_perm = config["permissions"]["dir"].get() file_perm = convert_perm(file_perm) dir_perm = convert_perm(dir_perm) for path in files: # Changing permissions on the destination file. self._log.debug( "setting file permissions on {}", displayable_path(path), ) if not check_permissions(path, file_perm): os.chmod(syspath(path), file_perm) # Checks if the destination path has the permissions configured. assert_permissions(path, file_perm, self._log) # Change permissions for the directories. for path in dirs: # Changing permissions on the destination directory. self._log.debug( "setting directory permissions on {}", displayable_path(path), ) if not check_permissions(path, dir_perm): os.chmod(syspath(path), dir_perm) # Checks if the destination path has the permissions configured. assert_permissions(path, dir_perm, self._log) ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������beetbox-beets-01f1faf/beetsplug/play.py�������������������������������������������������������������0000664�0000000�0000000�00000017137�14723254774�0020271�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# This file is part of beets. # Copyright 2016, David Hamp-Gonsalves # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the # "Software"), to deal in the Software without restriction, including # without limitation the rights to use, copy, modify, merge, publish, # distribute, sublicense, and/or sell copies of the Software, and to # permit persons to whom the Software is furnished to do so, subject to # the following conditions: # # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. """Send the results of a query to the configured music player as a playlist.""" import shlex import subprocess from os.path import relpath from beets import config, ui, util from beets.plugins import BeetsPlugin from beets.ui import Subcommand from beets.ui.commands import PromptChoice from beets.util import get_temp_filename # Indicate where arguments should be inserted into the command string. # If this is missing, they're placed at the end. ARGS_MARKER = "$args" def play( command_str, selection, paths, open_args, log, item_type="track", keep_open=False, ): """Play items in paths with command_str and optional arguments. If keep_open, return to beets, otherwise exit once command runs. """ # Print number of tracks or albums to be played, log command to be run. item_type += "s" if len(selection) > 1 else "" ui.print_("Playing {} {}.".format(len(selection), item_type)) log.debug("executing command: {} {!r}", command_str, open_args) try: if keep_open: command = shlex.split(command_str) command = command + open_args subprocess.call(command) else: util.interactive_open(open_args, command_str) except OSError as exc: raise ui.UserError(f"Could not play the query: {exc}") class PlayPlugin(BeetsPlugin): def __init__(self): super().__init__() config["play"].add( { "command": None, "use_folders": False, "relative_to": None, "raw": False, "warning_threshold": 100, "bom": False, } ) self.register_listener( "before_choose_candidate", self.before_choose_candidate_listener ) def commands(self): play_command = Subcommand( "play", help="send music to a player as a playlist" ) play_command.parser.add_album_option() play_command.parser.add_option( "-A", "--args", action="store", help="add additional arguments to the command", ) play_command.parser.add_option( "-y", "--yes", action="store_true", help="skip the warning threshold", ) play_command.func = self._play_command return [play_command] def _play_command(self, lib, opts, args): """The CLI command function for `beet play`. Create a list of paths from query, determine if tracks or albums are to be played. """ use_folders = config["play"]["use_folders"].get(bool) relative_to = config["play"]["relative_to"].get() if relative_to: relative_to = util.normpath(relative_to) # Perform search by album and add folders rather than tracks to # playlist. if opts.album: selection = lib.albums(ui.decargs(args)) paths = [] sort = lib.get_default_album_sort() for album in selection: if use_folders: paths.append(album.item_dir()) else: paths.extend(item.path for item in sort.sort(album.items())) item_type = "album" # Perform item query and add tracks to playlist. else: selection = lib.items(ui.decargs(args)) paths = [item.path for item in selection] item_type = "track" if relative_to: paths = [relpath(path, relative_to) for path in paths] if not selection: ui.print_(ui.colorize("text_warning", f"No {item_type} to play.")) return open_args = self._playlist_or_paths(paths) command_str = self._command_str(opts.args) # Check if the selection exceeds configured threshold. If True, # cancel, otherwise proceed with play command. if opts.yes or not self._exceeds_threshold( selection, command_str, open_args, item_type ): play(command_str, selection, paths, open_args, self._log, item_type) def _command_str(self, args=None): """Create a command string from the config command and optional args.""" command_str = config["play"]["command"].get() if not command_str: return util.open_anything() # Add optional arguments to the player command. if args: if ARGS_MARKER in command_str: return command_str.replace(ARGS_MARKER, args) else: return f"{command_str} {args}" else: # Don't include the marker in the command. return command_str.replace(" " + ARGS_MARKER, "") def _playlist_or_paths(self, paths): """Return either the raw paths of items or a playlist of the items.""" if config["play"]["raw"]: return paths else: return [self._create_tmp_playlist(paths)] def _exceeds_threshold( self, selection, command_str, open_args, item_type="track" ): """Prompt user whether to abort if playlist exceeds threshold. If True, cancel playback. If False, execute play command. """ warning_threshold = config["play"]["warning_threshold"].get(int) # Warn user before playing any huge playlists. if warning_threshold and len(selection) > warning_threshold: if len(selection) > 1: item_type += "s" ui.print_( ui.colorize( "text_warning", "You are about to queue {} {}.".format( len(selection), item_type ), ) ) if ui.input_options(("Continue", "Abort")) == "a": return True return False def _create_tmp_playlist(self, paths_list): """Create a temporary .m3u file. Return the filename.""" utf8_bom = config["play"]["bom"].get(bool) filename = get_temp_filename(__name__, suffix=".m3u") with open(filename, "wb") as m3u: if utf8_bom: m3u.write(b"\xef\xbb\xbf") for item in paths_list: m3u.write(item + b"\n") return filename def before_choose_candidate_listener(self, session, task): """Append a "Play" choice to the interactive importer prompt.""" return [PromptChoice("y", "plaY", self.importer_play)] def importer_play(self, session, task): """Get items from current import task and send to play function.""" selection = task.items paths = [item.path for item in selection] open_args = self._playlist_or_paths(paths) command_str = self._command_str() if not self._exceeds_threshold(selection, command_str, open_args): play( command_str, selection, paths, open_args, self._log, keep_open=True, ) ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������beetbox-beets-01f1faf/beetsplug/playlist.py���������������������������������������������������������0000664�0000000�0000000�00000015607�14723254774�0021165�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# This file is part of beets. # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the # "Software"), to deal in the Software without restriction, including # without limitation the rights to use, copy, modify, merge, publish, # distribute, sublicense, and/or sell copies of the Software, and to # permit persons to whom the Software is furnished to do so, subject to # the following conditions: # # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. import fnmatch import os import tempfile from typing import Sequence import beets from beets.dbcore.query import InQuery from beets.library import BLOB_TYPE from beets.util import path_as_posix class PlaylistQuery(InQuery[bytes]): """Matches files listed by a playlist file.""" @property def subvals(self) -> Sequence[BLOB_TYPE]: return [BLOB_TYPE(p) for p in self.pattern] def __init__(self, _, pattern: str, __): config = beets.config["playlist"] # Get the full path to the playlist playlist_paths = ( pattern, os.path.abspath( os.path.join( config["playlist_dir"].as_filename(), f"{pattern}.m3u", ) ), ) paths = [] for playlist_path in playlist_paths: if not fnmatch.fnmatch(playlist_path, "*.[mM]3[uU]"): # This is not am M3U playlist, skip this candidate continue try: f = open(beets.util.syspath(playlist_path), mode="rb") except OSError: continue if config["relative_to"].get() == "library": relative_to = beets.config["directory"].as_filename() elif config["relative_to"].get() == "playlist": relative_to = os.path.dirname(playlist_path) else: relative_to = config["relative_to"].as_filename() relative_to = beets.util.bytestring_path(relative_to) for line in f: if line[0] == "#": # ignore comments, and extm3u extension continue paths.append( beets.util.normpath( os.path.join(relative_to, line.rstrip()) ) ) f.close() break super().__init__("path", paths) class PlaylistPlugin(beets.plugins.BeetsPlugin): item_queries = {"playlist": PlaylistQuery} def __init__(self): super().__init__() self.config.add( { "auto": False, "playlist_dir": ".", "relative_to": "library", "forward_slash": False, } ) self.playlist_dir = self.config["playlist_dir"].as_filename() self.changes = {} if self.config["relative_to"].get() == "library": self.relative_to = beets.util.bytestring_path( beets.config["directory"].as_filename() ) elif self.config["relative_to"].get() != "playlist": self.relative_to = beets.util.bytestring_path( self.config["relative_to"].as_filename() ) else: self.relative_to = None if self.config["auto"]: self.register_listener("item_moved", self.item_moved) self.register_listener("item_removed", self.item_removed) self.register_listener("cli_exit", self.cli_exit) def item_moved(self, item, source, destination): self.changes[source] = destination def item_removed(self, item): if not os.path.exists(beets.util.syspath(item.path)): self.changes[item.path] = None def cli_exit(self, lib): for playlist in self.find_playlists(): self._log.info(f"Updating playlist: {playlist}") base_dir = beets.util.bytestring_path( self.relative_to if self.relative_to else os.path.dirname(playlist) ) try: self.update_playlist(playlist, base_dir) except beets.util.FilesystemError: self._log.error( "Failed to update playlist: {}".format( beets.util.displayable_path(playlist) ) ) def find_playlists(self): """Find M3U playlists in the playlist directory.""" try: dir_contents = os.listdir(beets.util.syspath(self.playlist_dir)) except OSError: self._log.warning( "Unable to open playlist directory {}".format( beets.util.displayable_path(self.playlist_dir) ) ) return for filename in dir_contents: if fnmatch.fnmatch(filename, "*.[mM]3[uU]"): yield os.path.join(self.playlist_dir, filename) def update_playlist(self, filename, base_dir): """Find M3U playlists in the specified directory.""" changes = 0 deletions = 0 with tempfile.NamedTemporaryFile(mode="w+b", delete=False) as tempfp: new_playlist = tempfp.name with open(filename, mode="rb") as fp: for line in fp: original_path = line.rstrip(b"\r\n") # Ensure that path from playlist is absolute is_relative = not os.path.isabs(line) if is_relative: lookup = os.path.join(base_dir, original_path) else: lookup = original_path try: new_path = self.changes[beets.util.normpath(lookup)] except KeyError: if self.config["forward_slash"]: line = path_as_posix(line) tempfp.write(line) else: if new_path is None: # Item has been deleted deletions += 1 continue changes += 1 if is_relative: new_path = os.path.relpath(new_path, base_dir) line = line.replace(original_path, new_path) if self.config["forward_slash"]: line = path_as_posix(line) tempfp.write(line) if changes or deletions: self._log.info( "Updated playlist {} ({} changes, {} deletions)".format( filename, changes, deletions ) ) beets.util.copy(new_playlist, filename, replace=True) beets.util.remove(new_playlist) �������������������������������������������������������������������������������������������������������������������������beetbox-beets-01f1faf/beetsplug/plexupdate.py�������������������������������������������������������0000664�0000000�0000000�00000007076�14723254774�0021500�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������"""Updates an Plex library whenever the beets library is changed. Plex Home users enter the Plex Token to enable updating. Put something like the following in your config.yaml to configure: plex: host: localhost port: 32400 token: token """ from urllib.parse import urlencode, urljoin from xml.etree import ElementTree import requests from beets import config from beets.plugins import BeetsPlugin def get_music_section( host, port, token, library_name, secure, ignore_cert_errors ): """Getting the section key for the music library in Plex.""" api_endpoint = append_token("library/sections", token) url = urljoin( "{}://{}:{}".format(get_protocol(secure), host, port), api_endpoint ) # Sends request. r = requests.get( url, verify=not ignore_cert_errors, timeout=10, ) # Parse xml tree and extract music section key. tree = ElementTree.fromstring(r.content) for child in tree.findall("Directory"): if child.get("title") == library_name: return child.get("key") def update_plex(host, port, token, library_name, secure, ignore_cert_errors): """Ignore certificate errors if configured to.""" if ignore_cert_errors: import urllib3 urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) """Sends request to the Plex api to start a library refresh. """ # Getting section key and build url. section_key = get_music_section( host, port, token, library_name, secure, ignore_cert_errors ) api_endpoint = f"library/sections/{section_key}/refresh" api_endpoint = append_token(api_endpoint, token) url = urljoin( "{}://{}:{}".format(get_protocol(secure), host, port), api_endpoint ) # Sends request and returns requests object. r = requests.get( url, verify=not ignore_cert_errors, timeout=10, ) return r def append_token(url, token): """Appends the Plex Home token to the api call if required.""" if token: url += "?" + urlencode({"X-Plex-Token": token}) return url def get_protocol(secure): if secure: return "https" else: return "http" class PlexUpdate(BeetsPlugin): def __init__(self): super().__init__() # Adding defaults. config["plex"].add( { "host": "localhost", "port": 32400, "token": "", "library_name": "Music", "secure": False, "ignore_cert_errors": False, } ) config["plex"]["token"].redact = True self.register_listener("database_change", self.listen_for_db_change) def listen_for_db_change(self, lib, model): """Listens for beets db change and register the update for the end""" self.register_listener("cli_exit", self.update) def update(self, lib): """When the client exists try to send refresh request to Plex server.""" self._log.info("Updating Plex library...") # Try to send update request. try: update_plex( config["plex"]["host"].get(), config["plex"]["port"].get(), config["plex"]["token"].get(), config["plex"]["library_name"].get(), config["plex"]["secure"].get(bool), config["plex"]["ignore_cert_errors"].get(bool), ) self._log.info("... started.") except requests.exceptions.RequestException: self._log.warning("Update failed.") ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������beetbox-beets-01f1faf/beetsplug/random.py�����������������������������������������������������������0000664�0000000�0000000�00000003650�14723254774�0020577�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# This file is part of beets. # Copyright 2016, Philippe Mongeau. # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the # "Software"), to deal in the Software without restriction, including # without limitation the rights to use, copy, modify, merge, publish, # distribute, sublicense, and/or sell copies of the Software, and to # permit persons to whom the Software is furnished to do so, subject to # the following conditions: # # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. """Get a random song or album from the library.""" from beets.plugins import BeetsPlugin from beets.random import random_objs from beets.ui import Subcommand, decargs, print_ def random_func(lib, opts, args): """Select some random items or albums and print the results.""" # Fetch all the objects matching the query into a list. query = decargs(args) if opts.album: objs = list(lib.albums(query)) else: objs = list(lib.items(query)) # Print a random subset. objs = random_objs( objs, opts.album, opts.number, opts.time, opts.equal_chance ) for obj in objs: print_(format(obj)) random_cmd = Subcommand("random", help="choose a random track or album") random_cmd.parser.add_option( "-n", "--number", action="store", type="int", help="number of objects to choose", default=1, ) random_cmd.parser.add_option( "-e", "--equal-chance", action="store_true", help="each artist has the same chance", ) random_cmd.parser.add_option( "-t", "--time", action="store", type="float", help="total length in minutes of objects to choose", ) random_cmd.parser.add_all_common_options() random_cmd.func = random_func class Random(BeetsPlugin): def commands(self): return [random_cmd] ����������������������������������������������������������������������������������������beetbox-beets-01f1faf/beetsplug/replaygain.py�������������������������������������������������������0000664�0000000�0000000�00000153253�14723254774�0021457�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# This file is part of beets. # Copyright 2016, Fabrice Laporte, Yevgeny Bezman, and Adrian Sampson. # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the # "Software"), to deal in the Software without restriction, including # without limitation the rights to use, copy, modify, merge, publish, # distribute, sublicense, and/or sell copies of the Software, and to # permit persons to whom the Software is furnished to do so, subject to # the following conditions: # # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. import collections import enum import math import optparse import os import queue import signal import subprocess import sys import warnings from abc import ABC, abstractmethod from dataclasses import dataclass from logging import Logger from multiprocessing.pool import ThreadPool from threading import Event, Thread from typing import ( Any, Callable, DefaultDict, Dict, List, Optional, Sequence, Tuple, Type, TypeVar, Union, cast, ) from confuse import ConfigView from beets import ui from beets.importer import ImportSession, ImportTask from beets.library import Album, Item, Library from beets.plugins import BeetsPlugin from beets.util import command_output, displayable_path, syspath # Utilities. class ReplayGainError(Exception): """Raised when a local (to a track or an album) error occurs in one of the backends. """ class FatalReplayGainError(Exception): """Raised when a fatal error occurs in one of the backends.""" class FatalGstreamerPluginReplayGainError(FatalReplayGainError): """Raised when a fatal error occurs in the GStreamerBackend when loading the required plugins.""" def call(args: List[Any], log: Logger, **kwargs: Any): """Execute the command and return its output or raise a ReplayGainError on failure. """ try: return command_output(args, **kwargs) except subprocess.CalledProcessError as e: log.debug(e.output.decode("utf8", "ignore")) raise ReplayGainError( "{} exited with status {}".format(args[0], e.returncode) ) except UnicodeEncodeError: # Due to a bug in Python 2's subprocess on Windows, Unicode # filenames can fail to encode on that platform. See: # https://github.com/google-code-export/beets/issues/499 raise ReplayGainError("argument encoding failed") def db_to_lufs(db: float) -> float: """Convert db to LUFS. According to https://wiki.hydrogenaud.io/index.php?title= ReplayGain_2.0_specification#Reference_level """ return db - 107 def lufs_to_db(db: float) -> float: """Convert LUFS to db. According to https://wiki.hydrogenaud.io/index.php?title= ReplayGain_2.0_specification#Reference_level """ return db + 107 # Backend base and plumbing classes. @dataclass class Gain: # gain: in LU to reference level gain: float # peak: part of full scale (FS is 1.0) peak: float class PeakMethod(enum.Enum): true = 1 sample = 2 class RgTask: """State and methods for a single replaygain calculation (rg version). Bundles the state (parameters and results) of a single replaygain calculation (either for one item, one disk, or one full album). This class provides methods to store the resulting gains and peaks as plain old rg tags. """ def __init__( self, items: Sequence[Item], album: Optional[Album], target_level: float, peak_method: Optional[PeakMethod], backend_name: str, log: Logger, ): self.items = items self.album = album self.target_level = target_level self.peak_method = peak_method self.backend_name = backend_name self._log = log self.album_gain: Optional[Gain] = None self.track_gains: Optional[List[Gain]] = None def _store_track_gain(self, item: Item, track_gain: Gain): """Store track gain for a single item in the database.""" item.rg_track_gain = track_gain.gain item.rg_track_peak = track_gain.peak item.store() self._log.debug( "applied track gain {0} LU, peak {1} of FS", item.rg_track_gain, item.rg_track_peak, ) def _store_album_gain(self, item: Item, album_gain: Gain): """Store album gain for a single item in the database. The caller needs to ensure that `self.album_gain is not None`. """ item.rg_album_gain = album_gain.gain item.rg_album_peak = album_gain.peak item.store() self._log.debug( "applied album gain {0} LU, peak {1} of FS", item.rg_album_gain, item.rg_album_peak, ) def _store_track(self, write: bool): """Store track gain for the first track of the task in the database.""" item = self.items[0] if self.track_gains is None or len(self.track_gains) != 1: # In some cases, backends fail to produce a valid # `track_gains` without throwing FatalReplayGainError # => raise non-fatal exception & continue raise ReplayGainError( "ReplayGain backend `{}` failed for track {}".format( self.backend_name, item ) ) self._store_track_gain(item, self.track_gains[0]) if write: item.try_write() self._log.debug("done analyzing {0}", item) def _store_album(self, write: bool): """Store track/album gains for all tracks of the task in the database.""" if ( self.album_gain is None or self.track_gains is None or len(self.track_gains) != len(self.items) ): # In some cases, backends fail to produce a valid # `album_gain` without throwing FatalReplayGainError # => raise non-fatal exception & continue raise ReplayGainError( "ReplayGain backend `{}` failed " "for some tracks in album {}".format( self.backend_name, self.album ) ) for item, track_gain in zip(self.items, self.track_gains): self._store_track_gain(item, track_gain) self._store_album_gain(item, self.album_gain) if write: item.try_write() self._log.debug("done analyzing {0}", item) def store(self, write: bool): """Store computed gains for the items of this task in the database.""" if self.album is not None: self._store_album(write) else: self._store_track(write) class R128Task(RgTask): """State and methods for a single replaygain calculation (r128 version). Bundles the state (parameters and results) of a single replaygain calculation (either for one item, one disk, or one full album). This class provides methods to store the resulting gains and peaks as R128 tags. """ def __init__( self, items: Sequence[Item], album: Optional[Album], target_level: float, backend_name: str, log: Logger, ): # R128_* tags do not store the track/album peak super().__init__(items, album, target_level, None, backend_name, log) def _store_track_gain(self, item: Item, track_gain: Gain): item.r128_track_gain = track_gain.gain item.store() self._log.debug("applied r128 track gain {0} LU", item.r128_track_gain) def _store_album_gain(self, item: Item, album_gain: Gain): """ The caller needs to ensure that `self.album_gain is not None`. """ item.r128_album_gain = album_gain.gain item.store() self._log.debug("applied r128 album gain {0} LU", item.r128_album_gain) AnyRgTask = TypeVar("AnyRgTask", bound=RgTask) class Backend(ABC): """An abstract class representing engine for calculating RG values.""" NAME = "" do_parallel = False def __init__(self, config: ConfigView, log: Logger): """Initialize the backend with the configuration view for the plugin. """ self._log = log @abstractmethod def compute_track_gain(self, task: AnyRgTask) -> AnyRgTask: """Computes the track gain for the tracks belonging to `task`, and sets the `track_gains` attribute on the task. Returns `task`. """ raise NotImplementedError() @abstractmethod def compute_album_gain(self, task: AnyRgTask) -> AnyRgTask: """Computes the album gain for the album belonging to `task`, and sets the `album_gain` attribute on the task. Returns `task`. """ raise NotImplementedError() # ffmpeg backend class FfmpegBackend(Backend): """A replaygain backend using ffmpeg's ebur128 filter.""" NAME = "ffmpeg" do_parallel = True def __init__(self, config: ConfigView, log: Logger): super().__init__(config, log) self._ffmpeg_path = "ffmpeg" # check that ffmpeg is installed try: ffmpeg_version_out = call([self._ffmpeg_path, "-version"], log) except OSError: raise FatalReplayGainError( f"could not find ffmpeg at {self._ffmpeg_path}" ) incompatible_ffmpeg = True for line in ffmpeg_version_out.stdout.splitlines(): if line.startswith(b"configuration:"): if b"--enable-libebur128" in line: incompatible_ffmpeg = False if line.startswith(b"libavfilter"): version = line.split(b" ", 1)[1].split(b"/", 1)[0].split(b".") version = tuple(map(int, version)) if version >= (6, 67, 100): incompatible_ffmpeg = False if incompatible_ffmpeg: raise FatalReplayGainError( "Installed FFmpeg version does not support ReplayGain." "calculation. Either libavfilter version 6.67.100 or above or" "the --enable-libebur128 configuration option is required." ) def compute_track_gain(self, task: AnyRgTask) -> AnyRgTask: """Computes the track gain for the tracks belonging to `task`, and sets the `track_gains` attribute on the task. Returns `task`. """ task.track_gains = [ self._analyse_item( item, task.target_level, task.peak_method, count_blocks=False, )[0] # take only the gain, discarding number of gating blocks for item in task.items ] return task def compute_album_gain(self, task: AnyRgTask) -> AnyRgTask: """Computes the album gain for the album belonging to `task`, and sets the `album_gain` attribute on the task. Returns `task`. """ target_level_lufs = db_to_lufs(task.target_level) # analyse tracks # Gives a list of tuples (track_gain, track_n_blocks) track_results: List[Tuple[Gain, int]] = [ self._analyse_item( item, task.target_level, task.peak_method, count_blocks=True, ) for item in task.items ] track_gains: List[Gain] = [tg for tg, _nb in track_results] # Album peak is maximum track peak album_peak = max(tg.peak for tg in track_gains) # Total number of BS.1770 gating blocks n_blocks = sum(nb for _tg, nb in track_results) def sum_of_track_powers(track_gain: Gain, track_n_blocks: int): # convert `LU to target_level` -> LUFS loudness = target_level_lufs - track_gain.gain # This reverses ITU-R BS.1770-4 p. 6 equation (5) to convert # from loudness to power. The result is the average gating # block power. power = 10 ** ((loudness + 0.691) / 10) # Multiply that average power by the number of gating blocks to get # the sum of all block powers in this track. return track_n_blocks * power # calculate album gain if n_blocks > 0: # Sum over all tracks to get the sum of BS.1770 gating block powers # for the entire album. sum_powers = sum( sum_of_track_powers(tg, nb) for tg, nb in track_results ) # compare ITU-R BS.1770-4 p. 6 equation (5) # Album gain is the replaygain of the concatenation of all tracks. album_gain = -0.691 + 10 * math.log10(sum_powers / n_blocks) else: album_gain = -70 # convert LUFS -> `LU to target_level` album_gain = target_level_lufs - album_gain self._log.debug( "{}: gain {} LU, peak {}", task.album, album_gain, album_peak, ) task.album_gain = Gain(album_gain, album_peak) task.track_gains = track_gains return task def _construct_cmd( self, item: Item, peak_method: Optional[PeakMethod] ) -> List[Union[str, bytes]]: """Construct the shell command to analyse items.""" return [ self._ffmpeg_path, "-nostats", "-hide_banner", "-i", item.path, "-map", "a:0", "-filter", "ebur128=peak={}".format( "none" if peak_method is None else peak_method.name ), "-f", "null", "-", ] def _analyse_item( self, item: Item, target_level: float, peak_method: Optional[PeakMethod], count_blocks: bool = True, ) -> Tuple[Gain, int]: """Analyse item. Return a pair of a Gain object and the number of gating blocks above the threshold. If `count_blocks` is False, the number of gating blocks returned will be 0. """ target_level_lufs = db_to_lufs(target_level) # call ffmpeg self._log.debug(f"analyzing {item}") cmd = self._construct_cmd(item, peak_method) self._log.debug("executing {0}", " ".join(map(displayable_path, cmd))) output = call(cmd, self._log).stderr.splitlines() # parse output if peak_method is None: peak = 0.0 else: line_peak = self._find_line( output, # `peak_method` is non-`None` in this arm of the conditional f" {peak_method.name.capitalize()} peak:".encode(), start_line=len(output) - 1, step_size=-1, ) peak = self._parse_float( output[ self._find_line( output, b" Peak:", line_peak, ) ] ) # convert TPFS -> part of FS peak = 10 ** (peak / 20) line_integrated_loudness = self._find_line( output, b" Integrated loudness:", start_line=len(output) - 1, step_size=-1, ) gain = self._parse_float( output[ self._find_line( output, b" I:", line_integrated_loudness, ) ] ) # convert LUFS -> LU from target level gain = target_level_lufs - gain # count BS.1770 gating blocks n_blocks = 0 if count_blocks: gating_threshold = self._parse_float( output[ self._find_line( output, b" Threshold:", start_line=line_integrated_loudness, ) ] ) for line in output: if not line.startswith(b"[Parsed_ebur128"): continue if line.endswith(b"Summary:"): continue line = line.split(b"M:", 1) if len(line) < 2: continue if self._parse_float(b"M: " + line[1]) >= gating_threshold: n_blocks += 1 self._log.debug( "{}: {} blocks over {} LUFS".format( item, n_blocks, gating_threshold ) ) self._log.debug("{}: gain {} LU, peak {}".format(item, gain, peak)) return Gain(gain, peak), n_blocks def _find_line( self, output: Sequence[bytes], search: bytes, start_line: int = 0, step_size: int = 1, ) -> int: """Return index of line beginning with `search`. Begins searching at index `start_line` in `output`. """ end_index = len(output) if step_size > 0 else -1 for i in range(start_line, end_index, step_size): if output[i].startswith(search): return i raise ReplayGainError( "ffmpeg output: missing {} after line {}".format( repr(search), start_line ) ) def _parse_float(self, line: bytes) -> float: """Extract a float from a key value pair in `line`. This format is expected: /[^:]:[[:space:]]*value.*/, where `value` is the float. """ # extract value parts = line.split(b":", 1) if len(parts) < 2: raise ReplayGainError( f"ffmpeg output: expected key value pair, found {line!r}" ) value = parts[1].lstrip() # strip unit value = value.split(b" ", 1)[0] # cast value to float try: return float(value) except ValueError: raise ReplayGainError( f"ffmpeg output: expected float value, found {value!r}" ) # mpgain/aacgain CLI tool backend. class CommandBackend(Backend): NAME = "command" do_parallel = True def __init__(self, config: ConfigView, log: Logger): super().__init__(config, log) config.add( { "command": "", "noclip": True, } ) self.command = cast(str, config["command"].as_str()) if self.command: # Explicit executable path. if not os.path.isfile(self.command): raise FatalReplayGainError( "replaygain command does not exist: {}".format(self.command) ) else: # Check whether the program is in $PATH. for cmd in ("mp3gain", "aacgain"): try: call([cmd, "-v"], self._log) self.command = cmd except OSError: pass if not self.command: raise FatalReplayGainError( "no replaygain command found: install mp3gain or aacgain" ) self.noclip = config["noclip"].get(bool) def compute_track_gain(self, task: AnyRgTask) -> AnyRgTask: """Computes the track gain for the tracks belonging to `task`, and sets the `track_gains` attribute on the task. Returns `task`. """ supported_items = list(filter(self.format_supported, task.items)) output = self.compute_gain(supported_items, task.target_level, False) task.track_gains = output return task def compute_album_gain(self, task: AnyRgTask) -> AnyRgTask: """Computes the album gain for the album belonging to `task`, and sets the `album_gain` attribute on the task. Returns `task`. """ # TODO: What should be done when not all tracks in the album are # supported? supported_items = list(filter(self.format_supported, task.items)) if len(supported_items) != len(task.items): self._log.debug("tracks are of unsupported format") task.album_gain = None task.track_gains = None return task output = self.compute_gain(supported_items, task.target_level, True) task.album_gain = output[-1] task.track_gains = output[:-1] return task def format_supported(self, item: Item) -> bool: """Checks whether the given item is supported by the selected tool.""" if "mp3gain" in self.command and item.format != "MP3": return False elif "aacgain" in self.command and item.format not in ("MP3", "AAC"): return False return True def compute_gain( self, items: Sequence[Item], target_level: float, is_album: bool, ) -> List[Gain]: """Computes the track or album gain of a list of items, returns a list of TrackGain objects. When computing album gain, the last TrackGain object returned is the album gain """ if not items: self._log.debug("no supported tracks to analyze") return [] """Compute ReplayGain values and return a list of results dictionaries as given by `parse_tool_output`. """ # Construct shell command. The "-o" option makes the output # easily parseable (tab-delimited). "-s s" forces gain # recalculation even if tags are already present and disables # tag-writing; this turns the mp3gain/aacgain tool into a gain # calculator rather than a tag manipulator because we take care # of changing tags ourselves. cmd: List[Union[bytes, str]] = [self.command, "-o", "-s", "s"] if self.noclip: # Adjust to avoid clipping. cmd = cmd + ["-k"] else: # Disable clipping warning. cmd = cmd + ["-c"] cmd = cmd + ["-d", str(int(target_level - 89))] cmd = cmd + [syspath(i.path) for i in items] self._log.debug("analyzing {0} files", len(items)) self._log.debug("executing {0}", " ".join(map(displayable_path, cmd))) output = call(cmd, self._log).stdout self._log.debug("analysis finished") return self.parse_tool_output( output, len(items) + (1 if is_album else 0) ) def parse_tool_output(self, text: bytes, num_lines: int) -> List[Gain]: """Given the tab-delimited output from an invocation of mp3gain or aacgain, parse the text and return a list of dictionaries containing information about each analyzed file. """ out = [] for line in text.split(b"\n")[1 : num_lines + 1]: parts = line.split(b"\t") if len(parts) != 6 or parts[0] == b"File": self._log.debug("bad tool output: {0}", text) raise ReplayGainError("mp3gain failed") # _file = parts[0] # _mp3gain = int(parts[1]) gain = float(parts[2]) peak = float(parts[3]) / (1 << 15) # _maxgain = int(parts[4]) # _mingain = int(parts[5]) out.append(Gain(gain, peak)) return out # GStreamer-based backend. class GStreamerBackend(Backend): NAME = "gstreamer" def __init__(self, config: ConfigView, log: Logger): super().__init__(config, log) self._import_gst() # Initialized a GStreamer pipeline of the form filesrc -> # decodebin -> audioconvert -> audioresample -> rganalysis -> # fakesink The connection between decodebin and audioconvert is # handled dynamically after decodebin figures out the type of # the input file. self._src = self.Gst.ElementFactory.make("filesrc", "src") self._decbin = self.Gst.ElementFactory.make("decodebin", "decbin") self._conv = self.Gst.ElementFactory.make("audioconvert", "conv") self._res = self.Gst.ElementFactory.make("audioresample", "res") self._rg = self.Gst.ElementFactory.make("rganalysis", "rg") if ( self._src is None or self._decbin is None or self._conv is None or self._res is None or self._rg is None ): raise FatalGstreamerPluginReplayGainError( "Failed to load required GStreamer plugins" ) # We check which files need gain ourselves, so all files given # to rganalsys should have their gain computed, even if it # already exists. self._rg.set_property("forced", True) self._sink = self.Gst.ElementFactory.make("fakesink", "sink") self._pipe = self.Gst.Pipeline() self._pipe.add(self._src) self._pipe.add(self._decbin) self._pipe.add(self._conv) self._pipe.add(self._res) self._pipe.add(self._rg) self._pipe.add(self._sink) self._src.link(self._decbin) self._conv.link(self._res) self._res.link(self._rg) self._rg.link(self._sink) self._bus = self._pipe.get_bus() self._bus.add_signal_watch() self._bus.connect("message::eos", self._on_eos) self._bus.connect("message::error", self._on_error) self._bus.connect("message::tag", self._on_tag) # Needed for handling the dynamic connection between decodebin # and audioconvert self._decbin.connect("pad-added", self._on_pad_added) self._decbin.connect("pad-removed", self._on_pad_removed) self._main_loop = self.GLib.MainLoop() self._files: List[bytes] = [] def _import_gst(self): """Import the necessary GObject-related modules and assign `Gst` and `GObject` fields on this object. """ try: import gi except ImportError: raise FatalReplayGainError( "Failed to load GStreamer: python-gi not found" ) try: gi.require_version("Gst", "1.0") except ValueError as e: raise FatalReplayGainError(f"Failed to load GStreamer 1.0: {e}") from gi.repository import GLib, GObject, Gst # Calling GObject.threads_init() is not needed for # PyGObject 3.10.2+ with warnings.catch_warnings(): warnings.simplefilter("ignore") GObject.threads_init() Gst.init([sys.argv[0]]) self.GObject = GObject self.GLib = GLib self.Gst = Gst def compute(self, items: Sequence[Item], target_level: float, album: bool): if len(items) == 0: return self._error = None self._files = [i.path for i in items] # FIXME: Turn this into DefaultDict[bytes, Gain] self._file_tags: DefaultDict[bytes, Dict[str, float]] = ( collections.defaultdict(dict) ) self._rg.set_property("reference-level", target_level) if album: self._rg.set_property("num-tracks", len(self._files)) if self._set_first_file(): self._main_loop.run() if self._error is not None: raise self._error def compute_track_gain(self, task: AnyRgTask) -> AnyRgTask: """Computes the track gain for the tracks belonging to `task`, and sets the `track_gains` attribute on the task. Returns `task`. """ self.compute(task.items, task.target_level, False) if len(self._file_tags) != len(task.items): raise ReplayGainError("Some tracks did not receive tags") ret = [] for item in task.items: ret.append( Gain( self._file_tags[item.path]["TRACK_GAIN"], self._file_tags[item.path]["TRACK_PEAK"], ) ) task.track_gains = ret return task def compute_album_gain(self, task: AnyRgTask) -> AnyRgTask: """Computes the album gain for the album belonging to `task`, and sets the `album_gain` attribute on the task. Returns `task`. """ items = list(task.items) self.compute(items, task.target_level, True) if len(self._file_tags) != len(items): raise ReplayGainError("Some items in album did not receive tags") # Collect track gains. track_gains = [] for item in items: try: gain = self._file_tags[item.path]["TRACK_GAIN"] peak = self._file_tags[item.path]["TRACK_PEAK"] except KeyError: raise ReplayGainError("results missing for track") track_gains.append(Gain(gain, peak)) # Get album gain information from the last track. last_tags = self._file_tags[items[-1].path] try: gain = last_tags["ALBUM_GAIN"] peak = last_tags["ALBUM_PEAK"] except KeyError: raise ReplayGainError("results missing for album") task.album_gain = Gain(gain, peak) task.track_gains = track_gains return task def close(self): self._bus.remove_signal_watch() def _on_eos(self, bus, message): # A file finished playing in all elements of the pipeline. The # RG tags have already been propagated. If we don't have a next # file, we stop processing. if not self._set_next_file(): self._pipe.set_state(self.Gst.State.NULL) self._main_loop.quit() def _on_error(self, bus, message): self._pipe.set_state(self.Gst.State.NULL) self._main_loop.quit() err, debug = message.parse_error() f = self._src.get_property("location") # A GStreamer error, either an unsupported format or a bug. self._error = ReplayGainError( f"Error {err!r} - {debug!r} on file {f!r}" ) def _on_tag(self, bus, message): tags = message.parse_tag() def handle_tag(taglist, tag, userdata): # The rganalysis element provides both the existing tags for # files and the new computes tags. In order to ensure we # store the computed tags, we overwrite the RG values of # received a second time. if tag == self.Gst.TAG_TRACK_GAIN: self._file_tags[self._file]["TRACK_GAIN"] = taglist.get_double( tag )[1] elif tag == self.Gst.TAG_TRACK_PEAK: self._file_tags[self._file]["TRACK_PEAK"] = taglist.get_double( tag )[1] elif tag == self.Gst.TAG_ALBUM_GAIN: self._file_tags[self._file]["ALBUM_GAIN"] = taglist.get_double( tag )[1] elif tag == self.Gst.TAG_ALBUM_PEAK: self._file_tags[self._file]["ALBUM_PEAK"] = taglist.get_double( tag )[1] elif tag == self.Gst.TAG_REFERENCE_LEVEL: self._file_tags[self._file]["REFERENCE_LEVEL"] = ( taglist.get_double(tag)[1] ) tags.foreach(handle_tag, None) def _set_first_file(self) -> bool: if len(self._files) == 0: return False self._file = self._files.pop(0) self._pipe.set_state(self.Gst.State.NULL) self._src.set_property("location", os.fsdecode(syspath(self._file))) self._pipe.set_state(self.Gst.State.PLAYING) return True def _set_file(self) -> bool: """Initialize the filesrc element with the next file to be analyzed.""" # No more files, we're done if len(self._files) == 0: return False self._file = self._files.pop(0) # Ensure the filesrc element received the paused state of the # pipeline in a blocking manner self._src.sync_state_with_parent() self._src.get_state(self.Gst.CLOCK_TIME_NONE) # Ensure the decodebin element receives the paused state of the # pipeline in a blocking manner self._decbin.sync_state_with_parent() self._decbin.get_state(self.Gst.CLOCK_TIME_NONE) # Disconnect the decodebin element from the pipeline, set its # state to READY to to clear it. self._decbin.unlink(self._conv) self._decbin.set_state(self.Gst.State.READY) # Set a new file on the filesrc element, can only be done in the # READY state self._src.set_state(self.Gst.State.READY) self._src.set_property("location", os.fsdecode(syspath(self._file))) self._decbin.link(self._conv) self._pipe.set_state(self.Gst.State.READY) return True def _set_next_file(self) -> bool: """Set the next file to be analyzed while keeping the pipeline in the PAUSED state so that the rganalysis element can correctly handle album gain. """ # A blocking pause self._pipe.set_state(self.Gst.State.PAUSED) self._pipe.get_state(self.Gst.CLOCK_TIME_NONE) # Try setting the next file ret = self._set_file() if ret: # Seek to the beginning in order to clear the EOS state of the # various elements of the pipeline self._pipe.seek_simple( self.Gst.Format.TIME, self.Gst.SeekFlags.FLUSH, 0 ) self._pipe.set_state(self.Gst.State.PLAYING) return ret def _on_pad_added(self, decbin, pad): sink_pad = self._conv.get_compatible_pad(pad, None) assert sink_pad is not None pad.link(sink_pad) def _on_pad_removed(self, decbin, pad): # Called when the decodebin element is disconnected from the # rest of the pipeline while switching input files peer = pad.get_peer() assert peer is None class AudioToolsBackend(Backend): """ReplayGain backend that uses `Python Audio Tools <http://audiotools.sourceforge.net/>`_ and its capabilities to read more file formats and compute ReplayGain values using it replaygain module. """ NAME = "audiotools" def __init__(self, config: ConfigView, log: Logger): super().__init__(config, log) self._import_audiotools() def _import_audiotools(self): """Check whether it's possible to import the necessary modules. There is no check on the file formats at runtime. :raises :exc:`ReplayGainError`: if the modules cannot be imported """ try: import audiotools import audiotools.replaygain except ImportError: raise FatalReplayGainError( "Failed to load audiotools: audiotools not found" ) self._mod_audiotools = audiotools self._mod_replaygain = audiotools.replaygain def open_audio_file(self, item: Item): """Open the file to read the PCM stream from the using ``item.path``. :return: the audiofile instance :rtype: :class:`audiotools.AudioFile` :raises :exc:`ReplayGainError`: if the file is not found or the file format is not supported """ try: audiofile = self._mod_audiotools.open( os.fsdecode(syspath(item.path)) ) except OSError: raise ReplayGainError(f"File {item.path} was not found") except self._mod_audiotools.UnsupportedFile: raise ReplayGainError(f"Unsupported file type {item.format}") return audiofile def init_replaygain(self, audiofile, item: Item): """Return an initialized :class:`audiotools.replaygain.ReplayGain` instance, which requires the sample rate of the song(s) on which the ReplayGain values will be computed. The item is passed in case the sample rate is invalid to log the stored item sample rate. :return: initialized replagain object :rtype: :class:`audiotools.replaygain.ReplayGain` :raises: :exc:`ReplayGainError` if the sample rate is invalid """ try: rg = self._mod_replaygain.ReplayGain(audiofile.sample_rate()) except ValueError: raise ReplayGainError(f"Unsupported sample rate {item.samplerate}") return return rg def compute_track_gain(self, task: AnyRgTask) -> AnyRgTask: """Computes the track gain for the tracks belonging to `task`, and sets the `track_gains` attribute on the task. Returns `task`. """ gains = [ self._compute_track_gain(i, task.target_level) for i in task.items ] task.track_gains = gains return task def _with_target_level(self, gain: float, target_level: float): """Return `gain` relative to `target_level`. Assumes `gain` is relative to 89 db. """ return gain + (target_level - 89) def _title_gain(self, rg, audiofile, target_level: float): """Get the gain result pair from PyAudioTools using the `ReplayGain` instance `rg` for the given `audiofile`. Wraps `rg.title_gain(audiofile.to_pcm())` and throws a `ReplayGainError` when the library fails. """ try: # The method needs an audiotools.PCMReader instance that can # be obtained from an audiofile instance. gain, peak = rg.title_gain(audiofile.to_pcm()) except ValueError as exc: # `audiotools.replaygain` can raise a `ValueError` if the sample # rate is incorrect. self._log.debug("error in rg.title_gain() call: {}", exc) raise ReplayGainError("audiotools audio data error") return self._with_target_level(gain, target_level), peak def _compute_track_gain(self, item: Item, target_level: float): """Compute ReplayGain value for the requested item. :rtype: :class:`Gain` """ audiofile = self.open_audio_file(item) rg = self.init_replaygain(audiofile, item) # Each call to title_gain on a ReplayGain object returns peak and gain # of the track. rg_track_gain, rg_track_peak = self._title_gain( rg, audiofile, target_level ) self._log.debug( "ReplayGain for track {0} - {1}: {2:.2f}, {3:.2f}", item.artist, item.title, rg_track_gain, rg_track_peak, ) return Gain(gain=rg_track_gain, peak=rg_track_peak) def compute_album_gain(self, task: AnyRgTask) -> AnyRgTask: """Computes the album gain for the album belonging to `task`, and sets the `album_gain` attribute on the task. Returns `task`. """ # The first item is taken and opened to get the sample rate to # initialize the replaygain object. The object is used for all the # tracks in the album to get the album values. item = list(task.items)[0] audiofile = self.open_audio_file(item) rg = self.init_replaygain(audiofile, item) track_gains = [] for item in task.items: audiofile = self.open_audio_file(item) rg_track_gain, rg_track_peak = self._title_gain( rg, audiofile, task.target_level ) track_gains.append(Gain(gain=rg_track_gain, peak=rg_track_peak)) self._log.debug( "ReplayGain for track {0}: {1:.2f}, {2:.2f}", item, rg_track_gain, rg_track_peak, ) # After getting the values for all tracks, it's possible to get the # album values. rg_album_gain, rg_album_peak = rg.album_gain() rg_album_gain = self._with_target_level( rg_album_gain, task.target_level ) self._log.debug( "ReplayGain for album {0}: {1:.2f}, {2:.2f}", task.items[0].album, rg_album_gain, rg_album_peak, ) task.album_gain = Gain(gain=rg_album_gain, peak=rg_album_peak) task.track_gains = track_gains return task class ExceptionWatcher(Thread): """Monitors a queue for exceptions asynchronously. Once an exception occurs, raise it and execute a callback. """ def __init__(self, queue: queue.Queue, callback: Callable[[], None]): self._queue = queue self._callback = callback self._stopevent = Event() Thread.__init__(self) def run(self): while not self._stopevent.is_set(): try: exc = self._queue.get_nowait() self._callback() raise exc except queue.Empty: # No exceptions yet, loop back to check # whether `_stopevent` is set pass def join(self, timeout: Optional[float] = None): self._stopevent.set() Thread.join(self, timeout) # Main plugin logic. BACKEND_CLASSES: List[Type[Backend]] = [ CommandBackend, GStreamerBackend, AudioToolsBackend, FfmpegBackend, ] BACKENDS: Dict[str, Type[Backend]] = {b.NAME: b for b in BACKEND_CLASSES} class ReplayGainPlugin(BeetsPlugin): """Provides ReplayGain analysis.""" def __init__(self): super().__init__() # default backend is 'command' for backward-compatibility. self.config.add( { "overwrite": False, "auto": True, "backend": "command", "threads": os.cpu_count(), "parallel_on_import": False, "per_disc": False, "peak": "true", "targetlevel": 89, "r128": ["Opus"], "r128_targetlevel": lufs_to_db(-23), } ) # FIXME: Consider renaming the configuration option and deprecating the # old name 'overwrite'. self.force_on_import = cast(bool, self.config["overwrite"].get(bool)) # Remember which backend is used for CLI feedback self.backend_name = self.config["backend"].as_str() if self.backend_name not in BACKENDS: raise ui.UserError( "Selected ReplayGain backend {} is not supported. " "Please select one of: {}".format( self.backend_name, ", ".join(BACKENDS.keys()) ) ) # FIXME: Consider renaming the configuration option to 'peak_method' # and deprecating the old name 'peak'. peak_method = self.config["peak"].as_str() if peak_method not in PeakMethod.__members__: raise ui.UserError( "Selected ReplayGain peak method {} is not supported. " "Please select one of: {}".format( peak_method, ", ".join(PeakMethod.__members__) ) ) # This only applies to plain old rg tags, r128 doesn't store peak # values. self.peak_method = PeakMethod[peak_method] # On-import analysis. if self.config["auto"]: self.register_listener("import_begin", self.import_begin) self.register_listener("import", self.import_end) self.import_stages = [self.imported] # Formats to use R128. self.r128_whitelist = self.config["r128"].as_str_seq() try: self.backend_instance = BACKENDS[self.backend_name]( self.config, self._log ) except (ReplayGainError, FatalReplayGainError) as e: raise ui.UserError(f"replaygain initialization failed: {e}") # Start threadpool lazily. self.pool = None def should_use_r128(self, item: Item) -> bool: """Checks the plugin setting to decide whether the calculation should be done using the EBU R128 standard and use R128_ tags instead. """ return item.format in self.r128_whitelist @staticmethod def has_r128_track_data(item: Item) -> bool: return item.r128_track_gain is not None @staticmethod def has_rg_track_data(item: Item) -> bool: return item.rg_track_gain is not None and item.rg_track_peak is not None def track_requires_gain(self, item: Item) -> bool: if self.should_use_r128(item): if not self.has_r128_track_data(item): return True else: if not self.has_rg_track_data(item): return True return False @staticmethod def has_r128_album_data(item: Item) -> bool: return ( item.r128_track_gain is not None and item.r128_album_gain is not None ) @staticmethod def has_rg_album_data(item: Item) -> bool: return item.rg_album_gain is not None and item.rg_album_peak is not None def album_requires_gain(self, album: Album) -> bool: # Skip calculating gain only when *all* files don't need # recalculation. This way, if any file among an album's tracks # needs recalculation, we still get an accurate album gain # value. for item in album.items(): if self.should_use_r128(item): if not self.has_r128_album_data(item): return True else: if not self.has_rg_album_data(item): return True return False def create_task( self, items: Sequence[Item], use_r128: bool, album: Optional[Album] = None, ) -> RgTask: if use_r128: return R128Task( items, album, self.config["r128_targetlevel"].as_number(), self.backend_instance.NAME, self._log, ) else: return RgTask( items, album, self.config["targetlevel"].as_number(), self.peak_method, self.backend_instance.NAME, self._log, ) def handle_album(self, album: Album, write: bool, force: bool = False): """Compute album and track replay gain store it in all of the album's items. If ``write`` is truthy then ``item.write()`` is called for each item. If replay gain information is already present in all items, nothing is done. """ if not force and not self.album_requires_gain(album): self._log.info("Skipping album {0}", album) return items_iter = iter(album.items()) use_r128 = self.should_use_r128(next(items_iter)) if any(use_r128 != self.should_use_r128(i) for i in items_iter): self._log.error( "Cannot calculate gain for album {0} (incompatible formats)", album, ) return self._log.info("analyzing {0}", album) discs: Dict[int, List[Item]] = {} if self.config["per_disc"].get(bool): for item in album.items(): if discs.get(item.disc) is None: discs[item.disc] = [] discs[item.disc].append(item) else: discs[1] = album.items() def store_cb(task: RgTask): task.store(write) for discnumber, items in discs.items(): task = self.create_task(items, use_r128, album=album) try: self._apply( self.backend_instance.compute_album_gain, args=[task], kwds={}, callback=store_cb, ) except ReplayGainError as e: self._log.info("ReplayGain error: {0}", e) except FatalReplayGainError as e: raise ui.UserError(f"Fatal replay gain error: {e}") def handle_track(self, item: Item, write: bool, force: bool = False): """Compute track replay gain and store it in the item. If ``write`` is truthy then ``item.write()`` is called to write the data to disk. If replay gain information is already present in the item, nothing is done. """ if not force and not self.track_requires_gain(item): self._log.info("Skipping track {0}", item) return use_r128 = self.should_use_r128(item) def store_cb(task: RgTask): task.store(write) task = self.create_task([item], use_r128) try: self._apply( self.backend_instance.compute_track_gain, args=[task], kwds={}, callback=store_cb, ) except ReplayGainError as e: self._log.info("ReplayGain error: {0}", e) except FatalReplayGainError as e: raise ui.UserError(f"Fatal replay gain error: {e}") def open_pool(self, threads: int): """Open a `ThreadPool` instance in `self.pool`""" if self.pool is None and self.backend_instance.do_parallel: self.pool = ThreadPool(threads) self.exc_queue: queue.Queue = queue.Queue() signal.signal(signal.SIGINT, self._interrupt) self.exc_watcher = ExceptionWatcher( self.exc_queue, # threads push exceptions here self.terminate_pool, # abort once an exception occurs ) self.exc_watcher.start() def _apply( self, func: Callable[..., AnyRgTask], args: List[Any], kwds: Dict[str, Any], callback: Callable[[AnyRgTask], Any], ): if self.pool is not None: def handle_exc(exc): """Handle exceptions in the async work.""" if isinstance(exc, ReplayGainError): self._log.info(exc.args[0]) # Log non-fatal exceptions. else: self.exc_queue.put(exc) self.pool.apply_async( func, args, kwds, callback, error_callback=handle_exc ) else: callback(func(*args, **kwds)) def terminate_pool(self): """Forcibly terminate the `ThreadPool` instance in `self.pool` Sends SIGTERM to all processes. """ if self.pool is not None: self.pool.terminate() self.pool.join() # Terminating the processes leaves the ExceptionWatcher's queues # in an unknown state, so don't wait for it. # self.exc_watcher.join() self.pool = None def _interrupt(self, signal, frame): try: self._log.info("interrupted") self.terminate_pool() sys.exit(0) except SystemExit: # Silence raised SystemExit ~ exit(0) pass def close_pool(self): """Regularly close the `ThreadPool` instance in `self.pool`.""" if self.pool is not None: self.pool.close() self.pool.join() self.exc_watcher.join() self.pool = None def import_begin(self, session: ImportSession): """Handle `import_begin` event -> open pool""" threads = cast(int, self.config["threads"].get(int)) if ( self.config["parallel_on_import"] and self.config["auto"] and threads ): self.open_pool(threads) def import_end(self, paths): """Handle `import` event -> close pool""" self.close_pool() def imported(self, session: ImportSession, task: ImportTask): """Add replay gain info to items or albums of ``task``.""" if self.config["auto"]: if task.is_album: self.handle_album(task.album, False, self.force_on_import) else: # Should be a SingletonImportTask assert hasattr(task, "item") self.handle_track(task.item, False, self.force_on_import) def command_func( self, lib: Library, opts: optparse.Values, args: List[str], ): try: write = ui.should_write(opts.write) force = opts.force # Bypass self.open_pool() if called with `--threads 0` if opts.threads != 0: threads = opts.threads or cast( int, self.config["threads"].get(int) ) self.open_pool(threads) if opts.album: albums = lib.albums(ui.decargs(args)) self._log.info( "Analyzing {} albums ~ {} backend...".format( len(albums), self.backend_name ) ) for album in albums: self.handle_album(album, write, force) else: items = lib.items(ui.decargs(args)) self._log.info( "Analyzing {} tracks ~ {} backend...".format( len(items), self.backend_name ) ) for item in items: self.handle_track(item, write, force) self.close_pool() except (SystemExit, KeyboardInterrupt): # Silence interrupt exceptions pass def commands(self) -> List[ui.Subcommand]: """Return the "replaygain" ui subcommand.""" cmd = ui.Subcommand("replaygain", help="analyze for ReplayGain") cmd.parser.add_album_option() cmd.parser.add_option( "-t", "--threads", dest="threads", type=int, help="change the number of threads, \ defaults to maximum available processors", ) cmd.parser.add_option( "-f", "--force", dest="force", action="store_true", default=False, help="analyze all files, including those that " "already have ReplayGain metadata", ) cmd.parser.add_option( "-w", "--write", default=None, action="store_true", help="write new metadata to files' tags", ) cmd.parser.add_option( "-W", "--nowrite", dest="write", action="store_false", help="don't write metadata (opposite of -w)", ) cmd.func = self.command_func return [cmd] �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������beetbox-beets-01f1faf/beetsplug/rewrite.py����������������������������������������������������������0000664�0000000�0000000�00000005261�14723254774�0021000�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# This file is part of beets. # Copyright 2016, Adrian Sampson. # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the # "Software"), to deal in the Software without restriction, including # without limitation the rights to use, copy, modify, merge, publish, # distribute, sublicense, and/or sell copies of the Software, and to # permit persons to whom the Software is furnished to do so, subject to # the following conditions: # # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. """Uses user-specified rewriting rules to canonicalize names for path formats. """ import re from collections import defaultdict from beets import library, ui from beets.plugins import BeetsPlugin def rewriter(field, rules): """Create a template field function that rewrites the given field with the given rewriting rules. ``rules`` must be a list of (pattern, replacement) pairs. """ def fieldfunc(item): value = item._values_fixed[field] for pattern, replacement in rules: if pattern.match(value.lower()): # Rewrite activated. return replacement # Not activated; return original value. return value return fieldfunc class RewritePlugin(BeetsPlugin): def __init__(self): super().__init__() self.config.add({}) # Gather all the rewrite rules for each field. rules = defaultdict(list) for key, view in self.config.items(): value = view.as_str() try: fieldname, pattern = key.split(None, 1) except ValueError: raise ui.UserError("invalid rewrite specification") if fieldname not in library.Item._fields: raise ui.UserError( "invalid field name (%s) in rewriter" % fieldname ) self._log.debug("adding template field {0}", key) pattern = re.compile(pattern.lower()) rules[fieldname].append((pattern, value)) if fieldname == "artist": # Special case for the artist field: apply the same # rewrite for "albumartist" as well. rules["albumartist"].append((pattern, value)) # Replace each template field with the new rewriter function. for fieldname, fieldrules in rules.items(): getter = rewriter(fieldname, fieldrules) self.template_fields[fieldname] = getter if fieldname in library.Album._fields: self.album_template_fields[fieldname] = getter �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������beetbox-beets-01f1faf/beetsplug/scrub.py������������������������������������������������������������0000664�0000000�0000000�00000012167�14723254774�0020440�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# This file is part of beets. # Copyright 2016, Adrian Sampson. # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the # "Software"), to deal in the Software without restriction, including # without limitation the rights to use, copy, modify, merge, publish, # distribute, sublicense, and/or sell copies of the Software, and to # permit persons to whom the Software is furnished to do so, subject to # the following conditions: # # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. """Cleans extraneous metadata from files' tags via a command or automatically whenever tags are written. """ import mediafile import mutagen from beets import config, ui, util from beets.plugins import BeetsPlugin _MUTAGEN_FORMATS = { "asf": "ASF", "apev2": "APEv2File", "flac": "FLAC", "id3": "ID3FileType", "mp3": "MP3", "mp4": "MP4", "oggflac": "OggFLAC", "oggspeex": "OggSpeex", "oggtheora": "OggTheora", "oggvorbis": "OggVorbis", "oggopus": "OggOpus", "trueaudio": "TrueAudio", "wavpack": "WavPack", "monkeysaudio": "MonkeysAudio", "optimfrog": "OptimFROG", } class ScrubPlugin(BeetsPlugin): """Removes extraneous metadata from files' tags.""" def __init__(self): super().__init__() self.config.add( { "auto": True, } ) if self.config["auto"]: self.register_listener("import_task_files", self.import_task_files) def commands(self): def scrub_func(lib, opts, args): # Walk through matching files and remove tags. for item in lib.items(ui.decargs(args)): self._log.info( "scrubbing: {0}", util.displayable_path(item.path) ) self._scrub_item(item, opts.write) scrub_cmd = ui.Subcommand("scrub", help="clean audio tags") scrub_cmd.parser.add_option( "-W", "--nowrite", dest="write", action="store_false", default=True, help="leave tags empty", ) scrub_cmd.func = scrub_func return [scrub_cmd] @staticmethod def _mutagen_classes(): """Get a list of file type classes from the Mutagen module.""" classes = [] for modname, clsname in _MUTAGEN_FORMATS.items(): mod = __import__(f"mutagen.{modname}", fromlist=[clsname]) classes.append(getattr(mod, clsname)) return classes def _scrub(self, path): """Remove all tags from a file.""" for cls in self._mutagen_classes(): # Try opening the file with this type, but just skip in the # event of any error. try: f = cls(util.syspath(path)) except Exception: continue if f.tags is None: continue # Remove the tag for this type. try: f.delete() except NotImplementedError: # Some Mutagen metadata subclasses (namely, ASFTag) do not # support .delete(), presumably because it is impossible to # remove them. In this case, we just remove all the tags. for tag in f.keys(): del f[tag] f.save() except (OSError, mutagen.MutagenError) as exc: self._log.error( "could not scrub {0}: {1}", util.displayable_path(path), exc ) def _scrub_item(self, item, restore): """Remove tags from an Item's associated file and, if `restore` is enabled, write the database's tags back to the file. """ # Get album art if we need to restore it. if restore: try: mf = mediafile.MediaFile( util.syspath(item.path), config["id3v23"].get(bool) ) except mediafile.UnreadableFileError as exc: self._log.error("could not open file to scrub: {0}", exc) return images = mf.images # Remove all tags. self._scrub(item.path) # Restore tags, if enabled. if restore: self._log.debug("writing new tags after scrub") item.try_write() if images: self._log.debug("restoring art") try: mf = mediafile.MediaFile( util.syspath(item.path), config["id3v23"].get(bool) ) mf.images = images mf.save() except mediafile.UnreadableFileError as exc: self._log.error("could not write tags: {0}", exc) def import_task_files(self, session, task): """Automatically scrub imported files.""" for item in task.imported_items(): self._log.debug( "auto-scrubbing {0}", util.displayable_path(item.path) ) self._scrub_item(item, ui.should_write()) ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������beetbox-beets-01f1faf/beetsplug/smartplaylist.py����������������������������������������������������0000664�0000000�0000000�00000031772�14723254774�0022235�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# This file is part of beets. # Copyright 2016, Dang Mai <contact@dangmai.net>. # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the # "Software"), to deal in the Software without restriction, including # without limitation the rights to use, copy, modify, merge, publish, # distribute, sublicense, and/or sell copies of the Software, and to # permit persons to whom the Software is furnished to do so, subject to # the following conditions: # # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. """Generates smart playlists based on beets queries.""" import json import os from urllib.request import pathname2url from beets import ui from beets.dbcore import OrQuery from beets.dbcore.query import MultipleSort, ParsingError from beets.library import Album, Item, parse_query_string from beets.plugins import BeetsPlugin from beets.plugins import send as send_event from beets.util import ( bytestring_path, displayable_path, mkdirall, normpath, path_as_posix, sanitize_path, syspath, ) class SmartPlaylistPlugin(BeetsPlugin): def __init__(self): super().__init__() self.config.add( { "relative_to": None, "playlist_dir": ".", "auto": True, "playlists": [], "uri_format": None, "fields": [], "forward_slash": False, "prefix": "", "urlencode": False, "pretend_paths": False, "output": "m3u", } ) self.config["prefix"].redact = True # May contain username/password. self._matched_playlists = None self._unmatched_playlists = None if self.config["auto"]: self.register_listener("database_change", self.db_change) def commands(self): spl_update = ui.Subcommand( "splupdate", help="update the smart playlists. Playlist names may be " "passed as arguments.", ) spl_update.parser.add_option( "-p", "--pretend", action="store_true", help="display query results but don't write playlist files.", ) spl_update.parser.add_option( "--pretend-paths", action="store_true", dest="pretend_paths", help="in pretend mode, log the playlist item URIs/paths.", ) spl_update.parser.add_option( "-d", "--playlist-dir", dest="playlist_dir", metavar="PATH", type="string", help="directory to write the generated playlist files to.", ) spl_update.parser.add_option( "--relative-to", dest="relative_to", metavar="PATH", type="string", help="generate playlist item paths relative to this path.", ) spl_update.parser.add_option( "--prefix", type="string", help="prepend string to every path in the playlist file.", ) spl_update.parser.add_option( "--forward-slash", action="store_true", dest="forward_slash", help="force forward slash in paths within playlists.", ) spl_update.parser.add_option( "--urlencode", action="store_true", help="URL-encode all paths.", ) spl_update.parser.add_option( "--uri-format", dest="uri_format", type="string", help="playlist item URI template, e.g. http://beets:8337/item/$id/file.", ) spl_update.parser.add_option( "--output", type="string", help="specify the playlist format: m3u|extm3u.", ) spl_update.func = self.update_cmd return [spl_update] def update_cmd(self, lib, opts, args): self.build_queries() if args: args = set(ui.decargs(args)) for a in list(args): if not a.endswith(".m3u"): args.add(f"{a}.m3u") playlists = { (name, q, a_q) for name, q, a_q in self._unmatched_playlists if name in args } if not playlists: raise ui.UserError( "No playlist matching any of {} found".format( [name for name, _, _ in self._unmatched_playlists] ) ) self._matched_playlists = playlists self._unmatched_playlists -= playlists else: self._matched_playlists = self._unmatched_playlists self.__apply_opts_to_config(opts) self.update_playlists(lib, opts.pretend) def __apply_opts_to_config(self, opts): for k, v in opts.__dict__.items(): if v is not None and k in self.config: self.config[k] = v def build_queries(self): """ Instantiate queries for the playlists. Each playlist has 2 queries: one or items one for albums, each with a sort. We must also remember its name. _unmatched_playlists is a set of tuples (name, (q, q_sort), (album_q, album_q_sort)). sort may be any sort, or NullSort, or None. None and NullSort are equivalent and both eval to False. More precisely - it will be NullSort when a playlist query ('query' or 'album_query') is a single item or a list with 1 element - it will be None when there are multiple items i a query """ self._unmatched_playlists = set() self._matched_playlists = set() for playlist in self.config["playlists"].get(list): if "name" not in playlist: self._log.warning("playlist configuration is missing name") continue playlist_data = (playlist["name"],) try: for key, model_cls in (("query", Item), ("album_query", Album)): qs = playlist.get(key) if qs is None: query_and_sort = None, None elif isinstance(qs, str): query_and_sort = parse_query_string(qs, model_cls) elif len(qs) == 1: query_and_sort = parse_query_string(qs[0], model_cls) else: # multiple queries and sorts queries, sorts = zip( *(parse_query_string(q, model_cls) for q in qs) ) query = OrQuery(queries) final_sorts = [] for s in sorts: if s: if isinstance(s, MultipleSort): final_sorts += s.sorts else: final_sorts.append(s) if not final_sorts: sort = None elif len(final_sorts) == 1: (sort,) = final_sorts else: sort = MultipleSort(final_sorts) query_and_sort = query, sort playlist_data += (query_and_sort,) except ParsingError as exc: self._log.warning( "invalid query in playlist {}: {}", playlist["name"], exc ) continue self._unmatched_playlists.add(playlist_data) def matches(self, model, query, album_query): if album_query and isinstance(model, Album): return album_query.match(model) if query and isinstance(model, Item): return query.match(model) return False def db_change(self, lib, model): if self._unmatched_playlists is None: self.build_queries() for playlist in self._unmatched_playlists: n, (q, _), (a_q, _) = playlist if self.matches(model, q, a_q): self._log.debug("{0} will be updated because of {1}", n, model) self._matched_playlists.add(playlist) self.register_listener("cli_exit", self.update_playlists) self._unmatched_playlists -= self._matched_playlists def update_playlists(self, lib, pretend=False): if pretend: self._log.info( "Showing query results for {0} smart playlists...", len(self._matched_playlists), ) else: self._log.info( "Updating {0} smart playlists...", len(self._matched_playlists) ) playlist_dir = self.config["playlist_dir"].as_filename() playlist_dir = bytestring_path(playlist_dir) tpl = self.config["uri_format"].get() prefix = bytestring_path(self.config["prefix"].as_str()) relative_to = self.config["relative_to"].get() if relative_to: relative_to = normpath(relative_to) # Maps playlist filenames to lists of track filenames. m3us = {} for playlist in self._matched_playlists: name, (query, q_sort), (album_query, a_q_sort) = playlist if pretend: self._log.info("Results for playlist {}:", name) else: self._log.info("Creating playlist {0}", name) items = [] if query: items.extend(lib.items(query, q_sort)) if album_query: for album in lib.albums(album_query, a_q_sort): items.extend(album.items()) # As we allow tags in the m3u names, we'll need to iterate through # the items and generate the correct m3u file names. for item in items: m3u_name = item.evaluate_template(name, True) m3u_name = sanitize_path(m3u_name, lib.replacements) if m3u_name not in m3us: m3us[m3u_name] = [] item_uri = item.path if tpl: item_uri = tpl.replace("$id", str(item.id)).encode("utf-8") else: if relative_to: item_uri = os.path.relpath(item_uri, relative_to) if self.config["forward_slash"].get(): item_uri = path_as_posix(item_uri) if self.config["urlencode"]: item_uri = bytestring_path(pathname2url(item_uri)) item_uri = prefix + item_uri if item_uri not in m3us[m3u_name]: m3us[m3u_name].append(PlaylistItem(item, item_uri)) if pretend and self.config["pretend_paths"]: print(displayable_path(item_uri)) elif pretend: print(item) if not pretend: # Write all of the accumulated track lists to files. for m3u in m3us: m3u_path = normpath( os.path.join(playlist_dir, bytestring_path(m3u)) ) mkdirall(m3u_path) pl_format = self.config["output"].get() if pl_format != "m3u" and pl_format != "extm3u": msg = "Unsupported output format '{}' provided! " msg += "Supported: m3u, extm3u" raise Exception(msg.format(pl_format)) extm3u = pl_format == "extm3u" with open(syspath(m3u_path), "wb") as f: keys = [] if extm3u: keys = self.config["fields"].get(list) f.write(b"#EXTM3U\n") for entry in m3us[m3u]: item = entry.item comment = "" if extm3u: attr = [(k, entry.item[k]) for k in keys] al = [ f" {a[0]}={json.dumps(str(a[1]))}" for a in attr ] attrs = "".join(al) comment = "#EXTINF:{}{},{} - {}\n".format( int(item.length), attrs, item.artist, item.title ) f.write(comment.encode("utf-8") + entry.uri + b"\n") # Send an event when playlists were updated. send_event("smartplaylist_update") if pretend: self._log.info( "Displayed results for {0} playlists", len(self._matched_playlists), ) else: self._log.info( "{0} playlists updated", len(self._matched_playlists) ) class PlaylistItem: def __init__(self, item, uri): self.item = item self.uri = uri ������beetbox-beets-01f1faf/beetsplug/sonosupdate.py������������������������������������������������������0000664�0000000�0000000�00000003110�14723254774�0021652�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# This file is part of beets. # Copyright 2018, Tobias Sauerwein. # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the # "Software"), to deal in the Software without restriction, including # without limitation the rights to use, copy, modify, merge, publish, # distribute, sublicense, and/or sell copies of the Software, and to # permit persons to whom the Software is furnished to do so, subject to # the following conditions: # # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. """Updates a Sonos library whenever the beets library is changed. This is based on the Kodi Update plugin. """ import soco from beets.plugins import BeetsPlugin class SonosUpdate(BeetsPlugin): def __init__(self): super().__init__() self.register_listener("database_change", self.listen_for_db_change) def listen_for_db_change(self, lib, model): """Listens for beets db change and register the update""" self.register_listener("cli_exit", self.update) def update(self, lib): """When the client exists try to send refresh request to a Sonos controller. """ self._log.info("Requesting a Sonos library update...") device = soco.discovery.any_soco() if device: device.music_library.start_library_update() else: self._log.warning("Could not find a Sonos device.") return self._log.info("Sonos update triggered") ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������beetbox-beets-01f1faf/beetsplug/spotify.py����������������������������������������������������������0000664�0000000�0000000�00000063703�14723254774�0021021�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# This file is part of beets. # Copyright 2019, Rahul Ahuja. # Copyright 2022, Alok Saboo. # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the # "Software"), to deal in the Software without restriction, including # without limitation the rights to use, copy, modify, merge, publish, # distribute, sublicense, and/or sell copies of the Software, and to # permit persons to whom the Software is furnished to do so, subject to # the following conditions: # # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. """Adds Spotify release and track search support to the autotagger, along with Spotify playlist construction. """ import base64 import collections import json import re import time import webbrowser import confuse import requests import unidecode from beets import ui from beets.autotag.hooks import AlbumInfo, TrackInfo from beets.dbcore import types from beets.library import DateType from beets.plugins import BeetsPlugin, MetadataSourcePlugin from beets.util.id_extractors import spotify_id_regex DEFAULT_WAITING_TIME = 5 class SpotifyAPIError(Exception): pass class SpotifyPlugin(MetadataSourcePlugin, BeetsPlugin): data_source = "Spotify" item_types = { "spotify_track_popularity": types.INTEGER, "spotify_acousticness": types.FLOAT, "spotify_danceability": types.FLOAT, "spotify_energy": types.FLOAT, "spotify_instrumentalness": types.FLOAT, "spotify_key": types.FLOAT, "spotify_liveness": types.FLOAT, "spotify_loudness": types.FLOAT, "spotify_mode": types.INTEGER, "spotify_speechiness": types.FLOAT, "spotify_tempo": types.FLOAT, "spotify_time_signature": types.INTEGER, "spotify_valence": types.FLOAT, "spotify_updated": DateType(), } # Base URLs for the Spotify API # Documentation: https://developer.spotify.com/web-api oauth_token_url = "https://accounts.spotify.com/api/token" open_track_url = "https://open.spotify.com/track/" search_url = "https://api.spotify.com/v1/search" album_url = "https://api.spotify.com/v1/albums/" track_url = "https://api.spotify.com/v1/tracks/" audio_features_url = "https://api.spotify.com/v1/audio-features/" id_regex = spotify_id_regex spotify_audio_features = { "acousticness": "spotify_acousticness", "danceability": "spotify_danceability", "energy": "spotify_energy", "instrumentalness": "spotify_instrumentalness", "key": "spotify_key", "liveness": "spotify_liveness", "loudness": "spotify_loudness", "mode": "spotify_mode", "speechiness": "spotify_speechiness", "tempo": "spotify_tempo", "time_signature": "spotify_time_signature", "valence": "spotify_valence", } def __init__(self): super().__init__() self.config.add( { "mode": "list", "tiebreak": "popularity", "show_failures": False, "artist_field": "albumartist", "album_field": "album", "track_field": "title", "region_filter": None, "regex": [], "client_id": "4e414367a1d14c75a5c5129a627fcab8", "client_secret": "f82bdc09b2254f1a8286815d02fd46dc", "tokenfile": "spotify_token.json", } ) self.config["client_secret"].redact = True self.tokenfile = self.config["tokenfile"].get( confuse.Filename(in_app_dir=True) ) # Path to the JSON file for storing the OAuth access token. self.setup() def setup(self): """Retrieve previously saved OAuth token or generate a new one.""" try: with open(self.tokenfile) as f: token_data = json.load(f) except OSError: self._authenticate() else: self.access_token = token_data["access_token"] def _authenticate(self): """Request an access token via the Client Credentials Flow: https://developer.spotify.com/documentation/general/guides/authorization-guide/#client-credentials-flow """ headers = { "Authorization": "Basic {}".format( base64.b64encode( ":".join( self.config[k].as_str() for k in ("client_id", "client_secret") ).encode() ).decode() ) } response = requests.post( self.oauth_token_url, data={"grant_type": "client_credentials"}, headers=headers, timeout=10, ) try: response.raise_for_status() except requests.exceptions.HTTPError as e: raise ui.UserError( "Spotify authorization failed: {}\n{}".format(e, response.text) ) self.access_token = response.json()["access_token"] # Save the token for later use. self._log.debug( "{} access token: {}", self.data_source, self.access_token ) with open(self.tokenfile, "w") as f: json.dump({"access_token": self.access_token}, f) def _handle_response( self, request_type, url, params=None, retry_count=0, max_retries=3 ): """Send a request, reauthenticating if necessary. :param request_type: Type of :class:`Request` constructor, e.g. ``requests.get``, ``requests.post``, etc. :type request_type: function :param url: URL for the new :class:`Request` object. :type url: str :param params: (optional) list of tuples or bytes to send in the query string for the :class:`Request`. :type params: dict :return: JSON data for the class:`Response <Response>` object. :rtype: dict """ try: response = request_type( url, headers={"Authorization": f"Bearer {self.access_token}"}, params=params, timeout=10, ) response.raise_for_status() return response.json() except requests.exceptions.ReadTimeout: self._log.error("ReadTimeout.") raise SpotifyAPIError("Request timed out.") except requests.exceptions.ConnectionError as e: self._log.error(f"Network error: {e}") raise SpotifyAPIError("Network error.") except requests.exceptions.RequestException as e: if e.response.status_code == 401: self._log.debug( f"{self.data_source} access token has expired. " f"Reauthenticating." ) self._authenticate() return self._handle_response(request_type, url, params=params) elif e.response.status_code == 404: raise SpotifyAPIError( f"API Error: {e.response.status_code}\n" f"URL: {url}\nparams: {params}" ) elif e.response.status_code == 429: if retry_count >= max_retries: raise SpotifyAPIError("Maximum retries reached.") seconds = response.headers.get( "Retry-After", DEFAULT_WAITING_TIME ) self._log.debug( f"Too many API requests. Retrying after " f"{seconds} seconds." ) time.sleep(int(seconds) + 1) return self._handle_response( request_type, url, params=params, retry_count=retry_count + 1, ) elif e.response.status_code == 503: self._log.error("Service Unavailable.") raise SpotifyAPIError("Service Unavailable.") elif e.response.status_code == 502: self._log.error("Bad Gateway.") raise SpotifyAPIError("Bad Gateway.") elif e.response is not None: raise SpotifyAPIError( f"{self.data_source} API error:\n{e.response.text}\n" f"URL:\n{url}\nparams:\n{params}" ) else: self._log.error(f"Request failed. Error: {e}") raise SpotifyAPIError("Request failed.") def album_for_id(self, album_id): """Fetch an album by its Spotify ID or URL and return an AlbumInfo object or None if the album is not found. :param album_id: Spotify ID or URL for the album :type album_id: str :return: AlbumInfo object for album :rtype: beets.autotag.hooks.AlbumInfo or None """ spotify_id = self._get_id("album", album_id, self.id_regex) if spotify_id is None: return None album_data = self._handle_response( requests.get, self.album_url + spotify_id ) if album_data["name"] == "": self._log.debug("Album removed from Spotify: {}", album_id) return None artist, artist_id = self.get_artist(album_data["artists"]) date_parts = [ int(part) for part in album_data["release_date"].split("-") ] release_date_precision = album_data["release_date_precision"] if release_date_precision == "day": year, month, day = date_parts elif release_date_precision == "month": year, month = date_parts day = None elif release_date_precision == "year": year = date_parts[0] month = None day = None else: raise ui.UserError( "Invalid `release_date_precision` returned " "by {} API: '{}'".format( self.data_source, release_date_precision ) ) tracks_data = album_data["tracks"] tracks_items = tracks_data["items"] while tracks_data["next"]: tracks_data = self._handle_response( requests.get, tracks_data["next"] ) tracks_items.extend(tracks_data["items"]) tracks = [] medium_totals = collections.defaultdict(int) for i, track_data in enumerate(tracks_items, start=1): track = self._get_track(track_data) track.index = i medium_totals[track.medium] += 1 tracks.append(track) for track in tracks: track.medium_total = medium_totals[track.medium] return AlbumInfo( album=album_data["name"], album_id=spotify_id, spotify_album_id=spotify_id, artist=artist, artist_id=artist_id, spotify_artist_id=artist_id, tracks=tracks, albumtype=album_data["album_type"], va=len(album_data["artists"]) == 1 and artist.lower() == "various artists", year=year, month=month, day=day, label=album_data["label"], mediums=max(medium_totals.keys()), data_source=self.data_source, data_url=album_data["external_urls"]["spotify"], ) def _get_track(self, track_data): """Convert a Spotify track object dict to a TrackInfo object. :param track_data: Simplified track object (https://developer.spotify.com/documentation/web-api/reference/object-model/#track-object-simplified) :type track_data: dict :return: TrackInfo object for track :rtype: beets.autotag.hooks.TrackInfo """ artist, artist_id = self.get_artist(track_data["artists"]) # Get album information for spotify tracks try: album = track_data["album"]["name"] except (KeyError, TypeError): album = None return TrackInfo( title=track_data["name"], track_id=track_data["id"], spotify_track_id=track_data["id"], artist=artist, album=album, artist_id=artist_id, spotify_artist_id=artist_id, length=track_data["duration_ms"] / 1000, index=track_data["track_number"], medium=track_data["disc_number"], medium_index=track_data["track_number"], data_source=self.data_source, data_url=track_data["external_urls"]["spotify"], ) def track_for_id(self, track_id=None, track_data=None): """Fetch a track by its Spotify ID or URL and return a TrackInfo object or None if the track is not found. :param track_id: (Optional) Spotify ID or URL for the track. Either ``track_id`` or ``track_data`` must be provided. :type track_id: str :param track_data: (Optional) Simplified track object dict. May be provided instead of ``track_id`` to avoid unnecessary API calls. :type track_data: dict :return: TrackInfo object for track :rtype: beets.autotag.hooks.TrackInfo or None """ if track_data is None: spotify_id = self._get_id("track", track_id, self.id_regex) if spotify_id is None: return None track_data = self._handle_response( requests.get, self.track_url + spotify_id ) track = self._get_track(track_data) # Get album's tracks to set `track.index` (position on the entire # release) and `track.medium_total` (total number of tracks on # the track's disc). album_data = self._handle_response( requests.get, self.album_url + track_data["album"]["id"] ) medium_total = 0 for i, track_data in enumerate(album_data["tracks"]["items"], start=1): if track_data["disc_number"] == track.medium: medium_total += 1 if track_data["id"] == track.track_id: track.index = i track.medium_total = medium_total return track @staticmethod def _construct_search_query(filters=None, keywords=""): """Construct a query string with the specified filters and keywords to be provided to the Spotify Search API (https://developer.spotify.com/documentation/web-api/reference/search/search/#writing-a-query---guidelines). :param filters: (Optional) Field filters to apply. :type filters: dict :param keywords: (Optional) Query keywords to use. :type keywords: str :return: Query string to be provided to the Search API. :rtype: str """ query_components = [ keywords, " ".join(":".join((k, v)) for k, v in filters.items()), ] query = " ".join([q for q in query_components if q]) if not isinstance(query, str): query = query.decode("utf8") return unidecode.unidecode(query) def _search_api(self, query_type, filters=None, keywords=""): """Query the Spotify Search API for the specified ``keywords``, applying the provided ``filters``. :param query_type: Item type to search across. Valid types are: 'album', 'artist', 'playlist', and 'track'. :type query_type: str :param filters: (Optional) Field filters to apply. :type filters: dict :param keywords: (Optional) Query keywords to use. :type keywords: str :return: JSON data for the class:`Response <Response>` object or None if no search results are returned. :rtype: dict or None """ query = self._construct_search_query(keywords=keywords, filters=filters) if not query: return None self._log.debug(f"Searching {self.data_source} for '{query}'") try: response = self._handle_response( requests.get, self.search_url, params={"q": query, "type": query_type}, ) except SpotifyAPIError as e: self._log.debug("Spotify API error: {}", e) return [] response_data = response.get(query_type + "s", {}).get("items", []) self._log.debug( "Found {} result(s) from {} for '{}'", len(response_data), self.data_source, query, ) return response_data def commands(self): # autotagger import command def queries(lib, opts, args): success = self._parse_opts(opts) if success: results = self._match_library_tracks(lib, ui.decargs(args)) self._output_match_results(results) spotify_cmd = ui.Subcommand( "spotify", help=f"build a {self.data_source} playlist" ) spotify_cmd.parser.add_option( "-m", "--mode", action="store", help='"open" to open {} with playlist, ' '"list" to print (default)'.format(self.data_source), ) spotify_cmd.parser.add_option( "-f", "--show-failures", action="store_true", dest="show_failures", help="list tracks that did not match a {} ID".format( self.data_source ), ) spotify_cmd.func = queries # spotifysync command sync_cmd = ui.Subcommand( "spotifysync", help="fetch track attributes from Spotify" ) sync_cmd.parser.add_option( "-f", "--force", dest="force_refetch", action="store_true", default=False, help="re-download data when already present", ) def func(lib, opts, args): items = lib.items(ui.decargs(args)) self._fetch_info(items, ui.should_write(), opts.force_refetch) sync_cmd.func = func return [spotify_cmd, sync_cmd] def _parse_opts(self, opts): if opts.mode: self.config["mode"].set(opts.mode) if opts.show_failures: self.config["show_failures"].set(True) if self.config["mode"].get() not in ["list", "open"]: self._log.warning( "{0} is not a valid mode", self.config["mode"].get() ) return False self.opts = opts return True def _match_library_tracks(self, library, keywords): """Get a list of simplified track object dicts for library tracks matching the specified ``keywords``. :param library: beets library object to query. :type library: beets.library.Library :param keywords: Query to match library items against. :type keywords: str :return: List of simplified track object dicts for library items matching the specified query. :rtype: list[dict] """ results = [] failures = [] items = library.items(keywords) if not items: self._log.debug( "Your beets query returned no items, skipping {}.", self.data_source, ) return self._log.info("Processing {} tracks...", len(items)) for item in items: # Apply regex transformations if provided for regex in self.config["regex"].get(): if ( not regex["field"] or not regex["search"] or not regex["replace"] ): continue value = item[regex["field"]] item[regex["field"]] = re.sub( regex["search"], regex["replace"], value ) # Custom values can be passed in the config (just in case) artist = item[self.config["artist_field"].get()] album = item[self.config["album_field"].get()] keywords = item[self.config["track_field"].get()] # Query the Web API for each track, look for the items' JSON data query_filters = {"artist": artist, "album": album} response_data_tracks = self._search_api( query_type="track", keywords=keywords, filters=query_filters ) if not response_data_tracks: query = self._construct_search_query( keywords=keywords, filters=query_filters ) failures.append(query) continue # Apply market filter if requested region_filter = self.config["region_filter"].get() if region_filter: response_data_tracks = [ track_data for track_data in response_data_tracks if region_filter in track_data["available_markets"] ] if ( len(response_data_tracks) == 1 or self.config["tiebreak"].get() == "first" ): self._log.debug( "{} track(s) found, count: {}", self.data_source, len(response_data_tracks), ) chosen_result = response_data_tracks[0] else: # Use the popularity filter self._log.debug( "Most popular track chosen, count: {}", len(response_data_tracks), ) chosen_result = max( response_data_tracks, key=lambda x: x["popularity"] ) results.append(chosen_result) failure_count = len(failures) if failure_count > 0: if self.config["show_failures"].get(): self._log.info( "{} track(s) did not match a {} ID:", failure_count, self.data_source, ) for track in failures: self._log.info("track: {}", track) self._log.info("") else: self._log.warning( "{} track(s) did not match a {} ID:\n" "use --show-failures to display", failure_count, self.data_source, ) return results def _output_match_results(self, results): """Open a playlist or print Spotify URLs for the provided track object dicts. :param results: List of simplified track object dicts (https://developer.spotify.com/documentation/web-api/reference/object-model/#track-object-simplified) :type results: list[dict] """ if results: spotify_ids = [track_data["id"] for track_data in results] if self.config["mode"].get() == "open": self._log.info( "Attempting to open {} with playlist".format( self.data_source ) ) spotify_url = "spotify:trackset:Playlist:" + ",".join( spotify_ids ) webbrowser.open(spotify_url) else: for spotify_id in spotify_ids: print(self.open_track_url + spotify_id) else: self._log.warning( f"No {self.data_source} tracks found from beets query" ) def _fetch_info(self, items, write, force): """Obtain track information from Spotify.""" self._log.debug("Total {} tracks", len(items)) for index, item in enumerate(items, start=1): self._log.info( "Processing {}/{} tracks - {} ", index, len(items), item ) # If we're not forcing re-downloading for all tracks, check # whether the popularity data is already present if not force: if "spotify_track_popularity" in item: self._log.debug("Popularity already present for: {}", item) continue try: spotify_track_id = item.spotify_track_id except AttributeError: self._log.debug("No track_id present for: {}", item) continue popularity, isrc, ean, upc = self.track_info(spotify_track_id) item["spotify_track_popularity"] = popularity item["isrc"] = isrc item["ean"] = ean item["upc"] = upc audio_features = self.track_audio_features(spotify_track_id) if audio_features is None: self._log.info("No audio features found for: {}", item) continue for feature in audio_features.keys(): if feature in self.spotify_audio_features.keys(): item[self.spotify_audio_features[feature]] = audio_features[ feature ] item["spotify_updated"] = time.time() item.store() if write: item.try_write() def track_info(self, track_id=None): """Fetch a track's popularity and external IDs using its Spotify ID.""" track_data = self._handle_response( requests.get, self.track_url + track_id ) self._log.debug( "track_popularity: {} and track_isrc: {}", track_data.get("popularity"), track_data.get("external_ids").get("isrc"), ) return ( track_data.get("popularity"), track_data.get("external_ids").get("isrc"), track_data.get("external_ids").get("ean"), track_data.get("external_ids").get("upc"), ) def track_audio_features(self, track_id=None): """Fetch track audio features by its Spotify ID.""" try: return self._handle_response( requests.get, self.audio_features_url + track_id ) except SpotifyAPIError as e: self._log.debug("Spotify API error: {}", e) return None �������������������������������������������������������������beetbox-beets-01f1faf/beetsplug/subsonicplaylist.py�������������������������������������������������0000664�0000000�0000000�00000014562�14723254774�0022732�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# This file is part of beets. # Copyright 2019, Joris Jensen # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the # "Software"), to deal in the Software without restriction, including # without limitation the rights to use, copy, modify, merge, publish, # distribute, sublicense, and/or sell copies of the Software, and to # permit persons to whom the Software is furnished to do so, subject to # the following conditions: # # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. import random import string from hashlib import md5 from urllib.parse import urlencode from xml.etree import ElementTree import requests from beets.dbcore import AndQuery from beets.dbcore.query import MatchQuery from beets.plugins import BeetsPlugin from beets.ui import Subcommand __author__ = "https://github.com/MrNuggelz" def filter_to_be_removed(items, keys): if len(items) > len(keys): dont_remove = [] for artist, album, title in keys: for item in items: if ( artist == item["artist"] and album == item["album"] and title == item["title"] ): dont_remove.append(item) return [item for item in items if item not in dont_remove] else: def to_be_removed(item): for artist, album, title in keys: if ( artist == item["artist"] and album == item["album"] and title == item["title"] ): return False return True return [item for item in items if to_be_removed(item)] class SubsonicPlaylistPlugin(BeetsPlugin): def __init__(self): super().__init__() self.config.add( { "delete": False, "playlist_ids": [], "playlist_names": [], "username": "", "password": "", } ) self.config["password"].redact = True def update_tags(self, playlist_dict, lib): with lib.transaction(): for query, playlist_tag in playlist_dict.items(): query = AndQuery( [ MatchQuery("artist", query[0]), MatchQuery("album", query[1]), MatchQuery("title", query[2]), ] ) items = lib.items(query) if not items: self._log.warn( "{} | track not found ({})", playlist_tag, query ) continue for item in items: item.subsonic_playlist = playlist_tag item.try_sync(write=True, move=False) def get_playlist(self, playlist_id): xml = self.send("getPlaylist", {"id": playlist_id}).text playlist = ElementTree.fromstring(xml)[0] if playlist.attrib.get("code", "200") != "200": alt_error = "error getting playlist, but no error message found" self._log.warn(playlist.attrib.get("message", alt_error)) return name = playlist.attrib.get("name", "undefined") tracks = [ (t.attrib["artist"], t.attrib["album"], t.attrib["title"]) for t in playlist ] return name, tracks def commands(self): def build_playlist(lib, opts, args): self.config.set_args(opts) ids = self.config["playlist_ids"].as_str_seq() if self.config["playlist_names"].as_str_seq(): playlists = ElementTree.fromstring( self.send("getPlaylists").text )[0] if playlists.attrib.get("code", "200") != "200": alt_error = ( "error getting playlists," " but no error message found" ) self._log.warn(playlists.attrib.get("message", alt_error)) return for name in self.config["playlist_names"].as_str_seq(): for playlist in playlists: if name == playlist.attrib["name"]: ids.append(playlist.attrib["id"]) playlist_dict = self.get_playlists(ids) # delete old tags if self.config["delete"]: existing = list(lib.items('subsonic_playlist:";"')) to_be_removed = filter_to_be_removed( existing, playlist_dict.keys() ) for item in to_be_removed: item["subsonic_playlist"] = "" with lib.transaction(): item.try_sync(write=True, move=False) self.update_tags(playlist_dict, lib) subsonicplaylist_cmds = Subcommand( "subsonicplaylist", help="import a subsonic playlist" ) subsonicplaylist_cmds.parser.add_option( "-d", "--delete", action="store_true", help="delete tag from items not in any playlist anymore", ) subsonicplaylist_cmds.func = build_playlist return [subsonicplaylist_cmds] def generate_token(self): salt = "".join(random.choices(string.ascii_lowercase + string.digits)) return ( md5((self.config["password"].get() + salt).encode()).hexdigest(), salt, ) def send(self, endpoint, params=None): if params is None: params = {} a, b = self.generate_token() params["u"] = self.config["username"] params["t"] = a params["s"] = b params["v"] = "1.12.0" params["c"] = "beets" resp = requests.get( "{}/rest/{}?{}".format( self.config["base_url"].get(), endpoint, urlencode(params) ), timeout=10, ) return resp def get_playlists(self, ids): output = {} for playlist_id in ids: name, tracks = self.get_playlist(playlist_id) for track in tracks: if track not in output: output[track] = ";" output[track] += name + ";" return output ����������������������������������������������������������������������������������������������������������������������������������������������beetbox-beets-01f1faf/beetsplug/subsonicupdate.py���������������������������������������������������0000664�0000000�0000000�00000012373�14723254774�0022351�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# This file is part of beets. # Copyright 2016, Adrian Sampson. # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the # "Software"), to deal in the Software without restriction, including # without limitation the rights to use, copy, modify, merge, publish, # distribute, sublicense, and/or sell copies of the Software, and to # permit persons to whom the Software is furnished to do so, subject to # the following conditions: # # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. """Updates Subsonic library on Beets import Your Beets configuration file should contain a "subsonic" section like the following: subsonic: url: https://mydomain.com:443/subsonic user: username pass: password auth: token For older Subsonic versions, token authentication is not supported, use password instead: subsonic: url: https://mydomain.com:443/subsonic user: username pass: password auth: pass """ import hashlib import random import string from binascii import hexlify import requests from beets import config from beets.plugins import BeetsPlugin __author__ = "https://github.com/maffo999" class SubsonicUpdate(BeetsPlugin): def __init__(self): super().__init__() # Set default configuration values config["subsonic"].add( { "user": "admin", "pass": "admin", "url": "http://localhost:4040", "auth": "token", } ) config["subsonic"]["pass"].redact = True self.register_listener("database_change", self.db_change) self.register_listener("smartplaylist_update", self.spl_update) def db_change(self, lib, model): self.register_listener("cli_exit", self.start_scan) def spl_update(self): self.register_listener("cli_exit", self.start_scan) @staticmethod def __create_token(): """Create salt and token from given password. :return: The generated salt and hashed token """ password = config["subsonic"]["pass"].as_str() # Pick the random sequence and salt the password r = string.ascii_letters + string.digits salt = "".join([random.choice(r) for _ in range(6)]) salted_password = password + salt token = hashlib.md5(salted_password.encode("utf-8")).hexdigest() # Put together the payload of the request to the server and the URL return salt, token @staticmethod def __format_url(endpoint): """Get the Subsonic URL to trigger the given endpoint. Uses either the url config option or the deprecated host, port, and context_path config options together. :return: Endpoint for updating Subsonic """ url = config["subsonic"]["url"].as_str() if url and url.endswith("/"): url = url[:-1] # @deprecated("Use url config option instead") if not url: host = config["subsonic"]["host"].as_str() port = config["subsonic"]["port"].get(int) context_path = config["subsonic"]["contextpath"].as_str() if context_path == "/": context_path = "" url = f"http://{host}:{port}{context_path}" return url + f"/rest/{endpoint}" def start_scan(self): user = config["subsonic"]["user"].as_str() auth = config["subsonic"]["auth"].as_str() url = self.__format_url("startScan") self._log.debug("URL is {0}", url) self._log.debug("auth type is {0}", config["subsonic"]["auth"]) if auth == "token": salt, token = self.__create_token() payload = { "u": user, "t": token, "s": salt, "v": "1.13.0", # Subsonic 5.3 and newer "c": "beets", "f": "json", } elif auth == "password": password = config["subsonic"]["pass"].as_str() encpass = hexlify(password.encode()).decode() payload = { "u": user, "p": f"enc:{encpass}", "v": "1.12.0", "c": "beets", "f": "json", } else: return try: response = requests.get( url, params=payload, timeout=10, ) json = response.json() if ( response.status_code == 200 and json["subsonic-response"]["status"] == "ok" ): count = json["subsonic-response"]["scanStatus"]["count"] self._log.info(f"Updating Subsonic; scanning {count} tracks") elif ( response.status_code == 200 and json["subsonic-response"]["status"] == "failed" ): error_message = json["subsonic-response"]["error"]["message"] self._log.error(f"Error: {error_message}") else: self._log.error("Error: {0}", json) except Exception as error: self._log.error(f"Error: {error}") ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������beetbox-beets-01f1faf/beetsplug/substitute.py�������������������������������������������������������0000664�0000000�0000000�00000003355�14723254774�0021534�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# This file is part of beets. # Copyright 2023, Daniele Ferone. # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the # "Software"), to deal in the Software without restriction, including # without limitation the rights to use, copy, modify, merge, publish, # distribute, sublicense, and/or sell copies of the Software, and to # permit persons to whom the Software is furnished to do so, subject to # the following conditions: # # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. """The substitute plugin module. Uses user-specified substitution rules to canonicalize names for path formats. """ import re from beets.plugins import BeetsPlugin class Substitute(BeetsPlugin): """The substitute plugin class. Create a template field function that substitute the given field with the given substitution rules. ``rules`` must be a list of (pattern, replacement) pairs. """ def tmpl_substitute(self, text): """Do the actual replacing.""" if text: for pattern, replacement in self.substitute_rules: text = pattern.sub(replacement, text) return text else: return "" def __init__(self): """Initialize the substitute plugin. Get the configuration, register template function and create list of substitute rules. """ super().__init__() self.template_funcs["substitute"] = self.tmpl_substitute self.substitute_rules = [ (re.compile(key, flags=re.IGNORECASE), value) for key, value in self.config.flatten().items() ] �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������beetbox-beets-01f1faf/beetsplug/the.py��������������������������������������������������������������0000664�0000000�0000000�00000006267�14723254774�0020106�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# This file is part of beets. # Copyright 2016, Blemjhoo Tezoulbr <baobab@heresiarch.info>. # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the # "Software"), to deal in the Software without restriction, including # without limitation the rights to use, copy, modify, merge, publish, # distribute, sublicense, and/or sell copies of the Software, and to # permit persons to whom the Software is furnished to do so, subject to # the following conditions: # # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. """Moves patterns in path formats (suitable for moving articles).""" import re from typing import List from beets.plugins import BeetsPlugin __author__ = "baobab@heresiarch.info" __version__ = "1.1" PATTERN_THE = "^the\\s" PATTERN_A = "^[a][n]?\\s" FORMAT = "{0}, {1}" class ThePlugin(BeetsPlugin): patterns: List[str] = [] def __init__(self): super().__init__() self.template_funcs["the"] = self.the_template_func self.config.add( { "the": True, "a": True, "format": "{0}, {1}", "strip": False, "patterns": [], } ) self.patterns = self.config["patterns"].as_str_seq() for p in self.patterns: if p: try: re.compile(p) except re.error: self._log.error("invalid pattern: {0}", p) else: if not (p.startswith("^") or p.endswith("$")): self._log.warning( 'warning: "{0}" will not ' "match string start/end", p, ) if self.config["a"]: self.patterns = [PATTERN_A] + self.patterns if self.config["the"]: self.patterns = [PATTERN_THE] + self.patterns if not self.patterns: self._log.warning("no patterns defined!") def unthe(self, text, pattern): """Moves pattern in the path format string or strips it text -- text to handle pattern -- regexp pattern (case ignore is already on) strip -- if True, pattern will be removed """ if text: r = re.compile(pattern, flags=re.IGNORECASE) try: t = r.findall(text)[0] except IndexError: return text else: r = re.sub(r, "", text).strip() if self.config["strip"]: return r else: fmt = self.config["format"].as_str() return fmt.format(r, t.strip()).strip() else: return "" def the_template_func(self, text): if not self.patterns: return text if text: for p in self.patterns: r = self.unthe(text, p) if r != text: self._log.debug('"{0}" -> "{1}"', text, r) break return r else: return "" �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������beetbox-beets-01f1faf/beetsplug/thumbnails.py�������������������������������������������������������0000664�0000000�0000000�00000023164�14723254774�0021467�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# This file is part of beets. # Copyright 2016, Bruno Cauet # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the # "Software"), to deal in the Software without restriction, including # without limitation the rights to use, copy, modify, merge, publish, # distribute, sublicense, and/or sell copies of the Software, and to # permit persons to whom the Software is furnished to do so, subject to # the following conditions: # # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. """Create freedesktop.org-compliant thumbnails for album folders This plugin is POSIX-only. Spec: standards.freedesktop.org/thumbnail-spec/latest/index.html """ import ctypes import ctypes.util import os import shutil from hashlib import md5 from pathlib import PurePosixPath from xdg import BaseDirectory from beets import util from beets.plugins import BeetsPlugin from beets.ui import Subcommand, decargs from beets.util import bytestring_path, displayable_path, syspath from beets.util.artresizer import ArtResizer BASE_DIR = os.path.join(BaseDirectory.xdg_cache_home, "thumbnails") NORMAL_DIR = bytestring_path(os.path.join(BASE_DIR, "normal")) LARGE_DIR = bytestring_path(os.path.join(BASE_DIR, "large")) class ThumbnailsPlugin(BeetsPlugin): def __init__(self): super().__init__() self.config.add( { "auto": True, "force": False, "dolphin": False, } ) if self.config["auto"] and self._check_local_ok(): self.register_listener("art_set", self.process_album) def commands(self): thumbnails_command = Subcommand( "thumbnails", help="Create album thumbnails" ) thumbnails_command.parser.add_option( "-f", "--force", dest="force", action="store_true", default=False, help="force regeneration of thumbnails deemed fine (existing & " "recent enough)", ) thumbnails_command.parser.add_option( "--dolphin", dest="dolphin", action="store_true", default=False, help="create Dolphin-compatible thumbnail information (for KDE)", ) thumbnails_command.func = self.process_query return [thumbnails_command] def process_query(self, lib, opts, args): self.config.set_args(opts) if self._check_local_ok(): for album in lib.albums(decargs(args)): self.process_album(album) def _check_local_ok(self): """Check that everything is ready: - local capability to resize images - thumbnail dirs exist (create them if needed) - detect whether we'll use PIL or IM - detect whether we'll use GIO or Python to get URIs """ if not ArtResizer.shared.local: self._log.warning( "No local image resizing capabilities, " "cannot generate thumbnails" ) return False for dir in (NORMAL_DIR, LARGE_DIR): if not os.path.exists(syspath(dir)): os.makedirs(syspath(dir)) if not ArtResizer.shared.can_write_metadata: raise RuntimeError( f"Thumbnails: ArtResizer backend {ArtResizer.shared.method}" f" unexpectedly cannot write image metadata." ) self._log.debug(f"using {ArtResizer.shared.method} to write metadata") uri_getter = GioURI() if not uri_getter.available: uri_getter = PathlibURI() self._log.debug("using {0.name} to compute URIs", uri_getter) self.get_uri = uri_getter.uri return True def process_album(self, album): """Produce thumbnails for the album folder.""" self._log.debug("generating thumbnail for {0}", album) if not album.artpath: self._log.info("album {0} has no art", album) return if self.config["dolphin"]: self.make_dolphin_cover_thumbnail(album) size = ArtResizer.shared.get_size(album.artpath) if not size: self._log.warning( "problem getting the picture size for {0}", album.artpath ) return wrote = True if max(size) >= 256: wrote &= self.make_cover_thumbnail(album, 256, LARGE_DIR) wrote &= self.make_cover_thumbnail(album, 128, NORMAL_DIR) if wrote: self._log.info("wrote thumbnail for {0}", album) else: self._log.info("nothing to do for {0}", album) def make_cover_thumbnail(self, album, size, target_dir): """Make a thumbnail of given size for `album` and put it in `target_dir`. """ target = os.path.join(target_dir, self.thumbnail_file_name(album.path)) if ( os.path.exists(syspath(target)) and os.stat(syspath(target)).st_mtime > os.stat(syspath(album.artpath)).st_mtime ): if self.config["force"]: self._log.debug( "found a suitable {1}x{1} thumbnail for {0}, " "forcing regeneration", album, size, ) else: self._log.debug( "{1}x{1} thumbnail for {0} exists and is " "recent enough", album, size, ) return False resized = ArtResizer.shared.resize(size, album.artpath, target) self.add_tags(album, resized) shutil.move(syspath(resized), syspath(target)) return True def thumbnail_file_name(self, path): """Compute the thumbnail file name See https://standards.freedesktop.org/thumbnail-spec/latest/x227.html """ uri = self.get_uri(path) hash = md5(uri.encode("utf-8")).hexdigest() return bytestring_path(f"{hash}.png") def add_tags(self, album, image_path): """Write required metadata to the thumbnail See https://standards.freedesktop.org/thumbnail-spec/latest/x142.html """ mtime = os.stat(syspath(album.artpath)).st_mtime metadata = { "Thumb::URI": self.get_uri(album.artpath), "Thumb::MTime": str(mtime), } try: ArtResizer.shared.write_metadata(image_path, metadata) except Exception: self._log.exception( "could not write metadata to {0}", displayable_path(image_path) ) def make_dolphin_cover_thumbnail(self, album): outfilename = os.path.join(album.path, b".directory") if os.path.exists(syspath(outfilename)): return artfile = os.path.split(album.artpath)[1] with open(syspath(outfilename), "w") as f: f.write("[Desktop Entry]\n") f.write("Icon=./{}".format(artfile.decode("utf-8"))) f.close() self._log.debug("Wrote file {0}", displayable_path(outfilename)) class URIGetter: available = False name = "Abstract base" def uri(self, path): raise NotImplementedError() class PathlibURI(URIGetter): available = True name = "Python Pathlib" def uri(self, path): return PurePosixPath(os.fsdecode(path)).as_uri() def copy_c_string(c_string): """Copy a `ctypes.POINTER(ctypes.c_char)` value into a new Python string and return it. The old memory is then safe to free. """ # This is a pretty dumb way to get a string copy, but it seems to # work. A more surefire way would be to allocate a ctypes buffer and copy # the data with `memcpy` or somesuch. s = ctypes.cast(c_string, ctypes.c_char_p).value return b"" + s class GioURI(URIGetter): """Use gio URI function g_file_get_uri. Paths must be utf-8 encoded.""" name = "GIO" def __init__(self): self.libgio = self.get_library() self.available = bool(self.libgio) if self.available: self.libgio.g_type_init() # for glib < 2.36 self.libgio.g_file_get_uri.argtypes = [ctypes.c_char_p] self.libgio.g_file_new_for_path.restype = ctypes.c_void_p self.libgio.g_file_get_uri.argtypes = [ctypes.c_void_p] self.libgio.g_file_get_uri.restype = ctypes.POINTER(ctypes.c_char) self.libgio.g_object_unref.argtypes = [ctypes.c_void_p] def get_library(self): lib_name = ctypes.util.find_library("gio-2") try: if not lib_name: return False return ctypes.cdll.LoadLibrary(lib_name) except OSError: return False def uri(self, path): g_file_ptr = self.libgio.g_file_new_for_path(path) if not g_file_ptr: raise RuntimeError( "No gfile pointer received for {}".format( displayable_path(path) ) ) try: uri_ptr = self.libgio.g_file_get_uri(g_file_ptr) finally: self.libgio.g_object_unref(g_file_ptr) if not uri_ptr: self.libgio.g_free(uri_ptr) raise RuntimeError( f"No URI received from the gfile pointer for {displayable_path(path)}" ) try: uri = copy_c_string(uri_ptr) finally: self.libgio.g_free(uri_ptr) try: return uri.decode(util._fsencoding()) except UnicodeDecodeError: raise RuntimeError(f"Could not decode filename from GIO: {uri!r}") ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������beetbox-beets-01f1faf/beetsplug/types.py������������������������������������������������������������0000664�0000000�0000000�00000003131�14723254774�0020455�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# This file is part of beets. # Copyright 2016, Thomas Scholtes. # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the # "Software"), to deal in the Software without restriction, including # without limitation the rights to use, copy, modify, merge, publish, # distribute, sublicense, and/or sell copies of the Software, and to # permit persons to whom the Software is furnished to do so, subject to # the following conditions: # # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. from confuse import ConfigValueError from beets import library from beets.dbcore import types from beets.plugins import BeetsPlugin class TypesPlugin(BeetsPlugin): @property def item_types(self): return self._types() @property def album_types(self): return self._types() def _types(self): if not self.config.exists(): return {} mytypes = {} for key, value in self.config.items(): if value.get() == "int": mytypes[key] = types.INTEGER elif value.get() == "float": mytypes[key] = types.FLOAT elif value.get() == "bool": mytypes[key] = types.BOOLEAN elif value.get() == "date": mytypes[key] = library.DateType() else: raise ConfigValueError( "unknown type '{}' for the '{}' field".format(value, key) ) return mytypes ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������beetbox-beets-01f1faf/beetsplug/unimported.py�������������������������������������������������������0000664�0000000�0000000�00000004672�14723254774�0021512�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# This file is part of beets. # Copyright 2019, Joris Jensen # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the # "Software"), to deal in the Software without restriction, including # without limitation the rights to use, copy, modify, merge, publish, # distribute, sublicense, and/or sell copies of the Software, and to # permit persons to whom the Software is furnished to do so, subject to # the following conditions: # # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. """ List all files in the library folder which are not listed in the beets library database, including art files """ import os from beets import util from beets.plugins import BeetsPlugin from beets.ui import Subcommand, print_ __author__ = "https://github.com/MrNuggelz" class Unimported(BeetsPlugin): def __init__(self): super().__init__() self.config.add({"ignore_extensions": [], "ignore_subdirectories": []}) def commands(self): def print_unimported(lib, opts, args): ignore_exts = [ ("." + x).encode() for x in self.config["ignore_extensions"].as_str_seq() ] ignore_dirs = [ os.path.join(lib.directory, x.encode()) for x in self.config["ignore_subdirectories"].as_str_seq() ] in_folder = set() for root, _, files in os.walk(lib.directory): # do not traverse if root is a child of an ignored directory if any(root.startswith(ignored) for ignored in ignore_dirs): continue for file in files: # ignore files with ignored extensions if any(file.endswith(ext) for ext in ignore_exts): continue in_folder.add(os.path.join(root, file)) in_library = {x.path for x in lib.items()} art_files = {x.artpath for x in lib.albums()} for f in in_folder - in_library - art_files: print_(util.displayable_path(f)) unimported = Subcommand( "unimported", help="list all files in the library folder which are not listed" " in the beets library database", ) unimported.func = print_unimported return [unimported] ����������������������������������������������������������������������beetbox-beets-01f1faf/beetsplug/web/����������������������������������������������������������������0000775�0000000�0000000�00000000000�14723254774�0017516�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������beetbox-beets-01f1faf/beetsplug/web/__init__.py�����������������������������������������������������0000664�0000000�0000000�00000037335�14723254774�0021642�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# This file is part of beets. # Copyright 2016, Adrian Sampson. # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the # "Software"), to deal in the Software without restriction, including # without limitation the rights to use, copy, modify, merge, publish, # distribute, sublicense, and/or sell copies of the Software, and to # permit persons to whom the Software is furnished to do so, subject to # the following conditions: # # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. """A Web interface to beets.""" import base64 import json import os import flask from flask import g, jsonify from unidecode import unidecode from werkzeug.routing import BaseConverter, PathConverter import beets.library from beets import ui, util from beets.plugins import BeetsPlugin # Utilities. def _rep(obj, expand=False): """Get a flat -- i.e., JSON-ish -- representation of a beets Item or Album object. For Albums, `expand` dictates whether tracks are included. """ out = dict(obj) if isinstance(obj, beets.library.Item): if app.config.get("INCLUDE_PATHS", False): out["path"] = util.displayable_path(out["path"]) else: del out["path"] # Filter all bytes attributes and convert them to strings. for key, value in out.items(): if isinstance(out[key], bytes): out[key] = base64.b64encode(value).decode("ascii") # Get the size (in bytes) of the backing file. This is useful # for the Tomahawk resolver API. try: out["size"] = os.path.getsize(util.syspath(obj.path)) except OSError: out["size"] = 0 return out elif isinstance(obj, beets.library.Album): if app.config.get("INCLUDE_PATHS", False): out["artpath"] = util.displayable_path(out["artpath"]) else: del out["artpath"] if expand: out["items"] = [_rep(item) for item in obj.items()] return out def json_generator(items, root, expand=False): """Generator that dumps list of beets Items or Albums as JSON :param root: root key for JSON :param items: list of :class:`Item` or :class:`Album` to dump :param expand: If true every :class:`Album` contains its items in the json representation :returns: generator that yields strings """ yield '{"%s":[' % root first = True for item in items: if first: first = False else: yield "," yield json.dumps(_rep(item, expand=expand)) yield "]}" def is_expand(): """Returns whether the current request is for an expanded response.""" return flask.request.args.get("expand") is not None def is_delete(): """Returns whether the current delete request should remove the selected files. """ return flask.request.args.get("delete") is not None def get_method(): """Returns the HTTP method of the current request.""" return flask.request.method def resource(name, patchable=False): """Decorates a function to handle RESTful HTTP requests for a resource.""" def make_responder(retriever): def responder(ids): entities = [retriever(id) for id in ids] entities = [entity for entity in entities if entity] if get_method() == "DELETE": if app.config.get("READONLY", True): return flask.abort(405) for entity in entities: entity.remove(delete=is_delete()) return flask.make_response(jsonify({"deleted": True}), 200) elif get_method() == "PATCH" and patchable: if app.config.get("READONLY", True): return flask.abort(405) for entity in entities: entity.update(flask.request.get_json()) entity.try_sync(True, False) # write, don't move if len(entities) == 1: return flask.jsonify(_rep(entities[0], expand=is_expand())) elif entities: return app.response_class( json_generator(entities, root=name), mimetype="application/json", ) elif get_method() == "GET": if len(entities) == 1: return flask.jsonify(_rep(entities[0], expand=is_expand())) elif entities: return app.response_class( json_generator(entities, root=name), mimetype="application/json", ) else: return flask.abort(404) else: return flask.abort(405) responder.__name__ = f"get_{name}" return responder return make_responder def resource_query(name, patchable=False): """Decorates a function to handle RESTful HTTP queries for resources.""" def make_responder(query_func): def responder(queries): entities = query_func(queries) if get_method() == "DELETE": if app.config.get("READONLY", True): return flask.abort(405) for entity in entities: entity.remove(delete=is_delete()) return flask.make_response(jsonify({"deleted": True}), 200) elif get_method() == "PATCH" and patchable: if app.config.get("READONLY", True): return flask.abort(405) for entity in entities: entity.update(flask.request.get_json()) entity.try_sync(True, False) # write, don't move return app.response_class( json_generator(entities, root=name), mimetype="application/json", ) elif get_method() == "GET": return app.response_class( json_generator( entities, root="results", expand=is_expand() ), mimetype="application/json", ) else: return flask.abort(405) responder.__name__ = f"query_{name}" return responder return make_responder def resource_list(name): """Decorates a function to handle RESTful HTTP request for a list of resources. """ def make_responder(list_all): def responder(): return app.response_class( json_generator(list_all(), root=name, expand=is_expand()), mimetype="application/json", ) responder.__name__ = f"all_{name}" return responder return make_responder def _get_unique_table_field_values(model, field, sort_field): """retrieve all unique values belonging to a key from a model""" if field not in model.all_keys() or sort_field not in model.all_keys(): raise KeyError with g.lib.transaction() as tx: rows = tx.query( "SELECT DISTINCT '{}' FROM '{}' ORDER BY '{}'".format( field, model._table, sort_field ) ) return [row[0] for row in rows] class IdListConverter(BaseConverter): """Converts comma separated lists of ids in urls to integer lists.""" def to_python(self, value): ids = [] for id in value.split(","): try: ids.append(int(id)) except ValueError: pass return ids def to_url(self, value): return ",".join(str(v) for v in value) class QueryConverter(PathConverter): """Converts slash separated lists of queries in the url to string list.""" def to_python(self, value): queries = value.split("/") """Do not do path substitution on regex value tests""" return [ query if "::" in query else query.replace("\\", os.sep) for query in queries ] def to_url(self, value): return "/".join([v.replace(os.sep, "\\") for v in value]) class EverythingConverter(PathConverter): part_isolating = False regex = ".*?" # Flask setup. app = flask.Flask(__name__) app.url_map.converters["idlist"] = IdListConverter app.url_map.converters["query"] = QueryConverter app.url_map.converters["everything"] = EverythingConverter @app.before_request def before_request(): g.lib = app.config["lib"] # Items. @app.route("/item/<idlist:ids>", methods=["GET", "DELETE", "PATCH"]) @resource("items", patchable=True) def get_item(id): return g.lib.get_item(id) @app.route("/item/") @app.route("/item/query/") @resource_list("items") def all_items(): return g.lib.items() @app.route("/item/<int:item_id>/file") def item_file(item_id): item = g.lib.get_item(item_id) # On Windows under Python 2, Flask wants a Unicode path. On Python 3, it # *always* wants a Unicode path. if os.name == "nt": item_path = util.syspath(item.path) else: item_path = os.fsdecode(item.path) base_filename = os.path.basename(item_path) # FIXME: Arguably, this should just use `displayable_path`: The latter # tries `_fsencoding()` first, but then falls back to `utf-8`, too. if isinstance(base_filename, bytes): try: unicode_base_filename = base_filename.decode("utf-8") except UnicodeError: unicode_base_filename = util.displayable_path(base_filename) else: unicode_base_filename = base_filename try: # Imitate http.server behaviour base_filename.encode("latin-1", "strict") except UnicodeError: safe_filename = unidecode(base_filename) else: safe_filename = unicode_base_filename response = flask.send_file( item_path, as_attachment=True, download_name=safe_filename ) return response @app.route("/item/query/<query:queries>", methods=["GET", "DELETE", "PATCH"]) @resource_query("items", patchable=True) def item_query(queries): return g.lib.items(queries) @app.route("/item/path/<everything:path>") def item_at_path(path): query = beets.library.PathQuery("path", path.encode("utf-8")) item = g.lib.items(query).get() if item: return flask.jsonify(_rep(item)) else: return flask.abort(404) @app.route("/item/values/<string:key>") def item_unique_field_values(key): sort_key = flask.request.args.get("sort_key", key) try: values = _get_unique_table_field_values( beets.library.Item, key, sort_key ) except KeyError: return flask.abort(404) return flask.jsonify(values=values) # Albums. @app.route("/album/<idlist:ids>", methods=["GET", "DELETE"]) @resource("albums") def get_album(id): return g.lib.get_album(id) @app.route("/album/") @app.route("/album/query/") @resource_list("albums") def all_albums(): return g.lib.albums() @app.route("/album/query/<query:queries>", methods=["GET", "DELETE"]) @resource_query("albums") def album_query(queries): return g.lib.albums(queries) @app.route("/album/<int:album_id>/art") def album_art(album_id): album = g.lib.get_album(album_id) if album and album.artpath: return flask.send_file(album.artpath.decode()) else: return flask.abort(404) @app.route("/album/values/<string:key>") def album_unique_field_values(key): sort_key = flask.request.args.get("sort_key", key) try: values = _get_unique_table_field_values( beets.library.Album, key, sort_key ) except KeyError: return flask.abort(404) return flask.jsonify(values=values) # Artists. @app.route("/artist/") def all_artists(): with g.lib.transaction() as tx: rows = tx.query("SELECT DISTINCT albumartist FROM albums") all_artists = [row[0] for row in rows] return flask.jsonify(artist_names=all_artists) # Library information. @app.route("/stats") def stats(): with g.lib.transaction() as tx: item_rows = tx.query("SELECT COUNT(*) FROM items") album_rows = tx.query("SELECT COUNT(*) FROM albums") return flask.jsonify( { "items": item_rows[0][0], "albums": album_rows[0][0], } ) # UI. @app.route("/") def home(): return flask.render_template("index.html") # Plugin hook. class WebPlugin(BeetsPlugin): def __init__(self): super().__init__() self.config.add( { "host": "127.0.0.1", "port": 8337, "cors": "", "cors_supports_credentials": False, "reverse_proxy": False, "include_paths": False, "readonly": True, } ) def commands(self): cmd = ui.Subcommand("web", help="start a Web interface") cmd.parser.add_option( "-d", "--debug", action="store_true", default=False, help="debug mode", ) def func(lib, opts, args): args = ui.decargs(args) if args: self.config["host"] = args.pop(0) if args: self.config["port"] = int(args.pop(0)) app.config["lib"] = lib # Normalizes json output app.config["JSONIFY_PRETTYPRINT_REGULAR"] = False app.config["INCLUDE_PATHS"] = self.config["include_paths"] app.config["READONLY"] = self.config["readonly"] # Enable CORS if required. if self.config["cors"]: self._log.info( "Enabling CORS with origin: {0}", self.config["cors"] ) from flask_cors import CORS app.config["CORS_ALLOW_HEADERS"] = "Content-Type" app.config["CORS_RESOURCES"] = { r"/*": {"origins": self.config["cors"].get(str)} } CORS( app, supports_credentials=self.config[ "cors_supports_credentials" ].get(bool), ) # Allow serving behind a reverse proxy if self.config["reverse_proxy"]: app.wsgi_app = ReverseProxied(app.wsgi_app) # Start the web application. app.run( host=self.config["host"].as_str(), port=self.config["port"].get(int), debug=opts.debug, threaded=True, ) cmd.func = func return [cmd] class ReverseProxied: """Wrap the application in this middleware and configure the front-end server to add these headers, to let you quietly bind this to a URL other than / and to an HTTP scheme that is different than what is used locally. In nginx: location /myprefix { proxy_pass http://192.168.0.1:5001; proxy_set_header Host $host; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Scheme $scheme; proxy_set_header X-Script-Name /myprefix; } From: http://flask.pocoo.org/snippets/35/ :param app: the WSGI application """ def __init__(self, app): self.app = app def __call__(self, environ, start_response): script_name = environ.get("HTTP_X_SCRIPT_NAME", "") if script_name: environ["SCRIPT_NAME"] = script_name path_info = environ["PATH_INFO"] if path_info.startswith(script_name): environ["PATH_INFO"] = path_info[len(script_name) :] scheme = environ.get("HTTP_X_SCHEME", "") if scheme: environ["wsgi.url_scheme"] = scheme return self.app(environ, start_response) ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������beetbox-beets-01f1faf/beetsplug/web/static/���������������������������������������������������������0000775�0000000�0000000�00000000000�14723254774�0021005�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������beetbox-beets-01f1faf/beetsplug/web/static/backbone.js����������������������������������������������0000664�0000000�0000000�00000123141�14723254774�0023111�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������// Backbone.js 0.5.3 // (c) 2010 Jeremy Ashkenas, DocumentCloud Inc. // Backbone may be freely distributed under the MIT license. // For all details and documentation: // http://documentcloud.github.com/backbone (function(){ // Initial Setup // ------------- // Save a reference to the global object. var root = this; // Save the previous value of the `Backbone` variable. var previousBackbone = root.Backbone; // The top-level namespace. All public Backbone classes and modules will // be attached to this. Exported for both CommonJS and the browser. var Backbone; if (typeof exports !== 'undefined') { Backbone = exports; } else { Backbone = root.Backbone = {}; } // Current version of the library. Keep in sync with `package.json`. Backbone.VERSION = '0.5.3'; // Require Underscore, if we're on the server, and it's not already present. var _ = root._; if (!_ && (typeof require !== 'undefined')) _ = require('underscore')._; // For Backbone's purposes, jQuery or Zepto owns the `$` variable. var $ = root.jQuery || root.Zepto; // Runs Backbone.js in *noConflict* mode, returning the `Backbone` variable // to its previous owner. Returns a reference to this Backbone object. Backbone.noConflict = function() { root.Backbone = previousBackbone; return this; }; // Turn on `emulateHTTP` to support legacy HTTP servers. Setting this option will // fake `"PUT"` and `"DELETE"` requests via the `_method` parameter and set a // `X-Http-Method-Override` header. Backbone.emulateHTTP = false; // Turn on `emulateJSON` to support legacy servers that can't deal with direct // `application/json` requests ... will encode the body as // `application/x-www-form-urlencoded` instead and will send the model in a // form param named `model`. Backbone.emulateJSON = false; // Backbone.Events // ----------------- // A module that can be mixed in to *any object* in order to provide it with // custom events. You may `bind` or `unbind` a callback function to an event; // `trigger`-ing an event fires all callbacks in succession. // // var object = {}; // _.extend(object, Backbone.Events); // object.bind('expand', function(){ alert('expanded'); }); // object.trigger('expand'); // Backbone.Events = { // Bind an event, specified by a string name, `ev`, to a `callback` function. // Passing `"all"` will bind the callback to all events fired. bind : function(ev, callback, context) { var calls = this._callbacks || (this._callbacks = {}); var list = calls[ev] || (calls[ev] = []); list.push([callback, context]); return this; }, // Remove one or many callbacks. If `callback` is null, removes all // callbacks for the event. If `ev` is null, removes all bound callbacks // for all events. unbind : function(ev, callback) { var calls; if (!ev) { this._callbacks = {}; } else if (calls = this._callbacks) { if (!callback) { calls[ev] = []; } else { var list = calls[ev]; if (!list) return this; for (var i = 0, l = list.length; i < l; i++) { if (list[i] && callback === list[i][0]) { list[i] = null; break; } } } } return this; }, // Trigger an event, firing all bound callbacks. Callbacks are passed the // same arguments as `trigger` is, apart from the event name. // Listening for `"all"` passes the true event name as the first argument. trigger : function(eventName) { var list, calls, ev, callback, args; var both = 2; if (!(calls = this._callbacks)) return this; while (both--) { ev = both ? eventName : 'all'; if (list = calls[ev]) { for (var i = 0, l = list.length; i < l; i++) { if (!(callback = list[i])) { list.splice(i, 1); i--; l--; } else { args = both ? Array.prototype.slice.call(arguments, 1) : arguments; callback[0].apply(callback[1] || this, args); } } } } return this; } }; // Backbone.Model // -------------- // Create a new model, with defined attributes. A client id (`cid`) // is automatically generated and assigned for you. Backbone.Model = function(attributes, options) { var defaults; attributes || (attributes = {}); if (defaults = this.defaults) { if (_.isFunction(defaults)) defaults = defaults.call(this); attributes = _.extend({}, defaults, attributes); } this.attributes = {}; this._escapedAttributes = {}; this.cid = _.uniqueId('c'); this.set(attributes, {silent : true}); this._changed = false; this._previousAttributes = _.clone(this.attributes); if (options && options.collection) this.collection = options.collection; this.initialize(attributes, options); }; // Attach all inheritable methods to the Model prototype. _.extend(Backbone.Model.prototype, Backbone.Events, { // A snapshot of the model's previous attributes, taken immediately // after the last `"change"` event was fired. _previousAttributes : null, // Has the item been changed since the last `"change"` event? _changed : false, // The default name for the JSON `id` attribute is `"id"`. MongoDB and // CouchDB users may want to set this to `"_id"`. idAttribute : 'id', // Initialize is an empty function by default. Override it with your own // initialization logic. initialize : function(){}, // Return a copy of the model's `attributes` object. toJSON : function() { return _.clone(this.attributes); }, // Get the value of an attribute. get : function(attr) { return this.attributes[attr]; }, // Get the HTML-escaped value of an attribute. escape : function(attr) { var html; if (html = this._escapedAttributes[attr]) return html; var val = this.attributes[attr]; return this._escapedAttributes[attr] = escapeHTML(val == null ? '' : '' + val); }, // Returns `true` if the attribute contains a value that is not null // or undefined. has : function(attr) { return this.attributes[attr] != null; }, // Set a hash of model attributes on the object, firing `"change"` unless you // choose to silence it. set : function(attrs, options) { // Extract attributes and options. options || (options = {}); if (!attrs) return this; if (attrs.attributes) attrs = attrs.attributes; var now = this.attributes, escaped = this._escapedAttributes; // Run validation. if (!options.silent && this.validate && !this._performValidation(attrs, options)) return false; // Check for changes of `id`. if (this.idAttribute in attrs) this.id = attrs[this.idAttribute]; // We're about to start triggering change events. var alreadyChanging = this._changing; this._changing = true; // Update attributes. for (var attr in attrs) { var val = attrs[attr]; if (!_.isEqual(now[attr], val)) { now[attr] = val; delete escaped[attr]; this._changed = true; if (!options.silent) this.trigger('change:' + attr, this, val, options); } } // Fire the `"change"` event, if the model has been changed. if (!alreadyChanging && !options.silent && this._changed) this.change(options); this._changing = false; return this; }, // Remove an attribute from the model, firing `"change"` unless you choose // to silence it. `unset` is a noop if the attribute doesn't exist. unset : function(attr, options) { if (!(attr in this.attributes)) return this; options || (options = {}); var value = this.attributes[attr]; // Run validation. var validObj = {}; validObj[attr] = void 0; if (!options.silent && this.validate && !this._performValidation(validObj, options)) return false; // Remove the attribute. delete this.attributes[attr]; delete this._escapedAttributes[attr]; if (attr == this.idAttribute) delete this.id; this._changed = true; if (!options.silent) { this.trigger('change:' + attr, this, void 0, options); this.change(options); } return this; }, // Clear all attributes on the model, firing `"change"` unless you choose // to silence it. clear : function(options) { options || (options = {}); var attr; var old = this.attributes; // Run validation. var validObj = {}; for (attr in old) validObj[attr] = void 0; if (!options.silent && this.validate && !this._performValidation(validObj, options)) return false; this.attributes = {}; this._escapedAttributes = {}; this._changed = true; if (!options.silent) { for (attr in old) { this.trigger('change:' + attr, this, void 0, options); } this.change(options); } return this; }, // Fetch the model from the server. If the server's representation of the // model differs from its current attributes, they will be overridden, // triggering a `"change"` event. fetch : function(options) { options || (options = {}); var model = this; var success = options.success; options.success = function(resp, status, xhr) { if (!model.set(model.parse(resp, xhr), options)) return false; if (success) success(model, resp); }; options.error = wrapError(options.error, model, options); return (this.sync || Backbone.sync).call(this, 'read', this, options); }, // Set a hash of model attributes, and sync the model to the server. // If the server returns an attributes hash that differs, the model's // state will be `set` again. save : function(attrs, options) { options || (options = {}); if (attrs && !this.set(attrs, options)) return false; var model = this; var success = options.success; options.success = function(resp, status, xhr) { if (!model.set(model.parse(resp, xhr), options)) return false; if (success) success(model, resp, xhr); }; options.error = wrapError(options.error, model, options); var method = this.isNew() ? 'create' : 'update'; return (this.sync || Backbone.sync).call(this, method, this, options); }, // Destroy this model on the server if it was already persisted. Upon success, the model is removed // from its collection, if it has one. destroy : function(options) { options || (options = {}); if (this.isNew()) return this.trigger('destroy', this, this.collection, options); var model = this; var success = options.success; options.success = function(resp) { model.trigger('destroy', model, model.collection, options); if (success) success(model, resp); }; options.error = wrapError(options.error, model, options); return (this.sync || Backbone.sync).call(this, 'delete', this, options); }, // Default URL for the model's representation on the server -- if you're // using Backbone's restful methods, override this to change the endpoint // that will be called. url : function() { var base = getUrl(this.collection) || this.urlRoot || urlError(); if (this.isNew()) return base; return base + (base.charAt(base.length - 1) == '/' ? '' : '/') + encodeURIComponent(this.id); }, // **parse** converts a response into the hash of attributes to be `set` on // the model. The default implementation is just to pass the response along. parse : function(resp, xhr) { return resp; }, // Create a new model with identical attributes to this one. clone : function() { return new this.constructor(this); }, // A model is new if it has never been saved to the server, and lacks an id. isNew : function() { return this.id == null; }, // Call this method to manually fire a `change` event for this model. // Calling this will cause all objects observing the model to update. change : function(options) { this.trigger('change', this, options); this._previousAttributes = _.clone(this.attributes); this._changed = false; }, // Determine if the model has changed since the last `"change"` event. // If you specify an attribute name, determine if that attribute has changed. hasChanged : function(attr) { if (attr) return this._previousAttributes[attr] != this.attributes[attr]; return this._changed; }, // Return an object containing all the attributes that have changed, or false // if there are no changed attributes. Useful for determining what parts of a // view need to be updated and/or what attributes need to be persisted to // the server. changedAttributes : function(now) { now || (now = this.attributes); var old = this._previousAttributes; var changed = false; for (var attr in now) { if (!_.isEqual(old[attr], now[attr])) { changed = changed || {}; changed[attr] = now[attr]; } } return changed; }, // Get the previous value of an attribute, recorded at the time the last // `"change"` event was fired. previous : function(attr) { if (!attr || !this._previousAttributes) return null; return this._previousAttributes[attr]; }, // Get all of the attributes of the model at the time of the previous // `"change"` event. previousAttributes : function() { return _.clone(this._previousAttributes); }, // Run validation against a set of incoming attributes, returning `true` // if all is well. If a specific `error` callback has been passed, // call that instead of firing the general `"error"` event. _performValidation : function(attrs, options) { var error = this.validate(attrs); if (error) { if (options.error) { options.error(this, error, options); } else { this.trigger('error', this, error, options); } return false; } return true; } }); // Backbone.Collection // ------------------- // Provides a standard collection class for our sets of models, ordered // or unordered. If a `comparator` is specified, the Collection will maintain // its models in sort order, as they're added and removed. Backbone.Collection = function(models, options) { options || (options = {}); if (options.comparator) this.comparator = options.comparator; _.bindAll(this, '_onModelEvent', '_removeReference'); this._reset(); if (models) this.reset(models, {silent: true}); this.initialize.apply(this, arguments); }; // Define the Collection's inheritable methods. _.extend(Backbone.Collection.prototype, Backbone.Events, { // The default model for a collection is just a **Backbone.Model**. // This should be overridden in most cases. model : Backbone.Model, // Initialize is an empty function by default. Override it with your own // initialization logic. initialize : function(){}, // The JSON representation of a Collection is an array of the // models' attributes. toJSON : function() { return this.map(function(model){ return model.toJSON(); }); }, // Add a model, or list of models to the set. Pass **silent** to avoid // firing the `added` event for every new model. add : function(models, options) { if (_.isArray(models)) { for (var i = 0, l = models.length; i < l; i++) { this._add(models[i], options); } } else { this._add(models, options); } return this; }, // Remove a model, or a list of models from the set. Pass silent to avoid // firing the `removed` event for every model removed. remove : function(models, options) { if (_.isArray(models)) { for (var i = 0, l = models.length; i < l; i++) { this._remove(models[i], options); } } else { this._remove(models, options); } return this; }, // Get a model from the set by id. get : function(id) { if (id == null) return null; return this._byId[id.id != null ? id.id : id]; }, // Get a model from the set by client id. getByCid : function(cid) { return cid && this._byCid[cid.cid || cid]; }, // Get the model at the given index. at: function(index) { return this.models[index]; }, // Force the collection to re-sort itself. You don't need to call this under normal // circumstances, as the set will maintain sort order as each item is added. sort : function(options) { options || (options = {}); if (!this.comparator) throw new Error('Cannot sort a set without a comparator'); this.models = this.sortBy(this.comparator); if (!options.silent) this.trigger('reset', this, options); return this; }, // Pluck an attribute from each model in the collection. pluck : function(attr) { return _.map(this.models, function(model){ return model.get(attr); }); }, // When you have more items than you want to add or remove individually, // you can reset the entire set with a new list of models, without firing // any `added` or `removed` events. Fires `reset` when finished. reset : function(models, options) { models || (models = []); options || (options = {}); this.each(this._removeReference); this._reset(); this.add(models, {silent: true}); if (!options.silent) this.trigger('reset', this, options); return this; }, // Fetch the default set of models for this collection, resetting the // collection when they arrive. If `add: true` is passed, appends the // models to the collection instead of resetting. fetch : function(options) { options || (options = {}); var collection = this; var success = options.success; options.success = function(resp, status, xhr) { collection[options.add ? 'add' : 'reset'](collection.parse(resp, xhr), options); if (success) success(collection, resp); }; options.error = wrapError(options.error, collection, options); return (this.sync || Backbone.sync).call(this, 'read', this, options); }, // Create a new instance of a model in this collection. After the model // has been created on the server, it will be added to the collection. // Returns the model, or 'false' if validation on a new model fails. create : function(model, options) { var coll = this; options || (options = {}); model = this._prepareModel(model, options); if (!model) return false; var success = options.success; options.success = function(nextModel, resp, xhr) { coll.add(nextModel, options); if (success) success(nextModel, resp, xhr); }; model.save(null, options); return model; }, // **parse** converts a response into a list of models to be added to the // collection. The default implementation is just to pass it through. parse : function(resp, xhr) { return resp; }, // Proxy to _'s chain. Can't be proxied the same way the rest of the // underscore methods are proxied because it relies on the underscore // constructor. chain: function () { return _(this.models).chain(); }, // Reset all internal state. Called when the collection is reset. _reset : function(options) { this.length = 0; this.models = []; this._byId = {}; this._byCid = {}; }, // Prepare a model to be added to this collection _prepareModel: function(model, options) { if (!(model instanceof Backbone.Model)) { var attrs = model; model = new this.model(attrs, {collection: this}); if (model.validate && !model._performValidation(attrs, options)) model = false; } else if (!model.collection) { model.collection = this; } return model; }, // Internal implementation of adding a single model to the set, updating // hash indexes for `id` and `cid` lookups. // Returns the model, or 'false' if validation on a new model fails. _add : function(model, options) { options || (options = {}); model = this._prepareModel(model, options); if (!model) return false; var already = this.getByCid(model); if (already) throw new Error(["Can't add the same model to a set twice", already.id]); this._byId[model.id] = model; this._byCid[model.cid] = model; var index = options.at != null ? options.at : this.comparator ? this.sortedIndex(model, this.comparator) : this.length; this.models.splice(index, 0, model); model.bind('all', this._onModelEvent); this.length++; if (!options.silent) model.trigger('add', model, this, options); return model; }, // Internal implementation of removing a single model from the set, updating // hash indexes for `id` and `cid` lookups. _remove : function(model, options) { options || (options = {}); model = this.getByCid(model) || this.get(model); if (!model) return null; delete this._byId[model.id]; delete this._byCid[model.cid]; this.models.splice(this.indexOf(model), 1); this.length--; if (!options.silent) model.trigger('remove', model, this, options); this._removeReference(model); return model; }, // Internal method to remove a model's ties to a collection. _removeReference : function(model) { if (this == model.collection) { delete model.collection; } model.unbind('all', this._onModelEvent); }, // Internal method called every time a model in the set fires an event. // Sets need to update their indexes when models change ids. All other // events simply proxy through. "add" and "remove" events that originate // in other collections are ignored. _onModelEvent : function(ev, model, collection, options) { if ((ev == 'add' || ev == 'remove') && collection != this) return; if (ev == 'destroy') { this._remove(model, options); } if (model && ev === 'change:' + model.idAttribute) { delete this._byId[model.previous(model.idAttribute)]; this._byId[model.id] = model; } this.trigger.apply(this, arguments); } }); // Underscore methods that we want to implement on the Collection. var methods = ['forEach', 'each', 'map', 'reduce', 'reduceRight', 'find', 'detect', 'filter', 'select', 'reject', 'every', 'all', 'some', 'any', 'include', 'contains', 'invoke', 'max', 'min', 'sortBy', 'sortedIndex', 'toArray', 'size', 'first', 'rest', 'last', 'without', 'indexOf', 'lastIndexOf', 'isEmpty', 'groupBy']; // Mix in each Underscore method as a proxy to `Collection#models`. _.each(methods, function(method) { Backbone.Collection.prototype[method] = function() { return _[method].apply(_, [this.models].concat(_.toArray(arguments))); }; }); // Backbone.Router // ------------------- // Routers map faux-URLs to actions, and fire events when routes are // matched. Creating a new one sets its `routes` hash, if not set statically. Backbone.Router = function(options) { options || (options = {}); if (options.routes) this.routes = options.routes; this._bindRoutes(); this.initialize.apply(this, arguments); }; // Cached regular expressions for matching named param parts and splatted // parts of route strings. var namedParam = /:([\w\d]+)/g; var splatParam = /\*([\w\d]+)/g; var escapeRegExp = /[-[\]{}()+?.,\\^$|#\s]/g; // Set up all inheritable **Backbone.Router** properties and methods. _.extend(Backbone.Router.prototype, Backbone.Events, { // Initialize is an empty function by default. Override it with your own // initialization logic. initialize : function(){}, // Manually bind a single named route to a callback. For example: // // this.route('search/:query/p:num', 'search', function(query, num) { // ... // }); // route : function(route, name, callback) { Backbone.history || (Backbone.history = new Backbone.History); if (!_.isRegExp(route)) route = this._routeToRegExp(route); Backbone.history.route(route, _.bind(function(fragment) { var args = this._extractParameters(route, fragment); callback.apply(this, args); this.trigger.apply(this, ['route:' + name].concat(args)); }, this)); }, // Simple proxy to `Backbone.history` to save a fragment into the history. navigate : function(fragment, triggerRoute) { Backbone.history.navigate(fragment, triggerRoute); }, // Bind all defined routes to `Backbone.history`. We have to reverse the // order of the routes here to support behavior where the most general // routes can be defined at the bottom of the route map. _bindRoutes : function() { if (!this.routes) return; var routes = []; for (var route in this.routes) { routes.unshift([route, this.routes[route]]); } for (var i = 0, l = routes.length; i < l; i++) { this.route(routes[i][0], routes[i][1], this[routes[i][1]]); } }, // Convert a route string into a regular expression, suitable for matching // against the current location hash. _routeToRegExp : function(route) { route = route.replace(escapeRegExp, "\\$&") .replace(namedParam, "([^\/]*)") .replace(splatParam, "(.*?)"); return new RegExp('^' + route + '$'); }, // Given a route, and a URL fragment that it matches, return the array of // extracted parameters. _extractParameters : function(route, fragment) { return route.exec(fragment).slice(1); } }); // Backbone.History // ---------------- // Handles cross-browser history management, based on URL fragments. If the // browser does not support `onhashchange`, falls back to polling. Backbone.History = function() { this.handlers = []; _.bindAll(this, 'checkUrl'); }; // Cached regex for cleaning hashes. var hashStrip = /^#*/; // Cached regex for detecting MSIE. var isExplorer = /msie [\w.]+/; // Has the history handling already been started? var historyStarted = false; // Set up all inheritable **Backbone.History** properties and methods. _.extend(Backbone.History.prototype, { // The default interval to poll for hash changes, if necessary, is // twenty times a second. interval: 50, // Get the cross-browser normalized URL fragment, either from the URL, // the hash, or the override. getFragment : function(fragment, forcePushState) { if (fragment == null) { if (this._hasPushState || forcePushState) { fragment = window.location.pathname; var search = window.location.search; if (search) fragment += search; if (fragment.indexOf(this.options.root) == 0) fragment = fragment.substr(this.options.root.length); } else { fragment = window.location.hash; } } return decodeURIComponent(fragment.replace(hashStrip, '')); }, // Start the hash change handling, returning `true` if the current URL matches // an existing route, and `false` otherwise. start : function(options) { // Figure out the initial configuration. Do we need an iframe? // Is pushState desired ... is it available? if (historyStarted) throw new Error("Backbone.history has already been started"); this.options = _.extend({}, {root: '/'}, this.options, options); this._wantsPushState = !!this.options.pushState; this._hasPushState = !!(this.options.pushState && window.history && window.history.pushState); var fragment = this.getFragment(); var docMode = document.documentMode; var oldIE = (isExplorer.exec(navigator.userAgent.toLowerCase()) && (!docMode || docMode <= 7)); if (oldIE) { this.iframe = $('<iframe src="javascript:0" tabindex="-1" />').hide().appendTo('body')[0].contentWindow; this.navigate(fragment); } // Depending on whether we're using pushState or hashes, and whether // 'onhashchange' is supported, determine how we check the URL state. if (this._hasPushState) { $(window).bind('popstate', this.checkUrl); } else if ('onhashchange' in window && !oldIE) { $(window).bind('hashchange', this.checkUrl); } else { setInterval(this.checkUrl, this.interval); } // Determine if we need to change the base url, for a pushState link // opened by a non-pushState browser. this.fragment = fragment; historyStarted = true; var loc = window.location; var atRoot = loc.pathname == this.options.root; if (this._wantsPushState && !this._hasPushState && !atRoot) { this.fragment = this.getFragment(null, true); window.location.replace(this.options.root + '#' + this.fragment); // Return immediately as browser will do redirect to new url return true; } else if (this._wantsPushState && this._hasPushState && atRoot && loc.hash) { this.fragment = loc.hash.replace(hashStrip, ''); window.history.replaceState({}, document.title, loc.protocol + '//' + loc.host + this.options.root + this.fragment); } if (!this.options.silent) { return this.loadUrl(); } }, // Add a route to be tested when the fragment changes. Routes added later may // override previous routes. route : function(route, callback) { this.handlers.unshift({route : route, callback : callback}); }, // Checks the current URL to see if it has changed, and if it has, // calls `loadUrl`, normalizing across the hidden iframe. checkUrl : function(e) { var current = this.getFragment(); if (current == this.fragment && this.iframe) current = this.getFragment(this.iframe.location.hash); if (current == this.fragment || current == decodeURIComponent(this.fragment)) return false; if (this.iframe) this.navigate(current); this.loadUrl() || this.loadUrl(window.location.hash); }, // Attempt to load the current URL fragment. If a route succeeds with a // match, returns `true`. If no defined routes matches the fragment, // returns `false`. loadUrl : function(fragmentOverride) { var fragment = this.fragment = this.getFragment(fragmentOverride); var matched = _.any(this.handlers, function(handler) { if (handler.route.test(fragment)) { handler.callback(fragment); return true; } }); return matched; }, // Save a fragment into the hash history. You are responsible for properly // URL-encoding the fragment in advance. This does not trigger // a `hashchange` event. navigate : function(fragment, triggerRoute) { var frag = (fragment || '').replace(hashStrip, ''); if (this.fragment == frag || this.fragment == decodeURIComponent(frag)) return; if (this._hasPushState) { var loc = window.location; if (frag.indexOf(this.options.root) != 0) frag = this.options.root + frag; this.fragment = frag; window.history.pushState({}, document.title, loc.protocol + '//' + loc.host + frag); } else { window.location.hash = this.fragment = frag; if (this.iframe && (frag != this.getFragment(this.iframe.location.hash))) { this.iframe.document.open().close(); this.iframe.location.hash = frag; } } if (triggerRoute) this.loadUrl(fragment); } }); // Backbone.View // ------------- // Creating a Backbone.View creates its initial element outside of the DOM, // if an existing element is not provided... Backbone.View = function(options) { this.cid = _.uniqueId('view'); this._configure(options || {}); this._ensureElement(); this.delegateEvents(); this.initialize.apply(this, arguments); }; // Element lookup, scoped to DOM elements within the current view. // This should be preferred to global lookups, if you're dealing with // a specific view. var selectorDelegate = function(selector) { return $(selector, this.el); }; // Cached regex to split keys for `delegate`. var eventSplitter = /^(\S+)\s*(.*)$/; // List of view options to be merged as properties. var viewOptions = ['model', 'collection', 'el', 'id', 'attributes', 'className', 'tagName']; // Set up all inheritable **Backbone.View** properties and methods. _.extend(Backbone.View.prototype, Backbone.Events, { // The default `tagName` of a View's element is `"div"`. tagName : 'div', // Attach the `selectorDelegate` function as the `$` property. $ : selectorDelegate, // Initialize is an empty function by default. Override it with your own // initialization logic. initialize : function(){}, // **render** is the core function that your view should override, in order // to populate its element (`this.el`), with the appropriate HTML. The // convention is for **render** to always return `this`. render : function() { return this; }, // Remove this view from the DOM. Note that the view isn't present in the // DOM by default, so calling this method may be a no-op. remove : function() { $(this.el).remove(); return this; }, // For small amounts of DOM Elements, where a full-blown template isn't // needed, use **make** to manufacture elements, one at a time. // // var el = this.make('li', {'class': 'row'}, this.model.escape('title')); // make : function(tagName, attributes, content) { var el = document.createElement(tagName); if (attributes) $(el).attr(attributes); if (content) $(el).html(content); return el; }, // Set callbacks, where `this.callbacks` is a hash of // // *{"event selector": "callback"}* // // { // 'mousedown .title': 'edit', // 'click .button': 'save' // } // // pairs. Callbacks will be bound to the view, with `this` set properly. // Uses event delegation for efficiency. // Omitting the selector binds the event to `this.el`. // This only works for delegate-able events: not `focus`, `blur`, and // not `change`, `submit`, and `reset` in Internet Explorer. delegateEvents : function(events) { if (!(events || (events = this.events))) return; if (_.isFunction(events)) events = events.call(this); $(this.el).unbind('.delegateEvents' + this.cid); for (var key in events) { var method = this[events[key]]; if (!method) throw new Error('Event "' + events[key] + '" does not exist'); var match = key.match(eventSplitter); var eventName = match[1], selector = match[2]; method = _.bind(method, this); eventName += '.delegateEvents' + this.cid; if (selector === '') { $(this.el).bind(eventName, method); } else { $(this.el).delegate(selector, eventName, method); } } }, // Performs the initial configuration of a View with a set of options. // Keys with special meaning *(model, collection, id, className)*, are // attached directly to the view. _configure : function(options) { if (this.options) options = _.extend({}, this.options, options); for (var i = 0, l = viewOptions.length; i < l; i++) { var attr = viewOptions[i]; if (options[attr]) this[attr] = options[attr]; } this.options = options; }, // Ensure that the View has a DOM element to render into. // If `this.el` is a string, pass it through `$()`, take the first // matching element, and re-assign it to `el`. Otherwise, create // an element from the `id`, `className` and `tagName` properties. _ensureElement : function() { if (!this.el) { var attrs = this.attributes || {}; if (this.id) attrs.id = this.id; if (this.className) attrs['class'] = this.className; this.el = this.make(this.tagName, attrs); } else if (_.isString(this.el)) { this.el = $(this.el).get(0); } } }); // The self-propagating extend function that Backbone classes use. var extend = function (protoProps, classProps) { var child = inherits(this, protoProps, classProps); child.extend = this.extend; return child; }; // Set up inheritance for the model, collection, and view. Backbone.Model.extend = Backbone.Collection.extend = Backbone.Router.extend = Backbone.View.extend = extend; // Map from CRUD to HTTP for our default `Backbone.sync` implementation. var methodMap = { 'create': 'POST', 'update': 'PUT', 'delete': 'DELETE', 'read' : 'GET' }; // Backbone.sync // ------------- // Override this function to change the manner in which Backbone persists // models to the server. You will be passed the type of request, and the // model in question. By default, uses makes a RESTful Ajax request // to the model's `url()`. Some possible customizations could be: // // * Use `setTimeout` to batch rapid-fire updates into a single request. // * Send up the models as XML instead of JSON. // * Persist models via WebSockets instead of Ajax. // // Turn on `Backbone.emulateHTTP` in order to send `PUT` and `DELETE` requests // as `POST`, with a `_method` parameter containing the true HTTP method, // as well as all requests with the body as `application/x-www-form-urlencoded` instead of // `application/json` with the model in a param named `model`. // Useful when interfacing with server-side languages like **PHP** that make // it difficult to read the body of `PUT` requests. Backbone.sync = function(method, model, options) { var type = methodMap[method]; // Default JSON-request options. var params = _.extend({ type: type, dataType: 'json' }, options); // Ensure that we have a URL. if (!params.url) { params.url = getUrl(model) || urlError(); } // Ensure that we have the appropriate request data. if (!params.data && model && (method == 'create' || method == 'update')) { params.contentType = 'application/json'; params.data = JSON.stringify(model.toJSON()); } // For older servers, emulate JSON by encoding the request into an HTML-form. if (Backbone.emulateJSON) { params.contentType = 'application/x-www-form-urlencoded'; params.data = params.data ? {model : params.data} : {}; } // For older servers, emulate HTTP by mimicking the HTTP method with `_method` // And an `X-HTTP-Method-Override` header. if (Backbone.emulateHTTP) { if (type === 'PUT' || type === 'DELETE') { if (Backbone.emulateJSON) params.data._method = type; params.type = 'POST'; params.beforeSend = function(xhr) { xhr.setRequestHeader('X-HTTP-Method-Override', type); }; } } // Don't process data on a non-GET request. if (params.type !== 'GET' && !Backbone.emulateJSON) { params.processData = false; } // Make the request. return $.ajax(params); }; // Helpers // ------- // Shared empty constructor function to aid in prototype-chain creation. var ctor = function(){}; // Helper function to correctly set up the prototype chain, for subclasses. // Similar to `goog.inherits`, but uses a hash of prototype properties and // class properties to be extended. var inherits = function(parent, protoProps, staticProps) { var child; // The constructor function for the new subclass is either defined by you // (the "constructor" property in your `extend` definition), or defaulted // by us to simply call `super()`. if (protoProps && protoProps.hasOwnProperty('constructor')) { child = protoProps.constructor; } else { child = function(){ return parent.apply(this, arguments); }; } // Inherit class (static) properties from parent. _.extend(child, parent); // Set the prototype chain to inherit from `parent`, without calling // `parent`'s constructor function. ctor.prototype = parent.prototype; child.prototype = new ctor(); // Add prototype properties (instance properties) to the subclass, // if supplied. if (protoProps) _.extend(child.prototype, protoProps); // Add static properties to the constructor function, if supplied. if (staticProps) _.extend(child, staticProps); // Correctly set child's `prototype.constructor`. child.prototype.constructor = child; // Set a convenience property in case the parent's prototype is needed later. child.__super__ = parent.prototype; return child; }; // Helper function to get a URL from a Model or Collection as a property // or as a function. var getUrl = function(object) { if (!(object && object.url)) return null; return _.isFunction(object.url) ? object.url() : object.url; }; // Throw an error when a URL is needed, and none is supplied. var urlError = function() { throw new Error('A "url" property or function must be specified'); }; // Wrap an optional error callback with a fallback error event. var wrapError = function(onError, model, options) { return function(resp) { if (onError) { onError(model, resp, options); } else { model.trigger('error', model, resp, options); } }; }; // Helper function to escape a string for HTML rendering. var escapeHTML = function(string) { return string.replace(/&(?!\w+;|#\d+;|#x[\da-f]+;)/gi, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"').replace(/'/g, ''').replace(/\//g,'/'); }; }).call(this); �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������beetbox-beets-01f1faf/beetsplug/web/static/beets.css������������������������������������������������0000664�0000000�0000000�00000005607�14723254774�0022631�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������body { font-family: Helvetica, Arial, sans-serif; } #header { position: fixed; left: 0; right: 0; top: 0; height: 36px; color: white; cursor: default; /* shadowy border */ box-shadow: 0 0 20px #999; -webkit-box-shadow: 0 0 20px #999; -moz-box-shadow: 0 0 20px #999; /* background gradient */ background: #0e0e0e; background: -moz-linear-gradient(top, #6b6b6b 0%, #0e0e0e 100%); background: -webkit-linear-gradient(top, #6b6b6b 0%,#0e0e0e 100%); } #header h1 { font-size: 1.1em; font-weight: bold; color: white; margin: 0.35em; float: left; } #entities { width: 17em; position: fixed; top: 36px; left: 0; bottom: 0; margin: 0; z-index: 1; background: #dde4eb; /* shadowy border */ box-shadow: 0 0 20px #666; -webkit-box-shadow: 0 0 20px #666; -moz-box-shadow: 0 0 20px #666; } #queryForm { display: block; text-align: center; margin: 0.25em 0; } #query { width: 95%; font-size: 1em; } #entities ul { width: 17em; position: fixed; top: 36px; left: 0; bottom: 0; margin: 2.2em 0 0 0; padding: 0; overflow-y: auto; overflow-x: hidden; } #entities ul li { list-style: none; padding: 4px 8px; margin: 0; cursor: default; } #entities ul li.selected { background: #7abcff; background: -moz-linear-gradient(top, #7abcff 0%, #60abf8 44%, #4096ee 100%); background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#7abcff), color-stop(44%,#60abf8), color-stop(100%,#4096ee)); color: white; } #entities ul li .playing { margin-left: 5px; font-size: 0.9em; } #main-detail, #extra-detail { position: fixed; left: 17em; margin: 1.0em 0 0 1.5em; } #main-detail { top: 36px; height: 98px; } #main-detail .artist, #main-detail .album, #main-detail .title { display: block; } #main-detail .title { font-size: 1.3em; font-weight: bold; } #main-detail .albumtitle { font-style: italic; } #extra-detail { overflow-x: hidden; overflow-y: auto; top: 134px; bottom: 0; right: 0; } /*Fix for correctly displaying line breaks in lyrics*/ #extra-detail .lyrics { white-space: pre-wrap; } #extra-detail dl dt, #extra-detail dl dd { list-style: none; margin: 0; padding: 0; } #extra-detail dl dt { width: 10em; float: left; text-align: right; font-weight: bold; clear: both; } #extra-detail dl dd { margin-left: 10.5em; } #player { float: left; width: 150px; height: 36px; } #player .play, #player .pause, #player .disabled { -webkit-appearance: none; font-size: 1em; font-family: Helvetica, Arial, sans-serif; background: none; border: none; color: white; padding: 5px; margin: 0; text-align: center; width: 36px; height: 36px; } #player .disabled { color: #666; } �������������������������������������������������������������������������������������������������������������������������beetbox-beets-01f1faf/beetsplug/web/static/beets.js�������������������������������������������������0000664�0000000�0000000�00000021233�14723254774�0022446�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������// Format times as minutes and seconds. var timeFormat = function(secs) { if (secs == undefined || isNaN(secs)) { return '0:00'; } secs = Math.round(secs); var mins = '' + Math.floor(secs / 60); secs = '' + (secs % 60); if (secs.length < 2) { secs = '0' + secs; } return mins + ':' + secs; } // jQuery extension encapsulating event hookups for audio element controls. $.fn.player = function(debug) { // Selected element should contain an HTML5 Audio element. var audio = $('audio', this).get(0); // Control elements that may be present, identified by class. var playBtn = $('.play', this); var pauseBtn = $('.pause', this); var disabledInd = $('.disabled', this); var timesEl = $('.times', this); var curTimeEl = $('.currentTime', this); var totalTimeEl = $('.totalTime', this); var sliderPlayedEl = $('.slider .played', this); var sliderLoadedEl = $('.slider .loaded', this); // Button events. playBtn.click(function() { audio.play(); }); pauseBtn.click(function(ev) { audio.pause(); }); // Utilities. var timePercent = function(cur, total) { if (cur == undefined || isNaN(cur) || total == undefined || isNaN(total) || total == 0) { return 0; } var ratio = cur / total; if (ratio > 1.0) { ratio = 1.0; } return (Math.round(ratio * 10000) / 100) + '%'; } // Event helpers. var dbg = function(msg) { if (debug) console.log(msg); } var showState = function() { if (audio.duration == undefined || isNaN(audio.duration)) { playBtn.hide(); pauseBtn.hide(); disabledInd.show(); timesEl.hide(); } else if (audio.paused) { playBtn.show(); pauseBtn.hide(); disabledInd.hide(); timesEl.show(); } else { playBtn.hide(); pauseBtn.show(); disabledInd.hide(); timesEl.show(); } } var showTimes = function() { curTimeEl.text(timeFormat(audio.currentTime)); totalTimeEl.text(timeFormat(audio.duration)); sliderPlayedEl.css('width', timePercent(audio.currentTime, audio.duration)); // last time buffered var bufferEnd = 0; for (var i = 0; i < audio.buffered.length; ++i) { if (audio.buffered.end(i) > bufferEnd) bufferEnd = audio.buffered.end(i); } sliderLoadedEl.css('width', timePercent(bufferEnd, audio.duration)); } // Initialize controls. showState(); showTimes(); // Bind events. $('audio', this).bind({ playing: function() { dbg('playing'); showState(); }, pause: function() { dbg('pause'); showState(); }, ended: function() { dbg('ended'); showState(); }, progress: function() { dbg('progress ' + audio.buffered); }, timeupdate: function() { dbg('timeupdate ' + audio.currentTime); showTimes(); }, durationchange: function() { dbg('durationchange ' + audio.duration); showState(); showTimes(); }, loadeddata: function() { dbg('loadeddata'); }, loadedmetadata: function() { dbg('loadedmetadata'); } }); } // Simple selection disable for jQuery. // Cut-and-paste from: // https://stackoverflow.com/questions/2700000 $.fn.disableSelection = function() { $(this).attr('unselectable', 'on') .css('-moz-user-select', 'none') .each(function() { this.onselectstart = function() { return false; }; }); }; $(function() { // Routes. var BeetsRouter = Backbone.Router.extend({ routes: { "item/query/:query": "itemQuery", }, itemQuery: function(query) { var queryURL = query.split(/\s+/).map(encodeURIComponent).join('/'); $.getJSON('item/query/' + queryURL, function(data) { var models = _.map( data['results'], function(d) { return new Item(d); } ); var results = new Items(models); app.showItems(results); }); } }); var router = new BeetsRouter(); // Model. var Item = Backbone.Model.extend({ urlRoot: 'item' }); var Items = Backbone.Collection.extend({ model: Item }); // Item views. var ItemEntryView = Backbone.View.extend({ tagName: "li", template: _.template($('#item-entry-template').html()), events: { 'click': 'select', 'dblclick': 'play' }, initialize: function() { this.playing = false; }, render: function() { $(this.el).html(this.template(this.model.toJSON())); this.setPlaying(this.playing); return this; }, select: function() { app.selectItem(this); }, play: function() { app.playItem(this.model); }, setPlaying: function(val) { this.playing = val; if (val) this.$('.playing').show(); else this.$('.playing').hide(); } }); //Holds Title, Artist, Album etc. var ItemMainDetailView = Backbone.View.extend({ tagName: "div", template: _.template($('#item-main-detail-template').html()), events: { 'click .play': 'play', }, render: function() { $(this.el).html(this.template(this.model.toJSON())); return this; }, play: function() { app.playItem(this.model); } }); // Holds Track no., Format, MusicBrainz link, Lyrics, Comments etc. var ItemExtraDetailView = Backbone.View.extend({ tagName: "div", template: _.template($('#item-extra-detail-template').html()), render: function() { $(this.el).html(this.template(this.model.toJSON())); return this; } }); // Main app view. var AppView = Backbone.View.extend({ el: $('body'), events: { 'submit #queryForm': 'querySubmit', }, querySubmit: function(ev) { ev.preventDefault(); router.navigate('item/query/' + encodeURIComponent($('#query').val()), true); }, initialize: function() { this.playingItem = null; this.shownItems = null; // Not sure why these events won't bind automatically. this.$('audio').bind({ 'play': _.bind(this.audioPlay, this), 'pause': _.bind(this.audioPause, this), 'ended': _.bind(this.audioEnded, this) }); }, showItems: function(items) { this.shownItems = items; $('#results').empty(); items.each(function(item) { var view = new ItemEntryView({model: item}); item.entryView = view; $('#results').append(view.render().el); }); }, selectItem: function(view) { // Mark row as selected. $('#results li').removeClass("selected"); $(view.el).addClass("selected"); // Show main and extra detail. var mainDetailView = new ItemMainDetailView({model: view.model}); $('#main-detail').empty().append(mainDetailView.render().el); var extraDetailView = new ItemExtraDetailView({model: view.model}); $('#extra-detail').empty().append(extraDetailView.render().el); }, playItem: function(item) { var url = 'item/' + item.get('id') + '/file'; $('#player audio').attr('src', url); $('#player audio').get(0).play(); if (this.playingItem != null) { this.playingItem.entryView.setPlaying(false); } item.entryView.setPlaying(true); this.playingItem = item; }, audioPause: function() { this.playingItem.entryView.setPlaying(false); }, audioPlay: function() { if (this.playingItem != null) this.playingItem.entryView.setPlaying(true); }, audioEnded: function() { this.playingItem.entryView.setPlaying(false); // Try to play the next track. var idx = this.shownItems.indexOf(this.playingItem); if (idx == -1) { // Not in current list. return; } var nextIdx = idx + 1; if (nextIdx >= this.shownItems.size()) { // End of list. return; } this.playItem(this.shownItems.at(nextIdx)); } }); var app = new AppView(); // App setup. Backbone.history.start({pushState: false}); // Disable selection on UI elements. $('#entities ul').disableSelection(); $('#header').disableSelection(); // Audio player setup. $('#player').player(); }); ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������beetbox-beets-01f1faf/beetsplug/web/static/jquery.js������������������������������������������������0000664�0000000�0000000�00000744653�14723254774�0022705�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/*! * jQuery JavaScript Library v1.7.1 * http://jquery.com/ * * Copyright 2016, John Resig * Dual licensed under the MIT or GPL Version 2 licenses. * http://jquery.org/license * * Includes Sizzle.js * http://sizzlejs.com/ * Copyright 2016, The Dojo Foundation * Released under the MIT, BSD, and GPL Licenses. * * Date: Mon Nov 21 21:11:03 2011 -0500 */ (function( window, undefined ) { // Use the correct document accordingly with window argument (sandbox) var document = window.document, navigator = window.navigator, location = window.location; var jQuery = (function() { // Define a local copy of jQuery var jQuery = function( selector, context ) { // The jQuery object is actually just the init constructor 'enhanced' return new jQuery.fn.init( selector, context, rootjQuery ); }, // Map over jQuery in case of overwrite _jQuery = window.jQuery, // Map over the $ in case of overwrite _$ = window.$, // A central reference to the root jQuery(document) rootjQuery, // A simple way to check for HTML strings or ID strings // Prioritize #id over <tag> to avoid XSS via location.hash (#9521) quickExpr = /^(?:[^#<]*(<[\w\W]+>)[^>]*$|#([\w\-]*)$)/, // Check if a string has a non-whitespace character in it rnotwhite = /\S/, // Used for trimming whitespace trimLeft = /^\s+/, trimRight = /\s+$/, // Match a standalone tag rsingleTag = /^<(\w+)\s*\/?>(?:<\/\1>)?$/, // JSON RegExp rvalidchars = /^[\],:{}\s]*$/, rvalidescape = /\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, rvalidtokens = /"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, rvalidbraces = /(?:^|:|,)(?:\s*\[)+/g, // Useragent RegExp rwebkit = /(webkit)[ \/]([\w.]+)/, ropera = /(opera)(?:.*version)?[ \/]([\w.]+)/, rmsie = /(msie) ([\w.]+)/, rmozilla = /(mozilla)(?:.*? rv:([\w.]+))?/, // Matches dashed string for camelizing rdashAlpha = /-([a-z]|[0-9])/ig, rmsPrefix = /^-ms-/, // Used by jQuery.camelCase as callback to replace() fcamelCase = function( all, letter ) { return ( letter + "" ).toUpperCase(); }, // Keep a UserAgent string for use with jQuery.browser userAgent = navigator.userAgent, // For matching the engine and version of the browser browserMatch, // The deferred used on DOM ready readyList, // The ready event handler DOMContentLoaded, // Save a reference to some core methods toString = Object.prototype.toString, hasOwn = Object.prototype.hasOwnProperty, push = Array.prototype.push, slice = Array.prototype.slice, trim = String.prototype.trim, indexOf = Array.prototype.indexOf, // [[Class]] -> type pairs class2type = {}; jQuery.fn = jQuery.prototype = { constructor: jQuery, init: function( selector, context, rootjQuery ) { var match, elem, ret, doc; // Handle $(""), $(null), or $(undefined) if ( !selector ) { return this; } // Handle $(DOMElement) if ( selector.nodeType ) { this.context = this[0] = selector; this.length = 1; return this; } // The body element only exists once, optimize finding it if ( selector === "body" && !context && document.body ) { this.context = document; this[0] = document.body; this.selector = selector; this.length = 1; return this; } // Handle HTML strings if ( typeof selector === "string" ) { // Are we dealing with HTML string or an ID? if ( selector.charAt(0) === "<" && selector.charAt( selector.length - 1 ) === ">" && selector.length >= 3 ) { // Assume that strings that start and end with <> are HTML and skip the regex check match = [ null, selector, null ]; } else { match = quickExpr.exec( selector ); } // Verify a match, and that no context was specified for #id if ( match && (match[1] || !context) ) { // HANDLE: $(html) -> $(array) if ( match[1] ) { context = context instanceof jQuery ? context[0] : context; doc = ( context ? context.ownerDocument || context : document ); // If a single string is passed in and it's a single tag // just do a createElement and skip the rest ret = rsingleTag.exec( selector ); if ( ret ) { if ( jQuery.isPlainObject( context ) ) { selector = [ document.createElement( ret[1] ) ]; jQuery.fn.attr.call( selector, context, true ); } else { selector = [ doc.createElement( ret[1] ) ]; } } else { ret = jQuery.buildFragment( [ match[1] ], [ doc ] ); selector = ( ret.cacheable ? jQuery.clone(ret.fragment) : ret.fragment ).childNodes; } return jQuery.merge( this, selector ); // HANDLE: $("#id") } else { elem = document.getElementById( match[2] ); // Check parentNode to catch when Blackberry 4.6 returns // nodes that are no longer in the document #6963 if ( elem && elem.parentNode ) { // Handle the case where IE and Opera return items // by name instead of ID if ( elem.id !== match[2] ) { return rootjQuery.find( selector ); } // Otherwise, we inject the element directly into the jQuery object this.length = 1; this[0] = elem; } this.context = document; this.selector = selector; return this; } // HANDLE: $(expr, $(...)) } else if ( !context || context.jquery ) { return ( context || rootjQuery ).find( selector ); // HANDLE: $(expr, context) // (which is just equivalent to: $(context).find(expr) } else { return this.constructor( context ).find( selector ); } // HANDLE: $(function) // Shortcut for document ready } else if ( jQuery.isFunction( selector ) ) { return rootjQuery.ready( selector ); } if ( selector.selector !== undefined ) { this.selector = selector.selector; this.context = selector.context; } return jQuery.makeArray( selector, this ); }, // Start with an empty selector selector: "", // The current version of jQuery being used jquery: "1.7.1", // The default length of a jQuery object is 0 length: 0, // The number of elements contained in the matched element set size: function() { return this.length; }, toArray: function() { return slice.call( this, 0 ); }, // Get the Nth element in the matched element set OR // Get the whole matched element set as a clean array get: function( num ) { return num == null ? // Return a 'clean' array this.toArray() : // Return just the object ( num < 0 ? this[ this.length + num ] : this[ num ] ); }, // Take an array of elements and push it onto the stack // (returning the new matched element set) pushStack: function( elems, name, selector ) { // Build a new jQuery matched element set var ret = this.constructor(); if ( jQuery.isArray( elems ) ) { push.apply( ret, elems ); } else { jQuery.merge( ret, elems ); } // Add the old object onto the stack (as a reference) ret.prevObject = this; ret.context = this.context; if ( name === "find" ) { ret.selector = this.selector + ( this.selector ? " " : "" ) + selector; } else if ( name ) { ret.selector = this.selector + "." + name + "(" + selector + ")"; } // Return the newly-formed element set return ret; }, // Execute a callback for every element in the matched set. // (You can seed the arguments with an array of args, but this is // only used internally.) each: function( callback, args ) { return jQuery.each( this, callback, args ); }, ready: function( fn ) { // Attach the listeners jQuery.bindReady(); // Add the callback readyList.add( fn ); return this; }, eq: function( i ) { i = +i; return i === -1 ? this.slice( i ) : this.slice( i, i + 1 ); }, first: function() { return this.eq( 0 ); }, last: function() { return this.eq( -1 ); }, slice: function() { return this.pushStack( slice.apply( this, arguments ), "slice", slice.call(arguments).join(",") ); }, map: function( callback ) { return this.pushStack( jQuery.map(this, function( elem, i ) { return callback.call( elem, i, elem ); })); }, end: function() { return this.prevObject || this.constructor(null); }, // For internal use only. // Behaves like an Array's method, not like a jQuery method. push: push, sort: [].sort, splice: [].splice }; // Give the init function the jQuery prototype for later instantiation jQuery.fn.init.prototype = jQuery.fn; jQuery.extend = jQuery.fn.extend = function() { var options, name, src, copy, copyIsArray, clone, target = arguments[0] || {}, i = 1, length = arguments.length, deep = false; // Handle a deep copy situation if ( typeof target === "boolean" ) { deep = target; target = arguments[1] || {}; // skip the boolean and the target i = 2; } // Handle case when target is a string or something (possible in deep copy) if ( typeof target !== "object" && !jQuery.isFunction(target) ) { target = {}; } // extend jQuery itself if only one argument is passed if ( length === i ) { target = this; --i; } for ( ; i < length; i++ ) { // Only deal with non-null/undefined values if ( (options = arguments[ i ]) != null ) { // Extend the base object for ( name in options ) { src = target[ name ]; copy = options[ name ]; // Prevent never-ending loop if ( target === copy ) { continue; } // Recurse if we're merging plain objects or arrays if ( deep && copy && ( jQuery.isPlainObject(copy) || (copyIsArray = jQuery.isArray(copy)) ) ) { if ( copyIsArray ) { copyIsArray = false; clone = src && jQuery.isArray(src) ? src : []; } else { clone = src && jQuery.isPlainObject(src) ? src : {}; } // Never move original objects, clone them target[ name ] = jQuery.extend( deep, clone, copy ); // Don't bring in undefined values } else if ( copy !== undefined ) { target[ name ] = copy; } } } } // Return the modified object return target; }; jQuery.extend({ noConflict: function( deep ) { if ( window.$ === jQuery ) { window.$ = _$; } if ( deep && window.jQuery === jQuery ) { window.jQuery = _jQuery; } return jQuery; }, // Is the DOM ready to be used? Set to true once it occurs. isReady: false, // A counter to track how many items to wait for before // the ready event fires. See #6781 readyWait: 1, // Hold (or release) the ready event holdReady: function( hold ) { if ( hold ) { jQuery.readyWait++; } else { jQuery.ready( true ); } }, // Handle when the DOM is ready ready: function( wait ) { // Either a released hold or an DOMready/load event and not yet ready if ( (wait === true && !--jQuery.readyWait) || (wait !== true && !jQuery.isReady) ) { // Make sure body exists, at least, in case IE gets a little overzealous (ticket #5443). if ( !document.body ) { return setTimeout( jQuery.ready, 1 ); } // Remember that the DOM is ready jQuery.isReady = true; // If a normal DOM Ready event fired, decrement, and wait if need be if ( wait !== true && --jQuery.readyWait > 0 ) { return; } // If there are functions bound, to execute readyList.fireWith( document, [ jQuery ] ); // Trigger any bound ready events if ( jQuery.fn.trigger ) { jQuery( document ).trigger( "ready" ).off( "ready" ); } } }, bindReady: function() { if ( readyList ) { return; } readyList = jQuery.Callbacks( "once memory" ); // Catch cases where $(document).ready() is called after the // browser event has already occurred. if ( document.readyState === "complete" ) { // Handle it asynchronously to allow scripts the opportunity to delay ready return setTimeout( jQuery.ready, 1 ); } // Mozilla, Opera and webkit nightlies currently support this event if ( document.addEventListener ) { // Use the handy event callback document.addEventListener( "DOMContentLoaded", DOMContentLoaded, false ); // A fallback to window.onload, that will always work window.addEventListener( "load", jQuery.ready, false ); // If IE event model is used } else if ( document.attachEvent ) { // ensure firing before onload, // maybe late but safe also for iframes document.attachEvent( "onreadystatechange", DOMContentLoaded ); // A fallback to window.onload, that will always work window.attachEvent( "onload", jQuery.ready ); // If IE and not a frame // continually check to see if the document is ready var toplevel = false; try { toplevel = window.frameElement == null; } catch(e) {} if ( document.documentElement.doScroll && toplevel ) { doScrollCheck(); } } }, // See test/unit/core.js for details concerning isFunction. // Since version 1.3, DOM methods and functions like alert // aren't supported. They return false on IE (#2968). isFunction: function( obj ) { return jQuery.type(obj) === "function"; }, isArray: Array.isArray || function( obj ) { return jQuery.type(obj) === "array"; }, // A crude way of determining if an object is a window isWindow: function( obj ) { return obj && typeof obj === "object" && "setInterval" in obj; }, isNumeric: function( obj ) { return !isNaN( parseFloat(obj) ) && isFinite( obj ); }, type: function( obj ) { return obj == null ? String( obj ) : class2type[ toString.call(obj) ] || "object"; }, isPlainObject: function( obj ) { // Must be an Object. // Because of IE, we also have to check the presence of the constructor property. // Make sure that DOM nodes and window objects don't pass through, as well if ( !obj || jQuery.type(obj) !== "object" || obj.nodeType || jQuery.isWindow( obj ) ) { return false; } try { // Not own constructor property must be Object if ( obj.constructor && !hasOwn.call(obj, "constructor") && !hasOwn.call(obj.constructor.prototype, "isPrototypeOf") ) { return false; } } catch ( e ) { // IE8,9 Will throw exceptions on certain host objects #9897 return false; } // Own properties are enumerated firstly, so to speed up, // if last one is own, then all properties are own. var key; for ( key in obj ) {} return key === undefined || hasOwn.call( obj, key ); }, isEmptyObject: function( obj ) { for ( var name in obj ) { return false; } return true; }, error: function( msg ) { throw new Error( msg ); }, parseJSON: function( data ) { if ( typeof data !== "string" || !data ) { return null; } // Make sure leading/trailing whitespace is removed (IE can't handle it) data = jQuery.trim( data ); // Attempt to parse using the native JSON parser first if ( window.JSON && window.JSON.parse ) { return window.JSON.parse( data ); } // Make sure the incoming data is actual JSON // Logic borrowed from http://json.org/json2.js if ( rvalidchars.test( data.replace( rvalidescape, "@" ) .replace( rvalidtokens, "]" ) .replace( rvalidbraces, "")) ) { return ( new Function( "return " + data ) )(); } jQuery.error( "Invalid JSON: " + data ); }, // Cross-browser xml parsing parseXML: function( data ) { var xml, tmp; try { if ( window.DOMParser ) { // Standard tmp = new DOMParser(); xml = tmp.parseFromString( data , "text/xml" ); } else { // IE xml = new ActiveXObject( "Microsoft.XMLDOM" ); xml.async = "false"; xml.loadXML( data ); } } catch( e ) { xml = undefined; } if ( !xml || !xml.documentElement || xml.getElementsByTagName( "parsererror" ).length ) { jQuery.error( "Invalid XML: " + data ); } return xml; }, noop: function() {}, // Evaluates a script in a global context // Workarounds based on findings by Jim Driscoll // http://weblogs.java.net/blog/driscoll/archive/2009/09/08/eval-javascript-global-context globalEval: function( data ) { if ( data && rnotwhite.test( data ) ) { // We use execScript on Internet Explorer // We use an anonymous function so that context is window // rather than jQuery in Firefox ( window.execScript || function( data ) { window[ "eval" ].call( window, data ); } )( data ); } }, // Convert dashed to camelCase; used by the css and data modules // Microsoft forgot to hump their vendor prefix (#9572) camelCase: function( string ) { return string.replace( rmsPrefix, "ms-" ).replace( rdashAlpha, fcamelCase ); }, nodeName: function( elem, name ) { return elem.nodeName && elem.nodeName.toUpperCase() === name.toUpperCase(); }, // args is for internal usage only each: function( object, callback, args ) { var name, i = 0, length = object.length, isObj = length === undefined || jQuery.isFunction( object ); if ( args ) { if ( isObj ) { for ( name in object ) { if ( callback.apply( object[ name ], args ) === false ) { break; } } } else { for ( ; i < length; ) { if ( callback.apply( object[ i++ ], args ) === false ) { break; } } } // A special, fast, case for the most common use of each } else { if ( isObj ) { for ( name in object ) { if ( callback.call( object[ name ], name, object[ name ] ) === false ) { break; } } } else { for ( ; i < length; ) { if ( callback.call( object[ i ], i, object[ i++ ] ) === false ) { break; } } } } return object; }, // Use native String.trim function wherever possible trim: trim ? function( text ) { return text == null ? "" : trim.call( text ); } : // Otherwise use our own trimming functionality function( text ) { return text == null ? "" : text.toString().replace( trimLeft, "" ).replace( trimRight, "" ); }, // results is for internal usage only makeArray: function( array, results ) { var ret = results || []; if ( array != null ) { // The window, strings (and functions) also have 'length' // Tweaked logic slightly to handle Blackberry 4.7 RegExp issues #6930 var type = jQuery.type( array ); if ( array.length == null || type === "string" || type === "function" || type === "regexp" || jQuery.isWindow( array ) ) { push.call( ret, array ); } else { jQuery.merge( ret, array ); } } return ret; }, inArray: function( elem, array, i ) { var len; if ( array ) { if ( indexOf ) { return indexOf.call( array, elem, i ); } len = array.length; i = i ? i < 0 ? Math.max( 0, len + i ) : i : 0; for ( ; i < len; i++ ) { // Skip accessing in sparse arrays if ( i in array && array[ i ] === elem ) { return i; } } } return -1; }, merge: function( first, second ) { var i = first.length, j = 0; if ( typeof second.length === "number" ) { for ( var l = second.length; j < l; j++ ) { first[ i++ ] = second[ j ]; } } else { while ( second[j] !== undefined ) { first[ i++ ] = second[ j++ ]; } } first.length = i; return first; }, grep: function( elems, callback, inv ) { var ret = [], retVal; inv = !!inv; // Go through the array, only saving the items // that pass the validator function for ( var i = 0, length = elems.length; i < length; i++ ) { retVal = !!callback( elems[ i ], i ); if ( inv !== retVal ) { ret.push( elems[ i ] ); } } return ret; }, // arg is for internal usage only map: function( elems, callback, arg ) { var value, key, ret = [], i = 0, length = elems.length, // jquery objects are treated as arrays isArray = elems instanceof jQuery || length !== undefined && typeof length === "number" && ( ( length > 0 && elems[ 0 ] && elems[ length -1 ] ) || length === 0 || jQuery.isArray( elems ) ) ; // Go through the array, translating each of the items to their if ( isArray ) { for ( ; i < length; i++ ) { value = callback( elems[ i ], i, arg ); if ( value != null ) { ret[ ret.length ] = value; } } // Go through every key on the object, } else { for ( key in elems ) { value = callback( elems[ key ], key, arg ); if ( value != null ) { ret[ ret.length ] = value; } } } // Flatten any nested arrays return ret.concat.apply( [], ret ); }, // A global GUID counter for objects guid: 1, // Bind a function to a context, optionally partially applying any // arguments. proxy: function( fn, context ) { if ( typeof context === "string" ) { var tmp = fn[ context ]; context = fn; fn = tmp; } // Quick check to determine if target is callable, in the spec // this throws a TypeError, but we will just return undefined. if ( !jQuery.isFunction( fn ) ) { return undefined; } // Simulated bind var args = slice.call( arguments, 2 ), proxy = function() { return fn.apply( context, args.concat( slice.call( arguments ) ) ); }; // Set the guid of unique handler to the same of original handler, so it can be removed proxy.guid = fn.guid = fn.guid || proxy.guid || jQuery.guid++; return proxy; }, // Mutifunctional method to get and set values to a collection // The value/s can optionally be executed if it's a function access: function( elems, key, value, exec, fn, pass ) { var length = elems.length; // Setting many attributes if ( typeof key === "object" ) { for ( var k in key ) { jQuery.access( elems, k, key[k], exec, fn, value ); } return elems; } // Setting one attribute if ( value !== undefined ) { // Optionally, function values get executed if exec is true exec = !pass && exec && jQuery.isFunction(value); for ( var i = 0; i < length; i++ ) { fn( elems[i], key, exec ? value.call( elems[i], i, fn( elems[i], key ) ) : value, pass ); } return elems; } // Getting an attribute return length ? fn( elems[0], key ) : undefined; }, now: function() { return ( new Date() ).getTime(); }, // Use of jQuery.browser is frowned upon. // More details: http://docs.jquery.com/Utilities/jQuery.browser uaMatch: function( ua ) { ua = ua.toLowerCase(); var match = rwebkit.exec( ua ) || ropera.exec( ua ) || rmsie.exec( ua ) || ua.indexOf("compatible") < 0 && rmozilla.exec( ua ) || []; return { browser: match[1] || "", version: match[2] || "0" }; }, sub: function() { function jQuerySub( selector, context ) { return new jQuerySub.fn.init( selector, context ); } jQuery.extend( true, jQuerySub, this ); jQuerySub.superclass = this; jQuerySub.fn = jQuerySub.prototype = this(); jQuerySub.fn.constructor = jQuerySub; jQuerySub.sub = this.sub; jQuerySub.fn.init = function init( selector, context ) { if ( context && context instanceof jQuery && !(context instanceof jQuerySub) ) { context = jQuerySub( context ); } return jQuery.fn.init.call( this, selector, context, rootjQuerySub ); }; jQuerySub.fn.init.prototype = jQuerySub.fn; var rootjQuerySub = jQuerySub(document); return jQuerySub; }, browser: {} }); // Populate the class2type map jQuery.each("Boolean Number String Function Array Date RegExp Object".split(" "), function(i, name) { class2type[ "[object " + name + "]" ] = name.toLowerCase(); }); browserMatch = jQuery.uaMatch( userAgent ); if ( browserMatch.browser ) { jQuery.browser[ browserMatch.browser ] = true; jQuery.browser.version = browserMatch.version; } // Deprecated, use jQuery.browser.webkit instead if ( jQuery.browser.webkit ) { jQuery.browser.safari = true; } // IE doesn't match non-breaking spaces with \s if ( rnotwhite.test( "\xA0" ) ) { trimLeft = /^[\s\xA0]+/; trimRight = /[\s\xA0]+$/; } // All jQuery objects should point back to these rootjQuery = jQuery(document); // Cleanup functions for the document ready method if ( document.addEventListener ) { DOMContentLoaded = function() { document.removeEventListener( "DOMContentLoaded", DOMContentLoaded, false ); jQuery.ready(); }; } else if ( document.attachEvent ) { DOMContentLoaded = function() { // Make sure body exists, at least, in case IE gets a little overzealous (ticket #5443). if ( document.readyState === "complete" ) { document.detachEvent( "onreadystatechange", DOMContentLoaded ); jQuery.ready(); } }; } // The DOM ready check for Internet Explorer function doScrollCheck() { if ( jQuery.isReady ) { return; } try { // If IE is used, use the trick by Diego Perini // http://javascript.nwbox.com/IEContentLoaded/ document.documentElement.doScroll("left"); } catch(e) { setTimeout( doScrollCheck, 1 ); return; } // and execute any waiting functions jQuery.ready(); } return jQuery; })(); // String to Object flags format cache var flagsCache = {}; // Convert String-formatted flags into Object-formatted ones and store in cache function createFlags( flags ) { var object = flagsCache[ flags ] = {}, i, length; flags = flags.split( /\s+/ ); for ( i = 0, length = flags.length; i < length; i++ ) { object[ flags[i] ] = true; } return object; } /* * Create a callback list using the following parameters: * * flags: an optional list of space-separated flags that will change how * the callback list behaves * * By default a callback list will act like an event callback list and can be * "fired" multiple times. * * Possible flags: * * once: will ensure the callback list can only be fired once (like a Deferred) * * memory: will keep track of previous values and will call any callback added * after the list has been fired right away with the latest "memorized" * values (like a Deferred) * * unique: will ensure a callback can only be added once (no duplicate in the list) * * stopOnFalse: interrupt callings when a callback returns false * */ jQuery.Callbacks = function( flags ) { // Convert flags from String-formatted to Object-formatted // (we check in cache first) flags = flags ? ( flagsCache[ flags ] || createFlags( flags ) ) : {}; var // Actual callback list list = [], // Stack of fire calls for repeatable lists stack = [], // Last fire value (for non-forgettable lists) memory, // Flag to know if list is currently firing firing, // First callback to fire (used internally by add and fireWith) firingStart, // End of the loop when firing firingLength, // Index of currently firing callback (modified by remove if needed) firingIndex, // Add one or several callbacks to the list add = function( args ) { var i, length, elem, type, actual; for ( i = 0, length = args.length; i < length; i++ ) { elem = args[ i ]; type = jQuery.type( elem ); if ( type === "array" ) { // Inspect recursively add( elem ); } else if ( type === "function" ) { // Add if not in unique mode and callback is not in if ( !flags.unique || !self.has( elem ) ) { list.push( elem ); } } } }, // Fire callbacks fire = function( context, args ) { args = args || []; memory = !flags.memory || [ context, args ]; firing = true; firingIndex = firingStart || 0; firingStart = 0; firingLength = list.length; for ( ; list && firingIndex < firingLength; firingIndex++ ) { if ( list[ firingIndex ].apply( context, args ) === false && flags.stopOnFalse ) { memory = true; // Mark as halted break; } } firing = false; if ( list ) { if ( !flags.once ) { if ( stack && stack.length ) { memory = stack.shift(); self.fireWith( memory[ 0 ], memory[ 1 ] ); } } else if ( memory === true ) { self.disable(); } else { list = []; } } }, // Actual Callbacks object self = { // Add a callback or a collection of callbacks to the list add: function() { if ( list ) { var length = list.length; add( arguments ); // Do we need to add the callbacks to the // current firing batch? if ( firing ) { firingLength = list.length; // With memory, if we're not firing then // we should call right away, unless previous // firing was halted (stopOnFalse) } else if ( memory && memory !== true ) { firingStart = length; fire( memory[ 0 ], memory[ 1 ] ); } } return this; }, // Remove a callback from the list remove: function() { if ( list ) { var args = arguments, argIndex = 0, argLength = args.length; for ( ; argIndex < argLength ; argIndex++ ) { for ( var i = 0; i < list.length; i++ ) { if ( args[ argIndex ] === list[ i ] ) { // Handle firingIndex and firingLength if ( firing ) { if ( i <= firingLength ) { firingLength--; if ( i <= firingIndex ) { firingIndex--; } } } // Remove the element list.splice( i--, 1 ); // If we have some unicity property then // we only need to do this once if ( flags.unique ) { break; } } } } } return this; }, // Control if a given callback is in the list has: function( fn ) { if ( list ) { var i = 0, length = list.length; for ( ; i < length; i++ ) { if ( fn === list[ i ] ) { return true; } } } return false; }, // Remove all callbacks from the list empty: function() { list = []; return this; }, // Have the list do nothing anymore disable: function() { list = stack = memory = undefined; return this; }, // Is it disabled? disabled: function() { return !list; }, // Lock the list in its current state lock: function() { stack = undefined; if ( !memory || memory === true ) { self.disable(); } return this; }, // Is it locked? locked: function() { return !stack; }, // Call all callbacks with the given context and arguments fireWith: function( context, args ) { if ( stack ) { if ( firing ) { if ( !flags.once ) { stack.push( [ context, args ] ); } } else if ( !( flags.once && memory ) ) { fire( context, args ); } } return this; }, // Call all the callbacks with the given arguments fire: function() { self.fireWith( this, arguments ); return this; }, // To know if the callbacks have already been called at least once fired: function() { return !!memory; } }; return self; }; var // Static reference to slice sliceDeferred = [].slice; jQuery.extend({ Deferred: function( func ) { var doneList = jQuery.Callbacks( "once memory" ), failList = jQuery.Callbacks( "once memory" ), progressList = jQuery.Callbacks( "memory" ), state = "pending", lists = { resolve: doneList, reject: failList, notify: progressList }, promise = { done: doneList.add, fail: failList.add, progress: progressList.add, state: function() { return state; }, // Deprecated isResolved: doneList.fired, isRejected: failList.fired, then: function( doneCallbacks, failCallbacks, progressCallbacks ) { deferred.done( doneCallbacks ).fail( failCallbacks ).progress( progressCallbacks ); return this; }, always: function() { deferred.done.apply( deferred, arguments ).fail.apply( deferred, arguments ); return this; }, pipe: function( fnDone, fnFail, fnProgress ) { return jQuery.Deferred(function( newDefer ) { jQuery.each( { done: [ fnDone, "resolve" ], fail: [ fnFail, "reject" ], progress: [ fnProgress, "notify" ] }, function( handler, data ) { var fn = data[ 0 ], action = data[ 1 ], returned; if ( jQuery.isFunction( fn ) ) { deferred[ handler ](function() { returned = fn.apply( this, arguments ); if ( returned && jQuery.isFunction( returned.promise ) ) { returned.promise().then( newDefer.resolve, newDefer.reject, newDefer.notify ); } else { newDefer[ action + "With" ]( this === deferred ? newDefer : this, [ returned ] ); } }); } else { deferred[ handler ]( newDefer[ action ] ); } }); }).promise(); }, // Get a promise for this deferred // If obj is provided, the promise aspect is added to the object promise: function( obj ) { if ( obj == null ) { obj = promise; } else { for ( var key in promise ) { obj[ key ] = promise[ key ]; } } return obj; } }, deferred = promise.promise({}), key; for ( key in lists ) { deferred[ key ] = lists[ key ].fire; deferred[ key + "With" ] = lists[ key ].fireWith; } // Handle state deferred.done( function() { state = "resolved"; }, failList.disable, progressList.lock ).fail( function() { state = "rejected"; }, doneList.disable, progressList.lock ); // Call given func if any if ( func ) { func.call( deferred, deferred ); } // All done! return deferred; }, // Deferred helper when: function( firstParam ) { var args = sliceDeferred.call( arguments, 0 ), i = 0, length = args.length, pValues = new Array( length ), count = length, pCount = length, deferred = length <= 1 && firstParam && jQuery.isFunction( firstParam.promise ) ? firstParam : jQuery.Deferred(), promise = deferred.promise(); function resolveFunc( i ) { return function( value ) { args[ i ] = arguments.length > 1 ? sliceDeferred.call( arguments, 0 ) : value; if ( !( --count ) ) { deferred.resolveWith( deferred, args ); } }; } function progressFunc( i ) { return function( value ) { pValues[ i ] = arguments.length > 1 ? sliceDeferred.call( arguments, 0 ) : value; deferred.notifyWith( promise, pValues ); }; } if ( length > 1 ) { for ( ; i < length; i++ ) { if ( args[ i ] && args[ i ].promise && jQuery.isFunction( args[ i ].promise ) ) { args[ i ].promise().then( resolveFunc(i), deferred.reject, progressFunc(i) ); } else { --count; } } if ( !count ) { deferred.resolveWith( deferred, args ); } } else if ( deferred !== firstParam ) { deferred.resolveWith( deferred, length ? [ firstParam ] : [] ); } return promise; } }); jQuery.support = (function() { var support, all, a, select, opt, input, marginDiv, fragment, tds, events, eventName, i, isSupported, div = document.createElement( "div" ), documentElement = document.documentElement; // Preliminary tests div.setAttribute("className", "t"); div.innerHTML = " <link/><table></table><a href='/a' style='top:1px;float:left;opacity:.55;'>a</a><input type='checkbox'/>"; all = div.getElementsByTagName( "*" ); a = div.getElementsByTagName( "a" )[ 0 ]; // Can't get basic test support if ( !all || !all.length || !a ) { return {}; } // First batch of supports tests select = document.createElement( "select" ); opt = select.appendChild( document.createElement("option") ); input = div.getElementsByTagName( "input" )[ 0 ]; support = { // IE strips leading whitespace when .innerHTML is used leadingWhitespace: ( div.firstChild.nodeType === 3 ), // Make sure that tbody elements aren't automatically inserted // IE will insert them into empty tables tbody: !div.getElementsByTagName("tbody").length, // Make sure that link elements get serialized correctly by innerHTML // This requires a wrapper element in IE htmlSerialize: !!div.getElementsByTagName("link").length, // Get the style information from getAttribute // (IE uses .cssText instead) style: /top/.test( a.getAttribute("style") ), // Make sure that URLs aren't manipulated // (IE normalizes it by default) hrefNormalized: ( a.getAttribute("href") === "/a" ), // Make sure that element opacity exists // (IE uses filter instead) // Use a regex to work around a WebKit issue. See #5145 opacity: /^0.55/.test( a.style.opacity ), // Verify style float existence // (IE uses styleFloat instead of cssFloat) cssFloat: !!a.style.cssFloat, // Make sure that if no value is specified for a checkbox // that it defaults to "on". // (WebKit defaults to "" instead) checkOn: ( input.value === "on" ), // Make sure that a selected-by-default option has a working selected property. // (WebKit defaults to false instead of true, IE too, if it's in an optgroup) optSelected: opt.selected, // Test setAttribute on camelCase class. If it works, we need attrFixes when doing get/setAttribute (ie6/7) getSetAttribute: div.className !== "t", // Tests for enctype support on a form(#6743) enctype: !!document.createElement("form").enctype, // Makes sure cloning an html5 element does not cause problems // Where outerHTML is undefined, this still works html5Clone: document.createElement("nav").cloneNode( true ).outerHTML !== "<:nav></:nav>", // Will be defined later submitBubbles: true, changeBubbles: true, focusinBubbles: false, deleteExpando: true, noCloneEvent: true, inlineBlockNeedsLayout: false, shrinkWrapBlocks: false, reliableMarginRight: true }; // Make sure checked status is properly cloned input.checked = true; support.noCloneChecked = input.cloneNode( true ).checked; // Make sure that the options inside disabled selects aren't marked as disabled // (WebKit marks them as disabled) select.disabled = true; support.optDisabled = !opt.disabled; // Test to see if it's possible to delete an expando from an element // Fails in Internet Explorer try { delete div.test; } catch( e ) { support.deleteExpando = false; } if ( !div.addEventListener && div.attachEvent && div.fireEvent ) { div.attachEvent( "onclick", function() { // Cloning a node shouldn't copy over any // bound event handlers (IE does this) support.noCloneEvent = false; }); div.cloneNode( true ).fireEvent( "onclick" ); } // Check if a radio maintains its value // after being appended to the DOM input = document.createElement("input"); input.value = "t"; input.setAttribute("type", "radio"); support.radioValue = input.value === "t"; input.setAttribute("checked", "checked"); div.appendChild( input ); fragment = document.createDocumentFragment(); fragment.appendChild( div.lastChild ); // WebKit doesn't clone checked state correctly in fragments support.checkClone = fragment.cloneNode( true ).cloneNode( true ).lastChild.checked; // Check if a disconnected checkbox will retain its checked // value of true after appended to the DOM (IE6/7) support.appendChecked = input.checked; fragment.removeChild( input ); fragment.appendChild( div ); div.innerHTML = ""; // Check if div with explicit width and no margin-right incorrectly // gets computed margin-right based on width of container. For more // info see bug #3333 // Fails in WebKit before Feb 2011 nightlies // WebKit Bug 13343 - getComputedStyle returns wrong value for margin-right if ( window.getComputedStyle ) { marginDiv = document.createElement( "div" ); marginDiv.style.width = "0"; marginDiv.style.marginRight = "0"; div.style.width = "2px"; div.appendChild( marginDiv ); support.reliableMarginRight = ( parseInt( ( window.getComputedStyle( marginDiv, null ) || { marginRight: 0 } ).marginRight, 10 ) || 0 ) === 0; } // Technique from Juriy Zaytsev // http://perfectionkills.com/detecting-event-support-without-browser-sniffing/ // We only care about the case where non-standard event systems // are used, namely in IE. Short-circuiting here helps us to // avoid an eval call (in setAttribute) which can cause CSP // to go haywire. See: https://developer.mozilla.org/en/Security/CSP if ( div.attachEvent ) { for( i in { submit: 1, change: 1, focusin: 1 }) { eventName = "on" + i; isSupported = ( eventName in div ); if ( !isSupported ) { div.setAttribute( eventName, "return;" ); isSupported = ( typeof div[ eventName ] === "function" ); } support[ i + "Bubbles" ] = isSupported; } } fragment.removeChild( div ); // Null elements to avoid leaks in IE fragment = select = opt = marginDiv = div = input = null; // Run tests that need a body at doc ready jQuery(function() { var container, outer, inner, table, td, offsetSupport, conMarginTop, ptlm, vb, style, html, body = document.getElementsByTagName("body")[0]; if ( !body ) { // Return for frameset docs that don't have a body return; } conMarginTop = 1; ptlm = "position:absolute;top:0;left:0;width:1px;height:1px;margin:0;"; vb = "visibility:hidden;border:0;"; style = "style='" + ptlm + "border:5px solid #000;padding:0;'"; html = "<div " + style + "><div></div></div>" + "<table " + style + " cellpadding='0' cellspacing='0'>" + "<tr><td></td></tr></table>"; container = document.createElement("div"); container.style.cssText = vb + "width:0;height:0;position:static;top:0;margin-top:" + conMarginTop + "px"; body.insertBefore( container, body.firstChild ); // Construct the test element div = document.createElement("div"); container.appendChild( div ); // Check if table cells still have offsetWidth/Height when they are set // to display:none and there are still other visible table cells in a // table row; if so, offsetWidth/Height are not reliable for use when // determining if an element has been hidden directly using // display:none (it is still safe to use offsets if a parent element is // hidden; don safety goggles and see bug #4512 for more information). // (only IE 8 fails this test) div.innerHTML = "<table><tr><td style='padding:0;border:0;display:none'></td><td>t</td></tr></table>"; tds = div.getElementsByTagName( "td" ); isSupported = ( tds[ 0 ].offsetHeight === 0 ); tds[ 0 ].style.display = ""; tds[ 1 ].style.display = "none"; // Check if empty table cells still have offsetWidth/Height // (IE <= 8 fail this test) support.reliableHiddenOffsets = isSupported && ( tds[ 0 ].offsetHeight === 0 ); // Figure out if the W3C box model works as expected div.innerHTML = ""; div.style.width = div.style.paddingLeft = "1px"; jQuery.boxModel = support.boxModel = div.offsetWidth === 2; if ( typeof div.style.zoom !== "undefined" ) { // Check if natively block-level elements act like inline-block // elements when setting their display to 'inline' and giving // them layout // (IE < 8 does this) div.style.display = "inline"; div.style.zoom = 1; support.inlineBlockNeedsLayout = ( div.offsetWidth === 2 ); // Check if elements with layout shrink-wrap their children // (IE 6 does this) div.style.display = ""; div.innerHTML = "<div style='width:4px;'></div>"; support.shrinkWrapBlocks = ( div.offsetWidth !== 2 ); } div.style.cssText = ptlm + vb; div.innerHTML = html; outer = div.firstChild; inner = outer.firstChild; td = outer.nextSibling.firstChild.firstChild; offsetSupport = { doesNotAddBorder: ( inner.offsetTop !== 5 ), doesAddBorderForTableAndCells: ( td.offsetTop === 5 ) }; inner.style.position = "fixed"; inner.style.top = "20px"; // safari subtracts parent border width here which is 5px offsetSupport.fixedPosition = ( inner.offsetTop === 20 || inner.offsetTop === 15 ); inner.style.position = inner.style.top = ""; outer.style.overflow = "hidden"; outer.style.position = "relative"; offsetSupport.subtractsBorderForOverflowNotVisible = ( inner.offsetTop === -5 ); offsetSupport.doesNotIncludeMarginInBodyOffset = ( body.offsetTop !== conMarginTop ); body.removeChild( container ); div = container = null; jQuery.extend( support, offsetSupport ); }); return support; })(); var rbrace = /^(?:\{.*\}|\[.*\])$/, rmultiDash = /([A-Z])/g; jQuery.extend({ cache: {}, // Please use with caution uuid: 0, // Unique for each copy of jQuery on the page // Non-digits removed to match rinlinejQuery expando: "jQuery" + ( jQuery.fn.jquery + Math.random() ).replace( /\D/g, "" ), // The following elements throw uncatchable exceptions if you // attempt to add expando properties to them. noData: { "embed": true, // Ban all objects except for Flash (which handle expandos) "object": "clsid:D27CDB6E-AE6D-11cf-96B8-444553540000", "applet": true }, hasData: function( elem ) { elem = elem.nodeType ? jQuery.cache[ elem[jQuery.expando] ] : elem[ jQuery.expando ]; return !!elem && !isEmptyDataObject( elem ); }, data: function( elem, name, data, pvt /* Internal Use Only */ ) { if ( !jQuery.acceptData( elem ) ) { return; } var privateCache, thisCache, ret, internalKey = jQuery.expando, getByName = typeof name === "string", // We have to handle DOM nodes and JS objects differently because IE6-7 // can't GC object references properly across the DOM-JS boundary isNode = elem.nodeType, // Only DOM nodes need the global jQuery cache; JS object data is // attached directly to the object so GC can occur automatically cache = isNode ? jQuery.cache : elem, // Only defining an ID for JS objects if its cache already exists allows // the code to shortcut on the same path as a DOM node with no cache id = isNode ? elem[ internalKey ] : elem[ internalKey ] && internalKey, isEvents = name === "events"; // Avoid doing any more work than we need to when trying to get data on an // object that has no data at all if ( (!id || !cache[id] || (!isEvents && !pvt && !cache[id].data)) && getByName && data === undefined ) { return; } if ( !id ) { // Only DOM nodes need a new unique ID for each element since their data // ends up in the global cache if ( isNode ) { elem[ internalKey ] = id = ++jQuery.uuid; } else { id = internalKey; } } if ( !cache[ id ] ) { cache[ id ] = {}; // Avoids exposing jQuery metadata on plain JS objects when the object // is serialized using JSON.stringify if ( !isNode ) { cache[ id ].toJSON = jQuery.noop; } } // An object can be passed to jQuery.data instead of a key/value pair; this gets // shallow copied over onto the existing cache if ( typeof name === "object" || typeof name === "function" ) { if ( pvt ) { cache[ id ] = jQuery.extend( cache[ id ], name ); } else { cache[ id ].data = jQuery.extend( cache[ id ].data, name ); } } privateCache = thisCache = cache[ id ]; // jQuery data() is stored in a separate object inside the object's internal data // cache in order to avoid key collisions between internal data and user-defined // data. if ( !pvt ) { if ( !thisCache.data ) { thisCache.data = {}; } thisCache = thisCache.data; } if ( data !== undefined ) { thisCache[ jQuery.camelCase( name ) ] = data; } // Users should not attempt to inspect the internal events object using jQuery.data, // it is undocumented and subject to change. But does anyone listen? No. if ( isEvents && !thisCache[ name ] ) { return privateCache.events; } // Check for both converted-to-camel and non-converted data property names // If a data property was specified if ( getByName ) { // First Try to find as-is property data ret = thisCache[ name ]; // Test for null|undefined property data if ( ret == null ) { // Try to find the camelCased property ret = thisCache[ jQuery.camelCase( name ) ]; } } else { ret = thisCache; } return ret; }, removeData: function( elem, name, pvt /* Internal Use Only */ ) { if ( !jQuery.acceptData( elem ) ) { return; } var thisCache, i, l, // Reference to internal data cache key internalKey = jQuery.expando, isNode = elem.nodeType, // See jQuery.data for more information cache = isNode ? jQuery.cache : elem, // See jQuery.data for more information id = isNode ? elem[ internalKey ] : internalKey; // If there is already no cache entry for this object, there is no // purpose in continuing if ( !cache[ id ] ) { return; } if ( name ) { thisCache = pvt ? cache[ id ] : cache[ id ].data; if ( thisCache ) { // Support array or space separated string names for data keys if ( !jQuery.isArray( name ) ) { // try the string as a key before any manipulation if ( name in thisCache ) { name = [ name ]; } else { // split the camel cased version by spaces unless a key with the spaces exists name = jQuery.camelCase( name ); if ( name in thisCache ) { name = [ name ]; } else { name = name.split( " " ); } } } for ( i = 0, l = name.length; i < l; i++ ) { delete thisCache[ name[i] ]; } // If there is no data left in the cache, we want to continue // and let the cache object itself get destroyed if ( !( pvt ? isEmptyDataObject : jQuery.isEmptyObject )( thisCache ) ) { return; } } } // See jQuery.data for more information if ( !pvt ) { delete cache[ id ].data; // Don't destroy the parent cache unless the internal data object // had been the only thing left in it if ( !isEmptyDataObject(cache[ id ]) ) { return; } } // Browsers that fail expando deletion also refuse to delete expandos on // the window, but it will allow it on all other JS objects; other browsers // don't care // Ensure that `cache` is not a window object #10080 if ( jQuery.support.deleteExpando || !cache.setInterval ) { delete cache[ id ]; } else { cache[ id ] = null; } // We destroyed the cache and need to eliminate the expando on the node to avoid // false lookups in the cache for entries that no longer exist if ( isNode ) { // IE does not allow us to delete expando properties from nodes, // nor does it have a removeAttribute function on Document nodes; // we must handle all of these cases if ( jQuery.support.deleteExpando ) { delete elem[ internalKey ]; } else if ( elem.removeAttribute ) { elem.removeAttribute( internalKey ); } else { elem[ internalKey ] = null; } } }, // For internal use only. _data: function( elem, name, data ) { return jQuery.data( elem, name, data, true ); }, // A method for determining if a DOM node can handle the data expando acceptData: function( elem ) { if ( elem.nodeName ) { var match = jQuery.noData[ elem.nodeName.toLowerCase() ]; if ( match ) { return !(match === true || elem.getAttribute("classid") !== match); } } return true; } }); jQuery.fn.extend({ data: function( key, value ) { var parts, attr, name, data = null; if ( typeof key === "undefined" ) { if ( this.length ) { data = jQuery.data( this[0] ); if ( this[0].nodeType === 1 && !jQuery._data( this[0], "parsedAttrs" ) ) { attr = this[0].attributes; for ( var i = 0, l = attr.length; i < l; i++ ) { name = attr[i].name; if ( name.indexOf( "data-" ) === 0 ) { name = jQuery.camelCase( name.substring(5) ); dataAttr( this[0], name, data[ name ] ); } } jQuery._data( this[0], "parsedAttrs", true ); } } return data; } else if ( typeof key === "object" ) { return this.each(function() { jQuery.data( this, key ); }); } parts = key.split("."); parts[1] = parts[1] ? "." + parts[1] : ""; if ( value === undefined ) { data = this.triggerHandler("getData" + parts[1] + "!", [parts[0]]); // Try to fetch any internally stored data first if ( data === undefined && this.length ) { data = jQuery.data( this[0], key ); data = dataAttr( this[0], key, data ); } return data === undefined && parts[1] ? this.data( parts[0] ) : data; } else { return this.each(function() { var self = jQuery( this ), args = [ parts[0], value ]; self.triggerHandler( "setData" + parts[1] + "!", args ); jQuery.data( this, key, value ); self.triggerHandler( "changeData" + parts[1] + "!", args ); }); } }, removeData: function( key ) { return this.each(function() { jQuery.removeData( this, key ); }); } }); function dataAttr( elem, key, data ) { // If nothing was found internally, try to fetch any // data from the HTML5 data-* attribute if ( data === undefined && elem.nodeType === 1 ) { var name = "data-" + key.replace( rmultiDash, "-$1" ).toLowerCase(); data = elem.getAttribute( name ); if ( typeof data === "string" ) { try { data = data === "true" ? true : data === "false" ? false : data === "null" ? null : jQuery.isNumeric( data ) ? parseFloat( data ) : rbrace.test( data ) ? jQuery.parseJSON( data ) : data; } catch( e ) {} // Make sure we set the data so it isn't changed later jQuery.data( elem, key, data ); } else { data = undefined; } } return data; } // checks a cache object for emptiness function isEmptyDataObject( obj ) { for ( var name in obj ) { // if the public data object is empty, the private is still empty if ( name === "data" && jQuery.isEmptyObject( obj[name] ) ) { continue; } if ( name !== "toJSON" ) { return false; } } return true; } function handleQueueMarkDefer( elem, type, src ) { var deferDataKey = type + "defer", queueDataKey = type + "queue", markDataKey = type + "mark", defer = jQuery._data( elem, deferDataKey ); if ( defer && ( src === "queue" || !jQuery._data(elem, queueDataKey) ) && ( src === "mark" || !jQuery._data(elem, markDataKey) ) ) { // Give room for hard-coded callbacks to fire first // and eventually mark/queue something else on the element setTimeout( function() { if ( !jQuery._data( elem, queueDataKey ) && !jQuery._data( elem, markDataKey ) ) { jQuery.removeData( elem, deferDataKey, true ); defer.fire(); } }, 0 ); } } jQuery.extend({ _mark: function( elem, type ) { if ( elem ) { type = ( type || "fx" ) + "mark"; jQuery._data( elem, type, (jQuery._data( elem, type ) || 0) + 1 ); } }, _unmark: function( force, elem, type ) { if ( force !== true ) { type = elem; elem = force; force = false; } if ( elem ) { type = type || "fx"; var key = type + "mark", count = force ? 0 : ( (jQuery._data( elem, key ) || 1) - 1 ); if ( count ) { jQuery._data( elem, key, count ); } else { jQuery.removeData( elem, key, true ); handleQueueMarkDefer( elem, type, "mark" ); } } }, queue: function( elem, type, data ) { var q; if ( elem ) { type = ( type || "fx" ) + "queue"; q = jQuery._data( elem, type ); // Speed up dequeue by getting out quickly if this is just a lookup if ( data ) { if ( !q || jQuery.isArray(data) ) { q = jQuery._data( elem, type, jQuery.makeArray(data) ); } else { q.push( data ); } } return q || []; } }, dequeue: function( elem, type ) { type = type || "fx"; var queue = jQuery.queue( elem, type ), fn = queue.shift(), hooks = {}; // If the fx queue is dequeued, always remove the progress sentinel if ( fn === "inprogress" ) { fn = queue.shift(); } if ( fn ) { // Add a progress sentinel to prevent the fx queue from being // automatically dequeued if ( type === "fx" ) { queue.unshift( "inprogress" ); } jQuery._data( elem, type + ".run", hooks ); fn.call( elem, function() { jQuery.dequeue( elem, type ); }, hooks ); } if ( !queue.length ) { jQuery.removeData( elem, type + "queue " + type + ".run", true ); handleQueueMarkDefer( elem, type, "queue" ); } } }); jQuery.fn.extend({ queue: function( type, data ) { if ( typeof type !== "string" ) { data = type; type = "fx"; } if ( data === undefined ) { return jQuery.queue( this[0], type ); } return this.each(function() { var queue = jQuery.queue( this, type, data ); if ( type === "fx" && queue[0] !== "inprogress" ) { jQuery.dequeue( this, type ); } }); }, dequeue: function( type ) { return this.each(function() { jQuery.dequeue( this, type ); }); }, // Based off of the plugin by Clint Helfers, with permission. // http://blindsignals.com/index.php/2009/07/jquery-delay/ delay: function( time, type ) { time = jQuery.fx ? jQuery.fx.speeds[ time ] || time : time; type = type || "fx"; return this.queue( type, function( next, hooks ) { var timeout = setTimeout( next, time ); hooks.stop = function() { clearTimeout( timeout ); }; }); }, clearQueue: function( type ) { return this.queue( type || "fx", [] ); }, // Get a promise resolved when queues of a certain type // are emptied (fx is the type by default) promise: function( type, object ) { if ( typeof type !== "string" ) { object = type; type = undefined; } type = type || "fx"; var defer = jQuery.Deferred(), elements = this, i = elements.length, count = 1, deferDataKey = type + "defer", queueDataKey = type + "queue", markDataKey = type + "mark", tmp; function resolve() { if ( !( --count ) ) { defer.resolveWith( elements, [ elements ] ); } } while( i-- ) { if (( tmp = jQuery.data( elements[ i ], deferDataKey, undefined, true ) || ( jQuery.data( elements[ i ], queueDataKey, undefined, true ) || jQuery.data( elements[ i ], markDataKey, undefined, true ) ) && jQuery.data( elements[ i ], deferDataKey, jQuery.Callbacks( "once memory" ), true ) )) { count++; tmp.add( resolve ); } } resolve(); return defer.promise(); } }); var rclass = /[\n\t\r]/g, rspace = /\s+/, rreturn = /\r/g, rtype = /^(?:button|input)$/i, rfocusable = /^(?:button|input|object|select|textarea)$/i, rclickable = /^a(?:rea)?$/i, rboolean = /^(?:autofocus|autoplay|async|checked|controls|defer|disabled|hidden|loop|multiple|open|readonly|required|scoped|selected)$/i, getSetAttribute = jQuery.support.getSetAttribute, nodeHook, boolHook, fixSpecified; jQuery.fn.extend({ attr: function( name, value ) { return jQuery.access( this, name, value, true, jQuery.attr ); }, removeAttr: function( name ) { return this.each(function() { jQuery.removeAttr( this, name ); }); }, prop: function( name, value ) { return jQuery.access( this, name, value, true, jQuery.prop ); }, removeProp: function( name ) { name = jQuery.propFix[ name ] || name; return this.each(function() { // try/catch handles cases where IE balks (such as removing a property on window) try { this[ name ] = undefined; delete this[ name ]; } catch( e ) {} }); }, addClass: function( value ) { var classNames, i, l, elem, setClass, c, cl; if ( jQuery.isFunction( value ) ) { return this.each(function( j ) { jQuery( this ).addClass( value.call(this, j, this.className) ); }); } if ( value && typeof value === "string" ) { classNames = value.split( rspace ); for ( i = 0, l = this.length; i < l; i++ ) { elem = this[ i ]; if ( elem.nodeType === 1 ) { if ( !elem.className && classNames.length === 1 ) { elem.className = value; } else { setClass = " " + elem.className + " "; for ( c = 0, cl = classNames.length; c < cl; c++ ) { if ( !~setClass.indexOf( " " + classNames[ c ] + " " ) ) { setClass += classNames[ c ] + " "; } } elem.className = jQuery.trim( setClass ); } } } } return this; }, removeClass: function( value ) { var classNames, i, l, elem, className, c, cl; if ( jQuery.isFunction( value ) ) { return this.each(function( j ) { jQuery( this ).removeClass( value.call(this, j, this.className) ); }); } if ( (value && typeof value === "string") || value === undefined ) { classNames = ( value || "" ).split( rspace ); for ( i = 0, l = this.length; i < l; i++ ) { elem = this[ i ]; if ( elem.nodeType === 1 && elem.className ) { if ( value ) { className = (" " + elem.className + " ").replace( rclass, " " ); for ( c = 0, cl = classNames.length; c < cl; c++ ) { className = className.replace(" " + classNames[ c ] + " ", " "); } elem.className = jQuery.trim( className ); } else { elem.className = ""; } } } } return this; }, toggleClass: function( value, stateVal ) { var type = typeof value, isBool = typeof stateVal === "boolean"; if ( jQuery.isFunction( value ) ) { return this.each(function( i ) { jQuery( this ).toggleClass( value.call(this, i, this.className, stateVal), stateVal ); }); } return this.each(function() { if ( type === "string" ) { // toggle individual class names var className, i = 0, self = jQuery( this ), state = stateVal, classNames = value.split( rspace ); while ( (className = classNames[ i++ ]) ) { // check each className given, space separated list state = isBool ? state : !self.hasClass( className ); self[ state ? "addClass" : "removeClass" ]( className ); } } else if ( type === "undefined" || type === "boolean" ) { if ( this.className ) { // store className if set jQuery._data( this, "__className__", this.className ); } // toggle whole className this.className = this.className || value === false ? "" : jQuery._data( this, "__className__" ) || ""; } }); }, hasClass: function( selector ) { var className = " " + selector + " ", i = 0, l = this.length; for ( ; i < l; i++ ) { if ( this[i].nodeType === 1 && (" " + this[i].className + " ").replace(rclass, " ").indexOf( className ) > -1 ) { return true; } } return false; }, val: function( value ) { var hooks, ret, isFunction, elem = this[0]; if ( !arguments.length ) { if ( elem ) { hooks = jQuery.valHooks[ elem.nodeName.toLowerCase() ] || jQuery.valHooks[ elem.type ]; if ( hooks && "get" in hooks && (ret = hooks.get( elem, "value" )) !== undefined ) { return ret; } ret = elem.value; return typeof ret === "string" ? // handle most common string cases ret.replace(rreturn, "") : // handle cases where value is null/undef or number ret == null ? "" : ret; } return; } isFunction = jQuery.isFunction( value ); return this.each(function( i ) { var self = jQuery(this), val; if ( this.nodeType !== 1 ) { return; } if ( isFunction ) { val = value.call( this, i, self.val() ); } else { val = value; } // Treat null/undefined as ""; convert numbers to string if ( val == null ) { val = ""; } else if ( typeof val === "number" ) { val += ""; } else if ( jQuery.isArray( val ) ) { val = jQuery.map(val, function ( value ) { return value == null ? "" : value + ""; }); } hooks = jQuery.valHooks[ this.nodeName.toLowerCase() ] || jQuery.valHooks[ this.type ]; // If set returns undefined, fall back to normal setting if ( !hooks || !("set" in hooks) || hooks.set( this, val, "value" ) === undefined ) { this.value = val; } }); } }); jQuery.extend({ valHooks: { option: { get: function( elem ) { // attributes.value is undefined in Blackberry 4.7 but // uses .value. See #6932 var val = elem.attributes.value; return !val || val.specified ? elem.value : elem.text; } }, select: { get: function( elem ) { var value, i, max, option, index = elem.selectedIndex, values = [], options = elem.options, one = elem.type === "select-one"; // Nothing was selected if ( index < 0 ) { return null; } // Loop through all the selected options i = one ? index : 0; max = one ? index + 1 : options.length; for ( ; i < max; i++ ) { option = options[ i ]; // Don't return options that are disabled or in a disabled optgroup if ( option.selected && (jQuery.support.optDisabled ? !option.disabled : option.getAttribute("disabled") === null) && (!option.parentNode.disabled || !jQuery.nodeName( option.parentNode, "optgroup" )) ) { // Get the specific value for the option value = jQuery( option ).val(); // We don't need an array for one selects if ( one ) { return value; } // Multi-Selects return an array values.push( value ); } } // Fixes Bug #2551 -- select.val() broken in IE after form.reset() if ( one && !values.length && options.length ) { return jQuery( options[ index ] ).val(); } return values; }, set: function( elem, value ) { var values = jQuery.makeArray( value ); jQuery(elem).find("option").each(function() { this.selected = jQuery.inArray( jQuery(this).val(), values ) >= 0; }); if ( !values.length ) { elem.selectedIndex = -1; } return values; } } }, attrFn: { val: true, css: true, html: true, text: true, data: true, width: true, height: true, offset: true }, attr: function( elem, name, value, pass ) { var ret, hooks, notxml, nType = elem.nodeType; // don't get/set attributes on text, comment and attribute nodes if ( !elem || nType === 3 || nType === 8 || nType === 2 ) { return; } if ( pass && name in jQuery.attrFn ) { return jQuery( elem )[ name ]( value ); } // Fallback to prop when attributes are not supported if ( typeof elem.getAttribute === "undefined" ) { return jQuery.prop( elem, name, value ); } notxml = nType !== 1 || !jQuery.isXMLDoc( elem ); // All attributes are lowercase // Grab necessary hook if one is defined if ( notxml ) { name = name.toLowerCase(); hooks = jQuery.attrHooks[ name ] || ( rboolean.test( name ) ? boolHook : nodeHook ); } if ( value !== undefined ) { if ( value === null ) { jQuery.removeAttr( elem, name ); return; } else if ( hooks && "set" in hooks && notxml && (ret = hooks.set( elem, value, name )) !== undefined ) { return ret; } else { elem.setAttribute( name, "" + value ); return value; } } else if ( hooks && "get" in hooks && notxml && (ret = hooks.get( elem, name )) !== null ) { return ret; } else { ret = elem.getAttribute( name ); // Non-existent attributes return null, we normalize to undefined return ret === null ? undefined : ret; } }, removeAttr: function( elem, value ) { var propName, attrNames, name, l, i = 0; if ( value && elem.nodeType === 1 ) { attrNames = value.toLowerCase().split( rspace ); l = attrNames.length; for ( ; i < l; i++ ) { name = attrNames[ i ]; if ( name ) { propName = jQuery.propFix[ name ] || name; // See #9699 for explanation of this approach (setting first, then removal) jQuery.attr( elem, name, "" ); elem.removeAttribute( getSetAttribute ? name : propName ); // Set corresponding property to false for boolean attributes if ( rboolean.test( name ) && propName in elem ) { elem[ propName ] = false; } } } } }, attrHooks: { type: { set: function( elem, value ) { // We can't allow the type property to be changed (since it causes problems in IE) if ( rtype.test( elem.nodeName ) && elem.parentNode ) { jQuery.error( "type property can't be changed" ); } else if ( !jQuery.support.radioValue && value === "radio" && jQuery.nodeName(elem, "input") ) { // Setting the type on a radio button after the value resets the value in IE6-9 // Reset value to it's default in case type is set after value // This is for element creation var val = elem.value; elem.setAttribute( "type", value ); if ( val ) { elem.value = val; } return value; } } }, // Use the value property for back compat // Use the nodeHook for button elements in IE6/7 (#1954) value: { get: function( elem, name ) { if ( nodeHook && jQuery.nodeName( elem, "button" ) ) { return nodeHook.get( elem, name ); } return name in elem ? elem.value : null; }, set: function( elem, value, name ) { if ( nodeHook && jQuery.nodeName( elem, "button" ) ) { return nodeHook.set( elem, value, name ); } // Does not return so that setAttribute is also used elem.value = value; } } }, propFix: { tabindex: "tabIndex", readonly: "readOnly", "for": "htmlFor", "class": "className", maxlength: "maxLength", cellspacing: "cellSpacing", cellpadding: "cellPadding", rowspan: "rowSpan", colspan: "colSpan", usemap: "useMap", frameborder: "frameBorder", contenteditable: "contentEditable" }, prop: function( elem, name, value ) { var ret, hooks, notxml, nType = elem.nodeType; // don't get/set properties on text, comment and attribute nodes if ( !elem || nType === 3 || nType === 8 || nType === 2 ) { return; } notxml = nType !== 1 || !jQuery.isXMLDoc( elem ); if ( notxml ) { // Fix name and attach hooks name = jQuery.propFix[ name ] || name; hooks = jQuery.propHooks[ name ]; } if ( value !== undefined ) { if ( hooks && "set" in hooks && (ret = hooks.set( elem, value, name )) !== undefined ) { return ret; } else { return ( elem[ name ] = value ); } } else { if ( hooks && "get" in hooks && (ret = hooks.get( elem, name )) !== null ) { return ret; } else { return elem[ name ]; } } }, propHooks: { tabIndex: { get: function( elem ) { // elem.tabIndex doesn't always return the correct value when it hasn't been explicitly set // http://fluidproject.org/blog/2008/01/09/getting-setting-and-removing-tabindex-values-with-javascript/ var attributeNode = elem.getAttributeNode("tabindex"); return attributeNode && attributeNode.specified ? parseInt( attributeNode.value, 10 ) : rfocusable.test( elem.nodeName ) || rclickable.test( elem.nodeName ) && elem.href ? 0 : undefined; } } } }); // Add the tabIndex propHook to attrHooks for back-compat (different case is intentional) jQuery.attrHooks.tabindex = jQuery.propHooks.tabIndex; // Hook for boolean attributes boolHook = { get: function( elem, name ) { // Align boolean attributes with corresponding properties // Fall back to attribute presence where some booleans are not supported var attrNode, property = jQuery.prop( elem, name ); return property === true || typeof property !== "boolean" && ( attrNode = elem.getAttributeNode(name) ) && attrNode.nodeValue !== false ? name.toLowerCase() : undefined; }, set: function( elem, value, name ) { var propName; if ( value === false ) { // Remove boolean attributes when set to false jQuery.removeAttr( elem, name ); } else { // value is true since we know at this point it's type boolean and not false // Set boolean attributes to the same name and set the DOM property propName = jQuery.propFix[ name ] || name; if ( propName in elem ) { // Only set the IDL specifically if it already exists on the element elem[ propName ] = true; } elem.setAttribute( name, name.toLowerCase() ); } return name; } }; // IE6/7 do not support getting/setting some attributes with get/setAttribute if ( !getSetAttribute ) { fixSpecified = { name: true, id: true }; // Use this for any attribute in IE6/7 // This fixes almost every IE6/7 issue nodeHook = jQuery.valHooks.button = { get: function( elem, name ) { var ret; ret = elem.getAttributeNode( name ); return ret && ( fixSpecified[ name ] ? ret.nodeValue !== "" : ret.specified ) ? ret.nodeValue : undefined; }, set: function( elem, value, name ) { // Set the existing or create a new attribute node var ret = elem.getAttributeNode( name ); if ( !ret ) { ret = document.createAttribute( name ); elem.setAttributeNode( ret ); } return ( ret.nodeValue = value + "" ); } }; // Apply the nodeHook to tabindex jQuery.attrHooks.tabindex.set = nodeHook.set; // Set width and height to auto instead of 0 on empty string( Bug #8150 ) // This is for removals jQuery.each([ "width", "height" ], function( i, name ) { jQuery.attrHooks[ name ] = jQuery.extend( jQuery.attrHooks[ name ], { set: function( elem, value ) { if ( value === "" ) { elem.setAttribute( name, "auto" ); return value; } } }); }); // Set contenteditable to false on removals(#10429) // Setting to empty string throws an error as an invalid value jQuery.attrHooks.contenteditable = { get: nodeHook.get, set: function( elem, value, name ) { if ( value === "" ) { value = "false"; } nodeHook.set( elem, value, name ); } }; } // Some attributes require a special call on IE if ( !jQuery.support.hrefNormalized ) { jQuery.each([ "href", "src", "width", "height" ], function( i, name ) { jQuery.attrHooks[ name ] = jQuery.extend( jQuery.attrHooks[ name ], { get: function( elem ) { var ret = elem.getAttribute( name, 2 ); return ret === null ? undefined : ret; } }); }); } if ( !jQuery.support.style ) { jQuery.attrHooks.style = { get: function( elem ) { // Return undefined in the case of empty string // Normalize to lowercase since IE uppercases css property names return elem.style.cssText.toLowerCase() || undefined; }, set: function( elem, value ) { return ( elem.style.cssText = "" + value ); } }; } // Safari mis-reports the default selected property of an option // Accessing the parent's selectedIndex property fixes it if ( !jQuery.support.optSelected ) { jQuery.propHooks.selected = jQuery.extend( jQuery.propHooks.selected, { get: function( elem ) { var parent = elem.parentNode; if ( parent ) { parent.selectedIndex; // Make sure that it also works with optgroups, see #5701 if ( parent.parentNode ) { parent.parentNode.selectedIndex; } } return null; } }); } // IE6/7 call enctype encoding if ( !jQuery.support.enctype ) { jQuery.propFix.enctype = "encoding"; } // Radios and checkboxes getter/setter if ( !jQuery.support.checkOn ) { jQuery.each([ "radio", "checkbox" ], function() { jQuery.valHooks[ this ] = { get: function( elem ) { // Handle the case where in Webkit "" is returned instead of "on" if a value isn't specified return elem.getAttribute("value") === null ? "on" : elem.value; } }; }); } jQuery.each([ "radio", "checkbox" ], function() { jQuery.valHooks[ this ] = jQuery.extend( jQuery.valHooks[ this ], { set: function( elem, value ) { if ( jQuery.isArray( value ) ) { return ( elem.checked = jQuery.inArray( jQuery(elem).val(), value ) >= 0 ); } } }); }); var rformElems = /^(?:textarea|input|select)$/i, rtypenamespace = /^([^\.]*)?(?:\.(.+))?$/, rhoverHack = /\bhover(\.\S+)?\b/, rkeyEvent = /^key/, rmouseEvent = /^(?:mouse|contextmenu)|click/, rfocusMorph = /^(?:focusinfocus|focusoutblur)$/, rquickIs = /^(\w*)(?:#([\w\-]+))?(?:\.([\w\-]+))?$/, quickParse = function( selector ) { var quick = rquickIs.exec( selector ); if ( quick ) { // 0 1 2 3 // [ _, tag, id, class ] quick[1] = ( quick[1] || "" ).toLowerCase(); quick[3] = quick[3] && new RegExp( "(?:^|\\s)" + quick[3] + "(?:\\s|$)" ); } return quick; }, quickIs = function( elem, m ) { var attrs = elem.attributes || {}; return ( (!m[1] || elem.nodeName.toLowerCase() === m[1]) && (!m[2] || (attrs.id || {}).value === m[2]) && (!m[3] || m[3].test( (attrs[ "class" ] || {}).value )) ); }, hoverHack = function( events ) { return jQuery.event.special.hover ? events : events.replace( rhoverHack, "mouseenter$1 mouseleave$1" ); }; /* * Helper functions for managing events -- not part of the public interface. * Props to Dean Edwards' addEvent library for many of the ideas. */ jQuery.event = { add: function( elem, types, handler, data, selector ) { var elemData, eventHandle, events, t, tns, type, namespaces, handleObj, handleObjIn, quick, handlers, special; // Don't attach events to noData or text/comment nodes (allow plain objects tho) if ( elem.nodeType === 3 || elem.nodeType === 8 || !types || !handler || !(elemData = jQuery._data( elem )) ) { return; } // Caller can pass in an object of custom data in lieu of the handler if ( handler.handler ) { handleObjIn = handler; handler = handleObjIn.handler; } // Make sure that the handler has a unique ID, used to find/remove it later if ( !handler.guid ) { handler.guid = jQuery.guid++; } // Init the element's event structure and main handler, if this is the first events = elemData.events; if ( !events ) { elemData.events = events = {}; } eventHandle = elemData.handle; if ( !eventHandle ) { elemData.handle = eventHandle = function( e ) { // Discard the second event of a jQuery.event.trigger() and // when an event is called after a page has unloaded return typeof jQuery !== "undefined" && (!e || jQuery.event.triggered !== e.type) ? jQuery.event.dispatch.apply( eventHandle.elem, arguments ) : undefined; }; // Add elem as a property of the handle fn to prevent a memory leak with IE non-native events eventHandle.elem = elem; } // Handle multiple events separated by a space // jQuery(...).bind("mouseover mouseout", fn); types = jQuery.trim( hoverHack(types) ).split( " " ); for ( t = 0; t < types.length; t++ ) { tns = rtypenamespace.exec( types[t] ) || []; type = tns[1]; namespaces = ( tns[2] || "" ).split( "." ).sort(); // If event changes its type, use the special event handlers for the changed type special = jQuery.event.special[ type ] || {}; // If selector defined, determine special event api type, otherwise given type type = ( selector ? special.delegateType : special.bindType ) || type; // Update special based on newly reset type special = jQuery.event.special[ type ] || {}; // handleObj is passed to all event handlers handleObj = jQuery.extend({ type: type, origType: tns[1], data: data, handler: handler, guid: handler.guid, selector: selector, quick: quickParse( selector ), namespace: namespaces.join(".") }, handleObjIn ); // Init the event handler queue if we're the first handlers = events[ type ]; if ( !handlers ) { handlers = events[ type ] = []; handlers.delegateCount = 0; // Only use addEventListener/attachEvent if the special events handler returns false if ( !special.setup || special.setup.call( elem, data, namespaces, eventHandle ) === false ) { // Bind the global event handler to the element if ( elem.addEventListener ) { elem.addEventListener( type, eventHandle, false ); } else if ( elem.attachEvent ) { elem.attachEvent( "on" + type, eventHandle ); } } } if ( special.add ) { special.add.call( elem, handleObj ); if ( !handleObj.handler.guid ) { handleObj.handler.guid = handler.guid; } } // Add to the element's handler list, delegates in front if ( selector ) { handlers.splice( handlers.delegateCount++, 0, handleObj ); } else { handlers.push( handleObj ); } // Keep track of which events have ever been used, for event optimization jQuery.event.global[ type ] = true; } // Nullify elem to prevent memory leaks in IE elem = null; }, global: {}, // Detach an event or set of events from an element remove: function( elem, types, handler, selector, mappedTypes ) { var elemData = jQuery.hasData( elem ) && jQuery._data( elem ), t, tns, type, origType, namespaces, origCount, j, events, special, handle, eventType, handleObj; if ( !elemData || !(events = elemData.events) ) { return; } // Once for each type.namespace in types; type may be omitted types = jQuery.trim( hoverHack( types || "" ) ).split(" "); for ( t = 0; t < types.length; t++ ) { tns = rtypenamespace.exec( types[t] ) || []; type = origType = tns[1]; namespaces = tns[2]; // Unbind all events (on this namespace, if provided) for the element if ( !type ) { for ( type in events ) { jQuery.event.remove( elem, type + types[ t ], handler, selector, true ); } continue; } special = jQuery.event.special[ type ] || {}; type = ( selector? special.delegateType : special.bindType ) || type; eventType = events[ type ] || []; origCount = eventType.length; namespaces = namespaces ? new RegExp("(^|\\.)" + namespaces.split(".").sort().join("\\.(?:.*\\.)?") + "(\\.|$)") : null; // Remove matching events for ( j = 0; j < eventType.length; j++ ) { handleObj = eventType[ j ]; if ( ( mappedTypes || origType === handleObj.origType ) && ( !handler || handler.guid === handleObj.guid ) && ( !namespaces || namespaces.test( handleObj.namespace ) ) && ( !selector || selector === handleObj.selector || selector === "**" && handleObj.selector ) ) { eventType.splice( j--, 1 ); if ( handleObj.selector ) { eventType.delegateCount--; } if ( special.remove ) { special.remove.call( elem, handleObj ); } } } // Remove generic event handler if we removed something and no more handlers exist // (avoids potential for endless recursion during removal of special event handlers) if ( eventType.length === 0 && origCount !== eventType.length ) { if ( !special.teardown || special.teardown.call( elem, namespaces ) === false ) { jQuery.removeEvent( elem, type, elemData.handle ); } delete events[ type ]; } } // Remove the expando if it's no longer used if ( jQuery.isEmptyObject( events ) ) { handle = elemData.handle; if ( handle ) { handle.elem = null; } // removeData also checks for emptiness and clears the expando if empty // so use it instead of delete jQuery.removeData( elem, [ "events", "handle" ], true ); } }, // Events that are safe to short-circuit if no handlers are attached. // Native DOM events should not be added, they may have inline handlers. customEvent: { "getData": true, "setData": true, "changeData": true }, trigger: function( event, data, elem, onlyHandlers ) { // Don't do events on text and comment nodes if ( elem && (elem.nodeType === 3 || elem.nodeType === 8) ) { return; } // Event object or event type var type = event.type || event, namespaces = [], cache, exclusive, i, cur, old, ontype, special, handle, eventPath, bubbleType; // focus/blur morphs to focusin/out; ensure we're not firing them right now if ( rfocusMorph.test( type + jQuery.event.triggered ) ) { return; } if ( type.indexOf( "!" ) >= 0 ) { // Exclusive events trigger only for the exact event (no namespaces) type = type.slice(0, -1); exclusive = true; } if ( type.indexOf( "." ) >= 0 ) { // Namespaced trigger; create a regexp to match event type in handle() namespaces = type.split("."); type = namespaces.shift(); namespaces.sort(); } if ( (!elem || jQuery.event.customEvent[ type ]) && !jQuery.event.global[ type ] ) { // No jQuery handlers for this event type, and it can't have inline handlers return; } // Caller can pass in an Event, Object, or just an event type string event = typeof event === "object" ? // jQuery.Event object event[ jQuery.expando ] ? event : // Object literal new jQuery.Event( type, event ) : // Just the event type (string) new jQuery.Event( type ); event.type = type; event.isTrigger = true; event.exclusive = exclusive; event.namespace = namespaces.join( "." ); event.namespace_re = event.namespace? new RegExp("(^|\\.)" + namespaces.join("\\.(?:.*\\.)?") + "(\\.|$)") : null; ontype = type.indexOf( ":" ) < 0 ? "on" + type : ""; // Handle a global trigger if ( !elem ) { // TODO: Stop taunting the data cache; remove global events and always attach to document cache = jQuery.cache; for ( i in cache ) { if ( cache[ i ].events && cache[ i ].events[ type ] ) { jQuery.event.trigger( event, data, cache[ i ].handle.elem, true ); } } return; } // Clean up the event in case it is being reused event.result = undefined; if ( !event.target ) { event.target = elem; } // Clone any incoming data and prepend the event, creating the handler arg list data = data != null ? jQuery.makeArray( data ) : []; data.unshift( event ); // Allow special events to draw outside the lines special = jQuery.event.special[ type ] || {}; if ( special.trigger && special.trigger.apply( elem, data ) === false ) { return; } // Determine event propagation path in advance, per W3C events spec (#9951) // Bubble up to document, then to window; watch for a global ownerDocument var (#9724) eventPath = [[ elem, special.bindType || type ]]; if ( !onlyHandlers && !special.noBubble && !jQuery.isWindow( elem ) ) { bubbleType = special.delegateType || type; cur = rfocusMorph.test( bubbleType + type ) ? elem : elem.parentNode; old = null; for ( ; cur; cur = cur.parentNode ) { eventPath.push([ cur, bubbleType ]); old = cur; } // Only add window if we got to document (e.g., not plain obj or detached DOM) if ( old && old === elem.ownerDocument ) { eventPath.push([ old.defaultView || old.parentWindow || window, bubbleType ]); } } // Fire handlers on the event path for ( i = 0; i < eventPath.length && !event.isPropagationStopped(); i++ ) { cur = eventPath[i][0]; event.type = eventPath[i][1]; handle = ( jQuery._data( cur, "events" ) || {} )[ event.type ] && jQuery._data( cur, "handle" ); if ( handle ) { handle.apply( cur, data ); } // Note that this is a bare JS function and not a jQuery handler handle = ontype && cur[ ontype ]; if ( handle && jQuery.acceptData( cur ) && handle.apply( cur, data ) === false ) { event.preventDefault(); } } event.type = type; // If nobody prevented the default action, do it now if ( !onlyHandlers && !event.isDefaultPrevented() ) { if ( (!special._default || special._default.apply( elem.ownerDocument, data ) === false) && !(type === "click" && jQuery.nodeName( elem, "a" )) && jQuery.acceptData( elem ) ) { // Call a native DOM method on the target with the same name name as the event. // Can't use an .isFunction() check here because IE6/7 fails that test. // Don't do default actions on window, that's where global variables be (#6170) // IE<9 dies on focus/blur to hidden element (#1486) if ( ontype && elem[ type ] && ((type !== "focus" && type !== "blur") || event.target.offsetWidth !== 0) && !jQuery.isWindow( elem ) ) { // Don't re-trigger an onFOO event when we call its FOO() method old = elem[ ontype ]; if ( old ) { elem[ ontype ] = null; } // Prevent re-triggering of the same event, since we already bubbled it above jQuery.event.triggered = type; elem[ type ](); jQuery.event.triggered = undefined; if ( old ) { elem[ ontype ] = old; } } } } return event.result; }, dispatch: function( event ) { // Make a writable jQuery.Event from the native event object event = jQuery.event.fix( event || window.event ); var handlers = ( (jQuery._data( this, "events" ) || {} )[ event.type ] || []), delegateCount = handlers.delegateCount, args = [].slice.call( arguments, 0 ), run_all = !event.exclusive && !event.namespace, handlerQueue = [], i, j, cur, jqcur, ret, selMatch, matched, matches, handleObj, sel, related; // Use the fix-ed jQuery.Event rather than the (read-only) native event args[0] = event; event.delegateTarget = this; // Determine handlers that should run if there are delegated events // Avoid disabled elements in IE (#6911) and non-left-click bubbling in Firefox (#3861) if ( delegateCount && !event.target.disabled && !(event.button && event.type === "click") ) { // Pregenerate a single jQuery object for reuse with .is() jqcur = jQuery(this); jqcur.context = this.ownerDocument || this; for ( cur = event.target; cur != this; cur = cur.parentNode || this ) { selMatch = {}; matches = []; jqcur[0] = cur; for ( i = 0; i < delegateCount; i++ ) { handleObj = handlers[ i ]; sel = handleObj.selector; if ( selMatch[ sel ] === undefined ) { selMatch[ sel ] = ( handleObj.quick ? quickIs( cur, handleObj.quick ) : jqcur.is( sel ) ); } if ( selMatch[ sel ] ) { matches.push( handleObj ); } } if ( matches.length ) { handlerQueue.push({ elem: cur, matches: matches }); } } } // Add the remaining (directly-bound) handlers if ( handlers.length > delegateCount ) { handlerQueue.push({ elem: this, matches: handlers.slice( delegateCount ) }); } // Run delegates first; they may want to stop propagation beneath us for ( i = 0; i < handlerQueue.length && !event.isPropagationStopped(); i++ ) { matched = handlerQueue[ i ]; event.currentTarget = matched.elem; for ( j = 0; j < matched.matches.length && !event.isImmediatePropagationStopped(); j++ ) { handleObj = matched.matches[ j ]; // Triggered event must either 1) be non-exclusive and have no namespace, or // 2) have namespace(s) a subset or equal to those in the bound event (both can have no namespace). if ( run_all || (!event.namespace && !handleObj.namespace) || event.namespace_re && event.namespace_re.test( handleObj.namespace ) ) { event.data = handleObj.data; event.handleObj = handleObj; ret = ( (jQuery.event.special[ handleObj.origType ] || {}).handle || handleObj.handler ) .apply( matched.elem, args ); if ( ret !== undefined ) { event.result = ret; if ( ret === false ) { event.preventDefault(); event.stopPropagation(); } } } } } return event.result; }, // Includes some event props shared by KeyEvent and MouseEvent // *** attrChange attrName relatedNode srcElement are not normalized, non-W3C, deprecated, will be removed in 1.8 *** props: "attrChange attrName relatedNode srcElement altKey bubbles cancelable ctrlKey currentTarget eventPhase metaKey relatedTarget shiftKey target timeStamp view which".split(" "), fixHooks: {}, keyHooks: { props: "char charCode key keyCode".split(" "), filter: function( event, original ) { // Add which for key events if ( event.which == null ) { event.which = original.charCode != null ? original.charCode : original.keyCode; } return event; } }, mouseHooks: { props: "button buttons clientX clientY fromElement offsetX offsetY pageX pageY screenX screenY toElement".split(" "), filter: function( event, original ) { var eventDoc, doc, body, button = original.button, fromElement = original.fromElement; // Calculate pageX/Y if missing and clientX/Y available if ( event.pageX == null && original.clientX != null ) { eventDoc = event.target.ownerDocument || document; doc = eventDoc.documentElement; body = eventDoc.body; event.pageX = original.clientX + ( doc && doc.scrollLeft || body && body.scrollLeft || 0 ) - ( doc && doc.clientLeft || body && body.clientLeft || 0 ); event.pageY = original.clientY + ( doc && doc.scrollTop || body && body.scrollTop || 0 ) - ( doc && doc.clientTop || body && body.clientTop || 0 ); } // Add relatedTarget, if necessary if ( !event.relatedTarget && fromElement ) { event.relatedTarget = fromElement === event.target ? original.toElement : fromElement; } // Add which for click: 1 === left; 2 === middle; 3 === right // Note: button is not normalized, so don't use it if ( !event.which && button !== undefined ) { event.which = ( button & 1 ? 1 : ( button & 2 ? 3 : ( button & 4 ? 2 : 0 ) ) ); } return event; } }, fix: function( event ) { if ( event[ jQuery.expando ] ) { return event; } // Create a writable copy of the event object and normalize some properties var i, prop, originalEvent = event, fixHook = jQuery.event.fixHooks[ event.type ] || {}, copy = fixHook.props ? this.props.concat( fixHook.props ) : this.props; event = jQuery.Event( originalEvent ); for ( i = copy.length; i; ) { prop = copy[ --i ]; event[ prop ] = originalEvent[ prop ]; } // Fix target property, if necessary (#1925, IE 6/7/8 & Safari2) if ( !event.target ) { event.target = originalEvent.srcElement || document; } // Target should not be a text node (#504, Safari) if ( event.target.nodeType === 3 ) { event.target = event.target.parentNode; } // For mouse/key events; add metaKey if it's not there (#3368, IE6/7/8) if ( event.metaKey === undefined ) { event.metaKey = event.ctrlKey; } return fixHook.filter? fixHook.filter( event, originalEvent ) : event; }, special: { ready: { // Make sure the ready event is setup setup: jQuery.bindReady }, load: { // Prevent triggered image.load events from bubbling to window.load noBubble: true }, focus: { delegateType: "focusin" }, blur: { delegateType: "focusout" }, beforeunload: { setup: function( data, namespaces, eventHandle ) { // We only want to do this special case on windows if ( jQuery.isWindow( this ) ) { this.onbeforeunload = eventHandle; } }, teardown: function( namespaces, eventHandle ) { if ( this.onbeforeunload === eventHandle ) { this.onbeforeunload = null; } } } }, simulate: function( type, elem, event, bubble ) { // Piggyback on a donor event to simulate a different one. // Fake originalEvent to avoid donor's stopPropagation, but if the // simulated event prevents default then we do the same on the donor. var e = jQuery.extend( new jQuery.Event(), event, { type: type, isSimulated: true, originalEvent: {} } ); if ( bubble ) { jQuery.event.trigger( e, null, elem ); } else { jQuery.event.dispatch.call( elem, e ); } if ( e.isDefaultPrevented() ) { event.preventDefault(); } } }; // Some plugins are using, but it's undocumented/deprecated and will be removed. // The 1.7 special event interface should provide all the hooks needed now. jQuery.event.handle = jQuery.event.dispatch; jQuery.removeEvent = document.removeEventListener ? function( elem, type, handle ) { if ( elem.removeEventListener ) { elem.removeEventListener( type, handle, false ); } } : function( elem, type, handle ) { if ( elem.detachEvent ) { elem.detachEvent( "on" + type, handle ); } }; jQuery.Event = function( src, props ) { // Allow instantiation without the 'new' keyword if ( !(this instanceof jQuery.Event) ) { return new jQuery.Event( src, props ); } // Event object if ( src && src.type ) { this.originalEvent = src; this.type = src.type; // Events bubbling up the document may have been marked as prevented // by a handler lower down the tree; reflect the correct value. this.isDefaultPrevented = ( src.defaultPrevented || src.returnValue === false || src.getPreventDefault && src.getPreventDefault() ) ? returnTrue : returnFalse; // Event type } else { this.type = src; } // Put explicitly provided properties onto the event object if ( props ) { jQuery.extend( this, props ); } // Create a timestamp if incoming event doesn't have one this.timeStamp = src && src.timeStamp || jQuery.now(); // Mark it as fixed this[ jQuery.expando ] = true; }; function returnFalse() { return false; } function returnTrue() { return true; } // jQuery.Event is based on DOM3 Events as specified by the ECMAScript Language Binding // http://www.w3.org/TR/2003/WD-DOM-Level-3-Events-20030331/ecma-script-binding.html jQuery.Event.prototype = { preventDefault: function() { this.isDefaultPrevented = returnTrue; var e = this.originalEvent; if ( !e ) { return; } // if preventDefault exists run it on the original event if ( e.preventDefault ) { e.preventDefault(); // otherwise set the returnValue property of the original event to false (IE) } else { e.returnValue = false; } }, stopPropagation: function() { this.isPropagationStopped = returnTrue; var e = this.originalEvent; if ( !e ) { return; } // if stopPropagation exists run it on the original event if ( e.stopPropagation ) { e.stopPropagation(); } // otherwise set the cancelBubble property of the original event to true (IE) e.cancelBubble = true; }, stopImmediatePropagation: function() { this.isImmediatePropagationStopped = returnTrue; this.stopPropagation(); }, isDefaultPrevented: returnFalse, isPropagationStopped: returnFalse, isImmediatePropagationStopped: returnFalse }; // Create mouseenter/leave events using mouseover/out and event-time checks jQuery.each({ mouseenter: "mouseover", mouseleave: "mouseout" }, function( orig, fix ) { jQuery.event.special[ orig ] = { delegateType: fix, bindType: fix, handle: function( event ) { var target = this, related = event.relatedTarget, handleObj = event.handleObj, selector = handleObj.selector, ret; // For mousenter/leave call the handler if related is outside the target. // NB: No relatedTarget if the mouse left/entered the browser window if ( !related || (related !== target && !jQuery.contains( target, related )) ) { event.type = handleObj.origType; ret = handleObj.handler.apply( this, arguments ); event.type = fix; } return ret; } }; }); // IE submit delegation if ( !jQuery.support.submitBubbles ) { jQuery.event.special.submit = { setup: function() { // Only need this for delegated form submit events if ( jQuery.nodeName( this, "form" ) ) { return false; } // Lazy-add a submit handler when a descendant form may potentially be submitted jQuery.event.add( this, "click._submit keypress._submit", function( e ) { // Node name check avoids a VML-related crash in IE (#9807) var elem = e.target, form = jQuery.nodeName( elem, "input" ) || jQuery.nodeName( elem, "button" ) ? elem.form : undefined; if ( form && !form._submit_attached ) { jQuery.event.add( form, "submit._submit", function( event ) { // If form was submitted by the user, bubble the event up the tree if ( this.parentNode && !event.isTrigger ) { jQuery.event.simulate( "submit", this.parentNode, event, true ); } }); form._submit_attached = true; } }); // return undefined since we don't need an event listener }, teardown: function() { // Only need this for delegated form submit events if ( jQuery.nodeName( this, "form" ) ) { return false; } // Remove delegated handlers; cleanData eventually reaps submit handlers attached above jQuery.event.remove( this, "._submit" ); } }; } // IE change delegation and checkbox/radio fix if ( !jQuery.support.changeBubbles ) { jQuery.event.special.change = { setup: function() { if ( rformElems.test( this.nodeName ) ) { // IE doesn't fire change on a check/radio until blur; trigger it on click // after a propertychange. Eat the blur-change in special.change.handle. // This still fires onchange a second time for check/radio after blur. if ( this.type === "checkbox" || this.type === "radio" ) { jQuery.event.add( this, "propertychange._change", function( event ) { if ( event.originalEvent.propertyName === "checked" ) { this._just_changed = true; } }); jQuery.event.add( this, "click._change", function( event ) { if ( this._just_changed && !event.isTrigger ) { this._just_changed = false; jQuery.event.simulate( "change", this, event, true ); } }); } return false; } // Delegated event; lazy-add a change handler on descendant inputs jQuery.event.add( this, "beforeactivate._change", function( e ) { var elem = e.target; if ( rformElems.test( elem.nodeName ) && !elem._change_attached ) { jQuery.event.add( elem, "change._change", function( event ) { if ( this.parentNode && !event.isSimulated && !event.isTrigger ) { jQuery.event.simulate( "change", this.parentNode, event, true ); } }); elem._change_attached = true; } }); }, handle: function( event ) { var elem = event.target; // Swallow native change events from checkbox/radio, we already triggered them above if ( this !== elem || event.isSimulated || event.isTrigger || (elem.type !== "radio" && elem.type !== "checkbox") ) { return event.handleObj.handler.apply( this, arguments ); } }, teardown: function() { jQuery.event.remove( this, "._change" ); return rformElems.test( this.nodeName ); } }; } // Create "bubbling" focus and blur events if ( !jQuery.support.focusinBubbles ) { jQuery.each({ focus: "focusin", blur: "focusout" }, function( orig, fix ) { // Attach a single capturing handler while someone wants focusin/focusout var attaches = 0, handler = function( event ) { jQuery.event.simulate( fix, event.target, jQuery.event.fix( event ), true ); }; jQuery.event.special[ fix ] = { setup: function() { if ( attaches++ === 0 ) { document.addEventListener( orig, handler, true ); } }, teardown: function() { if ( --attaches === 0 ) { document.removeEventListener( orig, handler, true ); } } }; }); } jQuery.fn.extend({ on: function( types, selector, data, fn, /*INTERNAL*/ one ) { var origFn, type; // Types can be a map of types/handlers if ( typeof types === "object" ) { // ( types-Object, selector, data ) if ( typeof selector !== "string" ) { // ( types-Object, data ) data = selector; selector = undefined; } for ( type in types ) { this.on( type, selector, data, types[ type ], one ); } return this; } if ( data == null && fn == null ) { // ( types, fn ) fn = selector; data = selector = undefined; } else if ( fn == null ) { if ( typeof selector === "string" ) { // ( types, selector, fn ) fn = data; data = undefined; } else { // ( types, data, fn ) fn = data; data = selector; selector = undefined; } } if ( fn === false ) { fn = returnFalse; } else if ( !fn ) { return this; } if ( one === 1 ) { origFn = fn; fn = function( event ) { // Can use an empty set, since event contains the info jQuery().off( event ); return origFn.apply( this, arguments ); }; // Use same guid so caller can remove using origFn fn.guid = origFn.guid || ( origFn.guid = jQuery.guid++ ); } return this.each( function() { jQuery.event.add( this, types, fn, data, selector ); }); }, one: function( types, selector, data, fn ) { return this.on.call( this, types, selector, data, fn, 1 ); }, off: function( types, selector, fn ) { if ( types && types.preventDefault && types.handleObj ) { // ( event ) dispatched jQuery.Event var handleObj = types.handleObj; jQuery( types.delegateTarget ).off( handleObj.namespace? handleObj.type + "." + handleObj.namespace : handleObj.type, handleObj.selector, handleObj.handler ); return this; } if ( typeof types === "object" ) { // ( types-object [, selector] ) for ( var type in types ) { this.off( type, selector, types[ type ] ); } return this; } if ( selector === false || typeof selector === "function" ) { // ( types [, fn] ) fn = selector; selector = undefined; } if ( fn === false ) { fn = returnFalse; } return this.each(function() { jQuery.event.remove( this, types, fn, selector ); }); }, bind: function( types, data, fn ) { return this.on( types, null, data, fn ); }, unbind: function( types, fn ) { return this.off( types, null, fn ); }, live: function( types, data, fn ) { jQuery( this.context ).on( types, this.selector, data, fn ); return this; }, die: function( types, fn ) { jQuery( this.context ).off( types, this.selector || "**", fn ); return this; }, delegate: function( selector, types, data, fn ) { return this.on( types, selector, data, fn ); }, undelegate: function( selector, types, fn ) { // ( namespace ) or ( selector, types [, fn] ) return arguments.length == 1? this.off( selector, "**" ) : this.off( types, selector, fn ); }, trigger: function( type, data ) { return this.each(function() { jQuery.event.trigger( type, data, this ); }); }, triggerHandler: function( type, data ) { if ( this[0] ) { return jQuery.event.trigger( type, data, this[0], true ); } }, toggle: function( fn ) { // Save reference to arguments for access in closure var args = arguments, guid = fn.guid || jQuery.guid++, i = 0, toggler = function( event ) { // Figure out which function to execute var lastToggle = ( jQuery._data( this, "lastToggle" + fn.guid ) || 0 ) % i; jQuery._data( this, "lastToggle" + fn.guid, lastToggle + 1 ); // Make sure that clicks stop event.preventDefault(); // and execute the function return args[ lastToggle ].apply( this, arguments ) || false; }; // link all the functions, so any of them can unbind this click handler toggler.guid = guid; while ( i < args.length ) { args[ i++ ].guid = guid; } return this.click( toggler ); }, hover: function( fnOver, fnOut ) { return this.mouseenter( fnOver ).mouseleave( fnOut || fnOver ); } }); jQuery.each( ("blur focus focusin focusout load resize scroll unload click dblclick " + "mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave " + "change select submit keydown keypress keyup error contextmenu").split(" "), function( i, name ) { // Handle event binding jQuery.fn[ name ] = function( data, fn ) { if ( fn == null ) { fn = data; data = null; } return arguments.length > 0 ? this.on( name, null, data, fn ) : this.trigger( name ); }; if ( jQuery.attrFn ) { jQuery.attrFn[ name ] = true; } if ( rkeyEvent.test( name ) ) { jQuery.event.fixHooks[ name ] = jQuery.event.keyHooks; } if ( rmouseEvent.test( name ) ) { jQuery.event.fixHooks[ name ] = jQuery.event.mouseHooks; } }); /*! * Sizzle CSS Selector Engine * Copyright 2016, The Dojo Foundation * Released under the MIT, BSD, and GPL Licenses. * More information: http://sizzlejs.com/ */ (function(){ var chunker = /((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^\[\]]*\]|['"][^'"]*['"]|[^\[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g, expando = "sizcache" + (Math.random() + '').replace('.', ''), done = 0, toString = Object.prototype.toString, hasDuplicate = false, baseHasDuplicate = true, rBackslash = /\\/g, rReturn = /\r\n/g, rNonWord = /\W/; // Here we check if the JavaScript engine is using some sort of // optimization where it does not always call our comparison // function. If that is the case, discard the hasDuplicate value. // Thus far that includes Google Chrome. [0, 0].sort(function() { baseHasDuplicate = false; return 0; }); var Sizzle = function( selector, context, results, seed ) { results = results || []; context = context || document; var origContext = context; if ( context.nodeType !== 1 && context.nodeType !== 9 ) { return []; } if ( !selector || typeof selector !== "string" ) { return results; } var m, set, checkSet, extra, ret, cur, pop, i, prune = true, contextXML = Sizzle.isXML( context ), parts = [], soFar = selector; // Reset the position of the chunker regexp (start from head) do { chunker.exec( "" ); m = chunker.exec( soFar ); if ( m ) { soFar = m[3]; parts.push( m[1] ); if ( m[2] ) { extra = m[3]; break; } } } while ( m ); if ( parts.length > 1 && origPOS.exec( selector ) ) { if ( parts.length === 2 && Expr.relative[ parts[0] ] ) { set = posProcess( parts[0] + parts[1], context, seed ); } else { set = Expr.relative[ parts[0] ] ? [ context ] : Sizzle( parts.shift(), context ); while ( parts.length ) { selector = parts.shift(); if ( Expr.relative[ selector ] ) { selector += parts.shift(); } set = posProcess( selector, set, seed ); } } } else { // Take a shortcut and set the context if the root selector is an ID // (but not if it'll be faster if the inner selector is an ID) if ( !seed && parts.length > 1 && context.nodeType === 9 && !contextXML && Expr.match.ID.test(parts[0]) && !Expr.match.ID.test(parts[parts.length - 1]) ) { ret = Sizzle.find( parts.shift(), context, contextXML ); context = ret.expr ? Sizzle.filter( ret.expr, ret.set )[0] : ret.set[0]; } if ( context ) { ret = seed ? { expr: parts.pop(), set: makeArray(seed) } : Sizzle.find( parts.pop(), parts.length === 1 && (parts[0] === "~" || parts[0] === "+") && context.parentNode ? context.parentNode : context, contextXML ); set = ret.expr ? Sizzle.filter( ret.expr, ret.set ) : ret.set; if ( parts.length > 0 ) { checkSet = makeArray( set ); } else { prune = false; } while ( parts.length ) { cur = parts.pop(); pop = cur; if ( !Expr.relative[ cur ] ) { cur = ""; } else { pop = parts.pop(); } if ( pop == null ) { pop = context; } Expr.relative[ cur ]( checkSet, pop, contextXML ); } } else { checkSet = parts = []; } } if ( !checkSet ) { checkSet = set; } if ( !checkSet ) { Sizzle.error( cur || selector ); } if ( toString.call(checkSet) === "[object Array]" ) { if ( !prune ) { results.push.apply( results, checkSet ); } else if ( context && context.nodeType === 1 ) { for ( i = 0; checkSet[i] != null; i++ ) { if ( checkSet[i] && (checkSet[i] === true || checkSet[i].nodeType === 1 && Sizzle.contains(context, checkSet[i])) ) { results.push( set[i] ); } } } else { for ( i = 0; checkSet[i] != null; i++ ) { if ( checkSet[i] && checkSet[i].nodeType === 1 ) { results.push( set[i] ); } } } } else { makeArray( checkSet, results ); } if ( extra ) { Sizzle( extra, origContext, results, seed ); Sizzle.uniqueSort( results ); } return results; }; Sizzle.uniqueSort = function( results ) { if ( sortOrder ) { hasDuplicate = baseHasDuplicate; results.sort( sortOrder ); if ( hasDuplicate ) { for ( var i = 1; i < results.length; i++ ) { if ( results[i] === results[ i - 1 ] ) { results.splice( i--, 1 ); } } } } return results; }; Sizzle.matches = function( expr, set ) { return Sizzle( expr, null, null, set ); }; Sizzle.matchesSelector = function( node, expr ) { return Sizzle( expr, null, null, [node] ).length > 0; }; Sizzle.find = function( expr, context, isXML ) { var set, i, len, match, type, left; if ( !expr ) { return []; } for ( i = 0, len = Expr.order.length; i < len; i++ ) { type = Expr.order[i]; if ( (match = Expr.leftMatch[ type ].exec( expr )) ) { left = match[1]; match.splice( 1, 1 ); if ( left.substr( left.length - 1 ) !== "\\" ) { match[1] = (match[1] || "").replace( rBackslash, "" ); set = Expr.find[ type ]( match, context, isXML ); if ( set != null ) { expr = expr.replace( Expr.match[ type ], "" ); break; } } } } if ( !set ) { set = typeof context.getElementsByTagName !== "undefined" ? context.getElementsByTagName( "*" ) : []; } return { set: set, expr: expr }; }; Sizzle.filter = function( expr, set, inplace, not ) { var match, anyFound, type, found, item, filter, left, i, pass, old = expr, result = [], curLoop = set, isXMLFilter = set && set[0] && Sizzle.isXML( set[0] ); while ( expr && set.length ) { for ( type in Expr.filter ) { if ( (match = Expr.leftMatch[ type ].exec( expr )) != null && match[2] ) { filter = Expr.filter[ type ]; left = match[1]; anyFound = false; match.splice(1,1); if ( left.substr( left.length - 1 ) === "\\" ) { continue; } if ( curLoop === result ) { result = []; } if ( Expr.preFilter[ type ] ) { match = Expr.preFilter[ type ]( match, curLoop, inplace, result, not, isXMLFilter ); if ( !match ) { anyFound = found = true; } else if ( match === true ) { continue; } } if ( match ) { for ( i = 0; (item = curLoop[i]) != null; i++ ) { if ( item ) { found = filter( item, match, i, curLoop ); pass = not ^ found; if ( inplace && found != null ) { if ( pass ) { anyFound = true; } else { curLoop[i] = false; } } else if ( pass ) { result.push( item ); anyFound = true; } } } } if ( found !== undefined ) { if ( !inplace ) { curLoop = result; } expr = expr.replace( Expr.match[ type ], "" ); if ( !anyFound ) { return []; } break; } } } // Improper expression if ( expr === old ) { if ( anyFound == null ) { Sizzle.error( expr ); } else { break; } } old = expr; } return curLoop; }; Sizzle.error = function( msg ) { throw new Error( "Syntax error, unrecognized expression: " + msg ); }; /** * Utility function for retrieving the text value of an array of DOM nodes * @param {Array|Element} elem */ var getText = Sizzle.getText = function( elem ) { var i, node, nodeType = elem.nodeType, ret = ""; if ( nodeType ) { if ( nodeType === 1 || nodeType === 9 ) { // Use textContent || innerText for elements if ( typeof elem.textContent === 'string' ) { return elem.textContent; } else if ( typeof elem.innerText === 'string' ) { // Replace IE's carriage returns return elem.innerText.replace( rReturn, '' ); } else { // Traverse it's children for ( elem = elem.firstChild; elem; elem = elem.nextSibling) { ret += getText( elem ); } } } else if ( nodeType === 3 || nodeType === 4 ) { return elem.nodeValue; } } else { // If no nodeType, this is expected to be an array for ( i = 0; (node = elem[i]); i++ ) { // Do not traverse comment nodes if ( node.nodeType !== 8 ) { ret += getText( node ); } } } return ret; }; var Expr = Sizzle.selectors = { order: [ "ID", "NAME", "TAG" ], match: { ID: /#((?:[\w\u00c0-\uFFFF\-]|\\.)+)/, CLASS: /\.((?:[\w\u00c0-\uFFFF\-]|\\.)+)/, NAME: /\[name=['"]*((?:[\w\u00c0-\uFFFF\-]|\\.)+)['"]*\]/, ATTR: /\[\s*((?:[\w\u00c0-\uFFFF\-]|\\.)+)\s*(?:(\S?=)\s*(?:(['"])(.*?)\3|(#?(?:[\w\u00c0-\uFFFF\-]|\\.)*)|)|)\s*\]/, TAG: /^((?:[\w\u00c0-\uFFFF\*\-]|\\.)+)/, CHILD: /:(only|nth|last|first)-child(?:\(\s*(even|odd|(?:[+\-]?\d+|(?:[+\-]?\d*)?n\s*(?:[+\-]\s*\d+)?))\s*\))?/, POS: /:(nth|eq|gt|lt|first|last|even|odd)(?:\((\d*)\))?(?=[^\-]|$)/, PSEUDO: /:((?:[\w\u00c0-\uFFFF\-]|\\.)+)(?:\((['"]?)((?:\([^\)]+\)|[^\(\)]*)+)\2\))?/ }, leftMatch: {}, attrMap: { "class": "className", "for": "htmlFor" }, attrHandle: { href: function( elem ) { return elem.getAttribute( "href" ); }, type: function( elem ) { return elem.getAttribute( "type" ); } }, relative: { "+": function(checkSet, part){ var isPartStr = typeof part === "string", isTag = isPartStr && !rNonWord.test( part ), isPartStrNotTag = isPartStr && !isTag; if ( isTag ) { part = part.toLowerCase(); } for ( var i = 0, l = checkSet.length, elem; i < l; i++ ) { if ( (elem = checkSet[i]) ) { while ( (elem = elem.previousSibling) && elem.nodeType !== 1 ) {} checkSet[i] = isPartStrNotTag || elem && elem.nodeName.toLowerCase() === part ? elem || false : elem === part; } } if ( isPartStrNotTag ) { Sizzle.filter( part, checkSet, true ); } }, ">": function( checkSet, part ) { var elem, isPartStr = typeof part === "string", i = 0, l = checkSet.length; if ( isPartStr && !rNonWord.test( part ) ) { part = part.toLowerCase(); for ( ; i < l; i++ ) { elem = checkSet[i]; if ( elem ) { var parent = elem.parentNode; checkSet[i] = parent.nodeName.toLowerCase() === part ? parent : false; } } } else { for ( ; i < l; i++ ) { elem = checkSet[i]; if ( elem ) { checkSet[i] = isPartStr ? elem.parentNode : elem.parentNode === part; } } if ( isPartStr ) { Sizzle.filter( part, checkSet, true ); } } }, "": function(checkSet, part, isXML){ var nodeCheck, doneName = done++, checkFn = dirCheck; if ( typeof part === "string" && !rNonWord.test( part ) ) { part = part.toLowerCase(); nodeCheck = part; checkFn = dirNodeCheck; } checkFn( "parentNode", part, doneName, checkSet, nodeCheck, isXML ); }, "~": function( checkSet, part, isXML ) { var nodeCheck, doneName = done++, checkFn = dirCheck; if ( typeof part === "string" && !rNonWord.test( part ) ) { part = part.toLowerCase(); nodeCheck = part; checkFn = dirNodeCheck; } checkFn( "previousSibling", part, doneName, checkSet, nodeCheck, isXML ); } }, find: { ID: function( match, context, isXML ) { if ( typeof context.getElementById !== "undefined" && !isXML ) { var m = context.getElementById(match[1]); // Check parentNode to catch when Blackberry 4.6 returns // nodes that are no longer in the document #6963 return m && m.parentNode ? [m] : []; } }, NAME: function( match, context ) { if ( typeof context.getElementsByName !== "undefined" ) { var ret = [], results = context.getElementsByName( match[1] ); for ( var i = 0, l = results.length; i < l; i++ ) { if ( results[i].getAttribute("name") === match[1] ) { ret.push( results[i] ); } } return ret.length === 0 ? null : ret; } }, TAG: function( match, context ) { if ( typeof context.getElementsByTagName !== "undefined" ) { return context.getElementsByTagName( match[1] ); } } }, preFilter: { CLASS: function( match, curLoop, inplace, result, not, isXML ) { match = " " + match[1].replace( rBackslash, "" ) + " "; if ( isXML ) { return match; } for ( var i = 0, elem; (elem = curLoop[i]) != null; i++ ) { if ( elem ) { if ( not ^ (elem.className && (" " + elem.className + " ").replace(/[\t\n\r]/g, " ").indexOf(match) >= 0) ) { if ( !inplace ) { result.push( elem ); } } else if ( inplace ) { curLoop[i] = false; } } } return false; }, ID: function( match ) { return match[1].replace( rBackslash, "" ); }, TAG: function( match, curLoop ) { return match[1].replace( rBackslash, "" ).toLowerCase(); }, CHILD: function( match ) { if ( match[1] === "nth" ) { if ( !match[2] ) { Sizzle.error( match[0] ); } match[2] = match[2].replace(/^\+|\s*/g, ''); // parse equations like 'even', 'odd', '5', '2n', '3n+2', '4n-1', '-n+6' var test = /(-?)(\d*)(?:n([+\-]?\d*))?/.exec( match[2] === "even" && "2n" || match[2] === "odd" && "2n+1" || !/\D/.test( match[2] ) && "0n+" + match[2] || match[2]); // calculate the numbers (first)n+(last) including if they are negative match[2] = (test[1] + (test[2] || 1)) - 0; match[3] = test[3] - 0; } else if ( match[2] ) { Sizzle.error( match[0] ); } // TODO: Move to normal caching system match[0] = done++; return match; }, ATTR: function( match, curLoop, inplace, result, not, isXML ) { var name = match[1] = match[1].replace( rBackslash, "" ); if ( !isXML && Expr.attrMap[name] ) { match[1] = Expr.attrMap[name]; } // Handle if an un-quoted value was used match[4] = ( match[4] || match[5] || "" ).replace( rBackslash, "" ); if ( match[2] === "~=" ) { match[4] = " " + match[4] + " "; } return match; }, PSEUDO: function( match, curLoop, inplace, result, not ) { if ( match[1] === "not" ) { // If we're dealing with a complex expression, or a simple one if ( ( chunker.exec(match[3]) || "" ).length > 1 || /^\w/.test(match[3]) ) { match[3] = Sizzle(match[3], null, null, curLoop); } else { var ret = Sizzle.filter(match[3], curLoop, inplace, true ^ not); if ( !inplace ) { result.push.apply( result, ret ); } return false; } } else if ( Expr.match.POS.test( match[0] ) || Expr.match.CHILD.test( match[0] ) ) { return true; } return match; }, POS: function( match ) { match.unshift( true ); return match; } }, filters: { enabled: function( elem ) { return elem.disabled === false && elem.type !== "hidden"; }, disabled: function( elem ) { return elem.disabled === true; }, checked: function( elem ) { return elem.checked === true; }, selected: function( elem ) { // Accessing this property makes selected-by-default // options in Safari work properly if ( elem.parentNode ) { elem.parentNode.selectedIndex; } return elem.selected === true; }, parent: function( elem ) { return !!elem.firstChild; }, empty: function( elem ) { return !elem.firstChild; }, has: function( elem, i, match ) { return !!Sizzle( match[3], elem ).length; }, header: function( elem ) { return (/h\d/i).test( elem.nodeName ); }, text: function( elem ) { var attr = elem.getAttribute( "type" ), type = elem.type; // IE6 and 7 will map elem.type to 'text' for new HTML5 types (search, etc) // use getAttribute instead to test this case return elem.nodeName.toLowerCase() === "input" && "text" === type && ( attr === type || attr === null ); }, radio: function( elem ) { return elem.nodeName.toLowerCase() === "input" && "radio" === elem.type; }, checkbox: function( elem ) { return elem.nodeName.toLowerCase() === "input" && "checkbox" === elem.type; }, file: function( elem ) { return elem.nodeName.toLowerCase() === "input" && "file" === elem.type; }, password: function( elem ) { return elem.nodeName.toLowerCase() === "input" && "password" === elem.type; }, submit: function( elem ) { var name = elem.nodeName.toLowerCase(); return (name === "input" || name === "button") && "submit" === elem.type; }, image: function( elem ) { return elem.nodeName.toLowerCase() === "input" && "image" === elem.type; }, reset: function( elem ) { var name = elem.nodeName.toLowerCase(); return (name === "input" || name === "button") && "reset" === elem.type; }, button: function( elem ) { var name = elem.nodeName.toLowerCase(); return name === "input" && "button" === elem.type || name === "button"; }, input: function( elem ) { return (/input|select|textarea|button/i).test( elem.nodeName ); }, focus: function( elem ) { return elem === elem.ownerDocument.activeElement; } }, setFilters: { first: function( elem, i ) { return i === 0; }, last: function( elem, i, match, array ) { return i === array.length - 1; }, even: function( elem, i ) { return i % 2 === 0; }, odd: function( elem, i ) { return i % 2 === 1; }, lt: function( elem, i, match ) { return i < match[3] - 0; }, gt: function( elem, i, match ) { return i > match[3] - 0; }, nth: function( elem, i, match ) { return match[3] - 0 === i; }, eq: function( elem, i, match ) { return match[3] - 0 === i; } }, filter: { PSEUDO: function( elem, match, i, array ) { var name = match[1], filter = Expr.filters[ name ]; if ( filter ) { return filter( elem, i, match, array ); } else if ( name === "contains" ) { return (elem.textContent || elem.innerText || getText([ elem ]) || "").indexOf(match[3]) >= 0; } else if ( name === "not" ) { var not = match[3]; for ( var j = 0, l = not.length; j < l; j++ ) { if ( not[j] === elem ) { return false; } } return true; } else { Sizzle.error( name ); } }, CHILD: function( elem, match ) { var first, last, doneName, parent, cache, count, diff, type = match[1], node = elem; switch ( type ) { case "only": case "first": while ( (node = node.previousSibling) ) { if ( node.nodeType === 1 ) { return false; } } if ( type === "first" ) { return true; } node = elem; case "last": while ( (node = node.nextSibling) ) { if ( node.nodeType === 1 ) { return false; } } return true; case "nth": first = match[2]; last = match[3]; if ( first === 1 && last === 0 ) { return true; } doneName = match[0]; parent = elem.parentNode; if ( parent && (parent[ expando ] !== doneName || !elem.nodeIndex) ) { count = 0; for ( node = parent.firstChild; node; node = node.nextSibling ) { if ( node.nodeType === 1 ) { node.nodeIndex = ++count; } } parent[ expando ] = doneName; } diff = elem.nodeIndex - last; if ( first === 0 ) { return diff === 0; } else { return ( diff % first === 0 && diff / first >= 0 ); } } }, ID: function( elem, match ) { return elem.nodeType === 1 && elem.getAttribute("id") === match; }, TAG: function( elem, match ) { return (match === "*" && elem.nodeType === 1) || !!elem.nodeName && elem.nodeName.toLowerCase() === match; }, CLASS: function( elem, match ) { return (" " + (elem.className || elem.getAttribute("class")) + " ") .indexOf( match ) > -1; }, ATTR: function( elem, match ) { var name = match[1], result = Sizzle.attr ? Sizzle.attr( elem, name ) : Expr.attrHandle[ name ] ? Expr.attrHandle[ name ]( elem ) : elem[ name ] != null ? elem[ name ] : elem.getAttribute( name ), value = result + "", type = match[2], check = match[4]; return result == null ? type === "!=" : !type && Sizzle.attr ? result != null : type === "=" ? value === check : type === "*=" ? value.indexOf(check) >= 0 : type === "~=" ? (" " + value + " ").indexOf(check) >= 0 : !check ? value && result !== false : type === "!=" ? value !== check : type === "^=" ? value.indexOf(check) === 0 : type === "$=" ? value.substr(value.length - check.length) === check : type === "|=" ? value === check || value.substr(0, check.length + 1) === check + "-" : false; }, POS: function( elem, match, i, array ) { var name = match[2], filter = Expr.setFilters[ name ]; if ( filter ) { return filter( elem, i, match, array ); } } } }; var origPOS = Expr.match.POS, fescape = function(all, num){ return "\\" + (num - 0 + 1); }; for ( var type in Expr.match ) { Expr.match[ type ] = new RegExp( Expr.match[ type ].source + (/(?![^\[]*\])(?![^\(]*\))/.source) ); Expr.leftMatch[ type ] = new RegExp( /(^(?:.|\r|\n)*?)/.source + Expr.match[ type ].source.replace(/\\(\d+)/g, fescape) ); } var makeArray = function( array, results ) { array = Array.prototype.slice.call( array, 0 ); if ( results ) { results.push.apply( results, array ); return results; } return array; }; // Perform a simple check to determine if the browser is capable of // converting a NodeList to an array using builtin methods. // Also verifies that the returned array holds DOM nodes // (which is not the case in the Blackberry browser) try { Array.prototype.slice.call( document.documentElement.childNodes, 0 )[0].nodeType; // Provide a fallback method if it does not work } catch( e ) { makeArray = function( array, results ) { var i = 0, ret = results || []; if ( toString.call(array) === "[object Array]" ) { Array.prototype.push.apply( ret, array ); } else { if ( typeof array.length === "number" ) { for ( var l = array.length; i < l; i++ ) { ret.push( array[i] ); } } else { for ( ; array[i]; i++ ) { ret.push( array[i] ); } } } return ret; }; } var sortOrder, siblingCheck; if ( document.documentElement.compareDocumentPosition ) { sortOrder = function( a, b ) { if ( a === b ) { hasDuplicate = true; return 0; } if ( !a.compareDocumentPosition || !b.compareDocumentPosition ) { return a.compareDocumentPosition ? -1 : 1; } return a.compareDocumentPosition(b) & 4 ? -1 : 1; }; } else { sortOrder = function( a, b ) { // The nodes are identical, we can exit early if ( a === b ) { hasDuplicate = true; return 0; // Fallback to using sourceIndex (in IE) if it's available on both nodes } else if ( a.sourceIndex && b.sourceIndex ) { return a.sourceIndex - b.sourceIndex; } var al, bl, ap = [], bp = [], aup = a.parentNode, bup = b.parentNode, cur = aup; // If the nodes are siblings (or identical) we can do a quick check if ( aup === bup ) { return siblingCheck( a, b ); // If no parents were found then the nodes are disconnected } else if ( !aup ) { return -1; } else if ( !bup ) { return 1; } // Otherwise they're somewhere else in the tree so we need // to build up a full list of the parentNodes for comparison while ( cur ) { ap.unshift( cur ); cur = cur.parentNode; } cur = bup; while ( cur ) { bp.unshift( cur ); cur = cur.parentNode; } al = ap.length; bl = bp.length; // Start walking down the tree looking for a discrepancy for ( var i = 0; i < al && i < bl; i++ ) { if ( ap[i] !== bp[i] ) { return siblingCheck( ap[i], bp[i] ); } } // We ended someplace up the tree so do a sibling check return i === al ? siblingCheck( a, bp[i], -1 ) : siblingCheck( ap[i], b, 1 ); }; siblingCheck = function( a, b, ret ) { if ( a === b ) { return ret; } var cur = a.nextSibling; while ( cur ) { if ( cur === b ) { return -1; } cur = cur.nextSibling; } return 1; }; } // Check to see if the browser returns elements by name when // querying by getElementById (and provide a workaround) (function(){ // We're going to inject a fake input element with a specified name var form = document.createElement("div"), id = "script" + (new Date()).getTime(), root = document.documentElement; form.innerHTML = "<a name='" + id + "'/>"; // Inject it into the root element, check its status, and remove it quickly root.insertBefore( form, root.firstChild ); // The workaround has to do additional checks after a getElementById // Which slows things down for other browsers (hence the branching) if ( document.getElementById( id ) ) { Expr.find.ID = function( match, context, isXML ) { if ( typeof context.getElementById !== "undefined" && !isXML ) { var m = context.getElementById(match[1]); return m ? m.id === match[1] || typeof m.getAttributeNode !== "undefined" && m.getAttributeNode("id").nodeValue === match[1] ? [m] : undefined : []; } }; Expr.filter.ID = function( elem, match ) { var node = typeof elem.getAttributeNode !== "undefined" && elem.getAttributeNode("id"); return elem.nodeType === 1 && node && node.nodeValue === match; }; } root.removeChild( form ); // release memory in IE root = form = null; })(); (function(){ // Check to see if the browser returns only elements // when doing getElementsByTagName("*") // Create a fake element var div = document.createElement("div"); div.appendChild( document.createComment("") ); // Make sure no comments are found if ( div.getElementsByTagName("*").length > 0 ) { Expr.find.TAG = function( match, context ) { var results = context.getElementsByTagName( match[1] ); // Filter out possible comments if ( match[1] === "*" ) { var tmp = []; for ( var i = 0; results[i]; i++ ) { if ( results[i].nodeType === 1 ) { tmp.push( results[i] ); } } results = tmp; } return results; }; } // Check to see if an attribute returns normalized href attributes div.innerHTML = "<a href='#'></a>"; if ( div.firstChild && typeof div.firstChild.getAttribute !== "undefined" && div.firstChild.getAttribute("href") !== "#" ) { Expr.attrHandle.href = function( elem ) { return elem.getAttribute( "href", 2 ); }; } // release memory in IE div = null; })(); if ( document.querySelectorAll ) { (function(){ var oldSizzle = Sizzle, div = document.createElement("div"), id = "__sizzle__"; div.innerHTML = "<p class='TEST'></p>"; // Safari can't handle uppercase or unicode characters when // in quirks mode. if ( div.querySelectorAll && div.querySelectorAll(".TEST").length === 0 ) { return; } Sizzle = function( query, context, extra, seed ) { context = context || document; // Only use querySelectorAll on non-XML documents // (ID selectors don't work in non-HTML documents) if ( !seed && !Sizzle.isXML(context) ) { // See if we find a selector to speed up var match = /^(\w+$)|^\.([\w\-]+$)|^#([\w\-]+$)/.exec( query ); if ( match && (context.nodeType === 1 || context.nodeType === 9) ) { // Speed-up: Sizzle("TAG") if ( match[1] ) { return makeArray( context.getElementsByTagName( query ), extra ); // Speed-up: Sizzle(".CLASS") } else if ( match[2] && Expr.find.CLASS && context.getElementsByClassName ) { return makeArray( context.getElementsByClassName( match[2] ), extra ); } } if ( context.nodeType === 9 ) { // Speed-up: Sizzle("body") // The body element only exists once, optimize finding it if ( query === "body" && context.body ) { return makeArray( [ context.body ], extra ); // Speed-up: Sizzle("#ID") } else if ( match && match[3] ) { var elem = context.getElementById( match[3] ); // Check parentNode to catch when Blackberry 4.6 returns // nodes that are no longer in the document #6963 if ( elem && elem.parentNode ) { // Handle the case where IE and Opera return items // by name instead of ID if ( elem.id === match[3] ) { return makeArray( [ elem ], extra ); } } else { return makeArray( [], extra ); } } try { return makeArray( context.querySelectorAll(query), extra ); } catch(qsaError) {} // qSA works strangely on Element-rooted queries // We can work around this by specifying an extra ID on the root // and working up from there (Thanks to Andrew Dupont for the technique) // IE 8 doesn't work on object elements } else if ( context.nodeType === 1 && context.nodeName.toLowerCase() !== "object" ) { var oldContext = context, old = context.getAttribute( "id" ), nid = old || id, hasParent = context.parentNode, relativeHierarchySelector = /^\s*[+~]/.test( query ); if ( !old ) { context.setAttribute( "id", nid ); } else { nid = nid.replace( /'/g, "\\$&" ); } if ( relativeHierarchySelector && hasParent ) { context = context.parentNode; } try { if ( !relativeHierarchySelector || hasParent ) { return makeArray( context.querySelectorAll( "[id='" + nid + "'] " + query ), extra ); } } catch(pseudoError) { } finally { if ( !old ) { oldContext.removeAttribute( "id" ); } } } } return oldSizzle(query, context, extra, seed); }; for ( var prop in oldSizzle ) { Sizzle[ prop ] = oldSizzle[ prop ]; } // release memory in IE div = null; })(); } (function(){ var html = document.documentElement, matches = html.matchesSelector || html.mozMatchesSelector || html.webkitMatchesSelector || html.msMatchesSelector; if ( matches ) { // Check to see if it's possible to do matchesSelector // on a disconnected node (IE 9 fails this) var disconnectedMatch = !matches.call( document.createElement( "div" ), "div" ), pseudoWorks = false; try { // This should fail with an exception // Gecko does not error, returns false instead matches.call( document.documentElement, "[test!='']:sizzle" ); } catch( pseudoError ) { pseudoWorks = true; } Sizzle.matchesSelector = function( node, expr ) { // Make sure that attribute selectors are quoted expr = expr.replace(/\=\s*([^'"\]]*)\s*\]/g, "='$1']"); if ( !Sizzle.isXML( node ) ) { try { if ( pseudoWorks || !Expr.match.PSEUDO.test( expr ) && !/!=/.test( expr ) ) { var ret = matches.call( node, expr ); // IE 9's matchesSelector returns false on disconnected nodes if ( ret || !disconnectedMatch || // As well, disconnected nodes are said to be in a document // fragment in IE 9, so check for that node.document && node.document.nodeType !== 11 ) { return ret; } } } catch(e) {} } return Sizzle(expr, null, null, [node]).length > 0; }; } })(); (function(){ var div = document.createElement("div"); div.innerHTML = "<div class='test e'></div><div class='test'></div>"; // Opera can't find a second classname (in 9.6) // Also, make sure that getElementsByClassName actually exists if ( !div.getElementsByClassName || div.getElementsByClassName("e").length === 0 ) { return; } // Safari caches class attributes, doesn't catch changes (in 3.2) div.lastChild.className = "e"; if ( div.getElementsByClassName("e").length === 1 ) { return; } Expr.order.splice(1, 0, "CLASS"); Expr.find.CLASS = function( match, context, isXML ) { if ( typeof context.getElementsByClassName !== "undefined" && !isXML ) { return context.getElementsByClassName(match[1]); } }; // release memory in IE div = null; })(); function dirNodeCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) { for ( var i = 0, l = checkSet.length; i < l; i++ ) { var elem = checkSet[i]; if ( elem ) { var match = false; elem = elem[dir]; while ( elem ) { if ( elem[ expando ] === doneName ) { match = checkSet[elem.sizset]; break; } if ( elem.nodeType === 1 && !isXML ){ elem[ expando ] = doneName; elem.sizset = i; } if ( elem.nodeName.toLowerCase() === cur ) { match = elem; break; } elem = elem[dir]; } checkSet[i] = match; } } } function dirCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) { for ( var i = 0, l = checkSet.length; i < l; i++ ) { var elem = checkSet[i]; if ( elem ) { var match = false; elem = elem[dir]; while ( elem ) { if ( elem[ expando ] === doneName ) { match = checkSet[elem.sizset]; break; } if ( elem.nodeType === 1 ) { if ( !isXML ) { elem[ expando ] = doneName; elem.sizset = i; } if ( typeof cur !== "string" ) { if ( elem === cur ) { match = true; break; } } else if ( Sizzle.filter( cur, [elem] ).length > 0 ) { match = elem; break; } } elem = elem[dir]; } checkSet[i] = match; } } } if ( document.documentElement.contains ) { Sizzle.contains = function( a, b ) { return a !== b && (a.contains ? a.contains(b) : true); }; } else if ( document.documentElement.compareDocumentPosition ) { Sizzle.contains = function( a, b ) { return !!(a.compareDocumentPosition(b) & 16); }; } else { Sizzle.contains = function() { return false; }; } Sizzle.isXML = function( elem ) { // documentElement is verified for cases where it doesn't yet exist // (such as loading iframes in IE - #4833) var documentElement = (elem ? elem.ownerDocument || elem : 0).documentElement; return documentElement ? documentElement.nodeName !== "HTML" : false; }; var posProcess = function( selector, context, seed ) { var match, tmpSet = [], later = "", root = context.nodeType ? [context] : context; // Position selectors must be done after the filter // And so must :not(positional) so we move all PSEUDOs to the end while ( (match = Expr.match.PSEUDO.exec( selector )) ) { later += match[0]; selector = selector.replace( Expr.match.PSEUDO, "" ); } selector = Expr.relative[selector] ? selector + "*" : selector; for ( var i = 0, l = root.length; i < l; i++ ) { Sizzle( selector, root[i], tmpSet, seed ); } return Sizzle.filter( later, tmpSet ); }; // EXPOSE // Override sizzle attribute retrieval Sizzle.attr = jQuery.attr; Sizzle.selectors.attrMap = {}; jQuery.find = Sizzle; jQuery.expr = Sizzle.selectors; jQuery.expr[":"] = jQuery.expr.filters; jQuery.unique = Sizzle.uniqueSort; jQuery.text = Sizzle.getText; jQuery.isXMLDoc = Sizzle.isXML; jQuery.contains = Sizzle.contains; })(); var runtil = /Until$/, rparentsprev = /^(?:parents|prevUntil|prevAll)/, // Note: This RegExp should be improved, or likely pulled from Sizzle rmultiselector = /,/, isSimple = /^.[^:#\[\.,]*$/, slice = Array.prototype.slice, POS = jQuery.expr.match.POS, // methods guaranteed to produce a unique set when starting from a unique set guaranteedUnique = { children: true, contents: true, next: true, prev: true }; jQuery.fn.extend({ find: function( selector ) { var self = this, i, l; if ( typeof selector !== "string" ) { return jQuery( selector ).filter(function() { for ( i = 0, l = self.length; i < l; i++ ) { if ( jQuery.contains( self[ i ], this ) ) { return true; } } }); } var ret = this.pushStack( "", "find", selector ), length, n, r; for ( i = 0, l = this.length; i < l; i++ ) { length = ret.length; jQuery.find( selector, this[i], ret ); if ( i > 0 ) { // Make sure that the results are unique for ( n = length; n < ret.length; n++ ) { for ( r = 0; r < length; r++ ) { if ( ret[r] === ret[n] ) { ret.splice(n--, 1); break; } } } } } return ret; }, has: function( target ) { var targets = jQuery( target ); return this.filter(function() { for ( var i = 0, l = targets.length; i < l; i++ ) { if ( jQuery.contains( this, targets[i] ) ) { return true; } } }); }, not: function( selector ) { return this.pushStack( winnow(this, selector, false), "not", selector); }, filter: function( selector ) { return this.pushStack( winnow(this, selector, true), "filter", selector ); }, is: function( selector ) { return !!selector && ( typeof selector === "string" ? // If this is a positional selector, check membership in the returned set // so $("p:first").is("p:last") won't return true for a doc with two "p". POS.test( selector ) ? jQuery( selector, this.context ).index( this[0] ) >= 0 : jQuery.filter( selector, this ).length > 0 : this.filter( selector ).length > 0 ); }, closest: function( selectors, context ) { var ret = [], i, l, cur = this[0]; // Array (deprecated as of jQuery 1.7) if ( jQuery.isArray( selectors ) ) { var level = 1; while ( cur && cur.ownerDocument && cur !== context ) { for ( i = 0; i < selectors.length; i++ ) { if ( jQuery( cur ).is( selectors[ i ] ) ) { ret.push({ selector: selectors[ i ], elem: cur, level: level }); } } cur = cur.parentNode; level++; } return ret; } // String var pos = POS.test( selectors ) || typeof selectors !== "string" ? jQuery( selectors, context || this.context ) : 0; for ( i = 0, l = this.length; i < l; i++ ) { cur = this[i]; while ( cur ) { if ( pos ? pos.index(cur) > -1 : jQuery.find.matchesSelector(cur, selectors) ) { ret.push( cur ); break; } else { cur = cur.parentNode; if ( !cur || !cur.ownerDocument || cur === context || cur.nodeType === 11 ) { break; } } } } ret = ret.length > 1 ? jQuery.unique( ret ) : ret; return this.pushStack( ret, "closest", selectors ); }, // Determine the position of an element within // the matched set of elements index: function( elem ) { // No argument, return index in parent if ( !elem ) { return ( this[0] && this[0].parentNode ) ? this.prevAll().length : -1; } // index in selector if ( typeof elem === "string" ) { return jQuery.inArray( this[0], jQuery( elem ) ); } // Locate the position of the desired element return jQuery.inArray( // If it receives a jQuery object, the first element is used elem.jquery ? elem[0] : elem, this ); }, add: function( selector, context ) { var set = typeof selector === "string" ? jQuery( selector, context ) : jQuery.makeArray( selector && selector.nodeType ? [ selector ] : selector ), all = jQuery.merge( this.get(), set ); return this.pushStack( isDisconnected( set[0] ) || isDisconnected( all[0] ) ? all : jQuery.unique( all ) ); }, andSelf: function() { return this.add( this.prevObject ); } }); // A painfully simple check to see if an element is disconnected // from a document (should be improved, where feasible). function isDisconnected( node ) { return !node || !node.parentNode || node.parentNode.nodeType === 11; } jQuery.each({ parent: function( elem ) { var parent = elem.parentNode; return parent && parent.nodeType !== 11 ? parent : null; }, parents: function( elem ) { return jQuery.dir( elem, "parentNode" ); }, parentsUntil: function( elem, i, until ) { return jQuery.dir( elem, "parentNode", until ); }, next: function( elem ) { return jQuery.nth( elem, 2, "nextSibling" ); }, prev: function( elem ) { return jQuery.nth( elem, 2, "previousSibling" ); }, nextAll: function( elem ) { return jQuery.dir( elem, "nextSibling" ); }, prevAll: function( elem ) { return jQuery.dir( elem, "previousSibling" ); }, nextUntil: function( elem, i, until ) { return jQuery.dir( elem, "nextSibling", until ); }, prevUntil: function( elem, i, until ) { return jQuery.dir( elem, "previousSibling", until ); }, siblings: function( elem ) { return jQuery.sibling( elem.parentNode.firstChild, elem ); }, children: function( elem ) { return jQuery.sibling( elem.firstChild ); }, contents: function( elem ) { return jQuery.nodeName( elem, "iframe" ) ? elem.contentDocument || elem.contentWindow.document : jQuery.makeArray( elem.childNodes ); } }, function( name, fn ) { jQuery.fn[ name ] = function( until, selector ) { var ret = jQuery.map( this, fn, until ); if ( !runtil.test( name ) ) { selector = until; } if ( selector && typeof selector === "string" ) { ret = jQuery.filter( selector, ret ); } ret = this.length > 1 && !guaranteedUnique[ name ] ? jQuery.unique( ret ) : ret; if ( (this.length > 1 || rmultiselector.test( selector )) && rparentsprev.test( name ) ) { ret = ret.reverse(); } return this.pushStack( ret, name, slice.call( arguments ).join(",") ); }; }); jQuery.extend({ filter: function( expr, elems, not ) { if ( not ) { expr = ":not(" + expr + ")"; } return elems.length === 1 ? jQuery.find.matchesSelector(elems[0], expr) ? [ elems[0] ] : [] : jQuery.find.matches(expr, elems); }, dir: function( elem, dir, until ) { var matched = [], cur = elem[ dir ]; while ( cur && cur.nodeType !== 9 && (until === undefined || cur.nodeType !== 1 || !jQuery( cur ).is( until )) ) { if ( cur.nodeType === 1 ) { matched.push( cur ); } cur = cur[dir]; } return matched; }, nth: function( cur, result, dir, elem ) { result = result || 1; var num = 0; for ( ; cur; cur = cur[dir] ) { if ( cur.nodeType === 1 && ++num === result ) { break; } } return cur; }, sibling: function( n, elem ) { var r = []; for ( ; n; n = n.nextSibling ) { if ( n.nodeType === 1 && n !== elem ) { r.push( n ); } } return r; } }); // Implement the identical functionality for filter and not function winnow( elements, qualifier, keep ) { // Can't pass null or undefined to indexOf in Firefox 4 // Set to 0 to skip string check qualifier = qualifier || 0; if ( jQuery.isFunction( qualifier ) ) { return jQuery.grep(elements, function( elem, i ) { var retVal = !!qualifier.call( elem, i, elem ); return retVal === keep; }); } else if ( qualifier.nodeType ) { return jQuery.grep(elements, function( elem, i ) { return ( elem === qualifier ) === keep; }); } else if ( typeof qualifier === "string" ) { var filtered = jQuery.grep(elements, function( elem ) { return elem.nodeType === 1; }); if ( isSimple.test( qualifier ) ) { return jQuery.filter(qualifier, filtered, !keep); } else { qualifier = jQuery.filter( qualifier, filtered ); } } return jQuery.grep(elements, function( elem, i ) { return ( jQuery.inArray( elem, qualifier ) >= 0 ) === keep; }); } function createSafeFragment( document ) { var list = nodeNames.split( "|" ), safeFrag = document.createDocumentFragment(); if ( safeFrag.createElement ) { while ( list.length ) { safeFrag.createElement( list.pop() ); } } return safeFrag; } var nodeNames = "abbr|article|aside|audio|canvas|datalist|details|figcaption|figure|footer|" + "header|hgroup|mark|meter|nav|output|progress|section|summary|time|video", rinlinejQuery = / jQuery\d+="(?:\d+|null)"/g, rleadingWhitespace = /^\s+/, rxhtmlTag = /<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/ig, rtagName = /<([\w:]+)/, rtbody = /<tbody/i, rhtml = /<|&#?\w+;/, rnoInnerhtml = /<(?:script|style)/i, rnocache = /<(?:script|object|embed|option|style)/i, rnoshimcache = new RegExp("<(?:" + nodeNames + ")", "i"), // checked="checked" or checked rchecked = /checked\s*(?:[^=]|=\s*.checked.)/i, rscriptType = /\/(java|ecma)script/i, rcleanScript = /^\s*<!(?:\[CDATA\[|\-\-)/, wrapMap = { option: [ 1, "<select multiple='multiple'>", "</select>" ], legend: [ 1, "<fieldset>", "</fieldset>" ], thead: [ 1, "<table>", "</table>" ], tr: [ 2, "<table><tbody>", "</tbody></table>" ], td: [ 3, "<table><tbody><tr>", "</tr></tbody></table>" ], col: [ 2, "<table><tbody></tbody><colgroup>", "</colgroup></table>" ], area: [ 1, "<map>", "</map>" ], _default: [ 0, "", "" ] }, safeFragment = createSafeFragment( document ); wrapMap.optgroup = wrapMap.option; wrapMap.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.thead; wrapMap.th = wrapMap.td; // IE can't serialize <link> and <script> tags normally if ( !jQuery.support.htmlSerialize ) { wrapMap._default = [ 1, "div<div>", "</div>" ]; } jQuery.fn.extend({ text: function( text ) { if ( jQuery.isFunction(text) ) { return this.each(function(i) { var self = jQuery( this ); self.text( text.call(this, i, self.text()) ); }); } if ( typeof text !== "object" && text !== undefined ) { return this.empty().append( (this[0] && this[0].ownerDocument || document).createTextNode( text ) ); } return jQuery.text( this ); }, wrapAll: function( html ) { if ( jQuery.isFunction( html ) ) { return this.each(function(i) { jQuery(this).wrapAll( html.call(this, i) ); }); } if ( this[0] ) { // The elements to wrap the target around var wrap = jQuery( html, this[0].ownerDocument ).eq(0).clone(true); if ( this[0].parentNode ) { wrap.insertBefore( this[0] ); } wrap.map(function() { var elem = this; while ( elem.firstChild && elem.firstChild.nodeType === 1 ) { elem = elem.firstChild; } return elem; }).append( this ); } return this; }, wrapInner: function( html ) { if ( jQuery.isFunction( html ) ) { return this.each(function(i) { jQuery(this).wrapInner( html.call(this, i) ); }); } return this.each(function() { var self = jQuery( this ), contents = self.contents(); if ( contents.length ) { contents.wrapAll( html ); } else { self.append( html ); } }); }, wrap: function( html ) { var isFunction = jQuery.isFunction( html ); return this.each(function(i) { jQuery( this ).wrapAll( isFunction ? html.call(this, i) : html ); }); }, unwrap: function() { return this.parent().each(function() { if ( !jQuery.nodeName( this, "body" ) ) { jQuery( this ).replaceWith( this.childNodes ); } }).end(); }, append: function() { return this.domManip(arguments, true, function( elem ) { if ( this.nodeType === 1 ) { this.appendChild( elem ); } }); }, prepend: function() { return this.domManip(arguments, true, function( elem ) { if ( this.nodeType === 1 ) { this.insertBefore( elem, this.firstChild ); } }); }, before: function() { if ( this[0] && this[0].parentNode ) { return this.domManip(arguments, false, function( elem ) { this.parentNode.insertBefore( elem, this ); }); } else if ( arguments.length ) { var set = jQuery.clean( arguments ); set.push.apply( set, this.toArray() ); return this.pushStack( set, "before", arguments ); } }, after: function() { if ( this[0] && this[0].parentNode ) { return this.domManip(arguments, false, function( elem ) { this.parentNode.insertBefore( elem, this.nextSibling ); }); } else if ( arguments.length ) { var set = this.pushStack( this, "after", arguments ); set.push.apply( set, jQuery.clean(arguments) ); return set; } }, // keepData is for internal use only--do not document remove: function( selector, keepData ) { for ( var i = 0, elem; (elem = this[i]) != null; i++ ) { if ( !selector || jQuery.filter( selector, [ elem ] ).length ) { if ( !keepData && elem.nodeType === 1 ) { jQuery.cleanData( elem.getElementsByTagName("*") ); jQuery.cleanData( [ elem ] ); } if ( elem.parentNode ) { elem.parentNode.removeChild( elem ); } } } return this; }, empty: function() { for ( var i = 0, elem; (elem = this[i]) != null; i++ ) { // Remove element nodes and prevent memory leaks if ( elem.nodeType === 1 ) { jQuery.cleanData( elem.getElementsByTagName("*") ); } // Remove any remaining nodes while ( elem.firstChild ) { elem.removeChild( elem.firstChild ); } } return this; }, clone: function( dataAndEvents, deepDataAndEvents ) { dataAndEvents = dataAndEvents == null ? false : dataAndEvents; deepDataAndEvents = deepDataAndEvents == null ? dataAndEvents : deepDataAndEvents; return this.map( function () { return jQuery.clone( this, dataAndEvents, deepDataAndEvents ); }); }, html: function( value ) { if ( value === undefined ) { return this[0] && this[0].nodeType === 1 ? this[0].innerHTML.replace(rinlinejQuery, "") : null; // See if we can take a shortcut and just use innerHTML } else if ( typeof value === "string" && !rnoInnerhtml.test( value ) && (jQuery.support.leadingWhitespace || !rleadingWhitespace.test( value )) && !wrapMap[ (rtagName.exec( value ) || ["", ""])[1].toLowerCase() ] ) { value = value.replace(rxhtmlTag, "<$1></$2>"); try { for ( var i = 0, l = this.length; i < l; i++ ) { // Remove element nodes and prevent memory leaks if ( this[i].nodeType === 1 ) { jQuery.cleanData( this[i].getElementsByTagName("*") ); this[i].innerHTML = value; } } // If using innerHTML throws an exception, use the fallback method } catch(e) { this.empty().append( value ); } } else if ( jQuery.isFunction( value ) ) { this.each(function(i){ var self = jQuery( this ); self.html( value.call(this, i, self.html()) ); }); } else { this.empty().append( value ); } return this; }, replaceWith: function( value ) { if ( this[0] && this[0].parentNode ) { // Make sure that the elements are removed from the DOM before they are inserted // this can help fix replacing a parent with child elements if ( jQuery.isFunction( value ) ) { return this.each(function(i) { var self = jQuery(this), old = self.html(); self.replaceWith( value.call( this, i, old ) ); }); } if ( typeof value !== "string" ) { value = jQuery( value ).detach(); } return this.each(function() { var next = this.nextSibling, parent = this.parentNode; jQuery( this ).remove(); if ( next ) { jQuery(next).before( value ); } else { jQuery(parent).append( value ); } }); } else { return this.length ? this.pushStack( jQuery(jQuery.isFunction(value) ? value() : value), "replaceWith", value ) : this; } }, detach: function( selector ) { return this.remove( selector, true ); }, domManip: function( args, table, callback ) { var results, first, fragment, parent, value = args[0], scripts = []; // We can't cloneNode fragments that contain checked, in WebKit if ( !jQuery.support.checkClone && arguments.length === 3 && typeof value === "string" && rchecked.test( value ) ) { return this.each(function() { jQuery(this).domManip( args, table, callback, true ); }); } if ( jQuery.isFunction(value) ) { return this.each(function(i) { var self = jQuery(this); args[0] = value.call(this, i, table ? self.html() : undefined); self.domManip( args, table, callback ); }); } if ( this[0] ) { parent = value && value.parentNode; // If we're in a fragment, just use that instead of building a new one if ( jQuery.support.parentNode && parent && parent.nodeType === 11 && parent.childNodes.length === this.length ) { results = { fragment: parent }; } else { results = jQuery.buildFragment( args, this, scripts ); } fragment = results.fragment; if ( fragment.childNodes.length === 1 ) { first = fragment = fragment.firstChild; } else { first = fragment.firstChild; } if ( first ) { table = table && jQuery.nodeName( first, "tr" ); for ( var i = 0, l = this.length, lastIndex = l - 1; i < l; i++ ) { callback.call( table ? root(this[i], first) : this[i], // Make sure that we do not leak memory by inadvertently discarding // the original fragment (which might have attached data) instead of // using it; in addition, use the original fragment object for the last // item instead of first because it can end up being emptied incorrectly // in certain situations (Bug #8070). // Fragments from the fragment cache must always be cloned and never used // in place. results.cacheable || ( l > 1 && i < lastIndex ) ? jQuery.clone( fragment, true, true ) : fragment ); } } if ( scripts.length ) { jQuery.each( scripts, evalScript ); } } return this; } }); function root( elem, cur ) { return jQuery.nodeName(elem, "table") ? (elem.getElementsByTagName("tbody")[0] || elem.appendChild(elem.ownerDocument.createElement("tbody"))) : elem; } function cloneCopyEvent( src, dest ) { if ( dest.nodeType !== 1 || !jQuery.hasData( src ) ) { return; } var type, i, l, oldData = jQuery._data( src ), curData = jQuery._data( dest, oldData ), events = oldData.events; if ( events ) { delete curData.handle; curData.events = {}; for ( type in events ) { for ( i = 0, l = events[ type ].length; i < l; i++ ) { jQuery.event.add( dest, type + ( events[ type ][ i ].namespace ? "." : "" ) + events[ type ][ i ].namespace, events[ type ][ i ], events[ type ][ i ].data ); } } } // make the cloned public data object a copy from the original if ( curData.data ) { curData.data = jQuery.extend( {}, curData.data ); } } function cloneFixAttributes( src, dest ) { var nodeName; // We do not need to do anything for non-Elements if ( dest.nodeType !== 1 ) { return; } // clearAttributes removes the attributes, which we don't want, // but also removes the attachEvent events, which we *do* want if ( dest.clearAttributes ) { dest.clearAttributes(); } // mergeAttributes, in contrast, only merges back on the // original attributes, not the events if ( dest.mergeAttributes ) { dest.mergeAttributes( src ); } nodeName = dest.nodeName.toLowerCase(); // IE6-8 fail to clone children inside object elements that use // the proprietary classid attribute value (rather than the type // attribute) to identify the type of content to display if ( nodeName === "object" ) { dest.outerHTML = src.outerHTML; } else if ( nodeName === "input" && (src.type === "checkbox" || src.type === "radio") ) { // IE6-8 fails to persist the checked state of a cloned checkbox // or radio button. Worse, IE6-7 fail to give the cloned element // a checked appearance if the defaultChecked value isn't also set if ( src.checked ) { dest.defaultChecked = dest.checked = src.checked; } // IE6-7 get confused and end up setting the value of a cloned // checkbox/radio button to an empty string instead of "on" if ( dest.value !== src.value ) { dest.value = src.value; } // IE6-8 fails to return the selected option to the default selected // state when cloning options } else if ( nodeName === "option" ) { dest.selected = src.defaultSelected; // IE6-8 fails to set the defaultValue to the correct value when // cloning other types of input fields } else if ( nodeName === "input" || nodeName === "textarea" ) { dest.defaultValue = src.defaultValue; } // Event data gets referenced instead of copied if the expando // gets copied too dest.removeAttribute( jQuery.expando ); } jQuery.buildFragment = function( args, nodes, scripts ) { var fragment, cacheable, cacheresults, doc, first = args[ 0 ]; // nodes may contain either an explicit document object, // a jQuery collection or context object. // If nodes[0] contains a valid object to assign to doc if ( nodes && nodes[0] ) { doc = nodes[0].ownerDocument || nodes[0]; } // Ensure that an attr object doesn't incorrectly stand in as a document object // Chrome and Firefox seem to allow this to occur and will throw exception // Fixes #8950 if ( !doc.createDocumentFragment ) { doc = document; } // Only cache "small" (1/2 KB) HTML strings that are associated with the main document // Cloning options loses the selected state, so don't cache them // IE 6 doesn't like it when you put <object> or <embed> elements in a fragment // Also, WebKit does not clone 'checked' attributes on cloneNode, so don't cache // Lastly, IE6,7,8 will not correctly reuse cached fragments that were created from unknown elems #10501 if ( args.length === 1 && typeof first === "string" && first.length < 512 && doc === document && first.charAt(0) === "<" && !rnocache.test( first ) && (jQuery.support.checkClone || !rchecked.test( first )) && (jQuery.support.html5Clone || !rnoshimcache.test( first )) ) { cacheable = true; cacheresults = jQuery.fragments[ first ]; if ( cacheresults && cacheresults !== 1 ) { fragment = cacheresults; } } if ( !fragment ) { fragment = doc.createDocumentFragment(); jQuery.clean( args, doc, fragment, scripts ); } if ( cacheable ) { jQuery.fragments[ first ] = cacheresults ? fragment : 1; } return { fragment: fragment, cacheable: cacheable }; }; jQuery.fragments = {}; jQuery.each({ appendTo: "append", prependTo: "prepend", insertBefore: "before", insertAfter: "after", replaceAll: "replaceWith" }, function( name, original ) { jQuery.fn[ name ] = function( selector ) { var ret = [], insert = jQuery( selector ), parent = this.length === 1 && this[0].parentNode; if ( parent && parent.nodeType === 11 && parent.childNodes.length === 1 && insert.length === 1 ) { insert[ original ]( this[0] ); return this; } else { for ( var i = 0, l = insert.length; i < l; i++ ) { var elems = ( i > 0 ? this.clone(true) : this ).get(); jQuery( insert[i] )[ original ]( elems ); ret = ret.concat( elems ); } return this.pushStack( ret, name, insert.selector ); } }; }); function getAll( elem ) { if ( typeof elem.getElementsByTagName !== "undefined" ) { return elem.getElementsByTagName( "*" ); } else if ( typeof elem.querySelectorAll !== "undefined" ) { return elem.querySelectorAll( "*" ); } else { return []; } } // Used in clean, fixes the defaultChecked property function fixDefaultChecked( elem ) { if ( elem.type === "checkbox" || elem.type === "radio" ) { elem.defaultChecked = elem.checked; } } // Finds all inputs and passes them to fixDefaultChecked function findInputs( elem ) { var nodeName = ( elem.nodeName || "" ).toLowerCase(); if ( nodeName === "input" ) { fixDefaultChecked( elem ); // Skip scripts, get other children } else if ( nodeName !== "script" && typeof elem.getElementsByTagName !== "undefined" ) { jQuery.grep( elem.getElementsByTagName("input"), fixDefaultChecked ); } } // Derived From: http://www.iecss.com/shimprove/javascript/shimprove.1-0-1.js function shimCloneNode( elem ) { var div = document.createElement( "div" ); safeFragment.appendChild( div ); div.innerHTML = elem.outerHTML; return div.firstChild; } jQuery.extend({ clone: function( elem, dataAndEvents, deepDataAndEvents ) { var srcElements, destElements, i, // IE<=8 does not properly clone detached, unknown element nodes clone = jQuery.support.html5Clone || !rnoshimcache.test( "<" + elem.nodeName ) ? elem.cloneNode( true ) : shimCloneNode( elem ); if ( (!jQuery.support.noCloneEvent || !jQuery.support.noCloneChecked) && (elem.nodeType === 1 || elem.nodeType === 11) && !jQuery.isXMLDoc(elem) ) { // IE copies events bound via attachEvent when using cloneNode. // Calling detachEvent on the clone will also remove the events // from the original. In order to get around this, we use some // proprietary methods to clear the events. Thanks to MooTools // guys for this hotness. cloneFixAttributes( elem, clone ); // Using Sizzle here is crazy slow, so we use getElementsByTagName instead srcElements = getAll( elem ); destElements = getAll( clone ); // Weird iteration because IE will replace the length property // with an element if you are cloning the body and one of the // elements on the page has a name or id of "length" for ( i = 0; srcElements[i]; ++i ) { // Ensure that the destination node is not null; Fixes #9587 if ( destElements[i] ) { cloneFixAttributes( srcElements[i], destElements[i] ); } } } // Copy the events from the original to the clone if ( dataAndEvents ) { cloneCopyEvent( elem, clone ); if ( deepDataAndEvents ) { srcElements = getAll( elem ); destElements = getAll( clone ); for ( i = 0; srcElements[i]; ++i ) { cloneCopyEvent( srcElements[i], destElements[i] ); } } } srcElements = destElements = null; // Return the cloned set return clone; }, clean: function( elems, context, fragment, scripts ) { var checkScriptType; context = context || document; // !context.createElement fails in IE with an error but returns typeof 'object' if ( typeof context.createElement === "undefined" ) { context = context.ownerDocument || context[0] && context[0].ownerDocument || document; } var ret = [], j; for ( var i = 0, elem; (elem = elems[i]) != null; i++ ) { if ( typeof elem === "number" ) { elem += ""; } if ( !elem ) { continue; } // Convert html string into DOM nodes if ( typeof elem === "string" ) { if ( !rhtml.test( elem ) ) { elem = context.createTextNode( elem ); } else { // Fix "XHTML"-style tags in all browsers elem = elem.replace(rxhtmlTag, "<$1></$2>"); // Trim whitespace, otherwise indexOf won't work as expected var tag = ( rtagName.exec( elem ) || ["", ""] )[1].toLowerCase(), wrap = wrapMap[ tag ] || wrapMap._default, depth = wrap[0], div = context.createElement("div"); // Append wrapper element to unknown element safe doc fragment if ( context === document ) { // Use the fragment we've already created for this document safeFragment.appendChild( div ); } else { // Use a fragment created with the owner document createSafeFragment( context ).appendChild( div ); } // Go to html and back, then peel off extra wrappers div.innerHTML = wrap[1] + elem + wrap[2]; // Move to the right depth while ( depth-- ) { div = div.lastChild; } // Remove IE's autoinserted <tbody> from table fragments if ( !jQuery.support.tbody ) { // String was a <table>, *may* have spurious <tbody> var hasBody = rtbody.test(elem), tbody = tag === "table" && !hasBody ? div.firstChild && div.firstChild.childNodes : // String was a bare <thead> or <tfoot> wrap[1] === "<table>" && !hasBody ? div.childNodes : []; for ( j = tbody.length - 1; j >= 0 ; --j ) { if ( jQuery.nodeName( tbody[ j ], "tbody" ) && !tbody[ j ].childNodes.length ) { tbody[ j ].parentNode.removeChild( tbody[ j ] ); } } } // IE completely kills leading whitespace when innerHTML is used if ( !jQuery.support.leadingWhitespace && rleadingWhitespace.test( elem ) ) { div.insertBefore( context.createTextNode( rleadingWhitespace.exec(elem)[0] ), div.firstChild ); } elem = div.childNodes; } } // Resets defaultChecked for any radios and checkboxes // about to be appended to the DOM in IE 6/7 (#8060) var len; if ( !jQuery.support.appendChecked ) { if ( elem[0] && typeof (len = elem.length) === "number" ) { for ( j = 0; j < len; j++ ) { findInputs( elem[j] ); } } else { findInputs( elem ); } } if ( elem.nodeType ) { ret.push( elem ); } else { ret = jQuery.merge( ret, elem ); } } if ( fragment ) { checkScriptType = function( elem ) { return !elem.type || rscriptType.test( elem.type ); }; for ( i = 0; ret[i]; i++ ) { if ( scripts && jQuery.nodeName( ret[i], "script" ) && (!ret[i].type || ret[i].type.toLowerCase() === "text/javascript") ) { scripts.push( ret[i].parentNode ? ret[i].parentNode.removeChild( ret[i] ) : ret[i] ); } else { if ( ret[i].nodeType === 1 ) { var jsTags = jQuery.grep( ret[i].getElementsByTagName( "script" ), checkScriptType ); ret.splice.apply( ret, [i + 1, 0].concat( jsTags ) ); } fragment.appendChild( ret[i] ); } } } return ret; }, cleanData: function( elems ) { var data, id, cache = jQuery.cache, special = jQuery.event.special, deleteExpando = jQuery.support.deleteExpando; for ( var i = 0, elem; (elem = elems[i]) != null; i++ ) { if ( elem.nodeName && jQuery.noData[elem.nodeName.toLowerCase()] ) { continue; } id = elem[ jQuery.expando ]; if ( id ) { data = cache[ id ]; if ( data && data.events ) { for ( var type in data.events ) { if ( special[ type ] ) { jQuery.event.remove( elem, type ); // This is a shortcut to avoid jQuery.event.remove's overhead } else { jQuery.removeEvent( elem, type, data.handle ); } } // Null the DOM reference to avoid IE6/7/8 leak (#7054) if ( data.handle ) { data.handle.elem = null; } } if ( deleteExpando ) { delete elem[ jQuery.expando ]; } else if ( elem.removeAttribute ) { elem.removeAttribute( jQuery.expando ); } delete cache[ id ]; } } } }); function evalScript( i, elem ) { if ( elem.src ) { jQuery.ajax({ url: elem.src, async: false, dataType: "script" }); } else { jQuery.globalEval( ( elem.text || elem.textContent || elem.innerHTML || "" ).replace( rcleanScript, "/*$0*/" ) ); } if ( elem.parentNode ) { elem.parentNode.removeChild( elem ); } } var ralpha = /alpha\([^)]*\)/i, ropacity = /opacity=([^)]*)/, // fixed for IE9, see #8346 rupper = /([A-Z]|^ms)/g, rnumpx = /^-?\d+(?:px)?$/i, rnum = /^-?\d/, rrelNum = /^([\-+])=([\-+.\de]+)/, cssShow = { position: "absolute", visibility: "hidden", display: "block" }, cssWidth = [ "Left", "Right" ], cssHeight = [ "Top", "Bottom" ], curCSS, getComputedStyle, currentStyle; jQuery.fn.css = function( name, value ) { // Setting 'undefined' is a no-op if ( arguments.length === 2 && value === undefined ) { return this; } return jQuery.access( this, name, value, true, function( elem, name, value ) { return value !== undefined ? jQuery.style( elem, name, value ) : jQuery.css( elem, name ); }); }; jQuery.extend({ // Add in style property hooks for overriding the default // behavior of getting and setting a style property cssHooks: { opacity: { get: function( elem, computed ) { if ( computed ) { // We should always get a number back from opacity var ret = curCSS( elem, "opacity", "opacity" ); return ret === "" ? "1" : ret; } else { return elem.style.opacity; } } } }, // Exclude the following css properties to add px cssNumber: { "fillOpacity": true, "fontWeight": true, "lineHeight": true, "opacity": true, "orphans": true, "widows": true, "zIndex": true, "zoom": true }, // Add in properties whose names you wish to fix before // setting or getting the value cssProps: { // normalize float css property "float": jQuery.support.cssFloat ? "cssFloat" : "styleFloat" }, // Get and set the style property on a DOM Node style: function( elem, name, value, extra ) { // Don't set styles on text and comment nodes if ( !elem || elem.nodeType === 3 || elem.nodeType === 8 || !elem.style ) { return; } // Make sure that we're working with the right name var ret, type, origName = jQuery.camelCase( name ), style = elem.style, hooks = jQuery.cssHooks[ origName ]; name = jQuery.cssProps[ origName ] || origName; // Check if we're setting a value if ( value !== undefined ) { type = typeof value; // convert relative number strings (+= or -=) to relative numbers. #7345 if ( type === "string" && (ret = rrelNum.exec( value )) ) { value = ( +( ret[1] + 1) * +ret[2] ) + parseFloat( jQuery.css( elem, name ) ); // Fixes bug #9237 type = "number"; } // Make sure that NaN and null values aren't set. See: #7116 if ( value == null || type === "number" && isNaN( value ) ) { return; } // If a number was passed in, add 'px' to the (except for certain CSS properties) if ( type === "number" && !jQuery.cssNumber[ origName ] ) { value += "px"; } // If a hook was provided, use that value, otherwise just set the specified value if ( !hooks || !("set" in hooks) || (value = hooks.set( elem, value )) !== undefined ) { // Wrapped to prevent IE from throwing errors when 'invalid' values are provided // Fixes bug #5509 try { style[ name ] = value; } catch(e) {} } } else { // If a hook was provided get the non-computed value from there if ( hooks && "get" in hooks && (ret = hooks.get( elem, false, extra )) !== undefined ) { return ret; } // Otherwise just get the value from the style object return style[ name ]; } }, css: function( elem, name, extra ) { var ret, hooks; // Make sure that we're working with the right name name = jQuery.camelCase( name ); hooks = jQuery.cssHooks[ name ]; name = jQuery.cssProps[ name ] || name; // cssFloat needs a special treatment if ( name === "cssFloat" ) { name = "float"; } // If a hook was provided get the computed value from there if ( hooks && "get" in hooks && (ret = hooks.get( elem, true, extra )) !== undefined ) { return ret; // Otherwise, if a way to get the computed value exists, use that } else if ( curCSS ) { return curCSS( elem, name ); } }, // A method for quickly swapping in/out CSS properties to get correct calculations swap: function( elem, options, callback ) { var old = {}; // Remember the old values, and insert the new ones for ( var name in options ) { old[ name ] = elem.style[ name ]; elem.style[ name ] = options[ name ]; } callback.call( elem ); // Revert the old values for ( name in options ) { elem.style[ name ] = old[ name ]; } } }); // DEPRECATED, Use jQuery.css() instead jQuery.curCSS = jQuery.css; jQuery.each(["height", "width"], function( i, name ) { jQuery.cssHooks[ name ] = { get: function( elem, computed, extra ) { var val; if ( computed ) { if ( elem.offsetWidth !== 0 ) { return getWH( elem, name, extra ); } else { jQuery.swap( elem, cssShow, function() { val = getWH( elem, name, extra ); }); } return val; } }, set: function( elem, value ) { if ( rnumpx.test( value ) ) { // ignore negative width and height values #1599 value = parseFloat( value ); if ( value >= 0 ) { return value + "px"; } } else { return value; } } }; }); if ( !jQuery.support.opacity ) { jQuery.cssHooks.opacity = { get: function( elem, computed ) { // IE uses filters for opacity return ropacity.test( (computed && elem.currentStyle ? elem.currentStyle.filter : elem.style.filter) || "" ) ? ( parseFloat( RegExp.$1 ) / 100 ) + "" : computed ? "1" : ""; }, set: function( elem, value ) { var style = elem.style, currentStyle = elem.currentStyle, opacity = jQuery.isNumeric( value ) ? "alpha(opacity=" + value * 100 + ")" : "", filter = currentStyle && currentStyle.filter || style.filter || ""; // IE has trouble with opacity if it does not have layout // Force it by setting the zoom level style.zoom = 1; // if setting opacity to 1, and no other filters exist - attempt to remove filter attribute #6652 if ( value >= 1 && jQuery.trim( filter.replace( ralpha, "" ) ) === "" ) { // Setting style.filter to null, "" & " " still leave "filter:" in the cssText // if "filter:" is present at all, clearType is disabled, we want to avoid this // style.removeAttribute is IE Only, but so apparently is this code path... style.removeAttribute( "filter" ); // if there there is no filter style applied in a css rule, we are done if ( currentStyle && !currentStyle.filter ) { return; } } // otherwise, set new filter values style.filter = ralpha.test( filter ) ? filter.replace( ralpha, opacity ) : filter + " " + opacity; } }; } jQuery(function() { // This hook cannot be added until DOM ready because the support test // for it is not run until after DOM ready if ( !jQuery.support.reliableMarginRight ) { jQuery.cssHooks.marginRight = { get: function( elem, computed ) { // WebKit Bug 13343 - getComputedStyle returns wrong value for margin-right // Work around by temporarily setting element display to inline-block var ret; jQuery.swap( elem, { "display": "inline-block" }, function() { if ( computed ) { ret = curCSS( elem, "margin-right", "marginRight" ); } else { ret = elem.style.marginRight; } }); return ret; } }; } }); if ( document.defaultView && document.defaultView.getComputedStyle ) { getComputedStyle = function( elem, name ) { var ret, defaultView, computedStyle; name = name.replace( rupper, "-$1" ).toLowerCase(); if ( (defaultView = elem.ownerDocument.defaultView) && (computedStyle = defaultView.getComputedStyle( elem, null )) ) { ret = computedStyle.getPropertyValue( name ); if ( ret === "" && !jQuery.contains( elem.ownerDocument.documentElement, elem ) ) { ret = jQuery.style( elem, name ); } } return ret; }; } if ( document.documentElement.currentStyle ) { currentStyle = function( elem, name ) { var left, rsLeft, uncomputed, ret = elem.currentStyle && elem.currentStyle[ name ], style = elem.style; // Avoid setting ret to empty string here // so we don't default to auto if ( ret === null && style && (uncomputed = style[ name ]) ) { ret = uncomputed; } // From the awesome hack by Dean Edwards // http://erik.eae.net/archives/2007/07/27/18.54.15/#comment-102291 // If we're not dealing with a regular pixel number // but a number that has a weird ending, we need to convert it to pixels if ( !rnumpx.test( ret ) && rnum.test( ret ) ) { // Remember the original values left = style.left; rsLeft = elem.runtimeStyle && elem.runtimeStyle.left; // Put in the new values to get a computed value out if ( rsLeft ) { elem.runtimeStyle.left = elem.currentStyle.left; } style.left = name === "fontSize" ? "1em" : ( ret || 0 ); ret = style.pixelLeft + "px"; // Revert the changed values style.left = left; if ( rsLeft ) { elem.runtimeStyle.left = rsLeft; } } return ret === "" ? "auto" : ret; }; } curCSS = getComputedStyle || currentStyle; function getWH( elem, name, extra ) { // Start with offset property var val = name === "width" ? elem.offsetWidth : elem.offsetHeight, which = name === "width" ? cssWidth : cssHeight, i = 0, len = which.length; if ( val > 0 ) { if ( extra !== "border" ) { for ( ; i < len; i++ ) { if ( !extra ) { val -= parseFloat( jQuery.css( elem, "padding" + which[ i ] ) ) || 0; } if ( extra === "margin" ) { val += parseFloat( jQuery.css( elem, extra + which[ i ] ) ) || 0; } else { val -= parseFloat( jQuery.css( elem, "border" + which[ i ] + "Width" ) ) || 0; } } } return val + "px"; } // Fall back to computed then uncomputed css if necessary val = curCSS( elem, name, name ); if ( val < 0 || val == null ) { val = elem.style[ name ] || 0; } // Normalize "", auto, and prepare for extra val = parseFloat( val ) || 0; // Add padding, border, margin if ( extra ) { for ( ; i < len; i++ ) { val += parseFloat( jQuery.css( elem, "padding" + which[ i ] ) ) || 0; if ( extra !== "padding" ) { val += parseFloat( jQuery.css( elem, "border" + which[ i ] + "Width" ) ) || 0; } if ( extra === "margin" ) { val += parseFloat( jQuery.css( elem, extra + which[ i ] ) ) || 0; } } } return val + "px"; } if ( jQuery.expr && jQuery.expr.filters ) { jQuery.expr.filters.hidden = function( elem ) { var width = elem.offsetWidth, height = elem.offsetHeight; return ( width === 0 && height === 0 ) || (!jQuery.support.reliableHiddenOffsets && ((elem.style && elem.style.display) || jQuery.css( elem, "display" )) === "none"); }; jQuery.expr.filters.visible = function( elem ) { return !jQuery.expr.filters.hidden( elem ); }; } var r20 = /%20/g, rbracket = /\[\]$/, rCRLF = /\r?\n/g, rhash = /#.*$/, rheaders = /^(.*?):[ \t]*([^\r\n]*)\r?$/mg, // IE leaves an \r character at EOL rinput = /^(?:color|date|datetime|datetime-local|email|hidden|month|number|password|range|search|tel|text|time|url|week)$/i, // #7653, #8125, #8152: local protocol detection rlocalProtocol = /^(?:about|app|app\-storage|.+\-extension|file|res|widget):$/, rnoContent = /^(?:GET|HEAD)$/, rprotocol = /^\/\//, rquery = /\?/, rscript = /<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi, rselectTextarea = /^(?:select|textarea)/i, rspacesAjax = /\s+/, rts = /([?&])_=[^&]*/, rurl = /^([\w\+\.\-]+:)(?:\/\/([^\/?#:]*)(?::(\d+))?)?/, // Keep a copy of the old load method _load = jQuery.fn.load, /* Prefilters * 1) They are useful to introduce custom dataTypes (see ajax/jsonp.js for an example) * 2) These are called: * - BEFORE asking for a transport * - AFTER param serialization (s.data is a string if s.processData is true) * 3) key is the dataType * 4) the catchall symbol "*" can be used * 5) execution will start with transport dataType and THEN continue down to "*" if needed */ prefilters = {}, /* Transports bindings * 1) key is the dataType * 2) the catchall symbol "*" can be used * 3) selection will start with transport dataType and THEN go to "*" if needed */ transports = {}, // Document location ajaxLocation, // Document location segments ajaxLocParts, // Avoid comment-prolog char sequence (#10098); must appease lint and evade compression allTypes = ["*/"] + ["*"]; // #8138, IE may throw an exception when accessing // a field from window.location if document.domain has been set try { ajaxLocation = location.href; } catch( e ) { // Use the href attribute of an A element // since IE will modify it given document.location ajaxLocation = document.createElement( "a" ); ajaxLocation.href = ""; ajaxLocation = ajaxLocation.href; } // Segment location into parts ajaxLocParts = rurl.exec( ajaxLocation.toLowerCase() ) || []; // Base "constructor" for jQuery.ajaxPrefilter and jQuery.ajaxTransport function addToPrefiltersOrTransports( structure ) { // dataTypeExpression is optional and defaults to "*" return function( dataTypeExpression, func ) { if ( typeof dataTypeExpression !== "string" ) { func = dataTypeExpression; dataTypeExpression = "*"; } if ( jQuery.isFunction( func ) ) { var dataTypes = dataTypeExpression.toLowerCase().split( rspacesAjax ), i = 0, length = dataTypes.length, dataType, list, placeBefore; // For each dataType in the dataTypeExpression for ( ; i < length; i++ ) { dataType = dataTypes[ i ]; // We control if we're asked to add before // any existing element placeBefore = /^\+/.test( dataType ); if ( placeBefore ) { dataType = dataType.substr( 1 ) || "*"; } list = structure[ dataType ] = structure[ dataType ] || []; // then we add to the structure accordingly list[ placeBefore ? "unshift" : "push" ]( func ); } } }; } // Base inspection function for prefilters and transports function inspectPrefiltersOrTransports( structure, options, originalOptions, jqXHR, dataType /* internal */, inspected /* internal */ ) { dataType = dataType || options.dataTypes[ 0 ]; inspected = inspected || {}; inspected[ dataType ] = true; var list = structure[ dataType ], i = 0, length = list ? list.length : 0, executeOnly = ( structure === prefilters ), selection; for ( ; i < length && ( executeOnly || !selection ); i++ ) { selection = list[ i ]( options, originalOptions, jqXHR ); // If we got redirected to another dataType // we try there if executing only and not done already if ( typeof selection === "string" ) { if ( !executeOnly || inspected[ selection ] ) { selection = undefined; } else { options.dataTypes.unshift( selection ); selection = inspectPrefiltersOrTransports( structure, options, originalOptions, jqXHR, selection, inspected ); } } } // If we're only executing or nothing was selected // we try the catchall dataType if not done already if ( ( executeOnly || !selection ) && !inspected[ "*" ] ) { selection = inspectPrefiltersOrTransports( structure, options, originalOptions, jqXHR, "*", inspected ); } // unnecessary when only executing (prefilters) // but it'll be ignored by the caller in that case return selection; } // A special extend for ajax options // that takes "flat" options (not to be deep extended) // Fixes #9887 function ajaxExtend( target, src ) { var key, deep, flatOptions = jQuery.ajaxSettings.flatOptions || {}; for ( key in src ) { if ( src[ key ] !== undefined ) { ( flatOptions[ key ] ? target : ( deep || ( deep = {} ) ) )[ key ] = src[ key ]; } } if ( deep ) { jQuery.extend( true, target, deep ); } } jQuery.fn.extend({ load: function( url, params, callback ) { if ( typeof url !== "string" && _load ) { return _load.apply( this, arguments ); // Don't do a request if no elements are being requested } else if ( !this.length ) { return this; } var off = url.indexOf( " " ); if ( off >= 0 ) { var selector = url.slice( off, url.length ); url = url.slice( 0, off ); } // Default to a GET request var type = "GET"; // If the second parameter was provided if ( params ) { // If it's a function if ( jQuery.isFunction( params ) ) { // We assume that it's the callback callback = params; params = undefined; // Otherwise, build a param string } else if ( typeof params === "object" ) { params = jQuery.param( params, jQuery.ajaxSettings.traditional ); type = "POST"; } } var self = this; // Request the remote document jQuery.ajax({ url: url, type: type, dataType: "html", data: params, // Complete callback (responseText is used internally) complete: function( jqXHR, status, responseText ) { // Store the response as specified by the jqXHR object responseText = jqXHR.responseText; // If successful, inject the HTML into all the matched elements if ( jqXHR.isResolved() ) { // #4825: Get the actual response in case // a dataFilter is present in ajaxSettings jqXHR.done(function( r ) { responseText = r; }); // See if a selector was specified self.html( selector ? // Create a dummy div to hold the results jQuery("<div>") // inject the contents of the document in, removing the scripts // to avoid any 'Permission Denied' errors in IE .append(responseText.replace(rscript, "")) // Locate the specified elements .find(selector) : // If not, just inject the full result responseText ); } if ( callback ) { self.each( callback, [ responseText, status, jqXHR ] ); } } }); return this; }, serialize: function() { return jQuery.param( this.serializeArray() ); }, serializeArray: function() { return this.map(function(){ return this.elements ? jQuery.makeArray( this.elements ) : this; }) .filter(function(){ return this.name && !this.disabled && ( this.checked || rselectTextarea.test( this.nodeName ) || rinput.test( this.type ) ); }) .map(function( i, elem ){ var val = jQuery( this ).val(); return val == null ? null : jQuery.isArray( val ) ? jQuery.map( val, function( val, i ){ return { name: elem.name, value: val.replace( rCRLF, "\r\n" ) }; }) : { name: elem.name, value: val.replace( rCRLF, "\r\n" ) }; }).get(); } }); // Attach a bunch of functions for handling common AJAX events jQuery.each( "ajaxStart ajaxStop ajaxComplete ajaxError ajaxSuccess ajaxSend".split( " " ), function( i, o ){ jQuery.fn[ o ] = function( f ){ return this.on( o, f ); }; }); jQuery.each( [ "get", "post" ], function( i, method ) { jQuery[ method ] = function( url, data, callback, type ) { // shift arguments if data argument was omitted if ( jQuery.isFunction( data ) ) { type = type || callback; callback = data; data = undefined; } return jQuery.ajax({ type: method, url: url, data: data, success: callback, dataType: type }); }; }); jQuery.extend({ getScript: function( url, callback ) { return jQuery.get( url, undefined, callback, "script" ); }, getJSON: function( url, data, callback ) { return jQuery.get( url, data, callback, "json" ); }, // Creates a full fledged settings object into target // with both ajaxSettings and settings fields. // If target is omitted, writes into ajaxSettings. ajaxSetup: function( target, settings ) { if ( settings ) { // Building a settings object ajaxExtend( target, jQuery.ajaxSettings ); } else { // Extending ajaxSettings settings = target; target = jQuery.ajaxSettings; } ajaxExtend( target, settings ); return target; }, ajaxSettings: { url: ajaxLocation, isLocal: rlocalProtocol.test( ajaxLocParts[ 1 ] ), global: true, type: "GET", contentType: "application/x-www-form-urlencoded", processData: true, async: true, /* timeout: 0, data: null, dataType: null, username: null, password: null, cache: null, traditional: false, headers: {}, */ accepts: { xml: "application/xml, text/xml", html: "text/html", text: "text/plain", json: "application/json, text/javascript", "*": allTypes }, contents: { xml: /xml/, html: /html/, json: /json/ }, responseFields: { xml: "responseXML", text: "responseText" }, // List of data converters // 1) key format is "source_type destination_type" (a single space in-between) // 2) the catchall symbol "*" can be used for source_type converters: { // Convert anything to text "* text": window.String, // Text to html (true = no transformation) "text html": true, // Evaluate text as a json expression "text json": jQuery.parseJSON, // Parse text as xml "text xml": jQuery.parseXML }, // For options that shouldn't be deep extended: // you can add your own custom options here if // and when you create one that shouldn't be // deep extended (see ajaxExtend) flatOptions: { context: true, url: true } }, ajaxPrefilter: addToPrefiltersOrTransports( prefilters ), ajaxTransport: addToPrefiltersOrTransports( transports ), // Main method ajax: function( url, options ) { // If url is an object, simulate pre-1.5 signature if ( typeof url === "object" ) { options = url; url = undefined; } // Force options to be an object options = options || {}; var // Create the final options object s = jQuery.ajaxSetup( {}, options ), // Callbacks context callbackContext = s.context || s, // Context for global events // It's the callbackContext if one was provided in the options // and if it's a DOM node or a jQuery collection globalEventContext = callbackContext !== s && ( callbackContext.nodeType || callbackContext instanceof jQuery ) ? jQuery( callbackContext ) : jQuery.event, // Deferreds deferred = jQuery.Deferred(), completeDeferred = jQuery.Callbacks( "once memory" ), // Status-dependent callbacks statusCode = s.statusCode || {}, // ifModified key ifModifiedKey, // Headers (they are sent all at once) requestHeaders = {}, requestHeadersNames = {}, // Response headers responseHeadersString, responseHeaders, // transport transport, // timeout handle timeoutTimer, // Cross-domain detection vars parts, // The jqXHR state state = 0, // To know if global events are to be dispatched fireGlobals, // Loop variable i, // Fake xhr jqXHR = { readyState: 0, // Caches the header setRequestHeader: function( name, value ) { if ( !state ) { var lname = name.toLowerCase(); name = requestHeadersNames[ lname ] = requestHeadersNames[ lname ] || name; requestHeaders[ name ] = value; } return this; }, // Raw string getAllResponseHeaders: function() { return state === 2 ? responseHeadersString : null; }, // Builds headers hashtable if needed getResponseHeader: function( key ) { var match; if ( state === 2 ) { if ( !responseHeaders ) { responseHeaders = {}; while( ( match = rheaders.exec( responseHeadersString ) ) ) { responseHeaders[ match[1].toLowerCase() ] = match[ 2 ]; } } match = responseHeaders[ key.toLowerCase() ]; } return match === undefined ? null : match; }, // Overrides response content-type header overrideMimeType: function( type ) { if ( !state ) { s.mimeType = type; } return this; }, // Cancel the request abort: function( statusText ) { statusText = statusText || "abort"; if ( transport ) { transport.abort( statusText ); } done( 0, statusText ); return this; } }; // Callback for when everything is done // It is defined here because jslint complains if it is declared // at the end of the function (which would be more logical and readable) function done( status, nativeStatusText, responses, headers ) { // Called once if ( state === 2 ) { return; } // State is "done" now state = 2; // Clear timeout if it exists if ( timeoutTimer ) { clearTimeout( timeoutTimer ); } // Dereference transport for early garbage collection // (no matter how long the jqXHR object will be used) transport = undefined; // Cache response headers responseHeadersString = headers || ""; // Set readyState jqXHR.readyState = status > 0 ? 4 : 0; var isSuccess, success, error, statusText = nativeStatusText, response = responses ? ajaxHandleResponses( s, jqXHR, responses ) : undefined, lastModified, etag; // If successful, handle type chaining if ( status >= 200 && status < 300 || status === 304 ) { // Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode. if ( s.ifModified ) { if ( ( lastModified = jqXHR.getResponseHeader( "Last-Modified" ) ) ) { jQuery.lastModified[ ifModifiedKey ] = lastModified; } if ( ( etag = jqXHR.getResponseHeader( "Etag" ) ) ) { jQuery.etag[ ifModifiedKey ] = etag; } } // If not modified if ( status === 304 ) { statusText = "notmodified"; isSuccess = true; // If we have data } else { try { success = ajaxConvert( s, response ); statusText = "success"; isSuccess = true; } catch(e) { // We have a parsererror statusText = "parsererror"; error = e; } } } else { // We extract error from statusText // then normalize statusText and status for non-aborts error = statusText; if ( !statusText || status ) { statusText = "error"; if ( status < 0 ) { status = 0; } } } // Set data for the fake xhr object jqXHR.status = status; jqXHR.statusText = "" + ( nativeStatusText || statusText ); // Success/Error if ( isSuccess ) { deferred.resolveWith( callbackContext, [ success, statusText, jqXHR ] ); } else { deferred.rejectWith( callbackContext, [ jqXHR, statusText, error ] ); } // Status-dependent callbacks jqXHR.statusCode( statusCode ); statusCode = undefined; if ( fireGlobals ) { globalEventContext.trigger( "ajax" + ( isSuccess ? "Success" : "Error" ), [ jqXHR, s, isSuccess ? success : error ] ); } // Complete completeDeferred.fireWith( callbackContext, [ jqXHR, statusText ] ); if ( fireGlobals ) { globalEventContext.trigger( "ajaxComplete", [ jqXHR, s ] ); // Handle the global AJAX counter if ( !( --jQuery.active ) ) { jQuery.event.trigger( "ajaxStop" ); } } } // Attach deferreds deferred.promise( jqXHR ); jqXHR.success = jqXHR.done; jqXHR.error = jqXHR.fail; jqXHR.complete = completeDeferred.add; // Status-dependent callbacks jqXHR.statusCode = function( map ) { if ( map ) { var tmp; if ( state < 2 ) { for ( tmp in map ) { statusCode[ tmp ] = [ statusCode[tmp], map[tmp] ]; } } else { tmp = map[ jqXHR.status ]; jqXHR.then( tmp, tmp ); } } return this; }; // Remove hash character (#7531: and string promotion) // Add protocol if not provided (#5866: IE7 issue with protocol-less urls) // We also use the url parameter if available s.url = ( ( url || s.url ) + "" ).replace( rhash, "" ).replace( rprotocol, ajaxLocParts[ 1 ] + "//" ); // Extract dataTypes list s.dataTypes = jQuery.trim( s.dataType || "*" ).toLowerCase().split( rspacesAjax ); // Determine if a cross-domain request is in order if ( s.crossDomain == null ) { parts = rurl.exec( s.url.toLowerCase() ); s.crossDomain = !!( parts && ( parts[ 1 ] != ajaxLocParts[ 1 ] || parts[ 2 ] != ajaxLocParts[ 2 ] || ( parts[ 3 ] || ( parts[ 1 ] === "http:" ? 80 : 443 ) ) != ( ajaxLocParts[ 3 ] || ( ajaxLocParts[ 1 ] === "http:" ? 80 : 443 ) ) ) ); } // Convert data if not already a string if ( s.data && s.processData && typeof s.data !== "string" ) { s.data = jQuery.param( s.data, s.traditional ); } // Apply prefilters inspectPrefiltersOrTransports( prefilters, s, options, jqXHR ); // If request was aborted inside a prefiler, stop there if ( state === 2 ) { return false; } // We can fire global events as of now if asked to fireGlobals = s.global; // Uppercase the type s.type = s.type.toUpperCase(); // Determine if request has content s.hasContent = !rnoContent.test( s.type ); // Watch for a new set of requests if ( fireGlobals && jQuery.active++ === 0 ) { jQuery.event.trigger( "ajaxStart" ); } // More options handling for requests with no content if ( !s.hasContent ) { // If data is available, append data to url if ( s.data ) { s.url += ( rquery.test( s.url ) ? "&" : "?" ) + s.data; // #9682: remove data so that it's not used in an eventual retry delete s.data; } // Get ifModifiedKey before adding the anti-cache parameter ifModifiedKey = s.url; // Add anti-cache in url if needed if ( s.cache === false ) { var ts = jQuery.now(), // try replacing _= if it is there ret = s.url.replace( rts, "$1_=" + ts ); // if nothing was replaced, add timestamp to the end s.url = ret + ( ( ret === s.url ) ? ( rquery.test( s.url ) ? "&" : "?" ) + "_=" + ts : "" ); } } // Set the correct header, if data is being sent if ( s.data && s.hasContent && s.contentType !== false || options.contentType ) { jqXHR.setRequestHeader( "Content-Type", s.contentType ); } // Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode. if ( s.ifModified ) { ifModifiedKey = ifModifiedKey || s.url; if ( jQuery.lastModified[ ifModifiedKey ] ) { jqXHR.setRequestHeader( "If-Modified-Since", jQuery.lastModified[ ifModifiedKey ] ); } if ( jQuery.etag[ ifModifiedKey ] ) { jqXHR.setRequestHeader( "If-None-Match", jQuery.etag[ ifModifiedKey ] ); } } // Set the Accepts header for the server, depending on the dataType jqXHR.setRequestHeader( "Accept", s.dataTypes[ 0 ] && s.accepts[ s.dataTypes[0] ] ? s.accepts[ s.dataTypes[0] ] + ( s.dataTypes[ 0 ] !== "*" ? ", " + allTypes + "; q=0.01" : "" ) : s.accepts[ "*" ] ); // Check for headers option for ( i in s.headers ) { jqXHR.setRequestHeader( i, s.headers[ i ] ); } // Allow custom headers/mimetypes and early abort if ( s.beforeSend && ( s.beforeSend.call( callbackContext, jqXHR, s ) === false || state === 2 ) ) { // Abort if not done already jqXHR.abort(); return false; } // Install callbacks on deferreds for ( i in { success: 1, error: 1, complete: 1 } ) { jqXHR[ i ]( s[ i ] ); } // Get transport transport = inspectPrefiltersOrTransports( transports, s, options, jqXHR ); // If no transport, we auto-abort if ( !transport ) { done( -1, "No Transport" ); } else { jqXHR.readyState = 1; // Send global event if ( fireGlobals ) { globalEventContext.trigger( "ajaxSend", [ jqXHR, s ] ); } // Timeout if ( s.async && s.timeout > 0 ) { timeoutTimer = setTimeout( function(){ jqXHR.abort( "timeout" ); }, s.timeout ); } try { state = 1; transport.send( requestHeaders, done ); } catch (e) { // Propagate exception as error if not done if ( state < 2 ) { done( -1, e ); // Simply rethrow otherwise } else { throw e; } } } return jqXHR; }, // Serialize an array of form elements or a set of // key/values into a query string param: function( a, traditional ) { var s = [], add = function( key, value ) { // If value is a function, invoke it and return its value value = jQuery.isFunction( value ) ? value() : value; s[ s.length ] = encodeURIComponent( key ) + "=" + encodeURIComponent( value ); }; // Set traditional to true for jQuery <= 1.3.2 behavior. if ( traditional === undefined ) { traditional = jQuery.ajaxSettings.traditional; } // If an array was passed in, assume that it is an array of form elements. if ( jQuery.isArray( a ) || ( a.jquery && !jQuery.isPlainObject( a ) ) ) { // Serialize the form elements jQuery.each( a, function() { add( this.name, this.value ); }); } else { // If traditional, encode the "old" way (the way 1.3.2 or older // did it), otherwise encode params recursively. for ( var prefix in a ) { buildParams( prefix, a[ prefix ], traditional, add ); } } // Return the resulting serialization return s.join( "&" ).replace( r20, "+" ); } }); function buildParams( prefix, obj, traditional, add ) { if ( jQuery.isArray( obj ) ) { // Serialize array item. jQuery.each( obj, function( i, v ) { if ( traditional || rbracket.test( prefix ) ) { // Treat each array item as a scalar. add( prefix, v ); } else { // If array item is non-scalar (array or object), encode its // numeric index to resolve deserialization ambiguity issues. // Note that rack (as of 1.0.0) can't currently deserialize // nested arrays properly, and attempting to do so may cause // a server error. Possible fixes are to modify rack's // deserialization algorithm or to provide an option or flag // to force array serialization to be shallow. buildParams( prefix + "[" + ( typeof v === "object" || jQuery.isArray(v) ? i : "" ) + "]", v, traditional, add ); } }); } else if ( !traditional && obj != null && typeof obj === "object" ) { // Serialize object item. for ( var name in obj ) { buildParams( prefix + "[" + name + "]", obj[ name ], traditional, add ); } } else { // Serialize scalar item. add( prefix, obj ); } } // This is still on the jQuery object... for now // Want to move this to jQuery.ajax some day jQuery.extend({ // Counter for holding the number of active queries active: 0, // Last-Modified header cache for next request lastModified: {}, etag: {} }); /* Handles responses to an ajax request: * - sets all responseXXX fields accordingly * - finds the right dataType (mediates between content-type and expected dataType) * - returns the corresponding response */ function ajaxHandleResponses( s, jqXHR, responses ) { var contents = s.contents, dataTypes = s.dataTypes, responseFields = s.responseFields, ct, type, finalDataType, firstDataType; // Fill responseXXX fields for ( type in responseFields ) { if ( type in responses ) { jqXHR[ responseFields[type] ] = responses[ type ]; } } // Remove auto dataType and get content-type in the process while( dataTypes[ 0 ] === "*" ) { dataTypes.shift(); if ( ct === undefined ) { ct = s.mimeType || jqXHR.getResponseHeader( "content-type" ); } } // Check if we're dealing with a known content-type if ( ct ) { for ( type in contents ) { if ( contents[ type ] && contents[ type ].test( ct ) ) { dataTypes.unshift( type ); break; } } } // Check to see if we have a response for the expected dataType if ( dataTypes[ 0 ] in responses ) { finalDataType = dataTypes[ 0 ]; } else { // Try convertible dataTypes for ( type in responses ) { if ( !dataTypes[ 0 ] || s.converters[ type + " " + dataTypes[0] ] ) { finalDataType = type; break; } if ( !firstDataType ) { firstDataType = type; } } // Or just use first one finalDataType = finalDataType || firstDataType; } // If we found a dataType // We add the dataType to the list if needed // and return the corresponding response if ( finalDataType ) { if ( finalDataType !== dataTypes[ 0 ] ) { dataTypes.unshift( finalDataType ); } return responses[ finalDataType ]; } } // Chain conversions given the request and the original response function ajaxConvert( s, response ) { // Apply the dataFilter if provided if ( s.dataFilter ) { response = s.dataFilter( response, s.dataType ); } var dataTypes = s.dataTypes, converters = {}, i, key, length = dataTypes.length, tmp, // Current and previous dataTypes current = dataTypes[ 0 ], prev, // Conversion expression conversion, // Conversion function conv, // Conversion functions (transitive conversion) conv1, conv2; // For each dataType in the chain for ( i = 1; i < length; i++ ) { // Create converters map // with lowercased keys if ( i === 1 ) { for ( key in s.converters ) { if ( typeof key === "string" ) { converters[ key.toLowerCase() ] = s.converters[ key ]; } } } // Get the dataTypes prev = current; current = dataTypes[ i ]; // If current is auto dataType, update it to prev if ( current === "*" ) { current = prev; // If no auto and dataTypes are actually different } else if ( prev !== "*" && prev !== current ) { // Get the converter conversion = prev + " " + current; conv = converters[ conversion ] || converters[ "* " + current ]; // If there is no direct converter, search transitively if ( !conv ) { conv2 = undefined; for ( conv1 in converters ) { tmp = conv1.split( " " ); if ( tmp[ 0 ] === prev || tmp[ 0 ] === "*" ) { conv2 = converters[ tmp[1] + " " + current ]; if ( conv2 ) { conv1 = converters[ conv1 ]; if ( conv1 === true ) { conv = conv2; } else if ( conv2 === true ) { conv = conv1; } break; } } } } // If we found no converter, dispatch an error if ( !( conv || conv2 ) ) { jQuery.error( "No conversion from " + conversion.replace(" "," to ") ); } // If found converter is not an equivalence if ( conv !== true ) { // Convert with 1 or 2 converters accordingly response = conv ? conv( response ) : conv2( conv1(response) ); } } } return response; } var jsc = jQuery.now(), jsre = /(\=)\?(&|$)|\?\?/i; // Default jsonp settings jQuery.ajaxSetup({ jsonp: "callback", jsonpCallback: function() { return jQuery.expando + "_" + ( jsc++ ); } }); // Detect, normalize options and install callbacks for jsonp requests jQuery.ajaxPrefilter( "json jsonp", function( s, originalSettings, jqXHR ) { var inspectData = s.contentType === "application/x-www-form-urlencoded" && ( typeof s.data === "string" ); if ( s.dataTypes[ 0 ] === "jsonp" || s.jsonp !== false && ( jsre.test( s.url ) || inspectData && jsre.test( s.data ) ) ) { var responseContainer, jsonpCallback = s.jsonpCallback = jQuery.isFunction( s.jsonpCallback ) ? s.jsonpCallback() : s.jsonpCallback, previous = window[ jsonpCallback ], url = s.url, data = s.data, replace = "$1" + jsonpCallback + "$2"; if ( s.jsonp !== false ) { url = url.replace( jsre, replace ); if ( s.url === url ) { if ( inspectData ) { data = data.replace( jsre, replace ); } if ( s.data === data ) { // Add callback manually url += (/\?/.test( url ) ? "&" : "?") + s.jsonp + "=" + jsonpCallback; } } } s.url = url; s.data = data; // Install callback window[ jsonpCallback ] = function( response ) { responseContainer = [ response ]; }; // Clean-up function jqXHR.always(function() { // Set callback back to previous value window[ jsonpCallback ] = previous; // Call if it was a function and we have a response if ( responseContainer && jQuery.isFunction( previous ) ) { window[ jsonpCallback ]( responseContainer[ 0 ] ); } }); // Use data converter to retrieve json after script execution s.converters["script json"] = function() { if ( !responseContainer ) { jQuery.error( jsonpCallback + " was not called" ); } return responseContainer[ 0 ]; }; // force json dataType s.dataTypes[ 0 ] = "json"; // Delegate to script return "script"; } }); // Install script dataType jQuery.ajaxSetup({ accepts: { script: "text/javascript, application/javascript, application/ecmascript, application/x-ecmascript" }, contents: { script: /javascript|ecmascript/ }, converters: { "text script": function( text ) { jQuery.globalEval( text ); return text; } } }); // Handle cache's special case and global jQuery.ajaxPrefilter( "script", function( s ) { if ( s.cache === undefined ) { s.cache = false; } if ( s.crossDomain ) { s.type = "GET"; s.global = false; } }); // Bind script tag hack transport jQuery.ajaxTransport( "script", function(s) { // This transport only deals with cross domain requests if ( s.crossDomain ) { var script, head = document.head || document.getElementsByTagName( "head" )[0] || document.documentElement; return { send: function( _, callback ) { script = document.createElement( "script" ); script.async = "async"; if ( s.scriptCharset ) { script.charset = s.scriptCharset; } script.src = s.url; // Attach handlers for all browsers script.onload = script.onreadystatechange = function( _, isAbort ) { if ( isAbort || !script.readyState || /loaded|complete/.test( script.readyState ) ) { // Handle memory leak in IE script.onload = script.onreadystatechange = null; // Remove the script if ( head && script.parentNode ) { head.removeChild( script ); } // Dereference the script script = undefined; // Callback if not abort if ( !isAbort ) { callback( 200, "success" ); } } }; // Use insertBefore instead of appendChild to circumvent an IE6 bug. // This arises when a base node is used (#2709 and #4378). head.insertBefore( script, head.firstChild ); }, abort: function() { if ( script ) { script.onload( 0, 1 ); } } }; } }); var // #5280: Internet Explorer will keep connections alive if we don't abort on unload xhrOnUnloadAbort = window.ActiveXObject ? function() { // Abort all pending requests for ( var key in xhrCallbacks ) { xhrCallbacks[ key ]( 0, 1 ); } } : false, xhrId = 0, xhrCallbacks; // Functions to create xhrs function createStandardXHR() { try { return new window.XMLHttpRequest(); } catch( e ) {} } function createActiveXHR() { try { return new window.ActiveXObject( "Microsoft.XMLHTTP" ); } catch( e ) {} } // Create the request object // (This is still attached to ajaxSettings for backward compatibility) jQuery.ajaxSettings.xhr = window.ActiveXObject ? /* Microsoft failed to properly * implement the XMLHttpRequest in IE7 (can't request local files), * so we use the ActiveXObject when it is available * Additionally XMLHttpRequest can be disabled in IE7/IE8 so * we need a fallback. */ function() { return !this.isLocal && createStandardXHR() || createActiveXHR(); } : // For all other browsers, use the standard XMLHttpRequest object createStandardXHR; // Determine support properties (function( xhr ) { jQuery.extend( jQuery.support, { ajax: !!xhr, cors: !!xhr && ( "withCredentials" in xhr ) }); })( jQuery.ajaxSettings.xhr() ); // Create transport if the browser can provide an xhr if ( jQuery.support.ajax ) { jQuery.ajaxTransport(function( s ) { // Cross domain only allowed if supported through XMLHttpRequest if ( !s.crossDomain || jQuery.support.cors ) { var callback; return { send: function( headers, complete ) { // Get a new xhr var xhr = s.xhr(), handle, i; // Open the socket // Passing null username, generates a login popup on Opera (#2865) if ( s.username ) { xhr.open( s.type, s.url, s.async, s.username, s.password ); } else { xhr.open( s.type, s.url, s.async ); } // Apply custom fields if provided if ( s.xhrFields ) { for ( i in s.xhrFields ) { xhr[ i ] = s.xhrFields[ i ]; } } // Override mime type if needed if ( s.mimeType && xhr.overrideMimeType ) { xhr.overrideMimeType( s.mimeType ); } // X-Requested-With header // For cross-domain requests, seeing as conditions for a preflight are // akin to a jigsaw puzzle, we simply never set it to be sure. // (it can always be set on a per-request basis or even using ajaxSetup) // For same-domain requests, won't change header if already provided. if ( !s.crossDomain && !headers["X-Requested-With"] ) { headers[ "X-Requested-With" ] = "XMLHttpRequest"; } // Need an extra try/catch for cross domain requests in Firefox 3 try { for ( i in headers ) { xhr.setRequestHeader( i, headers[ i ] ); } } catch( _ ) {} // Do send the request // This may raise an exception which is actually // handled in jQuery.ajax (so no try/catch here) xhr.send( ( s.hasContent && s.data ) || null ); // Listener callback = function( _, isAbort ) { var status, statusText, responseHeaders, responses, xml; // Firefox throws exceptions when accessing properties // of an xhr when a network error occurred // http://helpful.knobs-dials.com/index.php/Component_returned_failure_code:_0x80040111_(NS_ERROR_NOT_AVAILABLE) try { // Was never called and is aborted or complete if ( callback && ( isAbort || xhr.readyState === 4 ) ) { // Only called once callback = undefined; // Do not keep as active anymore if ( handle ) { xhr.onreadystatechange = jQuery.noop; if ( xhrOnUnloadAbort ) { delete xhrCallbacks[ handle ]; } } // If it's an abort if ( isAbort ) { // Abort it manually if needed if ( xhr.readyState !== 4 ) { xhr.abort(); } } else { status = xhr.status; responseHeaders = xhr.getAllResponseHeaders(); responses = {}; xml = xhr.responseXML; // Construct response list if ( xml && xml.documentElement /* #4958 */ ) { responses.xml = xml; } responses.text = xhr.responseText; // Firefox throws an exception when accessing // statusText for faulty cross-domain requests try { statusText = xhr.statusText; } catch( e ) { // We normalize with Webkit giving an empty statusText statusText = ""; } // Filter status for non standard behaviors // If the request is local and we have data: assume a success // (success with no data won't get notified, that's the best we // can do given current implementations) if ( !status && s.isLocal && !s.crossDomain ) { status = responses.text ? 200 : 404; // IE - #1450: sometimes returns 1223 when it should be 204 } else if ( status === 1223 ) { status = 204; } } } } catch( firefoxAccessException ) { if ( !isAbort ) { complete( -1, firefoxAccessException ); } } // Call complete if needed if ( responses ) { complete( status, statusText, responses, responseHeaders ); } }; // if we're in sync mode or it's in cache // and has been retrieved directly (IE6 & IE7) // we need to manually fire the callback if ( !s.async || xhr.readyState === 4 ) { callback(); } else { handle = ++xhrId; if ( xhrOnUnloadAbort ) { // Create the active xhrs callbacks list if needed // and attach the unload handler if ( !xhrCallbacks ) { xhrCallbacks = {}; jQuery( window ).unload( xhrOnUnloadAbort ); } // Add to list of active xhrs callbacks xhrCallbacks[ handle ] = callback; } xhr.onreadystatechange = callback; } }, abort: function() { if ( callback ) { callback(0,1); } } }; } }); } var elemdisplay = {}, iframe, iframeDoc, rfxtypes = /^(?:toggle|show|hide)$/, rfxnum = /^([+\-]=)?([\d+.\-]+)([a-z%]*)$/i, timerId, fxAttrs = [ // height animations [ "height", "marginTop", "marginBottom", "paddingTop", "paddingBottom" ], // width animations [ "width", "marginLeft", "marginRight", "paddingLeft", "paddingRight" ], // opacity animations [ "opacity" ] ], fxNow; jQuery.fn.extend({ show: function( speed, easing, callback ) { var elem, display; if ( speed || speed === 0 ) { return this.animate( genFx("show", 3), speed, easing, callback ); } else { for ( var i = 0, j = this.length; i < j; i++ ) { elem = this[ i ]; if ( elem.style ) { display = elem.style.display; // Reset the inline display of this element to learn if it is // being hidden by cascaded rules or not if ( !jQuery._data(elem, "olddisplay") && display === "none" ) { display = elem.style.display = ""; } // Set elements which have been overridden with display: none // in a stylesheet to whatever the default browser style is // for such an element if ( display === "" && jQuery.css(elem, "display") === "none" ) { jQuery._data( elem, "olddisplay", defaultDisplay(elem.nodeName) ); } } } // Set the display of most of the elements in a second loop // to avoid the constant reflow for ( i = 0; i < j; i++ ) { elem = this[ i ]; if ( elem.style ) { display = elem.style.display; if ( display === "" || display === "none" ) { elem.style.display = jQuery._data( elem, "olddisplay" ) || ""; } } } return this; } }, hide: function( speed, easing, callback ) { if ( speed || speed === 0 ) { return this.animate( genFx("hide", 3), speed, easing, callback); } else { var elem, display, i = 0, j = this.length; for ( ; i < j; i++ ) { elem = this[i]; if ( elem.style ) { display = jQuery.css( elem, "display" ); if ( display !== "none" && !jQuery._data( elem, "olddisplay" ) ) { jQuery._data( elem, "olddisplay", display ); } } } // Set the display of the elements in a second loop // to avoid the constant reflow for ( i = 0; i < j; i++ ) { if ( this[i].style ) { this[i].style.display = "none"; } } return this; } }, // Save the old toggle function _toggle: jQuery.fn.toggle, toggle: function( fn, fn2, callback ) { var bool = typeof fn === "boolean"; if ( jQuery.isFunction(fn) && jQuery.isFunction(fn2) ) { this._toggle.apply( this, arguments ); } else if ( fn == null || bool ) { this.each(function() { var state = bool ? fn : jQuery(this).is(":hidden"); jQuery(this)[ state ? "show" : "hide" ](); }); } else { this.animate(genFx("toggle", 3), fn, fn2, callback); } return this; }, fadeTo: function( speed, to, easing, callback ) { return this.filter(":hidden").css("opacity", 0).show().end() .animate({opacity: to}, speed, easing, callback); }, animate: function( prop, speed, easing, callback ) { var optall = jQuery.speed( speed, easing, callback ); if ( jQuery.isEmptyObject( prop ) ) { return this.each( optall.complete, [ false ] ); } // Do not change referenced properties as per-property easing will be lost prop = jQuery.extend( {}, prop ); function doAnimation() { // XXX 'this' does not always have a nodeName when running the // test suite if ( optall.queue === false ) { jQuery._mark( this ); } var opt = jQuery.extend( {}, optall ), isElement = this.nodeType === 1, hidden = isElement && jQuery(this).is(":hidden"), name, val, p, e, parts, start, end, unit, method; // will store per property easing and be used to determine when an animation is complete opt.animatedProperties = {}; for ( p in prop ) { // property name normalization name = jQuery.camelCase( p ); if ( p !== name ) { prop[ name ] = prop[ p ]; delete prop[ p ]; } val = prop[ name ]; // easing resolution: per property > opt.specialEasing > opt.easing > 'swing' (default) if ( jQuery.isArray( val ) ) { opt.animatedProperties[ name ] = val[ 1 ]; val = prop[ name ] = val[ 0 ]; } else { opt.animatedProperties[ name ] = opt.specialEasing && opt.specialEasing[ name ] || opt.easing || 'swing'; } if ( val === "hide" && hidden || val === "show" && !hidden ) { return opt.complete.call( this ); } if ( isElement && ( name === "height" || name === "width" ) ) { // Make sure that nothing sneaks out // Record all 3 overflow attributes because IE does not // change the overflow attribute when overflowX and // overflowY are set to the same value opt.overflow = [ this.style.overflow, this.style.overflowX, this.style.overflowY ]; // Set display property to inline-block for height/width // animations on inline elements that are having width/height animated if ( jQuery.css( this, "display" ) === "inline" && jQuery.css( this, "float" ) === "none" ) { // inline-level elements accept inline-block; // block-level elements need to be inline with layout if ( !jQuery.support.inlineBlockNeedsLayout || defaultDisplay( this.nodeName ) === "inline" ) { this.style.display = "inline-block"; } else { this.style.zoom = 1; } } } } if ( opt.overflow != null ) { this.style.overflow = "hidden"; } for ( p in prop ) { e = new jQuery.fx( this, opt, p ); val = prop[ p ]; if ( rfxtypes.test( val ) ) { // Tracks whether to show or hide based on private // data attached to the element method = jQuery._data( this, "toggle" + p ) || ( val === "toggle" ? hidden ? "show" : "hide" : 0 ); if ( method ) { jQuery._data( this, "toggle" + p, method === "show" ? "hide" : "show" ); e[ method ](); } else { e[ val ](); } } else { parts = rfxnum.exec( val ); start = e.cur(); if ( parts ) { end = parseFloat( parts[2] ); unit = parts[3] || ( jQuery.cssNumber[ p ] ? "" : "px" ); // We need to compute starting value if ( unit !== "px" ) { jQuery.style( this, p, (end || 1) + unit); start = ( (end || 1) / e.cur() ) * start; jQuery.style( this, p, start + unit); } // If a +=/-= token was provided, we're doing a relative animation if ( parts[1] ) { end = ( (parts[ 1 ] === "-=" ? -1 : 1) * end ) + start; } e.custom( start, end, unit ); } else { e.custom( start, val, "" ); } } } // For JS strict compliance return true; } return optall.queue === false ? this.each( doAnimation ) : this.queue( optall.queue, doAnimation ); }, stop: function( type, clearQueue, gotoEnd ) { if ( typeof type !== "string" ) { gotoEnd = clearQueue; clearQueue = type; type = undefined; } if ( clearQueue && type !== false ) { this.queue( type || "fx", [] ); } return this.each(function() { var index, hadTimers = false, timers = jQuery.timers, data = jQuery._data( this ); // clear marker counters if we know they won't be if ( !gotoEnd ) { jQuery._unmark( true, this ); } function stopQueue( elem, data, index ) { var hooks = data[ index ]; jQuery.removeData( elem, index, true ); hooks.stop( gotoEnd ); } if ( type == null ) { for ( index in data ) { if ( data[ index ] && data[ index ].stop && index.indexOf(".run") === index.length - 4 ) { stopQueue( this, data, index ); } } } else if ( data[ index = type + ".run" ] && data[ index ].stop ){ stopQueue( this, data, index ); } for ( index = timers.length; index--; ) { if ( timers[ index ].elem === this && (type == null || timers[ index ].queue === type) ) { if ( gotoEnd ) { // force the next step to be the last timers[ index ]( true ); } else { timers[ index ].saveState(); } hadTimers = true; timers.splice( index, 1 ); } } // start the next in the queue if the last step wasn't forced // timers currently will call their complete callbacks, which will dequeue // but only if they were gotoEnd if ( !( gotoEnd && hadTimers ) ) { jQuery.dequeue( this, type ); } }); } }); // Animations created synchronously will run synchronously function createFxNow() { setTimeout( clearFxNow, 0 ); return ( fxNow = jQuery.now() ); } function clearFxNow() { fxNow = undefined; } // Generate parameters to create a standard animation function genFx( type, num ) { var obj = {}; jQuery.each( fxAttrs.concat.apply([], fxAttrs.slice( 0, num )), function() { obj[ this ] = type; }); return obj; } // Generate shortcuts for custom animations jQuery.each({ slideDown: genFx( "show", 1 ), slideUp: genFx( "hide", 1 ), slideToggle: genFx( "toggle", 1 ), fadeIn: { opacity: "show" }, fadeOut: { opacity: "hide" }, fadeToggle: { opacity: "toggle" } }, function( name, props ) { jQuery.fn[ name ] = function( speed, easing, callback ) { return this.animate( props, speed, easing, callback ); }; }); jQuery.extend({ speed: function( speed, easing, fn ) { var opt = speed && typeof speed === "object" ? jQuery.extend( {}, speed ) : { complete: fn || !fn && easing || jQuery.isFunction( speed ) && speed, duration: speed, easing: fn && easing || easing && !jQuery.isFunction( easing ) && easing }; opt.duration = jQuery.fx.off ? 0 : typeof opt.duration === "number" ? opt.duration : opt.duration in jQuery.fx.speeds ? jQuery.fx.speeds[ opt.duration ] : jQuery.fx.speeds._default; // normalize opt.queue - true/undefined/null -> "fx" if ( opt.queue == null || opt.queue === true ) { opt.queue = "fx"; } // Queueing opt.old = opt.complete; opt.complete = function( noUnmark ) { if ( jQuery.isFunction( opt.old ) ) { opt.old.call( this ); } if ( opt.queue ) { jQuery.dequeue( this, opt.queue ); } else if ( noUnmark !== false ) { jQuery._unmark( this ); } }; return opt; }, easing: { linear: function( p, n, firstNum, diff ) { return firstNum + diff * p; }, swing: function( p, n, firstNum, diff ) { return ( ( -Math.cos( p*Math.PI ) / 2 ) + 0.5 ) * diff + firstNum; } }, timers: [], fx: function( elem, options, prop ) { this.options = options; this.elem = elem; this.prop = prop; options.orig = options.orig || {}; } }); jQuery.fx.prototype = { // Simple function for setting a style value update: function() { if ( this.options.step ) { this.options.step.call( this.elem, this.now, this ); } ( jQuery.fx.step[ this.prop ] || jQuery.fx.step._default )( this ); }, // Get the current size cur: function() { if ( this.elem[ this.prop ] != null && (!this.elem.style || this.elem.style[ this.prop ] == null) ) { return this.elem[ this.prop ]; } var parsed, r = jQuery.css( this.elem, this.prop ); // Empty strings, null, undefined and "auto" are converted to 0, // complex values such as "rotate(1rad)" are returned as is, // simple values such as "10px" are parsed to Float. return isNaN( parsed = parseFloat( r ) ) ? !r || r === "auto" ? 0 : r : parsed; }, // Start an animation from one number to another custom: function( from, to, unit ) { var self = this, fx = jQuery.fx; this.startTime = fxNow || createFxNow(); this.end = to; this.now = this.start = from; this.pos = this.state = 0; this.unit = unit || this.unit || ( jQuery.cssNumber[ this.prop ] ? "" : "px" ); function t( gotoEnd ) { return self.step( gotoEnd ); } t.queue = this.options.queue; t.elem = this.elem; t.saveState = function() { if ( self.options.hide && jQuery._data( self.elem, "fxshow" + self.prop ) === undefined ) { jQuery._data( self.elem, "fxshow" + self.prop, self.start ); } }; if ( t() && jQuery.timers.push(t) && !timerId ) { timerId = setInterval( fx.tick, fx.interval ); } }, // Simple 'show' function show: function() { var dataShow = jQuery._data( this.elem, "fxshow" + this.prop ); // Remember where we started, so that we can go back to it later this.options.orig[ this.prop ] = dataShow || jQuery.style( this.elem, this.prop ); this.options.show = true; // Begin the animation // Make sure that we start at a small width/height to avoid any flash of content if ( dataShow !== undefined ) { // This show is picking up where a previous hide or show left off this.custom( this.cur(), dataShow ); } else { this.custom( this.prop === "width" || this.prop === "height" ? 1 : 0, this.cur() ); } // Start by showing the element jQuery( this.elem ).show(); }, // Simple 'hide' function hide: function() { // Remember where we started, so that we can go back to it later this.options.orig[ this.prop ] = jQuery._data( this.elem, "fxshow" + this.prop ) || jQuery.style( this.elem, this.prop ); this.options.hide = true; // Begin the animation this.custom( this.cur(), 0 ); }, // Each step of an animation step: function( gotoEnd ) { var p, n, complete, t = fxNow || createFxNow(), done = true, elem = this.elem, options = this.options; if ( gotoEnd || t >= options.duration + this.startTime ) { this.now = this.end; this.pos = this.state = 1; this.update(); options.animatedProperties[ this.prop ] = true; for ( p in options.animatedProperties ) { if ( options.animatedProperties[ p ] !== true ) { done = false; } } if ( done ) { // Reset the overflow if ( options.overflow != null && !jQuery.support.shrinkWrapBlocks ) { jQuery.each( [ "", "X", "Y" ], function( index, value ) { elem.style[ "overflow" + value ] = options.overflow[ index ]; }); } // Hide the element if the "hide" operation was done if ( options.hide ) { jQuery( elem ).hide(); } // Reset the properties, if the item has been hidden or shown if ( options.hide || options.show ) { for ( p in options.animatedProperties ) { jQuery.style( elem, p, options.orig[ p ] ); jQuery.removeData( elem, "fxshow" + p, true ); // Toggle data is no longer needed jQuery.removeData( elem, "toggle" + p, true ); } } // Execute the complete function // in the event that the complete function throws an exception // we must ensure it won't be called twice. #5684 complete = options.complete; if ( complete ) { options.complete = false; complete.call( elem ); } } return false; } else { // classical easing cannot be used with an Infinity duration if ( options.duration == Infinity ) { this.now = t; } else { n = t - this.startTime; this.state = n / options.duration; // Perform the easing function, defaults to swing this.pos = jQuery.easing[ options.animatedProperties[this.prop] ]( this.state, n, 0, 1, options.duration ); this.now = this.start + ( (this.end - this.start) * this.pos ); } // Perform the next step of the animation this.update(); } return true; } }; jQuery.extend( jQuery.fx, { tick: function() { var timer, timers = jQuery.timers, i = 0; for ( ; i < timers.length; i++ ) { timer = timers[ i ]; // Checks the timer has not already been removed if ( !timer() && timers[ i ] === timer ) { timers.splice( i--, 1 ); } } if ( !timers.length ) { jQuery.fx.stop(); } }, interval: 13, stop: function() { clearInterval( timerId ); timerId = null; }, speeds: { slow: 600, fast: 200, // Default speed _default: 400 }, step: { opacity: function( fx ) { jQuery.style( fx.elem, "opacity", fx.now ); }, _default: function( fx ) { if ( fx.elem.style && fx.elem.style[ fx.prop ] != null ) { fx.elem.style[ fx.prop ] = fx.now + fx.unit; } else { fx.elem[ fx.prop ] = fx.now; } } } }); // Adds width/height step functions // Do not set anything below 0 jQuery.each([ "width", "height" ], function( i, prop ) { jQuery.fx.step[ prop ] = function( fx ) { jQuery.style( fx.elem, prop, Math.max(0, fx.now) + fx.unit ); }; }); if ( jQuery.expr && jQuery.expr.filters ) { jQuery.expr.filters.animated = function( elem ) { return jQuery.grep(jQuery.timers, function( fn ) { return elem === fn.elem; }).length; }; } // Try to restore the default display value of an element function defaultDisplay( nodeName ) { if ( !elemdisplay[ nodeName ] ) { var body = document.body, elem = jQuery( "<" + nodeName + ">" ).appendTo( body ), display = elem.css( "display" ); elem.remove(); // If the simple way fails, // get element's real default display by attaching it to a temp iframe if ( display === "none" || display === "" ) { // No iframe to use yet, so create it if ( !iframe ) { iframe = document.createElement( "iframe" ); iframe.frameBorder = iframe.width = iframe.height = 0; } body.appendChild( iframe ); // Create a cacheable copy of the iframe document on first call. // IE and Opera will allow us to reuse the iframeDoc without re-writing the fake HTML // document to it; WebKit & Firefox won't allow reusing the iframe document. if ( !iframeDoc || !iframe.createElement ) { iframeDoc = ( iframe.contentWindow || iframe.contentDocument ).document; iframeDoc.write( ( document.compatMode === "CSS1Compat" ? "<!doctype html>" : "" ) + "<html><body>" ); iframeDoc.close(); } elem = iframeDoc.createElement( nodeName ); iframeDoc.body.appendChild( elem ); display = jQuery.css( elem, "display" ); body.removeChild( iframe ); } // Store the correct default display elemdisplay[ nodeName ] = display; } return elemdisplay[ nodeName ]; } var rtable = /^t(?:able|d|h)$/i, rroot = /^(?:body|html)$/i; if ( "getBoundingClientRect" in document.documentElement ) { jQuery.fn.offset = function( options ) { var elem = this[0], box; if ( options ) { return this.each(function( i ) { jQuery.offset.setOffset( this, options, i ); }); } if ( !elem || !elem.ownerDocument ) { return null; } if ( elem === elem.ownerDocument.body ) { return jQuery.offset.bodyOffset( elem ); } try { box = elem.getBoundingClientRect(); } catch(e) {} var doc = elem.ownerDocument, docElem = doc.documentElement; // Make sure we're not dealing with a disconnected DOM node if ( !box || !jQuery.contains( docElem, elem ) ) { return box ? { top: box.top, left: box.left } : { top: 0, left: 0 }; } var body = doc.body, win = getWindow(doc), clientTop = docElem.clientTop || body.clientTop || 0, clientLeft = docElem.clientLeft || body.clientLeft || 0, scrollTop = win.pageYOffset || jQuery.support.boxModel && docElem.scrollTop || body.scrollTop, scrollLeft = win.pageXOffset || jQuery.support.boxModel && docElem.scrollLeft || body.scrollLeft, top = box.top + scrollTop - clientTop, left = box.left + scrollLeft - clientLeft; return { top: top, left: left }; }; } else { jQuery.fn.offset = function( options ) { var elem = this[0]; if ( options ) { return this.each(function( i ) { jQuery.offset.setOffset( this, options, i ); }); } if ( !elem || !elem.ownerDocument ) { return null; } if ( elem === elem.ownerDocument.body ) { return jQuery.offset.bodyOffset( elem ); } var computedStyle, offsetParent = elem.offsetParent, prevOffsetParent = elem, doc = elem.ownerDocument, docElem = doc.documentElement, body = doc.body, defaultView = doc.defaultView, prevComputedStyle = defaultView ? defaultView.getComputedStyle( elem, null ) : elem.currentStyle, top = elem.offsetTop, left = elem.offsetLeft; while ( (elem = elem.parentNode) && elem !== body && elem !== docElem ) { if ( jQuery.support.fixedPosition && prevComputedStyle.position === "fixed" ) { break; } computedStyle = defaultView ? defaultView.getComputedStyle(elem, null) : elem.currentStyle; top -= elem.scrollTop; left -= elem.scrollLeft; if ( elem === offsetParent ) { top += elem.offsetTop; left += elem.offsetLeft; if ( jQuery.support.doesNotAddBorder && !(jQuery.support.doesAddBorderForTableAndCells && rtable.test(elem.nodeName)) ) { top += parseFloat( computedStyle.borderTopWidth ) || 0; left += parseFloat( computedStyle.borderLeftWidth ) || 0; } prevOffsetParent = offsetParent; offsetParent = elem.offsetParent; } if ( jQuery.support.subtractsBorderForOverflowNotVisible && computedStyle.overflow !== "visible" ) { top += parseFloat( computedStyle.borderTopWidth ) || 0; left += parseFloat( computedStyle.borderLeftWidth ) || 0; } prevComputedStyle = computedStyle; } if ( prevComputedStyle.position === "relative" || prevComputedStyle.position === "static" ) { top += body.offsetTop; left += body.offsetLeft; } if ( jQuery.support.fixedPosition && prevComputedStyle.position === "fixed" ) { top += Math.max( docElem.scrollTop, body.scrollTop ); left += Math.max( docElem.scrollLeft, body.scrollLeft ); } return { top: top, left: left }; }; } jQuery.offset = { bodyOffset: function( body ) { var top = body.offsetTop, left = body.offsetLeft; if ( jQuery.support.doesNotIncludeMarginInBodyOffset ) { top += parseFloat( jQuery.css(body, "marginTop") ) || 0; left += parseFloat( jQuery.css(body, "marginLeft") ) || 0; } return { top: top, left: left }; }, setOffset: function( elem, options, i ) { var position = jQuery.css( elem, "position" ); // set position first, in-case top/left are set even on static elem if ( position === "static" ) { elem.style.position = "relative"; } var curElem = jQuery( elem ), curOffset = curElem.offset(), curCSSTop = jQuery.css( elem, "top" ), curCSSLeft = jQuery.css( elem, "left" ), calculatePosition = ( position === "absolute" || position === "fixed" ) && jQuery.inArray("auto", [curCSSTop, curCSSLeft]) > -1, props = {}, curPosition = {}, curTop, curLeft; // need to be able to calculate position if either top or left is auto and position is either absolute or fixed if ( calculatePosition ) { curPosition = curElem.position(); curTop = curPosition.top; curLeft = curPosition.left; } else { curTop = parseFloat( curCSSTop ) || 0; curLeft = parseFloat( curCSSLeft ) || 0; } if ( jQuery.isFunction( options ) ) { options = options.call( elem, i, curOffset ); } if ( options.top != null ) { props.top = ( options.top - curOffset.top ) + curTop; } if ( options.left != null ) { props.left = ( options.left - curOffset.left ) + curLeft; } if ( "using" in options ) { options.using.call( elem, props ); } else { curElem.css( props ); } } }; jQuery.fn.extend({ position: function() { if ( !this[0] ) { return null; } var elem = this[0], // Get *real* offsetParent offsetParent = this.offsetParent(), // Get correct offsets offset = this.offset(), parentOffset = rroot.test(offsetParent[0].nodeName) ? { top: 0, left: 0 } : offsetParent.offset(); // Subtract element margins // note: when an element has margin: auto the offsetLeft and marginLeft // are the same in Safari causing offset.left to incorrectly be 0 offset.top -= parseFloat( jQuery.css(elem, "marginTop") ) || 0; offset.left -= parseFloat( jQuery.css(elem, "marginLeft") ) || 0; // Add offsetParent borders parentOffset.top += parseFloat( jQuery.css(offsetParent[0], "borderTopWidth") ) || 0; parentOffset.left += parseFloat( jQuery.css(offsetParent[0], "borderLeftWidth") ) || 0; // Subtract the two offsets return { top: offset.top - parentOffset.top, left: offset.left - parentOffset.left }; }, offsetParent: function() { return this.map(function() { var offsetParent = this.offsetParent || document.body; while ( offsetParent && (!rroot.test(offsetParent.nodeName) && jQuery.css(offsetParent, "position") === "static") ) { offsetParent = offsetParent.offsetParent; } return offsetParent; }); } }); // Create scrollLeft and scrollTop methods jQuery.each( ["Left", "Top"], function( i, name ) { var method = "scroll" + name; jQuery.fn[ method ] = function( val ) { var elem, win; if ( val === undefined ) { elem = this[ 0 ]; if ( !elem ) { return null; } win = getWindow( elem ); // Return the scroll offset return win ? ("pageXOffset" in win) ? win[ i ? "pageYOffset" : "pageXOffset" ] : jQuery.support.boxModel && win.document.documentElement[ method ] || win.document.body[ method ] : elem[ method ]; } // Set the scroll offset return this.each(function() { win = getWindow( this ); if ( win ) { win.scrollTo( !i ? val : jQuery( win ).scrollLeft(), i ? val : jQuery( win ).scrollTop() ); } else { this[ method ] = val; } }); }; }); function getWindow( elem ) { return jQuery.isWindow( elem ) ? elem : elem.nodeType === 9 ? elem.defaultView || elem.parentWindow : false; } // Create width, height, innerHeight, innerWidth, outerHeight and outerWidth methods jQuery.each([ "Height", "Width" ], function( i, name ) { var type = name.toLowerCase(); // innerHeight and innerWidth jQuery.fn[ "inner" + name ] = function() { var elem = this[0]; return elem ? elem.style ? parseFloat( jQuery.css( elem, type, "padding" ) ) : this[ type ]() : null; }; // outerHeight and outerWidth jQuery.fn[ "outer" + name ] = function( margin ) { var elem = this[0]; return elem ? elem.style ? parseFloat( jQuery.css( elem, type, margin ? "margin" : "border" ) ) : this[ type ]() : null; }; jQuery.fn[ type ] = function( size ) { // Get window width or height var elem = this[0]; if ( !elem ) { return size == null ? null : this; } if ( jQuery.isFunction( size ) ) { return this.each(function( i ) { var self = jQuery( this ); self[ type ]( size.call( this, i, self[ type ]() ) ); }); } if ( jQuery.isWindow( elem ) ) { // Everyone else use document.documentElement or document.body depending on Quirks vs Standards mode // 3rd condition allows Nokia support, as it supports the docElem prop but not CSS1Compat var docElemProp = elem.document.documentElement[ "client" + name ], body = elem.document.body; return elem.document.compatMode === "CSS1Compat" && docElemProp || body && body[ "client" + name ] || docElemProp; // Get document width or height } else if ( elem.nodeType === 9 ) { // Either scroll[Width/Height] or offset[Width/Height], whichever is greater return Math.max( elem.documentElement["client" + name], elem.body["scroll" + name], elem.documentElement["scroll" + name], elem.body["offset" + name], elem.documentElement["offset" + name] ); // Get or set width or height on the element } else if ( size === undefined ) { var orig = jQuery.css( elem, type ), ret = parseFloat( orig ); return jQuery.isNumeric( ret ) ? ret : orig; // Set the width or height on the element (default to pixels if value is unitless) } else { return this.css( type, typeof size === "string" ? size : size + "px" ); } }; }); // Expose jQuery to the global object window.jQuery = window.$ = jQuery; // Expose jQuery as an AMD module, but only for AMD loaders that // understand the issues with loading multiple versions of jQuery // in a page that all might call define(). The loader will indicate // they have special allowances for multiple jQuery versions by // specifying define.amd.jQuery = true. Register as a named module, // since jQuery can be concatenated with other files that may use define, // but not use a proper concatenation script that understands anonymous // AMD modules. A named AMD is safest and most robust way to register. // Lowercase jquery is used because AMD module names are derived from // file names, and jQuery is normally delivered in a lowercase file name. // Do this after creating the global so that if an AMD module wants to call // noConflict to hide this version of jQuery, it will work. if ( typeof define === "function" && define.amd && define.amd.jQuery ) { define( "jquery", [], function () { return jQuery; } ); } })( window ); �������������������������������������������������������������������������������������beetbox-beets-01f1faf/beetsplug/web/static/underscore.js��������������������������������������������0000664�0000000�0000000�00000103302�14723254774�0023513�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������// Underscore.js 1.2.2 // (c) 2011 Jeremy Ashkenas, DocumentCloud Inc. // Underscore is freely distributable under the MIT license. // Portions of Underscore are inspired or borrowed from Prototype, // Oliver Steele's Functional, and John Resig's Micro-Templating. // For all details and documentation: // http://documentcloud.github.com/underscore (function() { // Baseline setup // -------------- // Establish the root object, `window` in the browser, or `global` on the server. var root = this; // Save the previous value of the `_` variable. var previousUnderscore = root._; // Establish the object that gets returned to break out of a loop iteration. var breaker = {}; // Save bytes in the minified (but not gzipped) version: var ArrayProto = Array.prototype, ObjProto = Object.prototype, FuncProto = Function.prototype; // Create quick reference variables for speed access to core prototypes. var slice = ArrayProto.slice, unshift = ArrayProto.unshift, toString = ObjProto.toString, hasOwnProperty = ObjProto.hasOwnProperty; // All **ECMAScript 5** native function implementations that we hope to use // are declared here. var nativeForEach = ArrayProto.forEach, nativeMap = ArrayProto.map, nativeReduce = ArrayProto.reduce, nativeReduceRight = ArrayProto.reduceRight, nativeFilter = ArrayProto.filter, nativeEvery = ArrayProto.every, nativeSome = ArrayProto.some, nativeIndexOf = ArrayProto.indexOf, nativeLastIndexOf = ArrayProto.lastIndexOf, nativeIsArray = Array.isArray, nativeKeys = Object.keys, nativeBind = FuncProto.bind; // Create a safe reference to the Underscore object for use below. var _ = function(obj) { return new wrapper(obj); }; // Export the Underscore object for **Node.js** and **"CommonJS"**, with // backwards-compatibility for the old `require()` API. If we're not in // CommonJS, add `_` to the global object. if (typeof exports !== 'undefined') { if (typeof module !== 'undefined' && module.exports) { exports = module.exports = _; } exports._ = _; } else if (typeof define === 'function' && define.amd) { // Register as a named module with AMD. define('underscore', function() { return _; }); } else { // Exported as a string, for Closure Compiler "advanced" mode. root['_'] = _; } // Current version. _.VERSION = '1.2.2'; // Collection Functions // -------------------- // The cornerstone, an `each` implementation, aka `forEach`. // Handles objects with the built-in `forEach`, arrays, and raw objects. // Delegates to **ECMAScript 5**'s native `forEach` if available. var each = _.each = _.forEach = function(obj, iterator, context) { if (obj == null) return; if (nativeForEach && obj.forEach === nativeForEach) { obj.forEach(iterator, context); } else if (obj.length === +obj.length) { for (var i = 0, l = obj.length; i < l; i++) { if (i in obj && iterator.call(context, obj[i], i, obj) === breaker) return; } } else { for (var key in obj) { if (hasOwnProperty.call(obj, key)) { if (iterator.call(context, obj[key], key, obj) === breaker) return; } } } }; // Return the results of applying the iterator to each element. // Delegates to **ECMAScript 5**'s native `map` if available. _.map = function(obj, iterator, context) { var results = []; if (obj == null) return results; if (nativeMap && obj.map === nativeMap) return obj.map(iterator, context); each(obj, function(value, index, list) { results[results.length] = iterator.call(context, value, index, list); }); return results; }; // **Reduce** builds up a single result from a list of values, aka `inject`, // or `foldl`. Delegates to **ECMAScript 5**'s native `reduce` if available. _.reduce = _.foldl = _.inject = function(obj, iterator, memo, context) { var initial = memo !== void 0; if (obj == null) obj = []; if (nativeReduce && obj.reduce === nativeReduce) { if (context) iterator = _.bind(iterator, context); return initial ? obj.reduce(iterator, memo) : obj.reduce(iterator); } each(obj, function(value, index, list) { if (!initial) { memo = value; initial = true; } else { memo = iterator.call(context, memo, value, index, list); } }); if (!initial) throw new TypeError("Reduce of empty array with no initial value"); return memo; }; // The right-associative version of reduce, also known as `foldr`. // Delegates to **ECMAScript 5**'s native `reduceRight` if available. _.reduceRight = _.foldr = function(obj, iterator, memo, context) { if (obj == null) obj = []; if (nativeReduceRight && obj.reduceRight === nativeReduceRight) { if (context) iterator = _.bind(iterator, context); return memo !== void 0 ? obj.reduceRight(iterator, memo) : obj.reduceRight(iterator); } var reversed = (_.isArray(obj) ? obj.slice() : _.toArray(obj)).reverse(); return _.reduce(reversed, iterator, memo, context); }; // Return the first value which passes a truth test. Aliased as `detect`. _.find = _.detect = function(obj, iterator, context) { var result; any(obj, function(value, index, list) { if (iterator.call(context, value, index, list)) { result = value; return true; } }); return result; }; // Return all the elements that pass a truth test. // Delegates to **ECMAScript 5**'s native `filter` if available. // Aliased as `select`. _.filter = _.select = function(obj, iterator, context) { var results = []; if (obj == null) return results; if (nativeFilter && obj.filter === nativeFilter) return obj.filter(iterator, context); each(obj, function(value, index, list) { if (iterator.call(context, value, index, list)) results[results.length] = value; }); return results; }; // Return all the elements for which a truth test fails. _.reject = function(obj, iterator, context) { var results = []; if (obj == null) return results; each(obj, function(value, index, list) { if (!iterator.call(context, value, index, list)) results[results.length] = value; }); return results; }; // Determine whether all of the elements match a truth test. // Delegates to **ECMAScript 5**'s native `every` if available. // Aliased as `all`. _.every = _.all = function(obj, iterator, context) { var result = true; if (obj == null) return result; if (nativeEvery && obj.every === nativeEvery) return obj.every(iterator, context); each(obj, function(value, index, list) { if (!(result = result && iterator.call(context, value, index, list))) return breaker; }); return result; }; // Determine if at least one element in the object matches a truth test. // Delegates to **ECMAScript 5**'s native `some` if available. // Aliased as `any`. var any = _.some = _.any = function(obj, iterator, context) { iterator = iterator || _.identity; var result = false; if (obj == null) return result; if (nativeSome && obj.some === nativeSome) return obj.some(iterator, context); each(obj, function(value, index, list) { if (result || (result = iterator.call(context, value, index, list))) return breaker; }); return !!result; }; // Determine if a given value is included in the array or object using `===`. // Aliased as `contains`. _.include = _.contains = function(obj, target) { var found = false; if (obj == null) return found; if (nativeIndexOf && obj.indexOf === nativeIndexOf) return obj.indexOf(target) != -1; found = any(obj, function(value) { return value === target; }); return found; }; // Invoke a method (with arguments) on every item in a collection. _.invoke = function(obj, method) { var args = slice.call(arguments, 2); return _.map(obj, function(value) { return (method.call ? method || value : value[method]).apply(value, args); }); }; // Convenience version of a common use case of `map`: fetching a property. _.pluck = function(obj, key) { return _.map(obj, function(value){ return value[key]; }); }; // Return the maximum element or (element-based computation). _.max = function(obj, iterator, context) { if (!iterator && _.isArray(obj)) return Math.max.apply(Math, obj); if (!iterator && _.isEmpty(obj)) return -Infinity; var result = {computed : -Infinity}; each(obj, function(value, index, list) { var computed = iterator ? iterator.call(context, value, index, list) : value; computed >= result.computed && (result = {value : value, computed : computed}); }); return result.value; }; // Return the minimum element (or element-based computation). _.min = function(obj, iterator, context) { if (!iterator && _.isArray(obj)) return Math.min.apply(Math, obj); if (!iterator && _.isEmpty(obj)) return Infinity; var result = {computed : Infinity}; each(obj, function(value, index, list) { var computed = iterator ? iterator.call(context, value, index, list) : value; computed < result.computed && (result = {value : value, computed : computed}); }); return result.value; }; // Shuffle an array. _.shuffle = function(obj) { var shuffled = [], rand; each(obj, function(value, index, list) { if (index == 0) { shuffled[0] = value; } else { rand = Math.floor(Math.random() * (index + 1)); shuffled[index] = shuffled[rand]; shuffled[rand] = value; } }); return shuffled; }; // Sort the object's values by a criterion produced by an iterator. _.sortBy = function(obj, iterator, context) { return _.pluck(_.map(obj, function(value, index, list) { return { value : value, criteria : iterator.call(context, value, index, list) }; }).sort(function(left, right) { var a = left.criteria, b = right.criteria; return a < b ? -1 : a > b ? 1 : 0; }), 'value'); }; // Groups the object's values by a criterion. Pass either a string attribute // to group by, or a function that returns the criterion. _.groupBy = function(obj, val) { var result = {}; var iterator = _.isFunction(val) ? val : function(obj) { return obj[val]; }; each(obj, function(value, index) { var key = iterator(value, index); (result[key] || (result[key] = [])).push(value); }); return result; }; // Use a comparator function to figure out at what index an object should // be inserted so as to maintain order. Uses binary search. _.sortedIndex = function(array, obj, iterator) { iterator || (iterator = _.identity); var low = 0, high = array.length; while (low < high) { var mid = (low + high) >> 1; iterator(array[mid]) < iterator(obj) ? low = mid + 1 : high = mid; } return low; }; // Safely convert anything iterable into a real, live array. _.toArray = function(iterable) { if (!iterable) return []; if (iterable.toArray) return iterable.toArray(); if (_.isArray(iterable)) return slice.call(iterable); if (_.isArguments(iterable)) return slice.call(iterable); return _.values(iterable); }; // Return the number of elements in an object. _.size = function(obj) { return _.toArray(obj).length; }; // Array Functions // --------------- // Get the first element of an array. Passing **n** will return the first N // values in the array. Aliased as `head`. The **guard** check allows it to work // with `_.map`. _.first = _.head = function(array, n, guard) { return (n != null) && !guard ? slice.call(array, 0, n) : array[0]; }; // Returns everything but the last entry of the array. Especcialy useful on // the arguments object. Passing **n** will return all the values in // the array, excluding the last N. The **guard** check allows it to work with // `_.map`. _.initial = function(array, n, guard) { return slice.call(array, 0, array.length - ((n == null) || guard ? 1 : n)); }; // Get the last element of an array. Passing **n** will return the last N // values in the array. The **guard** check allows it to work with `_.map`. _.last = function(array, n, guard) { if ((n != null) && !guard) { return slice.call(array, Math.max(array.length - n, 0)); } else { return array[array.length - 1]; } }; // Returns everything but the first entry of the array. Aliased as `tail`. // Especially useful on the arguments object. Passing an **index** will return // the rest of the values in the array from that index onward. The **guard** // check allows it to work with `_.map`. _.rest = _.tail = function(array, index, guard) { return slice.call(array, (index == null) || guard ? 1 : index); }; // Trim out all falsy values from an array. _.compact = function(array) { return _.filter(array, function(value){ return !!value; }); }; // Return a completely flattened version of an array. _.flatten = function(array, shallow) { return _.reduce(array, function(memo, value) { if (_.isArray(value)) return memo.concat(shallow ? value : _.flatten(value)); memo[memo.length] = value; return memo; }, []); }; // Return a version of the array that does not contain the specified value(s). _.without = function(array) { return _.difference(array, slice.call(arguments, 1)); }; // Produce a duplicate-free version of the array. If the array has already // been sorted, you have the option of using a faster algorithm. // Aliased as `unique`. _.uniq = _.unique = function(array, isSorted, iterator) { var initial = iterator ? _.map(array, iterator) : array; var result = []; _.reduce(initial, function(memo, el, i) { if (0 == i || (isSorted === true ? _.last(memo) != el : !_.include(memo, el))) { memo[memo.length] = el; result[result.length] = array[i]; } return memo; }, []); return result; }; // Produce an array that contains the union: each distinct element from all of // the passed-in arrays. _.union = function() { return _.uniq(_.flatten(arguments, true)); }; // Produce an array that contains every item shared between all the // passed-in arrays. (Aliased as "intersect" for back-compat.) _.intersection = _.intersect = function(array) { var rest = slice.call(arguments, 1); return _.filter(_.uniq(array), function(item) { return _.every(rest, function(other) { return _.indexOf(other, item) >= 0; }); }); }; // Take the difference between one array and another. // Only the elements present in just the first array will remain. _.difference = function(array, other) { return _.filter(array, function(value){ return !_.include(other, value); }); }; // Zip together multiple lists into a single array -- elements that share // an index go together. _.zip = function() { var args = slice.call(arguments); var length = _.max(_.pluck(args, 'length')); var results = new Array(length); for (var i = 0; i < length; i++) results[i] = _.pluck(args, "" + i); return results; }; // If the browser doesn't supply us with indexOf (I'm looking at you, **MSIE**), // we need this function. Return the position of the first occurrence of an // item in an array, or -1 if the item is not included in the array. // Delegates to **ECMAScript 5**'s native `indexOf` if available. // If the array is large and already in sort order, pass `true` // for **isSorted** to use binary search. _.indexOf = function(array, item, isSorted) { if (array == null) return -1; var i, l; if (isSorted) { i = _.sortedIndex(array, item); return array[i] === item ? i : -1; } if (nativeIndexOf && array.indexOf === nativeIndexOf) return array.indexOf(item); for (i = 0, l = array.length; i < l; i++) if (array[i] === item) return i; return -1; }; // Delegates to **ECMAScript 5**'s native `lastIndexOf` if available. _.lastIndexOf = function(array, item) { if (array == null) return -1; if (nativeLastIndexOf && array.lastIndexOf === nativeLastIndexOf) return array.lastIndexOf(item); var i = array.length; while (i--) if (array[i] === item) return i; return -1; }; // Generate an integer Array containing an arithmetic progression. A port of // the native Python `range()` function. See // [the Python documentation](http://docs.python.org/library/functions.html#range). _.range = function(start, stop, step) { if (arguments.length <= 1) { stop = start || 0; start = 0; } step = arguments[2] || 1; var len = Math.max(Math.ceil((stop - start) / step), 0); var idx = 0; var range = new Array(len); while(idx < len) { range[idx++] = start; start += step; } return range; }; // Function (ahem) Functions // ------------------ // Reusable constructor function for prototype setting. var ctor = function(){}; // Create a function bound to a given object (assigning `this`, and arguments, // optionally). Binding with arguments is also known as `curry`. // Delegates to **ECMAScript 5**'s native `Function.bind` if available. // We check for `func.bind` first, to fail fast when `func` is undefined. _.bind = function bind(func, context) { var bound, args; if (func.bind === nativeBind && nativeBind) return nativeBind.apply(func, slice.call(arguments, 1)); if (!_.isFunction(func)) throw new TypeError; args = slice.call(arguments, 2); return bound = function() { if (!(this instanceof bound)) return func.apply(context, args.concat(slice.call(arguments))); ctor.prototype = func.prototype; var self = new ctor; var result = func.apply(self, args.concat(slice.call(arguments))); if (Object(result) === result) return result; return self; }; }; // Bind all of an object's methods to that object. Useful for ensuring that // all callbacks defined on an object belong to it. _.bindAll = function(obj) { var funcs = slice.call(arguments, 1); if (funcs.length == 0) funcs = _.functions(obj); each(funcs, function(f) { obj[f] = _.bind(obj[f], obj); }); return obj; }; // Memoize an expensive function by storing its results. _.memoize = function(func, hasher) { var memo = {}; hasher || (hasher = _.identity); return function() { var key = hasher.apply(this, arguments); return hasOwnProperty.call(memo, key) ? memo[key] : (memo[key] = func.apply(this, arguments)); }; }; // Delays a function for the given number of milliseconds, and then calls // it with the arguments supplied. _.delay = function(func, wait) { var args = slice.call(arguments, 2); return setTimeout(function(){ return func.apply(func, args); }, wait); }; // Defers a function, scheduling it to run after the current call stack has // cleared. _.defer = function(func) { return _.delay.apply(_, [func, 1].concat(slice.call(arguments, 1))); }; // Returns a function, that, when invoked, will only be triggered at most once // during a given window of time. _.throttle = function(func, wait) { var context, args, timeout, throttling, more; var whenDone = _.debounce(function(){ more = throttling = false; }, wait); return function() { context = this; args = arguments; var later = function() { timeout = null; if (more) func.apply(context, args); whenDone(); }; if (!timeout) timeout = setTimeout(later, wait); if (throttling) { more = true; } else { func.apply(context, args); } whenDone(); throttling = true; }; }; // Returns a function, that, as long as it continues to be invoked, will not // be triggered. The function will be called after it stops being called for // N milliseconds. _.debounce = function(func, wait) { var timeout; return function() { var context = this, args = arguments; var later = function() { timeout = null; func.apply(context, args); }; clearTimeout(timeout); timeout = setTimeout(later, wait); }; }; // Returns a function that will be executed at most one time, no matter how // often you call it. Useful for lazy initialization. _.once = function(func) { var ran = false, memo; return function() { if (ran) return memo; ran = true; return memo = func.apply(this, arguments); }; }; // Returns the first function passed as an argument to the second, // allowing you to adjust arguments, run code before and after, and // conditionally execute the original function. _.wrap = function(func, wrapper) { return function() { var args = [func].concat(slice.call(arguments)); return wrapper.apply(this, args); }; }; // Returns a function that is the composition of a list of functions, each // consuming the return value of the function that follows. _.compose = function() { var funcs = slice.call(arguments); return function() { var args = slice.call(arguments); for (var i = funcs.length - 1; i >= 0; i--) { args = [funcs[i].apply(this, args)]; } return args[0]; }; }; // Returns a function that will only be executed after being called N times. _.after = function(times, func) { if (times <= 0) return func(); return function() { if (--times < 1) { return func.apply(this, arguments); } }; }; // Object Functions // ---------------- // Retrieve the names of an object's properties. // Delegates to **ECMAScript 5**'s native `Object.keys` _.keys = nativeKeys || function(obj) { if (obj !== Object(obj)) throw new TypeError('Invalid object'); var keys = []; for (var key in obj) if (hasOwnProperty.call(obj, key)) keys[keys.length] = key; return keys; }; // Retrieve the values of an object's properties. _.values = function(obj) { return _.map(obj, _.identity); }; // Return a sorted list of the function names available on the object. // Aliased as `methods` _.functions = _.methods = function(obj) { var names = []; for (var key in obj) { if (_.isFunction(obj[key])) names.push(key); } return names.sort(); }; // Extend a given object with all the properties in passed-in object(s). _.extend = function(obj) { each(slice.call(arguments, 1), function(source) { for (var prop in source) { if (source[prop] !== void 0) obj[prop] = source[prop]; } }); return obj; }; // Fill in a given object with default properties. _.defaults = function(obj) { each(slice.call(arguments, 1), function(source) { for (var prop in source) { if (obj[prop] == null) obj[prop] = source[prop]; } }); return obj; }; // Create a (shallow-cloned) duplicate of an object. _.clone = function(obj) { if (!_.isObject(obj)) return obj; return _.isArray(obj) ? obj.slice() : _.extend({}, obj); }; // Invokes interceptor with the obj, and then returns obj. // The primary purpose of this method is to "tap into" a method chain, in // order to perform operations on intermediate results within the chain. _.tap = function(obj, interceptor) { interceptor(obj); return obj; }; // Internal recursive comparison function. function eq(a, b, stack) { // Identical objects are equal. `0 === -0`, but they aren't identical. // See the Harmony `egal` proposal: http://wiki.ecmascript.org/doku.php?id=harmony:egal. if (a === b) return a !== 0 || 1 / a == 1 / b; // A strict comparison is necessary because `null == undefined`. if (a == null || b == null) return a === b; // Unwrap any wrapped objects. if (a._chain) a = a._wrapped; if (b._chain) b = b._wrapped; // Invoke a custom `isEqual` method if one is provided. if (_.isFunction(a.isEqual)) return a.isEqual(b); if (_.isFunction(b.isEqual)) return b.isEqual(a); // Compare `[[Class]]` names. var className = toString.call(a); if (className != toString.call(b)) return false; switch (className) { // Strings, numbers, dates, and booleans are compared by value. case '[object String]': // Primitives and their corresponding object wrappers are equivalent; thus, `"5"` is // equivalent to `new String("5")`. return String(a) == String(b); case '[object Number]': a = +a; b = +b; // `NaN`s are equivalent, but non-reflexive. An `egal` comparison is performed for // other numeric values. return a != a ? b != b : (a == 0 ? 1 / a == 1 / b : a == b); case '[object Date]': case '[object Boolean]': // Coerce dates and booleans to numeric primitive values. Dates are compared by their // millisecond representations. Note that invalid dates with millisecond representations // of `NaN` are not equivalent. return +a == +b; // RegExps are compared by their source patterns and flags. case '[object RegExp]': return a.source == b.source && a.global == b.global && a.multiline == b.multiline && a.ignoreCase == b.ignoreCase; } if (typeof a != 'object' || typeof b != 'object') return false; // Assume equality for cyclic structures. The algorithm for detecting cyclic // structures is adapted from ES 5.1 section 15.12.3, abstract operation `JO`. var length = stack.length; while (length--) { // Linear search. Performance is inversely proportional to the number of // unique nested structures. if (stack[length] == a) return true; } // Add the first object to the stack of traversed objects. stack.push(a); var size = 0, result = true; // Recursively compare objects and arrays. if (className == '[object Array]') { // Compare array lengths to determine if a deep comparison is necessary. size = a.length; result = size == b.length; if (result) { // Deep compare the contents, ignoring non-numeric properties. while (size--) { // Ensure commutative equality for sparse arrays. if (!(result = size in a == size in b && eq(a[size], b[size], stack))) break; } } } else { // Objects with different constructors are not equivalent. if ("constructor" in a != "constructor" in b || a.constructor != b.constructor) return false; // Deep compare objects. for (var key in a) { if (hasOwnProperty.call(a, key)) { // Count the expected number of properties. size++; // Deep compare each member. if (!(result = hasOwnProperty.call(b, key) && eq(a[key], b[key], stack))) break; } } // Ensure that both objects contain the same number of properties. if (result) { for (key in b) { if (hasOwnProperty.call(b, key) && !(size--)) break; } result = !size; } } // Remove the first object from the stack of traversed objects. stack.pop(); return result; } // Perform a deep comparison to check if two objects are equal. _.isEqual = function(a, b) { return eq(a, b, []); }; // Is a given array, string, or object empty? // An "empty" object has no enumerable own-properties. _.isEmpty = function(obj) { if (_.isArray(obj) || _.isString(obj)) return obj.length === 0; for (var key in obj) if (hasOwnProperty.call(obj, key)) return false; return true; }; // Is a given value a DOM element? _.isElement = function(obj) { return !!(obj && obj.nodeType == 1); }; // Is a given value an array? // Delegates to ECMA5's native Array.isArray _.isArray = nativeIsArray || function(obj) { return toString.call(obj) == '[object Array]'; }; // Is a given variable an object? _.isObject = function(obj) { return obj === Object(obj); }; // Is a given variable an arguments object? if (toString.call(arguments) == '[object Arguments]') { _.isArguments = function(obj) { return toString.call(obj) == '[object Arguments]'; }; } else { _.isArguments = function(obj) { return !!(obj && hasOwnProperty.call(obj, 'callee')); }; } // Is a given value a function? _.isFunction = function(obj) { return toString.call(obj) == '[object Function]'; }; // Is a given value a string? _.isString = function(obj) { return toString.call(obj) == '[object String]'; }; // Is a given value a number? _.isNumber = function(obj) { return toString.call(obj) == '[object Number]'; }; // Is the given value `NaN`? _.isNaN = function(obj) { // `NaN` is the only value for which `===` is not reflexive. return obj !== obj; }; // Is a given value a boolean? _.isBoolean = function(obj) { return obj === true || obj === false || toString.call(obj) == '[object Boolean]'; }; // Is a given value a date? _.isDate = function(obj) { return toString.call(obj) == '[object Date]'; }; // Is the given value a regular expression? _.isRegExp = function(obj) { return toString.call(obj) == '[object RegExp]'; }; // Is a given value equal to null? _.isNull = function(obj) { return obj === null; }; // Is a given variable undefined? _.isUndefined = function(obj) { return obj === void 0; }; // Utility Functions // ----------------- // Run Underscore.js in *noConflict* mode, returning the `_` variable to its // previous owner. Returns a reference to the Underscore object. _.noConflict = function() { root._ = previousUnderscore; return this; }; // Keep the identity function around for default iterators. _.identity = function(value) { return value; }; // Run a function **n** times. _.times = function (n, iterator, context) { for (var i = 0; i < n; i++) iterator.call(context, i); }; // Escape a string for HTML interpolation. _.escape = function(string) { return (''+string).replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"').replace(/'/g, ''').replace(/\//g,'/'); }; // Add your own custom functions to the Underscore object, ensuring that // they're correctly added to the OOP wrapper as well. _.mixin = function(obj) { each(_.functions(obj), function(name){ addToWrapper(name, _[name] = obj[name]); }); }; // Generate a unique integer id (unique within the entire client session). // Useful for temporary DOM ids. var idCounter = 0; _.uniqueId = function(prefix) { var id = idCounter++; return prefix ? prefix + id : id; }; // By default, Underscore uses ERB-style template delimiters, change the // following template settings to use alternative delimiters. _.templateSettings = { evaluate : /<%([\s\S]+?)%>/g, interpolate : /<%=([\s\S]+?)%>/g, escape : /<%-([\s\S]+?)%>/g }; // JavaScript micro-templating, similar to John Resig's implementation. // Underscore templating handles arbitrary delimiters, preserves whitespace, // and correctly escapes quotes within interpolated code. _.template = function(str, data) { var c = _.templateSettings; var tmpl = 'var __p=[],print=function(){__p.push.apply(__p,arguments);};' + 'with(obj||{}){__p.push(\'' + str.replace(/\\/g, '\\\\') .replace(/'/g, "\\'") .replace(c.escape, function(match, code) { return "',_.escape(" + code.replace(/\\'/g, "'") + "),'"; }) .replace(c.interpolate, function(match, code) { return "'," + code.replace(/\\'/g, "'") + ",'"; }) .replace(c.evaluate || null, function(match, code) { return "');" + code.replace(/\\'/g, "'") .replace(/[\r\n\t]/g, ' ') + ";__p.push('"; }) .replace(/\r/g, '\\r') .replace(/\n/g, '\\n') .replace(/\t/g, '\\t') + "');}return __p.join('');"; var func = new Function('obj', '_', tmpl); return data ? func(data, _) : function(data) { return func(data, _) }; }; // The OOP Wrapper // --------------- // If Underscore is called as a function, it returns a wrapped object that // can be used OO-style. This wrapper holds altered versions of all the // underscore functions. Wrapped objects may be chained. var wrapper = function(obj) { this._wrapped = obj; }; // Expose `wrapper.prototype` as `_.prototype` _.prototype = wrapper.prototype; // Helper function to continue chaining intermediate results. var result = function(obj, chain) { return chain ? _(obj).chain() : obj; }; // A method to easily add functions to the OOP wrapper. var addToWrapper = function(name, func) { wrapper.prototype[name] = function() { var args = slice.call(arguments); unshift.call(args, this._wrapped); return result(func.apply(_, args), this._chain); }; }; // Add all of the Underscore functions to the wrapper object. _.mixin(_); // Add all mutator Array functions to the wrapper. each(['pop', 'push', 'reverse', 'shift', 'sort', 'splice', 'unshift'], function(name) { var method = ArrayProto[name]; wrapper.prototype[name] = function() { method.apply(this._wrapped, arguments); return result(this._wrapped, this._chain); }; }); // Add all accessor Array functions to the wrapper. each(['concat', 'join', 'slice'], function(name) { var method = ArrayProto[name]; wrapper.prototype[name] = function() { return result(method.apply(this._wrapped, arguments), this._chain); }; }); // Start chaining a wrapped Underscore object. wrapper.prototype.chain = function() { this._chain = true; return this; }; // Extracts the result from a wrapped and chained object. wrapper.prototype.value = function() { return this._wrapped; }; }).call(this); ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������beetbox-beets-01f1faf/beetsplug/web/templates/������������������������������������������������������0000775�0000000�0000000�00000000000�14723254774�0021514�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������beetbox-beets-01f1faf/beetsplug/web/templates/index.html��������������������������������������������0000664�0000000�0000000�00000006352�14723254774�0023517�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������<!DOCTYPE html> <html> <head> <title>beets
beetbox-beets-01f1faf/beetsplug/zero.py000066400000000000000000000131101472325477400202660ustar00rootroot00000000000000# This file is part of beets. # Copyright 2016, Blemjhoo Tezoulbr . # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the # "Software"), to deal in the Software without restriction, including # without limitation the rights to use, copy, modify, merge, publish, # distribute, sublicense, and/or sell copies of the Software, and to # permit persons to whom the Software is furnished to do so, subject to # the following conditions: # # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. """Clears tag fields in media files.""" import re import confuse from mediafile import MediaFile from beets.importer import action from beets.plugins import BeetsPlugin from beets.ui import Subcommand, decargs, input_yn __author__ = "baobab@heresiarch.info" class ZeroPlugin(BeetsPlugin): def __init__(self): super().__init__() self.register_listener("write", self.write_event) self.register_listener( "import_task_choice", self.import_task_choice_event ) self.config.add( { "auto": True, "fields": [], "keep_fields": [], "update_database": False, } ) self.fields_to_progs = {} self.warned = False """Read the bulk of the config into `self.fields_to_progs`. After construction, `fields_to_progs` contains all the fields that should be zeroed as keys and maps each of those to a list of compiled regexes (progs) as values. A field is zeroed if its value matches one of the associated progs. If progs is empty, then the associated field is always zeroed. """ if self.config["fields"] and self.config["keep_fields"]: self._log.warning("cannot blacklist and whitelist at the same time") # Blacklist mode. elif self.config["fields"]: for field in self.config["fields"].as_str_seq(): self._set_pattern(field) # Whitelist mode. elif self.config["keep_fields"]: for field in MediaFile.fields(): if ( field not in self.config["keep_fields"].as_str_seq() and # These fields should always be preserved. field not in ("id", "path", "album_id") ): self._set_pattern(field) def commands(self): zero_command = Subcommand("zero", help="set fields to null") def zero_fields(lib, opts, args): if not decargs(args) and not input_yn( "Remove fields for all items? (Y/n)", True ): return for item in lib.items(decargs(args)): self.process_item(item) zero_command.func = zero_fields return [zero_command] def _set_pattern(self, field): """Populate `self.fields_to_progs` for a given field. Do some sanity checks then compile the regexes. """ if field not in MediaFile.fields(): self._log.error("invalid field: {0}", field) elif field in ("id", "path", "album_id"): self._log.warning( "field '{0}' ignored, zeroing " "it would be dangerous", field ) else: try: for pattern in self.config[field].as_str_seq(): prog = re.compile(pattern, re.IGNORECASE) self.fields_to_progs.setdefault(field, []).append(prog) except confuse.NotFoundError: # Matches everything self.fields_to_progs[field] = [] def import_task_choice_event(self, session, task): if task.choice_flag == action.ASIS and not self.warned: self._log.warning('cannot zero in "as-is" mode') self.warned = True # TODO request write in as-is mode def write_event(self, item, path, tags): if self.config["auto"]: self.set_fields(item, tags) def set_fields(self, item, tags): """Set values in `tags` to `None` if the field is in `self.fields_to_progs` and any of the corresponding `progs` matches the field value. Also update the `item` itself if `update_database` is set in the config. """ fields_set = False if not self.fields_to_progs: self._log.warning("no fields, nothing to do") return False for field, progs in self.fields_to_progs.items(): if field in tags: value = tags[field] match = _match_progs(tags[field], progs) else: value = "" match = not progs if match: fields_set = True self._log.debug("{0}: {1} -> None", field, value) tags[field] = None if self.config["update_database"]: item[field] = None return fields_set def process_item(self, item): tags = dict(item) if self.set_fields(item, tags): item.write(tags=tags) if self.config["update_database"]: item.store(fields=tags) def _match_progs(value, progs): """Check if `value` (as string) is matching any of the compiled regexes in the `progs` list. """ if not progs: return True for prog in progs: if prog.search(str(value)): return True return False beetbox-beets-01f1faf/codecov.yml000066400000000000000000000005141472325477400171140ustar00rootroot00000000000000# Don't post a comment on pull requests. comment: off # Sets non-blocking status checks # https://docs.codecov.com/docs/commit-status#informational coverage: status: project: default: informational: true patch: default: informational: true changes: no github_checks: annotations: false beetbox-beets-01f1faf/docs/000077500000000000000000000000001472325477400156775ustar00rootroot00000000000000beetbox-beets-01f1faf/docs/Makefile000066400000000000000000000112311472325477400173350ustar00rootroot00000000000000# Makefile for Sphinx documentation # # You can set these variables from the command line. SPHINXOPTS = SPHINXBUILD = sphinx-build PAPER = BUILDDIR = _build # When both are available, use Sphinx 2.x for autodoc compatibility. ifeq ($(shell which sphinx-build2 >/dev/null 2>&1 ; echo $$?),0) SPHINXBUILD = sphinx-build2 endif # Internal variables. PAPEROPT_a4 = -D latex_paper_size=a4 PAPEROPT_letter = -D latex_paper_size=letter ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest auto help: @echo "Please use \`make ' where is one of" @echo " html to make standalone HTML files" @echo " dirhtml to make HTML files named index.html in directories" @echo " singlehtml to make a single large HTML file" @echo " pickle to make pickle files" @echo " json to make JSON files" @echo " htmlhelp to make HTML files and a HTML help project" @echo " qthelp to make HTML files and a qthelp project" @echo " devhelp to make HTML files and a Devhelp project" @echo " epub to make an epub" @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" @echo " latexpdf to make LaTeX files and run them through pdflatex" @echo " text to make text files" @echo " man to make manual pages" @echo " changes to make an overview of all changed/added/deprecated items" @echo " linkcheck to check all external links for integrity" @echo " doctest to run all doctests embedded in the documentation (if enabled)" clean: -rm -rf $(BUILDDIR)/* html: $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." dirhtml: $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." singlehtml: $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml @echo @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." pickle: $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle @echo @echo "Build finished; now you can process the pickle files." json: $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json @echo @echo "Build finished; now you can process the JSON files." htmlhelp: $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp @echo @echo "Build finished; now you can run HTML Help Workshop with the" \ ".hhp project file in $(BUILDDIR)/htmlhelp." qthelp: $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp @echo @echo "Build finished; now you can run "qcollectiongenerator" with the" \ ".qhcp project file in $(BUILDDIR)/qthelp, like this:" @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/beets.qhcp" @echo "To view the help file:" @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/beets.qhc" devhelp: $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp @echo @echo "Build finished." @echo "To view the help file:" @echo "# mkdir -p $$HOME/.local/share/devhelp/beets" @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/beets" @echo "# devhelp" epub: $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub @echo @echo "Build finished. The epub file is in $(BUILDDIR)/epub." latex: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." @echo "Run \`make' in that directory to run these through (pdf)latex" \ "(use \`make latexpdf' here to do that automatically)." latexpdf: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo "Running LaTeX files through pdflatex..." make -C $(BUILDDIR)/latex all-pdf @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." text: $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text @echo @echo "Build finished. The text files are in $(BUILDDIR)/text." man: $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man @echo @echo "Build finished. The manual pages are in $(BUILDDIR)/man." changes: $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes @echo @echo "The overview file is in $(BUILDDIR)/changes." linkcheck: $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck @echo @echo "Link check complete; look for any errors in the above output " \ "or in $(BUILDDIR)/linkcheck/output.txt." doctest: $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest @echo "Testing of doctests in the sources finished, look at the " \ "results in $(BUILDDIR)/doctest/output.txt." beetbox-beets-01f1faf/docs/_static/000077500000000000000000000000001472325477400173255ustar00rootroot00000000000000beetbox-beets-01f1faf/docs/_static/beets.css000066400000000000000000000004721472325477400211440ustar00rootroot00000000000000html[data-theme="light"] { --pst-color-secondary: #a23632; } html[data-theme="light"] { --pst-color-inline-code: #a23632; } /* beetroot red: #a23632 */ /* beetroot green: #1B5801 */ /* beetroot green light: rgb(27, 150, 50) */ /* pydata teal (primary): #126A7E */ /* pydata violet (secondary): #7D0E70 */ beetbox-beets-01f1faf/docs/_static/beets_logo_nobg.png000066400000000000000000000263561472325477400231760ustar00rootroot00000000000000‰PNG  IHDRńńËQ pHYsgźŇR IDATxí} xTU–˙ÉJB!„-„€FYdy…¨ ¶Ť6nńß.đµÝ­Žmw›Ô§úąŕLkŁĚ×.ô¨#(Ú(öČPOqd[„ě9§’Wuď«JR•ÚŢ{uî÷UęŢűî{ďŢß©_îvÎą1MMMŔ`Ě‹@¬y«Î5gB€IĚżFŔä0‰}`yyąâcQ.Ć„&±p?˙üóĘ€O>ů¤âCq.„^ŘjďďľűNąë®»”cÇŽĺkĄŢ˙}Ű‚ T-Íߌ@¤ྸ ÄÇÇH`*şpáBÇćÍ›•6năKŚ@Xྸ¸űőëW 'rŻ^˝`۶m6üVŰąť/3!G€{âv ľăŽ;#JxuÚGtű÷ď_PRRâÚ/Öß†Ę °eË[ff¦ŞżĆiF ”pOě#şwŢy§\R‡\qq1`Ź­Č…8Ĺ„ÝO1ô/4ëô«Ô1\çÁrkTUÍ_Ľx±"çrŠ-Lbń=z´š››ëZĄnjHÎčÔG~ŔoĽáxë­·9—SŚ@č`ű­~H]] Đă&€ř4ů!Ř;°WVä\N1ˇA€Iě®úUꚀ8$ňÍřťŕ~Pcc#ŕ¶”ăđáĂŠ;—cŚ@h`ű«·!uÍ€Ä €ĚiňÎś9ĂŞ™2$ś Lb?Ő©/l~@J.@úXůa¸wś?wî\EÎĺ#\Ä~â©R×–\nh~H×Q)WĘ\łfŤăOú“"çZ7UYY© Fd‹mÝV¬e¤qÄ˙08p`бIűŕPş©˙#Íźś‡ )±‡űšVć>P¬Ž3î•+W]u•´ĹnZľ|ąĺŰl™rOŚ,ó7x ©ąźŹ ]3âRÜy{ŕhů¤ČąÖIQŰĐXıwď^gŁhqrĽňĘ+–młQ¤Ç$î€$ôŠ´J­ ©éqń©ÍD\ąÖBmm-Ěž=۶Ȋ–g•o2™2eŠŁ¬¬LjRLL L›¦[ń“Jp"0‰;€âČ‘#ŐĽĽ<—â\¨Á=c1tę Đ]sJKKÁj ]¨Ü˘Đ?§ęęją±şďľűě„•ÇÎ*LâÂŮÖZ{dę €´‘ZŞůýsĺăĐZ‘sÍ™"Řrůňeʤ¤¤Ŕ’%KTŹ śtÄ„TżJ]s‡ÔőžK‡ę™9rţ;ďĽcúą"¶_Áů®Cn™;őÔSO±27!Ť±)bđ^yĺ•Ě×Ńýę}őČ}b5@cĄűJll,|ńŶ3f¨î\ăÇȉ>ź•M›6ąÚ­Żuź>}ěÇŹ/Đçs:4pO®ú!uµ°J->66 ë<3§“;—† ¸@ć(,,TܹƎá?,eěŘ±Ž¶L-xá…Tc·ÄZµc OŹ!u‰÷!5˝"ˇ+ŞfNÇHŚű…çĎź‡™3g:HAÂťkĚ)o‹ŠŠÚ¬ŕ¨QŁě÷ßżÚf!ľTÄŔI+Ż rŻRăúY6µČt±ŰőňŐC‡Ţ™Ŕ‡~¨ŕV‘Ł˘˘B®Ľ—_Ď«ŻľŞĘ…8jĽ!ÔŻ´Öó}Rk­NŽóć«´Tó÷úőë ëL€”5îľűnG]]ťTéΨ^—,eÁś9s슢¨r.§BŤ“8@„=?h•Zţ˝{Ľ!c:č-g“3+V(rndSŹ<ň‚ŰHR-C×18=@Ë­KÝą đňË/«îŽ… &q€H6L2d<¤>ÜöCť6ČčL ®‹\îŃGuĹp€N¸@%©†řkˇř.WśŰ*]Ç{ĚŽ 0ŞśË©p Ŕ$ĘCęóĶMCѬ›qť u­µĐĐĐ@]u&@jˇ×^{mŞRJ¦•őžłš·Đ*· őG‹ĺŐ˝[·nźźŻjíŕďđ"Ŕ$Ţ«ÔÇÚRÓk»ăŠ5ölbŔ}Ř©f’ďě1cĆ8¶nÝš/Ö‰F ˝ć$ő¨Çµ­ŞfW‘gź}Ö–žž®ş28VÄA€ŰëşŘ·§  9¦~úé§|ŇóBÇĹ5ĺúëŻwŕ ҫЬzߎsŕnÍŮg7áw“»­Î?ţřăŞ;‡cáF€I$ÄçĎź/=éb+ŠRˇ–Dúht&p…|eŐŞU»Ý®ČąˇIˇ¨róÍ7;hßZ Éą8„ží6«$ŐŇZüᥗ^RĹ4ÇĂŹ“8HĎ›7OU‹ÚĄvV©ĹňÝm8ĽÎs HfEÎ nŠţQ­3Ů˙ЎËđfsĘŘ€M¸îě……B¸ťdÇm%UČâh` tR:TZĄÖ›'¶ő*" yÍŚŐí˝Ţ{ď˝^+mÝŰŃkdME˙(ô÷w›1ÝbÜWhÜ čz­đŇĄKUw ŽE &q‘ׯRkNô|}9ČBŻ HĄ¦¦†Ľf:ČđŔ×ç´WŽÔ·ćˇYO-ÇýÝxŕüwšbl+,ăé“8ČŔ­!鉭™'J…Ľ$şŤÇ}Ů~ňTËtĽţú늜ë[Ş5?X¤ţI{Ŕ iŢźsv3ć_r_#[aVěpăa„“8ČRŔy±*>ŇąJíçšî§S{ÜN÷°—ĂO<ářꫯ1Ż˝xk~°Rňšµ°âp(í-Ô•ˇU–NűŚm…˝!Ů<&qń§!ő#ÜCę&$BŰ&¸­Ö€TťŞ™‰î"—.]Ü“vľ;·őXk~°Ň®i>z†łZ g˙[ľÂ¶Â2FI1‰C ‰` ©©j Ř÷Ža»çÜąspë­·¶ëL€´ľ<ü`ás2pá¬Ű8y I­¬SO,¶Ń0NśIYŕ‰ŞřXç*µ°ş+^ó%žŚscš#‹a˙ţýp×]w)bž§í¨ńăÇÖ—–Gßtr#ą ę2TĚőŚ7ḒćÂB`[a E™Ä! ©Ńë‡<¤.ěE´wŰy°ü śçÓpYĚŐü`‘k\1?®3.`ÍńôĽ)–Ńâ´M«ŇZ`[a c~3‰C$TĂ”žÜŃUjń!´¬ß˘á2é>SąÖü`‘ŻŰ=Ő:ĹgkńK8b`[a s|łËÚɉzDtiëÎâ\4ű>O—6ţľţR5şż]%{ŐHLL„§ź~ÚŽĆůz7:IŮ8§ľ •Ŕ„ű¶Ţyć[45Üí.A¶ÂčŹM Ý.Ć$ˇH®ąćš2+Ô^‘1ąŮ+†–îčwýi€“kĐ"°±í'Ň-bŃv•/l…Oü–lr—Fýh›şń0bĚGń±ęĆŻS0W©ĹÖ’Ťo÷©bŽg<ý:,cóťŔô„ł˙ł­°'®FĚa‡P*ŠĄ8 Ćáp0Bç+Đ™Ŕµ^ž„%?X^Ży)®e9m…K´Tó7Ű Ëx5Ĺ$ˇdČq)H¸^˝\Gt©]÷ë"ä$y€;SôĺÎm?ƶÂícdäLâKG?¤ö×<±­ę‘˝/ůčJčŽę™‚¬¶îńv­Şm…˝áb–<&q%…*’ŞřŠş ©éąäL€T3i Ió%ľŻ˝8öFŢ+ĹŔ¶Â"ĆŹ3‰C,ŁÜÜ\uôčŃî!5ľŻŁşÔ­U•zḔ֮¶ťĎ¶Âmăc†«Lâ0H)”Cę@ŞĎ¶Â gś{yź8 ˛ o pŻr*~t°÷źHüôײ©avv¶ýčŃŁ<“ď ?ńáeô˝QRŁgŤ|­őäŇ–W‹Tđf+üÜsϩԇţYUUU)ŐŐŐ@ç/ÇÇÇi“edd¨ř" gR/«ßË$“„iHŤ$v˝Ťt©#IâŽÚ Ła…˛cÇeçÎť€ŞĄ¤’ 䌋ÁőOĘŐĐćĺŰSSS˙ˇÁŔÝÁđáĂaܸq*ý“Ó•ç¤đpÚ°)ęmHÝ÷^ÜBë˘pÚć*ǡ´KZ ćQśĽ|ýő× WŞB}}}kDŐßęsš†ńS§N%i>}şĘGÂř ťł “Ř?Ľ*=věŘ‚-[¶¸HĐmbř{c˛.ý@65Ľí¶ŰŕłĎ>ٶŮń@5řä“OśiUUCBÚ¶@¤á÷-·Üb˙ĺ/©ęí˛Űş/šŻ1‰Ă(}4,@ű_‰É=-9© g8·M6ř'[aćďŢ˝Ţ{ď=Ŕ^Čż!!&şŕł:ĹĹB§ŘXtDjŘMĐ€săj|^ž0ŃpąÉŻÇ’Sľ_ýęW€GľŞ™™™Ş_7GQa&q…ŤsGĄ_ż~ń•áR“­đńGTđĐÂŐW_ ¸" ús´ëúď¬Nť 75úĄ¤@źä$ČJJ‚ĚN‰‚‹Xí… xtky]=”˘CücŐ5PTuŃů©G˘·:á;ńŔsűţđ&ł Ä^@ eąÍ˝nt»‡Ô:ÓˇzżŢVŘ—÷tKL€źĄ§Ăđô®0(-Ұ· fhDB2o?[ ?VTÂÉÚÖ]vîÜp$cc—ą˛Ä2!O˝öÚkżűÝďÂ>¤n8‹sᏰy>ŚhűwNëşg y»B?ڇ3#ˇż;]îüÔ^ňŢCăę¶ýÍ7ßTg̡†łnF}“8Ě’ˇ!uNNŽŁ©Éͦľ÷ŕ*5žĂĘPöKZŇúşb;!ł;LĚęŮ8TŽt¨Áyôw§Ęa]éI8S/Ś˙…ŠÝ}÷ÝN2Gűj6“XřQ„+Їyб,ÚűčBý!fÚµ`|“­đ©µŢź44­ ÜÔ§ŚŔ^7§Śh¸í(; ?^ ç<]™ŕ?DřŰßţf›8q˘j´ş‡«>Lâ M§ VTT(ř úůÖçáJpţ™3g\o&çwd…Š@¶Â'>–M é=×÷č3z÷‚ś0—;ÚĆZě™?9zľ>QúAv\\ŕĘÔşbăŻĘ_ФlYŐuőŞý‘†jH]µŕĚɵúőŕ<•ŃMÎ4IŞäb5üۡb8Śßú°páBŰňĺËU}ľŐÓ– 1‘°ĺăŃëyë ERCŔ$ ô‡Ş!5őÄváÉ†č ž¶©ţ˙y­nDď§!öŞ’ă°îÄIŹz Ö™ µĎTŹ Î0 ‰I-ŃŰ#!ő„¨lqvř7‚ÓPr«ăt-{c‡źŇęŤM¦ě ă˛p^y¦÷ę ¸Żk…°łňüe˙!§2‰Ř˛ß^·n]Ôě)GśÄ‹/VčÔ>‚i‰śC"Ćŕ‡ü;)]ßb\»¦ĺ%aąŕn»ŠżeČ.K…eyr9„/‘ŢţÄÉšZx­p?”ŐÖI/:t(|óÍ7¶hĐôj_ÍF‚&ř ěI|jD L$”§Ą5˛‰ßbËŃ˝F[ÔMl…9ÇŔ°ĘŚŕ Ě`Oě…ZcůÇÂë…`˙…*WíöěŮ7Üplő-¨Č“¸ęĽ ř@"1Ř’x<(ŰIF"šŽl®|Ť -ßF$a 8äťO9% ­%ˇ3Ş|>qŐ ř×}a×9÷ď M&aÖ¬YĘĆŤU+Cq÷<µŐµANCh¤ĸ—ŚçˇČä:ˇ Ş5ŇwĘś{öm>“H#ĽŃzŃŽţŘ.Ç­GsŕޢÁQE` ŻN¸Íôř+ad·t-ËůŤ&”ůDd)Ób‰Ď‰ O´SUpřŁěÝ» †Bdp,ŰUęĄÉ¤ţCDöő#Ë=űbg¸˝d dÖá$;ĘYM˝˛w?žż !gUŮ–,Y˘J™I‚Ä­a‰„VvíÚE‡}űö9ÉMçň˘‰ŕ̡qB‡x;ÉŤÓGÉ»"ąăZ«•qňc›pĺůdRÖČřŹC3¤˛ůâîBŹ˝ä÷ßß¶`ÁŐj8šÄ­M'RĎM˝6őŢDrŠă"YpČŤ|G"»HŤ˝x"~â‘đˇ\Mn­˝Ţň3k“`ޑз&®AĽUČ`yçę `ç¨ô®“Đlňűďż·ˇk Ő`Ő ¨:¦$qk-&ă"7‘Z$wyyypČŤ/&Ď.rSďÝŇ“ÓÂYXöľcËł`zi?HhŠř’FXšÜŃ—F‹¨çwŕÖ“Ő^ :úL#Ţg)·0şśˇ^["7óFî8ě]äćߴȬVźsqĺůŠ*&pđ Íĺg`Ů"©¬ŐÔ3قĒ…iyëąKJJ‚¦‹kMą[ćŢţ:Č~6fëÉ—"ľ+( hŽč;E‡ť–Pbm?ýôSŰś9sT1Ϭń¨&q[BCżSN‚‹ĂrrĎÚŘŘ”Ţ;•¨´á8~;çÜ4ďĆẸí•ÜłŽĺF…âF[ňäZ=:(ŘąŽŁv—zöěIë(–PakRőń›VËé#n‡ŃŠy0·Ăh_»ŰX€hTÜđQ ~;†~±ówěFÁĂý÷ßo˙ë_˙Zŕ÷Ă v“8Hi™s{¬wd;,cL ĚďŮĆžACcACŕócĄ°m’Ĺ€Ţ=mÓ¦MSĹ<łĹ™Ä!–Á ¶oßž˙üóĎĂÖ­[}zŰCWĺÂÄô>•ĺBľ#p {áü»á(zÚÔ‚V«yŹB“fľSĐ_ŐK/˝ä•ŔcĐ0~˙lP˛zŔ .©Đ9ľYĂäŠN!v¸˘¶ý±q¸Řđŕ$µ\ŘĚýőף׽­úqOÜ:^ŁĹ±Ůłg;JKKĄ'Ą žďĂW„źéô|©ĐyôÍśŠ ýFôw%5Âĉw‹ŽŔ†˛S®¤ĄĄÁˇC‡Lk¶Č=±K”ÁŤ¬YłF™4i’{·Îy#0Ő€ü:3+ ýÓćőë ôŹT äXÂn·+ZÚlß܇@bo˝ő–ňŘcŹ9čxO1çG®Ľ’[†Íâ5އr¸·ňp‰ëĄtś î2ŘrssUW¦I"ÜYPôŹń đ­č–LĺŔAĽŹ›Ú+ z&ąuepcÖŢ{âţĽÝ†GŚ(č:Ő!^#ۢ˙wE.LĆĹ+ĆBŕP%ó-A%3‚C˝jŰ!CTcŐ´íÚpOÜ6>>_%_azÇăjčbtËöư‹GŐd§¸•Űiú6ÇJX+„—qOó›ß(¸M!őŔIxÄç㯄«ş˘ç†EŕÇŠłN·>Zăqg ¸¸Ř†ź«ZžŃżą'PBż˙ýď=Üż:„  ¶á¸}nóőĹ- n<ĽüňËŠ–6Ă7÷ÄH‰”°–z`'Ż}…aZŻŕ[Ă€ܶO•Đ)č9rÄ4űĆÜk’óó{ĺĘ•Ęoű[‰Ŕ¤¤ńôŐ™Ŕ~béâă23 #ŃíőÝaĹŠJ¤ëĺëű™Äľ"%”CŠĘ>(Oš„+›ä6ŐÇ‚ Uĺ¨ÄŁěnę-›ĽýöŰ>ÜiŚ"Lb?ĺ@ÇÍ 1ąM]wŇ*ôŻqx@*ű»rb˛Čä¬LHD2kĺśOZwZÚČßîZą–ŞŰÜąsńXRŞÚÂĽ0”Wˇ $%˙«’‚S!:d] Ë–-SÄ´QăLb?$óŔ(č#;_ĽĺvÔĂŻľxťăćAŕÔâÚçÓČKĚ3bśIěŁTŢyç?ŇBÖîÝ`vvźŔĹŚŽ¸~%š„jˇ íŹß}÷]EKő›IědČĎő˘E‹$ÓŢâCh›ĘÁZĐÜX řŹ[L2Î$öA,÷Ýwźrń"ôŰhdŃ < ó8X 1¨Š©_ŕRUU1r+™ÄíHőˇ•M›6Ióŕą9Ї•9ÚAÎś—“ńóhô¸"Ł©™Ä˘´tqZÔxöŮgĄaô5¨¦7Ą'[$é ˛Tr˘nHŤ[M†n“¸ ńaCMŤŰ©ůŔş`nwđ%+ pUZčšŕj 2żvíZĹ•a°“¸¬_ż^Á˙ŔŇ0z~˙~žčn+·r¶É ÷H´ó †Ź?ţXÓFŠ3‰[‘ř‹—ňRSaRyĺRĽÎqk!@ \břüóĎŤˇâLb/â ă˝RÇ=rđx>Ř \–Ě"Âú!őW_}Ą±±Lb/RŃ{wŹV.ą¬í)ëfŃZżJ˝nÝ:Ĺ-f뤲jŐ*:HÍ5&€Hµ’Cô!0<]öʲaĂC‚Ŕ$Ö‰eéŇĄŠ5ľGwČÂć9DCĐ©ĽđĽ-1i8“XÎ=;föí-”ŕh4!@î…3;ąťб¶ô1LbA"zÓłah^Ř'Ůí Q(ĘŃ(A€NěñcÇ1m„8“X·…Ŕ4ťišt‘Q«#†S§Üg8‰ů‘Ś3‰[Đ'/®­´„xáĺŔłH ‹ß~Ä3›číUUUáŻD;od·´zőjEÄjúÓQ˘˝n€ţ|-# Ă$n‘—_~)ÉÎćŔ\FÇb çňF Lb”ť#,ĄÉwtžŕáÁhBăú„‹čL^ ééébŇq&1ŠAoôMNďřŚ`Cü>#^‰ Lâ Á— lܸQ*6MŃ80„Ŕéş: \ž_Ě=1ЇӒ ÄQ› ůpyťŰż8a6lj4@˘žÄhđ­”––ş¶–pEşŹnßhBăú„“µµpIXŘęÓÇžMŁžÄ;věPÄźičđ|XD$ză%«ĄĆŹ1BJ%ő$.**’dÁj–Qť(Şr{8% FŽiH<˘žÄ8”–ÓMPx—.p"ę8pAÖÎ3fŚ!1z—••I‚I×éĘJ95Ô]şGtĂéÉ“'«F ęIŚJ’\ô ďŇEND ű±µ dĎĚĚTŤ@Ô“¸W ĹËúŇ"ŃßQyNjúÔ©SĄ´‘QObńśa ť5ĚřélĄ­·ŢŞJJD=‰őV)z«ÉŠ«&Nŕ§jÝšZIIIö™3gŞaz˝ß݉z§˘?i1Đ‚‡čF`[…Ü +Šbh@Ä:×^şlhqĺBŹŔVÝPzÖ¬Yˇioz§é<ęMĎŔ–o5!çŕ nř¶ŰnSŤÜ”¨'qßľ˛Oé3ő’»‘eÇu ´ %ş=z´=;;[ Á«‚öȨ'qNNަÎjEşĆ ë#đŁn><{ölĂ7:ęI¬·=Y#ď^‚\Á !P‹‹š»uűĂsćĚQö‚=(ęIŚĂ%ý&Ů5|ËPůW¨54˘ë›<ÓĂĽĽt´Ú}°¸ëG,ŹŔŹgĄ6b/,ĄŤš`ŁdFŤ%ÉGż:)]ä„%hĽ|¶ź•U-çÍ›§šˇ±Lb”Ňő×_/ÉjĎąóRšÖG`ďů P#(úôęŐË>nÜ8Ő -gŁ”Pą]V! T´`1 ąŽ!đĂsĄ©ŐLbaČ!*úOr-nŐáĐj™Ct @ń¶ž•Il–ˇ4IIÜň;˝ĺ–[¤_ě–3˛ť±t‘–B€¬˙‡˘×í±LbĘ;î¸CH8ĘNKiNXbôh)ęËŁiŞÝ ZZ˘$Ä=ô*$”á+äÄË·z‘™®ULbAd¤‚‰Š®!5iom(3ŢÉđB•9 ß›|(MÍgë~Ź<ň*f­?y jŮۇ‰UâÇQ˝–ŽjŃąá™?ľŞĄÍňÍ$ÖIjáÂ…*iëh٤Ĺó_ÜkpXę[?”FĄS¶ŹIěEl‹/–rż*= d¦ĆÁZč=ZÎť;×” d{ŰŁŹ>ŞŠn{h»i™u¸€nxÄł–ČË©‘=Z¶…<“Ř :¨±Ł>ůä“6ń‘ř\}Ĺq#@¶Ă˘žkŻ˝ÖŽÓ(ŐŚMb·"µ?ţńŹŇÜô©W=ÖJiÎ6úˇ´^íÖLía·!­üü|UĽüÍ©rOâuŽ›R§ÝU)››˘[ZŐµ÷¬%“ŘWÎĂ?¬6̵RMŢ-:Ě:Ő.„̡ąpµ°P™••e's¶†÷‰Ű•۲eË$á–ŕŢ➏Cm÷!\ŔPčuâŃbÉPőó·2Ü·ŘĉŐ{ď˝WęŤW•R<݇9Ř­óÜrăŤ7šł!-µfű ľW_}•,ś\%É#âňĹ<¬v!bžißŇťđ@lćigM™ÄžxäĐáŇoľů¦´ĺDóŞż?áQ–3ŚŤ@áůópY¨"­yNr€ ”IDATukIk“XC˘ťoŇ©˝óÎ;Ąaő§GŹůăâ`ôň2«ŞĄ8“XDŁť8öĆjĎž=]ĄHY`ŮţCčÚ…•@\ <˘'ń”)S ^ăö«Ç$n#W VŻ\ąŇ놭 üö"ž»P2n¤ŐgŹ\¬vUT-'Ož¬ş2Lq˙MÚ€pW›¨=óĚ3Ňüx®vŇŠ5c#°´DUËáÇŰéł±kÝ~íÄícäQâąçžSń?¸4?ţu«7—źń(ËĆA€ĽZŠe(&MgwPt}ôůŞ–î^q¨ăŞ5c" ?žgüřńƬ¨źµbű Vś¶%>űě3[§Nť´,h¸ÜŻěÝÇř@6&F‰ľ´hzHőš0a‚j”úR&q葾íŠ+¤ů1Ůż¸»·žŔ5·­®†z´DÓťřˇ?›Z»f¶o&q€[°`úôÓO{%ňj\ě8ľŠoâ*÷Ş4=K ŕiĆş•Iy,Y˛D]´h‘DdZý5şţiŰNř &Ř˝O€ŕúÓ˝ą‡×V ńViÚĂj­žl>ř ˛˙ţ|±NµHÜŤ§ËťźNř’Ö†vMAi©“’ńş™x/ÇG A‡/“8pL-ű2]Ü·oźúĘ+ݍ/˝ô’R^^.‘™N®~¶cŹL ¨9”Ó9úwč—’ }ń“çĽÎG 16FzH­ŕoZş`ÂDL.˝soĽń†‚:×Jaaˇ™Ű{k÷ÄDč“’˝““ˇwRôJĆ~wKLRäŕ;4Ťˇí?- ůˇ}ÝşuZÚĚß<ś±ôpÁ‹˝ÔÍ›7«¨ ˘¬Ył>ěˇé /úěÔů˘ů]ϤNNRą‰ä}‰ěIÉÇËŢDj徊IěMâ!ČĂ- •>K—.ě•Őőë×+‡6mÚ'Ožô‰ÔZµhŰę(*”ĐG Ô7g!ąűᛆçąřą"5RXĚ"NŤXô×Ě–féF@bC† Qé=´óíŘ3«[·nU¶oß;vě€]»vQo ŤŤŤ~‘» źVV[çügSŻť×%†¦Ą9Ó2:%F ŐüĘP!Ŕ$˛~<—4‡čsűí·Kw!™Ő={öĐJ78pč›>~‘[#öźn6Đčsëk3şÁîÝś‹iŇK-šh’ě—¬ŐH&±ĺ‰®cČe®ŞŻ"®x«Hf'ąq%ś†ç~őŢĄ5µPŠŠ(ä^zi[Ď,Ô#ÓŇĂîFݤ8 ­Ä&”$ŮŔŇř=j˙Ă?¨?ýô“˛mŰ6ŔĹ4çđĽ­a9őŇ9 ¤":)«Ěę۬8ÜÖ«ż¦ŕşU“Ř*’lie衫ľůćÓŘůHjŹV“O:PýŰS§áĆŢ=aNvKíS7ŕb Ä"7<Š˘¨ôÁci ˛˛R]»v­˛zőjřňË/óëq K 4ě¤ĂăČÁÁ˝úĂ(ś;[!Ô]’IŚŠ[ˇYÎ6đ¦˘eDé[CčÄG´Ľ*řôÓO Ž?nC­22Éłëď>‹'@ţËľđoččŔ Ƥ%'+őÄLbQ˛Q§yőSO=UP\\\°|ůr›72‹‡ČýqűnŘor׼ä$O ]»v“¦Ž3‰M-ľŕU~áÂ…*‘Źtµ%$$H>]W˙ŚŽ>ĆĹ/łZ`]Ä“Ä žč!ć›1Î$6ŁÔBXgr¸eË›ţ4HR$Y‹[RDć3Hjł…‹şžIl6 r}ýB`äČ‘ęÎť; üq›ŢĐâ:üÓŽÝ ?¤ŰŻD p“8¨ó+#Žęy«6l°eggKuˇˇéŇÂÎýĺK:% © äűL Ü‹hpÜŇĐÖöʶŰn»Íc›¶˘–ŕđú®d=TęęŽňTŁ×Ů×úńśŘW¤˘¸mKˇ{Ţ‚—_~Ů/ë‘Cöüť»ˇŘŔţ¶i1NNcL˘řsd‹hpĽMžxâ uăĆŤ¶~ýúIĺhOů…]{aS‹…tщsşﲲ˛ P«ŕUI<,ŁâIdŤć’¶éÓ§KĂkRŰ|ű`|€Nőž%# ý“ţäńšăLb3J-Âu¦áő?ţńŹě™múŞ|…îy_ÇEŻşKňľ¬ľ\8ÓgędŐRýB]8ëŠw1‰Cj”<çČęűďżoÓë!“ŔpÁ«R§—)XHYE ¨™&&Mg›^„‘mť€Aód}ďFçŰwîĹs©"ďßą\GâţýűG´ żťId@Łńqdúřý÷ßŰđ[š'“ăüçwÂn<ż9’á4ÚL‹aŕŔbŇôq&±éEhŚĐ)‘Hä‚9sćHóäśżŠ®b·ś©XEőĂi$±±Ę„ŕĹLâ€ÍŹDGŹs©H«ë/űZv:ěĐб®'¶{syöŠń…Lâ ‚ÉŹjF€ÎĄB•MIďš (ţZtľD#Šp†S8-‰srrÂůú°Ľ‹IŁď%h<ˇľ÷Ţ{^ˇ9ă OŻp…čP  “–3‰-!Fc6‚V®qxí±őę\ż˝r8”Bô$FßĆ+€Z1‰Źom™3gŞ_ýµ- ׋Áóăčú'ÔD.­‘OÉ,žü€ZevŇű¤ FĽ—IlD©X¸Ntć”7"‡Cë`ů¨NĺsÄ–D–IlI±»Qm™V­JP[ Lb Ž3"Đ‘żA?×Î>˛ŢŰČ5×\`­Ťy;÷ÄĆ”KTÔJ#˛Ţj݉“đ÷cĄaĐ„‹Z‡u=ńu×]§ôPŢĚ$6¨`˘ĄZDd‡ĂaÓ»ĚYuô8l8yŞĂ0ĐiŹd|ˇ<킎«Qµ´•ľ™ÄV’¦IŰB«Öč)Ħ?Zĺ˝â#đżx°[GB‘Îqn-uä1¦¸‡Il 1Yż’´ŹŚ§5Ú’““]Ť%ى·ĂŽłç\yľFŠŞŞ¤˘8”–ŇVJ0‰­$M“·…4»>ůäé,(RŘxc˙A8rń˘_­#Wşb3fŚ´TśIl)qšż13fĚpZ?ĹĆşšt,ék{ř|T=žE|´ZV·ś0a‚j~tĽ·ŔŤ”÷ëśË„ůóç«oľů¦ä!¤}GżŠD®Ńťnč­r4”Ź—AóC;ăę­¬ňÄV˘ŰđđĂ«z—¸ÇŃ"é_qhÝŢńŞűtCiě…-»ILb73ä{e©GŢN÷Ţ):ŇfM÷éDź4iR›ĺÍ~‘Ilv ZĽţ|đŠ=©]lćĆÓĺ­*Đ0Zż¨5yňdUĽßjq&±Ő$jÁöŕan*î%K-[ŤĘ Ű*ÎJy” ůp=.„iŹl±Ó>´–¶â7“ŘŠRµX›hQjÝşu¶îÝ»»ZF{Čo(Bçôň*ôžs\e(2uęT)mœ؊Rµ`›¨7]µj•äxŻ{\:÷©ŞÁ}€8Í™Ĺ`łISjń’eâLbËŇú ˇĎŃ®ÄJr ˙—‡śč7ý|{bŐęČ0‰­.a‹µŹ¶žđ#™Ž‰Y…®p qUZż?lUŁQ¬Lb Ž›eË–©¸â,­X“\Z잡,&-g[V´ÖnŘęŐ«UýaátŁnşé&UL[5CĆÓ3"°yóf{dGŞdęž™lŻ©©)Đç[1Í=±Ą%m7nśúÚkŻIóc­é¸¦E-˙Í$¶Ľ­ÝŔE‹©wÜq‡4?¦Ďš5ËÚ ZÇĂi ŽšĘĘJť 8Ž9âl@LL ”””ŘĐw—jÎůWkî‰ýĂ‹KrżrĺJ›f<~üx{´ÄÁ$6ŕŹ’«ä?ää™gžqÎŹçÍ›ç˙L|§M,<®ş'cÇŽ- í§hꉙĞżÎ11ĺĺ劕˝xx “Ř*śÇž›HX\UFŔLbo¨p#`"ţűkŚßR—íIEND®B`‚beetbox-beets-01f1faf/docs/changelog.rst000066400000000000000000010025531472325477400203660ustar00rootroot00000000000000Changelog ========= Changelog goes here! Please add your entry to the bottom of one of the lists below! Unreleased ---------- New features: * :doc:`/plugins/substitute`: Allow the replacement string to use capture groups from the match. It is thus possible to create more general rules, applying to many different artists at once. Bug fixes: * Check if running python from the Microsoft Store and provide feedback to install from python.org. :bug:`5467` * Fix bug where matcher doesn't consider medium number when importing. This makes it difficult to import hybrid SACDs and other releases with duplicate tracks. :bug:`5148` * Bring back test files and the manual to the source distribution tarball. :bug:`5513` For packagers: Other changes: * Changed `bitesize` label to `good first issue`. Our `contribute`_ page is now automatically populated with these issues. :bug:`4855` .. _contribute: https://github.com/beetbox/beets/contribute 2.1.0 (November 22, 2024) ------------------------- New features: * New template function added: ``%capitalize``. Converts the first letter of the text to uppercase and the rest to lowercase. * Ability to query albums with track db fields and vice-versa, for example ``beet list -a title:something`` or ``beet list artpath:cover``. Consequently album queries involving ``path`` field have been sped up, like ``beet list -a path:/path/``. * :doc:`plugins/ftintitle`: New ``keep_in_artist`` option for the plugin, which allows keeping the "feat." part in the artist metadata while still changing the title. * :doc:`plugins/autobpm`: Add new configuration option ``beat_track_kwargs`` which enables adjusting keyword arguments supplied to librosa's ``beat_track`` function call. * Beets now uses ``platformdirs`` to determine the default music directory. This location varies between systems -- for example, users can configure it on Unix systems via ``user-dirs.dirs(5)``. Bug fixes: * :doc:`plugins/ftintitle`: The detection of a "feat. X" part in a song title does not produce any false positives caused by words like "and" or "with" anymore. :bug:`5441` * :doc:`plugins/ftintitle`: The detection of a "feat. X" part now also matches such parts if they are in parentheses or brackets. :bug:`5436` * Improve naming of temporary files by separating the random part with the file extension. * Fix the ``auto`` value for the :ref:`reflink` config option. * Fix lyrics plugin only getting part of the lyrics from ``Genius.com`` :bug:`4815` * Album flexible fields are now correctly saved. For instance MusicBrainz external links such as `bandcamp_album_id` will be available on albums in addition to tracks. For albums already in your library, a re-import is required for the fields to be added. Such a re-import can be done with, in this case, `beet import -L data_source:=MusicBrainz`. * :doc:`plugins/autobpm`: Fix the ``TypeError`` where tempo was being returned as a numpy array. Update ``librosa`` dependency constraint to prevent similar issues in the future. :bug:`5289` * :doc:`plugins/discogs`: Fix the ``TypeError`` when there is no description. * Use single quotes in all SQL queries :bug:`4709` * :doc:`plugins/lyrics`: Update ``tekstowo`` backend to fetch lyrics directly since recent updates to their website made it unsearchable. :bug:`5456` * :doc:`plugins/convert`: Fixed the convert plugin ``no_convert`` option so that it no longer treats "and" and "or" queries the same. To maintain previous behaviour add commas between your query keywords. For help see :ref:`combiningqueries`. * Fix the ``TypeError`` when :ref:`set_fields` is provided non-string values. :bug:`4840` For packagers: * The minimum supported Python version is now 3.8. * The ``beet`` script has been removed from the repository. * The ``typing_extensions`` is required for Python 3.10 and below. Other changes: * :doc:`contributing`: The project now uses ``poetry`` for packaging and dependency management. This change affects project management and mostly affects beets developers. Please see updates in :ref:`getting-the-source` and :ref:`testing` for more information. * :doc:`contributing`: Since ``poetry`` now manages local virtual environments, `tox` has been replaced by a task runner ``poethepoet``. This change affects beets developers and contributors. Please see updates in the :ref:`development-tools` section for more details. Type ``poe`` while in the project directory to see the available commands. * Installation instructions have been made consistent across plugins documentation. Users should simply install ``beets`` with an ``extra`` of the corresponding plugin name in order to install extra dependencies for that plugin. * GitHub workflows have been reorganised for clarity: style, linting, type and docs checks now live in separate jobs and are named accordingly. * Added caching for dependency installation in all CI jobs which speeds them up a bit, especially the tests. * The linting workflow has been made to run only when Python files or documentation is changed, and they only check the changed files. When dependencies are updated (``poetry.lock``), then the entire code base is checked. * The long-deprecated ``beets.util.confit`` module has been removed. This may cause extremely outdated external plugins to fail to load. * :doc:`plugins/autobpm`: Add plugin dependencies to ``pyproject.toml`` under the ``autobpm`` extra and update the plugin installation instructions in the docs. Since importing the bpm calculation functionality from ``librosa`` takes around 4 seconds, update the plugin to only do so when it actually needs to calculate the bpm. Previously this import was being done immediately, so every ``beet`` invocation was being delayed by a couple of seconds. :bug:`5185` 2.0.0 (May 30, 2024) -------------------- With this release, beets now requires Python 3.7 or later (it removes support for Python 3.6). Major new features: * The beets importer UI received a major overhaul. Several new configuration options are available for customizing layout and colors: :ref:`ui_options`. :bug:`3721` :bug:`5028` New features: * :doc:`/plugins/edit`: Prefer editor from ``VISUAL`` environment variable over ``EDITOR``. * :ref:`config-cmd`: Prefer editor from ``VISUAL`` environment variable over ``EDITOR``. * :doc:`/plugins/listenbrainz`: Add initial support for importing history and playlists from `ListenBrainz` :bug:`1719` * :doc:`plugins/mbsubmit`: add new prompt choices helping further to submit unmatched tracks to MusicBrainz faster. * :doc:`plugins/spotify`: We now fetch track's ISRC, EAN, and UPC identifiers from Spotify when using the ``spotifysync`` command. :bug:`4992` * :doc:`plugins/discogs`: supply a value for the `cover_art_url` attribute, for use by `fetchart`. :bug:`429` * :ref:`update-cmd`: added ```-e``` flag for excluding fields from being updated. * :doc:`/plugins/deezer`: Import rank and other attributes from Deezer during import and add a function to update the rank of existing items. :bug:`4841` * resolve transl-tracklisting relations for pseudo releases and merge data with the actual release :bug:`654` * Fetchart: Use the right field (`spotify_album_id`) to obtain the Spotify album id :bug:`4803` * Prevent reimporting album if it is permanently removed from Spotify :bug:`4800` * Added option to use `cover_art_url` as an album art source in the `fetchart` plugin. :bug:`4707` * :doc:`/plugins/fetchart`: The plugin can now get album art from `spotify`. * Added option to specify a URL in the `embedart` plugin. :bug:`83` * :ref:`list-cmd` `singleton:true` queries have been made faster * :ref:`list-cmd` `singleton:1` and `singleton:0` can now alternatively be used in queries, same as `comp` * --from-logfile now parses log files using a UTF-8 encoding in `beets/beets/ui/commands.py`. :bug:`4693` * :doc:`/plugins/bareasc` lookups have been made faster * :ref:`list-cmd` lookups using the pattern operator `::` have been made faster * Added additional error handling for `spotify` plugin. :bug:`4686` * We now import the remixer field from Musicbrainz into the library. :bug:`4428` * :doc:`/plugins/mbsubmit`: Added a new `mbsubmit` command to print track information to be submitted to MusicBrainz after initial import. :bug:`4455` * Added `spotify_updated` field to track when the information was last updated. * We now import and tag the `album` information when importing singletons using Spotify source. :bug:`4398` * :doc:`/plugins/spotify`: The plugin now provides an additional command `spotifysync` that allows getting track popularity and audio features information from Spotify. :bug:`4094` * :doc:`/plugins/spotify`: The plugin now records Spotify-specific IDs in the `spotify_album_id`, `spotify_artist_id`, and `spotify_track_id` fields. :bug:`4348` * Create the parental directories for database if they do not exist. :bug:`3808` :bug:`4327` * :ref:`musicbrainz-config`: a new :ref:`musicbrainz.enabled` option allows disabling the MusicBrainz metadata source during the autotagging process * :doc:`/plugins/kodiupdate`: Now supports multiple kodi instances :bug:`4101` * Add the item fields ``bitrate_mode``, ``encoder_info`` and ``encoder_settings``. * Add query prefixes ``=`` and ``~``. * A new configuration option, :ref:`duplicate_keys`, lets you change which fields the beets importer uses to identify duplicates. :bug:`1133` :bug:`4199` * Add :ref:`exact match ` queries, using the prefixes ``=`` and ``=~``. :bug:`4251` * :doc:`/plugins/discogs`: Permit appending style to genre. * :doc:`plugins/discogs`: Implement item_candidates for matching singletons. * :doc:`plugins/discogs`: Check for compliant discogs_client module. * :doc:`/plugins/convert`: Add a new `auto_keep` option that automatically converts files but keeps the *originals* in the library. :bug:`1840` :bug:`4302` * Added a ``-P`` (or ``--disable-plugins``) flag to specify one/multiple plugin(s) to be disabled at startup. * :ref:`import-options`: Add support for re-running the importer on paths in log files that were created with the ``-l`` (or ``--logfile``) argument. :bug:`4379` :bug:`4387` * Preserve mtimes from archives :bug:`4392` * Add :ref:`%sunique{} ` template to disambiguate between singletons. :bug:`4438` * Add a new ``import.ignored_alias_types`` config option to allow for specific alias types to be skipped over when importing items/albums. * :doc:`/plugins/smartplaylist`: A new ``--pretend`` option lets the user see what a new or changed smart playlist saved in the config is actually returning. :bug:`4573` * :doc:`/plugins/fromfilename`: Add debug log messages that inform when the plugin replaced bad (missing) artist, title or tracknumber metadata. :bug:`4561` :bug:`4600` * :ref:`musicbrainz-config`: MusicBrainz release pages often link to related metadata sources like Discogs, Bandcamp, Spotify, Deezer and Beatport. When enabled via the :ref:`musicbrainz.external_ids` options, release ID's will be extracted from those URL's and imported to the library. :bug:`4220` * :doc:`/plugins/convert`: Add support for generating m3u8 playlists together with converted media files. :bug:`4373` * Fetch the ``release_group_title`` field from MusicBrainz. :bug: `4809` * :doc:`plugins/discogs`: Add support for applying album information on singleton imports. :bug: `4716` * :doc:`/plugins/smartplaylist`: During explicit runs of the ``splupdate`` command, the log message "Creating playlist ..."" is now displayed instead of hidden in the debug log, which states some form of progress through the UI. :bug:`4861` * :doc:`plugins/subsonicupdate`: Updates are now triggered whenever either the beets database is changed or a smart playlist is created/updated. :bug: `4862` * :doc:`plugins/importfeeds`: Add a new output format allowing to save a playlist once per import session. :bug: `4863` * Make ArtResizer work with :pypi:`PIL`/:pypi:`pillow` 10.0.0 removals. :bug:`4869` * A new configuration option, :ref:`duplicate_verbose_prompt`, allows changing how duplicates are presented during import. :bug: `4866` * :doc:`/plugins/embyupdate`: Add handling for private users by adding ``userid`` config option. :bug:`4402` * :doc:`/plugins/substitute`: Add the new plugin `substitute` as an alternative to the `rewrite` plugin. The main difference between them being that `rewrite` modifies files' metadata and `substitute` does not. :bug:`2786` * Add support for ``artists`` and ``albumartists`` multi-valued tags. :bug:`505` * :doc:`/plugins/autobpm`: Add the `autobpm` plugin which uses Librosa to calculate the BPM of the audio. :bug:`3856` * :doc:`/plugins/fetchart`: Fix the error with CoverArtArchive where the `maxwidth` option would not be used to download a pre-sized thumbnail for release groups, as is already done with releases. * :doc:`/plugins/fetchart`: Fix the error with CoverArtArchive where no cover would be found when the `maxwidth` option matches a pre-sized thumbnail size, but no thumbnail is provided by CAA. We now fallback to the raw image. * :doc:`/plugins/advancedrewrite`: Add an advanced version of the `rewrite` plugin which allows to replace fields based on a given library query. * :doc:`/plugins/lyrics`: Add LRCLIB as a new lyrics provider and a new `synced` option to prefer synced lyrics over plain lyrics. * :ref:`import-cmd`: Expose import.quiet_fallback as CLI option. * :ref:`import-cmd`: Expose `import.incremental_skip_later` as CLI option. * :doc:`/plugins/smartplaylist`: Expose config options as CLI options. * :doc:`/plugins/smartplaylist`: Add new option `smartplaylist.output`. * :doc:`/plugins/smartplaylist`: Add new option `smartplaylist.uri_format`. * Sorted the default configuration file into categories. :bug:`4987` * :doc:`/plugins/convert`: Don't treat WAVE (`.wav`) files as lossy anymore when using the `never_convert_lossy_files` option. They will get transcoded like the other lossless formats. * Add support for `barcode` field. :bug:`3172` * :doc:`/plugins/smartplaylist`: Add new config option `smartplaylist.fields`. * :doc:`/plugins/fetchart`: Defer source removal config option evaluation to the point where they are used really, supporting temporary config changes. Bug fixes: * Improve ListenBrainz error handling. :bug:`5459` * :doc:`/plugins/deezer`: Improve requests error handling. * :doc:`/plugins/lastimport`: Improve error handling in the `process_tracks` function and enable it to be used with other plugins. * :doc:`/plugins/spotify`: Improve handling of ConnectionError. * :doc:`/plugins/deezer`: Improve Deezer plugin error handling and set requests timeout to 10 seconds. :bug:`4983` * :doc:`/plugins/spotify`: Add bad gateway (502) error handling. * :doc:`/plugins/spotify`: Add a limit of 3 retries, instead of retrying endlessly when the API is not available. * Fix a crash when the Spotify API timeouts or does not return a `Retry-After` interval. :bug:`4942` * :doc:`/plugins/scrub`: Fixed the import behavior where scrubbed database tags were restored to newly imported tracks with config settings ``scrub.auto: yes`` and ``import.write: no``. :bug:`4326` * :doc:`/plugins/deezer`: Fixed the error where Deezer plugin would crash if non-Deezer id is passed during import. * :doc:`/plugins/fetchart`: Fix fetching from Cover Art Archive when the `maxwidth` option is set to one of the supported Cover Art Archive widths. * :doc:`/plugins/discogs`: Fix "Discogs plugin replacing Feat. or Ft. with a comma" by fixing an oversight that removed a functionality from the code base when the MetadataSourcePlugin abstract class was introduced in PR's #3335 and #3371. :bug:`4401` * :doc:`/plugins/convert`: Set default ``max_bitrate`` value to ``None`` to avoid transcoding when this parameter is not set. :bug:`4472` * :doc:`/plugins/replaygain`: Avoid a crash when errors occur in the analysis backend. :bug:`4506` * We now use Python's defaults for command-line argument encoding, which should reduce the chance for errors and "file not found" failures when invoking other command-line tools, especially on Windows. :bug:`4507` * We now respect the Spotify API's rate limiting, which avoids crashing when the API reports code 429 (too many requests). :bug:`4370` * Fix implicit paths OR queries (e.g. ``beet list /path/ , /other-path/``) which have previously been returning the entire library. :bug:`1865` * The Discogs release ID is now populated correctly to the discogs_albumid field again (it was no longer working after Discogs changed their release URL format). :bug:`4225` * The autotagger no longer considers all matches without a MusicBrainz ID as duplicates of each other. :bug:`4299` * :doc:`/plugins/convert`: Resize album art when embedding :bug:`2116` * :doc:`/plugins/deezer`: Fix auto tagger pagination issues (fetch beyond the first 25 tracks of a release). * :doc:`/plugins/spotify`: Fix auto tagger pagination issues (fetch beyond the first 50 tracks of a release). * :doc:`/plugins/lyrics`: Fix Genius search by using query params instead of body. * :doc:`/plugins/unimported`: The new ``ignore_subdirectories`` configuration option added in 1.6.0 now has a default value if it hasn't been set. * :doc:`/plugins/deezer`: Tolerate missing fields when searching for singleton tracks. :bug:`4116` * :doc:`/plugins/replaygain`: The type of the internal ``r128_track_gain`` and ``r128_album_gain`` fields was changed from integer to float to fix loss of precision due to truncation. :bug:`4169` * Fix a regression in the previous release that caused a `TypeError` when moving files across filesystems. :bug:`4168` * :doc:`/plugins/convert`: Deleting the original files during conversion no longer logs output when the ``quiet`` flag is enabled. * :doc:`plugins/web`: Fix handling of "query" requests. Previously queries consisting of more than one token (separated by a slash) always returned an empty result. * :doc:`/plugins/discogs`: Skip Discogs query on insufficiently tagged files (artist and album tags missing) to prevent arbitrary candidate results. :bug:`4227` * :doc:`plugins/lyrics`: Fixed issues with the Tekstowo.pl and Genius backends where some non-lyrics content got included in the lyrics * :doc:`plugins/limit`: Better header formatting to improve index * :doc:`plugins/replaygain`: Correctly handle the ``overwrite`` config option, which forces recomputing ReplayGain values on import even for tracks that already have the tags. * :doc:`plugins/embedart`: Fix a crash when using recent versions of ImageMagick and the ``compare_threshold`` option. :bug:`4272` * :doc:`plugins/lyrics`: Fixed issue with Genius header being included in lyrics, added test case of up-to-date Genius html * :doc:`plugins/importadded`: Fix a bug with recently added reflink import option that causes a crash when ImportAdded plugin enabled. :bug:`4389` * :doc:`plugins/convert`: Fix a bug with the `wma` format alias. * :doc:`/plugins/web`: Fix get file from item. * :doc:`/plugins/lastgenre`: Fix a duplicated entry for trip hop in the default genre list. :bug:`4510` * :doc:`plugins/lyrics`: Fixed issue with Tekstowo backend not actually checking if the found song matches. :bug:`4406` * :doc:`plugins/embedart`: Add support for ImageMagick 7.1.1-12 :bug:`4836` * :doc:`/plugins/fromfilename`: Fix failed detection of filename patterns. :bug:`4561` :bug:`4600` * Fix issue where deletion of flexible fields on an album doesn't cascade to items :bug:`4662` * Fix issue where ``beet write`` continuously retags the ``albumtypes`` metadata field in files. Additionally broken data could have been added to the library when the tag was read from file back into the library using ``beet update``. It is required for all users to **check if such broken data is present in the library**. Following the instructions `described here <https://github.com/beetbox/beets/pull/4582#issuecomment-1445023493>`_, a sanity check and potential fix is easily possible. :bug:`4528` * Fix updating "data_source" on re-imports and improve logging when flexible attributes are being re-imported. :bug:`4726` * :doc:`/plugins/fetchart`: Correctly select the cover art from fanart.tv with the highest number of likes * :doc:`/plugins/lyrics`: Fix a crash with the Google backend when processing some web pages. :bug:`4875` * Modifying flexible attributes of albums now cascade to the individual album tracks, similar to how fixed album attributes have been cascading to tracks already. A new option ``--noinherit/-I`` to :ref:`modify <modify-cmd>` allows changing this behaviour. :bug:`4822` * Fix bug where an interrupted import process poisons the database, causing a null path that can't be removed. :bug:`4906` * :doc:`/plugins/discogs`: Fix bug where empty artist and title fields would return None instead of an empty list. :bug:`4973` * Fix bug regarding displaying tracks that have been changed not being displayed unless the detail configuration is enabled. * :doc:`/plugins/web`: Fix range request support, allowing to play large audio/ opus files using e.g. a browser/firefox or gstreamer/mopidy directly. * Fix bug where `zsh` completion script made assumptions about the specific variant of `awk` installed and required specific settings for `sqlite3` and caching in `zsh`. :bug:`3546` * Remove unused functions :bug:`5103` * Fix bug where all media types are reported as the first media type when importing with MusicBrainz as the data source :bug:`4947` * Fix bug where unimported plugin would not ignore children directories of ignored directories. :bug:`5130` * Fix bug where some plugin commands hang indefinitely due to a missing `requests` timeout. * Fix cover art resizing logic to support multiple steps of resizing :bug:`5151` For plugin developers: * beets now explicitly prevents multiple plugins to define replacement functions for the same field. When previously defining `template_fields` for the same field in two plugins, the last loaded plugin would silently overwrite the function defined by the other plugin. Now, beets will raise an exception when this happens. :bug:`5002` * Allow reuse of some parts of beets' testing components. This may ease the work for externally developed plugins or related software (e.g. the beets plugin for Mopidy), if they need to create an in-memory instance of a beets music library for their tests. For packagers: * As noted above, the minimum Python version is now 3.7. * We fixed a version for the dependency on the `Confuse`_ library. :bug:`4167` * The minimum required version of :pypi:`mediafile` is now 0.9.0. Other changes: * Add ``sphinx`` and ``sphinx_rtd_theme`` as dependencies for a new ``docs`` extra :bug:`4643` * :doc:`/plugins/absubmit`: Deprecate the ``absubmit`` plugin since AcousticBrainz has stopped accepting new submissions. :bug:`4627` * :doc:`/plugins/acousticbrainz`: Deprecate the ``acousticbrainz`` plugin since the AcousticBrainz project has shut down. :bug:`4627` * :doc:`/plugins/limit`: Limit query results to head or tail (``lslimit`` command only) * :doc:`/plugins/fish`: Add ``--output`` option. * :doc:`/plugins/lyrics`: Remove Musixmatch from default enabled sources as they are currently blocking requests from the beets user agent. :bug:`4585` * :doc:`/faq`: :ref:`multidisc`: Elaborated the multi-disc FAQ :bug:`4806` * :doc:`/faq`: :ref:`src`: Removed some long lines. * Refactor the test cases to avoid test smells. 1.6.0 (November 27, 2021) ------------------------- This release is our first experiment with time-based releases! We are aiming to publish a new release of beets every 3 months. We therefore have a healthy but not dizzyingly long list of new features and fixes. With this release, beets now requires Python 3.6 or later (it removes support for Python 2.7, 3.4, and 3.5). There are also a few other dependency changes---if you're a maintainer of a beets package for a package manager, thank you for your ongoing efforts, and please see the list of notes below. Major new features: * When fetching genres from MusicBrainz, we now include genres from the release group (in addition to the release). We also prioritize genres based on the number of votes. Thanks to :user:`aereaux`. * Primary and secondary release types from MusicBrainz are now stored in a new ``albumtypes`` field. Thanks to :user:`edgars-supe`. :bug:`2200` * An accompanying new :doc:`/plugins/albumtypes` includes some options for formatting this new ``albumtypes`` field. Thanks to :user:`edgars-supe`. * The :ref:`modify-cmd` and :ref:`import-cmd` can now use :doc:`/reference/pathformat` formats when setting fields. For example, you can now do ``beet modify title='$track $title'`` to put track numbers into songs' titles. :bug:`488` Other new things: * :doc:`/plugins/permissions`: The plugin now sets cover art permissions to match the audio file permissions. * :doc:`/plugins/unimported`: A new configuration option supports excluding specific subdirectories in library. * :doc:`/plugins/info`: Add support for an ``--album`` flag. * :doc:`/plugins/export`: Similarly add support for an ``--album`` flag. * ``beet move`` now highlights path differences in color (when enabled). * When moving files and a direct rename of a file is not possible (for example, when crossing filesystems), beets now copies to a temporary file in the target folder first and then moves to the destination instead of directly copying the target path. This gets us closer to always updating files atomically. Thanks to :user:`catap`. :bug:`4060` * :doc:`/plugins/fetchart`: Add a new option to store cover art as non-progressive image. This is useful for DAPs that do not support progressive images. Set ``deinterlace: yes`` in your configuration to enable this conversion. * :doc:`/plugins/fetchart`: Add a new option to change the file format of cover art images. This may also be useful for DAPs that only support some image formats. * Support flexible attributes in ``%aunique``. :bug:`2678` :bug:`3553` * Make ``%aunique`` faster, especially when using inline fields. :bug:`4145` Bug fixes: * :doc:`/plugins/lyrics`: Fix a crash when Beautiful Soup is not installed. :bug:`4027` * :doc:`/plugins/discogs`: Support a new Discogs URL format for IDs. :bug:`4080` * :doc:`/plugins/discogs`: Remove built-in rate-limiting because the Discogs Python library we use now has its own rate-limiting. :bug:`4108` * :doc:`/plugins/export`: Fix some duplicated output. * :doc:`/plugins/aura`: Fix a potential security hole when serving image files. :bug:`4160` For plugin developers: * :py:meth:`beets.library.Item.destination` now accepts a `replacements` argument to be used in favor of the default. * The `pluginload` event is now sent after plugin types and queries are available, not before. * A new plugin event, `album_removed`, is called when an album is removed from the library (even when its file is not deleted from disk). Here are some notes for packagers: * As noted above, the minimum Python version is now 3.6. * We fixed a flaky test, named `test_album_art` in the `test_zero.py` file, that some distributions had disabled. Disabling this test should no longer be necessary. :bug:`4037` :bug:`4038` * This version of beets no longer depends on the `six`_ library. :bug:`4030` * The `gmusic` plugin was removed since Google Play Music has been shut down. Thus, the optional dependency on `gmusicapi` does not exist anymore. :bug:`4089` 1.5.0 (August 19, 2021) ----------------------- This long overdue release of beets includes far too many exciting and useful features than could ever be satisfactorily enumerated. As a technical detail, it also introduces two new external libraries: `MediaFile`_ and `Confuse`_ used to be part of beets but are now reusable dependencies---packagers, please take note. Finally, this is the last version of beets where we intend to support Python 2.x and 3.5; future releases will soon require Python 3.6. One non-technical change is that we moved our official ``#beets`` home on IRC from freenode to `Libera.Chat`_. .. _Libera.Chat: https://libera.chat/ Major new features: * Fields in queries now fall back to an item's album and check its fields too. Notably, this allows querying items by an album's attribute: in other words, ``beet list foo:bar`` will not only find tracks with the `foo` attribute; it will also find tracks *on albums* that have the `foo` attribute. This may be particularly useful in the :ref:`path-format-config`, which matches individual items to decide which path to use. Thanks to :user:`FichteFoll`. :bug:`2797` :bug:`2988` * A new :ref:`reflink` config option instructs the importer to create fast, copy-on-write file clones on filesystems that support them. Thanks to :user:`rubdos`. * A new :doc:`/plugins/unimported` lets you find untracked files in your library directory. * The :doc:`/plugins/aura` has arrived! Try out the future of remote music library access today. * We now fetch information about `works`_ from MusicBrainz. MusicBrainz matches provide the fields ``work`` (the title), ``mb_workid`` (the MBID), and ``work_disambig`` (the disambiguation string). Thanks to :user:`dosoe`. :bug:`2580` :bug:`3272` * A new :doc:`/plugins/parentwork` gets information about the original work, which is useful for classical music. Thanks to :user:`dosoe`. :bug:`2580` :bug:`3279` * :doc:`/plugins/bpd`: BPD now supports most of the features of version 0.16 of the MPD protocol. This is enough to get it talking to more complicated clients like ncmpcpp, but there are still some incompatibilities, largely due to MPD commands we don't support yet. (Let us know if you find an MPD client that doesn't get along with BPD!) :bug:`3214` :bug:`800` * A new :doc:`/plugins/deezer` can autotag tracks and albums using the `Deezer`_ database. Thanks to :user:`rhlahuja`. :bug:`3355` * A new :doc:`/plugins/bareasc` provides a new query type: "bare ASCII" queries that ignore accented characters, treating them as though they were plain ASCII characters. Use the ``#`` prefix with :ref:`list-cmd` or other commands. :bug:`3882` * :doc:`/plugins/fetchart`: The plugin can now get album art from `last.fm`_. :bug:`3530` * :doc:`/plugins/web`: The API now supports the HTTP `DELETE` and `PATCH` methods for modifying items. They are disabled by default; set ``readonly: no`` in your configuration file to enable modification via the API. :bug:`3870` Other new things: * ``beet remove`` now also allows interactive selection of items from the query, similar to ``beet modify``. * Enable HTTPS for MusicBrainz by default and add configuration option `https` for custom servers. See :ref:`musicbrainz-config` for more details. * :doc:`/plugins/mpdstats`: Add a new `strip_path` option to help build the right local path from MPD information. * :doc:`/plugins/convert`: Conversion can now parallelize conversion jobs on Python 3. * :doc:`/plugins/lastgenre`: Add a new `title_case` config option to make title-case formatting optional. * There's a new message when running ``beet config`` when there's no available configuration file. :bug:`3779` * When importing a duplicate album, the prompt now says "keep all" instead of "keep both" to reflect that there may be more than two albums involved. :bug:`3569` * :doc:`/plugins/chroma`: The plugin now updates file metadata after generating fingerprints through the `submit` command. * :doc:`/plugins/lastgenre`: Added more heavy metal genres to the built-in genre filter lists. * A new :doc:`/plugins/subsonicplaylist` can import playlists from a Subsonic server. * :doc:`/plugins/subsonicupdate`: The plugin now automatically chooses between token- and password-based authentication based on the server version. * A new :ref:`extra_tags` configuration option lets you use more metadata in MusicBrainz queries to further narrow the search. * A new :doc:`/plugins/fish` adds `Fish shell`_ tab autocompletion to beets. * :doc:`plugins/fetchart` and :doc:`plugins/embedart`: Added a new ``quality`` option that controls the quality of the image output when the image is resized. * :doc:`plugins/keyfinder`: Added support for `keyfinder-cli`_. Thanks to :user:`BrainDamage`. * :doc:`plugins/fetchart`: Added a new ``high_resolution`` config option to allow downloading of higher resolution iTunes artwork (at the expense of file size). :bug:`3391` * :doc:`plugins/discogs`: The plugin applies two new fields: `discogs_labelid` and `discogs_artistid`. :bug:`3413` * :doc:`/plugins/export`: Added a new ``-f`` (``--format``) flag, which can export your data as JSON, JSON lines, CSV, or XML. Thanks to :user:`austinmm`. :bug:`3402` * :doc:`/plugins/convert`: Added a new ``-l`` (``--link``) flag and ``link`` option as well as the ``-H`` (``--hardlink``) flag and ``hardlink`` option, which symlink or hardlink files that do not need to be converted (instead of copying them). :bug:`2324` * :doc:`/plugins/replaygain`: The plugin now supports a ``per_disc`` option that enables calculation of album ReplayGain on disc level instead of album level. Thanks to :user:`samuelnilsson`. :bug:`293` * :doc:`/plugins/replaygain`: The new ``ffmpeg`` ReplayGain backend supports ``R128_`` tags. :bug:`3056` * :doc:`plugins/replaygain`: A new ``r128_targetlevel`` configuration option defines the reference volume for files using ``R128_`` tags. ``targetlevel`` only configures the reference volume for ``REPLAYGAIN_`` files. :bug:`3065` * :doc:`/plugins/discogs`: The plugin now collects the "style" field. Thanks to :user:`thedevilisinthedetails`. :bug:`2579` :bug:`3251` * :doc:`/plugins/absubmit`: By default, the plugin now avoids re-analyzing files that already have AcousticBrainz data. There are new ``force`` and ``pretend`` options to help control this new behavior. Thanks to :user:`SusannaMaria`. :bug:`3318` * :doc:`/plugins/discogs`: The plugin now also gets genre information and a new ``discogs_albumid`` field from the Discogs API. Thanks to :user:`thedevilisinthedetails`. :bug:`465` :bug:`3322` * :doc:`/plugins/acousticbrainz`: The plugin now fetches two more additional fields: ``moods_mirex`` and ``timbre``. Thanks to :user:`malcops`. :bug:`2860` * :doc:`/plugins/playlist` and :doc:`/plugins/smartplaylist`: A new ``forward_slash`` config option facilitates compatibility with MPD on Windows. Thanks to :user:`MartyLake`. :bug:`3331` :bug:`3334` * The `data_source` field, which indicates which metadata source was used during an autotagging import, is now also applied as an album-level flexible attribute. :bug:`3350` :bug:`1693` * :doc:`/plugins/beatport`: The plugin now gets the musical key, BPM, and genre for each track. :bug:`2080` * A new :doc:`/plugins/bpsync` can synchronize metadata changes from the Beatport database (like the existing :doc:`/plugins/mbsync` for MusicBrainz). * :doc:`/plugins/hook`: The plugin now treats non-zero exit codes as errors. :bug:`3409` * :doc:`/plugins/subsonicupdate`: A new ``url`` configuration replaces the older (and now deprecated) separate ``host``, ``port``, and ``contextpath`` config options. As a consequence, the plugin can now talk to Subsonic over HTTPS. Thanks to :user:`jef`. :bug:`3449` * :doc:`/plugins/discogs`: The new ``index_tracks`` option enables incorporation of work names and intra-work divisions into imported track titles. Thanks to :user:`cole-miller`. :bug:`3459` * :doc:`/plugins/web`: The query API now interprets backslashes as path separators to support path queries. Thanks to :user:`nmeum`. :bug:`3567` * ``beet import`` now handles tar archives with bzip2 or gzip compression. :bug:`3606` * ``beet import`` *also* now handles 7z archives, via the `py7zr`_ library. Thanks to :user:`arogl`. :bug:`3906` * :doc:`/plugins/plexupdate`: Added an option to use a secure connection to Plex server, and to ignore certificate validation errors if necessary. :bug:`2871` * :doc:`/plugins/convert`: A new ``delete_originals`` configuration option can delete the source files after conversion during import. Thanks to :user:`logan-arens`. :bug:`2947` * There is a new ``--plugins`` (or ``-p``) CLI flag to specify a list of plugins to load. * A new :ref:`genres` option fetches genre information from MusicBrainz. This functionality depends on functionality that is currently unreleased in the `python-musicbrainzngs`_ library: see PR `#266 <https://github.com/alastair/python-musicbrainzngs/pull/266>`_. Thanks to :user:`aereaux`. * :doc:`/plugins/replaygain`: Analysis now happens in parallel using the ``command`` and ``ffmpeg`` backends. :bug:`3478` * :doc:`plugins/replaygain`: The bs1770gain backend is removed. Thanks to :user:`SamuelCook`. * Added ``trackdisambig`` which stores the recording disambiguation from MusicBrainz for each track. :bug:`1904` * :doc:`plugins/fetchart`: The new ``max_filesize`` configuration sets a maximum target image file size. * :doc:`/plugins/badfiles`: Checkers can now run during import with the ``check_on_import`` config option. * :doc:`/plugins/export`: The plugin is now much faster when using the `--include-keys` option is used. Thanks to :user:`ssssam`. * The importer's :ref:`set_fields` option now saves all updated fields to on-disk metadata. :bug:`3925` :bug:`3927` * We now fetch ISRC identifiers from MusicBrainz. Thanks to :user:`aereaux`. * :doc:`/plugins/metasync`: The plugin now also fetches the "Date Added" field from iTunes databases and stores it in the ``itunes_dateadded`` field. Thanks to :user:`sandersantema`. * :doc:`/plugins/lyrics`: Added a new Tekstowo.pl lyrics provider. Thanks to various people for the implementation and for reporting issues with the initial version. :bug:`3344` :bug:`3904` :bug:`3905` :bug:`3994` * ``beet update`` will now confirm that the user still wants to update if their library folder cannot be found, preventing the user from accidentally wiping out their beets database. Thanks to user: `logan-arens`. :bug:`1934` Fixes: * Adapt to breaking changes in Python's ``ast`` module in Python 3.8. * :doc:`/plugins/beatport`: Fix the assignment of the `genre` field, and rename `musical_key` to `initial_key`. :bug:`3387` * :doc:`/plugins/lyrics`: Fixed the Musixmatch backend for lyrics pages when lyrics are divided into multiple elements on the webpage, and when the lyrics are missing. * :doc:`/plugins/web`: Allow use of the backslash character in regex queries. :bug:`3867` * :doc:`/plugins/web`: Fixed a small bug that caused the album art path to be redacted even when ``include_paths`` option is set. :bug:`3866` * :doc:`/plugins/discogs`: Fixed a bug with the ``index_tracks`` option that sometimes caused the index to be discarded. Also, remove the extra semicolon that was added when there is no index track. * :doc:`/plugins/subsonicupdate`: The API client was using the `POST` method rather the `GET` method. Also includes better exception handling, response parsing, and tests. * :doc:`/plugins/the`: Fixed incorrect regex for "the" that matched any 3-letter combination of the letters t, h, e. :bug:`3701` * :doc:`/plugins/fetchart`: Fixed a bug that caused the plugin to not take environment variables, such as proxy servers, into account when making requests. :bug:`3450` * :doc:`/plugins/fetchart`: Temporary files for fetched album art that fail validation are now removed. * :doc:`/plugins/inline`: In function-style field definitions that refer to flexible attributes, values could stick around from one function invocation to the next. This meant that, when displaying a list of objects, later objects could seem to reuse values from earlier objects when they were missing a value for a given field. These values are now properly undefined. :bug:`2406` * :doc:`/plugins/bpd`: Seeking by fractions of a second now works as intended, fixing crashes in MPD clients like mpDris2 on seek. The ``playlistid`` command now works properly in its zero-argument form. :bug:`3214` * :doc:`/plugins/replaygain`: Fix a Python 3 incompatibility in the Python Audio Tools backend. :bug:`3305` * :doc:`/plugins/importadded`: Fixed a crash that occurred when the ``after_write`` signal was emitted. :bug:`3301` * :doc:`plugins/replaygain`: Fix the storage format for R128 gain tags. :bug:`3311` :bug:`3314` * :doc:`/plugins/discogs`: Fixed a crash that occurred when the master URI isn't set in the API response. :bug:`2965` :bug:`3239` * :doc:`/plugins/spotify`: Fix handling of year-only release dates returned by the Spotify albums API. Thanks to :user:`rhlahuja`. :bug:`3343` * Fixed a bug that caused the UI to display incorrect track numbers for tracks with index 0 when the ``per_disc_numbering`` option was set. :bug:`3346` * ``none_rec_action`` does not import automatically when ``timid`` is enabled. Thanks to :user:`RollingStar`. :bug:`3242` * Fix a bug that caused a crash when tagging items with the beatport plugin. :bug:`3374` * ``beet import`` now logs which files are ignored when in debug mode. :bug:`3764` * :doc:`/plugins/bpd`: Fix the transition to next track when in consume mode. Thanks to :user:`aereaux`. :bug:`3437` * :doc:`/plugins/lyrics`: Fix a corner-case with Genius lowercase artist names :bug:`3446` * :doc:`/plugins/parentwork`: Don't save tracks when nothing has changed. :bug:`3492` * Added a warning when configuration files defined in the `include` directive of the configuration file fail to be imported. :bug:`3498` * Added normalization to integer values in the database, which should avoid problems where fields like ``bpm`` would sometimes store non-integer values. :bug:`762` :bug:`3507` :bug:`3508` * Fix a crash when querying for null values. :bug:`3516` :bug:`3517` * :doc:`/plugins/lyrics`: Tolerate a missing lyrics div in the Genius scraper. Thanks to :user:`thejli21`. :bug:`3535` :bug:`3554` * :doc:`/plugins/lyrics`: Use the artist sort name to search for lyrics, which can help find matches when the artist name has special characters. Thanks to :user:`hashhar`. :bug:`3340` :bug:`3558` * :doc:`/plugins/replaygain`: Trying to calculate volume gain for an album consisting of some formats using ``ReplayGain`` and some using ``R128`` will no longer crash; instead it is skipped and and a message is logged. The log message has also been rewritten for to improve clarity. Thanks to :user:`autrimpo`. :bug:`3533` * :doc:`/plugins/lyrics`: Adapt the Genius backend to changes in markup to reduce the scraping failure rate. :bug:`3535` :bug:`3594` * :doc:`/plugins/lyrics`: Fix a crash when writing ReST files for a query without results or fetched lyrics. :bug:`2805` * :doc:`/plugins/fetchart`: Attempt to fetch pre-resized thumbnails from Cover Art Archive if the ``maxwidth`` option matches one of the sizes supported by the Cover Art Archive API. Thanks to :user:`trolley`. :bug:`3637` * :doc:`/plugins/ipfs`: Fix Python 3 compatibility. Thanks to :user:`musoke`. :bug:`2554` * Fix a bug that caused metadata starting with something resembling a drive letter to be incorrectly split into an extra directory after the colon. :bug:`3685` * :doc:`/plugins/mpdstats`: Don't record a skip when stopping MPD, as MPD keeps the current track in the queue. Thanks to :user:`aereaux`. :bug:`3722` * String-typed fields are now normalized to string values, avoiding an occasional crash when using both the :doc:`/plugins/fetchart` and the :doc:`/plugins/discogs` together. :bug:`3773` :bug:`3774` * Fix a bug causing PIL to generate poor quality JPEGs when resizing artwork. :bug:`3743` * :doc:`plugins/keyfinder`: Catch output from ``keyfinder-cli`` that is missing key. :bug:`2242` * :doc:`plugins/replaygain`: Disable parallel analysis on import by default. :bug:`3819` * :doc:`/plugins/mpdstats`: Fix Python 2/3 compatibility :bug:`3798` * :doc:`/plugins/discogs`: Replace the deprecated official `discogs-client` library with the community supported `python3-discogs-client`_ library. :bug:`3608` * :doc:`/plugins/chroma`: Fixed submitting AcoustID information for tracks that already have a fingerprint. :bug:`3834` * Allow equals within the value part of the ``--set`` option to the ``beet import`` command. :bug:`2984` * Duplicates can now generate checksums. Thanks :user:`wisp3rwind` for the pointer to how to solve. Thanks to :user:`arogl`. :bug:`2873` * Templates that use ``%ifdef`` now produce the expected behavior when used in conjunction with non-string fields from the :doc:`/plugins/types`. :bug:`3852` * :doc:`/plugins/lyrics`: Fix crashes when a website could not be retrieved, affecting at least the Genius source. :bug:`3970` * :doc:`/plugins/duplicates`: Fix a crash when running the ``dup`` command with a query that returns no results. :bug:`3943` * :doc:`/plugins/beatport`: Fix the default assignment of the musical key. :bug:`3377` * :doc:`/plugins/lyrics`: Improved searching on the Genius backend when the artist contains special characters. :bug:`3634` * :doc:`/plugins/parentwork`: Also get the composition date of the parent work, instead of just the child work. Thanks to :user:`aereaux`. :bug:`3650` * :doc:`/plugins/lyrics`: Fix a bug in the heuristic for detecting valid lyrics in the Google source. :bug:`2969` * :doc:`/plugins/thumbnails`: Fix a crash due to an incorrect string type on Python 3. :bug:`3360` * :doc:`/plugins/fetchart`: The Cover Art Archive source now iterates over all front images instead of blindly selecting the first one. * :doc:`/plugins/lyrics`: Removed the LyricWiki source (the site shut down on 21/09/2020). * :doc:`/plugins/subsonicupdate`: The plugin is now functional again. A new `auth` configuration option is required in the configuration to specify the flavor of authentication to use. :bug:`4002` For plugin developers: * `MediaFile`_ has been split into a standalone project. Where you used to do ``from beets import mediafile``, now just do ``import mediafile``. Beets re-exports MediaFile at the old location for backwards-compatibility, but a deprecation warning is raised if you do this since we might drop this wrapper in a future release. * Similarly, we've replaced beets' configuration library (previously called Confit) with a standalone version called `Confuse`_. Where you used to do ``from beets.util import confit``, now just do ``import confuse``. The code is almost identical apart from the name change. Again, we'll re-export at the old location (with a deprecation warning) for backwards compatibility, but we might stop doing this in a future release. * ``beets.util.command_output`` now returns a named tuple containing both the standard output and the standard error data instead of just stdout alone. Client code will need to access the ``stdout`` attribute on the return value. Thanks to :user:`zsinskri`. :bug:`3329` * There were sporadic failures in ``test.test_player``. Hopefully these are fixed. If they resurface, please reopen the relevant issue. :bug:`3309` :bug:`3330` * The ``beets.plugins.MetadataSourcePlugin`` base class has been added to simplify development of plugins which query album, track, and search APIs to provide metadata matches for the importer. Refer to the :doc:`/plugins/spotify` and the :doc:`/plugins/deezer` for examples of using this template class. :bug:`3355` * Accessing fields on an `Item` now falls back to the album's attributes. So, for example, ``item.foo`` will first look for a field `foo` on `item` and, if it doesn't exist, next tries looking for a field named `foo` on the album that contains `item`. If you specifically want to access an item's attributes, use ``Item.get(key, with_album=False)``. :bug:`2988` * ``Item.keys`` also has a ``with_album`` argument now, defaulting to ``True``. * A ``revision`` attribute has been added to ``Database``. It is increased on every transaction that mutates it. :bug:`2988` * The classes ``AlbumInfo`` and ``TrackInfo`` now convey arbitrary attributes instead of a fixed, built-in set of field names (which was important to address :bug:`1547`). Thanks to :user:`dosoe`. * Two new events, ``mb_album_extract`` and ``mb_track_extract``, let plugins add new fields based on MusicBrainz data. Thanks to :user:`dosoe`. For packagers: * Beets' library for manipulating media file metadata has now been split to a standalone project called `MediaFile`_, released as :pypi:`mediafile`. Beets now depends on this new package. Beets now depends on Mutagen transitively through MediaFile rather than directly, except in the case of one of beets' plugins (in particular, the :doc:`/plugins/scrub`). * Beets' library for configuration has been split into a standalone project called `Confuse`_, released as :pypi:`confuse`. Beets now depends on this package. Confuse has existed separately for some time and is used by unrelated projects, but until now we've been bundling a copy within beets. * We attempted to fix an unreliable test, so a patch to `skip <https://sources.debian.org/src/beets/1.4.7-2/debian/patches/skip-broken-test/>`_ or `repair <https://build.opensuse.org/package/view_file/openSUSE:Factory/beets/fix_test_command_line_option_relative_to_working_dir.diff?expand=1>`_ the test may no longer be necessary. * This version drops support for Python 3.4. * We have removed an optional dependency on bs1770gain. .. _Fish shell: https://fishshell.com/ .. _MediaFile: https://github.com/beetbox/mediafile .. _Confuse: https://github.com/beetbox/confuse .. _works: https://musicbrainz.org/doc/Work .. _Deezer: https://www.deezer.com .. _keyfinder-cli: https://github.com/EvanPurkhiser/keyfinder-cli .. _last.fm: https://last.fm .. _python3-discogs-client: https://github.com/joalla/discogs_client .. _py7zr: https://pypi.org/project/py7zr/ 1.4.9 (May 30, 2019) -------------------- This small update is part of our attempt to release new versions more often! There are a few important fixes, and we're clearing the deck for a change to beets' dependencies in the next version. The new feature is: * You can use the `NO_COLOR`_ environment variable to disable terminal colors. :bug:`3273` There are some fixes in this release: * Fix a regression in the last release that made the image resizer fail to detect older versions of ImageMagick. :bug:`3269` * :doc:`/plugins/gmusic`: The ``oauth_file`` config option now supports more flexible path values, including ``~`` for the home directory. :bug:`3270` * :doc:`/plugins/gmusic`: Fix a crash when using version 12.0.0 or later of the ``gmusicapi`` module. :bug:`3270` * Fix an incompatibility with Python 3.8's AST changes. :bug:`3278` Here's a note for packagers: * ``pathlib`` is now an optional test dependency on Python 3.4+, removing the need for `a Debian patch <https://sources.debian.org/src/beets/1.4.7-2/debian/patches/pathlib-is-stdlib/>`_. :bug:`3275` .. _NO_COLOR: https://no-color.org 1.4.8 (May 16, 2019) -------------------- This release is far too long in coming, but it's a good one. There is the usual torrent of new features and a ridiculously long line of fixes, but there are also some crucial maintenance changes. We officially support Python 3.7 and 3.8, and some performance optimizations can (anecdotally) make listing your library more than three times faster than in the previous version. The new core features are: * A new :ref:`config-aunique` configuration option allows setting default options for the :ref:`aunique` template function. * The ``albumdisambig`` field no longer includes the MusicBrainz release group disambiguation comment. A new ``releasegroupdisambig`` field has been added. :bug:`3024` * The :ref:`modify-cmd` command now allows resetting fixed attributes. For example, ``beet modify -a artist:beatles artpath!`` resets ``artpath`` attribute from matching albums back to the default value. :bug:`2497` * A new importer option, :ref:`ignore_data_tracks`, lets you skip audio tracks contained in data files. :bug:`3021` There are some new plugins: * The :doc:`/plugins/playlist` can query the beets library using M3U playlists. Thanks to :user:`Holzhaus` and :user:`Xenopathic`. :bug:`123` :bug:`3145` * The :doc:`/plugins/loadext` allows loading of SQLite extensions, primarily for use with the ICU SQLite extension for internationalization. :bug:`3160` :bug:`3226` * The :doc:`/plugins/subsonicupdate` can automatically update your Subsonic library. Thanks to :user:`maffo999`. :bug:`3001` And many improvements to existing plugins: * :doc:`/plugins/lastgenre`: Added option ``-A`` to match individual tracks and singletons. :bug:`3220` :bug:`3219` * :doc:`/plugins/play`: The plugin can now emit a UTF-8 BOM, fixing some issues with foobar2000 and Winamp. Thanks to :user:`mz2212`. :bug:`2944` * :doc:`/plugins/gmusic`: * Add a new option to automatically upload to Google Play Music library on track import. Thanks to :user:`shuaiscott`. * Add new options for Google Play Music authentication. Thanks to :user:`thetarkus`. :bug:`3002` * :doc:`/plugins/replaygain`: ``albumpeak`` on large collections is calculated as the average, not the maximum. :bug:`3008` :bug:`3009` * :doc:`/plugins/chroma`: * Now optionally has a bias toward looking up more relevant releases according to the :ref:`preferred` configuration options. Thanks to :user:`archer4499`. :bug:`3017` * Fingerprint values are now properly stored as strings, which prevents strange repeated output when running ``beet write``. Thanks to :user:`Holzhaus`. :bug:`3097` :bug:`2942` * :doc:`/plugins/convert`: The plugin now has an ``id3v23`` option that allows you to override the global ``id3v23`` option. Thanks to :user:`Holzhaus`. :bug:`3104` * :doc:`/plugins/spotify`: * The plugin now uses OAuth for authentication to the Spotify API. Thanks to :user:`rhlahuja`. :bug:`2694` :bug:`3123` * The plugin now works as an import metadata provider: you can match tracks and albums using the Spotify database. Thanks to :user:`rhlahuja`. :bug:`3123` * :doc:`/plugins/ipfs`: The plugin now supports a ``nocopy`` option which passes that flag to ipfs. Thanks to :user:`wildthyme`. * :doc:`/plugins/discogs`: The plugin now has rate limiting for the Discogs API. :bug:`3081` * :doc:`/plugins/mpdstats`, :doc:`/plugins/mpdupdate`: These plugins now use the ``MPD_PORT`` environment variable if no port is specified in the configuration file. :bug:`3223` * :doc:`/plugins/bpd`: * MPD protocol commands ``consume`` and ``single`` are now supported along with updated semantics for ``repeat`` and ``previous`` and new fields for ``status``. The bpd server now understands and ignores some additional commands. :bug:`3200` :bug:`800` * MPD protocol command ``idle`` is now supported, allowing the MPD version to be bumped to 0.14. :bug:`3205` :bug:`800` * MPD protocol command ``decoders`` is now supported. :bug:`3222` * The plugin now uses the main beets logging system. The special-purpose ``--debug`` flag has been removed. Thanks to :user:`arcresu`. :bug:`3196` * :doc:`/plugins/mbsync`: The plugin no longer queries MusicBrainz when either the ``mb_albumid`` or ``mb_trackid`` field is invalid. See also the discussion on `Google Groups`_ Thanks to :user:`arogl`. * :doc:`/plugins/export`: The plugin now also exports ``path`` field if the user explicitly specifies it with ``-i`` parameter. This only works when exporting library fields. :bug:`3084` * :doc:`/plugins/acousticbrainz`: The plugin now declares types for all its fields, which enables easier querying and avoids a problem where very small numbers would be stored as strings. Thanks to :user:`rain0r`. :bug:`2790` :bug:`3238` .. _Google Groups: https://groups.google.com/forum/#!searchin/beets-users/mbsync|sort:date/beets-users/iwCF6bNdh9A/i1xl4Gx8BQAJ Some improvements have been focused on improving beets' performance: * Querying the library is now faster: * We only convert fields that need to be displayed. Thanks to :user:`pprkut`. :bug:`3089` * We now compile templates once and reuse them instead of recompiling them to print out each matching object. Thanks to :user:`SimonPersson`. :bug:`3258` * Querying the library for items is now faster, for all queries that do not need to access album level properties. This was implemented by lazily fetching the album only when needed. Thanks to :user:`SimonPersson`. :bug:`3260` * :doc:`/plugins/absubmit`, :doc:`/plugins/badfiles`: Analysis now works in parallel (on Python 3 only). Thanks to :user:`bemeurer`. :bug:`2442` :bug:`3003` * :doc:`/plugins/mpdstats`: Use the ``currentsong`` MPD command instead of ``playlist`` to get the current song, improving performance when the playlist is long. Thanks to :user:`ray66`. :bug:`3207` :bug:`2752` Several improvements are related to usability: * The disambiguation string for identifying albums in the importer now shows the catalog number. Thanks to :user:`8h2a`. :bug:`2951` * Added whitespace padding to missing tracks dialog to improve readability. Thanks to :user:`jams2`. :bug:`2962` * The :ref:`move-cmd` command now lists the number of items already in-place. Thanks to :user:`RollingStar`. :bug:`3117` * Modify selection can now be applied early without selecting every item. :bug:`3083` * Beets now emits more useful messages during startup if SQLite returns an error. The SQLite error message is now attached to the beets message. :bug:`3005` * Fixed a confusing typo when the :doc:`/plugins/convert` plugin copies the art covers. :bug:`3063` Many fixes have been focused on issues where beets would previously crash: * Avoid a crash when archive extraction fails during import. :bug:`3041` * Missing album art file during an update no longer causes a fatal exception (instead, an error is logged and the missing file path is removed from the library). :bug:`3030` * When updating the database, beets no longer tries to move album art twice. :bug:`3189` * Fix an unhandled exception when pruning empty directories. :bug:`1996` :bug:`3209` * :doc:`/plugins/fetchart`: Added network connection error handling to backends so that beets won't crash if a request fails. Thanks to :user:`Holzhaus`. :bug:`1579` * :doc:`/plugins/badfiles`: Avoid a crash when the underlying tool emits undecodable output. :bug:`3165` * :doc:`/plugins/beatport`: Avoid a crash when the server produces an error. :bug:`3184` * :doc:`/plugins/bpd`: Fix crashes in the bpd server during exception handling. :bug:`3200` * :doc:`/plugins/bpd`: Fix a crash triggered when certain clients tried to list the albums belonging to a particular artist. :bug:`3007` :bug:`3215` * :doc:`/plugins/replaygain`: Avoid a crash when the ``bs1770gain`` tool emits malformed XML. :bug:`2983` :bug:`3247` There are many fixes related to compatibility with our dependencies including addressing changes interfaces: * On Python 2, pin the :pypi:`jellyfish` requirement to version 0.6.0 for compatibility. * Fix compatibility with Python 3.7 and its change to a name in the :stdlib:`re` module. :bug:`2978` * Fix several uses of deprecated standard-library features on Python 3.7. Thanks to :user:`arcresu`. :bug:`3197` * Fix compatibility with pre-release versions of Python 3.8. :bug:`3201` :bug:`3202` * :doc:`/plugins/web`: Fix an error when using more recent versions of Flask with CORS enabled. Thanks to :user:`rveachkc`. :bug:`2979`: :bug:`2980` * Avoid some deprecation warnings with certain versions of the MusicBrainz library. Thanks to :user:`zhelezov`. :bug:`2826` :bug:`3092` * Restore iTunes Store album art source, and remove the dependency on :pypi:`python-itunes`, which had gone unmaintained and was not Python-3-compatible. Thanks to :user:`ocelma` for creating :pypi:`python-itunes` in the first place. Thanks to :user:`nathdwek`. :bug:`2371` :bug:`2551` :bug:`2718` * :doc:`/plugins/lastgenre`, :doc:`/plugins/edit`: Avoid a deprecation warnings from the :pypi:`PyYAML` library by switching to the safe loader. Thanks to :user:`translit` and :user:`sbraz`. :bug:`3192` :bug:`3225` * Fix a problem when resizing images with :pypi:`PIL`/:pypi:`pillow` on Python 3. Thanks to :user:`architek`. :bug:`2504` :bug:`3029` And there are many other fixes: * R128 normalization tags are now properly deleted from files when the values are missing. Thanks to :user:`autrimpo`. :bug:`2757` * Display the artist credit when matching albums if the :ref:`artist_credit` configuration option is set. :bug:`2953` * With the :ref:`from_scratch` configuration option set, only writable fields are cleared. Beets now no longer ignores the format your music is saved in. :bug:`2972` * The ``%aunique`` template function now works correctly with the ``-f/--format`` option. :bug:`3043` * Fixed the ordering of items when manually selecting changes while updating tags Thanks to :user:`TaizoSimpson`. :bug:`3501` * The ``%title`` template function now works correctly with apostrophes. Thanks to :user:`GuilhermeHideki`. :bug:`3033` * :doc:`/plugins/lastgenre`: It's now possible to set the ``prefer_specific`` option without also setting ``canonical``. :bug:`2973` * :doc:`/plugins/fetchart`: The plugin now respects the ``ignore`` and ``ignore_hidden`` settings. :bug:`1632` * :doc:`/plugins/hook`: Fix byte string interpolation in hook commands. :bug:`2967` :bug:`3167` * :doc:`/plugins/the`: Log a message when something has changed, not when it hasn't. Thanks to :user:`arcresu`. :bug:`3195` * :doc:`/plugins/lastgenre`: The ``force`` config option now actually works. :bug:`2704` :bug:`3054` * Resizing image files with ImageMagick now avoids problems on systems where there is a ``convert`` command that is *not* ImageMagick's by using the ``magick`` executable when it is available. Thanks to :user:`ababyduck`. :bug:`2093` :bug:`3236` There is one new thing for plugin developers to know about: * In addition to prefix-based field queries, plugins can now define *named queries* that are not associated with any specific field. For example, the new :doc:`/plugins/playlist` supports queries like ``playlist:name`` although there is no field named ``playlist``. See :ref:`extend-query` for details. And some messages for packagers: * Note the changes to the dependencies on :pypi:`jellyfish` and :pypi:`munkres`. * The optional :pypi:`python-itunes` dependency has been removed. * Python versions 3.7 and 3.8 are now supported. 1.4.7 (May 29, 2018) -------------------- This new release includes lots of new features in the importer and the metadata source backends that it uses. We've changed how the beets importer handles non-audio tracks listed in metadata sources like MusicBrainz: * The importer now ignores non-audio tracks (namely, data and video tracks) listed in MusicBrainz. Also, a new option, :ref:`ignore_video_tracks`, lets you return to the old behavior and include these video tracks. :bug:`1210` * A new importer option, :ref:`ignored_media`, can let you skip certain media formats. :bug:`2688` There are other subtle improvements to metadata handling in the importer: * In the MusicBrainz backend, beets now imports the ``musicbrainz_releasetrackid`` field. This is a first step toward :bug:`406`. Thanks to :user:`Rawrmonkeys`. * A new importer configuration option, :ref:`artist_credit`, will tell beets to prefer the artist credit over the artist when autotagging. :bug:`1249` And there are even more new features: * :doc:`/plugins/replaygain`: The ``beet replaygain`` command now has ``--force``, ``--write`` and ``--nowrite`` options. :bug:`2778` * A new importer configuration option, :ref:`incremental_skip_later`, lets you avoid recording skipped directories to the list of "processed" directories in :ref:`incremental` mode. This way, you can revisit them later with another import. Thanks to :user:`sekjun9878`. :bug:`2773` * :doc:`/plugins/fetchart`: The configuration options now support finer-grained control via the ``sources`` option. You can now specify the search order for different *matching strategies* within different backends. * :doc:`/plugins/web`: A new ``cors_supports_credentials`` configuration option lets in-browser clients communicate with the server even when it is protected by an authorization mechanism (a proxy with HTTP authentication enabled, for example). * A new :doc:`/plugins/sonosupdate` plugin automatically notifies Sonos controllers to update the music library when the beets library changes. Thanks to :user:`cgtobi`. * :doc:`/plugins/discogs`: The plugin now stores master release IDs into ``mb_releasegroupid``. It also "simulates" track IDs using the release ID and the track list position. Thanks to :user:`dbogdanov`. :bug:`2336` * :doc:`/plugins/discogs`: Fetch the original year from master releases. :bug:`1122` There are lots and lots of fixes: * :doc:`/plugins/replaygain`: Fix a corner-case with the ``bs1770gain`` backend where ReplayGain values were assigned to the wrong files. The plugin now requires version 0.4.6 or later of the ``bs1770gain`` tool. :bug:`2777` * :doc:`/plugins/lyrics`: The plugin no longer crashes in the Genius source when BeautifulSoup is not found. Instead, it just logs a message and disables the source. :bug:`2911` * :doc:`/plugins/lyrics`: Handle network and API errors when communicating with Genius. :bug:`2771` * :doc:`/plugins/lyrics`: The ``lyrics`` command previously wrote ReST files by default, even when you didn't ask for them. This default has been fixed. * :doc:`/plugins/lyrics`: When writing ReST files, the ``lyrics`` command now groups lyrics by the ``albumartist`` field, rather than ``artist``. :bug:`2924` * Plugins can now see updated import task state, such as when rejecting the initial candidates and finding new ones via a manual search. Notably, this means that the importer prompt options that the :doc:`/plugins/edit` provides show up more reliably after doing a secondary import search. :bug:`2441` :bug:`2731` * :doc:`/plugins/importadded`: Fix a crash on non-autotagged imports. Thanks to :user:`m42i`. :bug:`2601` :bug:`1918` * :doc:`/plugins/plexupdate`: The Plex token is now redacted in configuration output. Thanks to :user:`Kovrinic`. :bug:`2804` * Avoid a crash when importing a non-ASCII filename when using an ASCII locale on Unix under Python 3. :bug:`2793` :bug:`2803` * Fix a problem caused by time zone misalignment that could make date queries fail to match certain dates that are near the edges of a range. For example, querying for dates within a certain month would fail to match dates within hours of the end of that month. :bug:`2652` * :doc:`/plugins/convert`: The plugin now runs before other plugin-provided import stages, which addresses an issue with generating ReplayGain data incompatible between the source and target file formats. Thanks to :user:`autrimpo`. :bug:`2814` * :doc:`/plugins/ftintitle`: The ``drop`` config option had no effect; it now does what it says it should do. :bug:`2817` * Importing a release with multiple release events now selects the event based on the order of your :ref:`preferred` countries rather than the order of release events in MusicBrainz. :bug:`2816` * :doc:`/plugins/web`: The time display in the web interface would incorrectly jump at the 30-second mark of every minute. Now, it correctly changes over at zero seconds. :bug:`2822` * :doc:`/plugins/web`: Fetching album art now works (instead of throwing an exception) under Python 3. Additionally, the server will now return a 404 response when the album ID is unknown (instead of throwing an exception and producing a 500 response). :bug:`2823` * :doc:`/plugins/web`: Fix an exception on Python 3 for filenames with non-Latin1 characters. (These characters are now converted to their ASCII equivalents.) :bug:`2815` * Partially fix bash completion for subcommand names that contain hyphens. Thanks to :user:`jhermann`. :bug:`2836` :bug:`2837` * :doc:`/plugins/replaygain`: Really fix album gain calculation using the GStreamer backend. :bug:`2846` * Avoid an error when doing a "no-op" move on non-existent files (i.e., moving a file onto itself). :bug:`2863` * :doc:`/plugins/discogs`: Fix the ``medium`` and ``medium_index`` values, which were occasionally incorrect for releases with two-sided mediums such as vinyl. Also fix the ``medium_total`` value, which now contains total number of tracks on the medium to which a track belongs, not the total number of different mediums present on the release. Thanks to :user:`dbogdanov`. :bug:`2887` * The importer now supports audio files contained in data tracks when they are listed in MusicBrainz: the corresponding audio tracks are now merged into the main track list. Thanks to :user:`jdetrey`. :bug:`1638` * :doc:`/plugins/keyfinder`: Avoid a crash when trying to process unmatched tracks. :bug:`2537` * :doc:`/plugins/mbsync`: Support MusicBrainz recording ID changes, relying on release track IDs instead. Thanks to :user:`jdetrey`. :bug:`1234` * :doc:`/plugins/mbsync`: We can now successfully update albums even when the first track has a missing MusicBrainz recording ID. :bug:`2920` There are a couple of changes for developers: * Plugins can now run their import stages *early*, before other plugins. Use the ``early_import_stages`` list instead of plain ``import_stages`` to request this behavior. :bug:`2814` * We again properly send ``albuminfo_received`` and ``trackinfo_received`` in all cases, most notably when using the ``mbsync`` plugin. This was a regression since version 1.4.1. :bug:`2921` 1.4.6 (December 21, 2017) ------------------------- The highlight of this release is "album merging," an oft-requested option in the importer to add new tracks to an existing album you already have in your library. This way, you no longer need to resort to removing the partial album from your library, combining the files manually, and importing again. Here are the larger new features in this release: * When the importer finds duplicate albums, you can now merge all the tracks---old and new---together and try importing them as a single, combined album. Thanks to :user:`udiboy1209`. :bug:`112` :bug:`2725` * :doc:`/plugins/lyrics`: The plugin can now produce reStructuredText files for beautiful, readable books of lyrics. Thanks to :user:`anarcat`. :bug:`2628` * A new :ref:`from_scratch` configuration option makes the importer remove old metadata before applying new metadata. This new feature complements the :doc:`zero </plugins/zero>` and :doc:`scrub </plugins/scrub>` plugins but is slightly different: beets clears out all the old tags it knows about and only keeps the new data it gets from the remote metadata source. Thanks to :user:`tummychow`. :bug:`934` :bug:`2755` There are also somewhat littler, but still great, new features: * :doc:`/plugins/convert`: A new ``no_convert`` option lets you skip transcoding items matching a query. Instead, the files are just copied as-is. Thanks to :user:`Stunner`. :bug:`2732` :bug:`2751` * :doc:`/plugins/fetchart`: A new quiet switch that only prints out messages when album art is missing. Thanks to :user:`euri10`. :bug:`2683` * :doc:`/plugins/mbcollection`: You can configure a custom MusicBrainz collection via the new ``collection`` configuration option. :bug:`2685` * :doc:`/plugins/mbcollection`: The collection update command can now remove albums from collections that are longer in the beets library. * :doc:`/plugins/fetchart`: The ``clearart`` command now asks for confirmation before touching your files. Thanks to :user:`konman2`. :bug:`2708` :bug:`2427` * :doc:`/plugins/mpdstats`: The plugin now correctly updates song statistics when MPD switches from a song to a stream and when it plays the same song multiple times consecutively. :bug:`2707` * :doc:`/plugins/acousticbrainz`: The plugin can now be configured to write only a specific list of tags. Thanks to :user:`woparry`. There are lots and lots of bug fixes: * :doc:`/plugins/hook`: Fixed a problem where accessing non-string properties of ``item`` or ``album`` (e.g., ``item.track``) would cause a crash. Thanks to :user:`broddo`. :bug:`2740` * :doc:`/plugins/play`: When ``relative_to`` is set, the plugin correctly emits relative paths even when querying for albums rather than tracks. Thanks to :user:`j000`. :bug:`2702` * We suppress a spurious Python warning about a ``BrokenPipeError`` being ignored. This was an issue when using beets in simple shell scripts. Thanks to :user:`Azphreal`. :bug:`2622` :bug:`2631` * :doc:`/plugins/replaygain`: Fix a regression in the previous release related to the new R128 tags. :bug:`2615` :bug:`2623` * :doc:`/plugins/lyrics`: The MusixMatch backend now detects and warns when the server has blocked the client. Thanks to :user:`anarcat`. :bug:`2634` :bug:`2632` * :doc:`/plugins/importfeeds`: Fix an error on Python 3 in certain configurations. Thanks to :user:`djl`. :bug:`2467` :bug:`2658` * :doc:`/plugins/edit`: Fix a bug when editing items during a re-import with the ``-L`` flag. Previously, diffs against against unrelated items could be shown or beets could crash. :bug:`2659` * :doc:`/plugins/kodiupdate`: Fix the server URL and add better error reporting. :bug:`2662` * Fixed a problem where "no-op" modifications would reset files' mtimes, resulting in unnecessary writes. This most prominently affected the :doc:`/plugins/edit` when saving the text file without making changes to some music. :bug:`2667` * :doc:`/plugins/chroma`: Fix a crash when running the ``submit`` command on Python 3 on Windows with non-ASCII filenames. :bug:`2671` * :doc:`/plugins/absubmit`: Fix an occasional crash on Python 3 when the AB analysis tool produced non-ASCII metadata. :bug:`2673` * :doc:`/plugins/duplicates`: Use the default tiebreak for items or albums when the configuration only specifies a tiebreak for the other kind of entity. Thanks to :user:`cgevans`. :bug:`2758` * :doc:`/plugins/duplicates`: Fix the ``--key`` command line option, which was ignored. * :doc:`/plugins/replaygain`: Fix album ReplayGain calculation with the GStreamer backend. :bug:`2636` * :doc:`/plugins/scrub`: Handle errors when manipulating files using newer versions of Mutagen. :bug:`2716` * :doc:`/plugins/fetchart`: The plugin no longer gets skipped during import when the "Edit Candidates" option is used from the :doc:`/plugins/edit`. :bug:`2734` * Fix a crash when numeric metadata fields contain just a minus or plus sign with no following numbers. Thanks to :user:`eigengrau`. :bug:`2741` * :doc:`/plugins/fromfilename`: Recognize file names that contain *only* a track number, such as `01.mp3`. Also, the plugin now allows underscores as a separator between fields. Thanks to :user:`Vrihub`. :bug:`2738` :bug:`2759` * Fixed an issue where images would be resized according to their longest edge, instead of their width, when using the ``maxwidth`` config option in the :doc:`/plugins/fetchart` and :doc:`/plugins/embedart`. Thanks to :user:`sekjun9878`. :bug:`2729` There are some changes for developers: * "Fixed fields" in Album and Item objects are now more strict about translating missing values into type-specific null-like values. This should help in cases where a string field is unexpectedly `None` sometimes instead of just showing up as an empty string. :bug:`2605` * Refactored the move functions the `beets.library` module and the `manipulate_files` function in `beets.importer` to use a single parameter describing the file operation instead of multiple Boolean flags. There is a new numerated type describing how to move, copy, or link files. :bug:`2682` 1.4.5 (June 20, 2017) --------------------- Version 1.4.5 adds some oft-requested features. When you're importing files, you can now manually set fields on the new music. Date queries have gotten much more powerful: you can write precise queries down to the second, and we now have *relative* queries like ``-1w``, which means *one week ago*. Here are the new features: * You can now set fields to certain values during :ref:`import-cmd`, using either a ``--set field=value`` command-line flag or a new :ref:`set_fields` configuration option under the `importer` section. Thanks to :user:`bartkl`. :bug:`1881` :bug:`2581` * :ref:`Date queries <datequery>` can now include times, so you can filter your music down to the second. Thanks to :user:`discopatrick`. :bug:`2506` :bug:`2528` * :ref:`Date queries <datequery>` can also be *relative*. You can say ``added:-1w..`` to match music added in the last week, for example. Thanks to :user:`euri10`. :bug:`2598` * A new :doc:`/plugins/gmusic` lets you interact with your Google Play Music library. Thanks to :user:`tigranl`. :bug:`2553` :bug:`2586` * :doc:`/plugins/replaygain`: We now keep R128 data in separate tags from classic ReplayGain data for formats that need it (namely, Ogg Opus). A new `r128` configuration option enables this behavior for specific formats. Thanks to :user:`autrimpo`. :bug:`2557` :bug:`2560` * The :ref:`move-cmd` command gained a new ``--export`` flag, which copies files to an external location without changing their paths in the library database. Thanks to :user:`SpirosChadoulos`. :bug:`435` :bug:`2510` There are also some bug fixes: * :doc:`/plugins/lastgenre`: Fix a crash when using the `prefer_specific` and `canonical` options together. Thanks to :user:`yacoob`. :bug:`2459` :bug:`2583` * :doc:`/plugins/web`: Fix a crash on Windows under Python 2 when serving non-ASCII filenames. Thanks to :user:`robot3498712`. :bug:`2592` :bug:`2593` * :doc:`/plugins/metasync`: Fix a crash in the Amarok backend when filenames contain quotes. Thanks to :user:`aranc23`. :bug:`2595` :bug:`2596` * More informative error messages are displayed when the file format is not recognized. :bug:`2599` 1.4.4 (June 10, 2017) --------------------- This release built up a longer-than-normal list of nifty new features. We now support DSF audio files and the importer can hard-link your files, for example. Here's a full list of new features: * Added support for DSF files, once a future version of Mutagen is released that supports them. Thanks to :user:`docbobo`. :bug:`459` :bug:`2379` * A new :ref:`hardlink` config option instructs the importer to create hard links on filesystems that support them. Thanks to :user:`jacobwgillespie`. :bug:`2445` * A new :doc:`/plugins/kodiupdate` lets you keep your Kodi library in sync with beets. Thanks to :user:`Pauligrinder`. :bug:`2411` * A new :ref:`bell` configuration option under the ``import`` section enables a terminal bell when input is required. Thanks to :user:`SpirosChadoulos`. :bug:`2366` :bug:`2495` * A new field, ``composer_sort``, is now supported and fetched from MusicBrainz. Thanks to :user:`dosoe`. :bug:`2519` :bug:`2529` * The MusicBrainz backend and :doc:`/plugins/discogs` now both provide a new attribute called ``track_alt`` that stores more nuanced, possibly non-numeric track index data. For example, some vinyl or tape media will report the side of the record using a letter instead of a number in that field. :bug:`1831` :bug:`2363` * :doc:`/plugins/web`: Added a new endpoint, ``/item/path/foo``, which will return the item info for the file at the given path, or 404. * :doc:`/plugins/web`: Added a new config option, ``include_paths``, which will cause paths to be included in item API responses if set to true. * The ``%aunique`` template function for :ref:`aunique` now takes a third argument that specifies which brackets to use around the disambiguator value. The argument can be any two characters that represent the left and right brackets. It defaults to `[]` and can also be blank to turn off bracketing. :bug:`2397` :bug:`2399` * Added a ``--move`` or ``-m`` option to the importer so that the files can be moved to the library instead of being copied or added "in place." :bug:`2252` :bug:`2429` * :doc:`/plugins/badfiles`: Added a ``--verbose`` or ``-v`` option. Results are now displayed only for corrupted files by default and for all the files when the verbose option is set. :bug:`1654` :bug:`2434` * :doc:`/plugins/embedart`: The explicit ``embedart`` command now asks for confirmation before embedding art into music files. Thanks to :user:`Stunner`. :bug:`1999` * You can now run beets by typing `python -m beets`. :bug:`2453` * :doc:`/plugins/smartplaylist`: Different playlist specifications that generate identically-named playlist files no longer conflict; instead, the resulting lists of tracks are concatenated. :bug:`2468` * :doc:`/plugins/missing`: A new mode lets you see missing albums from artists you have in your library. Thanks to :user:`qlyoung`. :bug:`2481` * :doc:`/plugins/web` : Add new `reverse_proxy` config option to allow serving the web plugins under a reverse proxy. * Importing a release with multiple release events now selects the event based on your :ref:`preferred` countries. :bug:`2501` * :doc:`/plugins/play`: A new ``-y`` or ``--yes`` parameter lets you skip the warning message if you enqueue more items than the warning threshold usually allows. * Fix a bug where commands which forked subprocesses would sometimes prevent further inputs. This bug mainly affected :doc:`/plugins/convert`. Thanks to :user:`jansol`. :bug:`2488` :bug:`2524` There are also quite a few fixes: * In the :ref:`replace` configuration option, we now replace a leading hyphen (-) with an underscore. :bug:`549` :bug:`2509` * :doc:`/plugins/absubmit`: We no longer filter audio files for specific formats---we will attempt the submission process for all formats. :bug:`2471` * :doc:`/plugins/mpdupdate`: Fix Python 3 compatibility. :bug:`2381` * :doc:`/plugins/replaygain`: Fix Python 3 compatibility in the ``bs1770gain`` backend. :bug:`2382` * :doc:`/plugins/bpd`: Report playback times as integers. :bug:`2394` * :doc:`/plugins/mpdstats`: Fix Python 3 compatibility. The plugin also now requires version 0.4.2 or later of the ``python-mpd2`` library. :bug:`2405` * :doc:`/plugins/mpdstats`: Improve handling of MPD status queries. * :doc:`/plugins/badfiles`: Fix Python 3 compatibility. * Fix some cases where album-level ReplayGain/SoundCheck metadata would be written to files incorrectly. :bug:`2426` * :doc:`/plugins/badfiles`: The command no longer bails out if the validator command is not found or exits with an error. :bug:`2430` :bug:`2433` * :doc:`/plugins/lyrics`: The Google search backend no longer crashes when the server responds with an error. :bug:`2437` * :doc:`/plugins/discogs`: You can now authenticate with Discogs using a personal access token. :bug:`2447` * Fix Python 3 compatibility when extracting rar archives in the importer. Thanks to :user:`Lompik`. :bug:`2443` :bug:`2448` * :doc:`/plugins/duplicates`: Fix Python 3 compatibility when using the ``copy`` and ``move`` options. :bug:`2444` * :doc:`/plugins/mbsubmit`: The tracks are now sorted properly. Thanks to :user:`awesomer`. :bug:`2457` * :doc:`/plugins/thumbnails`: Fix a string-related crash on Python 3. :bug:`2466` * :doc:`/plugins/beatport`: More than just 10 songs are now fetched per album. :bug:`2469` * On Python 3, the :ref:`terminal_encoding` setting is respected again for output and printing will no longer crash on systems configured with a limited encoding. * :doc:`/plugins/convert`: The default configuration uses FFmpeg's built-in AAC codec instead of faac. Thanks to :user:`jansol`. :bug:`2484` * Fix the importer's detection of multi-disc albums when other subdirectories are present. :bug:`2493` * Invalid date queries now print an error message instead of being silently ignored. Thanks to :user:`discopatrick`. :bug:`2513` :bug:`2517` * When the SQLite database stops being accessible, we now print a friendly error message. Thanks to :user:`Mary011196`. :bug:`1676` :bug:`2508` * :doc:`/plugins/web`: Avoid a crash when sending binary data, such as Chromaprint fingerprints, in music attributes. :bug:`2542` :bug:`2532` * Fix a hang when parsing templates that end in newlines. :bug:`2562` * Fix a crash when reading non-ASCII characters in configuration files on Windows under Python 3. :bug:`2456` :bug:`2565` :bug:`2566` We removed backends from two metadata plugins because of bitrot: * :doc:`/plugins/lyrics`: The Lyrics.com backend has been removed. (It stopped working because of changes to the site's URL structure.) :bug:`2548` :bug:`2549` * :doc:`/plugins/fetchart`: The documentation no longer recommends iTunes Store artwork lookup because the unmaintained `python-itunes`_ is broken. Want to adopt it? :bug:`2371` :bug:`1610` .. _python-itunes: https://github.com/ocelma/python-itunes 1.4.3 (January 9, 2017) ----------------------- Happy new year! This new version includes a cornucopia of new features from contributors, including new tags related to classical music and a new :doc:`/plugins/absubmit` for performing acoustic analysis on your music. The :doc:`/plugins/random` has a new mode that lets you generate time-limited music---for example, you might generate a random playlist that lasts the perfect length for your walk to work. We also access as many Web services as possible over secure connections now---HTTPS everywhere! The most visible new features are: * We now support the composer, lyricist, and arranger tags. The MusicBrainz data source will fetch data for these fields when the next version of `python-musicbrainzngs`_ is released. Thanks to :user:`ibmibmibm`. :bug:`506` :bug:`507` :bug:`1547` :bug:`2333` * A new :doc:`/plugins/absubmit` lets you run acoustic analysis software and upload the results for others to use. Thanks to :user:`inytar`. :bug:`2253` :bug:`2342` * :doc:`/plugins/play`: The plugin now provides an importer prompt choice to play the music you're about to import. Thanks to :user:`diomekes`. :bug:`2008` :bug:`2360` * We now use SSL to access Web services whenever possible. That includes MusicBrainz itself, several album art sources, some lyrics sources, and other servers. Thanks to :user:`tigranl`. :bug:`2307` * :doc:`/plugins/random`: A new ``--time`` option lets you generate a random playlist that takes a given amount of time. Thanks to :user:`diomekes`. :bug:`2305` :bug:`2322` Some smaller new features: * :doc:`/plugins/zero`: A new ``zero`` command manually triggers the zero plugin. Thanks to :user:`SJoshBrown`. :bug:`2274` :bug:`2329` * :doc:`/plugins/acousticbrainz`: The plugin will avoid re-downloading data for files that already have it by default. You can override this behavior using a new ``force`` option. Thanks to :user:`SusannaMaria`. :bug:`2347` :bug:`2349` * :doc:`/plugins/bpm`: The ``import.write`` configuration option now decides whether or not to write tracks after updating their BPM. :bug:`1992` And the fixes: * :doc:`/plugins/bpd`: Fix a crash on non-ASCII MPD commands. :bug:`2332` * :doc:`/plugins/scrub`: Avoid a crash when files cannot be read or written. :bug:`2351` * :doc:`/plugins/scrub`: The image type values on scrubbed files are preserved instead of being reset to "other." :bug:`2339` * :doc:`/plugins/web`: Fix a crash on Python 3 when serving files from the filesystem. :bug:`2353` * :doc:`/plugins/discogs`: Improve the handling of releases that contain subtracks. :bug:`2318` * :doc:`/plugins/discogs`: Fix a crash when a release does not contain format information, and increase robustness when other fields are missing. :bug:`2302` * :doc:`/plugins/lyrics`: The plugin now reports a beets-specific User-Agent header when requesting lyrics. :bug:`2357` * :doc:`/plugins/embyupdate`: The plugin now checks whether an API key or a password is provided in the configuration. * :doc:`/plugins/play`: The misspelled configuration option ``warning_treshold`` is no longer supported. For plugin developers: when providing new importer prompt choices (see :ref:`append_prompt_choices`), you can now provide new candidates for the user to consider. For example, you might provide an alternative strategy for picking between the available alternatives or for looking up a release on MusicBrainz. 1.4.2 (December 16, 2016) ------------------------- This is just a little bug fix release. With 1.4.2, we're also confident enough to recommend that anyone who's interested give Python 3 a try: bugs may still lurk, but we've deemed things safe enough for broad adoption. If you can, please install beets with ``pip3`` instead of ``pip2`` this time and let us know how it goes! Here are the fixes: * :doc:`/plugins/badfiles`: Fix a crash on non-ASCII filenames. :bug:`2299` * The ``%asciify{}`` path formatting function and the :ref:`asciify-paths` setting properly substitute path separators generated by converting some Unicode characters, such as ½ and ¢, into ASCII. * :doc:`/plugins/convert`: Fix a logging-related crash when filenames contain curly braces. Thanks to :user:`kierdavis`. :bug:`2323` * We've rolled back some changes to the included zsh completion script that were causing problems for some users. :bug:`2266` Also, we've removed some special handling for logging in the :doc:`/plugins/discogs` that we believe was unnecessary. If spurious log messages appear in this version, please let us know by filing a bug. 1.4.1 (November 25, 2016) ------------------------- Version 1.4 has **alpha-level** Python 3 support. Thanks to the heroic efforts of :user:`jrobeson`, beets should run both under Python 2.7, as before, and now under Python 3.4 and above. The support is still new: it undoubtedly contains bugs, so it may replace all your music with Limp Bizkit---but if you're brave and you have backups, please try installing on Python 3. Let us know how it goes. If you package beets for distribution, here's what you'll want to know: * This version of beets now depends on the `six`_ library. * We also bumped our minimum required version of `Mutagen`_ to 1.33 (from 1.27). * Please don't package beets as a Python 3 application *yet*, even though most things work under Python 3.4 and later. This version also makes a few changes to the command-line interface and configuration that you may need to know about: * :doc:`/plugins/duplicates`: The ``duplicates`` command no longer accepts multiple field arguments in the form ``-k title albumartist album``. Each argument must be prefixed with ``-k``, as in ``-k title -k albumartist -k album``. * The old top-level ``colors`` configuration option has been removed (the setting is now under ``ui``). * The deprecated ``list_format_album`` and ``list_format_item`` configuration options have been removed (see :ref:`format_album` and :ref:`format_item`). The are a few new features: * :doc:`/plugins/mpdupdate`, :doc:`/plugins/mpdstats`: When the ``host`` option is not set, these plugins will now look for the ``$MPD_HOST`` environment variable before falling back to ``localhost``. Thanks to :user:`tarruda`. :bug:`2175` * :doc:`/plugins/web`: Added an ``expand`` option to show the items of an album. :bug:`2050` * :doc:`/plugins/embyupdate`: The plugin can now use an API key instead of a password to authenticate with Emby. :bug:`2045` :bug:`2117` * :doc:`/plugins/acousticbrainz`: The plugin now adds a ``bpm`` field. * ``beet --version`` now includes the Python version used to run beets. * :doc:`/reference/pathformat` can now include unescaped commas (``,``) when they are not part of a function call. :bug:`2166` :bug:`2213` * The :ref:`update-cmd` command takes a new ``-F`` flag to specify the fields to update. Thanks to :user:`dangmai`. :bug:`2229` :bug:`2231` And there are a few bug fixes too: * :doc:`/plugins/convert`: The plugin no longer asks for confirmation if the query did not return anything to convert. :bug:`2260` :bug:`2262` * :doc:`/plugins/embedart`: The plugin now uses ``jpg`` as an extension rather than ``jpeg``, to ensure consistency with the :doc:`plugins/fetchart`. Thanks to :user:`tweitzel`. :bug:`2254` :bug:`2255` * :doc:`/plugins/embedart`: The plugin now works for all jpeg files, including those that are only recognizable by their magic bytes. :bug:`1545` :bug:`2255` * :doc:`/plugins/web`: The JSON output is no longer pretty-printed (for a space savings). :bug:`2050` * :doc:`/plugins/permissions`: Fix a regression in the previous release where the plugin would always fail to set permissions (and log a warning). :bug:`2089` * :doc:`/plugins/beatport`: Use track numbers from Beatport (instead of determining them from the order of tracks) and set the `medium_index` value. * With :ref:`per_disc_numbering` enabled, some metadata sources (notably, the :doc:`/plugins/beatport`) would not set the track number at all. This is fixed. :bug:`2085` * :doc:`/plugins/play`: Fix ``$args`` getting passed verbatim to the play command if it was set in the configuration but ``-A`` or ``--args`` was omitted. * With :ref:`ignore_hidden` enabled, non-UTF-8 filenames would cause a crash. This is fixed. :bug:`2168` * :doc:`/plugins/embyupdate`: Fixes authentication header problem that caused a problem that it was not possible to get tokens from the Emby API. * :doc:`/plugins/lyrics`: Some titles use a colon to separate the main title from a subtitle. To find more matches, the plugin now also searches for lyrics using the part part preceding the colon character. :bug:`2206` * Fix a crash when a query uses a date field and some items are missing that field. :bug:`1938` * :doc:`/plugins/discogs`: Subtracks are now detected and combined into a single track, two-sided mediums are treated as single discs, and tracks have ``media``, ``medium_total`` and ``medium`` set correctly. :bug:`2222` :bug:`2228`. * :doc:`/plugins/missing`: ``missing`` is now treated as an integer, allowing the use of (for example) ranges in queries. * :doc:`/plugins/smartplaylist`: Playlist names will be sanitized to ensure valid filenames. :bug:`2258` * The ID3 APIC tag now uses the Latin-1 encoding when possible instead of a Unicode encoding. This should increase compatibility with other software, especially with iTunes and when using ID3v2.3. Thanks to :user:`lazka`. :bug:`899` :bug:`2264` :bug:`2270` The last release, 1.3.19, also erroneously reported its version as "1.3.18" when you typed ``beet version``. This has been corrected. .. _six: https://pypi.org/project/six/ 1.3.19 (June 25, 2016) ---------------------- This is primarily a bug fix release: it cleans up a couple of regressions that appeared in the last version. But it also features the triumphant return of the :doc:`/plugins/beatport` and a modernized :doc:`/plugins/bpd`. It's also the first version where beets passes all its tests on Windows! May this herald a new age of cross-platform reliability for beets. New features: * :doc:`/plugins/beatport`: This metadata source plugin has arisen from the dead! It now works with Beatport's new OAuth-based API. Thanks to :user:`jbaiter`. :bug:`1989` :bug:`2067` * :doc:`/plugins/bpd`: The plugin now uses the modern GStreamer 1.0 instead of the old 0.10. Thanks to :user:`philippbeckmann`. :bug:`2057` :bug:`2062` * A new ``--force`` option for the :ref:`remove-cmd` command allows removal of items without prompting beforehand. :bug:`2042` * A new :ref:`duplicate_action` importer config option controls how duplicate albums or tracks treated in import task. :bug:`185` Some fixes for Windows: * Queries are now detected as paths when they contain backslashes (in addition to forward slashes). This only applies on Windows. * :doc:`/plugins/embedart`: Image similarity comparison with ImageMagick should now work on Windows. * :doc:`/plugins/fetchart`: The plugin should work more reliably with non-ASCII paths. And other fixes: * :doc:`/plugins/replaygain`: The ``bs1770gain`` backend now correctly calculates sample peak instead of true peak. This comes with a major speed increase. :bug:`2031` * :doc:`/plugins/lyrics`: Avoid a crash and a spurious warning introduced in the last version about a Google API key, which appeared even when you hadn't enabled the Google lyrics source. * Fix a hard-coded path to ``bash-completion`` to work better with Homebrew installations. Thanks to :user:`bismark`. :bug:`2038` * Fix a crash introduced in the previous version when the standard input was connected to a Unix pipe. :bug:`2041` * Fix a crash when specifying non-ASCII format strings on the command line with the ``-f`` option for many commands. :bug:`2063` * :doc:`/plugins/fetchart`: Determine the file extension for downloaded images based on the image's magic bytes. The plugin prints a warning if result is not consistent with the server-supplied ``Content-Type`` header. In previous versions, the plugin would use a ``.jpg`` extension for all images. :bug:`2053` 1.3.18 (May 31, 2016) --------------------- This update adds a new :doc:`/plugins/hook` that lets you integrate beets with command-line tools and an :doc:`/plugins/export` that can dump data from the beets database as JSON. You can also automatically translate lyrics using a machine translation service. The ``echonest`` plugin has been removed in this version because the API it used is `shutting down`_. You might want to try the :doc:`/plugins/acousticbrainz` instead. .. _shutting down: https://developer.spotify.com/news-stories/2016/03/29/api-improvements-update/ Some of the larger new features: * The new :doc:`/plugins/hook` lets you execute commands in response to beets events. * The new :doc:`/plugins/export` can export data from beets' database as JSON. Thanks to :user:`GuilhermeHideki`. * :doc:`/plugins/lyrics`: The plugin can now translate the fetched lyrics to your native language using the Bing translation API. Thanks to :user:`Kraymer`. * :doc:`/plugins/fetchart`: Album art can now be fetched from `fanart.tv`_. Smaller new things: * There are two new functions available in templates: ``%first`` and ``%ifdef``. See :ref:`template-functions`. * :doc:`/plugins/convert`: A new `album_art_maxwidth` setting lets you resize album art while copying it. * :doc:`/plugins/convert`: The `extension` setting is now optional for conversion formats. By default, the extension is the same as the name of the configured format. * :doc:`/plugins/importadded`: A new `preserve_write_mtimes` option lets you preserve mtime of files even when beets updates their metadata. * :doc:`/plugins/fetchart`: The `enforce_ratio` option now lets you tolerate images that are *almost* square but differ slightly from an exact 1:1 aspect ratio. * :doc:`/plugins/fetchart`: The plugin can now optionally save the artwork's source in an attribute in the database. * The :ref:`terminal_encoding` configuration option can now also override the *input* encoding. (Previously, it only affected the encoding of the standard *output* stream.) * A new :ref:`ignore_hidden` configuration option lets you ignore files that your OS marks as invisible. * :doc:`/plugins/web`: A new `values` endpoint lets you get the distinct values of a field. Thanks to :user:`sumpfralle`. :bug:`2010` .. _fanart.tv: https://fanart.tv/ Fixes: * Fix a problem with the :ref:`stats-cmd` command in exact mode when filenames on Windows use non-ASCII characters. :bug:`1891` * Fix a crash when iTunes Sound Check tags contained invalid data. :bug:`1895` * :doc:`/plugins/mbcollection`: The plugin now redacts your MusicBrainz password in the ``beet config`` output. :bug:`1907` * :doc:`/plugins/scrub`: Fix an occasional problem where scrubbing on import could undo the :ref:`id3v23` setting. :bug:`1903` * :doc:`/plugins/lyrics`: Add compatibility with some changes to the LyricsWiki page markup. :bug:`1912` :bug:`1909` * :doc:`/plugins/lyrics`: Fix retrieval from Musixmatch by improving the way we guess the URL for lyrics on that service. :bug:`1880` * :doc:`/plugins/edit`: Fail gracefully when the configured text editor command can't be invoked. :bug:`1927` * :doc:`/plugins/fetchart`: Fix a crash in the Wikipedia backend on non-ASCII artist and album names. :bug:`1960` * :doc:`/plugins/convert`: Change the default `ogg` encoding quality from 2 to 3 (to fit the default from the `oggenc(1)` manpage). :bug:`1982` * :doc:`/plugins/convert`: The `never_convert_lossy_files` option now considers AIFF a lossless format. :bug:`2005` * :doc:`/plugins/web`: A proper 404 error, instead of an internal exception, is returned when missing album art is requested. Thanks to :user:`sumpfralle`. :bug:`2011` * Tolerate more malformed floating-point numbers in metadata tags. :bug:`2014` * The :ref:`ignore` configuration option now includes the ``lost+found`` directory by default. * :doc:`/plugins/acousticbrainz`: AcousticBrainz lookups are now done over HTTPS. Thanks to :user:`Freso`. :bug:`2007` 1.3.17 (February 7, 2016) ------------------------- This release introduces one new plugin to fetch audio information from the `AcousticBrainz`_ project and another plugin to make it easier to submit your handcrafted metadata back to MusicBrainz. The importer also gained two oft-requested features: a way to skip the initial search process by specifying an ID ahead of time, and a way to *manually* provide metadata in the middle of the import process (via the :doc:`/plugins/edit`). Also, as of this release, the beets project has some new Internet homes! Our new domain name is `beets.io`_, and we have a shiny new GitHub organization: `beetbox`_. Here are the big new features: * A new :doc:`/plugins/acousticbrainz` fetches acoustic-analysis information from the `AcousticBrainz`_ project. Thanks to :user:`opatel99`, and thanks to `Google Code-In`_! :bug:`1784` * A new :doc:`/plugins/mbsubmit` lets you print music's current metadata in a format that the MusicBrainz data parser can understand. You can trigger it during an interactive import session. :bug:`1779` * A new ``--search-id`` importer option lets you manually specify IDs (i.e., MBIDs or Discogs IDs) for imported music. Doing this skips the initial candidate search, which can be important for huge albums where this initial lookup is slow. Also, the ``enter Id`` prompt choice now accepts several IDs, separated by spaces. :bug:`1808` * :doc:`/plugins/edit`: You can now edit metadata *on the fly* during the import process. The plugin provides two new interactive options: one to edit *your music's* metadata, and one to edit the *matched metadata* retrieved from MusicBrainz (or another data source). This feature is still in its early stages, so please send feedback if you find anything missing. :bug:`1846` :bug:`396` There are even more new features: * :doc:`/plugins/fetchart`: The Google Images backend has been restored. It now requires an API key from Google. Thanks to :user:`lcharlick`. :bug:`1778` * :doc:`/plugins/info`: A new option will print only fields' names and not their values. Thanks to :user:`GuilhermeHideki`. :bug:`1812` * The :ref:`fields-cmd` command now displays flexible attributes. Thanks to :user:`GuilhermeHideki`. :bug:`1818` * The :ref:`modify-cmd` command lets you interactively select which albums or items you want to change. :bug:`1843` * The :ref:`move-cmd` command gained a new ``--timid`` flag to print and confirm which files you want to move. :bug:`1843` * The :ref:`move-cmd` command no longer prints filenames for files that don't actually need to be moved. :bug:`1583` .. _Google Code-In: https://codein.withgoogle.com/ .. _AcousticBrainz: https://acousticbrainz.org/ Fixes: * :doc:`/plugins/play`: Fix a regression in the last version where there was no default command. :bug:`1793` * :doc:`/plugins/lastimport`: The plugin now works again after being broken by some unannounced changes to the Last.fm API. :bug:`1574` * :doc:`/plugins/play`: Fixed a typo in a configuration option. The option is now ``warning_threshold`` instead of ``warning_treshold``, but we kept the old name around for compatibility. Thanks to :user:`JesseWeinstein`. :bug:`1802` :bug:`1803` * :doc:`/plugins/edit`: Editing metadata now moves files, when appropriate (like the :ref:`modify-cmd` command). :bug:`1804` * The :ref:`stats-cmd` command no longer crashes when files are missing or inaccessible. :bug:`1806` * :doc:`/plugins/fetchart`: Possibly fix a Unicode-related crash when using some versions of pyOpenSSL. :bug:`1805` * :doc:`/plugins/replaygain`: Fix an intermittent crash with the GStreamer backend. :bug:`1855` * :doc:`/plugins/lastimport`: The plugin now works with the beets API key by default. You can still provide a different key the configuration. * :doc:`/plugins/replaygain`: Fix a crash using the Python Audio Tools backend. :bug:`1873` .. _beets.io: https://beets.io/ .. _Beetbox: https://github.com/beetbox 1.3.16 (December 28, 2015) -------------------------- The big news in this release is a new :doc:`interactive editor plugin </plugins/edit>`. It's really nifty: you can now change your music's metadata by making changes in a visual text editor, which can sometimes be far more efficient than the built-in :ref:`modify-cmd` command. No more carefully retyping the same artist name with slight capitalization changes. This version also adds an oft-requested "not" operator to beets' queries, so you can exclude music from any operation. It also brings friendlier formatting (and querying!) of song durations. The big new stuff: * A new :doc:`/plugins/edit` lets you manually edit your music's metadata using your favorite text editor. :bug:`164` :bug:`1706` * Queries can now use "not" logic. Type a ``^`` before part of a query to *exclude* matching music from the results. For example, ``beet list -a beatles ^album:1`` will find all your albums by the Beatles except for their singles compilation, "1." See :ref:`not_query`. :bug:`819` :bug:`1728` * A new :doc:`/plugins/embyupdate` can trigger a library refresh on an `Emby`_ server when your beets database changes. * Track length is now displayed as "M:SS" rather than a raw number of seconds. Queries on track length also accept this format: for example, ``beet list length:5:30..`` will find all your tracks that have a duration over 5 minutes and 30 seconds. You can turn off this new behavior using the ``format_raw_length`` configuration option. :bug:`1749` Smaller changes: * Three commands, ``modify``, ``update``, and ``mbsync``, would previously move files by default after changing their metadata. Now, these commands will only move files if you have the :ref:`config-import-copy` or :ref:`config-import-move` options enabled in your importer configuration. This way, if you configure the importer not to touch your filenames, other commands will respect that decision by default too. Each command also sprouted a ``--move`` command-line option to override this default (in addition to the ``--nomove`` flag they already had). :bug:`1697` * A new configuration option, ``va_name``, controls the album artist name for various-artists albums. The setting defaults to "Various Artists," the MusicBrainz standard. In order to match MusicBrainz, the :doc:`/plugins/discogs` also adopts the same setting. * :doc:`/plugins/info`: The ``info`` command now accepts a ``-f/--format`` option for customizing how items are displayed, just like the built-in ``list`` command. :bug:`1737` Some changes for developers: * Two new :ref:`plugin hooks <plugin_events>`, ``albuminfo_received`` and ``trackinfo_received``, let plugins intercept metadata as soon as it is received, before it is applied to music in the database. :bug:`872` * Plugins can now add options to the interactive importer prompts. See :ref:`append_prompt_choices`. :bug:`1758` Fixes: * :doc:`/plugins/plexupdate`: Fix a crash when Plex libraries use non-ASCII collection names. :bug:`1649` * :doc:`/plugins/discogs`: Maybe fix a crash when using some versions of the ``requests`` library. :bug:`1656` * Fix a race in the importer when importing two albums with the same artist and name in quick succession. The importer would fail to detect them as duplicates, claiming that there were "empty albums" in the database even when there were not. :bug:`1652` * :doc:`plugins/lastgenre`: Clean up the reggae-related genres somewhat. Thanks to :user:`Freso`. :bug:`1661` * The importer now correctly moves album art files when re-importing. :bug:`314` * :doc:`/plugins/fetchart`: In auto mode, the plugin now skips albums that already have art attached to them so as not to interfere with re-imports. :bug:`314` * :doc:`plugins/fetchart`: The plugin now only resizes album art if necessary, rather than always by default. :bug:`1264` * :doc:`plugins/fetchart`: Fix a bug where a database reference to a non-existent album art file would prevent the command from fetching new art. :bug:`1126` * :doc:`/plugins/thumbnails`: Fix a crash with Unicode paths. :bug:`1686` * :doc:`/plugins/embedart`: The ``remove_art_file`` option now works on import (as well as with the explicit command). :bug:`1662` :bug:`1675` * :doc:`/plugins/metasync`: Fix a crash when syncing with recent versions of iTunes. :bug:`1700` * :doc:`/plugins/duplicates`: Fix a crash when merging items. :bug:`1699` * :doc:`/plugins/smartplaylist`: More gracefully handle malformed queries and missing configuration. * Fix a crash with some files with unreadable iTunes SoundCheck metadata. :bug:`1666` * :doc:`/plugins/thumbnails`: Fix a nasty segmentation fault crash that arose with some library versions. :bug:`1433` * :doc:`/plugins/convert`: Fix a crash with Unicode paths in ``--pretend`` mode. :bug:`1735` * Fix a crash when sorting by nonexistent fields on queries. :bug:`1734` * Probably fix some mysterious errors when dealing with images using ImageMagick on Windows. :bug:`1721` * Fix a crash when writing some Unicode comment strings to MP3s that used older encodings. The encoding is now always updated to UTF-8. :bug:`879` * :doc:`/plugins/fetchart`: The Google Images backend has been removed. It used an API that has been shut down. :bug:`1760` * :doc:`/plugins/lyrics`: Fix a crash in the Google backend when searching for bands with regular-expression characters in their names, like Sunn O))). :bug:`1673` * :doc:`/plugins/scrub`: In ``auto`` mode, the plugin now *actually* only scrubs files on import, as the documentation always claimed it did---not every time files were written, as it previously did. :bug:`1657` * :doc:`/plugins/scrub`: Also in ``auto`` mode, album art is now correctly restored. :bug:`1657` * Possibly allow flexible attributes to be used with the ``%aunique`` template function. :bug:`1775` * :doc:`/plugins/lyrics`: The Genius backend is now more robust to communication errors. The backend has also been disabled by default, since the API it depends on is currently down. :bug:`1770` .. _Emby: https://emby.media 1.3.15 (October 17, 2015) ------------------------- This release adds a new plugin for checking file quality and a new source for lyrics. The larger features are: * A new :doc:`/plugins/badfiles` helps you scan for corruption in your music collection. Thanks to :user:`fxthomas`. :bug:`1568` * :doc:`/plugins/lyrics`: You can now fetch lyrics from Genius.com. Thanks to :user:`sadatay`. :bug:`1626` :bug:`1639` * :doc:`/plugins/zero`: The plugin can now use a "whitelist" policy as an alternative to the (default) "blacklist" mode. Thanks to :user:`adkow`. :bug:`1621` :bug:`1641` And there are smaller new features too: * Add new color aliases for standard terminal color names (e.g., cyan and magenta). Thanks to :user:`mathstuf`. :bug:`1548` * :doc:`/plugins/play`: A new ``--args`` option lets you specify options for the player command. :bug:`1532` * :doc:`/plugins/play`: A new ``raw`` configuration option lets the command work with players (such as VLC) that expect music filenames as arguments, rather than in a playlist. Thanks to :user:`nathdwek`. :bug:`1578` * :doc:`/plugins/play`: You can now configure the number of tracks that trigger a "lots of music" warning. :bug:`1577` * :doc:`/plugins/embedart`: A new ``remove_art_file`` option lets you clean up if you prefer *only* embedded album art. Thanks to :user:`jackwilsdon`. :bug:`1591` :bug:`733` * :doc:`/plugins/plexupdate`: A new ``library_name`` option allows you to select which Plex library to update. :bug:`1572` :bug:`1595` * A new ``include`` option lets you import external configuration files. This release has plenty of fixes: * :doc:`/plugins/lastgenre`: Fix a bug that prevented tag popularity from being considered. Thanks to :user:`svoos`. :bug:`1559` * Fixed a bug where plugins wouldn't be notified of the deletion of an item's art, for example with the ``clearart`` command from the :doc:`/plugins/embedart`. Thanks to :user:`nathdwek`. :bug:`1565` * :doc:`/plugins/fetchart`: The Google Images source is disabled by default (as it was before beets 1.3.9), as is the Wikipedia source (which was causing lots of unnecessary delays due to DBpedia downtime). To re-enable these sources, add ``wikipedia google`` to your ``sources`` configuration option. * The :ref:`list-cmd` command's help output now has a small query and format string example. Thanks to :user:`pkess`. :bug:`1582` * :doc:`/plugins/fetchart`: The plugin now fetches PNGs but not GIFs. (It still fetches JPEGs.) This avoids an error when trying to embed images, since not all formats support GIFs. :bug:`1588` * Date fields are now written in the correct order (year-month-day), which eliminates an intermittent bug where the latter two fields would not get written to files. Thanks to :user:`jdetrey`. :bug:`1303` :bug:`1589` * :doc:`/plugins/replaygain`: Avoid a crash when the PyAudioTools backend encounters an error. :bug:`1592` * The case sensitivity of path queries is more useful now: rather than just guessing based on the platform, we now check the case sensitivity of your filesystem. :bug:`1586` * Case-insensitive path queries might have returned nothing because of a wrong SQL query. * Fix a crash when a query contains a "+" or "-" alone in a component. :bug:`1605` * Fixed unit of file size to powers of two (MiB, GiB, etc.) instead of powers of ten (MB, GB, etc.). :bug:`1623` 1.3.14 (August 2, 2015) ----------------------- This is mainly a bugfix release, but we also have a nifty new plugin for `ipfs`_ and a bunch of new configuration options. The new features: * A new :doc:`/plugins/ipfs` lets you share music via a new, global, decentralized filesystem. :bug:`1397` * :doc:`/plugins/duplicates`: You can now merge duplicate track metadata (when detecting duplicate items), or duplicate album tracks (when detecting duplicate albums). * :doc:`/plugins/duplicates`: Duplicate resolution now uses an ordering to prioritize duplicates. By default, it prefers music with more complete metadata, but you can configure it to use any list of attributes. * :doc:`/plugins/metasync`: Added a new backend to fetch metadata from iTunes. This plugin is still in an experimental phase. :bug:`1450` * The `move` command has a new ``--pretend`` option, making the command show how the items will be moved without actually changing anything. * The importer now supports matching of "pregap" or HTOA (hidden track-one audio) tracks when they are listed in MusicBrainz. (This feature depends on a new version of the `python-musicbrainzngs`_ library that is not yet released, but will start working when it is available.) Thanks to :user:`ruippeixotog`. :bug:`1104` :bug:`1493` * :doc:`/plugins/plexupdate`: A new ``token`` configuration option lets you specify a key for Plex Home setups. Thanks to :user:`edcarroll`. :bug:`1494` Fixes: * :doc:`/plugins/fetchart`: Complain when the `enforce_ratio` or `min_width` options are enabled but no local imaging backend is available to carry them out. :bug:`1460` * :doc:`/plugins/importfeeds`: Avoid generating incorrect m3u filename when both of the `m3u` and `m3u_multi` options are enabled. :bug:`1490` * :doc:`/plugins/duplicates`: Avoid a crash when misconfigured. :bug:`1457` * :doc:`/plugins/mpdstats`: Avoid a crash when the music played is not in the beets library. Thanks to :user:`CodyReichert`. :bug:`1443` * Fix a crash with ArtResizer on Windows systems (affecting :doc:`/plugins/embedart`, :doc:`/plugins/fetchart`, and :doc:`/plugins/thumbnails`). :bug:`1448` * :doc:`/plugins/permissions`: Fix an error with non-ASCII paths. :bug:`1449` * Fix sorting by paths when the :ref:`sort_case_insensitive` option is enabled. :bug:`1451` * :doc:`/plugins/embedart`: Avoid an error when trying to embed invalid images into MPEG-4 files. * :doc:`/plugins/fetchart`: The Wikipedia source can now better deal artists that use non-standard capitalization (e.g., alt-J, dEUS). * :doc:`/plugins/web`: Fix searching for non-ASCII queries. Thanks to :user:`oldtopman`. :bug:`1470` * :doc:`/plugins/mpdupdate`: We now recommend the newer ``python-mpd2`` library instead of its unmaintained parent. Thanks to :user:`Somasis`. :bug:`1472` * The importer interface and log file now output a useful list of files (instead of the word "None") when in album-grouping mode. :bug:`1475` :bug:`825` * Fix some logging errors when filenames and other user-provided strings contain curly braces. :bug:`1481` * Regular expression queries over paths now work more reliably with non-ASCII characters in filenames. :bug:`1482` * Fix a bug where the autotagger's :ref:`ignored` setting was sometimes, well, ignored. :bug:`1487` * Fix a bug with Unicode strings when generating image thumbnails. :bug:`1485` * :doc:`/plugins/keyfinder`: Fix handling of Unicode paths. :bug:`1502` * :doc:`/plugins/fetchart`: When album art is already present, the message is now printed in the ``text_highlight_minor`` color (light gray). Thanks to :user:`Somasis`. :bug:`1512` * Some messages in the console UI now use plural nouns correctly. Thanks to :user:`JesseWeinstein`. :bug:`1521` * Sorting numerical fields (such as track) now works again. :bug:`1511` * :doc:`/plugins/replaygain`: Missing GStreamer plugins now cause a helpful error message instead of a crash. :bug:`1518` * Fix an edge case when producing sanitized filenames where the maximum path length conflicted with the :ref:`replace` rules. Thanks to Ben Ockmore. :bug:`496` :bug:`1361` * Fix an incompatibility with OS X 10.11 (where ``/usr/sbin`` seems not to be on the user's path by default). * Fix an incompatibility with certain JPEG files. Here's a relevant `Python bug`_. Thanks to :user:`nathdwek`. :bug:`1545` * Fix the :ref:`group_albums` importer mode so that it works correctly when files are not already in order by album. :bug:`1550` * The ``fields`` command no longer separates built-in fields from plugin-provided ones. This distinction was becoming increasingly unreliable. * :doc:`/plugins/duplicates`: Fix a Unicode warning when paths contained non-ASCII characters. :bug:`1551` * :doc:`/plugins/fetchart`: Work around a urllib3 bug that could cause a crash. :bug:`1555` :bug:`1556` * When you edit the configuration file with ``beet config -e`` and the file does not exist, beets creates an empty file before editing it. This fixes an error on OS X, where the ``open`` command does not work with non-existent files. :bug:`1480` * :doc:`/plugins/convert`: Fix a problem with filename encoding on Windows under Python 3. :bug:`2515` :bug:`2516` .. _Python bug: https://bugs.python.org/issue16512 .. _ipfs: https://ipfs.io 1.3.13 (April 24, 2015) ----------------------- This is a tiny bug-fix release. It copes with a dependency upgrade that broke beets. There are just two fixes: * Fix compatibility with `Jellyfish`_ version 0.5.0. * :doc:`/plugins/embedart`: In ``auto`` mode (the import hook), the plugin now respects the ``write`` config option under ``import``. If this is disabled, album art is no longer embedded on import in order to leave files untouched---in effect, ``auto`` is implicitly disabled. :bug:`1427` 1.3.12 (April 18, 2015) ----------------------- This little update makes queries more powerful, sorts music more intelligently, and removes a performance bottleneck. There's an experimental new plugin for synchronizing metadata with music players. Packagers should also note a new dependency in this version: the `Jellyfish`_ Python library makes our text comparisons (a big part of the auto-tagging process) go much faster. New features: * Queries can now use **"or" logic**: if you use a comma to separate parts of a query, items and albums will match *either* side of the comma. For example, ``beet ls foo , bar`` will get all the items matching `foo` or matching `bar`. See :ref:`combiningqueries`. :bug:`1423` * The autotagger's **matching algorithm is faster**. We now use the `Jellyfish`_ library to compute string similarity, which is better optimized than our hand-rolled edit distance implementation. :bug:`1389` * Sorting is now **case insensitive** by default. This means that artists will be sorted lexicographically regardless of case. For example, the artist alt-J will now properly sort before YACHT. (Previously, it would have ended up at the end of the list, after all the capital-letter artists.) You can turn this new behavior off using the :ref:`sort_case_insensitive` configuration option. See :ref:`query-sort`. :bug:`1429` * An experimental new :doc:`/plugins/metasync` lets you get metadata from your favorite music players, starting with Amarok. :bug:`1386` * :doc:`/plugins/fetchart`: There are new settings to control what constitutes "acceptable" images. The `minwidth` option constrains the minimum image width in pixels and the `enforce_ratio` option requires that images be square. :bug:`1394` Little fixes and improvements: * :doc:`/plugins/fetchart`: Remove a hard size limit when fetching from the Cover Art Archive. * The output of the :ref:`fields-cmd` command is now sorted. Thanks to :user:`multikatt`. :bug:`1402` * :doc:`/plugins/replaygain`: Fix a number of issues with the new ``bs1770gain`` backend on Windows. Also, fix missing debug output in import mode. :bug:`1398` * Beets should now be better at guessing the appropriate output encoding on Windows. (Specifically, the console output encoding is guessed separately from the encoding for command-line arguments.) A bug was also fixed where beets would ignore the locale settings and use UTF-8 by default. :bug:`1419` * :doc:`/plugins/discogs`: Better error handling when we can't communicate with Discogs on setup. :bug:`1417` * :doc:`/plugins/importadded`: Fix a crash when importing singletons in-place. :bug:`1416` * :doc:`/plugins/fuzzy`: Fix a regression causing a crash in the last release. :bug:`1422` * Fix a crash when the importer cannot open its log file. Thanks to :user:`barsanuphe`. :bug:`1426` * Fix an error when trying to write tags for items with flexible fields called `date` and `original_date` (which are not built-in beets fields). :bug:`1404` .. _Jellyfish: https://github.com/sunlightlabs/jellyfish 1.3.11 (April 5, 2015) ---------------------- In this release, we refactored the logging system to be more flexible and more useful. There are more granular levels of verbosity, the output from plugins should be more consistent, and several kinds of logging bugs should be impossible in the future. There are also two new plugins: one for filtering the files you import and an evolved plugin for using album art as directory thumbnails in file managers. There's a new source for album art, and the importer now records the source of match data. This is a particularly huge release---there's lots more below. There's one big change with this release: **Python 2.6 is no longer supported**. You'll need Python 2.7. Please trust us when we say this let us remove a surprising number of ugly hacks throughout the code. Major new features and bigger changes: * There are now **multiple levels of output verbosity**. On the command line, you can make beets somewhat verbose with ``-v`` or very verbose with ``-vv``. For the importer especially, this makes the first verbose mode much more manageable, while still preserving an option for overwhelmingly verbose debug output. :bug:`1244` * A new :doc:`/plugins/filefilter` lets you write regular expressions to automatically **avoid importing** certain files. Thanks to :user:`mried`. :bug:`1186` * A new :doc:`/plugins/thumbnails` generates cover-art **thumbnails for album folders** for Freedesktop.org-compliant file managers. (This replaces the :doc:`/plugins/freedesktop`, which only worked with the Dolphin file manager.) * :doc:`/plugins/replaygain`: There is a new backend that uses the `bs1770gain`_ analysis tool. Thanks to :user:`jmwatte`. :bug:`1343` * A new ``filesize`` field on items indicates the number of bytes in the file. :bug:`1291` * A new :ref:`searchlimit` configuration option allows you to specify how many search results you wish to see when looking up releases at MusicBrainz during import. :bug:`1245` * The importer now records the data source for a match in a new flexible attribute ``data_source`` on items and albums. :bug:`1311` * The colors used in the terminal interface are now configurable via the new config option ``colors``, nested under the option ``ui``. (Also, the `color` config option has been moved from top-level to under ``ui``. Beets will respect the old color setting, but will warn the user with a deprecation message.) :bug:`1238` * :doc:`/plugins/fetchart`: There's a new Wikipedia image source that uses DBpedia to find albums. Thanks to Tom Jaspers. :bug:`1194` * In the :ref:`config-cmd` command, the output is now redacted by default. Sensitive information like passwords and API keys is not included. The new ``--clear`` option disables redaction. :bug:`1376` You should probably also know about these core changes to the way beets works: * As mentioned above, Python 2.6 is no longer supported. * The ``tracktotal`` attribute is now a *track-level field* instead of an album-level one. This field stores the total number of tracks on the album, or if the :ref:`per_disc_numbering` config option is set, the total number of tracks on a particular medium (i.e., disc). The field was causing problems with that :ref:`per_disc_numbering` mode: different discs on the same album needed different track totals. The field can now work correctly in either mode. * To replace ``tracktotal`` as an album-level field, there is a new ``albumtotal`` computed attribute that provides the total number of tracks on the album. (The :ref:`per_disc_numbering` option has no influence on this field.) * The `list_format_album` and `list_format_item` configuration keys now affect (almost) every place where objects are printed and logged. (Previously, they only controlled the :ref:`list-cmd` command and a few other scattered pieces.) :bug:`1269` * Relatedly, the ``beet`` program now accept top-level options ``--format-item`` and ``--format-album`` before any subcommand to control how items and albums are displayed. :bug:`1271` * `list_format_album` and `list_format_album` have respectively been renamed :ref:`format_album` and :ref:`format_item`. The old names still work but each triggers a warning message. :bug:`1271` * :ref:`Path queries <pathquery>` are automatically triggered only if the path targeted by the query exists. Previously, just having a slash somewhere in the query was enough, so ``beet ls AC/DC`` wouldn't work to refer to the artist. There are also lots of medium-sized features in this update: * :doc:`/plugins/duplicates`: The command has a new ``--strict`` option that will only report duplicates if all attributes are explicitly set. :bug:`1000` * :doc:`/plugins/smartplaylist`: Playlist updating should now be faster: the plugin detects, for each playlist, whether it needs to be regenerated, instead of obliviously regenerating all of them. The ``splupdate`` command can now also take additional parameters that indicate the names of the playlists to regenerate. * :doc:`/plugins/play`: The command shows the output of the underlying player command and lets you interact with it. :bug:`1321` * The summary shown to compare duplicate albums during import now displays the old and new filesizes. :bug:`1291` * :doc:`/plugins/lastgenre`: Add *comedy*, *humor*, and *stand-up* as well as a longer list of classical music genre tags to the built-in whitelist and canonicalization tree. :bug:`1206` :bug:`1239` :bug:`1240` * :doc:`/plugins/web`: Add support for *cross-origin resource sharing* for more flexible in-browser clients. Thanks to Andre Miller. :bug:`1236` :bug:`1237` * :doc:`plugins/mbsync`: A new ``-f/--format`` option controls the output format when listing unrecognized items. The output is also now more helpful by default. :bug:`1246` * :doc:`/plugins/fetchart`: A new option, ``-n``, extracts the cover art of all matched albums into their respective directories. Another new flag, ``-a``, associates the extracted files with the albums in the database. :bug:`1261` * :doc:`/plugins/info`: A new option, ``-i``, can display only a specified subset of properties. :bug:`1287` * The number of missing/unmatched tracks is shown during import. :bug:`1088` * :doc:`/plugins/permissions`: The plugin now also adjusts the permissions of the directories. (Previously, it only affected files.) :bug:`1308` :bug:`1324` * :doc:`/plugins/ftintitle`: You can now configure the format that the plugin uses to add the artist to the title. Thanks to :user:`amishb`. :bug:`1377` And many little fixes and improvements: * :doc:`/plugins/replaygain`: Stop applying replaygain directly to source files when using the mp3gain backend. :bug:`1316` * Path queries are case-sensitive on non-Windows OSes. :bug:`1165` * :doc:`/plugins/lyrics`: Silence a warning about insecure requests in the new MusixMatch backend. :bug:`1204` * Fix a crash when ``beet`` is invoked without arguments. :bug:`1205` :bug:`1207` * :doc:`/plugins/fetchart`: Do not attempt to import directories as album art. :bug:`1177` :bug:`1211` * :doc:`/plugins/mpdstats`: Avoid double-counting some play events. :bug:`773` :bug:`1212` * Fix a crash when the importer deals with Unicode metadata in ``--pretend`` mode. :bug:`1214` * :doc:`/plugins/smartplaylist`: Fix ``album_query`` so that individual files are added to the playlist instead of directories. :bug:`1225` * Remove the ``beatport`` plugin. `Beatport`_ has shut off public access to their API and denied our request for an account. We have not heard from the company since 2013, so we are assuming access will not be restored. * Incremental imports now (once again) show a "skipped N directories" message. * :doc:`/plugins/embedart`: Handle errors in ImageMagick's output. :bug:`1241` * :doc:`/plugins/keyfinder`: Parse the underlying tool's output more robustly. :bug:`1248` * :doc:`/plugins/embedart`: We now show a comprehensible error message when ``beet embedart -f FILE`` is given a non-existent path. :bug:`1252` * Fix a crash when a file has an unrecognized image type tag. Thanks to Matthias Kiefer. :bug:`1260` * :doc:`/plugins/importfeeds` and :doc:`/plugins/smartplaylist`: Automatically create parent directories for playlist files (instead of crashing when the parent directory does not exist). :bug:`1266` * The :ref:`write-cmd` command no longer tries to "write" non-writable fields, such as the bitrate. :bug:`1268` * The error message when MusicBrainz is not reachable on the network is now much clearer. Thanks to Tom Jaspers. :bug:`1190` :bug:`1272` * Improve error messages when parsing query strings with shlex. :bug:`1290` * :doc:`/plugins/embedart`: Fix a crash that occurred when used together with the *check* plugin. :bug:`1241` * :doc:`/plugins/scrub`: Log an error instead of stopping when the ``beet scrub`` command cannot write a file. Also, avoid problems on Windows with Unicode filenames. :bug:`1297` * :doc:`/plugins/discogs`: Handle and log more kinds of communication errors. :bug:`1299` :bug:`1305` * :doc:`/plugins/lastgenre`: Bugs in the `pylast` library can no longer crash beets. * :doc:`/plugins/convert`: You can now configure the temporary directory for conversions. Thanks to :user:`autochthe`. :bug:`1382` :bug:`1383` * :doc:`/plugins/rewrite`: Fix a regression that prevented the plugin's rewriting from applying to album-level fields like ``$albumartist``. :bug:`1393` * :doc:`/plugins/play`: The plugin now sorts items according to the configuration in album mode. * :doc:`/plugins/fetchart`: The name for extracted art files is taken from the ``art_filename`` configuration option. :bug:`1258` * When there's a parse error in a query (for example, when you type a malformed date in a :ref:`date query <datequery>`), beets now stops with an error instead of silently ignoring the query component. * :doc:`/plugins/smartplaylist`: Stream-friendly smart playlists. The ``splupdate`` command can now also add a URL-encodable prefix to every path in the playlist file. For developers: * The ``database_change`` event now sends the item or album that is subject to a change. * The ``OptionParser`` is now a ``CommonOptionsParser`` that offers facilities for adding usual options (``--album``, ``--path`` and ``--format``). See :ref:`add_subcommands`. :bug:`1271` * The logging system in beets has been overhauled. Plugins now each have their own logger, which helps by automatically adjusting the verbosity level in import mode and by prefixing the plugin's name. Logging levels are dynamically set when a plugin is called, depending on how it is called (import stage, event or direct command). Finally, logging calls can (and should!) use modern ``{}``-style string formatting lazily. See :ref:`plugin-logging` in the plugin API docs. * A new ``import_task_created`` event lets you manipulate import tasks immediately after they are initialized. It's also possible to replace the originally created tasks by returning new ones using this event. .. _bs1770gain: http://bs1770gain.sourceforge.net 1.3.10 (January 5, 2015) ------------------------ This version adds a healthy helping of new features and fixes a critical MPEG-4--related bug. There are more lyrics sources, there new plugins for managing permissions and integrating with `Plex`_, and the importer has a new ``--pretend`` flag that shows which music *would* be imported. One backwards-compatibility note: the :doc:`/plugins/lyrics` now requires the `requests`_ library. If you use this plugin, you will need to install the library by typing ``pip install requests`` or the equivalent for your OS. Also, as an advance warning, this will be one of the last releases to support Python 2.6. If you have a system that cannot run Python 2.7, please consider upgrading soon. The new features are: * A new :doc:`/plugins/permissions` makes it easy to fix permissions on music files as they are imported. Thanks to :user:`xsteadfastx`. :bug:`1098` * A new :doc:`/plugins/plexupdate` lets you notify a `Plex`_ server when the database changes. Thanks again to xsteadfastx. :bug:`1120` * The :ref:`import-cmd` command now has a ``--pretend`` flag that lists the files that will be imported. Thanks to :user:`mried`. :bug:`1162` * :doc:`/plugins/lyrics`: Add `Musixmatch`_ source and introduce a new ``sources`` config option that lets you choose exactly where to look for lyrics and in which order. * :doc:`/plugins/lyrics`: Add Brazilian and Spanish sources to Google custom search engine. * Add a warning when importing a directory that contains no music. :bug:`1116` :bug:`1127` * :doc:`/plugins/zero`: Can now remove embedded images. :bug:`1129` :bug:`1100` * The :ref:`config-cmd` command can now be used to edit the configuration even when it has syntax errors. :bug:`1123` :bug:`1128` * :doc:`/plugins/lyrics`: Added a new ``force`` config option. :bug:`1150` As usual, there are loads of little fixes and improvements: * Fix a new crash with the latest version of Mutagen (1.26). * :doc:`/plugins/lyrics`: Avoid fetching truncated lyrics from the Google backed by merging text blocks separated by empty ``<div>`` tags before scraping. * We now print a better error message when the database file is corrupted. * :doc:`/plugins/discogs`: Only prompt for authentication when running the :ref:`import-cmd` command. :bug:`1123` * When deleting fields with the :ref:`modify-cmd` command, do not crash when the field cannot be removed (i.e., when it does not exist, when it is a built-in field, or when it is a computed field). :bug:`1124` * The deprecated ``echonest_tempo`` plugin has been removed. Please use the ``echonest`` plugin instead. * ``echonest`` plugin: Fingerprint-based lookup has been removed in accordance with `API changes`_. :bug:`1121` * ``echonest`` plugin: Avoid a crash when the song has no duration information. :bug:`896` * :doc:`/plugins/lyrics`: Avoid a crash when retrieving non-ASCII lyrics from the Google backend. :bug:`1135` :bug:`1136` * :doc:`/plugins/smartplaylist`: Sort specifiers are now respected in queries. Thanks to :user:`djl`. :bug:`1138` :bug:`1137` * :doc:`/plugins/ftintitle` and :doc:`/plugins/lyrics`: Featuring artists can now be detected when they use the Spanish word *con*. :bug:`1060` :bug:`1143` * :doc:`/plugins/mbcollection`: Fix an "HTTP 400" error caused by a change in the MusicBrainz API. :bug:`1152` * The ``%`` and ``_`` characters in path queries do not invoke their special SQL meaning anymore. :bug:`1146` * :doc:`/plugins/convert`: Command-line argument construction now works on Windows. Thanks to :user:`mluds`. :bug:`1026` :bug:`1157` :bug:`1158` * :doc:`/plugins/embedart`: Fix an erroneous missing-art error on Windows. Thanks to :user:`mluds`. :bug:`1163` * :doc:`/plugins/importadded`: Now works with in-place and symlinked imports. :bug:`1170` * :doc:`/plugins/ftintitle`: The plugin is now quiet when it runs as part of the import process. Thanks to :user:`Freso`. :bug:`1176` :bug:`1172` * :doc:`/plugins/ftintitle`: Fix weird behavior when the same artist appears twice in the artist string. Thanks to Marc Addeo. :bug:`1179` :bug:`1181` * :doc:`/plugins/lastgenre`: Match songs more robustly when they contain dashes. Thanks to :user:`djl`. :bug:`1156` * The :ref:`config-cmd` command can now use ``$EDITOR`` variables with arguments. .. _API changes: https://web.archive.org/web/20160814092627/https://developer.echonest.com/forums/thread/3650 .. _Plex: https://plex.tv/ .. _musixmatch: https://www.musixmatch.com/ 1.3.9 (November 17, 2014) ------------------------- This release adds two new standard plugins to beets: one for synchronizing Last.fm listening data and one for integrating with Linux desktops. And at long last, imports can now create symbolic links to music files instead of copying or moving them. We also gained the ability to search for album art on the iTunes Store and a new way to compute ReplayGain levels. The major new features are: * A new :doc:`/plugins/lastimport` lets you download your play count data from Last.fm into a flexible attribute. Thanks to Rafael Bodill. * A new :doc:`/plugins/freedesktop` creates metadata files for Freedesktop.org--compliant file managers. Thanks to :user:`kerobaros`. :bug:`1056`, :bug:`707` * A new :ref:`link` option in the ``import`` section creates symbolic links during import instead of moving or copying. Thanks to Rovanion Luckey. :bug:`710`, :bug:`114` * :doc:`/plugins/fetchart`: You can now search for art on the iTunes Store. There's also a new ``sources`` config option that lets you choose exactly where to look for images and in which order. * :doc:`/plugins/replaygain`: A new Python Audio Tools backend was added. Thanks to Francesco Rubino. :bug:`1070` * :doc:`/plugins/embedart`: You can now automatically check that new art looks similar to existing art---ensuring that you only get a better "version" of the art you already have. See :ref:`image-similarity-check`. * :doc:`/plugins/ftintitle`: The plugin now runs automatically on import. To disable this, unset the ``auto`` config flag. There are also core improvements and other substantial additions: * The ``media`` attribute is now a *track-level field* instead of an album-level one. This field stores the delivery mechanism for the music, so in its album-level incarnation, it could not represent heterogeneous releases---for example, an album consisting of a CD and a DVD. Now, tracks accurately indicate the media they appear on. Thanks to Heinz Wiesinger. * Re-imports of your existing music (see :ref:`reimport`) now preserve its added date and flexible attributes. Thanks to Stig Inge Lea Bjørnsen. * Slow queries, such as those over flexible attributes, should now be much faster when used with certain commands---notably, the :doc:`/plugins/play`. * :doc:`/plugins/bpd`: Add a new configuration option for setting the default volume. Thanks to IndiGit. * :doc:`/plugins/embedart`: A new ``ifempty`` config option lets you only embed album art when no album art is present. Thanks to kerobaros. * :doc:`/plugins/discogs`: Authenticate with the Discogs server. The plugin now requires a Discogs account due to new API restrictions. Thanks to :user:`multikatt`. :bug:`1027`, :bug:`1040` And countless little improvements and fixes: * Standard cover art in APEv2 metadata is now supported. Thanks to Matthias Kiefer. :bug:`1042` * :doc:`/plugins/convert`: Avoid a crash when embedding cover art fails. * :doc:`/plugins/mpdstats`: Fix an error on start (introduced in the previous version). Thanks to Zach Denton. * :doc:`/plugins/convert`: The ``--yes`` command-line flag no longer expects an argument. * :doc:`/plugins/play`: Remove the temporary .m3u file after sending it to the player. * The importer no longer tries to highlight partial differences in numeric quantities (track numbers and durations), which was often confusing. * Date-based queries that are malformed (not parse-able) no longer crash beets and instead fail silently. * :doc:`/plugins/duplicates`: Emit an error when the ``checksum`` config option is set incorrectly. * The migration from pre-1.1, non-YAML configuration files has been removed. If you need to upgrade an old config file, use an older version of beets temporarily. * :doc:`/plugins/discogs`: Recover from HTTP errors when communicating with the Discogs servers. Thanks to Dustin Rodriguez. * :doc:`/plugins/embedart`: Do not log "embedding album art into..." messages during the import process. * Fix a crash in the autotagger when files had only whitespace in their metadata. * :doc:`/plugins/play`: Fix a potential crash when the command outputs special characters. :bug:`1041` * :doc:`/plugins/web`: Queries typed into the search field are now treated as separate query components. :bug:`1045` * Date tags that use slashes instead of dashes as separators are now interpreted correctly. And WMA (ASF) files now map the ``comments`` field to the "Description" tag (in addition to "WM/Comments"). Thanks to Matthias Kiefer. :bug:`1043` * :doc:`/plugins/embedart`: Avoid resizing the image multiple times when embedding into an album. Thanks to :user:`kerobaros`. :bug:`1028`, :bug:`1036` * :doc:`/plugins/discogs`: Avoid a situation where a trailing comma could be appended to some artist names. :bug:`1049` * The output of the :ref:`stats-cmd` command is slightly different: the approximate size is now marked as such, and the total number of seconds only appears in exact mode. * :doc:`/plugins/convert`: A new ``copy_album_art`` option puts images alongside converted files. Thanks to Ăngel Alonso. :bug:`1050`, :bug:`1055` * There is no longer a "conflict" between two plugins that declare the same field with the same type. Thanks to Peter Schnebel. :bug:`1059` :bug:`1061` * :doc:`/plugins/chroma`: Limit the number of releases and recordings fetched as the result of an Acoustid match to avoid extremely long processing times for very popular music. :bug:`1068` * Fix an issue where modifying an album's field without actually changing it would not update the corresponding tracks to bring differing tracks back in line with the album. :bug:`856` * ``echonest`` plugin: When communicating with the Echo Nest servers fails repeatedly, log an error instead of exiting. :bug:`1096` * :doc:`/plugins/lyrics`: Avoid an error when the Google source returns a result without a title. Thanks to Alberto Leal. :bug:`1097` * Importing an archive will no longer leave temporary files behind in ``/tmp``. Thanks to :user:`multikatt`. :bug:`1067`, :bug:`1091` 1.3.8 (September 17, 2014) -------------------------- This release has two big new chunks of functionality. Queries now support **sorting** and user-defined fields can now have **types**. If you want to see all your songs in reverse chronological order, just type ``beet list year-``. It couldn't be easier. For details, see :ref:`query-sort`. Flexible field types mean that some functionality that has previously only worked for built-in fields, like range queries, can now work with plugin- and user-defined fields too. For starters, the ``echonest`` plugin and :doc:`/plugins/mpdstats` now mark the types of the fields they provide---so you can now say, for example, ``beet ls liveness:0.5..1.5`` for the Echo Nest "liveness" attribute. The :doc:`/plugins/types` makes it easy to specify field types in your config file. One upgrade note: if you use the :doc:`/plugins/discogs`, you will need to upgrade the Discogs client library to use this version. Just type ``pip install -U discogs-client``. Other new features: * :doc:`/plugins/info`: Target files can now be specified through library queries (in addition to filenames). The ``--library`` option prints library fields instead of tags. Multiple files can be summarized together with the new ``--summarize`` option. * :doc:`/plugins/mbcollection`: A new option lets you automatically update your collection on import. Thanks to Olin Gay. * :doc:`/plugins/convert`: A new ``never_convert_lossy_files`` option can prevent lossy transcoding. Thanks to Simon Kohlmeyer. * :doc:`/plugins/convert`: A new ``--yes`` command-line flag skips the confirmation. Still more fixes and little improvements: * Invalid state files don't crash the importer. * :doc:`/plugins/lyrics`: Only strip featured artists and parenthesized title suffixes if no lyrics for the original artist and title were found. * Fix a crash when reading some files with missing tags. * :doc:`/plugins/discogs`: Compatibility with the new 2.0 version of the `discogs_client`_ Python library. If you were using the old version, you will need to upgrade to the latest version of the library to use the correspondingly new version of the plugin (e.g., with ``pip install -U discogs-client``). Thanks to Andriy Kohut. * Fix a crash when writing files that can't be read. Thanks to Jocelyn De La Rosa. * The :ref:`stats-cmd` command now counts album artists. The album count also more accurately reflects the number of albums in the database. * :doc:`/plugins/convert`: Avoid crashes when tags cannot be written to newly converted files. * Formatting templates with item data no longer confusingly shows album-level data when the two are inconsistent. * Resuming imports and beginning incremental imports should now be much faster when there is a lot of previously-imported music to skip. * :doc:`/plugins/lyrics`: Remove ``<script>`` tags from scraped lyrics. Thanks to Bombardment. * :doc:`/plugins/play`: Add a ``relative_to`` config option. Thanks to BrainDamage. * Fix a crash when a MusicBrainz release has zero tracks. * The ``--version`` flag now works as an alias for the ``version`` command. * :doc:`/plugins/lastgenre`: Remove some unhelpful genres from the default whitelist. Thanks to gwern. * :doc:`/plugins/importfeeds`: A new ``echo`` output mode prints files' paths to standard error. Thanks to robotanarchy. * :doc:`/plugins/replaygain`: Restore some error handling when ``mp3gain`` output cannot be parsed. The verbose log now contains the bad tool output in this case. * :doc:`/plugins/convert`: Fix filename extensions when converting automatically. * The ``write`` plugin event allows plugins to change the tags that are written to a media file. * :doc:`/plugins/zero`: Do not delete database values; only media file tags are affected. .. _discogs_client: https://github.com/discogs/discogs_client 1.3.7 (August 22, 2014) ----------------------- This release of beets fixes all the bugs, and you can be confident that you will never again find any bugs in beets, ever. It also adds support for plain old AIFF files and adds three more plugins, including a nifty one that lets you measure a song's tempo by tapping out the beat on your keyboard. The importer deals more elegantly with duplicates and you can broaden your cover art search to the entire web with Google Image Search. The big new features are: * Support for AIFF files. Tags are stored as ID3 frames in one of the file's IFF chunks. Thanks to Evan Purkhiser for contributing support to `Mutagen`_. * The new :doc:`/plugins/importadded` reads files' modification times to set their "added" date. Thanks to Stig Inge Lea Bjørnsen. * The new :doc:`/plugins/bpm` lets you manually measure the tempo of a playing song. Thanks to aroquen. * The new :doc:`/plugins/spotify` generates playlists for your `Spotify`_ account. Thanks to Olin Gay. * A new :ref:`required` configuration option for the importer skips matches that are missing certain data. Thanks to oprietop. * When the importer detects duplicates, it now shows you some details about the potentially-replaced music so you can make an informed decision. Thanks to Howard Jones. * :doc:`/plugins/fetchart`: You can now optionally search for cover art on Google Image Search. Thanks to Lemutar. * A new :ref:`asciify-paths` configuration option replaces all non-ASCII characters in paths. .. _Mutagen: https://github.com/quodlibet/mutagen .. _Spotify: https://www.spotify.com/ And the multitude of little improvements and fixes: * Compatibility with the latest version of `Mutagen`_, 1.23. * :doc:`/plugins/web`: Lyrics now display readably with correct line breaks. Also, the detail view scrolls to reveal all of the lyrics. Thanks to Meet Udeshi. * :doc:`/plugins/play`: The ``command`` config option can now contain arguments (rather than just an executable). Thanks to Alessandro Ghedini. * Fix an error when using the :ref:`modify-cmd` command to remove a flexible attribute. Thanks to Pierre Rust. * :doc:`/plugins/info`: The command now shows audio properties (e.g., bitrate) in addition to metadata. Thanks Alessandro Ghedini. * Avoid a crash on Windows when writing to files with special characters in their names. * :doc:`/plugins/play`: Playing albums now generates filenames by default (as opposed to directories) for better compatibility. The ``use_folders`` option restores the old behavior. Thanks to Lucas Duailibe. * Fix an error when importing an empty directory with the ``--flat`` option. * :doc:`/plugins/mpdstats`: The last song in a playlist is now correctly counted as played. Thanks to Johann Klähn. * :doc:`/plugins/zero`: Prevent accidental nulling of dangerous fields (IDs and paths). Thanks to brunal. * The :ref:`remove-cmd` command now shows the paths of files that will be deleted. Thanks again to brunal. * Don't display changes for fields that are not in the restricted field set. This fixes :ref:`write-cmd` showing changes for fields that are not written to the file. * The :ref:`write-cmd` command avoids displaying the item name if there are no changes for it. * When using both the :doc:`/plugins/convert` and the :doc:`/plugins/scrub`, avoid scrubbing the source file of conversions. (Fix a regression introduced in the previous release.) * :doc:`/plugins/replaygain`: Logging is now quieter during import. Thanks to Yevgeny Bezman. * :doc:`/plugins/fetchart`: When loading art from the filesystem, we now prioritize covers with more keywords in them. This means that ``cover-front.jpg`` will now be taken before ``cover-back.jpg`` because it contains two keywords rather than one. Thanks to Fabrice Laporte. * :doc:`/plugins/lastgenre`: Remove duplicates from canonicalized genre lists. Thanks again to Fabrice Laporte. * The importer now records its progress when skipping albums. This means that incremental imports will no longer try to import albums again after you've chosen to skip them, and erroneous invitations to resume "interrupted" imports should be reduced. Thanks to jcassette. * :doc:`/plugins/bucket`: You can now customize the definition of alphanumeric "ranges" using regular expressions. And the heuristic for detecting years has been improved. Thanks to sotho. * Already-imported singleton tracks are skipped when resuming an import. * :doc:`/plugins/chroma`: A new ``auto`` configuration option disables fingerprinting on import. Thanks to ddettrittus. * :doc:`/plugins/convert`: A new ``--format`` option to can select the transcoding preset from the command-line. * :doc:`/plugins/convert`: Transcoding presets can now omit their filename extensions (extensions default to the name of the preset). * :doc:`/plugins/convert`: A new ``--pretend`` option lets you preview the commands the plugin will execute without actually taking any action. Thanks to Dietrich Daroch. * Fix a crash when a float-valued tag field only contained a ``+`` or ``-`` character. * Fixed a regression in the core that caused the :doc:`/plugins/scrub` not to work in ``auto`` mode. Thanks to Harry Khanna. * The :ref:`write-cmd` command now has a ``--force`` flag. Thanks again to Harry Khanna. * :doc:`/plugins/mbsync`: Track alignment now works with albums that have multiple copies of the same recording. Thanks to Rui Gonçalves. 1.3.6 (May 10, 2014) -------------------- This is primarily a bugfix release, but it also brings two new plugins: one for playing music in desktop players and another for organizing your directories into "buckets." It also brings huge performance optimizations to queries---your ``beet ls`` commands will now go much faster. New features: * The new :doc:`/plugins/play` lets you start your desktop music player with the songs that match a query. Thanks to David Hamp-Gonsalves. * The new :doc:`/plugins/bucket` provides a ``%bucket{}`` function for path formatting to generate folder names representing ranges of years or initial letter. Thanks to Fabrice Laporte. * Item and album queries are much faster. * :doc:`/plugins/ftintitle`: A new option lets you remove featured artists entirely instead of moving them to the title. Thanks to SUTJael. And those all-important bug fixes: * :doc:`/plugins/mbsync`: Fix a regression in 1.3.5 that broke the plugin entirely. * :ref:`Shell completion <completion>` now searches more common paths for its ``bash_completion`` dependency. * Fix encoding-related logging errors in :doc:`/plugins/convert` and :doc:`/plugins/replaygain`. * :doc:`/plugins/replaygain`: Suppress a deprecation warning emitted by later versions of PyGI. * Fix a crash when reading files whose iTunes SoundCheck tags contain non-ASCII characters. * The ``%if{}`` template function now appropriately interprets the condition as false when it contains the string "false". Thanks to Ayberk Yilmaz. * :doc:`/plugins/convert`: Fix conversion for files that include a video stream by ignoring it. Thanks to brunal. * :doc:`/plugins/fetchart`: Log an error instead of crashing when tag manipulation fails. * :doc:`/plugins/convert`: Log an error instead of crashing when embedding album art fails. * :doc:`/plugins/convert`: Embed cover art into converted files. Previously they were embedded into the source files. * New plugin event: `before_item_moved`. Thanks to Robert Speicher. 1.3.5 (April 15, 2014) ---------------------- This is a short-term release that adds some great new stuff to beets. There's support for tracking and calculating musical keys, the ReplayGain plugin was expanded to work with more music formats via GStreamer, we can now import directly from compressed archives, and the lyrics plugin is more robust. One note for upgraders and packagers: this version of beets has a new dependency in `enum34`_, which is a backport of the new `enum`_ standard library module. The major new features are: * Beets can now import `zip`, `tar`, and `rar` archives. Just type ``beet import music.zip`` to have beets transparently extract the files to import. * :doc:`/plugins/replaygain`: Added support for calculating ReplayGain values with GStreamer as well the mp3gain program. This enables ReplayGain calculation for any audio format. Thanks to Yevgeny Bezman. * :doc:`/plugins/lyrics`: Lyrics should now be found for more songs. Searching is now sensitive to featured artists and parenthesized title suffixes. When a song has multiple titles, lyrics from all the named songs are now concatenated. Thanks to Fabrice Laporte and Paul Phillips. In particular, a full complement of features for supporting musical keys are new in this release: * A new `initial_key` field is available in the database and files' tags. You can set the field manually using a command like ``beet modify initial_key=Am``. * The ``echonest`` plugin sets the `initial_key` field if the data is available. * A new :doc:`/plugins/keyfinder` runs a command-line tool to get the key from audio data and store it in the `initial_key` field. There are also many bug fixes and little enhancements: * ``echonest`` plugin: Truncate files larger than 50MB before uploading for analysis. * :doc:`/plugins/fetchart`: Fix a crash when the server does not specify a content type. Thanks to Lee Reinhardt. * :doc:`/plugins/convert`: The ``--keep-new`` flag now works correctly and the library includes the converted item. * The importer now logs a message instead of crashing when errors occur while opening the files to be imported. * :doc:`/plugins/embedart`: Better error messages in exceptional conditions. * Silenced some confusing error messages when searching for a non-MusicBrainz ID. Using an invalid ID (of any kind---Discogs IDs can be used there too) at the "Enter ID:" importer prompt now just silently returns no results. More info is in the verbose logs. * :doc:`/plugins/mbsync`: Fix application of album-level metadata. Due to a regression a few releases ago, only track-level metadata was being updated. * On Windows, paths on network shares (UNC paths) no longer cause "invalid filename" errors. * :doc:`/plugins/replaygain`: Fix crashes when attempting to log errors. * The :ref:`modify-cmd` command can now accept query arguments that contain = signs. An argument is considered a query part when a : appears before any =s. Thanks to mook. .. _enum34: https://pypi.python.org/pypi/enum34 .. _enum: https://docs.python.org/3.4/library/enum.html 1.3.4 (April 5, 2014) --------------------- This release brings a hodgepodge of medium-sized conveniences to beets. A new :ref:`config-cmd` command manages your configuration, we now have :ref:`bash completion <completion>`, and the :ref:`modify-cmd` command can delete attributes. There are also some significant performance optimizations to the autotagger's matching logic. One note for upgraders: if you use the :doc:`/plugins/fetchart`, it has a new dependency, the `requests`_ module. New stuff: * Added a :ref:`config-cmd` command to manage your configuration. It can show you what you currently have in your config file, point you at where the file should be, or launch your text editor to let you modify the file. Thanks to geigerzaehler. * Beets now ships with a shell command completion script! See :ref:`completion`. Thanks to geigerzaehler. * The :ref:`modify-cmd` command now allows removing flexible attributes. For example, ``beet modify artist:beatles oldies!`` deletes the ``oldies`` attribute from matching items. Thanks to brilnius. * Internally, beets has laid the groundwork for supporting multi-valued fields. Thanks to geigerzaehler. * The importer interface now shows the URL for MusicBrainz matches. Thanks to johtso. * :doc:`/plugins/smartplaylist`: Playlists can now be generated from multiple queries (combined with "or" logic). Album-level queries are also now possible and automatic playlist regeneration can now be disabled. Thanks to brilnius. * ``echonest`` plugin: Echo Nest similarity now weights the tempo in better proportion to other metrics. Also, options were added to specify custom thresholds and output formats. Thanks to Adam M. * Added the :ref:`after_write <plugin_events>` plugin event. * :doc:`/plugins/lastgenre`: Separator in genre lists can now be configured. Thanks to brilnius. * We now only use "primary" aliases for artist names from MusicBrainz. This eliminates some strange naming that could occur when the `languages` config option was set. Thanks to Filipe Fortes. * The performance of the autotagger's matching mechanism is vastly improved. This should be noticeable when matching against very large releases such as box sets. * The :ref:`import-cmd` command can now accept individual files as arguments even in non-singleton mode. Files are imported as one-track albums. Fixes: * Error messages involving paths no longer escape non-ASCII characters (for legibility). * Fixed a regression that made it impossible to use the :ref:`modify-cmd` command to add new flexible fields. Thanks to brilnius. * ``echonest`` plugin: Avoid crashing when the audio analysis fails. Thanks to Pedro Silva. * :doc:`/plugins/duplicates`: Fix checksumming command execution for files with quotation marks in their names. Thanks again to Pedro Silva. * Fix a crash when importing with both of the :ref:`group_albums` and :ref:`incremental` options enabled. Thanks to geigerzaehler. * Give a sensible error message when ``BEETSDIR`` points to a file. Thanks again to geigerzaehler. * Fix a crash when reading WMA files whose boolean-valued fields contain strings. Thanks to johtso. * :doc:`/plugins/fetchart`: The plugin now sends "beets" as the User-Agent when making scraping requests. This helps resolve some blocked requests. The plugin now also depends on the `requests`_ Python library. * The :ref:`write-cmd` command now only shows the changes to fields that will actually be written to a file. * :doc:`/plugins/duplicates`: Spurious reports are now avoided for tracks with missing values (e.g., no MBIDs). Thanks to Pedro Silva. * The default :ref:`replace` sanitation options now remove leading whitespace by default. Thanks to brilnius. * :doc:`/plugins/importfeeds`: Fix crash when importing albums containing ``/`` with the ``m3u_multi`` format. * Avoid crashing on Mutagen bugs while writing files' tags. * :doc:`/plugins/convert`: Display a useful error message when the FFmpeg executable can't be found. .. _requests: https://requests.readthedocs.io/en/master/ 1.3.3 (February 26, 2014) ------------------------- Version 1.3.3 brings a bunch changes to how item and album fields work internally. Along with laying the groundwork for some great things in the future, this brings a number of improvements to how you interact with beets. Here's what's new with fields in particular: * Plugin-provided fields can now be used in queries. For example, if you use the :doc:`/plugins/inline` to define a field called ``era``, you can now filter your library based on that field by typing something like ``beet list era:goldenage``. * Album-level flexible attributes and plugin-provided attributes can now be used in path formats (and other item-level templates). * :ref:`Date-based queries <datequery>` are now possible. Try getting every track you added in February 2014 with ``beet ls added:2014-02`` or in the whole decade with ``added:2010..``. Thanks to Stig Inge Lea Bjørnsen. * The :ref:`modify-cmd` command is now better at parsing and formatting fields. You can assign to boolean fields like ``comp``, for example, using either the words "true" or "false" or the numerals 1 and 0. Any boolean-esque value is normalized to a real boolean. The :ref:`update-cmd` and :ref:`write-cmd` commands also got smarter at formatting and colorizing changes. For developers, the short version of the story is that Item and Album objects provide *uniform access* across fixed, flexible, and computed attributes. You can write ``item.foo`` to access the ``foo`` field without worrying about where the data comes from. Unrelated new stuff: * The importer has a new interactive option (*G* for "Group albums"), command-line flag (``--group-albums``), and config option (:ref:`group_albums`) that lets you split apart albums that are mixed together in a single directory. Thanks to geigerzaehler. * A new ``--config`` command-line option lets you specify an additional configuration file. This option *combines* config settings with your default config file. (As part of this change, the ``BEETSDIR`` environment variable no longer combines---it *replaces* your default config file.) Thanks again to geigerzaehler. * :doc:`/plugins/ihate`: The plugin's configuration interface was overhauled. Its configuration is now much simpler---it uses beets queries instead of an ad-hoc per-field configuration. This is *backwards-incompatible*---if you use this plugin, you will need to update your configuration. Thanks to BrainDamage. Other little fixes: * ``echonest`` plugin: Tempo (BPM) is now always stored as an integer. Thanks to Heinz Wiesinger. * Fix Python 2.6 compatibility in some logging statements in :doc:`/plugins/chroma` and :doc:`/plugins/lastgenre`. * Prevent some crashes when things go really wrong when writing file metadata at the end of the import process. * New plugin events: ``item_removed`` (thanks to Romuald Conty) and ``item_copied`` (thanks to Stig Inge Lea Bjørnsen). * The ``pluginpath`` config option can now point to the directory containing plugin code. (Previously, it awkwardly needed to point at a directory containing a ``beetsplug`` directory, which would then contain your code. This is preserved as an option for backwards compatibility.) This change should also work around a long-standing issue when using ``pluginpath`` when beets is installed using pip. Many thanks to geigerzaehler. * :doc:`/plugins/web`: The ``/item/`` and ``/album/`` API endpoints now produce full details about albums and items, not just lists of IDs. Thanks to geigerzaehler. * Fix a potential crash when using image resizing with the :doc:`/plugins/fetchart` or :doc:`/plugins/embedart` without ImageMagick installed. * Also, when invoking ``convert`` for image resizing fails, we now log an error instead of crashing. * :doc:`/plugins/fetchart`: The ``beet fetchart`` command can now associate local images with albums (unless ``--force`` is provided). Thanks to brilnius. * :doc:`/plugins/fetchart`: Command output is now colorized. Thanks again to brilnius. * The :ref:`modify-cmd` command avoids writing files and committing to the database when nothing has changed. Thanks once more to brilnius. * The importer now uses the album artist field when guessing existing metadata for albums (rather than just the track artist field). Thanks to geigerzaehler. * :doc:`/plugins/fromfilename`: Fix a crash when a filename contained only a track number (e.g., ``02.mp3``). * :doc:`/plugins/convert`: Transcoding should now work on Windows. * :doc:`/plugins/duplicates`: The ``move`` and ``copy`` destination arguments are now treated as directories. Thanks to Pedro Silva. * The :ref:`modify-cmd` command now skips confirmation and prints a message if no changes are necessary. Thanks to brilnius. * :doc:`/plugins/fetchart`: When using the ``remote_priority`` config option, local image files are no longer completely ignored. * ``echonest`` plugin: Fix an issue causing the plugin to appear twice in the output of the ``beet version`` command. * :doc:`/plugins/lastgenre`: Fix an occasional crash when no tag weight was returned by Last.fm. * :doc:`/plugins/mpdstats`: Restore the ``last_played`` field. Thanks to Johann Klähn. * The :ref:`modify-cmd` command's output now clearly shows when a file has been deleted. * Album art in files with Vorbis Comments is now marked with the "front cover" type. Thanks to Jason Lefley. 1.3.2 (December 22, 2013) ------------------------- This update brings new plugins for fetching acoustic metrics and listening statistics, many more options for the duplicate detection plugin, and flexible options for fetching multiple genres. The "core" of beets gained a new built-in command: :ref:`beet write <write-cmd>` updates the metadata tags for files, bringing them back into sync with your database. Thanks to Heinz Wiesinger. We added some plugins and overhauled some existing ones: * The new ``echonest`` plugin plugin can fetch a wide range of `acoustic attributes`_ from `The Echo Nest`_, including the "speechiness" and "liveness" of each track. The new plugin supersedes an older version (``echonest_tempo``) that only fetched the BPM field. Thanks to Pedro Silva and Peter Schnebel. * The :doc:`/plugins/duplicates` got a number of new features, thanks to Pedro Silva: * The ``keys`` option lets you specify the fields used detect duplicates. * You can now use checksumming (via an external command) to find duplicates instead of metadata via the ``checksum`` option. * The plugin can perform actions on the duplicates it find. The new ``copy``, ``move``, ``delete``, ``delete_file``, and ``tag`` options perform those actions. * The new :doc:`/plugins/mpdstats` collects statistics about your listening habits from `MPD`_. Thanks to Peter Schnebel and Johann Klähn. * :doc:`/plugins/lastgenre`: The new ``multiple`` option has been replaced with the ``count`` option, which lets you limit the number of genres added to your music. (No more thousand-character genre fields!) Also, the ``min_weight`` field filters out nonsense tags to make your genres more relevant. Thanks to Peter Schnebel and rashley60. * :doc:`/plugins/lyrics`: A new ``--force`` option optionally re-downloads lyrics even when files already have them. Thanks to Bitdemon. As usual, there are also innumerable little fixes and improvements: * When writing ID3 tags for ReplayGain normalization, tags are written with both upper-case and lower-case TXXX frame descriptions. Previous versions of beets used only the upper-case style, which seems to be more standard, but some players (namely, Quod Libet and foobar2000) seem to only use lower-case names. * :doc:`/plugins/missing`: Avoid a possible error when an album's ``tracktotal`` field is missing. * :doc:`/plugins/ftintitle`: Fix an error when the sort artist is missing. * ``echonest_tempo``: The plugin should now match songs more reliably (i.e., fewer "no tempo found" messages). Thanks to Peter Schnebel. * :doc:`/plugins/convert`: Fix an "Item has no library" error when using the ``auto`` config option. * :doc:`/plugins/convert`: Fix an issue where files of the wrong format would have their transcoding skipped (and files with the right format would be needlessly transcoded). Thanks to Jakob Schnitzer. * Fix an issue that caused the :ref:`id3v23` option to work only occasionally. * Also fix using :ref:`id3v23` in conjunction with the ``scrub`` and ``embedart`` plugins. Thanks to Chris Cogburn. * :doc:`/plugins/ihate`: Fix an error when importing singletons. Thanks to Mathijs de Bruin. * The :ref:`clutter` option can now be a whitespace-separated list in addition to a YAML list. * Values for the :ref:`replace` option can now be empty (i.e., null is equivalent to the empty string). * :doc:`/plugins/lastgenre`: Fix a conflict between canonicalization and multiple genres. * When a match has a year but not a month or day, the autotagger now "zeros out" the month and day fields after applying the year. * For plugin developers: added an ``optparse`` callback utility function for performing actions based on arguments. Thanks to Pedro Silva. * :doc:`/plugins/scrub`: Fix scrubbing of MPEG-4 files. Thanks to Yevgeny Bezman. .. _Acoustic Attributes: https://web.archive.org/web/20160701063109/http://developer.echonest.com/acoustic-attributes.html .. _MPD: https://www.musicpd.org/ 1.3.1 (October 12, 2013) ------------------------ This release boasts a host of new little features, many of them contributed by beets' amazing and prolific community. It adds support for `Opus`_ files, transcoding to any format, and two new plugins: one that guesses metadata for "blank" files based on their filenames and one that moves featured artists into the title field. Here's the new stuff: * Add `Opus`_ audio support. Thanks to Rowan Lewis. * :doc:`/plugins/convert`: You can now transcode files to any audio format, rather than just MP3. Thanks again to Rowan Lewis. * The new :doc:`/plugins/fromfilename` guesses tags from the filenames during import when metadata tags themselves are missing. Thanks to Jan-Erik Dahlin. * The :doc:`/plugins/ftintitle`, by `@Verrus`_, is now distributed with beets. It helps you rewrite tags to move "featured" artists from the artist field to the title field. * The MusicBrainz data source now uses track artists over recording artists. This leads to better metadata when tagging classical music. Thanks to Henrique Ferreiro. * :doc:`/plugins/lastgenre`: You can now get multiple genres per album or track using the ``multiple`` config option. Thanks to rashley60 on GitHub. * A new :ref:`id3v23` config option makes beets write MP3 files' tags using the older ID3v2.3 metadata standard. Use this if you want your tags to be visible to Windows and some older players. And some fixes: * :doc:`/plugins/fetchart`: Better error message when the image file has an unrecognized type. * :doc:`/plugins/mbcollection`: Detect, log, and skip invalid MusicBrainz IDs (instead of failing with an API error). * :doc:`/plugins/info`: Fail gracefully when used erroneously with a directory. * ``echonest_tempo``: Fix an issue where the plugin could use the tempo from the wrong song when the API did not contain the requested song. * Fix a crash when a file's metadata included a very large number (one wider than 64 bits). These huge numbers are now replaced with zeroes in the database. * When a track on a MusicBrainz release has a different length from the underlying recording's length, the track length is now used instead. * With :ref:`per_disc_numbering` enabled, the ``tracktotal`` field is now set correctly (i.e., to the number of tracks on the disc). * :doc:`/plugins/scrub`: The ``scrub`` command now restores album art in addition to other (database-backed) tags. * :doc:`/plugins/mpdupdate`: Domain sockets can now begin with a tilde (which is correctly expanded to ``$HOME``) as well as a slash. Thanks to Johann Klähn. * :doc:`/plugins/lastgenre`: Fix a regression that could cause new genres found during import not to be persisted. * Fixed a crash when imported album art was also marked as "clutter" where the art would be deleted before it could be moved into place. This led to a "image.jpg not found during copy" error. Now clutter is removed (and directories pruned) much later in the process, after the ``import_task_files`` hook. * :doc:`/plugins/missing`: Fix an error when printing missing track names. Thanks to Pedro Silva. * Fix an occasional KeyError in the :ref:`update-cmd` command introduced in 1.3.0. * :doc:`/plugins/scrub`: Avoid preserving certain non-standard ID3 tags such as NCON. .. _Opus: https://www.opus-codec.org/ .. _@Verrus: https://github.com/Verrus 1.3.0 (September 11, 2013) -------------------------- Albums and items now have **flexible attributes**. This means that, when you want to store information about your music in the beets database, you're no longer constrained to the set of fields it supports out of the box (title, artist, track, etc.). Instead, you can use any field name you can think of and treat it just like the built-in fields. For example, you can use the :ref:`modify-cmd` command to set a new field on a track:: $ beet modify mood=sexy artist:miguel and then query your music based on that field:: $ beet ls mood:sunny or use templates to see the value of the field:: $ beet ls -f '$title: $mood' While this feature is nifty when used directly with the usual command-line suspects, it's especially useful for plugin authors and for future beets features. Stay tuned for great things built on this flexible attribute infrastructure. One side effect of this change: queries that include unknown fields will now match *nothing* instead of *everything*. So if you type ``beet ls fieldThatDoesNotExist:foo``, beets will now return no results, whereas previous versions would spit out a warning and then list your entire library. There's more detail than you could ever need `on the beets blog`_. .. _on the beets blog: https://beets.io/blog/flexattr.html 1.2.2 (August 27, 2013) ----------------------- This is a bugfix release. We're in the midst of preparing for a large change in beets 1.3, so 1.2.2 resolves some issues that came up over the last few weeks. Stay tuned! The improvements in this release are: * A new plugin event, ``item_moved``, is sent when files are moved on disk. Thanks to dsedivec. * :doc:`/plugins/lyrics`: More improvements to the Google backend by Fabrice Laporte. * :doc:`/plugins/bpd`: Fix for a crash when searching, thanks to Simon Chopin. * Regular expression queries (and other query types) over paths now work. (Previously, special query types were ignored for the ``path`` field.) * :doc:`/plugins/fetchart`: Look for images in the Cover Art Archive for the release group in addition to the specific release. Thanks to Filipe Fortes. * Fix a race in the importer that could cause files to be deleted before they were imported. This happened when importing one album, importing a duplicate album, and then asking for the first album to be replaced with the second. The situation could only arise when importing music from the library directory and when the two albums are imported close in time. 1.2.1 (June 22, 2013) --------------------- This release introduces a major internal change in the way that similarity scores are handled. It means that the importer interface can now show you exactly why a match is assigned its score and that the autotagger gained a few new options that let you customize how matches are prioritized and recommended. The refactoring work is due to the continued efforts of Tai Lee. The changes you'll notice while using the autotagger are: * The top 3 distance penalties are now displayed on the release listing, and all album and track penalties are now displayed on the track changes list. This should make it clear exactly which metadata is contributing to a low similarity score. * When displaying differences, the colorization has been made more consistent and helpful: red for an actual difference, yellow to indicate that a distance penalty is being applied, and light gray for no penalty (e.g., case changes) or disambiguation data. There are also three new (or overhauled) configuration options that let you customize the way that matches are selected: * The :ref:`ignored` setting lets you instruct the importer not to show you matches that have a certain penalty applied. * The :ref:`preferred` collection of settings specifies a sorted list of preferred countries and media types, or prioritizes releases closest to the original year for an album. * The :ref:`max_rec` settings can now be used for any distance penalty component. The recommendation will be downgraded if a non-zero penalty is being applied to the specified field. And some little enhancements and bug fixes: * Multi-disc directory names can now contain "disk" (in addition to "disc"). Thanks to John Hawthorn. * :doc:`/plugins/web`: Item and album counts are now exposed through the API for use with the Tomahawk resolver. Thanks to Uwe L. Korn. * Python 2.6 compatibility for ``beatport``, :doc:`/plugins/missing`, and :doc:`/plugins/duplicates`. Thanks to Wesley Bitter and Pedro Silva. * Don't move the config file during a null migration. Thanks to Theofilos Intzoglou. * Fix an occasional crash in the ``beatport`` when a length field was missing from the API response. Thanks to Timothy Appnel. * :doc:`/plugins/scrub`: Handle and log I/O errors. * :doc:`/plugins/lyrics`: The Google backend should now turn up more results. Thanks to Fabrice Laporte. * :doc:`/plugins/random`: Fix compatibility with Python 2.6. Thanks to Matthias Drochner. 1.2.0 (June 5, 2013) -------------------- There's a *lot* of new stuff in this release: new data sources for the autotagger, new plugins to look for problems in your library, tracking the date that you acquired new music, an awesome new syntax for doing queries over numeric fields, support for ALAC files, and major enhancements to the importer's UI and distance calculations. A special thanks goes out to all the contributors who helped make this release awesome. For the first time, beets can now tag your music using additional **data sources** to augment the matches from MusicBrainz. When you enable either of these plugins, the importer will start showing you new kinds of matches: * New :doc:`/plugins/discogs`: Get matches from the `Discogs`_ database. Thanks to Artem Ponomarenko and Tai Lee. * New ``beatport`` plugin: Get matches from the `Beatport`_ database. Thanks to Johannes Baiter. We also have two other new plugins that can scan your library to check for common problems, both by Pedro Silva: * New :doc:`/plugins/duplicates`: Find tracks or albums in your library that are **duplicated**. * New :doc:`/plugins/missing`: Find albums in your library that are **missing tracks**. There are also three more big features added to beets core: * Your library now keeps track of **when music was added** to it. The new ``added`` field is a timestamp reflecting when each item and album was imported and the new ``%time{}`` template function lets you format this timestamp for humans. Thanks to Lucas Duailibe. * When using queries to match on quantitative fields, you can now use **numeric ranges**. For example, you can get a list of albums from the '90s by typing ``beet ls year:1990..1999`` or find high-bitrate music with ``bitrate:128000..``. See :ref:`numericquery`. Thanks to Michael Schuerig. * **ALAC files** are now marked as ALAC instead of being conflated with AAC audio. Thanks to Simon Luijk. In addition, the importer saw various UI enhancements, thanks to Tai Lee: * More consistent format and colorization of album and track metadata. * Display data source URL for matches from the new data source plugins. This should make it easier to migrate data from Discogs or Beatport into MusicBrainz. * Display album disambiguation and disc titles in the track listing, when available. * Track changes are highlighted in yellow when they indicate a change in format to or from the style of :ref:`per_disc_numbering`. (As before, no penalty is applied because the track number is still "correct", just in a different format.) * Sort missing and unmatched tracks by index and title and group them together for better readability. * Indicate MusicBrainz ID mismatches. The calculation of the similarity score for autotagger matches was also improved, again thanks to Tai Lee. These changes, in general, help deal with the new metadata sources and help disambiguate between similar releases in the same MusicBrainz release group: * Strongly prefer releases with a matching MusicBrainz album ID. This helps beets re-identify the same release when re-importing existing files. * Prefer releases that are closest to the tagged ``year``. Tolerate files tagged with release or original year. * The new ``preferred_media`` config option lets you prefer a certain media type when the ``media`` field is unset on an album. * Apply minor penalties across a range of fields to differentiate between nearly identical releases: ``disctotal``, ``label``, ``catalognum``, ``country`` and ``albumdisambig``. As usual, there were also lots of other great littler enhancements: * :doc:`/plugins/random`: A new ``-e`` option gives an equal chance to each artist in your collection to avoid biasing random samples to prolific artists. Thanks to Georges Dubus. * The :ref:`modify-cmd` now correctly converts types when modifying non-string fields. You can now safely modify the "comp" flag and the "year" field, for example. Thanks to Lucas Duailibe. * :doc:`/plugins/convert`: You can now configure the path formats for converted files separately from your main library. Thanks again to Lucas Duailibe. * The importer output now shows the number of audio files in each album. Thanks to jayme on GitHub. * Plugins can now provide fields for both Album and Item templates, thanks to Pedro Silva. Accordingly, the :doc:`/plugins/inline` can also now define album fields. For consistency, the ``pathfields`` configuration section has been renamed ``item_fields`` (although the old name will still work for compatibility). * Plugins can also provide metadata matches for ID searches. For example, the new Discogs plugin lets you search for an album by its Discogs ID from the same prompt that previously just accepted MusicBrainz IDs. Thanks to Johannes Baiter. * The :ref:`fields-cmd` command shows template fields provided by plugins. Thanks again to Pedro Silva. * :doc:`/plugins/mpdupdate`: You can now communicate with MPD over a Unix domain socket. Thanks to John Hawthorn. And a batch of fixes: * Album art filenames now respect the :ref:`replace` configuration. * Friendly error messages are now printed when trying to read or write files that go missing. * The :ref:`modify-cmd` command can now change albums' album art paths (i.e., ``beet modify artpath=...`` works). Thanks to Lucas Duailibe. * :doc:`/plugins/zero`: Fix a crash when nulling out a field that contains None. * Templates can now refer to non-tag item fields (e.g., ``$id`` and ``$album_id``). * :doc:`/plugins/lyrics`: Lyrics searches should now turn up more results due to some fixes in dealing with special characters. .. _Discogs: https://discogs.com/ .. _Beatport: https://www.beatport.com/ 1.1.0 (April 29, 2013) ---------------------- This final release of 1.1 brings a little polish to the betas that introduced the new configuration system. The album art and lyrics plugins also got a little love. If you're upgrading from 1.0.0 or earlier, this release (like the 1.1 betas) will automatically migrate your configuration to the new system. * :doc:`/plugins/embedart`: The ``embedart`` command now embeds each album's associated art by default. The ``--file`` option invokes the old behavior, in which a specific image file is used. * :doc:`/plugins/lyrics`: A new (optional) Google Custom Search backend was added for finding lyrics on a wide array of sites. Thanks to Fabrice Laporte. * When automatically detecting the filesystem's maximum filename length, never guess more than 200 characters. This prevents errors on systems where the maximum length was misreported. You can, of course, override this default with the :ref:`max_filename_length` option. * :doc:`/plugins/fetchart`: Two new configuration options were added: ``cover_names``, the list of keywords used to identify preferred images, and ``cautious``, which lets you avoid falling back to images that don't contain those keywords. Thanks to Fabrice Laporte. * Avoid some error cases in the ``update`` command and the ``embedart`` and ``mbsync`` plugins. Invalid or missing files now cause error logs instead of crashing beets. Thanks to Lucas Duailibe. * :doc:`/plugins/lyrics`: Searches now strip "featuring" artists when searching for lyrics, which should increase the hit rate for these tracks. Thanks to Fabrice Laporte. * When listing the items in an album, the items are now always in track-number order. This should lead to more predictable listings from the :doc:`/plugins/importfeeds`. * :doc:`/plugins/smartplaylist`: Queries are now split using shell-like syntax instead of just whitespace, so you can now construct terms that contain spaces. * :doc:`/plugins/lastgenre`: The ``force`` config option now defaults to true and controls the behavior of the import hook. (Previously, new genres were always forced during import.) * :doc:`/plugins/web`: Fix an error when specifying the hostname on the command line. * :doc:`/plugins/web`: The underlying API was expanded slightly to support `Tomahawk`_ collections. And file transfers now have a "Content-Length" header. Thanks to Uwe L. Korn. * :doc:`/plugins/lastgenre`: Fix an error when using genre canonicalization. .. _Tomahawk: https://github.com/tomahawk-player/tomahawk 1.1b3 (March 16, 2013) ---------------------- This third beta of beets 1.1 brings a hodgepodge of little new features (and internal overhauls that will make improvements easier in the future). There are new options for getting metadata in a particular language and seeing more detail during the import process. There's also a new plugin for synchronizing your metadata with MusicBrainz. Under the hood, plugins can now extend the query syntax. New configuration options: * :ref:`languages` controls the preferred languages when selecting an alias from MusicBrainz. This feature requires `python-musicbrainzngs`_ 0.3 or later. Thanks to Sam Doshi. * :ref:`detail` enables a mode where all tracks are listed in the importer UI, as opposed to only changed tracks. * The ``--flat`` option to the ``beet import`` command treats an entire directory tree of music files as a single album. This can help in situations where a multi-disc album is split across multiple directories. * :doc:`/plugins/importfeeds`: An option was added to use absolute, rather than relative, paths. Thanks to Lucas Duailibe. Other stuff: * A new :doc:`/plugins/mbsync` provides a command that looks up each item and track in MusicBrainz and updates your library to reflect it. This can help you easily correct errors that have been fixed in the MB database. Thanks to Jakob Schnitzer. * :doc:`/plugins/fuzzy`: The ``fuzzy`` command was removed and replaced with a new query type. To perform fuzzy searches, use the ``~`` prefix with :ref:`list-cmd` or other commands. Thanks to Philippe Mongeau. * As part of the above, plugins can now extend the query syntax and new kinds of matching capabilities to beets. See :ref:`extend-query`. Thanks again to Philippe Mongeau. * :doc:`/plugins/convert`: A new ``--keep-new`` option lets you store transcoded files in your library while backing up the originals (instead of vice-versa). Thanks to Lucas Duailibe. * :doc:`/plugins/convert`: Also, a new ``auto`` config option will transcode audio files automatically during import. Thanks again to Lucas Duailibe. * :doc:`/plugins/chroma`: A new ``fingerprint`` command lets you generate and store fingerprints for items that don't yet have them. One more round of applause for Lucas Duailibe. * ``echonest_tempo``: API errors now issue a warning instead of exiting with an exception. We also avoid an error when track metadata contains newlines. * When the importer encounters an error (insufficient permissions, for example) when walking a directory tree, it now logs an error instead of crashing. * In path formats, null database values now expand to the empty string instead of the string "None". * Add "System Volume Information" (an internal directory found on some Windows filesystems) to the default ignore list. * Fix a crash when ReplayGain values were set to null. * Fix a crash when iTunes Sound Check tags contained invalid data. * Fix an error when the configuration file (``config.yaml``) is completely empty. * Fix an error introduced in 1.1b1 when importing using timid mode. Thanks to Sam Doshi. * :doc:`/plugins/convert`: Fix a bug when creating files with Unicode pathnames. * Fix a spurious warning from the Unidecode module when matching albums that are missing all metadata. * Fix Unicode errors when a directory or file doesn't exist when invoking the import command. Thanks to Lucas Duailibe. * :doc:`/plugins/mbcollection`: Show friendly, human-readable errors when MusicBrainz exceptions occur. * ``echonest_tempo``: Catch socket errors that are not handled by the Echo Nest library. * :doc:`/plugins/chroma`: Catch Acoustid Web service errors when submitting fingerprints. 1.1b2 (February 16, 2013) ------------------------- The second beta of beets 1.1 uses the fancy new configuration infrastructure to add many, many new config options. The import process is more flexible; filenames can be customized in more detail; and more. This release also supports Windows Media (ASF) files and iTunes Sound Check volume normalization. This version introduces one **change to the default behavior** that you should be aware of. Previously, when importing new albums matched in MusicBrainz, the date fields (``year``, ``month``, and ``day``) would be set to the release date of the *original* version of the album, as opposed to the specific date of the release selected. Now, these fields reflect the specific release and ``original_year``, etc., reflect the earlier release date. If you want the old behavior, just set :ref:`original_date` to true in your config file. New configuration options: * :ref:`default_action` lets you determine the default (just-hit-return) option is when considering a candidate. * :ref:`none_rec_action` lets you skip the prompt, and automatically choose an action, when there is no good candidate. Thanks to Tai Lee. * :ref:`max_rec` lets you define a maximum recommendation for albums with missing/extra tracks or differing track lengths/numbers. Thanks again to Tai Lee. * :ref:`original_date` determines whether, when importing new albums, the ``year``, ``month``, and ``day`` fields should reflect the specific (e.g., reissue) release date or the original release date. Note that the original release date is always available as ``original_year``, etc. * :ref:`clutter` controls which files should be ignored when cleaning up empty directories. Thanks to Steinþór Pálsson. * :doc:`/plugins/lastgenre`: A new configuration option lets you choose to retrieve artist-level tags as genres instead of album- or track-level tags. Thanks to Peter Fern and Peter Schnebel. * :ref:`max_filename_length` controls truncation of long filenames. Also, beets now tries to determine the filesystem's maximum length automatically if you leave this option unset. * :doc:`/plugins/fetchart`: The ``remote_priority`` option searches remote (Web) art sources even when local art is present. * You can now customize the character substituted for path separators (e.g., /) in filenames via ``path_sep_replace``. The default is an underscore. Use this setting with caution. Other new stuff: * Support for Windows Media/ASF audio files. Thanks to Dave Hayes. * New :doc:`/plugins/smartplaylist`: generate and maintain m3u playlist files based on beets queries. Thanks to Dang Mai Hai. * ReplayGain tags on MPEG-4/AAC files are now supported. And, even more astonishingly, ReplayGain values in MP3 and AAC files are now compatible with `iTunes Sound Check`_. Thanks to Dave Hayes. * Track titles in the importer UI's difference display are now either aligned vertically or broken across two lines for readability. Thanks to Tai Lee. * Albums and items have new fields reflecting the *original* release date (``original_year``, ``original_month``, and ``original_day``). Previously, when tagging from MusicBrainz, *only* the original date was stored; now, the old fields refer to the *specific* release date (e.g., when the album was reissued). * Some changes to the way candidates are recommended for selection, thanks to Tai Lee: * According to the new :ref:`max_rec` configuration option, partial album matches are downgraded to a "low" recommendation by default. * When a match isn't great but is either better than all the others or the only match, it is given a "low" (rather than "medium") recommendation. * There is no prompt default (i.e., input is required) when matches are bad: "low" or "none" recommendations or when choosing a candidate other than the first. * The importer's heuristic for coalescing the directories in a multi-disc album has been improved. It can now detect when two directories alongside each other share a similar prefix but a different number (e.g., "Album Disc 1" and "Album Disc 2") even when they are not alone in a common parent directory. Thanks once again to Tai Lee. * Album listings in the importer UI now show the release medium (CD, Vinyl, 3xCD, etc.) as well as the disambiguation string. Thanks to Peter Schnebel. * :doc:`/plugins/lastgenre`: The plugin can now get different genres for individual tracks on an album. Thanks to Peter Schnebel. * When getting data from MusicBrainz, the album disambiguation string (``albumdisambig``) now reflects both the release and the release group. * :doc:`/plugins/mpdupdate`: Sends an update message whenever *anything* in the database changes---not just when importing. Thanks to Dang Mai Hai. * When the importer UI shows a difference in track numbers or durations, they are now colorized based on the *suffixes* that differ. For example, when showing the difference between 2:01 and 2:09, only the last digit will be highlighted. * The importer UI no longer shows a change when the track length difference is less than 10 seconds. (This threshold was previously 2 seconds.) * Two new plugin events were added: *database_change* and *cli_exit*. Thanks again to Dang Mai Hai. * Plugins are now loaded in the order they appear in the config file. Thanks to Dang Mai Hai. * :doc:`/plugins/bpd`: Browse by album artist and album artist sort name. Thanks to Steinþór Pálsson. * ``echonest_tempo``: Don't attempt a lookup when the artist or track title is missing. * Fix an error when migrating the ``.beetsstate`` file on Windows. * A nicer error message is now given when the configuration file contains tabs. (YAML doesn't like tabs.) * Fix the ``-l`` (log path) command-line option for the ``import`` command. .. _iTunes Sound Check: https://support.apple.com/kb/HT2425 1.1b1 (January 29, 2013) ------------------------ This release entirely revamps beets' configuration system. The configuration file is now a `YAML`_ document and is located, along with other support files, in a common directory (e.g., ``~/.config/beets`` on Unix-like systems). .. _YAML: https://en.wikipedia.org/wiki/YAML * Renamed plugins: The ``rdm`` plugin has been renamed to ``random`` and ``fuzzy_search`` has been renamed to ``fuzzy``. * Renamed config options: Many plugins have a flag dictating whether their action runs at import time. This option had many names (``autofetch``, ``autoembed``, etc.) but is now consistently called ``auto``. * Reorganized import config options: The various ``import_*`` options are now organized under an ``import:`` heading and their prefixes have been removed. * New default file locations: The default filename of the library database is now ``library.db`` in the same directory as the config file, as opposed to ``~/.beetsmusic.blb`` previously. Similarly, the runtime state file is now called ``state.pickle`` in the same directory instead of ``~/.beetsstate``. It also adds some new features: * :doc:`/plugins/inline`: Inline definitions can now contain statements or blocks in addition to just expressions. Thanks to Florent Thoumie. * Add a configuration option, :ref:`terminal_encoding`, controlling the text encoding used to print messages to standard output. * The MusicBrainz hostname (and rate limiting) are now configurable. See :ref:`musicbrainz-config`. * You can now configure the similarity thresholds used to determine when the autotagger automatically accepts a metadata match. See :ref:`match-config`. * :doc:`/plugins/importfeeds`: Added a new configuration option that controls the base for relative paths used in m3u files. Thanks to Philippe Mongeau. 1.0.0 (January 29, 2013) ------------------------ After fifteen betas and two release candidates, beets has finally hit one-point-oh. Congratulations to everybody involved. This version of beets will remain stable and receive only bug fixes from here on out. New development is ongoing in the betas of version 1.1. * :doc:`/plugins/scrub`: Fix an incompatibility with Python 2.6. * :doc:`/plugins/lyrics`: Fix an issue that failed to find lyrics when metadata contained "real" apostrophes. * :doc:`/plugins/replaygain`: On Windows, emit a warning instead of crashing when analyzing non-ASCII filenames. * Silence a spurious warning from version 0.04.12 of the Unidecode module. 1.0rc2 (December 31, 2012) -------------------------- This second release candidate follows quickly after rc1 and fixes a few small bugs found since that release. There were a couple of regressions and some bugs in a newly added plugin. * ``echonest_tempo``: If the Echo Nest API limit is exceeded or a communication error occurs, the plugin now waits and tries again instead of crashing. Thanks to Zach Denton. * :doc:`/plugins/fetchart`: Fix a regression that caused crashes when art was not available from some sources. * Fix a regression on Windows that caused all relative paths to be "not found". 1.0rc1 (December 17, 2012) -------------------------- The first release candidate for beets 1.0 includes a deluge of new features contributed by beets users. The vast majority of the credit for this release goes to the growing and vibrant beets community. A million thanks to everybody who contributed to this release. There are new plugins for transcoding music, fuzzy searches, tempo collection, and fiddling with metadata. The ReplayGain plugin has been rebuilt from scratch. Album art images can now be resized automatically. Many other smaller refinements make things "just work" as smoothly as possible. With this release candidate, beets 1.0 is feature-complete. We'll be fixing bugs on the road to 1.0 but no new features will be added. Concurrently, work begins today on features for version 1.1. * New plugin: :doc:`/plugins/convert` **transcodes** music and embeds album art while copying to a separate directory. Thanks to Jakob Schnitzer and Andrew G. Dunn. * New plugin: :doc:`/plugins/fuzzy` lets you find albums and tracks using **fuzzy string matching** so you don't have to type (or even remember) their exact names. Thanks to Philippe Mongeau. * New plugin: ``echonest_tempo`` fetches **tempo** (BPM) information from `The Echo Nest`_. Thanks to David Brenner. * New plugin: :doc:`/plugins/the` adds a template function that helps format text for nicely-sorted directory listings. Thanks to Blemjhoo Tezoulbr. * New plugin: :doc:`/plugins/zero` **filters out undesirable fields** before they are written to your tags. Thanks again to Blemjhoo Tezoulbr. * New plugin: :doc:`/plugins/ihate` automatically skips (or warns you about) importing albums that match certain criteria. Thanks once again to Blemjhoo Tezoulbr. * :doc:`/plugins/replaygain`: This plugin has been completely overhauled to use the `mp3gain`_ or `aacgain`_ command-line tools instead of the failure-prone Gstreamer ReplayGain implementation. Thanks to Fabrice Laporte. * :doc:`/plugins/fetchart` and :doc:`/plugins/embedart`: Both plugins can now **resize album art** to avoid excessively large images. Use the ``maxwidth`` config option with either plugin. Thanks to Fabrice Laporte. * :doc:`/plugins/scrub`: Scrubbing now removes *all* types of tags from a file rather than just one. For example, if your FLAC file has both ordinary FLAC tags and ID3 tags, the ID3 tags are now also removed. * :ref:`stats-cmd` command: New ``--exact`` switch to make the file size calculation more accurate (thanks to Jakob Schnitzer). * :ref:`list-cmd` command: Templates given with ``-f`` can now show items' and albums' paths (using ``$path``). * The output of the :ref:`update-cmd`, :ref:`remove-cmd`, and :ref:`modify-cmd` commands now respects the :ref:`list_format_album` and :ref:`list_format_item` config options. Thanks to Mike Kazantsev. * The :ref:`art-filename` option can now be a template rather than a simple string. Thanks to Jarrod Beardwood. * Fix album queries for ``artpath`` and other non-item fields. * Null values in the database can now be matched with the empty-string regular expression, ``^$``. * Queries now correctly match non-string values in path format predicates. * When autotagging a various-artists album, the album artist field is now used instead of the majority track artist. * :doc:`/plugins/lastgenre`: Use the albums' existing genre tags if they pass the whitelist (thanks to Fabrice Laporte). * :doc:`/plugins/lastgenre`: Add a ``lastgenre`` command for fetching genres post facto (thanks to Jakob Schnitzer). * :doc:`/plugins/fetchart`: Local image filenames are now used in alphabetical order. * :doc:`/plugins/fetchart`: Fix a bug where cover art filenames could lack a ``.jpg`` extension. * :doc:`/plugins/lyrics`: Fix an exception with non-ASCII lyrics. * :doc:`/plugins/web`: The API now reports file sizes (for use with the `Tomahawk resolver`_). * :doc:`/plugins/web`: Files now download with a reasonable filename rather than just being called "file" (thanks to Zach Denton). * :doc:`/plugins/importfeeds`: Fix error in symlink mode with non-ASCII filenames. * :doc:`/plugins/mbcollection`: Fix an error when submitting a large number of releases (we now submit only 200 releases at a time instead of 350). Thanks to Jonathan Towne. * :doc:`/plugins/embedart`: Made the method for embedding art into FLAC files `standard <https://wiki.xiph.org/VorbisComment#METADATA_BLOCK_PICTURE>`_-compliant. Thanks to Daniele Sluijters. * Add the track mapping dictionary to the ``album_distance`` plugin function. * When an exception is raised while reading a file, the path of the file in question is now logged (thanks to Mike Kazantsev). * Truncate long filenames based on their *bytes* rather than their Unicode *characters*, fixing situations where encoded names could be too long. * Filename truncation now incorporates the length of the extension. * Fix an assertion failure when the MusicBrainz main database and search server disagree. * Fix a bug that caused the :doc:`/plugins/lastgenre` and other plugins not to modify files' tags even when they successfully change the database. * Fix a VFS bug leading to a crash in the :doc:`/plugins/bpd` when files had non-ASCII extensions. * Fix for changing date fields (like "year") with the :ref:`modify-cmd` command. * Fix a crash when input is read from a pipe without a specified encoding. * Fix some problem with identifying files on Windows with Unicode directory names in their path. * Fix a crash when Unicode queries were used with ``import -L`` re-imports. * Fix an error when fingerprinting files with Unicode filenames on Windows. * Warn instead of crashing when importing a specific file in singleton mode. * Add human-readable error messages when writing files' tags fails or when a directory can't be created. * Changed plugin loading so that modules can be imported without unintentionally loading the plugins they contain. .. _The Echo Nest: https://web.archive.org/web/20180329103558/http://the.echonest.com/ .. _Tomahawk resolver: https://beets.io/blog/tomahawk-resolver.html .. _mp3gain: http://mp3gain.sourceforge.net/download.php .. _aacgain: https://aacgain.altosdesign.com 1.0b15 (July 26, 2012) ---------------------- The fifteenth (!) beta of beets is compendium of small fixes and features, most of which represent long-standing requests. The improvements include matching albums with extra tracks, per-disc track numbering in multi-disc albums, an overhaul of the album art downloader, and robustness enhancements that should keep beets running even when things go wrong. All these smaller changes should help us focus on some larger changes coming before 1.0. Please note that this release contains one backwards-incompatible change: album art fetching, which was previously baked into the import workflow, is now encapsulated in a plugin (the :doc:`/plugins/fetchart`). If you want to continue fetching cover art for your music, enable this plugin after upgrading to beets 1.0b15. * The autotagger can now find matches for albums when you have **extra tracks** on your filesystem that aren't present in the MusicBrainz catalog. Previously, if you tried to match album with 15 audio files but the MusicBrainz entry had only 14 tracks, beets would ignore this match. Now, beets will show you matches even when they are "too short" and indicate which tracks from your disk are unmatched. * Tracks on multi-disc albums can now be **numbered per-disc** instead of per-album via the :ref:`per_disc_numbering` config option. * The default output format for the ``beet list`` command is now configurable via the :ref:`list_format_item` and :ref:`list_format_album` config options. Thanks to Fabrice Laporte. * Album **cover art fetching** is now encapsulated in the :doc:`/plugins/fetchart`. Be sure to enable this plugin if you're using this functionality. As a result of this new organization, the new plugin has gained a few new features: * "As-is" and non-autotagged imports can now have album art imported from the local filesystem (although Web repositories are still not searched in these cases). * A new command, ``beet fetchart``, allows you to download album art post-import. If you only want to fetch art manually, not automatically during import, set the new plugin's ``autofetch`` option to ``no``. * New album art sources have been added. * Errors when communicating with MusicBrainz now log an error message instead of halting the importer. * Similarly, filesystem manipulation errors now print helpful error messages instead of a messy traceback. They still interrupt beets, but they should now be easier for users to understand. Tracebacks are still available in verbose mode. * New metadata fields for `artist credits`_: ``artist_credit`` and ``albumartist_credit`` can now contain release- and recording-specific variations of the artist's name. See :ref:`itemfields`. * Revamped the way beets handles concurrent database access to avoid nondeterministic SQLite-related crashes when using the multithreaded importer. On systems where SQLite was compiled without ``usleep(3)`` support, multithreaded database access could cause an internal error (with the message "database is locked"). This release synchronizes access to the database to avoid internal SQLite contention, which should avoid this error. * Plugins can now add parallel stages to the import pipeline. See :ref:`writing-plugins`. * Beets now prints out an error when you use an unrecognized field name in a query: for example, when running ``beet ls -a artist:foo`` (because ``artist`` is an item-level field). * New plugin events: * ``import_task_choice`` is called after an import task has an action assigned. * ``import_task_files`` is called after a task's file manipulation has finished (copying or moving files, writing metadata tags). * ``library_opened`` is called when beets starts up and opens the library database. * :doc:`/plugins/lastgenre`: Fixed a problem where path formats containing ``$genre`` would use the old genre instead of the newly discovered one. * Fix a crash when moving files to a Samba share. * :doc:`/plugins/mpdupdate`: Fix TypeError crash (thanks to Philippe Mongeau). * When re-importing files with ``import_copy`` enabled, only files inside the library directory are moved. Files outside the library directory are still copied. This solves a problem (introduced in 1.0b14) where beets could crash after adding files to the library but before finishing copying them; during the next import, the (external) files would be moved instead of copied. * Artist sort names are now populated correctly for multi-artist tracks and releases. (Previously, they only reflected the first artist.) * When previewing changes during import, differences in track duration are now shown as "2:50 vs. 3:10" rather than separated with ``->`` like track numbers. This should clarify that beets isn't doing anything to modify lengths. * Fix a problem with query-based path format matching where a field-qualified pattern, like ``albumtype_soundtrack``, would match everything. * :doc:`/plugins/chroma`: Fix matching with ambiguous Acoustids. Some Acoustids are identified with multiple recordings; beets now considers any associated recording a valid match. This should reduce some cases of errant track reordering when using chroma. * Fix the ID3 tag name for the catalog number field. * :doc:`/plugins/chroma`: Fix occasional crash at end of fingerprint submission and give more context to "failed fingerprint generation" errors. * Interactive prompts are sent to stdout instead of stderr. * :doc:`/plugins/embedart`: Fix crash when audio files are unreadable. * :doc:`/plugins/bpd`: Fix crash when sockets disconnect (thanks to Matteo Mecucci). * Fix an assertion failure while importing with moving enabled when the file was already at its destination. * Fix Unicode values in the ``replace`` config option (thanks to Jakob Borg). * Use a nicer error message when input is requested but stdin is closed. * Fix errors on Windows for certain Unicode characters that can't be represented in the MBCS encoding. This required a change to the way that paths are represented in the database on Windows; if you find that beets' paths are out of sync with your filesystem with this release, delete and recreate your database with ``beet import -AWC /path/to/music``. * Fix ``import`` with relative path arguments on Windows. .. _artist credits: https://wiki.musicbrainz.org/Artist_Credit 1.0b14 (May 12, 2012) --------------------- The centerpiece of this beets release is the graceful handling of similarly-named albums. It's now possible to import two albums with the same artist and title and to keep them from conflicting in the filesystem. Many other awesome new features were contributed by the beets community, including regular expression queries, artist sort names, moving files on import. There are three new plugins: random song/album selection; MusicBrainz "collection" integration; and a plugin for interoperability with other music library systems. A million thanks to the (growing) beets community for making this a huge release. * The importer now gives you **choices when duplicates are detected**. Previously, when beets found an existing album or item in your library matching the metadata on a newly-imported one, it would just skip the new music to avoid introducing duplicates into your library. Now, you have three choices: skip the new music (the previous behavior), keep both, or remove the old music. See the :ref:`guide-duplicates` section in the autotagging guide for details. * Beets can now avoid storing identically-named albums in the same directory. The new ``%aunique{}`` template function, which is included in the default path formats, ensures that Crystal Castles' albums will be placed into different directories. See :ref:`aunique` for details. * Beets queries can now use **regular expressions**. Use an additional ``:`` in your query to enable regex matching. See :ref:`regex` for the full details. Thanks to Matteo Mecucci. * Artist **sort names** are now fetched from MusicBrainz. There are two new data fields, ``artist_sort`` and ``albumartist_sort``, that contain sortable artist names like "Beatles, The". These fields are also used to sort albums and items when using the ``list`` command. Thanks to Paul Provost. * Many other **new metadata fields** were added, including ASIN, label catalog number, disc title, encoder, and MusicBrainz release group ID. For a full list of fields, see :ref:`itemfields`. * :doc:`/plugins/chroma`: A new command, ``beet submit``, will **submit fingerprints** to the Acoustid database. Submitting your library helps increase the coverage and accuracy of Acoustid fingerprinting. The Chromaprint fingerprint and Acoustid ID are also now stored for all fingerprinted tracks. This version of beets *requires* at least version 0.6 of `pyacoustid`_ for fingerprinting to work. * The importer can now **move files**. Previously, beets could only copy files and delete the originals, which is inefficient if the source and destination are on the same filesystem. Use the ``import_move`` configuration option and see :doc:`/reference/config` for more details. Thanks to Domen KoĹľar. * New :doc:`/plugins/random`: Randomly select albums and tracks from your library. Thanks to Philippe Mongeau. * The :doc:`/plugins/mbcollection` by Jeffrey Aylesworth was added to the core beets distribution. * New :doc:`/plugins/importfeeds`: Catalog imported files in ``m3u`` playlist files or as symlinks for easy importing to other systems. Thanks to Fabrice Laporte. * The ``-f`` (output format) option to the ``beet list`` command can now contain template functions as well as field references. Thanks to Steve Dougherty. * A new command ``beet fields`` displays the available metadata fields (thanks to Matteo Mecucci). * The ``import`` command now has a ``--noincremental`` or ``-I`` flag to disable incremental imports (thanks to Matteo Mecucci). * When the autotagger fails to find a match, it now displays the number of tracks on the album (to help you guess what might be going wrong) and a link to the FAQ. * The default filename character substitutions were changed to be more conservative. The Windows "reserved characters" are substituted by default even on Unix platforms (this causes less surprise when using Samba shares to store music). To customize your character substitutions, see :ref:`the replace config option <replace>`. * :doc:`/plugins/lastgenre`: Added a "fallback" option when no suitable genre can be found (thanks to Fabrice Laporte). * :doc:`/plugins/rewrite`: Unicode rewriting rules are now allowed (thanks to Nicolas Dietrich). * Filename collisions are now avoided when moving album art. * :doc:`/plugins/bpd`: Print messages to show when directory tree is being constructed. * :doc:`/plugins/bpd`: Use Gstreamer's ``playbin2`` element instead of the deprecated ``playbin``. * :doc:`/plugins/bpd`: Random and repeat modes are now supported (thanks to Matteo Mecucci). * :doc:`/plugins/bpd`: Listings are now sorted (thanks once again to Matteo Mecucci). * Filenames are normalized with Unicode Normal Form D (NFD) on Mac OS X and NFC on all other platforms. * Significant internal restructuring to avoid SQLite locking errors. As part of these changes, the not-very-useful "save" plugin event has been removed. .. _pyacoustid: https://github.com/beetbox/pyacoustid 1.0b13 (March 16, 2012) ----------------------- Beets 1.0b13 consists of a plethora of small but important fixes and refinements. A lyrics plugin is now included with beets; new audio properties are catalogged; the ``list`` command has been made more powerful; the autotagger is more tolerant of different tagging styles; and importing with original file deletion now cleans up after itself more thoroughly. Many, many bugs—including several crashers—were fixed. This release lays the foundation for more features to come in the next couple of releases. * The :doc:`/plugins/lyrics`, originally by `Peter Brunner`_, is revamped and included with beets, making it easy to fetch **song lyrics**. * Items now expose their audio **sample rate**, number of **channels**, and **bits per sample** (bitdepth). See :doc:`/reference/pathformat` for a list of all available audio properties. Thanks to Andrew Dunn. * The ``beet list`` command now accepts a "format" argument that lets you **show specific information about each album or track**. For example, run ``beet ls -af '$album: $tracktotal' beatles`` to see how long each Beatles album is. Thanks to Philippe Mongeau. * The autotagger now tolerates tracks on multi-disc albums that are numbered per-disc. For example, if track 24 on a release is the first track on the second disc, then it is not penalized for having its track number set to 1 instead of 24. * The autotagger sets the disc number and disc total fields on autotagged albums. * The autotagger now also tolerates tracks whose track artists tags are set to "Various Artists". * Terminal colors are now supported on Windows via `Colorama`_ (thanks to Karl). * When previewing metadata differences, the importer now shows discrepancies in track length. * Importing with ``import_delete`` enabled now cleans up empty directories that contained deleting imported music files. * Similarly, ``import_delete`` now causes original album art imported from the disk to be deleted. * Plugin-supplied template values, such as those created by ``rewrite``, are now properly sanitized (for example, ``AC/DC`` properly becomes ``AC_DC``). * Filename extensions are now always lower-cased when copying and moving files. * The ``inline`` plugin now prints a more comprehensible error when exceptions occur in Python snippets. * The ``replace`` configuration option can now remove characters entirely (in addition to replacing them) if the special string ``<strip>`` is specified as the replacement. * New plugin API: plugins can now add fields to the MediaFile tag abstraction layer. See :ref:`writing-plugins`. * A reasonable error message is now shown when the import log file cannot be opened. * The import log file is now flushed and closed properly so that it can be used to monitor import progress, even when the import crashes. * Duplicate track matches are no longer shown when autotagging singletons. * The ``chroma`` plugin now logs errors when fingerprinting fails. * The ``lastgenre`` plugin suppresses more errors when dealing with the Last.fm API. * Fix a bug in the ``rewrite`` plugin that broke the use of multiple rules for a single field. * Fix a crash with non-ASCII characters in bytestring metadata fields (e.g., MusicBrainz IDs). * Fix another crash with non-ASCII characters in the configuration paths. * Fix a divide-by-zero crash on zero-length audio files. * Fix a crash in the ``chroma`` plugin when the Acoustid database had no recording associated with a fingerprint. * Fix a crash when an autotagging with an artist or album containing "AND" or "OR" (upper case). * Fix an error in the ``rewrite`` and ``inline`` plugins when the corresponding config sections did not exist. * Fix bitrate estimation for AAC files whose headers are missing the relevant data. * Fix the ``list`` command in BPD (thanks to Simon Chopin). .. _Colorama: https://pypi.python.org/pypi/colorama 1.0b12 (January 16, 2012) ------------------------- This release focuses on making beets' path formatting vastly more powerful. It adds a function syntax for transforming text. Via a new plugin, arbitrary Python code can also be used to define new path format fields. Each path format template can now be activated conditionally based on a query. Character set substitutions are also now configurable. In addition, beets avoids problematic filename conflicts by appending numbers to filenames that would otherwise conflict. Three new plugins (``inline``, ``scrub``, and ``rewrite``) are included in this release. * **Functions in path formats** provide a simple way to write complex file naming rules: for example, ``%upper{%left{$artist,1}}`` will insert the capitalized first letter of the track's artist. For more details, see :doc:`/reference/pathformat`. If you're interested in adding your own template functions via a plugin, see :ref:`writing-plugins`. * Plugins can also now define new path *fields* in addition to functions. * The new :doc:`/plugins/inline` lets you **use Python expressions to customize path formats** by defining new fields in the config file. * The configuration can **condition path formats based on queries**. That is, you can write a path format that is only used if an item matches a given query. (This supersedes the earlier functionality that only allowed conditioning on album type; if you used this feature in a previous version, you will need to replace, for example, ``soundtrack:`` with ``albumtype_soundtrack:``.) See :ref:`path-format-config`. * **Filename substitutions are now configurable** via the ``replace`` config value. You can choose which characters you think should be allowed in your directory and music file names. See :doc:`/reference/config`. * Beets now ensures that files have **unique filenames** by appending a number to any filename that would otherwise conflict with an existing file. * The new :doc:`/plugins/scrub` can remove extraneous metadata either manually or automatically. * The new :doc:`/plugins/rewrite` can canonicalize names for path formats. * The autotagging heuristics have been tweaked in situations where the MusicBrainz database did not contain track lengths. Previously, beets penalized matches where this was the case, leading to situations where seemingly good matches would have poor similarity. This penalty has been removed. * Fix an incompatibility in BPD with libmpc (the library that powers mpc and ncmpc). * Fix a crash when importing a partial match whose first track was missing. * The ``lastgenre`` plugin now correctly writes discovered genres to imported files (when tag-writing is enabled). * Add a message when skipping directories during an incremental import. * The default ignore settings now ignore all files beginning with a dot. * Date values in path formats (``$year``, ``$month``, and ``$day``) are now appropriately zero-padded. * Removed the ``--path-format`` global flag for ``beet``. * Removed the ``lastid`` plugin, which was deprecated in the previous version. 1.0b11 (December 12, 2011) -------------------------- This version of beets focuses on transitioning the autotagger to the new version of the MusicBrainz database (called NGS). This transition brings with it a number of long-overdue improvements: most notably, predictable behavior when tagging multi-disc albums and integration with the new `Acoustid`_ acoustic fingerprinting technology. The importer can also now tag *incomplete* albums when you're missing a few tracks from a given release. Two other new plugins are also included with this release: one for assigning genres and another for ReplayGain analysis. * Beets now communicates with MusicBrainz via the new `Next Generation Schema`_ (NGS) service via `python-musicbrainzngs`_. The bindings are included with this version of beets, but a future version will make them an external dependency. * The importer now detects **multi-disc albums** and tags them together. Using a heuristic based on the names of directories, certain structures are classified as multi-disc albums: for example, if a directory contains subdirectories labeled "disc 1" and "disc 2", these subdirectories will be coalesced into a single album for tagging. * The new :doc:`/plugins/chroma` uses the `Acoustid`_ **open-source acoustic fingerprinting** service. This replaces the old ``lastid`` plugin, which used Last.fm fingerprinting and is now deprecated. Fingerprinting with this library should be faster and more reliable. * The importer can now perform **partial matches**. This means that, if you're missing a few tracks from an album, beets can still tag the remaining tracks as a single album. (Thanks to `Simon Chopin`_.) * The new :doc:`/plugins/lastgenre` automatically **assigns genres to imported albums** and items based on Last.fm tags and an internal whitelist. (Thanks to `KraYmer`_.) * The :doc:`/plugins/replaygain`, written by `Peter Brunner`_, has been merged into the core beets distribution. Use it to analyze audio and **adjust playback levels** in ReplayGain-aware music players. * Albums are now tagged with their *original* release date rather than the date of any reissue, remaster, "special edition", or the like. * The config file and library databases are now given better names and locations on Windows. Namely, both files now reside in ``%APPDATA%``; the config file is named ``beetsconfig.ini`` and the database is called ``beetslibrary.blb`` (neither has a leading dot as on Unix). For backwards compatibility, beets will check the old locations first. * When entering an ID manually during tagging, beets now searches for anything that looks like an MBID in the entered string. This means that full MusicBrainz URLs now work as IDs at the prompt. (Thanks to derwin.) * The importer now ignores certain "clutter" files like ``.AppleDouble`` directories and ``._*`` files. The list of ignored patterns is configurable via the ``ignore`` setting; see :doc:`/reference/config`. * The database now keeps track of files' modification times so that, during an ``update``, unmodified files can be skipped. (Thanks to Jos van der Til.) * The album art fetcher now uses `albumart.org`_ as a fallback when the Amazon art downloader fails. * A new ``timeout`` config value avoids database locking errors on slow systems. * Fix a crash after using the "as Tracks" option during import. * Fix a Unicode error when tagging items with missing titles. * Fix a crash when the state file (``~/.beetsstate``) became emptied or corrupted. .. _KraYmer: https://github.com/KraYmer .. _Next Generation Schema: https://musicbrainz.org/doc/XML_Web_Service/Version_2 .. _python-musicbrainzngs: https://github.com/alastair/python-musicbrainzngs .. _acoustid: https://acoustid.org/ .. _Peter Brunner: https://github.com/Lugoues .. _Simon Chopin: https://github.com/laarmen .. _albumart.org: https://www.albumart.org/ 1.0b10 (September 22, 2011) --------------------------- This version of beets focuses on making it easier to manage your metadata *after* you've imported it. A bumper crop of new commands has been added: a manual tag editor (``modify``), a tool to pick up out-of-band deletions and modifications (``update``), and functionality for moving and copying files around (``move``). Furthermore, the concept of "re-importing" is new: you can choose to re-run beets' advanced autotagger on any files you already have in your library if you change your mind after you finish the initial import. As a couple of added bonuses, imports can now automatically skip previously-imported directories (with the ``-i`` flag) and there's an :doc:`experimental Web interface </plugins/web>` to beets in a new standard plugin. * A new ``beet modify`` command enables **manual, command-line-based modification** of music metadata. Pass it a query along with ``field=value`` pairs that specify the changes you want to make. * A new ``beet update`` command updates the database to reflect **changes in the on-disk metadata**. You can now use an external program to edit tags on files, remove files and directories, etc., and then run ``beet update`` to make sure your beets library is in sync. This will also rename files to reflect their new metadata. * A new ``beet move`` command can **copy or move files** into your library directory or to another specified directory. * When importing files that are already in the library database, the items are no longer duplicated---instead, the library is updated to reflect the new metadata. This way, the import command can be transparently used as a **re-import**. * Relatedly, the ``-L`` flag to the "import" command makes it take a query as its argument instead of a list of directories. The matched albums (or items, depending on the ``-s`` flag) are then re-imported. * A new flag ``-i`` to the import command runs **incremental imports**, keeping track of and skipping previously-imported directories. This has the effect of making repeated import commands pick up only newly-added directories. The ``import_incremental`` config option makes this the default. * When pruning directories, "clutter" files such as ``.DS_Store`` and ``Thumbs.db`` are ignored (and removed with otherwise-empty directories). * The :doc:`/plugins/web` encapsulates a simple **Web-based GUI for beets**. The current iteration can browse the library and play music in browsers that support HTML5 Audio. * When moving items that are part of an album, the album art implicitly moves too. * Files are no longer silently overwritten when moving and copying files. * Handle exceptions thrown when running Mutagen. * Fix a missing ``__future__`` import in ``embed art`` on Python 2.5. * Fix ID3 and MPEG-4 tag names for the album-artist field. * Fix Unicode encoding of album artist, album type, and label. * Fix crash when "copying" an art file that's already in place. 1.0b9 (July 9, 2011) -------------------- This release focuses on a large number of small fixes and improvements that turn beets into a well-oiled, music-devouring machine. See the full release notes, below, for a plethora of new features. * **Queries can now contain whitespace.** Spaces passed as shell arguments are now preserved, so you can use your shell's escaping syntax (quotes or backslashes, for instance) to include spaces in queries. For example, typing``beet ls "the knife"`` or ``beet ls the\ knife``. Read more in :doc:`/reference/query`. * Queries can **match items from the library by directory**. A ``path:`` prefix is optional; any query containing a path separator (/ on POSIX systems) is assumed to be a path query. Running ``beet ls path/to/music`` will show all the music in your library under the specified directory. The :doc:`/reference/query` reference again has more details. * **Local album art** is now automatically discovered and copied from the imported directories when available. * When choosing the "as-is" import album (or doing a non-autotagged import), **every album either has an "album artist" set or is marked as a compilation (Various Artists)**. The choice is made based on the homogeneity of the tracks' artists. This prevents compilations that are imported as-is from being scattered across many directories after they are imported. * The release **label** for albums and tracks is now fetched from !MusicBrainz, written to files, and stored in the database. * The "list" command now accepts a ``-p`` switch that causes it to **show paths** instead of titles. This makes the output of ``beet ls -p`` suitable for piping into another command such as `xargs`_. * Release year and label are now shown in the candidate selection list to help disambiguate different releases of the same album. * Prompts in the importer interface are now colorized for easy reading. The default option is always highlighted. * The importer now provides the option to specify a MusicBrainz ID manually if the built-in searching isn't working for a particular album or track. * ``$bitrate`` in path formats is now formatted as a human-readable kbps value instead of as a raw integer. * The import logger has been improved for "always-on" use. First, it is now possible to specify a log file in .beetsconfig. Also, logs are now appended rather than overwritten and contain timestamps. * Album art fetching and plugin events are each now run in separate pipeline stages during imports. This should bring additional performance when using album art plugins like embedart or beets-lyrics. * Accents and other Unicode decorators on characters are now treated more fairly by the autotagger. For example, if you're missing the acute accent on the "e" in "cafĂ©", that change won't be penalized. This introduces a new dependency on the `unidecode`_ Python module. * When tagging a track with no title set, the track's filename is now shown (instead of nothing at all). * The bitrate of lossless files is now calculated from their file size (rather than being fixed at 0 or reflecting the uncompressed audio bitrate). * Fixed a problem where duplicate albums or items imported at the same time would fail to be detected. * BPD now uses a persistent "virtual filesystem" in order to fake a directory structure. This means that your path format settings are respected in BPD's browsing hierarchy. This may come at a performance cost, however. The virtual filesystem used by BPD is available for reuse by plugins (e.g., the FUSE plugin). * Singleton imports (``beet import -s``) can now take individual files as arguments as well as directories. * Fix Unicode queries given on the command line. * Fix crasher in quiet singleton imports (``import -qs``). * Fix crash when autotagging files with no metadata. * Fix a rare deadlock when finishing the import pipeline. * Fix an issue that was causing mpdupdate to run twice for every album. * Fix a bug that caused release dates/years not to be fetched. * Fix a crasher when setting MBIDs on MP3s file metadata. * Fix a "broken pipe" error when piping beets' standard output. * A better error message is given when the database file is unopenable. * Suppress errors due to timeouts and bad responses from MusicBrainz. * Fix a crash on album queries with item-only field names. .. _xargs: https://en.wikipedia.org/wiki/xargs .. _unidecode: https://pypi.python.org/pypi/Unidecode/0.04.1 1.0b8 (April 28, 2011) ---------------------- This release of beets brings two significant new features. First, beets now has first-class support for "singleton" tracks. Previously, it was only really meant to manage whole albums, but many of us have lots of non-album tracks to keep track of alongside our collections of albums. So now beets makes it easy to tag, catalog, and manipulate your individual tracks. Second, beets can now (optionally) embed album art directly into file metadata rather than only storing it in a "file on the side." Check out the :doc:`/plugins/embedart` for that functionality. * Better support for **singleton (non-album) tracks**. Whereas beets previously only really supported full albums, now it can also keep track of individual, off-album songs. The "singleton" path format can be used to customize where these tracks are stored. To import singleton tracks, provide the -s switch to the import command or, while doing a normal full-album import, choose the "as Tracks" (T) option to add singletons to your library. To list only singleton or only album tracks, use the new ``singleton:`` query term: the query ``singleton:true`` matches only singleton tracks; ``singleton:false`` matches only album tracks. The ``lastid`` plugin has been extended to support matching individual items as well. * The importer/autotagger system has been heavily refactored in this release. If anything breaks as a result, please get in touch or just file a bug. * Support for **album art embedded in files**. A new :doc:`/plugins/embedart` implements this functionality. Enable the plugin to automatically embed downloaded album art into your music files' metadata. The plugin also provides the "embedart" and "extractart" commands for moving image files in and out of metadata. See the wiki for more details. (Thanks, daenney!) * The "distance" number, which quantifies how different an album's current and proposed metadata are, is now displayed as "similarity" instead. This should be less noisy and confusing; you'll now see 99.5% instead of 0.00489323. * A new "timid mode" in the importer asks the user every time, even when it makes a match with very high confidence. The ``-t`` flag on the command line and the ``import_timid`` config option control this mode. (Thanks to mdecker on GitHub!) * The multithreaded importer should now abort (either by selecting aBort or by typing ^C) much more quickly. Previously, it would try to get a lot of work done before quitting; now it gives up as soon as it can. * Added a new plugin event, ``album_imported``, which is called every time an album is added to the library. (Thanks, Lugoues!) * A new plugin method, ``register_listener``, is an imperative alternative to the ``@listen`` decorator (Thanks again, Lugoues!) * In path formats, ``$albumartist`` now falls back to ``$artist`` (as well as the other way around). * The importer now prints "(unknown album)" when no tags are present. * When autotagging, "and" is considered equal to "&". * Fix some crashes when deleting files that don't exist. * Fix adding individual tracks in BPD. * Fix crash when ``~/.beetsconfig`` does not exist. 1.0b7 (April 5, 2011) --------------------- Beta 7's focus is on better support for "various artists" releases. These albums can be treated differently via the new ``[paths]`` config section and the autotagger is better at handling them. It also includes a number of oft-requested improvements to the ``beet`` command-line tool, including several new configuration options and the ability to clean up empty directory subtrees. * **"Various artists" releases** are handled much more gracefully. The autotagger now sets the ``comp`` flag on albums whenever the album is identified as a "various artists" release by !MusicBrainz. Also, there is now a distinction between the "album artist" and the "track artist", the latter of which is never "Various Artists" or other such bogus stand-in. *(Thanks to Jonathan for the bulk of the implementation work on this feature!)* * The directory hierarchy can now be **customized based on release type**. In particular, the ``path_format`` setting in .beetsconfig has been replaced with a new ``[paths]`` section, which allows you to specify different path formats for normal and "compilation" (various artists) releases as well as for each album type (see below). The default path formats have been changed to use ``$albumartist`` instead of ``$artist``. * A **new ``albumtype`` field** reflects the release type `as specified by MusicBrainz`_. * When deleting files, beets now appropriately "prunes" the directory tree---empty directories are automatically cleaned up. *(Thanks to wlof on GitHub for this!)* * The tagger's output now always shows the album directory that is currently being tagged. This should help in situations where files' current tags are missing or useless. * The logging option (``-l``) to the ``import`` command now logs duplicate albums. * A new ``import_resume`` configuration option can be used to disable the importer's resuming feature or force it to resume without asking. This option may be either ``yes``, ``no``, or ``ask``, with the obvious meanings. The ``-p`` and ``-P`` command-line flags override this setting and correspond to the "yes" and "no" settings. * Resuming is automatically disabled when the importer is in quiet (``-q``) mode. Progress is still saved, however, and the ``-p`` flag (above) can be used to force resuming. * The ``BEETSCONFIG`` environment variable can now be used to specify the location of the config file that is at ~/.beetsconfig by default. * A new ``import_quiet_fallback`` config option specifies what should happen in quiet mode when there is no strong recommendation. The options are ``skip`` (the default) and "asis". * When importing with the "delete" option and importing files that are already at their destination, files could be deleted (leaving zero copies afterward). This is fixed. * The ``version`` command now lists all the loaded plugins. * A new plugin, called ``info``, just prints out audio file metadata. * Fix a bug where some files would be erroneously interpreted as MPEG-4 audio. * Fix permission bits applied to album art files. * Fix malformed !MusicBrainz queries caused by null characters. * Fix a bug with old versions of the Monkey's Audio format. * Fix a crash on broken symbolic links. * Retry in more cases when !MusicBrainz servers are slow/overloaded. * The old "albumify" plugin for upgrading databases was removed. .. _as specified by MusicBrainz: https://wiki.musicbrainz.org/ReleaseType 1.0b6 (January 20, 2011) ------------------------ This version consists primarily of bug fixes and other small improvements. It's in preparation for a more feature-ful release in beta 7. The most important issue involves correct ordering of autotagged albums. * **Quiet import:** a new "-q" command line switch for the import command suppresses all prompts for input; it pessimistically skips all albums that the importer is not completely confident about. * Added support for the **WavPack** and **Musepack** formats. Unfortunately, due to a limitation in the Mutagen library (used by beets for metadata manipulation), Musepack SV8 is not yet supported. Here's the `upstream bug`_ in question. * BPD now uses a pure-Python socket library and no longer requires eventlet/greenlet (the latter of which is a C extension). For the curious, the socket library in question is called `Bluelet`_. * Non-autotagged imports are now resumable (just like autotagged imports). * Fix a terrible and long-standing bug where track orderings were never applied. This manifested when the tagger appeared to be applying a reasonable ordering to the tracks but, later, the database reflects a completely wrong association of track names to files. The order applied was always just alphabetical by filename, which is frequently but not always what you want. * We now use Windows' "long filename" support. This API is fairly tricky, though, so some instability may still be present---please file a bug if you run into pathname weirdness on Windows. Also, filenames on Windows now never end in spaces. * Fix crash in lastid when the artist name is not available. * Fixed a spurious crash when ``LANG`` or a related environment variable is set to an invalid value (such as ``'UTF-8'`` on some installations of Mac OS X). * Fixed an error when trying to copy a file that is already at its destination. * When copying read-only files, the importer now tries to make the copy writable. (Previously, this would just crash the import.) * Fixed an ``UnboundLocalError`` when no matches are found during autotag. * Fixed a Unicode encoding error when entering special characters into the "manual search" prompt. * Added `` beet version`` command that just shows the current release version. .. _upstream bug: https://github.com/quodlibet/mutagen/issues/7 .. _Bluelet: https://github.com/sampsyo/bluelet 1.0b5 (September 28, 2010) -------------------------- This version of beets focuses on increasing the accuracy of the autotagger. The main addition is an included plugin that uses acoustic fingerprinting to match based on the audio content (rather than existing metadata). Additional heuristics were also added to the metadata-based tagger as well that should make it more reliable. This release also greatly expands the capabilities of beets' :doc:`plugin API </plugins/index>`. A host of other little features and fixes are also rolled into this release. * The ``lastid`` plugin adds Last.fm **acoustic fingerprinting support** to the autotagger. Similar to the PUIDs used by !MusicBrainz Picard, this system allows beets to recognize files that don't have any metadata at all. You'll need to install some dependencies for this plugin to work. * To support the above, there's also a new system for **extending the autotagger via plugins**. Plugins can currently add components to the track and album distance functions as well as augment the MusicBrainz search. The new API is documented at :doc:`/plugins/index`. * **String comparisons** in the autotagger have been augmented to act more intuitively. Previously, if your album had the title "Something (EP)" and it was officially called "Something", then beets would think this was a fairly significant change. It now checks for and appropriately reweights certain parts of each string. As another example, the title "The Great Album" is considered equal to "Great Album, The". * New **event system for plugins** (thanks, Jeff!). Plugins can now get callbacks from beets when certain events occur in the core. Again, the API is documented in :doc:`/plugins/index`. * The BPD plugin is now disabled by default. This greatly simplifies installation of the beets core, which is now 100% pure Python. To use BPD, though, you'll need to set ``plugins: bpd`` in your .beetsconfig. * The ``import`` command can now remove original files when it copies items into your library. (This might be useful if you're low on disk space.) Set the ``import_delete`` option in your .beetsconfig to ``yes``. * Importing without autotagging (``beet import -A``) now prints out album names as it imports them to indicate progress. * The new :doc:`/plugins/mpdupdate` will automatically update your MPD server's index whenever your beets library changes. * Efficiency tweak should reduce the number of !MusicBrainz queries per autotagged album. * A new ``-v`` command line switch enables debugging output. * Fixed bug that completely broke non-autotagged imports (``import -A``). * Fixed bug that logged the wrong paths when using ``import -l``. * Fixed autotagging for the creatively-named band `!!!`_. * Fixed normalization of relative paths. * Fixed escaping of ``/`` characters in paths on Windows. .. _!!!: https://musicbrainz.org/artist/f26c72d3-e52c-467b-b651-679c73d8e1a7.html 1.0b4 (August 9, 2010) ---------------------- This thrilling new release of beets focuses on making the tagger more usable in a variety of ways. First and foremost, it should now be much faster: the tagger now uses a multithreaded algorithm by default (although, because the new tagger is experimental, a single-threaded version is still available via a config option). Second, the tagger output now uses a little bit of ANSI terminal coloring to make changes stand out. This way, it should be faster to decide what to do with a proposed match: the more red you see, the worse the match is. Finally, the tagger can be safely interrupted (paused) and restarted later at the same point. Just enter ``b`` for aBort at any prompt to stop the tagging process and save its progress. (The progress-saving also works in the unthinkable event that beets crashes while tagging.) Among the under-the-hood changes in 1.0b4 is a major change to the way beets handles paths (filenames). This should make the whole system more tolerant to special characters in filenames, but it may break things (especially databases created with older versions of beets). As always, let me know if you run into weird problems with this release. Finally, this release's ``setup.py`` should install a ``beet.exe`` startup stub for Windows users. This should make running beets much easier: just type ``beet`` if you have your ``PATH`` environment variable set up correctly. The :doc:`/guides/main` guide has some tips on installing beets on Windows. Here's the detailed list of changes: * **Parallel tagger.** The autotagger has been reimplemented to use multiple threads. This means that it can concurrently read files from disk, talk to the user, communicate with MusicBrainz, and write data back to disk. Not only does this make the tagger much faster because independent work may be performed in parallel, but it makes the tagging process much more pleasant for large imports. The user can let albums queue up in the background while making a decision rather than waiting for beets between each question it asks. The parallel tagger is on by default but a sequential (single- threaded) version is still available by setting the ``threaded`` config value to ``no`` (because the parallel version is still quite experimental). * **Colorized tagger output.** The autotagger interface now makes it a little easier to see what's going on at a glance by highlighting changes with terminal colors. This feature is on by default, but you can turn it off by setting ``color`` to ``no`` in your ``.beetsconfig`` (if, for example, your terminal doesn't understand colors and garbles the output). * **Pause and resume imports.** The ``import`` command now keeps track of its progress, so if you're interrupted (beets crashes, you abort the process, an alien devours your motherboard, etc.), beets will try to resume from the point where you left off. The next time you run ``import`` on the same directory, it will ask if you want to resume. It accomplishes this by "fast-forwarding" through the albums in the directory until it encounters the last one it saw. (This means it might fail if that album can't be found.) Also, you can now abort the tagging process by entering ``b`` (for aBort) at any of the prompts. * Overhauled methods for handling filesystem paths to allow filenames that have badly encoded special characters. These changes are pretty fragile, so please report any bugs involving ``UnicodeError`` or SQLite ``ProgrammingError`` messages in this version. * The destination paths (the library directory structure) now respect album-level metadata. This means that if you have an album in which two tracks have different album-level attributes (like year, for instance), they will still wind up in the same directory together. (There's currently not a very smart method for picking the "correct" album-level metadata, but we'll fix that later.) * Fixed a bug where the CLI would fail completely if the ``LANG`` environment variable was not set. * Fixed removal of albums (``beet remove -a``): previously, the album record would stay around although the items were deleted. * The setup script now makes a ``beet.exe`` startup stub on Windows; Windows users can now just type ``beet`` at the prompt to run beets. * Fixed an occasional bug where Mutagen would complain that a tag was already present. * Fixed a bug with reading invalid integers from ID3 tags. * The tagger should now be a little more reluctant to reorder tracks that already have indices. 1.0b3 (July 22, 2010) --------------------- This release features two major additions to the autotagger's functionality: album art fetching and MusicBrainz ID tags. It also contains some important under-the-hood improvements: a new plugin architecture is introduced and the database schema is extended with explicit support for albums. This release has one major backwards-incompatibility. Because of the new way beets handles albums in the library, databases created with an old version of beets might have trouble with operations that deal with albums (like the ``-a`` switch to ``beet list`` and ``beet remove``, as well as the file browser for BPD). To "upgrade" an old database, you can use the included ``albumify`` plugin (see the fourth bullet point below). * **Album art.** The tagger now, by default, downloads album art from Amazon that is referenced in the MusicBrainz database. It places the album art alongside the audio files in a file called (for example) ``cover.jpg``. The ``import_art`` config option controls this behavior, as do the ``-r`` and ``-R`` options to the import command. You can set the name (minus extension) of the album art file with the ``art_filename`` config option. (See :doc:`/reference/config` for more information about how to configure the album art downloader.) * **Support for MusicBrainz ID tags.** The autotagger now keeps track of the MusicBrainz track, album, and artist IDs it matched for each file. It also looks for album IDs in new files it's importing and uses those to look up data in MusicBrainz. Furthermore, track IDs are used as a component of the tagger's distance metric now. (This obviously lays the groundwork for a utility that can update tags if the MB database changes, but that's `for the future`_.) Tangentially, this change required the database code to support a lightweight form of migrations so that new columns could be added to old databases--this is a delicate feature, so it would be very wise to make a backup of your database before upgrading to this version. * **Plugin architecture.** Add-on modules can now add new commands to the beets command-line interface. The ``bpd`` and ``dadd`` commands were removed from the beets core and turned into plugins; BPD is loaded by default. To load the non-default plugins, use the config options ``plugins`` (a space-separated list of plugin names) and ``pluginpath`` (a colon-separated list of directories to search beyond ``sys.path``). Plugins are just Python modules under the ``beetsplug`` namespace package containing subclasses of ``beets.plugins.BeetsPlugin``. See `the beetsplug directory`_ for examples or :doc:`/plugins/index` for instructions. * As a consequence of adding album art, the database was significantly refactored to keep track of some information at an album (rather than item) granularity. Databases created with earlier versions of beets should work fine, but they won't have any "albums" in them--they'll just be a bag of items. This means that commands like ``beet ls -a`` and ``beet rm -a`` won't match anything. To "upgrade" your database, you can use the included ``albumify`` plugin. Running ``beets albumify`` with the plugin activated (set ``plugins=albumify`` in your config file) will group all your items into albums, making beets behave more or less as it did before. * Fixed some bugs with encoding paths on Windows. Also, ``:`` is now replaced with ``-`` in path names (instead of ``_``) for readability. * ``MediaFile``s now have a ``format`` attribute, so you can use ``$format`` in your library path format strings like ``$artist - $album ($format)`` to get directories with names like ``Paul Simon - Graceland (FLAC)``. .. _for the future: https://github.com/google-code-export/beets/issues/69 .. _the beetsplug directory: https://github.com/beetbox/beets/tree/master/beetsplug Beets also now has its first third-party plugin: `beetfs`_, by Martin Eve! It exposes your music in a FUSE filesystem using a custom directory structure. Even cooler: it lets you keep your files intact on-disk while correcting their tags when accessed through FUSE. Check it out! .. _beetfs: https://github.com/jbaiter/beetfs 1.0b2 (July 7, 2010) -------------------- This release focuses on high-priority fixes and conspicuously missing features. Highlights include support for two new audio formats (Monkey's Audio and Ogg Vorbis) and an option to log untaggable albums during import. * **Support for Ogg Vorbis and Monkey's Audio** files and their tags. (This support should be considered preliminary: I haven't tested it heavily because I don't use either of these formats regularly.) * An option to the ``beet import`` command for **logging albums that are untaggable** (i.e., are skipped or taken "as-is"). Use ``beet import -l LOGFILE PATHS``. The log format is very simple: it's just a status (either "skip" or "asis") followed by the path to the album in question. The idea is that you can tag a large collection and automatically keep track of the albums that weren't found in MusicBrainz so you can come back and look at them later. * Fixed a ``UnicodeEncodeError`` on terminals that don't (or don't claim to) support UTF-8. * Importing without autotagging (``beet import -A``) is now faster and doesn't print out a bunch of whitespace. It also lets you specify single files on the command line (rather than just directories). * Fixed importer crash when attempting to read a corrupt file. * Reorganized code for CLI in preparation for adding pluggable subcommands. Also removed dependency on the aging ``cmdln`` module in favor of `a hand-rolled solution`_. .. _a hand-rolled solution: https://gist.github.com/462717 1.0b1 (June 17, 2010) --------------------- Initial release. �����������������������������������������������������������������������������������������������������������������������������������������������������beetbox-beets-01f1faf/docs/code_of_conduct.rst������������������������������������������������������0000664�0000000�0000000�00000000071�14723254774�0021544�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������.. code_of_conduct: .. include:: ../CODE_OF_CONDUCT.rst �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������beetbox-beets-01f1faf/docs/conf.py������������������������������������������������������������������0000664�0000000�0000000�00000003235�14723254774�0017201�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������AUTHOR = "Adrian Sampson" # General configuration extensions = ["sphinx.ext.autodoc", "sphinx.ext.extlinks"] exclude_patterns = ["_build"] source_suffix = ".rst" master_doc = "index" project = "beets" copyright = "2016, Adrian Sampson" version = "2.1" release = "2.1.0" pygments_style = "sphinx" # External links to the bug tracker and other sites. extlinks = { "bug": ("https://github.com/beetbox/beets/issues/%s", "#%s"), "user": ("https://github.com/%s", "%s"), "pypi": ("https://pypi.org/project/%s/", "%s"), "stdlib": ("https://docs.python.org/3/library/%s.html", "%s"), } linkcheck_ignore = [ r"https://github.com/beetbox/beets/issues/", r"https://github.com/[^/]+$", # ignore user pages r".*localhost.*", r"https?://127\.0\.0\.1", r"https://www.musixmatch.com/", # blocks requests r"https://genius.com/", # blocks requests ] # Options for HTML output htmlhelp_basename = "beetsdoc" # Options for LaTeX output latex_documents = [ ("index", "beets.tex", "beets Documentation", AUTHOR, "manual"), ] # Options for manual page output man_pages = [ ( "reference/cli", "beet", "music tagger and library organizer", [AUTHOR], 1, ), ( "reference/config", "beetsconfig", "beets configuration file", [AUTHOR], 5, ), ] # Options for pydata theme html_theme = "pydata_sphinx_theme" html_theme_options = { "collapse_navigation": True, "logo": { "text": "beets", }, "pygment_light_style": "bw", } html_title = "beets" html_logo = "_static/beets_logo_nobg.png" html_static_path = ["_static"] html_css_files = ["beets.css"] �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������beetbox-beets-01f1faf/docs/contributing.rst���������������������������������������������������������0000664�0000000�0000000�00000000063�14723254774�0021137�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������.. contributing: .. include:: ../CONTRIBUTING.rst �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������beetbox-beets-01f1faf/docs/dev/���������������������������������������������������������������������0000775�0000000�0000000�00000000000�14723254774�0016455�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������beetbox-beets-01f1faf/docs/dev/cli.rst��������������������������������������������������������������0000664�0000000�0000000�00000000632�14723254774�0017757�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������Providing a CLI =============== The ``beets.ui`` module houses interactions with the user via a terminal, the :doc:`/reference/cli`. The main function is called when the user types beet on the command line. The CLI functionality is organized into commands, some of which are built-in and some of which are provided by plugins. The built-in commands are all implemented in the ``beets.ui.commands`` submodule. ������������������������������������������������������������������������������������������������������beetbox-beets-01f1faf/docs/dev/importer.rst���������������������������������������������������������0000664�0000000�0000000�00000001760�14723254774�0021054�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������Music Importer ============== The importer component is responsible for the user-centric workflow that adds music to a library. This is one of the first aspects that a user experiences when using beets: it finds music in the filesystem, groups it into albums, finds corresponding metadata in MusicBrainz, asks the user for intervention, applies changes, and moves/copies files. A description of its user interface is given in :doc:`/guides/tagger`. The workflow is implemented in the ``beets.importer`` module and is distinct from the core logic for matching MusicBrainz metadata (in the ``beets.autotag`` module). The workflow is also decoupled from the command-line interface with the hope that, eventually, other (graphical) interfaces can be bolted onto the same importer implementation. The importer is multithreaded and follows the pipeline pattern. Each pipeline stage is a Python coroutine. The ``beets.util.pipeline`` module houses a generic, reusable implementation of a multithreaded pipeline. ����������������beetbox-beets-01f1faf/docs/dev/index.rst������������������������������������������������������������0000664�0000000�0000000�00000000622�14723254774�0020316�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������For Developers ============== This section contains information for developers. Read on if you're interested in hacking beets itself or creating plugins for it. See also the documentation for `MediaFile`_, the library used by beets to read and write metadata tags in media files. .. _MediaFile: https://mediafile.readthedocs.io/en/latest/ .. toctree:: plugins library importer cli ��������������������������������������������������������������������������������������������������������������beetbox-beets-01f1faf/docs/dev/library.rst����������������������������������������������������������0000664�0000000�0000000�00000022263�14723254774�0020660�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������Library Database API ==================== .. currentmodule:: beets.library This page describes the internal API of beets' core database features. It doesn't exhaustively document the API, but is aimed at giving an overview of the architecture to orient anyone who wants to dive into the code. The :class:`Library` object is the central repository for data in beets. It represents a database containing songs, which are :class:`Item` instances, and groups of items, which are :class:`Album` instances. The Library Class ----------------- The :class:`Library` is typically instantiated as a singleton. A single invocation of beets usually has only one :class:`Library`. It's powered by :class:`dbcore.Database` under the hood, which handles the `SQLite`_ abstraction, something like a very minimal `ORM`_. The library is also responsible for handling queries to retrieve stored objects. .. autoclass:: Library(path, directory[, path_formats[, replacements]]) .. automethod:: __init__ You can add new items or albums to the library: .. automethod:: add .. automethod:: add_album And there are methods for querying the database: .. automethod:: items .. automethod:: albums .. automethod:: get_item .. automethod:: get_album Any modifications must go through a :class:`Transaction` which you get can using this method: .. automethod:: transaction .. _SQLite: https://sqlite.org/index.html .. _ORM: https://en.wikipedia.org/wiki/Object-relational_mapping Model Classes ------------- The two model entities in beets libraries, :class:`Item` and :class:`Album`, share a base class, :class:`LibModel`, that provides common functionality. That class itself specialises :class:`dbcore.Model` which provides an ORM-like abstraction. To get or change the metadata of a model (an item or album), either access its attributes (e.g., ``print(album.year)`` or ``album.year = 2012``) or use the ``dict``-like interface (e.g. ``item['artist']``). Model base '''''''''' Models use dirty-flags to track when the object's metadata goes out of sync with the database. The dirty dictionary maps field names to booleans indicating whether the field has been written since the object was last synchronized (via load or store) with the database. .. autoclass:: LibModel .. automethod:: all_keys .. automethod:: __init__ .. autoattribute:: _types .. autoattribute:: _fields There are CRUD-like methods for interacting with the database: .. automethod:: store .. automethod:: load .. automethod:: remove .. automethod:: add The base class :class:`dbcore.Model` has a ``dict``-like interface, so normal the normal mapping API is supported: .. automethod:: keys .. automethod:: update .. automethod:: items .. note:: The :py:meth:`Album.items` method is not inherited from :py:meth:`LibModel.items` for historical reasons. .. automethod:: get Item '''' Each :class:`Item` object represents a song or track. (We use the more generic term item because, one day, beets might support non-music media.) An item can either be purely abstract, in which case it's just a bag of metadata fields, or it can have an associated file (indicated by ``item.path``). In terms of the underlying SQLite database, items are backed by a single table called items with one column per metadata fields. The metadata fields currently in use are listed in ``library.py`` in ``Item._fields``. To read and write a file's tags, we use the `MediaFile`_ library. To make changes to either the database or the tags on a file, you update an item's fields (e.g., ``item.title = "Let It Be"``) and then call ``item.write()``. .. _MediaFile: https://mediafile.readthedocs.io/en/latest/ Items also track their modification times (mtimes) to help detect when they become out of sync with on-disk metadata, mainly to speed up the :ref:`update-cmd` (which needs to check whether the database is in sync with the filesystem). This feature turns out to be sort of complicated. For any :class:`Item`, there are two mtimes: the on-disk mtime (maintained by the OS) and the database mtime (maintained by beets). Correspondingly, there is on-disk metadata (ID3 tags, for example) and DB metadata. The goal with the mtime is to ensure that the on-disk and DB mtimes match when the on-disk and DB metadata are in sync; this lets beets do a quick mtime check and avoid rereading files in some circumstances. Specifically, beets attempts to maintain the following invariant: If the on-disk metadata differs from the DB metadata, then the on-disk mtime must be greater than the DB mtime. As a result, it is always valid for the DB mtime to be zero (assuming that real disk mtimes are always positive). However, whenever possible, beets tries to set ``db_mtime = disk_mtime`` at points where it knows the metadata is synchronized. When it is possible that the metadata is out of sync, beets can then just set ``db_mtime = 0`` to return to a consistent state. This leads to the following implementation policy: * On every write of disk metadata (``Item.write()``), the DB mtime is updated to match the post-write disk mtime. * Same for metadata reads (``Item.read()``). * On every modification to DB metadata (``item.field = ...``), the DB mtime is reset to zero. .. autoclass:: Item .. automethod:: __init__ .. automethod:: from_path .. automethod:: get_album .. automethod:: destination .. automethod:: current_mtime The methods ``read()`` and ``write()`` are complementary: one reads a file's tags and updates the item's metadata fields accordingly while the other takes the item's fields and writes them to the file's tags. .. automethod:: read .. automethod:: write .. automethod:: try_write .. automethod:: try_sync The :class:`Item` class supplements the normal model interface so that they interacting with the filesystem as well: .. automethod:: move .. automethod:: remove Album ''''' An :class:`Album` is a collection of Items in the database. Every item in the database has either zero or one associated albums (accessible via ``item.album_id``). An item that has no associated album is called a singleton. Changing fields on an album (e.g. ``album.year = 2012``) updates the album itself and also changes the same field in all associated items. An :class:`Album` object keeps track of album-level metadata, which is (mostly) a subset of the track-level metadata. The album-level metadata fields are listed in ``Album._fields``. For those fields that are both item-level and album-level (e.g., ``year`` or ``albumartist``), every item in an album should share the same value. Albums use an SQLite table called ``albums``, in which each column is an album metadata field. .. autoclass:: Album .. automethod:: __init__ .. automethod:: item_dir .. automethod:: items Albums extend the normal model interface to also forward changes to their items: .. autoattribute:: item_keys .. automethod:: store .. automethod:: try_sync .. automethod:: move .. automethod:: remove Albums also manage album art, image files that are associated with each album: .. automethod:: set_art .. automethod:: move_art .. automethod:: art_destination Transactions '''''''''''' The :class:`Library` class provides the basic methods necessary to access and manipulate its contents. To perform more complicated operations atomically, or to interact directly with the underlying SQLite database, you must use a *transaction* (see this `blog post`_ for motivation). For example:: lib = Library() with lib.transaction() as tx: items = lib.items(query) lib.add_album(list(items)) .. _blog post: https://beets.io/blog/sqlite-nightmare.html .. currentmodule:: beets.dbcore.db .. autoclass:: Transaction :members: Queries ------- To access albums and items in a library, we use :doc:`/reference/query`. In beets, the :class:`Query` abstract base class represents a criterion that matches items or albums in the database. Every subclass of :class:`Query` must implement two methods, which implement two different ways of identifying matching items/albums. The ``clause()`` method should return an SQLite ``WHERE`` clause that matches appropriate albums/items. This allows for efficient batch queries. Correspondingly, the ``match(item)`` method should take an :class:`Item` object and return a boolean, indicating whether or not a specific item matches the criterion. This alternate implementation allows clients to determine whether items that have already been fetched from the database match the query. There are many different types of queries. Just as an example, :class:`FieldQuery` determines whether a certain field matches a certain value (an equality query). :class:`AndQuery` (like its abstract superclass, :class:`CollectionQuery`) takes a set of other query objects and bundles them together, matching only albums/items that match all constituent queries. Beets has a human-writable plain-text query syntax that can be parsed into :class:`Query` objects. Calling ``AndQuery.from_strings`` parses a list of query parts into a query object that can then be used with :class:`Library` objects. ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������beetbox-beets-01f1faf/docs/dev/plugins.rst����������������������������������������������������������0000664�0000000�0000000�00000063372�14723254774�0020703�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������.. _writing-plugins: Writing Plugins --------------- A beets plugin is just a Python module inside the ``beetsplug`` namespace package. (Check out this `Stack Overflow question about namespace packages`_ if you haven't heard of them.) So, to make one, create a directory called ``beetsplug`` and put two files in it: one called ``__init__.py`` and one called ``myawesomeplugin.py`` (but don't actually call it that). Your directory structure should look like this:: beetsplug/ __init__.py myawesomeplugin.py .. _Stack Overflow question about namespace packages: https://stackoverflow.com/questions/1675734/how-do-i-create-a-namespace-package-in-python/1676069#1676069 Then, you'll need to put this stuff in ``__init__.py`` to make ``beetsplug`` a namespace package:: from pkgutil import extend_path __path__ = extend_path(__path__, __name__) That's all for ``__init__.py``; you can can leave it alone. The meat of your plugin goes in ``myawesomeplugin.py``. There, you'll have to import the ``beets.plugins`` module and define a subclass of the ``BeetsPlugin`` class found therein. Here's a skeleton of a plugin file:: from beets.plugins import BeetsPlugin class MyPlugin(BeetsPlugin): pass Once you have your ``BeetsPlugin`` subclass, there's a variety of things your plugin can do. (Read on!) To use your new plugin, make sure the directory that contains your ``beetsplug`` directory is in the Python path (using ``PYTHONPATH`` or by installing in a `virtualenv`_, for example). Then, as described above, edit your ``config.yaml`` to include ``plugins: myawesomeplugin`` (substituting the name of the Python module containing your plugin). .. _virtualenv: https://pypi.org/project/virtualenv .. _add_subcommands: Add Commands to the CLI ^^^^^^^^^^^^^^^^^^^^^^^ Plugins can add new subcommands to the ``beet`` command-line interface. Define the plugin class' ``commands()`` method to return a list of ``Subcommand`` objects. (The ``Subcommand`` class is defined in the ``beets.ui`` module.) Here's an example plugin that adds a simple command:: from beets.plugins import BeetsPlugin from beets.ui import Subcommand my_super_command = Subcommand('super', help='do something super') def say_hi(lib, opts, args): print "Hello everybody! I'm a plugin!" my_super_command.func = say_hi class SuperPlug(BeetsPlugin): def commands(self): return [my_super_command] To make a subcommand, invoke the constructor like so: ``Subcommand(name, parser, help, aliases)``. The ``name`` parameter is the only required one and should just be the name of your command. ``parser`` can be an `OptionParser instance`_, but it defaults to an empty parser (you can extend it later). ``help`` is a description of your command, and ``aliases`` is a list of shorthand versions of your command name. .. _OptionParser instance: https://docs.python.org/library/optparse.html You'll need to add a function to your command by saying ``mycommand.func = myfunction``. This function should take the following parameters: ``lib`` (a beets ``Library`` object) and ``opts`` and ``args`` (command-line options and arguments as returned by `OptionParser.parse_args`_). .. _OptionParser.parse_args: https://docs.python.org/library/optparse.html#parsing-arguments The function should use any of the utility functions defined in ``beets.ui``. Try running ``pydoc beets.ui`` to see what's available. You can add command-line options to your new command using the ``parser`` member of the ``Subcommand`` class, which is a ``CommonOptionsParser`` instance. Just use it like you would a normal ``OptionParser`` in an independent script. Note that it offers several methods to add common options: ``--album``, ``--path`` and ``--format``. This feature is versatile and extensively documented, try ``pydoc beets.ui.CommonOptionsParser`` for more information. .. _plugin_events: Listen for Events ^^^^^^^^^^^^^^^^^ Event handlers allow plugins to run code whenever something happens in beets' operation. For instance, a plugin could write a log message every time an album is successfully autotagged or update MPD's index whenever the database is changed. You can "listen" for events using ``BeetsPlugin.register_listener``. Here's an example:: from beets.plugins import BeetsPlugin def loaded(): print 'Plugin loaded!' class SomePlugin(BeetsPlugin): def __init__(self): super().__init__() self.register_listener('pluginload', loaded) Note that if you want to access an attribute of your plugin (e.g. ``config`` or ``log``) you'll have to define a method and not a function. Here is the usual registration process in this case:: from beets.plugins import BeetsPlugin class SomePlugin(BeetsPlugin): def __init__(self): super().__init__() self.register_listener('pluginload', self.loaded) def loaded(self): self._log.info('Plugin loaded!') The events currently available are: * `pluginload`: called after all the plugins have been loaded after the ``beet`` command starts * `import`: called after a ``beet import`` command finishes (the ``lib`` keyword argument is a Library object; ``paths`` is a list of paths (strings) that were imported) * `album_imported`: called with an ``Album`` object every time the ``import`` command finishes adding an album to the library. Parameters: ``lib``, ``album`` * `album_removed`: called with an ``Album`` object every time an album is removed from the library (even when its file is not deleted from disk). * `item_copied`: called with an ``Item`` object whenever its file is copied. Parameters: ``item``, ``source`` path, ``destination`` path * `item_imported`: called with an ``Item`` object every time the importer adds a singleton to the library (not called for full-album imports). Parameters: ``lib``, ``item`` * `before_item_moved`: called with an ``Item`` object immediately before its file is moved. Parameters: ``item``, ``source`` path, ``destination`` path * `item_moved`: called with an ``Item`` object whenever its file is moved. Parameters: ``item``, ``source`` path, ``destination`` path * `item_linked`: called with an ``Item`` object whenever a symlink is created for a file. Parameters: ``item``, ``source`` path, ``destination`` path * `item_hardlinked`: called with an ``Item`` object whenever a hardlink is created for a file. Parameters: ``item``, ``source`` path, ``destination`` path * `item_reflinked`: called with an ``Item`` object whenever a reflink is created for a file. Parameters: ``item``, ``source`` path, ``destination`` path * `item_removed`: called with an ``Item`` object every time an item (singleton or album's part) is removed from the library (even when its file is not deleted from disk). * `write`: called with an ``Item`` object, a ``path``, and a ``tags`` dictionary just before a file's metadata is written to disk (i.e., just before the file on disk is opened). Event handlers may change the ``tags`` dictionary to customize the tags that are written to the media file. Event handlers may also raise a ``library.FileOperationError`` exception to abort the write operation. Beets will catch that exception, print an error message and continue. * `after_write`: called with an ``Item`` object after a file's metadata is written to disk (i.e., just after the file on disk is closed). * `import_task_created`: called immediately after an import task is initialized. Plugins can use this to, for example, change imported files of a task before anything else happens. It's also possible to replace the task with another task by returning a list of tasks. This list can contain zero or more `ImportTask`s. Returning an empty list will stop the task. Parameters: ``task`` (an `ImportTask`) and ``session`` (an `ImportSession`). * `import_task_start`: called when before an import task begins processing. Parameters: ``task`` and ``session``. * `import_task_apply`: called after metadata changes have been applied in an import task. This is called on the same thread as the UI, so use this sparingly and only for tasks that can be done quickly. For most plugins, an import pipeline stage is a better choice (see :ref:`plugin-stage`). Parameters: ``task`` and ``session``. * `import_task_before_choice`: called after candidate search for an import task before any decision is made about how/if to import or tag. Can be used to present information about the task or initiate interaction with the user before importing occurs. Return an importer action to take a specific action. Only one handler may return a non-None result. Parameters: ``task`` and ``session`` * `import_task_choice`: called after a decision has been made about an import task. This event can be used to initiate further interaction with the user. Use ``task.choice_flag`` to determine or change the action to be taken. Parameters: ``task`` and ``session``. * `import_task_files`: called after an import task finishes manipulating the filesystem (copying and moving files, writing metadata tags). Parameters: ``task`` and ``session``. * `library_opened`: called after beets starts up and initializes the main Library object. Parameter: ``lib``. * `database_change`: a modification has been made to the library database. The change might not be committed yet. Parameters: ``lib`` and ``model``. * `cli_exit`: called just before the ``beet`` command-line program exits. Parameter: ``lib``. * `import_begin`: called just before a ``beet import`` session starts up. Parameter: ``session``. * `trackinfo_received`: called after metadata for a track item has been fetched from a data source, such as MusicBrainz. You can modify the tags that the rest of the pipeline sees on a ``beet import`` operation or during later adjustments, such as ``mbsync``. Slow handlers of the event can impact the operation, since the event is fired for any fetched possible match `before` the user (or the autotagger machinery) gets to see the match. Parameter: ``info``. * `albuminfo_received`: like `trackinfo_received`, the event indicates new metadata for album items. The parameter is an ``AlbumInfo`` object instead of a ``TrackInfo``. Parameter: ``info``. * `before_choose_candidate`: called before the user is prompted for a decision during a ``beet import`` interactive session. Plugins can use this event for :ref:`appending choices to the prompt <append_prompt_choices>` by returning a list of ``PromptChoices``. Parameters: ``task`` and ``session``. * `mb_track_extract`: called after the metadata is obtained from MusicBrainz. The parameter is a ``dict`` containing the tags retrieved from MusicBrainz for a track. Plugins must return a new (potentially empty) ``dict`` with additional ``field: value`` pairs, which the autotagger will apply to the item, as flexible attributes if ``field`` is not a hardcoded field. Fields already present on the track are overwritten. Parameter: ``data`` * `mb_album_extract`: Like `mb_track_extract`, but for album tags. Overwrites tags set at the track level, if they have the same ``field``. Parameter: ``data`` The included ``mpdupdate`` plugin provides an example use case for event listeners. Extend the Autotagger ^^^^^^^^^^^^^^^^^^^^^ Plugins can also enhance the functionality of the autotagger. For a comprehensive example, try looking at the ``chroma`` plugin, which is included with beets. A plugin can extend three parts of the autotagger's process: the track distance function, the album distance function, and the initial MusicBrainz search. The distance functions determine how "good" a match is at the track and album levels; the initial search controls which candidates are presented to the matching algorithm. Plugins implement these extensions by implementing four methods on the plugin class: * ``track_distance(self, item, info)``: adds a component to the distance function (i.e., the similarity metric) for individual tracks. ``item`` is the track to be matched (an Item object) and ``info`` is the TrackInfo object that is proposed as a match. Should return a ``(dist, dist_max)`` pair of floats indicating the distance. * ``album_distance(self, items, album_info, mapping)``: like the above, but compares a list of items (representing an album) to an album-level MusicBrainz entry. ``items`` is a list of Item objects; ``album_info`` is an AlbumInfo object; and ``mapping`` is a dictionary that maps Items to their corresponding TrackInfo objects. * ``candidates(self, items, artist, album, va_likely)``: given a list of items comprised by an album to be matched, return a list of ``AlbumInfo`` objects for candidate albums to be compared and matched. * ``item_candidates(self, item, artist, album)``: given a *singleton* item, return a list of ``TrackInfo`` objects for candidate tracks to be compared and matched. * ``album_for_id(self, album_id)``: given an ID from user input or an album's tags, return a candidate AlbumInfo object (or None). * ``track_for_id(self, track_id)``: given an ID from user input or a file's tags, return a candidate TrackInfo object (or None). When implementing these functions, you may want to use the functions from the ``beets.autotag`` and ``beets.autotag.mb`` modules, both of which have somewhat helpful docstrings. Read Configuration Options ^^^^^^^^^^^^^^^^^^^^^^^^^^ Plugins can configure themselves using the ``config.yaml`` file. You can read configuration values in two ways. The first is to use `self.config` within your plugin class. This gives you a view onto the configuration values in a section with the same name as your plugin's module. For example, if your plugin is in ``greatplugin.py``, then `self.config` will refer to options under the ``greatplugin:`` section of the config file. For example, if you have a configuration value called "foo", then users can put this in their ``config.yaml``:: greatplugin: foo: bar To access this value, say ``self.config['foo'].get()`` at any point in your plugin's code. The `self.config` object is a *view* as defined by the `Confuse`_ library. .. _Confuse: https://confuse.readthedocs.io/en/latest/ If you want to access configuration values *outside* of your plugin's section, import the `config` object from the `beets` module. That is, just put ``from beets import config`` at the top of your plugin and access values from there. If your plugin provides configuration values for sensitive data (e.g., passwords, API keys, ...), you should add these to the config so they can be redacted automatically when users dump their config. This can be done by setting each value's `redact` flag, like so:: self.config['password'].redact = True Add Path Format Functions and Fields ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Beets supports *function calls* in its path format syntax (see :doc:`/reference/pathformat`). Beets includes a few built-in functions, but plugins can register new functions by adding them to the ``template_funcs`` dictionary. Here's an example:: class MyPlugin(BeetsPlugin): def __init__(self): super().__init__() self.template_funcs['initial'] = _tmpl_initial def _tmpl_initial(text): if text: return text[0].upper() else: return u'' This plugin provides a function ``%initial`` to path templates where ``%initial{$artist}`` expands to the artist's initial (its capitalized first character). Plugins can also add template *fields*, which are computed values referenced as ``$name`` in templates. To add a new field, add a function that takes an ``Item`` object to the ``template_fields`` dictionary on the plugin object. Here's an example that adds a ``$disc_and_track`` field:: class MyPlugin(BeetsPlugin): def __init__(self): super().__init__() self.template_fields['disc_and_track'] = _tmpl_disc_and_track def _tmpl_disc_and_track(item): """Expand to the disc number and track number if this is a multi-disc release. Otherwise, just expands to the track number. """ if item.disctotal > 1: return u'%02i.%02i' % (item.disc, item.track) else: return u'%02i' % (item.track) With this plugin enabled, templates can reference ``$disc_and_track`` as they can any standard metadata field. This field works for *item* templates. Similarly, you can register *album* template fields by adding a function accepting an ``Album`` argument to the ``album_template_fields`` dict. Extend MediaFile ^^^^^^^^^^^^^^^^ `MediaFile`_ is the file tag abstraction layer that beets uses to make cross-format metadata manipulation simple. Plugins can add fields to MediaFile to extend the kinds of metadata that they can easily manage. The ``MediaFile`` class uses ``MediaField`` descriptors to provide access to file tags. If you have created a descriptor you can add it through your plugins ``add_media_field()`` method. .. automethod:: beets.plugins.BeetsPlugin.add_media_field .. _MediaFile: https://mediafile.readthedocs.io/en/latest/ Here's an example plugin that provides a meaningless new field "foo":: class FooPlugin(BeetsPlugin): def __init__(self): field = mediafile.MediaField( mediafile.MP3DescStorageStyle(u'foo'), mediafile.StorageStyle(u'foo') ) self.add_media_field('foo', field) FooPlugin() item = Item.from_path('/path/to/foo/tag.mp3') assert item['foo'] == 'spam' item['foo'] == 'ham' item.write() # The "foo" tag of the file is now "ham" .. _plugin-stage: Add Import Pipeline Stages ^^^^^^^^^^^^^^^^^^^^^^^^^^ Many plugins need to add high-latency operations to the import workflow. For example, a plugin that fetches lyrics from the Web would, ideally, not block the progress of the rest of the importer. Beets allows plugins to add stages to the parallel import pipeline. Each stage is run in its own thread. Plugin stages run after metadata changes have been applied to a unit of music (album or track) and before file manipulation has occurred (copying and moving files, writing tags to disk). Multiple stages run in parallel but each stage processes only one task at a time and each task is processed by only one stage at a time. Plugins provide stages as functions that take two arguments: ``config`` and ``task``, which are ``ImportSession`` and ``ImportTask`` objects (both defined in ``beets.importer``). Add such a function to the plugin's ``import_stages`` field to register it:: from beets.plugins import BeetsPlugin class ExamplePlugin(BeetsPlugin): def __init__(self): super().__init__() self.import_stages = [self.stage] def stage(self, session, task): print('Importing something!') It is also possible to request your function to run early in the pipeline by adding the function to the plugin's ``early_import_stages`` field instead:: self.early_import_stages = [self.stage] .. _extend-query: Extend the Query Syntax ^^^^^^^^^^^^^^^^^^^^^^^ You can add new kinds of queries to beets' :doc:`query syntax </reference/query>`. There are two ways to add custom queries: using a prefix and using a name. Prefix-based query extension can apply to *any* field, while named queries are not associated with any field. For example, beets already supports regular expression queries, which are indicated by a colon prefix---plugins can do the same. For either kind of query extension, define a subclass of the ``Query`` type from the ``beets.dbcore.query`` module. Then: - To define a prefix-based query, define a ``queries`` method in your plugin class. Return from this method a dictionary mapping prefix strings to query classes. - To define a named query, defined dictionaries named either ``item_queries`` or ``album_queries``. These should map names to query types. So if you use ``{ "foo": FooQuery }``, then the query ``foo:bar`` will construct a query like ``FooQuery("bar")``. For prefix-based queries, you will want to extend ``FieldQuery``, which implements string comparisons on fields. To use it, create a subclass inheriting from that class and override the ``value_match`` class method. (Remember the ``@classmethod`` decorator!) The following example plugin declares a query using the ``@`` prefix to delimit exact string matches. The plugin will be used if we issue a command like ``beet ls @something`` or ``beet ls artist:@something``:: from beets.plugins import BeetsPlugin from beets.dbcore import FieldQuery class ExactMatchQuery(FieldQuery): @classmethod def value_match(self, pattern, val): return pattern == val class ExactMatchPlugin(BeetsPlugin): def queries(self): return { '@': ExactMatchQuery } Flexible Field Types ^^^^^^^^^^^^^^^^^^^^ If your plugin uses flexible fields to store numbers or other non-string values, you can specify the types of those fields. A rating plugin, for example, might want to declare that the ``rating`` field should have an integer type:: from beets.plugins import BeetsPlugin from beets.dbcore import types class RatingPlugin(BeetsPlugin): item_types = {'rating': types.INTEGER} @property def album_types(self): return {'rating': types.INTEGER} A plugin may define two attributes: `item_types` and `album_types`. Each of those attributes is a dictionary mapping a flexible field name to a type instance. You can find the built-in types in the `beets.dbcore.types` and `beets.library` modules or implement your own type by inheriting from the `Type` class. Specifying types has several advantages: * Code that accesses the field like ``item['my_field']`` gets the right type (instead of just a string). * You can use advanced queries (like :ref:`ranges <numericquery>`) from the command line. * User input for flexible fields may be validated and converted. .. _plugin-logging: Logging ^^^^^^^ Each plugin object has a ``_log`` attribute, which is a ``Logger`` from the `standard Python logging module`_. The logger is set up to `PEP 3101`_, str.format-style string formatting. So you can write logging calls like this:: self._log.debug(u'Processing {0.title} by {0.artist}', item) .. _PEP 3101: https://www.python.org/dev/peps/pep-3101/ .. _standard Python logging module: https://docs.python.org/2/library/logging.html When beets is in verbose mode, plugin messages are prefixed with the plugin name to make them easier to see. Which messages will be logged depends on the logging level and the action performed: * Inside import stages and event handlers, the default is ``WARNING`` messages and above. * Everywhere else, the default is ``INFO`` or above. The verbosity can be increased with ``--verbose`` (``-v``) flags: each flags lowers the level by a notch. That means that, with a single ``-v`` flag, event handlers won't have their ``DEBUG`` messages displayed, but command functions (for example) will. With ``-vv`` on the command line, ``DEBUG`` messages will be displayed everywhere. This addresses a common pattern where plugins need to use the same code for a command and an import stage, but the command needs to print more messages than the import stage. (For example, you'll want to log "found lyrics for this song" when you're run explicitly as a command, but you don't want to noisily interrupt the importer interface when running automatically.) .. _append_prompt_choices: Append Prompt Choices ^^^^^^^^^^^^^^^^^^^^^ Plugins can also append choices to the prompt presented to the user during an import session. To do so, add a listener for the ``before_choose_candidate`` event, and return a list of ``PromptChoices`` that represent the additional choices that your plugin shall expose to the user:: from beets.plugins import BeetsPlugin from beets.ui.commands import PromptChoice class ExamplePlugin(BeetsPlugin): def __init__(self): super().__init__() self.register_listener('before_choose_candidate', self.before_choose_candidate_event) def before_choose_candidate_event(self, session, task): return [PromptChoice('p', 'Print foo', self.foo), PromptChoice('d', 'Do bar', self.bar)] def foo(self, session, task): print('User has chosen "Print foo"!') def bar(self, session, task): print('User has chosen "Do bar"!') The previous example modifies the standard prompt:: # selection (default 1), Skip, Use as-is, as Tracks, Group albums, Enter search, enter Id, aBort? by appending two additional options (``Print foo`` and ``Do bar``):: # selection (default 1), Skip, Use as-is, as Tracks, Group albums, Enter search, enter Id, aBort, Print foo, Do bar? If the user selects a choice, the ``callback`` attribute of the corresponding ``PromptChoice`` will be called. It is the responsibility of the plugin to check for the status of the import session and decide the choices to be appended: for example, if a particular choice should only be presented if the album has no candidates, the relevant checks against ``task.candidates`` should be performed inside the plugin's ``before_choose_candidate_event`` accordingly. Please make sure that the short letter for each of the choices provided by the plugin is not already in use: the importer will emit a warning and discard all but one of the choices using the same letter, giving priority to the core importer prompt choices. As a reference, the following characters are used by the choices on the core importer prompt, and hence should not be used: ``a``, ``s``, ``u``, ``t``, ``g``, ``e``, ``i``, ``b``. Additionally, the callback function can optionally specify the next action to be performed by returning a ``importer.action`` value. It may also return a ``autotag.Proposal`` value to update the set of current proposals to be considered. ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������beetbox-beets-01f1faf/docs/faq.rst������������������������������������������������������������������0000664�0000000�0000000�00000034055�14723254774�0017207�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������FAQ ### Here are some answers to frequently-asked questions from IRC and elsewhere. Got a question that isn't answered here? Try the `discussion board`_, or :ref:`filing an issue <bugs>` in the bug tracker. .. _mailing list: https://groups.google.com/group/beets-users .. _discussion board: https://github.com/beetbox/beets/discussions/ .. contents:: :local: :depth: 2 How do I… ========= .. _move: …rename my files according to a new path format configuration? -------------------------------------------------------------- Just run the :ref:`move-cmd` command. Use a :doc:`query </reference/query>` to rename a subset of your music or leave the query off to rename everything. .. _asispostfacto: …find all the albums I imported "as-is"? ---------------------------------------- Enable the :ref:`import log <import_log>` to automatically record whenever you skip an album or accept one "as-is". Alternatively, you can find all the albums in your library that are missing MBIDs using a command like this:: beet ls -a mb_albumid::^$ Assuming your files didn't have MBIDs already, then this will roughly correspond to those albums that didn't get autotagged. .. _discdir: …create "Disc N" directories for multi-disc albums? --------------------------------------------------- Use the :doc:`/plugins/inline` along with the ``%if{}`` function to accomplish this:: plugins: inline paths: default: $albumartist/$album%aunique{}/%if{$multidisc,Disc $disc/}$track $title item_fields: multidisc: 1 if disctotal > 1 else 0 This ``paths`` configuration only contains the ``default`` key: it leaves the ``comp`` and ``singleton`` keys as their default values, as documented in :ref:`path-format-config`. To create "Disc N" directories for compilations and singletons, you will need to specify similar templates for those keys as well. .. _multidisc: …import a multi-disc album? --------------------------- As of 1.0b11, beets tags multi-disc albums as a *single unit*. To get a good match, it needs to treat all of the album's parts together as a single release. To help with this, the importer uses a simple heuristic to guess when a directory represents a multi-disc album that's been divided into multiple subdirectories. When it finds a situation like this, it collapses all of the items in the subdirectories into a single release for tagging. The heuristic works by looking at the names of directories. If multiple subdirectories of a common parent directory follow the pattern "(title) disc (number) (...)" and the *prefix* (everything up to the number) is the same, the directories are collapsed together. One of the key words "disc" or "CD" must be present to make this work. If you have trouble tagging a multi-disc album, consider the ``--flat`` flag (which treats a whole tree as a single album) or just putting all the tracks into a single directory to force them to be tagged together. .. _mbid: …enter a MusicBrainz ID? ------------------------ An MBID looks like one of these: - ``https://musicbrainz.org/release/ded77dcf-7279-457e-955d-625bd3801b87`` - ``d569deba-8c6b-4d08-8c43-d0e5a1b8c7f3`` Beets can recognize either the hex-with-dashes UUID-style string or the full URL that contains it (as of 1.0b11). You can get these IDs by `searching on the MusicBrainz web site <https://musicbrainz.org/>`__ and going to a *release* page (when tagging full albums) or a *recording* page (when tagging singletons). Then, copy the URL of the page and paste it into beets. Note that MusicBrainz has both "releases" and "release groups," which link together different versions of the same album. Use *release* IDs here. .. _upgrade: …upgrade to the latest version of beets? ---------------------------------------- Run a command like this:: pip install -U beets The ``-U`` flag tells `pip`_ to upgrade beets to the latest version. If you want a specific version, you can specify with using ``==`` like so:: pip install beets==1.0rc2 .. _src: …run the latest source version of beets? ---------------------------------------- Beets sees regular releases (about every six weeks or so), but sometimes it's helpful to run on the "bleeding edge". To run the latest source: 1. Uninstall beets. If you installed using ``pip``, you can just run ``pip uninstall beets``. 2. Install from source. Choose one of these methods: - Directly from GitHub using ``python -m pip install git+https://github.com/beetbox/beets.git`` command. Depending on your system, you may need to use ``pip3`` and ``python3`` instead of ``pip`` and ``python`` respectively. - Use ``pip`` to install the latest snapshot tarball. Type: ``pip install https://github.com/beetbox/beets/tarball/master`` - Use ``pip`` to install an "editable" version of beets based on an automatic source checkout. For example, run ``pip install -e git+https://github.com/beetbox/beets#egg=beets`` to clone beets and install it, allowing you to modify the source in-place to try out changes. - Clone source code and install it in editable mode .. code-block:: shell git clone https://github.com/beetbox/beets.git poetry install This approach lets you decide where the source is stored, with any changes immediately reflected in your environment. More details about the beets source are available on the :doc:`developer documentation </dev/index>` pages. .. _bugs: …report a bug in beets? ----------------------- We use the `issue tracker <https://github.com/beetbox/beets/issues>`__ on GitHub. `Enter a new issue <https://github.com/beetbox/beets/issues/new>`__ there to report a bug. Please follow these guidelines when reporting an issue: - Most importantly: if beets is crashing, please `include the traceback <https://imgur.com/jacoj>`__. Tracebacks can be more readable if you put them in a pastebin (e.g., `Gist <https://gist.github.com/>`__ or `Hastebin <https://hastebin.com/>`__), especially when communicating over IRC or email. - Turn on beets' debug output (using the -v option: for example, ``beet -v import ...``) and include that with your bug report. Look through this verbose output for any red flags that might point to the problem. - If you can, try installing the latest beets source code to see if the bug is fixed in an unreleased version. You can also look at the :doc:`latest changelog entries </changelog>` for descriptions of the problem you're seeing. - Try to narrow your problem down to something specific. Is a particular plugin causing the problem? (You can disable plugins to see whether the problem goes away.) Is a some music file or a single album leading to the crash? (Try importing individual albums to determine which one is causing the problem.) Is some entry in your configuration file causing it? Et cetera. - If you do narrow the problem down to a particular audio file or album, include it with your bug report so the developers can run tests. If you've never reported a bug before, Mozilla has some well-written `general guidelines for good bug reports`_. .. _general guidelines for good bug reports: https://developer.mozilla.org/en-US/docs/Mozilla/QA/Bug_writing_guidelines .. _find-config: …find the configuration file (config.yaml)? ------------------------------------------- You create this file yourself; beets just reads it. See :doc:`/reference/config`. .. _special-chars: …avoid using special characters in my filenames? ------------------------------------------------ Use the ``%asciify{}`` function in your path formats. See :ref:`template-functions`. .. _move-dir: …point beets at a new music directory? -------------------------------------- If you want to move your music from one directory to another, the best way is to let beets do it for you. First, edit your configuration and set the ``directory`` setting to the new place. Then, type ``beet move`` to have beets move all your files. If you've already moved your music *outside* of beets, you have a few options: - Move the music back (with an ordinary ``mv``) and then use the above steps. - Delete your database and re-create it from the new paths using ``beet import -AWC``. - Resort to manually modifying the SQLite database (not recommended). Why does beets… =============== .. _nomatch: …complain that it can't find a match? ------------------------------------- There are a number of possibilities: - First, make sure the album is in `the MusicBrainz database <https://musicbrainz.org/>`__. You can search on their site to make sure it's cataloged there. (If not, anyone can edit MusicBrainz---so consider adding the data yourself.) - If the album in question is a multi-disc release, see the relevant FAQ answer above. - The music files' metadata might be insufficient. Try using the "enter search" or "enter ID" options to help the matching process find the right MusicBrainz entry. - If you have a lot of files that are missing metadata, consider using :doc:`acoustic fingerprinting </plugins/chroma>` or :doc:`filename-based guesses </plugins/fromfilename>` for that music. If none of these situations apply and you're still having trouble tagging something, please :ref:`file a bug report <bugs>`. .. _plugins: …appear to be missing some plugins? ----------------------------------- Please make sure you're using the latest version of beets---you might be using a version earlier than the one that introduced the plugin. In many cases, the plugin may be introduced in beets "trunk" (the latest source version) and might not be released yet. Take a look at :doc:`the changelog </changelog>` to see which version added the plugin. (You can type ``beet version`` to check which version of beets you have installed.) If you want to live on the bleeding edge and use the latest source version of beets, you can check out the source (see :ref:`the relevant question <src>`). To see the beets documentation for your version (and avoid confusion with new features in trunk), select your version from the menu in the sidebar. .. _kill: …ignore control-C during an import? ----------------------------------- Typing a ^C (control-C) control sequence will not halt beets' multithreaded importer while it is waiting at a prompt for user input. Instead, hit "return" (dismissing the prompt) after typing ^C. Alternatively, just type a "b" for "aBort" at most prompts. Typing ^C *will* work if the importer interface is between prompts. Also note that beets may take some time to quit after ^C is typed; it tries to clean up after itself briefly even when canceled. (For developers: this is because the UI thread is blocking on ``input`` and cannot be interrupted by the main thread, which is trying to close all pipeline stages in the exception handler by setting a flag. There is no simple way to remedy this.) .. _id3v24: …not change my ID3 tags? ------------------------ Beets writes `ID3v2.4`_ tags by default. Some software, including Windows (i.e., Windows Explorer and Windows Media Player) and `id3lib/id3v2 <http://id3v2.sourceforge.net/>`__, don't support v2.4 tags. When using 2.4-unaware software, it might look like the tags are unmodified or missing completely. To enable ID3v2.3 tags, enable the :ref:`id3v23` config option. .. _invalid: .. _ID3v2.4: https://id3.org/id3v2.4.0-structure …complain that a file is "unreadable"? -------------------------------------- Beets will log a message like "unreadable file: /path/to/music.mp3" when it encounters files that *look* like music files (according to their extension) but seem to be broken. Most of the time, this is because the file is corrupted. To check whether the file is intact, try opening it in another media player (e.g., `VLC <https://www.videolan.org/vlc/index.html>`__) to see whether it can read the file. You can also use specialized programs for checking file integrity---for example, type ``metaflac --list music.flac`` to check FLAC files. If beets still complains about a file that seems to be valid, `file a bug <https://github.com/beetbox/beets/issues/new>`__ and we'll look into it. There's always a possibility that there's a bug "upstream" in the `Mutagen <https://github.com/quodlibet/mutagen>`__ library used by beets, in which case we'll forward the bug to that project's tracker. .. _importhang: …seem to "hang" after an import finishes? ----------------------------------------- Probably not. Beets uses a *multithreaded importer* that overlaps many different activities: it can prompt you for decisions while, in the background, it talks to MusicBrainz and copies files. This means that, even after you make your last decision, there may be a backlog of files to be copied into place and tags to be written. (Plugin tasks, like looking up lyrics and genres, also run at this time.) If beets pauses after you see all the albums go by, have patience. .. _replaceq: …put a bunch of underscores in my filenames? -------------------------------------------- When naming files, beets replaces certain characters to avoid causing problems on the filesystem. For example, leading dots can confusingly hide files on Unix and several non-alphanumeric characters are forbidden on Windows. The :ref:`replace` config option controls which replacements are made. By default, beets makes filenames safe for all known platforms by replacing several patterns with underscores. This means that, even on Unix, filenames are made Windows-safe so that network filesystems (such as SMB) can be used safely. Most notably, Windows forbids trailing dots, so a folder called "M.I.A." will be rewritten to "M.I.A\_" by default. Change the ``replace`` config if you don't want this behavior and don't need Windows-safe names. .. _pathq: …say "command not found"? ------------------------- You need to put the ``beet`` program on your system's search path. If you installed using pip, the command ``pip show -f beets`` can show you where ``beet`` was placed on your system. If you need help extending your ``$PATH``, try `this Super User answer`_. .. _this Super User answer: https://superuser.com/a/284361/4569 .. _pip: https://pip.pypa.io/en/stable/ �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������beetbox-beets-01f1faf/docs/guides/������������������������������������������������������������������0000775�0000000�0000000�00000000000�14723254774�0017157�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������beetbox-beets-01f1faf/docs/guides/advanced.rst������������������������������������������������������0000664�0000000�0000000�00000017500�14723254774�0021461�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������Advanced Awesomeness ==================== So you have beets up and running and you've started :doc:`importing your music </guides/tagger>`. There's a lot more that beets can do now that it has cataloged your collection. Here's a few features to get you started. Most of these tips involve :doc:`plugins </plugins/index>` and fiddling with beets' :doc:`configuration </reference/config>`. So use your favorite text editor to create a config file before you continue. Fetch album art, genres, and lyrics ----------------------------------- Beets can help you fill in more than just the basic taxonomy metadata that comes from MusicBrainz. Plugins can provide :doc:`album art </plugins/fetchart>`, :doc:`lyrics </plugins/lyrics>`, and :doc:`genres </plugins/lastgenre>` from databases around the Web. If you want beets to get any of this data automatically during the import process, just enable any of the three relevant plugins (see :doc:`/plugins/index`). For example, put this line in your :doc:`config file </reference/config>` to enable all three:: plugins: fetchart lyrics lastgenre Each plugin also has a command you can run to fetch data manually. For example, if you want to get lyrics for all the Beatles tracks in your collection, just type ``beet lyrics beatles`` after enabling the plugin. Read more about using each of these plugins: * :doc:`/plugins/fetchart` (and its accompanying :doc:`/plugins/embedart`) * :doc:`/plugins/lyrics` * :doc:`/plugins/lastgenre` Customize your file and folder names ------------------------------------ Beets uses an extremely flexible template system to name the folders and files that organize your music in your filesystem. Take a look at :ref:`path-format-config` for the basics: use fields like ``$year`` and ``$title`` to build up a naming scheme. But if you need more flexibility, there are two features you need to know about: * :ref:`Template functions <template-functions>` are simple expressions you can use in your path formats to add logic to your names. For example, you can get an artist's first initial using ``%upper{%left{$albumartist,1}}``. * If you need more flexibility, the :doc:`/plugins/inline` lets you write snippets of Python code that generate parts of your filenames. The equivalent code for getting an artist initial with the *inline* plugin looks like ``initial: albumartist[0].upper()``. If you already have music in your library and want to update their names according to a new scheme, just run the :ref:`move-cmd` command to rename everything. Stream your music to another computer ------------------------------------- Sometimes it can be really convenient to store your music on one machine and play it on another. For example, I like to keep my music on a server at home, but play it at work (without copying my whole library locally). The :doc:`/plugins/web` makes streaming your music easy---it's sort of like having your own personal Spotify. First, enable the ``web`` plugin (see :doc:`/plugins/index`). Run the server by typing ``beet web`` and head to http://localhost:8337 in a browser. You can browse your collection with queries and, if your browser supports it, play music using HTML5 audio. Transcode music files for media players --------------------------------------- Do you ever find yourself transcoding high-quality rips to a lower-bitrate, lossy format for your phone or music player? Beets can help with that. You'll first need to install `ffmpeg`_. Then, enable beets' :doc:`/plugins/convert`. Set a destination directory in your :doc:`config file </reference/config>` like so:: convert: dest: ~/converted_music Then, use the command ``beet convert QUERY`` to transcode everything matching the query and drop the resulting files in that directory, named according to your path formats. For example, ``beet convert long winters`` will move over everything by the Long Winters for listening on the go. The plugin has many more dials you can fiddle with to get your conversions how you like them. Check out :doc:`its documentation </plugins/convert>`. .. _ffmpeg: https://www.ffmpeg.org Store any data you like ----------------------- The beets database keeps track of a long list of :ref:`built-in fields <itemfields>`, but you're not limited to just that list. Say, for example, that you like to categorize your music by the setting where it should be played. You can invent a new ``context`` attribute to store this. Set the field using the :ref:`modify-cmd` command:: beet modify context=party artist:'beastie boys' By default, beets will show you the changes that are about to be applied and ask if you really want to apply them to all, some or none of the items or albums. You can type y for "yes", n for "no", or s for "select". If you choose the latter, the command will prompt you for each individual matching item or album. Then :doc:`query </reference/query>` your music just as you would with any other field:: beet ls context:mope You can even use these fields in your filenames (see :ref:`path-format-config`). And, unlike :ref:`built-in fields <itemfields>`, such fields can be removed:: beet modify context! artist:'beastie boys' Read more than you ever wanted to know about the *flexible attributes* feature `on the beets blog`_. .. _on the beets blog: https://beets.io/blog/flexattr.html Choose a path style manually for some music ------------------------------------------- Sometimes, you need to categorize some songs differently in your file system. For example, you might want to group together all the music you don't really like, but keep around to play for friends and family. This is, of course, impossible to determine automatically using metadata from MusicBrainz. Instead, use a flexible attribute (see above) to store a flag on the music you want to categorize, like so:: beet modify bad=1 christmas Then, you can query on this field in your path formats to sort this music differently. Put something like this in your configuration file:: paths: bad:1: Bad/$artist/$title Used together, flexible attributes and path format conditions let you sort your music by any criteria you can imagine. Automatically add new music to your library ------------------------------------------- As a command-line tool, beets is perfect for automated operation via a cron job or the like. To use it this way, you might want to use these options in your :doc:`config file </reference/config>`: .. code-block:: yaml import: incremental: yes quiet: yes log: /path/to/log.txt The :ref:`incremental` option will skip importing any directories that have been imported in the past. :ref:`quiet` avoids asking you any questions (since this will be run automatically, no input is possible). You might also want to use the :ref:`quiet_fallback` options to configure what should happen when no near-perfect match is found -- this option depends on your level of paranoia. Finally, :ref:`import_log` will make beets record its decisions so you can come back later and see what you need to handle manually. The last step is to set up cron or some other automation system to run ``beet import /path/to/incoming/music``. Useful reports -------------- Since beets has a quite powerful query tool, this list contains some useful and powerful queries to run on your library. * See a list of all albums which have files which are 128 bit rate:: beet list bitrate:128000 * See a list of all albums with the tracks listed in order of bit rate:: beet ls -f '$bitrate $artist - $title' bitrate+ * See a list of albums and their formats:: beet ls -f '$albumartist $album $format' | sort | uniq Note that ``beet ls --album -f '... $format'`` doesn't do what you want, because ``format`` is an item-level field, not an album-level one. If an album's tracks exist in multiple formats, the album will appear in the list once for each format. ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������beetbox-beets-01f1faf/docs/guides/index.rst���������������������������������������������������������0000664�0000000�0000000�00000000360�14723254774�0021017�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������Guides ====== This section contains a couple of walkthroughs that will help you get familiar with beets. If you're new to beets, you'll want to begin with the :doc:`main` guide. .. toctree:: :maxdepth: 1 main tagger advanced ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������beetbox-beets-01f1faf/docs/guides/main.rst����������������������������������������������������������0000664�0000000�0000000�00000032046�14723254774�0020642�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������Getting Started =============== Welcome to `beets`_! This guide will help you begin using it to make your music collection better. .. _beets: https://beets.io/ Installing ---------- You will need Python. Beets works on Python 3.8 or later. * **macOS** 11 (Big Sur) includes Python 3.8 out of the box. You can opt for a more recent Python installing it via `Homebrew`_ (``brew install python3``). There's also a `MacPorts`_ port. Run ``port install beets`` or ``port install beets-full`` to include many third-party plugins. * On **Debian or Ubuntu**, depending on the version, beets is available as an official package (`Debian details`_, `Ubuntu details`_), so try typing: ``apt-get install beets``. But the version in the repositories might lag behind, so make sure you read the right version of these docs. If you want the latest version, you can get everything you need to install with pip as described below by running: ``apt-get install python-dev python-pip`` * On **Arch Linux**, `beets is in [community] <Arch community_>`_, so just run ``pacman -S beets``. (There's also a bleeding-edge `dev package <AUR_>`_ in the AUR, which will probably set your computer on fire.) * On **Alpine Linux**, `beets is in the community repository <Alpine package_>`_ and can be installed with ``apk add beets``. * For **Gentoo Linux**, beets is in Portage as ``media-sound/beets``. Just run ``emerge beets`` to install. There are several USE flags available for optional plugin dependencies. * On **FreeBSD**, there's a `beets port <FreeBSD_>`_ at ``audio/beets``. * On **OpenBSD**, there's a `beets port <OpenBSD_>`_ can be installed with ``pkg_add beets``. * For **Slackware**, there's a `SlackBuild`_ available. * On **Fedora** 22 or later, there's a `DNF package`_ you can install with ``sudo dnf install beets beets-plugins beets-doc``. * On **Solus**, run ``eopkg install beets``. * On **NixOS**, there's a `package <NixOS_>`_ you can install with ``nix-env -i beets``. .. _DNF package: https://packages.fedoraproject.org/pkgs/beets/ .. _SlackBuild: https://slackbuilds.org/repository/14.2/multimedia/beets/ .. _FreeBSD: http://portsmon.freebsd.org/portoverview.py?category=audio&portname=beets .. _AUR: https://aur.archlinux.org/packages/beets-git/ .. _Debian details: https://tracker.debian.org/pkg/beets .. _Ubuntu details: https://launchpad.net/ubuntu/+source/beets .. _OpenBSD: http://openports.se/audio/beets .. _Arch community: https://www.archlinux.org/packages/community/any/beets/ .. _Alpine package: https://pkgs.alpinelinux.org/package/edge/community/x86_64/beets .. _NixOS: https://github.com/NixOS/nixpkgs/tree/master/pkgs/tools/audio/beets .. _MacPorts: https://www.macports.org If you have `pip`_, just say ``pip install beets`` (or ``pip install --user beets`` if you run into permissions problems). To install without pip, download beets from `its PyPI page`_ and run ``python setup.py install`` in the directory therein. .. _its PyPI page: https://pypi.org/project/beets/#files .. _pip: https://pip.pypa.io The best way to upgrade beets to a new version is by running ``pip install -U beets``. You may want to follow `@b33ts`_ on Twitter to hear about progress on new versions. .. _@b33ts: https://twitter.com/b33ts Installing by Hand on macOS 10.11 and Higher ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Starting with version 10.11 (El Capitan), macOS has a new security feature called `System Integrity Protection`_ (SIP) that prevents you from modifying some parts of the system. This means that some ``pip`` commands may fail with a permissions error. (You probably *won't* run into this if you've installed Python yourself with `Homebrew`_ or otherwise. You can also try `MacPorts`_.) If this happens, you can install beets for the current user only by typing ``pip install --user beets``. If you do that, you might want to add ``~/Library/Python/3.6/bin`` to your ``$PATH``. .. _System Integrity Protection: https://support.apple.com/en-us/HT204899 .. _Homebrew: https://brew.sh Installing on Windows ^^^^^^^^^^^^^^^^^^^^^ Installing beets on Windows can be tricky. Following these steps might help you get it right: 1. If you don't have it, `install Python`_ (you want at least Python 3.8). The installer should give you the option to "add Python to PATH." Check this box. If you do that, you can skip the next step. 2. If you haven't done so already, set your ``PATH`` environment variable to include Python and its scripts. To do so, open the "Settings" application, then access the "System" screen, then access the "About" tab, and then hit "Advanced system settings" located on the right side of the screen. This should open the "System Properties" screen, then select the "Advanced" tab, then hit the "Environmental Variables..." button, and then look for the PATH variable in the table. Add the following to the end of the variable's value: ``;C:\Python38;C:\Python38\Scripts``. You may need to adjust these paths to point to your Python installation. 3. Now install beets by running: ``pip install beets`` 4. You're all set! Type ``beet`` at the command prompt to make sure everything's in order. Windows users may also want to install a context menu item for importing files into beets. Download the `beets.reg`_ file and open it in a text file to make sure the paths to Python match your system. Then double-click the file add the necessary keys to your registry. You can then right-click a directory and choose "Import with beets". Because I don't use Windows myself, I may have missed something. If you have trouble or you have more detail to contribute here, please direct it to `the mailing list`_. .. _install Python: https://python.org/download/ .. _beets.reg: https://github.com/beetbox/beets/blob/master/extra/beets.reg .. _install pip: https://pip.pypa.io/en/stable/installing/ .. _get-pip.py: https://bootstrap.pypa.io/get-pip.py Configuring ----------- You'll want to set a few basic options before you start using beets. The :doc:`configuration </reference/config>` is stored in a text file. You can show its location by running ``beet config -p``, though it may not exist yet. Run ``beet config -e`` to edit the configuration in your favorite text editor. The file will start out empty, but here's good place to start:: directory: ~/music library: ~/data/musiclibrary.db Change that first path to a directory where you'd like to keep your music. Then, for ``library``, choose a good place to keep a database file that keeps an index of your music. (The config's format is `YAML`_. You'll want to configure your text editor to use spaces, not real tabs, for indentation. Also, ``~`` means your home directory in these paths, even on Windows.) The default configuration assumes you want to start a new organized music folder (that ``directory`` above) and that you'll *copy* cleaned-up music into that empty folder using beets' ``import`` command (see below). But you can configure beets to behave many other ways: * Start with a new empty directory, but *move* new music in instead of copying it (saving disk space). Put this in your config file:: import: move: yes * Keep your current directory structure; importing should never move or copy files but instead just correct the tags on music. Put the line ``copy: no`` under the ``import:`` heading in your config file to disable any copying or renaming. Make sure to point ``directory`` at the place where your music is currently stored. * Keep your current directory structure and *do not* correct files' tags: leave files completely unmodified on your disk. (Corrected tags will still be stored in beets' database, and you can use them to do renaming or tag changes later.) Put this in your config file:: import: copy: no write: no to disable renaming and tag-writing. There are approximately six million other configuration options you can set here, including the directory and file naming scheme. See :doc:`/reference/config` for a full reference. .. _YAML: https://yaml.org/ Importing Your Library ---------------------- The next step is to import your music files into the beets library database. Because this can involve modifying files and moving them around, data loss is always a possibility, so now would be a good time to make sure you have a recent backup of all your music. We'll wait. There are two good ways to bring your existing library into beets. You can either: (a) quickly bring all your files with all their current metadata into beets' database, or (b) use beets' highly-refined autotagger to find canonical metadata for every album you import. Option (a) is really fast, but option (b) makes sure all your songs' tags are exactly right from the get-go. The point about speed bears repeating: using the autotagger on a large library can take a very long time, and it's an interactive process. So set aside a good chunk of time if you're going to go that route. For more on the interactive tagging process, see :doc:`tagger`. If you've got time and want to tag all your music right once and for all, do this:: $ beet import /path/to/my/music (Note that by default, this command will *copy music into the directory you specified above*. If you want to use your current directory structure, set the ``import.copy`` config option.) To take the fast, un-autotagged path, just say:: $ beet import -A /my/huge/mp3/library Note that you just need to add ``-A`` for "don't autotag". Adding More Music ----------------- If you've ripped or... otherwise obtained some new music, you can add it with the ``beet import`` command, the same way you imported your library. Like so:: $ beet import ~/some_great_album This will attempt to autotag the new album (interactively) and add it to your library. There are, of course, more options for this command---just type ``beet help import`` to see what's available. Seeing Your Music ----------------- If you want to query your music library, the ``beet list`` (shortened to ``beet ls``) command is for you. You give it a :doc:`query string </reference/query>`, which is formatted something like a Google search, and it gives you a list of songs. Thus:: $ beet ls the magnetic fields The Magnetic Fields - Distortion - Three-Way The Magnetic Fields - Distortion - California Girls The Magnetic Fields - Distortion - Old Fools $ beet ls hissing gronlandic of Montreal - Hissing Fauna, Are You the Destroyer? - Gronlandic Edit $ beet ls bird The Knife - The Knife - Bird The Mae Shi - Terrorbird - Revelation Six $ beet ls album:bird The Mae Shi - Terrorbird - Revelation Six By default, a search term will match any of a handful of :ref:`common attributes <keywordquery>` of songs. (They're also implicitly joined by ANDs: a track must match *all* criteria in order to match the query.) To narrow a search term to a particular metadata field, just put the field before the term, separated by a : character. So ``album:bird`` only looks for ``bird`` in the "album" field of your songs. (Need to know more? :doc:`/reference/query/` will answer all your questions.) The ``beet list`` command also has an ``-a`` option, which searches for albums instead of songs:: $ beet ls -a forever Bon Iver - For Emma, Forever Ago Freezepop - Freezepop Forever There's also an ``-f`` option (for *format*) that lets you specify what gets displayed in the results of a search:: $ beet ls -a forever -f "[$format] $album ($year) - $artist - $title" [MP3] For Emma, Forever Ago (2009) - Bon Iver - Flume [AAC] Freezepop Forever (2011) - Freezepop - Harebrained Scheme In the format option, field references like `$format` and `$year` are filled in with data from each result. You can see a full list of available fields by running ``beet fields``. Beets also has a ``stats`` command, just in case you want to see how much music you have:: $ beet stats Tracks: 13019 Total time: 4.9 weeks Total size: 71.1 GB Artists: 548 Albums: 1094 Keep Playing ------------ This is only the beginning of your long and prosperous journey with beets. To keep learning, take a look at :doc:`advanced` for a sampling of what else is possible. You'll also want to glance over the :doc:`/reference/cli` page for a more detailed description of all of beets' functionality. (Like deleting music! That's important.) Also, check out :doc:`beets' plugins </plugins/index>`. The real power of beets is in its extensibility---with plugins, beets can do almost anything for your music collection. You can always get help using the ``beet help`` command. The plain ``beet help`` command lists all the available commands; then, for example, ``beet help import`` gives more specific help about the ``import`` command. If you need more of a walkthrough, you can read an illustrated one `on the beets blog <https://beets.io/blog/walkthrough.html>`_. Please let us know what you think of beets via `the discussion board`_ or `Mastodon`_. .. _the mailing list: https://groups.google.com/group/beets-users .. _the discussion board: https://github.com/beetbox/beets/discussions .. _mastodon: https://fosstodon.org/@beets ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������beetbox-beets-01f1faf/docs/guides/tagger.rst��������������������������������������������������������0000664�0000000�0000000�00000033667�14723254774�0021201�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������Using the Auto-Tagger ===================== Beets' automatic metadata correcter is sophisticated but complicated and cryptic. This is a guide to help you through its myriad inputs and options. An Apology and a Brief Interlude -------------------------------- I would like to sincerely apologize that the autotagger in beets is so fussy. It asks you a *lot* of complicated questions, insecurely asking that you verify nearly every assumption it makes. This means importing and correcting the tags for a large library can be an endless, tedious process. I'm sorry for this. Maybe it will help to think of it as a tradeoff. By carefully examining every album you own, you get to become more familiar with your library, its extent, its variation, and its quirks. People used to spend hours lovingly sorting and resorting their shelves of LPs. In the iTunes age, many of us toss our music into a heap and forget about it. This is great for some people. But there's value in intimate, complete familiarity with your collection. So instead of a chore, try thinking of correcting tags as quality time with your music collection. That's what I do. One practical piece of advice: because beets' importer runs in multiple threads, it queues up work in the background while it's waiting for you to respond. So if you find yourself waiting for beets for a few seconds between every question it asks you, try walking away from the computer for a while, making some tea, and coming back. Beets will have a chance to catch up with you and will ask you questions much more quickly. Back to the guide. Overview -------- Beets' tagger is invoked using the ``beet import`` command. Point it at a directory and it imports the files into your library, tagging them as it goes (unless you pass ``--noautotag``, of course). There are several assumptions beets currently makes about the music you import. In time, we'd like to remove all of these limitations. * Your music should be organized by album into directories. That is, the tagger assumes that each album is in a single directory. These directories can be arbitrarily deep (like ``music/2010/hiphop/seattle/freshespresso/glamour``), but any directory with music files in it is interpreted as a separate album. There are, however, a couple of exceptions to this rule: First, directories that look like separate parts of a *multi-disc album* are tagged together as a single release. If two adjacent albums have a common prefix, followed by "disc," "disk," or "CD" and then a number, they are tagged together. Second, if you have jumbled directories containing more than one album, you can ask beets to split them apart for you based on their metadata. Use either the ``--group-albums`` command-line flag or the *G* interactive option described below. * The music may have bad tags, but it's not completely untagged. This is because beets by default infers tags based on existing metadata. But this is not a hard and fast rule---there are a few ways to tag metadata-poor music: * You can use the *E* or *I* options described below to search in MusicBrainz for a specific album or song. * The :doc:`Acoustid plugin </plugins/chroma>` extends the autotagger to use acoustic fingerprinting to find information for arbitrary audio. Install that plugin if you're willing to spend a little more CPU power to get tags for unidentified albums. (But be aware that it does slow down the process.) * The :doc:`FromFilename plugin </plugins/fromfilename>` adds the ability to guess tags from the filenames. Use this plugin if your tracks have useful names (like "03 Call Me Maybe.mp3") but their tags don't reflect that. * Currently, MP3, AAC, FLAC, ALAC, Ogg Vorbis, Monkey's Audio, WavPack, Musepack, Windows Media, Opus, and AIFF files are supported. (Do you use some other format? Please `file a feature request`_!) .. _file a feature request: https://github.com/beetbox/beets/issues/new Now that that's out of the way, let's tag some music. .. _import-options: Options ------- To import music, just say ``beet import MUSICDIR``. There are, of course, a few command-line options you should know: * ``beet import -A``: don't try to autotag anything; just import files (this goes much faster than with autotagging enabled) * ``beet import -W``: when autotagging, don't write new tags to the files themselves (just keep the new metadata in beets' database) * ``beet import -C``: don't copy imported files to your music directory; leave them where they are * ``beet import -m``: move imported files to your music directory (overrides the ``-c`` option) * ``beet import -l LOGFILE``: write a message to ``LOGFILE`` every time you skip an album or choose to take its tags "as-is" (see below) or the album is skipped as a duplicate; this lets you come back later and reexamine albums that weren't tagged successfully. Run ``beet import --from-logfile=LOGFILE`` rerun the importer on such paths from the logfile. * ``beet import -q``: quiet mode. Never prompt for input and, instead, conservatively skip any albums that need your opinion. The ``-ql`` combination is recommended. * ``beet import -t``: timid mode, which is sort of the opposite of "quiet." The importer will ask your permission for everything it does, confirming even very good matches with a prompt. * ``beet import -p``: automatically resume an interrupted import. The importer keeps track of imports that don't finish completely (either due to a crash or because you stop them halfway through) and, by default, prompts you to decide whether to resume them. The ``-p`` flag automatically says "yes" to this question. Relatedly, ``-P`` flag automatically says "no." * ``beet import -s``: run in *singleton* mode, tagging individual tracks instead of whole albums at a time. See the "as Tracks" choice below. This means you can use ``beet import -AC`` to quickly add a bunch of files to your library without doing anything to them. * ``beet import -g``: assume there are multiple albums contained in each directory. The tracks contained a directory are grouped by album artist and album name and you will be asked to import each of these groups separately. See the "Group albums" choice below. Similarity ---------- So you import an album into your beets library. It goes like this:: $ beet imp witchinghour Tagging: Ladytron - Witching Hour (Similarity: 98.4%) * Last One Standing -> The Last One Standing * Beauty -> Beauty*2 * White Light Generation -> Whitelightgenerator * All the Way -> All the Way... Here, beets gives you a preview of the album match it has found. It shows you which track titles will be changed if the match is applied. In this case, beets has found a match and thinks it's a good enough match to proceed without asking your permission. It has reported the *similarity* for the match it's found. Similarity is a measure of how well-matched beets thinks a tagging option is. 100% similarity means a perfect match 0% indicates a truly horrible match. In this case, beets has proceeded automatically because it found an option with very high similarity (98.4%). But, as you'll notice, if the similarity isn't quite so high, beets will ask you to confirm changes. This is because beets can't be very confident about more dissimilar matches, and you (as a human) are better at making the call than a computer. So it occasionally asks for help. Choices ------- When beets needs your input about a match, it says something like this:: Tagging: Beirut - Lon Gisland (Similarity: 94.4%) * Scenic World (Second Version) -> Scenic World [A]pply, More candidates, Skip, Use as-is, as Tracks, Enter search, enter Id, or aBort? When beets asks you this question, it wants you to enter one of the capital letters: A, M, S, U, T, G, E, I or B. That is, you can choose one of the following: * *A*: Apply the suggested changes shown and move on. * *M*: Show more options. (See the Candidates section, below.) * *S*: Skip this album entirely and move on to the next one. * *U*: Import the album without changing any tags. This is a good option for albums that aren't in the MusicBrainz database, like your friend's operatic faux-goth solo record that's only on two CD-Rs in the universe. * *T*: Import the directory as *singleton* tracks, not as an album. Choose this if the tracks don't form a real release---you just have one or more loner tracks that aren't a full album. This will temporarily flip the tagger into *singleton* mode, which attempts to match each track individually. * *G*: Group tracks in this directory by *album artist* and *album* and import groups as albums. If the album artist for a track is not set then the artist is used to group that track. For each group importing proceeds as for directories. This is helpful if a directory contains multiple albums. * *E*: Enter an artist and album to use as a search in the database. Use this option if beets hasn't found any good options because the album is mistagged or untagged. * *I*: Enter a metadata backend ID to use as search in the database. Use this option to specify a backend entity (for example, a MusicBrainz release or recording) directly, by pasting its ID or the full URL. You can also specify several IDs by separating them by a space. * *B*: Cancel this import task altogether. No further albums will be tagged; beets shuts down immediately. The next time you attempt to import the same directory, though, beets will ask you if you want to resume tagging where you left off. Note that the option with ``[B]rackets`` is the default---so if you want to apply the changes, you can just hit return without entering anything. Candidates ---------- If you choose the M option, or if beets isn't very confident about any of the choices it found, it will present you with a list of choices (called candidates), like so:: Finding tags for "Panther - Panther". Candidates: 1. Panther - Yourself (66.8%) 2. Tav Falco's Panther Burns - Return of the Blue Panther (30.4%) # selection (default 1), Skip, Use as-is, or Enter search, or aBort? Here, you have many of the same options as before, but you can also enter a number to choose one of the options that beets has found. Don't worry about guessing---beets will show you the proposed changes and ask you to confirm them, just like the earlier example. As the prompt suggests, you can just hit return to select the first candidate. .. _guide-duplicates: Duplicates ---------- If beets finds an album or item in your library that seems to be the same as the one you're importing, you may see a prompt like this:: This album is already in the library! [S]kip new, Keep all, Remove old, Merge all? Beets wants to keep you safe from duplicates, which can be a real pain, so you have four choices in this situation. You can skip importing the new music, choosing to keep the stuff you already have in your library; you can keep both the old and the new music; you can remove the existing music and choose the new stuff; or you can merge all the new and old tracks into a single album. If you choose that "remove" option, any duplicates will be removed from your library database---and, if the corresponding files are located inside of your beets library directory, the files themselves will be deleted as well. If you choose "merge", beets will try re-importing the existing and new tracks as one bundle together. This is particularly helpful when you have an album that's missing some tracks and then want to import the remaining songs. The importer will ask you the same questions as it would if you were importing all tracks at once. If you choose to keep two identically-named albums, beets can avoid storing both in the same directory. See :ref:`aunique` for details. Fingerprinting -------------- You may have noticed by now that beets' autotagger works pretty well for most files, but can get confused when files don't have any metadata (or have wildly incorrect metadata). In this case, you need *acoustic fingerprinting*, a technology that identifies songs from the audio itself. With fingerprinting, beets can autotag files that have very bad or missing tags. The :doc:`"chroma" plugin </plugins/chroma>`, distributed with beets, uses the `Chromaprint`_ open-source fingerprinting technology, but it's disabled by default. That's because it's sort of tricky to install. See the :doc:`/plugins/chroma` page for a guide to getting it set up. Before you jump into acoustic fingerprinting with both feet, though, give beets a try without it. You may be surprised at how well metadata-based matching works. .. _Chromaprint: https://acoustid.org/chromaprint Album Art, Lyrics, Genres and Such ---------------------------------- Aside from the basic stuff, beets can optionally fetch more specialized metadata. As a rule, plugins are responsible for getting information that doesn't come directly from the MusicBrainz database. This includes :doc:`album cover art </plugins/fetchart>`, :doc:`song lyrics </plugins/lyrics>`, and :doc:`musical genres </plugins/lastgenre>`. Check out the :doc:`list of plugins </plugins/index>` to pick and choose the data you want. Missing Albums? --------------- If you're having trouble tagging a particular album with beets, check to make sure the album is present in `the MusicBrainz database`_. You can search on their site to make sure it's cataloged there. If not, anyone can edit MusicBrainz---so consider adding the data yourself. .. _the MusicBrainz database: https://musicbrainz.org/ If you think beets is ignoring an album that's listed in MusicBrainz, please `file a bug report`_. .. _file a bug report: https://github.com/beetbox/beets/issues I Hope That Makes Sense ----------------------- If we haven't made the process clear, please post on `the discussion board`_ and we'll try to improve this guide. .. _the mailing list: https://groups.google.com/group/beets-users .. _the discussion board: https://github.com/beetbox/beets/discussions/ �������������������������������������������������������������������������beetbox-beets-01f1faf/docs/index.rst����������������������������������������������������������������0000664�0000000�0000000�00000002374�14723254774�0017546�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������beets: the music geek's media organizer ======================================= Welcome to the documentation for `beets`_, the media library management system for obsessive music geeks. If you're new to beets, begin with the :doc:`guides/main` guide. That guide walks you through installing beets, setting it up how you like it, and starting to build your music library. Then you can get a more detailed look at beets' features in the :doc:`/reference/cli/` and :doc:`/reference/config` references. You might also be interested in exploring the :doc:`plugins </plugins/index>`. If you still need help, you can drop by the ``#beets`` IRC channel on Libera.Chat, drop by `the discussion board`_, send email to `the mailing list`_, or `file a bug`_ in the issue tracker. Please let us know where you think this documentation can be improved. .. _beets: https://beets.io/ .. _the mailing list: https://groups.google.com/group/beets-users .. _file a bug: https://github.com/beetbox/beets/issues .. _the discussion board: https://github.com/beetbox/beets/discussions/ Contents -------- .. toctree:: :maxdepth: 2 guides/index reference/index plugins/index faq team contributing code_of_conduct dev/index .. toctree:: :maxdepth: 1 changelog ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������beetbox-beets-01f1faf/docs/modd.conf����������������������������������������������������������������0000664�0000000�0000000�00000000121�14723254774�0017463�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������**/*.rst { prep: make html } _build/html/** { daemon: devd -m _build/html } �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������beetbox-beets-01f1faf/docs/plugins/�����������������������������������������������������������������0000775�0000000�0000000�00000000000�14723254774�0017360�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������beetbox-beets-01f1faf/docs/plugins/absubmit.rst�����������������������������������������������������0000664�0000000�0000000�00000005535�14723254774�0021730�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������AcousticBrainz Submit Plugin ============================ The ``absubmit`` plugin lets you submit acoustic analysis results to an `AcousticBrainz`_ server. This plugin is now deprecated since the AcousicBrainz project has been shut down. As an alternative the `beets-xtractor`_ plugin can be used. Warning ------- The AcousticBrainz project has shut down. To use this plugin you must set the ``base_url`` configuration option to a server offering the AcousticBrainz API. Installation ------------ The ``absubmit`` plugin requires the `streaming_extractor_music`_ program to run. Its source can be found on `GitHub`_, and while it is possible to compile the extractor from source, AcousticBrainz would prefer if you used their binary (see the AcousticBrainz `FAQ`_). Then, install ``beets`` with ``absubmit`` extra pip install "beets[absubmit]" Lastly, enable the plugin in your configuration (see :ref:`using-plugins`). Submitting Data --------------- To run the analysis program and upload its results, type:: beet absubmit [-f] [-d] [QUERY] By default, the command will only look for AcousticBrainz data when the tracks don't already have it; the ``-f`` or ``--force`` switch makes it refetch data even when it already exists. You can use the ``-d`` or ``--dry`` switch to check which files will be analyzed, before you start a longer period of processing. The plugin works on music with a MusicBrainz track ID attached. The plugin will also skip music that the analysis tool doesn't support. `streaming_extractor_music`_ currently supports files with the extensions ``mp3``, ``ogg``, ``oga``, ``flac``, ``mp4``, ``m4a``, ``m4r``, ``m4b``, ``m4p``, ``aac``, ``wma``, ``asf``, ``mpc``, ``wv``, ``spx``, ``tta``, ``3g2``, ``aif``, ``aiff`` and ``ape``. Configuration ------------- To configure the plugin, make a ``absubmit:`` section in your configuration file. The available options are: - **auto**: Analyze every file on import. Otherwise, you need to use the ``beet absubmit`` command explicitly. Default: ``no`` - **extractor**: The absolute path to the `streaming_extractor_music`_ binary. Default: search for the program in your ``$PATH`` - **force**: Analyze items and submit of AcousticBrainz data even for tracks that already have it. Default: ``no``. - **pretend**: Do not analyze and submit of AcousticBrainz data but print out the items which would be processed. Default: ``no``. - **base_url**: The base URL of the AcousticBrainz server. The plugin has no function if this option is not set. Default: None .. _streaming_extractor_music: https://essentia.upf.edu/ .. _FAQ: https://acousticbrainz.org/faq .. _pip: https://pip.pypa.io .. _requests: https://requests.readthedocs.io/en/master/ .. _github: https://github.com/MTG/essentia .. _AcousticBrainz: https://acousticbrainz.org .. _beets-xtractor: https://github.com/adamjakab/BeetsPluginXtractor �������������������������������������������������������������������������������������������������������������������������������������������������������������������beetbox-beets-01f1faf/docs/plugins/acousticbrainz.rst�����������������������������������������������0000664�0000000�0000000�00000004665�14723254774�0023145�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������AcousticBrainz Plugin ===================== The ``acousticbrainz`` plugin gets acoustic-analysis information from the `AcousticBrainz`_ project. This plugin is now deprecated since the AcousicBrainz project has been shut down. As an alternative the `beets-xtractor`_ plugin can be used. .. _AcousticBrainz: https://acousticbrainz.org/ .. _beets-xtractor: https://github.com/adamjakab/BeetsPluginXtractor Enable the ``acousticbrainz`` plugin in your configuration (see :ref:`using-plugins`) and run it by typing:: $ beet acousticbrainz [-f] [QUERY] By default, the command will only look for AcousticBrainz data when the tracks doesn't already have it; the ``-f`` or ``--force`` switch makes it re-download data even when it already exists. If you specify a query, only matching tracks will be processed; otherwise, the command processes every track in your library. For all tracks with a MusicBrainz recording ID, the plugin currently sets these fields: * ``average_loudness`` * ``bpm`` * ``chords_changes_rate`` * ``chords_key`` * ``chords_number_rate`` * ``chords_scale`` * ``danceable`` * ``gender`` * ``genre_rosamerica`` * ``initial_key`` (This is a built-in beets field, which can also be provided by :doc:`/plugins/keyfinder`.) * ``key_strength`` * ``mood_acoustic`` * ``mood_aggressive`` * ``mood_electronic`` * ``mood_happy`` * ``mood_party`` * ``mood_relaxed`` * ``mood_sad`` * ``moods_mirex`` * ``rhythm`` * ``timbre`` * ``tonal`` * ``voice_instrumental`` Warning ------- The AcousticBrainz project has shut down. To use this plugin you must set the ``base_url`` configuration option to a server offering the AcousticBrainz API. Automatic Tagging ----------------- To automatically tag files using AcousticBrainz data during import, just enable the ``acousticbrainz`` plugin (see :ref:`using-plugins`). When importing new files, beets will query the AcousticBrainz API using MBID and set the appropriate metadata. Configuration ------------- To configure the plugin, make a ``acousticbrainz:`` section in your configuration file. The available options are: - **auto**: Enable AcousticBrainz during ``beet import``. Default: ``yes``. - **force**: Download AcousticBrainz data even for tracks that already have it. Default: ``no``. - **tags**: Which tags from the list above to set on your files. Default: [] (all). - **base_url**: The base URL of the AcousticBrainz server. The plugin has no function if this option is not set. Default: None ���������������������������������������������������������������������������beetbox-beets-01f1faf/docs/plugins/advancedrewrite.rst����������������������������������������������0000664�0000000�0000000�00000007167�14723254774�0023274�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������Advanced Rewrite Plugin ======================= The ``advancedrewrite`` plugin lets you easily substitute values in your templates and path formats, similarly to the :doc:`/plugins/rewrite`. It's recommended to read the documentation of that plugin first. The *advanced* rewrite plugin does not only support the simple rule format of the ``rewrite`` plugin, but also an advanced format: there, the plugin doesn't consider the value of the rewritten field, but instead checks if the given item matches a :doc:`query </reference/query>`. Only then, the field is replaced with the given value. It's also possible to replace multiple fields at once, and even supports multi-valued fields. To use advanced field rewriting, first enable the ``advancedrewrite`` plugin (see :ref:`using-plugins`). Then, make a ``advancedrewrite:`` section in your config file to contain your rewrite rules. In contrast to the normal ``rewrite`` plugin, you need to provide a list of replacement rule objects, which can have a different syntax depending on the rule complexity. The simple syntax is the same as the one of the rewrite plugin and allows to replace a single field:: advancedrewrite: - artist ODD EYE CIRCLE: ěť´ë‹¬ěť ě†Śë…€ ě¤ë“śě•„ěť´ěŤ¨í´ The advanced syntax consists of a query to match against, as well as a map of replacements to apply. For example, to credit all songs of ODD EYE CIRCLE before 2023 to their original group name, you can use the following rule:: advancedrewrite: - match: "mb_artistid:dec0f331-cb08-4c8e-9c9f-aeb1f0f6d88c year:..2022" replacements: artist: ěť´ë‹¬ěť ě†Śë…€ ě¤ë“śě•„ěť´ěŤ¨í´ artist_sort: LOONA / ODD EYE CIRCLE Note how the sort name is also rewritten within the same rule. You can specify as many fields as you'd like in the replacements map. If you need to work with multi-valued fields, you can use the following syntax:: advancedrewrite: - match: "artist:ë°°ěś ëą feat. 김미í„" replacements: artists: - ěś ëą - 미미 As a convenience, the plugin applies patterns for the ``artist`` field to the ``albumartist`` field as well. (Otherwise, you would probably want to duplicate every rule for ``artist`` and ``albumartist``.) Make sure to properly quote your query strings if they contain spaces, otherwise they might not do what you expect, or even cause beets to crash. Take the following example:: advancedrewrite: # BAD, DON'T DO THIS! - match: album:THE ALBUM replacements: artist: New artist On the first sight, this might look sane, and replace the artist of the album *THE ALBUM* with *New artist*. However, due to the space and missing quotes, this query will evaluate to ``album:THE`` and match ``ALBUM`` on any field, including ``artist``. As ``artist`` is the field being replaced, this query will result in infinite recursion and ultimately crash beets. Instead, you should use the following rule:: advancedrewrite: # Note the quotes around the query string! - match: album:"THE ALBUM" replacements: artist: New artist A word of warning: This plugin theoretically only applies to templates and path formats; it initially does not modify files' metadata tags or the values tracked by beets' library database, but since it *rewrites all field lookups*, it modifies the file's metadata anyway. See comments in issue :bug:`2786`. As an alternative to this plugin the simpler but less powerful :doc:`/plugins/rewrite` can be used. If you don't want to modify the item's metadata and only replace values in file paths, you can check out the :doc:`/plugins/substitute`. ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������beetbox-beets-01f1faf/docs/plugins/albumtypes.rst���������������������������������������������������0000664�0000000�0000000�00000004244�14723254774�0022303�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������AlbumTypes Plugin ================= The ``albumtypes`` plugin adds the ability to format and output album types, such as "Album", "EP", "Single", etc. For the list of available album types, see the `MusicBrainz documentation`_. To use the ``albumtypes`` plugin, enable it in your configuration (see :ref:`using-plugins`). The plugin defines a new field ``$atypes``, which you can use in your path formats or elsewhere. .. _MusicBrainz documentation: https://musicbrainz.org/doc/Release_Group/Type A bug introduced in beets 1.6.0 could have possibly imported broken data into the ``albumtypes`` library field. Please follow the instructions `described here <https://github.com/beetbox/beets/pull/4582#issuecomment-1445023493>`_ for a sanity check and potential fix. :bug:`4528` Configuration ------------- To configure the plugin, make a ``albumtypes:`` section in your configuration file. The available options are: - **types**: An ordered list of album type to format mappings. The order of the mappings determines their order in the output. If a mapping is missing or blank, it will not be in the output. - **ignore_va**: A list of types that should not be output for Various Artists albums. Useful for not adding redundant information - various artist albums are often compilations. - **bracket**: Defines the brackets to enclose each album type in the output. The default configuration looks like this:: albumtypes: types: - ep: 'EP' - single: 'Single' - soundtrack: 'OST' - live: 'Live' - compilation: 'Anthology' - remix: 'Remix' ignore_va: compilation bracket: '[]' Examples -------- With path formats configured like:: paths: default: $albumartist/[$year]$atypes $album/... albumtype:soundtrack: Various Artists/$album [$year]$atypes/... comp: Various Artists/$album [$year]$atypes/... The default plugin configuration generates paths that look like this, for example:: Aphex Twin/[1993][EP][Remix] On Remixes Pink Floyd/[1995][Live] p·u·l·s·e Various Artists/20th Century Lullabies [1999] Various Artists/Ocean's Eleven [2001][OST] ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������beetbox-beets-01f1faf/docs/plugins/aura.rst���������������������������������������������������������0000664�0000000�0000000�00000016672�14723254774�0021056�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������AURA Plugin =========== This plugin is a server implementation of the `AURA`_ specification using the `Flask`_ framework. AURA is still a work in progress and doesn't yet have a stable version, but this server should be kept up to date. You are advised to read the :ref:`aura-issues` section. .. _AURA: https://auraspec.readthedocs.io .. _Flask: https://palletsprojects.com/p/flask/ Install ------- To use the ``aura`` plugin, first enable it in your configuration (see :ref:`using-plugins`). Then, install ``beets`` with ``aura`` extra pip install "beets[aura]" Usage ----- Use ``beet aura`` to start the AURA server. By default Flask's built-in server is used, which will give a warning about using it in a production environment. It is safe to ignore this warning if the server will have only a few users. Alternatively, you can use ``beet aura -d`` to start the server in `development mode`_, which will reload the server every time the AURA plugin file is changed. You can specify the hostname and port number used by the server in your :doc:`configuration file </reference/config>`. For more detail see the :ref:`configuration` section below. If you would prefer to use a different WSGI server, such as gunicorn or uWSGI, then see :ref:`aura-external-server`. AURA is designed to separate the client and server functionality. This plugin provides the server but not the client, so unless you like looking at JSON you will need a separate client. Currently the only client is `AURA Web Client`_. In order to use a local browser client with ``file:///`` see :ref:`aura-cors`. By default the API is served under http://127.0.0.1:8337/aura/. For example information about the track with an id of 3 can be obtained at http://127.0.0.1:8337/aura/tracks/3. **Note the absence of a trailing slash**: http://127.0.0.1:8337/aura/tracks/3/ returns a ``404 Not Found`` error. .. _development mode: https://flask.palletsprojects.com/en/1.1.x/server .. _AURA Web Client: https://sr.ht/~callum/aura-web-client/ .. _configuration: Configuration ------------- To configure the plugin, make an ``aura:`` section in your configuration file. The available options are: - **host**: The server hostname. Set this to ``0.0.0.0`` to bind to all interfaces. Default: ``127.0.0.1``. - **port**: The server port. Default: ``8337``. - **cors**: A YAML list of origins to allow CORS requests from (see :ref:`aura-cors`, below). Default: disabled. - **cors_supports_credentials**: Allow authenticated requests when using CORS. Default: disabled. - **page_limit**: The number of items responses should be truncated to if the client does not specify. Default ``500``. .. _aura-cors: Cross-Origin Resource Sharing (CORS) ------------------------------------ `CORS`_ allows browser clients to make requests to the AURA server. You should set the ``cors`` configuration option to a YAML list of allowed origins. For example:: aura: cors: - http://www.example.com - https://aura.example.org In order to use the plugin with a local browser client accessed using ``file:///`` you must include ``'null'`` in the list of allowed origins (including quote marks):: aura: cors: - 'null' Alternatively you use ``'*'`` to enable access from all origins. Note that there are security implications if you set the origin to ``'*'``, so please research this before using it. Note the use of quote marks when allowing all origins. If the server is behind a proxy that uses credentials, you might want to set the ``cors_supports_credentials`` configuration option to true to let in-browser clients log in. Note that this option has not been tested, so it may not work. .. _CORS: https://en.wikipedia.org/wiki/Cross-origin_resource_sharing .. _aura-external-server: Using an External WSGI Server ----------------------------- If you would like to use a different WSGI server (not Flask's built-in one), then you can! The ``beetsplug.aura`` module provides a WSGI callable called ``create_app()`` which can be used by many WSGI servers. For example to run the AURA server using `gunicorn`_ use ``gunicorn 'beetsplug.aura:create_app()'``, or for `uWSGI`_ use ``uwsgi --http :8337 --module 'beetsplug.aura:create_app()'``. Note that these commands just show how to use the AURA app and you would probably use something a bit different in a production environment. Read the relevant server's documentation to figure out what you need. .. _gunicorn: https://gunicorn.org .. _uWSGI: https://uwsgi-docs.readthedocs.io Reverse Proxy Support --------------------- The plugin should work behind a reverse proxy without further configuration, however this has not been tested extensively. For details of what headers must be rewritten and a sample NGINX configuration see `Flask proxy setups`_. It is (reportedly) possible to run the application under a URL prefix (for example so you could have ``/foo/aura/server`` rather than ``/aura/server``), but you'll have to work it out for yourself :-) If using NGINX, do **not** add a trailing slash (``/``) to the URL where the application is running, otherwise you will get a 404. However if you are using Apache then you **should** add a trailing slash. .. _Flask proxy setups: https://flask.palletsprojects.com/en/1.1.x/deploying/wsgi-standalone/#proxy-setups .. _aura-issues: Issues ------ As of writing there are some differences between the specification and this implementation: - Compound filters are not specified in AURA, but this server interprets multiple ``filter`` parameters as AND. See `issue #19`_ for discussion. - The ``bitrate`` parameter used for content negotiation is not supported. Adding support for this is doable, but the way Flask handles acceptable MIME types means it's a lot easier not to bother with it. This means an error could be returned even if no transcoding was required. It is possible that some attributes required by AURA could be absent from the server's response if beets does not have a saved value for them. However, this has not happened so far. Beets fields (including flexible fields) that do not have an AURA equivalent are not provided in any resource's attributes section, however these fields may be used for filtering. The ``mimetype`` and ``framecount`` attributes for track resources are not supported. The first is due to beets storing the file type (e.g. ``MP3``), so it is hard to filter by MIME type. The second is because there is no corresponding beets field. Artists are defined by the ``artist`` field on beets Items, which means some albums have no ``artists`` relationship. Albums only have related artists when their beets ``albumartist`` field is the same as the ``artist`` field on at least one of it's constituent tracks. The only art tracked by beets is a single cover image, so only albums have related images at the moment. This could be expanded to looking in the same directory for other images, and relating tracks to their album's image. There are likely to be some performance issues, especially with larger libraries. Sorting, pagination and inclusion (most notably of images) are probably the main offenders. On a related note, the program attempts to import Pillow every time it constructs an image resource object, which is not good. The beets library is accessed using a so called private function (with a single leading underscore) ``beets.ui.__init__._open_library()``. This shouldn't cause any issues but it is probably not best practice. .. _issue #19: https://github.com/beetbox/aura/issues/19 ����������������������������������������������������������������������beetbox-beets-01f1faf/docs/plugins/autobpm.rst������������������������������������������������������0000664�0000000�0000000�00000002314�14723254774�0021561�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������AutoBPM Plugin ============== The `autobpm` plugin uses the `Librosa`_ library to calculate the BPM of a track from its audio data and store it in the `bpm` field of your database. It does so automatically when importing music or through the ``beet autobpm [QUERY]`` command. Install ------- To use the ``autobpm`` plugin, first enable it in your configuration (see :ref:`using-plugins`). Then, install ``beets`` with ``autobpm`` extra .. code-block:: bash pip install "beets[autobpm]" Configuration ------------- To configure the plugin, make a ``autobpm:`` section in your configuration file. The available options are: - **auto**: Analyze every file on import. Otherwise, you need to use the ``beet autobpm`` command explicitly. Default: ``yes`` - **overwrite**: Calculate a BPM even for files that already have a `bpm` value. Default: ``no``. - **beat_track_kwargs**: Any extra keyword arguments that you would like to provide to librosa's `beat_track`_ function call, for example: .. code-block:: yaml autobpm: beat_track_kwargs: start_bpm: 160 .. _Librosa: https://github.com/librosa/librosa/ .. _beat_track: https://librosa.org/doc/latest/generated/librosa.beat.beat_track.html ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������beetbox-beets-01f1faf/docs/plugins/badfiles.rst�����������������������������������������������������0000664�0000000�0000000�00000004232�14723254774�0021664�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������Bad Files Plugin ================ The ``badfiles`` plugin adds a ``beet bad`` command to check for missing and corrupt files. Configuring ----------- First, enable the ``badfiles`` plugin (see :ref:`using-plugins`). The default configuration defines the following default checkers, which you may need to install yourself: * `mp3val`_ for MP3 files * `FLAC`_ command-line tools for FLAC files You can also add custom commands for a specific extension, like this:: badfiles: check_on_import: yes commands: ogg: myoggchecker --opt1 --opt2 flac: flac --test --warnings-as-errors --silent Custom commands will be run once for each file of the specified type, with the path to the file as the last argument. Commands must return a status code greater than zero for a file to be considered corrupt. You can run the checkers when importing files by using the `check_on_import` option. When on, checkers will be run against every imported file and warnings and errors will be presented when selecting a tagging option. .. _mp3val: http://mp3val.sourceforge.net/ .. _flac: https://xiph.org/flac/ Using ----- Type ``beet bad`` with a query according to beets' usual query syntax. For instance, this will run a check on all songs containing the word "wolf":: beet bad wolf This one will run checks on a specific album:: beet bad album_id:1234 Here is an example where the FLAC decoder signals a corrupt file:: beet bad title::^$ /tank/Music/__/00.flac: command exited with status 1 00.flac: *** Got error code 2:FLAC__STREAM_DECODER_ERROR_STATUS_FRAME_CRC_MISMATCH 00.flac: ERROR while decoding data state = FLAC__STREAM_DECODER_READ_FRAME Note that the default ``mp3val`` checker is a bit verbose and can output a lot of "stream error" messages, even for files that play perfectly well. Generally, if more than one stream error happens, or if a stream error happens in the middle of a file, this is a bad sign. By default, only errors for the bad files will be shown. In order for the results for all of the checked files to be seen, including the uncorrupted ones, use the ``-v`` or ``--verbose`` option. ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������beetbox-beets-01f1faf/docs/plugins/bareasc.rst������������������������������������������������������0000664�0000000�0000000�00000005523�14723254774�0021517�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������Bare-ASCII Search Plugin ======================== The ``bareasc`` plugin provides a prefixed query that searches your library using simple ASCII character matching, with accented characters folded to their base ASCII character. This can be useful if you want to find a track with accented characters in the title or artist, particularly if you are not confident you have the accents correct. It is also not unknown for the accents to not be correct in the database entry or wrong in the CD information. First, enable the plugin named ``bareasc`` (see :ref:`using-plugins`). You'll then be able to use the ``#`` prefix to use bare-ASCII matching:: $ beet ls '#dvorak' István KertĂ©sz - REQUIEM - Dvořàk: Requiem, op.89 - Confutatis maledictis Command ------- In addition to the query prefix, the plugin provides a utility ``bareasc`` command. This command is **exactly** the same as the ``beet list`` command except that the output is passed through the bare-ASCII transformation before being printed. This allows you to easily check what the library data looks like in bare ASCII, which can be useful if you are trying to work out why a query is not matching. Using the same example track as above:: $ beet bareasc 'Dvořàk' Istvan Kertesz - REQUIEM - Dvorak: Requiem, op.89 - Confutatis maledictis Note: the ``bareasc`` command does *not* automatically use bare-ASCII queries. If you want a bare-ASCII query you still need to specify the ``#`` prefix. Notes ----- If the query string is all in lower case, the comparison ignores case as well as accents. The default ``bareasc`` prefix (``#``) is used as a comment character in some shells so may need to be protected (for example in quotes) when typed into the command line. The bare ASCII transliteration is quite simple. It may not give the expected output for all languages. For example, German u-umlaut ``ĂĽ`` is transformed into ASCII ``u``, not into ``ue``. The bare ASCII transformation also changes Unicode punctuation like double quotes, apostrophes and even some hyphens. It is often best to leave out punctuation in the queries. Note that the punctuation changes are often not even visible with normal terminal fonts. You can always use the ``bareasc`` command to print the transformed entries and use a command like ``diff`` to compare with the output from the ``list`` command. Configuration ------------- To configure the plugin, make a ``bareasc:`` section in your configuration file. The only available option is: - **prefix**: The character used to designate bare-ASCII queries. Default: ``#``, which may need to be escaped in some shells. Credits ------- The hard work in this plugin is done in Sean Burke's `Unidecode <https://pypi.org/project/Unidecode/>`__ library. Thanks are due to Sean and to all the people who created the Python version and the beets extensible query architecture. �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������beetbox-beets-01f1faf/docs/plugins/beatport.rst�����������������������������������������������������0000664�0000000�0000000�00000003230�14723254774�0021730�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������Beatport Plugin =============== The ``beatport`` plugin adds support for querying the `Beatport`_ catalogue during the autotagging process. This can potentially be helpful for users whose collection includes a lot of diverse electronic music releases, for which both MusicBrainz and (to a lesser degree) `Discogs`_ show no matches. .. _Discogs: https://discogs.com Installation ------------ To use the ``beatport`` plugin, first enable it in your configuration (see :ref:`using-plugins`). Then, install ``beets`` with ``beatport`` extra .. code-block:: bash pip install "beets[beatport]" You will also need to register for a `Beatport`_ account. The first time you run the :ref:`import-cmd` command after enabling the plugin, it will ask you to authorize with Beatport by visiting the site in a browser. On the site you will be asked to enter your username and password to authorize beets to query the Beatport API. You will then be displayed with a single line of text that you should paste as a whole into your terminal. This will store the authentication data for subsequent runs and you will not be required to repeat the above steps. Matches from Beatport should now show up alongside matches from MusicBrainz and other sources. If you have a Beatport ID or a URL for a release or track you want to tag, you can just enter one of the two at the "enter Id" prompt in the importer. You can also search for an id like so:: beet import path/to/music/library --search-id id Configuration ------------- This plugin can be configured like other metadata source plugins as described in :ref:`metadata-source-plugin-configuration`. .. _Beatport: https://www.beatport.com/ ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������beetbox-beets-01f1faf/docs/plugins/beetsweb.png�����������������������������������������������������0000664�0000000�0000000�00000101766�14723254774�0021701�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������‰PNG  ��� IHDR��&��r���öçá��ńPLTEJd}’„qJ‹Íh–ąľÁݵ»ąľÄťź  ��(((NKH:::RRRrrrŐŐŐËËĚĆĆĹúúú___˙˙˙ýţţüüýűűüúúúřřů÷÷÷őőőóóôđđđňňňîîîěěěęęęéččçççĺĺĺăăăâââŕŕŕßßßÝÝÝÚÚÚŘŘŘ×××ÖÖÖÔÔÔŇŇŇĎĎĎÍÍÍËËËÉÉÉÇÇÇÄÄÄÁÁÁľľľ»»Ľą¸¸¶¶¶łłł±±±°°°ŻŻŻ®®®¬«Ş§§§ˇ  —“““ʉ€€uvtjjjeeeccc___[[[YYYVVVRRRNNNKKKIIIFFFCCC???<<<888333111///+++&&&"""�����/D$I�Z�2`DpS‚7c†G[l.Us1E]!4N*�>�<& Bw`“;•Ëq˘‘xŻš~¬Łł«šşŢ•ÖíşěŕËďćŘ÷ďŢ÷ďçűőíţůň˙ý÷˙˙ýţţ˙öý˙í÷ţéńřÜăęÜăëÝäëŕćěßçďáéđäëńĺíôÜáćÓÚâÔÖŮĹÍÔżĹ̵ŔË®·Ŕ¦«±š˘¬™Ł~Š–]envfR‰qT‰g@…]+yO iBc&�T �S3]G,rX9Šye˘‰e{RşĄŚÂ¬Ë·ˇÖðćÔ˝ŘÎĂÝÖĚŃÇľĘŔ¸Ăş±Ľ´¬ą°ŁťµĘ¨˝ŃłČŮżĐŢĆÖăÓŕëĚÜęŘćóßęűÓËřĚßű¶Ň÷äčë§Âí§ĚúĽř‡˛ńw›őyşýp¶üi±úe®ůa­ů]Ş÷W¦öS˘ôNźóKśđGšďH›ń> ö*šô�Źď^°ükµţ_uŚm‹¤|–«„›łŚ˘ş“¬Ăś¬»|ŁĽu“±YˇJq’�anzóuqóżeâ¤K±s řÝŤÇł\AŚ×lş­­őĽ·ö­™ňŹ€ňOkóîBń= ínJďöźśďVPŁ ZÝ™���tRNSţţÁţÖÖżĹÓÁÄul`EżÖÖüv}Y|��€źIDATxÚÔÎE‚„@ …aV,a€çŐz˙óŤ%-č¸< Ľň‘•Ĺ®Çl°ą`‰ĂÚöđŽ‚ďňř«ţđmľ˝Ű‘îˇ,łâX@ÜżÎí|ĄÁ‡ˇů&Ä66Ţ%Ć·;áÇŽ–|˘Ä;}FľW‹:lűƢoß¶|[ň-°áó^ßć|ź¨Vĺe–7’đď9ŕn‚żŻI|ŐOűâő8ŕS_1BŠ áE%úŔŘÇ3ňmŢgŐ×ŠŹ¸úlűŚ|ŢékĆ×˝ĎÔǵđµĺ>+ľy¤ţ\fy-OŇĐ÷ý ¤Ű }7ZKŻk>^ úÂŚ˝ŕëüôUżµ?ě‡Ă~/l_dy•”’¤„™/«ő6’ ž#¬ć…ÄZff{™Čăő^…™ÉL ŃÝľĹ2Ű–ň�Ž®¶K=ő•3Ń$Rř|ž†SUýOŰeš·rŐĘů,b »µoîůU+ć±Yŕ8KXoEjĺ|áÎax€yľÜ†0ŚľQŔŠř”Q7R˛U€ĎCůnŕ;ĐmŠĎęńYC|*ş=ź7ÉgÍň1€®Éó…B!oűDŮ6™"\,’訂:łś#„Ä&/—ËÇŃł˛^ˇ­­˝­­ś…gޕĝ*"Cu?ř‘Á'/áĺçŠ;đ`ď˙hůb–I2’3h“iÂzR˛ÄŮęX˙–ţ±ęŮ$Bp!$‹-W¶lŰR)K2É­ ŢÓí¬·ˇýĹŮL€Ŕ&ąŻ.®ÜĂqăÜŢŻ’/©Ŕ.ŰÂÂ*<[ůpT˙ çËČĐżżG¸”@śŇ‹o×,ź‡đŽ ŢźK?UşűŰ(‰řn‰Ôĺs‰ ˘ŕů(äË™‰ôĘU«ŇJ0mŰdŞt€Žęđh¸Úá€RЉĺ‘Je¤Ü<ĽĐ¶a#hC{Q 0Ą»Źů¶űđw°Ŕ=8 Ç2Řh‰çCµtd}ľ áăl6ôÝĹ™Š3)“ś»‰É™Ý®E`âăĆăů%ľoS>Ž`bp6ÝĄaľ ů¸Eľ@—ř8#ö,™Kݞm˘9ݵ‰˛ŠÍŻŢ@UKĚyóŽŹŽ8Ť_€^ŰjTŰ"&©»­łm˛?űÂÁ]\ tĽxp§ŕŢš˙Ű)ťyµx`ű`ţů§˙ŕŤPŐ|ßGĂWąěŞT:•Ö‚Ű&Óµ”Zł ăÚd=ÓJ)ÍΗGG]—Ś–/0mKudé¸6IYOl@×fHqŤ0hKéR)»—†-¸(LpuńSGă9ŤńU>tşĺ§ôwv śţiń™ź¶ë#_/éřtçŔáŻěż^’Ţ+T(ß>tňA¸˝-ďŻnľ?^`ÄwâK ľ ăăŕßôu ň%Ľ“09Ű$‰TFKéÚ ´‰Ö®Mđ§Iy=Kłeí«Qí)ČWă¬pC ¸WŚu1Zamč$t1ăîůř!†ľűőÇé=-»»?ş:˝÷Ł]}-—¦|÷ÇôîŻ~ˇŽ|ącj$Ŕ–Č%Pł|…1wş ňu?¬Ë—Žgš„°{|~z•‰´qm2ŞÜO“am“ ,ĂĎ—m‡T*;–/p âK M6XA›€"Ą•j úŔmrQČŇż{ÖĽóÂË/~´«÷˝ç_~áý?"kżĽŞĺůď[&¤ŃTę!¬GI/ź`É|q “BîYÚd¦©KUŰ%đěđpµ$Á2r~Ův ȶIISól›Ŕß°Đ& eĘÔŮ’Ú@-7Č/4Ҷ‰”ĄÖ=kżŮ3wţ<ÝŰşoÎüů ľöËÚ°Ě·?íÜŁQlPMßĆ7á§™ćůääs ňąLÁS~XEm›ĚňÜçÍ ˙?ťťÖ05síčHeÓ¦M•ĘčSsě“kß°aăćŤöÇÉD0@Ŕ€“ť1´p˘8,ŃÁz÷`‚ź……Hł đ±^ăáÄ×!|5tú7Á×~Ľ«·uďĚYRÂď Y\˵_íŕ&Á¦®ýt—ÂŔiŽŃßÔż?.°^ď+_‡ň]Ôř†ř~"ƉďÇĽt¶IĄÓZcr ÚÄ}¦u˛kxĚţó;6Ü5[[.H';Ż×Úäz§óěŁíO“Mö§IÇđ<đÝä n‹3š(z3˘$ŹÂ´ˇ >Dˇń‰ë†p>LPä©Ňwoň-żn7[ ąçö°ő-˙eźý]ř÷ż}‡˙ËťžĽ2ńč[řč‡đ˝:|—ęřÁchŘëş|ďnůήÇ×1Ű#¶Kâ}·m‰z5éÄÚÎîžîεIíˇtĽű:¨ćEŃ[7qÝ…uk2Ö×€Á &Hq[ă|Ŕ(ĎÚ´Ą…©—°µ$ä1a|CédjA:·W­Ďg3űâF–2™Ô.cÎçö™séěľť&”ŹW ĺ{·á› OUäQ„<J14™†ůĂâ�ß3qh“ĚĹ 6aŃ(śŤi9P:W`î‰ë•JÂÄĽhÔ&ŚL.X¸päýO›y¨ÚĂ@4´Pó·Zo-Ôô˙˙ŁěA Ćŕ[^kK3–öÜňúĂjDË’ItV]Őma‹ä*¦n×đş'u%.âY> ¶âzŘ˙„vl‘Wţň}ŤvÚÓ%Űńpj§ýďŮ×Ađ€ßň­jËFşŽľĹóżĂÇ&ż?®úÍßč˙~»¦?L™{w(Łť÷˙©ž#[$\é˛{—LĽ]s’wNGÁĺLöLl˘Őd‰î#t–Şq›_2ĚwBs±îńó?RRŞn'e„H¨ uś™_ą52ĄöTüřa~>÷üŐ8ž?®eĚ|x9ó5ń‹´ŕçŔ”1󳉓_˙ţm-‹Ô:&R&‹VuQÖC`ăL•ĆęX8ujźŰŤyĆQBľ]¸öĚÇKÍü@L|KeÎ|LďńÝmĐŕSăÖ<Ű‚/dĽžďîĎô#ľBĹóÉć+źäW—źÔµM(¦[˙ňéóQ#{ČôćXhOj>Ę•S]–óůdşZ-=l÷ůÝ|eďĎđóß–7«Ç|u嚯×ňsÍĚÁ_Ţ(ŘR))6>LúK¤ĘrrYŠÚŽŰ­)mOm°¶ç×|뙯ľä×Z&<ĹďĎóąfţŚžBşËG;)őßßĆďK>Â,«ú09uZFlsąľóóă±5_“˙úx=˙#CKţö±|ÉŕĺˇÜ$Ye}8rü¸ŰXÉQŽŻé™m¬fGš]ĎX™Şźëűp™`G$edx)m Ĺ şJFĘ?Тp)ÉbśRńcćăLü|Ŕωź´Ţă+ŠOŻ´ä“e>‹sŰĹ÷Ăîg'—Vţ=&µmÝ‚›FÇ”ÜV}_÷“ËĆ{Í=ľ˙řYńź}łČŽÂpvYekMw+#5 ‘¤AMf¸KĽ^%x”đIRŐ5 rpçĎR‘ţVů˝Ş7;Ťý8íĚ&xuš'ŹMŃÔçŽ-ŠčÍx×ZIÇIŚvJË^X·¤1:2Ë$x$ˇhZšź'çöOrú·ćű“>ż?ĹÉR˙9E˛Ô?YčO´l!í"2«çCY 1±yZ9Á1Đ%+mŇáMÇ1LËRŃXqd`(;1¸&PŃJËJÖ«UkżHwX‘ŠĄ¬W_#ŞNÚ•{ývóśż Ńą’?ź|ZĂ6í »YłşJ“uH0±–vM4�IVőśln®ŞzĺˇCNʬî;+qͰ„+ôş‡ĹĐ"8çd.ř „XČĂü|ů% …9'ţb.Č•đýdŰůsÜ(kJŔÄ’~čNę®;Ő-IŰ�7]ĘŇĄ ŚĆ°Ůó_&8‘_†ůÝXjDÖűÁ¬‚1&ơ×MˇŤQµ <ŹvwÂB�É9gĂ'A°Űá§ĘhKväMéy>cŤHĄ­Š¬ÂLMÖÄČnÓ|™BąRXQ…ZOcŕ`'ž DŔŢLPćWA¤áOµÚÂţCŽ˙ęëë—ŻŢĽćŰžs&Ž?‰Âtnü7a»LgVÎB* ěŐ⸸ M€Eńř�¬Éa©dJ%X“`ţ—N  ™ŇŚ*ó?öAתnĚ.Źżüd‡ŮÝ'7{»śô®‰}{«řŽăn±Oß?ž>=ýřöÇłM×!¶ť•lӝ纼Ô:Ę\L—~Oɸ®!çaľĚťĽÁ ů‡ë°hcŰŁx[$v*�dTšA TĄtlĹnV˛3ĺě2ä ŕĹŢőŹ?¸Ú…"v†e\€ŔÚ59¸¬ş-úD0‘ E©,lPa4ý|4bőžÂs°&)ʰTä<Đ÷îv?Y7ݏD–6ŤávďöWť@uL€’:‚î “ç;9%NžłaÎć0H‹Lö(žśsQ'çŕ·9ÝťśďśűË}Ş[ĆŮĽ'<ţş««Ţ· ôý÷[…T'rŰý]vŹşîŽmră7műdíŇůsgĘ/~őÔaÎĺŰňż¸úoMG„a, g¨–đ.'TőamŇ8\,‡ŽÂ˙ řýÄq­wĐćÜm.ačřĽŔ±ËSť‚ůgˇ„x2ę&¦…Éhëq\”t?bw˝nĹěĆ•6ŰiWéɱ;oŠĆ‡<Öcwěňfr;vďÚ˝{g.݇!ęýŔ’ çD‘I@˘ías¸łőŚ ľ2íô&u»ľY’»Pöui¦oóĆV˝ťQ3,ˇřŠĂęÖ#»˛^LŁŰ3Î0ťZ",QŻđH¬ 7…U{|őŇąóçË—~»¶çSç·' xŚEąqÇ:•⋟“…GŹň®V{ËŔ'Ş|q1•šŘq356'đͱTý0Çĺ—†b±ˇ ďaNŕ:źqűÜś÷žgţ{x—šţoń}8çbbů4ŮWź*g]<;.şşE5‚!xWC«´áE€îb­„‹ź(ÖíW3[Ç.‡ŇŽxO+@ÝýşiőYű*ŞVčPíÓ+›ťVÇĐR·H6&x’ĎnOß•Éŕ3Ôp6{OŞ®x"'%Ó†BÜۡ0ΔŠIP– 9Ňíuvn{űşýĆ®ś±u<ˇôôŁ˝[˦‡ýÝ}­í×›cfŘî6ôÍ^şCÝČÝ’»'D7wŞ9§Óć(0äČHpžo÷¬^Ľx~ęĘż­­]ß3Ă:<r”s1Äl#đ¨đBńĄĎČÂĂG ëÔŔ敲…ŘE>ČÍ˙Yîůđůˇ_ćN’Â/źŐź~ŞLĆ_}äŢ;rß–ľđ—xkńÔřČTq©Ę¸}m¬€ŽĽ“aPŇC ścoŽç¨ZQ… Mh¶/�ÇĘiÝѧ°rŠ4ęmÍZtŽÉë§'–«A˝^ćřĹůĐJÍWÖZ¬—xľöôÄ˙ŕí۶;“޲Sd×1i=:˝ď+ŹĎ¨÷őŰOjŮEo/,=¸X®ôôÓçşŕ\`„úŕ2’l‰JĘKŁŘ»;“Íŕ“gO6›ąSíöÂÍoc’KĆÂ8Ź Ghô$™ŇÔ{×wÉőúőťlŰaôĐťO}ŠŠ-Ă(µd"ťŠůűÖw×ő ›ÉÁÄč±wĐݚ܉ö9ۢ¶›]´ě‰%"…ÉÁ„ĚIçâĄüű^[ŰS#tń”qŚaűĺ„‚)EŻ ô‰łW|ń3jňEÚ8tŠ!Äí•f۬fëߎ˼őî'¤řÖgÄĹ’‰W?ĂX„|řČ “łď»S–‹G脉čˇĆÇ{<lbf“Ŕvz=,ďö]˝B±§×Ĺ˝Üůl»·s“‡ww¸8´z™ăyŹ«ŁÇÓ†iĹăćY Ç žľnGłIŁÖ/ﯻŔźŕfIŢś!ăćZ˛Ü&݇f“M˝}mě¦LKmŚńCĹvg¶'˘Č&.Ţ®wÄťmŤôäÓçz»šuěHŚĂŻWŐăaěhu]Őçú˝Á# /MTukĎkbB­¤`BSz[&“A´‚ŮLfűX?B×?ŇĐ]ą[†BĂô‘ŃC&!dŠI˙€ź*8Ľőľí™Ě6Ło5ŻŞyQ ök Ă(Ń€í4Đ?bbH4 tôRF‘‡ś˙úzł[‡íNC 8a|1ĹŠ‚Tö¬ýë?^Ľ¶ ýăÚőťŘ‘M¦‹ďĺvç>™"űŢĚÝöDů¤ĄĎJv6ą?ó]î×mdqď­{˙ ůpď-ź-ĽőđŢ_—Ţů¸d˝÷đ©…N[<ĎôÝI‚;ľđηS.;ńň' Ź<Re Í!#¦ëUwsnbŮ0ŞBĂĐŤ¤KʦÚ4Ě9¸—N1측ÇÍji|ţ˘ŤĂ›¦°<dD‡Ş¬GYJłĎ¤^6&őŽ•#ăµp@®`(" `‰´…Îő¸Y+ĺő¸Q/Ź›‡błĆń?Xśľk”kϨmČŻĹdc§Ů XŞwШ.ĎĎú45°lÎzĄd57çĽAS4†}#9™Ą{ŐĐ'D ¨©Aę-™Ě–môb8ÉÚ;ß‚) )Ůßüf(`źĹuLÂN´MĂ @ÉÜ®tąÎ©ű´¨/@K`%2†ßţţŇO2¤H¦úž¸óíĽ=†žÉŤŃ-M# Q‘!é&żşzőĘŐ+««˙şvÂâLĘaîř(wĽřňŁgřľšüůăe 4rżţ0wn˙Óź”ňŻ}ĂĽümi˘Ť,äNó¬w†›ĚżšË}Në[rź2<3ţöwŹś!4?}đčQKŔ=?CxwŁÚ5|¸ťo"zćÉqó i"‚f­­‘¨şX6Ż%…•*ź_©16"ćôÄ_VČľůňÄrý0Űź¶Đ�ś&ś§ť4ĐľbŻMń¨:r¸Ť%˵)kˇ>Ĺ›ŐâJM(,×JîËČ-gŠ+ŐI¸#~ XO:ÝAÚ» KČzCś~äě´ ę…Úď—Í™nź97ĐÓąąa„ýz ź.tĹ»\¦{Á ®Ô.w·ö řĂ?j*Hż!˘IÄŚRS,´âlH)Ô%˘fwÝ2Tł»oŃáw€‰˘(v´ý!ç[!5•M§ÓYâµhlEh0jŇwfH íĺ×Ň:&ć@x zĎP_pCţ~ýˇÁ?ž180Ń5·Ŕ“Ůë˙şzíęŐKSĄ‹«˝JxŽă)&ď?1EŠ/ťúsÂ±ŚµńIŠ 8TÎíËť%äýǧŢĎ=7Ă’j<!ĽőŢŚĽůč)ÂőKńp•p…đŇ[żŔĽ@öďý‚đüd^ź¶ÜîĹĘx¬,XÍ9ÄěiЏR& ˇ^¦(°d_mŠtŔĐÂ$o"['Ž“7OŚ ‹cou—ęĺ˘I±˝kY‹ŽV8¶°\/3 0aÉ&$‚IÇj¸\źjŔŔs?L<Ó‘Mr<Ëpmrĺ ÖŁo|{ŇcŠYń—L3iĘ`„>ÇspHW‡ĚDÂÔC´iő)ĹÁDKŮă„3ČáPČ/fp„‘”Ş@ޤf¶ÝÓó¨,k&˘cm‹c`0–ʤ2F ‚z06¤Íň°ˇŕD  éFGqĐŘźPžě"Č&b�¨ôUe]s śŐţÍÚę•K—.•ŘŇůŐë3`‚u0yrŠł^úşđôq"°Ĺ?{¤ţK|Ňq09Ăž(›o í�,dyĐp’ěő©Kő †ŔtřüŮe‹ăH‘jŚCz|x†´0™^&o84]«Y!¶ÍZ¬1ΤSΛsଛf ô Ě”¦k-•E`â|Ňa�_ót^Gv© OŕĆ—ć ćB0CŤ›Ő˝Z™]®ŕ7ýâötÚCW\w&»Áه[ …ŤąÍKI9¸dĚb™q_ČƆ;QŞÓEf(˘Gí°8}$Š€‰Ýˤ·íVÁčm[Ó™dú¶Ľ†0! IĂ8Çd…b" ŞvĐůÇ Ž@p4•5®CĂŁ!‘–1ď!HDú4@�í)ÓŽA}»!X$űĘ$‚RPż–#'®Ż^˝tńâ9Ž9żöÇÚ|ôč&’ú«â«OM˛±~ďÉ’…%ěG&ă€ĂzwĎO¬—żťDá0µŚňÎD FÇÄuŠńďLXKůئć–G@‘1Ž’qŕ�Ý„ )ÄZ“H¤°L1™¶çË…żŔ8d’®Axô1+“Ą‰)ü%řźZÇ,đK犣ÓH#ŕ€ě·ˇ8C;O,ŐfŤ¤ŠMł>mqě—ľkYÓ“Ü&ś ýß„łhÄÂ~Ă GLÍKTS ŤšJ 5MŰ8ॅŃĂ–VÖ3…˘Ą0�ýźg6•ĘîRŁ·Đ2˘q•ŇvĽ¤řîlZ ëżÉ¦Q•mL4´Óh‡%G"–śÁ°i„śĎ(ń…cŁŕQ¶í8ä0ĹŇd3–Eug2Ž�Çî#»ô°'>xg6&¬ĺţzí 0ąHĘ˙Íeűă1ł” Ż<ó±sd1÷űć÷ľWźLíÍ'Ż| Óg­?ĎÍÓĎĽo˙ÚČ}<~Qťüó_3/ćęż>Ö~ ŽňoĎŻěÝSfČű”L Xrpd_µ0Z&ś…•eţ/k3BóYQ¬–öÓąeŕŻOY,b©ŚšU‹–¨ůě1‚…K(6]`™?f1…ŃC„ĹbEÖÍŠkĺh)ŤÓ ĄYíř ,t‡�XA [L(ÉZiÁ”—±"ă—ý"]ř¤#«éť ±ëíúŤ�I˛Cˇ13¸J‰¨h&f$1¦ŕrĘ4˛dĂ·%¨6&iŮnŹeŔÇŽ $“Ű#ĂGĄ”ě´)ˇg IS!9ab­˛!$Ld�&®KvH»É¬¦M€F1gÄĽ3“Hnhě¶´'x§żęoÖ®]ştńüoŻWH믡µ‘üĘXý˘Ú0ČÖ‰ů^,'gmSW¬Ś;2ůě ±ĚdµÄXŤdŤY¨0¸ýÓ©J‰ěK¤ź«0“K©t}š°l\kO?ěî/ĎŢC“V—8[*ú$ń¦ŕw“öYä}Y¬x»Ň3ÍjÉę–f;`´şEiÖUôŤ®YX‡&’G:íů~á�a‹ľ {‡Őz°LľˇV\Öäď6$o·—™”wKeš`űcDzƶÝ~kÖh#l ů†p/+ˇŞ ´¨,‰Šâ iJ0 *ľ0˘ç E5xGDyCZZS©”hZqb,“±tz,•Ct%E–TęŁwnMk’yÇÖ42‰Ş©QŠITĄ¦DBR•˙*Ů0ä˙Ú ŕ€‰F1QLşH[~ł{C»’şIfRŐěŃu@Âěsß~óő×ßţzÎÍÚB…˘×Ă)Z&vŇ6ă’60ŽŃqŢ(ŕ­Ź§¦Í!¦ŘP:Ýf}h«5vqůLë…5Űý킡˝ś0Ű4ˇâŘŃŽQ&ŃÂ8n*“Ôć¶Ůq^¬WxţŔđľH¸#·˛‰˛ˇŮ0˘PÔĎ7 ©a˙€ôËj•Á“ĎŔ^-9hP,­8’"fÚ–Y2|0ÓÔ5Ů9ŁÖÂDÔnü )7. Sv®4őfÉ �E‰LM’ů˘ÉfRŃčđ*&žˇbŰ”ęŕ–Xă”8­Ž©ŐŢ:ŁhYn.n.!Ëă&ëŤ7˝ČŤ‘ű@H‹qHŁÂX7ż§7٧ÁĂjoŰ~k7ż{gČVçV3^ 98˙(µî€őĘ&ýßŮ7ěČm ŢÂ9ÝAH `f¸÷ż«PŻ÷a ʦӋ®!čř÷-¨”ÂWńđdĆ˙/lÂĄU)Äi§Í;RŚcă±§g»\ěůŃ«ŇÎřôrKńéá)Ŕř<gÂő=h ^’V3yh`Ž µ˝ßbbg¸ěÉg©E�q˘]ówĄŇ7ňţ;áíúŚľ9ń/ůćü¸ăöżŻď´č˝É•GŚ[šOJ‡Ł[Ć}ćän×wŘѶ_¶¤}>iÁ!)6äĚ&¬zúü1Uű‹Ţ‰ČhŮpiŐWTö¶kôţ›űĽ(a:™7|šńżţ‘~¬[ŘBôÓŔ‚KňŐť5ÁĹľ\cÎ<úsA@S"„ŤÎąě¨‰i(â•3˝Xř°—Ż÷ýő Â' ´Xiąő ŚÇö˝$ôA0hś˛°ŇëűnQĎ h·ëóëQ†·.Ü0s1%!Ď{x)¨’ť'yüŻóďFL|eľ¦Ľö˙5˛HC>ěĘÇşíŻţ˙—Ń"‹K·ŞeńÚŻ!çq8Ś/ ivš¸ß"ň¨‘ńćc˛v—]OËBĘčÓś­uŤŞ‚×kďµÖ^[o­×®Ą6¨ĂíÖRr·¨äe ‹EűVg¶Ě ®¬×ˇŚRŽü:óŃ.ţ…s„ţŔoż s‚_ť:óUÍÖvĎo/´ęşňÝX*ÚőčCÉ:úśCú¬&K¬ré1AwÁ€b9nĎőüŚuÜĂQL—gZ XšŔ¤ ˝źwDjŚKÔ”Yä[-+˙@Aa5ÁQíŃěU*śuŔ—#Łőeň˝đ qŠ˙ü¸9M>6˛ĽÜŐŽfštĚ:¶„Ŕ|¶*©ä˘•ĘK6“RĚtU«ŐZ5ł—«n„árC—›\L§Oc”Rě㦵6Şłjpi,vHÄQÎN‘ÄŻ?F{W5IsT‹uýsE*Ó<©u‹Ź,ś™-| SxáËýČŻÇ|uř$đ›ó‡Ä§wľ-|«Ż”f^9©‘Ż ¶ÎG9lÖa2LVjWíŁ´X†Ĺ˘rŤă1ÁV‡‹ ‰yÓíÓ=6PDż¬<l|Čă>Q;HĂëÂĐęó@ĹźóĺSÎŻ3żŤŞŞ¦śźg>ü•ogůóĺür†o3źFüü߲ó‡u~ťůL,|+Q5ĘË ŹIâĎV­5˝A·Ţ­>ň"Q»Áë°†=Č4Hl€E1sĆqŚs(=ő«ŢL­Ý8emćk¸Áv^6řDô‹Ő•Ď:y0đW>lüŽĎ‘ÎogřŐ:<đ‘BË9~Ó çוß>IĚßń›ó›ř¦sA¨ń»ř<¨{~?ŕf«^|–Î|Zyhżř÷M Úś¤ů]ĺâŹYőS""ˇ/ęŐWR~v˛6KŤěI~:ŕ§|eÔć5ňň_âăňú;>Śřź+‹zŔWäźVţšŔ’Ĺ_ńń?ń›ů.}A|{ůLŻ‹űúň;{óm�1Â�Tµ¸µśýý§ąÜŞse˝ŠĚźžÖú ţ/MWý˙Ç·ű?ýx 3-Ŕm7ÓÜ3é p?ͤŕ1Ě´�Ďa&äČ�GšIŕ(3-Ŕ쏪–Şí¶¬';gŐÜ8Ň…a§ćę«ďşĂĚĚĚ̢…Ła’/B¦e3Ź5 Î0ÓżX'żcOŰ ČąW­žN˝§O¸źpl…ŁşŞĽU…ń{çć•·O‹/”:pF ÉFžŘź#Ç”aĺݬ[»ľOTMS ĺÍŃFEוŤŞŻé % śhÂ?QlÜüĹ>%6B€@ľ¤^))ëÂVţ†úV6¬_ż±PŢ@axÝzB•n S őůPę€&ަiĘćC¨ĺS«¦®JUuU­Ş«lë–4%šK Żë U«­®¨­ŐU­ďţľźö÷ýű¶F‹ÇŠy|Ż(Ž*cŞ–˝öĺ›|ĺwJ®‰®ëę懠ÉŃ©Sż`XĹĺÇŕ&~źSHůDčęĂ ä,]Ăě#“'O˝§PM/B#ˇ$B·l(٨Z4c0ËîGM§¨†qgSJuěęĄÍ˙C˙ÓtĂP᳉<·\l?Bś¬KĽôuý}$Š}¶ăÄqNRÝđŁÓPJMx©×ü3uÓt#bAiÓÖŻ®É7÷Ń «#D4# ´Mtc 4‘Pź‚„®[‘Đ<ş·ČŇK׏§%ˇďđS4fÖOź,öcĐÝMb ‰nřF8ü¬ĺT8“K8ç •=ńܱëÂŁ”†·Ţ�410Áú¦Cü“„ýĹťu)ÔtÉú9‰Vw¤ĆsąŢ(›Ůú4ÖrŐĘíÜšÁŰÝ]źMŚ˘Ńž¤ăb§.n}Ú´"ŐŔ›tĂšŽp‘•ÄŃ‚őöĹ5Á„4iůlStČqĽ7jś=svZh:–,$±ůtA“G¨·Ôňś“5Í=Ý4ńŹ˘ˇÄ×BÂĎ@Ĺ %˝fUŇÉĚwMLJ�LŢhB(Ą\“ćË•Äţ5KĘâ´,Hy ]ÎőMŞzĄ§éÓLýsOZF­á”ŚnE0é> AóB|CyÓšPo—RňÁ�Ĺ»ĽOĚ5ůt3±ÓBĂń~‚łxÜŃÚ’cýÄBZt1×/žÍŕŞÝG& ’ű ®Y¬IĹžtďxk8-ÇA˙¬ÜN9.evžL'ťÍş´ă2.–Z0x(¬^ Ś&¶Ť[QëYúúDJrÎY[ćϾמöŇÝ hrÓÚło<ÎńTďĆ“•”ů {~ę#€&P"„ú_JřIó©­‘\âV=±¬­Đl†ćć&/dëi Čű¬�M@¬b?9{°_ßŘ5ëQ¬Ź3gźŔďÜ,ÖgŘś}"r2ąTź!wV?‹ÉŮŇĘŽ[ÎüĐĘ=?ö•o„ˇ€&ţá?őFĂŹbW-zt¶ű‡×tíVjĚ č×.Ä:„äÉbÖ­ í“ůýÂ;§,H’dHö˘„.˙ŇO$őůĽŤ˛"˘ůoaĂĽDčł'±aŻQ6cn}Ž®l/4n•íóĂŮđVĘ˝ Ŕ«ůź_ĺ\Űt|ÖĽ•‡'NşçVÓÚđ‘IcÇÝą¸‰tĄšöY3fž˛Úí3vzY3‹`ö‘9ł? CYVS<Ą‘ݳƌąëV±ŠŁsOZ»˙eď.Ýa� gW<RWĹ#ÄË3ńˇÚ ć•<R–űĆŐ˙0ô×6Šîm+ř8 Ź†”cY&LDXŤĚ5×)a•e °]Űęá’¶Ô xŽ«B›ž j–šő¸ÄAń|$"˛ąăCYél`Í„Ř@J� ‰HçL'k`‡un§]€)­Úě�-·Ikäؽπ‰Ă(ăśfÂZ‹#˛É ŚŐŘDz/׿ČŕZ&†Ź÷€řH> ĂI&9đ!z§Ťđi!EŠ’2D&lCĎ<’¨¤¸©L“MťłL$/Ľęlh˛ă… Ç™7¨§ßMDE2ë8ëŘ·Čİ!&‡ďí¤•ăJ˘‘AmZ&"‘D ýđ?ý;‰JfłĚňÔ¬ű“0NVzţCGl??Öa¸ăËęĂ·Ăý„™?‹ßîW~şÍ÷_W˙Ů;‹4Ů­چWkfh,f3Ż!<Ěě1“›™Gaf¦Ť„iůeç–ÝŐy (űŁěF}Ç’GĄ;®.ŻÜ€łĽ˛Š‰/ôă4&ž¦ŇR¦(\ڑ֓î{¶wçMw\[ ®ĂjŠ—ë°:ż~wŻŰ!SBUę‰Ń•ŚŔ ČWf¦§&¦I“`ţś`qm�KZ¨'¤ą‘µrRÄ C y*'drÂť<ŞI5ŽjŇnG–Tˇ/ĂD!Ýŕ˙#ňů|Ž€#MÇwüMpş¸”-đ_'5éw¸%$I1,˝H\ú™Ĺ @ŽLNŕŐĵ˝MW×763ĹĆÚÂâ˙iBµ’ĐŁĹ—¦§ˇHčď»®cąIM‚ů…őíť»»í,Ńěô6ć‚X“^§ZI¨?ó' ™Ë“`ŔĎuÇ6ś„&ÁB°uosƵÍ,a9SµťíµĹ Ö$,&Ő ,áýiË�>-X#<Ďu]8b[–kKVwş“ş¦Č˛4DFŚ)Hř÷EŐÝĆÎúbÔ¤^«”ÉŢź‡5ĺŔ ÚG8Ŕ&I C·“Őd§e©˛Č2„@›()zĺ޵… Ö¤Q§–K¦¦ŕH”9Çu2bĂ0LSłbM¶:†rD\¸$$ătI""v‚¬Uű‰j‚á‹•Č’IHâ3gŮ©VĐĆ1SŞ1ÔdamÇ“…Ń<~rđ#űŕÖ+ĂřěŁßaźK¤™z«Y˛p*˝ÍĹŕ&…\h‰•^ÂȦ E€¦iŠ9Ôdq»ž¬%‚€Ť>0év쥫ě2ŁŞr‰ťyö&â­—éŇ8[˘5úTÖëN‘'łŢt0 —zN~vzŠ,!G(k@O?‡ţIŤPUUŽ5Yř˘Č!m—š `";űĎ7$ B€KăŚXë51Ň®Őî9LÍ.Ţb#MZ¤I173=é{—„§-ŐŚţ‹PD’i‚ě¬wő¤%îń'žůýj¬ ”¸ôů37ť@5ů䡧¶°˙‘±süŔ„q-&~Żcxš\î×D”“Ćć¨&TLBKĚ,(ÔC(PȲ"j\“ĄŤ¦$0şÍÁćńgżNh"°žÝýüMhrěÖúÍĎţxöŮ?{˙Ů%ÜSJÝNUöT“1u&Ą­ˇ&Ť:^Mfg&©ĺX„‰˛¦Ź }ňDĺPbĂ%şĹăqXŽ"’śĐdł.,mĺü+żĆš\b^ű‰Ń» Ý:ŤĂ;/_˝đęźcÜx*˝öÝ%¦÷0—Ő€ěłŰK‡5™F1á–ü'‡Č€ŚJL˘â„Bäq~K¤Ż¸1%IHT“Fňö_vÎł+Š´éăîń#<çęAL›ĚYɰIŘusNĆ@ĚŔ Ŕs$öŔ„îž�‚9+`$ŠD_đ-ČüĎżúšfXąŁ÷2%ťŞ«{ćL˙¬Ş«»ŞEEűŢŻh,„ 9p.ŽRđQvć«o±h¦žłÄ1':Ľ‰Ř<Ő›lBĚ1ť (aHř×eIź}ś8 –ŘVnuÚđ#be›×†źs٦-xÂĘ“zv•oóОmlˇbłáŹE,µRX-+B˘RÝşüŃé`ÖUy#·§”ßj:VĄ ą‰łr“4NM8]ÉľDR˘¶ÄŢY%ă�±đ«ťX—µd ›°ŽYbîzÄv‘uëÚk% żţÝ$˘ňösQšô&ąUtĘoż¬RPHÁŻÔ͉ ŔQ˘d¤#–$ŐčË›Äq»ĆŚ9đ%@„}ŻhĄśşďµq~VşS ţ…§żă˛ř„ghq\|˘GŢTcó‡ŠáĎRl‘"Š)ˇ5¬‹˙›ÄDŻŰvÍŐ{´űgĘégLš˛9…u�‹?LPWĹ­¬ÝĐ"ź(ÉĎőA÷Mćő“÷M“ ëáL™0%16(ż‘Ő'“'şŇ&*?T>¨i ýW)ˇ1ąR¶=>�V,LDĹę÷D$‹†‰H]…µŁěIFÍúĎ{ĘRť˘Ą®ŠÁj[ĆůUIş(IżŕoÎp#§E%wa…’X«»¦{“e¸oŔśŕ’Źß@j=§śĄn`đE~vŇ„‰‰jła{ŽĹˇ>$Č o“Ň{·P˘Ź~n‡ăÄ’IˇNßĹžF‰Žg:ŠXźěLÇä˝pL®¶ŠÄ‡JLčRí¤7QLLp¦:ćZń„Ć‚ô”ß„­-ŢRż *%¸‹Ĺ†?~Ę '6…˙w9Ô{ŻŞ˘á ±Ş`ľ¶ŢŻÍÄä) /Cdą˝߯§Ť“`c&Ą†@^7Ä䣫Y=ŘEř“·Ç˛öźŰŇ%„Ě ő¨#ř§0)CsÓęgLpĽnĎŘ3u‰ âO•ę"L ç’7.WÄ<“ص‰u~Iɬ<˘N9ŠąOú1Áâ6}wqç„ĺMNdbˇD&zŕ\r¶šů$[ă“ëj¸¸~ &Ű­ńň&%yÇÍHe7˛ŢډC™NgâąÄ„G†ŮýB‰*L¸˛>P;ż$ŕ×dZ2;&ŠC-¤a§˘|ĐHŮłaRhďEVĐÝ#R„#š0‘¤óJ‘›(k$úę+ĘůśúĎň=F`Ŕ,„0yÁA§™˛2 ěÔ]Â;`WŮÄ6ц‰gŢ6ýÍŽ 8©lݵg÷yoEŠł<ÍeinD—Tw¬xŕ«6gî>ż˘yW_yZŻ(… ĎEQĐYŮ11o (SÓÖJp]ťń[łyÉ&ŠĘ đ¡ÚÁ“ś±„V1WXx[Á„™-Ę0YŮ0óS0™™ŤyfÇDú^*r’:–Y(Â>¶ž}¸Lć0$Ú{VSĘJĚÖĎŠ O,3‘Yú ĽŹáŮôÓLjxß&sVü.]*IPU%fÉšŮ0Áxf«f"±ŢďŕI°|°…ßů0€Ýo ¤ą łҡ`KU°µ-†Rٰ•˛€ÉÜĹ„ëÓ–(fźč’صł`˘ňMĹU*hąý’'‡*u¦\Ş>tV,ďˇJ?QBOEiaťŻ˘Ľ¸€^÷śT^…‰ĄíNˇĚeL0‰Yň*łcÂD‹Ş±äŰj<)řW~óäą;ÚhÄŠ;Ş-�ŠA+®ŃĄ4¶`Î>Ë7k0*Ń Ţmi*n u®c–ĹáďŻäŻoĂÉŐ)úČĂ$vé¦ř„„ď+łbÂDś¸rÄ0qéáÉĤWRr;÷¨X§ ±tŐrˇŠ Ěe±Ác.m+żžýě, U ̵ŘŘ„ÔL,ć—^ˇĚő˛$Ż!Ĺ«é^Ď4a4Ż6E4gzô°]nްŠL6$╍ńq+młaâŕb‚ű¸–6Q”mđdbňŔ@mĘÖEů;g,°ç>;豢ô‡¬ŞĽ–ëT[ĎfbňŔÔÜ;ňL”ć?ęśĆÄĄŐ$â Su<őn«ďťţ¤Ă§i5u0­¶.Ń4®u.mK]ď$%ÚVëhÝg¸"“Ř@’°v6Llđ/ĹnU޸v¬ŞňÚ1Y$|Ř8EÉ~¦ŢŁúĺgoćVł‡asEE í«*dľËž-qe?qQVż€wÉ!đDqRĂgs߲¤»z4W(’-éç|ôÔË›ApšóĄő©.Í[LÝÇ*žĽgxÚśŢm`˛<ö˝‰ÉĆŮ0q¨�A>ń+»ýB”bRńĎt&G«+ÎÔ QqýxUŮmT#5Ůűąď0(fTşBcę˝\'»@#ʰGTJÍŘŞĚ2§1Ń·ž˙(µ‘†S?>ß[lďńzeŕ1Ľ^Ăă÷µŃhuuič5iß’7XźV_ż‹Žöy.öi.·SX�¬üľVńE&6˛(9©†eý�OŠP€Ě«*5ř„K×v˙I#@ĺXŐY22%Ľ°‰˛b˘úŞĘ&¶0L &<›Űp\1śč$wŚâ¬ľ¸Źëz5—®íH«ŻŐ5 ÷ďúäóu˝şËÄdb¶ľ«ÔeřP lŻŻőŔ¤g‡yt; ÜŃ 2PúĚŞEN5ĚI1ń1ďÍ™µ%tůbˇY=ëĽwÄ)k¨Ç€DK> ëÁŁ`natĐć:&2Đ]>ŕwy‹í{ř×čÓ×™ ŮŃAűéĺęŮz›'S$&śzx›¨Ë2tö˝Îk»±¶¨·ťĐ«ţ¨ÁÄ.ž.Y[ză¨SmBśib ¸T@šéUUéíăNľŘ˝p Ůź˝M 7ĺ—ŢĘ NX1ôE˘Ř é†Oą~´Z9tXŔ§ĽČ©ßŢHť¸âC=XéZWL—z6]ě˝gŻ |†k/ÎÇď÷wäťěă8TĎ}\@ŚIÎř¶whĚ_L‹kŁĆ›Ř¨$˛źU"+­¸Î±Ţ�UŤËŢ]ő„ru8–1ˇ¶ćť¬V-â —Ń/°PGďÂäeµúv ‡+9 ä7„Ś Gĺ=°â Lžú8ńĄ\ę÷ůž€—V󹼫oäv{ť)ăn3…- )]Ç™Ő7sűĽÎv“Nç%ÎflDMn‚k?`ă<–úJ‘†”`R„ Č”A] \–vߥ Nr› ˇâ€Üjőž4Bd)˘S{č¤ÓÄ„†8 ľO§÷ĐQ§@[öKlG&=šŢLĂĆ@\zćwDťĚŚ—‡ŢZźć6G:'.\Ľń5 »“ ůCş‘htkěkž#j0ĺíçŘ™8DËG>.Lăâ5Ƥ˘-™% `Kp嚯yW·ŕ„Ö¬ZâŰpÍ©l”’ě•m™{Îűäˇí=BUMÍâv[ab—đ ůÄĄ]đ&Ý›”ů;Ń;,…Ý|çHç&-yĂ30ÁAQ‰U¬f޶t–&LpĹ'5 áĆK1ĹĘ2VńgiÔ{ČX‡Év3·Ç@Œኯý´ŰëŹkČęŃ€ŚÉ…Ë­5HLÖ eaŻ­LÖ )aĹk,6–)•k˘üLcČ1(Ü"*p Žc›đz7©ałnAd1&#ţ[Çz˝[č){ĂĄoČĂö@#öşML8¤hWsLL>ĽJOőŤWLLz€I~Äť˙¸z IÇÍś:ËĎ`©žéä­ŮĹ!Ž"LE&EÔ L°`Lč…ŻÓ­}Ä。ˇ~ÎMN}ń é°5šiHbgSLÝ޸ú™l�zů¬9Ź÷Ď?L JĚ4ŻM­ćn-ţâ /z5}Czť±.}Ďëń@úĄž´]ťxĆç˙dĎ·—Ç{5$'ú†ťu:c’x±ÎwˇĎcÔěüb<1oġOÓ×cŻ–şkÜ=?0™ąOů×!Ý—˘üO0'1r!ÜiţęÜňÄŹ‚!^ž`Ă‚ýĽ0°ä /őWWcđlčŘ{ů čÁäżúË˙k™ŞM™SeI.—[.äś˙0ÉąÔ§ďáă iđ|đhRo=đ«/Yx©3 ŠŠ·­)b¦¨8`¶ Ěçm·ůýSU˛ř¶9_˝ćŇ×f vëÓÎ%ŕqîŐďľúúČ×_őkWEăŢËîÉď;űöͨc…ľĽń»ź» ź �Îɇí˝üĆ:$čŰű É¬Żžť~N]”sź5Ç1'x�č×j˘“[’§ĺ·-劓kDÝV¬>ŕ!꜡‡âÁá}Ł•D="´GU¦YѤ,'Ó xmÍeA.ŞŁć:&0š¦!"E-& §ůe?>üëŻ?SnŃg÷ …ÝÂożő`E™Ň‡ˇ€;c˘LŐ›Um4Â5Ź”Ő3˝‰Ć!Löüů§ź"úćę4UžÓ!máMN˙Ń%w,”L˙ťO—Ż\ąäŃIcéĘ• E–[¬Ý4Ŕ÷Ôe+WĆ`)Ę6oݰB`-›+7oÝČúŘűÄšM źŮĹş-ë—‡Y=๤(gš8ÉĎ+-•›9ÔUëqN±b˝<ß*F]Á©TĽ۶€Éß6ŇyHtĽšW€I޸p!˝ÂQŃôË/p •ţ€âŹK>ˇ„aý“ߡ˙“ő25ţJtę뮊”ťĆĄŻuŢÓÜ€Őß. Ľ„0Yě…&Ž> 3ŤżťHŔ§ŰÇ—ď;loň[Ęťľ9Ý-* Oâ™P0ů[nÖŰ{\G…19@CŔFć&pű$G&j^au)łŤUްÎs‚@)‹«„#„‰0á„7qZđ0¨Ť#śä[ë;Čܤô>¦ę1™Áîďđ& @‚8LL&čRÂUÚG=˘â*0á*‚Óu«źpi…PÂ0‘úKŻŠň¶F\ěd7ĽÉ!śŁ‘ţäŁółh0±ç{ + “EţuëַݵÓ0 ěô:±ť˘üĺe],Îł¤©ň;HLD36Î7‚/EuĚL0ÁĄSP”fÇ%.gL¸Â$·O¶ĎwuOÁ„ő=azUđiFÍÜ„[třT]2rˇZ@Ć|h‚ 3ťÉ"iŔ%QÄ}?÷)/·ßźnŤtŕşöMäp±äŔds0Ë~ÄÂä, S“41%7>ËÔ{ šžÂâ°Ô;“&°’XlĆÇ$&,öý4.Ô ÁÄÄ©gŚ Č{$#е0Lř‹:d®˝-Lţó–=‹{ž`’Ő+áT4‘”ńŞ)ąIE!I…~ú€8 ” ĆD˝/ %ɧtN¤ĄĄ~Ö@©;Ě ŰĤ[b˘LÁD±Aąź¸üňmb˘yµ˙„Ý'4—y‡ wţ$˛sŤcxТĺť? ĎăŘébnř bBŻ3Lą€’– ˛Ť©M“«ß‰ ŁÚ?‘Ý÷V˝‰¶!ąçßďŔr[>KżP0\˛áoža˛>~›żbë™»vÎ.Â0Y}l‹Ô‹7a" ÍlC¨h{żoÂŻ}g˘î(h�2Ţ€I tČ.[o —ŃF#†?ě!źőŘ/´a™ňF¸–ŹîČ#Č駆&ţ,#ÓFšC˘“"óžşéÂ1 Ó3>&/S1yHsú ěLć™U0çëźAU‘˝`QŐ˝‰9Ú]p×[ĆÄc𺡹uMđ ^ŻŽFsŤ7ÂJLˇ± * dß`~N} &y˝lřă]†´ĆRÓ؆ĎÁ<¤ĽE“öl;g%÷xţţś8‘ľë÷}Ák¤€,s¤łĎÔď§ŃĐP÷ĎßżíŞ¸nçÜ„/ps—gßý-Mp†1;˝�slđ5 :A†ÝÂ&ä:‹č·,„ť·Š‰·¦ m@«©őÄŐ×k“ę»u-đ$||N7‰��qçĎw@­g{:üXéă˘Y€ĐDO5t <ÚvłeÖŰĚ~@°W“ZߣkíMŽJLnő“|‰ËÍyĺ}’2X-1a# نë­âXČXĹ-"x¸-Î6`‡ MÇUuđ çO” FAĆMË›śÄHço°OęÄ!€.…;Nß&&ľ&ÔşbeĚYśĹí{—řŰuz sö`9" ţĎ 2‚Î ;›,ęÓ$&ĂnĂđn» ţü…YŚ#Ě~Ŕ‚QC㨣ȍ$%…Ű0¸í‚›)DekJŠaνĐ6gě޻狺Xľ@Ň(9Ą+-Sô,-;ÓÓűÔÖ”dš¸šSRtÁ™_îąXcŐî+bCjJ­Wđ:¸TCŕtAëńmj•ŕwPqŹŠfÄ*śžřß`2ęä濤;t˛.-ďe±u;ąrÚĂ/XŚn@t†˛ĆˇóB«o˧á-śZ€ :0ˇűď©ŮřQú»Úp®xX®ČÄdúęĹ€NŔĆE°SÍÔżĐ;fžwŞlĄÎ0Rä|şC}CMĄĹŮŰę!žîMDĽMö.´“é-âNŔŤ·ŹsúZîđóµä7¬?ľđn-q÷ť¨÷ř¸á/éRŻŹч GĚ~ŔďŐě§sítjD&&ňŐžá•gĽP¬ą0g*ô–„ëE^(,ÁÝÖB5I˛…Ao­`-Ü€Rľ3B¬ŢÚ f`Ň­ bËuő«0ĺ‹Úµ\Ƥ…;üÜž†Üţ`OÎ Ó?N­&yçP­p;ĆŽôĚo‰:×ĺżě÷ĂhdŐÝěK»v}‘—ۧą#“…×’íéÝZ“vĆÄŢcb˘kŚÉÔcĄOłZtü ąnł'ÇęŘIďĹHF;CĂćHçI°eť]ş‰Éę»Y—÷9Ü«E¨7YŔWuĚËt`+„‰Ć`9 ŻąyLz“WýhîşsĚ‚É5ęöşü8fĂßŔŤ#ݤ#ťkîG? 4Ţ<Ú; ¤Ö»#7čüĺĎ8Ź0#rŻ*¤ÎŚ- 4Ž‘NĎŐ\W!Ťxŕ(Ěđ˛ ~Aż°]bb%¶}φw—0&7ńĄÍW¨ó=4|q?ŕŻĆtŁ^ę‹ÉBĐYß@ß}OxYIŃwt�׺€I!č\Ďî-$:őĺ"QR­ů”ą¶>łĂOö˙ąôuŤdżĽ—ßźc áo(Ní¦}”Ó˝őýLL67Đë=tçPL–mŘľ#nšěŘşJ™/0'›3ö^ľK¨%íĘĽP»łË›pˇ×Ł%]@&‘2®ć&gb·!]϶]ß\Fsźguřąř ţÔÝß}}©Ţ­ąŃđ×éßÉ-;3»}e? ˇoŕŹŕsD$&byü†ĺKgČ{q•y ßHwyřń®Ű0tŻŐËç1g^ď@á‘ţ·ˇm Ă´uco¨˙ĎTá4·ůŇX«ep p1ú;¸=Y7°çPL¶®űëź1ţ}ˇĚL¬>=ą2ů˛ik6Đ„1oLµ•ŠzrĂ< QDŻe? uHdb"bă–XĺgŞ-¬7K¬Ů8o0™]ÜĹ‹z˙=7ŕŇüéÓú#“ĺqÁ_­µNám[ďo]Ŕä?-k'V?`¤coc‰hq@(Ą·-`ň_«0ň1áŢďXłkó˘N`lńöŞů‰{>Ëż‚É’ŘĄ±±KmčÚ<L9ăNu)6±=/0Yľj`^Ëę“„Řeĺ1čDµ|Ý˙“S8s„P�Ç«Gŕ󱺻ŮYęŤ÷ŐXÝŹ°ń`2/ <¤^ć6F¨»ó[Ec˙Ěb7Ţ@&«HÜÚŻĚdé@QVN6ń™ś9sţN^…ńć2YÁۤYö­™“3ÇÝłä޵Ą*ĄLŇÖ~O&gÎ=Ľš…A:™”}G&ŮäĚŁ[Yľ¦rĘä2áăËż‡ą(N|q*^÷iqńĎú†LögĄSĺŹď…+¤We›vĄ“ Ú˘m‡ąđ'RVĽ-ĆA¤lĎĹą?Â~u& ĘĽňň˘ŠĘjiŰŽ2AG §΀ŁčćÂźŇ8ôŚĹnóű ·?*Ţýg}u&ÎŢťóYáäyń–ĄâĐÚ2č  „YźÉ“Ćý 9ôÚKs\Ʊä }Ůmî¸r‰s ş•VřŐýŕŹúęLfIutďÚjÍű6Ţ;KČ˙ź‰DGq�¨I �{bŚDM›©Biž…žÄK \â86śÔŻ‡ÍŽ2Fc ĹĄËŕ4äâTŤ RŁ6ŃŹ4kÖ*ĚŞ"§Úäč±÷=Ľc$I+ýÔĽŻŤy *´Dîć‘ĺZ:Ęgbâ)©ß°W6ÉqÂ@^ć=3™JnË�Ëś'‹Q¤2Âţ1Î)r…t yśT6xcRe–Z­÷¦Ů|e´€ÖI“Öääp`Ń9§Šai0jM'¤18{Ňh{“7Ł7“˛ë18žżüŁĎ|LL–VĘ ^©( E·éń Ă­ÁóI:M™ú¤î˝ö\€˝„­<\•ŃFŰźf±FťsŚć2:PžÇäs" F´ŮJÚ˝“Ź©‚‰5‹t ˘¦r’§Jd _(`M}2<÷gmŤĹĘú—pęŕÜŃ!hcäNuő©Ź˛I@%dGŃŐŐÁýDqŕŢl'íú“uLrIńV0i.2ĚW”0őYµ÷Ţ„%óÂ×ÄŞ~N±ţŞ1膉ŕX%É8űxJnUßt‘C ôídüŽÉ*Lžm‘É*Ňٰ„‡Ä.ŁĂ€3őg+^{_Pňv80×\c×Ăq@Ë©úĚQ 0íš*•îŕÁĺážWb¬iáeK…“u¸"›1±6 č€!u¬`6'ęłžď˝ “ŕÔů¦jQŚ+tĽ>°cŇMŐǦjȸ:$ĂGĘÇ“hs¬ŰN6$@¤ż1éŁI˶’Ń9ʍĄęďxÄ$4G}ę„CůgäĆZarTŮF_0QĄi€y·ĄvLVbâÝň¸Ś •ŞŹ^]®“Ăů1ÍoFšł®O÷~*ÄüĎĂŹ€$| „‰©ŐÁ™'ŁMDF*<b3ßŕ<0z§ßęYŹÉŽIѢ#¨ĺŁëtm€-‰úč_{7 ¸&ÁŔ·'Ő@Ű©ŹhŃĹH9N$ÁIҸ)Ń9 &ż\ J…śm¦“·bâŁä}Ěĺ1˦еM+l@?x!ÜD=·“GMÝUä°]4o[€ĘËD4ĚbL=ď˝#L’ŰĽq‘Žo‰‰ß1Y‡‰ /ňqŽ>— »9-‡4…1Ť!+ÍĄźJźŇK8ř)MS˘¤9•‰Ó<•áq¦f˘Osň ~|˙vLÖb2†˘1x\KÁÍă* Őlřpďé˘ôÂčľtAµ ŞŻ[ţËł<ĺFTŘ`ů´c˛“˙AaÜ”°c˛˙4mŻńi$Ńţ^µ¬“u„§Ź¬“•řńżŇ>ďQ‹vLVb2p=î¬Ŕä“xřŘúľc˛“ßě}YT×öwX¬ő=}ÓTŐ€s'0 Π÷ŢFŔĚwž§Ó 2¨ˇ}\KP¨nz¬îfTpDqG 0á?äń{AŻŠŻI 1Żßoź:ÝŐ‚-J{‡6µW8u†}ęŢ›ţÝsNUýöţý—ą#?ls˙+§č“MÂäXĘóţ{‰1ÝÓČH0[KLŽĘ,ćئ Ĺk¨őg$üL]™•É-Ë 'µ+çęx–OGď[ 6E1Ą˘Ž>(_Ó5~8ą˝¸˛ß‹Ěs0™­AjĹĘ„ŤK\ 9’Ź^THtÚ9˘OäüÚ^XXř~pĄÔtŹrVíaĹ$†/LśG‚˝pŠ5ź÷ćyŃ=k3`Ňőëź˙ţŻýőĎ*n>µőČş é~(űE٤JöáÇýŠT÷kXż”z…=Ŕ™no> A[%ŢLŽ^ĎúĽ¶Ă­AŐýB¶,»ÉëäóFÖĺ¶>ôĚyžĆĎí4`2KSćĚU.°âAe®é`VăÜžf»Ň»q㟤`Cٸ‘Dk9L¬\ŕ8!@ś+Çę‘"K¤"ę -ôQôî'Ůd”JrśyaÓ“™ĺž×íśIŮĎÉĄ)»tľ6o|Ú<ĘPľ»Ĺ«˘Ë€Él 0\m.…¬¤ž+é[HżÜŠ ×+ ŐBv¤?"ötňoRjŠ„¦ŔôäţÄú€#f@J“.˘Ś&îĄgŮΖ†Ć<É©n®á9WŃ>]ŮŰŚ_(¸'ABó  J󸇓”ës¶) Č& &&ŔâŐÉWkĎ9 í¶+Ű˝ő 3íg;ö’t›ިN9$=Ř]j*�'©îm:2@Đ1s(>8}×!Ććóxý×Yi(ŁÉł†kř‘yQ'Ńľ˛źYČ[Š×ÄĽk%éÂ#&dć; łÜyŚŐDÖaÂn»ĺžm‡¸ßiżČ,PˇÜcŘĎ Żgľ©źZö3Övý ła"q8‚‰LcŐg±óÄy6ńśe}ľ�ÎŁ#gYż¦áw{˙ŢË“—ůóUY„˛—ń;2ÁW“‘$šçÄ<Tn…‡K‡É_ ň˙Ů=—/^ś0@»wëQFJçŮÎzIš3—PÁ&¸şđŻŮŇ INX†¤î2»"k0‘pÝ'×ă‚ÉňŽB<ąyů,מĄ4óĽ=JĘ~} «„˛źÔű®ďa&K»Š‚@GÄ˝ëşöźĆţ č´Ä)ŹbŔÄäbˇ–öł&oÔ+6ŮÉG$�ą´ N!µV(Ý @Ͳ%â~oâM‡(’Gě>€ 4ü I0QłŰ‡ü~t:„˛ßřb®őwŤĂÄ» R~L®Lî íż§`ÂĚ´ŻÂ)3`2@Ňö·ëŃó`R2$Uôóŕ~¬{I©Ö$PrĄ­ž«Ó]f8(&(O p‡8a˛<“}¬Ź«nőhËD‡ó >e€Iďť[!dŮřp€`"xqíżŃ©0Árň¨Ĺ=;3`RÂaRa ŇŰŹ7ÜW:ěAÉ«“č;Q4TSÁŠí„�rŹÉÖR{e©]’8Lř“ÎÍ”… çĎO‰&Ř<,#¨ŠłI&Nuźí#U7€€`r0Yaĺ0ńcýtuâlҧÄ@˛»Őë3`2 ŁźÝ<9›\dš *ÝL<ŁAűBî^Ʊ„í,ŔňR[ń7´°éX˘„·`śO:jPeÁe˘t¨J‡‰r˛A·ăšXM&¤ő绂aqtáóz¬%ˇ}ÓWJÜmźőK6&«vĐŻŻIb_ÉĎo^ś˝)�ńęmď5IčĎvĐ`łyu{˝ đptP(V×]m’lJď¦l˛ś0ń®ědc Nçş3‹¶˝Đá#˝ł,äËě˝´×řŽCëoi’oF»ykşřŮ–<¬w˘aÂX{żÉ7`2łi…"ęa“µ?EŤčÝaú×ĺxźt ŮÉĚźB•oÜí«ä§.�é ůví Vi˝Ł~hý=:ĂÄ[Xu-ÍűĂŻâ«  ăkÍ#z ë2^ŻĹA9±á˘D$«M2ŞTFaŠš€?rµ6®:qĹ·:—ş*·đŁ#mý^§gţĽy¨BĺdY‰öĺµ×đŢ\üě«ó?hoŢŇ‚aš×HóÚńÄ­ý'î™}ĎĹßŐ0y]ôtś^.ßG+×đ#ťsę‹öyxŻÇŻâ×÷BÍĂú<§ęó„öź0L~Y0‘MňKřÚf`­˝*öZ4D/D ˘}Q˝Nď™a^x˛“—7ĺ%Đd› QDňkČ^3`’şrµóEIŤµď4Γä)�3Ą¦rlđ"5gť˝özŔÄ€ žhOłű&a˛šB˙tÚę2žtsRuRĐŇQ˘\=ĂţÄ_ĹĘ Ü8Ă  WgŻ˝&01`rŠŤ ’‘MtéúSw’ýMŞŢâ]PąÄve'  )•ěö–N¸čěµ×&LÄŹŚżĺxqćŔŢ’Ů8Ż7gŁźşŇs˛ťo­ Č˝]쎿ÚjnęݴѧÁJ{9_ęęŽ K©ólŐěRµµ\ŽbŻ˝f01`"áűlPŞf–÷Q+””‹tý”Ýžo•§tůi˧Ô¦‡ş_š#/HO“®±ţKh¤(ÝćáË:{íµ‚‰ÔzKŢr†íŞÇÇ=Ö–D´Öý`ďlëhÎľNv¤µÚŠ˝Lŕ s�…Ö$bŻIDlĽŔ™IŔ*Čěµ× &Lx;řµÍAŔä®T{śÝ–.mm'»-×G`2NŁ&6 s;·Đ)öëş±÷|Ĺ^{ť`bŔÄ&ÓĎ˙űO>ýcý ľů1T©‹@Rö&µLÄ€] ĂÁşÁhÝP*učěµ×&L`“_}řÉ‘‚íÁ«y “Gv Ąm@Öů)A)•źBRR€l:({íŐEýÍâ§;şb:ĆoLRL)&üüÉvéŔęŐŽČj‚źĽ~óŘ0ąH§V”A´xæśŰQ˙ŠŘkdÎéŃ{ب*Ő_Â13`bţEáöí…GśŕĎÎÄÉu¨ĆŞq×’‰†´;·‹LN°ä†ęNóTW”;¤š“;ŇŇÍj 5®�uŻŠ˝†Ź/ęDďµ6¨Qˋׯµš]TŽ«uGaÇőÂŃ€Iü‘ĂugůőžTŤâůp.Ńž‘‰Ăš¬$}B(·Gř$ŔV•őS Ř]ü# L}Uě5X`+#ű¬OŤ|ëó®Ękň ĺͰ– ­äu˛*îXí¸2Żź;¦w–†VPÝ€É,M1-;{#,Ű'ŐG-óg6¸kJĎć&›¤ôfo\ÚÍF%ĐÔr[ćoÜ$"ÓĄŢüíí ¨TovîV°ý°*˝*ö§!ę/+o˘śX@ěz?‡IgŮđ^‡c%ËáXQ4 rGŻ3ŕDеŢoŔ$.‹ÍQ»ôÓ©LCŃş)6Ţ#‹É˘ńŠŘkNő ŰĹŁ÷Ş´¦®ÚÔôş×ŮXČĹaRş2Ł Žk&vÉ‘H°ÂŃăä&eˇkT7`2kSLܦpÔdţŃŹ^ŻJČ,!‰ľ˙á/< , X˛¸,ż2ö­7őçćä3Ď>óShç!"Ľîyř.űŻűŞ„ăňÜ—çĆwĽŽÁ‡ĦSţ#ögÔ üť¨±űóö›OiSů§°×<Ç@v9§ęVÜĽţ(»¨bíXjh5i ş ŽçĚÂŃÇ]-ëŽqÇä& Á:¸Ź%Ă13Řk±®ˇ/- teF¶’"ÖýF˛7LÇ‹‚nDüýářŽA‡#Ăú�ç‘»ýlÂënÂ9ţŽÇŠŕ~úáp ŠłI§züfśMbő)ňsY‘ ýMť%‹kÜ0ńx×çoŰf-Íě,Ţ–źźĎJ‡®"XŔd‹ŠeÇ»Ž{J2Ď·çço› cë¸*`"ęł7˝–ąŠ,Óńě_U‘źÇЬËXĺG Ś‚•ŤőĘňĆŚ+ޡׯśČf¶L(hĎç3úí““÷ß˙HÁŘ3`âéf}Üq¤ 0±!Ç6‚†OŔDÔă0˝¦YSa"ţ‚ţáÔ2éÜ6Îc8ˇĐrúČ_ÇšëN0nÉIŞNŞŔłl|ě5^ńč=<Oěj(<Ţ‘· GĂä Őy(z˛—ŻT”6pÜÜŞľRoa˙đË_ţňçć~É{óQô”&ÄcÓaŇÁJ†©ó’÷)Ç‹›×¬]›ä<ÉÚAd»‰ˇ8`BŃ{ă!§sC'ŰJBĆ$pă¸*öa‡xŽégxxŕúN¶;´—ÝőŞ×ŮaážĆâÜtŚo:©sa�IuöćMMvIŇRŻ-Čl\Đ“ÓlŻ]—Ó$KOĦJ¦tđŘFd&ŚđUmť�LNPĽ9úJěx]ŹŰť.·Çő‡ßÝňŃ/ŮÎŁEˇUťěŃűôĺ 0ü¸‹Äę<Ěď GKčťöh;+ úz*4ÇtkÉęÉpŚÇ .¬f—*ř–QŻĄ^+ía–ŹŃn?‹bL®«¤As˙A+^G×K6±éĽŃńŔ†=Ç\Ŕ�“’zôábżdÂöÔQfŹ7ęoE^ᇏ‡˛î…Ô•ď}đ¨Ťň–lÎżI˶´ş<T玹ä8y(¤6n…cĐëňlĘżçÂ``Ë=QŹĂ Ľńă-ďć6Ń÷ß݇’� ‘z­§‚Y¶ť13 Ň®Y†/‹DlÎcł‡W“Ó÷«,ĂR݉‡×9LJję%$¶PŇ”…Kް¤(qGý!ÄĎ硋WuنUUů ß§×áč‰rtÇpś˝gFö>ÜáĂp+qěç©×�Ś>éYŃPoľ–­žóŘ&˝ŘuŞŮÍKl#ü3�F)†%Ą['ÍĆő碋řŁÂ)Ćxý%ă0c5Ů™›—·µ_ŮKź„ĄK€`2 ńĚZ(е˘‡±&µOĂd´öÔCŕh0QŽ[Úňňňą ĘňµUŕŐJ¶×Š˝fśM(‡V&�ϬÉÖs{A&r&¤Ę"ő\™|‘‰#,ő+o/'ŹJ Áxm`b°×dÄ3¬á§-~Lś•<ŰôŐäF·VÜ•.Š#,} ÄóqQ=±Ú Ľ60é ¨?‘ăőQÓľ "­MX&–Ĺ!;`O»{Ť`’ÜhŹĽ^Łw$€‡ÉńRl2‚JÝ'Y4üÚŔÄ`݉x ĺ<#+©×x‰bŠĐĄH"6đŘŠ8D®5©’čl0ŇÁűřvň7»Ż łŢ`Żm âBUĄgk~ţ!ż$RŻQ)˛­ˇ Dl-<xlڱȵâZK7ĂĄĹAßľÂímD@xÍ`bä7QžJ˘w.?˙C˛¬‡©O!+˝N öš®ÝL–yź(Q S¦"ś -ňFŽ2/ŕť“M�Ĺ€‰‘{-K@0´3Y¬./7WŽťnMŃąmł€É,”ţfŠś>�3`2;ńŁ_0•ú¦¤[Sxe·Í6xT˛UHB —ďŮŘńú<Ó^TIP€Ě€Ię*®Ú8ŹhhëÖűcĄí›Ţ'XjQbˇ őŘ:jÁm“馫qťe:cc3¬)¤:_%Ťřf¬·őŘ?Ofkh:N¸"`^[[ë ·çLtŰg-ůS® &s ŰŇšhéSKMO·¦'`ăoěn8&\űEÎ6YŠÎę6SśÎ5Ćmwź×©ËűiUQD ůy3ş3˘E�ÝZěźźňÖ ·đp”"ŕN„€é˘·†×ŠÜµ ś2˙ćg?GÎď˘T}tç r’ÝSHk6Ńa–ZŃ×Dşµp6™`‚ ŤĂDź‡Š¸“<L®˛äÖÍ›ßc¬OK ěC q‰€+Ŕ ]ęŢČI›ę÷ĂYA/IéÔ&~msŃ"Ý‚j 奆̭ŕ:yôy* aźCľž€ Đý|†v×–˘~Ík×ů÷ ć[ «‡F‘Ö–ĺälpč©ęKíaD,t¨†=ä Ř슀‰T·Řżź ŘĐ4O˛Ő-’¬Ëâ)—®_«J3Âdܡ’šŇ®~Ŕu‡Aɱˇ©Áínl QáÎňC8 NkjRÝ$¸®đˇŘ?ä ÖünHoäśi{•¦$j]ęĚăâ$ČÓÔŕÁ ˛¸ŠŕµŠQ0hŔ„D—':2aÎ>…Ŕş]=B=4BZű#Z–”M3/i,µXč HŔöš¬m:ˇ…SͶÓŢ’öW 0?şTw^Đ´g€É7�É>čăř¸fĆ}Ł`Ň/ď(W«ÂB~ś6ýgV¨â^.ß>đéJBÔű'ä cŰ57ĎŤ >,”)TĐéÍĚközřĽ;!®„ BhĂqáÖXh®â¶ĺ!&ç$?ż`}®¶ZšoěaGZz&ţ,ÔCiŤúv”3L|á™K-"ŞF°i0éW©č`÷ŢĽĆJíű'xĄ JNc ŞOŔWž&\§Š+ç$× ęŹ”sÖ|g»‰CHýÝôáŃ�Ź&HţWkÚ¬ZŢÜťŔ¬$ôň@ kâLćmň{ÔĐśÍ:‹ŘôJv01ß[“ÄĆť¸e“±šD1Řp´ŕ‰ł”즮EZC‚�żtiÂ< Xřš‚9+’ÂéÖ¤n‹–YKŔä0Bm$\Ç5úI Eĺ<ľ$ŇvtŕGŢÉ vw1˛OŚř{­ÝÇ‹8ţ×rď Ö??,őG^¸Ţ]Ä%˙ޱ–«űđ“ół âýę…ŰďXQĐáXÖIˇĹn÷ŃAŹ8 /íÚôů)¤C‡ iöLŚ: ędśMÄj’ĽnĂú$&ň«Őť@%Z=TĐL”sěźüôgOmJ:XjĺőŠH·†lŃ0™Ŕ˛Ť±ý¸U*î8PĂúPéAÄ۶&;żĎĚ0ÁĎÜ;1?W «tçŃĘŇm–`÷ŽAźúÓ”ÝŔ“^A^î®GúG¸´-:BˇëţR-"°˘„Ž)žŁE& ®ŚÎC—çŘŽP•“~̱&k’_šg ĺ,Y‡I´z¨€ V”_óôlᄯĘ‌˛R¤[sJJ$›€Éî|ĐY8LR4ôSĺ€_É ¬&Đ&©ż^Ž‚Évďř«ěfÇ!ź&ő'`‚Ő$Ł‚Ş &ę3`""qčuQLˇ€×xşuÔëÔaBsn ÂaŇcŔDŇ‘RĄý§uÜś˘Ęa"ń$ŽWgů1‰ď8H´&ĄđT°”n-:›dăGXl`Aşjd¶Q V7KĘ‚ hͿNjXľ]C˝ĽÍ±–-;ó)»»lĎ'ď‚ŕÓ0YN’”‚@Ť‚Éx&žě\črGB˝€ËÍe ăJĄ.Útüעa‚ó¬4Î&;ó p„e}ČKo&$7LQEňµŰ ”žmUÜĹ(–Z˝H·¶PŽ$`Ä€[ą}Ĺ.Üč»ËŹĘűy€Ă3ÂäNCccÂ}KIĚĎS…cžP,ýX`Š‘„`źú0_t ^€ČcĘjBnÖQżâčíZ)Ŕěáv�Ý v®ăŢŚŁtbÓ™0V“§óŻŤŮµ·°ç˘ą„zh4iÍŃÍ÷lŠY#Xjzş5-Ű|5ˇ-i|?F !źAŹ<fT€ĆÓl[>+!·™ßÂâ—ĂĎ]Á >`ĺnô–Đ©¤Rˇa©?±yŚ9 ůw›býUa Ü莶š ^tĺ-"ĐÇ֜aćźîa»0¸ú4»MľŐVö)hG g´P!˙n2`˘ç_kDâ5J´ö&U¤)¤µş+ďlkŠúQß,5¤[ăć'`“éĆ69qÇśćE—ß(D6ŕ¦őúöö †Şßűđý6¸=7†83÷Pnn.^Şˇ1żŹÖçÁΓŰęB˘ľ!�6Ş"%źš•˙ńxAâ/čq“!ŢŻ5¬÷‡ú[ß§@ĂôÜBh†®MÍÜöń‘6ŔÇł~[A[Ó–ßĚq/Ç­=94`˘›ţj6M=TŚË7Ń ?Ű” nş»B…0?Ë’?wˇżçÇ{ýdBßOĹEűšë÷ŃPD�ĐCUş"^|D3&ôţôŔH¨ hŠytKŐË5˝bN$`Đ€‰IłßDO´öiMOĎ6ŤĄ¦§[ ظ)‚§ÍĹ}Ü‘ů dąÍC,Lk¸řźĹ'j˘]úźk §DšS=bšŠ Š4Lć¦Í_«ÁdŮŽ˝&KŐ%“Á^›&€BĆ dÝÚ/A\ŤýÚž=n›ÎI‹á÷JÍö‚÷˙ą3ĂĀɜwÖ­_żćM9u¶«‰)™Ţ˙ěîgßËö %h_=րɜ”Ô…‹ßš#§Ľ,LiNffV€ž/VgfÍ›öCË ž­sŕ2ť“mE ~ŻP‚ÖtËńŠabŔ$-U;ó˝,LdzEJńŕ ˝Ą,ňu±»ěg {ż¬5j¬‚“Ö/)đT4áGoĎeŢŇ:©C>”óÖ¨vâeô^c“ćMţJ4©UĚ&LRSS�†YÁä´Ą˘Ô/<nŢciÄâf"ęY­Qsš8iżbŚ8!Óü¦oIĘłĺCëŔe›iuIůęsÉöŞV&ŔČ,abž0éňK¨vuć<dÜ\ă‘Öą)g_ől­R¨ßLŞÁ=pŇ.2úжš¨h2)¬÷‘ß:éöp}Đ%|s˘5Ď’5őj\¶Ěą=Ů´ĺIu«"צa][}ŠdĂÖskH–lt¦ ×S‘öO‰Ë/řWąÝ{̮ˌ5¦ÖTˉ]ĹAÚLµj–>I GŚ‚MÄý,ýÜŻF膤nę%0ÉĎ ńdkĄśËĆH0“ŧ›”öýw@ĂĽ'_Îťüw ĺűÉÉÉo&ęĽ5ůĄ,}=ůů?% Ž“˘÷@ě9ĎŢë$&çŔřąÄŠ[Żś!rX…%T v˛Ą 8-8iÉőZ6µö«ř»~źâŞ+Ě5š>č}éúöü‚Š?Ld8N‘˝źREĽµŢŠ?cň>-EEÉPřÓß7“_H@Ă)8›¤~?ůőđדߢósü}WŻ<^ üSV“ÖŽ˛yçŠr8L,&�“y˝›[đÖâä)Ô.í±žÖ9iôë{LW¶ާÜY&\ô ’•Č e˙eAd¶)B>”1ĚUB…h(ç˛™Ş­XHşiŠ8†ř&ż”lŹ'ëÓ�“[´˘�Ă·&żP°°|®<ů2^u.§^‰^ŰUw×Kç&¬é\QÖÉňL«% &”{Ä|ä^˝„Ő$˝Š8!Ęśą6¬&íś“¦1e¦ř15©6Y¦ěrH˛ň,ůP}‚ËFaÉ :˘Čk´ÁĚTčIç«ÉÇß<ţćűÉĎS&żś7ůŐ“˙đc©±ĹŻO}ş"Ď şBpŕtWGĺmŐçáŢL‚çŮ6v§şÂÂ7,€‰ÔSXA,pZMçĹ&›člŇÇłů‚lÄ!%¨hŃ0©%H˝Ä#K‘”§äC‹h8ˇhW޵8’ÉFÇŹ[“ź |űř1ţ–{krřń—_Oǧ§qŁ\áYťÝ¤˙îY-ÖmŠkVvł+%éůÔu†W6Dß~0ą6D_5#qŇ<ôd7ÍďIš`!t&ĄĂŇţO~:xËHŞÔ‹tHmm¨—ÎO… Ýu÷ @ŁMü´ŐŽi0AÓDĽű_±QÚsL°”<ţÎ.ÍL´ëđ×ő¨}ű-Póý·ńç¬G ęËşÁ×1zP‹ásúŻcŃ$ץGYđ"F8ߤ,äö®č¤a_Ą®×ăű¦VÄ´„‡É)Đ'~Ë«+Š\Ő8. �09ÇŠ›ťg–!m‘aí«Ďň#¬ůH~Aáz†ˇ•‡˙čž+{ -Ś_{ú &ŁŕĄUÜÎٲĄ‰>ő^ü4]>T®\6Ŕ„:ţČłŹ‡My< „Hsžü=ÜŘĄyßOÖKľ'“˙‰&lqĂUNI*wŃĆáS)ľĆxUŹ&ę'\“dŻ®VB”>Ň<ÜŐkt'Ď1"RÓmřn$"E…ÜůPâĂŇťµ§Ŕ"ŰĎx¶^n÷‰ÄJvG˘nĺś´Ŕ’ˇŮ8?Âu*ÚP5 füŚşHř= ÔN•-×DC{¨i˘SĚDIÔVb"4 KÚ[X<Ý�4_HrĘ÷(ç=ÁębŠ&% "ů*ýöž5›[›ĽnO#ó„ú_&%A¸V‘«[ń¨­>rp{v…ÔŚÎŇAŻg5n1Ö§WśO†ÜžD‡ÉÁk›|Pîl¶#š|˛­‡Ň{(ńYunAá¶f»Ä»kŻmËo(ćjś´ L­Á�˝vĎĎo^śłÉ{0'Ç0‡ÄB!š?8fS¦4yş|¨¬sŮdÔOsš«nĘ­Ďyé@yë›Ç_ Sçđv´nˇ/L ÁQu-ëŘyTé$ţŘ"řŻU”Śěă˘~bÓ!Wźki×®3 ¶Ţŕ#.~đźŐĄ'ßŘĘúýIZ¶—Á’Cľ}ZÄ`0Rń.Cž„†‰ţWähe$>Až*�*ĆĹÄ™ą”éňˇSÄ\µň3а´ŻľúęëŻQ ¤âé5mqÂä^ç/EHtW~”úúíµ błdŔőż–uG#'‡ \‡6}´tHČýńPĄËˇá6¶–3ěď‚ ťD`Ö‡QąŽÁĚ$š–Č0Ô2!…d‹â‘»‚eŠ�(7APá.‚ŠFÓ…>( Ý6]>4ňźŁH8¦ tK˛é0™Áâ†I_wŃ oŻyŕd™÷Zűşˇ7vĺV@O6ŔŐ˙Ň­ĺ0éŰ[$¦ô‰2§űÓ´B— L7Zzúˇ·› ř6¶‡ü@Ř]ÖÇîD*ţ bČ—Ŕ0ůç`rîłG/)W'LúݱľĹ§ĘĺCľuyź2Ŕ„¶†;>W†¦ţ7ś&LâővGYŕdY†űŁ.SŰß]9·Łń$zÔ5yź0e<ŔŁ2�­2¤®ÎËÇ“ىŠÚřâE)i¶§Pb㆑X63ąŤĹ^MÝé(ó"řtg{‡ÉŁ.Öď[ŃYĚCřčÂa˛Ěú` 9Y¶B÷Ť&üpŇvća7;ŚŘ>őÝ&˙Y0Á=hČ€IĽf›…hś›Î˘ł%ćĹXPJ‚nĐ�Ż"Jgą¦ţ÷Łí ü$°Ŕ¤AČýŃN:‹(0oĎžßP¦Š®nÇŐgÁDí*˘!&/g©ď„EEkÁv«ËĚŠĹxľ%ĎDnŽ<L¶Ç„ @zäK;Ę:ËBľĚ.‚‰%ř&Nś~6iqŕ±\>ŔÂĂđ’®2·îőňű,>Fa‚ôâ­x@]v¦tĐ·ßLüaxş&öÎQ&łí§w,ô'qOﲂo¸>şŢyŇđŮGké[•,}C“ź ?%śXiĹ sůéRď^¶{;¤řK‚x ë[eeý+4ő?źĂő\Ń9§J†V ą?ŚÚŞxN6ß^†÷±j%Ű]w„EáP@G¸‚óďg|ń‚L^&ť‹Š˛ţ`ĹŐčŹÂňÓŰň bS×�úV,<¦Tžďj ÝËAx·Ő»,z yíÁő‡Cnφ¶˙JMÔĎI®Y‡<ruţřžK]!FDđ`^ wá‚\şMnA0K„Ş<ľďŇ-ŰĆĐ€çĄ`bŔd4m.L®]łÎÁa"U‹ÜkúGť[·R&RÚ­Ż?źB]S†íi·nˇ`b#l0€:?·sř`rlđ<F.·Ź>7Ď™G‚)†ĎÉóDźpuG\ő1ćÓ.*5Ă· ‡őáV˘âö‰!÷K™“űĽJďý&zî5’[O&'ż›�•éÔµ”'_ˇí i­&^Nn–4×ď>GŹ&"tOřsGĹ㉊ÖmÝUcú%ú6°¨Š>ôrfŔdçO *ÚĚ“žÔŕo˙Óą×lôáć‹áÇ“ßÖĎ{ňÝçőS¨kö´'“_ŁÓ†Ő„\ą‡=…*@ŔĂ'Ç€I‚]TTŔ¤ńüÓą×lÄoÄöňýwvT¦Q×€P 8^Đů ‰ňý·Ý ËŹ“Ɖm01ŻéfżŹÎ˝fSč`aÂoo×*`(™tęgˇČ'0„ČÝiřDř ďAiŔäu9›pXš»§ć^ŁĂ«Ěa‚ŠŤ@M]›Cô&p%Ź”ďA8řęÉ·˘ç«×�&L¸¨XM2+őÜk‚Y0Č´­„W†ĎŁ©kiŃ0áŶôo|Óá޲ôŘ€I‚Ăä ç– VÝi&4\šš{Ťk(ľµóŠ˙ÉwTę=s|ÇaÂ=O´cŚ0‰ÉĆ6ŃabeLÄ[ŘşłŃą×`&<倱6YŻ M]›$bě<�&Eáń˙O¸blxrG‘<lŔ$Qa˘‹ŠRíŕĆMľÚěUš’{ vëńăχońĘtęÚ-ęŔ(UtHnő<ďđÉ~ 0Iđ/ÄJ¤Đ™qz;Mc¬Ĺ ®éUQ‰ľÂ"5SGý0Ń3T(¨E3ătĽ‹“ăÓ=¦§[›qNâĂÄPĹw!Ć÷<´čˇ|Sfউü=/tĐ€‰HaóĘŤ¨k)°Řô59öÍ•ńŹ-6LÔĚÜĽĂ$Ć÷<Ĺ˝ĺŮOŇu©«¶`VęŚ!ňód¶„ <mŠ; [ĽŘžuk“éĹ2r)3¬&.ŽČv÷«žUyÖŮ‚ôJŔ •55ŕŇuúÔë<űFź×7MäOT\ľJ>â4`ýkŘ“Ý4µ¸Lyö­ß}wŁ?ć­ëVޱĆđµŞţŕJ{ě,Ó=%- Ť$Ćç÷ĐĎĂü�ž'$bý6đ{=Ľ×Ăy%Ö’VšUŻ)(Dţ\BËŹ*Lxč =LçęŰ= …3­i©}©Š+Š)­XŘŃe—¬ VÔOX O\Ż4ˢ p¤$©Ë„˘b /n&Ä_´Đ۵ě5pa=Çú=7ŘXČĹůT A �¤8˝µ­6p65 ‰>šu F�Ş“u‘?®ĺ§˘Bb€^/…0Ń…!(�ů łMÝ$±+E·bd`Ăď-ZŃ·.n™·ŕJ'×ω¸–Ř„`bĹ4Ńš©Ąy«,“ÎŃĽŘ”é1·Ş‚^şňáëaă8Ť�@�<ÂĆÖďŐWĹŐÜü$©Şľ¬C´š‘?·¦ĺçr¶Rĺ.m:ţ«Ö,)LÄŰÔfA<0"Č 2łmŢY,é`Vゞśf{íúś&Yo­ŁÖô lB99±‹čł–AŞ]f¨•µ®Ń.ÉoťŘĄš¤Ľ!ŐTüMÂí×n¤›t0ł1-u¤Yí©›µ}Ž 1ßyÁŔ$űÚ×ŮÍë‰í,�űüHćŇ|}[ń6/”Ř=.P#­lgÍrű""a @?*Më;ĚÁJĽBj&g>NI)*?hł-Â`<Ď–ĺÔÚŹ1 SëcjťĄ–ťm;ĎŔ&”CĎ3jń,mâÖw°@Qä1Ŕu…'YB,üDŃđeŢpqH˝ťhÜöi•ťGß°sťÝNěĺ_´Űca=·OĐrŃçö_A€`ŕpdX¨"�°o1—öĂęáŻf®Đ’Bł6đY;[!ň7rrWś.tiF•%׺{@çĐ0ą@ÉMř»1ŃwJŇ€1aŮvĆĚ,<QŰţ§Z”BkJ6ˇją×ÓĹHz+ĽçČŔXK\c÷ĄŢ“»Xw4,¸ŠÁ¤ćtqpÁE×X‹›2Î2ś}Ďe/ŮÔc5ůH\“÷né»L óMŢ·–�öq@’]D°´ôńĹA峎bśË™ă |?ÝʵüŽ—fA8O%íZjŔ­›žSO¶ˇ8O™ŮjĎba�ZčÔŇ']DćŘýQ­ĘŘ.b G’·ŤŽ0L. ¶˝Bň÷[CŘĘNc÷9Q†Ć m*&đęç¡C@z;v‰őŁűtâoĎ{˝¶ů^ůp®sh0yźói€ů“;jŔűŽőŽş´«¤áD©–/)ç^ł<ű¸ÖV«·eXď`ˇLV#݉Î&GŽ X 3`Q{žák÷Ů `€<[—1Dµ‚áÖŽ†îŘ`Gî9tąępNśÔ4@®ć!éÎ(ŇË좡Á¤˛Č!Ąŕ?4xŢ2¬”îR‚‰ňVŻuĽ—Ý[”&‹O˛ŕ3ŕ€vç0`˛ř4óűq‹�č⌠¬ > {q|m«¸Oż:ŔŔ‚>gŔŃ®Či�şˇXŢH˘~ľ«G ýxęm`âkfŔ„‹Ú§Ú”ů÷‚‰IŔÄ$L„ab-Ŕä\Ś lRďYz´.NÄŮť&\k»Ţ°CĐmwű¬&u'ĘdŃĐ`˛·¸ž?ý6UŻĆa‚u×Ř/Ď~w;ö{(>ŽşÝtEň  ňáÉř~ŕ”�\D ÂŇ$hź{!µfÖdŰp IĎÚÇ•E­w|a @~Źt� 0ńíEŰ8ÂFeş‡ŁÔC ‘*iRP_ž }5 ě}f¶›RovóüŢ˝á¸.§ŽŐz°\ ĄN –™jŐDÎ]@…† ¦şó;ćv—Ő§>GÄ-aťG> Ě4ęVia~#‘�@ůžaęÁ‘'¤ÍZ~y-ßÇńŠE@i�ngHw‘…34Ńý{D ŠmÇ€‰B¨`»[÷Y™e¶ŤG«÷U„łö]ž( ˇ µ…·ŠµVX!4Ęšžív4;[^`AZM„tčmŻÉÔ{ÚLŕzAŰtl�% ţŢä»cG"ŔR;U”‹â«żŻtŚŁň·ŔŹ ?ý"öŘ~”S]ů‚ůđ$�8ŕ[“˙A{ó–o€”ĎU§µąđÓŹh–',ňçU3ó5-?uŐ{Ű Z]#ëC\r][‹×€I$W#3·`Ĺ?Ď«Ąj8Ią™CgH´o…ębd`ËčŕýXjl‚”ý§ż`®pĎ :Ůś‚ϱ]ë­˘q€Ý'śíÜ6Á¸¬qńö‚ ˝źíŻćëRě#,—ßCJ=!ŮGÁ|ôč€NŐ?FFÎQȰ>Ë‹YBń/J ˘ą4"BŤMG…zekAÁa§¤Č”B8?˙_˘<m>*R ˝EEŚ l­ö[ ó[tÍzşß{……‡Uz§Ú›_xŘ×›ß/U窍şk Řg®ľßÖŔ˝s5ĐلˊÖ×äÔĎŚ\¨ďM óăWŢrńĘČőBhŹOź5]äEtÝxŇŃMK«%g˘)D1Ą+›ÂűDCď¶ń«-Ö°¶¨Ię-Ôž™eşŃ+ĚYO‘’ă|j1ř&ŠI×é W…(šT(Oµbe`ĂÄiz}ú­É“ŚĘHCŐZŘ—ŠZł»¸Č,:é?őŐŔ„ó ^”ě5ěKGŰŮ$)Ż^Ăăţ×E‰Ů‹ł¤Č˘ W'Ęś4YR~HB)L¦Ëžë-™1Ž??(0±Io­J#¬Tg:S*]čő}šüĎŐńÄ{~h01`rž J&J+<–§ěĄëÁŞ=¬řp=�”ÎH‹ ׺:—“Ëü; ®A‰żßß|’*O˛ö-ť$ř‡‰YŮk) OĹ6žtk0ŮĎîȤ6tÁ{úr»ô‡‰|·­&1&›Ô“ű+­&—Ř€”&]d—ĚCXcN–Í &ÎM‰Ô›Örź)Ę•Š0ۨîd‘&íVwŠ`r ÓˇHµÇ€‘9Ęü%U¤t; @¶O3źgz¤Ţ´€>Źć­ú˘ćýĚ€IÝą’zé"Ď´…ł+ÁD¦ÁęłŘy›ŇM”ţYśM Ě·ˇąąą…ţ¸Fg•©Çž§P’±±žM"8Ý<kZťÚĽFš÷Ź7&ű'vfo¬ŐU0áWďq–ÚňµU¤rm{i€É*ڤ×Ü$Ňôđ/wÔ€$Ѝ^ťĐBozŁe�µ)¦‹1—“–úôy˙03`‚ż?0ĆţBK†^MŞˇW &ăr‚ ~_)Ĺô˛0ńfúŃ&,I›~|hPőşU˙†6(ąy°¨Nüܡé'`r»uóć­$› ňóŠŚÁ#•;ý>U“KĐçýŁĚ€I¨®ŁÔ?g^ĘEđe±šPüÎ�9(çŠęą.mp6 /w� źŇ~îő-—§ˇÁąáGÍ^7ÁdÄĄö§"Ξů+ôđŘľ&@¤± ·B4 oő‡“ś4zT—Ú(´�˙AfŔÄ·qŞ×XËíbÓ!ŢëÍ”… ç/”.ŕČ‚ĄĄhXéٍ祟tFU1ŔGî˛*sđÍ˝ĹŰyŔßt<Qj*1§N×ÂĽ|ZlßXh¤dXŚŚű÷ZH©;yo:žZ ŕ?Ć T˛†nzĂ&Ű”sśŠvĐúP’ѭ٠IJw2`A^CxŮü&@”ébno®b€ Űݲć,„ů�“hµ?şzQ^5T˛ĎZŃ3†ˇ>˙UŇÄđ?»fŔ¤·ŐwĄĹÎße? H<ţO˛!†4›,Ç/˝µŻp;k’XMf“»~_Mđ+#|ŻLĄMGWű‹‚Éu6ţ6Ź\a-óâT$`Tů!0;„łÉ›ÇŠ‚Ç2Dv%Čj˛0É0™ż áżËÓL‚Ľ&šqŔ«Á‘xT>ľĂ ,§0@ˇé§ĂĘ:P~ó\Q UŠŐıńĹ«ŢÂŮd‰¦XQ‚0ťÄXM&’LBL‘*]¨ÔU%9L–„[\0é÷ř�“EÝ;đŰâçvqčj&8Í$±ţŢ BL “«ś+",Z€m€—“Äł™aň&ÜFĽË:J˝€ÉŇhµ?‚‰"9k¬Ąˇ ů9ä§m:ţëspÄ˝ä$m:ý‹;JC#ĐlĽ &o&ęjB&Oă$˝Ä ^&Ŕ!~ľď@ŹOWű#LŚ646ćśÁÉtq8ČĎQ…äűčl2Š]‡ł‰_h>đ:& z5‘źn*Żl5©¤Ô6¸�&(&l÷‡¬„ Sű3;tµ?l-šŤˇľĆĘ 10čC2ŤOŮźtŢŤDŔŐOO:+Ď-ŔDÉ‚IFú &sŇR’‚‰˛dUĆŠ+–Ď{:ÝšďEqbďÍţ¸<ëHľŹ.*FşK˛ (|Ź÷xŁÔţVĺĘÍÍ=4 ˘ˇfQťF6l+hkĘí÷mlÝ Í[z3¶ľ/ć%L–/y0™—h0áô#nţ XˇS¬H·¦5`"#›¨ŕ=‚¸óŘą×`<:ĎĄ]¨pŹěÝ1¤şT ‡z¦¨ýqSŃŠůaŽęő©čT}ŞęŽžG wâŔdMbä¸yÍÚ5×:Y0„čňiéÖP*6˝#ŞRY&şÜ]´úH7žX˘ĂöD…ęÂPťäÇ Ühš`bŔd)Á$se"äÄNµK<EÝŞµYâ0AŁwhňŚlT©^żÖG@#2FVÍ™&ÓÂűŞŢ˝˛U a`’ła]˘Ăż˛‰ňś�]”aÍO0Aă¨Öz¬Z¶:äާŹđDl»Ź;^&b řÂdŮ’·Lđ¤“h0QRSŞŹ± ľ ›[ŢľÁŘ�4oČE6 i|Aő 6T§Ť<”kNµ,Kb»_ &°"LÖŻ[ťůÁ$±Ź°lśSé¬a®™ř›Ţ¨IŘ8˝M:đ®ă2]Ąól»Ôž,1`2#L˛9LŇ—-NXXýäÝĺłťĂřĺŰó¶"âŕAŔ$ÜF’°!gÓî6$¤˝ F(¨(/t61`Â?&‹ů«ŕÓ q¤°p{~_MD㽡”„í]äŞÜŔˇ¤ÄQ¸y�J-9#L ,‰ú¤“°GXĹz‡É ˝.ŰXOg“ Z#ŰN{ §Ű÷6KĘ|°”.Ńł3FŇÎ3bEž+6`23LÖŃk & &$„/ó¸śËlÔ.ŐtqđĆKýE- Ű ((ˇ»ŻšR\8i®ç.=ĆvflŔŃĎĂxWĎa’–p09^$`2jCZ·Ýů”a­Ď5˘ŃÚ˝H†¤ŰňY‰*ŃHFmgc&3ĂdíęĚéxĐźtî›NO‹ť«'@AR®p˘Zm ŃP˘2˛˝÷áűmľ¨‘şë……Í+˛ź÷MÇ)lĆ/ţşóë“9K𪞟`§˝6‘ĄD2eŞN—˘FTĆ ‘ďD O‘gţBěŃŁţf4=ęďµÉbÚsÄŃ$!a˘gKÉÚd™®($ŃĘČfC-zD‘ůÄŘ0A _3·–™Ő‘˘Łţ^;¬ĺ‹‰8š< ˝ćTŻ1aă>M«âéx>QA-fÔźpHpĐ›ú𞣟`člb˛ĹXbdyćĚHňL0ąĘn·"PĂb~‘€=Ź—Â˙DďÔ¨?!čôč‰ ţn Ô5ľç$ L`¶š™Ĺ¦Ä€I} “q‡ę© a1?O8Đ/q€­ĽcÓ˘ţ<«ą`dZBĂäíĚwh1yDX±ç$LČ8$ůY ŮŔ`›‰ĹV÷ŽúĚá˙űĆ˙mtH‰Ă„ź`A\S*‰ůiz`X Pőň^5đ^Ą¨ż€“˘ţÔô$ňJşĘ Ń…xŔ˛Ŕb‚€B6gîâĚ&´šđ4ęO1-‰`)Ňe’`™Ú«ŕ},1Řčď™˙Cd ’ď˨FíV˙óżľń_˙oc˝V…%·nnmÝÜâ�~ČńćÉŇaěCÁLPĄíń;ÜÜUB˝rřë{ŘX˝×ĽĆî„ ”Ňż kGȱĽüŽ2˙)Úć-ĺ<“9i×OH«fŇt»Ŕ†k‰L�ń‹çXí˙ŻV®‚ €Ff“sď“‚h€š(€:Üůżw˛úuś¬ĽkËŮŞ.6"F­XŐŚ`rŔȢěÎÝAś^ă̡€É÷…Δ«śůáÜ•»ÜQkţ4*)jÎŕűŁĽŚÓ•uî".˛*ĂQh����IEND®B`‚����������beetbox-beets-01f1faf/docs/plugins/bpd.rst����������������������������������������������������������0000664�0000000�0000000�00000012302�14723254774�0020655�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������BPD Plugin ========== BPD is a music player using music from a beets library. It runs as a daemon and implements the MPD protocol, so it's compatible with all the great MPD clients out there. I'm using `Theremin`_, `gmpc`_, `Sonata`_, and `Ario`_ successfully. .. _Theremin: https://github.com/TheStalwart/Theremin .. _gmpc: https://gmpc.wikia.com/wiki/Gnome_Music_Player_Client .. _Sonata: http://sonata.berlios.de/ .. _Ario: http://ario-player.sourceforge.net/ Dependencies ------------ Before you can use BPD, you'll need the media library called `GStreamer`_ (along with its Python bindings) on your system. * On Mac OS X, you can use `Homebrew`_. Run ``brew install gstreamer gst-plugins-base pygobject3``. * On Linux, you need to install GStreamer 1.0 and the GObject bindings for python. Under Ubuntu, they are called ``python-gi`` and ``gstreamer1.0``. You will also need the various GStreamer plugin packages to make everything work. See the :doc:`/plugins/chroma` documentation for more information on installing GStreamer plugins. Once you have system dependencies installed, install ``beets`` with ``bpd`` extra which installs Python bindings for ``GStreamer``: .. code-block:: console pip install "beets[bpd]" .. _GStreamer: https://gstreamer.freedesktop.org/download .. _Homebrew: https://brew.sh Usage ----- To use the ``bpd`` plugin, first enable it in your configuration (see :ref:`using-plugins`). Then, you can run BPD by invoking:: $ beet bpd Fire up your favorite MPD client to start playing music. The MPD site has `a long list of available clients`_. Here are my favorites: .. _a long list of available clients: https://mpd.wikia.com/wiki/Clients * Linux: `gmpc`_, `Sonata`_ * Mac: `Theremin`_ * Windows: I don't know. Get in touch if you have a recommendation. * iPhone/iPod touch: `Rigelian`_ .. _Rigelian: https://www.rigelian.net/ One nice thing about MPD's (and thus BPD's) client-server architecture is that the client can just as easily on a different computer from the server as it can be run locally. Control your music from your laptop (or phone!) while it plays on your headless server box. Rad! Configuration ------------- To configure the plugin, make a ``bpd:`` section in your configuration file. The available options are: - **host**: Default: Bind to all interfaces. - **port**: Default: 6600 - **password**: Default: No password. - **volume**: Initial volume, as a percentage. Default: 100 - **control_port**: Port for the internal control socket. Default: 6601 Here's an example:: bpd: host: 127.0.0.1 port: 6600 password: seekrit volume: 100 Implementation Notes -------------------- In the real MPD, the user can browse a music directory as it appears on disk. In beets, we like to abstract away from the directory structure. Therefore, BPD creates a "virtual" directory structure (artist/album/track) to present to clients. This is static for now and cannot be reconfigured like the real on-disk directory structure can. (Note that an obvious solution to this is just string matching on items' destination, but this requires examining the entire library Python-side for every query.) BPD plays music using GStreamer's ``playbin`` player, which has a simple API but doesn't support many advanced playback features. Differences from the real MPD ----------------------------- BPD currently supports version 0.16 of `the MPD protocol`_, but several of the commands and features are "pretend" implementations or have slightly different behaviour to their MPD equivalents. BPD aims to look enough like MPD that it can interact with the ecosystem of clients, but doesn't try to be a fully-fledged MPD replacement in terms of its playback capabilities. .. _the MPD protocol: https://www.musicpd.org/doc/protocol/ These are some of the known differences between BPD and MPD: * BPD doesn't currently support versioned playlists. Many clients, however, use plchanges instead of playlistinfo to get the current playlist, so plchanges contains a dummy implementation that just calls playlistinfo. * Stored playlists aren't supported (BPD understands the commands though). * The ``stats`` command always send zero for ``playtime``, which is supposed to indicate the amount of time the server has spent playing music. BPD doesn't currently keep track of this. * The ``update`` command regenerates the directory tree from the beets database synchronously, whereas MPD does this in the background. * Advanced playback features like cross-fade, ReplayGain and MixRamp are not supported due to BPD's simple audio player backend. * Advanced query syntax is not currently supported. * Clients can't use the ``tagtypes`` mask to hide fields. * BPD's ``random`` mode is not deterministic and doesn't support priorities. * Mounts and streams are not supported. BPD can only play files from disk. * Stickers are not supported (although this is basically a flexattr in beets nomenclature so this is feasible to add). * There is only a single password, and is enabled it grants access to all features rather than having permissions-based granularity. * Partitions and alternative outputs are not supported; BPD can only play one song at a time. * Client channels are not implemented. ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������beetbox-beets-01f1faf/docs/plugins/bpm.rst����������������������������������������������������������0000664�0000000�0000000�00000002176�14723254774�0020676�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������BPM Plugin ========== This ``bpm`` plugin lets you to get the tempo (beats per minute) of a song by tapping out the beat on your keyboard. Usage ----- To use the ``bpm`` plugin, first enable it in your configuration (see :ref:`using-plugins`). Then, play a song you want to measure in your favorite media player and type:: beet bpm <song> You'll be prompted to press Enter three times to the rhythm. This typically allows to determine the BPM within 5% accuracy. The plugin works best if you wrap it in a script that gets the playing song. for instance, with ``mpc`` you can do something like:: beet bpm $(mpc |head -1|tr -d "-") If :ref:`import.write <config-import-write>` is ``yes``, the song's tags are written to disk. Configuration ------------- To configure the plugin, make a ``bpm:`` section in your configuration file. The available options are: - **max_strokes**: The maximum number of strokes to accept when tapping out the BPM. Default: 3. - **overwrite**: Overwrite the track's existing BPM. Default: ``yes``. Credit ------ This plugin is inspired by a similar feature present in the Banshee media player. ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������beetbox-beets-01f1faf/docs/plugins/bpsync.rst�������������������������������������������������������0000664�0000000�0000000�00000002625�14723254774�0021415�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������BPSync Plugin ============= This plugin provides the ``bpsync`` command, which lets you fetch metadata from Beatport for albums and tracks that already have Beatport IDs. This plugin works similarly to :doc:`/plugins/mbsync`. If you have downloaded music from Beatport, this can speed up the initial import if you just import "as-is" and then use ``bpsync`` to get up-to-date tags that are written to the files according to your beets configuration. Usage ----- Enable the ``bpsync`` plugin in your configuration (see :ref:`using-plugins`) and then run ``beet bpsync QUERY`` to fetch updated metadata for a part of your collection (or omit the query to run over your whole library). This plugin treats albums and singletons (non-album tracks) separately. It first processes all matching singletons and then proceeds on to full albums. The same query is used to search for both kinds of entities. The command has a few command-line options: * To preview the changes that would be made without applying them, use the ``-p`` (``--pretend``) flag. * By default, files will be moved (renamed) according to their metadata if they are inside your beets library directory. To disable this, use the ``-M`` (``--nomove``) command-line option. * If you have the ``import.write`` configuration option enabled, then this plugin will write new metadata to files' tags. To disable this, use the ``-W`` (``--nowrite``) option. �����������������������������������������������������������������������������������������������������������beetbox-beets-01f1faf/docs/plugins/bucket.rst�������������������������������������������������������0000664�0000000�0000000�00000006202�14723254774�0021367�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������Bucket Plugin ============= The ``bucket`` plugin groups your files into buckets folders representing *ranges*. This kind of organization can classify your music by periods of time (e.g,. *1960s*, *1970s*, etc.), or divide overwhelmingly large folders into smaller subfolders by grouping albums or artists alphabetically (e.g. *A-F*, *G-M*, *N-Z*). To use the ``bucket`` plugin, first enable it in your configuration (see :ref:`using-plugins`). The plugin provides a :ref:`template function <template-functions>` called ``%bucket`` for use in path format expressions:: paths: default: /%bucket{$year}/%bucket{$artist}/$albumartist-$album-$year Then, define your ranges in the ``bucket:`` section of the config file:: bucket: bucket_alpha: ['A-F', 'G-M', 'N-Z'] bucket_year: ['1980s', '1990s', '2000s'] The ``bucket_year`` parameter is used for all substitutions occurring on the ``$year`` field, while ``bucket_alpha`` takes care of textual fields. The definition of a range is somewhat loose, and multiple formats are allowed: - For alpha ranges: the range is defined by the lowest and highest (ASCII-wise) alphanumeric characters in the string you provide. For example, ``ABCD``, ``A-D``, ``A->D``, and ``[AD]`` are all equivalent. - For year ranges: digits characters are extracted and the two extreme years define the range. For example, ``1975-77``, ``1975,76,77`` and ``1975-1977`` are equivalent. If no upper bound is given, the range is extended to current year (unless a later range is defined). For example, ``1975`` encompasses all years from 1975 until now. The ``%bucket`` template function guesses whether to use alpha- or year-style buckets depending on the text it receives. It can guess wrong if, for example, an artist or album happens to begin with four digits. Provide ``alpha`` as the second argument to the template to avoid this automatic detection: for example, use ``%bucket{$artist,alpha}``. Configuration ------------- To configure the plugin, make a ``bucket:`` section in your configuration file. The available options are: - **bucket_alpha**: Ranges to use for all substitutions occurring on textual fields. Default: none. - **bucket_alpha_regex**: A ``range: regex`` mapping (one per line) where ``range`` is one of the `bucket_alpha` ranges and ``value`` is a regex that overrides original range definition. Default: none. - **bucket_year**: Ranges to use for all substitutions occurring on the ``$year`` field. Default: none. - **extrapolate**: Enable this if you want to group your files into multiple year ranges without enumerating them all. This option will generate year bucket names by reproducing characteristics of declared buckets. Default: ``no`` Here's an example:: bucket: bucket_year: ['2000-05'] extrapolate: true bucket_alpha: ['A - D', 'E - L', 'M - R', 'S - Z'] bucket_alpha_regex: 'A - D': ^[0-9a-dA-D…äÄ] This configuration creates five-year ranges for any input year. The `A - D` bucket now matches also all artists starting with ä or Ă„ and 0 to 9 and … (ellipsis). The other alpha buckets work as ranges. ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������beetbox-beets-01f1faf/docs/plugins/chroma.rst�������������������������������������������������������0000664�0000000�0000000�00000013111�14723254774�0021360�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������Chromaprint/Acoustid Plugin =========================== Acoustic fingerprinting is a technique for identifying songs from the way they "sound" rather from their existing metadata. That means that beets' autotagger can theoretically use fingerprinting to tag files that don't have any ID3 information at all (or have completely incorrect data). This plugin uses an open-source fingerprinting technology called `Chromaprint`_ and its associated Web service, called `Acoustid`_. .. _Chromaprint: https://acoustid.org/chromaprint .. _acoustid: https://acoustid.org/ Turning on fingerprinting can increase the accuracy of the autotagger---especially on files with very poor metadata---but it comes at a cost. First, it can be trickier to set up than beets itself (you need to set up the native fingerprinting library, whereas all of the beets core is written in pure Python). Also, fingerprinting takes significantly more CPU and memory than ordinary tagging---which means that imports will go substantially slower. If you're willing to pay the performance cost for fingerprinting, read on! Installing Dependencies ----------------------- To get fingerprinting working, you'll need to install three things: 1. `pyacoustid`_ Python library (version 0.6 or later). You can install it by installing ``beets`` with ``chroma`` extra .. code-block:: bash pip install "beets[chroma]" 2. the `Chromaprint`_ library_ or |command-line-tool|_ 3. an |audio-decoder|_ .. |command-line-tool| replace:: command line tool .. |audio-decoder| replace:: audio decoder .. _command-line-tool: Installing the Binary Command-Line Tool ''''''''''''''''''''''''''''''''''''''' The simplest way to get up and running, especially on Windows, is to `download`_ the appropriate Chromaprint binary package and place the ``fpcalc`` (or ``fpcalc.exe``) on your shell search path. On Windows, this means something like ``C:\\Program Files``. On OS X or Linux, put the executable somewhere like ``/usr/local/bin``. .. _download: https://acoustid.org/chromaprint .. _library: Installing the Library '''''''''''''''''''''' On OS X and Linux, you can also use a library installed by your package manager, which has some advantages (automatic upgrades, etc.). The Chromaprint site has links to packages for major Linux distributions. If you use `Homebrew`_ on Mac OS X, you can install the library with ``brew install chromaprint``. .. _Homebrew: https://brew.sh/ .. _audio-decoder: Audio Decoder ''''''''''''' You will also need a mechanism for decoding audio files supported by the `audioread`_ library: * OS X has a number of decoders already built into Core Audio, so there's no need to install anything. * On Linux, you can install `GStreamer`_ with `PyGObject`_, `FFmpeg`_, or `MAD`_ with `pymad`_. How you install these will depend on your distribution. For example, on Ubuntu, run ``apt-get install gstreamer1.0 python-gi``. On Arch Linux, you want ``pacman -S gstreamer python2-gobject``. If you use GStreamer, be sure to install its codec plugins also (``gst-plugins-good``, etc.). Note that if you install beets in a virtualenv, you'll need it to have ``--system-site-packages`` enabled for Python to see the GStreamer bindings. * On Windows, builds are provided by `GStreamer`_ .. _audioread: https://github.com/beetbox/audioread .. _pyacoustid: https://github.com/beetbox/pyacoustid .. _FFmpeg: https://ffmpeg.org/ .. _pymad: https://spacepants.org/src/pymad/ .. _MAD: https://www.underbit.com/products/mad/ .. _Core Audio: https://developer.apple.com/technologies/mac/audio-and-video.html .. _Gstreamer: https://gstreamer.freedesktop.org/ .. _PyGObject: https://wiki.gnome.org/Projects/PyGObject To decode audio formats (MP3, FLAC, etc.) with GStreamer, you'll need the standard set of Gstreamer plugins. For example, on Ubuntu, install the packages ``gstreamer1.0-plugins-good``, ``gstreamer1.0-plugins-bad``, and ``gstreamer1.0-plugins-ugly``. Usage ----- Once you have all the dependencies sorted out, enable the ``chroma`` plugin in your configuration (see :ref:`using-plugins`) to benefit from fingerprinting the next time you run ``beet import``. (The plugin doesn't produce any obvious output by default. If you want to confirm that it's enabled, you can try running in verbose mode once with ``beet -v import``.) You can also use the ``beet fingerprint`` command to generate fingerprints for items already in your library. (Provide a query to fingerprint a subset of your library.) The generated fingerprints will be stored in the library database. If you have the ``import.write`` config option enabled, they will also be written to files' metadata. .. _submitfp: Configuration ------------- There is one configuration option in the ``chroma:`` section, ``auto``, which controls whether to fingerprint files during the import process. To disable fingerprint-based autotagging, set it to ``no``, like so:: chroma: auto: no Submitting Fingerprints ----------------------- You can help expand the `Acoustid`_ database by submitting fingerprints for the music in your collection. To do this, first `get an API key`_ from the Acoustid service. Just use an OpenID or MusicBrainz account to log in and you'll get a short token string. Then, add the key to your ``config.yaml`` as the value ``apikey`` in a section called ``acoustid`` like so:: acoustid: apikey: AbCd1234 Then, run ``beet submit``. (You can also provide a query to submit a subset of your library.) The command will use stored fingerprints if they're available; otherwise it will fingerprint each file before submitting it. .. _get an API key: https://acoustid.org/api-key �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������beetbox-beets-01f1faf/docs/plugins/convert.rst������������������������������������������������������0000664�0000000�0000000�00000024443�14723254774�0021601�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������Convert Plugin ============== The ``convert`` plugin lets you convert parts of your collection to a directory of your choice, transcoding audio and embedding album art along the way. It can transcode to and from any format using a configurable command line. Optionally an m3u playlist file containing all the converted files can be saved to the destination path. Installation ------------ To use the ``convert`` plugin, first enable it in your configuration (see :ref:`using-plugins`). By default, the plugin depends on `FFmpeg`_ to transcode the audio, so you might want to install it. .. _FFmpeg: https://ffmpeg.org Usage ----- To convert a part of your collection, run ``beet convert QUERY``. The command will transcode all the files matching the query to the destination directory given by the ``-d`` (``--dest``) option or the ``dest`` configuration. The path layout mirrors that of your library, but it may be customized through the ``paths`` configuration. Files that have been previously converted---and thus already exist in the destination directory---will be skipped. The plugin uses a command-line program to transcode the audio. With the ``-f`` (``--format``) option you can choose the transcoding command and customize the available commands :ref:`through the configuration <convert-format-config>`. Unless the ``-y`` (``--yes``) flag is set, the command will list all the items to be converted and ask for your confirmation. The ``-a`` (or ``--album``) option causes the command to match albums instead of tracks. By default, the command places converted files into the destination directory and leaves your library pristine. To instead back up your original files into the destination directory and keep converted files in your library, use the ``-k`` (or ``--keep-new``) option. To test your configuration without taking any actions, use the ``--pretend`` flag. The plugin will print out the commands it will run instead of executing them. By default, files that do not need to be transcoded will be copied to their destination. Passing the ``-l`` (``--link``) flag creates symbolic links instead, passing ``-H`` (``--hardlink``) creates hard links. Note that album art embedding is disabled for files that are linked. Refer to the ``link`` and ``hardlink`` options below. The ``-m`` (or ``--playlist``) option enables the plugin to create an m3u8 playlist file in the destination folder given by the ``-d`` (``--dest``) option or the ``dest`` configuration. The path to the playlist file can either be absolute or relative to the ``dest`` directory. The contents will always be relative paths to media files, which tries to ensure compatibility when read from external drives or on computers other than the one used for the conversion. There is one caveat though: A list generated on Unix/macOS can't be read on Windows and vice versa. Depending on the beets user's settings a generated playlist potentially could contain unicode characters. This is supported, playlists are written in `M3U8 format`_. Configuration ------------- To configure the plugin, make a ``convert:`` section in your configuration file. The available options are: - **auto**: Import transcoded versions of your files automatically during imports. With this option enabled, the importer will transcode all (in the default configuration) non-MP3 files over the maximum bitrate before adding them to your library. Default: ``no``. - **auto_keep**: Convert your files automatically on import to **dest** but import the non transcoded version. It uses the default format you have defined in your config file. Default: ``no``. .. note:: You probably want to use only one of the `auto` and `auto_keep` options, not both. Enabling both will convert your files twice on import, which you probably don't want. - **tmpdir**: The directory where temporary files will be stored during import. Default: none (system default), - **copy_album_art**: Copy album art when copying or transcoding albums matched using the ``-a`` option. Default: ``no``. - **album_art_maxwidth**: Downscale album art if it's too big. The resize operation reduces image width to at most ``maxwidth`` pixels while preserving the aspect ratio. The specified image size will apply to both embedded album art and external image files. - **dest**: The directory where the files will be converted (or copied) to. Default: none. - **embed**: Embed album art in converted items. Default: ``yes``. - **id3v23**: Can be used to override the global ``id3v23`` option. Default: ``inherit``. - **max_bitrate**: By default, the plugin does not transcode files that are already in the destination format. This option instead also transcodes files with high bitrates, even if they are already in the same format as the output. Note that this does not guarantee that all converted files will have a lower bitrate---that depends on the encoder and its configuration. Default: none. - **no_convert**: Does not transcode items matching the query string provided (see :doc:`/reference/query`). For example, to not convert AAC or WMA formats, you can use ``format:AAC, format:WMA`` or ``path::\.(m4a|wma)$``. If you only want to transcode WMA format, you can use a negative query, e.g., ``^path::\.(wma)$``, to not convert any other format except WMA. - **never_convert_lossy_files**: Cross-conversions between lossy codecs---such as mp3, ogg vorbis, etc.---makes little sense as they will decrease quality even further. If set to ``yes``, lossy files are always copied. Default: ``no``. - **paths**: The directory structure and naming scheme for the converted files. Uses the same format as the top-level ``paths`` section (see :ref:`path-format-config`). Default: Reuse your top-level path format settings. - **quiet**: Prevent the plugin from announcing every file it processes. Default: ``false``. - **threads**: The number of threads to use for parallel encoding. By default, the plugin will detect the number of processors available and use them all. - **link**: By default, files that do not need to be transcoded will be copied to their destination. This option creates symbolic links instead. Note that options such as ``embed`` that modify the output files after the transcoding step will cause the original files to be modified as well if ``link`` is enabled. For this reason, album-art embedding is disabled for files that are linked. Default: ``false``. - **hardlink**: This options works similar to ``link``, but it creates hard links instead of symlinks. This option overrides ``link``. Only works when converting to a directory on the same filesystem as the library. Default: ``false``. - **delete_originals**: Transcoded files will be copied or moved to their destination, depending on the import configuration. By default, the original files are not modified by the plugin. This option deletes the original files after the transcoding step has completed. Default: ``false``. - **playlist**: The name of a playlist file that should be written on each run of the plugin. A relative file path (e.g `playlists/mylist.m3u8`) is allowed as well. The final destination of the playlist file will always be relative to the destination path (``dest``, ``--dest``, ``-d``). This configuration is overridden by the ``-m`` (``--playlist``) command line option. Default: none. You can also configure the format to use for transcoding (see the next section): - **format**: The name of the format to transcode to when none is specified on the command line. Default: ``mp3``. - **formats**: A set of formats and associated command lines for transcoding each. .. _convert-format-config: Configuring the transcoding command ``````````````````````````````````` You can customize the transcoding command through the ``formats`` map and select a command with the ``--format`` command-line option or the ``format`` configuration. :: convert: format: speex formats: speex: command: ffmpeg -i $source -y -acodec speex $dest extension: spx wav: ffmpeg -i $source -y -acodec pcm_s16le $dest In this example ``beet convert`` will use the *speex* command by default. To convert the audio to `wav`, run ``beet convert -f wav``. This will also use the format key (``wav``) as the file extension. Each entry in the ``formats`` map consists of a key (the name of the format) as well as the command and optionally the file extension. ``extension`` is the filename extension to be used for newly transcoded files. If only the command is given as a string or the extension is not provided, the file extension defaults to the format's name. ``command`` is the command to use to transcode audio. The tokens ``$source`` and ``$dest`` in the command are replaced with the paths to the existing and new file. The plugin in comes with default commands for the most common audio formats: `mp3`, `alac`, `flac`, `aac`, `opus`, `ogg`, `wma`. For details have a look at the output of ``beet config -d``. For a one-command-fits-all solution use the ``convert.command`` and ``convert.extension`` options. If these are set, the formats are ignored and the given command is used for all conversions. :: convert: command: ffmpeg -i $source -y -vn -aq 2 $dest extension: mp3 Gapless MP3 encoding ```````````````````` While FFmpeg cannot produce "`gapless`_" MP3s by itself, you can create them by using `LAME`_ directly. Use a shell script like this to pipe the output of FFmpeg into the LAME tool:: #!/bin/sh ffmpeg -i "$1" -f wav - | lame -V 2 --noreplaygain - "$2" Then configure the ``convert`` plugin to use the script:: convert: command: /path/to/script.sh $source $dest extension: mp3 This strategy configures FFmpeg to produce a WAV file with an accurate length header for LAME to use. Using ``--noreplaygain`` disables gain analysis; you can use the :doc:`/plugins/replaygain` to do this analysis. See the LAME `documentation`_ and the `HydrogenAudio wiki`_ for other LAME configuration options and a thorough discussion of MP3 encoding. .. _documentation: https://lame.sourceforge.io/index.php .. _HydrogenAudio wiki: https://wiki.hydrogenaud.io/index.php?title=LAME .. _gapless: https://wiki.hydrogenaud.io/index.php?title=Gapless_playback .. _LAME: https://lame.sourceforge.io/index.php .. _M3U8 format: https://en.wikipedia.org/wiki/M3U#M3U8 �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������beetbox-beets-01f1faf/docs/plugins/deezer.rst�������������������������������������������������������0000664�0000000�0000000�00000002041�14723254774�0021365�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������Deezer Plugin ============== The ``deezer`` plugin provides metadata matches for the importer using the `Deezer`_ `Album`_ and `Track`_ APIs. .. _Deezer: https://www.deezer.com .. _Album: https://developers.deezer.com/api/album .. _Track: https://developers.deezer.com/api/track Basic Usage ----------- First, enable the ``deezer`` plugin (see :ref:`using-plugins`). You can enter the URL for an album or song on Deezer at the ``enter Id`` prompt during import:: Enter search, enter Id, aBort, eDit, edit Candidates, plaY? i Enter release ID: https://www.deezer.com/en/album/572261 Configuration ------------- This plugin can be configured like other metadata source plugins as described in :ref:`metadata-source-plugin-configuration`. The ``deezer`` plugin provides an additional command ``deezerupdate`` to update the ``rank`` information from Deezer. The ``rank`` (ranges from 0 to 1M) is a global indicator of a song's popularity on Deezer that is updated daily based on streams. The higher the ``rank``, the more popular the track is. �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������beetbox-beets-01f1faf/docs/plugins/discogs.rst������������������������������������������������������0000664�0000000�0000000�00000011202�14723254774�0021541�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������Discogs Plugin ============== The ``discogs`` plugin extends the autotagger's search capabilities to include matches from the `Discogs`_ database. Files can be imported as albums or as singletons. Since `Discogs`_ matches are always based on `Discogs`_ releases, the album tag is written even to singletons. This enhances the importers results when reimporting as (full or partial) albums later on. .. _Discogs: https://discogs.com Installation ------------ To use the ``discogs`` plugin, first enable it in your configuration (see :ref:`using-plugins`). Then, install ``beets`` with ``discogs`` extra .. code-block:: bash pip install "beets[discogs]" You will also need to register for a `Discogs`_ account, and provide authentication credentials via a personal access token or an OAuth2 authorization. Matches from Discogs will now show up during import alongside matches from MusicBrainz. The search terms sent to the Discogs API are based on the artist and album tags of your tracks. If those are empty no query will be issued. If you have a Discogs ID for an album you want to tag, you can also enter it at the "enter Id" prompt in the importer. OAuth Authorization ``````````````````` The first time you run the :ref:`import-cmd` command after enabling the plugin, it will ask you to authorize with Discogs by visiting the site in a browser. Subsequent runs will not require re-authorization. Authentication via Personal Access Token ```````````````````````````````````````` As an alternative to OAuth, you can get a token from Discogs and add it to your configuration. To get a personal access token (called a "user token" in the `python3-discogs-client`_ documentation): #. login to `Discogs`_; #. visit the `Developer settings page <https://www.discogs.com/settings/developers>`_; #. press the *Generate new token* button; #. copy the generated token; #. place it in your configuration in the ``discogs`` section as the ``user_token`` option: .. code-block:: yaml discogs: user_token: "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" Configuration ------------- This plugin can be configured like other metadata source plugins as described in :ref:`metadata-source-plugin-configuration`. There is one additional option in the ``discogs:`` section, ``index_tracks``. Index tracks (see the `Discogs guidelines <https://support.discogs.com/hc/en-us/articles/360005055373-Database-Guidelines-12-Tracklisting#Index_Tracks_And_Headings>`_), along with headers, mark divisions between distinct works on the same release or within works. When ``index_tracks`` is enabled: .. code-block:: yaml discogs: index_tracks: yes beets will incorporate the names of the divisions containing each track into the imported track's title. For example, importing `this album <https://www.discogs.com/Handel-Sutherland-Kirkby-Kwella-Nelson-Watkinson-Bowman-Rolfe-Johnson-Elliott-Partridge-Thomas-The-A/release/2026070>`_ would result in track names like: .. code-block:: text Messiah, Part I: No.1: Sinfony Messiah, Part II: No.22: Chorus- Behold The Lamb Of God Athalia, Act I, Scene I: Sinfonia whereas with ``index_tracks`` disabled you'd get: .. code-block:: text No.1: Sinfony No.22: Chorus- Behold The Lamb Of God Sinfonia This option is useful when importing classical music. Other configurations available under ``discogs:`` are: - **append_style_genre**: Appends the Discogs style (if found) to the genre tag. This can be useful if you want more granular genres to categorize your music. For example, a release in Discogs might have a genre of "Electronic" and a style of "Techno": enabling this setting would set the genre to be "Electronic, Techno" (assuming default separator of ``", "``) instead of just "Electronic". Default: ``False`` - **separator**: How to join multiple genre and style values from Discogs into a string. Default: ``", "`` Troubleshooting --------------- Several issues have been encountered with the Discogs API. If you have one, please start by searching for `a similar issue on the repo <https://github.com/beetbox/beets/issues?utf8=%E2%9C%93&q=is%3Aissue+discogs>`_. Here are two things you can try: * Try deleting the token file (``~/.config/beets/discogs_token.json`` by default) to force re-authorization. * Make sure that your system clock is accurate. The Discogs servers can reject your request if your clock is too out of sync. Matching tracks by Discogs ID is not yet supported. The ``--group-albums`` option in album import mode provides an alternative to singleton mode for autotagging tracks that are not in album-related folders. .. _python3-discogs-client: https://github.com/joalla/discogs_client ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������beetbox-beets-01f1faf/docs/plugins/duplicates.rst���������������������������������������������������0000664�0000000�0000000�00000013420�14723254774�0022247�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������Duplicates Plugin ================= This plugin adds a new command, ``duplicates`` or ``dup``, which finds and lists duplicate tracks or albums in your collection. Usage ----- To use the ``duplicates`` plugin, first enable it in your configuration (see :ref:`using-plugins`). By default, the ``beet duplicates`` command lists the names of tracks in your library that are duplicates. It assumes that Musicbrainz track and album ids are unique to each track or album. That is, it lists every track or album with an ID that has been seen before in the library. You can customize the output format, count the number of duplicate tracks or albums, and list all tracks that have duplicates or just the duplicates themselves via command-line switches :: -h, --help show this help message and exit -f FMT, --format=FMT print with custom format -a, --album show duplicate albums instead of tracks -c, --count count duplicate tracks or albums -C PROG, --checksum=PROG report duplicates based on arbitrary command -d, --delete delete items from library and disk -F, --full show all versions of duplicate tracks or albums -s, --strict report duplicates only if all attributes are set -k, --key report duplicates based on keys (can be used multiple times) -M, --merge merge duplicate items -m DEST, --move=DEST move items to dest -o DEST, --copy=DEST copy items to dest -p, --path print paths for matched items or albums -t TAG, --tag=TAG tag matched items with 'k=v' attribute Configuration ------------- To configure the plugin, make a ``duplicates:`` section in your configuration file. The available options mirror the command-line options: - **album**: List duplicate albums instead of tracks. Default: ``no``. - **checksum**: Use an arbitrary command to compute a checksum of items. This overrides the ``keys`` option the first time it is run; however, because it caches the resulting checksum as ``flexattrs`` in the database, you can use ``--key=name_of_the_checksumming_program --key=any_other_keys`` (or set the ``keys`` configuration option) the second time around. Default: ``ffmpeg -i {file} -f crc -``. - **copy**: A destination base directory into which to copy matched items. Default: none (disabled). - **count**: Print a count of duplicate tracks or albums in the format ``$albumartist - $album - $title: $count`` (for tracks) or ``$albumartist - $album: $count`` (for albums). Default: ``no``. - **delete**: Removes matched items from the library and from the disk. Default: ``no`` - **format**: A specific format with which to print every track or album. This uses the same template syntax as beets' :doc:`path formats</reference/pathformat>`. The usage is inspired by, and therefore similar to, the :ref:`list <list-cmd>` command. Default: :ref:`format_item` - **full**: List every track or album that has duplicates, not just the duplicates themselves. Default: ``no`` - **keys**: Define in which track or album fields duplicates are to be searched. By default, the plugin uses the musicbrainz track and album IDs for this purpose. Using the ``keys`` option (as a YAML list in the configuration file, or as space-delimited strings in the command-line), you can extend this behavior to consider other attributes. Default: ``[mb_trackid, mb_albumid]`` - **merge**: Merge duplicate items by consolidating tracks and-or metadata where possible. - **move**: A destination base directory into which it will move matched items. Default: none (disabled). - **path**: Output the path instead of metadata when listing duplicates. Default: ``no``. - **strict**: Do not report duplicate matches if some of the attributes are not defined (ie. null or empty). Default: ``no`` - **tag**: A ``key=value`` pair. The plugin will add a new ``key`` attribute with ``value`` value as a flexattr to the database for duplicate items. Default: ``no``. - **tiebreak**: Dictionary of lists of attributes keyed by ``items`` or ``albums`` to use when choosing duplicates. By default, the tie-breaking procedure favors the most complete metadata attribute set. If you would like to consider the lower bitrates as duplicates, for example, set ``tiebreak: items: [bitrate]``. Default: ``{}``. Examples -------- List all duplicate tracks in your collection:: beet duplicates List all duplicate tracks from 2008:: beet duplicates year:2008 Print out a unicode histogram of duplicate track years using `spark`_:: beet duplicates -f '$year' | spark â–†â–â–†â–▄▇▇▄▇▇â–â–▇▆▇▂▄â–â–â–â–â–‚â–â–â–â–â–â–â–▂▇▆▂▇â–▇▇â–▆▆▇â–â–‡â–▇▆â–â–â–‚â–‡ Print out a listing of all albums with duplicate tracks, and respective counts:: beet duplicates -ac The same as the above but include the original album, and show the path:: beet duplicates -acf '$path' Get tracks with the same title, artist, and album:: beet duplicates -k title -k albumartist -k album Compute Adler CRC32 or MD5 checksums, storing them as flexattrs, and report back duplicates based on those values:: beet dup -C 'ffmpeg -i {file} -f crc -' beet dup -C 'md5sum {file}' Copy highly danceable items to ``party`` directory:: beet dup --copy /tmp/party Move likely duplicates to ``trash`` directory:: beet dup --move ${HOME}/.Trash Delete items (careful!), if they're Nickelback:: beet duplicates --delete -k albumartist -k albumartist:nickelback Tag duplicate items with some flag:: beet duplicates --tag dup=1 Ignore items with undefined keys:: beet duplicates --strict Merge and delete duplicate albums with different missing tracks:: beet duplicates --album --merge --delete .. _spark: https://github.com/holman/spark ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������beetbox-beets-01f1faf/docs/plugins/edit.rst���������������������������������������������������������0000664�0000000�0000000�00000004443�14723254774�0021044�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������Edit Plugin =========== The ``edit`` plugin lets you modify music metadata using your favorite text editor. Enable the ``edit`` plugin in your configuration (see :ref:`using-plugins`) and then type:: beet edit QUERY Your text editor (i.e., the command in your ``$VISUAL`` or ``$EDITOR`` environment variable) will open with a list of tracks to edit. Make your changes and exit your text editor to apply them to your music. Command-Line Options -------------------- The ``edit`` command has these command-line options: - ``-a`` or ``--album``: Edit albums instead of individual items. - ``-f FIELD`` or ``--field FIELD``: Specify an additional field to edit (in addition to the defaults set in the configuration). - ``--all``: Edit *all* available fields. Interactive Usage ----------------- The ``edit`` plugin can also be invoked during an import session. If enabled, it adds two new options to the user prompt:: [A]pply, More candidates, Skip, Use as-is, as Tracks, Group albums, Enter search, enter Id, aBort, eDit, edit Candidates? - ``eDit``: use this option for using the original items' metadata as the starting point for your edits. - ``edit Candidates``: use this option for using a candidate's metadata as the starting point for your edits. Please note that currently the interactive usage of the plugin will only allow you to change the item-level fields. In case you need to edit the album-level fields, the recommended approach is to invoke the plugin via the command line in album mode (``beet edit -a QUERY``) after the import. Also, please be aware that the ``edit Candidates`` choice can only be used with the matches found during the initial search (and currently not supporting the candidates found via the ``Enter search`` or ``enter Id`` choices). You might find the ``--search-id SEARCH_ID`` :ref:`import-cmd` option useful for those cases where you already have a specific candidate ID that you want to edit. Configuration ------------- To configure the plugin, make an ``edit:`` section in your configuration file. The available options are: - **itemfields**: A space-separated list of item fields to include in the editor by default. Default: ``track title artist album`` - **albumfields**: The same when editing albums (with the ``-a`` option). Default: ``album albumartist`` �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������beetbox-beets-01f1faf/docs/plugins/embedart.rst�����������������������������������������������������0000664�0000000�0000000�00000012643�14723254774�0021703�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������EmbedArt Plugin =============== Typically, beets stores album art in a "file on the side": along with each album, there is a file (named "cover.jpg" by default) that stores the album art. You might want to embed the album art directly into each file's metadata. While this will take more space than the external-file approach, it is necessary for displaying album art in some media players (iPods, for example). Embedding Art Automatically --------------------------- To use the ``embedart`` plugin, first enable it in your configuration (see :ref:`using-plugins`). Then, install ``beets`` with ``embedart`` extra .. code-block:: bash pip install "beets[embedart]" You'll also want to enable the :doc:`/plugins/fetchart` to obtain the images to be embedded. Art will be embedded after each album has its cover art set. This behavior can be disabled with the ``auto`` config option (see below). .. _image-similarity-check: Image Similarity '''''''''''''''' When importing a lot of files with the ``auto`` option, one may be reluctant to overwrite existing embedded art for all of them. You can tell beets to avoid embedding images that are too different from the existing ones. This works by computing the perceptual hashes (`PHASH`_) of the two images and checking that the difference between the two does not exceed a threshold. You can set the threshold with the ``compare_threshold`` option. A threshold of 0 (the default) disables similarity checking and always embeds new images. Set the threshold to another number---we recommend between 10 and 100---to adjust the sensitivity of the comparison. The smaller the threshold number, the more similar the images must be. This feature requires `ImageMagick`_. Configuration ------------- To configure the plugin, make an ``embedart:`` section in your configuration file. The available options are: - **auto**: Enable automatic album art embedding. Default: ``yes``. - **compare_threshold**: How similar candidate art must be to existing art to be written to the file (see :ref:`image-similarity-check`). Default: 0 (disabled). - **ifempty**: Avoid embedding album art for files that already have art embedded. Default: ``no``. - **maxwidth**: A maximum width to downscale images before embedding them (the original image file is not altered). The resize operation reduces image width to at most ``maxwidth`` pixels. The height is recomputed so that the aspect ratio is preserved. See also :ref:`image-resizing` for further caveats about image resizing. Default: 0 (disabled). - **quality**: The JPEG quality level to use when compressing images (when ``maxwidth`` is set). This should be either a number from 1 to 100 or 0 to use the default quality. 65–75 is usually a good starting point. The default behavior depends on the imaging tool used for scaling: ImageMagick tries to estimate the input image quality and uses 92 if it cannot be determined, and PIL defaults to 75. Default: 0 (disabled) - **remove_art_file**: Automatically remove the album art file for the album after it has been embedded. This option is best used alongside the :doc:`FetchArt </plugins/fetchart>` plugin to download art with the purpose of directly embedding it into the file's metadata without an "intermediate" album art file. Default: ``no``. Note: ``compare_threshold`` option requires `ImageMagick`_, and ``maxwidth`` requires either `ImageMagick`_ or `Pillow`_. .. _Pillow: https://github.com/python-pillow/Pillow .. _ImageMagick: https://www.imagemagick.org/ .. _PHASH: http://www.fmwconcepts.com/misc_tests/perceptual_hash_test_results_510/ Manually Embedding and Extracting Art ------------------------------------- The ``embedart`` plugin provides a couple of commands for manually managing embedded album art: * ``beet embedart [-f IMAGE] QUERY``: embed images in every track of the albums matching the query. If the ``-f`` (``--file``) option is given, then use a specific image file from the filesystem; otherwise, each album embeds its own currently associated album art. The command prompts for confirmation before making the change unless you specify the ``-y`` (``--yes``) option. * ``beet embedart [-u IMAGE_URL] QUERY``: embed image specified in the URL into every track of the albums matching the query. The ``-u`` (``--url``) option can be used to specify the URL of the image to be used. The command prompts for confirmation before making the change unless you specify the ``-y`` (``--yes``) option. * ``beet extractart [-a] [-n FILE] QUERY``: extracts the images for all albums matching the query. The images are placed inside the album folder. You can specify the destination file name using the ``-n`` option, but leave off the extension: it will be chosen automatically. The destination filename is specified using the ``art_filename`` configuration option. It defaults to ``cover`` if it's not specified via ``-o`` nor the config. Using ``-a``, the extracted image files are automatically associated with the corresponding album. * ``beet extractart -o FILE QUERY``: extracts the image from an item matching the query and stores it in a file. You have to specify the destination file using the ``-o`` option, but leave off the extension: it will be chosen automatically. * ``beet clearart QUERY``: removes all embedded images from all items matching the query. The command prompts for confirmation before making the change unless you specify the ``-y`` (``--yes``) option. ���������������������������������������������������������������������������������������������beetbox-beets-01f1faf/docs/plugins/embyupdate.rst���������������������������������������������������0000664�0000000�0000000�00000002751�14723254774�0022256�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������EmbyUpdate Plugin ================= ``embyupdate`` is a plugin that lets you automatically update `Emby`_'s library whenever you change your beets library. To use it, first enable the your configuration (see :ref:`using-plugins`). Then, install ``beets`` with ``embyupdate`` extra .. code-block:: bash pip install "beets[embyupdate]" Then, you'll want to configure the specifics of your Emby server. You can do that using an ``emby`` section in your ``config.yaml`` .. code-block:: yaml emby: host: localhost port: 8096 username: user apikey: apikey With that all in place, you'll see beets send the "update" command to your Emby server every time you change your beets library. .. _Emby: https://emby.media/ Configuration ------------- The available options under the ``emby:`` section are: - **host**: The Emby server host. You also can include ``http://`` or ``https://``. Default: ``localhost`` - **port**: The Emby server port. Default: 8096 - **username**: A username of an Emby user that is allowed to refresh the library. - **userid**: A user ID of an Emby user that is allowed to refresh the library. (This is only necessary for private users i.e. when the user is hidden from login screens) - **apikey**: An Emby API key for the user. - **password**: The password for the user. (This is only necessary if no API key is provided.) You can choose to authenticate either with ``apikey`` or ``password``, but only one of those two is required. �����������������������beetbox-beets-01f1faf/docs/plugins/export.rst�������������������������������������������������������0000664�0000000�0000000�00000005562�14723254774�0021443�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������Export Plugin ============= The ``export`` plugin lets you get data from the items and export the content as `JSON`_, `CSV`_, or `XML`_. .. _JSON: https://www.json.org .. _CSV: https://fileinfo.com/extension/csv .. _XML: https://fileinfo.com/extension/xml Enable the ``export`` plugin (see :ref:`using-plugins` for help). Then, type ``beet export`` followed by a :doc:`query </reference/query>` to get the data from your library. For example, run this:: $ beet export beatles to print a JSON file containing information about your Beatles tracks. Command-Line Options -------------------- The ``export`` command has these command-line options: * ``--include-keys`` or ``-i``: Choose the properties to include in the output data. The argument is a comma-separated list of simple glob patterns where ``*`` matches any string. For example:: $ beet export -i 'title,mb*' beatles will include the ``title`` property and all properties starting with ``mb``. You can add the ``-i`` option multiple times to the command line. * ``--library`` or ``-l``: Show data from the library database instead of the files' tags. * ``--album`` or ``-a``: Show data from albums instead of tracks (implies ``--library``). * ``--output`` or ``-o``: Path for an output file. If not informed, will print the data in the console. * ``--append``: Appends the data to the file instead of writing. * ``--format`` or ``-f``: Specifies the format the data will be exported as. If not informed, JSON will be used by default. The format options include csv, json, `jsonlines <https://jsonlines.org/>`_ and xml. Configuration ------------- To configure the plugin, make a ``export:`` section in your configuration file. For JSON export, these options are available under the ``json`` and ``jsonlines`` keys: - **ensure_ascii**: Escape non-ASCII characters with ``\uXXXX`` entities. - **indent**: The number of spaces for indentation. - **separators**: A ``[item_separator, dict_separator]`` tuple. - **sort_keys**: Sorts the keys in JSON dictionaries. Those options match the options from the `Python json module`_. Similarly, these options are available for the CSV format under the ``csv`` key: - **delimiter**: Used as the separating character between fields. The default value is a comma (,). - **dialect**: The kind of CSV file to produce. The default is `excel`. These options match the options from the `Python csv module`_. .. _Python json module: https://docs.python.org/2/library/json.html#basic-usage .. _Python csv module: https://docs.python.org/3/library/csv.html#csv-fmt-params The default options look like this:: export: json: formatting: ensure_ascii: false indent: 4 separators: [',' , ': '] sort_keys: true csv: formatting: delimiter: ',' dialect: excel ����������������������������������������������������������������������������������������������������������������������������������������������beetbox-beets-01f1faf/docs/plugins/fetchart.rst�����������������������������������������������������0000664�0000000�0000000�00000030503�14723254774�0021713�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������FetchArt Plugin =============== The ``fetchart`` plugin retrieves album art images from various sources on the Web and stores them as image files. To use the ``fetchart`` plugin, first enable it in your configuration (see :ref:`using-plugins`). Then, install ``beets`` with ``fetchart`` extra .. code-block:: bash pip install "beets[fetchart]" Fetching Album Art During Import -------------------------------- When the plugin is enabled, it automatically tries to get album art for every album you import. By default, beets stores album art image files alongside the music files for an album in a file called ``cover.jpg``. To customize the name of this file, use the :ref:`art-filename` config option. To embed the art into the files' tags, use the :doc:`/plugins/embedart`. (You'll want to have both plugins enabled.) Configuration ------------- To configure the plugin, make a ``fetchart:`` section in your configuration file. The available options are: - **auto**: Enable automatic album art fetching during import. Default: ``yes``. - **cautious**: Pick only trusted album art by ignoring filenames that do not contain one of the keywords in ``cover_names``. Default: ``no``. - **cover_names**: Prioritize images containing words in this list. Default: ``cover front art album folder``. - **minwidth**: Only images with a width bigger or equal to ``minwidth`` are considered as valid album art candidates. Default: 0. - **maxwidth**: A maximum image width to downscale fetched images if they are too big. The resize operation reduces image width to at most ``maxwidth`` pixels. The height is recomputed so that the aspect ratio is preserved. See the section on :ref:`cover-art-archive-maxwidth` below for additional information regarding the Cover Art Archive source. Default: 0 (no maximum is enforced). - **quality**: The JPEG quality level to use when compressing images (when ``maxwidth`` is set). This should be either a number from 1 to 100 or 0 to use the default quality. 65–75 is usually a good starting point. The default behavior depends on the imaging tool used for scaling: ImageMagick tries to estimate the input image quality and uses 92 if it cannot be determined, and PIL defaults to 75. Default: 0 (disabled) - **max_filesize**: The maximum size of a target piece of cover art in bytes. When using an ImageMagick backend this sets ``-define jpeg:extent=max_filesize``. Using PIL this will reduce JPG quality by up to 50% to attempt to reach the target filesize. Neither method is *guaranteed* to reach the target size, however in most cases it should succeed. Default: 0 (disabled) - **enforce_ratio**: Only images with a width:height ratio of 1:1 are considered as valid album art candidates if set to ``yes``. It is also possible to specify a certain deviation to the exact ratio to still be considered valid. This can be done either in pixels (``enforce_ratio: 10px``) or as a percentage of the longer edge (``enforce_ratio: 0.5%``). Default: ``no``. - **sources**: List of sources to search for images. An asterisk `*` expands to all available sources. Default: ``filesystem coverart itunes amazon albumart``, i.e., everything but ``wikipedia``, ``google``, ``fanarttv`` and ``lastfm``. Enable those sources for more matches at the cost of some speed. They are searched in the given order, thus in the default config, no remote (Web) art source are queried if local art is found in the filesystem. To use a local image as fallback, move it to the end of the list. For even more fine-grained control over the search order, see the section on :ref:`album-art-sources` below. - **google_key**: Your Google API key (to enable the Google Custom Search backend). Default: None. - **google_engine**: The custom search engine to use. Default: The `beets custom search engine`_, which searches the entire web. - **fanarttv_key**: The personal API key for requesting art from fanart.tv. See below. - **lastfm_key**: The personal API key for requesting art from Last.fm. See below. - **store_source**: If enabled, fetchart stores the artwork's source in a flexible tag named ``art_source``. See below for the rationale behind this. Default: ``no``. - **high_resolution**: If enabled, fetchart retrieves artwork in the highest resolution it can find (warning: image files can sometimes reach >20MB). Default: ``no``. - **deinterlace**: If enabled, `Pillow`_ or `ImageMagick`_ backends are instructed to store cover art as non-progressive JPEG. You might need this if you use DAPs that don't support progressive images. Default: ``no``. - **cover_format**: If enabled, forced the cover image into the specified format. Most often, this will be either ``JPEG`` or ``PNG`` [#imgformats]_. Also respects ``deinterlace``. Default: None (leave unchanged). Note: ``maxwidth`` and ``enforce_ratio`` options require either `ImageMagick`_ or `Pillow`_. .. note:: Previously, there was a ``remote_priority`` option to specify when to look for art on the filesystem. This is still respected, but a deprecation message will be shown until you replace this configuration with the new ``filesystem`` value in the ``sources`` array. .. _beets custom search engine: https://cse.google.com.au:443/cse/publicurl?cx=001442825323518660753:hrh5ch1gjzm .. _Pillow: https://github.com/python-pillow/Pillow .. _ImageMagick: https://www.imagemagick.org/ .. [#imgformats] Other image formats are available, though the full list depends on your system and what backend you are using. If you're using the ImageMagick backend, you can use ``magick identify -list format`` to get a full list of all supported formats, and you can use the Python function PIL.features.pilinfo() to print a list of all supported formats in Pillow (``python3 -c 'import PIL.features as f; f.pilinfo()'``). Here's an example that makes plugin select only images that contain ``front`` or ``back`` keywords in their filenames and prioritizes the iTunes source over others:: fetchart: cautious: true cover_names: front back sources: itunes * Manually Fetching Album Art --------------------------- Use the ``fetchart`` command to download album art after albums have already been imported:: $ beet fetchart [-f] [query] By default, the command will only look for album art when the album doesn't already have it; the ``-f`` or ``--force`` switch makes it search for art in Web databases regardless. If you specify a query, only matching albums will be processed; otherwise, the command processes every album in your library. Display Only Missing Album Art ------------------------------ Use the ``fetchart`` command with the ``-q`` switch in order to display only missing art:: $ beet fetchart [-q] [query] By default the command will display all albums matching the ``query``. When the ``-q`` or ``--quiet`` switch is given, only albums for which artwork has been fetched, or for which artwork could not be found will be printed. .. _image-resizing: Image Resizing -------------- Beets can resize images using `Pillow`_, `ImageMagick`_, or a server-side resizing proxy. If either Pillow or ImageMagick is installed, beets will use those; otherwise, it falls back to the resizing proxy. If the resizing proxy is used, no resizing is performed for album art found on the filesystem---only downloaded art is resized. Server-side resizing can also be slower than local resizing, so consider installing one of the two backends for better performance. When using ImageMagick, beets looks for the ``convert`` executable in your path. On some versions of Windows, the program can be shadowed by a system-provided ``convert.exe``. On these systems, you may need to modify your ``%PATH%`` environment variable so that ImageMagick comes first or use Pillow instead. .. _Pillow: https://github.com/python-pillow/Pillow .. _ImageMagick: https://www.imagemagick.org/ .. _album-art-sources: Album Art Sources ----------------- By default, this plugin searches for art in the local filesystem as well as on the Cover Art Archive, the iTunes Store, Amazon, and AlbumArt.org, in that order. You can reorder the sources or remove some to speed up the process using the ``sources`` configuration option. When looking for local album art, beets checks for image files located in the same folder as the music files you're importing. Beets prefers to use an image file whose name contains "cover", "front", "art", "album" or "folder", but in the absence of well-known names, it will use any image file in the same folder as your music files. For some of the art sources, the backend service can match artwork by various criteria. If you want finer control over the search order in such cases, you can use this alternative syntax for the ``sources`` option:: fetchart: sources: - filesystem - coverart: release - itunes - coverart: releasegroup - '*' where listing a source without matching criteria will default to trying all available strategies. Entries of the forms ``coverart: release releasegroup`` and ``coverart: *`` are also valid. Currently, only the ``coverart`` source supports multiple criteria: namely, ``release`` and ``releasegroup``, which refer to the respective MusicBrainz IDs. When you choose to apply changes during an import, beets will search for art as described above. For "as-is" imports (and non-autotagged imports using the ``-A`` flag), beets only looks for art on the local filesystem. Google custom search '''''''''''''''''''' To use the google image search backend you need to `register for a Google API key`_. Set the ``google_key`` configuration option to your key, then add ``google`` to the list of sources in your configuration. .. _register for a Google API key: https://console.developers.google.com. Optionally, you can `define a custom search engine`_. Get your search engine's token and use it for your ``google_engine`` configuration option. The default engine searches the entire web for cover art. .. _define a custom search engine: https://www.google.com/cse/all Note that the Google custom search API is limited to 100 queries per day. After that, the fetchart plugin will fall back on other declared data sources. Fanart.tv ''''''''' Although not strictly necessary right now, you might think about `registering a personal fanart.tv API key`_. Set the ``fanarttv_key`` configuration option to your key, then add ``fanarttv`` to the list of sources in your configuration. .. _registering a personal fanart.tv API key: https://fanart.tv/get-an-api-key/ More detailed information can be found `on their Wiki`_. Specifically, the personal key will give you earlier access to new art. .. _on their Wiki: https://wiki.fanart.tv/General/personal%20api/ Last.fm ''''''' To use the Last.fm backend, you need to `register for a Last.fm API key`_. Set the ``lastfm_key`` configuration option to your API key, then add ``lastfm`` to the list of sources in your configuration. .. _register for a Last.fm API key: https://www.last.fm/api/account/create Spotify ''''''' Spotify backend is enabled by default and will update album art if a valid Spotify album id is found. .. _pip: https://pip.pypa.io .. _BeautifulSoup: https://www.crummy.com/software/BeautifulSoup/bs4/doc/ Cover Art URL ''''''''''''' The `fetchart` plugin can also use a flexible attribute field ``cover_art_url`` where you can manually specify the image URL to be used as cover art. Any custom plugin can use this field to provide the cover art and ``fetchart`` will use it as a source. .. _cover-art-archive-maxwidth: Cover Art Archive Pre-sized Thumbnails -------------------------------------- The CAA provides pre-sized thumbnails of width 250, 500, and 1200 pixels. If you set the `maxwidth` option to one of these values, the corresponding image will be downloaded, saving `beets` the need to scale down the image. It can also speed up the downloading process, as some cover arts can sometimes be very large. Storing the Artwork's Source ---------------------------- Storing the current artwork's source might be used to narrow down ``fetchart`` commands. For example, if some albums have artwork placed manually in their directories that should not be replaced by a forced album art fetch, you could do ``beet fetchart -f ^art_source:filesystem`` The values written to ``art_source`` are the same names used in the ``sources`` configuration value. ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������beetbox-beets-01f1faf/docs/plugins/filefilter.rst���������������������������������������������������0000664�0000000�0000000�00000002031�14723254774�0022233�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������FileFilter Plugin ================= The ``filefilter`` plugin allows you to skip files during import using regular expressions. To use the ``filefilter`` plugin, enable it in your configuration (see :ref:`using-plugins`). Configuration ------------- To configure the plugin, make a ``filefilter:`` section in your configuration file. The available options are: - **path**: A regular expression to filter files based on their path and name. Default: ``.*`` (import everything) - **album_path** and **singleton_path**: You may specify different regular expressions used for imports of albums and singletons. This way, you can automatically skip singletons when importing albums if the names (and paths) of the files are distinguishable via a regex. The regexes defined here take precedence over the global ``path`` option. Here's an example:: filefilter: path: .*\d\d[^/]+$ # will only import files which names start with two digits album_path: .*\d\d[^/]+$ singleton_path: .*/(?!\d\d)[^/]+$ �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������beetbox-beets-01f1faf/docs/plugins/fish.rst���������������������������������������������������������0000664�0000000�0000000�00000005230�14723254774�0021043�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������Fish Plugin =========== The ``fish`` plugin adds a ``beet fish`` command that creates a `Fish shell`_ tab-completion file named ``beet.fish`` in ``~/.config/fish/completions``. This enables tab-completion of ``beet`` commands for the `Fish shell`_. .. _Fish shell: https://fishshell.com/ Configuration ------------- Enable the ``fish`` plugin (see :ref:`using-plugins`) on a system running the `Fish shell`_. Usage ----- Type ``beet fish`` to generate the ``beet.fish`` completions file at: ``~/.config/fish/completions/``. If you later install or disable plugins, run ``beet fish`` again to update the completions based on the enabled plugins. For users not accustomed to tab completion… After you type ``beet`` followed by a space in your shell prompt and then the ``TAB`` key, you should see a list of the beets commands (and their abbreviated versions) that can be invoked in your current environment. Similarly, typing ``beet -<TAB>`` will show you all the option flags available to you, which also applies to subcommands such as ``beet import -<TAB>``. If you type ``beet ls`` followed by a space and then the and the ``TAB`` key, you will see a list of all the album/track fields that can be used in beets queries. For example, typing ``beet ls ge<TAB>`` will complete to ``genre:`` and leave you ready to type the rest of your query. Options ------- In addition to beets commands, plugin commands, and option flags, the generated completions also include by default all the album/track fields. If you only want the former and do not want the album/track fields included in the generated completions, use ``beet fish -f`` to only generate completions for beets/plugin commands and option flags. If you want generated completions to also contain album/track field *values* for the items in your library, you can use the ``-e`` or ``--extravalues`` option. For example: ``beet fish -e genre`` or ``beet fish -e genre -e albumartist`` In the latter case, subsequently typing ``beet list genre: <TAB>`` will display a list of all the genres in your library and ``beet list albumartist: <TAB>`` will show a list of the album artists in your library. Keep in mind that all of these values will be put into the generated completions file, so use this option with care when specified fields contain a large number of values. Libraries with, for example, very large numbers of genres/artists may result in higher memory utilization, completion latency, et cetera. This option is not meant to replace database queries altogether. By default, the completion file will be generated at ``~/.config/fish/completions/``. If you want to save it somewhere else, you can use the ``-o`` or ``--output`` option. ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������beetbox-beets-01f1faf/docs/plugins/freedesktop.rst��������������������������������������������������0000664�0000000�0000000�00000000344�14723254774�0022426�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������Freedesktop Plugin ================== The ``freedesktop`` plugin created .directory files in your album folders. This plugin is now deprecated and replaced by the :doc:`/plugins/thumbnails` with the ``dolphin`` option enabled. ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������beetbox-beets-01f1faf/docs/plugins/fromfilename.rst�������������������������������������������������0000664�0000000�0000000�00000001010�14723254774�0022546�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������FromFilename Plugin =================== The ``fromfilename`` plugin helps to tag albums that are missing tags altogether but where the filenames contain useful information like the artist and title. When you attempt to import a track that's missing a title, this plugin will look at the track's filename and guess its track number, title, and artist. These will be used to search in MusicBrainz and match track ordering. To use the ``fromfilename`` plugin, enable it in your configuration (see :ref:`using-plugins`). ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������beetbox-beets-01f1faf/docs/plugins/ftintitle.rst����������������������������������������������������0000664�0000000�0000000�00000003140�14723254774�0022112�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������FtInTitle Plugin ================ The ``ftintitle`` plugin automatically moves "featured" artists from the ``artist`` field to the ``title`` field. According to `MusicBrainz style`_, featured artists are part of the artist field. That means that, if you tag your music using MusicBrainz, you'll have tracks in your library like "Tellin' Me Things" by the artist "Blakroc feat. RZA". If you prefer to tag this as "Tellin' Me Things feat. RZA" by "Blakroc", then this plugin is for you. To use the ``ftintitle`` plugin, enable it in your configuration (see :ref:`using-plugins`). Configuration ------------- To configure the plugin, make a ``ftintitle:`` section in your configuration file. The available options are: - **auto**: Enable metadata rewriting during import. Default: ``yes``. - **drop**: Remove featured artists entirely instead of adding them to the title field. Default: ``no``. - **format**: Defines the format for the featuring X part of the new title field. In this format the ``{0}`` is used to define where the featured artists are placed. Default: ``feat. {0}`` - **keep_in_artist**: Keep the featuring X part in the artist field. This can be useful if you still want to be able to search for features in the artist field. Default: ``no``. Running Manually ---------------- From the command line, type:: $ beet ftintitle [QUERY] The query is optional; if it's left off, the transformation will be applied to your entire collection. Use the ``-d`` flag to remove featured artists (equivalent of the ``drop`` config option). .. _MusicBrainz style: https://musicbrainz.org/doc/Style ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������beetbox-beets-01f1faf/docs/plugins/fuzzy.rst��������������������������������������������������������0000664�0000000�0000000�00000001534�14723254774�0021304�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������Fuzzy Search Plugin =================== The ``fuzzy`` plugin provides a prefixed query that searches your library using fuzzy pattern matching. This can be useful if you want to find a track with complicated characters in the title. First, enable the plugin named ``fuzzy`` (see :ref:`using-plugins`). You'll then be able to use the ``~`` prefix to use fuzzy matching:: $ beet ls '~Vareoldur' Sigur RĂłs - Valtari - Varðeldur Configuration ------------- To configure the plugin, make a ``fuzzy:`` section in your configuration file. The available options are: - **threshold**: The "sensitivity" of the fuzzy match. A value of 1.0 will show only perfect matches and a value of 0.0 will match everything. Default: 0.7. - **prefix**: The character used to designate fuzzy queries. Default: ``~``, which may need to be escaped in some shells. ��������������������������������������������������������������������������������������������������������������������������������������������������������������������beetbox-beets-01f1faf/docs/plugins/gmusic.rst�������������������������������������������������������0000664�0000000�0000000�00000000222�14723254774�0021375�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������Gmusic Plugin ============= The ``gmusic`` plugin interfaced beets to Google Play Music. It has been removed after the shutdown of this service. ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������beetbox-beets-01f1faf/docs/plugins/hook.rst���������������������������������������������������������0000664�0000000�0000000�00000004161�14723254774�0021054�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������Hook Plugin =========== Internally, beets uses *events* to tell plugins when something happens. For example, one event fires when the importer finishes processes a song, and another triggers just before the ``beet`` command exits. The ``hook`` plugin lets you run commands in response to these events. .. _hook-configuration: Configuration ------------- To configure the plugin, make a ``hook`` section in your configuration file. The available options are: - **hooks**: A list of events and the commands to run (see :ref:`individual-hook-configuration`). Default: Empty. .. _individual-hook-configuration: Configuring Each Hook ''''''''''''''''''''' Each element under ``hooks`` should have these keys: - **event**: The name of the event that will trigger this hook. See the :ref:`plugin events <plugin_events>` documentation for a list of possible values. - **command**: The command to run when this hook executes. .. _command-substitution: Command Substitution '''''''''''''''''''' Commands can access the parameters of events using `Python string formatting`_. Use ``{name}`` in your command and the plugin will substitute it with the named value. The name can also refer to a field, as in ``{album.path}``. .. _Python string formatting: https://www.python.org/dev/peps/pep-3101/ You can find a list of all available events and their arguments in the :ref:`plugin events <plugin_events>` documentation. Example Configuration --------------------- .. code-block:: yaml hook: hooks: # Output on exit: # beets just exited! # have a nice day! - event: cli_exit command: echo "beets just exited!" - event: cli_exit command: echo "have a nice day!" # Output on item import: # importing "<file_name_here>" # Where <file_name_here> is the item being imported - event: item_imported command: echo "importing \"{item.path}\"" # Output on write: # writing to "<file_name_here>" # Where <file_name_here> is the file being written to - event: write command: echo "writing to {path}" ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������beetbox-beets-01f1faf/docs/plugins/ihate.rst��������������������������������������������������������0000664�0000000�0000000�00000002057�14723254774�0021210�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������IHate Plugin ============ The ``ihate`` plugin allows you to automatically skip things you hate during import or warn you about them. You specify queries (see :doc:`/reference/query`) and the plugin skips (or warns about) albums or items that match any query. To use the ``ihate`` plugin, enable it in your configuration (see :ref:`using-plugins`). Configuration ------------- To configure the plugin, make an ``ihate:`` section in your configuration file. The available options are: - **skip**: Never import items and albums that match a query in this list. Default: ``[]`` (empty list). - **warn**: Print a warning message for matches in this list of queries. Default: ``[]``. Here's an example:: ihate: warn: - artist:rnb - genre:soul # Only warn about tribute albums in rock genre. - genre:rock album:tribute skip: - genre::russian\srock - genre:polka - artist:manowar - album:christmas The plugin trusts your decision in "as-is" imports. ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������beetbox-beets-01f1faf/docs/plugins/importadded.rst��������������������������������������������������0000664�0000000�0000000�00000003571�14723254774�0022414�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������ImportAdded Plugin ================== The ``importadded`` plugin is useful when an existing collection is imported and the time when albums and items were added should be preserved. To use the ``importadded`` plugin, enable it in your configuration (see :ref:`using-plugins`). Usage ----- The :abbr:`mtime (modification time)` of files that are imported into the library are assumed to represent the time when the items were originally added. The ``item.added`` field is populated as follows: * For singleton items with no album, ``item.added`` is set to the item's file mtime before it was imported. * For items that are part of an album, ``album.added`` and ``item.added`` are set to the oldest mtime of the files in the album before they were imported. The mtime of album directories is ignored. This plugin can optionally be configured to also preserve mtimes at import using the ``preserve_mtimes`` option. When ``preserve_write_mtimes`` option is set, this plugin preserves mtimes after each write to files using the ``item.added`` attribute. File modification times are preserved as follows: * For all items: * ``item.mtime`` is set to the mtime of the file from which the item is imported from. * The mtime of the file ``item.path`` is set to ``item.mtime``. Note that there is no ``album.mtime`` field in the database and that the mtime of album directories on disk aren't preserved. Configuration ------------- To configure the plugin, make an ``importadded:`` section in your configuration file. There are two options available: - **preserve_mtimes**: After importing files, re-set their mtimes to their original value. Default: ``no``. - **preserve_write_mtimes**: After writing files, re-set their mtimes to their original value. Default: ``no``. Reimport -------- This plugin will skip reimported singleton items and reimported albums and all of their items. ���������������������������������������������������������������������������������������������������������������������������������������beetbox-beets-01f1faf/docs/plugins/importfeeds.rst��������������������������������������������������0000664�0000000�0000000�00000003541�14723254774�0022436�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������ImportFeeds Plugin ================== This plugin helps you keep track of newly imported music in your library. To use the ``importfeeds`` plugin, enable it in your configuration (see :ref:`using-plugins`). Configuration ------------- To configure the plugin, make an ``importfeeds:`` section in your configuration file. The available options are: - **absolute_path**: Use absolute paths instead of relative paths. Some applications may need this to work properly. Default: ``no``. - **dir**: The output directory. Default: Your beets library directory. - **formats**: Select the kind of output. Use one or more of: - **m3u**: Catalog the imports in a centralized playlist. - **m3u_multi**: Create a new playlist for each import (uniquely named by appending the date and track/album name). - **m3u_session**: Create a new playlist for each import session. The file is named as ``m3u_name`` appending the date and time the import session was started. - **link**: Create a symlink for each imported item. This is the recommended setting to propagate beets imports to your iTunes library: just drag and drop the ``dir`` folder on the iTunes dock icon. - **echo**: Do not write a playlist file at all, but echo a list of new file paths to the terminal. Default: None. - **m3u_name**: Playlist name used by the ``m3u`` format and as a prefix used by the ``m3u_session`` format. Default: ``imported.m3u``. - **relative_to**: Make the m3u paths relative to another folder than where the playlist is being written. If you're using importfeeds to generate a playlist for MPD, you should set this to the root of your music library. Default: None. Here's an example configuration for this plugin:: importfeeds: formats: m3u link dir: ~/imports/ relative_to: ~/Music/ m3u_name: newfiles.m3u ���������������������������������������������������������������������������������������������������������������������������������������������������������������beetbox-beets-01f1faf/docs/plugins/index.rst��������������������������������������������������������0000664�0000000�0000000�00000040636�14723254774�0021232�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������Plugins ======= Plugins extend beets' core functionality. They add new commands, fetch additional data during import, provide new metadata sources, and much more. If beets by itself doesn't do what you want it to, you may just need to enable a plugin---or, if you want to do something new, :doc:`writing a plugin </dev/plugins>` is easy if you know a little Python. .. _using-plugins: Using Plugins ------------- To use one of the plugins included with beets (see the rest of this page for a list), just use the ``plugins`` option in your :doc:`config.yaml </reference/config>` file, like so:: plugins: inline convert web The value for ``plugins`` can be a space-separated list of plugin names or a YAML list like ``[foo, bar]``. You can see which plugins are currently enabled by typing ``beet version``. Each plugin has its own set of options that can be defined in a section bearing its name:: plugins: inline convert web convert: auto: true Some plugins have special dependencies that you'll need to install. The documentation page for each plugin will list them in the setup instructions. For some, you can use ``pip``'s "extras" feature to install the dependencies, like this:: pip install beets[fetchart,lyrics,lastgenre] .. _metadata-source-plugin-configuration: Using Metadata Source Plugins ----------------------------- Some plugins provide sources for metadata in addition to MusicBrainz. These plugins share the following configuration option: - **source_weight**: Penalty applied to matches during import. Set to 0.0 to disable. Default: ``0.5``. For example, to equally consider matches from Discogs and MusicBrainz add the following to your configuration:: plugins: discogs discogs: source_weight: 0.0 .. toctree:: :hidden: absubmit acousticbrainz advancedrewrite albumtypes aura autobpm badfiles bareasc beatport bpd bpm bpsync bucket chroma convert deezer discogs duplicates edit embedart embyupdate export fetchart filefilter fish freedesktop fromfilename ftintitle fuzzy gmusic hook ihate importadded importfeeds info inline ipfs keyfinder kodiupdate lastgenre lastimport limit listenbrainz loadext lyrics mbcollection mbsubmit mbsync metasync missing mpdstats mpdupdate parentwork permissions play playlist plexupdate random replaygain rewrite scrub smartplaylist sonosupdate spotify subsonicplaylist subsonicupdate substitute the thumbnails types unimported web zero .. _autotagger_extensions: Autotagger Extensions --------------------- :doc:`chroma <chroma>` Use acoustic fingerprinting to identify audio files with missing or incorrect metadata. :doc:`discogs <discogs>` Search for releases in the `Discogs`_ database. :doc:`spotify <spotify>` Search for releases in the `Spotify`_ database. :doc:`deezer <deezer>` Search for releases in the `Deezer`_ database. :doc:`fromfilename <fromfilename>` Guess metadata for untagged tracks from their filenames. .. _Discogs: https://www.discogs.com/ .. _Spotify: https://www.spotify.com .. _Deezer: https://www.deezer.com/ Metadata -------- :doc:`absubmit <absubmit>` Analyse audio with the `streaming_extractor_music`_ program and submit the metadata to an AcousticBrainz server :doc:`acousticbrainz <acousticbrainz>` Fetch various AcousticBrainz metadata :doc:`autobpm <autobpm>` Use `Librosa`_ to calculate the BPM from the audio. :doc:`bpm <bpm>` Measure tempo using keystrokes. :doc:`bpsync <bpsync>` Fetch updated metadata from Beatport. :doc:`edit <edit>` Edit metadata from a text editor. :doc:`embedart <embedart>` Embed album art images into files' metadata. :doc:`fetchart <fetchart>` Fetch album cover art from various sources. :doc:`ftintitle <ftintitle>` Move "featured" artists from the artist field to the title field. :doc:`keyfinder <keyfinder>` Use the `KeyFinder`_ program to detect the musical key from the audio. :doc:`importadded <importadded>` Use file modification times for guessing the value for the `added` field in the database. :doc:`lastgenre <lastgenre>` Fetch genres based on Last.fm tags. :doc:`lastimport <lastimport>` Collect play counts from Last.fm. :doc:`lyrics <lyrics>` Automatically fetch song lyrics. :doc:`mbsync <mbsync>` Fetch updated metadata from MusicBrainz. :doc:`metasync <metasync>` Fetch metadata from local or remote sources :doc:`mpdstats <mpdstats>` Connect to `MPD`_ and update the beets library with play statistics (last_played, play_count, skip_count, rating). :doc:`parentwork <parentwork>` Fetch work titles and works they are part of. :doc:`replaygain <replaygain>` Calculate volume normalization for players that support it. :doc:`scrub <scrub>` Clean extraneous metadata from music files. :doc:`zero <zero>` Nullify fields by pattern or unconditionally. .. _Librosa: https://github.com/librosa/librosa/ .. _KeyFinder: http://www.ibrahimshaath.co.uk/keyfinder/ .. _streaming_extractor_music: https://acousticbrainz.org/download Path Formats ------------ :doc:`albumtypes <albumtypes>` Format album type in path formats. :doc:`bucket <bucket>` Group your files into bucket directories that cover different field values ranges. :doc:`inline <inline>` Use Python snippets to customize path format strings. :doc:`rewrite <rewrite>` Substitute values in path formats. :doc:`advancedrewrite <advancedrewrite>` Substitute field values for items matching a query. :doc:`substitute <substitute>` As an alternative to :doc:`rewrite <rewrite>`, use this plugin. The main difference between them is that this plugin never modifies the files metadata. :doc:`the <the>` Move patterns in path formats (i.e., move "a" and "the" to the end). Interoperability ---------------- :doc:`aura <aura>` A server implementation of the `AURA`_ specification. :doc:`badfiles <badfiles>` Check audio file integrity. :doc:`embyupdate <embyupdate>` Automatically notifies `Emby`_ whenever the beets library changes. :doc:`fish <fish>` Adds `Fish shell`_ tab autocompletion to ``beet`` commands. :doc:`importfeeds <importfeeds>` Keep track of imported files via ``.m3u`` playlist file(s) or symlinks. :doc:`ipfs <ipfs>` Import libraries from friends and get albums from them via ipfs. :doc:`kodiupdate <kodiupdate>` Automatically notifies `Kodi`_ whenever the beets library changes. :doc:`mpdupdate <mpdupdate>` Automatically notifies `MPD`_ whenever the beets library changes. :doc:`play <play>` Play beets queries in your music player. :doc:`playlist <playlist>` Use M3U playlists to query the beets library. :doc:`plexupdate <plexupdate>` Automatically notifies `Plex`_ whenever the beets library changes. :doc:`smartplaylist <smartplaylist>` Generate smart playlists based on beets queries. :doc:`sonosupdate <sonosupdate>` Automatically notifies `Sonos`_ whenever the beets library changes. :doc:`thumbnails <thumbnails>` Get thumbnails with the cover art on your album folders. :doc:`subsonicupdate <subsonicupdate>` Automatically notifies `Subsonic`_ whenever the beets library changes. .. _AURA: https://auraspec.readthedocs.io .. _Emby: https://emby.media .. _Fish shell: https://fishshell.com/ .. _Plex: https://plex.tv .. _Kodi: https://kodi.tv .. _Sonos: https://sonos.com .. _Subsonic: http://www.subsonic.org/ Miscellaneous ------------- :doc:`bareasc <bareasc>` Search albums and tracks with bare ASCII string matching. :doc:`bpd <bpd>` A music player for your beets library that emulates `MPD`_ and is compatible with `MPD clients`_. :doc:`convert <convert>` Transcode music and embed album art while exporting to a different directory. :doc:`duplicates <duplicates>` List duplicate tracks or albums. :doc:`export <export>` Export data from queries to a format. :doc:`filefilter <filefilter>` Automatically skip files during the import process based on regular expressions. :doc:`fuzzy <fuzzy>` Search albums and tracks with fuzzy string matching. :doc:`hook <hook>` Run a command when an event is emitted by beets. :doc:`ihate <ihate>` Automatically skip albums and tracks during the import process. :doc:`info <info>` Print music files' tags to the console. :doc:`loadext <loadext>` Load SQLite extensions. :doc:`mbcollection <mbcollection>` Maintain your MusicBrainz collection list. :doc:`mbsubmit <mbsubmit>` Print an album's tracks in a MusicBrainz-friendly format. :doc:`missing <missing>` List missing tracks. `mstream`_ A music streaming server + webapp that can be used alongside beets. :doc:`random <random>` Randomly choose albums and tracks from your library. :doc:`spotify <spotify>` Create Spotify playlists from the Beets library. :doc:`types <types>` Declare types for flexible attributes. :doc:`web <web>` An experimental Web-based GUI for beets. .. _MPD: https://www.musicpd.org/ .. _MPD clients: https://mpd.wikia.com/wiki/Clients .. _mstream: https://github.com/IrosTheBeggar/mStream .. _other-plugins: Other Plugins ------------- In addition to the plugins that come with beets, there are several plugins that are maintained by the beets community. To use an external plugin, there are two options for installation: * Make sure it's in the Python path (known as ``sys.path`` to developers). This just means the plugin has to be installed on your system (e.g., with a ``setup.py`` script or a command like ``pip`` or ``easy_install``). * Set the ``pluginpath`` config variable to point to the directory containing the plugin. (See :doc:`/reference/config`.) Once the plugin is installed, enable it by placing its name on the ``plugins`` line in your config file. Here are a few of the plugins written by the beets community: `beets-alternatives`_ Manages external files. `beet-amazon`_ Adds Amazon.com as a tagger data source. `beets-artistcountry`_ Fetches the artist's country of origin from MusicBrainz. `beets-autofix`_ Automates repetitive tasks to keep your library in order. `beets-autogenre`_ Assigns genres to your library items using the :doc:`lastgenre <lastgenre>` and `beets-xtractor`_ plugins as well as additional rules. `beets-audible`_ Adds Audible as a tagger data source and provides other features for managing audiobook collections. `beets-barcode`_ Lets you scan or enter barcodes for physical media to search for their metadata. `beetcamp`_ Enables **bandcamp.com** autotagger with a fairly extensive amount of metadata. `beetstream`_ Server implementation of the `Subsonic API`_ specification, serving the beets library and (:doc:`smartplaylist <smartplaylist>` plugin generated) M3U playlists, allowing you to stream your music on a multitude of clients. `beets-bpmanalyser`_ Analyses songs and calculates their tempo (BPM). `beets-check`_ Automatically checksums your files to detect corruption. `A cmus plugin`_ Integrates with the `cmus`_ console music player. `beets-copyartifacts`_ Helps bring non-music files along during import. `beets-describe`_ Gives you the full picture of a single attribute of your library items. `drop2beets`_ Automatically imports singles as soon as they are dropped in a folder (using Linux's ``inotify``). You can also set a sub-folders hierarchy to set flexible attributes by the way. `dsedivec`_ Has two plugins: ``edit`` and ``moveall``. `beets-follow`_ Lets you check for new albums from artists you like. `beetFs`_ Is a FUSE filesystem for browsing the music in your beets library. (Might be out of date.) `beets-goingrunning`_ Generates playlists to go with your running sessions. `beets-ibroadcast`_ Uploads tracks to the `iBroadcast`_ cloud service. `beets-importreplace`_ Lets you perform regex replacements on incoming metadata. `beets-jiosaavn`_ Adds JioSaavn.com as a tagger data source. `beets-more`_ Finds versions of indexed releases with more tracks, like deluxe and anniversary editions. `beets-mosaic`_ Generates a montage of a mosaic from cover art. `beets-mpd-utils`_ Plugins to interface with `MPD`_. Comes with ``mpd_tracker`` (track play/skip counts from MPD) and ``mpd_dj`` (auto-add songs to your queue.) `beets-noimport`_ Adds and removes directories from the incremental import skip list. `beets-originquery`_ Augments MusicBrainz queries with locally-sourced data to improve autotagger results. `beets-plexsync`_ Allows you to sync your Plex library with your beets library, create smart playlists in Plex, and import online playlists (from services like Spotify) into Plex. `beets-setlister`_ Generate playlists from the setlists of a given artist. `beet-summarize`_ Can compute lots of counts and statistics about your music library. `beets-usertag`_ Lets you use keywords to tag and organize your music. `beets-webm3u`_ Serves the (:doc:`smartplaylist <smartplaylist>` plugin generated) M3U playlists via HTTP. `beets-webrouter`_ Serves multiple beets webapps (e.g. :doc:`web <web>`, `beets-webm3u`_, `beetstream`_, :doc:`aura <aura>`) using a single command/process/host/port, each under a different path. `whatlastgenre`_ Fetches genres from various music sites. `beets-xtractor`_ Extracts low- and high-level musical information from your songs. `beets-ydl`_ Downloads audio from youtube-dl sources and import into beets. `beets-ytimport`_ Download and import your liked songs from YouTube into beets. `beets-yearfixer`_ Attempts to fix all missing ``original_year`` and ``year`` fields. `beets-youtube`_ Adds YouTube Music as a tagger data source. .. _beets-barcode: https://github.com/8h2a/beets-barcode .. _beetcamp: https://github.com/snejus/beetcamp .. _beetstream: https://github.com/BinaryBrain/Beetstream .. _Subsonic API: http://www.subsonic.org/pages/api.jsp .. _beets-check: https://github.com/geigerzaehler/beets-check .. _beets-copyartifacts: https://github.com/adammillerio/beets-copyartifacts .. _dsedivec: https://github.com/dsedivec/beets-plugins .. _beets-artistcountry: https://github.com/agrausem/beets-artistcountry .. _beetFs: https://github.com/jbaiter/beetfs .. _Beet-MusicBrainz-Collection: https://github.com/jeffayle/Beet-MusicBrainz-Collection/ .. _A cmus plugin: https://github.com/coolkehon/beets/blob/master/beetsplug/cmus.py .. _cmus: http://cmus.sourceforge.net/ .. _beet-amazon: https://github.com/jmwatte/beet-amazon .. _beets-alternatives: https://github.com/geigerzaehler/beets-alternatives .. _beets-follow: https://github.com/nolsto/beets-follow .. _beets-ibroadcast: https://github.com/ctrueden/beets-ibroadcast .. _iBroadcast: https://ibroadcast.com/ .. _beets-importreplace: https://github.com/edgars-supe/beets-importreplace .. _beets-setlister: https://github.com/tomjaspers/beets-setlister .. _beets-noimport: https://gitlab.com/tiago.dias/beets-noimport .. _whatlastgenre: https://github.com/YetAnotherNerd/whatlastgenre/tree/master/plugin/beets .. _beets-usertag: https://github.com/igordertigor/beets-usertag .. _beets-plexsync: https://github.com/arsaboo/beets-plexsync .. _beets-jiosaavn: https://github.com/arsaboo/beets-jiosaavn .. _beets-youtube: https://github.com/arsaboo/beets-youtube .. _beets-ydl: https://github.com/vmassuchetto/beets-ydl .. _beets-ytimport: https://github.com/mgoltzsche/beets-ytimport .. _beet-summarize: https://github.com/steven-murray/beet-summarize .. _beets-mosaic: https://github.com/SusannaMaria/beets-mosaic .. _beets-goingrunning: https://pypi.org/project/beets-goingrunning .. _beets-xtractor: https://github.com/adamjakab/BeetsPluginXtractor .. _beets-yearfixer: https://github.com/adamjakab/BeetsPluginYearFixer .. _beets-autofix: https://github.com/adamjakab/BeetsPluginAutofix .. _beets-describe: https://github.com/adamjakab/BeetsPluginDescribe .. _beets-bpmanalyser: https://github.com/adamjakab/BeetsPluginBpmAnalyser .. _beets-originquery: https://github.com/x1ppy/beets-originquery .. _drop2beets: https://github.com/martinkirch/drop2beets .. _beets-audible: https://github.com/Neurrone/beets-audible .. _beets-more: https://forgejo.sny.sh/sun/beetsplug/src/branch/main/more .. _beets-mpd-utils: https://github.com/thekakkun/beets-mpd-utils .. _beets-webm3u: https://github.com/mgoltzsche/beets-webm3u .. _beets-webrouter: https://github.com/mgoltzsche/beets-webrouter .. _beets-autogenre: https://github.com/mgoltzsche/beets-autogenre ��������������������������������������������������������������������������������������������������beetbox-beets-01f1faf/docs/plugins/info.rst���������������������������������������������������������0000664�0000000�0000000�00000003176�14723254774�0021054�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������Info Plugin =========== The ``info`` plugin provides a command that dumps the current tag values for any file format supported by beets. It works like a supercharged version of `mp3info`_ or `id3v2`_. Enable the ``info`` plugin in your configuration (see :ref:`using-plugins`) and then type:: $ beet info /path/to/music.flac and the plugin will enumerate all the tags in the specified file. It also accepts multiple filenames in a single command-line. You can also enter a :doc:`query </reference/query>` to inspect music from your library:: $ beet info beatles If you just want to see specific properties you can use the ``--include-keys`` option to filter them. The argument is a comma-separated list of field names. For example:: $ beet info -i 'title,mb_artistid' beatles Will only show the ``title`` and ``mb_artistid`` properties. You can add the ``-i`` option multiple times to the command line. Additional command-line options include: * ``--library`` or ``-l``: Show data from the library database instead of the files' tags. * ``--album`` or ``-a``: Show data from albums instead of tracks (implies ``--library``). * ``--summarize`` or ``-s``: Merge all the information from multiple files into a single list of values. If the tags differ across the files, print ``[various]``. * ``--format`` or ``-f``: Specify a specific format with which to print every item. This uses the same template syntax as beets’ :doc:`path formats </reference/pathformat>`. * ``--keys-only`` or ``-k``: Show the name of the tags without the values. .. _id3v2: http://id3v2.sourceforge.net .. _mp3info: https://www.ibiblio.org/mp3info/ ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������beetbox-beets-01f1faf/docs/plugins/inline.rst�������������������������������������������������������0000664�0000000�0000000�00000004604�14723254774�0021374�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������Inline Plugin ============= The ``inline`` plugin lets you use Python to customize your path formats. Using it, you can define template fields in your beets configuration file and refer to them from your template strings in the ``paths:`` section (see :doc:`/reference/config/`). To use the ``inline`` plugin, enable it in your configuration (see :ref:`using-plugins`). Then, make a ``item_fields:`` block in your config file. Under this key, every line defines a new template field; the key is the name of the field (you'll use the name to refer to the field in your templates) and the value is a Python expression or function body. The Python code has all of a track's fields in scope, so you can refer to any normal attributes (such as ``artist`` or ``title``) as Python variables. Here are a couple of examples of expressions:: item_fields: initial: albumartist[0].upper() + u'.' disc_and_track: u'%02i.%02i' % (disc, track) if disctotal > 1 else u'%02i' % (track) Note that YAML syntax allows newlines in values if the subsequent lines are indented. These examples define ``$initial`` and ``$disc_and_track`` fields that can be referenced in path templates like so:: paths: default: $initial/$artist/$album%aunique{}/$disc_and_track $title Block Definitions ----------------- If you need to use statements like ``import``, you can write a Python function body instead of a single expression. In this case, you'll need to ``return`` a result for the value of the path field, like so:: item_fields: filename: | import os from beets.util import bytestring_path return bytestring_path(os.path.basename(path)) You might want to use the YAML syntax for "block literals," in which a leading ``|`` character indicates a multi-line block of text. Album Fields ------------ The above examples define fields for *item* templates, but you can also define fields for *album* templates. Use the ``album_fields`` configuration section. In this context, all existing album fields are available as variables along with ``items``, which is a list of items in the album. This example defines a ``$bitrate`` field for albums as the average of the tracks' fields:: album_fields: bitrate: | total = 0 for item in items: total += item.bitrate return total / len(items) ����������������������������������������������������������������������������������������������������������������������������beetbox-beets-01f1faf/docs/plugins/ipfs.rst���������������������������������������������������������0000664�0000000�0000000�00000005667�14723254774�0021071�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������IPFS Plugin =========== The ``ipfs`` plugin makes it easy to share your library and music with friends. The plugin uses `ipfs`_ for storing the library and file content. .. _ipfs: https://ipfs.io/ Installation ------------ This plugin requires `go-ipfs`_ to be running as a daemon and that the associated ``ipfs`` command is on the user's ``$PATH``. .. _go-ipfs: https://github.com/ipfs/go-ipfs Once you have the client installed, enable the ``ipfs`` plugin in your configuration (see :ref:`using-plugins`). Usage ----- This plugin can store and retrieve music individually, or it can share entire library databases. Adding '''''' To add albums to ipfs, making them shareable, use the ``-a`` or ``--add`` flag. If used without arguments it will add all albums in the local library. When added, all items and albums will get an "ipfs" field in the database containing the hash of that specific file/folder. Newly imported albums will be added automatically to ipfs by default (see below). Retrieving '''''''''' You can give the ipfs hash for some music to a friend. They can get that album from ipfs, and import it into beets, using the ``-g`` or ``--get`` flag. If the argument passed to the ``-g`` flag isn't an ipfs hash, it will be used as a query instead, getting all albums matching the query. Sharing Libraries ''''''''''''''''' Using the ``-p`` or ``--publish`` flag, a copy of the local library will be published to ipfs. Only albums/items with ipfs records in the database will published, and local paths will be stripped from the library. A hash of the library will be returned to the user. A friend can then import this remote library by using the ``-i`` or ``--import`` flag. To tag an imported library with a specific name by passing a name as the second argument to ``-i,`` after the hash. The content of all remote libraries will be combined into an additional library as long as the content doesn't already exist in the joined library. When remote libraries has been imported you can search them by using the ``-l`` or ``--list`` flag. The hash of albums matching the query will be returned, and this can then be used with ``-g`` to fetch and import the album to the local library. Ipfs can be mounted as a FUSE file system. This means that music in a remote library can be streamed directly, without importing them to the local library first. If the ``/ipfs`` folder is mounted then matching queries will be sent to the :doc:`/plugins/play` using the ``-m`` or ``--play`` flag. Configuration ------------- The ipfs plugin will automatically add imported albums to ipfs and add those hashes to the database. This can be turned off by setting the ``auto`` option in the ``ipfs:`` section of the config to ``no``. If the setting ``nocopy`` is true (defaults false) then the plugin will pass the ``--nocopy`` option when adding things to ipfs. If the filestore option of ipfs is enabled this will mean files are neither removed from beets nor copied somewhere else. �������������������������������������������������������������������������beetbox-beets-01f1faf/docs/plugins/keyfinder.rst����������������������������������������������������0000664�0000000�0000000�00000002500�14723254774�0022067�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������Key Finder Plugin ================= The `keyfinder` plugin uses either the `KeyFinder`_ or `keyfinder-cli`_ program to detect the musical key of a track from its audio data and store it in the `initial_key` field of your database. It does so automatically when importing music or through the ``beet keyfinder [QUERY]`` command. To use the ``keyfinder`` plugin, enable it in your configuration (see :ref:`using-plugins`). Configuration ------------- To configure the plugin, make a ``keyfinder:`` section in your configuration file. The available options are: - **auto**: Analyze every file on import. Otherwise, you need to use the ``beet keyfinder`` command explicitly. Default: ``yes`` - **bin**: The name of the program use for key analysis. You can use either `KeyFinder`_ or `keyfinder-cli`_. If you installed the KeyFinder GUI on a Mac, for example, you want something like ``/Applications/KeyFinder.app/Contents/MacOS/KeyFinder``. If using `keyfinder-cli`_, the binary must be named ``keyfinder-cli``. Default: ``KeyFinder`` (i.e., search for the program in your ``$PATH``).. - **overwrite**: Calculate a key even for files that already have an `initial_key` value. Default: ``no``. .. _KeyFinder: http://www.ibrahimshaath.co.uk/keyfinder/ .. _keyfinder-cli: https://github.com/EvanPurkhiser/keyfinder-cli/ ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������beetbox-beets-01f1faf/docs/plugins/kodiupdate.rst���������������������������������������������������0000664�0000000�0000000�00000003015�14723254774�0022242�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������KodiUpdate Plugin ================= The ``kodiupdate`` plugin lets you automatically update `Kodi`_'s music library whenever you change your beets library. To use ``kodiupdate`` plugin, enable it in your configuration (see :ref:`using-plugins`). Then, you'll want to configure the specifics of your Kodi host. You can do that using a ``kodi:`` section in your ``config.yaml``, which looks like this:: kodi: host: localhost port: 8080 user: kodi pwd: kodi To update multiple Kodi instances, specify them as an array:: kodi: - host: x.x.x.x port: 8080 user: kodi pwd: kodi - host: y.y.y.y port: 8081 user: kodi2 pwd: kodi2 To use the ``kodiupdate`` plugin, first enable it in your configuration (see :ref:`using-plugins`). Then, install ``beets`` with ``kodiupdate`` extra .. code-block:: bash pip install "beets[kodiupdate]" You'll also need to enable JSON-RPC in Kodi. In Kodi's interface, navigate to System/Settings/Network/Services and choose "Allow control of Kodi via HTTP." With that all in place, you'll see beets send the "update" command to your Kodi host every time you change your beets library. .. _Kodi: https://kodi.tv/ Configuration ------------- The available options under the ``kodi:`` section are: - **host**: The Kodi host name. Default: ``localhost`` - **port**: The Kodi host port. Default: 8080 - **user**: The Kodi host user. Default: ``kodi`` - **pwd**: The Kodi host password. Default: ``kodi`` �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������beetbox-beets-01f1faf/docs/plugins/lastgenre.rst����������������������������������������������������0000664�0000000�0000000�00000014362�14723254774�0022104�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������LastGenre Plugin ================ The ``lastgenre`` plugin fetches *tags* from `Last.fm`_ and assigns them as genres to your albums and items. .. _Last.fm: https://last.fm/ Installation ------------ To use the ``lastgenre`` plugin, first enable it in your configuration (see :ref:`using-plugins`). Then, install ``beets`` with ``lastgenre`` extra .. code-block:: bash pip install "beets[lastgenre]" Usage ----- The plugin chooses genres based on a *whitelist*, meaning that only certain tags can be considered genres. This way, tags like "my favorite music" or "seen live" won't be considered genres. The plugin ships with a fairly extensive `internal whitelist`_, but you can set your own in the config file using the ``whitelist`` configuration value or forgo a whitelist altogether by setting the option to `false`. The genre list file should contain one genre per line. Blank lines are ignored. For the curious, the default genre list is generated by a `script that scrapes Wikipedia`_. .. _script that scrapes Wikipedia: https://gist.github.com/1241307 .. _internal whitelist: https://raw.githubusercontent.com/beetbox/beets/master/beetsplug/lastgenre/genres.txt Canonicalization ^^^^^^^^^^^^^^^^ The plugin can also *canonicalize* genres, meaning that more obscure genres can be turned into coarser-grained ones that are present in the whitelist. This works using a `tree of nested genre names`_, represented using `YAML`_, where the leaves of the tree represent the most specific genres. The most common way to use this would be with a custom whitelist containing only a desired subset of genres. Consider for a example this minimal whitelist:: rock heavy metal pop together with the default genre tree. Then an item that has its genre specified as *viking metal* would actually be tagged as *heavy metal* because neither *viking metal* nor its parent *black metal* are in the whitelist. It always tries to use the most specific genre that's available in the whitelist. The relevant subtree path in the default tree looks like this:: - rock: - heavy metal: - black metal: - viking metal Considering that, it's not very useful to use the default whitelist (which contains about any genre contained in the tree) with canonicalization because nothing would ever be matched to a more generic node since all the specific subgenres are in the whitelist to begin with. .. _YAML: https://yaml.org/ .. _tree of nested genre names: https://raw.githubusercontent.com/beetbox/beets/master/beetsplug/lastgenre/genres-tree.yaml Genre Source ^^^^^^^^^^^^ When looking up genres for albums or individual tracks, you can choose whether to use Last.fm tags on the album, the artist, or the track. For example, you might want all the albums for a certain artist to carry the same genre. The default is "album". When set to "track", the plugin will fetch *both* album-level and track-level genres for your music when importing albums. Multiple Genres ^^^^^^^^^^^^^^^ By default, the plugin chooses the most popular tag on Last.fm as a genre. If you prefer to use a *list* of popular genre tags, you can increase the number of the ``count`` config option. Lists of up to *count* genres will then be used instead of single genres. The genres are separated by commas by default, but you can change this with the ``separator`` config option. `Last.fm`_ provides a popularity factor, a.k.a. *weight*, for each tag ranging from 100 for the most popular tag down to 0 for the least popular. The plugin uses this weight to discard unpopular tags. The default is to ignore tags with a weight less then 10. You can change this by setting the ``min_weight`` config option. Specific vs. Popular Genres ^^^^^^^^^^^^^^^^^^^^^^^^^^^ By default, the plugin sorts genres by popularity. However, you can use the ``prefer_specific`` option to override this behavior and instead sort genres by specificity, as determined by your whitelist and canonicalization tree. For instance, say you have both ``folk`` and ``americana`` in your whitelist and canonicalization tree and ``americana`` is a leaf within ``folk``. If Last.fm returns both of those tags, lastgenre is going to use the most popular, which is often the most generic (in this case ``folk``). By setting ``prefer_specific`` to true, lastgenre would use ``americana`` instead. Configuration ------------- To configure the plugin, make a ``lastgenre:`` section in your configuration file. The available options are: - **auto**: Fetch genres automatically during import. Default: ``yes``. - **canonical**: Use a canonicalization tree. Setting this to ``yes`` will use a built-in tree. You can also set it to a path, like the ``whitelist`` config value, to use your own tree. Default: ``no`` (disabled). - **count**: Number of genres to fetch. Default: 1 - **fallback**: A string if to use a fallback genre when no genre is found. You can use the empty string ``''`` to reset the genre. Default: None. - **force**: By default, beets will always fetch new genres, even if the files already have one. To instead leave genres in place in when they pass the whitelist, set the ``force`` option to ``no``. Default: ``yes``. - **min_weight**: Minimum popularity factor below which genres are discarded. Default: 10. - **prefer_specific**: Sort genres by the most to least specific, rather than most to least popular. Default: ``no``. - **source**: Which entity to look up in Last.fm. Can be either ``artist``, ``album`` or ``track``. Default: ``album``. - **separator**: A separator for multiple genres. Default: ``', '``. - **whitelist**: The filename of a custom genre list, ``yes`` to use the internal whitelist, or ``no`` to consider all genres valid. Default: ``yes``. - **title_case**: Convert the new tags to TitleCase before saving. Default: ``yes``. Running Manually ---------------- In addition to running automatically on import, the plugin can also be run manually from the command line. Use the command ``beet lastgenre [QUERY]`` to fetch genres for albums or items matching a certain query. By default, ``beet lastgenre`` matches albums. To match individual tracks or singletons, use the ``-A`` switch: ``beet lastgenre -A [QUERY]``. To disable automatic genre fetching on import, set the ``auto`` config option to false. ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������beetbox-beets-01f1faf/docs/plugins/lastimport.rst���������������������������������������������������0000664�0000000�0000000�00000003305�14723254774�0022311�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������LastImport Plugin ================= The ``lastimport`` plugin downloads play-count data from your `Last.fm`_ library into beets' database. You can later create :doc:`smart playlists </plugins/smartplaylist>` by querying ``play_count`` and do other fun stuff with this field. .. _Last.fm: https://last.fm Installation ------------ To use the ``lastimport`` plugin, first enable it in your configuration (see :ref:`using-plugins`). Then, install ``beets`` with ``lastimport`` extra .. code-block:: bash pip install "beets[lastimport]" Next, add your Last.fm username to your beets configuration file:: lastfm: user: beetsfanatic Importing Play Counts --------------------- Simply run ``beet lastimport`` and wait for the plugin to request tracks from Last.fm and match them to your beets library. (You will be notified of tracks in your Last.fm profile that do not match any songs in your library.) Then, your matched tracks will be populated with the ``play_count`` field, which you can use in any query or template. For example:: $ beet ls -f '$title: $play_count' play_count:5.. Eple (Melody A.M.): 60 To see more information (namely, the specific play counts for matched tracks), use the ``-v`` option. Configuration ------------- Aside from the required ``lastfm.user`` field, this plugin has some specific options under the ``lastimport:`` section: * **per_page**: The number of tracks to request from the API at once. Default: 500. * **retry_limit**: How many times should we re-send requests to Last.fm on failure? Default: 3. By default, the plugin will use beets's own Last.fm API key. You can also override it with your own API key:: lastfm: api_key: your_api_key ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������beetbox-beets-01f1faf/docs/plugins/limit.rst��������������������������������������������������������0000664�0000000�0000000�00000003150�14723254774�0021227�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������Limit Query Plugin ================== ``limit`` is a plugin to limit a query to the first or last set of results. We also provide a query prefix ``'<n'`` to inline the same behavior in the ``list`` command. They are analogous to piping results: $ beet [list|ls] [QUERY] | [head|tail] -n n There are two provided interfaces: 1. ``beet lslimit [--head n | --tail n] [QUERY]`` returns the head or tail of a query 2. ``beet [list|ls] [QUERY] '<n'`` returns the head of a query There are two differences in behavior: 1. The query prefix does not support tail. 2. The query prefix could appear anywhere in the query but will only have the same behavior as the ``lslimit`` command and piping to ``head`` when it appears last. Performance for the query previx is much worse due to the current singleton-based implementation. So why does the query prefix exist? Because it composes with any other query-based API or plugin (see :doc:`/reference/query`). For example, you can use the query prefix in ``smartplaylist`` (see :doc:`/plugins/smartplaylist`) to limit the number of tracks in a smart playlist for applications like most played and recently added. Configuration ------------- Enable the ``limit`` plugin in your configuration (see :ref:`using-plugins`). Examples -------- First 10 tracks $ beet ls | head -n 10 $ beet lslimit --head 10 $ beet ls '<10' Last 10 tracks $ beet ls | tail -n 10 $ beet lslimit --tail 10 100 mostly recently released tracks $ beet lslimit --head 100 year- month- day- $ beet ls year- month- day- '<100' $ beet lslimit --tail 100 year+ month+ day+ ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������beetbox-beets-01f1faf/docs/plugins/listenbrainz.rst�������������������������������������������������0000664�0000000�0000000�00000001351�14723254774�0022616�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������.. _listenbrainz: ListenBrainz Plugin =================== The ListenBrainz plugin for beets allows you to interact with the ListenBrainz service. Installation ------------ To enable the ListenBrainz plugin, add the following to your beets configuration file (`config.yaml`): .. code-block:: yaml plugins: - listenbrainz You can then configure the plugin by providing your Listenbrainz token (see intructions `here`_`)and username:: listenbrainz: token: TOKEN username: LISTENBRAINZ_USERNAME Usage ----- Once the plugin is enabled, you can import the listening history using the `lbimport` command in beets. .. _here: https://listenbrainz.readthedocs.io/en/latest/users/api/index.html#get-the-user-token���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������beetbox-beets-01f1faf/docs/plugins/loadext.rst������������������������������������������������������0000664�0000000�0000000�00000003117�14723254774�0021554�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������Load Extension Plugin ===================== Beets uses an SQLite database to store and query library information, which has support for extensions to extend its functionality. The ``loadext`` plugin lets you enable these SQLite extensions within beets. One of the primary uses of this within beets is with the `"ICU" extension`_, which adds support for case insensitive querying of non-ASCII characters. .. _"ICU" extension: https://www.sqlite.org/src/dir?ci=7461d2e120f21493&name=ext/icu Configuration ------------- To configure the plugin, make a ``loadext`` section in your configuration file. The section must consist of a list of paths to extensions to load, which looks like this: .. code-block:: yaml loadext: - libicu If a relative path is specified, it is resolved relative to the beets configuration directory. If no file extension is specified, the default dynamic library extension for the current platform will be used. Building the ICU extension -------------------------- This section is for **advanced** users only, and is not an in-depth guide on building the extension. To compile the ICU extension, you will need a few dependencies: - gcc - icu-devtools - libicu - libicu-dev - libsqlite3-dev Here's roughly how to download, build and install the extension (although the specifics may vary from system to system): .. code-block:: shell $ wget https://sqlite.org/2019/sqlite-src-3280000.zip $ unzip sqlite-src-3280000.zip $ cd sqlite-src-3280000/ext/icu $ gcc -shared -fPIC icu.c `icu-config --ldflags` -o libicu.so $ cp libicu.so ~/.config/beets �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������beetbox-beets-01f1faf/docs/plugins/lyrics.rst�������������������������������������������������������0000664�0000000�0000000�00000015030�14723254774�0021416�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������Lyrics Plugin ============= The ``lyrics`` plugin fetches and stores song lyrics from databases on the Web. Namely, the current version of the plugin uses `Genius.com`_, `Tekstowo.pl`_, `LRCLIB`_ and, optionally, the Google custom search API. .. _Genius.com: https://genius.com/ .. _Tekstowo.pl: https://www.tekstowo.pl/ .. _LRCLIB: https://lrclib.net/ Fetch Lyrics During Import -------------------------- To automatically fetch lyrics for songs you import, first enable it in your configuration (see :ref:`using-plugins`). Then, install ``beets`` with ``lyrics`` extra .. code-block:: bash pip install "beets[lyrics]" When importing new files, beets will now fetch lyrics for files that don't already have them. The lyrics will be stored in the beets database. If the ``import.write`` config option is on, then the lyrics will also be written to the files' tags. Configuration ------------- To configure the plugin, make a ``lyrics:`` section in your configuration file. The available options are: - **auto**: Fetch lyrics automatically during import. Default: ``yes``. - **bing_client_secret**: Your Bing Translation application password (to :ref:`lyrics-translation`) - **bing_lang_from**: By default all lyrics with a language other than ``bing_lang_to`` are translated. Use a list of lang codes to restrict the set of source languages to translate. Default: ``[]`` - **bing_lang_to**: Language to translate lyrics into. Default: None. - **fallback**: By default, the file will be left unchanged when no lyrics are found. Use the empty string ``''`` to reset the lyrics in such a case. Default: None. - **force**: By default, beets won't fetch lyrics if the files already have ones. To instead always fetch lyrics, set the ``force`` option to ``yes``. Default: ``no``. - **google_API_key**: Your Google API key (to enable the Google Custom Search backend). Default: None. - **google_engine_ID**: The custom search engine to use. Default: The `beets custom search engine`_, which gathers an updated list of sources known to be scrapeable. - **sources**: List of sources to search for lyrics. An asterisk ``*`` expands to all available sources. Default: ``google genius tekstowo lrclib``, i.e., all the available sources. The ``google`` source will be automatically deactivated if no ``google_API_key`` is setup. The ``google``, ``genius``, and ``tekstowo`` sources will only be enabled if BeautifulSoup is installed. - **synced**: Prefer synced lyrics over plain lyrics if a source offers them. Currently `lrclib` is the only source that provides them. Default: `no`. Here's an example of ``config.yaml``:: lyrics: fallback: '' google_API_key: AZERTYUIOPQSDFGHJKLMWXCVBN1234567890_ab google_engine_ID: 009217259823014548361:lndtuqkycfu .. _beets custom search engine: https://www.google.com:443/cse/publicurl?cx=009217259823014548361:lndtuqkycfu Fetching Lyrics Manually ------------------------ The ``lyrics`` command provided by this plugin fetches lyrics for items that match a query (see :doc:`/reference/query`). For example, ``beet lyrics magnetic fields absolutely cuckoo`` will get the lyrics for the appropriate Magnetic Fields song, ``beet lyrics magnetic fields`` will get lyrics for all my tracks by that band, and ``beet lyrics`` will get lyrics for my entire library. The lyrics will be added to the beets database and, if ``import.write`` is on, embedded into files' metadata. The ``-p`` option to the ``lyrics`` command makes it print lyrics out to the console so you can view the fetched (or previously-stored) lyrics. The ``-f`` option forces the command to fetch lyrics, even for tracks that already have lyrics. Inversely, the ``-l`` option restricts operations to lyrics that are locally available, which show lyrics faster without using the network at all. Rendering Lyrics into Other Formats ----------------------------------- The ``-r directory`` option renders all lyrics as `reStructuredText`_ (ReST) documents in ``directory`` (by default, the current directory). That directory, in turn, can be parsed by tools like `Sphinx`_ to generate HTML, ePUB, or PDF documents. A minimal ``conf.py`` and ``index.rst`` files are created the first time the command is run. They are not overwritten on subsequent runs, so you can safely modify these files to customize the output. .. _Sphinx: https://www.sphinx-doc.org/ .. _reStructuredText: http://docutils.sourceforge.net/rst.html Sphinx supports various `builders <https://www.sphinx-doc.org/en/stable/builders.html>`_, but here are a few suggestions. * Build an HTML version:: sphinx-build -b html . _build/html * Build an ePUB3 formatted file, usable on ebook readers:: sphinx-build -b epub3 . _build/epub * Build a PDF file, which incidentally also builds a LaTeX file:: sphinx-build -b latex %s _build/latex && make -C _build/latex all-pdf .. _activate-google-custom-search: Activate Google Custom Search ------------------------------ You need to `register for a Google API key`_. Set the ``google_API_key`` configuration option to your key. Then add ``google`` to the list of sources in your configuration (or use default list, which includes it as long as you have an API key). If you use default ``google_engine_ID``, we recommend limiting the sources to ``google`` as the other sources are already included in the Google results. .. _register for a Google API key: https://console.developers.google.com/ Optionally, you can `define a custom search engine`_. Get your search engine's token and use it for your ``google_engine_ID`` configuration option. By default, beets use a list of sources known to be scrapeable. .. _define a custom search engine: https://www.google.com/cse/all Note that the Google custom search API is limited to 100 queries per day. After that, the lyrics plugin will fall back on other declared data sources. .. _BeautifulSoup: https://www.crummy.com/software/BeautifulSoup/bs4/doc/ Activate Genius and Tekstowo.pl Lyrics -------------------------------------- These backends are enabled by default. .. _lyrics-translation: Activate On-the-Fly Translation ------------------------------- You need to register for a Microsoft Azure Marketplace free account and to the `Microsoft Translator API`_. Follow the four steps process, specifically at step 3 enter ``beets`` as *Client ID* and copy/paste the generated *Client secret* into your ``bing_client_secret`` configuration, alongside ``bing_lang_to`` target `language code`. .. _Microsoft Translator API: https://docs.microsoft.com/en-us/azure/cognitive-services/translator/translator-how-to-signup ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������beetbox-beets-01f1faf/docs/plugins/mbcollection.rst�������������������������������������������������0000664�0000000�0000000�00000002637�14723254774�0022574�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������MusicBrainz Collection Plugin ============================= The ``mbcollection`` plugin lets you submit your catalog to MusicBrainz to maintain your `music collection`_ list there. .. _music collection: https://musicbrainz.org/doc/Collections To begin, just enable the ``mbcollection`` plugin in your configuration (see :ref:`using-plugins`). Then, add your MusicBrainz username and password to your :doc:`configuration file </reference/config>` under a ``musicbrainz`` section:: musicbrainz: user: you pass: seekrit Then, use the ``beet mbupdate`` command to send your albums to MusicBrainz. The command automatically adds all of your albums to the first collection it finds. If you don't have a MusicBrainz collection yet, you may need to add one to your profile first. The command has one command-line option: * To remove albums from the collection which are no longer present in the beets database, use the ``-r`` (``--remove``) flag. Configuration ------------- To configure the plugin, make a ``mbcollection:`` section in your configuration file. There is one option available: - **auto**: Automatically amend your MusicBrainz collection whenever you import a new album. Default: ``no``. - **collection**: The MBID of which MusicBrainz collection to update. Default: ``None``. - **remove**: Remove albums from collections which are no longer present in the beets database. Default: ``no``. �������������������������������������������������������������������������������������������������beetbox-beets-01f1faf/docs/plugins/mbsubmit.rst�����������������������������������������������������0000664�0000000�0000000�00000007700�14723254774�0021740�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������MusicBrainz Submit Plugin ========================= The ``mbsubmit`` plugin provides extra prompt choices when an import session fails to find a good enough match for a release. Additionally, it provides an ``mbsubmit`` command that prints the tracks of the current album in a format that is parseable by MusicBrainz's `track parser`_. The prompt choices are: - Print the tracks to stdout in a format suitable for MusicBrainz's `track parser`_. - Open the program `Picard`_ with the unmatched folder as an input, allowing you to start submitting the unmatched release to MusicBrainz with many input fields already filled in, thanks to Picard reading the preexisting tags of the files. For the last option, `Picard`_ is assumed to be installed and available on the machine including a ``picard`` executable. Picard developers list `download options`_. `other GNU/Linux distributions`_ may distribute Picard via their package manager as well. .. _track parser: https://wiki.musicbrainz.org/History:How_To_Parse_Track_Listings .. _Picard: https://picard.musicbrainz.org/ .. _download options: https://picard.musicbrainz.org/downloads/ .. _other GNU/Linux distributions: https://repology.org/project/picard-tagger/versions Usage ----- Enable the ``mbsubmit`` plugin in your configuration (see :ref:`using-plugins`) and select one of the options mentioned above. Here the option ``Print tracks`` choice is demonstrated:: No matching release found for 3 tracks. For help, see: https://beets.readthedocs.org/en/latest/faq.html#nomatch [U]se as-is, as Tracks, Group albums, Skip, Enter search, enter Id, aBort, Print tracks, Open files with Picard? p 01. An Obscure Track - An Obscure Artist (3:37) 02. Another Obscure Track - An Obscure Artist (2:05) 03. The Third Track - Another Obscure Artist (3:02) No matching release found for 3 tracks. For help, see: https://beets.readthedocs.org/en/latest/faq.html#nomatch [U]se as-is, as Tracks, Group albums, Skip, Enter search, enter Id, aBort, Print tracks? You can also run ``beet mbsubmit QUERY`` to print the track information for any album:: $ beet mbsubmit album:"An Obscure Album" 01. An Obscure Track - An Obscure Artist (3:37) 02. Another Obscure Track - An Obscure Artist (2:05) 03. The Third Track - Another Obscure Artist (3:02) As MusicBrainz currently does not support submitting albums programmatically, the recommended workflow is to copy the output of the ``Print tracks`` choice and paste it into the parser that can be found by clicking on the "Track Parser" button on MusicBrainz "Tracklist" tab. Configuration ------------- To configure the plugin, make a ``mbsubmit:`` section in your configuration file. The following options are available: - **format**: The format used for printing the tracks, defined using the same template syntax as beets’ :doc:`path formats </reference/pathformat>`. Default: ``$track. $title - $artist ($length)``. - **threshold**: The minimum strength of the autotagger recommendation that will cause the ``Print tracks`` choice to be displayed on the prompt. Default: ``medium`` (causing the choice to be displayed for all albums that have a recommendation of medium strength or lower). Valid values: ``none``, ``low``, ``medium``, ``strong``. - **picard_path**: The path to the ``picard`` executable. Could be an absolute path, and if not, ``$PATH`` is consulted. The default value is simply ``picard``. Windows users will have to find and specify the absolute path to their ``picard.exe``. That would probably be: ``C:\Program Files\MusicBrainz Picard\picard.exe``. Please note that some values of the ``threshold`` configuration option might require other ``beets`` command line switches to be enabled in order to work as intended. In particular, setting a threshold of ``strong`` will only display the prompt if ``timid`` mode is enabled. You can find more information about how the recommendation system works at :ref:`match-config`. ����������������������������������������������������������������beetbox-beets-01f1faf/docs/plugins/mbsync.rst�������������������������������������������������������0000664�0000000�0000000�00000003421�14723254774�0021405�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������MBSync Plugin ============= This plugin provides the ``mbsync`` command, which lets you fetch metadata from MusicBrainz for albums and tracks that already have MusicBrainz IDs. This is useful for updating tags as they are fixed in the MusicBrainz database, or when you change your mind about some config options that change how tags are written to files. If you have a music library that is already nicely tagged by a program that also uses MusicBrainz like Picard, this can speed up the initial import if you just import "as-is" and then use ``mbsync`` to get up-to-date tags that are written to the files according to your beets configuration. Usage ----- Enable the ``mbsync`` plugin in your configuration (see :ref:`using-plugins`) and then run ``beet mbsync QUERY`` to fetch updated metadata for a part of your collection (or omit the query to run over your whole library). This plugin treats albums and singletons (non-album tracks) separately. It first processes all matching singletons and then proceeds on to full albums. The same query is used to search for both kinds of entities. The command has a few command-line options: * To preview the changes that would be made without applying them, use the ``-p`` (``--pretend``) flag. * By default, files will be moved (renamed) according to their metadata if they are inside your beets library directory. To disable this, use the ``-M`` (``--nomove``) command-line option. * If you have the ``import.write`` configuration option enabled, then this plugin will write new metadata to files' tags. To disable this, use the ``-W`` (``--nowrite``) option. * To customize the output of unrecognized items, use the ``-f`` (``--format``) option. The default output is ``format_item`` or ``format_album`` for items and albums, respectively. �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������beetbox-beets-01f1faf/docs/plugins/metasync.rst�����������������������������������������������������0000664�0000000�0000000�00000003435�14723254774�0021742�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������MetaSync Plugin =============== This plugin provides the ``metasync`` command, which lets you fetch certain metadata from other sources: for example, your favorite audio player. Currently, the plugin supports synchronizing with the `Amarok`_ music player, and with `iTunes`_. It can fetch the rating, score, first-played date, last-played date, play count, and track uid from Amarok. .. _Amarok: https://amarok.kde.org/ .. _iTunes: https://www.apple.com/itunes/ Installation ------------ Enable the ``metasync`` plugin in your configuration (see :ref:`using-plugins`). To synchronize with Amarok, you'll need the `dbus-python`_ library. In such case, install ``beets`` with ``metasync`` extra .. code-block:: bash pip install "beets[metasync]" .. _dbus-python: https://dbus.freedesktop.org/releases/dbus-python/ Configuration ------------- To configure the plugin, make a ``metasync:`` section in your configuration file. The available options are: - **source**: A list of comma-separated sources to fetch metadata from. Set this to "amarok" or "itunes" to enable synchronization with that player. Default: empty The follow subsections describe additional configure required for some players. itunes '''''' The path to your iTunes library **xml** file has to be configured, e.g.:: metasync: source: itunes itunes: library: ~/Music/iTunes Library.xml Please note the indentation. Usage ----- Run ``beet metasync QUERY`` to fetch metadata from the configured list of sources. The command has a few command-line options: * To preview the changes that would be made without applying them, use the ``-p`` (``--pretend``) flag. * To specify temporary sources to fetch metadata from, use the ``-s`` (``--source``) flag with a comma-separated list of a sources. �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������beetbox-beets-01f1faf/docs/plugins/missing.rst������������������������������������������������������0000664�0000000�0000000�00000005456�14723254774�0021575�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������Missing Plugin ============== This plugin adds a new command, ``missing`` or ``miss``, which finds and lists, for every album in your collection, which or how many tracks are missing. Listing missing files requires one network call to MusicBrainz. Merely counting missing files avoids any network calls. Usage ----- Add the ``missing`` plugin to your configuration (see :ref:`using-plugins`). By default, the ``beet missing`` command lists the names of tracks that your library is missing from each album. It can also list the names of albums that your library is missing from each artist. You can customize the output format, count the number of missing tracks per album, or total up the number of missing tracks over your whole library, using command-line switches:: -f FORMAT, --format=FORMAT print with custom FORMAT -c, --count count missing tracks per album -t, --total count total of missing tracks or albums -a, --album show missing albums for artist instead of tracks …or by editing corresponding options. Note that ``-c`` is ignored when used with ``-a``. Configuration ------------- To configure the plugin, make a ``missing:`` section in your configuration file. The available options are: - **count**: Print a count of missing tracks per album, with ``format`` defaulting to ``$albumartist - $album: $missing``. Default: ``no``. - **format**: A specific format with which to print every track. This uses the same template syntax as beets' :doc:`path formats </reference/pathformat>`. The usage is inspired by, and therefore similar to, the :ref:`list <list-cmd>` command. Default: :ref:`format_item`. - **total**: Print a single count of missing tracks in all albums. Default: ``no``. Here's an example :: missing: format: $albumartist - $album - $title count: no total: no Template Fields --------------- With this plugin enabled, the ``$missing`` template field expands to the number of tracks missing from each album. Examples -------- List all missing tracks in your collection:: beet missing List all missing albums in your collection:: beet missing -a List all missing tracks from 2008:: beet missing year:2008 Print out a unicode histogram of the missing track years using `spark`_:: beet missing -f '$year' | spark â–†â–â–†â–▄▇▇▄▇▇â–â–▇▆▇▂▄â–â–â–â–â–‚â–â–â–â–â–â–â–▂▇▆▂▇â–▇▇â–▆▆▇â–â–‡â–▇▆â–â–â–‚â–‡ Print out a listing of all albums with missing tracks, and respective counts:: beet missing -c Print out a count of the total number of missing tracks:: beet missing -t Call this plugin from other beet commands:: beet ls -a -f '$albumartist - $album: $missing' .. _spark: https://github.com/holman/spark ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������beetbox-beets-01f1faf/docs/plugins/mpdstats.rst�����������������������������������������������������0000664�0000000�0000000�00000007270�14723254774�0021757�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������MPDStats Plugin ================ ``mpdstats`` is a plugin for beets that collects statistics about your listening habits from `MPD`_. It collects the following information about tracks: * ``play_count``: The number of times you *fully* listened to this track. * ``skip_count``: The number of times you *skipped* this track. * ``last_played``: UNIX timestamp when you last played this track. * ``rating``: A rating based on ``play_count`` and ``skip_count``. To gather these statistics it runs as an MPD client and watches the current state of MPD. This means that ``mpdstats`` needs to be running continuously for it to work. .. _MPD: https://www.musicpd.org/ Installing Dependencies ----------------------- This plugin requires the python-mpd2 library in order to talk to the MPD server. To use the ``mpdstats`` plugin, first enable it in your configuration (see :ref:`using-plugins`). Then, install ``beets`` with ``mpdstats`` extra pip install "beets[mpdstats]" Usage ----- Use the ``mpdstats`` command to fire it up:: $ beet mpdstats Configuration ------------- To configure the plugin, make an ``mpd:`` section in your configuration file. The available options are: - **host**: The MPD server hostname. Default: The ``$MPD_HOST`` environment variable if set, falling back to ``localhost`` otherwise. - **port**: The MPD server port. Default: The ``$MPD_PORT`` environment variable if set, falling back to 6600 otherwise. - **password**: The MPD server password. Default: None. - **music_directory**: If your MPD library is at a different location from the beets library (e.g., because one is mounted on a NFS share), specify the path here. - **strip_path**: If your MPD library contains local path, specify the part to remove here. Combining this with **music_directory** you can mangle MPD path to match the beets library one. Default: The beets library directory. - **rating**: Enable rating updates. Default: ``yes``. - **rating_mix**: Tune the way rating is calculated (see below). Default: 0.75. A Word on Ratings ----------------- Ratings are calculated based on the *play_count*, *skip_count* and the last *action* (play or skip). It consists in one part of a *stable_rating* and in another part on a *rolling_rating*. The *stable_rating* is calculated like this:: stable_rating = (play_count + 1.0) / (play_count + skip_count + 2.0) So if the *play_count* equals the *skip_count*, the *stable_rating* is always 0.5. More *play_counts* adjust the rating up to 1.0. More *skip_counts* adjust it down to 0.0. One of the disadvantages of this rating system, is that it doesn't really cover *recent developments*. e.g. a song that you loved last year and played over 50 times will keep a high rating even if you skipped it the last 10 times. That's were the *rolling_rating* comes in. If a song has been fully played, the *rolling_rating* is calculated like this:: rolling_rating = old_rating + (1.0 - old_rating) / 2.0 If a song has been skipped, like this:: rolling_rating = old_rating - old_rating / 2.0 So *rolling_rating* adapts pretty fast to *recent developments*. But it's too fast. Taking the example from above, your old favorite with 50 plays will get a negative rating (<0.5) the first time you skip it. Also not good. To take the best of both worlds, we mix the ratings together with the ``rating_mix`` factor. A ``rating_mix`` of 0.0 means all *rolling* and 1.0 means all *stable*. We found 0.75 to be a good compromise, but fell free to play with that. Warning ------- This has only been tested with MPD versions >= 0.16. It may not work on older versions. If that is the case, please report an `issue`_. .. _issue: https://github.com/beetbox/beets/issues ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������beetbox-beets-01f1faf/docs/plugins/mpdupdate.rst����������������������������������������������������0000664�0000000�0000000�00000002446�14723254774�0022103�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������MPDUpdate Plugin ================ ``mpdupdate`` is a very simple plugin for beets that lets you automatically update `MPD`_'s index whenever you change your beets library. .. _MPD: https://www.musicpd.org/ To use ``mpdupdate`` plugin, enable it in your configuration (see :ref:`using-plugins`). Then, you'll probably want to configure the specifics of your MPD server. You can do that using an ``mpd:`` section in your ``config.yaml``, which looks like this:: mpd: host: localhost port: 6600 password: seekrit With that all in place, you'll see beets send the "update" command to your MPD server every time you change your beets library. If you want to communicate with MPD over a Unix domain socket instead over TCP, just give the path to the socket in the filesystem for the ``host`` setting. (Any ``host`` value starting with a slash or a tilde is interpreted as a domain socket.) Configuration ------------- The available options under the ``mpd:`` section are: - **host**: The MPD server name. Default: The ``$MPD_HOST`` environment variable if set, falling back to ``localhost`` otherwise. - **port**: The MPD server port. Default: The ``$MPD_PORT`` environment variable if set, falling back to 6600 otherwise. - **password**: The MPD server password. Default: None. ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������beetbox-beets-01f1faf/docs/plugins/parentwork.rst���������������������������������������������������0000664�0000000�0000000�00000005410�14723254774�0022306�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������ParentWork Plugin ================= The ``parentwork`` plugin fetches the work title, parent work title and parent work composer from MusicBrainz. In the MusicBrainz database, a recording can be associated with a work. A work can itself be associated with another work, for example one being part of the other (what we call the *direct parent*). This plugin looks the work id from the library and then looks up the direct parent, then the direct parent of the direct parent and so on until it reaches the top. The work at the top is what we call the *parent work*. This plugin is especially designed for classical music. For classical music, just fetching the work title as in MusicBrainz is not satisfying, because MusicBrainz has separate works for, for example, all the movements of a symphony. This plugin aims to solve this problem by also fetching the parent work, which would be the whole symphony in this example. The plugin can detect changes in ``mb_workid`` so it knows when to re-fetch other metadata, such as ``parentwork``. To do this, when it runs, it stores a copy of ``mb_workid`` in the bookkeeping field ``parentwork_workid_current``. At any later run of ``beet parentwork`` it will check if the tags ``mb_workid`` and ``parentwork_workid_current`` are still identical. If it is not the case, it means the work has changed and all the tags need to be fetched again. This plugin adds seven tags: - **parentwork**: The title of the parent work. - **mb_parentworkid**: The MusicBrainz id of the parent work. - **parentwork_disambig**: The disambiguation of the parent work title. - **parent_composer**: The composer of the parent work. - **parent_composer_sort**: The sort name of the parent work composer. - **work_date**: The composition date of the work, or the first parent work that has a composition date. Format: yyyy-mm-dd. - **parentwork_workid_current**: The MusicBrainz id of the work as it was when the parentwork was retrieved. This tag exists only for internal bookkeeping, to keep track of recordings whose works have changed. - **parentwork_date**: The composition date of the parent work. To use the ``parentwork`` plugin, enable it in your configuration (see :ref:`using-plugins`). Configuration ------------- To configure the plugin, make a ``parentwork:`` section in your configuration file. The available options are: - **force**: As a default, ``parentwork`` only fetches work info for recordings that do not already have a ``parentwork`` tag or where ``mb_workid`` differs from ``parentwork_workid_current``. If ``force`` is enabled, it fetches it for all recordings. Default: ``no`` - **auto**: If enabled, automatically fetches works at import. It takes quite some time, because beets is restricted to one MusicBrainz query per second. Default: ``no`` ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������beetbox-beets-01f1faf/docs/plugins/permissions.rst��������������������������������������������������0000664�0000000�0000000�00000001205�14723254774�0022463�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������Permissions Plugin ================== The ``permissions`` plugin allows you to set file permissions for imported music files and its directories. To use the ``permissions`` plugin, enable it in your configuration (see :ref:`using-plugins`). Permissions will be adjusted automatically on import. Configuration ------------- To configure the plugin, make an ``permissions:`` section in your configuration file. The ``file`` config value therein uses **octal modes** to specify the desired permissions. The default flags for files are octal 644 and 755 for directories. Here's an example:: permissions: file: 644 dir: 755 �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������beetbox-beets-01f1faf/docs/plugins/play.rst���������������������������������������������������������0000664�0000000�0000000�00000010667�14723254774�0021071�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������Play Plugin =========== The ``play`` plugin allows you to pass the results of a query to a music player in the form of an m3u playlist or paths on the command line. Command Line Usage ------------------ To use the ``play`` plugin, enable it in your configuration (see :ref:`using-plugins`). Then use it by invoking the ``beet play`` command with a query. The command will create a temporary m3u file and open it using an appropriate application. You can query albums instead of tracks using the ``-a`` option. By default, the playlist is opened using the ``open`` command on OS X, ``xdg-open`` on other Unixes, and ``start`` on Windows. To configure the command, you can use a ``play:`` section in your configuration file:: play: command: /Applications/VLC.app/Contents/MacOS/VLC You can also specify additional space-separated options to command (like you would on the command-line):: play: command: /usr/bin/command --option1 --option2 some_other_option While playing you'll be able to interact with the player if it is a command-line oriented, and you'll get its output in real time. Interactive Usage ----------------- The ``play`` plugin can also be invoked during an import. If enabled, the plugin adds a ``plaY`` option to the prompt, so pressing ``y`` will execute the configured command and play the items currently being imported. Once the configured command exits, you will be returned to the import decision prompt. If your player is configured to run in the background (in a client/server setup), the music will play until you choose to stop it, and the import operation continues immediately. Configuration ------------- To configure the plugin, make a ``play:`` section in your configuration file. The available options are: - **command**: The command used to open the playlist. Default: ``open`` on OS X, ``xdg-open`` on other Unixes and ``start`` on Windows. Insert ``$args`` to use the ``--args`` feature. - **relative_to**: If set, emit paths relative to this directory. Default: None. - **use_folders**: When using the ``-a`` option, the m3u will contain the paths to each track on the matched albums. Enable this option to store paths to folders instead. Default: ``no``. - **raw**: Instead of creating a temporary m3u playlist and then opening it, simply call the command with the paths returned by the query as arguments. Default: ``no``. - **warning_threshold**: Set the minimum number of files to play which will trigger a warning to be emitted. If set to ``no``, warning are never issued. Default: 100. - **bom**: Set whether or not a UTF-8 Byte Order Mark should be emitted into the m3u file. If you're using foobar2000 or Winamp, this is needed. Default: ``no``. Optional Arguments ------------------ The ``--args`` (or ``-A``) flag to the ``play`` command lets you specify additional arguments for your player command. Options are inserted after the configured ``command`` string and before the playlist filename. For example, if you have the plugin configured like this:: play: command: mplayer -quiet and you occasionally want to shuffle the songs you play, you can type:: $ beet play --args -shuffle to get beets to execute this command:: mplayer -quiet -shuffle /path/to/playlist.m3u instead of the default. If you need to insert arguments somewhere other than the end of the ``command`` string, use ``$args`` to indicate where to insert them. For example:: play: command: mpv $args --playlist indicates that you need to insert extra arguments before specifying the playlist. The ``--yes`` (or ``-y``) flag to the ``play`` command will skip the warning message if you choose to play more items than the **warning_threshold** value usually allows. Note on the Leakage of the Generated Playlists ---------------------------------------------- Because the command that will open the generated ``.m3u`` files can be arbitrarily configured by the user, beets won't try to delete those files. For this reason, using this plugin will leave one or several playlist(s) in the directory selected to create temporary files (Most likely ``/tmp/`` on Unix-like systems. See `tempfile.tempdir`_ in the Python docs.). Leaking those playlists until they are externally wiped could be an issue for privacy or storage reasons. If this is the case for you, you might want to use the ``raw`` config option described above. .. _tempfile.tempdir: https://docs.python.org/2/library/tempfile.html#tempfile.tempdir �������������������������������������������������������������������������beetbox-beets-01f1faf/docs/plugins/playlist.rst�����������������������������������������������������0000664�0000000�0000000�00000004520�14723254774�0021754�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������Playlist Plugin =============== ``playlist`` is a plugin to use playlists in m3u format. To use it, enable the ``playlist`` plugin in your configuration (see :ref:`using-plugins`). Then configure your playlists like this:: playlist: auto: no relative_to: ~/Music playlist_dir: ~/.mpd/playlists forward_slash: no It is possible to query the library based on a playlist by specifying its absolute path:: $ beet ls playlist:/path/to/someplaylist.m3u The plugin also supports referencing playlists by name. The playlist is then searched in the playlist_dir and the ".m3u" extension is appended to the name:: $ beet ls playlist:anotherplaylist A playlist query will use the paths found in the playlist file to match items in the beets library. ``playlist:`` submits a regular beets :ref:`query<queries>` similar to a :ref:`specific fields query<fieldsquery>`. If you want the list in any particular order, you can use the standard beets query syntax for :ref:`sorting<query-sort>`:: $ beet ls playlist:/path/to/someplaylist.m3u artist+ year+ Playlist queries do not reflect the original order in the m3u file. The plugin can also update playlists in the playlist directory automatically every time an item is moved or deleted. This can be controlled by the ``auto`` configuration option. Configuration ------------- To configure the plugin, make a ``playlist:`` section in your configuration file. In addition to the ``playlists`` described above, the other configuration options are: - **auto**: If this is set to ``yes``, then anytime an item in the library is moved or removed, the plugin will update all playlists in the ``playlist_dir`` directory that contain that item to reflect the change. Default: ``no`` - **playlist_dir**: Where to read playlist files from. Default: The current working directory (i.e., ``'.'``). - **relative_to**: Interpret paths in the playlist files relative to a base directory. Instead of setting it to a fixed path, it is also possible to set it to ``playlist`` to use the playlist's parent directory or to ``library`` to use the library directory. Default: ``library`` - **forward_slash**: Forces forward slashes in the generated playlist files. If you intend to use this plugin to generate playlists for MPD on Windows, set this to yes. Default: Use system separator. ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������beetbox-beets-01f1faf/docs/plugins/plexupdate.rst���������������������������������������������������0000664�0000000�0000000�00000002775�14723254774�0022300�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������PlexUpdate Plugin ================= ``plexupdate`` is a very simple plugin for beets that lets you automatically update `Plex`_'s music library whenever you change your beets library. Firstly, install ``beets`` with ``plexupdate`` extra .. code-block:: console pip install "beets[plexupdate]" Then, enable ``plexupdate`` plugin it in your configuration (see :ref:`using-plugins`). Optionally, configure the specifics of your Plex server. You can do this using a ``plex:`` section in your ``config.yaml``: .. code-block:: yaml plex: host: "localhost" port: 32400 token: "TOKEN" The ``token`` key is optional: you'll need to use it when in a Plex Home (see Plex's own `documentation about tokens`_). With that all in place, you'll see beets send the "update" command to your Plex server every time you change your beets library. .. _Plex: https://plex.tv/ .. _documentation about tokens: https://support.plex.tv/hc/en-us/articles/204059436-Finding-your-account-token-X-Plex-Token Configuration ------------- The available options under the ``plex:`` section are: - **host**: The Plex server name. Default: ``localhost``. - **port**: The Plex server port. Default: 32400. - **token**: The Plex Home token. Default: Empty. - **library_name**: The name of the Plex library to update. Default: ``Music`` - **secure**: Use secure connections to the Plex server. Default: ``False`` - **ignore_cert_errors**: Ignore TLS certificate errors when using secure connections. Default: ``False`` ���beetbox-beets-01f1faf/docs/plugins/random.rst�������������������������������������������������������0000664�0000000�0000000�00000002315�14723254774�0021373�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������Random Plugin ============= The ``random`` plugin provides a command that randomly selects tracks or albums from your library. This can be helpful if you need some help deciding what to listen to. First, enable the plugin named ``random`` (see :ref:`using-plugins`). You'll then be able to use the ``beet random`` command:: $ beet random Aesop Rock - None Shall Pass - The Harbor Is Yours The command has several options that resemble those for the ``beet list`` command (see :doc:`/reference/cli`). To choose an album instead of a single track, use ``-a``; to print paths to items instead of metadata, use ``-p``; and to use a custom format for printing, use ``-f FORMAT``. If the ``-e`` option is passed, the random choice will be even among artists (the albumartist field). This makes sure that your anthology of Bob Dylan won't make you listen to Bob Dylan 50% of the time. The ``-n NUMBER`` option controls the number of objects that are selected and printed (default 1). To select 5 tracks from your library, type ``beet random -n5``. As an alternative, you can use ``-t MINUTES`` to choose a set of music with a given play time. To select tracks that total one hour, for example, type ``beet random -t60``. �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������beetbox-beets-01f1faf/docs/plugins/replaygain.rst���������������������������������������������������0000664�0000000�0000000�00000015372�14723254774�0022255�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������ReplayGain Plugin ================= This plugin adds support for `ReplayGain`_, a technique for normalizing audio playback levels. .. _ReplayGain: https://wiki.hydrogenaudio.org/index.php?title=ReplayGain Installation ------------ This plugin can use one of many backends to compute the ReplayGain values: GStreamer, mp3gain (and its cousin, aacgain), Python Audio Tools or ffmpeg. ffmpeg and mp3gain can be easier to install. mp3gain supports less audio formats than the other backend. Once installed, this plugin analyzes all files during the import process. This can be a slow process; to instead analyze after the fact, disable automatic analysis and use the ``beet replaygain`` command (see below). To speed up analysis with some of the available backends, this plugin processes tracks or albums (when using the ``-a`` option) in parallel. By default, a single thread is used per logical core of your CPU. GStreamer ````````` To use `GStreamer`_ for ReplayGain analysis, you will of course need to install GStreamer and plugins for compatibility with your audio files. You will need at least GStreamer 1.0 and `PyGObject 3.x`_ (a.k.a. ``python-gi``). .. _PyGObject 3.x: https://pygobject.readthedocs.io/en/latest/ .. _GStreamer: https://gstreamer.freedesktop.org/ Then, install ``beets`` with ``replaygain`` extra which installs ``GStreamer`` bindings for Python .. code-block:: bash pip install "beets[replaygain]" Lastly, enable the ``replaygain`` plugin in your configuration (see :ref:`using-plugins`) and specify the GStreamer backend by adding this to your configuration file:: replaygain: backend: gstreamer The GStreamer backend does not support parallel analysis. mp3gain and aacgain ``````````````````` In order to use this backend, you will need to install the `mp3gain`_ command-line tool or the `aacgain`_ fork thereof. Here are some hints: * On Mac OS X, you can use `Homebrew`_. Type ``brew install aacgain``. * On Linux, `mp3gain`_ is probably in your repositories. On Debian or Ubuntu, for example, you can run ``apt-get install mp3gain``. * On Windows, download and install the original `mp3gain`_. .. _mp3gain: http://mp3gain.sourceforge.net/download.php .. _aacgain: https://aacgain.altosdesign.com .. _Homebrew: https://brew.sh Then, enable the plugin (see :ref:`using-plugins`) and specify the "command" backend in your configuration file:: replaygain: backend: command If beets doesn't automatically find the ``mp3gain`` or ``aacgain`` executable, you can configure the path explicitly like so:: replaygain: command: /Applications/MacMP3Gain.app/Contents/Resources/aacgain Python Audio Tools `````````````````` This backend uses the `Python Audio Tools`_ package to compute ReplayGain for a range of different file formats. The package is not available via PyPI; it must be installed manually (only versions preceding 3.x are compatible). On OS X, most of the dependencies can be installed with `Homebrew`_:: brew install mpg123 mp3gain vorbisgain faad2 libvorbis The Python Audio Tools backend does not support parallel analysis. .. _Python Audio Tools: http://audiotools.sourceforge.net ffmpeg `````` This backend uses ffmpeg to calculate EBU R128 gain values. To use it, install the `ffmpeg`_ command-line tool and select the ``ffmpeg`` backend in your config file. .. _ffmpeg: https://ffmpeg.org Configuration ------------- To configure the plugin, make a ``replaygain:`` section in your configuration file. The available options are: - **auto**: Enable ReplayGain analysis during import. Default: ``yes``. - **threads**: The number of parallel threads to run the analysis in. Overridden by ``--threads`` at the command line. Default: # of logical CPU cores - **parallel_on_import**: Whether to enable parallel analysis during import. As of now this ReplayGain data is not written to files properly, so this option is disabled by default. If you wish to enable it, remember to run ``beet write`` after importing to actually write to the imported files. Default: ``no`` - **backend**: The analysis backend; either ``gstreamer``, ``command``, ``audiotools`` or ``ffmpeg``. Default: ``command``. - **overwrite**: On import, re-analyze files that already have ReplayGain tags. Note that, for historical reasons, the name of this option is somewhat unfortunate: It does not decide whether tags are written to the files (which is controlled by the :ref:`import.write <config-import-write>` option). Default: ``no``. - **targetlevel**: A number of decibels for the target loudness level for files using ``REPLAYGAIN_`` tags. Default: ``89``. - **r128_targetlevel**: The target loudness level in decibels (i.e. ``<loudness in LUFS> + 107``) for files using ``R128_`` tags. Default: 84 (Use ``83`` for ATSC A/85, ``84`` for EBU R128 or ``89`` for ReplayGain 2.0.) - **r128**: A space separated list of formats that will use ``R128_`` tags with integer values instead of the common ``REPLAYGAIN_`` tags with floating point values. Requires the "ffmpeg" backend. Default: ``Opus``. - **per_disc**: Calculate album ReplayGain on disc level instead of album level. Default: ``no`` These options only work with the "command" backend: - **command**: The path to the ``mp3gain`` or ``aacgain`` executable (if beets cannot find it by itself). For example: ``/Applications/MacMP3Gain.app/Contents/Resources/aacgain``. Default: Search in your ``$PATH``. - **noclip**: Reduce the amount of ReplayGain adjustment to whatever amount would keep clipping from occurring. Default: ``yes``. This option only works with the "ffmpeg" backend: - **peak**: Either ``true`` (the default) or ``sample``. ``true`` is more accurate but slower. Manual Analysis --------------- By default, the plugin will analyze all items an albums as they are implemented. However, you can also manually analyze files that are already in your library. Use the ``beet replaygain`` command:: $ beet replaygain [-Waf] [QUERY] The ``-a`` flag analyzes whole albums instead of individual tracks. Provide a query (see :doc:`/reference/query`) to indicate which items or albums to analyze. Files that already have ReplayGain values are skipped unless ``-f`` is supplied. Use ``-w`` (write tags) or ``-W`` (don't write tags) to control whether ReplayGain tags are written into the music files, or stored in the beets database only (the default is to use :ref:`the importer's configuration <config-import-write>`). To execute with a different number of threads, call ``beet replaygain --threads N``:: $ beet replaygain --threads N [-Waf] [QUERY] with N any integer. To disable parallelism, use ``--threads 0``. ReplayGain analysis is not fast, so you may want to disable it during import. Use the ``auto`` config option to control this:: replaygain: auto: no ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������beetbox-beets-01f1faf/docs/plugins/rewrite.rst������������������������������������������������������0000664�0000000�0000000�00000003313�14723254774�0021573�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������Rewrite Plugin ============== The ``rewrite`` plugin lets you easily substitute values in your templates and path formats. Specifically, it is intended to let you *canonicalize* names such as artists: for example, perhaps you want albums from The Jimi Hendrix Experience to be sorted into the same folder as solo Hendrix albums. To use field rewriting, first enable the ``rewrite`` plugin (see :ref:`using-plugins`). Then, make a ``rewrite:`` section in your config file to contain your rewrite rules. Each rule consists of a field name, a regular expression pattern, and a replacement value. Rules are written ``fieldname regex: replacement``. For example, this line implements the Jimi Hendrix example above:: rewrite: artist The Jimi Hendrix Experience: Jimi Hendrix This will make ``$artist`` in your templates expand to "Jimi Hendrix" where it would otherwise be "The Jimi Hendrix Experience". The pattern is a case-insensitive regular expression. This means you can use ordinary regular expression syntax to match multiple artists. For example, you might use:: rewrite: artist .*jimi hendrix.*: Jimi Hendrix As a convenience, the plugin applies patterns for the ``artist`` field to the ``albumartist`` field as well. (Otherwise, you would probably want to duplicate every rule for ``artist`` and ``albumartist``.) A word of warning: This plugin theoretically only applies to templates and path formats; it initially does not modify files' metadata tags or the values tracked by beets' library database, but since it *rewrites all field lookups*, it modifies the file's metadata anyway. See comments in issue :bug:`2786`. As an alternative to this plugin the :doc:`/plugins/substitute` could be used. ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������beetbox-beets-01f1faf/docs/plugins/scrub.rst��������������������������������������������������������0000664�0000000�0000000�00000003310�14723254774�0021225�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������Scrub Plugin ============= The ``scrub`` plugin lets you remove extraneous metadata from files' tags. If you'd prefer never to see crufty tags that come from other tools, the plugin can automatically remove all non-beets-tracked tags whenever a file's metadata is written to disk by removing the tag entirely before writing new data. The plugin also provides a command that lets you manually remove files' tags. Automatic Scrubbing ------------------- To automatically remove files' tags before writing new ones, enable ``scrub`` plugin in your configuration (see :ref:`using-plugins`) and install ``beets`` with ``scrub`` extra .. code-block:: bash pip install "beets[scrub]" When importing new files (with ``import.write`` turned on) or modifying files' tags with the ``beet modify`` command, beets will first strip all types of tags entirely and then write the database-tracked metadata to the file. This behavior can be disabled with the ``auto`` config option (see below). Manual Scrubbing ---------------- The ``scrub`` command provided by this plugin removes tags from files and then rewrites their database-tracked metadata. To run it, just type ``beet scrub QUERY`` where ``QUERY`` matches the tracks to be scrubbed. Use this command with caution, however, because any information in the tags that is out of sync with the database will be lost. The ``-W`` (or ``--nowrite``) option causes the command to just remove tags but not restore any information. This will leave the files with no metadata whatsoever. Configuration ------------- To configure the plugin, make a ``scrub:`` section in your configuration file. There is one option: - **auto**: Enable metadata stripping during import. Default: ``yes``. ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������beetbox-beets-01f1faf/docs/plugins/smartplaylist.rst������������������������������������������������0000664�0000000�0000000�00000015617�14723254774�0023034�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������Smart Playlist Plugin ===================== ``smartplaylist`` is a plugin to generate smart playlists in m3u format based on beets queries every time your library changes. This plugin is specifically created to work well with `MPD's`_ playlist functionality. .. _MPD's: https://www.musicpd.org/ To use it, enable the ``smartplaylist`` plugin in your configuration (see :ref:`using-plugins`). Then configure your smart playlists like the following example:: smartplaylist: relative_to: ~/Music playlist_dir: ~/.mpd/playlists forward_slash: no playlists: - name: all.m3u query: '' - name: beatles.m3u query: 'artist:Beatles' You can generate as many playlists as you want by adding them to the ``playlists`` section, using beets query syntax (see :doc:`/reference/query`) for ``query`` and the file name to be generated for ``name``. The query will be split using shell-like syntax, so if you need to use spaces in the query, be sure to quote them (e.g., ``artist:"The Beatles"``). If you have existing files with the same names, you should back them up---they will be overwritten when the plugin runs. For more advanced usage, you can use template syntax (see :doc:`/reference/pathformat/`) in the ``name`` field. For example:: - name: 'ReleasedIn$year.m3u' query: 'year::201(0|1)' This will query all the songs in 2010 and 2011 and generate the two playlist files ``ReleasedIn2010.m3u`` and ``ReleasedIn2011.m3u`` using those songs. You can also gather the results of several queries by putting them in a list. (Items that match both queries are not included twice.) For example:: - name: 'BeatlesUniverse.m3u' query: ['artist:beatles', 'genre:"beatles cover"'] Note that since beets query syntax is in effect, you can also use sorting directives:: - name: 'Chronological Beatles' query: 'artist:Beatles year+' - name: 'Mixed Rock' query: ['artist:Beatles year+', 'artist:"Led Zeppelin" bitrate+'] The former case behaves as expected, however please note that in the latter the sorts will be merged: ``year+ bitrate+`` will apply to both the Beatles and Led Zeppelin. If that bothers you, please get in touch. For querying albums instead of items (mainly useful with extensible fields), use the ``album_query`` field. ``query`` and ``album_query`` can be used at the same time. The following example gathers single items but also items belonging to albums that have a ``for_travel`` extensible field set to 1:: - name: 'MyTravelPlaylist.m3u' album_query: 'for_travel:1' query: 'for_travel:1' By default, each playlist is automatically regenerated at the end of the session if an item or album it matches changed in the library database. To force regeneration, you can invoke it manually from the command line:: $ beet splupdate This will regenerate all smart playlists. You can also specify which ones you want to regenerate:: $ beet splupdate BeatlesUniverse.m3u MyTravelPlaylist You can also use this plugin together with the :doc:`mpdupdate`, in order to automatically notify MPD of the playlist change, by adding ``mpdupdate`` to the ``plugins`` line in your config file *after* the ``smartplaylist`` plugin. While changing existing playlists in the beets configuration it can help to use the ``--pretend`` option to find out if the edits work as expected. The results of the queries will be printed out instead of being written to the playlist file. $ beet splupdate --pretend BeatlesUniverse.m3u The ``pretend_paths`` configuration option sets whether the items should be displayed as per the user's ``format_item`` setting or what the file paths as they would be written to the m3u file look like. In case you want to export additional fields from the beets database into the generated playlists, you can do so by specifying them within the ``fields`` configuration option and setting the ``output`` option to ``extm3u``. For instance the following configuration exports the ``id`` and ``genre`` fields: smartplaylist: playlist_dir: /data/playlists relative_to: /data/playlists output: extm3u fields: - id - genre playlists: - name: all.m3u query: '' A resulting ``all.m3u`` file could look as follows: #EXTM3U #EXTINF:805 id="1931" genre="Jazz",Miles Davis - Autumn Leaves ../music/Albums/Miles Davis/Autumn Leaves/02 Autumn Leaves.mp3 To give a usage example, the `webm3u`_ and `Beetstream`_ plugins read the exported ``id`` field, allowing you to serve your local m3u playlists via HTTP. .. _Beetstream: https://github.com/BinaryBrain/Beetstream .. _webm3u: https://github.com/mgoltzsche/beets-webm3u Configuration ------------- To configure the plugin, make a ``smartplaylist:`` section in your configuration file. In addition to the ``playlists`` described above, the other configuration options are: - **auto**: Regenerate the playlist after every database change. Default: ``yes``. - **playlist_dir**: Where to put the generated playlist files. Default: The current working directory (i.e., ``'.'``). - **relative_to**: Generate paths in the playlist files relative to a base directory. If you intend to use this plugin to generate playlists for MPD, point this to your MPD music directory. Default: Use absolute paths. - **forward_slash**: Forces forward slashes in the generated playlist files. If you intend to use this plugin to generate playlists for MPD on Windows, set this to yes. Default: Use system separator. - **prefix**: Prepend this string to every path in the playlist file. For example, you could use the URL for a server where the music is stored. Default: empty string. - **urlencode**: URL-encode all paths. Default: ``no``. - **pretend_paths**: When running with ``--pretend``, show the actual file paths that will be written to the m3u file. Default: ``false``. - **uri_format**: Template with an ``$id`` placeholder used generate a playlist item URI, e.g. ``http://beets:8337/item/$id/file``. When this option is specified, the local path-related options ``prefix``, ``relative_to``, ``forward_slash`` and ``urlencode`` are ignored. - **output**: Specify the playlist format: m3u|extm3u. Default ``m3u``. - **fields**: Specify the names of the additional item fields to export into the playlist. This allows using e.g. the ``id`` field within other tools such as the `webm3u`_ and `Beetstream`_ plugins. To use this option, you must set the ``output`` option to ``extm3u``. .. _Beetstream: https://github.com/BinaryBrain/Beetstream .. _webm3u: https://github.com/mgoltzsche/beets-webm3u For many configuration options, there is a corresponding CLI option, e.g. ``--playlist-dir``, ``--relative-to``, ``--prefix``, ``--forward-slash``, ``--urlencode``, ``--uri-format``, ``--output``, ``--pretend-paths``. CLI options take precedence over those specified within the configuration file. �����������������������������������������������������������������������������������������������������������������beetbox-beets-01f1faf/docs/plugins/sonosupdate.rst��������������������������������������������������0000664�0000000�0000000�00000001142�14723254774�0022454�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������SonosUpdate Plugin ================== The ``sonosupdate`` plugin lets you automatically update `Sonos`_'s music library whenever you change your beets library. To use ``sonosupdate`` plugin, enable it in your configuration (see :ref:`using-plugins`). To use the ``sonosupdate`` plugin, first enable it in your configuration (see :ref:`using-plugins`). Then, install ``beets`` with ``sonosupdate`` extra pip install "beets[sonosupdate]" With that all in place, you'll see beets send the "update" command to your Sonos controller every time you change your beets library. .. _Sonos: https://sonos.com/ ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������beetbox-beets-01f1faf/docs/plugins/spotify.rst������������������������������������������������������0000664�0000000�0000000�00000012651�14723254774�0021614�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������Spotify Plugin ============== The ``spotify`` plugin generates `Spotify`_ playlists from tracks in your library with the ``beet spotify`` command using the `Spotify Search API`_. Also, the plugin can use the Spotify `Album`_ and `Track`_ APIs to provide metadata matches for the importer. .. _Spotify: https://www.spotify.com/ .. _Spotify Search API: https://developer.spotify.com/documentation/web-api/reference/#/operations/search .. _Album: https://developer.spotify.com/documentation/web-api/reference/#/operations/get-an-album .. _Track: https://developer.spotify.com/documentation/web-api/reference/#/operations/get-track Why Use This Plugin? -------------------- * You're a Beets user and Spotify user already. * You have playlists or albums you'd like to make available in Spotify from Beets without having to search for each artist/album/track. * You want to check which tracks in your library are available on Spotify. * You want to autotag music with metadata from the Spotify API. * You want to obtain track popularity and audio features (e.g., danceability) Basic Usage ----------- First, enable the ``spotify`` plugin (see :ref:`using-plugins`). Then, use the ``spotify`` command with a beets query:: beet spotify [OPTIONS...] QUERY Here's an example:: $ beet spotify "In The Lonely Hour" Processing 14 tracks... https://open.spotify.com/track/19w0OHr8SiZzRhjpnjctJ4 https://open.spotify.com/track/3PRLM4FzhplXfySa4B7bxS [...] Command-line options include: * ``-m MODE`` or ``--mode=MODE`` where ``MODE`` is either "list" or "open" controls whether to print out the playlist (for copying and pasting) or open it in the Spotify app. (See below.) * ``--show-failures`` or ``-f``: List the tracks that did not match a Spotify ID. You can enter the URL for an album or song on Spotify at the ``enter Id`` prompt during import:: Enter search, enter Id, aBort, eDit, edit Candidates, plaY? i Enter release ID: https://open.spotify.com/album/2rFYTHFBLQN3AYlrymBPPA Configuration ------------- This plugin can be configured like other metadata source plugins as described in :ref:`metadata-source-plugin-configuration`. In addition, the following configuration options are provided. The default options should work as-is, but there are some options you can put in config.yaml under the ``spotify:`` section: - **mode**: One of the following: - ``list``: Print out the playlist as a list of links. This list can then be pasted in to a new or existing Spotify playlist. - ``open``: This mode actually sends a link to your default browser with instructions to open Spotify with the playlist you created. Until this has been tested on all platforms, it will remain optional. Default: ``list``. - **region_filter**: A two-character country abbreviation, to limit results to that market. Default: None. - **show_failures**: List each lookup that does not return a Spotify ID (and therefore cannot be added to a playlist). Default: ``no``. - **tiebreak**: How to choose the track if there is more than one identical result. For example, there might be multiple releases of the same album. The options are ``popularity`` and ``first`` (to just choose the first match returned). Default: ``popularity``. - **regex**: An array of regex transformations to perform on the track/album/artist fields before sending them to Spotify. Can be useful for changing certain abbreviations, like ft. -> feat. See the examples below. Default: None. Here's an example:: spotify: source_weight: 0.7 mode: open region_filter: US show_failures: on tiebreak: first regex: [ { field: "albumartist", # Field in the item object to regex. search: "Something", # String to look for. replace: "Replaced" # Replacement value. }, { field: "title", search: "Something Else", replace: "AlsoReplaced" } ] Obtaining Track Popularity and Audio Features from Spotify ---------------------------------------------------------- Spotify provides information on track `popularity`_ and audio `features`_ that can be used for music discovery. .. _popularity: https://developer.spotify.com/documentation/web-api/reference/#/operations/get-track .. _features: https://developer.spotify.com/documentation/web-api/reference/#/operations/get-audio-features The ``spotify`` plugin provides an additional command ``spotifysync`` to obtain these track attributes from Spotify: * ``beet spotifysync [-f]``: obtain popularity and audio features information for every track in the library. By default, ``spotifysync`` will skip tracks that already have this information populated. Using the ``-f`` or ``-force`` option will download the data even for tracks that already have it. Please note that ``spotifysync`` works on tracks that have the Spotify track identifiers. So run ``spotifysync`` only after importing your music, during which Spotify identifiers will be added for tracks where Spotify is chosen as the tag source. In addition to ``popularity``, the command currently sets these audio features for all tracks with a Spotify track ID: * ``acousticness`` * ``danceability`` * ``energy`` * ``instrumentalness`` * ``key`` * ``liveness`` * ``loudness`` * ``mode`` * ``speechiness`` * ``tempo`` * ``time_signature`` * ``valence`` ���������������������������������������������������������������������������������������beetbox-beets-01f1faf/docs/plugins/subsonicplaylist.rst���������������������������������������������0000664�0000000�0000000�00000003252�14723254774�0023523�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������Subsonic Playlist Plugin ======================== The ``subsonicplaylist`` plugin allows to import playlists from a subsonic server. This is done by retrieving the track info from the subsonic server, searching for them in the beets library, and adding the playlist names to the `subsonic_playlist` tag of the found items. The content of the tag has the format: subsonic_playlist: ";first playlist;second playlist;" To get all items in a playlist use the query `;playlist name;`. Command Line Usage ------------------ To use the ``subsonicplaylist`` plugin, enable it in your configuration (see :ref:`using-plugins`). Then use it by invoking the ``subsonicplaylist`` command. Next, configure the plugin to connect to your Subsonic server, like this:: subsonicplaylist: base_url: http://subsonic.example.com username: someUser password: somePassword After this you can import your playlists by invoking the `subsonicplaylist` command. By default only the tags of the items found for playlists will be updated. This means that, if one imported a playlist, then delete one song from it and imported the playlist again, the deleted song will still have the playlist set in its `subsonic_playlist` tag. To solve this problem one can use the `-d/--delete` flag. This resets all `subsonic_playlist` tag before importing playlists. Here's an example configuration with all the available options and their default values:: subsonicplaylist: base_url: "https://your.subsonic.server" delete: no playlist_ids: [] playlist_names: [] username: '' password: '' The `base_url`, `username`, and `password` options are required. ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������beetbox-beets-01f1faf/docs/plugins/subsonicupdate.rst�����������������������������������������������0000664�0000000�0000000�00000003325�14723254774�0023145�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������SubsonicUpdate Plugin ===================== ``subsonicupdate`` is a very simple plugin for beets that lets you automatically update `Subsonic`_'s index whenever you change your beets library. .. _Subsonic: http://www.subsonic.org/pages/index.jsp To use ``subsonicupdate`` plugin, enable it in your configuration (see :ref:`using-plugins`). Then, you'll probably want to configure the specifics of your Subsonic server. You can do that using a ``subsonic:`` section in your ``config.yaml``, which looks like this:: subsonic: url: https://example.com:443/subsonic user: username pass: password auth: token With that all in place, this plugin will send a REST API call to your Subsonic server every time you change your beets library. Due to a current limitation of the API, all libraries visible to that user will be scanned. If the :doc:`/plugins/smartplaylist` is used, creating or changing any playlist will trigger a Subsonic update as well. This plugin requires Subsonic with an active Premium license (or active trial) or any other `Subsonic API compatible`_ server implementing the ``startScan`` endpoint. .. _Subsonic API compatible: http://www.subsonic.org/pages/api.jsp Configuration ------------- The available options under the ``subsonic:`` section are: - **url**: The Subsonic server resource. Default: ``http://localhost:4040`` - **user**: The Subsonic user. Default: ``admin`` - **pass**: The Subsonic user password. (This may either be a clear-text password or hex-encoded with the prefix ``enc:``.) Default: ``admin`` - **auth**: The authentication method. Possible choices are ``token`` or ``password``. ``token`` authentication is preferred to avoid sending cleartext password. �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������beetbox-beets-01f1faf/docs/plugins/substitute.rst���������������������������������������������������0000664�0000000�0000000�00000003255�14723254774�0022332�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������Substitute Plugin ================= The ``substitute`` plugin lets you easily substitute values in your templates and path formats. Specifically, it is intended to let you *canonicalize* names such as artists: For example, perhaps you want albums from The Jimi Hendrix Experience to be sorted into the same folder as solo Hendrix albums. This plugin is intended as a replacement for the ``rewrite`` plugin. While the ``rewrite`` plugin modifies the metadata, this plugin does not. Enable the ``substitute`` plugin (see :ref:`using-plugins`), then make a ``substitute:`` section in your config file to contain your rules. Each rule consists of a case-insensitive regular expression pattern, and a replacement string. For example, you might use: .. code-block:: yaml substitute: .*jimi hendrix.*: Jimi Hendrix The replacement can be an expression utilising the matched regex, allowing us to create more general rules. Say for example, we want to sort all albums by multiple artists into the directory of the first artist. We can thus capture everything before the first ``,``, `` &`` or `` and``, and use this capture group in the output, discarding the rest of the string. .. code-block:: yaml substitute: ^(.*?)(,| &| and).*: \1 This would handle all the below cases in a single rule: Bob Dylan and The Band -> Bob Dylan Neil Young & Crazy Horse -> Neil Young James Yorkston, Nina Persson & The Second Hand Orchestra -> James Yorkston To apply the substitution, you have to call the function ``%substitute{}`` in the paths section. For example: .. code-block:: yaml paths: default: \%substitute{$albumartist}/$year - $album\%aunique{}/$track - $title ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������beetbox-beets-01f1faf/docs/plugins/the.rst����������������������������������������������������������0000664�0000000�0000000�00000003165�14723254774�0020677�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������The Plugin ========== The ``the`` plugin allows you to move patterns in path formats. It's suitable, for example, for moving articles from string start to the end. This is useful for quick search on filesystems and generally looks good. Plugin does not change tags. By default plugin supports English "the, a, an", but custom regexp patterns can be added by user. How it works:: The Something -> Something, The A Band -> Band, A An Orchestra -> Orchestra, An To use the ``the`` plugin, enable it (see :doc:`/plugins/index`) and then use a template function called ``%the`` in path format expressions:: paths: default: %the{$albumartist}/($year) $album/$track $title The default configuration moves all English articles to the end of the string, but you can override these defaults to make more complex changes. Configuration ------------- To configure the plugin, make a ``the:`` section in your configuration file. The available options are: - **a**: Handle "A/An" moves. Default: ``yes``. - **the**: handle "The" moves. Default: ``yes``. - **patterns**: Custom regexp patterns, space-separated. Custom patterns are case-insensitive regular expressions. Patterns can be matched anywhere in the string (not just the beginning), so use ``^`` if you intend to match leading words. Default: ``[]``. - **strip**: Remove the article altogether instead of moving it to the end. Default: ``no``. - **format**: A Python format string for the output. Use ``{0}`` to indicate the part without the article and ``{1}`` for the article. Spaces are already trimmed from ends of both parts. Default: ``'{0}, {1}'``. �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������beetbox-beets-01f1faf/docs/plugins/thumbnails.rst���������������������������������������������������0000664�0000000�0000000�00000002641�14723254774�0022263�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������Thumbnails Plugin ================== The ``thumbnails`` plugin creates thumbnails for your album folders with the album cover. This works on freedesktop.org-compliant file managers such as Nautilus or Thunar, and is therefore POSIX-only. To use the ``thumbnails`` plugin, enable ``thumbnails`` and :doc:`/plugins/fetchart` in your configuration (see :ref:`using-plugins`) and install ``beets`` with ``thumbnails`` and ``fetchart`` extras .. code-block:: bash pip install "beets[fetchart,thumbnails]" ``thumbnails`` need to resize the covers, and therefore requires either `ImageMagick`_ or `Pillow`_. .. _Pillow: https://github.com/python-pillow/Pillow .. _ImageMagick: https://www.imagemagick.org/ Configuration ------------- To configure the plugin, make a ``thumbnails`` section in your configuration file. The available options are - **auto**: Whether the thumbnail should be automatically set on import. Default: ``yes``. - **force**: Generate the thumbnail even when there's one that seems fine (more recent than the cover art). Default: ``no``. - **dolphin**: Generate dolphin-compatible thumbnails. Dolphin (KDE file explorer) does not respect freedesktop.org's standard on thumbnails. This functionality replaces the :doc:`/plugins/freedesktop` Default: ``no`` Usage ----- The ``thumbnails`` command provided by this plugin creates a thumbnail for albums that match a query (see :doc:`/reference/query`). �����������������������������������������������������������������������������������������������beetbox-beets-01f1faf/docs/plugins/types.rst��������������������������������������������������������0000664�0000000�0000000�00000001550�14723254774�0021257�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������Types Plugin ============ The ``types`` plugin lets you declare types for attributes you use in your library. For example, you can declare that a ``rating`` field is numeric so that you can query it with ranges---which isn't possible when the field is considered a string (the default). Enable the ``types`` plugin as described in :doc:`/plugins/index` and then add a ``types`` section to your :doc:`configuration file </reference/config>`. The configuration section should map field name to one of ``int``, ``float``, ``bool``, or ``date``. Here's an example:: types: rating: int Now you can assign numeric ratings to tracks and albums and use :ref:`range queries <numericquery>` to filter them.:: beet modify "My favorite track" rating=5 beet ls rating:4..5 beet modify --album "My favorite album" rating=5 beet ls --album rating:4..5 ��������������������������������������������������������������������������������������������������������������������������������������������������������beetbox-beets-01f1faf/docs/plugins/unimported.rst���������������������������������������������������0000664�0000000�0000000�00000001325�14723254774�0022301�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������Unimported Plugin ================= The ``unimported`` plugin allows one to list all files in the library folder which are not listed in the beets library database, including art files. Command Line Usage ------------------ To use the ``unimported`` plugin, enable it in your configuration (see :ref:`using-plugins`). Then use it by invoking the ``beet unimported`` command. The command will list all files in the library folder which are not imported. You can exclude file extensions or entire subdirectories using the configuration file:: unimported: ignore_extensions: jpg png ignore_subdirectories: NonMusic data temp The default configuration lists all unimported files, ignoring no extensions. �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������beetbox-beets-01f1faf/docs/plugins/web.rst����������������������������������������������������������0000664�0000000�0000000�00000020775�14723254774�0020702�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������Web Plugin ========== The ``web`` plugin is a very basic alternative interface to beets that supplements the CLI. It can't do much right now, and the interface is a little clunky, but you can use it to query and browse your music and---in browsers that support HTML5 Audio---you can even play music. While it's not meant to replace the CLI, a graphical interface has a number of advantages in certain situations. For example, when editing a tag, a natural CLI makes you retype the whole thing---common GUI conventions can be used to just edit the part of the tag you want to change. A graphical interface could also drastically increase the number of people who can use beets. Install ------- To use the ``web`` plugin, first enable it in your configuration (see :ref:`using-plugins`). Then, install ``beets`` with ``web`` extra .. code-block:: bash pip install "beets[web]" Run the Server -------------- Then just type ``beet web`` to start the server and go to http://localhost:8337/. This is what it looks like: .. image:: beetsweb.png You can also specify the hostname and port number used by the Web server. These can be specified on the command line or in the ``[web]`` section of your :doc:`configuration file </reference/config>`. On the command line, use ``beet web [HOSTNAME] [PORT]``. Or the configuration options below. Usage ----- Type queries into the little search box. Double-click a track to play it with HTML5 Audio. Configuration ------------- To configure the plugin, make a ``web:`` section in your configuration file. The available options are: - **host**: The server hostname. Set this to 0.0.0.0 to bind to all interfaces. Default: Bind to 127.0.0.1. - **port**: The server port. Default: 8337. - **cors**: The CORS allowed origin (see :ref:`web-cors`, below). Default: CORS is disabled. - **cors_supports_credentials**: Support credentials when using CORS (see :ref:`web-cors`, below). Default: CORS_SUPPORTS_CREDENTIALS is disabled. - **reverse_proxy**: If true, enable reverse proxy support (see :ref:`reverse-proxy`, below). Default: false. - **include_paths**: If true, includes paths in item objects. Default: false. - **readonly**: If true, DELETE and PATCH operations are not allowed. Only GET is permitted. Default: true. Implementation -------------- The Web backend is built using a simple REST+JSON API with the excellent `Flask`_ library. The frontend is a single-page application written with `Backbone.js`_. This allows future non-Web clients to use the same backend API. .. _Backbone.js: https://backbonejs.org Eventually, to make the Web player really viable, we should use a Flash fallback for unsupported formats/browsers. There are a number of options for this: * `audio.js`_ * `html5media`_ * `MediaElement.js`_ .. _audio.js: https://kolber.github.io/audiojs/ .. _html5media: https://html5media.info/ .. _MediaElement.js: https://www.mediaelementjs.com/ .. _web-cors: Cross-Origin Resource Sharing (CORS) ------------------------------------ The ``web`` plugin's API can be used as a backend for an in-browser client. By default, browsers will only allow access from clients running on the same server as the API. (You will get an arcane error about ``XMLHttpRequest`` otherwise.) A technology called `CORS`_ lets you relax this restriction. If you want to use an in-browser client hosted elsewhere (or running from a different server on your machine), set the ``cors`` configuration option to the "origin" (protocol, host, and optional port number) where the client is served. Or set it to ``'*'`` to enable access from all origins. Note that there are security implications if you set the origin to ``'*'``, so please research this before using it. If the ``web`` server is behind a proxy that uses credentials, you might want to set the ``cors_supports_credentials`` configuration option to true to let in-browser clients log in. For example:: web: host: 0.0.0.0 cors: 'http://example.com' .. _CORS: https://en.wikipedia.org/wiki/Cross-origin_resource_sharing .. _reverse-proxy: Reverse Proxy Support --------------------- When the server is running behind a reverse proxy, you can tell the plugin to respect forwarded headers. Specifically, this can help when you host the plugin at a base URL other than the root ``/`` or when you use the proxy to handle secure connections. Enable the ``reverse_proxy`` configuration option if you do this. Technically, this option lets the proxy provide ``X-Script-Name`` and ``X-Scheme`` HTTP headers to control the plugin's the ``SCRIPT_NAME`` and its ``wsgi.url_scheme`` parameter. Here's a sample `Nginx`_ configuration that serves the web plugin under the /beets directory:: location /beets { proxy_pass http://127.0.0.1:8080; proxy_set_header Host $host; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Scheme $scheme; proxy_set_header X-Script-Name /beets; } .. _Nginx: https://www.nginx.com JSON API -------- ``GET /item/`` ++++++++++++++ Responds with a list of all tracks in the beets library. :: { "items": [ { "id": 6, "title": "A Song", ... }, { "id": 12, "title": "Another Song", ... } ... ] } ``GET /item/6`` +++++++++++++++ Looks for an item with id *6* in the beets library and responds with its JSON representation. :: { "id": 6, "title": "A Song", ... } If there is no item with that id responds with a *404* status code. ``DELETE /item/6`` ++++++++++++++++++ Removes the item with id *6* from the beets library. If the *?delete* query string is included, the matching file will be deleted from disk. Only allowed if ``readonly`` configuration option is set to ``no``. ``PATCH /item/6`` ++++++++++++++++++ Updates the item with id *6* and write the changes to the music file. The body should be a JSON object containing the changes to the object. Returns the updated JSON representation. :: { "id": 6, "title": "A Song", ... } Only allowed if ``readonly`` configuration option is set to ``no``. ``GET /item/6,12,13`` +++++++++++++++++++++ Response with a list of tracks with the ids *6*, *12* and *13*. The format of the response is the same as for `GET /item/`_. It is *not guaranteed* that the response includes all the items requested. If a track is not found it is silently dropped from the response. This endpoint also supports *DELETE* and *PATCH* methods as above, to operate on all items of the list. ``GET /item/path/...`` ++++++++++++++++++++++ Look for an item at the given absolute path on the server. If it corresponds to a track, return the track in the same format as ``/item/*``. If the server runs UNIX, you'll need to include an extra leading slash: ``http://localhost:8337/item/path//Users/beets/Music/Foo/Bar/Baz.mp3`` ``GET /item/query/querystring`` +++++++++++++++++++++++++++++++ Returns a list of tracks matching the query. The *querystring* must be a valid query as described in :doc:`/reference/query`. :: { "results": [ { "id" : 6, "title": "A Song" }, { "id" : 12, "title": "Another Song" } ] } Path elements are joined as parts of a query. For example, ``/item/query/foo/bar`` will be converted to the query ``foo,bar``. To specify literal path separators in a query, use a backslash instead of a slash. This endpoint also supports *DELETE* and *PATCH* methods as above, to operate on all items returned by the query. ``GET /item/6/file`` ++++++++++++++++++++ Sends the media file for the track. If the item or its corresponding file do not exist a *404* status code is returned. Albums ++++++ For albums, the following endpoints are provided: * ``GET /album/`` * ``GET /album/5`` * ``GET /album/5/art`` * ``DELETE /album/5`` * ``GET /album/5,7`` * ``DELETE /album/5,7`` * ``GET /album/query/querystring`` * ``DELETE /album/query/querystring`` The interface and response format is similar to the item API, except replacing the encapsulation key ``"items"`` with ``"albums"`` when requesting ``/album/`` or ``/album/5,7``. In addition we can request the cover art of an album with ``GET /album/5/art``. You can also add the '?expand' flag to get the individual items of an album. ``DELETE`` is only allowed if ``readonly`` configuration option is set to ``no``. ``GET /stats`` ++++++++++++++ Responds with the number of tracks and albums in the database. :: { "items": 5, "albums": 3 } .. _Flask: https://flask.palletsprojects.com/en/1.1.x/ ���beetbox-beets-01f1faf/docs/plugins/zero.rst���������������������������������������������������������0000664�0000000�0000000�00000004557�14723254774�0021104�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������Zero Plugin =========== The ``zero`` plugin allows you to null fields in files' metadata tags. Fields can be nulled unconditionally or conditioned on a pattern match. For example, the plugin can strip useless comments like "ripped by MyGreatRipper." The plugin can work in one of two modes: * ``fields``: A blacklist, where you choose the tags you want to remove (used by default). * ``keep_fields``: A whitelist, where you instead specify the tags you want to keep. To use the ``zero`` plugin, enable the plugin in your configuration (see :ref:`using-plugins`). Configuration ------------- Make a ``zero:`` section in your configuration file. You can specify the fields to nullify and the conditions for nullifying them: * Set ``auto`` to ``yes`` to null fields automatically on import. Default: ``yes``. * Set ``fields`` to a whitespace-separated list of fields to remove. You can get the list of all available fields by running ``beet fields``. In addition, the ``images`` field allows you to remove any images embedded in the media file. * Set ``keep_fields`` to *invert* the logic of the plugin. Only these fields will be kept; other fields will be removed. Remember to set only ``fields`` or ``keep_fields``---not both! * To conditionally filter a field, use ``field: [regexp, regexp]`` to specify regular expressions. * By default this plugin only affects files' tags; the beets database is left unchanged. To update the tags in the database, set the ``update_database`` option to true. For example:: zero: fields: month day genre genres comments comments: [EAC, LAME, from.+collection, 'ripped by'] genre: [rnb, 'power metal'] genres: [rnb, 'power metal'] update_database: true If a custom pattern is not defined for a given field, the field will be nulled unconditionally. Note that the plugin currently does not zero fields when importing "as-is". Manually Triggering Zero ------------------------ You can also type ``beet zero [QUERY]`` to manually invoke the plugin on music in your library. Preserving Album Art -------------------- If you use the ``keep_fields`` option, the plugin will remove embedded album art from files' tags unless you tell it not to. To keep the album art, include the special field ``images`` in the list. For example:: zero: keep_fields: title artist album year track genre genres images �������������������������������������������������������������������������������������������������������������������������������������������������beetbox-beets-01f1faf/docs/reference/���������������������������������������������������������������0000775�0000000�0000000�00000000000�14723254774�0017635�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������beetbox-beets-01f1faf/docs/reference/cli.rst��������������������������������������������������������0000664�0000000�0000000�00000054060�14723254774�0021143�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������Command-Line Interface ====================== .. only:: man SYNOPSIS -------- | **beet** [*args*...] *command* [*args*...] | **beet help** *command* .. only:: html **beet** is the command-line interface to beets. You invoke beets by specifying a *command*, like so:: beet COMMAND [ARGS...] The rest of this document describes the available commands. If you ever need a quick list of what's available, just type ``beet help`` or ``beet help COMMAND`` for help with a specific command. Beets also offers shell completion. For bash, see the `completion`_ command; for zsh, see the accompanying `completion script`_ for the ``beet`` command. Commands -------- .. only:: html Here are the built-in commands available in beets: .. contents:: :local: :depth: 1 Also be sure to see the :ref:`global flags <global-flags>`. .. _import-cmd: import `````` :: beet import [-CWAPRqst] [-l LOGPATH] PATH... beet import [options] -L QUERY Add music to your library, attempting to get correct tags for it from MusicBrainz. Point the command at some music: directories, single files, or compressed archives. The music will be copied to a configurable directory structure and added to a library database. The command is interactive and will try to get you to verify MusicBrainz tags that it thinks are suspect. See the :doc:`autotagging guide </guides/tagger>` for detail on how to use the interactive tag-correction flow. Directories passed to the import command can contain either a single album or many, in which case the leaf directories will be considered albums (the latter case is true of typical Artist/Album organizations and many people's "downloads" folders). The path can also be a single song or an archive. Beets supports `zip` and `tar` archives out of the box. To extract `rar` files, install the `rarfile`_ package and the `unrar` command. To extract `7z` files, install the `py7zr`_ package. Optional command flags: * By default, the command copies files to your library directory and updates the ID3 tags on your music. In order to move the files, instead of copying, use the ``-m`` (move) option. If you'd like to leave your music files untouched, try the ``-C`` (don't copy) and ``-W`` (don't write tags) options. You can also disable this behavior by default in the configuration file (below). * Also, you can disable the autotagging behavior entirely using ``-A`` (don't autotag)---then your music will be imported with its existing metadata. * During a long tagging import, it can be useful to keep track of albums that weren't tagged successfully---either because they're not in the MusicBrainz database or because something's wrong with the files. Use the ``-l`` option to specify a filename to log every time you skip an album or import it "as-is" or an album gets skipped as a duplicate. You can later review the file manually or import skipped paths from the logfile automatically by using the ``--from-logfile LOGFILE`` argument. * Relatedly, the ``-q`` (quiet) option can help with large imports by autotagging without ever bothering to ask for user input. Whenever the normal autotagger mode would ask for confirmation, the quiet mode performs a fallback action that can be configured using the ``quiet_fallback`` configuration or ``--quiet-fallback`` CLI option. By default it pessimistically ``skip``s the file. Alternatively, it can be used as is, by configuring ``asis``. * Speaking of resuming interrupted imports, the tagger will prompt you if it seems like the last import of the directory was interrupted (by you or by a crash). If you want to skip this prompt, you can say "yes" automatically by providing ``-p`` or "no" using ``-P``. The resuming feature can be disabled by default using a configuration option (see below). * If you want to import only the *new* stuff from a directory, use the ``-i`` option to run an *incremental* import. With this flag, beets will keep track of every directory it ever imports and avoid importing them again. This is useful if you have an "incoming" directory that you periodically add things to. To get this to work correctly, you'll need to use an incremental import *every time* you run an import on the directory in question---including the first time, when no subdirectories will be skipped. So consider enabling the ``incremental`` configuration option. * If you don't want to record skipped files during an *incremental* import, use the ``--incremental-skip-later`` flag which corresponds to the ``incremental_skip_later`` configuration option. Setting the flag prevents beets from persisting skip decisions during a non-interactive import so that a user can make a decision regarding previously skipped files during a subsequent interactive import run. To record skipped files during incremental import explicitly, use the ``--noincremental-skip-later`` option. * When beets applies metadata to your music, it will retain the value of any existing tags that weren't overwritten, and import them into the database. You may prefer to only use existing metadata for finding matches, and to erase it completely when new metadata is applied. You can enforce this behavior with the ``--from-scratch`` option, or the ``from_scratch`` configuration option. * By default, beets will proceed without asking if it finds a very close metadata match. To disable this and have the importer ask you every time, use the ``-t`` (for *timid*) option. * The importer typically works in a whole-album-at-a-time mode. If you instead want to import individual, non-album tracks, use the *singleton* mode by supplying the ``-s`` option. * If you have an album that's split across several directories under a common top directory, use the ``--flat`` option. This takes all the music files under the directory (recursively) and treats them as a single large album instead of as one album per directory. This can help with your more stubborn multi-disc albums. * Similarly, if you have one directory that contains multiple albums, use the ``--group-albums`` option to split the files based on their metadata before matching them as separate albums. * If you want to preview which files would be imported, use the ``--pretend`` option. If set, beets will just print a list of files that it would otherwise import. * If you already have a metadata backend ID that matches the items to be imported, you can instruct beets to restrict the search to that ID instead of searching for other candidates by using the ``--search-id SEARCH_ID`` option. Multiple IDs can be specified by simply repeating the option several times. * You can supply ``--set field=value`` to assign `field` to `value` on import. Values support the same template syntax as beets' :doc:`path formats <pathformat>`. These assignments will merge with (and possibly override) the :ref:`set_fields` configuration dictionary. You can use the option multiple times on the command line, like so:: beet import --set genre="Alternative Rock" --set mood="emotional" .. _rarfile: https://pypi.python.org/pypi/rarfile/ .. _py7zr: https://pypi.org/project/py7zr/ .. only:: html .. _reimport: Reimporting ^^^^^^^^^^^ The ``import`` command can also be used to "reimport" music that you've already added to your library. This is useful when you change your mind about some selections you made during the initial import, or if you prefer to import everything "as-is" and then correct tags later. Just point the ``beet import`` command at a directory of files that are already catalogged in your library. Beets will automatically detect this situation and avoid duplicating any items. In this situation, the "copy files" option (``-c``/``-C`` on the command line or ``copy`` in the config file) has slightly different behavior: it causes files to be *moved*, rather than duplicated, if they're already in your library. (The same is true, of course, if ``move`` is enabled.) That is, your directory structure will be updated to reflect the new tags if copying is enabled; you never end up with two copies of the file. The ``-L`` (``--library``) flag is also useful for retagging. Instead of listing paths you want to import on the command line, specify a :doc:`query string <query>` that matches items from your library. In this case, the ``-s`` (singleton) flag controls whether the query matches individual items or full albums. If you want to retag your whole library, just supply a null query, which matches everything: ``beet import -L`` Note that, if you just want to update your files' tags according to changes in the MusicBrainz database, the :doc:`/plugins/mbsync` is a better choice. Reimporting uses the full matching machinery to guess metadata matches; ``mbsync`` just relies on MusicBrainz IDs. .. _list-cmd: list ```` :: beet list [-apf] QUERY :doc:`Queries <query>` the database for music. Want to search for "Gronlandic Edit" by of Montreal? Try ``beet list gronlandic``. Maybe you want to see everything released in 2009 with "vegetables" in the title? Try ``beet list year:2009 title:vegetables``. You can also specify the sort order. (Read more in :doc:`query`.) You can use the ``-a`` switch to search for albums instead of individual items. In this case, the queries you use are restricted to album-level fields: for example, you can search for ``year:1969`` but query parts for item-level fields like ``title:foo`` will be ignored. Remember that ``artist`` is an item-level field; ``albumartist`` is the corresponding album field. The ``-p`` option makes beets print out filenames of matched items, which might be useful for piping into other Unix commands (such as `xargs`_). Similarly, the ``-f`` option lets you specify a specific format with which to print every album or track. This uses the same template syntax as beets' :doc:`path formats <pathformat>`. For example, the command ``beet ls -af '$album: $albumtotal' beatles`` prints out the number of tracks on each Beatles album. In Unix shells, remember to enclose the template argument in single quotes to avoid environment variable expansion. .. _xargs: https://en.wikipedia.org/wiki/Xargs .. _remove-cmd: remove `````` :: beet remove [-adf] QUERY Remove music from your library. This command uses the same :doc:`query <query>` syntax as the ``list`` command. By default, it just removes entries from the library database; it doesn't touch the files on disk. To actually delete the files, use the ``-d`` flag. When the ``-a`` flag is given, the command operates on albums instead of individual tracks. When you run the ``remove`` command, it prints a list of all affected items in the library and asks for your permission before removing them. You can then choose to abort (type `n`), confirm (`y`), or interactively choose some of the items (`s`). In the latter case, the command will prompt you for every matching item or album and invite you to type `y` to remove the item/album, `n` to keep it or `q` to exit and only remove the items/albums selected up to this point. This option lets you choose precisely which tracks/albums to remove without spending too much time to carefully craft a query. If you do not want to be prompted at all, use the ``-f`` option. .. _modify-cmd: modify `````` :: beet modify [-IMWay] [-f FORMAT] QUERY [FIELD=VALUE...] [FIELD!...] Change the metadata for items or albums in the database. Supply a :doc:`query <query>` matching the things you want to change and a series of ``field=value`` pairs. For example, ``beet modify genius of love artist="Tom Tom Club"`` will change the artist for the track "Genius of Love." To remove fields (which is only possible for flexible attributes), follow a field name with an exclamation point: ``field!``. Values can also be *templates*, using the same syntax as :doc:`path formats <pathformat>`. For example, ``beet modify artist='$artist_sort'`` will copy the artist sort name into the artist field for all your tracks, and ``beet modify title='$track $title'`` will add track numbers to their title metadata. The ``-a`` option changes to querying album fields instead of track fields and also enables to operate on albums in addition to the individual tracks. Without this flag, the command will only change *track-level* data, even if all the tracks belong to the same album. If you want to change an *album-level* field, such as ``year`` or ``albumartist``, you'll want to use the ``-a`` flag to avoid a confusing situation where the data for individual tracks conflicts with the data for the whole album. Modifications issued using ``-a`` by default cascade to individual tracks. To prevent this behavior, use ``-I``/``--noinherit``. Items will automatically be moved around when necessary if they're in your library directory, but you can disable that with ``-M``. Tags will be written to the files according to the settings you have for imports, but these can be overridden with ``-w`` (write tags, the default) and ``-W`` (don't write tags). When you run the ``modify`` command, it prints a list of all affected items in the library and asks for your permission before making any changes. You can then choose to abort the change (type `n`), confirm (`y`), or interactively choose some of the items (`s`). In the latter case, the command will prompt you for every matching item or album and invite you to type `y` to apply the changes, `n` to discard them or `q` to exit and apply the selected changes. This option lets you choose precisely which data to change without spending too much time to carefully craft a query. To skip the prompts entirely, use the ``-y`` option. .. _move-cmd: move ```` :: beet move [-capt] [-d DIR] QUERY Move or copy items in your library. This command, by default, acts as a library consolidator: items matching the query are renamed into your library directory structure. By specifying a destination directory with ``-d`` manually, you can move items matching a query anywhere in your filesystem. The ``-c`` option copies files instead of moving them. As with other commands, the ``-a`` option matches albums instead of items. The ``-e`` flag (for "export") copies files without changing the database. To perform a "dry run", just use the ``-p`` (for "pretend") flag. This will show you a list of files that would be moved but won't actually change anything on disk. The ``-t`` option sets the timid mode which will ask again before really moving or copying the files. .. _update-cmd: update `````` :: beet update [-F] FIELD [-e] EXCLUDE_FIELD [-aM] QUERY Update the library (and, by default, move files) to reflect out-of-band metadata changes and file deletions. This will scan all the matched files and read their tags, populating the database with the new values. By default, files will be renamed according to their new metadata; disable this with ``-M``. Beets will skip files if their modification times have not changed, so any out-of-band metadata changes must also update these for ``beet update`` to recognise that the files have been edited. To perform a "dry run" of an update, just use the ``-p`` (for "pretend") flag. This will show you all the proposed changes but won't actually change anything on disk. By default, all the changed metadata will be populated back to the database. If you only want certain fields to be written, specify them with the ```-F``` flags (which can be used multiple times). Alternatively, specify fields to *not* write with ```-e``` flags (which can be used multiple times). For the list of supported fields, please see ```beet fields```. When an updated track is part of an album, the album-level fields of *all* tracks from the album are also updated. (Specifically, the command copies album-level data from the first track on the album and applies it to the rest of the tracks.) This means that, if album-level fields aren't identical within an album, some changes shown by the ``update`` command may be overridden by data from other tracks on the same album. This means that running the ``update`` command multiple times may show the same changes being applied. .. _write-cmd: write ````` :: beet write [-pf] [QUERY] Write metadata from the database into files' tags. When you make changes to the metadata stored in beets' library database (during import or with the :ref:`modify-cmd` command, for example), you often have the option of storing changes only in the database, leaving your files untouched. The ``write`` command lets you later change your mind and write the contents of the database into the files. By default, this writes the changes only if there is a difference between the database and the tags in the file. You can think of this command as the opposite of :ref:`update-cmd`. The ``-p`` option previews metadata changes without actually applying them. The ``-f`` option forces a write to the file, even if the file tags match the database. This is useful for making sure that enabled plugins that run on write (e.g., the Scrub and Zero plugins) are run on the file. .. _stats-cmd: stats ````` :: beet stats [-e] [QUERY] Show some statistics on your entire library (if you don't provide a :doc:`query <query>`) or the matched items (if you do). By default, the command calculates file sizes using their bitrate and duration. The ``-e`` (``--exact``) option reads the exact sizes of each file (but is slower). The exact mode also outputs the exact duration in seconds. .. _fields-cmd: fields `````` :: beet fields Show the item and album metadata fields available for use in :doc:`query` and :doc:`pathformat`. The listing includes any template fields provided by plugins and any flexible attributes you've manually assigned to your items and albums. .. _config-cmd: config `````` :: beet config [-pdc] beet config -e Show or edit the user configuration. This command does one of three things: * With no options, print a YAML representation of the current user configuration. With the ``--default`` option, beets' default options are also included in the dump. * The ``--path`` option instead shows the path to your configuration file. This can be combined with the ``--default`` flag to show where beets keeps its internal defaults. * By default, sensitive information like passwords is removed when dumping the configuration. The ``--clear`` option includes this sensitive data. * With the ``--edit`` option, beets attempts to open your config file for editing. It first tries the ``$EDITOR`` environment variable, followed by ``$EDITOR`` and then a fallback option depending on your platform: ``open`` on OS X, ``xdg-open`` on Unix, and direct invocation on Windows. .. _global-flags: Global Flags ------------ Beets has a few "global" flags that affect all commands. These must appear between the executable name (``beet``) and the command---for example, ``beet -v import ...``. * ``-l LIBPATH``: specify the library database file to use. * ``-d DIRECTORY``: specify the library root directory. * ``-v``: verbose mode; prints out a deluge of debugging information. Please use this flag when reporting bugs. You can use it twice, as in ``-vv``, to make beets even more verbose. * ``-c FILE``: read a specified YAML :doc:`configuration file <config>`. This configuration works as an overlay: rather than replacing your normal configuration options entirely, the two are merged. Any individual options set in this config file will override the corresponding settings in your base configuration. * ``-p plugins``: specify a comma-separated list of plugins to enable. If specified, the plugin list in your configuration is ignored. The long form of this argument also allows specifying no plugins, effectively disabling all plugins: ``--plugins=``. * ``-P plugins``: specify a comma-separated list of plugins to disable in a specific beets run. This will overwrite ``-p`` if used with it. To disable all plugins, use ``--plugins=`` instead. Beets also uses the ``BEETSDIR`` environment variable to look for configuration and data. .. _completion: Shell Completion ---------------- Beets includes support for shell command completion. The command ``beet completion`` prints out a `bash`_ 3.2 script; to enable completion put a line like this into your ``.bashrc`` or similar file:: eval "$(beet completion)" Or, to avoid slowing down your shell startup time, you can pipe the ``beet completion`` output to a file and source that instead. You will also need to source the `bash-completion`_ script, which is probably available via your package manager. On OS X, you can install it via Homebrew with ``brew install bash-completion``; Homebrew will give you instructions for sourcing the script. .. _bash-completion: https://github.com/scop/bash-completion .. _bash: https://www.gnu.org/software/bash/ The completion script suggests names of subcommands and (after typing ``-``) options of the given command. If you are using a command that accepts a query, the script will also complete field names. :: beet list ar[TAB] # artist: artist_credit: artist_sort: artpath: beet list artp[TAB] beet list artpath\: (Don't worry about the slash in front of the colon: this is a escape sequence for the shell and won't be seen by beets.) Completion of plugin commands only works for those plugins that were enabled when running ``beet completion``. If you add a plugin later on you will want to re-generate the script. zsh ``` If you use zsh, take a look at the included `completion script`_. The script should be placed in a directory that is part of your ``fpath``, and `not` sourced in your ``.zshrc``. Running ``echo $fpath`` will give you a list of valid directories. Another approach is to use zsh's bash completion compatibility. This snippet defines some bash-specific functions to make this work without errors:: autoload bashcompinit bashcompinit _get_comp_words_by_ref() { :; } compopt() { :; } _filedir() { :; } eval "$(beet completion)" .. _completion script: https://github.com/beetbox/beets/blob/master/extra/_beet .. only:: man See Also -------- ``https://beets.readthedocs.org/`` :manpage:`beetsconfig(5)` ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������beetbox-beets-01f1faf/docs/reference/config.rst�����������������������������������������������������0000664�0000000�0000000�00000117316�14723254774�0021645�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������Configuration ============= Beets has an extensive configuration system that lets you customize nearly every aspect of its operation. To configure beets, you create a file called ``config.yaml``. The location of the file depends on your platform (type ``beet config -p`` to see the path on your system): * On Unix-like OSes, write ``~/.config/beets/config.yaml``. * On Windows, use ``%APPDATA%\beets\config.yaml``. This is usually in a directory like ``C:\Users\You\AppData\Roaming``. * On OS X, you can use either the Unix location or ``~/Library/Application Support/beets/config.yaml``. You can launch your text editor to create or update your configuration by typing ``beet config -e``. (See the :ref:`config-cmd` command for details.) It is also possible to customize the location of the configuration file and even use multiple layers of configuration. See `Configuration Location`_, below. The config file uses `YAML`_ syntax. You can use the full power of YAML, but most configuration options are simple key/value pairs. This means your config file will look like this:: option: value another_option: foo bigger_option: key: value foo: bar In YAML, you will need to use spaces (not tabs!) to indent some lines. If you have questions about more sophisticated syntax, take a look at the `YAML`_ documentation. .. _YAML: https://yaml.org/ The rest of this page enumerates the dizzying litany of configuration options available in beets. You might also want to see an :ref:`example <config-example>`. .. contents:: :local: :depth: 2 Global Options -------------- These options control beets' global operation. library ~~~~~~~ Path to the beets library file. By default, beets will use a file called ``library.db`` alongside your configuration file. directory ~~~~~~~~~ The directory to which files will be copied/moved when adding them to the library. Defaults to a folder called ``Music`` in your home directory. plugins ~~~~~~~ A space-separated list of plugin module names to load. See :ref:`using-plugins`. include ~~~~~~~ A space-separated list of extra configuration files to include. Filenames are relative to the directory containing ``config.yaml``. pluginpath ~~~~~~~~~~ Directories to search for plugins. Each Python file or directory in a plugin path represents a plugin and should define a subclass of :class:`BeetsPlugin`. A plugin can then be loaded by adding the filename to the `plugins` configuration. The plugin path can either be a single string or a list of strings---so, if you have multiple paths, format them as a YAML list like so:: pluginpath: - /path/one - /path/two .. _ignore: ignore ~~~~~~ A list of glob patterns specifying file and directory names to be ignored when importing. By default, this consists of ``.*``, ``*~``, ``System Volume Information``, ``lost+found`` (i.e., beets ignores Unix-style hidden files, backup files, and directories that appears at the root of some Linux and Windows filesystems). .. _ignore_hidden: ignore_hidden ~~~~~~~~~~~~~ Either ``yes`` or ``no``; whether to ignore hidden files when importing. On Windows, the "Hidden" property of files is used to detect whether or not a file is hidden. On OS X, the file's "IsHidden" flag is used to detect whether or not a file is hidden. On both OS X and other platforms (excluding Windows), files (and directories) starting with a dot are detected as hidden files. .. _replace: replace ~~~~~~~ A set of regular expression/replacement pairs to be applied to all filenames created by beets. Typically, these replacements are used to avoid confusing problems or errors with the filesystem (for example, leading dots, which hide files on Unix, and trailing whitespace, which is illegal on Windows). To override these substitutions, specify a mapping from regular expression to replacement strings. For example, ``[xy]: z`` will make beets replace all instances of the characters ``x`` or ``y`` with the character ``z``. If you do change this value, be certain that you include at least enough substitutions to avoid causing errors on your operating system. Here are the default substitutions used by beets, which are sufficient to avoid unexpected behavior on all popular platforms:: replace: '[\\/]': _ '^\.': _ '[\x00-\x1f]': _ '[<>:"\?\*\|]': _ '\.$': _ '\s+$': '' '^\s+': '' '^-': _ These substitutions remove forward and back slashes, leading dots, and control characters—all of which is a good idea on any OS. The fourth line removes the Windows "reserved characters" (useful even on Unix for compatibility with Windows-influenced network filesystems like Samba). Trailing dots and trailing whitespace, which can cause problems on Windows clients, are also removed. When replacements other than the defaults are used, it is possible that they will increase the length of the path. In the scenario where this leads to a conflict with the maximum filename length, the default replacements will be used to resolve the conflict and beets will display a warning. Note that paths might contain special characters such as typographical quotes (``“”``). With the configuration above, those will not be replaced as they don't match the typewriter quote (``"``). To also strip these special characters, you can either add them to the replacement list or use the :ref:`asciify-paths` configuration option below. .. _path-sep-replace: path_sep_replace ~~~~~~~~~~~~~~~~ A string that replaces the path separator (for example, the forward slash ``/`` on Linux and MacOS, and the backward slash ``\\`` on Windows) when generating filenames with beets. This option is related to :ref:`replace`, but is distinct from it for technical reasons. .. warning:: Changing this option is potentially dangerous. For example, setting it to the actual path separator could create directories in unexpected locations. Use caution when changing it and always try it out on a small number of files before applying it to your whole library. Default: ``_``. .. _asciify-paths: asciify_paths ~~~~~~~~~~~~~ Convert all non-ASCII characters in paths to ASCII equivalents. For example, if your path template for singletons is ``singletons/$title`` and the title of a track is "CafĂ©", then the track will be saved as ``singletons/Cafe.mp3``. The changes take place before applying the :ref:`replace` configuration and are roughly equivalent to wrapping all your path templates in the ``%asciify{}`` :ref:`template function <template-functions>`. This uses the `unidecode module`_ which is language agnostic, so some characters may be transliterated from a different language than expected. For example, Japanese kanji will usually use their Chinese readings. Default: ``no``. .. _unidecode module: https://pypi.org/project/Unidecode .. _art-filename: art_filename ~~~~~~~~~~~~ When importing album art, the name of the file (without extension) where the cover art image should be placed. This is a template string, so you can use any of the syntax available to :doc:`/reference/pathformat`. Defaults to ``cover`` (i.e., images will be named ``cover.jpg`` or ``cover.png`` and placed in the album's directory). threaded ~~~~~~~~ Either ``yes`` or ``no``, indicating whether the autotagger should use multiple threads. This makes things substantially faster by overlapping work: for example, it can copy files for one album in parallel with looking up data in MusicBrainz for a different album. You may want to disable this when debugging problems with the autotagger. Defaults to ``yes``. .. _list_format_item: .. _format_item: format_item ~~~~~~~~~~~ Format to use when listing *individual items* with the :ref:`list-cmd` command and other commands that need to print out items. Defaults to ``$artist - $album - $title``. The ``-f`` command-line option overrides this setting. It used to be named `list_format_item`. .. _list_format_album: .. _format_album: format_album ~~~~~~~~~~~~ Format to use when listing *albums* with :ref:`list-cmd` and other commands. Defaults to ``$albumartist - $album``. The ``-f`` command-line option overrides this setting. It used to be named `list_format_album`. .. _sort_item: sort_item ~~~~~~~~~ Default sort order to use when fetching items from the database. Defaults to ``artist+ album+ disc+ track+``. Explicit sort orders override this default. .. _sort_album: sort_album ~~~~~~~~~~ Default sort order to use when fetching albums from the database. Defaults to ``albumartist+ album+``. Explicit sort orders override this default. .. _sort_case_insensitive: sort_case_insensitive ~~~~~~~~~~~~~~~~~~~~~ Either ``yes`` or ``no``, indicating whether the case should be ignored when sorting lexicographic fields. When set to ``no``, lower-case values will be placed after upper-case values (e.g., *Bar Qux foo*), while ``yes`` would result in the more expected *Bar foo Qux*. Default: ``yes``. .. _original_date: original_date ~~~~~~~~~~~~~ Either ``yes`` or ``no``, indicating whether matched albums should have their ``year``, ``month``, and ``day`` fields set to the release date of the *original* version of an album rather than the selected version of the release. That is, if this option is turned on, then ``year`` will always equal ``original_year`` and so on. Default: ``no``. .. _overwrite_null: overwrite_null ~~~~~~~~~~~~~~ This confusingly-named option indicates which fields have meaningful `null` values. If an album or track field is in the corresponding list, then an existing value for this field in an item in the database can be overwritten with `null`. By default, however, `null` is interpreted as information about the field being unavailable, so it would not overwrite existing values. For example:: overwrite_null: album: ["albumid"] track: ["title", "date"] .. _artist_credit: artist_credit ~~~~~~~~~~~~~ Either ``yes`` or ``no``, indicating whether matched tracks and albums should use the artist credit, rather than the artist. That is, if this option is turned on, then ``artist`` will contain the artist as credited on the release. .. _per_disc_numbering: per_disc_numbering ~~~~~~~~~~~~~~~~~~ A boolean controlling the track numbering style on multi-disc releases. By default (``per_disc_numbering: no``), tracks are numbered per-release, so the first track on the second disc has track number N+1 where N is the number of tracks on the first disc. If this ``per_disc_numbering`` is enabled, then the first (non-pregap) track on each disc always has track number 1. If you enable ``per_disc_numbering``, you will likely want to change your :ref:`path-format-config` also to include ``$disc`` before ``$track`` to make filenames sort correctly in album directories. For example, you might want to use a path format like this:: paths: default: $albumartist/$album%aunique{}/$disc-$track $title When this option is off (the default), even "pregap" hidden tracks are numbered from one, not zero, so other track numbers may appear to be bumped up by one. When it is on, the pregap track for each disc can be numbered zero. .. _config-aunique: aunique ~~~~~~~ These options are used to generate a string that is guaranteed to be unique among all albums in the library who share the same set of keys. The defaults look like this:: aunique: keys: albumartist album disambiguators: albumtype year label catalognum albumdisambig releasegroupdisambig bracket: '[]' See :ref:`aunique` for more details. .. _config-sunique: sunique ~~~~~~~ Like :ref:`config-aunique` above for albums, these options control the generation of a unique string to disambiguate *singletons* that share similar metadata. The defaults look like this:: sunique: keys: artist title disambiguators: year trackdisambig bracket: '[]' See :ref:`sunique` for more details. .. _terminal_encoding: terminal_encoding ~~~~~~~~~~~~~~~~~ The text encoding, as `known to Python`_, to use for messages printed to the standard output. It's also used to read messages from the standard input. By default, this is determined automatically from the locale environment variables. .. _known to python: https://docs.python.org/2/library/codecs.html#standard-encodings .. _clutter: clutter ~~~~~~~ When beets imports all the files in a directory, it tries to remove the directory if it's empty. A directory is considered empty if it only contains files whose names match the glob patterns in `clutter`, which should be a list of strings. The default list consists of "Thumbs.DB" and ".DS_Store". The importer only removes recursively searched subdirectories---the top-level directory you specify on the command line is never deleted. .. _max_filename_length: max_filename_length ~~~~~~~~~~~~~~~~~~~ Set the maximum number of characters in a filename, after which names will be truncated. By default, beets tries to ask the filesystem for the correct maximum. .. _id3v23: id3v23 ~~~~~~ By default, beets writes MP3 tags using the ID3v2.4 standard, the latest version of ID3. Enable this option to instead use the older ID3v2.3 standard, which is preferred by certain older software such as Windows Media Player. .. _va_name: va_name ~~~~~~~ Sets the albumartist for various-artist compilations. Defaults to ``'Various Artists'`` (the MusicBrainz standard). Affects other sources, such as :doc:`/plugins/discogs`, too. .. _ui_options: UI Options ---------- The options that allow for customization of the visual appearance of the console interface. These options are available in this section: color ~~~~~ Either ``yes`` or ``no``; whether to use color in console output (currently only in the ``import`` command). Turn this off if your terminal doesn't support ANSI colors. .. note:: The `color` option was previously a top-level configuration. This is still respected, but a deprecation message will be shown until your top-level `color` configuration has been nested under `ui`. .. _colors: colors ~~~~~~ The colors that are used throughout the user interface. These are only used if the ``color`` option is set to ``yes``. For example, you might have a section in your configuration file that looks like this:: ui: colors: text_success: ['bold', 'green'] text_warning: ['bold', 'yellow'] text_error: ['bold', 'red'] text_highlight: ['bold', 'red'] text_highlight_minor: ['white'] action_default: ['bold', 'cyan'] action: ['bold', 'cyan'] # New colors after UI overhaul text: ['normal'] text_faint: ['faint'] import_path: ['bold', 'blue'] import_path_items: ['bold', 'blue'] added: ['green'] removed: ['red'] changed: ['yellow'] added_highlight: ['bold', 'green'] removed_highlight: ['bold', 'red'] changed_highlight: ['bold', 'yellow'] text_diff_added: ['bold', 'red'] text_diff_removed: ['bold', 'red'] text_diff_changed: ['bold', 'red'] action_description: ['white'] Available colors: black, darkred, darkgreen, brown (darkyellow), darkblue, purple (darkmagenta), teal (darkcyan), lightgray, darkgray, red, green, yellow, blue, fuchsia (magenta), turquoise (cyan), white Legacy UI colors config directive used strings. If any colors value is still a string instead of a list, it will be translated to list automatically. For example ``blue`` will become ``['blue']``. terminal_width ~~~~~~~~~~~~~~ Controls line wrapping on non-Unix systems. On Unix systems, the width of the terminal is detected automatically. If this fails, or on non-Unix systems, the specified value is used as a fallback. Defaults to ``80`` characters:: ui: terminal_width: 80 length_diff_thresh ~~~~~~~~~~~~~~~~~~ Beets compares the length of the imported track with the length the metadata source provides. If any tracks differ by at least ``length_diff_thresh`` seconds, they will be colored with ``text_highlight``. Below this threshold, different track lengths are colored with ``text_highlight_minor``. ``length_diff_thresh`` does not impact which releases are selected in autotagger matching or distance score calculation (see :ref:`match-config`, ``distance_weights`` and :ref:`colors`):: ui: length_diff_thresh: 10.0 import ~~~~~~ When importing, beets will read several options to configure the visuals of the import dialogue. There are two layouts controlling how horizontal space and line wrapping is dealt with: ``column`` and ``newline``. The indentation of the respective elements of the import UI can also be configured. For example setting ``4`` for ``match_header`` will indent the very first block of a proposed match by five characters in the terminal:: ui: import: indentation: match_header: 4 match_details: 4 match_tracklist: 7 layout: newline Importer Options ---------------- The options that control the :ref:`import-cmd` command are indented under the ``import:`` key. For example, you might have a section in your configuration file that looks like this:: import: write: yes copy: yes resume: no These options are available in this section: .. _config-import-write: write ~~~~~ Either ``yes`` or ``no``, controlling whether metadata (e.g., ID3) tags are written to files when using ``beet import``. Defaults to ``yes``. The ``-w`` and ``-W`` command-line options override this setting. .. _config-import-copy: copy ~~~~ Either ``yes`` or ``no``, indicating whether to **copy** files into the library directory when using ``beet import``. Defaults to ``yes``. Can be overridden with the ``-c`` and ``-C`` command-line options. The option is ignored if ``move`` is enabled (i.e., beets can move or copy files but it doesn't make sense to do both). .. _config-import-move: move ~~~~ Either ``yes`` or ``no``, indicating whether to **move** files into the library directory when using ``beet import``. Defaults to ``no``. The effect is similar to the ``copy`` option but you end up with only one copy of the imported file. ("Moving" works even across filesystems; if necessary, beets will copy and then delete when a simple rename is impossible.) Moving files can be risky—it's a good idea to keep a backup in case beets doesn't do what you expect with your files. This option *overrides* ``copy``, so enabling it will always move (and not copy) files. The ``-c`` switch to the ``beet import`` command, however, still takes precedence. .. _link: link ~~~~ Either ``yes`` or ``no``, indicating whether to use symbolic links instead of moving or copying files. (It conflicts with the ``move``, ``copy`` and ``hardlink`` options.) Defaults to ``no``. This option only works on platforms that support symbolic links: i.e., Unixes. It will fail on Windows. It's likely that you'll also want to set ``write`` to ``no`` if you use this option to preserve the metadata on the linked files. .. _hardlink: hardlink ~~~~~~~~ Either ``yes`` or ``no``, indicating whether to use hard links instead of moving, copying, or symlinking files. (It conflicts with the ``move``, ``copy``, and ``link`` options.) Defaults to ``no``. As with symbolic links (see :ref:`link`, above), this will not work on Windows and you will want to set ``write`` to ``no``. Otherwise, metadata on the original file will be modified. .. _reflink: reflink ~~~~~~~ Either ``yes``, ``no``, or ``auto``, indicating whether to use copy-on-write `file clones`_ (a.k.a. "reflinks") instead of copying or moving files. The ``auto`` option uses reflinks when possible and falls back to plain copying when necessary. Defaults to ``no``. This kind of clone is only available on certain filesystems: for example, btrfs and APFS. For more details on filesystem support, see the `pyreflink`_ documentation. Note that you need to install ``pyreflink``, either through ``python -m pip install beets[reflink]`` or ``python -m pip install reflink``. The option is ignored if ``move`` is enabled (i.e., beets can move or copy files but it doesn't make sense to do both). .. _file clones: https://en.wikipedia.org/wiki/Copy-on-write .. _pyreflink: https://reflink.readthedocs.io/en/latest/ resume ~~~~~~ Either ``yes``, ``no``, or ``ask``. Controls whether interrupted imports should be resumed. "Yes" means that imports are always resumed when possible; "no" means resuming is disabled entirely; "ask" (the default) means that the user should be prompted when resuming is possible. The ``-p`` and ``-P`` flags correspond to the "yes" and "no" settings and override this option. .. _incremental: incremental ~~~~~~~~~~~ Either ``yes`` or ``no``, controlling whether imported directories are recorded and whether these recorded directories are skipped. This corresponds to the ``-i`` flag to ``beet import``. .. _incremental_skip_later: incremental_skip_later ~~~~~~~~~~~~~~~~~~~~~~ Either ``yes`` or ``no``, controlling whether skipped directories are recorded in the incremental list. When set to ``yes``, skipped directories won't be recorded, and beets will try to import them again later. When set to ``no``, skipped directories will be recorded, and skipped later. Defaults to ``no``. .. _from_scratch: from_scratch ~~~~~~~~~~~~ Either ``yes`` or ``no`` (default), controlling whether existing metadata is discarded when a match is applied. This corresponds to the ``--from-scratch`` flag to ``beet import``. .. _quiet: quiet ~~~~~ Either ``yes`` or ``no`` (default), controlling whether to ask for a manual decision from the user when the importer is unsure how to proceed. This corresponds to the ``--quiet`` flag to ``beet import``. .. _quiet_fallback: quiet_fallback ~~~~~~~~~~~~~~ Either ``skip`` (default) or ``asis``, specifying what should happen in quiet mode (see the ``-q`` flag to ``import``, above) when there is no strong recommendation. .. _none_rec_action: none_rec_action ~~~~~~~~~~~~~~~ Either ``ask`` (default), ``asis`` or ``skip``. Specifies what should happen during an interactive import session when there is no recommendation. Useful when you are only interested in processing medium and strong recommendations interactively. timid ~~~~~ Either ``yes`` or ``no``, controlling whether the importer runs in *timid* mode, in which it asks for confirmation on every autotagging match, even the ones that seem very close. Defaults to ``no``. The ``-t`` command-line flag controls the same setting. .. _import_log: log ~~~ Specifies a filename where the importer's log should be kept. By default, no log is written. This can be overridden with the ``-l`` flag to ``import``. .. _default_action: default_action ~~~~~~~~~~~~~~ One of ``apply``, ``skip``, ``asis``, or ``none``, indicating which option should be the *default* when selecting an action for a given match. This is the action that will be taken when you type return without an option letter. The default is ``apply``. .. _languages: languages ~~~~~~~~~ A list of locale names to search for preferred aliases. For example, setting this to ``en`` uses the transliterated artist name "Pyotr Ilyich Tchaikovsky" instead of the Cyrillic script for the composer's name when tagging from MusicBrainz. You can use a space-separated list of language abbreviations, like ``en jp es``, to specify a preference order. Defaults to an empty list, meaning that no language is preferred. .. _ignored_alias_types: ignored_alias_types ~~~~~~~~~~~~~~~~~~~ A list of alias types to be ignored when importing new items. See the `MusicBrainz Documentation` for more information on aliases. .._MusicBrainz Documentation: https://musicbrainz.org/doc/Aliases .. _detail: detail ~~~~~~ Whether the importer UI should show detailed information about each match it finds. When enabled, this mode prints out the title of every track, regardless of whether it matches the original metadata. (The default behavior only shows changes.) Default: ``no``. .. _group_albums: group_albums ~~~~~~~~~~~~ By default, the beets importer groups tracks into albums based on the directories they reside in. This option instead uses files' metadata to partition albums. Enable this option if you have directories that contain tracks from many albums mixed together. The ``--group-albums`` or ``-g`` option to the :ref:`import-cmd` command is equivalent, and the *G* interactive option invokes the same workflow. Default: ``no``. .. _autotag: autotag ~~~~~~~ By default, the beets importer always attempts to autotag new music. If most of your collection consists of obscure music, you may be interested in disabling autotagging by setting this option to ``no``. (You can re-enable it with the ``-a`` flag to the :ref:`import-cmd` command.) Default: ``yes``. .. _duplicate_keys: duplicate_keys ~~~~~~~~~~~~~~ The fields used to find duplicates when importing. There are two sub-values here: ``album`` and ``item``. Each one is a list of field names; if an existing object (album or item) in the library matches the new object on all of these fields, the importer will consider it a duplicate. Default:: album: albumartist album item: artist title .. _duplicate_action: duplicate_action ~~~~~~~~~~~~~~~~ Either ``skip``, ``keep``, ``remove``, ``merge`` or ``ask``. Controls how duplicates are treated in import task. "skip" means that new item(album or track) will be skipped; "keep" means keep both old and new items; "remove" means remove old item; "merge" means merge into one album; "ask" means the user should be prompted for the action each time. The default is ``ask``. .. _duplicate_verbose_prompt: duplicate_verbose_prompt ~~~~~~~~~~~~~~~~~~~~~~~~ Usually when duplicates are detected during import, information about the existing and the newly imported album is summarized. Enabling this option also lists details on individual tracks. The :ref:`format_item setting <format_item>` is applied, which would, considering the default, look like this: .. code-block:: console This item is already in the library! Old: 1 items, MP3, 320kbps, 5:56, 13.6 MiB Artist Name - Album Name - Third Track Title New: 2 items, MP3, 320kbps, 7:18, 17.1 MiB Artist Name - Album Name - First Track Title Artist Name - Album Name - Second Track Title [S]kip new, Keep all, Remove old, Merge all? Default: ``no``. .. _bell: bell ~~~~ Ring the terminal bell to get your attention when the importer needs your input. Default: ``no``. .. _set_fields: set_fields ~~~~~~~~~~ A dictionary indicating fields to set to values for newly imported music. Here's an example:: set_fields: genre: 'To Listen' collection: 'Unordered' Other field/value pairs supplied via the ``--set`` option on the command-line override any settings here for fields with the same name. Values support the same template syntax as beets' :doc:`path formats <pathformat>`. Fields are set on both the album and each individual track of the album. Fields are persisted to the media files of each track. Default: ``{}`` (empty). .. _singleton_album_disambig: singleton_album_disambig ~~~~~~~~~~~~~~~~~~~~~~~~ During singleton imports and if the metadata source provides it, album names are appended to the disambiguation string of matching track candidates. For example: ``The Artist - The Title (Discogs, Index 3, Track B1, [The Album]``. This feature is currently supported by the :doc:`/plugins/discogs` and the :doc:`/plugins/spotify`. Default: ``yes``. .. _musicbrainz-config: MusicBrainz Options ------------------- You can instruct beets to use `your own MusicBrainz database`_ instead of the `main server`_. Use the ``host``, ``https`` and ``ratelimit`` options under a ``musicbrainz:`` header, like so:: musicbrainz: host: localhost:5000 https: no ratelimit: 100 The ``host`` key, of course, controls the Web server hostname (and port, optionally) that will be contacted by beets (default: musicbrainz.org). The ``https`` key makes the client use HTTPS instead of HTTP. This setting applies only to custom servers. The official MusicBrainz server always uses HTTPS. (Default: no.) The server must have search indices enabled (see `Building search indexes`_). The ``ratelimit`` option, an integer, controls the number of Web service requests per second (default: 1). **Do not change the rate limit setting** if you're using the main MusicBrainz server---on this public server, you're `limited`_ to one request per second. .. _your own MusicBrainz database: https://musicbrainz.org/doc/MusicBrainz_Server/Setup .. _main server: https://musicbrainz.org/ .. _limited: https://musicbrainz.org/doc/XML_Web_Service/Rate_Limiting .. _Building search indexes: https://musicbrainz.org/doc/Development/Search_server_setup .. _musicbrainz.enabled: enabled ~~~~~~~ This option allows you to disable using MusicBrainz as a metadata source. This applies if you use plugins that fetch data from alternative sources and should make the import process quicker. Default: ``yes``. .. _searchlimit: searchlimit ~~~~~~~~~~~ The number of matches returned when sending search queries to the MusicBrainz server. Default: ``5``. .. _extra_tags: extra_tags ~~~~~~~~~~ By default, beets will use only the artist, album, and track count to query MusicBrainz. Additional tags to be queried can be supplied with the ``extra_tags`` setting. For example:: musicbrainz: extra_tags: [year, catalognum, country, media, label] This setting should improve the autotagger results if the metadata with the given tags match the metadata returned by MusicBrainz. Note that the only tags supported by this setting are the ones listed in the above example. Default: ``[]`` .. _genres: genres ~~~~~~ Use MusicBrainz genre tags to populate (and replace if it's already set) the ``genre`` tag. This will make it a list of all the genres tagged for the release and the release-group on MusicBrainz, separated by "; " and sorted by the total number of votes. Default: ``no`` .. _musicbrainz.external_ids: external_ids ~~~~~~~~~~~~ Set any of the ``external_ids`` options to ``yes`` to enable the MusicBrainz importer to look for links to related metadata sources. If such a link is available the release ID will be extracted from the URL provided and imported to the beets library:: musicbrainz: external_ids: discogs: yes spotify: yes bandcamp: yes beatport: yes deezer: yes tidal: yes The library fields of the corresponding :ref:`autotagger_extensions` are used to save the data (``discogs_albumid``, ``bandcamp_album_id``, ``spotify_album_id``, ``beatport_album_id``, ``deezer_album_id``, ``tidal_album_id``). On re-imports existing data will be overwritten. The default of all options is ``no``. .. _match-config: Autotagger Matching Options --------------------------- You can configure some aspects of the logic beets uses when automatically matching MusicBrainz results under the ``match:`` section. To control how *tolerant* the autotagger is of differences, use the ``strong_rec_thresh`` option, which reflects the distance threshold below which beets will make a "strong recommendation" that the metadata be used. Strong recommendations are accepted automatically (except in "timid" mode), so you can use this to make beets ask your opinion more or less often. The threshold is a *distance* value between 0.0 and 1.0, so you can think of it as the opposite of a *similarity* value. For example, if you want to automatically accept any matches above 90% similarity, use:: match: strong_rec_thresh: 0.10 The default strong recommendation threshold is 0.04. The ``medium_rec_thresh`` and ``rec_gap_thresh`` options work similarly. When a match is below the *medium* recommendation threshold or the distance between it and the next-best match is above the *gap* threshold, the importer will suggest that match but not automatically confirm it. Otherwise, you'll see a list of options to choose from. .. _max_rec: max_rec ~~~~~~~ As mentioned above, autotagger matches have *recommendations* that control how the UI behaves for a certain quality of match. The recommendation for a certain match is based on the overall distance calculation. But you can also control the recommendation when a specific distance penalty is applied by defining *maximum* recommendations for each field: To define maxima, use keys under ``max_rec:`` in the ``match`` section. The defaults are "medium" for missing and unmatched tracks and "strong" (i.e., no maximum) for everything else:: match: max_rec: missing_tracks: medium unmatched_tracks: medium If a recommendation is higher than the configured maximum and the indicated penalty is applied, the recommendation is downgraded. The setting for each field can be one of ``none``, ``low``, ``medium`` or ``strong``. When the maximum recommendation is ``strong``, no "downgrading" occurs. The available penalty names here are: * source * artist * album * media * mediums * year * country * label * catalognum * albumdisambig * album_id * tracks * missing_tracks * unmatched_tracks * track_title * track_artist * track_index * track_length * track_id .. _preferred: preferred ~~~~~~~~~ In addition to comparing the tagged metadata with the match metadata for similarity, you can also specify an ordered list of preferred countries and media types. A distance penalty will be applied if the country or media type from the match metadata doesn't match. The specified values are preferred in descending order (i.e., the first item will be most preferred). Each item may be a regular expression, and will be matched case insensitively. The number of media will be stripped when matching preferred media (e.g. "2x" in "2xCD"). You can also tell the autotagger to prefer matches that have a release year closest to the original year for an album. Here's an example:: match: preferred: countries: ['US', 'GB|UK'] media: ['CD', 'Digital Media|File'] original_year: yes By default, none of these options are enabled. .. _ignored: ignored ~~~~~~~ You can completely avoid matches that have certain penalties applied by adding the penalty name to the ``ignored`` setting:: match: ignored: missing_tracks unmatched_tracks The available penalties are the same as those for the :ref:`max_rec` setting. For example, setting ``ignored: missing_tracks`` will skip any album matches where your audio files are missing some of the tracks. The importer will not attempt to display these matches. It does not ignore the fact that the album is missing tracks, which would allow these matches to apply more easily. To do that, you'll want to adjust the penalty for missing tracks. .. _required: required ~~~~~~~~ You can avoid matches that lack certain required information. Add the tags you want to enforce to the ``required`` setting:: match: required: year label catalognum country No tags are required by default. .. _ignored_media: ignored_media ~~~~~~~~~~~~~ A list of media (i.e., formats) in metadata databases to ignore when matching music. You can use this to ignore all media that usually contain video instead of audio, for example:: match: ignored_media: ['Data CD', 'DVD', 'DVD-Video', 'Blu-ray', 'HD-DVD', 'VCD', 'SVCD', 'UMD', 'VHS'] No formats are ignored by default. .. _ignore_data_tracks: ignore_data_tracks ~~~~~~~~~~~~~~~~~~~ By default, audio files contained in data tracks within a release are included in the album's tracklist. If you want them to be included, set it ``no``. Default: ``yes``. .. _ignore_video_tracks: ignore_video_tracks ~~~~~~~~~~~~~~~~~~~ By default, video tracks within a release will be ignored. If you want them to be included (for example if you would like to track the audio-only versions of the video tracks), set it to ``no``. Default: ``yes``. .. _path-format-config: Path Format Configuration ------------------------- You can also configure the directory hierarchy beets uses to store music. These settings appear under the ``paths:`` key. Each string is a template string that can refer to metadata fields like ``$artist`` or ``$title``. The filename extension is added automatically. At the moment, you can specify three special paths: ``default`` for most releases, ``comp`` for "various artist" releases with no dominant artist, and ``singleton`` for non-album tracks. The defaults look like this:: paths: default: $albumartist/$album%aunique{}/$track $title singleton: Non-Album/$artist/$title comp: Compilations/$album%aunique{}/$track $title Note the use of ``$albumartist`` instead of ``$artist``; this ensures that albums will be well-organized. For more about these format strings, see :doc:`pathformat`. The ``aunique{}`` function ensures that identically-named albums are placed in different directories; see :ref:`aunique` for details. In addition to ``default``, ``comp``, and ``singleton``, you can condition path queries based on beets queries (see :doc:`/reference/query`). This means that a config file like this:: paths: albumtype:soundtrack: Soundtracks/$album/$track $title will place soundtrack albums in a separate directory. The queries are tested in the order they appear in the configuration file, meaning that if an item matches multiple queries, beets will use the path format for the *first* matching query. Note that the special ``singleton`` and ``comp`` path format conditions are, in fact, just shorthand for the explicit queries ``singleton:true`` and ``comp:true``. In contrast, ``default`` is special and has no query equivalent: the ``default`` format is only used if no queries match. Configuration Location ---------------------- The beets configuration file is usually located in a standard location that depends on your OS, but there are a couple of ways you can tell beets where to look. Environment Variable ~~~~~~~~~~~~~~~~~~~~ First, you can set the ``BEETSDIR`` environment variable to a directory containing a ``config.yaml`` file. This replaces your configuration in the default location. This also affects where auxiliary files, like the library database, are stored by default (that's where relative paths are resolved to). This environment variable is useful if you need to manage multiple beets libraries with separate configurations. Command-Line Option ~~~~~~~~~~~~~~~~~~~ Alternatively, you can use the ``--config`` command-line option to indicate a YAML file containing options that will then be merged with your existing options (from ``BEETSDIR`` or the default locations). This is useful if you want to keep your configuration mostly the same but modify a few options as a batch. For example, you might have different strategies for importing files, each with a different set of importer options. Default Location ~~~~~~~~~~~~~~~~ In the absence of a ``BEETSDIR`` variable, beets searches a few places for your configuration, depending on the platform: - On Unix platforms, including OS X:``~/.config/beets`` and then ``$XDG_CONFIG_DIR/beets``, if the environment variable is set. - On OS X, we also search ``~/Library/Application Support/beets`` before the Unixy locations. - On Windows: ``~\AppData\Roaming\beets``, and then ``%APPDATA%\beets``, if the environment variable is set. Beets uses the first directory in your platform's list that contains ``config.yaml``. If no config file exists, the last path in the list is used. .. _config-example: Example ------- Here's an example file:: directory: /var/mp3 import: copy: yes write: yes log: beetslog.txt art_filename: albumart plugins: bpd pluginpath: ~/beets/myplugins ui: color: yes paths: default: $genre/$albumartist/$album/$track $title singleton: Singletons/$artist - $title comp: $genre/$album/$track $title albumtype:soundtrack: Soundtracks/$album/$track $title .. only:: man See Also -------- ``https://beets.readthedocs.org/`` :manpage:`beet(1)` ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������beetbox-beets-01f1faf/docs/reference/index.rst������������������������������������������������������0000664�0000000�0000000�00000000415�14723254774�0021476�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������Reference ========= This section contains reference materials for various parts of beets. To get started with beets as a new user, though, you may want to read the :doc:`/guides/main` guide first. .. toctree:: :maxdepth: 2 cli config pathformat query ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������beetbox-beets-01f1faf/docs/reference/pathformat.rst�������������������������������������������������0000664�0000000�0000000�00000030430�14723254774�0022534�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������Path Formats ============ The ``paths:`` section of the config file (see :doc:`config`) lets you specify the directory and file naming scheme for your music library. Templates substitute symbols like ``$title`` (any field value prefixed by ``$``) with the appropriate value from the track's metadata. Beets adds the filename extension automatically. For example, consider this path format string: ``$albumartist/$album/$track $title`` Here are some paths this format will generate: * ``Yeah Yeah Yeahs/It's Blitz!/01 Zero.mp3`` * ``Spank Rock/YoYoYoYoYo/11 Competition.mp3`` * ``The Magnetic Fields/Realism/01 You Must Be Out of Your Mind.mp3`` Because ``$`` is used to delineate a field reference, you can use ``$$`` to emit a dollars sign. As with `Python template strings`_, ``${title}`` is equivalent to ``$title``; you can use this if you need to separate a field name from the text that follows it. .. _Python template strings: https://docs.python.org/library/string.html#template-strings A Note About Artists -------------------- Note that in path formats, you almost certainly want to use ``$albumartist`` and not ``$artist``. The latter refers to the "track artist" when it is present, which means that albums that have tracks from different artists on them (like `Stop Making Sense`_, for example) will be placed into different folders! Continuing with the Stop Making Sense example, you'll end up with most of the tracks in a "Talking Heads" directory and one in a "Tom Tom Club" directory. You probably don't want that! So use ``$albumartist``. .. _Stop Making Sense: https://musicbrainz.org/release/798dcaab-0f1a-4f02-a9cb-61d5b0ddfd36.html As a convenience, however, beets allows ``$albumartist`` to fall back to the value for ``$artist`` and vice-versa if one tag is present but the other is not. .. _template-functions: Template Functions ------------------ Beets path formats also support *function calls*, which can be used to transform text and perform logical manipulations. The syntax for function calls is like this: ``%func{arg,arg}``. For example, the ``upper`` function makes its argument upper-case, so ``%upper{beets rocks}`` will be replaced with ``BEETS ROCKS``. You can, of course, nest function calls and place variable references in function arguments, so ``%upper{$artist}`` becomes the upper-case version of the track's artists. These functions are built in to beets: * ``%lower{text}``: Convert ``text`` to lowercase. * ``%upper{text}``: Convert ``text`` to UPPERCASE. * ``%capitalize{text}``: Make the first letter of ``text`` UPPERCASE and the rest lowercase. * ``%title{text}``: Convert ``text`` to Title Case. * ``%left{text,n}``: Return the first ``n`` characters of ``text``. * ``%right{text,n}``: Return the last ``n`` characters of ``text``. * ``%if{condition,text}`` or ``%if{condition,truetext,falsetext}``: If ``condition`` is nonempty (or nonzero, if it's a number), then returns the second argument. Otherwise, returns the third argument if specified (or nothing if ``falsetext`` is left off). * ``%asciify{text}``: Convert non-ASCII characters to their ASCII equivalents. For example, "cafĂ©" becomes "cafe". Uses the mapping provided by the `unidecode module`_. See the :ref:`asciify-paths` configuration option. * ``%aunique{identifiers,disambiguators,brackets}``: Provides a unique string to disambiguate similar albums in the database. See :ref:`aunique`, below. * ``%sunique{identifiers,disambiguators,brackets}``: Similarly, a unique string to disambiguate similar singletons in the database. See :ref:`sunique`, below. * ``%time{date_time,format}``: Return the date and time in any format accepted by `strftime`_. For example, to get the year some music was added to your library, use ``%time{$added,%Y}``. * ``%first{text}``: Returns the first item, separated by ``;`` (a semicolon followed by a space). You can use ``%first{text,count,skip}``, where ``count`` is the number of items (default 1) and ``skip`` is number to skip (default 0). You can also use ``%first{text,count,skip,sep,join}`` where ``sep`` is the separator, like ``;`` or ``/`` and join is the text to concatenate the items. * ``%ifdef{field}``, ``%ifdef{field,truetext}`` or ``%ifdef{field,truetext,falsetext}``: Checks if an flexible attribute ``field`` is defined. If it exists, then return ``truetext`` or ``field`` (default). Otherwise, returns ``falsetext``. The ``field`` should be entered without ``$``. Note that this doesn't work with built-in :ref:`itemfields`, as they are always defined. .. _unidecode module: https://pypi.org/project/Unidecode .. _strftime: https://docs.python.org/3/library/time.html#time.strftime Plugins can extend beets with more template functions (see :ref:`templ_plugins`). .. _aunique: Album Disambiguation -------------------- Occasionally, bands release two albums with the same name (c.f. Crystal Castles, Weezer, and any situation where a single has the same name as an album or EP). Beets ships with special support, in the form of the ``%aunique{}`` template function, to avoid placing two identically-named albums in the same directory on disk. The ``aunique`` function detects situations where two albums have some identical fields and emits text from additional fields to disambiguate the albums. For example, if you have both Crystal Castles albums in your library, ``%aunique{}`` will expand to "[2008]" for one album and "[2010]" for the other. The function detects that you have two albums with the same artist and title but that they have different release years. For full flexibility, the ``%aunique`` function takes three arguments. The first two are whitespace-separated lists of album field names: a set of *identifiers* and a set of *disambiguators*. The third argument is a pair of characters used to surround the disambiguator. Any group of albums with identical values for all the identifiers will be considered "duplicates". Then, the function tries each disambiguator field, looking for one that distinguishes each of the duplicate albums from each other. The first such field is used as the result for ``%aunique``. If no field suffices, an arbitrary number is used to distinguish the two albums. The default identifiers are ``albumartist album`` and the default disambiguators are ``albumtype year label catalognum albumdisambig releasegroupdisambig``. So you can get reasonable disambiguation behavior if you just use ``%aunique{}`` with no parameters in your path forms (as in the default path formats), but you can customize the disambiguation if, for example, you include the year by default in path formats. The default characters used as brackets are ``[]``. To change this, provide a third argument to the ``%aunique`` function consisting of two characters: the left and right brackets. Or, to turn off bracketing entirely, leave argument blank. One caveat: When you import an album that is named identically to one already in your library, the *first* album—the one already in your library— will not consider itself a duplicate at import time. This means that ``%aunique{}`` will expand to nothing for this album and no disambiguation string will be used at its import time. Only the second album will receive a disambiguation string. If you want to add the disambiguation string to both albums, just run ``beet move`` (possibly restricted by a query) to update the paths for the albums. .. _sunique: Singleton Disambiguation ------------------------ It is also possible to have singleton tracks with the same name and the same artist. Beets provides the ``%sunique{}`` template to avoid giving these tracks the same file path. It has the same arguments as the :ref:`%aunique <aunique>` template, but the default values are different. The default identifiers are ``artist title`` and the default disambiguators are ``year trackdisambig``. Syntax Details -------------- The characters ``$``, ``%``, ``{``, ``}``, and ``,`` are "special" in the path template syntax. This means that, for example, if you want a ``%`` character to appear in your paths, you'll need to be careful that you don't accidentally write a function call. To escape any of these characters (except ``{``, and ``,`` outside a function argument), prefix it with a ``$``. For example, ``$$`` becomes ``$``; ``$%`` becomes ``%``, etc. The only exceptions are: * ``${``, which is ambiguous with the variable reference syntax (like ``${title}``). To insert a ``{`` alone, it's always sufficient to just type ``{``. * commas are used as argument separators in function calls. Inside of a function's argument, use ``$,`` to get a literal ``,`` character. Outside of any function argument, escaping is not necessary: ``,`` by itself will produce ``,`` in the output. If a value or function is undefined, the syntax is simply left unreplaced. For example, if you write ``$foo`` in a path template, this will yield ``$foo`` in the resulting paths because "foo" is not a valid field name. The same is true of syntax errors like unclosed ``{}`` pairs; if you ever see template syntax constructs leaking into your paths, check your template for errors. If an error occurs in the Python code that implements a function, the function call will be expanded to a string that describes the exception so you can debug your template. For example, the second parameter to ``%left`` must be an integer; if you write ``%left{foo,bar}``, this will be expanded to something like ``<ValueError: invalid literal for int()>``. .. _itemfields: Available Values ---------------- Here's a list of the different values available to path formats. The current list can be found definitively by running the command ``beet fields``. Note that plugins can add new (or replace existing) template values (see :ref:`templ_plugins`). Ordinary metadata: * title * artist * artist_sort: The "sort name" of the track artist (e.g., "Beatles, The" or "White, Jack"). * artist_credit: The track-specific `artist credit`_ name, which may be a variation of the artist's "canonical" name. * album * albumartist: The artist for the entire album, which may be different from the artists for the individual tracks. * albumartist_sort * albumartist_credit * genre * composer * grouping * year, month, day: The release date of the specific release. * original_year, original_month, original_day: The release date of the original version of the album. * track * tracktotal * disc * disctotal * lyrics * comments * bpm * comp: Compilation flag. * albumtype: The MusicBrainz album type; the MusicBrainz wiki has a `list of type names`_. * label * asin * catalognum * script * language * country * albumstatus * media * albumdisambig * disctitle * encoder .. _artist credit: https://wiki.musicbrainz.org/Artist_Credit .. _list of type names: https://musicbrainz.org/doc/Release_Group/Type Audio information: * length (in seconds) * bitrate (in kilobits per second, with units: e.g., "192kbps") * bitrate_mode (e.g., "CBR", "VBR" or "ABR", only available for the MP3 format) * encoder_info (e.g., "LAME 3.97.0", only available for some formats) * encoder_settings (e.g., "-V2", only available for the MP3 format) * format (e.g., "MP3" or "FLAC") * channels * bitdepth (only available for some formats) * samplerate (in kilohertz, with units: e.g., "48kHz") MusicBrainz and fingerprint information: * mb_trackid * mb_releasetrackid * mb_albumid * mb_artistid * mb_albumartistid * mb_releasegroupid * acoustid_fingerprint * acoustid_id Library metadata: * mtime: The modification time of the audio file. * added: The date and time that the music was added to your library. * path: The item's filename. .. _templ_plugins: Template functions and values provided by plugins ------------------------------------------------- Beets plugins can provide additional fields and functions to templates. See the :doc:`/plugins/index` page for a full list of plugins. Some plugin-provided constructs include: * ``$missing`` by :doc:`/plugins/missing`: The number of missing tracks per album. * ``%bucket{text}`` by :doc:`/plugins/bucket`: Substitute a string by the range it belongs to. * ``%the{text}`` by :doc:`/plugins/the`: Moves English articles to ends of strings. The :doc:`/plugins/inline` lets you define template fields in your beets configuration file using Python snippets. And for more advanced processing, you can go all-in and write a dedicated plugin to register your own fields and functions (see :ref:`writing-plugins`). ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������beetbox-beets-01f1faf/docs/reference/query.rst������������������������������������������������������0000664�0000000�0000000�00000030350�14723254774�0021535�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������.. _queries: Queries ======= Many of beets' :doc:`commands <cli>` are built around **query strings:** searches that select tracks and albums from your library. This page explains the query string syntax, which is meant to vaguely resemble the syntax used by Web search engines. .. _keywordquery: Keyword ------- This command:: $ beet list love will show all tracks matching the query string ``love``. By default any unadorned word like this matches in a track's title, artist, album name, album artist, genre and comments. See below on how to search other fields. For example, this is what I might see when I run the command above:: Against Me! - Reinventing Axl Rose - I Still Love You Julie Air - Love 2 - Do the Joy Bag Raiders - Turbo Love - Shooting Stars Bat for Lashes - Two Suns - Good Love ... .. _combiningqueries: Combining Keywords ------------------ Multiple keywords are implicitly joined with a Boolean "and." That is, if a query has two keywords, it only matches tracks that contain *both* keywords. For example, this command:: $ beet ls magnetic tomorrow matches songs from the album "The House of Tomorrow" by The Magnetic Fields in my library. It *doesn't* match other songs by the Magnetic Fields, nor does it match "Tomorrowland" by Walter Meego---those songs only have *one* of the two keywords I specified. Keywords can also be joined with a Boolean "or" using a comma. For example, the command:: $ beet ls magnetic tomorrow , beatles yesterday will match both "The House of Tomorrow" by the Magnetic Fields, as well as "Yesterday" by The Beatles. Note that the comma has to be followed by a space (e.g., ``foo,bar`` will be treated as a single keyword, *not* as an OR-query). .. _fieldsquery: Specific Fields --------------- Sometimes, a broad keyword match isn't enough. Beets supports a syntax that lets you query a specific field---only the artist, only the track title, and so on. Just say ``field:value``, where ``field`` is the name of the thing you're trying to match (such as ``artist``, ``album``, or ``title``) and ``value`` is the keyword you're searching for. For example, while this query:: $ beet list dream matches a lot of songs in my library, this more-specific query:: $ beet list artist:dream only matches songs by the artist The-Dream. One query I especially appreciate is one that matches albums by year:: $ beet list -a year:2012 Recall that ``-a`` makes the ``list`` command show albums instead of individual tracks, so this command shows me all the releases I have from this year. For multi-valued tags (such as ``artists`` or ``albumartists``), a regular expression search must be used to search for a single value within the multi-valued tag. Note that you can filter albums by querying tracks fields and vice versa:: $ beet list -a title:love and vice versa:: $ beet list art_path::love Phrases ------- You can query for strings with spaces in them by quoting or escaping them using your shell's argument syntax. For example, this command:: $ beet list the rebel shows several tracks in my library, but these (equivalent) commands:: $ beet list "the rebel" $ beet list the\ rebel only match the track "The Rebel" by Buck 65. Note that the quotes and backslashes are not part of beets' syntax; I'm just using the escaping functionality of my shell (bash or zsh, for instance) to pass ``the rebel`` as a single argument instead of two. .. _exact-match: Exact Matches ------------- While ordinary queries perform *substring* matches, beets can also match whole strings by adding either ``=`` (case-sensitive) or ``=~`` (ignore case) after the field name's colon and before the expression:: $ beet list artist:air $ beet list artist:=~air $ beet list artist:=AIR The first query is a simple substring one that returns tracks by Air, AIR, and Air Supply. The second query returns tracks by Air and AIR, since both are a case-insensitive match for the entire expression, but does not return anything by Air Supply. The third query, which requires a case-sensitive exact match, returns tracks by AIR only. Exact matches may be performed on phrases as well:: $ beet list artist:=~"dave matthews" $ beet list artist:="Dave Matthews" Both of these queries return tracks by Dave Matthews, but not by Dave Matthews Band. To search for exact matches across *all* fields, just prefix the expression with a single ``=`` or ``=~``:: $ beet list =~crash $ beet list ="American Football" .. _regex: Regular Expressions ------------------- In addition to simple substring and exact matches, beets also supports regular expression matching for more advanced queries. To run a regex query, use an additional ``:`` between the field name and the expression:: $ beet list "artist::Ann(a|ie)" That query finds songs by Anna Calvi and Annie but not Annuals. Similarly, this query prints the path to any file in my library that's missing a track title:: $ beet list -p title::^$ To search *all* fields using a regular expression, just prefix the expression with a single ``:``, like so:: $ beet list ":Ho[pm]eless" Regular expressions are case-sensitive and build on `Python's built-in implementation`_. See Python's documentation for specifics on regex syntax. Most command-line shells will try to interpret common characters in regular expressions, such as ``()[]|``. To type those characters, you'll need to escape them (e.g., with backslashes or quotation marks, depending on your shell). .. _Python's built-in implementation: https://docs.python.org/library/re.html .. _numericquery: Numeric Range Queries --------------------- For numeric fields, such as year, bitrate, and track, you can query using one- or two-sided intervals. That is, you can find music that falls within a *range* of values. To use ranges, write a query that has two dots (``..``) at the beginning, middle, or end of a string of numbers. Dots in the beginning let you specify a maximum (e.g., ``..7``); dots at the end mean a minimum (``4..``); dots in the middle mean a range (``4..7``). For example, this command finds all your albums that were released in the '90s:: $ beet list -a year:1990..1999 and this command finds MP3 files with bitrates of 128k or lower:: $ beet list format:MP3 bitrate:..128000 The ``length`` field also lets you use a "M:SS" format. For example, this query finds tracks that are less than four and a half minutes in length:: $ beet list length:..4:30 .. _datequery: Date and Date Range Queries --------------------------- Date-valued fields, such as *added* and *mtime*, have a special query syntax that lets you specify years, months, and days as well as ranges between dates. Dates are written separated by hyphens, like ``year-month-day``, but the month and day are optional. If you leave out the day, for example, you will get matches for the whole month. Date *intervals*, like the numeric intervals described above, are separated by two dots (``..``). You can specify a start, an end, or both. Here is an example that finds all the albums added in 2008:: $ beet ls -a 'added:2008' Find all items added in the years 2008, 2009 and 2010:: $ beet ls 'added:2008..2010' Find all items added before the year 2010:: $ beet ls 'added:..2009' Find all items added on or after 2008-12-01 but before 2009-10-12:: $ beet ls 'added:2008-12..2009-10-11' Find all items with a file modification time between 2008-12-01 and 2008-12-03:: $ beet ls 'mtime:2008-12-01..2008-12-02' You can also add an optional time value to date queries, specifying hours, minutes, and seconds. Times are separated from dates by a space, an uppercase 'T' or a lowercase 't', for example: ``2008-12-01T23:59:59``. If you specify a time, then the date must contain a year, month, and day. The minutes and seconds are optional. Here is an example that finds all items added on 2008-12-01 at or after 22:00 but before 23:00:: $ beet ls 'added:2008-12-01T22' To find all items added on or after 2008-12-01 at 22:45:: $ beet ls 'added:2008-12-01T22:45..' To find all items added on 2008-12-01, at or after 22:45:20 but before 22:45:41:: $ beet ls 'added:2008-12-01T22:45:20..2008-12-01T22:45:40' Here are example of the three ways to separate dates from times. All of these queries do the same thing:: $ beet ls 'added:2008-12-01T22:45:20' $ beet ls 'added:2008-12-01t22:45:20' $ beet ls 'added:2008-12-01 22:45:20' You can also use *relative* dates. For example, ``-3w`` means three weeks ago, and ``+4d`` means four days in the future. A relative date has three parts: - Either ``+`` or ``-``, to indicate the past or the future. The sign is optional; if you leave this off, it defaults to the future. - A number. - A letter indicating the unit: ``d``, ``w``, ``m`` or ``y``, meaning days, weeks, months or years. (A "month" is always 30 days and a "year" is always 365 days.) Here's an example that finds all the albums added since last week:: $ beet ls -a 'added:-1w..' And here's an example that lists items added in a two-week period starting four weeks ago:: $ beet ls 'added:-6w..-4w' .. _not_query: Query Term Negation ------------------- Query terms can also be negated, acting like a Boolean "not," by prefixing them with ``-`` or ``^``. This has the effect of returning all the items that do **not** match the query term. For example, this command:: $ beet list ^love matches all the songs in the library that do not have "love" in any of their fields. Negation can be combined with the rest of the query mechanisms, so you can negate specific fields, regular expressions, etc. For example, this command:: $ beet list -a artist:dylan ^year:1980..1989 "^album::the(y)?" matches all the albums with an artist containing "dylan", but excluding those released in the eighties and those that have "the" or "they" on the title. The syntax supports both ``^`` and ``-`` as synonyms because the latter indicates flags on the command line. To use a minus sign in a command-line query, use a double dash ``--`` to separate the options from the query:: $ beet list -a -- artist:dylan -year:1980..1990 "-album::the(y)?" .. _pathquery: Path Queries ------------ Sometimes it's useful to find all the items in your library that are (recursively) inside a certain directory. Use the ``path:`` field to do this:: $ beet list path:/my/music/directory In fact, beets automatically recognizes any query term containing a path separator (``/`` on POSIX systems) as a path query if that path exists, so this command is equivalent as long as ``/my/music/directory`` exist:: $ beet list /my/music/directory Note that this only matches items that are *already in your library*, so a path query won't necessarily find *all* the audio files in a directory---just the ones you've already added to your beets library. Path queries are case sensitive if the queried path is on a case-sensitive filesystem. .. _query-sort: Sort Order ---------- Queries can specify a sort order. Use the name of the `field` you want to sort on, followed by a ``+`` or ``-`` sign to indicate ascending or descending sort. For example, this command:: $ beet list -a year+ will list all albums in chronological order. You can also specify several sort orders, which will be used in the same order as they appear in your query:: $ beet list -a genre+ year+ This command will sort all albums by genre and, in each genre, in chronological order. The ``artist`` and ``albumartist`` keys are special: they attempt to use their corresponding ``artist_sort`` and ``albumartist_sort`` fields for sorting transparently (but fall back to the ordinary fields when those are empty). Lexicographic sorts are case insensitive by default, resulting in the following sort order: ``Bar foo Qux``. This behavior can be changed with the :ref:`sort_case_insensitive` configuration option. Case sensitive sort will result in lower-case values being placed after upper-case values, e.g., ``Bar Qux foo``. Note that when sorting by fields that are not present on all items (such as flexible fields, or those defined by plugins) in *ascending* order, the items that lack that particular field will be listed at the *beginning* of the list. You can set the default sorting behavior with the :ref:`sort_item` and :ref:`sort_album` configuration options. ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������beetbox-beets-01f1faf/docs/team.rst�����������������������������������������������������������������0000664�0000000�0000000�00000003735�14723254774�0017367�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������Team #### This is an introduction of beets' core-team members, collaborators and frequent contributors. Refer to this list to find out who to ask about your collaboration idea, discuss a usage-question, request a review of your open PR. Beets is a huge project and not everyone involved, knows everything. We hope this helps to point you in the right direction in the first place and should give you an idea of what you can expect from these *knowledge owners*. @arsaboo ======== * The master of the Spotify plugin * Testing out new contributions * beets as a music discovery tool @bal-e ====== * Documentation * The Fish plugin * Type annotations @govynnus ========= * The AURA plugin * The AURA specification * The web plugin * The plugin ecosystem * The library database API and its documentation @jackwilsdon ============ * Broad knowledge around beets' configuration and plugins * Assists in discussion forums frequently * Knows internals of beets and puts new contributors into the right direction @joj0 ===== * The Discogs plugin * Other metadata source plugins * Generalization of source plugin logic (The MetaDataSourcePlugin abstract class) * Good documentation throughout the project * The smartplaylist plugin * Things around m3u and other playlist formats @RollingStar ============ * Data visualization * ListenBrainz / Last.fm * Smart playlists * Library reports * MusicBrainz fields and searching * Project organization and roadmap @sampsyo ======== * The founder * Knows almost everything ;-) @serene-arc =========== * Good documentation throughout the project * Experienced Python developer * Experienced in test-driven-development * Code quality * Typing @wisp3rwind =========== * Mr. Tidy - Keeping the code in shape * Focus on improving core things rather than implementing new features @ybnd ===== * The replaygain plugin * Improving the general parallelism of plugins * Experienced with web scrapers * Experienced with Flask and JavaScript integration * The web plugin �����������������������������������beetbox-beets-01f1faf/extra/������������������������������������������������������������������������0000775�0000000�0000000�00000000000�14723254774�0016072�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������beetbox-beets-01f1faf/extra/_beet�������������������������������������������������������������������0000664�0000000�0000000�00000023644�14723254774�0017104�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#compdef beet # zsh completion for beets music library manager and MusicBrainz tagger: https://beets.io/ # Default values for BEETS_LIBRARY & BEETS_CONFIG needed for the cache checking function. # They will be updated under the assumption that the config file is in the same directory as the library. local BEETS_LIBRARY=~/.config/beets/library.db local BEETS_CONFIG=~/.config/beets/config.yaml # Use separate caches for file locations, command completions, and query completions. # This allows the use of different rules for when to update each one. zstyle ":completion:${curcontext%:*}:*" cache-policy _beet_check_cache zstyle ":completion:${curcontext%:*}:*" use-cache true _beet_check_cache () { local cachefile="$(basename ${1})" if [[ ! -a "${1}" ]] || [[ "${1}" -ot =beet ]]; then # always update the cache if it doesn't exist, or if the beet executable changes return 0 fi case cachefile; in (beetslibrary) if [[ ! -a "${~BEETS_LIBRARY}" ]] || [[ "${1}" -ot "${~BEETS_CONFIG}" ]]; then return 0 fi ;; (beetscmds) _retrieve_cache beetslibrary if [[ "${1}" -ot "${~BEETS_CONFIG}" ]]; then return 0 fi ;; esac return 1 } # useful: argument to _regex_arguments for matching any word local matchany=/$'[^\0]##\0'/ # arguments to _regex_arguments for completing files and directories local -a files dirs files=("$matchany" ':file:file:_files') dirs=("$matchany" ':dir:directory:_dirs') # Retrieve or update caches if ! _retrieve_cache beetslibrary || _cache_invalid beetslibrary; then local BEETS_LIBRARY="${$(beet config|grep library|cut -f 2 -d ' '):-${BEETS_LIBRARY}}" local BEETS_CONFIG="${$(beet config -p):-${BEETS_CONFIG}}" _store_cache beetslibrary BEETS_LIBRARY BEETS_CONFIG fi if ! _retrieve_cache beetscmds || _cache_invalid beetscmds; then local -a subcommands fields beets_regex_words_subcmds beets_regex_words_help query modify local subcmd cmddesc matchquery matchmodify field fieldargs queryelem modifyelem # Useful function for joining grouped lines of output into single lines (taken from _completion_helpers) _join_lines() { awk -v SEP="$1" -v ARG2="$2" -v START="$3" -v END2="$4" 'BEGIN {if(START==""){f=1}{f=0}; if(ARG2 ~ "^[0-9]+"){LINE1 = "^[[:space:]]{0,"ARG2"}[^[:space:]]"}else{LINE1 = ARG2}} ($0 ~ END2 && f>0 && END2!="") {exit} ($0 ~ START && f<1) {f=1; if(length(START)!=0){next}} ($0 ~ LINE1 && f>0) {if(f<2){f=2; printf("%s",$0)}else{printf("\n%s",$0)}; next} (f>1) {gsub(/^[[:space:]]+|[[:space:]]+$/,"",$0); printf("%s%s",SEP, $0); next} END {print ""}' } # Variables used for completing subcommands and queries subcommands=(${${(f)"$(beet help | _join_lines ' ' 3 'Commands:')"}[@]}) fields=($(beet fields | grep -G '^ ' | sort -u | colrm 1 2)) for field in "${fields[@]}" do fieldargs="$fieldargs '$field:::{_beet_field_values $field}'" done queryelem="_values -S : 'query field (add an extra : to match by regexp)' '::' $fieldargs" modifyelem="_values -S = 'modify field (replace = with ! to remove field)' $(echo "'${^fields[@]}:: '")" # regexps for matching query and modify terms on the command line matchquery=/"(${(j/|/)fields[@]})"$':[^\0]##\0'/ matchmodify=/"(${(j/|/)fields[@]})"$'(=[^\0]##|!)\0'/ # create completion function for queries _regex_arguments _beet_query "$matchany" \# \( "$matchquery" ":query:query string:$queryelem" \) \# local "beets_query"="$(which _beet_query)" # arguments for _regex_arguments for completing lists of queries and modifications beets_query_args=( \( "$matchquery" ":query:query string:{_beet_query}" \) \# ) beets_modify_args=( \( "$matchmodify" ":modify:modify string:$modifyelem" \) \# ) # now build arguments for _beet and _beet_help completion functions beets_regex_words_subcmds=('(') for i in ${subcommands[@]}; do subcmd="${i[(w)1]}" # remove first word and parenthesised alias, replace : with -, [ with (, ] with ), and remove single quotes cmddesc="${${${${${i[(w)2,-1]##\(*\) #}//:/-}//\[/(}//\]/)}//\'/}" # update arguments needed for creating _beet beets_regex_words_subcmds+=(/"${subcmd}"$'\0'/ ":subcmds:subcommands:((${subcmd}:${cmddesc// /\ }))") beets_regex_words_subcmds+=(\( "${matchany}" ":option:option:{_beet_subcmd ${subcmd}}" \) \# \|) # update arguments needed for creating _beet_help beets_regex_words_help+=("${subcmd}:${cmddesc}") done beets_regex_words_subcmds[-1]=')' _store_cache beetscmds beets_regex_words_subcmds beets_regex_words_help beets_query_args beets_modify_args beets_query else # Evaluate the variable containing the query completer function eval "${beets_query}" fi # Function for getting unique values for field from database (you may need to change the path to the database). _beet_field_values() { local -a output fieldvals local sqlcmd="select distinct $1 from items;" _retrieve_cache beetslibrary case ${1} in lyrics) fieldvals= ;; *) if [[ "$(sqlite3 ${~BEETS_LIBRARY} ${sqlcmd} 2>&1)" =~ "no such column" ]]; then sqlcmd="select distinct value from item_attributes where key=='$1' and value!='';" fi output="$(sqlite3 -list -noheader ${~BEETS_LIBRARY} ${sqlcmd} 2>/dev/null)" fieldvals=("${(f)output[@]}") ;; esac compadd -P \" -S \" -M 'm:{[:lower:][:upper:]}={[:upper:][:lower:]}' -Q -a fieldvals } # This function takes a beet subcommand as its first argument, and then uses _regex_words to set ${reply[@]} # to an array containing arguments for the _regex_arguments function. _beet_subcmd_options() { local shortopt optarg optdesc local matchany=/$'[^\0]##\0'/ local -a regex_words regex_words=() for i in ${${(f)"$(beet help $1 | awk '/^ +-/{if(x)print x;x=$0;next}/^ *$/{if(x) exit}{if(x) x=x$0}END{print x}')"}[@]} do opt="${i[(w)1]/,/}" optarg="${${${i## #[-a-zA-Z]# }##[- ]##*}%%[, ]*}" optdesc="${${${${${i[(w)2,-1]/[A-Z, ]#--[-a-z]##[=A-Z]# #/}//:/-}//\[/(}//\]/)}//\'/}" case $optarg; in ("") if [[ "$1" == "import" && "$opt" == "-L" ]]; then regex_words+=("$opt:$optdesc:\${beets_query_args[@]}") else regex_words+=("$opt:$optdesc") fi ;; (LOG) local -a files files=("$matchany" ':file:file:_files') regex_words+=("$opt:$optdesc:\$files") ;; (CONFIG) local -a configfile configfile=("$matchany" ':file:config file:{_files -g *.yaml}') regex_words+=("$opt:$optdesc:\$configfile") ;; (LIB|LIBRARY) local -a libfile libfile=("$matchany" ':file:database file:{_files -g *.db}') regex_words+=("$opt:$optdesc:\$libfile") ;; (DIR|DIRECTORY|DEST) local -a dirs dirs=("$matchany" ':dir:directory:_dirs') regex_words+=("$opt:$optdesc:\$dirs") ;; (SOURCE) if [[ "${1}" -eq lastgenre ]]; then local -a lastgenresource lastgenresource=(/$'(artist|album|track)\0'/ ':source:genre source:(artist album track)') regex_words+=("$opt:$optdesc:\$lastgenresource") else regex_words+=("$opt:$optdesc:\$matchany") fi ;; (*) regex_words+=("$opt:$optdesc:\$matchany") ;; esac done _regex_words options "$1 options" "${regex_words[@]}" } ## Function for completing subcommands. It calls another completion function which is first created if it doesn't already exist. _beet_subcmd() { local -a options local subcmd="${1}" if [[ ! $(type _beet_${subcmd} | grep function) =~ function ]]; then if ! _retrieve_cache "beets${subcmd}" || _cache_invalid "beets${subcmd}"; then local matchany=/$'[^\0]##\0'/ local -a files files=("$matchany" ':file:file:_files') # get arguments for completing subcommand options _beet_subcmd_options "$subcmd" options=("${reply[@]}" \#) _retrieve_cache beetscmds case ${subcmd}; in (import) _regex_arguments _beet_import "${matchany}" /"${subcmd}"$'\0'/ "${options[@]}" "${files[@]}" \# ;; (modify) _regex_arguments _beet_modify "${matchany}" /"${subcmd}"$'\0'/ "${options[@]}" \ "${beets_query_args[@]}" "${beets_modify_args[@]}" ;; (fields|migrate|version|config) _regex_arguments _beet_${subcmd} "${matchany}" /"${subcmd}"$'\0'/ "${options[@]}" ;; (help) _regex_words subcmds "subcommands" "${beets_regex_words_help[@]}" _regex_arguments _beet_help "${matchany}" /$'help\0'/ "${options[@]}" "${reply[@]}" ;; (*) # Other commands have options followed by a query _regex_arguments _beet_${subcmd} "${matchany}" /"${subcmd}"$'\0'/ "${options[@]}" "${beets_query_args[@]}" ;; esac # Store completion function in a cache file local "beets_${subcmd}"="$(which _beet_${subcmd})" _store_cache "beets${subcmd}" "beets_${subcmd}" else # Evaluate the function which is stored in $beets_${subcmd} local var="beets_${subcmd}" eval "${(P)var}" fi fi _beet_${subcmd} } # Global options local -a globalopts _regex_words options "global options" '-c:path to configuration file:$files' '-v:print debugging information' \ '-l:library database file to use:$files' '-h:show this help message and exit' '-d:destination music directory:$dirs' globalopts=("${reply[@]}") # Create main completion function _regex_arguments _beet "$matchany" \( "${globalopts[@]}" \# \) "${beets_regex_words_subcmds[@]}" # Set tag-order so that options are completed separately from arguments zstyle ":completion:${curcontext}:" tag-order '! options' # Execute the completion function _beet "$@" # Local Variables: # mode:shell-script # End: ��������������������������������������������������������������������������������������������beetbox-beets-01f1faf/extra/ascii_logo.txt����������������������������������������������������������0000664�0000000�0000000�00000000347�14723254774�0020747�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������[][][][] [][] [] [][][][] [][] [] [][][][] [][][][] [][][][] [][] [] [][] [] [][][][] [][][][] [][] [][] [][][][] [][][][] [][][][] [][][][] [][][][] [][] [][] [][] [][] [][] [][] [][][][] �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������beetbox-beets-01f1faf/extra/beets.reg���������������������������������������������������������������0000664�0000000�0000000�00000000720�14723254774�0017672�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������˙ţW�i�n�d�o�w�s� �R�e�g�i�s�t�r�y� �E�d�i�t�o�r� �V�e�r�s�i�o�n� �5�.�0�0� � � � �[�H�K�E�Y�_�C�L�A�S�S�E�S�_�R�O�O�T�\�D�i�r�e�c�t�o�r�y�\�s�h�e�l�l�\�b�e�e�t�s�]� � �@�=�"�I�m�p�o�r�t� �w�i�t�h� �b�e�e�t�s�"� � � � �[�H�K�E�Y�_�C�L�A�S�S�E�S�_�R�O�O�T�\�D�i�r�e�c�t�o�r�y�\�s�h�e�l�l�\�b�e�e�t�s�\�c�o�m�m�a�n�d�]� � �@�=�"�\�"�C�:�\�\�P�r�o�g�r�a�m� �F�i�l�e�s�\�\�P�y�t�h�o�n�3�8�\�\�S�c�r�i�p�t�s�\�\�b�e�e�t�.�e�x�e�\�"� �i�m�p�o�r�t� �\�"�%�1�\�"�"� � � � �������������������������������������������������beetbox-beets-01f1faf/extra/release.py��������������������������������������������������������������0000775�0000000�0000000�00000010250�14723254774�0020065�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#!/usr/bin/env python3 """A utility script for automating the beets release process.""" from __future__ import annotations import re import subprocess from datetime import datetime, timezone from pathlib import Path from typing import Callable import click import tomli from packaging.version import Version, parse BASE = Path(__file__).parent.parent.absolute() PYPROJECT = BASE / "pyproject.toml" CHANGELOG = BASE / "docs" / "changelog.rst" MD_CHANGELOG_SECTION_LIST = re.compile(r"- .+?(?=\n\n###|$)", re.DOTALL) version_header = r"\d+\.\d+\.\d+ \([^)]+\)" RST_LATEST_CHANGES = re.compile( rf"{version_header}\n--+\s+(.+?)\n\n+{version_header}", re.DOTALL ) def update_docs_config(text: str, new: Version) -> str: new_major_minor = f"{new.major}.{new.minor}" text = re.sub(r"(?<=version = )[^\n]+", f'"{new_major_minor}"', text) return re.sub(r"(?<=release = )[^\n]+", f'"{new}"', text) def update_changelog(text: str, new: Version) -> str: new_header = f"{new} ({datetime.now(timezone.utc).date():%B %d, %Y})" return re.sub( # do not match if the new version is already present r"\nUnreleased\n--+\n", rf""" Unreleased ---------- New features: Bug fixes: For packagers: Other changes: {new_header} {'-' * len(new_header)} """, text, ) UpdateVersionCallable = Callable[[str, Version], str] FILENAME_AND_UPDATE_TEXT: list[tuple[Path, UpdateVersionCallable]] = [ ( PYPROJECT, lambda text, new: re.sub(r"(?<=\nversion = )[^\n]+", f'"{new}"', text), ), ( BASE / "beets" / "__init__.py", lambda text, new: re.sub( r"(?<=__version__ = )[^\n]+", f'"{new}"', text ), ), (CHANGELOG, update_changelog), (BASE / "docs" / "conf.py", update_docs_config), ] def validate_new_version( ctx: click.Context, param: click.Argument, value: Version ) -> Version: """Validate the version is newer than the current one.""" with PYPROJECT.open("rb") as f: current = parse(tomli.load(f)["tool"]["poetry"]["version"]) if not value > current: msg = f"version must be newer than {current}" raise click.BadParameter(msg) return value def bump_version(new: Version) -> None: """Update the version number in specified files.""" for path, perform_update in FILENAME_AND_UPDATE_TEXT: with path.open("r+") as f: contents = f.read() f.seek(0) f.write(perform_update(contents, new)) f.truncate() def rst2md(text: str) -> str: """Use Pandoc to convert text from ReST to Markdown.""" # Other backslashes with verbatim ranges. rst = re.sub(r"(?<=[\s(])`([^`]+)`(?=[^_])", r"``\1``", text) # Bug numbers. rst = re.sub(r":bug:`(\d+)`", r":bug: (#\1)", rst) # Users. rst = re.sub(r":user:`(\w+)`", r"@\1", rst) return ( subprocess.check_output( ["/usr/bin/pandoc", "--from=rst", "--to=gfm", "--wrap=none"], input=rst.encode(), ) .decode() .strip() ) def changelog_as_markdown() -> str: """Get the latest changelog entry as hacked up Markdown.""" with CHANGELOG.open() as f: contents = f.read() m = RST_LATEST_CHANGES.search(contents) rst = m.group(1) if m else "" # Convert with Pandoc. md = rst2md(rst) # Make sections stand out md = re.sub(r"^(\w.+?):$", r"### \1", md, flags=re.M) # Highlight plugin names md = re.sub( r"^- `/?plugins/(\w+)`:?", r"- Plugin **`\1`**:", md, flags=re.M ) # Highlights command names. md = re.sub(r"^- `(\w+)-cmd`:?", r"- Command **`\1`**:", md, flags=re.M) # sort list items alphabetically for each of the sections return MD_CHANGELOG_SECTION_LIST.sub( lambda m: "\n".join(sorted(m.group().splitlines())), md ) @click.group() def cli(): pass @cli.command() @click.argument("version", type=Version, callback=validate_new_version) def bump(version: Version) -> None: """Bump the version in project files.""" bump_version(version) @cli.command() def changelog(): """Get the most recent version's changelog as Markdown.""" print(changelog_as_markdown()) if __name__ == "__main__": cli() ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������beetbox-beets-01f1faf/poetry.lock�������������������������������������������������������������������0000664�0000000�0000000�00001040361�14723254774�0017150�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# This file is automatically @generated by Poetry 1.8.4 and should not be changed by hand. [[package]] name = "accessible-pygments" version = "0.0.4" description = "A collection of accessible pygments styles" optional = true python-versions = "*" files = [ {file = "accessible-pygments-0.0.4.tar.gz", hash = "sha256:e7b57a9b15958e9601c7e9eb07a440c813283545a20973f2574a5f453d0e953e"}, {file = "accessible_pygments-0.0.4-py2.py3-none-any.whl", hash = "sha256:416c6d8c1ea1c5ad8701903a20fcedf953c6e720d64f33dc47bfb2d3f2fa4e8d"}, ] [package.dependencies] pygments = ">=1.5" [[package]] name = "alabaster" version = "0.7.13" description = "A configurable sidebar-enabled Sphinx theme" optional = true python-versions = ">=3.6" files = [ {file = "alabaster-0.7.13-py3-none-any.whl", hash = "sha256:1ee19aca801bbabb5ba3f5f258e4422dfa86f82f3e9cefb0859b283cdd7f62a3"}, {file = "alabaster-0.7.13.tar.gz", hash = "sha256:a27a4a084d5e690e16e01e03ad2b2e552c61a65469419b907243193de1a84ae2"}, ] [[package]] name = "anyio" version = "4.5.2" description = "High level compatibility layer for multiple asynchronous event loop implementations" optional = false python-versions = ">=3.8" files = [ {file = "anyio-4.5.2-py3-none-any.whl", hash = "sha256:c011ee36bc1e8ba40e5a81cb9df91925c218fe9b778554e0b56a21e1b5d4716f"}, {file = "anyio-4.5.2.tar.gz", hash = "sha256:23009af4ed04ce05991845451e11ef02fc7c5ed29179ac9a420e5ad0ac7ddc5b"}, ] [package.dependencies] exceptiongroup = {version = ">=1.0.2", markers = "python_version < \"3.11\""} idna = ">=2.8" sniffio = ">=1.1" typing-extensions = {version = ">=4.1", markers = "python_version < \"3.11\""} [package.extras] doc = ["Sphinx (>=7.4,<8.0)", "packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx-rtd-theme"] test = ["anyio[trio]", "coverage[toml] (>=7)", "exceptiongroup (>=1.2.0)", "hypothesis (>=4.0)", "psutil (>=5.9)", "pytest (>=7.0)", "pytest-mock (>=3.6.1)", "trustme", "truststore (>=0.9.1)", "uvloop (>=0.21.0b1)"] trio = ["trio (>=0.26.1)"] [[package]] name = "appdirs" version = "1.4.4" description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." optional = true python-versions = "*" files = [ {file = "appdirs-1.4.4-py2.py3-none-any.whl", hash = "sha256:a841dacd6b99318a741b166adb07e19ee71a274450e68237b4650ca1055ab128"}, {file = "appdirs-1.4.4.tar.gz", hash = "sha256:7d5d0167b2b1ba821647616af46a749d1c653740dd0d2415100fe26e27afdf41"}, ] [[package]] name = "audioread" version = "3.0.1" description = "Multi-library, cross-platform audio decoding." optional = true python-versions = ">=3.6" files = [ {file = "audioread-3.0.1-py3-none-any.whl", hash = "sha256:4cdce70b8adc0da0a3c9e0d85fb10b3ace30fbdf8d1670fd443929b61d117c33"}, {file = "audioread-3.0.1.tar.gz", hash = "sha256:ac5460a5498c48bdf2e8e767402583a4dcd13f4414d286f42ce4379e8b35066d"}, ] [package.extras] test = ["tox"] [[package]] name = "babel" version = "2.16.0" description = "Internationalization utilities" optional = true python-versions = ">=3.8" files = [ {file = "babel-2.16.0-py3-none-any.whl", hash = "sha256:368b5b98b37c06b7daf6696391c3240c938b37767d4584413e8438c5c435fa8b"}, {file = "babel-2.16.0.tar.gz", hash = "sha256:d1f3554ca26605fe173f3de0c65f750f5a42f924499bf134de6423582298e316"}, ] [package.dependencies] pytz = {version = ">=2015.7", markers = "python_version < \"3.9\""} [package.extras] dev = ["freezegun (>=1.0,<2.0)", "pytest (>=6.0)", "pytest-cov"] [[package]] name = "beautifulsoup4" version = "4.12.3" description = "Screen-scraping library" optional = false python-versions = ">=3.6.0" files = [ {file = "beautifulsoup4-4.12.3-py3-none-any.whl", hash = "sha256:b80878c9f40111313e55da8ba20bdba06d8fa3969fc68304167741bbf9e082ed"}, {file = "beautifulsoup4-4.12.3.tar.gz", hash = "sha256:74e3d1928edc070d21748185c46e3fb33490f22f52a3addee9aee0f4f7781051"}, ] [package.dependencies] soupsieve = ">1.2" [package.extras] cchardet = ["cchardet"] chardet = ["chardet"] charset-normalizer = ["charset-normalizer"] html5lib = ["html5lib"] lxml = ["lxml"] [[package]] name = "blinker" version = "1.8.2" description = "Fast, simple object-to-object and broadcast signaling" optional = false python-versions = ">=3.8" files = [ {file = "blinker-1.8.2-py3-none-any.whl", hash = "sha256:1779309f71bf239144b9399d06ae925637cf6634cf6bd131104184531bf67c01"}, {file = "blinker-1.8.2.tar.gz", hash = "sha256:8f77b09d3bf7c795e969e9486f39c2c5e9c39d4ee07424be2bc594ece9642d83"}, ] [[package]] name = "brotli" version = "1.1.0" description = "Python bindings for the Brotli compression library" optional = false python-versions = "*" files = [ {file = "Brotli-1.1.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:e1140c64812cb9b06c922e77f1c26a75ec5e3f0fb2bf92cc8c58720dec276752"}, {file = "Brotli-1.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c8fd5270e906eef71d4a8d19b7c6a43760c6abcfcc10c9101d14eb2357418de9"}, {file = "Brotli-1.1.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1ae56aca0402a0f9a3431cddda62ad71666ca9d4dc3a10a142b9dce2e3c0cda3"}, {file = "Brotli-1.1.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:43ce1b9935bfa1ede40028054d7f48b5469cd02733a365eec8a329ffd342915d"}, {file = "Brotli-1.1.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:7c4855522edb2e6ae7fdb58e07c3ba9111e7621a8956f481c68d5d979c93032e"}, {file = "Brotli-1.1.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:38025d9f30cf4634f8309c6874ef871b841eb3c347e90b0851f63d1ded5212da"}, {file = "Brotli-1.1.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:e6a904cb26bfefc2f0a6f240bdf5233be78cd2488900a2f846f3c3ac8489ab80"}, {file = "Brotli-1.1.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:a37b8f0391212d29b3a91a799c8e4a2855e0576911cdfb2515487e30e322253d"}, {file = "Brotli-1.1.0-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:e84799f09591700a4154154cab9787452925578841a94321d5ee8fb9a9a328f0"}, {file = "Brotli-1.1.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:f66b5337fa213f1da0d9000bc8dc0cb5b896b726eefd9c6046f699b169c41b9e"}, {file = "Brotli-1.1.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:5dab0844f2cf82be357a0eb11a9087f70c5430b2c241493fc122bb6f2bb0917c"}, {file = "Brotli-1.1.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e4fe605b917c70283db7dfe5ada75e04561479075761a0b3866c081d035b01c1"}, {file = "Brotli-1.1.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:1e9a65b5736232e7a7f91ff3d02277f11d339bf34099a56cdab6a8b3410a02b2"}, {file = "Brotli-1.1.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:58d4b711689366d4a03ac7957ab8c28890415e267f9b6589969e74b6e42225ec"}, {file = "Brotli-1.1.0-cp310-cp310-win32.whl", hash = "sha256:be36e3d172dc816333f33520154d708a2657ea63762ec16b62ece02ab5e4daf2"}, {file = "Brotli-1.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:0c6244521dda65ea562d5a69b9a26120769b7a9fb3db2fe9545935ed6735b128"}, {file = "Brotli-1.1.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:a3daabb76a78f829cafc365531c972016e4aa8d5b4bf60660ad8ecee19df7ccc"}, {file = "Brotli-1.1.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c8146669223164fc87a7e3de9f81e9423c67a79d6b3447994dfb9c95da16e2d6"}, {file = "Brotli-1.1.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:30924eb4c57903d5a7526b08ef4a584acc22ab1ffa085faceb521521d2de32dd"}, {file = "Brotli-1.1.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ceb64bbc6eac5a140ca649003756940f8d6a7c444a68af170b3187623b43bebf"}, {file = "Brotli-1.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a469274ad18dc0e4d316eefa616d1d0c2ff9da369af19fa6f3daa4f09671fd61"}, {file = "Brotli-1.1.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:524f35912131cc2cabb00edfd8d573b07f2d9f21fa824bd3fb19725a9cf06327"}, {file = "Brotli-1.1.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:5b3cc074004d968722f51e550b41a27be656ec48f8afaeeb45ebf65b561481dd"}, {file = "Brotli-1.1.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:19c116e796420b0cee3da1ccec3b764ed2952ccfcc298b55a10e5610ad7885f9"}, {file = "Brotli-1.1.0-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:510b5b1bfbe20e1a7b3baf5fed9e9451873559a976c1a78eebaa3b86c57b4265"}, {file = "Brotli-1.1.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:a1fd8a29719ccce974d523580987b7f8229aeace506952fa9ce1d53a033873c8"}, {file = "Brotli-1.1.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c247dd99d39e0338a604f8c2b3bc7061d5c2e9e2ac7ba9cc1be5a69cb6cd832f"}, {file = "Brotli-1.1.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:1b2c248cd517c222d89e74669a4adfa5577e06ab68771a529060cf5a156e9757"}, {file = "Brotli-1.1.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:2a24c50840d89ded6c9a8fdc7b6ed3692ed4e86f1c4a4a938e1e92def92933e0"}, {file = "Brotli-1.1.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f31859074d57b4639318523d6ffdca586ace54271a73ad23ad021acd807eb14b"}, {file = "Brotli-1.1.0-cp311-cp311-win32.whl", hash = "sha256:39da8adedf6942d76dc3e46653e52df937a3c4d6d18fdc94a7c29d263b1f5b50"}, {file = "Brotli-1.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:aac0411d20e345dc0920bdec5548e438e999ff68d77564d5e9463a7ca9d3e7b1"}, {file = "Brotli-1.1.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:32d95b80260d79926f5fab3c41701dbb818fde1c9da590e77e571eefd14abe28"}, {file = "Brotli-1.1.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b760c65308ff1e462f65d69c12e4ae085cff3b332d894637f6273a12a482d09f"}, {file = "Brotli-1.1.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:316cc9b17edf613ac76b1f1f305d2a748f1b976b033b049a6ecdfd5612c70409"}, {file = "Brotli-1.1.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:caf9ee9a5775f3111642d33b86237b05808dafcd6268faa492250e9b78046eb2"}, {file = "Brotli-1.1.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:70051525001750221daa10907c77830bc889cb6d865cc0b813d9db7fefc21451"}, {file = "Brotli-1.1.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7f4bf76817c14aa98cc6697ac02f3972cb8c3da93e9ef16b9c66573a68014f91"}, {file = "Brotli-1.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d0c5516f0aed654134a2fc936325cc2e642f8a0e096d075209672eb321cff408"}, {file = "Brotli-1.1.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6c3020404e0b5eefd7c9485ccf8393cfb75ec38ce75586e046573c9dc29967a0"}, {file = "Brotli-1.1.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:4ed11165dd45ce798d99a136808a794a748d5dc38511303239d4e2363c0695dc"}, {file = "Brotli-1.1.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:4093c631e96fdd49e0377a9c167bfd75b6d0bad2ace734c6eb20b348bc3ea180"}, {file = "Brotli-1.1.0-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:7e4c4629ddad63006efa0ef968c8e4751c5868ff0b1c5c40f76524e894c50248"}, {file = "Brotli-1.1.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:861bf317735688269936f755fa136a99d1ed526883859f86e41a5d43c61d8966"}, {file = "Brotli-1.1.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:87a3044c3a35055527ac75e419dfa9f4f3667a1e887ee80360589eb8c90aabb9"}, {file = "Brotli-1.1.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:c5529b34c1c9d937168297f2c1fde7ebe9ebdd5e121297ff9c043bdb2ae3d6fb"}, {file = "Brotli-1.1.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:ca63e1890ede90b2e4454f9a65135a4d387a4585ff8282bb72964fab893f2111"}, {file = "Brotli-1.1.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e79e6520141d792237c70bcd7a3b122d00f2613769ae0cb61c52e89fd3443839"}, {file = "Brotli-1.1.0-cp312-cp312-win32.whl", hash = "sha256:5f4d5ea15c9382135076d2fb28dde923352fe02951e66935a9efaac8f10e81b0"}, {file = "Brotli-1.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:906bc3a79de8c4ae5b86d3d75a8b77e44404b0f4261714306e3ad248d8ab0951"}, {file = "Brotli-1.1.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:8bf32b98b75c13ec7cf774164172683d6e7891088f6316e54425fde1efc276d5"}, {file = "Brotli-1.1.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:7bc37c4d6b87fb1017ea28c9508b36bbcb0c3d18b4260fcdf08b200c74a6aee8"}, {file = "Brotli-1.1.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3c0ef38c7a7014ffac184db9e04debe495d317cc9c6fb10071f7fefd93100a4f"}, {file = "Brotli-1.1.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:91d7cc2a76b5567591d12c01f019dd7afce6ba8cba6571187e21e2fc418ae648"}, {file = "Brotli-1.1.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a93dde851926f4f2678e704fadeb39e16c35d8baebd5252c9fd94ce8ce68c4a0"}, {file = "Brotli-1.1.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f0db75f47be8b8abc8d9e31bc7aad0547ca26f24a54e6fd10231d623f183d089"}, {file = "Brotli-1.1.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:6967ced6730aed543b8673008b5a391c3b1076d834ca438bbd70635c73775368"}, {file = "Brotli-1.1.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:7eedaa5d036d9336c95915035fb57422054014ebdeb6f3b42eac809928e40d0c"}, {file = "Brotli-1.1.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:d487f5432bf35b60ed625d7e1b448e2dc855422e87469e3f450aa5552b0eb284"}, {file = "Brotli-1.1.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:832436e59afb93e1836081a20f324cb185836c617659b07b129141a8426973c7"}, {file = "Brotli-1.1.0-cp313-cp313-win32.whl", hash = "sha256:43395e90523f9c23a3d5bdf004733246fba087f2948f87ab28015f12359ca6a0"}, {file = "Brotli-1.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:9011560a466d2eb3f5a6e4929cf4a09be405c64154e12df0dd72713f6500e32b"}, {file = "Brotli-1.1.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:a090ca607cbb6a34b0391776f0cb48062081f5f60ddcce5d11838e67a01928d1"}, {file = "Brotli-1.1.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2de9d02f5bda03d27ede52e8cfe7b865b066fa49258cbab568720aa5be80a47d"}, {file = "Brotli-1.1.0-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2333e30a5e00fe0fe55903c8832e08ee9c3b1382aacf4db26664a16528d51b4b"}, {file = "Brotli-1.1.0-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4d4a848d1837973bf0f4b5e54e3bec977d99be36a7895c61abb659301b02c112"}, {file = "Brotli-1.1.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:fdc3ff3bfccdc6b9cc7c342c03aa2400683f0cb891d46e94b64a197910dc4064"}, {file = "Brotli-1.1.0-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:5eeb539606f18a0b232d4ba45adccde4125592f3f636a6182b4a8a436548b914"}, {file = "Brotli-1.1.0-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:fd5f17ff8f14003595ab414e45fce13d073e0762394f957182e69035c9f3d7c2"}, {file = "Brotli-1.1.0-cp36-cp36m-musllinux_1_1_ppc64le.whl", hash = "sha256:069a121ac97412d1fe506da790b3e69f52254b9df4eb665cd42460c837193354"}, {file = "Brotli-1.1.0-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:e93dfc1a1165e385cc8239fab7c036fb2cd8093728cbd85097b284d7b99249a2"}, {file = "Brotli-1.1.0-cp36-cp36m-musllinux_1_2_aarch64.whl", hash = "sha256:aea440a510e14e818e67bfc4027880e2fb500c2ccb20ab21c7a7c8b5b4703d75"}, {file = "Brotli-1.1.0-cp36-cp36m-musllinux_1_2_i686.whl", hash = "sha256:6974f52a02321b36847cd19d1b8e381bf39939c21efd6ee2fc13a28b0d99348c"}, {file = "Brotli-1.1.0-cp36-cp36m-musllinux_1_2_ppc64le.whl", hash = "sha256:a7e53012d2853a07a4a79c00643832161a910674a893d296c9f1259859a289d2"}, {file = "Brotli-1.1.0-cp36-cp36m-musllinux_1_2_x86_64.whl", hash = "sha256:d7702622a8b40c49bffb46e1e3ba2e81268d5c04a34f460978c6b5517a34dd52"}, {file = "Brotli-1.1.0-cp36-cp36m-win32.whl", hash = "sha256:a599669fd7c47233438a56936988a2478685e74854088ef5293802123b5b2460"}, {file = "Brotli-1.1.0-cp36-cp36m-win_amd64.whl", hash = "sha256:d143fd47fad1db3d7c27a1b1d66162e855b5d50a89666af46e1679c496e8e579"}, {file = "Brotli-1.1.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:11d00ed0a83fa22d29bc6b64ef636c4552ebafcef57154b4ddd132f5638fbd1c"}, {file = "Brotli-1.1.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f733d788519c7e3e71f0855c96618720f5d3d60c3cb829d8bbb722dddce37985"}, {file = "Brotli-1.1.0-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:929811df5462e182b13920da56c6e0284af407d1de637d8e536c5cd00a7daf60"}, {file = "Brotli-1.1.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:0b63b949ff929fbc2d6d3ce0e924c9b93c9785d877a21a1b678877ffbbc4423a"}, {file = "Brotli-1.1.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:d192f0f30804e55db0d0e0a35d83a9fead0e9a359a9ed0285dbacea60cc10a84"}, {file = "Brotli-1.1.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:f296c40e23065d0d6650c4aefe7470d2a25fffda489bcc3eb66083f3ac9f6643"}, {file = "Brotli-1.1.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:919e32f147ae93a09fe064d77d5ebf4e35502a8df75c29fb05788528e330fe74"}, {file = "Brotli-1.1.0-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:23032ae55523cc7bccb4f6a0bf368cd25ad9bcdcc1990b64a647e7bbcce9cb5b"}, {file = "Brotli-1.1.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:224e57f6eac61cc449f498cc5f0e1725ba2071a3d4f48d5d9dffba42db196438"}, {file = "Brotli-1.1.0-cp37-cp37m-musllinux_1_2_aarch64.whl", hash = "sha256:cb1dac1770878ade83f2ccdf7d25e494f05c9165f5246b46a621cc849341dc01"}, {file = "Brotli-1.1.0-cp37-cp37m-musllinux_1_2_i686.whl", hash = "sha256:3ee8a80d67a4334482d9712b8e83ca6b1d9bc7e351931252ebef5d8f7335a547"}, {file = "Brotli-1.1.0-cp37-cp37m-musllinux_1_2_ppc64le.whl", hash = "sha256:5e55da2c8724191e5b557f8e18943b1b4839b8efc3ef60d65985bcf6f587dd38"}, {file = "Brotli-1.1.0-cp37-cp37m-musllinux_1_2_x86_64.whl", hash = "sha256:d342778ef319e1026af243ed0a07c97acf3bad33b9f29e7ae6a1f68fd083e90c"}, {file = "Brotli-1.1.0-cp37-cp37m-win32.whl", hash = "sha256:587ca6d3cef6e4e868102672d3bd9dc9698c309ba56d41c2b9c85bbb903cdb95"}, {file = "Brotli-1.1.0-cp37-cp37m-win_amd64.whl", hash = "sha256:2954c1c23f81c2eaf0b0717d9380bd348578a94161a65b3a2afc62c86467dd68"}, {file = "Brotli-1.1.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:efa8b278894b14d6da122a72fefcebc28445f2d3f880ac59d46c90f4c13be9a3"}, {file = "Brotli-1.1.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:03d20af184290887bdea3f0f78c4f737d126c74dc2f3ccadf07e54ceca3bf208"}, {file = "Brotli-1.1.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6172447e1b368dcbc458925e5ddaf9113477b0ed542df258d84fa28fc45ceea7"}, {file = "Brotli-1.1.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a743e5a28af5f70f9c080380a5f908d4d21d40e8f0e0c8901604d15cfa9ba751"}, {file = "Brotli-1.1.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:0541e747cce78e24ea12d69176f6a7ddb690e62c425e01d31cc065e69ce55b48"}, {file = "Brotli-1.1.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:cdbc1fc1bc0bff1cef838eafe581b55bfbffaed4ed0318b724d0b71d4d377619"}, {file = "Brotli-1.1.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:890b5a14ce214389b2cc36ce82f3093f96f4cc730c1cffdbefff77a7c71f2a97"}, {file = "Brotli-1.1.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:1ab4fbee0b2d9098c74f3057b2bc055a8bd92ccf02f65944a241b4349229185a"}, {file = "Brotli-1.1.0-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:141bd4d93984070e097521ed07e2575b46f817d08f9fa42b16b9b5f27b5ac088"}, {file = "Brotli-1.1.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:fce1473f3ccc4187f75b4690cfc922628aed4d3dd013d047f95a9b3919a86596"}, {file = "Brotli-1.1.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:d2b35ca2c7f81d173d2fadc2f4f31e88cc5f7a39ae5b6db5513cf3383b0e0ec7"}, {file = "Brotli-1.1.0-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:af6fa6817889314555aede9a919612b23739395ce767fe7fcbea9a80bf140fe5"}, {file = "Brotli-1.1.0-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:2feb1d960f760a575dbc5ab3b1c00504b24caaf6986e2dc2b01c09c87866a943"}, {file = "Brotli-1.1.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:4410f84b33374409552ac9b6903507cdb31cd30d2501fc5ca13d18f73548444a"}, {file = "Brotli-1.1.0-cp38-cp38-win32.whl", hash = "sha256:db85ecf4e609a48f4b29055f1e144231b90edc90af7481aa731ba2d059226b1b"}, {file = "Brotli-1.1.0-cp38-cp38-win_amd64.whl", hash = "sha256:3d7954194c36e304e1523f55d7042c59dc53ec20dd4e9ea9d151f1b62b4415c0"}, {file = "Brotli-1.1.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:5fb2ce4b8045c78ebbc7b8f3c15062e435d47e7393cc57c25115cfd49883747a"}, {file = "Brotli-1.1.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:7905193081db9bfa73b1219140b3d315831cbff0d8941f22da695832f0dd188f"}, {file = "Brotli-1.1.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a77def80806c421b4b0af06f45d65a136e7ac0bdca3c09d9e2ea4e515367c7e9"}, {file = "Brotli-1.1.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8dadd1314583ec0bf2d1379f7008ad627cd6336625d6679cf2f8e67081b83acf"}, {file = "Brotli-1.1.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:901032ff242d479a0efa956d853d16875d42157f98951c0230f69e69f9c09bac"}, {file = "Brotli-1.1.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:22fc2a8549ffe699bfba2256ab2ed0421a7b8fadff114a3d201794e45a9ff578"}, {file = "Brotli-1.1.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:ae15b066e5ad21366600ebec29a7ccbc86812ed267e4b28e860b8ca16a2bc474"}, {file = "Brotli-1.1.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:949f3b7c29912693cee0afcf09acd6ebc04c57af949d9bf77d6101ebb61e388c"}, {file = "Brotli-1.1.0-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:89f4988c7203739d48c6f806f1e87a1d96e0806d44f0fba61dba81392c9e474d"}, {file = "Brotli-1.1.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:de6551e370ef19f8de1807d0a9aa2cdfdce2e85ce88b122fe9f6b2b076837e59"}, {file = "Brotli-1.1.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:0737ddb3068957cf1b054899b0883830bb1fec522ec76b1098f9b6e0f02d9419"}, {file = "Brotli-1.1.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:4f3607b129417e111e30637af1b56f24f7a49e64763253bbc275c75fa887d4b2"}, {file = "Brotli-1.1.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:6c6e0c425f22c1c719c42670d561ad682f7bfeeef918edea971a79ac5252437f"}, {file = "Brotli-1.1.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:494994f807ba0b92092a163a0a283961369a65f6cbe01e8891132b7a320e61eb"}, {file = "Brotli-1.1.0-cp39-cp39-win32.whl", hash = "sha256:f0d8a7a6b5983c2496e364b969f0e526647a06b075d034f3297dc66f3b360c64"}, {file = "Brotli-1.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:cdad5b9014d83ca68c25d2e9444e28e967ef16e80f6b436918c700c117a85467"}, {file = "Brotli-1.1.0.tar.gz", hash = "sha256:81de08ac11bcb85841e440c13611c00b67d3bf82698314928d0b676362546724"}, ] [[package]] name = "brotlicffi" version = "1.1.0.0" description = "Python CFFI bindings to the Brotli library" optional = false python-versions = ">=3.7" files = [ {file = "brotlicffi-1.1.0.0-cp37-abi3-macosx_10_9_x86_64.whl", hash = "sha256:9b7ae6bd1a3f0df532b6d67ff674099a96d22bc0948955cb338488c31bfb8851"}, {file = "brotlicffi-1.1.0.0-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:19ffc919fa4fc6ace69286e0a23b3789b4219058313cf9b45625016bf7ff996b"}, {file = "brotlicffi-1.1.0.0-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9feb210d932ffe7798ee62e6145d3a757eb6233aa9a4e7db78dd3690d7755814"}, {file = "brotlicffi-1.1.0.0-cp37-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:84763dbdef5dd5c24b75597a77e1b30c66604725707565188ba54bab4f114820"}, {file = "brotlicffi-1.1.0.0-cp37-abi3-win32.whl", hash = "sha256:1b12b50e07c3911e1efa3a8971543e7648100713d4e0971b13631cce22c587eb"}, {file = "brotlicffi-1.1.0.0-cp37-abi3-win_amd64.whl", hash = "sha256:994a4f0681bb6c6c3b0925530a1926b7a189d878e6e5e38fae8efa47c5d9c613"}, {file = "brotlicffi-1.1.0.0-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:2e4aeb0bd2540cb91b069dbdd54d458da8c4334ceaf2d25df2f4af576d6766ca"}, {file = "brotlicffi-1.1.0.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4b7b0033b0d37bb33009fb2fef73310e432e76f688af76c156b3594389d81391"}, {file = "brotlicffi-1.1.0.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:54a07bb2374a1eba8ebb52b6fafffa2afd3c4df85ddd38fcc0511f2bb387c2a8"}, {file = "brotlicffi-1.1.0.0-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7901a7dc4b88f1c1475de59ae9be59799db1007b7d059817948d8e4f12e24e35"}, {file = "brotlicffi-1.1.0.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:ce01c7316aebc7fce59da734286148b1d1b9455f89cf2c8a4dfce7d41db55c2d"}, {file = "brotlicffi-1.1.0.0-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:246f1d1a90279bb6069de3de8d75a8856e073b8ff0b09dcca18ccc14cec85979"}, {file = "brotlicffi-1.1.0.0-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cc4bc5d82bc56ebd8b514fb8350cfac4627d6b0743382e46d033976a5f80fab6"}, {file = "brotlicffi-1.1.0.0-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:37c26ecb14386a44b118ce36e546ce307f4810bc9598a6e6cb4f7fca725ae7e6"}, {file = "brotlicffi-1.1.0.0-pp37-pypy37_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ca72968ae4eaf6470498d5c2887073f7efe3b1e7d7ec8be11a06a79cc810e990"}, {file = "brotlicffi-1.1.0.0-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:add0de5b9ad9e9aa293c3aa4e9deb2b61e99ad6c1634e01d01d98c03e6a354cc"}, {file = "brotlicffi-1.1.0.0-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:9b6068e0f3769992d6b622a1cd2e7835eae3cf8d9da123d7f51ca9c1e9c333e5"}, {file = "brotlicffi-1.1.0.0-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8557a8559509b61e65083f8782329188a250102372576093c88930c875a69838"}, {file = "brotlicffi-1.1.0.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2a7ae37e5d79c5bdfb5b4b99f2715a6035e6c5bf538c3746abc8e26694f92f33"}, {file = "brotlicffi-1.1.0.0-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:391151ec86bb1c683835980f4816272a87eaddc46bb91cbf44f62228b84d8cca"}, {file = "brotlicffi-1.1.0.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:2f3711be9290f0453de8eed5275d93d286abe26b08ab4a35d7452caa1fef532f"}, {file = "brotlicffi-1.1.0.0-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:1a807d760763e398bbf2c6394ae9da5815901aa93ee0a37bca5efe78d4ee3171"}, {file = "brotlicffi-1.1.0.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fa8ca0623b26c94fccc3a1fdd895be1743b838f3917300506d04aa3346fd2a14"}, {file = "brotlicffi-1.1.0.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3de0cf28a53a3238b252aca9fed1593e9d36c1d116748013339f0949bfc84112"}, {file = "brotlicffi-1.1.0.0-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6be5ec0e88a4925c91f3dea2bb0013b3a2accda6f77238f76a34a1ea532a1cb0"}, {file = "brotlicffi-1.1.0.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:d9eb71bb1085d996244439154387266fd23d6ad37161f6f52f1cd41dd95a3808"}, {file = "brotlicffi-1.1.0.0.tar.gz", hash = "sha256:b77827a689905143f87915310b93b273ab17888fd43ef350d4832c4a71083c13"}, ] [package.dependencies] cffi = ">=1.0.0" [[package]] name = "certifi" version = "2024.8.30" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.6" files = [ {file = "certifi-2024.8.30-py3-none-any.whl", hash = "sha256:922820b53db7a7257ffbda3f597266d435245903d80737e34f8a45ff3e3230d8"}, {file = "certifi-2024.8.30.tar.gz", hash = "sha256:bec941d2aa8195e248a60b31ff9f0558284cf01a52591ceda73ea9afffd69fd9"}, ] [[package]] name = "cffi" version = "1.17.1" description = "Foreign Function Interface for Python calling C code." optional = false python-versions = ">=3.8" files = [ {file = "cffi-1.17.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:df8b1c11f177bc2313ec4b2d46baec87a5f3e71fc8b45dab2ee7cae86d9aba14"}, {file = "cffi-1.17.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8f2cdc858323644ab277e9bb925ad72ae0e67f69e804f4898c070998d50b1a67"}, {file = "cffi-1.17.1-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:edae79245293e15384b51f88b00613ba9f7198016a5948b5dddf4917d4d26382"}, {file = "cffi-1.17.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:45398b671ac6d70e67da8e4224a065cec6a93541bb7aebe1b198a61b58c7b702"}, {file = "cffi-1.17.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ad9413ccdeda48c5afdae7e4fa2192157e991ff761e7ab8fdd8926f40b160cc3"}, {file = "cffi-1.17.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5da5719280082ac6bd9aa7becb3938dc9f9cbd57fac7d2871717b1feb0902ab6"}, {file = "cffi-1.17.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2bb1a08b8008b281856e5971307cc386a8e9c5b625ac297e853d36da6efe9c17"}, {file = "cffi-1.17.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:045d61c734659cc045141be4bae381a41d89b741f795af1dd018bfb532fd0df8"}, {file = "cffi-1.17.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:6883e737d7d9e4899a8a695e00ec36bd4e5e4f18fabe0aca0efe0a4b44cdb13e"}, {file = "cffi-1.17.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:6b8b4a92e1c65048ff98cfe1f735ef8f1ceb72e3d5f0c25fdb12087a23da22be"}, {file = "cffi-1.17.1-cp310-cp310-win32.whl", hash = "sha256:c9c3d058ebabb74db66e431095118094d06abf53284d9c81f27300d0e0d8bc7c"}, {file = "cffi-1.17.1-cp310-cp310-win_amd64.whl", hash = "sha256:0f048dcf80db46f0098ccac01132761580d28e28bc0f78ae0d58048063317e15"}, {file = "cffi-1.17.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a45e3c6913c5b87b3ff120dcdc03f6131fa0065027d0ed7ee6190736a74cd401"}, {file = "cffi-1.17.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:30c5e0cb5ae493c04c8b42916e52ca38079f1b235c2f8ae5f4527b963c401caf"}, {file = "cffi-1.17.1-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f75c7ab1f9e4aca5414ed4d8e5c0e303a34f4421f8a0d47a4d019ceff0ab6af4"}, {file = "cffi-1.17.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a1ed2dd2972641495a3ec98445e09766f077aee98a1c896dcb4ad0d303628e41"}, {file = "cffi-1.17.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:46bf43160c1a35f7ec506d254e5c890f3c03648a4dbac12d624e4490a7046cd1"}, {file = "cffi-1.17.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a24ed04c8ffd54b0729c07cee15a81d964e6fee0e3d4d342a27b020d22959dc6"}, {file = "cffi-1.17.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:610faea79c43e44c71e1ec53a554553fa22321b65fae24889706c0a84d4ad86d"}, {file = "cffi-1.17.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:a9b15d491f3ad5d692e11f6b71f7857e7835eb677955c00cc0aefcd0669adaf6"}, {file = "cffi-1.17.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:de2ea4b5833625383e464549fec1bc395c1bdeeb5f25c4a3a82b5a8c756ec22f"}, {file = "cffi-1.17.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:fc48c783f9c87e60831201f2cce7f3b2e4846bf4d8728eabe54d60700b318a0b"}, {file = "cffi-1.17.1-cp311-cp311-win32.whl", hash = "sha256:85a950a4ac9c359340d5963966e3e0a94a676bd6245a4b55bc43949eee26a655"}, {file = "cffi-1.17.1-cp311-cp311-win_amd64.whl", hash = "sha256:caaf0640ef5f5517f49bc275eca1406b0ffa6aa184892812030f04c2abf589a0"}, {file = "cffi-1.17.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:805b4371bf7197c329fcb3ead37e710d1bca9da5d583f5073b799d5c5bd1eee4"}, {file = "cffi-1.17.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:733e99bc2df47476e3848417c5a4540522f234dfd4ef3ab7fafdf555b082ec0c"}, {file = "cffi-1.17.1-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1257bdabf294dceb59f5e70c64a3e2f462c30c7ad68092d01bbbfb1c16b1ba36"}, {file = "cffi-1.17.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:da95af8214998d77a98cc14e3a3bd00aa191526343078b530ceb0bd710fb48a5"}, {file = "cffi-1.17.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d63afe322132c194cf832bfec0dc69a99fb9bb6bbd550f161a49e9e855cc78ff"}, {file = "cffi-1.17.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f79fc4fc25f1c8698ff97788206bb3c2598949bfe0fef03d299eb1b5356ada99"}, {file = "cffi-1.17.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b62ce867176a75d03a665bad002af8e6d54644fad99a3c70905c543130e39d93"}, {file = "cffi-1.17.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:386c8bf53c502fff58903061338ce4f4950cbdcb23e2902d86c0f722b786bbe3"}, {file = "cffi-1.17.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:4ceb10419a9adf4460ea14cfd6bc43d08701f0835e979bf821052f1805850fe8"}, {file = "cffi-1.17.1-cp312-cp312-win32.whl", hash = "sha256:a08d7e755f8ed21095a310a693525137cfe756ce62d066e53f502a83dc550f65"}, {file = "cffi-1.17.1-cp312-cp312-win_amd64.whl", hash = "sha256:51392eae71afec0d0c8fb1a53b204dbb3bcabcb3c9b807eedf3e1e6ccf2de903"}, {file = "cffi-1.17.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f3a2b4222ce6b60e2e8b337bb9596923045681d71e5a082783484d845390938e"}, {file = "cffi-1.17.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0984a4925a435b1da406122d4d7968dd861c1385afe3b45ba82b750f229811e2"}, {file = "cffi-1.17.1-cp313-cp313-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d01b12eeeb4427d3110de311e1774046ad344f5b1a7403101878976ecd7a10f3"}, {file = "cffi-1.17.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:706510fe141c86a69c8ddc029c7910003a17353970cff3b904ff0686a5927683"}, {file = "cffi-1.17.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:de55b766c7aa2e2a3092c51e0483d700341182f08e67c63630d5b6f200bb28e5"}, {file = "cffi-1.17.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c59d6e989d07460165cc5ad3c61f9fd8f1b4796eacbd81cee78957842b834af4"}, {file = "cffi-1.17.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd398dbc6773384a17fe0d3e7eeb8d1a21c2200473ee6806bb5e6a8e62bb73dd"}, {file = "cffi-1.17.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:3edc8d958eb099c634dace3c7e16560ae474aa3803a5df240542b305d14e14ed"}, {file = "cffi-1.17.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:72e72408cad3d5419375fc87d289076ee319835bdfa2caad331e377589aebba9"}, {file = "cffi-1.17.1-cp313-cp313-win32.whl", hash = "sha256:e03eab0a8677fa80d646b5ddece1cbeaf556c313dcfac435ba11f107ba117b5d"}, {file = "cffi-1.17.1-cp313-cp313-win_amd64.whl", hash = "sha256:f6a16c31041f09ead72d69f583767292f750d24913dadacf5756b966aacb3f1a"}, {file = "cffi-1.17.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:636062ea65bd0195bc012fea9321aca499c0504409f413dc88af450b57ffd03b"}, {file = "cffi-1.17.1-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c7eac2ef9b63c79431bc4b25f1cd649d7f061a28808cbc6c47b534bd789ef964"}, {file = "cffi-1.17.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e221cf152cff04059d011ee126477f0d9588303eb57e88923578ace7baad17f9"}, {file = "cffi-1.17.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:31000ec67d4221a71bd3f67df918b1f88f676f1c3b535a7eb473255fdc0b83fc"}, {file = "cffi-1.17.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6f17be4345073b0a7b8ea599688f692ac3ef23ce28e5df79c04de519dbc4912c"}, {file = "cffi-1.17.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0e2b1fac190ae3ebfe37b979cc1ce69c81f4e4fe5746bb401dca63a9062cdaf1"}, {file = "cffi-1.17.1-cp38-cp38-win32.whl", hash = "sha256:7596d6620d3fa590f677e9ee430df2958d2d6d6de2feeae5b20e82c00b76fbf8"}, {file = "cffi-1.17.1-cp38-cp38-win_amd64.whl", hash = "sha256:78122be759c3f8a014ce010908ae03364d00a1f81ab5c7f4a7a5120607ea56e1"}, {file = "cffi-1.17.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b2ab587605f4ba0bf81dc0cb08a41bd1c0a5906bd59243d56bad7668a6fc6c16"}, {file = "cffi-1.17.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:28b16024becceed8c6dfbc75629e27788d8a3f9030691a1dbf9821a128b22c36"}, {file = "cffi-1.17.1-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1d599671f396c4723d016dbddb72fe8e0397082b0a77a4fab8028923bec050e8"}, {file = "cffi-1.17.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ca74b8dbe6e8e8263c0ffd60277de77dcee6c837a3d0881d8c1ead7268c9e576"}, {file = "cffi-1.17.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f7f5baafcc48261359e14bcd6d9bff6d4b28d9103847c9e136694cb0501aef87"}, {file = "cffi-1.17.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:98e3969bcff97cae1b2def8ba499ea3d6f31ddfdb7635374834cf89a1a08ecf0"}, {file = "cffi-1.17.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cdf5ce3acdfd1661132f2a9c19cac174758dc2352bfe37d98aa7512c6b7178b3"}, {file = "cffi-1.17.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:9755e4345d1ec879e3849e62222a18c7174d65a6a92d5b346b1863912168b595"}, {file = "cffi-1.17.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:f1e22e8c4419538cb197e4dd60acc919d7696e5ef98ee4da4e01d3f8cfa4cc5a"}, {file = "cffi-1.17.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:c03e868a0b3bc35839ba98e74211ed2b05d2119be4e8a0f224fba9384f1fe02e"}, {file = "cffi-1.17.1-cp39-cp39-win32.whl", hash = "sha256:e31ae45bc2e29f6b2abd0de1cc3b9d5205aa847cafaecb8af1476a609a2f6eb7"}, {file = "cffi-1.17.1-cp39-cp39-win_amd64.whl", hash = "sha256:d016c76bdd850f3c626af19b0542c9677ba156e4ee4fccfdd7848803533ef662"}, {file = "cffi-1.17.1.tar.gz", hash = "sha256:1c39c6016c32bc48dd54561950ebd6836e1670f2ae46128f67cf49e789c52824"}, ] [package.dependencies] pycparser = "*" [[package]] name = "charset-normalizer" version = "3.4.0" description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." optional = false python-versions = ">=3.7.0" files = [ {file = "charset_normalizer-3.4.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:4f9fc98dad6c2eaa32fc3af1417d95b5e3d08aff968df0cd320066def971f9a6"}, {file = "charset_normalizer-3.4.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0de7b687289d3c1b3e8660d0741874abe7888100efe14bd0f9fd7141bcbda92b"}, {file = "charset_normalizer-3.4.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5ed2e36c3e9b4f21dd9422f6893dec0abf2cca553af509b10cd630f878d3eb99"}, {file = "charset_normalizer-3.4.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:40d3ff7fc90b98c637bda91c89d51264a3dcf210cade3a2c6f838c7268d7a4ca"}, {file = "charset_normalizer-3.4.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1110e22af8ca26b90bd6364fe4c763329b0ebf1ee213ba32b68c73de5752323d"}, {file = "charset_normalizer-3.4.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:86f4e8cca779080f66ff4f191a685ced73d2f72d50216f7112185dc02b90b9b7"}, {file = "charset_normalizer-3.4.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7f683ddc7eedd742e2889d2bfb96d69573fde1d92fcb811979cdb7165bb9c7d3"}, {file = "charset_normalizer-3.4.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:27623ba66c183eca01bf9ff833875b459cad267aeeb044477fedac35e19ba907"}, {file = "charset_normalizer-3.4.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:f606a1881d2663630ea5b8ce2efe2111740df4b687bd78b34a8131baa007f79b"}, {file = "charset_normalizer-3.4.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:0b309d1747110feb25d7ed6b01afdec269c647d382c857ef4663bbe6ad95a912"}, {file = "charset_normalizer-3.4.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:136815f06a3ae311fae551c3df1f998a1ebd01ddd424aa5603a4336997629e95"}, {file = "charset_normalizer-3.4.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:14215b71a762336254351b00ec720a8e85cada43b987da5a042e4ce3e82bd68e"}, {file = "charset_normalizer-3.4.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:79983512b108e4a164b9c8d34de3992f76d48cadc9554c9e60b43f308988aabe"}, {file = "charset_normalizer-3.4.0-cp310-cp310-win32.whl", hash = "sha256:c94057af19bc953643a33581844649a7fdab902624d2eb739738a30e2b3e60fc"}, {file = "charset_normalizer-3.4.0-cp310-cp310-win_amd64.whl", hash = "sha256:55f56e2ebd4e3bc50442fbc0888c9d8c94e4e06a933804e2af3e89e2f9c1c749"}, {file = "charset_normalizer-3.4.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:0d99dd8ff461990f12d6e42c7347fd9ab2532fb70e9621ba520f9e8637161d7c"}, {file = "charset_normalizer-3.4.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c57516e58fd17d03ebe67e181a4e4e2ccab1168f8c2976c6a334d4f819fe5944"}, {file = "charset_normalizer-3.4.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:6dba5d19c4dfab08e58d5b36304b3f92f3bd5d42c1a3fa37b5ba5cdf6dfcbcee"}, {file = "charset_normalizer-3.4.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bf4475b82be41b07cc5e5ff94810e6a01f276e37c2d55571e3fe175e467a1a1c"}, {file = "charset_normalizer-3.4.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ce031db0408e487fd2775d745ce30a7cd2923667cf3b69d48d219f1d8f5ddeb6"}, {file = "charset_normalizer-3.4.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8ff4e7cdfdb1ab5698e675ca622e72d58a6fa2a8aa58195de0c0061288e6e3ea"}, {file = "charset_normalizer-3.4.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3710a9751938947e6327ea9f3ea6332a09bf0ba0c09cae9cb1f250bd1f1549bc"}, {file = "charset_normalizer-3.4.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:82357d85de703176b5587dbe6ade8ff67f9f69a41c0733cf2425378b49954de5"}, {file = "charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:47334db71978b23ebcf3c0f9f5ee98b8d65992b65c9c4f2d34c2eaf5bcaf0594"}, {file = "charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:8ce7fd6767a1cc5a92a639b391891bf1c268b03ec7e021c7d6d902285259685c"}, {file = "charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:f1a2f519ae173b5b6a2c9d5fa3116ce16e48b3462c8b96dfdded11055e3d6365"}, {file = "charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:63bc5c4ae26e4bc6be6469943b8253c0fd4e4186c43ad46e713ea61a0ba49129"}, {file = "charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:bcb4f8ea87d03bc51ad04add8ceaf9b0f085ac045ab4d74e73bbc2dc033f0236"}, {file = "charset_normalizer-3.4.0-cp311-cp311-win32.whl", hash = "sha256:9ae4ef0b3f6b41bad6366fb0ea4fc1d7ed051528e113a60fa2a65a9abb5b1d99"}, {file = "charset_normalizer-3.4.0-cp311-cp311-win_amd64.whl", hash = "sha256:cee4373f4d3ad28f1ab6290684d8e2ebdb9e7a1b74fdc39e4c211995f77bec27"}, {file = "charset_normalizer-3.4.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0713f3adb9d03d49d365b70b84775d0a0d18e4ab08d12bc46baa6132ba78aaf6"}, {file = "charset_normalizer-3.4.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:de7376c29d95d6719048c194a9cf1a1b0393fbe8488a22008610b0361d834ecf"}, {file = "charset_normalizer-3.4.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4a51b48f42d9358460b78725283f04bddaf44a9358197b889657deba38f329db"}, {file = "charset_normalizer-3.4.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b295729485b06c1a0683af02a9e42d2caa9db04a373dc38a6a58cdd1e8abddf1"}, {file = "charset_normalizer-3.4.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ee803480535c44e7f5ad00788526da7d85525cfefaf8acf8ab9a310000be4b03"}, {file = "charset_normalizer-3.4.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3d59d125ffbd6d552765510e3f31ed75ebac2c7470c7274195b9161a32350284"}, {file = "charset_normalizer-3.4.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8cda06946eac330cbe6598f77bb54e690b4ca93f593dee1568ad22b04f347c15"}, {file = "charset_normalizer-3.4.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:07afec21bbbbf8a5cc3651aa96b980afe2526e7f048fdfb7f1014d84acc8b6d8"}, {file = "charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6b40e8d38afe634559e398cc32b1472f376a4099c75fe6299ae607e404c033b2"}, {file = "charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:b8dcd239c743aa2f9c22ce674a145e0a25cb1566c495928440a181ca1ccf6719"}, {file = "charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:84450ba661fb96e9fd67629b93d2941c871ca86fc38d835d19d4225ff946a631"}, {file = "charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:44aeb140295a2f0659e113b31cfe92c9061622cadbc9e2a2f7b8ef6b1e29ef4b"}, {file = "charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:1db4e7fefefd0f548d73e2e2e041f9df5c59e178b4c72fbac4cc6f535cfb1565"}, {file = "charset_normalizer-3.4.0-cp312-cp312-win32.whl", hash = "sha256:5726cf76c982532c1863fb64d8c6dd0e4c90b6ece9feb06c9f202417a31f7dd7"}, {file = "charset_normalizer-3.4.0-cp312-cp312-win_amd64.whl", hash = "sha256:b197e7094f232959f8f20541ead1d9862ac5ebea1d58e9849c1bf979255dfac9"}, {file = "charset_normalizer-3.4.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:dd4eda173a9fcccb5f2e2bd2a9f423d180194b1bf17cf59e3269899235b2a114"}, {file = "charset_normalizer-3.4.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e9e3c4c9e1ed40ea53acf11e2a386383c3304212c965773704e4603d589343ed"}, {file = "charset_normalizer-3.4.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:92a7e36b000bf022ef3dbb9c46bfe2d52c047d5e3f3343f43204263c5addc250"}, {file = "charset_normalizer-3.4.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:54b6a92d009cbe2fb11054ba694bc9e284dad30a26757b1e372a1fdddaf21920"}, {file = "charset_normalizer-3.4.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ffd9493de4c922f2a38c2bf62b831dcec90ac673ed1ca182fe11b4d8e9f2a64"}, {file = "charset_normalizer-3.4.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:35c404d74c2926d0287fbd63ed5d27eb911eb9e4a3bb2c6d294f3cfd4a9e0c23"}, {file = "charset_normalizer-3.4.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4796efc4faf6b53a18e3d46343535caed491776a22af773f366534056c4e1fbc"}, {file = "charset_normalizer-3.4.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e7fdd52961feb4c96507aa649550ec2a0d527c086d284749b2f582f2d40a2e0d"}, {file = "charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:92db3c28b5b2a273346bebb24857fda45601aef6ae1c011c0a997106581e8a88"}, {file = "charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ab973df98fc99ab39080bfb0eb3a925181454d7c3ac8a1e695fddfae696d9e90"}, {file = "charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:4b67fdab07fdd3c10bb21edab3cbfe8cf5696f453afce75d815d9d7223fbe88b"}, {file = "charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:aa41e526a5d4a9dfcfbab0716c7e8a1b215abd3f3df5a45cf18a12721d31cb5d"}, {file = "charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ffc519621dce0c767e96b9c53f09c5d215578e10b02c285809f76509a3931482"}, {file = "charset_normalizer-3.4.0-cp313-cp313-win32.whl", hash = "sha256:f19c1585933c82098c2a520f8ec1227f20e339e33aca8fa6f956f6691b784e67"}, {file = "charset_normalizer-3.4.0-cp313-cp313-win_amd64.whl", hash = "sha256:707b82d19e65c9bd28b81dde95249b07bf9f5b90ebe1ef17d9b57473f8a64b7b"}, {file = "charset_normalizer-3.4.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:dbe03226baf438ac4fda9e2d0715022fd579cb641c4cf639fa40d53b2fe6f3e2"}, {file = "charset_normalizer-3.4.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dd9a8bd8900e65504a305bf8ae6fa9fbc66de94178c420791d0293702fce2df7"}, {file = "charset_normalizer-3.4.0-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b8831399554b92b72af5932cdbbd4ddc55c55f631bb13ff8fe4e6536a06c5c51"}, {file = "charset_normalizer-3.4.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a14969b8691f7998e74663b77b4c36c0337cb1df552da83d5c9004a93afdb574"}, {file = "charset_normalizer-3.4.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dcaf7c1524c0542ee2fc82cc8ec337f7a9f7edee2532421ab200d2b920fc97cf"}, {file = "charset_normalizer-3.4.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:425c5f215d0eecee9a56cdb703203dda90423247421bf0d67125add85d0c4455"}, {file = "charset_normalizer-3.4.0-cp37-cp37m-musllinux_1_2_aarch64.whl", hash = "sha256:d5b054862739d276e09928de37c79ddeec42a6e1bfc55863be96a36ba22926f6"}, {file = "charset_normalizer-3.4.0-cp37-cp37m-musllinux_1_2_i686.whl", hash = "sha256:f3e73a4255342d4eb26ef6df01e3962e73aa29baa3124a8e824c5d3364a65748"}, {file = "charset_normalizer-3.4.0-cp37-cp37m-musllinux_1_2_ppc64le.whl", hash = "sha256:2f6c34da58ea9c1a9515621f4d9ac379871a8f21168ba1b5e09d74250de5ad62"}, {file = "charset_normalizer-3.4.0-cp37-cp37m-musllinux_1_2_s390x.whl", hash = "sha256:f09cb5a7bbe1ecae6e87901a2eb23e0256bb524a79ccc53eb0b7629fbe7677c4"}, {file = "charset_normalizer-3.4.0-cp37-cp37m-musllinux_1_2_x86_64.whl", hash = "sha256:0099d79bdfcf5c1f0c2c72f91516702ebf8b0b8ddd8905f97a8aecf49712c621"}, {file = "charset_normalizer-3.4.0-cp37-cp37m-win32.whl", hash = "sha256:9c98230f5042f4945f957d006edccc2af1e03ed5e37ce7c373f00a5a4daa6149"}, {file = "charset_normalizer-3.4.0-cp37-cp37m-win_amd64.whl", hash = "sha256:62f60aebecfc7f4b82e3f639a7d1433a20ec32824db2199a11ad4f5e146ef5ee"}, {file = "charset_normalizer-3.4.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:af73657b7a68211996527dbfeffbb0864e043d270580c5aef06dc4b659a4b578"}, {file = "charset_normalizer-3.4.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:cab5d0b79d987c67f3b9e9c53f54a61360422a5a0bc075f43cab5621d530c3b6"}, {file = "charset_normalizer-3.4.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:9289fd5dddcf57bab41d044f1756550f9e7cf0c8e373b8cdf0ce8773dc4bd417"}, {file = "charset_normalizer-3.4.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6b493a043635eb376e50eedf7818f2f322eabbaa974e948bd8bdd29eb7ef2a51"}, {file = "charset_normalizer-3.4.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9fa2566ca27d67c86569e8c85297aaf413ffab85a8960500f12ea34ff98e4c41"}, {file = "charset_normalizer-3.4.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a8e538f46104c815be19c975572d74afb53f29650ea2025bbfaef359d2de2f7f"}, {file = "charset_normalizer-3.4.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6fd30dc99682dc2c603c2b315bded2799019cea829f8bf57dc6b61efde6611c8"}, {file = "charset_normalizer-3.4.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2006769bd1640bdf4d5641c69a3d63b71b81445473cac5ded39740a226fa88ab"}, {file = "charset_normalizer-3.4.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:dc15e99b2d8a656f8e666854404f1ba54765871104e50c8e9813af8a7db07f12"}, {file = "charset_normalizer-3.4.0-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:ab2e5bef076f5a235c3774b4f4028a680432cded7cad37bba0fd90d64b187d19"}, {file = "charset_normalizer-3.4.0-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:4ec9dd88a5b71abfc74e9df5ebe7921c35cbb3b641181a531ca65cdb5e8e4dea"}, {file = "charset_normalizer-3.4.0-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:43193c5cda5d612f247172016c4bb71251c784d7a4d9314677186a838ad34858"}, {file = "charset_normalizer-3.4.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:aa693779a8b50cd97570e5a0f343538a8dbd3e496fa5dcb87e29406ad0299654"}, {file = "charset_normalizer-3.4.0-cp38-cp38-win32.whl", hash = "sha256:7706f5850360ac01d80c89bcef1640683cc12ed87f42579dab6c5d3ed6888613"}, {file = "charset_normalizer-3.4.0-cp38-cp38-win_amd64.whl", hash = "sha256:c3e446d253bd88f6377260d07c895816ebf33ffffd56c1c792b13bff9c3e1ade"}, {file = "charset_normalizer-3.4.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:980b4f289d1d90ca5efcf07958d3eb38ed9c0b7676bf2831a54d4f66f9c27dfa"}, {file = "charset_normalizer-3.4.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:f28f891ccd15c514a0981f3b9db9aa23d62fe1a99997512b0491d2ed323d229a"}, {file = "charset_normalizer-3.4.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a8aacce6e2e1edcb6ac625fb0f8c3a9570ccc7bfba1f63419b3769ccf6a00ed0"}, {file = "charset_normalizer-3.4.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bd7af3717683bea4c87acd8c0d3d5b44d56120b26fd3f8a692bdd2d5260c620a"}, {file = "charset_normalizer-3.4.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5ff2ed8194587faf56555927b3aa10e6fb69d931e33953943bc4f837dfee2242"}, {file = "charset_normalizer-3.4.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e91f541a85298cf35433bf66f3fab2a4a2cff05c127eeca4af174f6d497f0d4b"}, {file = "charset_normalizer-3.4.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:309a7de0a0ff3040acaebb35ec45d18db4b28232f21998851cfa709eeff49d62"}, {file = "charset_normalizer-3.4.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:285e96d9d53422efc0d7a17c60e59f37fbf3dfa942073f666db4ac71e8d726d0"}, {file = "charset_normalizer-3.4.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:5d447056e2ca60382d460a604b6302d8db69476fd2015c81e7c35417cfabe4cd"}, {file = "charset_normalizer-3.4.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:20587d20f557fe189b7947d8e7ec5afa110ccf72a3128d61a2a387c3313f46be"}, {file = "charset_normalizer-3.4.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:130272c698667a982a5d0e626851ceff662565379baf0ff2cc58067b81d4f11d"}, {file = "charset_normalizer-3.4.0-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:ab22fbd9765e6954bc0bcff24c25ff71dcbfdb185fcdaca49e81bac68fe724d3"}, {file = "charset_normalizer-3.4.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:7782afc9b6b42200f7362858f9e73b1f8316afb276d316336c0ec3bd73312742"}, {file = "charset_normalizer-3.4.0-cp39-cp39-win32.whl", hash = "sha256:2de62e8801ddfff069cd5c504ce3bc9672b23266597d4e4f50eda28846c322f2"}, {file = "charset_normalizer-3.4.0-cp39-cp39-win_amd64.whl", hash = "sha256:95c3c157765b031331dd4db3c775e58deaee050a3042fcad72cbc4189d7c8dca"}, {file = "charset_normalizer-3.4.0-py3-none-any.whl", hash = "sha256:fe9f97feb71aa9896b81973a7bbada8c49501dc73e58a10fcef6663af95e5079"}, {file = "charset_normalizer-3.4.0.tar.gz", hash = "sha256:223217c3d4f82c3ac5e29032b3f1c2eb0fb591b72161f86d93f5719079dae93e"}, ] [[package]] name = "click" version = "8.1.7" description = "Composable command line interface toolkit" optional = false python-versions = ">=3.7" files = [ {file = "click-8.1.7-py3-none-any.whl", hash = "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28"}, {file = "click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de"}, ] [package.dependencies] colorama = {version = "*", markers = "platform_system == \"Windows\""} [[package]] name = "codecov" version = "2.1.13" description = "Hosted coverage reports for GitHub, Bitbucket and Gitlab" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" files = [ {file = "codecov-2.1.13-py2.py3-none-any.whl", hash = "sha256:c2ca5e51bba9ebb43644c43d0690148a55086f7f5e6fd36170858fa4206744d5"}, {file = "codecov-2.1.13.tar.gz", hash = "sha256:2362b685633caeaf45b9951a9b76ce359cd3581dd515b430c6c3f5dfb4d92a8c"}, ] [package.dependencies] coverage = "*" requests = ">=2.7.9" [[package]] name = "colorama" version = "0.4.6" description = "Cross-platform colored terminal text." optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" files = [ {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, ] [[package]] name = "confuse" version = "2.0.1" description = "Painless YAML configuration." optional = false python-versions = ">=3.6" files = [ {file = "confuse-2.0.1-py3-none-any.whl", hash = "sha256:9b9e5bbc70e2cb9b318bcab14d917ec88e21bf1b724365e3815eb16e37aabd2a"}, {file = "confuse-2.0.1.tar.gz", hash = "sha256:7379a2ad49aaa862b79600cc070260c1b7974d349f4fa5e01f9afa6c4dd0611f"}, ] [package.dependencies] pyyaml = "*" [[package]] name = "coverage" version = "7.6.1" description = "Code coverage measurement for Python" optional = false python-versions = ">=3.8" files = [ {file = "coverage-7.6.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b06079abebbc0e89e6163b8e8f0e16270124c154dc6e4a47b413dd538859af16"}, {file = "coverage-7.6.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:cf4b19715bccd7ee27b6b120e7e9dd56037b9c0681dcc1adc9ba9db3d417fa36"}, {file = "coverage-7.6.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e61c0abb4c85b095a784ef23fdd4aede7a2628478e7baba7c5e3deba61070a02"}, {file = "coverage-7.6.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fd21f6ae3f08b41004dfb433fa895d858f3f5979e7762d052b12aef444e29afc"}, {file = "coverage-7.6.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f59d57baca39b32db42b83b2a7ba6f47ad9c394ec2076b084c3f029b7afca23"}, {file = "coverage-7.6.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:a1ac0ae2b8bd743b88ed0502544847c3053d7171a3cff9228af618a068ed9c34"}, {file = "coverage-7.6.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e6a08c0be454c3b3beb105c0596ebdc2371fab6bb90c0c0297f4e58fd7e1012c"}, {file = "coverage-7.6.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:f5796e664fe802da4f57a168c85359a8fbf3eab5e55cd4e4569fbacecc903959"}, {file = "coverage-7.6.1-cp310-cp310-win32.whl", hash = "sha256:7bb65125fcbef8d989fa1dd0e8a060999497629ca5b0efbca209588a73356232"}, {file = "coverage-7.6.1-cp310-cp310-win_amd64.whl", hash = "sha256:3115a95daa9bdba70aea750db7b96b37259a81a709223c8448fa97727d546fe0"}, {file = "coverage-7.6.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:7dea0889685db8550f839fa202744652e87c60015029ce3f60e006f8c4462c93"}, {file = "coverage-7.6.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ed37bd3c3b063412f7620464a9ac1314d33100329f39799255fb8d3027da50d3"}, {file = "coverage-7.6.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d85f5e9a5f8b73e2350097c3756ef7e785f55bd71205defa0bfdaf96c31616ff"}, {file = "coverage-7.6.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9bc572be474cafb617672c43fe989d6e48d3c83af02ce8de73fff1c6bb3c198d"}, {file = "coverage-7.6.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0c0420b573964c760df9e9e86d1a9a622d0d27f417e1a949a8a66dd7bcee7bc6"}, {file = "coverage-7.6.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1f4aa8219db826ce6be7099d559f8ec311549bfc4046f7f9fe9b5cea5c581c56"}, {file = "coverage-7.6.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:fc5a77d0c516700ebad189b587de289a20a78324bc54baee03dd486f0855d234"}, {file = "coverage-7.6.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b48f312cca9621272ae49008c7f613337c53fadca647d6384cc129d2996d1133"}, {file = "coverage-7.6.1-cp311-cp311-win32.whl", hash = "sha256:1125ca0e5fd475cbbba3bb67ae20bd2c23a98fac4e32412883f9bcbaa81c314c"}, {file = "coverage-7.6.1-cp311-cp311-win_amd64.whl", hash = "sha256:8ae539519c4c040c5ffd0632784e21b2f03fc1340752af711f33e5be83a9d6c6"}, {file = "coverage-7.6.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:95cae0efeb032af8458fc27d191f85d1717b1d4e49f7cb226cf526ff28179778"}, {file = "coverage-7.6.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5621a9175cf9d0b0c84c2ef2b12e9f5f5071357c4d2ea6ca1cf01814f45d2391"}, {file = "coverage-7.6.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:260933720fdcd75340e7dbe9060655aff3af1f0c5d20f46b57f262ab6c86a5e8"}, {file = "coverage-7.6.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:07e2ca0ad381b91350c0ed49d52699b625aab2b44b65e1b4e02fa9df0e92ad2d"}, {file = "coverage-7.6.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c44fee9975f04b33331cb8eb272827111efc8930cfd582e0320613263ca849ca"}, {file = "coverage-7.6.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:877abb17e6339d96bf08e7a622d05095e72b71f8afd8a9fefc82cf30ed944163"}, {file = "coverage-7.6.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:3e0cadcf6733c09154b461f1ca72d5416635e5e4ec4e536192180d34ec160f8a"}, {file = "coverage-7.6.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c3c02d12f837d9683e5ab2f3d9844dc57655b92c74e286c262e0fc54213c216d"}, {file = "coverage-7.6.1-cp312-cp312-win32.whl", hash = "sha256:e05882b70b87a18d937ca6768ff33cc3f72847cbc4de4491c8e73880766718e5"}, {file = "coverage-7.6.1-cp312-cp312-win_amd64.whl", hash = "sha256:b5d7b556859dd85f3a541db6a4e0167b86e7273e1cdc973e5b175166bb634fdb"}, {file = "coverage-7.6.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:a4acd025ecc06185ba2b801f2de85546e0b8ac787cf9d3b06e7e2a69f925b106"}, {file = "coverage-7.6.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a6d3adcf24b624a7b778533480e32434a39ad8fa30c315208f6d3e5542aeb6e9"}, {file = "coverage-7.6.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d0c212c49b6c10e6951362f7c6df3329f04c2b1c28499563d4035d964ab8e08c"}, {file = "coverage-7.6.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6e81d7a3e58882450ec4186ca59a3f20a5d4440f25b1cff6f0902ad890e6748a"}, {file = "coverage-7.6.1-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:78b260de9790fd81e69401c2dc8b17da47c8038176a79092a89cb2b7d945d060"}, {file = "coverage-7.6.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a78d169acd38300060b28d600344a803628c3fd585c912cacc9ea8790fe96862"}, {file = "coverage-7.6.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:2c09f4ce52cb99dd7505cd0fc8e0e37c77b87f46bc9c1eb03fe3bc9991085388"}, {file = "coverage-7.6.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6878ef48d4227aace338d88c48738a4258213cd7b74fd9a3d4d7582bb1d8a155"}, {file = "coverage-7.6.1-cp313-cp313-win32.whl", hash = "sha256:44df346d5215a8c0e360307d46ffaabe0f5d3502c8a1cefd700b34baf31d411a"}, {file = "coverage-7.6.1-cp313-cp313-win_amd64.whl", hash = "sha256:8284cf8c0dd272a247bc154eb6c95548722dce90d098c17a883ed36e67cdb129"}, {file = "coverage-7.6.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:d3296782ca4eab572a1a4eca686d8bfb00226300dcefdf43faa25b5242ab8a3e"}, {file = "coverage-7.6.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:502753043567491d3ff6d08629270127e0c31d4184c4c8d98f92c26f65019962"}, {file = "coverage-7.6.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6a89ecca80709d4076b95f89f308544ec8f7b4727e8a547913a35f16717856cb"}, {file = "coverage-7.6.1-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a318d68e92e80af8b00fa99609796fdbcdfef3629c77c6283566c6f02c6d6704"}, {file = "coverage-7.6.1-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:13b0a73a0896988f053e4fbb7de6d93388e6dd292b0d87ee51d106f2c11b465b"}, {file = "coverage-7.6.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:4421712dbfc5562150f7554f13dde997a2e932a6b5f352edcce948a815efee6f"}, {file = "coverage-7.6.1-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:166811d20dfea725e2e4baa71fffd6c968a958577848d2131f39b60043400223"}, {file = "coverage-7.6.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:225667980479a17db1048cb2bf8bfb39b8e5be8f164b8f6628b64f78a72cf9d3"}, {file = "coverage-7.6.1-cp313-cp313t-win32.whl", hash = "sha256:170d444ab405852903b7d04ea9ae9b98f98ab6d7e63e1115e82620807519797f"}, {file = "coverage-7.6.1-cp313-cp313t-win_amd64.whl", hash = "sha256:b9f222de8cded79c49bf184bdbc06630d4c58eec9459b939b4a690c82ed05657"}, {file = "coverage-7.6.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6db04803b6c7291985a761004e9060b2bca08da6d04f26a7f2294b8623a0c1a0"}, {file = "coverage-7.6.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:f1adfc8ac319e1a348af294106bc6a8458a0f1633cc62a1446aebc30c5fa186a"}, {file = "coverage-7.6.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a95324a9de9650a729239daea117df21f4b9868ce32e63f8b650ebe6cef5595b"}, {file = "coverage-7.6.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b43c03669dc4618ec25270b06ecd3ee4fa94c7f9b3c14bae6571ca00ef98b0d3"}, {file = "coverage-7.6.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8929543a7192c13d177b770008bc4e8119f2e1f881d563fc6b6305d2d0ebe9de"}, {file = "coverage-7.6.1-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:a09ece4a69cf399510c8ab25e0950d9cf2b42f7b3cb0374f95d2e2ff594478a6"}, {file = "coverage-7.6.1-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:9054a0754de38d9dbd01a46621636689124d666bad1936d76c0341f7d71bf569"}, {file = "coverage-7.6.1-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:0dbde0f4aa9a16fa4d754356a8f2e36296ff4d83994b2c9d8398aa32f222f989"}, {file = "coverage-7.6.1-cp38-cp38-win32.whl", hash = "sha256:da511e6ad4f7323ee5702e6633085fb76c2f893aaf8ce4c51a0ba4fc07580ea7"}, {file = "coverage-7.6.1-cp38-cp38-win_amd64.whl", hash = "sha256:3f1156e3e8f2872197af3840d8ad307a9dd18e615dc64d9ee41696f287c57ad8"}, {file = "coverage-7.6.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:abd5fd0db5f4dc9289408aaf34908072f805ff7792632250dcb36dc591d24255"}, {file = "coverage-7.6.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:547f45fa1a93154bd82050a7f3cddbc1a7a4dd2a9bf5cb7d06f4ae29fe94eaf8"}, {file = "coverage-7.6.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:645786266c8f18a931b65bfcefdbf6952dd0dea98feee39bd188607a9d307ed2"}, {file = "coverage-7.6.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9e0b2df163b8ed01d515807af24f63de04bebcecbd6c3bfeff88385789fdf75a"}, {file = "coverage-7.6.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:609b06f178fe8e9f89ef676532760ec0b4deea15e9969bf754b37f7c40326dbc"}, {file = "coverage-7.6.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:702855feff378050ae4f741045e19a32d57d19f3e0676d589df0575008ea5004"}, {file = "coverage-7.6.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:2bdb062ea438f22d99cba0d7829c2ef0af1d768d1e4a4f528087224c90b132cb"}, {file = "coverage-7.6.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:9c56863d44bd1c4fe2abb8a4d6f5371d197f1ac0ebdee542f07f35895fc07f36"}, {file = "coverage-7.6.1-cp39-cp39-win32.whl", hash = "sha256:6e2cd258d7d927d09493c8df1ce9174ad01b381d4729a9d8d4e38670ca24774c"}, {file = "coverage-7.6.1-cp39-cp39-win_amd64.whl", hash = "sha256:06a737c882bd26d0d6ee7269b20b12f14a8704807a01056c80bb881a4b2ce6ca"}, {file = "coverage-7.6.1-pp38.pp39.pp310-none-any.whl", hash = "sha256:e9a6e0eb86070e8ccaedfbd9d38fec54864f3125ab95419970575b42af7541df"}, {file = "coverage-7.6.1.tar.gz", hash = "sha256:953510dfb7b12ab69d20135a0662397f077c59b1e6379a768e97c59d852ee51d"}, ] [package.dependencies] tomli = {version = "*", optional = true, markers = "python_full_version <= \"3.11.0a6\" and extra == \"toml\""} [package.extras] toml = ["tomli"] [[package]] name = "dbus-python" version = "1.3.2" description = "Python bindings for libdbus" optional = true python-versions = ">=3.7" files = [ {file = "dbus-python-1.3.2.tar.gz", hash = "sha256:ad67819308618b5069537be237f8e68ca1c7fcc95ee4a121fe6845b1418248f8"}, ] [package.extras] doc = ["sphinx", "sphinx_rtd_theme"] test = ["tap.py"] [[package]] name = "decorator" version = "5.1.1" description = "Decorators for Humans" optional = true python-versions = ">=3.5" files = [ {file = "decorator-5.1.1-py3-none-any.whl", hash = "sha256:b8c3f85900b9dc423225913c5aace94729fe1fa9763b38939a95226f02d37186"}, {file = "decorator-5.1.1.tar.gz", hash = "sha256:637996211036b6385ef91435e4fae22989472f9d571faba8927ba8253acbc330"}, ] [[package]] name = "docutils" version = "0.20.1" description = "Docutils -- Python Documentation Utilities" optional = true python-versions = ">=3.7" files = [ {file = "docutils-0.20.1-py3-none-any.whl", hash = "sha256:96f387a2c5562db4476f09f13bbab2192e764cac08ebbf3a34a95d9b1e4a59d6"}, {file = "docutils-0.20.1.tar.gz", hash = "sha256:f08a4e276c3a1583a86dce3e34aba3fe04d02bba2dd51ed16106244e8a923e3b"}, ] [[package]] name = "exceptiongroup" version = "1.2.2" description = "Backport of PEP 654 (exception groups)" optional = false python-versions = ">=3.7" files = [ {file = "exceptiongroup-1.2.2-py3-none-any.whl", hash = "sha256:3111b9d131c238bec2f8f516e123e14ba243563fb135d3fe885990585aa7795b"}, {file = "exceptiongroup-1.2.2.tar.gz", hash = "sha256:47c2edf7c6738fafb49fd34290706d1a1a2f4d1c6df275526b62cbb4aa5393cc"}, ] [package.extras] test = ["pytest (>=6)"] [[package]] name = "filetype" version = "1.2.0" description = "Infer file type and MIME type of any file/buffer. No external dependencies." optional = false python-versions = "*" files = [ {file = "filetype-1.2.0-py2.py3-none-any.whl", hash = "sha256:7ce71b6880181241cf7ac8697a2f1eb6a8bd9b429f7ad6d27b8db9ba5f1c2d25"}, {file = "filetype-1.2.0.tar.gz", hash = "sha256:66b56cd6474bf41d8c54660347d37afcc3f7d1970648de365c102ef77548aadb"}, ] [[package]] name = "flask" version = "3.0.3" description = "A simple framework for building complex web applications." optional = false python-versions = ">=3.8" files = [ {file = "flask-3.0.3-py3-none-any.whl", hash = "sha256:34e815dfaa43340d1d15a5c3a02b8476004037eb4840b34910c6e21679d288f3"}, {file = "flask-3.0.3.tar.gz", hash = "sha256:ceb27b0af3823ea2737928a4d99d125a06175b8512c445cbd9a9ce200ef76842"}, ] [package.dependencies] blinker = ">=1.6.2" click = ">=8.1.3" importlib-metadata = {version = ">=3.6.0", markers = "python_version < \"3.10\""} itsdangerous = ">=2.1.2" Jinja2 = ">=3.1.2" Werkzeug = ">=3.0.0" [package.extras] async = ["asgiref (>=3.2)"] dotenv = ["python-dotenv"] [[package]] name = "flask-cors" version = "5.0.0" description = "A Flask extension adding a decorator for CORS support" optional = true python-versions = "*" files = [ {file = "Flask_Cors-5.0.0-py2.py3-none-any.whl", hash = "sha256:b9e307d082a9261c100d8fb0ba909eec6a228ed1b60a8315fd85f783d61910bc"}, {file = "flask_cors-5.0.0.tar.gz", hash = "sha256:5aadb4b950c4e93745034594d9f3ea6591f734bb3662e16e255ffbf5e89c88ef"}, ] [package.dependencies] Flask = ">=0.9" [[package]] name = "h11" version = "0.14.0" description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1" optional = false python-versions = ">=3.7" files = [ {file = "h11-0.14.0-py3-none-any.whl", hash = "sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761"}, {file = "h11-0.14.0.tar.gz", hash = "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d"}, ] [[package]] name = "httpcore" version = "1.0.7" description = "A minimal low-level HTTP client." optional = false python-versions = ">=3.8" files = [ {file = "httpcore-1.0.7-py3-none-any.whl", hash = "sha256:a3fff8f43dc260d5bd363d9f9cf1830fa3a458b332856f34282de498ed420edd"}, {file = "httpcore-1.0.7.tar.gz", hash = "sha256:8551cb62a169ec7162ac7be8d4817d561f60e08eaa485234898414bb5a8a0b4c"}, ] [package.dependencies] certifi = "*" h11 = ">=0.13,<0.15" [package.extras] asyncio = ["anyio (>=4.0,<5.0)"] http2 = ["h2 (>=3,<5)"] socks = ["socksio (==1.*)"] trio = ["trio (>=0.22.0,<1.0)"] [[package]] name = "httpx" version = "0.27.2" description = "The next generation HTTP client." optional = false python-versions = ">=3.8" files = [ {file = "httpx-0.27.2-py3-none-any.whl", hash = "sha256:7bb2708e112d8fdd7829cd4243970f0c223274051cb35ee80c03301ee29a3df0"}, {file = "httpx-0.27.2.tar.gz", hash = "sha256:f7c2be1d2f3c3c3160d441802406b206c2b76f5947b11115e6df10c6c65e66c2"}, ] [package.dependencies] anyio = "*" certifi = "*" httpcore = "==1.*" idna = "*" sniffio = "*" [package.extras] brotli = ["brotli", "brotlicffi"] cli = ["click (==8.*)", "pygments (==2.*)", "rich (>=10,<14)"] http2 = ["h2 (>=3,<5)"] socks = ["socksio (==1.*)"] zstd = ["zstandard (>=0.18.0)"] [[package]] name = "idna" version = "3.10" description = "Internationalized Domain Names in Applications (IDNA)" optional = false python-versions = ">=3.6" files = [ {file = "idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3"}, {file = "idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9"}, ] [package.extras] all = ["flake8 (>=7.1.1)", "mypy (>=1.11.2)", "pytest (>=8.3.2)", "ruff (>=0.6.2)"] [[package]] name = "ifaddr" version = "0.2.0" description = "Cross-platform network interface and IP address enumeration library" optional = true python-versions = "*" files = [ {file = "ifaddr-0.2.0-py3-none-any.whl", hash = "sha256:085e0305cfe6f16ab12d72e2024030f5d52674afad6911bb1eee207177b8a748"}, {file = "ifaddr-0.2.0.tar.gz", hash = "sha256:cc0cbfcaabf765d44595825fb96a99bb12c79716b73b44330ea38ee2b0c4aed4"}, ] [[package]] name = "imagesize" version = "1.4.1" description = "Getting image size from png/jpeg/jpeg2000/gif file" optional = true python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" files = [ {file = "imagesize-1.4.1-py2.py3-none-any.whl", hash = "sha256:0d8d18d08f840c19d0ee7ca1fd82490fdc3729b7ac93f49870406ddde8ef8d8b"}, {file = "imagesize-1.4.1.tar.gz", hash = "sha256:69150444affb9cb0d5cc5a92b3676f0b2fb7cd9ae39e947a5e11a36b4497cd4a"}, ] [[package]] name = "importlib-metadata" version = "8.5.0" description = "Read metadata from Python packages" optional = false python-versions = ">=3.8" files = [ {file = "importlib_metadata-8.5.0-py3-none-any.whl", hash = "sha256:45e54197d28b7a7f1559e60b95e7c567032b602131fbd588f1497f47880aa68b"}, {file = "importlib_metadata-8.5.0.tar.gz", hash = "sha256:71522656f0abace1d072b9e5481a48f07c138e00f079c38c8f883823f9c26bd7"}, ] [package.dependencies] zipp = ">=3.20" [package.extras] check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1)"] cover = ["pytest-cov"] doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] enabler = ["pytest-enabler (>=2.2)"] perf = ["ipython"] test = ["flufl.flake8", "importlib-resources (>=1.3)", "jaraco.test (>=5.4)", "packaging", "pyfakefs", "pytest (>=6,!=8.1.*)", "pytest-perf (>=0.9.2)"] type = ["pytest-mypy"] [[package]] name = "importlib-resources" version = "6.4.5" description = "Read resources from Python packages" optional = true python-versions = ">=3.8" files = [ {file = "importlib_resources-6.4.5-py3-none-any.whl", hash = "sha256:ac29d5f956f01d5e4bb63102a5a19957f1b9175e45649977264a1416783bb717"}, {file = "importlib_resources-6.4.5.tar.gz", hash = "sha256:980862a1d16c9e147a59603677fa2aa5fd82b87f223b6cb870695bcfce830065"}, ] [package.dependencies] zipp = {version = ">=3.1.0", markers = "python_version < \"3.10\""} [package.extras] check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1)"] cover = ["pytest-cov"] doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] enabler = ["pytest-enabler (>=2.2)"] test = ["jaraco.test (>=5.4)", "pytest (>=6,!=8.1.*)", "zipp (>=3.17)"] type = ["pytest-mypy"] [[package]] name = "inflate64" version = "1.0.0" description = "deflate64 compression/decompression library" optional = false python-versions = ">=3.8" files = [ {file = "inflate64-1.0.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a90c0bdf4a7ecddd8a64cc977181810036e35807f56b0bcacee9abb0fcfd18dc"}, {file = "inflate64-1.0.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:57fe7c14aebf1c5a74fc3b70d355be1280a011521a76aa3895486e62454f4242"}, {file = "inflate64-1.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d90730165f471d61a1a694a5e354f3ffa938227e8dcecb62d5d728e8069cee94"}, {file = "inflate64-1.0.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:543f400201f5c101141af3c79c82059e1aa6ef4f1584a7f1fa035fb2e465097f"}, {file = "inflate64-1.0.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6ceca14f7ec19fb44b047f56c50efb7521b389d222bba2b0a10286a0caeb03fa"}, {file = "inflate64-1.0.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:b559937a42f0c175b4d2dfc7eb53b97bdc87efa9add15ed5549c6abc1e89d02f"}, {file = "inflate64-1.0.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:5ff8bd2a562343fcbc4eea26fdc368904a3b5f6bb8262344274d3d74a1de15bb"}, {file = "inflate64-1.0.0-cp310-cp310-win_amd64.whl", hash = "sha256:0fe481f31695d35a433c3044ac8fd5d9f5069aaad03a0c04b570eb258ce655aa"}, {file = "inflate64-1.0.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:35a45f6979ad5874d4d4898c2fc770b136e61b96b850118fdaec5a5af1b9123a"}, {file = "inflate64-1.0.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:022ca1cc928e7365a05f7371ff06af143c6c667144965e2cf9a9236a2ae1c291"}, {file = "inflate64-1.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:46792ecf3565d64fd2c519b0a780c03a57e195613c9954ef94e739a057b3fd06"}, {file = "inflate64-1.0.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3a70ea2e456c15f7aa7c74b8ab8f20b4f8940ec657604c9f0a9de3342f280fff"}, {file = "inflate64-1.0.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3e243ea9bd36a035059f2365bd6d156ff59717fbafb0255cb0c75bf151bf6904"}, {file = "inflate64-1.0.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:4dc392dec1cd11cacda3d2637214ca45e38202e8a4f31d4a4e566d6e90625fc4"}, {file = "inflate64-1.0.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:8b402a50eda7ee75f342fc346d33a41bca58edc222a4b17f9be0db1daed459fa"}, {file = "inflate64-1.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:f5924499dc8800928c0ee4580fa8eb4ffa880b2cce4431537d0390e503a9c9ee"}, {file = "inflate64-1.0.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:0c644bf7208e20825ca3bbb5fb1f7f495cfcb49eb01a5f67338796d44a42f2bf"}, {file = "inflate64-1.0.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:9964a4eaf26a9d36f82a1d9b12c28e35800dd3d99eb340453ed12ac90c2976a8"}, {file = "inflate64-1.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2cccded63865640d03253897be7232b2bbac295fe43914c61f86a57aa23bb61d"}, {file = "inflate64-1.0.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d491f104fb3701926ebd82b8c9250dfba0ddcab584504e26f1e4adb26730378d"}, {file = "inflate64-1.0.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5ebad4a6cd2a2c1d81be0b09d4006479f3b258803c49a9224ef8ca0b649072fa"}, {file = "inflate64-1.0.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:6823b2c0cff3a8159140f3b17ec64fb8ec0e663b45a6593618ecdde8aeecb5b2"}, {file = "inflate64-1.0.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:228d504239d27958e71fc77e3119a6ac4528127df38468a0c95a5bd3927204b8"}, {file = "inflate64-1.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:ae2572e06bcfe15e3bbf77d4e4a6d6c55e2a70d6abceaaf60c5c3653ddb96dfd"}, {file = "inflate64-1.0.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:c10ca61212a753bbce6d341e7cfa779c161b839281f1f9fdc15cf1f324ce7c5b"}, {file = "inflate64-1.0.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:a982dc93920f9450da4d4f25c5e5c1288ef053b1d618cedc91adb67e035e35f5"}, {file = "inflate64-1.0.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:ca0310b2c55bc40394c5371db2a22f705fd594226cc09432e1eb04d3aed83930"}, {file = "inflate64-1.0.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e95044ae55a161144445527a2efad550851fecc699066423d24b2634a6a83710"}, {file = "inflate64-1.0.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:34de6902c39d9225459583d5034182d371fc694bc3cfd6c0fc89aa62e9809faf"}, {file = "inflate64-1.0.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:ebafbd813213dc470719cd0a2bcb53aab89d9059f4e75386048b4c4dcdb2fd99"}, {file = "inflate64-1.0.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:75448c7b414dadaeeb11dab9f75e022aa1e0ee19b00f570e9f58e933603d71ac"}, {file = "inflate64-1.0.0-cp38-cp38-win_amd64.whl", hash = "sha256:2be4e01c1b04761874cb44b35b6103ca5846bc36c18fc3ff5e8cbcd8bfc15e9f"}, {file = "inflate64-1.0.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:bf2981b95c1f26242bb084d9a07f3feb0cfe3d6d0a8d90f42389803bc1252c4a"}, {file = "inflate64-1.0.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9373ccf0661cc72ac84a0ad622634144da5ce7d57c9572ed0723d67a149feed2"}, {file = "inflate64-1.0.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e4650c6f65011ec57cf5cd96b92d5b7c6f59e502930c86eb8227c93cf02dc270"}, {file = "inflate64-1.0.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a475e8822f1a74c873e60b8f270773757ade024097ca39e43402d47c049c67d4"}, {file = "inflate64-1.0.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d4367480733ac8daf368f6fc704b7c9db85521ee745eb5bd443f4b97d2051acc"}, {file = "inflate64-1.0.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:6c5775c91f94f5eced9160fb0af12a09f3e030194f91a6a46e706a79350bd056"}, {file = "inflate64-1.0.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:d76d205b844d78ce04768060084ef20e64dcc63a3e9166674f857acaf4d140ed"}, {file = "inflate64-1.0.0-cp39-cp39-win_amd64.whl", hash = "sha256:92f0dc6af0e8e97324981178dc442956cbff1247a56d1e201af8d865244653f8"}, {file = "inflate64-1.0.0-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:f79542478e49e471e8b23556700e6f688a40dc93e9a746f77a546c13251b59b1"}, {file = "inflate64-1.0.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9a270be6b10cde01258c0097a663a307c62d12c78eb8f62f8e29f205335942c9"}, {file = "inflate64-1.0.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1616a87ff04f583e9558cc247ec0b72a30d540ee0c17cc77823be175c0ec92f0"}, {file = "inflate64-1.0.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:137ca6b315f0157a786c3a755a09395ca69aed8bcf42ad3437cb349f5ebc86d2"}, {file = "inflate64-1.0.0-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:8140942d1614bdeb5a9ddd7559348c5c77f884a42424aef7ccf149ccfb93aa08"}, {file = "inflate64-1.0.0-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9fe3f9051338bb7d07b5e7d88420d666b5109f33ae39aa55ecd1a053c0f22b1b"}, {file = "inflate64-1.0.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:36342338e957c790fc630d4afcdcc3926beb2ecaea0b302336079e8fa37e57a0"}, {file = "inflate64-1.0.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:9b65cc701ef33ab20dbfd1d64088ffd89a8c265b356d2c21ba0ec565661645ef"}, {file = "inflate64-1.0.0-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:dd6d3e7d47df43210a995fd1f5989602b64de3f2a17cf4cbff553518b3577fd4"}, {file = "inflate64-1.0.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8f033b2879696b855200cde5ca4e293132c7499df790acb2c0dacb336d5e83b1"}, {file = "inflate64-1.0.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5f816d1c8a0593375c289e285c96deaee9c2d8742cb0edbd26ee05588a9ae657"}, {file = "inflate64-1.0.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:1facd35319b6a391ee4c3d709c7c650bcada8cd7141d86cd8c2257287f45e6e6"}, {file = "inflate64-1.0.0.tar.gz", hash = "sha256:3278827b803cf006a1df251f3e13374c7d26db779e5a33329cc11789b804bc2d"}, ] [package.extras] check = ["check-manifest", "flake8", "flake8-black", "flake8-deprecated", "isort (>=5.0.3)", "mypy (>=0.940)", "mypy-extensions (>=0.4.1)", "pygments", "readme-renderer", "twine"] docs = ["docutils", "sphinx (>=5.0)"] test = ["pyannotate", "pytest"] [[package]] name = "iniconfig" version = "2.0.0" description = "brain-dead simple config-ini parsing" optional = false python-versions = ">=3.7" files = [ {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"}, {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, ] [[package]] name = "itsdangerous" version = "2.2.0" description = "Safely pass data to untrusted environments and back." optional = false python-versions = ">=3.8" files = [ {file = "itsdangerous-2.2.0-py3-none-any.whl", hash = "sha256:c6242fc49e35958c8b15141343aa660db5fc54d4f13a1db01a3f5891b98700ef"}, {file = "itsdangerous-2.2.0.tar.gz", hash = "sha256:e0050c0b7da1eea53ffaf149c0cfbb5c6e2e2b69c4bef22c81fa6eb73e5f6173"}, ] [[package]] name = "jellyfish" version = "1.1.0" description = "Approximate and phonetic matching of strings." optional = false python-versions = ">=3.7" files = [ {file = "jellyfish-1.1.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:feb1fa5838f2bb6dbc9f6d07dabf4b9d91e130b289d72bd70dc33b651667688f"}, {file = "jellyfish-1.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:623fa58cca9b8e594a46e7b9cf3af629588a202439d97580a153d6af24736a1b"}, {file = "jellyfish-1.1.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a87e4a17006f7cdd7027a053aeeaacfb0b3366955e242cd5b74bbf882bafe022"}, {file = "jellyfish-1.1.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f10fa36491840bda29f2164cc49e61244ea27c5db5a66aaa437724f5626f5610"}, {file = "jellyfish-1.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:24f91daaa515284cdb691b1e01b0f91f9c9e51e685420725a1ded4f54d5376ff"}, {file = "jellyfish-1.1.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:65e58350618ebb1488246998a7356a8c9a7c839ec3ecfe936df55be6776fc173"}, {file = "jellyfish-1.1.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:5c5ed62b23093b11de130c3fe1b381a2d3bfaf086757fa21341ac6f30a353e92"}, {file = "jellyfish-1.1.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:c42aa02e791d3e5a8fc6a96bec9f64ebbb2afef27b01eca201b56132e3d0c64e"}, {file = "jellyfish-1.1.0-cp310-none-win32.whl", hash = "sha256:84680353261161c627cbdd622ea4243e3d3da75894bfacc2f3fcbbe56e8e59d4"}, {file = "jellyfish-1.1.0-cp310-none-win_amd64.whl", hash = "sha256:017c794b89d827d0306cb056fc5fbd040ff558a90ff0e68a6b60d6e6ba661fe3"}, {file = "jellyfish-1.1.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:fed2e4ecf9b4995d2aa771453d0a0fdf47a5e1b13dbd74b98a30cb0070ede30c"}, {file = "jellyfish-1.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:61a382ba8a3d3cd0bd50029062d54d3a0726679be248789fef6a3901eee47a60"}, {file = "jellyfish-1.1.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0a4b526ed2080b97431454075c46c19baddc944e95cc605248e32a2a07be231e"}, {file = "jellyfish-1.1.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:0fa7450c3217724b73099cb18ee594926fcbc1cc4d9964350f31a4c1dc267b35"}, {file = "jellyfish-1.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:33ebb6e9647d5d52f4d461a163449f6d1c73f1a80ccbe98bb17efac0062a6423"}, {file = "jellyfish-1.1.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:759172602343115f910d7c63b39239051e32425115bc31ab4dafdaf6177f880c"}, {file = "jellyfish-1.1.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:273fdc362ccdb09259eec9bc4abdc2467d9a54bd94d05ae22e71423dd1357255"}, {file = "jellyfish-1.1.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:bd5c335f8d762447691dc0572f4eaf0cfdfbfffb6dce740341425ab1b32134ff"}, {file = "jellyfish-1.1.0-cp311-none-win32.whl", hash = "sha256:cc16a60a42f1541ad9c13c72c797107388227f01189aa3c0ec7ee9b939e57ea8"}, {file = "jellyfish-1.1.0-cp311-none-win_amd64.whl", hash = "sha256:95dfe61eabf360a92e6d76d1c4dbafa29bcb3f70e2ad7354de2661141fcce038"}, {file = "jellyfish-1.1.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:828a7000d369cbd4d812b88510c01fdab20b73dc54c63cdbe03bdff67ab362d0"}, {file = "jellyfish-1.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:e250dc1074d730a03c96ac9dfce44716cf45e0e2825cbddaf32a015cdf9cf594"}, {file = "jellyfish-1.1.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:87dc2a82c45b773a579fb695a5956a54106c1187f27c9ccee8508726d2e59cfc"}, {file = "jellyfish-1.1.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e41677ec860454da5977c698fc64fed73b4054a92c5c62ba7d1af535f8082ac7"}, {file = "jellyfish-1.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e9d4002d01252f18eb26f28b66f6c9ce0696221804d8769553c5912b2f221a18"}, {file = "jellyfish-1.1.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:936df26c10ca6cd6b4f0fb97753087354c568e2129c197cbb4e0f0172db7511f"}, {file = "jellyfish-1.1.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:684c2093fa0d68a91146e15a1e9ca859259b19d3bc36ec4d60948d86751f744e"}, {file = "jellyfish-1.1.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:2fcaefebe9d67f282d89d3a66646b77184a42b3eca2771636789b2dc1288c003"}, {file = "jellyfish-1.1.0-cp312-none-win32.whl", hash = "sha256:e512c99941a257541ffd9f75c7a5c4689de0206841b72f1eb015599d17fed2c3"}, {file = "jellyfish-1.1.0-cp312-none-win_amd64.whl", hash = "sha256:2b928bad2887c662783a4d9b5828ed1fa0e943f680589f7fc002c456fc02e184"}, {file = "jellyfish-1.1.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:5d510b04e2a39f27aef391ca18bf527ec5d9a2438a63731b87faada83996cb92"}, {file = "jellyfish-1.1.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:57d005cc5daa4d0a8d88341d86b1dce24e3f1d7721da75326c0b7af598a4f58c"}, {file = "jellyfish-1.1.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:889edab0fb2a29d29c148c9327752df525c9bdaef03eef01d1bd9c1f90b47ebf"}, {file = "jellyfish-1.1.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:937b657aacba8fe8482ebc5fea5ba1aee987ecb9da0f037bfb8a1a9045d05746"}, {file = "jellyfish-1.1.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3cb5088436ce1fdabcb46aed3a3cc215f0432313596f4e5abe5300ed833b697c"}, {file = "jellyfish-1.1.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:af74156301a0ff05a22e8cf46250678e23fa447279ba6dffbf9feff01128f51d"}, {file = "jellyfish-1.1.0-cp313-cp313-musllinux_1_1_i686.whl", hash = "sha256:3f978bc430bbed4df3c10b2a66be7b5bddd09e6c2856c7a17fa2298fb193d4d4"}, {file = "jellyfish-1.1.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:b460f0bbde533f6f8624c1d7439e7f511b227ca18a58781e7f38f21961bd3f09"}, {file = "jellyfish-1.1.0-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:7cd4b706cb6c4739846d78a398c67996cb451b09a732a625793cfe8d4f37af1b"}, {file = "jellyfish-1.1.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:61cded25b47fe6b4c2ea9478c0a5a7531845218525a1b2627c67907ee9fe9b15"}, {file = "jellyfish-1.1.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:04bf33577059afba33227977e4a2c08ccb954eb77c849fde564af3e31ee509d9"}, {file = "jellyfish-1.1.0-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:327496501a44fbdfe0602fdc6a7d4317a7598202f1f652c9c4f0a49529a385cd"}, {file = "jellyfish-1.1.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0d1e6bac549cc2919b83d0ebe26566404ae3dfef5ef86229d1d826e3aeaba4b"}, {file = "jellyfish-1.1.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:b5fec525f15b39687dbfd75589333df4e6f6d15d3b1e0ada02bf206363dfd2af"}, {file = "jellyfish-1.1.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:8b2faf015e86a9efd5679b3abde83cbd8f3104b9e89445aa76b8481b206b3e67"}, {file = "jellyfish-1.1.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:b73efda07d52a1583afb8915a5f9feb017d0b60ae6d03071b21cc4f0a8a08ec1"}, {file = "jellyfish-1.1.0-cp38-none-win32.whl", hash = "sha256:4a5199583a956d313be825972d7c14a0d9e455884acd12c03d05e4272c6c3bb8"}, {file = "jellyfish-1.1.0-cp38-none-win_amd64.whl", hash = "sha256:755b68920a839f9e2b4813f0990a8dadcc9a24980bb29839f636ab5e36aaa256"}, {file = "jellyfish-1.1.0-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:e965241e54f9cb9be6fe8f7a1376b6cc61ff831de017bde9150156771820f669"}, {file = "jellyfish-1.1.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3e59a4c3bf0847dfff44195a4c250bc9e281b1c403f6212534ee36fc7c913dc1"}, {file = "jellyfish-1.1.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:84fa4e72b7754060d352604e07ea89af98403b0436caad443276ae46135b7fd7"}, {file = "jellyfish-1.1.0-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:125e9bfd1cc2c053eae3afa04fa142bbc8b3c1290a40a3416271b221f7e6bc87"}, {file = "jellyfish-1.1.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e4a8fff36462bf1bdaa339d58fadd7e79a63690902e6d7ddd65a84efc3a4cc6d"}, {file = "jellyfish-1.1.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:6b438b3d7f970cfd8f77b30b05694537a54c08f3775b35debae45ff5a469f1a5"}, {file = "jellyfish-1.1.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:cf8d26c3735b5c2764cc53482dec14bb9b794ba829db3cd4c9a29d194a61cada"}, {file = "jellyfish-1.1.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:f341d0582ecac0aa73f380056dc8d25d8a60104f94debe8bf3f924a32a11588d"}, {file = "jellyfish-1.1.0-cp39-none-win32.whl", hash = "sha256:49f2be59573b22d0adb615585ff66ca050198ec1f9f6784eec168bcd8137caf5"}, {file = "jellyfish-1.1.0-cp39-none-win_amd64.whl", hash = "sha256:c58988138666b1cd860004c1afc7a09bb402e71e16e1f324be5c5d2b85fdfa3e"}, {file = "jellyfish-1.1.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:54effec80c7a5013bea8e2ea6cd87fdd35a2c5b35f86ccf69ec33f4212245f25"}, {file = "jellyfish-1.1.0-pp310-pypy310_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:12ae67e9016c9a173453023fd7b400ec002bbc106c12722d914c53951acfa190"}, {file = "jellyfish-1.1.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:efd342f9d4fb0ead8a3c30fe26e442308fb665ca37f4aa97baf448d814469bf1"}, {file = "jellyfish-1.1.0-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:b0dc9f1bb335b6caa412c3d27028e25d315ef2bc993d425db93e451d7bc28056"}, {file = "jellyfish-1.1.0-pp310-pypy310_pp73-musllinux_1_1_i686.whl", hash = "sha256:3f12cb59b3266e37ec47bd7c2c37faadc74ae8ccdc0190444daeafda3bd93da2"}, {file = "jellyfish-1.1.0-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:c7ea99734b7767243b5b98eca953f0d719b48b0d630af3965638699728ef7523"}, {file = "jellyfish-1.1.0-pp38-pypy38_pp73-macosx_10_12_x86_64.whl", hash = "sha256:1a90889fdb96ca27fc176e19a472c736e044d7190c924d9b7cfb0444881f921c"}, {file = "jellyfish-1.1.0-pp38-pypy38_pp73-macosx_11_0_arm64.whl", hash = "sha256:c01cdf0d52d07e07fb0dfa2b3c03ca3b5a07088f08b38b06376ed228d842e501"}, {file = "jellyfish-1.1.0-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1a4678a2623cc83fde7ff683ba78d308edf7e54a1c81dd295cdf525761b9fcc1"}, {file = "jellyfish-1.1.0-pp38-pypy38_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b557b8e1fdad4a36f467ee44f5532a4a13e5300b93b2b5e70ff75d0d16458132"}, {file = "jellyfish-1.1.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b5c34d12730d912bafab9f6daaa7fb2c6fa6afc0a8fc2c4cdc017df485d8d843"}, {file = "jellyfish-1.1.0-pp38-pypy38_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:d977a1e0fa3814d517b16d58a39a16e449bbd900b966dd921e770d0fd67bfa45"}, {file = "jellyfish-1.1.0-pp38-pypy38_pp73-musllinux_1_1_i686.whl", hash = "sha256:6662152bf510cc7daef18965dd80cfa98710b479bda87a3170c86c4e0a6dc1ab"}, {file = "jellyfish-1.1.0-pp38-pypy38_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:e447e3807c73aeda7b592919c105bf98ce0297a228aff68aafe4fe70a39b9a78"}, {file = "jellyfish-1.1.0-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:ca252e6088c6afe5f8138ce9f557157ad0329f0610914ba50729c641d57cd662"}, {file = "jellyfish-1.1.0-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:b2512ab6a1625a168796faaa159e1d1b8847cb3d0cc2b1b09ae77ff0623e7d10"}, {file = "jellyfish-1.1.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b868da3186306efb48fbd8a8dee0a742a5c8bc9c4c74aa5003914a8600435ba8"}, {file = "jellyfish-1.1.0-pp39-pypy39_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bcc2cb1f007ddfad2f9175a8c1f934a8a0a6cc73187e2339fe1a4b3fd90b263e"}, {file = "jellyfish-1.1.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4e17885647f3a0faf1518cf6b319865b2e84439cfc16a3ea14468513c0fba227"}, {file = "jellyfish-1.1.0-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:84ea543d05e6b7a7a704d45ebd9c753e2425da01fc5000ddc149031be541c4d5"}, {file = "jellyfish-1.1.0-pp39-pypy39_pp73-musllinux_1_1_i686.whl", hash = "sha256:065a59ab0d02969d45e5ab4b0315ed6f5977a4eb8eaef24f2589e25b85822d18"}, {file = "jellyfish-1.1.0-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:f747f34071e1558151b342a2bf96b813e04b5384024ba7c50f3c907fbaab484f"}, {file = "jellyfish-1.1.0.tar.gz", hash = "sha256:2a2eec494c81dc1eb23dfef543110dad1873538eccaffabea8520bdac8aecbc1"}, ] [[package]] name = "jinja2" version = "3.1.4" description = "A very fast and expressive template engine." optional = false python-versions = ">=3.7" files = [ {file = "jinja2-3.1.4-py3-none-any.whl", hash = "sha256:bc5dd2abb727a5319567b7a813e6a2e7318c39f4f487cfe6c89c6f9c7d25197d"}, {file = "jinja2-3.1.4.tar.gz", hash = "sha256:4a3aee7acbbe7303aede8e9648d13b8bf88a429282aa6122a993f0ac800cb369"}, ] [package.dependencies] MarkupSafe = ">=2.0" [package.extras] i18n = ["Babel (>=2.7)"] [[package]] name = "joblib" version = "1.4.2" description = "Lightweight pipelining with Python functions" optional = true python-versions = ">=3.8" files = [ {file = "joblib-1.4.2-py3-none-any.whl", hash = "sha256:06d478d5674cbc267e7496a410ee875abd68e4340feff4490bcb7afb88060ae6"}, {file = "joblib-1.4.2.tar.gz", hash = "sha256:2382c5816b2636fbd20a09e0f4e9dad4736765fdfb7dca582943b9c1366b3f0e"}, ] [[package]] name = "langdetect" version = "1.0.9" description = "Language detection library ported from Google's language-detection." optional = true python-versions = "*" files = [ {file = "langdetect-1.0.9-py2-none-any.whl", hash = "sha256:7cbc0746252f19e76f77c0b1690aadf01963be835ef0cd4b56dddf2a8f1dfc2a"}, {file = "langdetect-1.0.9.tar.gz", hash = "sha256:cbc1fef89f8d062739774bd51eda3da3274006b3661d199c2655f6b3f6d605a0"}, ] [package.dependencies] six = "*" [[package]] name = "lazy-loader" version = "0.4" description = "Makes it easy to load subpackages and functions on demand." optional = true python-versions = ">=3.7" files = [ {file = "lazy_loader-0.4-py3-none-any.whl", hash = "sha256:342aa8e14d543a154047afb4ba8ef17f5563baad3fc610d7b15b213b0f119efc"}, {file = "lazy_loader-0.4.tar.gz", hash = "sha256:47c75182589b91a4e1a85a136c074285a5ad4d9f39c63e0d7fb76391c4574cd1"}, ] [package.dependencies] packaging = "*" [package.extras] dev = ["changelist (==0.5)"] lint = ["pre-commit (==3.7.0)"] test = ["pytest (>=7.4)", "pytest-cov (>=4.1)"] [[package]] name = "librosa" version = "0.10.2.post1" description = "Python module for audio and music processing" optional = true python-versions = ">=3.7" files = [ {file = "librosa-0.10.2.post1-py3-none-any.whl", hash = "sha256:dc882750e8b577a63039f25661b7e39ec4cfbacc99c1cffba666cd664fb0a7a0"}, {file = "librosa-0.10.2.post1.tar.gz", hash = "sha256:cd99f16717cbcd1e0983e37308d1db46a6f7dfc2e396e5a9e61e6821e44bd2e7"}, ] [package.dependencies] audioread = ">=2.1.9" decorator = ">=4.3.0" joblib = ">=0.14" lazy-loader = ">=0.1" msgpack = ">=1.0" numba = ">=0.51.0" numpy = ">=1.20.3,<1.22.0 || >1.22.0,<1.22.1 || >1.22.1,<1.22.2 || >1.22.2" pooch = ">=1.1" scikit-learn = ">=0.20.0" scipy = ">=1.2.0" soundfile = ">=0.12.1" soxr = ">=0.3.2" typing-extensions = ">=4.1.1" [package.extras] display = ["matplotlib (>=3.5.0)"] docs = ["ipython (>=7.0)", "matplotlib (>=3.5.0)", "mir-eval (>=0.5)", "numba (>=0.51)", "numpydoc", "presets", "sphinx (!=1.3.1)", "sphinx-copybutton (>=0.5.2)", "sphinx-gallery (>=0.7)", "sphinx-multiversion (>=0.2.3)", "sphinx-rtd-theme (>=1.2.0)", "sphinxcontrib-svg2pdfconverter"] tests = ["matplotlib (>=3.5.0)", "packaging (>=20.0)", "pytest", "pytest-cov", "pytest-mpl", "resampy (>=0.2.2)", "samplerate", "types-decorator"] [[package]] name = "llvmlite" version = "0.41.1" description = "lightweight wrapper around basic LLVM functionality" optional = true python-versions = ">=3.8" files = [ {file = "llvmlite-0.41.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c1e1029d47ee66d3a0c4d6088641882f75b93db82bd0e6178f7bd744ebce42b9"}, {file = "llvmlite-0.41.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:150d0bc275a8ac664a705135e639178883293cf08c1a38de3bbaa2f693a0a867"}, {file = "llvmlite-0.41.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1eee5cf17ec2b4198b509272cf300ee6577229d237c98cc6e63861b08463ddc6"}, {file = "llvmlite-0.41.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0dd0338da625346538f1173a17cabf21d1e315cf387ca21b294ff209d176e244"}, {file = "llvmlite-0.41.1-cp310-cp310-win32.whl", hash = "sha256:fa1469901a2e100c17eb8fe2678e34bd4255a3576d1a543421356e9c14d6e2ae"}, {file = "llvmlite-0.41.1-cp310-cp310-win_amd64.whl", hash = "sha256:2b76acee82ea0e9304be6be9d4b3840208d050ea0dcad75b1635fa06e949a0ae"}, {file = "llvmlite-0.41.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:210e458723436b2469d61b54b453474e09e12a94453c97ea3fbb0742ba5a83d8"}, {file = "llvmlite-0.41.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:855f280e781d49e0640aef4c4af586831ade8f1a6c4df483fb901cbe1a48d127"}, {file = "llvmlite-0.41.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b67340c62c93a11fae482910dc29163a50dff3dfa88bc874872d28ee604a83be"}, {file = "llvmlite-0.41.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2181bb63ef3c607e6403813421b46982c3ac6bfc1f11fa16a13eaafb46f578e6"}, {file = "llvmlite-0.41.1-cp311-cp311-win_amd64.whl", hash = "sha256:9564c19b31a0434f01d2025b06b44c7ed422f51e719ab5d24ff03b7560066c9a"}, {file = "llvmlite-0.41.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:5940bc901fb0325970415dbede82c0b7f3e35c2d5fd1d5e0047134c2c46b3281"}, {file = "llvmlite-0.41.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:8b0a9a47c28f67a269bb62f6256e63cef28d3c5f13cbae4fab587c3ad506778b"}, {file = "llvmlite-0.41.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f8afdfa6da33f0b4226af8e64cfc2b28986e005528fbf944d0a24a72acfc9432"}, {file = "llvmlite-0.41.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8454c1133ef701e8c050a59edd85d238ee18bb9a0eb95faf2fca8b909ee3c89a"}, {file = "llvmlite-0.41.1-cp38-cp38-win32.whl", hash = "sha256:2d92c51e6e9394d503033ffe3292f5bef1566ab73029ec853861f60ad5c925d0"}, {file = "llvmlite-0.41.1-cp38-cp38-win_amd64.whl", hash = "sha256:df75594e5a4702b032684d5481db3af990b69c249ccb1d32687b8501f0689432"}, {file = "llvmlite-0.41.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:04725975e5b2af416d685ea0769f4ecc33f97be541e301054c9f741003085802"}, {file = "llvmlite-0.41.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:bf14aa0eb22b58c231243dccf7e7f42f7beec48970f2549b3a6acc737d1a4ba4"}, {file = "llvmlite-0.41.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:92c32356f669e036eb01016e883b22add883c60739bc1ebee3a1cc0249a50828"}, {file = "llvmlite-0.41.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:24091a6b31242bcdd56ae2dbea40007f462260bc9bdf947953acc39dffd54f8f"}, {file = "llvmlite-0.41.1-cp39-cp39-win32.whl", hash = "sha256:880cb57ca49e862e1cd077104375b9d1dfdc0622596dfa22105f470d7bacb309"}, {file = "llvmlite-0.41.1-cp39-cp39-win_amd64.whl", hash = "sha256:92f093986ab92e71c9ffe334c002f96defc7986efda18397d0f08534f3ebdc4d"}, {file = "llvmlite-0.41.1.tar.gz", hash = "sha256:f19f767a018e6ec89608e1f6b13348fa2fcde657151137cb64e56d48598a92db"}, ] [[package]] name = "lxml" version = "5.3.0" description = "Powerful and Pythonic XML processing library combining libxml2/libxslt with the ElementTree API." optional = true python-versions = ">=3.6" files = [ {file = "lxml-5.3.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:dd36439be765e2dde7660212b5275641edbc813e7b24668831a5c8ac91180656"}, {file = "lxml-5.3.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ae5fe5c4b525aa82b8076c1a59d642c17b6e8739ecf852522c6321852178119d"}, {file = "lxml-5.3.0-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:501d0d7e26b4d261fca8132854d845e4988097611ba2531408ec91cf3fd9d20a"}, {file = "lxml-5.3.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fb66442c2546446944437df74379e9cf9e9db353e61301d1a0e26482f43f0dd8"}, {file = "lxml-5.3.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9e41506fec7a7f9405b14aa2d5c8abbb4dbbd09d88f9496958b6d00cb4d45330"}, {file = "lxml-5.3.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f7d4a670107d75dfe5ad080bed6c341d18c4442f9378c9f58e5851e86eb79965"}, {file = "lxml-5.3.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:41ce1f1e2c7755abfc7e759dc34d7d05fd221723ff822947132dc934d122fe22"}, {file = "lxml-5.3.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:44264ecae91b30e5633013fb66f6ddd05c006d3e0e884f75ce0b4755b3e3847b"}, {file = "lxml-5.3.0-cp310-cp310-manylinux_2_28_ppc64le.whl", hash = "sha256:3c174dc350d3ec52deb77f2faf05c439331d6ed5e702fc247ccb4e6b62d884b7"}, {file = "lxml-5.3.0-cp310-cp310-manylinux_2_28_s390x.whl", hash = "sha256:2dfab5fa6a28a0b60a20638dc48e6343c02ea9933e3279ccb132f555a62323d8"}, {file = "lxml-5.3.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:b1c8c20847b9f34e98080da785bb2336ea982e7f913eed5809e5a3c872900f32"}, {file = "lxml-5.3.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:2c86bf781b12ba417f64f3422cfc302523ac9cd1d8ae8c0f92a1c66e56ef2e86"}, {file = "lxml-5.3.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:c162b216070f280fa7da844531169be0baf9ccb17263cf5a8bf876fcd3117fa5"}, {file = "lxml-5.3.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:36aef61a1678cb778097b4a6eeae96a69875d51d1e8f4d4b491ab3cfb54b5a03"}, {file = "lxml-5.3.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:f65e5120863c2b266dbcc927b306c5b78e502c71edf3295dfcb9501ec96e5fc7"}, {file = "lxml-5.3.0-cp310-cp310-win32.whl", hash = "sha256:ef0c1fe22171dd7c7c27147f2e9c3e86f8bdf473fed75f16b0c2e84a5030ce80"}, {file = "lxml-5.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:052d99051e77a4f3e8482c65014cf6372e61b0a6f4fe9edb98503bb5364cfee3"}, {file = "lxml-5.3.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:74bcb423462233bc5d6066e4e98b0264e7c1bed7541fff2f4e34fe6b21563c8b"}, {file = "lxml-5.3.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a3d819eb6f9b8677f57f9664265d0a10dd6551d227afb4af2b9cd7bdc2ccbf18"}, {file = "lxml-5.3.0-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5b8f5db71b28b8c404956ddf79575ea77aa8b1538e8b2ef9ec877945b3f46442"}, {file = "lxml-5.3.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2c3406b63232fc7e9b8783ab0b765d7c59e7c59ff96759d8ef9632fca27c7ee4"}, {file = "lxml-5.3.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2ecdd78ab768f844c7a1d4a03595038c166b609f6395e25af9b0f3f26ae1230f"}, {file = "lxml-5.3.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:168f2dfcfdedf611eb285efac1516c8454c8c99caf271dccda8943576b67552e"}, {file = "lxml-5.3.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aa617107a410245b8660028a7483b68e7914304a6d4882b5ff3d2d3eb5948d8c"}, {file = "lxml-5.3.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:69959bd3167b993e6e710b99051265654133a98f20cec1d9b493b931942e9c16"}, {file = "lxml-5.3.0-cp311-cp311-manylinux_2_28_ppc64le.whl", hash = "sha256:bd96517ef76c8654446fc3db9242d019a1bb5fe8b751ba414765d59f99210b79"}, {file = "lxml-5.3.0-cp311-cp311-manylinux_2_28_s390x.whl", hash = "sha256:ab6dd83b970dc97c2d10bc71aa925b84788c7c05de30241b9e96f9b6d9ea3080"}, {file = "lxml-5.3.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:eec1bb8cdbba2925bedc887bc0609a80e599c75b12d87ae42ac23fd199445654"}, {file = "lxml-5.3.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:6a7095eeec6f89111d03dabfe5883a1fd54da319c94e0fb104ee8f23616b572d"}, {file = "lxml-5.3.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:6f651ebd0b21ec65dfca93aa629610a0dbc13dbc13554f19b0113da2e61a4763"}, {file = "lxml-5.3.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:f422a209d2455c56849442ae42f25dbaaba1c6c3f501d58761c619c7836642ec"}, {file = "lxml-5.3.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:62f7fdb0d1ed2065451f086519865b4c90aa19aed51081979ecd05a21eb4d1be"}, {file = "lxml-5.3.0-cp311-cp311-win32.whl", hash = "sha256:c6379f35350b655fd817cd0d6cbeef7f265f3ae5fedb1caae2eb442bbeae9ab9"}, {file = "lxml-5.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:9c52100e2c2dbb0649b90467935c4b0de5528833c76a35ea1a2691ec9f1ee7a1"}, {file = "lxml-5.3.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:e99f5507401436fdcc85036a2e7dc2e28d962550afe1cbfc07c40e454256a859"}, {file = "lxml-5.3.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:384aacddf2e5813a36495233b64cb96b1949da72bef933918ba5c84e06af8f0e"}, {file = "lxml-5.3.0-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:874a216bf6afaf97c263b56371434e47e2c652d215788396f60477540298218f"}, {file = "lxml-5.3.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:65ab5685d56914b9a2a34d67dd5488b83213d680b0c5d10b47f81da5a16b0b0e"}, {file = "lxml-5.3.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:aac0bbd3e8dd2d9c45ceb82249e8bdd3ac99131a32b4d35c8af3cc9db1657179"}, {file = "lxml-5.3.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b369d3db3c22ed14c75ccd5af429086f166a19627e84a8fdade3f8f31426e52a"}, {file = "lxml-5.3.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c24037349665434f375645fa9d1f5304800cec574d0310f618490c871fd902b3"}, {file = "lxml-5.3.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:62d172f358f33a26d6b41b28c170c63886742f5b6772a42b59b4f0fa10526cb1"}, {file = "lxml-5.3.0-cp312-cp312-manylinux_2_28_ppc64le.whl", hash = "sha256:c1f794c02903c2824fccce5b20c339a1a14b114e83b306ff11b597c5f71a1c8d"}, {file = "lxml-5.3.0-cp312-cp312-manylinux_2_28_s390x.whl", hash = "sha256:5d6a6972b93c426ace71e0be9a6f4b2cfae9b1baed2eed2006076a746692288c"}, {file = "lxml-5.3.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:3879cc6ce938ff4eb4900d901ed63555c778731a96365e53fadb36437a131a99"}, {file = "lxml-5.3.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:74068c601baff6ff021c70f0935b0c7bc528baa8ea210c202e03757c68c5a4ff"}, {file = "lxml-5.3.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:ecd4ad8453ac17bc7ba3868371bffb46f628161ad0eefbd0a855d2c8c32dd81a"}, {file = "lxml-5.3.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:7e2f58095acc211eb9d8b5771bf04df9ff37d6b87618d1cbf85f92399c98dae8"}, {file = "lxml-5.3.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e63601ad5cd8f860aa99d109889b5ac34de571c7ee902d6812d5d9ddcc77fa7d"}, {file = "lxml-5.3.0-cp312-cp312-win32.whl", hash = "sha256:17e8d968d04a37c50ad9c456a286b525d78c4a1c15dd53aa46c1d8e06bf6fa30"}, {file = "lxml-5.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:c1a69e58a6bb2de65902051d57fde951febad631a20a64572677a1052690482f"}, {file = "lxml-5.3.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:8c72e9563347c7395910de6a3100a4840a75a6f60e05af5e58566868d5eb2d6a"}, {file = "lxml-5.3.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e92ce66cd919d18d14b3856906a61d3f6b6a8500e0794142338da644260595cd"}, {file = "lxml-5.3.0-cp313-cp313-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1d04f064bebdfef9240478f7a779e8c5dc32b8b7b0b2fc6a62e39b928d428e51"}, {file = "lxml-5.3.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5c2fb570d7823c2bbaf8b419ba6e5662137f8166e364a8b2b91051a1fb40ab8b"}, {file = "lxml-5.3.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0c120f43553ec759f8de1fee2f4794452b0946773299d44c36bfe18e83caf002"}, {file = "lxml-5.3.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:562e7494778a69086f0312ec9689f6b6ac1c6b65670ed7d0267e49f57ffa08c4"}, {file = "lxml-5.3.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:423b121f7e6fa514ba0c7918e56955a1d4470ed35faa03e3d9f0e3baa4c7e492"}, {file = "lxml-5.3.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:c00f323cc00576df6165cc9d21a4c21285fa6b9989c5c39830c3903dc4303ef3"}, {file = "lxml-5.3.0-cp313-cp313-manylinux_2_28_ppc64le.whl", hash = "sha256:1fdc9fae8dd4c763e8a31e7630afef517eab9f5d5d31a278df087f307bf601f4"}, {file = "lxml-5.3.0-cp313-cp313-manylinux_2_28_s390x.whl", hash = "sha256:658f2aa69d31e09699705949b5fc4719cbecbd4a97f9656a232e7d6c7be1a367"}, {file = "lxml-5.3.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:1473427aff3d66a3fa2199004c3e601e6c4500ab86696edffdbc84954c72d832"}, {file = "lxml-5.3.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a87de7dd873bf9a792bf1e58b1c3887b9264036629a5bf2d2e6579fe8e73edff"}, {file = "lxml-5.3.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:0d7b36afa46c97875303a94e8f3ad932bf78bace9e18e603f2085b652422edcd"}, {file = "lxml-5.3.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:cf120cce539453ae086eacc0130a324e7026113510efa83ab42ef3fcfccac7fb"}, {file = "lxml-5.3.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:df5c7333167b9674aa8ae1d4008fa4bc17a313cc490b2cca27838bbdcc6bb15b"}, {file = "lxml-5.3.0-cp313-cp313-win32.whl", hash = "sha256:c802e1c2ed9f0c06a65bc4ed0189d000ada8049312cfeab6ca635e39c9608957"}, {file = "lxml-5.3.0-cp313-cp313-win_amd64.whl", hash = "sha256:406246b96d552e0503e17a1006fd27edac678b3fcc9f1be71a2f94b4ff61528d"}, {file = "lxml-5.3.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:8f0de2d390af441fe8b2c12626d103540b5d850d585b18fcada58d972b74a74e"}, {file = "lxml-5.3.0-cp36-cp36m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1afe0a8c353746e610bd9031a630a95bcfb1a720684c3f2b36c4710a0a96528f"}, {file = "lxml-5.3.0-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:56b9861a71575f5795bde89256e7467ece3d339c9b43141dbdd54544566b3b94"}, {file = "lxml-5.3.0-cp36-cp36m-manylinux_2_28_x86_64.whl", hash = "sha256:9fb81d2824dff4f2e297a276297e9031f46d2682cafc484f49de182aa5e5df99"}, {file = "lxml-5.3.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:2c226a06ecb8cdef28845ae976da407917542c5e6e75dcac7cc33eb04aaeb237"}, {file = "lxml-5.3.0-cp36-cp36m-musllinux_1_2_x86_64.whl", hash = "sha256:7d3d1ca42870cdb6d0d29939630dbe48fa511c203724820fc0fd507b2fb46577"}, {file = "lxml-5.3.0-cp36-cp36m-win32.whl", hash = "sha256:094cb601ba9f55296774c2d57ad68730daa0b13dc260e1f941b4d13678239e70"}, {file = "lxml-5.3.0-cp36-cp36m-win_amd64.whl", hash = "sha256:eafa2c8658f4e560b098fe9fc54539f86528651f61849b22111a9b107d18910c"}, {file = "lxml-5.3.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:cb83f8a875b3d9b458cada4f880fa498646874ba4011dc974e071a0a84a1b033"}, {file = "lxml-5.3.0-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:25f1b69d41656b05885aa185f5fdf822cb01a586d1b32739633679699f220391"}, {file = "lxml-5.3.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:23e0553b8055600b3bf4a00b255ec5c92e1e4aebf8c2c09334f8368e8bd174d6"}, {file = "lxml-5.3.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9ada35dd21dc6c039259596b358caab6b13f4db4d4a7f8665764d616daf9cc1d"}, {file = "lxml-5.3.0-cp37-cp37m-manylinux_2_28_aarch64.whl", hash = "sha256:81b4e48da4c69313192d8c8d4311e5d818b8be1afe68ee20f6385d0e96fc9512"}, {file = "lxml-5.3.0-cp37-cp37m-manylinux_2_28_x86_64.whl", hash = "sha256:2bc9fd5ca4729af796f9f59cd8ff160fe06a474da40aca03fcc79655ddee1a8b"}, {file = "lxml-5.3.0-cp37-cp37m-musllinux_1_2_aarch64.whl", hash = "sha256:07da23d7ee08577760f0a71d67a861019103e4812c87e2fab26b039054594cc5"}, {file = "lxml-5.3.0-cp37-cp37m-musllinux_1_2_x86_64.whl", hash = "sha256:ea2e2f6f801696ad7de8aec061044d6c8c0dd4037608c7cab38a9a4d316bfb11"}, {file = "lxml-5.3.0-cp37-cp37m-win32.whl", hash = "sha256:5c54afdcbb0182d06836cc3d1be921e540be3ebdf8b8a51ee3ef987537455f84"}, {file = "lxml-5.3.0-cp37-cp37m-win_amd64.whl", hash = "sha256:f2901429da1e645ce548bf9171784c0f74f0718c3f6150ce166be39e4dd66c3e"}, {file = "lxml-5.3.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:c56a1d43b2f9ee4786e4658c7903f05da35b923fb53c11025712562d5cc02753"}, {file = "lxml-5.3.0-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6ee8c39582d2652dcd516d1b879451500f8db3fe3607ce45d7c5957ab2596040"}, {file = "lxml-5.3.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0fdf3a3059611f7585a78ee10399a15566356116a4288380921a4b598d807a22"}, {file = "lxml-5.3.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:146173654d79eb1fc97498b4280c1d3e1e5d58c398fa530905c9ea50ea849b22"}, {file = "lxml-5.3.0-cp38-cp38-manylinux_2_28_aarch64.whl", hash = "sha256:0a7056921edbdd7560746f4221dca89bb7a3fe457d3d74267995253f46343f15"}, {file = "lxml-5.3.0-cp38-cp38-manylinux_2_28_x86_64.whl", hash = "sha256:9e4b47ac0f5e749cfc618efdf4726269441014ae1d5583e047b452a32e221920"}, {file = "lxml-5.3.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:f914c03e6a31deb632e2daa881fe198461f4d06e57ac3d0e05bbcab8eae01945"}, {file = "lxml-5.3.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:213261f168c5e1d9b7535a67e68b1f59f92398dd17a56d934550837143f79c42"}, {file = "lxml-5.3.0-cp38-cp38-win32.whl", hash = "sha256:218c1b2e17a710e363855594230f44060e2025b05c80d1f0661258142b2add2e"}, {file = "lxml-5.3.0-cp38-cp38-win_amd64.whl", hash = "sha256:315f9542011b2c4e1d280e4a20ddcca1761993dda3afc7a73b01235f8641e903"}, {file = "lxml-5.3.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:1ffc23010330c2ab67fac02781df60998ca8fe759e8efde6f8b756a20599c5de"}, {file = "lxml-5.3.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:2b3778cb38212f52fac9fe913017deea2fdf4eb1a4f8e4cfc6b009a13a6d3fcc"}, {file = "lxml-5.3.0-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4b0c7a688944891086ba192e21c5229dea54382f4836a209ff8d0a660fac06be"}, {file = "lxml-5.3.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:747a3d3e98e24597981ca0be0fd922aebd471fa99d0043a3842d00cdcad7ad6a"}, {file = "lxml-5.3.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:86a6b24b19eaebc448dc56b87c4865527855145d851f9fc3891673ff97950540"}, {file = "lxml-5.3.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b11a5d918a6216e521c715b02749240fb07ae5a1fefd4b7bf12f833bc8b4fe70"}, {file = "lxml-5.3.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:68b87753c784d6acb8a25b05cb526c3406913c9d988d51f80adecc2b0775d6aa"}, {file = "lxml-5.3.0-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:109fa6fede314cc50eed29e6e56c540075e63d922455346f11e4d7a036d2b8cf"}, {file = "lxml-5.3.0-cp39-cp39-manylinux_2_28_ppc64le.whl", hash = "sha256:02ced472497b8362c8e902ade23e3300479f4f43e45f4105c85ef43b8db85229"}, {file = "lxml-5.3.0-cp39-cp39-manylinux_2_28_s390x.whl", hash = "sha256:6b038cc86b285e4f9fea2ba5ee76e89f21ed1ea898e287dc277a25884f3a7dfe"}, {file = "lxml-5.3.0-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:7437237c6a66b7ca341e868cda48be24b8701862757426852c9b3186de1da8a2"}, {file = "lxml-5.3.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:7f41026c1d64043a36fda21d64c5026762d53a77043e73e94b71f0521939cc71"}, {file = "lxml-5.3.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:482c2f67761868f0108b1743098640fbb2a28a8e15bf3f47ada9fa59d9fe08c3"}, {file = "lxml-5.3.0-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:1483fd3358963cc5c1c9b122c80606a3a79ee0875bcac0204149fa09d6ff2727"}, {file = "lxml-5.3.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:2dec2d1130a9cda5b904696cec33b2cfb451304ba9081eeda7f90f724097300a"}, {file = "lxml-5.3.0-cp39-cp39-win32.whl", hash = "sha256:a0eabd0a81625049c5df745209dc7fcef6e2aea7793e5f003ba363610aa0a3ff"}, {file = "lxml-5.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:89e043f1d9d341c52bf2af6d02e6adde62e0a46e6755d5eb60dc6e4f0b8aeca2"}, {file = "lxml-5.3.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:7b1cd427cb0d5f7393c31b7496419da594fe600e6fdc4b105a54f82405e6626c"}, {file = "lxml-5.3.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:51806cfe0279e06ed8500ce19479d757db42a30fd509940b1701be9c86a5ff9a"}, {file = "lxml-5.3.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ee70d08fd60c9565ba8190f41a46a54096afa0eeb8f76bd66f2c25d3b1b83005"}, {file = "lxml-5.3.0-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:8dc2c0395bea8254d8daebc76dcf8eb3a95ec2a46fa6fae5eaccee366bfe02ce"}, {file = "lxml-5.3.0-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:6ba0d3dcac281aad8a0e5b14c7ed6f9fa89c8612b47939fc94f80b16e2e9bc83"}, {file = "lxml-5.3.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:6e91cf736959057f7aac7adfc83481e03615a8e8dd5758aa1d95ea69e8931dba"}, {file = "lxml-5.3.0-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:94d6c3782907b5e40e21cadf94b13b0842ac421192f26b84c45f13f3c9d5dc27"}, {file = "lxml-5.3.0-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c300306673aa0f3ed5ed9372b21867690a17dba38c68c44b287437c362ce486b"}, {file = "lxml-5.3.0-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:78d9b952e07aed35fe2e1a7ad26e929595412db48535921c5013edc8aa4a35ce"}, {file = "lxml-5.3.0-pp37-pypy37_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:01220dca0d066d1349bd6a1726856a78f7929f3878f7e2ee83c296c69495309e"}, {file = "lxml-5.3.0-pp37-pypy37_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:2d9b8d9177afaef80c53c0a9e30fa252ff3036fb1c6494d427c066a4ce6a282f"}, {file = "lxml-5.3.0-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:20094fc3f21ea0a8669dc4c61ed7fa8263bd37d97d93b90f28fc613371e7a875"}, {file = "lxml-5.3.0-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:ace2c2326a319a0bb8a8b0e5b570c764962e95818de9f259ce814ee666603f19"}, {file = "lxml-5.3.0-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:92e67a0be1639c251d21e35fe74df6bcc40cba445c2cda7c4a967656733249e2"}, {file = "lxml-5.3.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd5350b55f9fecddc51385463a4f67a5da829bc741e38cf689f38ec9023f54ab"}, {file = "lxml-5.3.0-pp38-pypy38_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:4c1fefd7e3d00921c44dc9ca80a775af49698bbfd92ea84498e56acffd4c5469"}, {file = "lxml-5.3.0-pp38-pypy38_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:71a8dd38fbd2f2319136d4ae855a7078c69c9a38ae06e0c17c73fd70fc6caad8"}, {file = "lxml-5.3.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:97acf1e1fd66ab53dacd2c35b319d7e548380c2e9e8c54525c6e76d21b1ae3b1"}, {file = "lxml-5.3.0-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:68934b242c51eb02907c5b81d138cb977b2129a0a75a8f8b60b01cb8586c7b21"}, {file = "lxml-5.3.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b710bc2b8292966b23a6a0121f7a6c51d45d2347edcc75f016ac123b8054d3f2"}, {file = "lxml-5.3.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:18feb4b93302091b1541221196a2155aa296c363fd233814fa11e181adebc52f"}, {file = "lxml-5.3.0-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:3eb44520c4724c2e1a57c0af33a379eee41792595023f367ba3952a2d96c2aab"}, {file = "lxml-5.3.0-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:609251a0ca4770e5a8768ff902aa02bf636339c5a93f9349b48eb1f606f7f3e9"}, {file = "lxml-5.3.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:516f491c834eb320d6c843156440fe7fc0d50b33e44387fcec5b02f0bc118a4c"}, {file = "lxml-5.3.0.tar.gz", hash = "sha256:4e109ca30d1edec1ac60cdbe341905dc3b8f55b16855e03a54aaf59e51ec8c6f"}, ] [package.extras] cssselect = ["cssselect (>=0.7)"] html-clean = ["lxml-html-clean"] html5 = ["html5lib"] htmlsoup = ["BeautifulSoup4"] source = ["Cython (>=3.0.11)"] [[package]] name = "markupsafe" version = "2.1.5" description = "Safely add untrusted strings to HTML/XML markup." optional = false python-versions = ">=3.7" files = [ {file = "MarkupSafe-2.1.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a17a92de5231666cfbe003f0e4b9b3a7ae3afb1ec2845aadc2bacc93ff85febc"}, {file = "MarkupSafe-2.1.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:72b6be590cc35924b02c78ef34b467da4ba07e4e0f0454a2c5907f473fc50ce5"}, {file = "MarkupSafe-2.1.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e61659ba32cf2cf1481e575d0462554625196a1f2fc06a1c777d3f48e8865d46"}, {file = "MarkupSafe-2.1.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2174c595a0d73a3080ca3257b40096db99799265e1c27cc5a610743acd86d62f"}, {file = "MarkupSafe-2.1.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ae2ad8ae6ebee9d2d94b17fb62763125f3f374c25618198f40cbb8b525411900"}, {file = "MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:075202fa5b72c86ad32dc7d0b56024ebdbcf2048c0ba09f1cde31bfdd57bcfff"}, {file = "MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:598e3276b64aff0e7b3451b72e94fa3c238d452e7ddcd893c3ab324717456bad"}, {file = "MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fce659a462a1be54d2ffcacea5e3ba2d74daa74f30f5f143fe0c58636e355fdd"}, {file = "MarkupSafe-2.1.5-cp310-cp310-win32.whl", hash = "sha256:d9fad5155d72433c921b782e58892377c44bd6252b5af2f67f16b194987338a4"}, {file = "MarkupSafe-2.1.5-cp310-cp310-win_amd64.whl", hash = "sha256:bf50cd79a75d181c9181df03572cdce0fbb75cc353bc350712073108cba98de5"}, {file = "MarkupSafe-2.1.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:629ddd2ca402ae6dbedfceeba9c46d5f7b2a61d9749597d4307f943ef198fc1f"}, {file = "MarkupSafe-2.1.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:5b7b716f97b52c5a14bffdf688f971b2d5ef4029127f1ad7a513973cfd818df2"}, {file = "MarkupSafe-2.1.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6ec585f69cec0aa07d945b20805be741395e28ac1627333b1c5b0105962ffced"}, {file = "MarkupSafe-2.1.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b91c037585eba9095565a3556f611e3cbfaa42ca1e865f7b8015fe5c7336d5a5"}, {file = "MarkupSafe-2.1.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7502934a33b54030eaf1194c21c692a534196063db72176b0c4028e140f8f32c"}, {file = "MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:0e397ac966fdf721b2c528cf028494e86172b4feba51d65f81ffd65c63798f3f"}, {file = "MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:c061bb86a71b42465156a3ee7bd58c8c2ceacdbeb95d05a99893e08b8467359a"}, {file = "MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:3a57fdd7ce31c7ff06cdfbf31dafa96cc533c21e443d57f5b1ecc6cdc668ec7f"}, {file = "MarkupSafe-2.1.5-cp311-cp311-win32.whl", hash = "sha256:397081c1a0bfb5124355710fe79478cdbeb39626492b15d399526ae53422b906"}, {file = "MarkupSafe-2.1.5-cp311-cp311-win_amd64.whl", hash = "sha256:2b7c57a4dfc4f16f7142221afe5ba4e093e09e728ca65c51f5620c9aaeb9a617"}, {file = "MarkupSafe-2.1.5-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:8dec4936e9c3100156f8a2dc89c4b88d5c435175ff03413b443469c7c8c5f4d1"}, {file = "MarkupSafe-2.1.5-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:3c6b973f22eb18a789b1460b4b91bf04ae3f0c4234a0a6aa6b0a92f6f7b951d4"}, {file = "MarkupSafe-2.1.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ac07bad82163452a6884fe8fa0963fb98c2346ba78d779ec06bd7a6262132aee"}, {file = "MarkupSafe-2.1.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f5dfb42c4604dddc8e4305050aa6deb084540643ed5804d7455b5df8fe16f5e5"}, {file = "MarkupSafe-2.1.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ea3d8a3d18833cf4304cd2fc9cbb1efe188ca9b5efef2bdac7adc20594a0e46b"}, {file = "MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:d050b3361367a06d752db6ead6e7edeb0009be66bc3bae0ee9d97fb326badc2a"}, {file = "MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:bec0a414d016ac1a18862a519e54b2fd0fc8bbfd6890376898a6c0891dd82e9f"}, {file = "MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:58c98fee265677f63a4385256a6d7683ab1832f3ddd1e66fe948d5880c21a169"}, {file = "MarkupSafe-2.1.5-cp312-cp312-win32.whl", hash = "sha256:8590b4ae07a35970728874632fed7bd57b26b0102df2d2b233b6d9d82f6c62ad"}, {file = "MarkupSafe-2.1.5-cp312-cp312-win_amd64.whl", hash = "sha256:823b65d8706e32ad2df51ed89496147a42a2a6e01c13cfb6ffb8b1e92bc910bb"}, {file = "MarkupSafe-2.1.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:c8b29db45f8fe46ad280a7294f5c3ec36dbac9491f2d1c17345be8e69cc5928f"}, {file = "MarkupSafe-2.1.5-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ec6a563cff360b50eed26f13adc43e61bc0c04d94b8be985e6fb24b81f6dcfdf"}, {file = "MarkupSafe-2.1.5-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a549b9c31bec33820e885335b451286e2969a2d9e24879f83fe904a5ce59d70a"}, {file = "MarkupSafe-2.1.5-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4f11aa001c540f62c6166c7726f71f7573b52c68c31f014c25cc7901deea0b52"}, {file = "MarkupSafe-2.1.5-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:7b2e5a267c855eea6b4283940daa6e88a285f5f2a67f2220203786dfa59b37e9"}, {file = "MarkupSafe-2.1.5-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:2d2d793e36e230fd32babe143b04cec8a8b3eb8a3122d2aceb4a371e6b09b8df"}, {file = "MarkupSafe-2.1.5-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:ce409136744f6521e39fd8e2a24c53fa18ad67aa5bc7c2cf83645cce5b5c4e50"}, {file = "MarkupSafe-2.1.5-cp37-cp37m-win32.whl", hash = "sha256:4096e9de5c6fdf43fb4f04c26fb114f61ef0bf2e5604b6ee3019d51b69e8c371"}, {file = "MarkupSafe-2.1.5-cp37-cp37m-win_amd64.whl", hash = "sha256:4275d846e41ecefa46e2015117a9f491e57a71ddd59bbead77e904dc02b1bed2"}, {file = "MarkupSafe-2.1.5-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:656f7526c69fac7f600bd1f400991cc282b417d17539a1b228617081106feb4a"}, {file = "MarkupSafe-2.1.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:97cafb1f3cbcd3fd2b6fbfb99ae11cdb14deea0736fc2b0952ee177f2b813a46"}, {file = "MarkupSafe-2.1.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f3fbcb7ef1f16e48246f704ab79d79da8a46891e2da03f8783a5b6fa41a9532"}, {file = "MarkupSafe-2.1.5-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fa9db3f79de01457b03d4f01b34cf91bc0048eb2c3846ff26f66687c2f6d16ab"}, {file = "MarkupSafe-2.1.5-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ffee1f21e5ef0d712f9033568f8344d5da8cc2869dbd08d87c84656e6a2d2f68"}, {file = "MarkupSafe-2.1.5-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:5dedb4db619ba5a2787a94d877bc8ffc0566f92a01c0ef214865e54ecc9ee5e0"}, {file = "MarkupSafe-2.1.5-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:30b600cf0a7ac9234b2638fbc0fb6158ba5bdcdf46aeb631ead21248b9affbc4"}, {file = "MarkupSafe-2.1.5-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:8dd717634f5a044f860435c1d8c16a270ddf0ef8588d4887037c5028b859b0c3"}, {file = "MarkupSafe-2.1.5-cp38-cp38-win32.whl", hash = "sha256:daa4ee5a243f0f20d528d939d06670a298dd39b1ad5f8a72a4275124a7819eff"}, {file = "MarkupSafe-2.1.5-cp38-cp38-win_amd64.whl", hash = "sha256:619bc166c4f2de5caa5a633b8b7326fbe98e0ccbfacabd87268a2b15ff73a029"}, {file = "MarkupSafe-2.1.5-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:7a68b554d356a91cce1236aa7682dc01df0edba8d043fd1ce607c49dd3c1edcf"}, {file = "MarkupSafe-2.1.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:db0b55e0f3cc0be60c1f19efdde9a637c32740486004f20d1cff53c3c0ece4d2"}, {file = "MarkupSafe-2.1.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3e53af139f8579a6d5f7b76549125f0d94d7e630761a2111bc431fd820e163b8"}, {file = "MarkupSafe-2.1.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:17b950fccb810b3293638215058e432159d2b71005c74371d784862b7e4683f3"}, {file = "MarkupSafe-2.1.5-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4c31f53cdae6ecfa91a77820e8b151dba54ab528ba65dfd235c80b086d68a465"}, {file = "MarkupSafe-2.1.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:bff1b4290a66b490a2f4719358c0cdcd9bafb6b8f061e45c7a2460866bf50c2e"}, {file = "MarkupSafe-2.1.5-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:bc1667f8b83f48511b94671e0e441401371dfd0f0a795c7daa4a3cd1dde55bea"}, {file = "MarkupSafe-2.1.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5049256f536511ee3f7e1b3f87d1d1209d327e818e6ae1365e8653d7e3abb6a6"}, {file = "MarkupSafe-2.1.5-cp39-cp39-win32.whl", hash = "sha256:00e046b6dd71aa03a41079792f8473dc494d564611a8f89bbbd7cb93295ebdcf"}, {file = "MarkupSafe-2.1.5-cp39-cp39-win_amd64.whl", hash = "sha256:fa173ec60341d6bb97a89f5ea19c85c5643c1e7dedebc22f5181eb73573142c5"}, {file = "MarkupSafe-2.1.5.tar.gz", hash = "sha256:d283d37a890ba4c1ae73ffadf8046435c76e7bc2247bbb63c00bd1a709c6544b"}, ] [[package]] name = "mediafile" version = "0.13.0" description = "Handles low-level interfacing for files' tags. Wraps Mutagen to" optional = false python-versions = ">=3.7" files = [ {file = "mediafile-0.13.0-py3-none-any.whl", hash = "sha256:cd8d183d0e0671b5203a86e92cf4e3338ecc892a1ec9dcd7ec0ed87779e514cb"}, {file = "mediafile-0.13.0.tar.gz", hash = "sha256:de71063e1bffe9733d6ccad526ea7dac8a9ce760105827f81ab0cb034c729a6d"}, ] [package.dependencies] filetype = ">=1.2.0" mutagen = ">=1.46" [package.extras] test = ["tox"] [[package]] name = "mock" version = "5.1.0" description = "Rolling backport of unittest.mock for all Pythons" optional = false python-versions = ">=3.6" files = [ {file = "mock-5.1.0-py3-none-any.whl", hash = "sha256:18c694e5ae8a208cdb3d2c20a993ca1a7b0efa258c247a1e565150f477f83744"}, {file = "mock-5.1.0.tar.gz", hash = "sha256:5e96aad5ccda4718e0a229ed94b2024df75cc2d55575ba5762d31f5767b8767d"}, ] [package.extras] build = ["blurb", "twine", "wheel"] docs = ["sphinx"] test = ["pytest", "pytest-cov"] [[package]] name = "msgpack" version = "1.1.0" description = "MessagePack serializer" optional = true python-versions = ">=3.8" files = [ {file = "msgpack-1.1.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7ad442d527a7e358a469faf43fda45aaf4ac3249c8310a82f0ccff9164e5dccd"}, {file = "msgpack-1.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:74bed8f63f8f14d75eec75cf3d04ad581da6b914001b474a5d3cd3372c8cc27d"}, {file = "msgpack-1.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:914571a2a5b4e7606997e169f64ce53a8b1e06f2cf2c3a7273aa106236d43dd5"}, {file = "msgpack-1.1.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c921af52214dcbb75e6bdf6a661b23c3e6417f00c603dd2070bccb5c3ef499f5"}, {file = "msgpack-1.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d8ce0b22b890be5d252de90d0e0d119f363012027cf256185fc3d474c44b1b9e"}, {file = "msgpack-1.1.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:73322a6cc57fcee3c0c57c4463d828e9428275fb85a27aa2aa1a92fdc42afd7b"}, {file = "msgpack-1.1.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:e1f3c3d21f7cf67bcf2da8e494d30a75e4cf60041d98b3f79875afb5b96f3a3f"}, {file = "msgpack-1.1.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:64fc9068d701233effd61b19efb1485587560b66fe57b3e50d29c5d78e7fef68"}, {file = "msgpack-1.1.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:42f754515e0f683f9c79210a5d1cad631ec3d06cea5172214d2176a42e67e19b"}, {file = "msgpack-1.1.0-cp310-cp310-win32.whl", hash = "sha256:3df7e6b05571b3814361e8464f9304c42d2196808e0119f55d0d3e62cd5ea044"}, {file = "msgpack-1.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:685ec345eefc757a7c8af44a3032734a739f8c45d1b0ac45efc5d8977aa4720f"}, {file = "msgpack-1.1.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:3d364a55082fb2a7416f6c63ae383fbd903adb5a6cf78c5b96cc6316dc1cedc7"}, {file = "msgpack-1.1.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:79ec007767b9b56860e0372085f8504db5d06bd6a327a335449508bbee9648fa"}, {file = "msgpack-1.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:6ad622bf7756d5a497d5b6836e7fc3752e2dd6f4c648e24b1803f6048596f701"}, {file = "msgpack-1.1.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8e59bca908d9ca0de3dc8684f21ebf9a690fe47b6be93236eb40b99af28b6ea6"}, {file = "msgpack-1.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5e1da8f11a3dd397f0a32c76165cf0c4eb95b31013a94f6ecc0b280c05c91b59"}, {file = "msgpack-1.1.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:452aff037287acb1d70a804ffd022b21fa2bb7c46bee884dbc864cc9024128a0"}, {file = "msgpack-1.1.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8da4bf6d54ceed70e8861f833f83ce0814a2b72102e890cbdfe4b34764cdd66e"}, {file = "msgpack-1.1.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:41c991beebf175faf352fb940bf2af9ad1fb77fd25f38d9142053914947cdbf6"}, {file = "msgpack-1.1.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:a52a1f3a5af7ba1c9ace055b659189f6c669cf3657095b50f9602af3a3ba0fe5"}, {file = "msgpack-1.1.0-cp311-cp311-win32.whl", hash = "sha256:58638690ebd0a06427c5fe1a227bb6b8b9fdc2bd07701bec13c2335c82131a88"}, {file = "msgpack-1.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:fd2906780f25c8ed5d7b323379f6138524ba793428db5d0e9d226d3fa6aa1788"}, {file = "msgpack-1.1.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:d46cf9e3705ea9485687aa4001a76e44748b609d260af21c4ceea7f2212a501d"}, {file = "msgpack-1.1.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:5dbad74103df937e1325cc4bfeaf57713be0b4f15e1c2da43ccdd836393e2ea2"}, {file = "msgpack-1.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:58dfc47f8b102da61e8949708b3eafc3504509a5728f8b4ddef84bd9e16ad420"}, {file = "msgpack-1.1.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4676e5be1b472909b2ee6356ff425ebedf5142427842aa06b4dfd5117d1ca8a2"}, {file = "msgpack-1.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:17fb65dd0bec285907f68b15734a993ad3fc94332b5bb21b0435846228de1f39"}, {file = "msgpack-1.1.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a51abd48c6d8ac89e0cfd4fe177c61481aca2d5e7ba42044fd218cfd8ea9899f"}, {file = "msgpack-1.1.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2137773500afa5494a61b1208619e3871f75f27b03bcfca7b3a7023284140247"}, {file = "msgpack-1.1.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:398b713459fea610861c8a7b62a6fec1882759f308ae0795b5413ff6a160cf3c"}, {file = "msgpack-1.1.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:06f5fd2f6bb2a7914922d935d3b8bb4a7fff3a9a91cfce6d06c13bc42bec975b"}, {file = "msgpack-1.1.0-cp312-cp312-win32.whl", hash = "sha256:ad33e8400e4ec17ba782f7b9cf868977d867ed784a1f5f2ab46e7ba53b6e1e1b"}, {file = "msgpack-1.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:115a7af8ee9e8cddc10f87636767857e7e3717b7a2e97379dc2054712693e90f"}, {file = "msgpack-1.1.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:071603e2f0771c45ad9bc65719291c568d4edf120b44eb36324dcb02a13bfddf"}, {file = "msgpack-1.1.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0f92a83b84e7c0749e3f12821949d79485971f087604178026085f60ce109330"}, {file = "msgpack-1.1.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:4a1964df7b81285d00a84da4e70cb1383f2e665e0f1f2a7027e683956d04b734"}, {file = "msgpack-1.1.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:59caf6a4ed0d164055ccff8fe31eddc0ebc07cf7326a2aaa0dbf7a4001cd823e"}, {file = "msgpack-1.1.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0907e1a7119b337971a689153665764adc34e89175f9a34793307d9def08e6ca"}, {file = "msgpack-1.1.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:65553c9b6da8166e819a6aa90ad15288599b340f91d18f60b2061f402b9a4915"}, {file = "msgpack-1.1.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:7a946a8992941fea80ed4beae6bff74ffd7ee129a90b4dd5cf9c476a30e9708d"}, {file = "msgpack-1.1.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:4b51405e36e075193bc051315dbf29168d6141ae2500ba8cd80a522964e31434"}, {file = "msgpack-1.1.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:b4c01941fd2ff87c2a934ee6055bda4ed353a7846b8d4f341c428109e9fcde8c"}, {file = "msgpack-1.1.0-cp313-cp313-win32.whl", hash = "sha256:7c9a35ce2c2573bada929e0b7b3576de647b0defbd25f5139dcdaba0ae35a4cc"}, {file = "msgpack-1.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:bce7d9e614a04d0883af0b3d4d501171fbfca038f12c77fa838d9f198147a23f"}, {file = "msgpack-1.1.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c40ffa9a15d74e05ba1fe2681ea33b9caffd886675412612d93ab17b58ea2fec"}, {file = "msgpack-1.1.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f1ba6136e650898082d9d5a5217d5906d1e138024f836ff48691784bbe1adf96"}, {file = "msgpack-1.1.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e0856a2b7e8dcb874be44fea031d22e5b3a19121be92a1e098f46068a11b0870"}, {file = "msgpack-1.1.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:471e27a5787a2e3f974ba023f9e265a8c7cfd373632247deb225617e3100a3c7"}, {file = "msgpack-1.1.0-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:646afc8102935a388ffc3914b336d22d1c2d6209c773f3eb5dd4d6d3b6f8c1cb"}, {file = "msgpack-1.1.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:13599f8829cfbe0158f6456374e9eea9f44eee08076291771d8ae93eda56607f"}, {file = "msgpack-1.1.0-cp38-cp38-win32.whl", hash = "sha256:8a84efb768fb968381e525eeeb3d92857e4985aacc39f3c47ffd00eb4509315b"}, {file = "msgpack-1.1.0-cp38-cp38-win_amd64.whl", hash = "sha256:879a7b7b0ad82481c52d3c7eb99bf6f0645dbdec5134a4bddbd16f3506947feb"}, {file = "msgpack-1.1.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:53258eeb7a80fc46f62fd59c876957a2d0e15e6449a9e71842b6d24419d88ca1"}, {file = "msgpack-1.1.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:7e7b853bbc44fb03fbdba34feb4bd414322180135e2cb5164f20ce1c9795ee48"}, {file = "msgpack-1.1.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f3e9b4936df53b970513eac1758f3882c88658a220b58dcc1e39606dccaaf01c"}, {file = "msgpack-1.1.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:46c34e99110762a76e3911fc923222472c9d681f1094096ac4102c18319e6468"}, {file = "msgpack-1.1.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8a706d1e74dd3dea05cb54580d9bd8b2880e9264856ce5068027eed09680aa74"}, {file = "msgpack-1.1.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:534480ee5690ab3cbed89d4c8971a5c631b69a8c0883ecfea96c19118510c846"}, {file = "msgpack-1.1.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:8cf9e8c3a2153934a23ac160cc4cba0ec035f6867c8013cc6077a79823370346"}, {file = "msgpack-1.1.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:3180065ec2abbe13a4ad37688b61b99d7f9e012a535b930e0e683ad6bc30155b"}, {file = "msgpack-1.1.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:c5a91481a3cc573ac8c0d9aace09345d989dc4a0202b7fcb312c88c26d4e71a8"}, {file = "msgpack-1.1.0-cp39-cp39-win32.whl", hash = "sha256:f80bc7d47f76089633763f952e67f8214cb7b3ee6bfa489b3cb6a84cfac114cd"}, {file = "msgpack-1.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:4d1b7ff2d6146e16e8bd665ac726a89c74163ef8cd39fa8c1087d4e52d3a2325"}, {file = "msgpack-1.1.0.tar.gz", hash = "sha256:dd432ccc2c72b914e4cb77afce64aab761c1137cc698be3984eee260bcb2896e"}, ] [[package]] name = "multivolumefile" version = "0.2.3" description = "multi volume file wrapper library" optional = false python-versions = ">=3.6" files = [ {file = "multivolumefile-0.2.3-py3-none-any.whl", hash = "sha256:237f4353b60af1703087cf7725755a1f6fcaeeea48421e1896940cd1c920d678"}, {file = "multivolumefile-0.2.3.tar.gz", hash = "sha256:a0648d0aafbc96e59198d5c17e9acad7eb531abea51035d08ce8060dcad709d6"}, ] [package.extras] check = ["check-manifest", "flake8", "flake8-black", "isort (>=5.0.3)", "pygments", "readme-renderer", "twine"] test = ["coverage[toml] (>=5.2)", "coveralls (>=2.1.1)", "hypothesis", "pyannotate", "pytest", "pytest-cov"] type = ["mypy", "mypy-extensions"] [[package]] name = "munkres" version = "1.1.4" description = "Munkres (Hungarian) algorithm for the Assignment Problem" optional = false python-versions = "*" files = [ {file = "munkres-1.1.4-py2.py3-none-any.whl", hash = "sha256:6b01867d4a8480d865aea2326e4b8f7c46431e9e55b4a2e32d989307d7bced2a"}, {file = "munkres-1.1.4.tar.gz", hash = "sha256:fc44bf3c3979dada4b6b633ddeeb8ffbe8388ee9409e4d4e8310c2da1792db03"}, ] [[package]] name = "musicbrainzngs" version = "0.7.1" description = "Python bindings for the MusicBrainz NGS and the Cover Art Archive webservices" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" files = [ {file = "musicbrainzngs-0.7.1-py2.py3-none-any.whl", hash = "sha256:e841a8f975104c0a72290b09f59326050194081a5ae62ee512f41915090e1a10"}, {file = "musicbrainzngs-0.7.1.tar.gz", hash = "sha256:ab1c0100fd0b305852e65f2ed4113c6de12e68afd55186987b8ed97e0f98e627"}, ] [[package]] name = "mutagen" version = "1.47.0" description = "read and write audio tags for many formats" optional = false python-versions = ">=3.7" files = [ {file = "mutagen-1.47.0-py3-none-any.whl", hash = "sha256:edd96f50c5907a9539d8e5bba7245f62c9f520aef333d13392a79a4f70aca719"}, {file = "mutagen-1.47.0.tar.gz", hash = "sha256:719fadef0a978c31b4cf3c956261b3c58b6948b32023078a2117b1de09f0fc99"}, ] [[package]] name = "mypy" version = "1.13.0" description = "Optional static typing for Python" optional = false python-versions = ">=3.8" files = [ {file = "mypy-1.13.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6607e0f1dd1fb7f0aca14d936d13fd19eba5e17e1cd2a14f808fa5f8f6d8f60a"}, {file = "mypy-1.13.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8a21be69bd26fa81b1f80a61ee7ab05b076c674d9b18fb56239d72e21d9f4c80"}, {file = "mypy-1.13.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7b2353a44d2179846a096e25691d54d59904559f4232519d420d64da6828a3a7"}, {file = "mypy-1.13.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:0730d1c6a2739d4511dc4253f8274cdd140c55c32dfb0a4cf8b7a43f40abfa6f"}, {file = "mypy-1.13.0-cp310-cp310-win_amd64.whl", hash = "sha256:c5fc54dbb712ff5e5a0fca797e6e0aa25726c7e72c6a5850cfd2adbc1eb0a372"}, {file = "mypy-1.13.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:581665e6f3a8a9078f28d5502f4c334c0c8d802ef55ea0e7276a6e409bc0d82d"}, {file = "mypy-1.13.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3ddb5b9bf82e05cc9a627e84707b528e5c7caaa1c55c69e175abb15a761cec2d"}, {file = "mypy-1.13.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:20c7ee0bc0d5a9595c46f38beb04201f2620065a93755704e141fcac9f59db2b"}, {file = "mypy-1.13.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:3790ded76f0b34bc9c8ba4def8f919dd6a46db0f5a6610fb994fe8efdd447f73"}, {file = "mypy-1.13.0-cp311-cp311-win_amd64.whl", hash = "sha256:51f869f4b6b538229c1d1bcc1dd7d119817206e2bc54e8e374b3dfa202defcca"}, {file = "mypy-1.13.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:5c7051a3461ae84dfb5dd15eff5094640c61c5f22257c8b766794e6dd85e72d5"}, {file = "mypy-1.13.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:39bb21c69a5d6342f4ce526e4584bc5c197fd20a60d14a8624d8743fffb9472e"}, {file = "mypy-1.13.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:164f28cb9d6367439031f4c81e84d3ccaa1e19232d9d05d37cb0bd880d3f93c2"}, {file = "mypy-1.13.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:a4c1bfcdbce96ff5d96fc9b08e3831acb30dc44ab02671eca5953eadad07d6d0"}, {file = "mypy-1.13.0-cp312-cp312-win_amd64.whl", hash = "sha256:a0affb3a79a256b4183ba09811e3577c5163ed06685e4d4b46429a271ba174d2"}, {file = "mypy-1.13.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:a7b44178c9760ce1a43f544e595d35ed61ac2c3de306599fa59b38a6048e1aa7"}, {file = "mypy-1.13.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:5d5092efb8516d08440e36626f0153b5006d4088c1d663d88bf79625af3d1d62"}, {file = "mypy-1.13.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:de2904956dac40ced10931ac967ae63c5089bd498542194b436eb097a9f77bc8"}, {file = "mypy-1.13.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:7bfd8836970d33c2105562650656b6846149374dc8ed77d98424b40b09340ba7"}, {file = "mypy-1.13.0-cp313-cp313-win_amd64.whl", hash = "sha256:9f73dba9ec77acb86457a8fc04b5239822df0c14a082564737833d2963677dbc"}, {file = "mypy-1.13.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:100fac22ce82925f676a734af0db922ecfea991e1d7ec0ceb1e115ebe501301a"}, {file = "mypy-1.13.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:7bcb0bb7f42a978bb323a7c88f1081d1b5dee77ca86f4100735a6f541299d8fb"}, {file = "mypy-1.13.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bde31fc887c213e223bbfc34328070996061b0833b0a4cfec53745ed61f3519b"}, {file = "mypy-1.13.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:07de989f89786f62b937851295ed62e51774722e5444a27cecca993fc3f9cd74"}, {file = "mypy-1.13.0-cp38-cp38-win_amd64.whl", hash = "sha256:4bde84334fbe19bad704b3f5b78c4abd35ff1026f8ba72b29de70dda0916beb6"}, {file = "mypy-1.13.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:0246bcb1b5de7f08f2826451abd947bf656945209b140d16ed317f65a17dc7dc"}, {file = "mypy-1.13.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:7f5b7deae912cf8b77e990b9280f170381fdfbddf61b4ef80927edd813163732"}, {file = "mypy-1.13.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7029881ec6ffb8bc233a4fa364736789582c738217b133f1b55967115288a2bc"}, {file = "mypy-1.13.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:3e38b980e5681f28f033f3be86b099a247b13c491f14bb8b1e1e134d23bb599d"}, {file = "mypy-1.13.0-cp39-cp39-win_amd64.whl", hash = "sha256:a6789be98a2017c912ae6ccb77ea553bbaf13d27605d2ca20a76dfbced631b24"}, {file = "mypy-1.13.0-py3-none-any.whl", hash = "sha256:9c250883f9fd81d212e0952c92dbfcc96fc237f4b7c92f56ac81fd48460b3e5a"}, {file = "mypy-1.13.0.tar.gz", hash = "sha256:0291a61b6fbf3e6673e3405cfcc0e7650bebc7939659fdca2702958038bd835e"}, ] [package.dependencies] mypy-extensions = ">=1.0.0" tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} typing-extensions = ">=4.6.0" [package.extras] dmypy = ["psutil (>=4.0)"] faster-cache = ["orjson"] install-types = ["pip"] mypyc = ["setuptools (>=50)"] reports = ["lxml"] [[package]] name = "mypy-extensions" version = "1.0.0" description = "Type system extensions for programs checked with the mypy type checker." optional = false python-versions = ">=3.5" files = [ {file = "mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d"}, {file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"}, ] [[package]] name = "numba" version = "0.58.1" description = "compiling Python code using LLVM" optional = true python-versions = ">=3.8" files = [ {file = "numba-0.58.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:07f2fa7e7144aa6f275f27260e73ce0d808d3c62b30cff8906ad1dec12d87bbe"}, {file = "numba-0.58.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:7bf1ddd4f7b9c2306de0384bf3854cac3edd7b4d8dffae2ec1b925e4c436233f"}, {file = "numba-0.58.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:bc2d904d0319d7a5857bd65062340bed627f5bfe9ae4a495aef342f072880d50"}, {file = "numba-0.58.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:4e79b6cc0d2bf064a955934a2e02bf676bc7995ab2db929dbbc62e4c16551be6"}, {file = "numba-0.58.1-cp310-cp310-win_amd64.whl", hash = "sha256:81fe5b51532478149b5081311b0fd4206959174e660c372b94ed5364cfb37c82"}, {file = "numba-0.58.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:bcecd3fb9df36554b342140a4d77d938a549be635d64caf8bd9ef6c47a47f8aa"}, {file = "numba-0.58.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a1eaa744f518bbd60e1f7ccddfb8002b3d06bd865b94a5d7eac25028efe0e0ff"}, {file = "numba-0.58.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:bf68df9c307fb0aa81cacd33faccd6e419496fdc621e83f1efce35cdc5e79cac"}, {file = "numba-0.58.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:55a01e1881120e86d54efdff1be08381886fe9f04fc3006af309c602a72bc44d"}, {file = "numba-0.58.1-cp311-cp311-win_amd64.whl", hash = "sha256:811305d5dc40ae43c3ace5b192c670c358a89a4d2ae4f86d1665003798ea7a1a"}, {file = "numba-0.58.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:ea5bfcf7d641d351c6a80e8e1826eb4a145d619870016eeaf20bbd71ef5caa22"}, {file = "numba-0.58.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:e63d6aacaae1ba4ef3695f1c2122b30fa3d8ba039c8f517784668075856d79e2"}, {file = "numba-0.58.1-cp38-cp38-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:6fe7a9d8e3bd996fbe5eac0683227ccef26cba98dae6e5cee2c1894d4b9f16c1"}, {file = "numba-0.58.1-cp38-cp38-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:898af055b03f09d33a587e9425500e5be84fc90cd2f80b3fb71c6a4a17a7e354"}, {file = "numba-0.58.1-cp38-cp38-win_amd64.whl", hash = "sha256:d3e2fe81fe9a59fcd99cc572002101119059d64d31eb6324995ee8b0f144a306"}, {file = "numba-0.58.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5c765aef472a9406a97ea9782116335ad4f9ef5c9f93fc05fd44aab0db486954"}, {file = "numba-0.58.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:9e9356e943617f5e35a74bf56ff6e7cc83e6b1865d5e13cee535d79bf2cae954"}, {file = "numba-0.58.1-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:240e7a1ae80eb6b14061dc91263b99dc8d6af9ea45d310751b780888097c1aaa"}, {file = "numba-0.58.1-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:45698b995914003f890ad839cfc909eeb9c74921849c712a05405d1a79c50f68"}, {file = "numba-0.58.1-cp39-cp39-win_amd64.whl", hash = "sha256:bd3dda77955be03ff366eebbfdb39919ce7c2620d86c906203bed92124989032"}, {file = "numba-0.58.1.tar.gz", hash = "sha256:487ded0633efccd9ca3a46364b40006dbdaca0f95e99b8b83e778d1195ebcbaa"}, ] [package.dependencies] importlib-metadata = {version = "*", markers = "python_version < \"3.9\""} llvmlite = "==0.41.*" numpy = ">=1.22,<1.27" [[package]] name = "numpy" version = "1.24.4" description = "Fundamental package for array computing in Python" optional = true python-versions = ">=3.8" files = [ {file = "numpy-1.24.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c0bfb52d2169d58c1cdb8cc1f16989101639b34c7d3ce60ed70b19c63eba0b64"}, {file = "numpy-1.24.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ed094d4f0c177b1b8e7aa9cba7d6ceed51c0e569a5318ac0ca9a090680a6a1b1"}, {file = "numpy-1.24.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:79fc682a374c4a8ed08b331bef9c5f582585d1048fa6d80bc6c35bc384eee9b4"}, {file = "numpy-1.24.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7ffe43c74893dbf38c2b0a1f5428760a1a9c98285553c89e12d70a96a7f3a4d6"}, {file = "numpy-1.24.4-cp310-cp310-win32.whl", hash = "sha256:4c21decb6ea94057331e111a5bed9a79d335658c27ce2adb580fb4d54f2ad9bc"}, {file = "numpy-1.24.4-cp310-cp310-win_amd64.whl", hash = "sha256:b4bea75e47d9586d31e892a7401f76e909712a0fd510f58f5337bea9572c571e"}, {file = "numpy-1.24.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f136bab9c2cfd8da131132c2cf6cc27331dd6fae65f95f69dcd4ae3c3639c810"}, {file = "numpy-1.24.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e2926dac25b313635e4d6cf4dc4e51c8c0ebfed60b801c799ffc4c32bf3d1254"}, {file = "numpy-1.24.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:222e40d0e2548690405b0b3c7b21d1169117391c2e82c378467ef9ab4c8f0da7"}, {file = "numpy-1.24.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7215847ce88a85ce39baf9e89070cb860c98fdddacbaa6c0da3ffb31b3350bd5"}, {file = "numpy-1.24.4-cp311-cp311-win32.whl", hash = "sha256:4979217d7de511a8d57f4b4b5b2b965f707768440c17cb70fbf254c4b225238d"}, {file = "numpy-1.24.4-cp311-cp311-win_amd64.whl", hash = "sha256:b7b1fc9864d7d39e28f41d089bfd6353cb5f27ecd9905348c24187a768c79694"}, {file = "numpy-1.24.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1452241c290f3e2a312c137a9999cdbf63f78864d63c79039bda65ee86943f61"}, {file = "numpy-1.24.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:04640dab83f7c6c85abf9cd729c5b65f1ebd0ccf9de90b270cd61935eef0197f"}, {file = "numpy-1.24.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a5425b114831d1e77e4b5d812b69d11d962e104095a5b9c3b641a218abcc050e"}, {file = "numpy-1.24.4-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd80e219fd4c71fc3699fc1dadac5dcf4fd882bfc6f7ec53d30fa197b8ee22dc"}, {file = "numpy-1.24.4-cp38-cp38-win32.whl", hash = "sha256:4602244f345453db537be5314d3983dbf5834a9701b7723ec28923e2889e0bb2"}, {file = "numpy-1.24.4-cp38-cp38-win_amd64.whl", hash = "sha256:692f2e0f55794943c5bfff12b3f56f99af76f902fc47487bdfe97856de51a706"}, {file = "numpy-1.24.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:2541312fbf09977f3b3ad449c4e5f4bb55d0dbf79226d7724211acc905049400"}, {file = "numpy-1.24.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:9667575fb6d13c95f1b36aca12c5ee3356bf001b714fc354eb5465ce1609e62f"}, {file = "numpy-1.24.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f3a86ed21e4f87050382c7bc96571755193c4c1392490744ac73d660e8f564a9"}, {file = "numpy-1.24.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d11efb4dbecbdf22508d55e48d9c8384db795e1b7b51ea735289ff96613ff74d"}, {file = "numpy-1.24.4-cp39-cp39-win32.whl", hash = "sha256:6620c0acd41dbcb368610bb2f4d83145674040025e5536954782467100aa8835"}, {file = "numpy-1.24.4-cp39-cp39-win_amd64.whl", hash = "sha256:befe2bf740fd8373cf56149a5c23a0f601e82869598d41f8e188a0e9869926f8"}, {file = "numpy-1.24.4-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:31f13e25b4e304632a4619d0e0777662c2ffea99fcae2029556b17d8ff958aef"}, {file = "numpy-1.24.4-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:95f7ac6540e95bc440ad77f56e520da5bf877f87dca58bd095288dce8940532a"}, {file = "numpy-1.24.4-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:e98f220aa76ca2a977fe435f5b04d7b3470c0a2e6312907b37ba6068f26787f2"}, {file = "numpy-1.24.4.tar.gz", hash = "sha256:80f5e3a4e498641401868df4208b74581206afbee7cf7b8329daae82676d9463"}, ] [[package]] name = "oauthlib" version = "3.2.2" description = "A generic, spec-compliant, thorough implementation of the OAuth request-signing logic" optional = false python-versions = ">=3.6" files = [ {file = "oauthlib-3.2.2-py3-none-any.whl", hash = "sha256:8139f29aac13e25d502680e9e19963e83f16838d48a0d71c287fe40e7067fbca"}, {file = "oauthlib-3.2.2.tar.gz", hash = "sha256:9859c40929662bec5d64f34d01c99e093149682a3f38915dc0655d5a633dd918"}, ] [package.extras] rsa = ["cryptography (>=3.0.0)"] signals = ["blinker (>=1.4.0)"] signedtoken = ["cryptography (>=3.0.0)", "pyjwt (>=2.0.0,<3)"] [[package]] name = "packaging" version = "24.2" description = "Core utilities for Python packages" optional = false python-versions = ">=3.8" files = [ {file = "packaging-24.2-py3-none-any.whl", hash = "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759"}, {file = "packaging-24.2.tar.gz", hash = "sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f"}, ] [[package]] name = "pillow" version = "10.4.0" description = "Python Imaging Library (Fork)" optional = true python-versions = ">=3.8" files = [ {file = "pillow-10.4.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:4d9667937cfa347525b319ae34375c37b9ee6b525440f3ef48542fcf66f2731e"}, {file = "pillow-10.4.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:543f3dc61c18dafb755773efc89aae60d06b6596a63914107f75459cf984164d"}, {file = "pillow-10.4.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7928ecbf1ece13956b95d9cbcfc77137652b02763ba384d9ab508099a2eca856"}, {file = "pillow-10.4.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e4d49b85c4348ea0b31ea63bc75a9f3857869174e2bf17e7aba02945cd218e6f"}, {file = "pillow-10.4.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:6c762a5b0997f5659a5ef2266abc1d8851ad7749ad9a6a5506eb23d314e4f46b"}, {file = "pillow-10.4.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:a985e028fc183bf12a77a8bbf36318db4238a3ded7fa9df1b9a133f1cb79f8fc"}, {file = "pillow-10.4.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:812f7342b0eee081eaec84d91423d1b4650bb9828eb53d8511bcef8ce5aecf1e"}, {file = "pillow-10.4.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:ac1452d2fbe4978c2eec89fb5a23b8387aba707ac72810d9490118817d9c0b46"}, {file = "pillow-10.4.0-cp310-cp310-win32.whl", hash = "sha256:bcd5e41a859bf2e84fdc42f4edb7d9aba0a13d29a2abadccafad99de3feff984"}, {file = "pillow-10.4.0-cp310-cp310-win_amd64.whl", hash = "sha256:ecd85a8d3e79cd7158dec1c9e5808e821feea088e2f69a974db5edf84dc53141"}, {file = "pillow-10.4.0-cp310-cp310-win_arm64.whl", hash = "sha256:ff337c552345e95702c5fde3158acb0625111017d0e5f24bf3acdb9cc16b90d1"}, {file = "pillow-10.4.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:0a9ec697746f268507404647e531e92889890a087e03681a3606d9b920fbee3c"}, {file = "pillow-10.4.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:dfe91cb65544a1321e631e696759491ae04a2ea11d36715eca01ce07284738be"}, {file = "pillow-10.4.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5dc6761a6efc781e6a1544206f22c80c3af4c8cf461206d46a1e6006e4429ff3"}, {file = "pillow-10.4.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5e84b6cc6a4a3d76c153a6b19270b3526a5a8ed6b09501d3af891daa2a9de7d6"}, {file = "pillow-10.4.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:bbc527b519bd3aa9d7f429d152fea69f9ad37c95f0b02aebddff592688998abe"}, {file = "pillow-10.4.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:76a911dfe51a36041f2e756b00f96ed84677cdeb75d25c767f296c1c1eda1319"}, {file = "pillow-10.4.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:59291fb29317122398786c2d44427bbd1a6d7ff54017075b22be9d21aa59bd8d"}, {file = "pillow-10.4.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:416d3a5d0e8cfe4f27f574362435bc9bae57f679a7158e0096ad2beb427b8696"}, {file = "pillow-10.4.0-cp311-cp311-win32.whl", hash = "sha256:7086cc1d5eebb91ad24ded9f58bec6c688e9f0ed7eb3dbbf1e4800280a896496"}, {file = "pillow-10.4.0-cp311-cp311-win_amd64.whl", hash = "sha256:cbed61494057c0f83b83eb3a310f0bf774b09513307c434d4366ed64f4128a91"}, {file = "pillow-10.4.0-cp311-cp311-win_arm64.whl", hash = "sha256:f5f0c3e969c8f12dd2bb7e0b15d5c468b51e5017e01e2e867335c81903046a22"}, {file = "pillow-10.4.0-cp312-cp312-macosx_10_10_x86_64.whl", hash = "sha256:673655af3eadf4df6b5457033f086e90299fdd7a47983a13827acf7459c15d94"}, {file = "pillow-10.4.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:866b6942a92f56300012f5fbac71f2d610312ee65e22f1aa2609e491284e5597"}, {file = "pillow-10.4.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:29dbdc4207642ea6aad70fbde1a9338753d33fb23ed6956e706936706f52dd80"}, {file = "pillow-10.4.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bf2342ac639c4cf38799a44950bbc2dfcb685f052b9e262f446482afaf4bffca"}, {file = "pillow-10.4.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:f5b92f4d70791b4a67157321c4e8225d60b119c5cc9aee8ecf153aace4aad4ef"}, {file = "pillow-10.4.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:86dcb5a1eb778d8b25659d5e4341269e8590ad6b4e8b44d9f4b07f8d136c414a"}, {file = "pillow-10.4.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:780c072c2e11c9b2c7ca37f9a2ee8ba66f44367ac3e5c7832afcfe5104fd6d1b"}, {file = "pillow-10.4.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:37fb69d905be665f68f28a8bba3c6d3223c8efe1edf14cc4cfa06c241f8c81d9"}, {file = "pillow-10.4.0-cp312-cp312-win32.whl", hash = "sha256:7dfecdbad5c301d7b5bde160150b4db4c659cee2b69589705b6f8a0c509d9f42"}, {file = "pillow-10.4.0-cp312-cp312-win_amd64.whl", hash = "sha256:1d846aea995ad352d4bdcc847535bd56e0fd88d36829d2c90be880ef1ee4668a"}, {file = "pillow-10.4.0-cp312-cp312-win_arm64.whl", hash = "sha256:e553cad5179a66ba15bb18b353a19020e73a7921296a7979c4a2b7f6a5cd57f9"}, {file = "pillow-10.4.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8bc1a764ed8c957a2e9cacf97c8b2b053b70307cf2996aafd70e91a082e70df3"}, {file = "pillow-10.4.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:6209bb41dc692ddfee4942517c19ee81b86c864b626dbfca272ec0f7cff5d9fb"}, {file = "pillow-10.4.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bee197b30783295d2eb680b311af15a20a8b24024a19c3a26431ff83eb8d1f70"}, {file = "pillow-10.4.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1ef61f5dd14c300786318482456481463b9d6b91ebe5ef12f405afbba77ed0be"}, {file = "pillow-10.4.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:297e388da6e248c98bc4a02e018966af0c5f92dfacf5a5ca22fa01cb3179bca0"}, {file = "pillow-10.4.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:e4db64794ccdf6cb83a59d73405f63adbe2a1887012e308828596100a0b2f6cc"}, {file = "pillow-10.4.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:bd2880a07482090a3bcb01f4265f1936a903d70bc740bfcb1fd4e8a2ffe5cf5a"}, {file = "pillow-10.4.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4b35b21b819ac1dbd1233317adeecd63495f6babf21b7b2512d244ff6c6ce309"}, {file = "pillow-10.4.0-cp313-cp313-win32.whl", hash = "sha256:551d3fd6e9dc15e4c1eb6fc4ba2b39c0c7933fa113b220057a34f4bb3268a060"}, {file = "pillow-10.4.0-cp313-cp313-win_amd64.whl", hash = "sha256:030abdbe43ee02e0de642aee345efa443740aa4d828bfe8e2eb11922ea6a21ea"}, {file = "pillow-10.4.0-cp313-cp313-win_arm64.whl", hash = "sha256:5b001114dd152cfd6b23befeb28d7aee43553e2402c9f159807bf55f33af8a8d"}, {file = "pillow-10.4.0-cp38-cp38-macosx_10_10_x86_64.whl", hash = "sha256:8d4d5063501b6dd4024b8ac2f04962d661222d120381272deea52e3fc52d3736"}, {file = "pillow-10.4.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:7c1ee6f42250df403c5f103cbd2768a28fe1a0ea1f0f03fe151c8741e1469c8b"}, {file = "pillow-10.4.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b15e02e9bb4c21e39876698abf233c8c579127986f8207200bc8a8f6bb27acf2"}, {file = "pillow-10.4.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7a8d4bade9952ea9a77d0c3e49cbd8b2890a399422258a77f357b9cc9be8d680"}, {file = "pillow-10.4.0-cp38-cp38-manylinux_2_28_aarch64.whl", hash = "sha256:43efea75eb06b95d1631cb784aa40156177bf9dd5b4b03ff38979e048258bc6b"}, {file = "pillow-10.4.0-cp38-cp38-manylinux_2_28_x86_64.whl", hash = "sha256:950be4d8ba92aca4b2bb0741285a46bfae3ca699ef913ec8416c1b78eadd64cd"}, {file = "pillow-10.4.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:d7480af14364494365e89d6fddc510a13e5a2c3584cb19ef65415ca57252fb84"}, {file = "pillow-10.4.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:73664fe514b34c8f02452ffb73b7a92c6774e39a647087f83d67f010eb9a0cf0"}, {file = "pillow-10.4.0-cp38-cp38-win32.whl", hash = "sha256:e88d5e6ad0d026fba7bdab8c3f225a69f063f116462c49892b0149e21b6c0a0e"}, {file = "pillow-10.4.0-cp38-cp38-win_amd64.whl", hash = "sha256:5161eef006d335e46895297f642341111945e2c1c899eb406882a6c61a4357ab"}, {file = "pillow-10.4.0-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:0ae24a547e8b711ccaaf99c9ae3cd975470e1a30caa80a6aaee9a2f19c05701d"}, {file = "pillow-10.4.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:298478fe4f77a4408895605f3482b6cc6222c018b2ce565c2b6b9c354ac3229b"}, {file = "pillow-10.4.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:134ace6dc392116566980ee7436477d844520a26a4b1bd4053f6f47d096997fd"}, {file = "pillow-10.4.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:930044bb7679ab003b14023138b50181899da3f25de50e9dbee23b61b4de2126"}, {file = "pillow-10.4.0-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:c76e5786951e72ed3686e122d14c5d7012f16c8303a674d18cdcd6d89557fc5b"}, {file = "pillow-10.4.0-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:b2724fdb354a868ddf9a880cb84d102da914e99119211ef7ecbdc613b8c96b3c"}, {file = "pillow-10.4.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:dbc6ae66518ab3c5847659e9988c3b60dc94ffb48ef9168656e0019a93dbf8a1"}, {file = "pillow-10.4.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:06b2f7898047ae93fad74467ec3d28fe84f7831370e3c258afa533f81ef7f3df"}, {file = "pillow-10.4.0-cp39-cp39-win32.whl", hash = "sha256:7970285ab628a3779aecc35823296a7869f889b8329c16ad5a71e4901a3dc4ef"}, {file = "pillow-10.4.0-cp39-cp39-win_amd64.whl", hash = "sha256:961a7293b2457b405967af9c77dcaa43cc1a8cd50d23c532e62d48ab6cdd56f5"}, {file = "pillow-10.4.0-cp39-cp39-win_arm64.whl", hash = "sha256:32cda9e3d601a52baccb2856b8ea1fc213c90b340c542dcef77140dfa3278a9e"}, {file = "pillow-10.4.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:5b4815f2e65b30f5fbae9dfffa8636d992d49705723fe86a3661806e069352d4"}, {file = "pillow-10.4.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:8f0aef4ef59694b12cadee839e2ba6afeab89c0f39a3adc02ed51d109117b8da"}, {file = "pillow-10.4.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9f4727572e2918acaa9077c919cbbeb73bd2b3ebcfe033b72f858fc9fbef0026"}, {file = "pillow-10.4.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ff25afb18123cea58a591ea0244b92eb1e61a1fd497bf6d6384f09bc3262ec3e"}, {file = "pillow-10.4.0-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:dc3e2db6ba09ffd7d02ae9141cfa0ae23393ee7687248d46a7507b75d610f4f5"}, {file = "pillow-10.4.0-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:02a2be69f9c9b8c1e97cf2713e789d4e398c751ecfd9967c18d0ce304efbf885"}, {file = "pillow-10.4.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:0755ffd4a0c6f267cccbae2e9903d95477ca2f77c4fcf3a3a09570001856c8a5"}, {file = "pillow-10.4.0-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:a02364621fe369e06200d4a16558e056fe2805d3468350df3aef21e00d26214b"}, {file = "pillow-10.4.0-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:1b5dea9831a90e9d0721ec417a80d4cbd7022093ac38a568db2dd78363b00908"}, {file = "pillow-10.4.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9b885f89040bb8c4a1573566bbb2f44f5c505ef6e74cec7ab9068c900047f04b"}, {file = "pillow-10.4.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:87dd88ded2e6d74d31e1e0a99a726a6765cda32d00ba72dc37f0651f306daaa8"}, {file = "pillow-10.4.0-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:2db98790afc70118bd0255c2eeb465e9767ecf1f3c25f9a1abb8ffc8cfd1fe0a"}, {file = "pillow-10.4.0-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:f7baece4ce06bade126fb84b8af1c33439a76d8a6fd818970215e0560ca28c27"}, {file = "pillow-10.4.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:cfdd747216947628af7b259d274771d84db2268ca062dd5faf373639d00113a3"}, {file = "pillow-10.4.0.tar.gz", hash = "sha256:166c1cd4d24309b30d61f79f4a9114b7b2313d7450912277855ff5dfd7cd4a06"}, ] [package.extras] docs = ["furo", "olefile", "sphinx (>=7.3)", "sphinx-copybutton", "sphinx-inline-tabs", "sphinxext-opengraph"] fpx = ["olefile"] mic = ["olefile"] tests = ["check-manifest", "coverage", "defusedxml", "markdown2", "olefile", "packaging", "pyroma", "pytest", "pytest-cov", "pytest-timeout"] typing = ["typing-extensions"] xmp = ["defusedxml"] [[package]] name = "platformdirs" version = "4.3.6" description = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`." optional = false python-versions = ">=3.8" files = [ {file = "platformdirs-4.3.6-py3-none-any.whl", hash = "sha256:73e575e1408ab8103900836b97580d5307456908a03e92031bab39e4554cc3fb"}, {file = "platformdirs-4.3.6.tar.gz", hash = "sha256:357fb2acbc885b0419afd3ce3ed34564c13c9b95c89360cd9563f73aa5e2b907"}, ] [package.extras] docs = ["furo (>=2024.8.6)", "proselint (>=0.14)", "sphinx (>=8.0.2)", "sphinx-autodoc-typehints (>=2.4)"] test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=8.3.2)", "pytest-cov (>=5)", "pytest-mock (>=3.14)"] type = ["mypy (>=1.11.2)"] [[package]] name = "pluggy" version = "1.5.0" description = "plugin and hook calling mechanisms for python" optional = false python-versions = ">=3.8" files = [ {file = "pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669"}, {file = "pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1"}, ] [package.extras] dev = ["pre-commit", "tox"] testing = ["pytest", "pytest-benchmark"] [[package]] name = "pooch" version = "1.8.2" description = "A friend to fetch your data files" optional = true python-versions = ">=3.7" files = [ {file = "pooch-1.8.2-py3-none-any.whl", hash = "sha256:3529a57096f7198778a5ceefd5ac3ef0e4d06a6ddaf9fc2d609b806f25302c47"}, {file = "pooch-1.8.2.tar.gz", hash = "sha256:76561f0de68a01da4df6af38e9955c4c9d1a5c90da73f7e40276a5728ec83d10"}, ] [package.dependencies] packaging = ">=20.0" platformdirs = ">=2.5.0" requests = ">=2.19.0" [package.extras] progress = ["tqdm (>=4.41.0,<5.0.0)"] sftp = ["paramiko (>=2.7.0)"] xxhash = ["xxhash (>=1.4.3)"] [[package]] name = "psutil" version = "6.1.0" description = "Cross-platform lib for process and system monitoring in Python." optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7" files = [ {file = "psutil-6.1.0-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:ff34df86226c0227c52f38b919213157588a678d049688eded74c76c8ba4a5d0"}, {file = "psutil-6.1.0-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:c0e0c00aa18ca2d3b2b991643b799a15fc8f0563d2ebb6040f64ce8dc027b942"}, {file = "psutil-6.1.0-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:000d1d1ebd634b4efb383f4034437384e44a6d455260aaee2eca1e9c1b55f047"}, {file = "psutil-6.1.0-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:5cd2bcdc75b452ba2e10f0e8ecc0b57b827dd5d7aaffbc6821b2a9a242823a76"}, {file = "psutil-6.1.0-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:045f00a43c737f960d273a83973b2511430d61f283a44c96bf13a6e829ba8fdc"}, {file = "psutil-6.1.0-cp27-none-win32.whl", hash = "sha256:9118f27452b70bb1d9ab3198c1f626c2499384935aaf55388211ad982611407e"}, {file = "psutil-6.1.0-cp27-none-win_amd64.whl", hash = "sha256:a8506f6119cff7015678e2bce904a4da21025cc70ad283a53b099e7620061d85"}, {file = "psutil-6.1.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:6e2dcd475ce8b80522e51d923d10c7871e45f20918e027ab682f94f1c6351688"}, {file = "psutil-6.1.0-cp36-abi3-macosx_11_0_arm64.whl", hash = "sha256:0895b8414afafc526712c498bd9de2b063deaac4021a3b3c34566283464aff8e"}, {file = "psutil-6.1.0-cp36-abi3-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9dcbfce5d89f1d1f2546a2090f4fcf87c7f669d1d90aacb7d7582addece9fb38"}, {file = "psutil-6.1.0-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:498c6979f9c6637ebc3a73b3f87f9eb1ec24e1ce53a7c5173b8508981614a90b"}, {file = "psutil-6.1.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d905186d647b16755a800e7263d43df08b790d709d575105d419f8b6ef65423a"}, {file = "psutil-6.1.0-cp36-cp36m-win32.whl", hash = "sha256:6d3fbbc8d23fcdcb500d2c9f94e07b1342df8ed71b948a2649b5cb060a7c94ca"}, {file = "psutil-6.1.0-cp36-cp36m-win_amd64.whl", hash = "sha256:1209036fbd0421afde505a4879dee3b2fd7b1e14fee81c0069807adcbbcca747"}, {file = "psutil-6.1.0-cp37-abi3-win32.whl", hash = "sha256:1ad45a1f5d0b608253b11508f80940985d1d0c8f6111b5cb637533a0e6ddc13e"}, {file = "psutil-6.1.0-cp37-abi3-win_amd64.whl", hash = "sha256:a8fb3752b491d246034fa4d279ff076501588ce8cbcdbb62c32fd7a377d996be"}, {file = "psutil-6.1.0.tar.gz", hash = "sha256:353815f59a7f64cdaca1c0307ee13558a0512f6db064e92fe833784f08539c7a"}, ] [package.extras] dev = ["black", "check-manifest", "coverage", "packaging", "pylint", "pyperf", "pypinfo", "pytest-cov", "requests", "rstcheck", "ruff", "sphinx", "sphinx_rtd_theme", "toml-sort", "twine", "virtualenv", "wheel"] test = ["pytest", "pytest-xdist", "setuptools"] [[package]] name = "py7zr" version = "0.22.0" description = "Pure python 7-zip library" optional = false python-versions = ">=3.8" files = [ {file = "py7zr-0.22.0-py3-none-any.whl", hash = "sha256:993b951b313500697d71113da2681386589b7b74f12e48ba13cc12beca79d078"}, {file = "py7zr-0.22.0.tar.gz", hash = "sha256:c6c7aea5913535184003b73938490f9a4d8418598e533f9ca991d3b8e45a139e"}, ] [package.dependencies] brotli = {version = ">=1.1.0", markers = "platform_python_implementation == \"CPython\""} brotlicffi = {version = ">=1.1.0.0", markers = "platform_python_implementation == \"PyPy\""} inflate64 = ">=1.0.0,<1.1.0" multivolumefile = ">=0.2.3" psutil = {version = "*", markers = "sys_platform != \"cygwin\""} pybcj = ">=1.0.0,<1.1.0" pycryptodomex = ">=3.16.0" pyppmd = ">=1.1.0,<1.2.0" pyzstd = ">=0.15.9" texttable = "*" [package.extras] check = ["black (>=23.1.0)", "check-manifest", "flake8 (<8)", "flake8-black (>=0.3.6)", "flake8-deprecated", "flake8-isort", "isort (>=5.0.3)", "lxml", "mypy (>=0.940)", "mypy-extensions (>=0.4.1)", "pygments", "readme-renderer", "twine", "types-psutil"] debug = ["pytest", "pytest-leaks", "pytest-profiling"] docs = ["docutils", "sphinx (>=5.0)", "sphinx-a4doc", "sphinx-py3doc-enhanced-theme"] test = ["coverage[toml] (>=5.2)", "coveralls (>=2.1.1)", "py-cpuinfo", "pytest", "pytest-benchmark", "pytest-cov", "pytest-remotedata", "pytest-timeout"] test-compat = ["libarchive-c"] [[package]] name = "pyacoustid" version = "1.3.0" description = "bindings for Chromaprint acoustic fingerprinting and the Acoustid API" optional = true python-versions = "*" files = [ {file = "pyacoustid-1.3.0.tar.gz", hash = "sha256:5f4f487191c19ebb908270b1b7b5297f132da332b1568b96a914574c079ed177"}, ] [package.dependencies] audioread = "*" requests = "*" [[package]] name = "pybcj" version = "1.0.2" description = "bcj filter library" optional = false python-versions = ">=3.8" files = [ {file = "pybcj-1.0.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7bff28d97e47047d69a4ac6bf59adda738cf1d00adde8819117fdb65d966bdbc"}, {file = "pybcj-1.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:198e0b4768b4025eb3309273d7e81dc53834b9a50092be6e0d9b3983cfd35c35"}, {file = "pybcj-1.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:fa26415b4a118ea790de9d38f244312f2510a9bb5c65e560184d241a6f391a2d"}, {file = "pybcj-1.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fabb2be57e4ca28ea36c13146cdf97d73abd27c51741923fc6ba1e8cd33e255c"}, {file = "pybcj-1.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:75d6d613bae6f27678d5e44e89d61018779726aa6aa950c516d33a04b8af8c59"}, {file = "pybcj-1.0.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:3ffae79ef8a1ea81ea2748ad7b7ad9b882aa88ddf65ce90f9e944df639eccc61"}, {file = "pybcj-1.0.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:bdb4d8ff5cba3e0bd1adee7d20dbb2b4d80cb31ac04d6ea1cd06cfc02d2ecd0d"}, {file = "pybcj-1.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:a29be917fbc99eca204b08407e0971e0205bfdad4b74ec915930675f352b669d"}, {file = "pybcj-1.0.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:a2562ebe5a0abec4da0229f8abb5e90ee97b178f19762eb925c1159be36828b3"}, {file = "pybcj-1.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:af19bc61ded933001cd68f004ae2042bf1a78eb498a3c685ebd655fa1be90dbe"}, {file = "pybcj-1.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f3f4a447800850aba7724a2274ea0a4800724520c1caf38f7d0dabf2f89a5e15"}, {file = "pybcj-1.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ce1c8af7a4761d2b1b531864d84113948daa0c4245775c63bd9874cb955f4662"}, {file = "pybcj-1.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8007371f6f2b462f5aa05d5c2135d0a1bcf5b7bdd9bd15d86c730f588d10b7d3"}, {file = "pybcj-1.0.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:1079ca63ff8da5c936b76863690e0bd2489e8d4e0a3a340e032095dae805dd91"}, {file = "pybcj-1.0.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e9a785eb26884429d9b9f6326e68c3638828c83bf6d42d2463c97ad5385caff2"}, {file = "pybcj-1.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:9ea46e2d45469d13b7f25b08efcdb140220bab1ac5a850db0954591715b8caaa"}, {file = "pybcj-1.0.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:21b5f2460629167340403d359289a173e0729ce8e84e3ce99462009d5d5e01a4"}, {file = "pybcj-1.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:2940fb85730b9869254559c491cd83cf777e56c76a8a60df60e4be4f2a4248d7"}, {file = "pybcj-1.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f40f3243139d675f43793a4e35c410c370f7b91ccae74e70c8b2f4877869f90e"}, {file = "pybcj-1.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9c2b3e60b65c7ac73e44335934e1e122da8d56db87840984601b3c5dc0ae4c19"}, {file = "pybcj-1.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:746550dc7b5af4d04bb5fa4d065f18d39c925bcb5dee30db75747cd9a58bb6e8"}, {file = "pybcj-1.0.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:8ce9b62b6aaa5b08773be8a919ecc4e865396c969f982b685eeca6e80c82abb7"}, {file = "pybcj-1.0.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:493eab2b1f6f546730a6de0c5ceb75ce16f3767154e8ae30e2b70d41b928b7d2"}, {file = "pybcj-1.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:ef55b96b7f2ed823e0b924de902065ec42ade856366c287dbb073fabd6b90ec1"}, {file = "pybcj-1.0.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:2ed5b3dd9c209fe7b90990dee4ef21870dca39db1cd326553c314ee1b321c1cc"}, {file = "pybcj-1.0.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:22a94885723f8362d4cb468e68910eef92d3e2b1293de82b8eacb4198ef6655f"}, {file = "pybcj-1.0.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:b8f9368036c9e658d8e3b3534086d298a5349c864542b34657cbe57c260daa49"}, {file = "pybcj-1.0.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:87108181c7a6ac4d3fc1e4551cab5db5eea7f9fdca611175243234cd94bcc59b"}, {file = "pybcj-1.0.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:db57f26b8c0162cfddb52b869efb1741b8c5e67fc536994f743074985f714c55"}, {file = "pybcj-1.0.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:bdf5bcac4f1da36ad43567ea6f6ef404347658dbbe417c87cdb1699f327d6337"}, {file = "pybcj-1.0.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:5c3171bb95c9b45cbcad25589e1ae4f4ca4ea99dc1724c4e0671eb6b9055514e"}, {file = "pybcj-1.0.2-cp38-cp38-win_amd64.whl", hash = "sha256:f9a2585e0da9cf343ea27421995b881736a1eb604a7c1d4ca74126af94c3d4a8"}, {file = "pybcj-1.0.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:fdb7cd8271471a5979d84915c1ee57eea7e0a69c893225fc418db66883b0e2a7"}, {file = "pybcj-1.0.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:e96ae14062bdcddc3197300e6ee4efa6fbc6749be917db934eac66d0daaecb68"}, {file = "pybcj-1.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a54ebdc8423ba99d75372708a882fcfc3b14d9d52cf195295ad53e5a47dab37f"}, {file = "pybcj-1.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3602be737c6e9553c45ae89e6b0e556f64f34dabf27d5260317d1824d31b79d3"}, {file = "pybcj-1.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:63dd2ca52a48841f561bfec0fa3f208d375b0a8dcd3d7b236459e683ae29221d"}, {file = "pybcj-1.0.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:8204a714029784b1a08a3d790430d80b423b68615c5b1e67aabca5bd5419b77d"}, {file = "pybcj-1.0.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:fde2376b180ae2620c102fbc3ef06638d306feae83964aaa5051ecbdda54845a"}, {file = "pybcj-1.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:3b8d7810fb587adbffba025330cf212d9bbed8f29559656d05cb6609673f306a"}, {file = "pybcj-1.0.2.tar.gz", hash = "sha256:c7f5bef7f47723c53420e377bc64d2553843bee8bcac5f0ad076ab1524780018"}, ] [package.extras] check = ["check-manifest", "flake8 (<5)", "flake8-black", "flake8-colors", "flake8-isort", "flake8-pyi", "flake8-typing-imports", "mypy (>=0.812)", "mypy-extensions (>=0.4.3)", "pygments", "readme-renderer"] test = ["coverage[toml] (>=5.2)", "hypothesis", "pytest (>=6.0)", "pytest-cov"] [[package]] name = "pycairo" version = "1.26.1" description = "Python interface for cairo" optional = true python-versions = ">=3.8" files = [ {file = "pycairo-1.26.1-cp310-cp310-win32.whl", hash = "sha256:b93b9e3072826a346f1f79cb1becc403d1ba4a3971cad61d144db0fe6dcb6be8"}, {file = "pycairo-1.26.1-cp310-cp310-win_amd64.whl", hash = "sha256:acfc76924ed668d8fea50f6cc6097b9a57ef6cd3dc3f2fa20814380d639a6dd2"}, {file = "pycairo-1.26.1-cp310-cp310-win_arm64.whl", hash = "sha256:067191315c3b4d09cad1ec57cdb8fc1d72e2574e89389c268a94f22d4fa98b5f"}, {file = "pycairo-1.26.1-cp311-cp311-win32.whl", hash = "sha256:56a29623aa7b4adbde5024c61ff001455b5a3def79e512742ea45ab36c3fe24b"}, {file = "pycairo-1.26.1-cp311-cp311-win_amd64.whl", hash = "sha256:8d2889e03a095de5da9e68a589b691a3ada09d60ef18b5fc1b1b99f2a7794297"}, {file = "pycairo-1.26.1-cp311-cp311-win_arm64.whl", hash = "sha256:7a307111de345304ed8eadd7f81ebd7fb1fc711224aa314a4e8e33af7dfa3d27"}, {file = "pycairo-1.26.1-cp312-cp312-win32.whl", hash = "sha256:5cc1808e9e30ccd0f4d84ba7700db5aab5673f8b6b901760369ebb88a0823436"}, {file = "pycairo-1.26.1-cp312-cp312-win_amd64.whl", hash = "sha256:36131a726f568b2dbc5e78ff50fbaa379e69db00614d46d66b1e4289caf9b1ce"}, {file = "pycairo-1.26.1-cp312-cp312-win_arm64.whl", hash = "sha256:5577b51543ea4c283c15f436d891e9eaf6fd43fe74882adb032fba2c271f3fe9"}, {file = "pycairo-1.26.1-cp38-cp38-win32.whl", hash = "sha256:27ec7b42c58af35dc11352881262dce4254378b0f11be0959d1c13edb4539d2c"}, {file = "pycairo-1.26.1-cp38-cp38-win_amd64.whl", hash = "sha256:27357994d277b3fd10a45e9ef58f80a4cb5e3291fe76c5edd58d2d06335eb8e7"}, {file = "pycairo-1.26.1-cp39-cp39-win32.whl", hash = "sha256:e68300d1c2196d1d34de3432885ae9ff78e10426fa16f765742a11c6f8fe0a71"}, {file = "pycairo-1.26.1-cp39-cp39-win_amd64.whl", hash = "sha256:ce049930e294c29b53c68dcaab3df97cc5de7eb1d3d8e8a9f5c77e7164cd6e85"}, {file = "pycairo-1.26.1-cp39-cp39-win_arm64.whl", hash = "sha256:22e1db531d4ed3167a98f0ea165bfa2a30df9d6eb22361c38158c031065999a4"}, {file = "pycairo-1.26.1.tar.gz", hash = "sha256:a11b999ce55b798dbf13516ab038e0ce8b6ec299b208d7c4e767a6f7e68e8430"}, ] [[package]] name = "pycparser" version = "2.22" description = "C parser in Python" optional = false python-versions = ">=3.8" files = [ {file = "pycparser-2.22-py3-none-any.whl", hash = "sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc"}, {file = "pycparser-2.22.tar.gz", hash = "sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6"}, ] [[package]] name = "pycryptodomex" version = "3.21.0" description = "Cryptographic library for Python" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7" files = [ {file = "pycryptodomex-3.21.0-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:dbeb84a399373df84a69e0919c1d733b89e049752426041deeb30d68e9867822"}, {file = "pycryptodomex-3.21.0-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:a192fb46c95489beba9c3f002ed7d93979423d1b2a53eab8771dbb1339eb3ddd"}, {file = "pycryptodomex-3.21.0-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:1233443f19d278c72c4daae749872a4af3787a813e05c3561c73ab0c153c7b0f"}, {file = "pycryptodomex-3.21.0-cp27-cp27m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bbb07f88e277162b8bfca7134b34f18b400d84eac7375ce73117f865e3c80d4c"}, {file = "pycryptodomex-3.21.0-cp27-cp27m-musllinux_1_1_aarch64.whl", hash = "sha256:e859e53d983b7fe18cb8f1b0e29d991a5c93be2c8dd25db7db1fe3bd3617f6f9"}, {file = "pycryptodomex-3.21.0-cp27-cp27m-win32.whl", hash = "sha256:ef046b2e6c425647971b51424f0f88d8a2e0a2a63d3531817968c42078895c00"}, {file = "pycryptodomex-3.21.0-cp27-cp27m-win_amd64.whl", hash = "sha256:da76ebf6650323eae7236b54b1b1f0e57c16483be6e3c1ebf901d4ada47563b6"}, {file = "pycryptodomex-3.21.0-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:c07e64867a54f7e93186a55bec08a18b7302e7bee1b02fd84c6089ec215e723a"}, {file = "pycryptodomex-3.21.0-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:56435c7124dd0ce0c8bdd99c52e5d183a0ca7fdcd06c5d5509423843f487dd0b"}, {file = "pycryptodomex-3.21.0-cp27-cp27mu-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:65d275e3f866cf6fe891411be9c1454fb58809ccc5de6d3770654c47197acd65"}, {file = "pycryptodomex-3.21.0-cp27-cp27mu-musllinux_1_1_aarch64.whl", hash = "sha256:5241bdb53bcf32a9568770a6584774b1b8109342bd033398e4ff2da052123832"}, {file = "pycryptodomex-3.21.0-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:34325b84c8b380675fd2320d0649cdcbc9cf1e0d1526edbe8fce43ed858cdc7e"}, {file = "pycryptodomex-3.21.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:103c133d6cd832ae7266feb0a65b69e3a5e4dbbd6f3a3ae3211a557fd653f516"}, {file = "pycryptodomex-3.21.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:77ac2ea80bcb4b4e1c6a596734c775a1615d23e31794967416afc14852a639d3"}, {file = "pycryptodomex-3.21.0-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9aa0cf13a1a1128b3e964dc667e5fe5c6235f7d7cfb0277213f0e2a783837cc2"}, {file = "pycryptodomex-3.21.0-cp36-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:46eb1f0c8d309da63a2064c28de54e5e614ad17b7e2f88df0faef58ce192fc7b"}, {file = "pycryptodomex-3.21.0-cp36-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:cc7e111e66c274b0df5f4efa679eb31e23c7545d702333dfd2df10ab02c2a2ce"}, {file = "pycryptodomex-3.21.0-cp36-abi3-musllinux_1_2_i686.whl", hash = "sha256:770d630a5c46605ec83393feaa73a9635a60e55b112e1fb0c3cea84c2897aa0a"}, {file = "pycryptodomex-3.21.0-cp36-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:52e23a0a6e61691134aa8c8beba89de420602541afaae70f66e16060fdcd677e"}, {file = "pycryptodomex-3.21.0-cp36-abi3-win32.whl", hash = "sha256:a3d77919e6ff56d89aada1bd009b727b874d464cb0e2e3f00a49f7d2e709d76e"}, {file = "pycryptodomex-3.21.0-cp36-abi3-win_amd64.whl", hash = "sha256:b0e9765f93fe4890f39875e6c90c96cb341767833cfa767f41b490b506fa9ec0"}, {file = "pycryptodomex-3.21.0-pp27-pypy_73-manylinux2010_x86_64.whl", hash = "sha256:feaecdce4e5c0045e7a287de0c4351284391fe170729aa9182f6bd967631b3a8"}, {file = "pycryptodomex-3.21.0-pp27-pypy_73-win32.whl", hash = "sha256:365aa5a66d52fd1f9e0530ea97f392c48c409c2f01ff8b9a39c73ed6f527d36c"}, {file = "pycryptodomex-3.21.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:3efddfc50ac0ca143364042324046800c126a1d63816d532f2e19e6f2d8c0c31"}, {file = "pycryptodomex-3.21.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0df2608682db8279a9ebbaf05a72f62a321433522ed0e499bc486a6889b96bf3"}, {file = "pycryptodomex-3.21.0-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5823d03e904ea3e53aebd6799d6b8ec63b7675b5d2f4a4bd5e3adcb512d03b37"}, {file = "pycryptodomex-3.21.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:27e84eeff24250ffec32722334749ac2a57a5fd60332cd6a0680090e7c42877e"}, {file = "pycryptodomex-3.21.0-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:8ef436cdeea794015263853311f84c1ff0341b98fc7908e8a70595a68cefd971"}, {file = "pycryptodomex-3.21.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7a1058e6dfe827f4209c5cae466e67610bcd0d66f2f037465daa2a29d92d952b"}, {file = "pycryptodomex-3.21.0-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9ba09a5b407cbb3bcb325221e346a140605714b5e880741dc9a1e9ecf1688d42"}, {file = "pycryptodomex-3.21.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:8a9d8342cf22b74a746e3c6c9453cb0cfbb55943410e3a2619bd9164b48dc9d9"}, {file = "pycryptodomex-3.21.0.tar.gz", hash = "sha256:222d0bd05381dd25c32dd6065c071ebf084212ab79bab4599ba9e6a3e0009e6c"}, ] [[package]] name = "pydata-sphinx-theme" version = "0.14.4" description = "Bootstrap-based Sphinx theme from the PyData community" optional = true python-versions = ">=3.8" files = [ {file = "pydata_sphinx_theme-0.14.4-py3-none-any.whl", hash = "sha256:ac15201f4c2e2e7042b0cad8b30251433c1f92be762ddcefdb4ae68811d918d9"}, {file = "pydata_sphinx_theme-0.14.4.tar.gz", hash = "sha256:f5d7a2cb7a98e35b9b49d3b02cec373ad28958c2ed5c9b1ffe6aff6c56e9de5b"}, ] [package.dependencies] accessible-pygments = "*" Babel = "*" beautifulsoup4 = "*" docutils = "!=0.17.0" packaging = "*" pygments = ">=2.7" sphinx = ">=5.0" typing-extensions = "*" [package.extras] a11y = ["pytest-playwright"] dev = ["nox", "pre-commit", "pydata-sphinx-theme[doc,test]", "pyyaml"] doc = ["ablog (>=0.11.0rc2)", "colorama", "ipykernel", "ipyleaflet", "jupyter_sphinx", "jupyterlite-sphinx", "linkify-it-py", "matplotlib", "myst-parser", "nbsphinx", "numpy", "numpydoc", "pandas", "plotly", "rich", "sphinx-autoapi (>=3.0.0)", "sphinx-copybutton", "sphinx-design", "sphinx-favicon (>=1.0.1)", "sphinx-sitemap", "sphinx-togglebutton", "sphinxcontrib-youtube (<1.4)", "sphinxext-rediraffe", "xarray"] test = ["pytest", "pytest-cov", "pytest-regressions"] [[package]] name = "pygments" version = "2.18.0" description = "Pygments is a syntax highlighting package written in Python." optional = true python-versions = ">=3.8" files = [ {file = "pygments-2.18.0-py3-none-any.whl", hash = "sha256:b8e6aca0523f3ab76fee51799c488e38782ac06eafcf95e7ba832985c8e7b13a"}, {file = "pygments-2.18.0.tar.gz", hash = "sha256:786ff802f32e91311bff3889f6e9a86e81505fe99f2735bb6d60ae0c5004f199"}, ] [package.extras] windows-terminal = ["colorama (>=0.4.6)"] [[package]] name = "pygobject" version = "3.48.2" description = "Python bindings for GObject Introspection" optional = true python-versions = "<4,>=3.8" files = [ {file = "pygobject-3.48.2.tar.gz", hash = "sha256:c3c0a7afbe5b2c1c64dc0530109b4dd571085153dbedfbccb8ec7c5ad233f977"}, ] [package.dependencies] pycairo = ">=1.16" [package.extras] dev = ["flake8", "pytest", "pytest-cov"] docs = ["sphinx (>=4.0,<5.0)", "sphinx-rtd-theme (>=0.5,<2.0)"] [[package]] name = "pylast" version = "5.3.0" description = "A Python interface to Last.fm and Libre.fm" optional = false python-versions = ">=3.8" files = [ {file = "pylast-5.3.0-py3-none-any.whl", hash = "sha256:4cc47cdcb05baf24a5cea10a012c17df0fe13e22911296a69835b127458a7308"}, {file = "pylast-5.3.0.tar.gz", hash = "sha256:637943b1b0e6045dd85ed7389db6071a1fea45cc7ff90dc6126fd509ca6fae2f"}, ] [package.dependencies] httpx = "*" [package.extras] tests = ["flaky", "pytest", "pytest-cov", "pytest-random-order", "pyyaml"] [[package]] name = "pyppmd" version = "1.1.0" description = "PPMd compression/decompression library" optional = false python-versions = ">=3.8" files = [ {file = "pyppmd-1.1.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:c5cd428715413fe55abf79dc9fc54924ba7e518053e1fc0cbdf80d0d99cf1442"}, {file = "pyppmd-1.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0e96cc43f44b7658be2ea764e7fa99c94cb89164dbb7cdf209178effc2168319"}, {file = "pyppmd-1.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:dd20142869094bceef5ab0b160f4fff790ad1f612313a1e3393a51fc3ba5d57e"}, {file = "pyppmd-1.1.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a4f9b51e45c11e805e74ea6f6355e98a6423b5bbd92f45aceee24761bdc3d3b8"}, {file = "pyppmd-1.1.0-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:459f85e928fb968d0e34fb6191fd8c4e710012d7d884fa2b317b2e11faac7c59"}, {file = "pyppmd-1.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f73cf2aaf60477eef17f5497d14b6099d8be9748390ad2b83d1c88214d050c05"}, {file = "pyppmd-1.1.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:2ea3ae0e92c0b5345cd3a4e145e01bbd79c2d95355481ea5d833b5c0cb202a2d"}, {file = "pyppmd-1.1.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:775172c740133c0162a01c1a5443d0e312246881cdd6834421b644d89a634b91"}, {file = "pyppmd-1.1.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:14421030f1d46f69829698bdd960698a3b3df0925e3c470e82cfcdd4446b7bc1"}, {file = "pyppmd-1.1.0-cp310-cp310-win32.whl", hash = "sha256:b691264f9962532aca3bba5be848b6370e596d0a2ca722c86df388be08d0568a"}, {file = "pyppmd-1.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:216b0d969a3f06e35fbfef979706d987d105fcb1e37b0b1324f01ee143719c4a"}, {file = "pyppmd-1.1.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:1f8c51044ee4df1b004b10bf6b3c92f95ea86cfe1111210d303dca44a56e4282"}, {file = "pyppmd-1.1.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ac25b3a13d1ac9b8f0bde46952e10848adc79d932f2b548a6491ef8825ae0045"}, {file = "pyppmd-1.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c8d3003eebe6aabe22ba744a38a146ed58a25633420d5da882b049342b7c8036"}, {file = "pyppmd-1.1.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7c520656bc12100aa6388df27dd7ac738577f38bf43f4a4bea78e1861e579ea5"}, {file = "pyppmd-1.1.0-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8c2a3e807028159a705951f5cb5d005f94caed11d0984e59cc50506de543e22d"}, {file = "pyppmd-1.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ec8a2447e69444703e2b273247bfcd4b540ec601780eff07da16344c62d2993d"}, {file = "pyppmd-1.1.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:b9e0c8053e69cad6a92a0889b3324f567afc75475b4f54727de553ac4fc85780"}, {file = "pyppmd-1.1.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:5938d256e8d2a2853dc3af8bb58ae6b4a775c46fc891dbe1826a0b3ceb624031"}, {file = "pyppmd-1.1.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:1ce5822d8bea920856232ccfb3c26b56b28b6846ea1b0eb3d5cb9592a026649e"}, {file = "pyppmd-1.1.0-cp311-cp311-win32.whl", hash = "sha256:2a9e894750f2a52b03e3bc0d7cf004d96c3475a59b1af7e797d808d7d29c9ffe"}, {file = "pyppmd-1.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:969555c72e72fe2b4dd944127521a8f2211caddb5df452bbc2506b5adfac539e"}, {file = "pyppmd-1.1.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:9d6ef8fd818884e914bc209f7961c9400a4da50d178bba25efcef89f09ec9169"}, {file = "pyppmd-1.1.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:95f28e2ecf3a9656bd7e766aaa1162b6872b575627f18715f8b046e8617c124a"}, {file = "pyppmd-1.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:37f3557ea65ee417abcdf5f49d35df00bb9f6f252639cae57aeefcd0dd596133"}, {file = "pyppmd-1.1.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6e84b25d088d7727d50218f57f92127cdb839acd6ec3de670b6680a4cf0b2d2a"}, {file = "pyppmd-1.1.0-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:99ed42891986dac8c2ecf52bddfb777900233d867aa18849dbba6f3335600466"}, {file = "pyppmd-1.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c6fe69b82634488ada75ba07efb90cd5866fa3d64a2c12932b6e8ae207a14e5f"}, {file = "pyppmd-1.1.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:60981ffde1fe6ade750b690b35318c41a1160a8505597fda2c39a74409671217"}, {file = "pyppmd-1.1.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:46e8240315476f57aac23d71e6de003e122b65feba7c68f4cc46a089a82a7cd4"}, {file = "pyppmd-1.1.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:c0308e2e76ecb4c878a18c2d7a7c61dbca89b4ef138f65d5f5ead139154dcdea"}, {file = "pyppmd-1.1.0-cp312-cp312-win32.whl", hash = "sha256:b4fa4c27dc1314d019d921f2aa19e17f99250557e7569eeb70e180558f46af74"}, {file = "pyppmd-1.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:c269d21e15f4175df27cf00296476097af76941f948734c642d7fb6e85b9b3b9"}, {file = "pyppmd-1.1.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:a04ef5fd59818b035855723af85ce008c8191d31216706ffcbeedc505efca269"}, {file = "pyppmd-1.1.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1e3ebcf5f95142268afa5cc46457d9dab2d29a3ccfd020a1129dd9d6bd021be1"}, {file = "pyppmd-1.1.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:4ad046a9525d1f52e93bc642a4cec0bf344a3ba1a15923e424e7a50f8ca003d8"}, {file = "pyppmd-1.1.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:169e5023c86ed1f7587961900f58aa78ad8a3d59de1e488a2228b5ba3de52402"}, {file = "pyppmd-1.1.0-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:baf798e76edd9da975cc536f943756a1b1755eb8ed87371f86f76d7c16e8d034"}, {file = "pyppmd-1.1.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d63be8c068879194c1e7548d0c57f54a4d305ba204cd0c7499b678f0aee893ef"}, {file = "pyppmd-1.1.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:d5fc178a3c21af78858acbac9782fca6a927267694c452e0882c55fec6e78319"}, {file = "pyppmd-1.1.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:28a1ab1ef0a31adce9b4c837b7b9acb01ce8f1f702ff3ff884f03d21c2f6b9bb"}, {file = "pyppmd-1.1.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:5fef43bfe98ada0a608adf03b2d205e071259027ab50523954c42eef7adcef67"}, {file = "pyppmd-1.1.0-cp38-cp38-win32.whl", hash = "sha256:6b980902797eab821299a1c9f42fa78eff2826a6b0b0f6bde8a621f9765ffd55"}, {file = "pyppmd-1.1.0-cp38-cp38-win_amd64.whl", hash = "sha256:80cde69013f357483abe0c3ff30c55dc5e6b4f72b068f91792ce282c51dc0bff"}, {file = "pyppmd-1.1.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:2aeea1bf585c6b8771fa43a6abd704da92f8a46a6d0020953af15d7f3c82e48c"}, {file = "pyppmd-1.1.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:7759bdb137694d4ab0cfa5ff2c75c212d90714c7da93544694f68001a0c38e12"}, {file = "pyppmd-1.1.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:db64a4fe956a2e700a737a1d019f526e6ccece217c163b28b354a43464cc495b"}, {file = "pyppmd-1.1.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3f788ae8f5a9e79cd777b7969d3401b2a2b87f47abe306c2a03baca30595e9bd"}, {file = "pyppmd-1.1.0-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:324a178935c140210fca2043c688b77e79281da8172d2379a06e094f41735851"}, {file = "pyppmd-1.1.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:363030bbcb7902fb9eeb59ffc262581ca5dd7790ba950328242fd2491c54d99b"}, {file = "pyppmd-1.1.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:31b882584f86440b0ff7906385c9f9d9853e5799197abaafdae2245f87d03f01"}, {file = "pyppmd-1.1.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:b991b4501492ec3380b605fe30bee0b61480d305e98519d81c2a658b2de01593"}, {file = "pyppmd-1.1.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:b6108044d943b826f97a9e79201242f61392d6c1fadba463b2069c4e6bc961e1"}, {file = "pyppmd-1.1.0-cp39-cp39-win32.whl", hash = "sha256:c45ce2968b7762d2cacf622b0a8f260295c6444e0883fd21a21017e3eaef16ed"}, {file = "pyppmd-1.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:f5289f32ab4ec5f96a95da51309abd1769f928b0bff62047b3bc25c878c16ccb"}, {file = "pyppmd-1.1.0-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:ad5da9f7592158e6b6b51d7cd15e536d8b23afbb4d22cba4e5744c7e0a3548b1"}, {file = "pyppmd-1.1.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bc6543e7d12ef0a1466d291d655e3d6bca59c7336dbb53b62ccdd407822fb52b"}, {file = "pyppmd-1.1.0-pp310-pypy310_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a5e4008a45910e3c8c227f6f240de67eb14454c015dc3d8060fc41e230f395d3"}, {file = "pyppmd-1.1.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9301fa39d1fb0ed09a10b4c5d7f0074113e96a1ead16ba7310bedf95f7ef660c"}, {file = "pyppmd-1.1.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:59521a3c6028da0cb5780ba16880047b00163432a6b975da2f6123adfc1b0be8"}, {file = "pyppmd-1.1.0-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:d7ec02f1778dd68547e497625d66d7858ce10ea199146eb1d80ee23ba42954be"}, {file = "pyppmd-1.1.0-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f062ca743f9b99fe88d417b4d351af9b4ff1a7cbd3d765c058bb97de976d57f1"}, {file = "pyppmd-1.1.0-pp38-pypy38_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:088e326b180a0469ac936849f5e1e5320118c22c9d9e673e9c8551153b839c84"}, {file = "pyppmd-1.1.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:897fa9ab5ff588a1000b8682835c5acf219329aa2bbfec478100e57d1204eeab"}, {file = "pyppmd-1.1.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:3af4338cc48cd59ee213af61d936419774a0f8600b9aa2013cd1917b108424f0"}, {file = "pyppmd-1.1.0-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:cce8cd2d4ceebe2dbf41db6dfebe4c2e621314b3af8a2df2cba5eb5fa277f122"}, {file = "pyppmd-1.1.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:62e57927dbcb91fb6290a41cd83743b91b9d85858efb16a0dd34fac208ee1c6b"}, {file = "pyppmd-1.1.0-pp39-pypy39_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:435317949a6f35e54cdf08e0af6916ace427351e7664ac1593980114668f0aaa"}, {file = "pyppmd-1.1.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4f66b0d0e32b8fb8707f1d2552f13edfc2917e8ed0bdf4d62e2ce190d2c70834"}, {file = "pyppmd-1.1.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:650a663a591e06fb8096c213f4070b158981c8c3bf9c166ce7e4c360873f2750"}, {file = "pyppmd-1.1.0.tar.gz", hash = "sha256:1d38ce2e4b7eb84b53bc8a52380b94f66ba6c39328b8800b30c2b5bf31693973"}, ] [package.extras] check = ["check-manifest", "flake8 (<5)", "flake8-black", "flake8-isort", "isort (>=5.0.3)", "mypy (>=0.812)", "mypy-extensions (>=0.4.3)", "pygments", "readme-renderer"] docs = ["sphinx (>=2.3)", "sphinx-rtd-theme"] fuzzer = ["atheris", "hypothesis"] test = ["coverage[toml] (>=5.2)", "hypothesis", "pytest (>=6.0)", "pytest-benchmark", "pytest-cov", "pytest-timeout"] [[package]] name = "pytest" version = "8.3.3" description = "pytest: simple powerful testing with Python" optional = false python-versions = ">=3.8" files = [ {file = "pytest-8.3.3-py3-none-any.whl", hash = "sha256:a6853c7375b2663155079443d2e45de913a911a11d669df02a50814944db57b2"}, {file = "pytest-8.3.3.tar.gz", hash = "sha256:70b98107bd648308a7952b06e6ca9a50bc660be218d53c257cc1fc94fda10181"}, ] [package.dependencies] colorama = {version = "*", markers = "sys_platform == \"win32\""} exceptiongroup = {version = ">=1.0.0rc8", markers = "python_version < \"3.11\""} iniconfig = "*" packaging = "*" pluggy = ">=1.5,<2" tomli = {version = ">=1", markers = "python_version < \"3.11\""} [package.extras] dev = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"] [[package]] name = "pytest-cov" version = "5.0.0" description = "Pytest plugin for measuring coverage." optional = false python-versions = ">=3.8" files = [ {file = "pytest-cov-5.0.0.tar.gz", hash = "sha256:5837b58e9f6ebd335b0f8060eecce69b662415b16dc503883a02f45dfeb14857"}, {file = "pytest_cov-5.0.0-py3-none-any.whl", hash = "sha256:4f0764a1219df53214206bf1feea4633c3b558a2925c8b59f144f682861ce652"}, ] [package.dependencies] coverage = {version = ">=5.2.1", extras = ["toml"]} pytest = ">=4.6" [package.extras] testing = ["fields", "hunter", "process-tests", "pytest-xdist", "virtualenv"] [[package]] name = "pytest-flask" version = "1.3.0" description = "A set of py.test fixtures to test Flask applications." optional = false python-versions = ">=3.7" files = [ {file = "pytest-flask-1.3.0.tar.gz", hash = "sha256:58be1c97b21ba3c4d47e0a7691eb41007748506c36bf51004f78df10691fa95e"}, {file = "pytest_flask-1.3.0-py3-none-any.whl", hash = "sha256:c0e36e6b0fddc3b91c4362661db83fa694d1feb91fa505475be6732b5bc8c253"}, ] [package.dependencies] Flask = "*" pytest = ">=5.2" Werkzeug = "*" [package.extras] docs = ["Sphinx", "sphinx-rtd-theme"] [[package]] name = "python-dateutil" version = "2.9.0.post0" description = "Extensions to the standard Python datetime module" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" files = [ {file = "python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3"}, {file = "python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427"}, ] [package.dependencies] six = ">=1.5" [[package]] name = "python-mpd2" version = "3.1.1" description = "A Python MPD client library" optional = false python-versions = ">=3.6" files = [ {file = "python-mpd2-3.1.1.tar.gz", hash = "sha256:4baec3584cc43ed9948d5559079fafc2679b06b2ade273e909b3582654b2b3f5"}, {file = "python_mpd2-3.1.1-py2.py3-none-any.whl", hash = "sha256:86bf1100a0b135959d74a9a7a58cf0515bf30bb54eb25ae6fb8e175e50300fc3"}, ] [package.extras] twisted = ["Twisted"] [[package]] name = "python3-discogs-client" version = "2.7.1" description = "Python API client for Discogs" optional = false python-versions = "*" files = [ {file = "python3_discogs_client-2.7.1-py3-none-any.whl", hash = "sha256:5fb5f3d2f288a8ce2c8c152444258bacedb35b7d61bc466bddae332b6c737444"}, {file = "python3_discogs_client-2.7.1.tar.gz", hash = "sha256:f2453582f5d044ea5847d27cfe56473179e51c9a836913b46db803c20ae598f9"}, ] [package.dependencies] oauthlib = "*" python-dateutil = "*" requests = "*" [package.extras] docs = ["myst-parser", "pydata-sphinx-theme", "sphinx"] [[package]] name = "pytz" version = "2024.2" description = "World timezone definitions, modern and historical" optional = true python-versions = "*" files = [ {file = "pytz-2024.2-py2.py3-none-any.whl", hash = "sha256:31c7c1817eb7fae7ca4b8c7ee50c72f93aa2dd863de768e1ef4245d426aa0725"}, {file = "pytz-2024.2.tar.gz", hash = "sha256:2aa355083c50a0f93fa581709deac0c9ad65cca8a9e9beac660adcbd493c798a"}, ] [[package]] name = "pyxdg" version = "0.28" description = "PyXDG contains implementations of freedesktop.org standards in python." optional = false python-versions = "*" files = [ {file = "pyxdg-0.28-py2.py3-none-any.whl", hash = "sha256:bdaf595999a0178ecea4052b7f4195569c1ff4d344567bccdc12dfdf02d545ab"}, {file = "pyxdg-0.28.tar.gz", hash = "sha256:3267bb3074e934df202af2ee0868575484108581e6f3cb006af1da35395e88b4"}, ] [[package]] name = "pyyaml" version = "6.0.2" description = "YAML parser and emitter for Python" optional = false python-versions = ">=3.8" files = [ {file = "PyYAML-6.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0a9a2848a5b7feac301353437eb7d5957887edbf81d56e903999a75a3d743086"}, {file = "PyYAML-6.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:29717114e51c84ddfba879543fb232a6ed60086602313ca38cce623c1d62cfbf"}, {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8824b5a04a04a047e72eea5cec3bc266db09e35de6bdfe34c9436ac5ee27d237"}, {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7c36280e6fb8385e520936c3cb3b8042851904eba0e58d277dca80a5cfed590b"}, {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ec031d5d2feb36d1d1a24380e4db6d43695f3748343d99434e6f5f9156aaa2ed"}, {file = "PyYAML-6.0.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:936d68689298c36b53b29f23c6dbb74de12b4ac12ca6cfe0e047bedceea56180"}, {file = "PyYAML-6.0.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:23502f431948090f597378482b4812b0caae32c22213aecf3b55325e049a6c68"}, {file = "PyYAML-6.0.2-cp310-cp310-win32.whl", hash = "sha256:2e99c6826ffa974fe6e27cdb5ed0021786b03fc98e5ee3c5bfe1fd5015f42b99"}, {file = "PyYAML-6.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:a4d3091415f010369ae4ed1fc6b79def9416358877534caf6a0fdd2146c87a3e"}, {file = "PyYAML-6.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cc1c1159b3d456576af7a3e4d1ba7e6924cb39de8f67111c735f6fc832082774"}, {file = "PyYAML-6.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1e2120ef853f59c7419231f3bf4e7021f1b936f6ebd222406c3b60212205d2ee"}, {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d225db5a45f21e78dd9358e58a98702a0302f2659a3c6cd320564b75b86f47c"}, {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5ac9328ec4831237bec75defaf839f7d4564be1e6b25ac710bd1a96321cc8317"}, {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ad2a3decf9aaba3d29c8f537ac4b243e36bef957511b4766cb0057d32b0be85"}, {file = "PyYAML-6.0.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ff3824dc5261f50c9b0dfb3be22b4567a6f938ccce4587b38952d85fd9e9afe4"}, {file = "PyYAML-6.0.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:797b4f722ffa07cc8d62053e4cff1486fa6dc094105d13fea7b1de7d8bf71c9e"}, {file = "PyYAML-6.0.2-cp311-cp311-win32.whl", hash = "sha256:11d8f3dd2b9c1207dcaf2ee0bbbfd5991f571186ec9cc78427ba5bd32afae4b5"}, {file = "PyYAML-6.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:e10ce637b18caea04431ce14fabcf5c64a1c61ec9c56b071a4b7ca131ca52d44"}, {file = "PyYAML-6.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:c70c95198c015b85feafc136515252a261a84561b7b1d51e3384e0655ddf25ab"}, {file = "PyYAML-6.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ce826d6ef20b1bc864f0a68340c8b3287705cae2f8b4b1d932177dcc76721725"}, {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f71ea527786de97d1a0cc0eacd1defc0985dcf6b3f17bb77dcfc8c34bec4dc5"}, {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9b22676e8097e9e22e36d6b7bda33190d0d400f345f23d4065d48f4ca7ae0425"}, {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80bab7bfc629882493af4aa31a4cfa43a4c57c83813253626916b8c7ada83476"}, {file = "PyYAML-6.0.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:0833f8694549e586547b576dcfaba4a6b55b9e96098b36cdc7ebefe667dfed48"}, {file = "PyYAML-6.0.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8b9c7197f7cb2738065c481a0461e50ad02f18c78cd75775628afb4d7137fb3b"}, {file = "PyYAML-6.0.2-cp312-cp312-win32.whl", hash = "sha256:ef6107725bd54b262d6dedcc2af448a266975032bc85ef0172c5f059da6325b4"}, {file = "PyYAML-6.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:7e7401d0de89a9a855c839bc697c079a4af81cf878373abd7dc625847d25cbd8"}, {file = "PyYAML-6.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba"}, {file = "PyYAML-6.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1"}, {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133"}, {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484"}, {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5"}, {file = "PyYAML-6.0.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc"}, {file = "PyYAML-6.0.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652"}, {file = "PyYAML-6.0.2-cp313-cp313-win32.whl", hash = "sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183"}, {file = "PyYAML-6.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563"}, {file = "PyYAML-6.0.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:24471b829b3bf607e04e88d79542a9d48bb037c2267d7927a874e6c205ca7e9a"}, {file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7fded462629cfa4b685c5416b949ebad6cec74af5e2d42905d41e257e0869f5"}, {file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d84a1718ee396f54f3a086ea0a66d8e552b2ab2017ef8b420e92edbc841c352d"}, {file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9056c1ecd25795207ad294bcf39f2db3d845767be0ea6e6a34d856f006006083"}, {file = "PyYAML-6.0.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:82d09873e40955485746739bcb8b4586983670466c23382c19cffecbf1fd8706"}, {file = "PyYAML-6.0.2-cp38-cp38-win32.whl", hash = "sha256:43fa96a3ca0d6b1812e01ced1044a003533c47f6ee8aca31724f78e93ccc089a"}, {file = "PyYAML-6.0.2-cp38-cp38-win_amd64.whl", hash = "sha256:01179a4a8559ab5de078078f37e5c1a30d76bb88519906844fd7bdea1b7729ff"}, {file = "PyYAML-6.0.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:688ba32a1cffef67fd2e9398a2efebaea461578b0923624778664cc1c914db5d"}, {file = "PyYAML-6.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a8786accb172bd8afb8be14490a16625cbc387036876ab6ba70912730faf8e1f"}, {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8e03406cac8513435335dbab54c0d385e4a49e4945d2909a581c83647ca0290"}, {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f753120cb8181e736c57ef7636e83f31b9c0d1722c516f7e86cf15b7aa57ff12"}, {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3b1fdb9dc17f5a7677423d508ab4f243a726dea51fa5e70992e59a7411c89d19"}, {file = "PyYAML-6.0.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0b69e4ce7a131fe56b7e4d770c67429700908fc0752af059838b1cfb41960e4e"}, {file = "PyYAML-6.0.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a9f8c2e67970f13b16084e04f134610fd1d374bf477b17ec1599185cf611d725"}, {file = "PyYAML-6.0.2-cp39-cp39-win32.whl", hash = "sha256:6395c297d42274772abc367baaa79683958044e5d3835486c16da75d2a694631"}, {file = "PyYAML-6.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:39693e1f8320ae4f43943590b49779ffb98acb81f788220ea932a6b6c51004d8"}, {file = "pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e"}, ] [[package]] name = "pyzstd" version = "0.16.2" description = "Python bindings to Zstandard (zstd) compression library." optional = false python-versions = ">=3.5" files = [ {file = "pyzstd-0.16.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:637376c8f8cbd0afe1cab613f8c75fd502bd1016bf79d10760a2d5a00905fe62"}, {file = "pyzstd-0.16.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:3e7a7118cbcfa90ca2ddbf9890c7cb582052a9a8cf2b7e2c1bbaf544bee0f16a"}, {file = "pyzstd-0.16.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a74cb1ba05876179525144511eed3bd5a509b0ab2b10632c1215a85db0834dfd"}, {file = "pyzstd-0.16.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7c084dde218ffbf112e507e72cbf626b8f58ce9eb23eec129809e31037984662"}, {file = "pyzstd-0.16.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d4646459ebd3d7a59ddbe9312f020bcf7cdd1f059a2ea07051258f7af87a0b31"}, {file = "pyzstd-0.16.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:14bfc2833cc16d7657fc93259edeeaa793286e5031b86ca5dc861ba49b435fce"}, {file = "pyzstd-0.16.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f27d488f19e5bf27d1e8aa1ae72c6c0a910f1e1ffbdf3c763d02ab781295dd27"}, {file = "pyzstd-0.16.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:91e134ca968ff7dcfa8b7d433318f01d309b74ee87e0d2bcadc117c08e1c80db"}, {file = "pyzstd-0.16.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:6b5f64cd3963c58b8f886eb6139bb8d164b42a74f8a1bb95d49b4804f4592d61"}, {file = "pyzstd-0.16.2-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:0b4a8266871b9e0407f9fd8e8d077c3558cf124d174e6357b523d14f76971009"}, {file = "pyzstd-0.16.2-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:1bb19f7acac30727354c25125922aa59f44d82e0e6a751df17d0d93ff6a73853"}, {file = "pyzstd-0.16.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:3008325b7368e794d66d4d98f2ee1d867ef5afd09fd388646ae02b25343c420d"}, {file = "pyzstd-0.16.2-cp310-cp310-win32.whl", hash = "sha256:66f2d5c0bbf5bf32c577aa006197b3525b80b59804450e2c32fbcc2d16e850fd"}, {file = "pyzstd-0.16.2-cp310-cp310-win_amd64.whl", hash = "sha256:5fe5f5459ebe1161095baa7a86d04ab625b35148f6c425df0347ed6c90a2fd58"}, {file = "pyzstd-0.16.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1c1bdbe7f01c7f37d5cd07be70e32a84010d7dfd6677920c0de04cf7d245b60d"}, {file = "pyzstd-0.16.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1882a3ceaaf9adc12212d587d150ec5e58cfa9a765463d803d739abbd3ac0f7a"}, {file = "pyzstd-0.16.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ea46a8b9d60f6a6eba29facba54c0f0d70328586f7ef0da6f57edf7e43db0303"}, {file = "pyzstd-0.16.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d7865bc06589cdcecdede0deefe3da07809d5b7ad9044c224d7b2a0867256957"}, {file = "pyzstd-0.16.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:52f938a65b409c02eb825e8c77fc5ea54508b8fc44b5ce226db03011691ae8cc"}, {file = "pyzstd-0.16.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e97620d3f53a0282947304189deef7ca7f7d0d6dfe15033469dc1c33e779d5e5"}, {file = "pyzstd-0.16.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c7c40e9983d017108670dc8df68ceef14c7c1cf2d19239213274783041d0e64c"}, {file = "pyzstd-0.16.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:7cd4b3b2c6161066e4bde6af1cf78ed3acf5d731884dd13fdf31f1db10830080"}, {file = "pyzstd-0.16.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:454f31fd84175bb203c8c424f2255a343fa9bd103461a38d1bf50487c3b89508"}, {file = "pyzstd-0.16.2-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:5ef754a93743f08fb0386ce3596780bfba829311b49c8f4107af1a4bcc16935d"}, {file = "pyzstd-0.16.2-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:be81081db9166e10846934f0e3576a263cbe18d81eca06e6a5c23533f8ce0dc6"}, {file = "pyzstd-0.16.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:738bcb2fa1e5f1868986f5030955e64de53157fa1141d01f3a4daf07a1aaf644"}, {file = "pyzstd-0.16.2-cp311-cp311-win32.whl", hash = "sha256:0ea214c9b97046867d1657d55979021028d583704b30c481a9c165191b08d707"}, {file = "pyzstd-0.16.2-cp311-cp311-win_amd64.whl", hash = "sha256:c17c0fc02f0e75b0c7cd21f8eaf4c6ce4112333b447d93da1773a5f705b2c178"}, {file = "pyzstd-0.16.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d4081fd841a9efe9ded7290ee7502dbf042c4158b90edfadea3b8a072c8ec4e1"}, {file = "pyzstd-0.16.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fd3fa45d2aeb65367dd702806b2e779d13f1a3fa2d13d5ec777cfd09de6822de"}, {file = "pyzstd-0.16.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8b5f0d2c07994a5180d8259d51df6227a57098774bb0618423d7eb4a7303467"}, {file = "pyzstd-0.16.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:60c9d25b15c7ae06ed5d516d096a0d8254f9bed4368b370a09cccf191eaab5cb"}, {file = "pyzstd-0.16.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:29acf31ce37254f6cad08deb24b9d9ba954f426fa08f8fae4ab4fdc51a03f4ae"}, {file = "pyzstd-0.16.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1ec77612a17697a9f7cf6634ffcee616eba9b997712fdd896e77fd19ab3a0618"}, {file = "pyzstd-0.16.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:313ea4974be93be12c9a640ab40f0fc50a023178aae004a8901507b74f190173"}, {file = "pyzstd-0.16.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e91acdefc8c2c6c3b8d5b1b5fe837dce4e591ecb7c0a2a50186f552e57d11203"}, {file = "pyzstd-0.16.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:929bd91a403539e72b5b5cb97f725ac4acafe692ccf52f075e20cd9bf6e5493d"}, {file = "pyzstd-0.16.2-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:740837a379aa32d110911ebcbbc524f9a9b145355737527543a884bd8777ca4f"}, {file = "pyzstd-0.16.2-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:adfc0e80dd157e6d1e0b0112c8ecc4b58a7a23760bd9623d74122ef637cfbdb6"}, {file = "pyzstd-0.16.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:79b183beae1c080ad3dca39019e49b7785391947f9aab68893ad85d27828c6e7"}, {file = "pyzstd-0.16.2-cp312-cp312-win32.whl", hash = "sha256:b8d00631a3c466bc313847fab2a01f6b73b3165de0886fb03210e08567ae3a89"}, {file = "pyzstd-0.16.2-cp312-cp312-win_amd64.whl", hash = "sha256:c0d43764e9a60607f35d8cb3e60df772a678935ab0e02e2804d4147377f4942c"}, {file = "pyzstd-0.16.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:3ae9ae7ad730562810912d7ecaf1fff5eaf4c726f4b4dfe04784ed5f06d7b91f"}, {file = "pyzstd-0.16.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2ce8d3c213f76a564420f3d0137066ac007ce9fb4e156b989835caef12b367a7"}, {file = "pyzstd-0.16.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c2c14dac23c865e2d78cebd9087e148674b7154f633afd4709b4cd1520b99a61"}, {file = "pyzstd-0.16.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4527969d66a943e36ef374eda847e918077de032d58b5df84d98ffd717b6fa77"}, {file = "pyzstd-0.16.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cd8256149b88e657e99f31e6d4b114c8ff2935951f1d8bb8e1fe501b224999c0"}, {file = "pyzstd-0.16.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5bd1f1822d65c9054bf36d35307bf8ed4aa2d2d6827431761a813628ff671b1d"}, {file = "pyzstd-0.16.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6f6733f4d373ec9ad2c1976cf06f973a3324c1f9abe236d114d6bb91165a397d"}, {file = "pyzstd-0.16.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:7bec165ab6524663f00b69bfefd13a46a69fed3015754abaf81b103ec73d92c6"}, {file = "pyzstd-0.16.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:e4460fa6949aac6528a1ad0de8871079600b12b3ef4db49316306786a3598321"}, {file = "pyzstd-0.16.2-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:75df79ea0315c97d88337953a17daa44023dbf6389f8151903d371513f503e3c"}, {file = "pyzstd-0.16.2-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:93e1d45f4a196afb6f18682c79bdd5399277ead105b67f30b35c04c207966071"}, {file = "pyzstd-0.16.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:075e18b871f38a503b5d23e40a661adfc750bd4bd0bb8b208c1e290f3ceb8fa2"}, {file = "pyzstd-0.16.2-cp313-cp313-win32.whl", hash = "sha256:9e4295eb299f8d87e3487852bca033d30332033272a801ca8130e934475e07a9"}, {file = "pyzstd-0.16.2-cp313-cp313-win_amd64.whl", hash = "sha256:18deedc70f858f4cf574e59f305d2a0678e54db2751a33dba9f481f91bc71c28"}, {file = "pyzstd-0.16.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:a9892b707ef52f599098b1e9528df0e7849c5ec01d3e8035fb0e67de4b464839"}, {file = "pyzstd-0.16.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4fbd647864341f3c174c4a6d7f20e6ea6b4be9d840fb900dc0faf0849561badc"}, {file = "pyzstd-0.16.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:20ac2c15656cc6194c4fed1cb0e8159f9394d4ea1d58be755448743d2ec6c9c4"}, {file = "pyzstd-0.16.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b239fb9a20c1be3374b9a2bd183ba624fd22ad7a3f67738c0d80cda68b4ae1d3"}, {file = "pyzstd-0.16.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cc52400412cdae2635e0978b8d6bcc0028cc638fdab2fd301f6d157675d26896"}, {file = "pyzstd-0.16.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3b766a6aeb8dbb6c46e622e7a1aebfa9ab03838528273796941005a5ce7257b1"}, {file = "pyzstd-0.16.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:abd4b8676052f9d59579242bf3cfe5fd02532b6a9a93ab7737c118ae3b8509dc"}, {file = "pyzstd-0.16.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:1c6c0a677aac7c0e3d2d2605d4d68ffa9893fdeeb2e071040eb7c8750969d463"}, {file = "pyzstd-0.16.2-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:15f9c2d612e7e2023d68d321d1b479846751f792af89141931d44e82ae391394"}, {file = "pyzstd-0.16.2-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:11740bff847aad23beef4085a1bb767d101895881fe891f0a911aa27d43c372c"}, {file = "pyzstd-0.16.2-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:b9067483ebe860e4130a03ee665b3d7be4ec1608b208e645d5e7eb3492379464"}, {file = "pyzstd-0.16.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:988f0ba19b14c2fe0afefc444ac1edfb2f497b7d7c3212b2f587504cc2ec804e"}, {file = "pyzstd-0.16.2-cp39-cp39-win32.whl", hash = "sha256:8855acb1c3e3829030b9e9e9973b19e2d70f33efb14ad5c474b4d086864c959c"}, {file = "pyzstd-0.16.2-cp39-cp39-win_amd64.whl", hash = "sha256:018e88378df5e76f5e1d8cf4416576603b6bc4a103cbc66bb593eaac54c758de"}, {file = "pyzstd-0.16.2-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:4b631117b97a42ff6dfd0ffc885a92fff462d7c34766b28383c57b996f863338"}, {file = "pyzstd-0.16.2-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:56493a3fbe1b651a02102dd0902b0aa2377a732ff3544fb6fb3f114ca18db52f"}, {file = "pyzstd-0.16.2-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f1eae9bdba4a1e5d3181331f403114ff5b8ce0f4b569f48eba2b9beb2deef1e4"}, {file = "pyzstd-0.16.2-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e1be6972391c8aeecc7e61feb96ffc8e77a401bcba6ed994e7171330c45a1948"}, {file = "pyzstd-0.16.2-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:761439d687e3a5687c2ff5c6a1190e1601362a4a3e8c6c82ff89719d51d73e19"}, {file = "pyzstd-0.16.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:f5fbdb8cf31b60b2dc586fecb9b73e2f172c21a0b320ed275f7b8d8a866d9003"}, {file = "pyzstd-0.16.2-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:183f26e34f9becf0f2db38be9c0bfb136753d228bcb47c06c69175901bea7776"}, {file = "pyzstd-0.16.2-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:88318b64b5205a67748148d6d244097fa6cf61fcea02ad3435511b9e7155ae16"}, {file = "pyzstd-0.16.2-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:73142aa2571b6480136a1865ebda8257e09eabbc8bcd54b222202f6fa4febe1e"}, {file = "pyzstd-0.16.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1d3f8877c29a97f1b1bba16f3d3ab01ad10ad3da7bad317aecf36aaf8848b37c"}, {file = "pyzstd-0.16.2-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d1f25754562473ac7de856b8331ebd5964f5d85601045627a5f0bb0e4e899990"}, {file = "pyzstd-0.16.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:6ce17e84310080c55c02827ad9bb17893c00a845c8386a328b346f814aabd2c1"}, {file = "pyzstd-0.16.2.tar.gz", hash = "sha256:179c1a2ea1565abf09c5f2fd72f9ce7c54b2764cf7369e05c0bfd8f1f67f63d2"}, ] [[package]] name = "rarfile" version = "4.2" description = "RAR archive reader for Python" optional = false python-versions = ">=3.6" files = [ {file = "rarfile-4.2-py3-none-any.whl", hash = "sha256:8757e1e3757e32962e229cab2432efc1f15f210823cc96ccba0f6a39d17370c9"}, {file = "rarfile-4.2.tar.gz", hash = "sha256:8e1c8e72d0845ad2b32a47ab11a719bc2e41165ec101fd4d3fe9e92aa3f469ef"}, ] [[package]] name = "reflink" version = "0.2.2" description = "Python reflink wraps around platform specific reflink implementations" optional = true python-versions = "*" files = [ {file = "reflink-0.2.2-cp36-cp36m-win32.whl", hash = "sha256:8435c7153af4d6e66dc8acb48a9372c8ec6f978a09cdf7b57cd6656d969e343a"}, {file = "reflink-0.2.2-cp36-cp36m-win_amd64.whl", hash = "sha256:be4787c6208faf7fc892390909cf01e34e650ea67c37bf345addefd597ed90e1"}, {file = "reflink-0.2.2.tar.gz", hash = "sha256:882375ee7319275ae5f6a6a1287406365dac1e9643b91ad10e5187d3f84253bd"}, ] [package.dependencies] cffi = "*" [[package]] name = "requests" version = "2.32.3" description = "Python HTTP for Humans." optional = false python-versions = ">=3.8" files = [ {file = "requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6"}, {file = "requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760"}, ] [package.dependencies] certifi = ">=2017.4.17" charset-normalizer = ">=2,<4" idna = ">=2.5,<4" urllib3 = ">=1.21.1,<3" [package.extras] socks = ["PySocks (>=1.5.6,!=1.5.7)"] use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] [[package]] name = "requests-oauthlib" version = "2.0.0" description = "OAuthlib authentication support for Requests." optional = false python-versions = ">=3.4" files = [ {file = "requests-oauthlib-2.0.0.tar.gz", hash = "sha256:b3dffaebd884d8cd778494369603a9e7b58d29111bf6b41bdc2dcd87203af4e9"}, {file = "requests_oauthlib-2.0.0-py2.py3-none-any.whl", hash = "sha256:7dd8a5c40426b779b0868c404bdef9768deccf22749cde15852df527e6269b36"}, ] [package.dependencies] oauthlib = ">=3.0.0" requests = ">=2.0.0" [package.extras] rsa = ["oauthlib[signedtoken] (>=3.0.0)"] [[package]] name = "resampy" version = "0.4.3" description = "Efficient signal resampling" optional = true python-versions = "*" files = [ {file = "resampy-0.4.3-py3-none-any.whl", hash = "sha256:ad2ed64516b140a122d96704e32bc0f92b23f45419e8b8f478e5a05f83edcebd"}, {file = "resampy-0.4.3.tar.gz", hash = "sha256:a0d1c28398f0e55994b739650afef4e3974115edbe96cd4bb81968425e916e47"}, ] [package.dependencies] importlib-resources = {version = "*", markers = "python_version < \"3.9\""} numba = ">=0.53" numpy = ">=1.17" [package.extras] design = ["optuna (>=2.10.0)"] docs = ["numpydoc", "sphinx (!=1.3.1)"] tests = ["pytest (<8)", "pytest-cov", "scipy (>=1.1)"] [[package]] name = "responses" version = "0.25.3" description = "A utility library for mocking out the `requests` Python library." optional = false python-versions = ">=3.8" files = [ {file = "responses-0.25.3-py3-none-any.whl", hash = "sha256:521efcbc82081ab8daa588e08f7e8a64ce79b91c39f6e62199b19159bea7dbcb"}, {file = "responses-0.25.3.tar.gz", hash = "sha256:617b9247abd9ae28313d57a75880422d55ec63c29d33d629697590a034358dba"}, ] [package.dependencies] pyyaml = "*" requests = ">=2.30.0,<3.0" urllib3 = ">=1.25.10,<3.0" [package.extras] tests = ["coverage (>=6.0.0)", "flake8", "mypy", "pytest (>=7.0.0)", "pytest-asyncio", "pytest-cov", "pytest-httpserver", "tomli", "tomli-w", "types-PyYAML", "types-requests"] [[package]] name = "ruff" version = "0.8.0" description = "An extremely fast Python linter and code formatter, written in Rust." optional = false python-versions = ">=3.7" files = [ {file = "ruff-0.8.0-py3-none-linux_armv6l.whl", hash = "sha256:fcb1bf2cc6706adae9d79c8d86478677e3bbd4ced796ccad106fd4776d395fea"}, {file = "ruff-0.8.0-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:295bb4c02d58ff2ef4378a1870c20af30723013f441c9d1637a008baaf928c8b"}, {file = "ruff-0.8.0-py3-none-macosx_11_0_arm64.whl", hash = "sha256:7b1f1c76b47c18fa92ee78b60d2d20d7e866c55ee603e7d19c1e991fad933a9a"}, {file = "ruff-0.8.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eb0d4f250a7711b67ad513fde67e8870109e5ce590a801c3722580fe98c33a99"}, {file = "ruff-0.8.0-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:0e55cce9aa93c5d0d4e3937e47b169035c7e91c8655b0974e61bb79cf398d49c"}, {file = "ruff-0.8.0-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3f4cd64916d8e732ce6b87f3f5296a8942d285bbbc161acee7fe561134af64f9"}, {file = "ruff-0.8.0-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:c5c1466be2a2ebdf7c5450dd5d980cc87c8ba6976fb82582fea18823da6fa362"}, {file = "ruff-0.8.0-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2dabfd05b96b7b8f2da00d53c514eea842bff83e41e1cceb08ae1966254a51df"}, {file = "ruff-0.8.0-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:facebdfe5a5af6b1588a1d26d170635ead6892d0e314477e80256ef4a8470cf3"}, {file = "ruff-0.8.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:87a8e86bae0dbd749c815211ca11e3a7bd559b9710746c559ed63106d382bd9c"}, {file = "ruff-0.8.0-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:85e654f0ded7befe2d61eeaf3d3b1e4ef3894469cd664ffa85006c7720f1e4a2"}, {file = "ruff-0.8.0-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:83a55679c4cb449fa527b8497cadf54f076603cc36779b2170b24f704171ce70"}, {file = "ruff-0.8.0-py3-none-musllinux_1_2_i686.whl", hash = "sha256:812e2052121634cf13cd6fddf0c1871d0ead1aad40a1a258753c04c18bb71bbd"}, {file = "ruff-0.8.0-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:780d5d8523c04202184405e60c98d7595bdb498c3c6abba3b6d4cdf2ca2af426"}, {file = "ruff-0.8.0-py3-none-win32.whl", hash = "sha256:5fdb6efecc3eb60bba5819679466471fd7d13c53487df7248d6e27146e985468"}, {file = "ruff-0.8.0-py3-none-win_amd64.whl", hash = "sha256:582891c57b96228d146725975fbb942e1f30a0c4ba19722e692ca3eb25cc9b4f"}, {file = "ruff-0.8.0-py3-none-win_arm64.whl", hash = "sha256:ba93e6294e9a737cd726b74b09a6972e36bb511f9a102f1d9a7e1ce94dd206a6"}, {file = "ruff-0.8.0.tar.gz", hash = "sha256:a7ccfe6331bf8c8dad715753e157457faf7351c2b69f62f32c165c2dbcbacd44"}, ] [[package]] name = "scikit-learn" version = "1.3.2" description = "A set of python modules for machine learning and data mining" optional = true python-versions = ">=3.8" files = [ {file = "scikit-learn-1.3.2.tar.gz", hash = "sha256:a2f54c76accc15a34bfb9066e6c7a56c1e7235dda5762b990792330b52ccfb05"}, {file = "scikit_learn-1.3.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e326c0eb5cf4d6ba40f93776a20e9a7a69524c4db0757e7ce24ba222471ee8a1"}, {file = "scikit_learn-1.3.2-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:535805c2a01ccb40ca4ab7d081d771aea67e535153e35a1fd99418fcedd1648a"}, {file = "scikit_learn-1.3.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1215e5e58e9880b554b01187b8c9390bf4dc4692eedeaf542d3273f4785e342c"}, {file = "scikit_learn-1.3.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0ee107923a623b9f517754ea2f69ea3b62fc898a3641766cb7deb2f2ce450161"}, {file = "scikit_learn-1.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:35a22e8015048c628ad099da9df5ab3004cdbf81edc75b396fd0cff8699ac58c"}, {file = "scikit_learn-1.3.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6fb6bc98f234fda43163ddbe36df8bcde1d13ee176c6dc9b92bb7d3fc842eb66"}, {file = "scikit_learn-1.3.2-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:18424efee518a1cde7b0b53a422cde2f6625197de6af36da0b57ec502f126157"}, {file = "scikit_learn-1.3.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3271552a5eb16f208a6f7f617b8cc6d1f137b52c8a1ef8edf547db0259b2c9fb"}, {file = "scikit_learn-1.3.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fc4144a5004a676d5022b798d9e573b05139e77f271253a4703eed295bde0433"}, {file = "scikit_learn-1.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:67f37d708f042a9b8d59551cf94d30431e01374e00dc2645fa186059c6c5d78b"}, {file = "scikit_learn-1.3.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:8db94cd8a2e038b37a80a04df8783e09caac77cbe052146432e67800e430c028"}, {file = "scikit_learn-1.3.2-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:61a6efd384258789aa89415a410dcdb39a50e19d3d8410bd29be365bcdd512d5"}, {file = "scikit_learn-1.3.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cb06f8dce3f5ddc5dee1715a9b9f19f20d295bed8e3cd4fa51e1d050347de525"}, {file = "scikit_learn-1.3.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5b2de18d86f630d68fe1f87af690d451388bb186480afc719e5f770590c2ef6c"}, {file = "scikit_learn-1.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:0402638c9a7c219ee52c94cbebc8fcb5eb9fe9c773717965c1f4185588ad3107"}, {file = "scikit_learn-1.3.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:a19f90f95ba93c1a7f7924906d0576a84da7f3b2282ac3bfb7a08a32801add93"}, {file = "scikit_learn-1.3.2-cp38-cp38-macosx_12_0_arm64.whl", hash = "sha256:b8692e395a03a60cd927125eef3a8e3424d86dde9b2370d544f0ea35f78a8073"}, {file = "scikit_learn-1.3.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:15e1e94cc23d04d39da797ee34236ce2375ddea158b10bee3c343647d615581d"}, {file = "scikit_learn-1.3.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:785a2213086b7b1abf037aeadbbd6d67159feb3e30263434139c98425e3dcfcf"}, {file = "scikit_learn-1.3.2-cp38-cp38-win_amd64.whl", hash = "sha256:64381066f8aa63c2710e6b56edc9f0894cc7bf59bd71b8ce5613a4559b6145e0"}, {file = "scikit_learn-1.3.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6c43290337f7a4b969d207e620658372ba3c1ffb611f8bc2b6f031dc5c6d1d03"}, {file = "scikit_learn-1.3.2-cp39-cp39-macosx_12_0_arm64.whl", hash = "sha256:dc9002fc200bed597d5d34e90c752b74df516d592db162f756cc52836b38fe0e"}, {file = "scikit_learn-1.3.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1d08ada33e955c54355d909b9c06a4789a729977f165b8bae6f225ff0a60ec4a"}, {file = "scikit_learn-1.3.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:763f0ae4b79b0ff9cca0bf3716bcc9915bdacff3cebea15ec79652d1cc4fa5c9"}, {file = "scikit_learn-1.3.2-cp39-cp39-win_amd64.whl", hash = "sha256:ed932ea780517b00dae7431e031faae6b49b20eb6950918eb83bd043237950e0"}, ] [package.dependencies] joblib = ">=1.1.1" numpy = ">=1.17.3,<2.0" scipy = ">=1.5.0" threadpoolctl = ">=2.0.0" [package.extras] benchmark = ["matplotlib (>=3.1.3)", "memory-profiler (>=0.57.0)", "pandas (>=1.0.5)"] docs = ["Pillow (>=7.1.2)", "matplotlib (>=3.1.3)", "memory-profiler (>=0.57.0)", "numpydoc (>=1.2.0)", "pandas (>=1.0.5)", "plotly (>=5.14.0)", "pooch (>=1.6.0)", "scikit-image (>=0.16.2)", "seaborn (>=0.9.0)", "sphinx (>=6.0.0)", "sphinx-copybutton (>=0.5.2)", "sphinx-gallery (>=0.10.1)", "sphinx-prompt (>=1.3.0)", "sphinxext-opengraph (>=0.4.2)"] examples = ["matplotlib (>=3.1.3)", "pandas (>=1.0.5)", "plotly (>=5.14.0)", "pooch (>=1.6.0)", "scikit-image (>=0.16.2)", "seaborn (>=0.9.0)"] tests = ["black (>=23.3.0)", "matplotlib (>=3.1.3)", "mypy (>=1.3)", "numpydoc (>=1.2.0)", "pandas (>=1.0.5)", "pooch (>=1.6.0)", "pyamg (>=4.0.0)", "pytest (>=7.1.2)", "pytest-cov (>=2.9.0)", "ruff (>=0.0.272)", "scikit-image (>=0.16.2)"] [[package]] name = "scipy" version = "1.9.3" description = "Fundamental algorithms for scientific computing in Python" optional = true python-versions = ">=3.8" files = [ {file = "scipy-1.9.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:1884b66a54887e21addf9c16fb588720a8309a57b2e258ae1c7986d4444d3bc0"}, {file = "scipy-1.9.3-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:83b89e9586c62e787f5012e8475fbb12185bafb996a03257e9675cd73d3736dd"}, {file = "scipy-1.9.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1a72d885fa44247f92743fc20732ae55564ff2a519e8302fb7e18717c5355a8b"}, {file = "scipy-1.9.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d01e1dd7b15bd2449c8bfc6b7cc67d630700ed655654f0dfcf121600bad205c9"}, {file = "scipy-1.9.3-cp310-cp310-win_amd64.whl", hash = "sha256:68239b6aa6f9c593da8be1509a05cb7f9efe98b80f43a5861cd24c7557e98523"}, {file = "scipy-1.9.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b41bc822679ad1c9a5f023bc93f6d0543129ca0f37c1ce294dd9d386f0a21096"}, {file = "scipy-1.9.3-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:90453d2b93ea82a9f434e4e1cba043e779ff67b92f7a0e85d05d286a3625df3c"}, {file = "scipy-1.9.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:83c06e62a390a9167da60bedd4575a14c1f58ca9dfde59830fc42e5197283dab"}, {file = "scipy-1.9.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:abaf921531b5aeaafced90157db505e10345e45038c39e5d9b6c7922d68085cb"}, {file = "scipy-1.9.3-cp311-cp311-win_amd64.whl", hash = "sha256:06d2e1b4c491dc7d8eacea139a1b0b295f74e1a1a0f704c375028f8320d16e31"}, {file = "scipy-1.9.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:5a04cd7d0d3eff6ea4719371cbc44df31411862b9646db617c99718ff68d4840"}, {file = "scipy-1.9.3-cp38-cp38-macosx_12_0_arm64.whl", hash = "sha256:545c83ffb518094d8c9d83cce216c0c32f8c04aaf28b92cc8283eda0685162d5"}, {file = "scipy-1.9.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0d54222d7a3ba6022fdf5773931b5d7c56efe41ede7f7128c7b1637700409108"}, {file = "scipy-1.9.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cff3a5295234037e39500d35316a4c5794739433528310e117b8a9a0c76d20fc"}, {file = "scipy-1.9.3-cp38-cp38-win_amd64.whl", hash = "sha256:2318bef588acc7a574f5bfdff9c172d0b1bf2c8143d9582e05f878e580a3781e"}, {file = "scipy-1.9.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:d644a64e174c16cb4b2e41dfea6af722053e83d066da7343f333a54dae9bc31c"}, {file = "scipy-1.9.3-cp39-cp39-macosx_12_0_arm64.whl", hash = "sha256:da8245491d73ed0a994ed9c2e380fd058ce2fa8a18da204681f2fe1f57f98f95"}, {file = "scipy-1.9.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4db5b30849606a95dcf519763dd3ab6fe9bd91df49eba517359e450a7d80ce2e"}, {file = "scipy-1.9.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c68db6b290cbd4049012990d7fe71a2abd9ffbe82c0056ebe0f01df8be5436b0"}, {file = "scipy-1.9.3-cp39-cp39-win_amd64.whl", hash = "sha256:5b88e6d91ad9d59478fafe92a7c757d00c59e3bdc3331be8ada76a4f8d683f58"}, {file = "scipy-1.9.3.tar.gz", hash = "sha256:fbc5c05c85c1a02be77b1ff591087c83bc44579c6d2bd9fb798bb64ea5e1a027"}, ] [package.dependencies] numpy = ">=1.18.5,<1.26.0" [package.extras] dev = ["flake8", "mypy", "pycodestyle", "typing_extensions"] doc = ["matplotlib (>2)", "numpydoc", "pydata-sphinx-theme (==0.9.0)", "sphinx (!=4.1.0)", "sphinx-panels (>=0.5.2)", "sphinx-tabs"] test = ["asv", "gmpy2", "mpmath", "pytest", "pytest-cov", "pytest-xdist", "scikit-umfpack", "threadpoolctl"] [[package]] name = "six" version = "1.16.0" description = "Python 2 and 3 compatibility utilities" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" files = [ {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, ] [[package]] name = "sniffio" version = "1.3.1" description = "Sniff out which async library your code is running under" optional = false python-versions = ">=3.7" files = [ {file = "sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2"}, {file = "sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc"}, ] [[package]] name = "snowballstemmer" version = "2.2.0" description = "This package provides 29 stemmers for 28 languages generated from Snowball algorithms." optional = true python-versions = "*" files = [ {file = "snowballstemmer-2.2.0-py2.py3-none-any.whl", hash = "sha256:c8e1716e83cc398ae16824e5572ae04e0d9fc2c6b985fb0f900f5f0c96ecba1a"}, {file = "snowballstemmer-2.2.0.tar.gz", hash = "sha256:09b16deb8547d3412ad7b590689584cd0fe25ec8db3be37788be3810cbf19cb1"}, ] [[package]] name = "soco" version = "0.30.6" description = "SoCo (Sonos Controller) is a simple library to control Sonos speakers." optional = true python-versions = ">=3.6" files = [ {file = "soco-0.30.6-py2.py3-none-any.whl", hash = "sha256:06c486218d0558a89276ed573ae2264d8e9bfd95a46a7dc253e03d19a3e6f423"}, {file = "soco-0.30.6.tar.gz", hash = "sha256:7ae48e865dbf1d9fae8023e1b69465c2c4c17048992a05e9c017b35c43d4f4f2"}, ] [package.dependencies] appdirs = "*" ifaddr = "*" lxml = "*" requests = "*" xmltodict = "*" [package.extras] events-asyncio = ["aiohttp"] testing = ["black (>=22.12.0)", "coveralls", "flake8", "graphviz", "importlib-metadata (<5)", "pylint", "pytest (>=2.5)", "pytest-cov (<2.6.0)", "requests-mock", "sphinx (==4.5.0)", "sphinx-rtd-theme", "twine", "wheel"] [[package]] name = "soundfile" version = "0.12.1" description = "An audio library based on libsndfile, CFFI and NumPy" optional = true python-versions = "*" files = [ {file = "soundfile-0.12.1-py2.py3-none-any.whl", hash = "sha256:828a79c2e75abab5359f780c81dccd4953c45a2c4cd4f05ba3e233ddf984b882"}, {file = "soundfile-0.12.1-py2.py3-none-macosx_10_9_x86_64.whl", hash = "sha256:d922be1563ce17a69582a352a86f28ed8c9f6a8bc951df63476ffc310c064bfa"}, {file = "soundfile-0.12.1-py2.py3-none-macosx_11_0_arm64.whl", hash = "sha256:bceaab5c4febb11ea0554566784bcf4bc2e3977b53946dda2b12804b4fe524a8"}, {file = "soundfile-0.12.1-py2.py3-none-manylinux_2_17_x86_64.whl", hash = "sha256:2dc3685bed7187c072a46ab4ffddd38cef7de9ae5eb05c03df2ad569cf4dacbc"}, {file = "soundfile-0.12.1-py2.py3-none-manylinux_2_31_x86_64.whl", hash = "sha256:074247b771a181859d2bc1f98b5ebf6d5153d2c397b86ee9e29ba602a8dfe2a6"}, {file = "soundfile-0.12.1-py2.py3-none-win32.whl", hash = "sha256:59dfd88c79b48f441bbf6994142a19ab1de3b9bb7c12863402c2bc621e49091a"}, {file = "soundfile-0.12.1-py2.py3-none-win_amd64.whl", hash = "sha256:0d86924c00b62552b650ddd28af426e3ff2d4dc2e9047dae5b3d8452e0a49a77"}, {file = "soundfile-0.12.1.tar.gz", hash = "sha256:e8e1017b2cf1dda767aef19d2fd9ee5ebe07e050d430f77a0a7c66ba08b8cdae"}, ] [package.dependencies] cffi = ">=1.0" [package.extras] numpy = ["numpy"] [[package]] name = "soupsieve" version = "2.6" description = "A modern CSS selector implementation for Beautiful Soup." optional = false python-versions = ">=3.8" files = [ {file = "soupsieve-2.6-py3-none-any.whl", hash = "sha256:e72c4ff06e4fb6e4b5a9f0f55fe6e81514581fca1515028625d0f299c602ccc9"}, {file = "soupsieve-2.6.tar.gz", hash = "sha256:e2e68417777af359ec65daac1057404a3c8a5455bb8abc36f1a9866ab1a51abb"}, ] [[package]] name = "soxr" version = "0.3.7" description = "High quality, one-dimensional sample-rate conversion library" optional = true python-versions = ">=3.6" files = [ {file = "soxr-0.3.7-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ac81c4af6a993d5b7c0b466bbac4835bad2b14ec32f342b2c1f83e4cf825e301"}, {file = "soxr-0.3.7-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8d8a2b3e7f8d0255e2484fb82cb66c86da6fb25b342ef793cceca9ce9a61aa16"}, {file = "soxr-0.3.7-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a0cd6eb6f6bbda2e8de36672cf2f0529ced6e638773150744ef075be0cc4f52c"}, {file = "soxr-0.3.7-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e47d86af35b942c92606fc2d5dfccf3f01309329475571ae2312bbf9edc3a790"}, {file = "soxr-0.3.7-cp310-cp310-win_amd64.whl", hash = "sha256:0e291adfaf9f2a7c4dd180a1b8c280f9beb1c84cb381853e4f4b3434d002ed7f"}, {file = "soxr-0.3.7-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:9e811450f0e91972932bd37ac58e32e44002c2c99db2aa926a9e7ba164545034"}, {file = "soxr-0.3.7-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:9cea63014ce91035074e1228c9340e2b8609faf964e268705fcac5135d05060c"}, {file = "soxr-0.3.7-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bfab27830f6217a15b83445988225c3aeea3bbccfa9399ced291e53e1b05925d"}, {file = "soxr-0.3.7-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:286858e3078d76c11b6d490b66fed3c9bb2a4229759f6be03ceef5c02189bf2c"}, {file = "soxr-0.3.7-cp311-cp311-win_amd64.whl", hash = "sha256:54985ff33292192d2937be80df3e5f3a44d6d53e6835f727d6b99b7cdd3f1611"}, {file = "soxr-0.3.7-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:83c74ef6d61d7dcd81be26f91bee0a420f792f5c1982266f2a80e655f0650a98"}, {file = "soxr-0.3.7-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:cb1e14663a43fe88b8fbc287822a159028366a820abe1a0a9670fb53618cb47b"}, {file = "soxr-0.3.7-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:48acdfbcf870ab54f645b1cfd641bce92c1e3a67346c3bf0f6c0ad2873c1dd35"}, {file = "soxr-0.3.7-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ea663b76f2b0ec1576b8a43aef317aec080abc0a67a4015fcd9f3407039f260a"}, {file = "soxr-0.3.7-cp312-cp312-win_amd64.whl", hash = "sha256:42da0d9eb79c70e5a41917f1b48a032e241a48eb4a1bcea7c80577302ff26974"}, {file = "soxr-0.3.7-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:511c6b2279c8ddd83459d129d69f628f7aae4616ae0a1912963985bd89e35df7"}, {file = "soxr-0.3.7-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a37c518c0b5d70162956d808d6c2e249bae0672e414e0dcfc101e200d8c31f3c"}, {file = "soxr-0.3.7-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:27f2890528d2b2e358938ab660a6b8346802863f5b6b646204d7ff8ab0ca2c66"}, {file = "soxr-0.3.7-cp37-cp37m-win_amd64.whl", hash = "sha256:52467c8c012495544a6dcfcce6b5bcbbc653d24fe9bb33c0b6191acecdb5e297"}, {file = "soxr-0.3.7-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:ce12b93747958f2769d6b297e6e27c73d9ad635fe8104ef052bece9c8a322824"}, {file = "soxr-0.3.7-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:1cd65dc7b96ea3cb6c8c48e6020e859680556cc42dd3d4de44779530cce21037"}, {file = "soxr-0.3.7-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d994f1a7690b1b13ab639ea33e0c1d78415b64d88d6df4af705a9443f97b9687"}, {file = "soxr-0.3.7-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e87b58bc9e8c2caa16f07726f666bd043f0a49ca937baa803ce7708003b27833"}, {file = "soxr-0.3.7-cp38-cp38-win_amd64.whl", hash = "sha256:07f4c0c6125ea1482fa187ad5f007216712ee0a93586a9b2f80e79c0bf944cf7"}, {file = "soxr-0.3.7-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:e5267c3ba34d4b873d9bbe3a9e58418b01ae4fd04349a4f944d9943b9ddac0f7"}, {file = "soxr-0.3.7-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:6e39668c250e221db888cf3b290a16fbe10a702d9a4eb604a127f720040de583"}, {file = "soxr-0.3.7-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f8ceeb74e5a55d903cc286d3bd12c2d8f8c85d02894071e9ec92ab405430907c"}, {file = "soxr-0.3.7-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0eed6bf58192dd1bb93becd2444de4d712689713d727b32fd55623ae9aae7df7"}, {file = "soxr-0.3.7-cp39-cp39-win_amd64.whl", hash = "sha256:7221302b4547d02a3f38dd3cd15317ab2b78873c75921db5f4a070848f0c71be"}, {file = "soxr-0.3.7.tar.gz", hash = "sha256:436ddff00c6eb2c75b79c19cfdca7527b1e31b5fad738652f044045ba6258593"}, ] [package.dependencies] numpy = "*" [package.extras] docs = ["linkify-it-py", "myst-parser", "sphinx", "sphinx-book-theme"] test = ["pytest"] [[package]] name = "sphinx" version = "7.1.2" description = "Python documentation generator" optional = true python-versions = ">=3.8" files = [ {file = "sphinx-7.1.2-py3-none-any.whl", hash = "sha256:d170a81825b2fcacb6dfd5a0d7f578a053e45d3f2b153fecc948c37344eb4cbe"}, {file = "sphinx-7.1.2.tar.gz", hash = "sha256:780f4d32f1d7d1126576e0e5ecc19dc32ab76cd24e950228dcf7b1f6d3d9e22f"}, ] [package.dependencies] alabaster = ">=0.7,<0.8" babel = ">=2.9" colorama = {version = ">=0.4.5", markers = "sys_platform == \"win32\""} docutils = ">=0.18.1,<0.21" imagesize = ">=1.3" importlib-metadata = {version = ">=4.8", markers = "python_version < \"3.10\""} Jinja2 = ">=3.0" packaging = ">=21.0" Pygments = ">=2.13" requests = ">=2.25.0" snowballstemmer = ">=2.0" sphinxcontrib-applehelp = "*" sphinxcontrib-devhelp = "*" sphinxcontrib-htmlhelp = ">=2.0.0" sphinxcontrib-jsmath = "*" sphinxcontrib-qthelp = "*" sphinxcontrib-serializinghtml = ">=1.1.5" [package.extras] docs = ["sphinxcontrib-websupport"] lint = ["docutils-stubs", "flake8 (>=3.5.0)", "flake8-simplify", "isort", "mypy (>=0.990)", "ruff", "sphinx-lint", "types-requests"] test = ["cython", "filelock", "html5lib", "pytest (>=4.6)"] [[package]] name = "sphinxcontrib-applehelp" version = "1.0.4" description = "sphinxcontrib-applehelp is a Sphinx extension which outputs Apple help books" optional = true python-versions = ">=3.8" files = [ {file = "sphinxcontrib-applehelp-1.0.4.tar.gz", hash = "sha256:828f867945bbe39817c210a1abfd1bc4895c8b73fcaade56d45357a348a07d7e"}, {file = "sphinxcontrib_applehelp-1.0.4-py3-none-any.whl", hash = "sha256:29d341f67fb0f6f586b23ad80e072c8e6ad0b48417db2bde114a4c9746feb228"}, ] [package.extras] lint = ["docutils-stubs", "flake8", "mypy"] test = ["pytest"] [[package]] name = "sphinxcontrib-devhelp" version = "1.0.2" description = "sphinxcontrib-devhelp is a sphinx extension which outputs Devhelp document." optional = true python-versions = ">=3.5" files = [ {file = "sphinxcontrib-devhelp-1.0.2.tar.gz", hash = "sha256:ff7f1afa7b9642e7060379360a67e9c41e8f3121f2ce9164266f61b9f4b338e4"}, {file = "sphinxcontrib_devhelp-1.0.2-py2.py3-none-any.whl", hash = "sha256:8165223f9a335cc1af7ffe1ed31d2871f325254c0423bc0c4c7cd1c1e4734a2e"}, ] [package.extras] lint = ["docutils-stubs", "flake8", "mypy"] test = ["pytest"] [[package]] name = "sphinxcontrib-htmlhelp" version = "2.0.1" description = "sphinxcontrib-htmlhelp is a sphinx extension which renders HTML help files" optional = true python-versions = ">=3.8" files = [ {file = "sphinxcontrib-htmlhelp-2.0.1.tar.gz", hash = "sha256:0cbdd302815330058422b98a113195c9249825d681e18f11e8b1f78a2f11efff"}, {file = "sphinxcontrib_htmlhelp-2.0.1-py3-none-any.whl", hash = "sha256:c38cb46dccf316c79de6e5515e1770414b797162b23cd3d06e67020e1d2a6903"}, ] [package.extras] lint = ["docutils-stubs", "flake8", "mypy"] test = ["html5lib", "pytest"] [[package]] name = "sphinxcontrib-jsmath" version = "1.0.1" description = "A sphinx extension which renders display math in HTML via JavaScript" optional = true python-versions = ">=3.5" files = [ {file = "sphinxcontrib-jsmath-1.0.1.tar.gz", hash = "sha256:a9925e4a4587247ed2191a22df5f6970656cb8ca2bd6284309578f2153e0c4b8"}, {file = "sphinxcontrib_jsmath-1.0.1-py2.py3-none-any.whl", hash = "sha256:2ec2eaebfb78f3f2078e73666b1415417a116cc848b72e5172e596c871103178"}, ] [package.extras] test = ["flake8", "mypy", "pytest"] [[package]] name = "sphinxcontrib-qthelp" version = "1.0.3" description = "sphinxcontrib-qthelp is a sphinx extension which outputs QtHelp document." optional = true python-versions = ">=3.5" files = [ {file = "sphinxcontrib-qthelp-1.0.3.tar.gz", hash = "sha256:4c33767ee058b70dba89a6fc5c1892c0d57a54be67ddd3e7875a18d14cba5a72"}, {file = "sphinxcontrib_qthelp-1.0.3-py2.py3-none-any.whl", hash = "sha256:bd9fc24bcb748a8d51fd4ecaade681350aa63009a347a8c14e637895444dfab6"}, ] [package.extras] lint = ["docutils-stubs", "flake8", "mypy"] test = ["pytest"] [[package]] name = "sphinxcontrib-serializinghtml" version = "1.1.5" description = "sphinxcontrib-serializinghtml is a sphinx extension which outputs \"serialized\" HTML files (json and pickle)." optional = true python-versions = ">=3.5" files = [ {file = "sphinxcontrib-serializinghtml-1.1.5.tar.gz", hash = "sha256:aa5f6de5dfdf809ef505c4895e51ef5c9eac17d0f287933eb49ec495280b6952"}, {file = "sphinxcontrib_serializinghtml-1.1.5-py2.py3-none-any.whl", hash = "sha256:352a9a00ae864471d3a7ead8d7d79f5fc0b57e8b3f95e9867eb9eb28999b92fd"}, ] [package.extras] lint = ["docutils-stubs", "flake8", "mypy"] test = ["pytest"] [[package]] name = "texttable" version = "1.7.0" description = "module to create simple ASCII tables" optional = false python-versions = "*" files = [ {file = "texttable-1.7.0-py2.py3-none-any.whl", hash = "sha256:72227d592c82b3d7f672731ae73e4d1f88cd8e2ef5b075a7a7f01a23a3743917"}, {file = "texttable-1.7.0.tar.gz", hash = "sha256:2d2068fb55115807d3ac77a4ca68fa48803e84ebb0ee2340f858107a36522638"}, ] [[package]] name = "threadpoolctl" version = "3.5.0" description = "threadpoolctl" optional = true python-versions = ">=3.8" files = [ {file = "threadpoolctl-3.5.0-py3-none-any.whl", hash = "sha256:56c1e26c150397e58c4926da8eeee87533b1e32bef131bd4bf6a2f45f3185467"}, {file = "threadpoolctl-3.5.0.tar.gz", hash = "sha256:082433502dd922bf738de0d8bcc4fdcbf0979ff44c42bd40f5af8a282f6fa107"}, ] [[package]] name = "tomli" version = "2.1.0" description = "A lil' TOML parser" optional = false python-versions = ">=3.8" files = [ {file = "tomli-2.1.0-py3-none-any.whl", hash = "sha256:a5c57c3d1c56f5ccdf89f6523458f60ef716e210fc47c4cfb188c5ba473e0391"}, {file = "tomli-2.1.0.tar.gz", hash = "sha256:3f646cae2aec94e17d04973e4249548320197cfabdf130015d023de4b74d8ab8"}, ] [[package]] name = "types-beautifulsoup4" version = "4.12.0.20241020" description = "Typing stubs for beautifulsoup4" optional = false python-versions = ">=3.8" files = [ {file = "types-beautifulsoup4-4.12.0.20241020.tar.gz", hash = "sha256:158370d08d0cd448bd11b132a50ff5279237a5d4b5837beba074de152a513059"}, {file = "types_beautifulsoup4-4.12.0.20241020-py3-none-any.whl", hash = "sha256:c95e66ce15a4f5f0835f7fbc5cd886321ae8294f977c495424eaf4225307fd30"}, ] [package.dependencies] types-html5lib = "*" [[package]] name = "types-flask-cors" version = "5.0.0.20240902" description = "Typing stubs for Flask-Cors" optional = false python-versions = ">=3.8" files = [ {file = "types-Flask-Cors-5.0.0.20240902.tar.gz", hash = "sha256:8921b273bf7cd9636df136b66408efcfa6338a935e5c8f53f5eff1cee03f3394"}, {file = "types_Flask_Cors-5.0.0.20240902-py3-none-any.whl", hash = "sha256:595e5f36056cd128ab905832e055f2e5d116fbdc685356eea4490bc77df82137"}, ] [package.dependencies] Flask = ">=2.0.0" [[package]] name = "types-html5lib" version = "1.1.11.20241018" description = "Typing stubs for html5lib" optional = false python-versions = ">=3.8" files = [ {file = "types-html5lib-1.1.11.20241018.tar.gz", hash = "sha256:98042555ff78d9e3a51c77c918b1041acbb7eb6c405408d8a9e150ff5beccafa"}, {file = "types_html5lib-1.1.11.20241018-py3-none-any.whl", hash = "sha256:3f1e064d9ed2c289001ae6392c84c93833abb0816165c6ff0abfc304a779f403"}, ] [[package]] name = "types-pillow" version = "10.2.0.20240822" description = "Typing stubs for Pillow" optional = false python-versions = ">=3.8" files = [ {file = "types-Pillow-10.2.0.20240822.tar.gz", hash = "sha256:559fb52a2ef991c326e4a0d20accb3bb63a7ba8d40eb493e0ecb0310ba52f0d3"}, {file = "types_Pillow-10.2.0.20240822-py3-none-any.whl", hash = "sha256:d9dab025aba07aeb12fd50a6799d4eac52a9603488eca09d7662543983f16c5d"}, ] [[package]] name = "types-pyyaml" version = "6.0.12.20240917" description = "Typing stubs for PyYAML" optional = false python-versions = ">=3.8" files = [ {file = "types-PyYAML-6.0.12.20240917.tar.gz", hash = "sha256:d1405a86f9576682234ef83bcb4e6fff7c9305c8b1fbad5e0bcd4f7dbdc9c587"}, {file = "types_PyYAML-6.0.12.20240917-py3-none-any.whl", hash = "sha256:392b267f1c0fe6022952462bf5d6523f31e37f6cea49b14cee7ad634b6301570"}, ] [[package]] name = "types-requests" version = "2.32.0.20241016" description = "Typing stubs for requests" optional = false python-versions = ">=3.8" files = [ {file = "types-requests-2.32.0.20241016.tar.gz", hash = "sha256:0d9cad2f27515d0e3e3da7134a1b6f28fb97129d86b867f24d9c726452634d95"}, {file = "types_requests-2.32.0.20241016-py3-none-any.whl", hash = "sha256:4195d62d6d3e043a4eaaf08ff8a62184584d2e8684e9d2aa178c7915a7da3747"}, ] [package.dependencies] urllib3 = ">=2" [[package]] name = "types-urllib3" version = "1.26.25.14" description = "Typing stubs for urllib3" optional = false python-versions = "*" files = [ {file = "types-urllib3-1.26.25.14.tar.gz", hash = "sha256:229b7f577c951b8c1b92c1bc2b2fdb0b49847bd2af6d1cc2a2e3dd340f3bda8f"}, {file = "types_urllib3-1.26.25.14-py3-none-any.whl", hash = "sha256:9683bbb7fb72e32bfe9d2be6e04875fbe1b3eeec3cbb4ea231435aa7fd6b4f0e"}, ] [[package]] name = "typing-extensions" version = "4.12.2" description = "Backported and Experimental Type Hints for Python 3.8+" optional = false python-versions = ">=3.8" files = [ {file = "typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d"}, {file = "typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8"}, ] [[package]] name = "unidecode" version = "1.3.8" description = "ASCII transliterations of Unicode text" optional = false python-versions = ">=3.5" files = [ {file = "Unidecode-1.3.8-py3-none-any.whl", hash = "sha256:d130a61ce6696f8148a3bd8fe779c99adeb4b870584eeb9526584e9aa091fd39"}, {file = "Unidecode-1.3.8.tar.gz", hash = "sha256:cfdb349d46ed3873ece4586b96aa75258726e2fa8ec21d6f00a591d98806c2f4"}, ] [[package]] name = "urllib3" version = "2.2.3" description = "HTTP library with thread-safe connection pooling, file post, and more." optional = false python-versions = ">=3.8" files = [ {file = "urllib3-2.2.3-py3-none-any.whl", hash = "sha256:ca899ca043dcb1bafa3e262d73aa25c465bfb49e0bd9dd5d59f1d0acba2f8fac"}, {file = "urllib3-2.2.3.tar.gz", hash = "sha256:e7d814a81dad81e6caf2ec9fdedb284ecc9c73076b62654547cc64ccdcae26e9"}, ] [package.extras] brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)"] h2 = ["h2 (>=4,<5)"] socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] zstd = ["zstandard (>=0.18.0)"] [[package]] name = "werkzeug" version = "3.0.6" description = "The comprehensive WSGI web application library." optional = false python-versions = ">=3.8" files = [ {file = "werkzeug-3.0.6-py3-none-any.whl", hash = "sha256:1bc0c2310d2fbb07b1dd1105eba2f7af72f322e1e455f2f93c993bee8c8a5f17"}, {file = "werkzeug-3.0.6.tar.gz", hash = "sha256:a8dd59d4de28ca70471a34cba79bed5f7ef2e036a76b3ab0835474246eb41f8d"}, ] [package.dependencies] MarkupSafe = ">=2.1.1" [package.extras] watchdog = ["watchdog (>=2.3)"] [[package]] name = "xmltodict" version = "0.14.2" description = "Makes working with XML feel like you are working with JSON" optional = true python-versions = ">=3.6" files = [ {file = "xmltodict-0.14.2-py2.py3-none-any.whl", hash = "sha256:20cc7d723ed729276e808f26fb6b3599f786cbc37e06c65e192ba77c40f20aac"}, {file = "xmltodict-0.14.2.tar.gz", hash = "sha256:201e7c28bb210e374999d1dde6382923ab0ed1a8a5faeece48ab525b7810a553"}, ] [[package]] name = "zipp" version = "3.20.2" description = "Backport of pathlib-compatible object wrapper for zip files" optional = false python-versions = ">=3.8" files = [ {file = "zipp-3.20.2-py3-none-any.whl", hash = "sha256:a817ac80d6cf4b23bf7f2828b7cabf326f15a001bea8b1f9b49631780ba28350"}, {file = "zipp-3.20.2.tar.gz", hash = "sha256:bc9eb26f4506fda01b81bcde0ca78103b6e62f991b381fec825435c836edbc29"}, ] [package.extras] check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1)"] cover = ["pytest-cov"] doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] enabler = ["pytest-enabler (>=2.2)"] test = ["big-O", "importlib-resources", "jaraco.functools", "jaraco.itertools", "jaraco.test", "more-itertools", "pytest (>=6,!=8.1.*)", "pytest-ignore-flaky"] type = ["pytest-mypy"] [extras] absubmit = ["requests"] aura = ["Pillow", "flask", "flask-cors"] autobpm = ["librosa", "resampy"] beatport = ["requests-oauthlib"] bpd = ["PyGObject"] chroma = ["pyacoustid"] discogs = ["python3-discogs-client"] docs = ["pydata-sphinx-theme", "sphinx"] embedart = ["Pillow"] embyupdate = ["requests"] fetchart = ["Pillow", "beautifulsoup4", "langdetect", "requests"] import = ["py7zr", "rarfile"] kodiupdate = ["requests"] lastgenre = ["pylast"] lastimport = ["pylast"] lyrics = ["beautifulsoup4", "langdetect", "requests"] metasync = ["dbus-python"] mpdstats = ["python-mpd2"] plexupdate = ["requests"] reflink = ["reflink"] replaygain = ["PyGObject"] scrub = ["mutagen"] sonosupdate = ["soco"] thumbnails = ["Pillow", "pyxdg"] web = ["flask", "flask-cors"] [metadata] lock-version = "2.0" python-versions = ">=3.8,<4" content-hash = "07f39a89dbb7ea5102327e5b2cccfde258ad190ba21b0793c044c2f45aa89f00" �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������beetbox-beets-01f1faf/pyproject.toml����������������������������������������������������������������0000664�0000000�0000000�00000017014�14723254774�0017666�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������[tool.poetry] name = "beets" version = "2.1.0" description = "music tagger and library organizer" authors = ["Adrian Sampson <adrian@radbox.org>"] maintainers = ["Serene-Arc"] license = "MIT" readme = "README.rst" homepage = "https://beets.io/" repository = "https://github.com/beetbox/beets" documentation = "https://beets.readthedocs.io/en/stable/" classifiers = [ "Topic :: Multimedia :: Sound/Audio", "Topic :: Multimedia :: Sound/Audio :: Players :: MP3", "License :: OSI Approved :: MIT License", "Environment :: Console", "Environment :: Web Environment", "Programming Language :: Python", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: Implementation :: CPython", ] packages = [ { include = "beets" }, { include = "beetsplug" }, ] include = ["test", "man/**/*"] # extra files to include in the sdist [tool.poetry.urls] Changelog = "https://github.com/beetbox/beets/blob/master/docs/changelog.rst" "Bug Tracker" = "https://github.com/beetbox/beets/issues" [tool.poetry.dependencies] python = ">=3.8,<4" colorama = { version = "*", markers = "sys_platform == 'win32'" } confuse = ">=1.5.0" jellyfish = "*" mediafile = ">=0.12.0" munkres = ">=1.0.0" musicbrainzngs = ">=0.4" platformdirs = ">=3.5.0" pyyaml = "*" typing_extensions = { version = "*", python = "<=3.10" } unidecode = ">=1.3.6" beautifulsoup4 = { version = "*", optional = true } dbus-python = { version = "*", optional = true } flask = { version = "*", optional = true } flask-cors = { version = "*", optional = true } langdetect = { version = "*", optional = true } librosa = { version = "^0.10.2.post1", optional = true } mutagen = { version = ">=1.33", optional = true } Pillow = { version = "*", optional = true } py7zr = { version = "*", optional = true } pyacoustid = { version = "*", optional = true } PyGObject = { version = "*", optional = true } pylast = { version = "*", optional = true } python-mpd2 = { version = ">=0.4.2", optional = true } python3-discogs-client = { version = ">=2.3.15", optional = true } pyxdg = { version = "*", optional = true } rarfile = { version = "*", optional = true } reflink = { version = "*", optional = true } requests = { version = "*", optional = true } resampy = { version = ">=0.4.3", optional = true } requests-oauthlib = { version = ">=0.6.1", optional = true } soco = { version = "*", optional = true } pydata-sphinx-theme = { version = "*", optional = true } sphinx = { version = "*", optional = true } [tool.poetry.group.test.dependencies] beautifulsoup4 = "*" codecov = ">=2.1.13" flask = "*" mock = "*" pylast = "*" pytest = "*" pytest-cov = "*" pytest-flask = "*" python-mpd2 = "*" python3-discogs-client = ">=2.3.15" py7zr = "*" pyxdg = "*" rarfile = "*" requests_oauthlib = "*" responses = ">=0.3.0" [tool.poetry.group.lint.dependencies] ruff = ">=0.6.4" [tool.poetry.group.typing.dependencies] mypy = "*" types-beautifulsoup4 = "*" types-Flask-Cors = "*" types-Pillow = "*" types-PyYAML = "*" types-requests = "*" types-urllib3 = "*" [tool.poetry.group.release.dependencies] click = ">=8.1.7" packaging = ">=24.0" tomli = ">=2.0.1" [tool.poetry.extras] # inline comments note required external / non-python dependencies absubmit = ["requests"] # extractor binary from https://acousticbrainz.org/download aura = ["flask", "flask-cors", "Pillow"] autobpm = ["librosa", "resampy"] # badfiles # mp3val and flac beatport = ["requests-oauthlib"] bpd = ["PyGObject"] # python-gi and GStreamer 1.0+ chroma = ["pyacoustid"] # chromaprint or fpcalc # convert # ffmpeg docs = ["pydata-sphinx-theme", "sphinx"] discogs = ["python3-discogs-client"] embedart = ["Pillow"] # ImageMagick embyupdate = ["requests"] fetchart = ["beautifulsoup4", "langdetect", "Pillow", "requests"] import = ["py7zr", "rarfile"] # ipfs # go-ipfs # keyfinder # KeyFinder kodiupdate = ["requests"] lastgenre = ["pylast"] lastimport = ["pylast"] lyrics = ["beautifulsoup4", "langdetect", "requests"] metasync = ["dbus-python"] mpdstats = ["python-mpd2"] plexupdate = ["requests"] reflink = ["reflink"] replaygain = [ "PyGObject", ] # python-gi and GStreamer 1.0+ or mp3gain/aacgain or Python Audio Tools or ffmpeg scrub = ["mutagen"] sonosupdate = ["soco"] thumbnails = ["Pillow", "pyxdg"] web = ["flask", "flask-cors"] [tool.poetry.scripts] beet = "beets.ui:main" [build-system] requires = ["poetry-core>=1.0.0"] build-backend = "poetry.core.masonry.api" [tool.pipx-install] poethepoet = ">=0.26" poetry = ">=1.8" [tool.poe.tasks.build] help = "Build the package" shell = """ make -C docs man rm -rf man mv docs/_build/man . poetry build """ [tool.poe.tasks.bump] help = "Bump project version and update relevant files" cmd = "python ./extra/release.py bump $version" args = { version = { help = "The new version to set", positional = true, required = true } } [tool.poe.tasks.changelog] help = "Print the latest version's changelog in Markdown" cmd = "python ./extra/release.py changelog" [tool.poe.tasks.check-docs-links] help = "Check the documentation for broken URLs" cmd = "make -C docs linkcheck" [tool.poe.tasks.check-format] help = "Check the code for style issues" cmd = "ruff format --check --diff" [tool.poe.tasks.check-types] help = "Check the code for typing issues. Accepts mypy options." cmd = "mypy" [tool.poe.tasks.docs] help = "Build documentation" cmd = "make -C docs html" [tool.poe.tasks.format] help = "Format the codebase" cmd = "ruff format" [tool.poe.tasks.lint] help = "Check the code for linting issues. Accepts ruff options." cmd = "ruff check" [tool.poe.tasks.update-dependencies] help = "Update dependencies to their latest versions." cmd = "poetry update -vv" [tool.poe.tasks.test] help = "Run tests with pytest" cmd = "pytest $OPTS" env.OPTS.default = "-p no:cov" [tool.poe.tasks.test-with-coverage] help = "Run tests and record coverage" ref = "test" # record coverage in beets and beetsplug packages # save xml for coverage upload to coveralls # save html report for local dev use # measure coverage across logical branches # show which tests cover specific lines in the code (see the HTML report) env.OPTS = """ --cov=beets --cov=beetsplug --cov-report=xml:.reports/coverage.xml --cov-report=html:.reports/html --cov-branch --cov-context=test """ [tool.poe.tasks.check-temp-files] help = "Run each test module one by one and check for leftover temp files" shell = """ setopt nullglob for file in test/**/*.py; do print Temp files created by $file && poe test $file &>/dev/null tempfiles=(/tmp/**/tmp* /tmp/beets/**/*) if (( $#tempfiles )); then print -l $'\t'$^tempfiles rm -r --interactive=never $tempfiles &>/dev/null fi done """ interpreter = "zsh" [tool.ruff] target-version = "py38" line-length = 80 [tool.ruff.lint] select = [ # "ARG", # flake8-unused-arguments # "C4", # flake8-comprehensions "E", # pycodestyle "F", # pyflakes # "B", # flake8-bugbear "I", # isort "N", # pep8-naming "PT", # flake8-pytest-style # "RUF", # ruff # "UP", # pyupgrade "W", # pycodestyle ] [tool.ruff.lint.per-file-ignores] "beets/**" = ["PT"] [tool.ruff.lint.isort] split-on-trailing-comma = false [tool.ruff.lint.pycodestyle] max-line-length = 88 [tool.ruff.lint.flake8-pytest-style] fixture-parentheses = false mark-parentheses = false parametrize-names-type = "csv" [tool.ruff.lint.flake8-unused-arguments] ignore-variadic-names = true [tool.ruff.lint.pep8-naming] classmethod-decorators = ["cached_classproperty"] extend-ignore-names = ["assert*", "cached_classproperty"] ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������beetbox-beets-01f1faf/setup.cfg���������������������������������������������������������������������0000664�0000000�0000000�00000001620�14723254774�0016567�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������[tool:pytest] # do not litter the working directory cache_dir = /tmp/pytest_cache # slightly more verbose output console_output_style = count addopts = # show all skipped/failed/xfailed tests in the summary except passed -ra --strict-config markers = integration_test: mark a test as an integration test [coverage:run] data_file = .reports/coverage/data branch = true relative_files = true omit = beets/test/* [coverage:report] precision = 2 skip_empty = true show_missing = true exclude_lines = pragma: no cover if TYPE_CHECKING if typing.TYPE_CHECKING raise AssertionError raise NotImplementedError [coverage:html] show_contexts = true [mypy] files = beets,beetsplug,test,extra,docs allow_any_generics = false # FIXME: Would be better to actually type the libraries (if under our control), # or write our own stubs. For now, silence errors ignore_missing_imports = true ����������������������������������������������������������������������������������������������������������������beetbox-beets-01f1faf/test/�������������������������������������������������������������������������0000775�0000000�0000000�00000000000�14723254774�0015726�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������beetbox-beets-01f1faf/test/__init__.py��������������������������������������������������������������0000664�0000000�0000000�00000000042�14723254774�0020033�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Make python -m testall.py work. ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������beetbox-beets-01f1faf/test/conftest.py��������������������������������������������������������������0000664�0000000�0000000�00000000533�14723254774�0020126�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������import os import pytest def pytest_runtest_setup(item: pytest.Item): """Skip integration tests if INTEGRATION_TEST environment variable is not set.""" if os.environ.get("INTEGRATION_TEST"): return if next(item.iter_markers(name="integration_test"), None): pytest.skip(f"INTEGRATION_TEST=1 required: {item.nodeid}") ���������������������������������������������������������������������������������������������������������������������������������������������������������������������beetbox-beets-01f1faf/test/plugins/�����������������������������������������������������������������0000775�0000000�0000000�00000000000�14723254774�0017407�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������beetbox-beets-01f1faf/test/plugins/__init__.py������������������������������������������������������0000664�0000000�0000000�00000000000�14723254774�0021506�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������beetbox-beets-01f1faf/test/plugins/lyrics_download_samples.py���������������������������������������0000664�0000000�0000000�00000003234�14723254774�0024703�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# This file is part of beets. # Copyright 2016, Fabrice Laporte # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the # "Software"), to deal in the Software without restriction, including # without limitation the rights to use, copy, modify, merge, publish, # distribute, sublicense, and/or sell copies of the Software, and to # permit persons to whom the Software is furnished to do so, subject to # the following conditions: # # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. import os import sys import requests from test.plugins import test_lyrics def mkdir_p(path): try: os.makedirs(path) except OSError: if os.path.isdir(path): pass else: raise def safe_open_w(path): """Open "path" for writing, creating any parent directories as needed.""" mkdir_p(os.path.dirname(path)) return open(path, "w") def main(argv=None): """Download one lyrics sample page per referenced source.""" if argv is None: argv = sys.argv print("Fetching samples from:") for s in test_lyrics.GOOGLE_SOURCES + test_lyrics.DEFAULT_SOURCES: print(s["url"]) url = s["url"] + s["path"] fn = test_lyrics.url_to_filename(url) if not os.path.isfile(fn): html = requests.get( url, verify=False, timeout=10, ).text with safe_open_w(fn) as f: f.write(html.encode("utf-8")) if __name__ == "__main__": sys.exit(main()) ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������beetbox-beets-01f1faf/test/plugins/test_acousticbrainz.py�������������������������������������������0000664�0000000�0000000�00000006723�14723254774�0024050�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# This file is part of beets. # Copyright 2016, Nathan Dwek. # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the # "Software"), to deal in the Software without restriction, including # without limitation the rights to use, copy, modify, merge, publish, # distribute, sublicense, and/or sell copies of the Software, and to # permit persons to whom the Software is furnished to do so, subject to # the following conditions: # # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. """Tests for the 'acousticbrainz' plugin.""" import json import os.path import unittest from beets.test._common import RSRC from beetsplug.acousticbrainz import ABSCHEME, AcousticPlugin class MapDataToSchemeTest(unittest.TestCase): def test_basic(self): ab = AcousticPlugin() data = {"key 1": "value 1", "key 2": "value 2"} scheme = {"key 1": "attribute 1", "key 2": "attribute 2"} mapping = set(ab._map_data_to_scheme(data, scheme)) assert mapping == { ("attribute 1", "value 1"), ("attribute 2", "value 2"), } def test_recurse(self): ab = AcousticPlugin() data = { "key": "value", "group": { "subkey": "subvalue", "subgroup": {"subsubkey": "subsubvalue"}, }, } scheme = { "key": "attribute 1", "group": { "subkey": "attribute 2", "subgroup": {"subsubkey": "attribute 3"}, }, } mapping = set(ab._map_data_to_scheme(data, scheme)) assert mapping == { ("attribute 1", "value"), ("attribute 2", "subvalue"), ("attribute 3", "subsubvalue"), } def test_composite(self): ab = AcousticPlugin() data = {"key 1": "part 1", "key 2": "part 2"} scheme = {"key 1": ("attribute", 0), "key 2": ("attribute", 1)} mapping = set(ab._map_data_to_scheme(data, scheme)) assert mapping == {("attribute", "part 1 part 2")} def test_realistic(self): ab = AcousticPlugin() data_path = os.path.join(RSRC, b"acousticbrainz/data.json") with open(data_path) as res: data = json.load(res) mapping = set(ab._map_data_to_scheme(data, ABSCHEME)) expected = { ("chords_key", "A"), ("average_loudness", 0.815025985241), ("mood_acoustic", 0.415711194277), ("chords_changes_rate", 0.0445116683841), ("tonal", 0.874250173569), ("mood_sad", 0.299694597721), ("bpm", 162.532119751), ("gender", "female"), ("initial_key", "A minor"), ("chords_number_rate", 0.00194468453992), ("mood_relaxed", 0.123632438481), ("chords_scale", "minor"), ("voice_instrumental", "instrumental"), ("key_strength", 0.636936545372), ("genre_rosamerica", "roc"), ("mood_party", 0.234383180737), ("mood_aggressive", 0.0779221653938), ("danceable", 0.143928021193), ("rhythm", "VienneseWaltz"), ("mood_electronic", 0.339881360531), ("mood_happy", 0.0894767045975), ("moods_mirex", "Cluster3"), ("timbre", "bright"), } assert mapping == expected ���������������������������������������������beetbox-beets-01f1faf/test/plugins/test_advancedrewrite.py������������������������������������������0000664�0000000�0000000�00000010351�14723254774�0024167�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# This file is part of beets. # Copyright 2023, Max Rumpf. # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the # "Software"), to deal in the Software without restriction, including # without limitation the rights to use, copy, modify, merge, publish, # distribute, sublicense, and/or sell copies of the Software, and to # permit persons to whom the Software is furnished to do so, subject to # the following conditions: # # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. """Test the advancedrewrite plugin for various configurations.""" import pytest from beets.test.helper import PluginTestCase from beets.ui import UserError PLUGIN_NAME = "advancedrewrite" class AdvancedRewritePluginTest(PluginTestCase): plugin = "advancedrewrite" preload_plugin = False def test_simple_rewrite_example(self): with self.configure_plugin( [{"artist ODD EYE CIRCLE": "ěť´ë‹¬ěť ě†Śë…€ ě¤ë“śě•„이써í´"}] ): item = self.add_item( artist="ODD EYE CIRCLE", albumartist="ODD EYE CIRCLE", ) assert item.artist == "ěť´ë‹¬ěť ě†Śë…€ ě¤ë“śě•„이써í´" def test_advanced_rewrite_example(self): with self.configure_plugin( [ { "match": "mb_artistid:dec0f331-cb08-4c8e-9c9f-aeb1f0f6d88c year:..2022", # noqa: E501 "replacements": { "artist": "ěť´ë‹¬ěť ě†Śë…€ ě¤ë“śě•„이써í´", "artist_sort": "LOONA / ODD EYE CIRCLE", }, }, ] ): item_a = self.add_item( artist="ODD EYE CIRCLE", artist_sort="ODD EYE CIRCLE", mb_artistid="dec0f331-cb08-4c8e-9c9f-aeb1f0f6d88c", year=2017, ) item_b = self.add_item( artist="ODD EYE CIRCLE", artist_sort="ODD EYE CIRCLE", mb_artistid="dec0f331-cb08-4c8e-9c9f-aeb1f0f6d88c", year=2023, ) # Assert that all replacements were applied to item_a assert "ěť´ë‹¬ěť ě†Śë…€ ě¤ë“śě•„이써í´" == item_a.artist assert "LOONA / ODD EYE CIRCLE" == item_a.artist_sort assert "LOONA / ODD EYE CIRCLE" == item_a.albumartist_sort # Assert that no replacements were applied to item_b assert "ODD EYE CIRCLE" == item_b.artist def test_advanced_rewrite_example_with_multi_valued_field(self): with self.configure_plugin( [ { "match": "artist:ë°°ěś ëą feat. 김미í„", "replacements": {"artists": ["ěś ëą", "미미"]}, }, ] ): item = self.add_item( artist="ë°°ěś ëą feat. 김미í„", artists=["ë°°ěś ëą", "김미í„"], ) assert item.artists == ["ěś ëą", "미미"] def test_fail_when_replacements_empty(self): with pytest.raises( UserError, match="Advanced rewrites must have at least one replacement", ), self.configure_plugin([{"match": "artist:A", "replacements": {}}]): pass def test_fail_when_rewriting_single_valued_field_with_list(self): with pytest.raises( UserError, match="Field artist is not a multi-valued field but a list was given: C, D", ), self.configure_plugin( [ { "match": "artist:'A & B'", "replacements": {"artist": ["C", "D"]}, }, ] ): pass def test_combined_rewrite_example(self): with self.configure_plugin( [ {"artist A": "B"}, {"match": "album:'C'", "replacements": {"artist": "D"}}, ] ): item = self.add_item(artist="A", albumartist="A") assert item.artist == "B" item = self.add_item(artist="C", albumartist="C", album="C") assert item.artist == "D" ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������beetbox-beets-01f1faf/test/plugins/test_albumtypes.py�����������������������������������������������0000664�0000000�0000000�00000007271�14723254774�0023214�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# This file is part of beets. # Copyright 2021, Edgars Supe. # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the # "Software"), to deal in the Software without restriction, including # without limitation the rights to use, copy, modify, merge, publish, # distribute, sublicense, and/or sell copies of the Software, and to # permit persons to whom the Software is furnished to do so, subject to # the following conditions: # # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. """Tests for the 'albumtypes' plugin.""" from typing import Sequence, Tuple from beets.autotag.mb import VARIOUS_ARTISTS_ID from beets.test.helper import PluginTestCase from beetsplug.albumtypes import AlbumTypesPlugin class AlbumTypesPluginTest(PluginTestCase): """Tests for albumtypes plugin.""" plugin = "albumtypes" def test_renames_types(self): """Tests if the plugin correctly renames the specified types.""" self._set_config( types=[("ep", "EP"), ("remix", "Remix")], ignore_va=[], bracket="()" ) album = self._create_album(album_types=["ep", "remix"]) subject = AlbumTypesPlugin() result = subject._atypes(album) assert "(EP)(Remix)" == result return def test_returns_only_specified_types(self): """Tests if the plugin returns only non-blank types given in config.""" self._set_config( types=[("ep", "EP"), ("soundtrack", "")], ignore_va=[], bracket="()" ) album = self._create_album(album_types=["ep", "remix", "soundtrack"]) subject = AlbumTypesPlugin() result = subject._atypes(album) assert "(EP)" == result def test_respects_type_order(self): """Tests if the types are returned in the same order as config.""" self._set_config( types=[("remix", "Remix"), ("ep", "EP")], ignore_va=[], bracket="()" ) album = self._create_album(album_types=["ep", "remix"]) subject = AlbumTypesPlugin() result = subject._atypes(album) assert "(Remix)(EP)" == result return def test_ignores_va(self): """Tests if the specified type is ignored for VA albums.""" self._set_config( types=[("ep", "EP"), ("soundtrack", "OST")], ignore_va=["ep"], bracket="()", ) album = self._create_album( album_types=["ep", "soundtrack"], artist_id=VARIOUS_ARTISTS_ID ) subject = AlbumTypesPlugin() result = subject._atypes(album) assert "(OST)" == result def test_respects_defaults(self): """Tests if the plugin uses the default values if config not given.""" album = self._create_album( album_types=[ "ep", "single", "soundtrack", "live", "compilation", "remix", ], artist_id=VARIOUS_ARTISTS_ID, ) subject = AlbumTypesPlugin() result = subject._atypes(album) assert "[EP][Single][OST][Live][Remix]" == result def _set_config( self, types: Sequence[Tuple[str, str]], ignore_va: Sequence[str], bracket: str, ): self.config["albumtypes"]["types"] = types self.config["albumtypes"]["ignore_va"] = ignore_va self.config["albumtypes"]["bracket"] = bracket def _create_album(self, album_types: Sequence[str], artist_id: str = "0"): return self.add_album( albumtypes=album_types, mb_albumartistid=artist_id ) ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������beetbox-beets-01f1faf/test/plugins/test_art.py������������������������������������������������������0000664�0000000�0000000�00000110370�14723254774�0021610�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# This file is part of beets. # Copyright 2016, Adrian Sampson. # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the # "Software"), to deal in the Software without restriction, including # without limitation the rights to use, copy, modify, merge, publish, # distribute, sublicense, and/or sell copies of the Software, and to # permit persons to whom the Software is furnished to do so, subject to # the following conditions: # # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. """Tests for the album art fetchers.""" import os import shutil from unittest.mock import patch import confuse import pytest import responses from beets import config, importer, logging, util from beets.autotag import AlbumInfo, AlbumMatch from beets.test import _common from beets.test.helper import ( BeetsTestCase, CleanupModulesMixin, FetchImageHelper, capture_log, ) from beets.util import syspath from beets.util.artresizer import ArtResizer from beetsplug import fetchart logger = logging.getLogger("beets.test_art") class Settings: """Used to pass settings to the ArtSources when the plugin isn't fully instantiated. """ def __init__(self, **kwargs): for k, v in kwargs.items(): setattr(self, k, v) class UseThePlugin(CleanupModulesMixin, BeetsTestCase): modules = (fetchart.__name__, ArtResizer.__module__) def setUp(self): super().setUp() self.plugin = fetchart.FetchArtPlugin() class FetchImageTestCase(FetchImageHelper, UseThePlugin): pass class CAAHelper: """Helper mixin for mocking requests to the Cover Art Archive.""" MBID_RELASE = "rid" MBID_GROUP = "rgid" RELEASE_URL = "coverartarchive.org/release/{}".format(MBID_RELASE) GROUP_URL = "coverartarchive.org/release-group/{}".format(MBID_GROUP) RELEASE_URL = "https://" + RELEASE_URL GROUP_URL = "https://" + GROUP_URL RESPONSE_RELEASE = """{ "images": [ { "approved": false, "back": false, "comment": "GIF", "edit": 12345, "front": true, "id": 12345, "image": "http://coverartarchive.org/release/rid/12345.gif", "thumbnails": { "1200": "http://coverartarchive.org/release/rid/12345-1200.jpg", "250": "http://coverartarchive.org/release/rid/12345-250.jpg", "500": "http://coverartarchive.org/release/rid/12345-500.jpg", "large": "http://coverartarchive.org/release/rid/12345-500.jpg", "small": "http://coverartarchive.org/release/rid/12345-250.jpg" }, "types": [ "Front" ] }, { "approved": false, "back": false, "comment": "", "edit": 12345, "front": false, "id": 12345, "image": "http://coverartarchive.org/release/rid/12345.jpg", "thumbnails": { "1200": "http://coverartarchive.org/release/rid/12345-1200.jpg", "250": "http://coverartarchive.org/release/rid/12345-250.jpg", "500": "http://coverartarchive.org/release/rid/12345-500.jpg", "large": "http://coverartarchive.org/release/rid/12345-500.jpg", "small": "http://coverartarchive.org/release/rid/12345-250.jpg" }, "types": [ "Front" ] } ], "release": "https://musicbrainz.org/release/releaseid" }""" RESPONSE_RELEASE_WITHOUT_THUMBNAILS = """{ "images": [ { "approved": false, "back": false, "comment": "GIF", "edit": 12345, "front": true, "id": 12345, "image": "http://coverartarchive.org/release/rid/12345.gif", "types": [ "Front" ] }, { "approved": false, "back": false, "comment": "", "edit": 12345, "front": false, "id": 12345, "image": "http://coverartarchive.org/release/rid/12345.jpg", "thumbnails": { "large": "http://coverartarchive.org/release/rgid/12345-500.jpg", "small": "http://coverartarchive.org/release/rgid/12345-250.jpg" }, "types": [ "Front" ] } ], "release": "https://musicbrainz.org/release/releaseid" }""" RESPONSE_GROUP = """{ "images": [ { "approved": false, "back": false, "comment": "", "edit": 12345, "front": true, "id": 12345, "image": "http://coverartarchive.org/release/releaseid/12345.jpg", "thumbnails": { "1200": "http://coverartarchive.org/release/rgid/12345-1200.jpg", "250": "http://coverartarchive.org/release/rgid/12345-250.jpg", "500": "http://coverartarchive.org/release/rgid/12345-500.jpg", "large": "http://coverartarchive.org/release/rgid/12345-500.jpg", "small": "http://coverartarchive.org/release/rgid/12345-250.jpg" }, "types": [ "Front" ] } ], "release": "https://musicbrainz.org/release/release-id" }""" RESPONSE_GROUP_WITHOUT_THUMBNAILS = """{ "images": [ { "approved": false, "back": false, "comment": "", "edit": 12345, "front": true, "id": 12345, "image": "http://coverartarchive.org/release/releaseid/12345.jpg", "types": [ "Front" ] } ], "release": "https://musicbrainz.org/release/release-id" }""" def mock_caa_response(self, url, json): responses.add( responses.GET, url, body=json, content_type="application/json" ) class FetchImageTest(FetchImageTestCase): URL = "http://example.com/test.jpg" def setUp(self): super().setUp() self.dpath = os.path.join(self.temp_dir, b"arttest") self.source = fetchart.RemoteArtSource(logger, self.plugin.config) self.settings = Settings(maxwidth=0) self.candidate = fetchart.Candidate(logger, url=self.URL) def test_invalid_type_returns_none(self): self.mock_response(self.URL, "image/watercolour") self.source.fetch_image(self.candidate, self.settings) assert self.candidate.path is None def test_jpeg_type_returns_path(self): self.mock_response(self.URL, "image/jpeg") self.source.fetch_image(self.candidate, self.settings) assert self.candidate.path is not None def test_extension_set_by_content_type(self): self.mock_response(self.URL, "image/png") self.source.fetch_image(self.candidate, self.settings) assert os.path.splitext(self.candidate.path)[1] == b".png" self.assertExists(self.candidate.path) def test_does_not_rely_on_server_content_type(self): self.mock_response(self.URL, "image/jpeg", "image/png") self.source.fetch_image(self.candidate, self.settings) assert os.path.splitext(self.candidate.path)[1] == b".png" self.assertExists(self.candidate.path) class FSArtTest(UseThePlugin): def setUp(self): super().setUp() self.dpath = os.path.join(self.temp_dir, b"arttest") os.mkdir(syspath(self.dpath)) self.source = fetchart.FileSystem(logger, self.plugin.config) self.settings = Settings(cautious=False, cover_names=("art",)) def test_finds_jpg_in_directory(self): _common.touch(os.path.join(self.dpath, b"a.jpg")) candidate = next(self.source.get(None, self.settings, [self.dpath])) assert candidate.path == os.path.join(self.dpath, b"a.jpg") def test_appropriately_named_file_takes_precedence(self): _common.touch(os.path.join(self.dpath, b"a.jpg")) _common.touch(os.path.join(self.dpath, b"art.jpg")) candidate = next(self.source.get(None, self.settings, [self.dpath])) assert candidate.path == os.path.join(self.dpath, b"art.jpg") def test_non_image_file_not_identified(self): _common.touch(os.path.join(self.dpath, b"a.txt")) with pytest.raises(StopIteration): next(self.source.get(None, self.settings, [self.dpath])) def test_cautious_skips_fallback(self): _common.touch(os.path.join(self.dpath, b"a.jpg")) self.settings.cautious = True with pytest.raises(StopIteration): next(self.source.get(None, self.settings, [self.dpath])) def test_empty_dir(self): with pytest.raises(StopIteration): next(self.source.get(None, self.settings, [self.dpath])) def test_precedence_amongst_correct_files(self): images = [b"front-cover.jpg", b"front.jpg", b"back.jpg"] paths = [os.path.join(self.dpath, i) for i in images] for p in paths: _common.touch(p) self.settings.cover_names = ["cover", "front", "back"] candidates = [ candidate.path for candidate in self.source.get(None, self.settings, [self.dpath]) ] assert candidates == paths class CombinedTest(FetchImageTestCase, CAAHelper): ASIN = "xxxx" MBID = "releaseid" AMAZON_URL = "https://images.amazon.com/images/P/{}.01.LZZZZZZZ.jpg".format( ASIN ) AAO_URL = "https://www.albumart.org/index_detail.php?asin={}".format(ASIN) def setUp(self): super().setUp() self.dpath = os.path.join(self.temp_dir, b"arttest") os.mkdir(syspath(self.dpath)) def test_main_interface_returns_amazon_art(self): self.mock_response(self.AMAZON_URL) album = _common.Bag(asin=self.ASIN) candidate = self.plugin.art_for_album(album, None) assert candidate is not None def test_main_interface_returns_none_for_missing_asin_and_path(self): album = _common.Bag() candidate = self.plugin.art_for_album(album, None) assert candidate is None def test_main_interface_gives_precedence_to_fs_art(self): _common.touch(os.path.join(self.dpath, b"art.jpg")) self.mock_response(self.AMAZON_URL) album = _common.Bag(asin=self.ASIN) candidate = self.plugin.art_for_album(album, [self.dpath]) assert candidate is not None assert candidate.path == os.path.join(self.dpath, b"art.jpg") def test_main_interface_falls_back_to_amazon(self): self.mock_response(self.AMAZON_URL) album = _common.Bag(asin=self.ASIN) candidate = self.plugin.art_for_album(album, [self.dpath]) assert candidate is not None assert not candidate.path.startswith(self.dpath) def test_main_interface_tries_amazon_before_aao(self): self.mock_response(self.AMAZON_URL) album = _common.Bag(asin=self.ASIN) self.plugin.art_for_album(album, [self.dpath]) assert len(responses.calls) == 1 assert responses.calls[0].request.url == self.AMAZON_URL def test_main_interface_falls_back_to_aao(self): self.mock_response(self.AMAZON_URL, content_type="text/html") album = _common.Bag(asin=self.ASIN) self.plugin.art_for_album(album, [self.dpath]) assert responses.calls[-1].request.url == self.AAO_URL def test_main_interface_uses_caa_when_mbid_available(self): self.mock_caa_response(self.RELEASE_URL, self.RESPONSE_RELEASE) self.mock_caa_response(self.GROUP_URL, self.RESPONSE_GROUP) self.mock_response( "http://coverartarchive.org/release/rid/12345.gif", content_type="image/gif", ) self.mock_response( "http://coverartarchive.org/release/rid/12345.jpg", content_type="image/jpeg", ) album = _common.Bag( mb_albumid=self.MBID_RELASE, mb_releasegroupid=self.MBID_GROUP, asin=self.ASIN, ) candidate = self.plugin.art_for_album(album, None) assert candidate is not None assert len(responses.calls) == 3 assert responses.calls[0].request.url == self.RELEASE_URL def test_local_only_does_not_access_network(self): album = _common.Bag(mb_albumid=self.MBID, asin=self.ASIN) self.plugin.art_for_album(album, None, local_only=True) assert len(responses.calls) == 0 def test_local_only_gets_fs_image(self): _common.touch(os.path.join(self.dpath, b"art.jpg")) album = _common.Bag(mb_albumid=self.MBID, asin=self.ASIN) candidate = self.plugin.art_for_album( album, [self.dpath], local_only=True ) assert candidate is not None assert candidate.path == os.path.join(self.dpath, b"art.jpg") assert len(responses.calls) == 0 class AAOTest(UseThePlugin): ASIN = "xxxx" AAO_URL = f"https://www.albumart.org/index_detail.php?asin={ASIN}" def setUp(self): super().setUp() self.source = fetchart.AlbumArtOrg(logger, self.plugin.config) self.settings = Settings() @responses.activate def run(self, *args, **kwargs): super().run(*args, **kwargs) def mock_response(self, url, body): responses.add(responses.GET, url, body=body, content_type="text/html") def test_aao_scraper_finds_image(self): body = """ <br /> <a href=\"TARGET_URL\" title=\"View larger image\" class=\"thickbox\" style=\"color: #7E9DA2; text-decoration:none;\"> <img src=\"http://www.albumart.org/images/zoom-icon.jpg\" alt=\"View larger image\" width=\"17\" height=\"15\" border=\"0\"/></a> """ self.mock_response(self.AAO_URL, body) album = _common.Bag(asin=self.ASIN) candidate = next(self.source.get(album, self.settings, [])) assert candidate.url == "TARGET_URL" def test_aao_scraper_returns_no_result_when_no_image_present(self): self.mock_response(self.AAO_URL, "blah blah") album = _common.Bag(asin=self.ASIN) with pytest.raises(StopIteration): next(self.source.get(album, self.settings, [])) class ITunesStoreTest(UseThePlugin): def setUp(self): super().setUp() self.source = fetchart.ITunesStore(logger, self.plugin.config) self.settings = Settings() self.album = _common.Bag(albumartist="some artist", album="some album") @responses.activate def run(self, *args, **kwargs): super().run(*args, **kwargs) def mock_response(self, url, json): responses.add( responses.GET, url, body=json, content_type="application/json" ) def test_itunesstore_finds_image(self): json = """{ "results": [ { "artistName": "some artist", "collectionName": "some album", "artworkUrl100": "url_to_the_image" } ] }""" self.mock_response(fetchart.ITunesStore.API_URL, json) candidate = next(self.source.get(self.album, self.settings, [])) assert candidate.url == "url_to_the_image" assert candidate.match == fetchart.Candidate.MATCH_EXACT def test_itunesstore_no_result(self): json = '{"results": []}' self.mock_response(fetchart.ITunesStore.API_URL, json) expected = "got no results" with capture_log("beets.test_art") as logs: with pytest.raises(StopIteration): next(self.source.get(self.album, self.settings, [])) assert expected in logs[1] def test_itunesstore_requestexception(self): responses.add( responses.GET, fetchart.ITunesStore.API_URL, json={"error": "not found"}, status=404, ) expected = "iTunes search failed: 404 Client Error" with capture_log("beets.test_art") as logs: with pytest.raises(StopIteration): next(self.source.get(self.album, self.settings, [])) assert expected in logs[1] def test_itunesstore_fallback_match(self): json = """{ "results": [ { "collectionName": "some album", "artworkUrl100": "url_to_the_image" } ] }""" self.mock_response(fetchart.ITunesStore.API_URL, json) candidate = next(self.source.get(self.album, self.settings, [])) assert candidate.url == "url_to_the_image" assert candidate.match == fetchart.Candidate.MATCH_FALLBACK def test_itunesstore_returns_result_without_artwork(self): json = """{ "results": [ { "artistName": "some artist", "collectionName": "some album" } ] }""" self.mock_response(fetchart.ITunesStore.API_URL, json) expected = "Malformed itunes candidate" with capture_log("beets.test_art") as logs: with pytest.raises(StopIteration): next(self.source.get(self.album, self.settings, [])) assert expected in logs[1] def test_itunesstore_returns_no_result_when_error_received(self): json = '{"error": {"errors": [{"reason": "some reason"}]}}' self.mock_response(fetchart.ITunesStore.API_URL, json) expected = "not found in json. Fields are" with capture_log("beets.test_art") as logs: with pytest.raises(StopIteration): next(self.source.get(self.album, self.settings, [])) assert expected in logs[1] def test_itunesstore_returns_no_result_with_malformed_response(self): json = """bla blup""" self.mock_response(fetchart.ITunesStore.API_URL, json) expected = "Could not decode json response:" with capture_log("beets.test_art") as logs: with pytest.raises(StopIteration): next(self.source.get(self.album, self.settings, [])) assert expected in logs[1] class GoogleImageTest(UseThePlugin): def setUp(self): super().setUp() self.source = fetchart.GoogleImages(logger, self.plugin.config) self.settings = Settings() @responses.activate def run(self, *args, **kwargs): super().run(*args, **kwargs) def mock_response(self, url, json): responses.add( responses.GET, url, body=json, content_type="application/json" ) def test_google_art_finds_image(self): album = _common.Bag(albumartist="some artist", album="some album") json = '{"items": [{"link": "url_to_the_image"}]}' self.mock_response(fetchart.GoogleImages.URL, json) candidate = next(self.source.get(album, self.settings, [])) assert candidate.url == "url_to_the_image" def test_google_art_returns_no_result_when_error_received(self): album = _common.Bag(albumartist="some artist", album="some album") json = '{"error": {"errors": [{"reason": "some reason"}]}}' self.mock_response(fetchart.GoogleImages.URL, json) with pytest.raises(StopIteration): next(self.source.get(album, self.settings, [])) def test_google_art_returns_no_result_with_malformed_response(self): album = _common.Bag(albumartist="some artist", album="some album") json = """bla blup""" self.mock_response(fetchart.GoogleImages.URL, json) with pytest.raises(StopIteration): next(self.source.get(album, self.settings, [])) class CoverArtArchiveTest(UseThePlugin, CAAHelper): def setUp(self): super().setUp() self.source = fetchart.CoverArtArchive(logger, self.plugin.config) self.settings = Settings(maxwidth=0) @responses.activate def run(self, *args, **kwargs): super().run(*args, **kwargs) def test_caa_finds_image(self): album = _common.Bag( mb_albumid=self.MBID_RELASE, mb_releasegroupid=self.MBID_GROUP ) self.mock_caa_response(self.RELEASE_URL, self.RESPONSE_RELEASE) self.mock_caa_response(self.GROUP_URL, self.RESPONSE_GROUP) candidates = list(self.source.get(album, self.settings, [])) assert len(candidates) == 3 assert len(responses.calls) == 2 assert responses.calls[0].request.url == self.RELEASE_URL def test_fetchart_uses_caa_pre_sized_maxwidth_thumbs(self): # CAA provides pre-sized thumbnails of width 250px, 500px, and 1200px # We only test with one of them here maxwidth = 1200 self.settings = Settings(maxwidth=maxwidth) album = _common.Bag( mb_albumid=self.MBID_RELASE, mb_releasegroupid=self.MBID_GROUP ) self.mock_caa_response(self.RELEASE_URL, self.RESPONSE_RELEASE) self.mock_caa_response(self.GROUP_URL, self.RESPONSE_GROUP) candidates = list(self.source.get(album, self.settings, [])) assert len(candidates) == 3 for candidate in candidates: assert f"-{maxwidth}.jpg" in candidate.url def test_caa_finds_image_if_maxwidth_is_set_and_thumbnails_is_empty(self): # CAA provides pre-sized thumbnails of width 250px, 500px, and 1200px # We only test with one of them here maxwidth = 1200 self.settings = Settings(maxwidth=maxwidth) album = _common.Bag( mb_albumid=self.MBID_RELASE, mb_releasegroupid=self.MBID_GROUP ) self.mock_caa_response( self.RELEASE_URL, self.RESPONSE_RELEASE_WITHOUT_THUMBNAILS ) self.mock_caa_response( self.GROUP_URL, self.RESPONSE_GROUP_WITHOUT_THUMBNAILS, ) candidates = list(self.source.get(album, self.settings, [])) assert len(candidates) == 3 for candidate in candidates: assert f"-{maxwidth}.jpg" not in candidate.url class FanartTVTest(UseThePlugin): RESPONSE_MULTIPLE = """{ "name": "artistname", "mbid_id": "artistid", "albums": { "thereleasegroupid": { "albumcover": [ { "id": "24", "url": "http://example.com/1.jpg", "likes": "0" }, { "id": "42", "url": "http://example.com/2.jpg", "likes": "0" }, { "id": "23", "url": "http://example.com/3.jpg", "likes": "0" } ], "cdart": [ { "id": "123", "url": "http://example.com/4.jpg", "likes": "0", "disc": "1", "size": "1000" } ] } } }""" RESPONSE_NO_ART = """{ "name": "artistname", "mbid_id": "artistid", "albums": { "thereleasegroupid": { "cdart": [ { "id": "123", "url": "http://example.com/4.jpg", "likes": "0", "disc": "1", "size": "1000" } ] } } }""" RESPONSE_ERROR = """{ "status": "error", "error message": "the error message" }""" RESPONSE_MALFORMED = "bla blup" def setUp(self): super().setUp() self.source = fetchart.FanartTV(logger, self.plugin.config) self.settings = Settings() @responses.activate def run(self, *args, **kwargs): super().run(*args, **kwargs) def mock_response(self, url, json): responses.add( responses.GET, url, body=json, content_type="application/json" ) def test_fanarttv_finds_image(self): album = _common.Bag(mb_releasegroupid="thereleasegroupid") self.mock_response( fetchart.FanartTV.API_ALBUMS + "thereleasegroupid", self.RESPONSE_MULTIPLE, ) candidate = next(self.source.get(album, self.settings, [])) assert candidate.url == "http://example.com/1.jpg" def test_fanarttv_returns_no_result_when_error_received(self): album = _common.Bag(mb_releasegroupid="thereleasegroupid") self.mock_response( fetchart.FanartTV.API_ALBUMS + "thereleasegroupid", self.RESPONSE_ERROR, ) with pytest.raises(StopIteration): next(self.source.get(album, self.settings, [])) def test_fanarttv_returns_no_result_with_malformed_response(self): album = _common.Bag(mb_releasegroupid="thereleasegroupid") self.mock_response( fetchart.FanartTV.API_ALBUMS + "thereleasegroupid", self.RESPONSE_MALFORMED, ) with pytest.raises(StopIteration): next(self.source.get(album, self.settings, [])) def test_fanarttv_only_other_images(self): # The source used to fail when there were images present, but no cover album = _common.Bag(mb_releasegroupid="thereleasegroupid") self.mock_response( fetchart.FanartTV.API_ALBUMS + "thereleasegroupid", self.RESPONSE_NO_ART, ) with pytest.raises(StopIteration): next(self.source.get(album, self.settings, [])) @_common.slow_test() class ArtImporterTest(UseThePlugin): def setUp(self): super().setUp() # Mock the album art fetcher to always return our test file. self.art_file = os.path.join(self.temp_dir, b"tmpcover.jpg") _common.touch(self.art_file) self.old_afa = self.plugin.art_for_album self.afa_response = fetchart.Candidate(logger, path=self.art_file) def art_for_album(i, p, local_only=False): return self.afa_response self.plugin.art_for_album = art_for_album # Test library. os.mkdir(syspath(os.path.join(self.libdir, b"album"))) itempath = os.path.join(self.libdir, b"album", b"test.mp3") shutil.copyfile( syspath(os.path.join(_common.RSRC, b"full.mp3")), syspath(itempath), ) self.i = _common.item() self.i.path = itempath self.album = self.lib.add_album([self.i]) self.lib._connection().commit() # The import configuration. self.session = _common.import_session(self.lib) # Import task for the coroutine. self.task = importer.ImportTask(None, None, [self.i]) self.task.is_album = True self.task.album = self.album info = AlbumInfo( album="some album", album_id="albumid", artist="some artist", artist_id="artistid", tracks=[], ) self.task.set_choice(AlbumMatch(0, info, {}, set(), set())) def tearDown(self): super().tearDown() self.plugin.art_for_album = self.old_afa def _fetch_art(self, should_exist): """Execute the fetch_art coroutine for the task and return the album's resulting artpath. ``should_exist`` specifies whether to assert that art path was set (to the correct value) or or that the path was not set. """ # Execute the two relevant parts of the importer. self.plugin.fetch_art(self.session, self.task) self.plugin.assign_art(self.session, self.task) artpath = self.lib.albums()[0].artpath if should_exist: assert artpath == os.path.join( os.path.dirname(self.i.path), b"cover.jpg" ) self.assertExists(artpath) else: assert artpath is None return artpath def test_fetch_art(self): assert not self.lib.albums()[0].artpath self._fetch_art(True) def test_art_not_found(self): self.afa_response = None self._fetch_art(False) def test_no_art_for_singleton(self): self.task.is_album = False self._fetch_art(False) def test_leave_original_file_in_place(self): self._fetch_art(True) self.assertExists(self.art_file) def test_delete_original_file(self): prev_move = config["import"]["move"].get() try: config["import"]["move"] = True self._fetch_art(True) self.assertNotExists(self.art_file) finally: config["import"]["move"] = prev_move def test_do_not_delete_original_if_already_in_place(self): artdest = os.path.join(os.path.dirname(self.i.path), b"cover.jpg") shutil.copyfile(syspath(self.art_file), syspath(artdest)) self.afa_response = fetchart.Candidate(logger, path=artdest) self._fetch_art(True) def test_fetch_art_if_imported_file_deleted(self): # See #1126. Test the following scenario: # - Album art imported, `album.artpath` set. # - Imported album art file subsequently deleted (by user or other # program). # `fetchart` should import album art again instead of printing the # message "<album> has album art". self._fetch_art(True) util.remove(self.album.artpath) self.plugin.batch_fetch_art( self.lib, self.lib.albums(), force=False, quiet=False ) self.assertExists(self.album.artpath) class ArtForAlbumTest(UseThePlugin): """Tests that fetchart.art_for_album respects the scale & filesize configurations (e.g., minwidth, enforce_ratio, max_filesize) """ IMG_225x225 = os.path.join(_common.RSRC, b"abbey.jpg") IMG_348x348 = os.path.join(_common.RSRC, b"abbey-different.jpg") IMG_500x490 = os.path.join(_common.RSRC, b"abbey-similar.jpg") IMG_225x225_SIZE = os.stat(util.syspath(IMG_225x225)).st_size IMG_348x348_SIZE = os.stat(util.syspath(IMG_348x348)).st_size RESIZE_OP = "resize" DEINTERLACE_OP = "deinterlace" REFORMAT_OP = "reformat" def setUp(self): super().setUp() self.old_fs_source_get = fetchart.FileSystem.get def fs_source_get(_self, album, settings, paths): if paths: yield fetchart.Candidate(logger, path=self.image_file) fetchart.FileSystem.get = fs_source_get self.album = _common.Bag() def tearDown(self): fetchart.FileSystem.get = self.old_fs_source_get super().tearDown() def assertImageIsValidArt(self, image_file, should_exist): self.assertExists(image_file) self.image_file = image_file candidate = self.plugin.art_for_album(self.album, [""], True) if should_exist: assert candidate is not None assert candidate.path == self.image_file self.assertExists(candidate.path) else: assert candidate is None def _assert_image_operated(self, image_file, operation, should_operate): self.image_file = image_file with patch.object( ArtResizer.shared, operation, return_value=self.image_file ) as mock_operation: self.plugin.art_for_album(self.album, [""], True) assert mock_operation.called == should_operate def _require_backend(self): """Skip the test if the art resizer doesn't have ImageMagick or PIL (so comparisons and measurements are unavailable). """ if not ArtResizer.shared.local: self.skipTest("ArtResizer has no local imaging backend available") def test_respect_minwidth(self): self._require_backend() self.plugin.minwidth = 300 self.assertImageIsValidArt(self.IMG_225x225, False) self.assertImageIsValidArt(self.IMG_348x348, True) def test_respect_enforce_ratio_yes(self): self._require_backend() self.plugin.enforce_ratio = True self.assertImageIsValidArt(self.IMG_500x490, False) self.assertImageIsValidArt(self.IMG_225x225, True) def test_respect_enforce_ratio_no(self): self.plugin.enforce_ratio = False self.assertImageIsValidArt(self.IMG_500x490, True) def test_respect_enforce_ratio_px_above(self): self._require_backend() self.plugin.enforce_ratio = True self.plugin.margin_px = 5 self.assertImageIsValidArt(self.IMG_500x490, False) def test_respect_enforce_ratio_px_below(self): self._require_backend() self.plugin.enforce_ratio = True self.plugin.margin_px = 15 self.assertImageIsValidArt(self.IMG_500x490, True) def test_respect_enforce_ratio_percent_above(self): self._require_backend() self.plugin.enforce_ratio = True self.plugin.margin_percent = (500 - 490) / 500 * 0.5 self.assertImageIsValidArt(self.IMG_500x490, False) def test_respect_enforce_ratio_percent_below(self): self._require_backend() self.plugin.enforce_ratio = True self.plugin.margin_percent = (500 - 490) / 500 * 1.5 self.assertImageIsValidArt(self.IMG_500x490, True) def test_resize_if_necessary(self): self._require_backend() self.plugin.maxwidth = 300 self._assert_image_operated(self.IMG_225x225, self.RESIZE_OP, False) self._assert_image_operated(self.IMG_348x348, self.RESIZE_OP, True) def test_fileresize(self): self._require_backend() self.plugin.max_filesize = self.IMG_225x225_SIZE // 2 self._assert_image_operated(self.IMG_225x225, self.RESIZE_OP, True) def test_fileresize_if_necessary(self): self._require_backend() self.plugin.max_filesize = self.IMG_225x225_SIZE self._assert_image_operated(self.IMG_225x225, self.RESIZE_OP, False) self.assertImageIsValidArt(self.IMG_225x225, True) def test_fileresize_no_scale(self): self._require_backend() self.plugin.maxwidth = 300 self.plugin.max_filesize = self.IMG_225x225_SIZE // 2 self._assert_image_operated(self.IMG_225x225, self.RESIZE_OP, True) def test_fileresize_and_scale(self): self._require_backend() self.plugin.maxwidth = 200 self.plugin.max_filesize = self.IMG_225x225_SIZE // 2 self._assert_image_operated(self.IMG_225x225, self.RESIZE_OP, True) def test_deinterlace(self): self._require_backend() self.plugin.deinterlace = True self._assert_image_operated(self.IMG_225x225, self.DEINTERLACE_OP, True) self.plugin.deinterlace = False self._assert_image_operated( self.IMG_225x225, self.DEINTERLACE_OP, False ) def test_deinterlace_and_resize(self): self._require_backend() self.plugin.maxwidth = 300 self.plugin.deinterlace = True self._assert_image_operated(self.IMG_348x348, self.DEINTERLACE_OP, True) self._assert_image_operated(self.IMG_348x348, self.RESIZE_OP, True) class DeprecatedConfigTest(BeetsTestCase): """While refactoring the plugin, the remote_priority option was deprecated, and a new codepath should translate its effect. Check that it actually does so. """ # If we subclassed UseThePlugin, the configuration change would either be # overwritten by BeetsTestCase or be set after constructing the # plugin object def setUp(self): super().setUp() config["fetchart"]["remote_priority"] = True self.plugin = fetchart.FetchArtPlugin() def test_moves_filesystem_to_end(self): assert isinstance(self.plugin.sources[-1], fetchart.FileSystem) class EnforceRatioConfigTest(BeetsTestCase): """Throw some data at the regexes.""" def _load_with_config(self, values, should_raise): if should_raise: for v in values: config["fetchart"]["enforce_ratio"] = v with pytest.raises(confuse.ConfigValueError): fetchart.FetchArtPlugin() else: for v in values: config["fetchart"]["enforce_ratio"] = v fetchart.FetchArtPlugin() def test_px(self): self._load_with_config("0px 4px 12px 123px".split(), False) self._load_with_config("00px stuff5px".split(), True) def test_percent(self): self._load_with_config("0% 0.00% 5.1% 5% 100%".split(), False) self._load_with_config("00% 1.234% foo5% 100.1%".split(), True) ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������beetbox-beets-01f1faf/test/plugins/test_aura.py�����������������������������������������������������0000664�0000000�0000000�00000010360�14723254774�0021750�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������import os from http import HTTPStatus from pathlib import Path from typing import Any, Dict, Optional import pytest from flask.testing import Client from beets.test.helper import TestHelper @pytest.fixture(scope="session", autouse=True) def helper(): helper = TestHelper() helper.setup_beets() yield helper helper.teardown_beets() @pytest.fixture(scope="session") def app(helper): from beetsplug.aura import create_app app = create_app() app.config["lib"] = helper.lib return app @pytest.fixture(scope="session") def item(helper): return helper.add_item_fixture( album="Album", title="Title", artist="Artist", albumartist="Album Artist", ) @pytest.fixture(scope="session") def album(helper, item): return helper.lib.add_album([item]) @pytest.fixture(scope="session", autouse=True) def _other_album_and_item(helper): """Add another item and album to prove that filtering works.""" item = helper.add_item_fixture( album="Other Album", title="Other Title", artist="Other Artist", albumartist="Other Album Artist", ) helper.lib.add_album([item]) class TestAuraResponse: @pytest.fixture def get_response_data(self, client: Client, item): """Return a callback accepting `endpoint` and `params` parameters.""" def get( endpoint: str, params: Dict[str, str] ) -> Optional[Dict[str, Any]]: """Add additional `params` and GET the given endpoint. `include` parameter is added to every call to check that the functionality that fetches related entities works. Before returning the response data, ensure that the request is successful. """ response = client.get( endpoint, query_string={"include": "tracks,artists,albums", **params}, ) assert response.status_code == HTTPStatus.OK return response.json return get @pytest.fixture(scope="class") def track_document(self, item, album): return { "type": "track", "id": str(item.id), "attributes": { "album": item.album, "albumartist": item.albumartist, "artist": item.artist, "size": Path(os.fsdecode(item.path)).stat().st_size, "title": item.title, "track": 1, }, "relationships": { "albums": {"data": [{"id": str(album.id), "type": "album"}]}, "artists": {"data": [{"id": item.artist, "type": "artist"}]}, }, } @pytest.fixture(scope="class") def artist_document(self, item): return { "type": "artist", "id": item.artist, "attributes": {"name": item.artist}, "relationships": { "tracks": {"data": [{"id": str(item.id), "type": "track"}]} }, } @pytest.fixture(scope="class") def album_document(self, album): return { "type": "album", "id": str(album.id), "attributes": {"artist": album.albumartist, "title": album.album}, "relationships": { "tracks": {"data": [{"id": str(album.id), "type": "track"}]} }, } def test_tracks( self, get_response_data, item, album_document, artist_document, track_document, ): data = get_response_data("/aura/tracks", {"filter[title]": item.title}) assert data == { "data": [track_document], "included": [artist_document, album_document], } def test_artists( self, get_response_data, item, artist_document, track_document ): data = get_response_data( "/aura/artists", {"filter[artist]": item.artist} ) assert data == {"data": [artist_document], "included": [track_document]} def test_albums( self, get_response_data, album, album_document, track_document ): data = get_response_data("/aura/albums", {"filter[album]": album.album}) assert data == {"data": [album_document], "included": [track_document]} ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������beetbox-beets-01f1faf/test/plugins/test_autobpm.py��������������������������������������������������0000664�0000000�0000000�00000002161�14723254774�0022467�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������import importlib.util import os import pytest from beets.test.helper import ImportHelper, PluginMixin github_ci = os.environ.get("GITHUB_ACTIONS") == "true" if not github_ci and not importlib.util.find_spec("librosa"): pytest.skip("librosa isn't available", allow_module_level=True) class TestAutoBPMPlugin(PluginMixin, ImportHelper): plugin = "autobpm" @pytest.fixture(scope="class", name="lib") def fixture_lib(self): self.setup_beets() yield self.lib self.teardown_beets() @pytest.fixture(scope="class") def item(self): return self.add_item_fixture() @pytest.fixture(scope="class") def importer(self, lib): self.import_media = [] self.prepare_album_for_import(1) track = self.import_media[0] track.bpm = None track.save() return self.setup_importer(autotag=False) def test_command(self, lib, item): self.run_command("autobpm", lib=lib) item.load() assert item.bpm == 117 def test_import(self, lib, importer): importer.run() assert lib.items().get().bpm == 117 ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������beetbox-beets-01f1faf/test/plugins/test_bareasc.py��������������������������������������������������0000664�0000000�0000000�00000005625�14723254774�0022430�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# This file is part of beets. # Copyright 2021, Graham R. Cobb. """Tests for the 'bareasc' plugin.""" from beets import logging from beets.test.helper import PluginTestCase, capture_stdout class BareascPluginTest(PluginTestCase): """Test bare ASCII query matching.""" plugin = "bareasc" def setUp(self): """Set up test environment for bare ASCII query matching.""" super().setUp() self.log = logging.getLogger("beets.web") self.config["bareasc"]["prefix"] = "#" # Add library elements. Note that self.lib.add overrides any "id=<n>" # and assigns the next free id number. self.add_item(title="with accents", album_id=2, artist="AntonĂ­n Dvořák") self.add_item(title="without accents", artist="AntonĂ­n Dvorak") self.add_item(title="with umlaut", album_id=2, artist="BrĂĽggen") self.add_item(title="without umlaut or e", artist="Bruggen") self.add_item(title="without umlaut with e", artist="Brueggen") def test_bareasc_search(self): test_cases = [ ( "dvorak", ["without accents"], ), # Normal search, no accents, not using bare-ASCII match. ( "dvořák", ["with accents"], ), # Normal search, with accents, not using bare-ASCII match. ( "#dvorak", ["without accents", "with accents"], ), # Bare-ASCII search, no accents. ( "#dvořák", ["without accents", "with accents"], ), # Bare-ASCII search, with accents. ( "#dvořäk", ["without accents", "with accents"], ), # Bare-ASCII search, with incorrect accent. ( "#Bruggen", ["without umlaut or e", "with umlaut"], ), # Bare-ASCII search, with no umlaut. ( "#BrĂĽggen", ["without umlaut or e", "with umlaut"], ), # Bare-ASCII search, with umlaut. ] for query, expected_titles in test_cases: with self.subTest(query=query, expected_titles=expected_titles): items = self.lib.items(query) assert [item.title for item in items] == expected_titles def test_bareasc_list_output(self): """Bare-ASCII version of list command - check output.""" with capture_stdout() as output: self.run_command("bareasc", "with accents") assert "Antonin Dvorak" in output.getvalue() def test_bareasc_format_output(self): """Bare-ASCII version of list -f command - check output.""" with capture_stdout() as output: self.run_command( "bareasc", "with accents", "-f", "$artist:: $title" ) assert "Antonin Dvorak:: with accents\n" == output.getvalue() �����������������������������������������������������������������������������������������������������������beetbox-beets-01f1faf/test/plugins/test_beatport.py�������������������������������������������������0000664�0000000�0000000�00000053656�14723254774�0022657�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# This file is part of beets. # Copyright 2016, Adrian Sampson. # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the # "Software"), to deal in the Software without restriction, including # without limitation the rights to use, copy, modify, merge, publish, # distribute, sublicense, and/or sell copies of the Software, and to # permit persons to whom the Software is furnished to do so, subject to # the following conditions: # # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. """Tests for the 'beatport' plugin.""" from datetime import timedelta from beets.test import _common from beets.test.helper import BeetsTestCase from beetsplug import beatport class BeatportTest(BeetsTestCase): def _make_release_response(self): """Returns a dict that mimics a response from the beatport API. The results were retrieved from: https://oauth-api.beatport.com/catalog/3/releases?id=1742984 The list of elements on the returned dict is incomplete, including just those required for the tests on this class. """ results = { "id": 1742984, "type": "release", "name": "Charade", "slug": "charade", "releaseDate": "2016-04-11", "publishDate": "2016-04-11", "audioFormat": "", "category": "Release", "currentStatus": "General Content", "catalogNumber": "GR089", "description": "", "label": { "id": 24539, "name": "Gravitas Recordings", "type": "label", "slug": "gravitas-recordings", }, "artists": [ { "id": 326158, "name": "Supersillyus", "slug": "supersillyus", "type": "artist", } ], "genres": [ {"id": 9, "name": "Breaks", "slug": "breaks", "type": "genre"} ], } return results def _make_tracks_response(self): """Return a list that mimics a response from the beatport API. The results were retrieved from: https://oauth-api.beatport.com/catalog/3/tracks?releaseId=1742984 The list of elements on the returned list is incomplete, including just those required for the tests on this class. """ results = [ { "id": 7817567, "type": "track", "sku": "track-7817567", "name": "Mirage a Trois", "trackNumber": 1, "mixName": "Original Mix", "title": "Mirage a Trois (Original Mix)", "slug": "mirage-a-trois-original-mix", "releaseDate": "2016-04-11", "publishDate": "2016-04-11", "currentStatus": "General Content", "length": "7:05", "lengthMs": 425421, "bpm": 90, "key": { "standard": { "letter": "G", "sharp": False, "flat": False, "chord": "minor", }, "shortName": "Gmin", }, "artists": [ { "id": 326158, "name": "Supersillyus", "slug": "supersillyus", "type": "artist", } ], "genres": [ { "id": 9, "name": "Breaks", "slug": "breaks", "type": "genre", } ], "subGenres": [ { "id": 209, "name": "Glitch Hop", "slug": "glitch-hop", "type": "subgenre", } ], "release": { "id": 1742984, "name": "Charade", "type": "release", "slug": "charade", }, "label": { "id": 24539, "name": "Gravitas Recordings", "type": "label", "slug": "gravitas-recordings", "status": True, }, }, { "id": 7817568, "type": "track", "sku": "track-7817568", "name": "Aeon Bahamut", "trackNumber": 2, "mixName": "Original Mix", "title": "Aeon Bahamut (Original Mix)", "slug": "aeon-bahamut-original-mix", "releaseDate": "2016-04-11", "publishDate": "2016-04-11", "currentStatus": "General Content", "length": "7:38", "lengthMs": 458000, "bpm": 100, "key": { "standard": { "letter": "G", "sharp": False, "flat": False, "chord": "major", }, "shortName": "Gmaj", }, "artists": [ { "id": 326158, "name": "Supersillyus", "slug": "supersillyus", "type": "artist", } ], "genres": [ { "id": 9, "name": "Breaks", "slug": "breaks", "type": "genre", } ], "subGenres": [ { "id": 209, "name": "Glitch Hop", "slug": "glitch-hop", "type": "subgenre", } ], "release": { "id": 1742984, "name": "Charade", "type": "release", "slug": "charade", }, "label": { "id": 24539, "name": "Gravitas Recordings", "type": "label", "slug": "gravitas-recordings", "status": True, }, }, { "id": 7817569, "type": "track", "sku": "track-7817569", "name": "Trancendental Medication", "trackNumber": 3, "mixName": "Original Mix", "title": "Trancendental Medication (Original Mix)", "slug": "trancendental-medication-original-mix", "releaseDate": "2016-04-11", "publishDate": "2016-04-11", "currentStatus": "General Content", "length": "1:08", "lengthMs": 68571, "bpm": 141, "key": { "standard": { "letter": "F", "sharp": False, "flat": False, "chord": "major", }, "shortName": "Fmaj", }, "artists": [ { "id": 326158, "name": "Supersillyus", "slug": "supersillyus", "type": "artist", } ], "genres": [ { "id": 9, "name": "Breaks", "slug": "breaks", "type": "genre", } ], "subGenres": [ { "id": 209, "name": "Glitch Hop", "slug": "glitch-hop", "type": "subgenre", } ], "release": { "id": 1742984, "name": "Charade", "type": "release", "slug": "charade", }, "label": { "id": 24539, "name": "Gravitas Recordings", "type": "label", "slug": "gravitas-recordings", "status": True, }, }, { "id": 7817570, "type": "track", "sku": "track-7817570", "name": "A List of Instructions for When I'm Human", "trackNumber": 4, "mixName": "Original Mix", "title": "A List of Instructions for When I'm Human (Original Mix)", "slug": "a-list-of-instructions-for-when-im-human-original-mix", "releaseDate": "2016-04-11", "publishDate": "2016-04-11", "currentStatus": "General Content", "length": "6:57", "lengthMs": 417913, "bpm": 88, "key": { "standard": { "letter": "A", "sharp": False, "flat": False, "chord": "minor", }, "shortName": "Amin", }, "artists": [ { "id": 326158, "name": "Supersillyus", "slug": "supersillyus", "type": "artist", } ], "genres": [ { "id": 9, "name": "Breaks", "slug": "breaks", "type": "genre", } ], "subGenres": [ { "id": 209, "name": "Glitch Hop", "slug": "glitch-hop", "type": "subgenre", } ], "release": { "id": 1742984, "name": "Charade", "type": "release", "slug": "charade", }, "label": { "id": 24539, "name": "Gravitas Recordings", "type": "label", "slug": "gravitas-recordings", "status": True, }, }, { "id": 7817571, "type": "track", "sku": "track-7817571", "name": "The Great Shenanigan", "trackNumber": 5, "mixName": "Original Mix", "title": "The Great Shenanigan (Original Mix)", "slug": "the-great-shenanigan-original-mix", "releaseDate": "2016-04-11", "publishDate": "2016-04-11", "currentStatus": "General Content", "length": "9:49", "lengthMs": 589875, "bpm": 123, "key": { "standard": { "letter": "E", "sharp": False, "flat": True, "chord": "major", }, "shortName": "E♭maj", }, "artists": [ { "id": 326158, "name": "Supersillyus", "slug": "supersillyus", "type": "artist", } ], "genres": [ { "id": 9, "name": "Breaks", "slug": "breaks", "type": "genre", } ], "subGenres": [ { "id": 209, "name": "Glitch Hop", "slug": "glitch-hop", "type": "subgenre", } ], "release": { "id": 1742984, "name": "Charade", "type": "release", "slug": "charade", }, "label": { "id": 24539, "name": "Gravitas Recordings", "type": "label", "slug": "gravitas-recordings", "status": True, }, }, { "id": 7817572, "type": "track", "sku": "track-7817572", "name": "Charade", "trackNumber": 6, "mixName": "Original Mix", "title": "Charade (Original Mix)", "slug": "charade-original-mix", "releaseDate": "2016-04-11", "publishDate": "2016-04-11", "currentStatus": "General Content", "length": "7:05", "lengthMs": 425423, "bpm": 123, "key": { "standard": { "letter": "A", "sharp": False, "flat": False, "chord": "major", }, "shortName": "Amaj", }, "artists": [ { "id": 326158, "name": "Supersillyus", "slug": "supersillyus", "type": "artist", } ], "genres": [ { "id": 9, "name": "Breaks", "slug": "breaks", "type": "genre", } ], "subGenres": [ { "id": 209, "name": "Glitch Hop", "slug": "glitch-hop", "type": "subgenre", } ], "release": { "id": 1742984, "name": "Charade", "type": "release", "slug": "charade", }, "label": { "id": 24539, "name": "Gravitas Recordings", "type": "label", "slug": "gravitas-recordings", "status": True, }, }, ] return results def setUp(self): super().setUp() # Set up 'album'. response_release = self._make_release_response() self.album = beatport.BeatportRelease(response_release) # Set up 'tracks'. response_tracks = self._make_tracks_response() self.tracks = [beatport.BeatportTrack(t) for t in response_tracks] # Set up 'test_album'. self.test_album = self.mk_test_album() # Set up 'test_tracks' self.test_tracks = self.test_album.items() def mk_test_album(self): items = [_common.item() for _ in range(6)] for item in items: item.album = "Charade" item.catalognum = "GR089" item.label = "Gravitas Recordings" item.artist = "Supersillyus" item.year = 2016 item.comp = False item.label_name = "Gravitas Recordings" item.genre = "Glitch Hop" item.year = 2016 item.month = 4 item.day = 11 item.mix_name = "Original Mix" items[0].title = "Mirage a Trois" items[1].title = "Aeon Bahamut" items[2].title = "Trancendental Medication" items[3].title = "A List of Instructions for When I'm Human" items[4].title = "The Great Shenanigan" items[5].title = "Charade" items[0].length = timedelta(minutes=7, seconds=5).total_seconds() items[1].length = timedelta(minutes=7, seconds=38).total_seconds() items[2].length = timedelta(minutes=1, seconds=8).total_seconds() items[3].length = timedelta(minutes=6, seconds=57).total_seconds() items[4].length = timedelta(minutes=9, seconds=49).total_seconds() items[5].length = timedelta(minutes=7, seconds=5).total_seconds() items[0].url = "mirage-a-trois-original-mix" items[1].url = "aeon-bahamut-original-mix" items[2].url = "trancendental-medication-original-mix" items[3].url = "a-list-of-instructions-for-when-im-human-original-mix" items[4].url = "the-great-shenanigan-original-mix" items[5].url = "charade-original-mix" counter = 0 for item in items: counter += 1 item.track_number = counter items[0].bpm = 90 items[1].bpm = 100 items[2].bpm = 141 items[3].bpm = 88 items[4].bpm = 123 items[5].bpm = 123 items[0].initial_key = "Gmin" items[1].initial_key = "Gmaj" items[2].initial_key = "Fmaj" items[3].initial_key = "Amin" items[4].initial_key = "E♭maj" items[5].initial_key = "Amaj" for item in items: self.lib.add(item) album = self.lib.add_album(items) album.store() return album # Test BeatportRelease. def test_album_name_applied(self): assert self.album.name == self.test_album["album"] def test_catalog_number_applied(self): assert self.album.catalog_number == self.test_album["catalognum"] def test_label_applied(self): assert self.album.label_name == self.test_album["label"] def test_category_applied(self): assert self.album.category == "Release" def test_album_url_applied(self): assert self.album.url == "https://beatport.com/release/charade/1742984" # Test BeatportTrack. def test_title_applied(self): for track, test_track in zip(self.tracks, self.test_tracks): assert track.name == test_track.title def test_mix_name_applied(self): for track, test_track in zip(self.tracks, self.test_tracks): assert track.mix_name == test_track.mix_name def test_length_applied(self): for track, test_track in zip(self.tracks, self.test_tracks): assert int(track.length.total_seconds()) == int(test_track.length) def test_track_url_applied(self): # Specify beatport ids here because an 'item.id' is beets-internal. ids = [ 7817567, 7817568, 7817569, 7817570, 7817571, 7817572, ] # Concatenate with 'id' to pass strict equality test. for track, test_track, id in zip(self.tracks, self.test_tracks, ids): assert ( track.url == f"https://beatport.com/track/{test_track.url}/{id}" ) def test_bpm_applied(self): for track, test_track in zip(self.tracks, self.test_tracks): assert track.bpm == test_track.bpm def test_initial_key_applied(self): for track, test_track in zip(self.tracks, self.test_tracks): assert track.initial_key == test_track.initial_key def test_genre_applied(self): for track, test_track in zip(self.tracks, self.test_tracks): assert track.genre == test_track.genre class BeatportResponseEmptyTest(BeetsTestCase): def _make_tracks_response(self): results = [ { "id": 7817567, "name": "Mirage a Trois", "genres": [ { "id": 9, "name": "Breaks", "slug": "breaks", "type": "genre", } ], "subGenres": [ { "id": 209, "name": "Glitch Hop", "slug": "glitch-hop", "type": "subgenre", } ], } ] return results def setUp(self): super().setUp() # Set up 'tracks'. self.response_tracks = self._make_tracks_response() self.tracks = [beatport.BeatportTrack(t) for t in self.response_tracks] # Make alias to be congruent with class `BeatportTest`. self.test_tracks = self.response_tracks def test_response_tracks_empty(self): response_tracks = [] tracks = [beatport.BeatportTrack(t) for t in response_tracks] assert tracks == [] def test_sub_genre_empty_fallback(self): """No 'sub_genre' is provided. Test if fallback to 'genre' works.""" self.response_tracks[0]["subGenres"] = [] tracks = [beatport.BeatportTrack(t) for t in self.response_tracks] self.test_tracks[0]["subGenres"] = [] assert tracks[0].genre == self.test_tracks[0]["genres"][0]["name"] def test_genre_empty(self): """No 'genre' is provided. Test if 'sub_genre' is applied.""" self.response_tracks[0]["genres"] = [] tracks = [beatport.BeatportTrack(t) for t in self.response_tracks] self.test_tracks[0]["genres"] = [] assert tracks[0].genre == self.test_tracks[0]["subGenres"][0]["name"] ����������������������������������������������������������������������������������beetbox-beets-01f1faf/test/plugins/test_bucket.py���������������������������������������������������0000664�0000000�0000000�00000015417�14723254774�0022305�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# This file is part of beets. # Copyright 2016, Fabrice Laporte. # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the # "Software"), to deal in the Software without restriction, including # without limitation the rights to use, copy, modify, merge, publish, # distribute, sublicense, and/or sell copies of the Software, and to # permit persons to whom the Software is furnished to do so, subject to # the following conditions: # # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. """Tests for the 'bucket' plugin.""" import pytest from beets import config, ui from beets.test.helper import BeetsTestCase from beetsplug import bucket class BucketPluginTest(BeetsTestCase): def setUp(self): super().setUp() self.plugin = bucket.BucketPlugin() def _setup_config( self, bucket_year=[], bucket_alpha=[], bucket_alpha_regex={}, extrapolate=False, ): config["bucket"]["bucket_year"] = bucket_year config["bucket"]["bucket_alpha"] = bucket_alpha config["bucket"]["bucket_alpha_regex"] = bucket_alpha_regex config["bucket"]["extrapolate"] = extrapolate self.plugin.setup() def test_year_single_year(self): """If a single year is given, range starts from this year and stops at the year preceding the one of next bucket.""" self._setup_config(bucket_year=["1950s", "1970s"]) assert self.plugin._tmpl_bucket("1959") == "1950s" assert self.plugin._tmpl_bucket("1969") == "1950s" def test_year_single_year_last_folder(self): """If a single year is given for the last bucket, extend it to current year.""" self._setup_config(bucket_year=["1950", "1970"]) assert self.plugin._tmpl_bucket("2014") == "1970" assert self.plugin._tmpl_bucket("2025") == "2025" def test_year_two_years(self): """Buckets can be named with the 'from-to' syntax.""" self._setup_config(bucket_year=["1950-59", "1960-1969"]) assert self.plugin._tmpl_bucket("1959") == "1950-59" assert self.plugin._tmpl_bucket("1969") == "1960-1969" def test_year_multiple_years(self): """Buckets can be named by listing all the years""" self._setup_config(bucket_year=["1950,51,52,53"]) assert self.plugin._tmpl_bucket("1953") == "1950,51,52,53" assert self.plugin._tmpl_bucket("1974") == "1974" def test_year_out_of_range(self): """If no range match, return the year""" self._setup_config(bucket_year=["1950-59", "1960-69"]) assert self.plugin._tmpl_bucket("1974") == "1974" self._setup_config(bucket_year=[]) assert self.plugin._tmpl_bucket("1974") == "1974" def test_year_out_of_range_extrapolate(self): """If no defined range match, extrapolate all ranges using the most common syntax amongst existing buckets and return the matching one.""" self._setup_config(bucket_year=["1950-59", "1960-69"], extrapolate=True) assert self.plugin._tmpl_bucket("1914") == "1910-19" # pick single year format self._setup_config( bucket_year=["1962-81", "2002", "2012"], extrapolate=True ) assert self.plugin._tmpl_bucket("1983") == "1982" # pick from-end format self._setup_config( bucket_year=["1962-81", "2002", "2012-14"], extrapolate=True ) assert self.plugin._tmpl_bucket("1983") == "1982-01" # extrapolate add ranges, but never modifies existing ones self._setup_config( bucket_year=["1932", "1942", "1952", "1962-81", "2002"], extrapolate=True, ) assert self.plugin._tmpl_bucket("1975") == "1962-81" def test_alpha_all_chars(self): """Alphabet buckets can be named by listing all their chars""" self._setup_config(bucket_alpha=["ABCD", "FGH", "IJKL"]) assert self.plugin._tmpl_bucket("garry") == "FGH" def test_alpha_first_last_chars(self): """Alphabet buckets can be named by listing the 'from-to' syntax""" self._setup_config(bucket_alpha=["0->9", "A->D", "F-H", "I->Z"]) assert self.plugin._tmpl_bucket("garry") == "F-H" assert self.plugin._tmpl_bucket("2pac") == "0->9" def test_alpha_out_of_range(self): """If no range match, return the initial""" self._setup_config(bucket_alpha=["ABCD", "FGH", "IJKL"]) assert self.plugin._tmpl_bucket("errol") == "E" self._setup_config(bucket_alpha=[]) assert self.plugin._tmpl_bucket("errol") == "E" def test_alpha_regex(self): """Check regex is used""" self._setup_config( bucket_alpha=["foo", "bar"], bucket_alpha_regex={"foo": "^[a-d]", "bar": "^[e-z]"}, ) assert self.plugin._tmpl_bucket("alpha") == "foo" assert self.plugin._tmpl_bucket("delta") == "foo" assert self.plugin._tmpl_bucket("zeta") == "bar" assert self.plugin._tmpl_bucket("Alpha") == "A" def test_alpha_regex_mix(self): """Check mixing regex and non-regex is possible""" self._setup_config( bucket_alpha=["A - D", "E - L"], bucket_alpha_regex={"A - D": "^[0-9a-dA-D…äÄ]"}, ) assert self.plugin._tmpl_bucket("alpha") == "A - D" assert self.plugin._tmpl_bucket("Ă„rzte") == "A - D" assert self.plugin._tmpl_bucket("112") == "A - D" assert self.plugin._tmpl_bucket("…and Oceans") == "A - D" assert self.plugin._tmpl_bucket("Eagles") == "E - L" def test_bad_alpha_range_def(self): """If bad alpha range definition, a UserError is raised.""" with pytest.raises(ui.UserError): self._setup_config(bucket_alpha=["$%"]) def test_bad_year_range_def_no4digits(self): """If bad year range definition, a UserError is raised. Range origin must be expressed on 4 digits. """ with pytest.raises(ui.UserError): self._setup_config(bucket_year=["62-64"]) def test_bad_year_range_def_nodigits(self): """If bad year range definition, a UserError is raised. At least the range origin must be declared. """ with pytest.raises(ui.UserError): self._setup_config(bucket_year=["nodigits"]) def check_span_from_str(self, sstr, dfrom, dto): d = bucket.span_from_str(sstr) assert dfrom == d["from"] assert dto == d["to"] def test_span_from_str(self): self.check_span_from_str("1980 2000", 1980, 2000) self.check_span_from_str("1980 00", 1980, 2000) self.check_span_from_str("1930 00", 1930, 2000) self.check_span_from_str("1930 50", 1930, 1950) �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������beetbox-beets-01f1faf/test/plugins/test_convert.py��������������������������������������������������0000664�0000000�0000000�00000030303�14723254774�0022477�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# This file is part of beets. # Copyright 2016, Thomas Scholtes. # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the # "Software"), to deal in the Software without restriction, including # without limitation the rights to use, copy, modify, merge, publish, # distribute, sublicense, and/or sell copies of the Software, and to # permit persons to whom the Software is furnished to do so, subject to # the following conditions: # # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. import fnmatch import os.path import re import sys import unittest import pytest from mediafile import MediaFile from beets import util from beets.library import Item from beets.test import _common from beets.test.helper import ( AsIsImporterMixin, ImportHelper, PluginTestCase, capture_log, control_stdin, ) from beets.util import bytestring_path, displayable_path from beetsplug import convert def shell_quote(text): import shlex return shlex.quote(text) class ConvertMixin: def tagged_copy_cmd(self, tag): """Return a conversion command that copies files and appends `tag` to the copy. """ if re.search("[^a-zA-Z0-9]", tag): raise ValueError( "tag '{}' must only contain letters and digits".format(tag) ) # A Python script that copies the file and appends a tag. stub = os.path.join(_common.RSRC, b"convert_stub.py").decode("utf-8") return "{} {} $source $dest {}".format( shell_quote(sys.executable), shell_quote(stub), tag ) def assertFileTag(self, path, tag): """Assert that the path is a file and the files content ends with `tag`. """ display_tag = tag tag = tag.encode("utf-8") self.assertIsFile(path) with open(path, "rb") as f: f.seek(-len(display_tag), os.SEEK_END) assert ( f.read() == tag ), f"{displayable_path(path)} is not tagged with {display_tag}" def assertNoFileTag(self, path, tag): """Assert that the path is a file and the files content does not end with `tag`. """ display_tag = tag tag = tag.encode("utf-8") self.assertIsFile(path) with open(path, "rb") as f: f.seek(-len(tag), os.SEEK_END) assert ( f.read() != tag ), f"{displayable_path(path)} is unexpectedly tagged with {display_tag}" class ConvertTestCase(ConvertMixin, PluginTestCase): db_on_disk = True plugin = "convert" @_common.slow_test() class ImportConvertTest(AsIsImporterMixin, ImportHelper, ConvertTestCase): def setUp(self): super().setUp() self.config["convert"] = { "dest": os.path.join(self.temp_dir, b"convert"), "command": self.tagged_copy_cmd("convert"), # Enforce running convert "max_bitrate": 1, "auto": True, "quiet": False, } def test_import_converted(self): self.run_asis_importer() item = self.lib.items().get() self.assertFileTag(item.path, "convert") # FIXME: fails on windows @unittest.skipIf(sys.platform == "win32", "win32") def test_import_original_on_convert_error(self): # `false` exits with non-zero code self.config["convert"]["command"] = "false" self.run_asis_importer() item = self.lib.items().get() assert item is not None self.assertIsFile(item.path) def test_delete_originals(self): self.config["convert"]["delete_originals"] = True self.run_asis_importer() for path in self.importer.paths: for root, dirnames, filenames in os.walk(path): assert ( len(fnmatch.filter(filenames, "*.mp3")) == 0 ), f"Non-empty import directory {util.displayable_path(path)}" def get_count_of_import_files(self): import_file_count = 0 for path in self.importer.paths: for root, _, filenames in os.walk(path): import_file_count += len(filenames) return import_file_count class ConvertCommand: """A mixin providing a utility method to run the `convert`command in tests. """ def run_convert_path(self, path, *args): """Run the `convert` command on a given path.""" # The path is currently a filesystem bytestring. Convert it to # an argument bytestring. path = path.decode(util._fsencoding()).encode(util.arg_encoding()) args = args + (b"path:" + path,) return self.run_command("convert", *args) def run_convert(self, *args): """Run the `convert` command on `self.item`.""" return self.run_convert_path(self.item.path, *args) @_common.slow_test() class ConvertCliTest(ConvertTestCase, ConvertCommand): def setUp(self): super().setUp() self.album = self.add_album_fixture(ext="ogg") self.item = self.album.items()[0] self.convert_dest = bytestring_path( os.path.join(self.temp_dir, b"convert_dest") ) self.config["convert"] = { "dest": self.convert_dest, "paths": {"default": "converted"}, "format": "mp3", "formats": { "mp3": self.tagged_copy_cmd("mp3"), "ogg": self.tagged_copy_cmd("ogg"), "opus": { "command": self.tagged_copy_cmd("opus"), "extension": "ops", }, }, } def test_convert(self): with control_stdin("y"): self.run_convert() converted = os.path.join(self.convert_dest, b"converted.mp3") self.assertFileTag(converted, "mp3") def test_convert_with_auto_confirmation(self): self.run_convert("--yes") converted = os.path.join(self.convert_dest, b"converted.mp3") self.assertFileTag(converted, "mp3") def test_reject_confirmation(self): with control_stdin("n"): self.run_convert() converted = os.path.join(self.convert_dest, b"converted.mp3") self.assertNotExists(converted) def test_convert_keep_new(self): assert os.path.splitext(self.item.path)[1] == b".ogg" with control_stdin("y"): self.run_convert("--keep-new") self.item.load() assert os.path.splitext(self.item.path)[1] == b".mp3" def test_format_option(self): with control_stdin("y"): self.run_convert("--format", "opus") converted = os.path.join(self.convert_dest, b"converted.ops") self.assertFileTag(converted, "opus") def test_embed_album_art(self): self.config["convert"]["embed"] = True image_path = os.path.join(_common.RSRC, b"image-2x3.jpg") self.album.artpath = image_path self.album.store() with open(os.path.join(image_path), "rb") as f: image_data = f.read() with control_stdin("y"): self.run_convert() converted = os.path.join(self.convert_dest, b"converted.mp3") mediafile = MediaFile(converted) assert mediafile.images[0].data == image_data def test_skip_existing(self): converted = os.path.join(self.convert_dest, b"converted.mp3") self.touch(converted, content="XXX") self.run_convert("--yes") with open(converted) as f: assert f.read() == "XXX" def test_pretend(self): self.run_convert("--pretend") converted = os.path.join(self.convert_dest, b"converted.mp3") self.assertNotExists(converted) def test_empty_query(self): with capture_log("beets.convert") as logs: self.run_convert("An impossible query") assert logs[0] == "convert: Empty query result." def test_no_transcode_when_maxbr_set_high_and_different_formats(self): self.config["convert"]["max_bitrate"] = 5000 with control_stdin("y"): self.run_convert() converted = os.path.join(self.convert_dest, b"converted.mp3") self.assertFileTag(converted, "mp3") def test_transcode_when_maxbr_set_low_and_different_formats(self): self.config["convert"]["max_bitrate"] = 5 with control_stdin("y"): self.run_convert() converted = os.path.join(self.convert_dest, b"converted.mp3") self.assertFileTag(converted, "mp3") def test_transcode_when_maxbr_set_to_none_and_different_formats(self): with control_stdin("y"): self.run_convert() converted = os.path.join(self.convert_dest, b"converted.mp3") self.assertFileTag(converted, "mp3") def test_no_transcode_when_maxbr_set_high_and_same_formats(self): self.config["convert"]["max_bitrate"] = 5000 self.config["convert"]["format"] = "ogg" with control_stdin("y"): self.run_convert() converted = os.path.join(self.convert_dest, b"converted.ogg") self.assertNoFileTag(converted, "ogg") def test_transcode_when_maxbr_set_low_and_same_formats(self): self.config["convert"]["max_bitrate"] = 5 self.config["convert"]["format"] = "ogg" with control_stdin("y"): self.run_convert() converted = os.path.join(self.convert_dest, b"converted.ogg") self.assertFileTag(converted, "ogg") def test_transcode_when_maxbr_set_to_none_and_same_formats(self): self.config["convert"]["format"] = "ogg" with control_stdin("y"): self.run_convert() converted = os.path.join(self.convert_dest, b"converted.ogg") self.assertNoFileTag(converted, "ogg") def test_playlist(self): with control_stdin("y"): self.run_convert("--playlist", "playlist.m3u8") m3u_created = os.path.join(self.convert_dest, b"playlist.m3u8") assert os.path.exists(m3u_created) def test_playlist_pretend(self): self.run_convert("--playlist", "playlist.m3u8", "--pretend") m3u_created = os.path.join(self.convert_dest, b"playlist.m3u8") assert not os.path.exists(m3u_created) @_common.slow_test() class NeverConvertLossyFilesTest(ConvertTestCase, ConvertCommand): """Test the effect of the `never_convert_lossy_files` option.""" def setUp(self): super().setUp() self.convert_dest = os.path.join(self.temp_dir, b"convert_dest") self.config["convert"] = { "dest": self.convert_dest, "paths": {"default": "converted"}, "never_convert_lossy_files": True, "format": "mp3", "formats": { "mp3": self.tagged_copy_cmd("mp3"), }, } def test_transcode_from_lossless(self): [item] = self.add_item_fixtures(ext="flac") with control_stdin("y"): self.run_convert_path(item.path) converted = os.path.join(self.convert_dest, b"converted.mp3") self.assertFileTag(converted, "mp3") def test_transcode_from_lossy(self): self.config["convert"]["never_convert_lossy_files"] = False [item] = self.add_item_fixtures(ext="ogg") with control_stdin("y"): self.run_convert_path(item.path) converted = os.path.join(self.convert_dest, b"converted.mp3") self.assertFileTag(converted, "mp3") def test_transcode_from_lossy_prevented(self): [item] = self.add_item_fixtures(ext="ogg") with control_stdin("y"): self.run_convert_path(item.path) converted = os.path.join(self.convert_dest, b"converted.ogg") self.assertNoFileTag(converted, "mp3") class TestNoConvert: """Test the effect of the `no_convert` option.""" @pytest.mark.parametrize( "config_value, should_skip", [ ("", False), ("bitrate:320", False), ("bitrate:320 format:ogg", False), ("bitrate:320 , format:ogg", True), ], ) def test_no_convert_skip(self, config_value, should_skip): item = Item(format="ogg", bitrate=256) convert.config["convert"]["no_convert"] = config_value assert convert.in_no_convert(item) == should_skip �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������beetbox-beets-01f1faf/test/plugins/test_discogs.py��������������������������������������������������0000664�0000000�0000000�00000040671�14723254774�0022463�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# This file is part of beets. # Copyright 2016, Adrian Sampson. # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the # "Software"), to deal in the Software without restriction, including # without limitation the rights to use, copy, modify, merge, publish, # distribute, sublicense, and/or sell copies of the Software, and to # permit persons to whom the Software is furnished to do so, subject to # the following conditions: # # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. """Tests for discogs plugin.""" import pytest from beets import config from beets.test._common import Bag from beets.test.helper import BeetsTestCase, capture_log from beets.util.id_extractors import extract_discogs_id_regex from beetsplug.discogs import DiscogsPlugin class DGAlbumInfoTest(BeetsTestCase): def _make_release(self, tracks=None): """Returns a Bag that mimics a discogs_client.Release. The list of elements on the returned Bag is incomplete, including just those required for the tests on this class.""" data = { "id": "ALBUM ID", "uri": "https://www.discogs.com/release/release/13633721", "title": "ALBUM TITLE", "year": "3001", "artists": [ {"name": "ARTIST NAME", "id": "ARTIST ID", "join": ","} ], "formats": [ { "descriptions": ["FORMAT DESC 1", "FORMAT DESC 2"], "name": "FORMAT", "qty": 1, } ], "styles": ["STYLE1", "STYLE2"], "genres": ["GENRE1", "GENRE2"], "labels": [ { "name": "LABEL NAME", "catno": "CATALOG NUMBER", } ], "tracklist": [], } if tracks: for recording in tracks: data["tracklist"].append(recording) return Bag( data=data, # Make some fields available as properties, as they are # accessed by DiscogsPlugin methods. title=data["title"], artists=[Bag(data=d) for d in data["artists"]], ) def _make_track(self, title, position="", duration="", type_=None): track = {"title": title, "position": position, "duration": duration} if type_ is not None: # Test samples on discogs_client do not have a 'type_' field, but # the API seems to return it. Values: 'track' for regular tracks, # 'heading' for descriptive texts (ie. not real tracks - 12.13.2). track["type_"] = type_ return track def _make_release_from_positions(self, positions): """Return a Bag that mimics a discogs_client.Release with a tracklist where tracks have the specified `positions`.""" tracks = [ self._make_track("TITLE%s" % i, position) for (i, position) in enumerate(positions, start=1) ] return self._make_release(tracks) def test_parse_media_for_tracks(self): tracks = [ self._make_track("TITLE ONE", "1", "01:01"), self._make_track("TITLE TWO", "2", "02:02"), ] release = self._make_release(tracks=tracks) d = DiscogsPlugin().get_album_info(release) t = d.tracks assert d.media == "FORMAT" assert t[0].media == d.media assert t[1].media == d.media def test_parse_medium_numbers_single_medium(self): release = self._make_release_from_positions(["1", "2"]) d = DiscogsPlugin().get_album_info(release) t = d.tracks assert d.mediums == 1 assert t[0].medium == 1 assert t[0].medium_total == 2 assert t[1].medium == 1 assert t[0].medium_total == 2 def test_parse_medium_numbers_two_mediums(self): release = self._make_release_from_positions(["1-1", "2-1"]) d = DiscogsPlugin().get_album_info(release) t = d.tracks assert d.mediums == 2 assert t[0].medium == 1 assert t[0].medium_total == 1 assert t[1].medium == 2 assert t[1].medium_total == 1 def test_parse_medium_numbers_two_mediums_two_sided(self): release = self._make_release_from_positions(["A1", "B1", "C1"]) d = DiscogsPlugin().get_album_info(release) t = d.tracks assert d.mediums == 2 assert t[0].medium == 1 assert t[0].medium_total == 2 assert t[0].medium_index == 1 assert t[1].medium == 1 assert t[1].medium_total == 2 assert t[1].medium_index == 2 assert t[2].medium == 2 assert t[2].medium_total == 1 assert t[2].medium_index == 1 def test_parse_track_indices(self): release = self._make_release_from_positions(["1", "2"]) d = DiscogsPlugin().get_album_info(release) t = d.tracks assert t[0].medium_index == 1 assert t[0].index == 1 assert t[0].medium_total == 2 assert t[1].medium_index == 2 assert t[1].index == 2 assert t[1].medium_total == 2 def test_parse_track_indices_several_media(self): release = self._make_release_from_positions( ["1-1", "1-2", "2-1", "3-1"] ) d = DiscogsPlugin().get_album_info(release) t = d.tracks assert d.mediums == 3 assert t[0].medium_index == 1 assert t[0].index == 1 assert t[0].medium_total == 2 assert t[1].medium_index == 2 assert t[1].index == 2 assert t[1].medium_total == 2 assert t[2].medium_index == 1 assert t[2].index == 3 assert t[2].medium_total == 1 assert t[3].medium_index == 1 assert t[3].index == 4 assert t[3].medium_total == 1 def test_parse_position(self): """Test the conversion of discogs `position` to medium, medium_index and subtrack_index.""" # List of tuples (discogs_position, (medium, medium_index, subindex) positions = [ ("1", (None, "1", None)), ("A12", ("A", "12", None)), ("12-34", ("12-", "34", None)), ("CD1-1", ("CD1-", "1", None)), ("1.12", (None, "1", "12")), ("12.a", (None, "12", "A")), ("12.34", (None, "12", "34")), ("1ab", (None, "1", "AB")), # Non-standard ("IV", ("IV", None, None)), ] d = DiscogsPlugin() for position, expected in positions: assert d.get_track_index(position) == expected def test_parse_tracklist_without_sides(self): """Test standard Discogs position 12.2.9#1: "without sides".""" release = self._make_release_from_positions(["1", "2", "3"]) d = DiscogsPlugin().get_album_info(release) assert d.mediums == 1 assert len(d.tracks) == 3 def test_parse_tracklist_with_sides(self): """Test standard Discogs position 12.2.9#2: "with sides".""" release = self._make_release_from_positions(["A1", "A2", "B1", "B2"]) d = DiscogsPlugin().get_album_info(release) assert d.mediums == 1 # 2 sides = 1 LP assert len(d.tracks) == 4 def test_parse_tracklist_multiple_lp(self): """Test standard Discogs position 12.2.9#3: "multiple LP".""" release = self._make_release_from_positions(["A1", "A2", "B1", "C1"]) d = DiscogsPlugin().get_album_info(release) assert d.mediums == 2 # 3 sides = 1 LP + 1 LP assert len(d.tracks) == 4 def test_parse_tracklist_multiple_cd(self): """Test standard Discogs position 12.2.9#4: "multiple CDs".""" release = self._make_release_from_positions( ["1-1", "1-2", "2-1", "3-1"] ) d = DiscogsPlugin().get_album_info(release) assert d.mediums == 3 assert len(d.tracks) == 4 def test_parse_tracklist_non_standard(self): """Test non standard Discogs position.""" release = self._make_release_from_positions(["I", "II", "III", "IV"]) d = DiscogsPlugin().get_album_info(release) assert d.mediums == 1 assert len(d.tracks) == 4 def test_parse_tracklist_subtracks_dot(self): """Test standard Discogs position 12.2.9#5: "sub tracks, dots".""" release = self._make_release_from_positions(["1", "2.1", "2.2", "3"]) d = DiscogsPlugin().get_album_info(release) assert d.mediums == 1 assert len(d.tracks) == 3 release = self._make_release_from_positions( ["A1", "A2.1", "A2.2", "A3"] ) d = DiscogsPlugin().get_album_info(release) assert d.mediums == 1 assert len(d.tracks) == 3 def test_parse_tracklist_subtracks_letter(self): """Test standard Discogs position 12.2.9#5: "sub tracks, letter".""" release = self._make_release_from_positions(["A1", "A2a", "A2b", "A3"]) d = DiscogsPlugin().get_album_info(release) assert d.mediums == 1 assert len(d.tracks) == 3 release = self._make_release_from_positions( ["A1", "A2.a", "A2.b", "A3"] ) d = DiscogsPlugin().get_album_info(release) assert d.mediums == 1 assert len(d.tracks) == 3 def test_parse_tracklist_subtracks_extra_material(self): """Test standard Discogs position 12.2.9#6: "extra material".""" release = self._make_release_from_positions(["1", "2", "Video 1"]) d = DiscogsPlugin().get_album_info(release) assert d.mediums == 2 assert len(d.tracks) == 3 def test_parse_tracklist_subtracks_indices(self): """Test parsing of subtracks that include index tracks.""" release = self._make_release_from_positions(["", "", "1.1", "1.2"]) # Track 1: Index track with medium title release.data["tracklist"][0]["title"] = "MEDIUM TITLE" # Track 2: Index track with track group title release.data["tracklist"][1]["title"] = "TRACK GROUP TITLE" d = DiscogsPlugin().get_album_info(release) assert d.mediums == 1 assert d.tracks[0].disctitle == "MEDIUM TITLE" assert len(d.tracks) == 1 assert d.tracks[0].title == "TRACK GROUP TITLE" def test_parse_tracklist_subtracks_nested_logical(self): """Test parsing of subtracks defined inside a index track that are logical subtracks (ie. should be grouped together into a single track). """ release = self._make_release_from_positions(["1", "", "3"]) # Track 2: Index track with track group title, and sub_tracks release.data["tracklist"][1]["title"] = "TRACK GROUP TITLE" release.data["tracklist"][1]["sub_tracks"] = [ self._make_track("TITLE ONE", "2.1", "01:01"), self._make_track("TITLE TWO", "2.2", "02:02"), ] d = DiscogsPlugin().get_album_info(release) assert d.mediums == 1 assert len(d.tracks) == 3 assert d.tracks[1].title == "TRACK GROUP TITLE" def test_parse_tracklist_subtracks_nested_physical(self): """Test parsing of subtracks defined inside a index track that are physical subtracks (ie. should not be grouped together). """ release = self._make_release_from_positions(["1", "", "4"]) # Track 2: Index track with track group title, and sub_tracks release.data["tracklist"][1]["title"] = "TRACK GROUP TITLE" release.data["tracklist"][1]["sub_tracks"] = [ self._make_track("TITLE ONE", "2", "01:01"), self._make_track("TITLE TWO", "3", "02:02"), ] d = DiscogsPlugin().get_album_info(release) assert d.mediums == 1 assert len(d.tracks) == 4 assert d.tracks[1].title == "TITLE ONE" assert d.tracks[2].title == "TITLE TWO" def test_parse_tracklist_disctitles(self): """Test parsing of index tracks that act as disc titles.""" release = self._make_release_from_positions( ["", "1-1", "1-2", "", "2-1"] ) # Track 1: Index track with medium title (Cd1) release.data["tracklist"][0]["title"] = "MEDIUM TITLE CD1" # Track 4: Index track with medium title (Cd2) release.data["tracklist"][3]["title"] = "MEDIUM TITLE CD2" d = DiscogsPlugin().get_album_info(release) assert d.mediums == 2 assert d.tracks[0].disctitle == "MEDIUM TITLE CD1" assert d.tracks[1].disctitle == "MEDIUM TITLE CD1" assert d.tracks[2].disctitle == "MEDIUM TITLE CD2" assert len(d.tracks) == 3 def test_parse_minimal_release(self): """Test parsing of a release with the minimal amount of information.""" data = { "id": 123, "uri": "https://www.discogs.com/release/123456-something", "tracklist": [self._make_track("A", "1", "01:01")], "artists": [{"name": "ARTIST NAME", "id": 321, "join": ""}], "title": "TITLE", } release = Bag( data=data, title=data["title"], artists=[Bag(data=d) for d in data["artists"]], ) d = DiscogsPlugin().get_album_info(release) assert d.artist == "ARTIST NAME" assert d.album == "TITLE" assert len(d.tracks) == 1 def test_parse_release_without_required_fields(self): """Test parsing of a release that does not have the required fields.""" release = Bag(data={}, refresh=lambda *args: None) with capture_log() as logs: d = DiscogsPlugin().get_album_info(release) assert d is None assert "Release does not contain the required fields" in logs[0] def test_album_for_id(self): """Test parsing for a valid Discogs release_id""" test_patterns = [ ( "http://www.discogs.com/G%C3%BCnther-Lause-Meru-Ep/release/4354798", 4354798, ), ( "http://www.discogs.com/release/4354798-G%C3%BCnther-Lause-Meru-Ep", 4354798, ), ( "http://www.discogs.com/G%C3%BCnther-4354798Lause-Meru-Ep/release/4354798", # NOQA E501 4354798, ), ( "http://www.discogs.com/release/4354798-G%C3%BCnther-4354798Lause-Meru-Ep/", # NOQA E501 4354798, ), ("[r4354798]", 4354798), ("r4354798", 4354798), ("4354798", 4354798), ("yet-another-metadata-provider.org/foo/12345", ""), ("005b84a0-ecd6-39f1-b2f6-6eb48756b268", ""), ] for test_pattern, expected in test_patterns: match = extract_discogs_id_regex(test_pattern) if not match: match = "" assert match == expected def test_default_genre_style_settings(self): """Test genre default settings, genres to genre, styles to style""" release = self._make_release_from_positions(["1", "2"]) d = DiscogsPlugin().get_album_info(release) assert d.genre == "GENRE1, GENRE2" assert d.style == "STYLE1, STYLE2" def test_append_style_to_genre(self): """Test appending style to genre if config enabled""" config["discogs"]["append_style_genre"] = True release = self._make_release_from_positions(["1", "2"]) d = DiscogsPlugin().get_album_info(release) assert d.genre == "GENRE1, GENRE2, STYLE1, STYLE2" assert d.style == "STYLE1, STYLE2" def test_append_style_to_genre_no_style(self): """Test nothing appended to genre if style is empty""" config["discogs"]["append_style_genre"] = True release = self._make_release_from_positions(["1", "2"]) release.data["styles"] = [] d = DiscogsPlugin().get_album_info(release) assert d.genre == "GENRE1, GENRE2" assert d.style is None @pytest.mark.parametrize( "formats, expected_media, expected_albumtype", [ (None, None, None), ( [ { "descriptions": ['7"', "Single", "45 RPM"], "name": "Vinyl", "qty": 1, } ], "Vinyl", '7", Single, 45 RPM', ), ], ) def test_get_media_and_albumtype(formats, expected_media, expected_albumtype): result = DiscogsPlugin.get_media_and_albumtype(formats) assert result == (expected_media, expected_albumtype) �����������������������������������������������������������������������beetbox-beets-01f1faf/test/plugins/test_edit.py�����������������������������������������������������0000664�0000000�0000000�00000043300�14723254774�0021745�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# This file is part of beets. # Copyright 2016, Adrian Sampson and Diego Moreda. # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the # "Software"), to deal in the Software without restriction, including # without limitation the rights to use, copy, modify, merge, publish, # distribute, sublicense, and/or sell copies of the Software, and to # permit persons to whom the Software is furnished to do so, subject to # the following conditions: # # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. import codecs from unittest.mock import patch from beets.dbcore.query import TrueQuery from beets.library import Item from beets.test import _common from beets.test.helper import ( AutotagStub, BeetsTestCase, ImportTestCase, PluginMixin, TerminalImportMixin, control_stdin, ) class ModifyFileMocker: """Helper for modifying a file, replacing or editing its contents. Used for mocking the calls to the external editor during testing. """ def __init__(self, contents=None, replacements=None): """`self.contents` and `self.replacements` are initialized here, in order to keep the rest of the functions of this class with the same signature as `EditPlugin.get_editor()`, making mocking easier. - `contents`: string with the contents of the file to be used for `overwrite_contents()` - `replacement`: dict with the in-place replacements to be used for `replace_contents()`, in the form {'previous string': 'new string'} TODO: check if it can be solved more elegantly with a decorator """ self.contents = contents self.replacements = replacements self.action = self.overwrite_contents if replacements: self.action = self.replace_contents # The two methods below mock the `edit` utility function in the plugin. def overwrite_contents(self, filename, log): """Modify `filename`, replacing its contents with `self.contents`. If `self.contents` is empty, the file remains unchanged. """ if self.contents: with codecs.open(filename, "w", encoding="utf-8") as f: f.write(self.contents) def replace_contents(self, filename, log): """Modify `filename`, reading its contents and replacing the strings specified in `self.replacements`. """ with codecs.open(filename, "r", encoding="utf-8") as f: contents = f.read() for old, new_ in self.replacements.items(): contents = contents.replace(old, new_) with codecs.open(filename, "w", encoding="utf-8") as f: f.write(contents) class EditMixin(PluginMixin): """Helper containing some common functionality used for the Edit tests.""" plugin = "edit" def assertItemFieldsModified( self, library_items, items, fields=[], allowed=["path"] ): """Assert that items in the library (`lib_items`) have different values on the specified `fields` (and *only* on those fields), compared to `items`. An empty `fields` list results in asserting that no modifications have been performed. `allowed` is a list of field changes that are ignored (they may or may not have changed; the assertion doesn't care). """ for lib_item, item in zip(library_items, items): diff_fields = [ field for field in lib_item._fields if lib_item[field] != item[field] ] assert set(diff_fields).difference(allowed) == set(fields) def run_mocked_interpreter(self, modify_file_args={}, stdin=[]): """Run the edit command during an import session, with mocked stdin and yaml writing. """ m = ModifyFileMocker(**modify_file_args) with patch("beetsplug.edit.edit", side_effect=m.action): with control_stdin("\n".join(stdin)): self.importer.run() def run_mocked_command(self, modify_file_args={}, stdin=[], args=[]): """Run the edit command, with mocked stdin and yaml writing, and passing `args` to `run_command`.""" m = ModifyFileMocker(**modify_file_args) with patch("beetsplug.edit.edit", side_effect=m.action): with control_stdin("\n".join(stdin)): self.run_command("edit", *args) @_common.slow_test() @patch("beets.library.Item.write") class EditCommandTest(EditMixin, BeetsTestCase): """Black box tests for `beetsplug.edit`. Command line interaction is simulated using `test.helper.control_stdin()`, and yaml editing via an external editor is simulated using `ModifyFileMocker`. """ ALBUM_COUNT = 1 TRACK_COUNT = 10 def setUp(self): super().setUp() # Add an album, storing the original fields for comparison. self.album = self.add_album_fixture(track_count=self.TRACK_COUNT) self.album_orig = {f: self.album[f] for f in self.album._fields} self.items_orig = [ {f: item[f] for f in item._fields} for item in self.album.items() ] def assertCounts( self, mock_write, album_count=ALBUM_COUNT, track_count=TRACK_COUNT, write_call_count=TRACK_COUNT, title_starts_with="", ): """Several common assertions on Album, Track and call counts.""" assert len(self.lib.albums()) == album_count assert len(self.lib.items()) == track_count assert mock_write.call_count == write_call_count assert all( i.title.startswith(title_starts_with) for i in self.lib.items() ) def test_title_edit_discard(self, mock_write): """Edit title for all items in the library, then discard changes.""" # Edit track titles. self.run_mocked_command( {"replacements": {"t\u00eftle": "modified t\u00eftle"}}, # Cancel. ["c"], ) self.assertCounts( mock_write, write_call_count=0, title_starts_with="t\u00eftle" ) self.assertItemFieldsModified(self.album.items(), self.items_orig, []) def test_title_edit_apply(self, mock_write): """Edit title for all items in the library, then apply changes.""" # Edit track titles. self.run_mocked_command( {"replacements": {"t\u00eftle": "modified t\u00eftle"}}, # Apply changes. ["a"], ) self.assertCounts( mock_write, write_call_count=self.TRACK_COUNT, title_starts_with="modified t\u00eftle", ) self.assertItemFieldsModified( self.album.items(), self.items_orig, ["title", "mtime"] ) def test_single_title_edit_apply(self, mock_write): """Edit title for one item in the library, then apply changes.""" # Edit one track title. self.run_mocked_command( {"replacements": {"t\u00eftle 9": "modified t\u00eftle 9"}}, # Apply changes. ["a"], ) self.assertCounts( mock_write, write_call_count=1, ) # No changes except on last item. self.assertItemFieldsModified( list(self.album.items())[:-1], self.items_orig[:-1], [] ) assert list(self.album.items())[-1].title == "modified t\u00eftle 9" def test_noedit(self, mock_write): """Do not edit anything.""" # Do not edit anything. self.run_mocked_command( {"contents": None}, # No stdin. [], ) self.assertCounts( mock_write, write_call_count=0, title_starts_with="t\u00eftle" ) self.assertItemFieldsModified(self.album.items(), self.items_orig, []) def test_album_edit_apply(self, mock_write): """Edit the album field for all items in the library, apply changes. By design, the album should not be updated."" """ # Edit album. self.run_mocked_command( {"replacements": {"\u00e4lbum": "modified \u00e4lbum"}}, # Apply changes. ["a"], ) self.assertCounts(mock_write, write_call_count=self.TRACK_COUNT) self.assertItemFieldsModified( self.album.items(), self.items_orig, ["album", "mtime"] ) # Ensure album is *not* modified. self.album.load() assert self.album.album == "\u00e4lbum" def test_single_edit_add_field(self, mock_write): """Edit the yaml file appending an extra field to the first item, then apply changes.""" # Append "foo: bar" to item with id == 2. ("id: 1" would match both # "id: 1" and "id: 10") self.run_mocked_command( {"replacements": {"id: 2": "id: 2\nfoo: bar"}}, # Apply changes. ["a"], ) assert self.lib.items("id:2")[0].foo == "bar" # Even though a flexible attribute was written (which is not directly # written to the tags), write should still be called since templates # might use it. self.assertCounts( mock_write, write_call_count=1, title_starts_with="t\u00eftle" ) def test_a_album_edit_apply(self, mock_write): """Album query (-a), edit album field, apply changes.""" self.run_mocked_command( {"replacements": {"\u00e4lbum": "modified \u00e4lbum"}}, # Apply changes. ["a"], args=["-a"], ) self.album.load() self.assertCounts(mock_write, write_call_count=self.TRACK_COUNT) assert self.album.album == "modified \u00e4lbum" self.assertItemFieldsModified( self.album.items(), self.items_orig, ["album", "mtime"] ) def test_a_albumartist_edit_apply(self, mock_write): """Album query (-a), edit albumartist field, apply changes.""" self.run_mocked_command( {"replacements": {"album artist": "modified album artist"}}, # Apply changes. ["a"], args=["-a"], ) self.album.load() self.assertCounts(mock_write, write_call_count=self.TRACK_COUNT) assert self.album.albumartist == "the modified album artist" self.assertItemFieldsModified( self.album.items(), self.items_orig, ["albumartist", "mtime"] ) def test_malformed_yaml(self, mock_write): """Edit the yaml file incorrectly (resulting in a malformed yaml document).""" # Edit the yaml file to an invalid file. self.run_mocked_command( {"contents": "!MALFORMED"}, # Edit again to fix? No. ["n"], ) self.assertCounts( mock_write, write_call_count=0, title_starts_with="t\u00eftle" ) def test_invalid_yaml(self, mock_write): """Edit the yaml file incorrectly (resulting in a well-formed but invalid yaml document).""" # Edit the yaml file to an invalid but parseable file. self.run_mocked_command( {"contents": "wellformed: yes, but invalid"}, # No stdin. [], ) self.assertCounts( mock_write, write_call_count=0, title_starts_with="t\u00eftle" ) @_common.slow_test() class EditDuringImporterTestCase( EditMixin, TerminalImportMixin, ImportTestCase ): """TODO""" IGNORED = ["added", "album_id", "id", "mtime", "path"] def setUp(self): super().setUp() # Create some mediafiles, and store them for comparison. self.prepare_album_for_import(1) self.items_orig = [Item.from_path(f.path) for f in self.import_media] self.matcher = AutotagStub().install() self.matcher.matching = AutotagStub.GOOD def tearDown(self): super().tearDown() self.matcher.restore() @_common.slow_test() class EditDuringImporterNonSingletonTest(EditDuringImporterTestCase): def setUp(self): super().setUp() self.importer = self.setup_importer() def test_edit_apply_asis(self): """Edit the album field for all items in the library, apply changes, using the original item tags. """ # Edit track titles. self.run_mocked_interpreter( {"replacements": {"Tag Track": "Edited Track"}}, # eDit, Apply changes. ["d", "a"], ) # Check that only the 'title' field is modified. self.assertItemFieldsModified( self.lib.items(), self.items_orig, ["title"], self.IGNORED + [ "albumartist", "mb_albumartistid", "mb_albumartistids", ], ) assert all("Edited Track" in i.title for i in self.lib.items()) # Ensure album is *not* fetched from a candidate. assert self.lib.albums()[0].mb_albumid == "" def test_edit_discard_asis(self): """Edit the album field for all items in the library, discard changes, using the original item tags. """ # Edit track titles. self.run_mocked_interpreter( {"replacements": {"Tag Track": "Edited Track"}}, # eDit, Cancel, Use as-is. ["d", "c", "u"], ) # Check that nothing is modified, the album is imported ASIS. self.assertItemFieldsModified( self.lib.items(), self.items_orig, [], self.IGNORED + ["albumartist", "mb_albumartistid"], ) assert all("Tag Track" in i.title for i in self.lib.items()) # Ensure album is *not* fetched from a candidate. assert self.lib.albums()[0].mb_albumid == "" def test_edit_apply_candidate(self): """Edit the album field for all items in the library, apply changes, using a candidate. """ # Edit track titles. self.run_mocked_interpreter( {"replacements": {"Applied Track": "Edited Track"}}, # edit Candidates, 1, Apply changes. ["c", "1", "a"], ) # Check that 'title' field is modified, and other fields come from # the candidate. assert all("Edited Track " in i.title for i in self.lib.items()) assert all("match " in i.mb_trackid for i in self.lib.items()) # Ensure album is fetched from a candidate. assert "albumid" in self.lib.albums()[0].mb_albumid def test_edit_retag_apply(self): """Import the album using a candidate, then retag and edit and apply changes. """ self.run_mocked_interpreter( {}, # 1, Apply changes. ["1", "a"], ) # Retag and edit track titles. On retag, the importer will reset items # ids but not the db connections. self.importer.paths = [] self.importer.query = TrueQuery() self.run_mocked_interpreter( {"replacements": {"Applied Track": "Edited Track"}}, # eDit, Apply changes. ["d", "a"], ) # Check that 'title' field is modified, and other fields come from # the candidate. assert all("Edited Track " in i.title for i in self.lib.items()) assert all("match " in i.mb_trackid for i in self.lib.items()) # Ensure album is fetched from a candidate. assert "albumid" in self.lib.albums()[0].mb_albumid def test_edit_discard_candidate(self): """Edit the album field for all items in the library, discard changes, using a candidate. """ # Edit track titles. self.run_mocked_interpreter( {"replacements": {"Applied Track": "Edited Track"}}, # edit Candidates, 1, Apply changes. ["c", "1", "a"], ) # Check that 'title' field is modified, and other fields come from # the candidate. assert all("Edited Track " in i.title for i in self.lib.items()) assert all("match " in i.mb_trackid for i in self.lib.items()) # Ensure album is fetched from a candidate. assert "albumid" in self.lib.albums()[0].mb_albumid def test_edit_apply_candidate_singleton(self): """Edit the album field for all items in the library, apply changes, using a candidate and singleton mode. """ # Edit track titles. self.run_mocked_interpreter( {"replacements": {"Applied Track": "Edited Track"}}, # edit Candidates, 1, Apply changes, aBort. ["c", "1", "a", "b"], ) # Check that 'title' field is modified, and other fields come from # the candidate. assert all("Edited Track " in i.title for i in self.lib.items()) assert all("match " in i.mb_trackid for i in self.lib.items()) @_common.slow_test() class EditDuringImporterSingletonTest(EditDuringImporterTestCase): def setUp(self): super().setUp() self.importer = self.setup_singleton_importer() def test_edit_apply_asis_singleton(self): """Edit the album field for all items in the library, apply changes, using the original item tags and singleton mode. """ # Edit track titles. self.run_mocked_interpreter( {"replacements": {"Tag Track": "Edited Track"}}, # eDit, Apply changes, aBort. ["d", "a", "b"], ) # Check that only the 'title' field is modified. self.assertItemFieldsModified( self.lib.items(), self.items_orig, ["title"], self.IGNORED + ["albumartist", "mb_albumartistid"], ) assert all("Edited Track" in i.title for i in self.lib.items()) ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������beetbox-beets-01f1faf/test/plugins/test_embedart.py�������������������������������������������������0000664�0000000�0000000�00000027673�14723254774�0022622�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# This file is part of beets. # Copyright 2016, Thomas Scholtes. # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the # "Software"), to deal in the Software without restriction, including # without limitation the rights to use, copy, modify, merge, publish, # distribute, sublicense, and/or sell copies of the Software, and to # permit persons to whom the Software is furnished to do so, subject to # the following conditions: # # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. import os.path import shutil import tempfile import unittest from unittest.mock import MagicMock, patch import pytest from mediafile import MediaFile from beets import art, config, logging, ui from beets.test import _common from beets.test.helper import BeetsTestCase, FetchImageHelper, PluginMixin from beets.util import bytestring_path, displayable_path, syspath from beets.util.artresizer import ArtResizer from test.test_art_resize import DummyIMBackend def require_artresizer_compare(test): def wrapper(*args, **kwargs): if not ArtResizer.shared.can_compare: raise unittest.SkipTest("compare not available") else: return test(*args, **kwargs) wrapper.__name__ = test.__name__ return wrapper class EmbedartCliTest(PluginMixin, FetchImageHelper, BeetsTestCase): plugin = "embedart" small_artpath = os.path.join(_common.RSRC, b"image-2x3.jpg") abbey_artpath = os.path.join(_common.RSRC, b"abbey.jpg") abbey_similarpath = os.path.join(_common.RSRC, b"abbey-similar.jpg") abbey_differentpath = os.path.join(_common.RSRC, b"abbey-different.jpg") def setUp(self): super().setUp() # Converter is threaded self.io.install() def _setup_data(self, artpath=None): if not artpath: artpath = self.small_artpath with open(syspath(artpath), "rb") as f: self.image_data = f.read() def test_embed_art_from_file_with_yes_input(self): self._setup_data() album = self.add_album_fixture() item = album.items()[0] self.io.addinput("y") self.run_command("embedart", "-f", self.small_artpath) mediafile = MediaFile(syspath(item.path)) assert mediafile.images[0].data == self.image_data def test_embed_art_from_file_with_no_input(self): self._setup_data() album = self.add_album_fixture() item = album.items()[0] self.io.addinput("n") self.run_command("embedart", "-f", self.small_artpath) mediafile = MediaFile(syspath(item.path)) # make sure that images array is empty (nothing embedded) assert not mediafile.images def test_embed_art_from_file(self): self._setup_data() album = self.add_album_fixture() item = album.items()[0] self.run_command("embedart", "-y", "-f", self.small_artpath) mediafile = MediaFile(syspath(item.path)) assert mediafile.images[0].data == self.image_data def test_embed_art_from_album(self): self._setup_data() album = self.add_album_fixture() item = album.items()[0] album.artpath = self.small_artpath album.store() self.run_command("embedart", "-y") mediafile = MediaFile(syspath(item.path)) assert mediafile.images[0].data == self.image_data def test_embed_art_remove_art_file(self): self._setup_data() album = self.add_album_fixture() logging.getLogger("beets.embedart").setLevel(logging.DEBUG) handle, tmp_path = tempfile.mkstemp() tmp_path = bytestring_path(tmp_path) os.write(handle, self.image_data) os.close(handle) album.artpath = tmp_path album.store() config["embedart"]["remove_art_file"] = True self.run_command("embedart", "-y") if os.path.isfile(syspath(tmp_path)): os.remove(syspath(tmp_path)) self.fail( "Artwork file {} was not deleted".format( displayable_path(tmp_path) ) ) def test_art_file_missing(self): self.add_album_fixture() logging.getLogger("beets.embedart").setLevel(logging.DEBUG) with pytest.raises(ui.UserError): self.run_command("embedart", "-y", "-f", "/doesnotexist") def test_embed_non_image_file(self): album = self.add_album_fixture() logging.getLogger("beets.embedart").setLevel(logging.DEBUG) handle, tmp_path = tempfile.mkstemp() tmp_path = bytestring_path(tmp_path) os.write(handle, b"I am not an image.") os.close(handle) try: self.run_command("embedart", "-y", "-f", tmp_path) finally: os.remove(syspath(tmp_path)) mediafile = MediaFile(syspath(album.items()[0].path)) assert not mediafile.images # No image added. @require_artresizer_compare def test_reject_different_art(self): self._setup_data(self.abbey_artpath) album = self.add_album_fixture() item = album.items()[0] self.run_command("embedart", "-y", "-f", self.abbey_artpath) config["embedart"]["compare_threshold"] = 20 self.run_command("embedart", "-y", "-f", self.abbey_differentpath) mediafile = MediaFile(syspath(item.path)) assert ( mediafile.images[0].data == self.image_data ), f"Image written is not {displayable_path(self.abbey_artpath)}" @require_artresizer_compare def test_accept_similar_art(self): self._setup_data(self.abbey_similarpath) album = self.add_album_fixture() item = album.items()[0] self.run_command("embedart", "-y", "-f", self.abbey_artpath) config["embedart"]["compare_threshold"] = 20 self.run_command("embedart", "-y", "-f", self.abbey_similarpath) mediafile = MediaFile(syspath(item.path)) assert ( mediafile.images[0].data == self.image_data ), f"Image written is not {displayable_path(self.abbey_similarpath)}" def test_non_ascii_album_path(self): resource_path = os.path.join(_common.RSRC, b"image.mp3") album = self.add_album_fixture() trackpath = album.items()[0].path albumpath = album.path shutil.copy(syspath(resource_path), syspath(trackpath)) self.run_command("extractart", "-n", "extracted") self.assertExists(os.path.join(albumpath, b"extracted.png")) def test_extracted_extension(self): resource_path = os.path.join(_common.RSRC, b"image-jpeg.mp3") album = self.add_album_fixture() trackpath = album.items()[0].path albumpath = album.path shutil.copy(syspath(resource_path), syspath(trackpath)) self.run_command("extractart", "-n", "extracted") self.assertExists(os.path.join(albumpath, b"extracted.jpg")) def test_clear_art_with_yes_input(self): self._setup_data() album = self.add_album_fixture() item = album.items()[0] self.io.addinput("y") self.run_command("embedart", "-f", self.small_artpath) self.io.addinput("y") self.run_command("clearart") mediafile = MediaFile(syspath(item.path)) assert not mediafile.images def test_clear_art_with_no_input(self): self._setup_data() album = self.add_album_fixture() item = album.items()[0] self.io.addinput("y") self.run_command("embedart", "-f", self.small_artpath) self.io.addinput("n") self.run_command("clearart") mediafile = MediaFile(syspath(item.path)) assert mediafile.images[0].data == self.image_data def test_embed_art_from_url_with_yes_input(self): self._setup_data() album = self.add_album_fixture() item = album.items()[0] self.mock_response("http://example.com/test.jpg", "image/jpeg") self.io.addinput("y") self.run_command("embedart", "-u", "http://example.com/test.jpg") mediafile = MediaFile(syspath(item.path)) assert mediafile.images[0].data == self.IMAGEHEADER.get( "image/jpeg" ).ljust(32, b"\x00") def test_embed_art_from_url_png(self): self._setup_data() album = self.add_album_fixture() item = album.items()[0] self.mock_response("http://example.com/test.png", "image/png") self.run_command("embedart", "-y", "-u", "http://example.com/test.png") mediafile = MediaFile(syspath(item.path)) assert mediafile.images[0].data == self.IMAGEHEADER.get( "image/png" ).ljust(32, b"\x00") def test_embed_art_from_url_not_image(self): self._setup_data() album = self.add_album_fixture() item = album.items()[0] self.mock_response("http://example.com/test.html", "text/html") self.run_command("embedart", "-y", "-u", "http://example.com/test.html") mediafile = MediaFile(syspath(item.path)) assert not mediafile.images class DummyArtResizer(ArtResizer): """An `ArtResizer` which pretends that ImageMagick is available, and has a sufficiently recent version to support image comparison. """ def __init__(self): self.local_method = DummyIMBackend() @patch("beets.util.artresizer.subprocess") @patch("beets.art.extract") class ArtSimilarityTest(unittest.TestCase): def setUp(self): self.item = _common.item() self.log = logging.getLogger("beets.embedart") self.artresizer = DummyArtResizer() def _similarity(self, threshold): return art.check_art_similarity( self.log, self.item, b"path", threshold, artresizer=self.artresizer, ) def _popen(self, status=0, stdout="", stderr=""): """Create a mock `Popen` object.""" popen = MagicMock(returncode=status) popen.communicate.return_value = stdout, stderr return popen def _mock_popens( self, mock_extract, mock_subprocess, compare_status=0, compare_stdout="", compare_stderr="", convert_status=0, ): mock_extract.return_value = b"extracted_path" mock_subprocess.Popen.side_effect = [ # The `convert` call. self._popen(convert_status), # The `compare` call. self._popen(compare_status, compare_stdout, compare_stderr), ] def test_compare_success_similar(self, mock_extract, mock_subprocess): self._mock_popens(mock_extract, mock_subprocess, 0, "10", "err") assert self._similarity(20) def test_compare_success_different(self, mock_extract, mock_subprocess): self._mock_popens(mock_extract, mock_subprocess, 0, "10", "err") assert not self._similarity(5) def test_compare_status1_similar(self, mock_extract, mock_subprocess): self._mock_popens(mock_extract, mock_subprocess, 1, "out", "10") assert self._similarity(20) def test_compare_status1_different(self, mock_extract, mock_subprocess): self._mock_popens(mock_extract, mock_subprocess, 1, "out", "10") assert not self._similarity(5) def test_compare_failed(self, mock_extract, mock_subprocess): self._mock_popens(mock_extract, mock_subprocess, 2, "out", "10") assert self._similarity(20) is None def test_compare_parsing_error(self, mock_extract, mock_subprocess): self._mock_popens(mock_extract, mock_subprocess, 0, "foo", "bar") assert self._similarity(20) is None def test_compare_parsing_error_and_failure( self, mock_extract, mock_subprocess ): self._mock_popens(mock_extract, mock_subprocess, 1, "foo", "bar") assert self._similarity(20) is None def test_convert_failure(self, mock_extract, mock_subprocess): self._mock_popens(mock_extract, mock_subprocess, convert_status=1) assert self._similarity(20) is None ���������������������������������������������������������������������beetbox-beets-01f1faf/test/plugins/test_embyupdate.py�����������������������������������������������0000664�0000000�0000000�00000021656�14723254774�0023171�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������import responses from beets.test.helper import PluginTestCase from beetsplug import embyupdate class EmbyUpdateTest(PluginTestCase): plugin = "embyupdate" def setUp(self): super().setUp() self.config["emby"] = { "host": "localhost", "port": 8096, "username": "username", "password": "password", } def test_api_url_only_name(self): assert ( embyupdate.api_url( self.config["emby"]["host"].get(), self.config["emby"]["port"].get(), "/Library/Refresh", ) == "http://localhost:8096/Library/Refresh?format=json" ) def test_api_url_http(self): assert ( embyupdate.api_url( "http://localhost", self.config["emby"]["port"].get(), "/Library/Refresh", ) == "http://localhost:8096/Library/Refresh?format=json" ) def test_api_url_https(self): assert ( embyupdate.api_url( "https://localhost", self.config["emby"]["port"].get(), "/Library/Refresh", ) == "https://localhost:8096/Library/Refresh?format=json" ) def test_password_data(self): assert embyupdate.password_data( self.config["emby"]["username"].get(), self.config["emby"]["password"].get(), ) == { "username": "username", "password": "5baa61e4c9b93f3f0682250b6cf8331b7ee68fd8", "passwordMd5": "5f4dcc3b5aa765d61d8327deb882cf99", } def test_create_header_no_token(self): assert embyupdate.create_headers( "e8837bc1-ad67-520e-8cd2-f629e3155721" ) == { "x-emby-authorization": ( "MediaBrowser " 'UserId="e8837bc1-ad67-520e-8cd2-f629e3155721", ' 'Client="other", ' 'Device="beets", ' 'DeviceId="beets", ' 'Version="0.0.0"' ) } def test_create_header_with_token(self): assert embyupdate.create_headers( "e8837bc1-ad67-520e-8cd2-f629e3155721", token="abc123" ) == { "x-emby-authorization": ( "MediaBrowser " 'UserId="e8837bc1-ad67-520e-8cd2-f629e3155721", ' 'Client="other", ' 'Device="beets", ' 'DeviceId="beets", ' 'Version="0.0.0"' ), "x-mediabrowser-token": "abc123", } @responses.activate def test_get_token(self): body = ( '{"User":{"Name":"username", ' '"ServerId":"1efa5077976bfa92bc71652404f646ec",' '"Id":"2ec276a2642e54a19b612b9418a8bd3b","HasPassword":true,' '"HasConfiguredPassword":true,' '"HasConfiguredEasyPassword":false,' '"LastLoginDate":"2015-11-09T08:35:03.6357440Z",' '"LastActivityDate":"2015-11-09T08:35:03.6665060Z",' '"Configuration":{"AudioLanguagePreference":"",' '"PlayDefaultAudioTrack":true,"SubtitleLanguagePreference":"",' '"DisplayMissingEpisodes":false,' '"DisplayUnairedEpisodes":false,' '"GroupMoviesIntoBoxSets":false,' '"DisplayChannelsWithinViews":[],' '"ExcludeFoldersFromGrouping":[],"GroupedFolders":[],' '"SubtitleMode":"Default","DisplayCollectionsView":true,' '"DisplayFoldersView":false,"EnableLocalPassword":false,' '"OrderedViews":[],"IncludeTrailersInSuggestions":true,' '"EnableCinemaMode":true,"LatestItemsExcludes":[],' '"PlainFolderViews":[],"HidePlayedInLatest":true,' '"DisplayChannelsInline":false},' '"Policy":{"IsAdministrator":true,"IsHidden":false,' '"IsDisabled":false,"BlockedTags":[],' '"EnableUserPreferenceAccess":true,"AccessSchedules":[],' '"BlockUnratedItems":[],' '"EnableRemoteControlOfOtherUsers":false,' '"EnableSharedDeviceControl":true,' '"EnableLiveTvManagement":true,"EnableLiveTvAccess":true,' '"EnableMediaPlayback":true,' '"EnableAudioPlaybackTranscoding":true,' '"EnableVideoPlaybackTranscoding":true,' '"EnableContentDeletion":false,' '"EnableContentDownloading":true,"EnableSync":true,' '"EnableSyncTranscoding":true,"EnabledDevices":[],' '"EnableAllDevices":true,"EnabledChannels":[],' '"EnableAllChannels":true,"EnabledFolders":[],' '"EnableAllFolders":true,"InvalidLoginAttemptCount":0,' '"EnablePublicSharing":true}},' '"SessionInfo":{"SupportedCommands":[],' '"QueueableMediaTypes":[],"PlayableMediaTypes":[],' '"Id":"89f3b33f8b3a56af22088733ad1d76b3",' '"UserId":"2ec276a2642e54a19b612b9418a8bd3b",' '"UserName":"username","AdditionalUsers":[],' '"ApplicationVersion":"Unknown version",' '"Client":"Unknown app",' '"LastActivityDate":"2015-11-09T08:35:03.6665060Z",' '"DeviceName":"Unknown device","DeviceId":"Unknown device id",' '"SupportsRemoteControl":false,"PlayState":{"CanSeek":false,' '"IsPaused":false,"IsMuted":false,"RepeatMode":"RepeatNone"}},' '"AccessToken":"4b19180cf02748f7b95c7e8e76562fc8",' '"ServerId":"1efa5077976bfa92bc71652404f646ec"}' ) responses.add( responses.POST, ("http://localhost:8096" "/Users/AuthenticateByName"), body=body, status=200, content_type="application/json", ) headers = { "x-emby-authorization": ( "MediaBrowser " 'UserId="e8837bc1-ad67-520e-8cd2-f629e3155721", ' 'Client="other", ' 'Device="beets", ' 'DeviceId="beets", ' 'Version="0.0.0"' ) } auth_data = { "username": "username", "password": "5baa61e4c9b93f3f0682250b6cf8331b7ee68fd8", "passwordMd5": "5f4dcc3b5aa765d61d8327deb882cf99", } assert ( embyupdate.get_token("http://localhost", 8096, headers, auth_data) == "4b19180cf02748f7b95c7e8e76562fc8" ) @responses.activate def test_get_user(self): body = ( '[{"Name":"username",' '"ServerId":"1efa5077976bfa92bc71652404f646ec",' '"Id":"2ec276a2642e54a19b612b9418a8bd3b","HasPassword":true,' '"HasConfiguredPassword":true,' '"HasConfiguredEasyPassword":false,' '"LastLoginDate":"2015-11-09T08:35:03.6357440Z",' '"LastActivityDate":"2015-11-09T08:42:39.3693220Z",' '"Configuration":{"AudioLanguagePreference":"",' '"PlayDefaultAudioTrack":true,"SubtitleLanguagePreference":"",' '"DisplayMissingEpisodes":false,' '"DisplayUnairedEpisodes":false,' '"GroupMoviesIntoBoxSets":false,' '"DisplayChannelsWithinViews":[],' '"ExcludeFoldersFromGrouping":[],"GroupedFolders":[],' '"SubtitleMode":"Default","DisplayCollectionsView":true,' '"DisplayFoldersView":false,"EnableLocalPassword":false,' '"OrderedViews":[],"IncludeTrailersInSuggestions":true,' '"EnableCinemaMode":true,"LatestItemsExcludes":[],' '"PlainFolderViews":[],"HidePlayedInLatest":true,' '"DisplayChannelsInline":false},' '"Policy":{"IsAdministrator":true,"IsHidden":false,' '"IsDisabled":false,"BlockedTags":[],' '"EnableUserPreferenceAccess":true,"AccessSchedules":[],' '"BlockUnratedItems":[],' '"EnableRemoteControlOfOtherUsers":false,' '"EnableSharedDeviceControl":true,' '"EnableLiveTvManagement":true,"EnableLiveTvAccess":true,' '"EnableMediaPlayback":true,' '"EnableAudioPlaybackTranscoding":true,' '"EnableVideoPlaybackTranscoding":true,' '"EnableContentDeletion":false,' '"EnableContentDownloading":true,' '"EnableSync":true,"EnableSyncTranscoding":true,' '"EnabledDevices":[],"EnableAllDevices":true,' '"EnabledChannels":[],"EnableAllChannels":true,' '"EnabledFolders":[],"EnableAllFolders":true,' '"InvalidLoginAttemptCount":0,"EnablePublicSharing":true}}]' ) responses.add( responses.GET, "http://localhost:8096/Users/Public", body=body, status=200, content_type="application/json", ) response = embyupdate.get_user("http://localhost", 8096, "username") assert response[0]["Id"] == "2ec276a2642e54a19b612b9418a8bd3b" assert response[0]["Name"] == "username" ����������������������������������������������������������������������������������beetbox-beets-01f1faf/test/plugins/test_export.py���������������������������������������������������0000664�0000000�0000000�00000006051�14723254774�0022343�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# This file is part of beets. # Copyright 2019, Carl Suster # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the # "Software"), to deal in the Software without restriction, including # without limitation the rights to use, copy, modify, merge, publish, # distribute, sublicense, and/or sell copies of the Software, and to # permit persons to whom the Software is furnished to do so, subject to # the following conditions: # # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. """Test the beets.export utilities associated with the export plugin.""" import json import re # used to test csv format from xml.etree import ElementTree from xml.etree.ElementTree import Element from beets.test.helper import PluginTestCase class ExportPluginTest(PluginTestCase): plugin = "export" def setUp(self): super().setUp() self.test_values = {"title": "xtitle", "album": "xalbum"} def execute_command(self, format_type, artist): query = ",".join(self.test_values.keys()) out = self.run_with_output( "export", "-f", format_type, "-i", query, artist ) return out def create_item(self): (item,) = self.add_item_fixtures() item.artist = "xartist" item.title = self.test_values["title"] item.album = self.test_values["album"] item.write() item.store() return item def test_json_output(self): item1 = self.create_item() out = self.execute_command(format_type="json", artist=item1.artist) json_data = json.loads(out)[0] for key, val in self.test_values.items(): assert key in json_data assert val == json_data[key] def test_jsonlines_output(self): item1 = self.create_item() out = self.execute_command(format_type="jsonlines", artist=item1.artist) json_data = json.loads(out) for key, val in self.test_values.items(): assert key in json_data assert val == json_data[key] def test_csv_output(self): item1 = self.create_item() out = self.execute_command(format_type="csv", artist=item1.artist) csv_list = re.split("\r", re.sub("\n", "", out)) head = re.split(",", csv_list[0]) vals = re.split(",|\r", csv_list[1]) for index, column in enumerate(head): assert self.test_values.get(column, None) is not None assert vals[index] == self.test_values[column] def test_xml_output(self): item1 = self.create_item() out = self.execute_command(format_type="xml", artist=item1.artist) library = ElementTree.fromstring(out) assert isinstance(library, Element) for track in library[0]: for details in track: tag = details.tag txt = details.text assert tag in self.test_values, tag assert self.test_values[tag] == txt, txt ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������beetbox-beets-01f1faf/test/plugins/test_fetchart.py�������������������������������������������������0000664�0000000�0000000�00000007103�14723254774�0022621�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# This file is part of beets. # Copyright 2016, Thomas Scholtes. # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the # "Software"), to deal in the Software without restriction, including # without limitation the rights to use, copy, modify, merge, publish, # distribute, sublicense, and/or sell copies of the Software, and to # permit persons to whom the Software is furnished to do so, subject to # the following conditions: # # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. import ctypes import os import sys from beets import util from beets.test.helper import PluginTestCase class FetchartCliTest(PluginTestCase): plugin = "fetchart" def setUp(self): super().setUp() self.config["fetchart"]["cover_names"] = "c\xc3\xb6ver.jpg" self.config["art_filename"] = "mycover" self.album = self.add_album() self.cover_path = os.path.join(self.album.path, b"mycover.jpg") def check_cover_is_stored(self): assert self.album["artpath"] == self.cover_path with open(util.syspath(self.cover_path)) as f: assert f.read() == "IMAGE" def hide_file_windows(self): hidden_mask = 2 success = ctypes.windll.kernel32.SetFileAttributesW( self.cover_path, hidden_mask ) if not success: self.skipTest("unable to set file attributes") def test_set_art_from_folder(self): self.touch(b"c\xc3\xb6ver.jpg", dir=self.album.path, content="IMAGE") self.run_command("fetchart") self.album.load() self.check_cover_is_stored() def test_filesystem_does_not_pick_up_folder(self): os.makedirs(os.path.join(self.album.path, b"mycover.jpg")) self.run_command("fetchart") self.album.load() assert self.album["artpath"] is None def test_filesystem_does_not_pick_up_ignored_file(self): self.touch(b"co_ver.jpg", dir=self.album.path, content="IMAGE") self.config["ignore"] = ["*_*"] self.run_command("fetchart") self.album.load() assert self.album["artpath"] is None def test_filesystem_picks_up_non_ignored_file(self): self.touch(b"cover.jpg", dir=self.album.path, content="IMAGE") self.config["ignore"] = ["*_*"] self.run_command("fetchart") self.album.load() self.check_cover_is_stored() def test_filesystem_does_not_pick_up_hidden_file(self): self.touch(b".cover.jpg", dir=self.album.path, content="IMAGE") if sys.platform == "win32": self.hide_file_windows() self.config["ignore"] = [] # By default, ignore includes '.*'. self.config["ignore_hidden"] = True self.run_command("fetchart") self.album.load() assert self.album["artpath"] is None def test_filesystem_picks_up_non_hidden_file(self): self.touch(b"cover.jpg", dir=self.album.path, content="IMAGE") self.config["ignore_hidden"] = True self.run_command("fetchart") self.album.load() self.check_cover_is_stored() def test_filesystem_picks_up_hidden_file(self): self.touch(b".cover.jpg", dir=self.album.path, content="IMAGE") if sys.platform == "win32": self.hide_file_windows() self.config["ignore"] = [] # By default, ignore includes '.*'. self.config["ignore_hidden"] = False self.run_command("fetchart") self.album.load() self.check_cover_is_stored() �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������beetbox-beets-01f1faf/test/plugins/test_filefilter.py�����������������������������������������������0000664�0000000�0000000�00000006441�14723254774�0023152�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# This file is part of beets. # Copyright 2016, Malte Ried. # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the # "Software"), to deal in the Software without restriction, including # without limitation the rights to use, copy, modify, merge, publish, # distribute, sublicense, and/or sell copies of the Software, and to # permit persons to whom the Software is furnished to do so, subject to # the following conditions: # # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. """Tests for the `filefilter` plugin.""" from beets.test.helper import ImportTestCase, PluginMixin from beets.util import bytestring_path class FileFilterPluginMixin(PluginMixin, ImportTestCase): plugin = "filefilter" preload_plugin = False def setUp(self): super().setUp() self.prepare_tracks_for_import() def prepare_tracks_for_import(self): self.album_track, self.other_album_track, self.single_track = ( bytestring_path(self.prepare_album_for_import(1, album_path=p)[0]) for p in [ self.import_path / "album", self.import_path / "other_album", self.import_path, ] ) self.all_tracks = { self.album_track, self.other_album_track, self.single_track, } def _run(self, config, expected_album_count, expected_paths): with self.configure_plugin(config): self.importer.run() assert len(self.lib.albums()) == expected_album_count assert {i.path for i in self.lib.items()} == expected_paths class FileFilterPluginNonSingletonTest(FileFilterPluginMixin): def setUp(self): super().setUp() self.importer = self.setup_importer(autotag=False, copy=False) def test_import_default(self): """The default configuration should import everything.""" self._run({}, 3, self.all_tracks) def test_import_nothing(self): self._run({"path": "not_there"}, 0, set()) def test_global_config(self): self._run( {"path": ".*album.*"}, 2, {self.album_track, self.other_album_track}, ) def test_album_config(self): self._run( {"album_path": ".*other_album.*"}, 1, {self.other_album_track}, ) def test_singleton_config(self): """Check that singleton configuration is ignored for album import.""" self._run({"singleton_path": ".*other_album.*"}, 3, self.all_tracks) class FileFilterPluginSingletonTest(FileFilterPluginMixin): def setUp(self): super().setUp() self.importer = self.setup_singleton_importer(autotag=False, copy=False) def test_global_config(self): self._run( {"path": ".*album.*"}, 0, {self.album_track, self.other_album_track} ) def test_album_config(self): """Check that album configuration is ignored for singleton import.""" self._run({"album_path": ".*other_album.*"}, 0, self.all_tracks) def test_singleton_config(self): self._run( {"singleton_path": ".*other_album.*"}, 0, {self.other_album_track} ) �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������beetbox-beets-01f1faf/test/plugins/test_ftintitle.py������������������������������������������������0000664�0000000�0000000�00000015470�14723254774�0023031�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# This file is part of beets. # Copyright 2016, Fabrice Laporte. # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the # "Software"), to deal in the Software without restriction, including # without limitation the rights to use, copy, modify, merge, publish, # distribute, sublicense, and/or sell copies of the Software, and to # permit persons to whom the Software is furnished to do so, subject to # the following conditions: # # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. """Tests for the 'ftintitle' plugin.""" import unittest from beets.test.helper import PluginTestCase from beetsplug import ftintitle class FtInTitlePluginFunctional(PluginTestCase): plugin = "ftintitle" def _ft_add_item(self, path, artist, title, aartist): return self.add_item( path=path, artist=artist, artist_sort=artist, title=title, albumartist=aartist, ) def _ft_set_config( self, ftformat, drop=False, auto=True, keep_in_artist=False ): self.config["ftintitle"]["format"] = ftformat self.config["ftintitle"]["drop"] = drop self.config["ftintitle"]["auto"] = auto self.config["ftintitle"]["keep_in_artist"] = keep_in_artist def test_functional_drop(self): item = self._ft_add_item("/", "Alice ft Bob", "Song 1", "Alice") self.run_command("ftintitle", "-d") item.load() assert item["artist"] == "Alice" assert item["title"] == "Song 1" def test_functional_not_found(self): item = self._ft_add_item("/", "Alice ft Bob", "Song 1", "George") self.run_command("ftintitle", "-d") item.load() # item should be unchanged assert item["artist"] == "Alice ft Bob" assert item["title"] == "Song 1" def test_functional_custom_format(self): self._ft_set_config("feat. {0}") item = self._ft_add_item("/", "Alice ft Bob", "Song 1", "Alice") self.run_command("ftintitle") item.load() assert item["artist"] == "Alice" assert item["title"] == "Song 1 feat. Bob" self._ft_set_config("featuring {0}") item = self._ft_add_item("/", "Alice feat. Bob", "Song 1", "Alice") self.run_command("ftintitle") item.load() assert item["artist"] == "Alice" assert item["title"] == "Song 1 featuring Bob" self._ft_set_config("with {0}") item = self._ft_add_item("/", "Alice feat Bob", "Song 1", "Alice") self.run_command("ftintitle") item.load() assert item["artist"] == "Alice" assert item["title"] == "Song 1 with Bob" def test_functional_keep_in_artist(self): self._ft_set_config("feat. {0}", keep_in_artist=True) item = self._ft_add_item("/", "Alice ft Bob", "Song 1", "Alice") self.run_command("ftintitle") item.load() assert item["artist"] == "Alice ft Bob" assert item["title"] == "Song 1 feat. Bob" item = self._ft_add_item("/", "Alice ft Bob", "Song 1", "Alice") self.run_command("ftintitle", "-d") item.load() assert item["artist"] == "Alice ft Bob" assert item["title"] == "Song 1" class FtInTitlePluginTest(unittest.TestCase): def setUp(self): """Set up configuration""" ftintitle.FtInTitlePlugin() def test_find_feat_part(self): test_cases = [ { "artist": "Alice ft. Bob", "album_artist": "Alice", "feat_part": "Bob", }, { "artist": "Alice feat Bob", "album_artist": "Alice", "feat_part": "Bob", }, { "artist": "Alice featuring Bob", "album_artist": "Alice", "feat_part": "Bob", }, { "artist": "Alice & Bob", "album_artist": "Alice", "feat_part": "Bob", }, { "artist": "Alice and Bob", "album_artist": "Alice", "feat_part": "Bob", }, { "artist": "Alice With Bob", "album_artist": "Alice", "feat_part": "Bob", }, { "artist": "Alice defeat Bob", "album_artist": "Alice", "feat_part": None, }, { "artist": "Alice & Bob", "album_artist": "Bob", "feat_part": "Alice", }, { "artist": "Alice ft. Bob", "album_artist": "Bob", "feat_part": "Alice", }, { "artist": "Alice ft. Carol", "album_artist": "Bob", "feat_part": None, }, ] for test_case in test_cases: feat_part = ftintitle.find_feat_part( test_case["artist"], test_case["album_artist"] ) assert feat_part == test_case["feat_part"] def test_split_on_feat(self): parts = ftintitle.split_on_feat("Alice ft. Bob") assert parts == ("Alice", "Bob") parts = ftintitle.split_on_feat("Alice feat Bob") assert parts == ("Alice", "Bob") parts = ftintitle.split_on_feat("Alice feat. Bob") assert parts == ("Alice", "Bob") parts = ftintitle.split_on_feat("Alice featuring Bob") assert parts == ("Alice", "Bob") parts = ftintitle.split_on_feat("Alice & Bob") assert parts == ("Alice", "Bob") parts = ftintitle.split_on_feat("Alice and Bob") assert parts == ("Alice", "Bob") parts = ftintitle.split_on_feat("Alice With Bob") assert parts == ("Alice", "Bob") parts = ftintitle.split_on_feat("Alice defeat Bob") assert parts == ("Alice defeat Bob", None) def test_contains_feat(self): assert ftintitle.contains_feat("Alice ft. Bob") assert ftintitle.contains_feat("Alice feat. Bob") assert ftintitle.contains_feat("Alice feat Bob") assert ftintitle.contains_feat("Alice featuring Bob") assert ftintitle.contains_feat("Alice (ft. Bob)") assert ftintitle.contains_feat("Alice (feat. Bob)") assert ftintitle.contains_feat("Alice [ft. Bob]") assert ftintitle.contains_feat("Alice [feat. Bob]") assert not ftintitle.contains_feat("Alice defeat Bob") assert not ftintitle.contains_feat("Aliceft.Bob") assert not ftintitle.contains_feat("Alice (defeat Bob)") assert not ftintitle.contains_feat("Live and Let Go") assert not ftintitle.contains_feat("Come With Me") ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������beetbox-beets-01f1faf/test/plugins/test_hook.py�����������������������������������������������������0000664�0000000�0000000�00000010460�14723254774�0021761�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# This file is part of beets. # Copyright 2015, Thomas Scholtes. # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the # "Software"), to deal in the Software without restriction, including # without limitation the rights to use, copy, modify, merge, publish, # distribute, sublicense, and/or sell copies of the Software, and to # permit persons to whom the Software is furnished to do so, subject to # the following conditions: # # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. from __future__ import annotations import os.path import sys import unittest from contextlib import contextmanager from typing import Callable, Iterator from beets import plugins from beets.test.helper import PluginTestCase, capture_log class HookTestCase(PluginTestCase): plugin = "hook" preload_plugin = False def _get_hook(self, event: str, command: str) -> dict[str, str]: return {"event": event, "command": command} class HookLogsTest(HookTestCase): @contextmanager def _configure_logs(self, command: str) -> Iterator[list[str]]: config = {"hooks": [self._get_hook("test_event", command)]} with self.configure_plugin(config), capture_log("beets.hook") as logs: plugins.send("test_event") yield logs def test_hook_empty_command(self): with self._configure_logs("") as logs: assert 'hook: invalid command ""' in logs # FIXME: fails on windows @unittest.skipIf(sys.platform == "win32", "win32") def test_hook_non_zero_exit(self): with self._configure_logs('sh -c "exit 1"') as logs: assert "hook: hook for test_event exited with status 1" in logs def test_hook_non_existent_command(self): with self._configure_logs("non-existent-command") as logs: logs = "\n".join(logs) assert "hook: hook for test_event failed: " in logs # The error message is different for each OS. Unfortunately the text is # different in each case, where the only shared text is the string # 'file' and substring 'Err' assert "Err" in logs assert "file" in logs class HookCommandTest(HookTestCase): TEST_HOOK_COUNT = 2 events = [f"test_event_{i}" for i in range(TEST_HOOK_COUNT)] def setUp(self): super().setUp() temp_dir = os.fsdecode(self.temp_dir) self.paths = [os.path.join(temp_dir, e) for e in self.events] def _test_command( self, make_test_path: Callable[[str, str], str], send_path_kwarg: bool = False, ) -> None: """Check that each of the configured hooks is executed. Configure hooks for each event: 1. Use the given 'make_test_path' callable to create a test path from the event and the original path. 2. Configure a hook with a command to touch this path. For each of the original paths: 1. Send a test event 2. Assert that a file has been created under the original path, which proves that the configured hook command has been executed. """ hooks = [ self._get_hook(e, f"touch {make_test_path(e, p)}") for e, p in zip(self.events, self.paths) ] with self.configure_plugin({"hooks": hooks}): for event, path in zip(self.events, self.paths): if send_path_kwarg: plugins.send(event, path=path) else: plugins.send(event) assert os.path.isfile(path) @unittest.skipIf(sys.platform == "win32", "win32") def test_hook_no_arguments(self): self._test_command(lambda _, p: p) @unittest.skipIf(sys.platform == "win32", "win32") def test_hook_event_substitution(self): self._test_command(lambda e, p: p.replace(e, "{event}")) @unittest.skipIf(sys.platform == "win32", "win32") def test_hook_argument_substitution(self): self._test_command(lambda *_: "{path}", send_path_kwarg=True) @unittest.skipIf(sys.platform == "win32", "win32") def test_hook_bytes_interpolation(self): self.paths = [p.encode() for p in self.paths] self._test_command(lambda *_: "{path}", send_path_kwarg=True) ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������beetbox-beets-01f1faf/test/plugins/test_ihate.py����������������������������������������������������0000664�0000000�0000000�00000002765�14723254774�0022124�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������"""Tests for the 'ihate' plugin""" import unittest from beets import importer from beets.library import Item from beetsplug.ihate import IHatePlugin class IHatePluginTest(unittest.TestCase): def test_hate(self): match_pattern = {} test_item = Item( genre="TestGenre", album="TestAlbum", artist="TestArtist" ) task = importer.SingletonImportTask(None, test_item) # Empty query should let it pass. assert not IHatePlugin.do_i_hate_this(task, match_pattern) # 1 query match. match_pattern = ["artist:bad_artist", "artist:TestArtist"] assert IHatePlugin.do_i_hate_this(task, match_pattern) # 2 query matches, either should trigger. match_pattern = ["album:test", "artist:testartist"] assert IHatePlugin.do_i_hate_this(task, match_pattern) # Query is blocked by AND clause. match_pattern = ["album:notthis genre:testgenre"] assert not IHatePlugin.do_i_hate_this(task, match_pattern) # Both queries are blocked by AND clause with unmatched condition. match_pattern = [ "album:notthis genre:testgenre", "artist:testartist album:notthis", ] assert not IHatePlugin.do_i_hate_this(task, match_pattern) # Only one query should fire. match_pattern = [ "album:testalbum genre:testgenre", "artist:testartist album:notthis", ] assert IHatePlugin.do_i_hate_this(task, match_pattern) �����������beetbox-beets-01f1faf/test/plugins/test_importadded.py����������������������������������������������0000664�0000000�0000000�00000015053�14723254774�0023320�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# This file is part of beets. # Copyright 2016, Stig Inge Lea Bjornsen. # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the # "Software"), to deal in the Software without restriction, including # without limitation the rights to use, copy, modify, merge, publish, # distribute, sublicense, and/or sell copies of the Software, and to # permit persons to whom the Software is furnished to do so, subject to # the following conditions: # # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. """Tests for the `importadded` plugin.""" import os import pytest from beets import importer from beets.test.helper import AutotagStub, ImportTestCase, PluginMixin from beets.util import displayable_path, syspath from beetsplug.importadded import ImportAddedPlugin _listeners = ImportAddedPlugin.listeners def preserve_plugin_listeners(): """Preserve the initial plugin listeners as they would otherwise be deleted after the first setup / tear down cycle. """ if not ImportAddedPlugin.listeners: ImportAddedPlugin.listeners = _listeners def modify_mtimes(paths, offset=-60000): for i, path in enumerate(paths, start=1): mstat = os.stat(path) os.utime(syspath(path), (mstat.st_atime, mstat.st_mtime + offset * i)) class ImportAddedTest(PluginMixin, ImportTestCase): # The minimum mtime of the files to be imported plugin = "importadded" min_mtime = None def setUp(self): preserve_plugin_listeners() super().setUp() self.prepare_album_for_import(2) # Different mtimes on the files to be imported in order to test the # plugin modify_mtimes(mfile.path for mfile in self.import_media) self.min_mtime = min( os.path.getmtime(mfile.path) for mfile in self.import_media ) self.matcher = AutotagStub().install() self.matcher.macthin = AutotagStub.GOOD self.importer = self.setup_importer() self.importer.add_choice(importer.action.APPLY) def tearDown(self): super().tearDown() self.matcher.restore() def find_media_file(self, item): """Find the pre-import MediaFile for an Item""" for m in self.import_media: if m.title.replace("Tag", "Applied") == item.title: return m raise AssertionError( "No MediaFile found for Item " + displayable_path(item.path) ) def assertEqualTimes(self, first, second, msg=None): """For comparing file modification times at a sufficient precision""" assert first == pytest.approx(second, rel=1e-4), msg def assertAlbumImport(self): self.importer.run() album = self.lib.albums().get() assert album.added == self.min_mtime for item in album.items(): assert item.added == self.min_mtime def test_import_album_with_added_dates(self): self.assertAlbumImport() def test_import_album_inplace_with_added_dates(self): self.config["import"]["copy"] = False self.config["import"]["move"] = False self.config["import"]["link"] = False self.config["import"]["hardlink"] = False self.assertAlbumImport() def test_import_album_with_preserved_mtimes(self): self.config["importadded"]["preserve_mtimes"] = True self.importer.run() album = self.lib.albums().get() assert album.added == self.min_mtime for item in album.items(): self.assertEqualTimes(item.added, self.min_mtime) mediafile_mtime = os.path.getmtime(self.find_media_file(item).path) self.assertEqualTimes(item.mtime, mediafile_mtime) self.assertEqualTimes(os.path.getmtime(item.path), mediafile_mtime) def test_reimported_album_skipped(self): # Import and record the original added dates self.importer.run() album = self.lib.albums().get() album_added_before = album.added items_added_before = {item.path: item.added for item in album.items()} # Newer Item path mtimes as if Beets had modified them modify_mtimes(items_added_before.keys(), offset=10000) # Reimport self.setup_importer(import_dir=self.libdir) self.importer.run() # Verify the reimported items album = self.lib.albums().get() self.assertEqualTimes(album.added, album_added_before) items_added_after = {item.path: item.added for item in album.items()} for item_path, added_after in items_added_after.items(): self.assertEqualTimes( items_added_before[item_path], added_after, "reimport modified Item.added for " + displayable_path(item_path), ) def test_import_singletons_with_added_dates(self): self.config["import"]["singletons"] = True self.importer.run() for item in self.lib.items(): mfile = self.find_media_file(item) self.assertEqualTimes(item.added, os.path.getmtime(mfile.path)) def test_import_singletons_with_preserved_mtimes(self): self.config["import"]["singletons"] = True self.config["importadded"]["preserve_mtimes"] = True self.importer.run() for item in self.lib.items(): mediafile_mtime = os.path.getmtime(self.find_media_file(item).path) self.assertEqualTimes(item.added, mediafile_mtime) self.assertEqualTimes(item.mtime, mediafile_mtime) self.assertEqualTimes(os.path.getmtime(item.path), mediafile_mtime) def test_reimported_singletons_skipped(self): self.config["import"]["singletons"] = True # Import and record the original added dates self.importer.run() items_added_before = { item.path: item.added for item in self.lib.items() } # Newer Item path mtimes as if Beets had modified them modify_mtimes(items_added_before.keys(), offset=10000) # Reimport self.setup_importer(import_dir=self.libdir, singletons=True) self.importer.run() # Verify the reimported items items_added_after = {item.path: item.added for item in self.lib.items()} for item_path, added_after in items_added_after.items(): self.assertEqualTimes( items_added_before[item_path], added_after, "reimport modified Item.added for " + displayable_path(item_path), ) �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������beetbox-beets-01f1faf/test/plugins/test_importfeeds.py����������������������������������������������0000664�0000000�0000000�00000005040�14723254774�0023340�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������import datetime import os import os.path from beets import config from beets.library import Album, Item from beets.test.helper import BeetsTestCase from beetsplug.importfeeds import ImportFeedsPlugin class ImportfeedsTestTest(BeetsTestCase): def setUp(self): super().setUp() self.importfeeds = ImportFeedsPlugin() self.feeds_dir = os.path.join(os.fsdecode(self.temp_dir), "importfeeds") config["importfeeds"]["dir"] = self.feeds_dir def test_multi_format_album_playlist(self): config["importfeeds"]["formats"] = "m3u_multi" album = Album(album="album/name", id=1) item_path = os.path.join("path", "to", "item") item = Item(title="song", album_id=1, path=item_path) self.lib.add(album) self.lib.add(item) self.importfeeds.album_imported(self.lib, album) playlist_path = os.path.join( self.feeds_dir, os.listdir(self.feeds_dir)[0] ) assert playlist_path.endswith("album_name.m3u") with open(playlist_path) as playlist: assert item_path in playlist.read() def test_playlist_in_subdir(self): config["importfeeds"]["formats"] = "m3u" config["importfeeds"]["m3u_name"] = os.path.join( "subdir", "imported.m3u" ) album = Album(album="album/name", id=1) item_path = os.path.join("path", "to", "item") item = Item(title="song", album_id=1, path=item_path) self.lib.add(album) self.lib.add(item) self.importfeeds.album_imported(self.lib, album) playlist = os.path.join( self.feeds_dir, config["importfeeds"]["m3u_name"].get() ) playlist_subdir = os.path.dirname(playlist) assert os.path.isdir(playlist_subdir) assert os.path.isfile(playlist) def test_playlist_per_session(self): config["importfeeds"]["formats"] = "m3u_session" config["importfeeds"]["m3u_name"] = "imports.m3u" album = Album(album="album/name", id=1) item_path = os.path.join("path", "to", "item") item = Item(title="song", album_id=1, path=item_path) self.lib.add(album) self.lib.add(item) self.importfeeds.import_begin(self) self.importfeeds.album_imported(self.lib, album) date = datetime.datetime.now().strftime("%Y%m%d_%Hh%M") playlist = os.path.join(self.feeds_dir, f"imports_{date}.m3u") assert os.path.isfile(playlist) with open(playlist) as playlist_contents: assert item_path in playlist_contents.read() ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������beetbox-beets-01f1faf/test/plugins/test_info.py�����������������������������������������������������0000664�0000000�0000000�00000007371�14723254774�0021763�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# This file is part of beets. # Copyright 2016, Thomas Scholtes. # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the # "Software"), to deal in the Software without restriction, including # without limitation the rights to use, copy, modify, merge, publish, # distribute, sublicense, and/or sell copies of the Software, and to # permit persons to whom the Software is furnished to do so, subject to # the following conditions: # # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. from mediafile import MediaFile from beets.test.helper import PluginTestCase from beets.util import displayable_path class InfoTest(PluginTestCase): plugin = "info" def test_path(self): path = self.create_mediafile_fixture() mediafile = MediaFile(path) mediafile.albumartist = "AAA" mediafile.disctitle = "DDD" mediafile.genres = ["a", "b", "c"] mediafile.composer = None mediafile.save() out = self.run_with_output("info", path) assert displayable_path(path) in out assert "albumartist: AAA" in out assert "disctitle: DDD" in out assert "genres: a; b; c" in out assert "composer:" not in out def test_item_query(self): item1, item2 = self.add_item_fixtures(count=2) item1.album = "xxxx" item1.write() item1.album = "yyyy" item1.store() out = self.run_with_output("info", "album:yyyy") assert displayable_path(item1.path) in out assert "album: xxxx" in out assert displayable_path(item2.path) not in out def test_item_library_query(self): (item,) = self.add_item_fixtures() item.album = "xxxx" item.store() out = self.run_with_output("info", "--library", "album:xxxx") assert displayable_path(item.path) in out assert "album: xxxx" in out def test_collect_item_and_path(self): path = self.create_mediafile_fixture() mediafile = MediaFile(path) (item,) = self.add_item_fixtures() item.album = mediafile.album = "AAA" item.tracktotal = mediafile.tracktotal = 5 item.title = "TTT" mediafile.title = "SSS" item.write() item.store() mediafile.save() out = self.run_with_output("info", "--summarize", "album:AAA", path) assert "album: AAA" in out assert "tracktotal: 5" in out assert "title: [various]" in out def test_collect_item_and_path_with_multi_values(self): path = self.create_mediafile_fixture() mediafile = MediaFile(path) (item,) = self.add_item_fixtures() item.album = mediafile.album = "AAA" item.tracktotal = mediafile.tracktotal = 5 item.title = "TTT" mediafile.title = "SSS" item.albumartists = ["Artist A", "Artist B"] mediafile.albumartists = ["Artist C", "Artist D"] item.artists = ["Artist A", "Artist Z"] mediafile.artists = ["Artist A", "Artist Z"] item.write() item.store() mediafile.save() out = self.run_with_output("info", "--summarize", "album:AAA", path) assert "album: AAA" in out assert "tracktotal: 5" in out assert "title: [various]" in out assert "albumartists: [various]" in out assert "artists: Artist A; Artist Z" in out def test_custom_format(self): self.add_item_fixtures() out = self.run_with_output( "info", "--library", "--format", "$track. $title - $artist ($length)", ) assert "02. tĂŻtle 0 - the artist (0:01)\n" == out �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������beetbox-beets-01f1faf/test/plugins/test_ipfs.py�����������������������������������������������������0000664�0000000�0000000�00000005547�14723254774�0021774�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# This file is part of beets. # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the # "Software"), to deal in the Software without restriction, including # without limitation the rights to use, copy, modify, merge, publish, # distribute, sublicense, and/or sell copies of the Software, and to # permit persons to whom the Software is furnished to do so, subject to # the following conditions: # # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. import os from unittest.mock import Mock, patch from beets.test import _common from beets.test.helper import PluginTestCase from beets.util import _fsencoding, bytestring_path from beetsplug.ipfs import IPFSPlugin @patch("beets.util.command_output", Mock()) class IPFSPluginTest(PluginTestCase): plugin = "ipfs" def test_stored_hashes(self): test_album = self.mk_test_album() ipfs = IPFSPlugin() added_albums = ipfs.ipfs_added_albums(self.lib, self.lib.path) added_album = added_albums.get_album(1) assert added_album.ipfs == test_album.ipfs found = False want_item = test_album.items()[2] for check_item in added_album.items(): try: if check_item.get("ipfs", with_album=False): ipfs_item = os.path.basename(want_item.path).decode( _fsencoding(), ) want_path = "/ipfs/{}/{}".format(test_album.ipfs, ipfs_item) want_path = bytestring_path(want_path) assert check_item.path == want_path assert ( check_item.get("ipfs", with_album=False) == want_item.ipfs ) assert check_item.title == want_item.title found = True except AttributeError: pass assert found def mk_test_album(self): items = [_common.item() for _ in range(3)] items[0].title = "foo bar" items[0].artist = "1one" items[0].album = "baz" items[0].year = 2001 items[0].comp = True items[1].title = "baz qux" items[1].artist = "2two" items[1].album = "baz" items[1].year = 2002 items[1].comp = True items[2].title = "beets 4 eva" items[2].artist = "3three" items[2].album = "foo" items[2].year = 2003 items[2].comp = False items[2].ipfs = "QmfM9ic5LJj7V6ecozFx1MkSoaaiq3PXfhJoFvyqzpLXSk" for item in items: self.lib.add(item) album = self.lib.add_album(items) album.ipfs = "QmfM9ic5LJj7V6ecozFx1MkSoaaiq3PXfhJoFvyqzpLXSf" album.store(inherit=False) return album ���������������������������������������������������������������������������������������������������������������������������������������������������������beetbox-beets-01f1faf/test/plugins/test_keyfinder.py������������������������������������������������0000664�0000000�0000000�00000004722�14723254774�0023005�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# This file is part of beets. # Copyright 2016, Thomas Scholtes. # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the # "Software"), to deal in the Software without restriction, including # without limitation the rights to use, copy, modify, merge, publish, # distribute, sublicense, and/or sell copies of the Software, and to # permit persons to whom the Software is furnished to do so, subject to # the following conditions: # # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. from unittest.mock import patch from beets import util from beets.library import Item from beets.test.helper import AsIsImporterMixin, ImportTestCase, PluginMixin @patch("beets.util.command_output") class KeyFinderTest(AsIsImporterMixin, PluginMixin, ImportTestCase): plugin = "keyfinder" def test_add_key(self, command_output): item = Item(path="/file") item.add(self.lib) command_output.return_value = util.CommandOutput(b"dbm", b"") self.run_command("keyfinder") item.load() assert item["initial_key"] == "C#m" command_output.assert_called_with( ["KeyFinder", "-f", util.syspath(item.path)] ) def test_add_key_on_import(self, command_output): command_output.return_value = util.CommandOutput(b"dbm", b"") self.run_asis_importer() item = self.lib.items().get() assert item["initial_key"] == "C#m" def test_force_overwrite(self, command_output): self.config["keyfinder"]["overwrite"] = True item = Item(path="/file", initial_key="F") item.add(self.lib) command_output.return_value = util.CommandOutput(b"C#m", b"") self.run_command("keyfinder") item.load() assert item["initial_key"] == "C#m" def test_do_not_overwrite(self, command_output): item = Item(path="/file", initial_key="F") item.add(self.lib) command_output.return_value = util.CommandOutput(b"dbm", b"") self.run_command("keyfinder") item.load() assert item["initial_key"] == "F" def test_no_key(self, command_output): item = Item(path="/file") item.add(self.lib) command_output.return_value = util.CommandOutput(b"", b"") self.run_command("keyfinder") item.load() assert item["initial_key"] is None ����������������������������������������������beetbox-beets-01f1faf/test/plugins/test_lastgenre.py������������������������������������������������0000664�0000000�0000000�00000020355�14723254774�0023011�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# This file is part of beets. # Copyright 2016, Fabrice Laporte. # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the # "Software"), to deal in the Software without restriction, including # without limitation the rights to use, copy, modify, merge, publish, # distribute, sublicense, and/or sell copies of the Software, and to # permit persons to whom the Software is furnished to do so, subject to # the following conditions: # # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. """Tests for the 'lastgenre' plugin.""" from unittest.mock import Mock from beets import config from beets.test import _common from beets.test.helper import BeetsTestCase from beetsplug import lastgenre class LastGenrePluginTest(BeetsTestCase): def setUp(self): super().setUp() self.plugin = lastgenre.LastGenrePlugin() def _setup_config( self, whitelist=False, canonical=False, count=1, prefer_specific=False ): config["lastgenre"]["canonical"] = canonical config["lastgenre"]["count"] = count config["lastgenre"]["prefer_specific"] = prefer_specific if isinstance(whitelist, (bool, (str,))): # Filename, default, or disabled. config["lastgenre"]["whitelist"] = whitelist self.plugin.setup() if not isinstance(whitelist, (bool, (str,))): # Explicit list of genres. self.plugin.whitelist = whitelist def test_default(self): """Fetch genres with whitelist and c14n deactivated""" self._setup_config() assert self.plugin._resolve_genres(["delta blues"]) == "Delta Blues" def test_c14n_only(self): """Default c14n tree funnels up to most common genre except for *wrong* genres that stay unchanged. """ self._setup_config(canonical=True, count=99) assert self.plugin._resolve_genres(["delta blues"]) == "Blues" assert self.plugin._resolve_genres(["iota blues"]) == "Iota Blues" def test_whitelist_only(self): """Default whitelist rejects *wrong* (non existing) genres.""" self._setup_config(whitelist=True) assert self.plugin._resolve_genres(["iota blues"]) == "" def test_whitelist_c14n(self): """Default whitelist and c14n both activated result in all parents genres being selected (from specific to common). """ self._setup_config(canonical=True, whitelist=True, count=99) assert ( self.plugin._resolve_genres(["delta blues"]) == "Delta Blues, Blues" ) def test_whitelist_custom(self): """Keep only genres that are in the whitelist.""" self._setup_config(whitelist={"blues", "rock", "jazz"}, count=2) assert self.plugin._resolve_genres(["pop", "blues"]) == "Blues" self._setup_config(canonical="", whitelist={"rock"}) assert self.plugin._resolve_genres(["delta blues"]) == "" def test_count(self): """Keep the n first genres, as we expect them to be sorted from more to less popular. """ self._setup_config(whitelist={"blues", "rock", "jazz"}, count=2) assert ( self.plugin._resolve_genres(["jazz", "pop", "rock", "blues"]) == "Jazz, Rock" ) def test_count_c14n(self): """Keep the n first genres, after having applied c14n when necessary""" self._setup_config( whitelist={"blues", "rock", "jazz"}, canonical=True, count=2 ) # thanks to c14n, 'blues' superseeds 'country blues' and takes the # second slot assert ( self.plugin._resolve_genres( ["jazz", "pop", "country blues", "rock"] ) == "Jazz, Blues" ) def test_c14n_whitelist(self): """Genres first pass through c14n and are then filtered""" self._setup_config(canonical=True, whitelist={"rock"}) assert self.plugin._resolve_genres(["delta blues"]) == "" def test_empty_string_enables_canonical(self): """For backwards compatibility, setting the `canonical` option to the empty string enables it using the default tree. """ self._setup_config(canonical="", count=99) assert self.plugin._resolve_genres(["delta blues"]) == "Blues" def test_empty_string_enables_whitelist(self): """Again for backwards compatibility, setting the `whitelist` option to the empty string enables the default set of genres. """ self._setup_config(whitelist="") assert self.plugin._resolve_genres(["iota blues"]) == "" def test_prefer_specific_loads_tree(self): """When prefer_specific is enabled but canonical is not the tree still has to be loaded. """ self._setup_config(prefer_specific=True, canonical=False) assert self.plugin.c14n_branches != [] def test_prefer_specific_without_canonical(self): """Prefer_specific works without canonical.""" self._setup_config(prefer_specific=True, canonical=False, count=4) assert ( self.plugin._resolve_genres(["math rock", "post-rock"]) == "Post-Rock, Math Rock" ) def test_no_duplicate(self): """Remove duplicated genres.""" self._setup_config(count=99) assert self.plugin._resolve_genres(["blues", "blues"]) == "Blues" def test_tags_for(self): class MockPylastElem: def __init__(self, name): self.name = name def get_name(self): return self.name class MockPylastObj: def get_top_tags(self): tag1 = Mock() tag1.weight = 90 tag1.item = MockPylastElem("Pop") tag2 = Mock() tag2.weight = 40 tag2.item = MockPylastElem("Rap") return [tag1, tag2] plugin = lastgenre.LastGenrePlugin() res = plugin._tags_for(MockPylastObj()) assert res == ["pop", "rap"] res = plugin._tags_for(MockPylastObj(), min_weight=50) assert res == ["pop"] def test_get_genre(self): mock_genres = {"track": "1", "album": "2", "artist": "3"} def mock_fetch_track_genre(self, obj=None): return mock_genres["track"] def mock_fetch_album_genre(self, obj): return mock_genres["album"] def mock_fetch_artist_genre(self, obj): return mock_genres["artist"] lastgenre.LastGenrePlugin.fetch_track_genre = mock_fetch_track_genre lastgenre.LastGenrePlugin.fetch_album_genre = mock_fetch_album_genre lastgenre.LastGenrePlugin.fetch_artist_genre = mock_fetch_artist_genre self._setup_config(whitelist=False) item = _common.item() item.genre = mock_genres["track"] config["lastgenre"] = {"force": False} res = self.plugin._get_genre(item) assert res == (item.genre, "keep") config["lastgenre"] = {"force": True, "source": "track"} res = self.plugin._get_genre(item) assert res == (mock_genres["track"], "track") config["lastgenre"] = {"source": "album"} res = self.plugin._get_genre(item) assert res == (mock_genres["album"], "album") config["lastgenre"] = {"source": "artist"} res = self.plugin._get_genre(item) assert res == (mock_genres["artist"], "artist") mock_genres["artist"] = None res = self.plugin._get_genre(item) assert res == (item.genre, "original") config["lastgenre"] = {"fallback": "rap"} item.genre = None res = self.plugin._get_genre(item) assert res == (config["lastgenre"]["fallback"].get(), "fallback") def test_sort_by_depth(self): self._setup_config(canonical=True) # Normal case. tags = ("electronic", "ambient", "post-rock", "downtempo") res = self.plugin._sort_by_depth(tags) assert res == ["post-rock", "downtempo", "ambient", "electronic"] # Non-canonical tag ('chillout') present. tags = ("electronic", "ambient", "chillout") res = self.plugin._sort_by_depth(tags) assert res == ["ambient", "electronic"] �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������beetbox-beets-01f1faf/test/plugins/test_limit.py����������������������������������������������������0000664�0000000�0000000�00000007352�14723254774�0022145�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# This file is part of beets. # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the # "Software"), to deal in the Software without restriction, including # without limitation the rights to use, copy, modify, merge, publish, # distribute, sublicense, and/or sell copies of the Software, and to # permit persons to whom the Software is furnished to do so, subject to # the following conditions: # # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. """Tests for the 'limit' plugin.""" from beets.test.helper import PluginTestCase class LimitPluginTest(PluginTestCase): """Unit tests for LimitPlugin Note: query prefix tests do not work correctly with `run_with_output`. """ plugin = "limit" def setUp(self): super().setUp() # we'll create an even number of tracks in the library self.num_test_items = 10 assert self.num_test_items % 2 == 0 for item_no, item in enumerate( self.add_item_fixtures(count=self.num_test_items) ): item.track = item_no + 1 item.store() # our limit tests will use half of this number self.num_limit = self.num_test_items // 2 self.num_limit_prefix = "".join(["'", "<", str(self.num_limit), "'"]) # a subset of tests has only `num_limit` results, identified by a # range filter on the track number self.track_head_range = "track:.." + str(self.num_limit) self.track_tail_range = "track:" + str(self.num_limit + 1) + ".." def test_no_limit(self): """Returns all when there is no limit or filter.""" result = self.run_with_output("lslimit") assert result.count("\n") == self.num_test_items def test_lslimit_head(self): """Returns the expected number with `lslimit --head`.""" result = self.run_with_output("lslimit", "--head", str(self.num_limit)) assert result.count("\n") == self.num_limit def test_lslimit_tail(self): """Returns the expected number with `lslimit --tail`.""" result = self.run_with_output("lslimit", "--tail", str(self.num_limit)) assert result.count("\n") == self.num_limit def test_lslimit_head_invariant(self): """Returns the expected number with `lslimit --head` and a filter.""" result = self.run_with_output( "lslimit", "--head", str(self.num_limit), self.track_tail_range ) assert result.count("\n") == self.num_limit def test_lslimit_tail_invariant(self): """Returns the expected number with `lslimit --tail` and a filter.""" result = self.run_with_output( "lslimit", "--tail", str(self.num_limit), self.track_head_range ) assert result.count("\n") == self.num_limit def test_prefix(self): """Returns the expected number with the query prefix.""" result = self.lib.items(self.num_limit_prefix) assert len(result) == self.num_limit def test_prefix_when_correctly_ordered(self): """Returns the expected number with the query prefix and filter when the prefix portion (correctly) appears last.""" correct_order = self.track_tail_range + " " + self.num_limit_prefix result = self.lib.items(correct_order) assert len(result) == self.num_limit def test_prefix_when_incorrectly_ordred(self): """Returns no results with the query prefix and filter when the prefix portion (incorrectly) appears first.""" incorrect_order = self.num_limit_prefix + " " + self.track_tail_range result = self.lib.items(incorrect_order) assert len(result) == 0 ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������beetbox-beets-01f1faf/test/plugins/test_lyrics.py���������������������������������������������������0000664�0000000�0000000�00000061760�14723254774�0022337�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# This file is part of beets. # Copyright 2016, Fabrice Laporte. # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the # "Software"), to deal in the Software without restriction, including # without limitation the rights to use, copy, modify, merge, publish, # distribute, sublicense, and/or sell copies of the Software, and to # permit persons to whom the Software is furnished to do so, subject to # the following conditions: # # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. """Tests for the 'lyrics' plugin.""" import itertools import os import re import unittest from unittest.mock import MagicMock, patch import confuse import pytest import requests from beets import logging from beets.library import Item from beets.test import _common from beets.util import bytestring_path from beetsplug import lyrics log = logging.getLogger("beets.test_lyrics") raw_backend = lyrics.Backend({}, log) google = lyrics.Google(MagicMock(), log) genius = lyrics.Genius(MagicMock(), log) tekstowo = lyrics.Tekstowo(MagicMock(), log) lrclib = lyrics.LRCLib(MagicMock(), log) class LyricsPluginTest(unittest.TestCase): def setUp(self): """Set up configuration.""" lyrics.LyricsPlugin() def test_search_artist(self): item = Item(artist="Alice ft. Bob", title="song") assert ("Alice ft. Bob", ["song"]) in lyrics.search_pairs(item) assert ("Alice", ["song"]) in lyrics.search_pairs(item) item = Item(artist="Alice feat Bob", title="song") assert ("Alice feat Bob", ["song"]) in lyrics.search_pairs(item) assert ("Alice", ["song"]) in lyrics.search_pairs(item) item = Item(artist="Alice feat. Bob", title="song") assert ("Alice feat. Bob", ["song"]) in lyrics.search_pairs(item) assert ("Alice", ["song"]) in lyrics.search_pairs(item) item = Item(artist="Alice feats Bob", title="song") assert ("Alice feats Bob", ["song"]) in lyrics.search_pairs(item) assert ("Alice", ["song"]) not in lyrics.search_pairs(item) item = Item(artist="Alice featuring Bob", title="song") assert ("Alice featuring Bob", ["song"]) in lyrics.search_pairs(item) assert ("Alice", ["song"]) in lyrics.search_pairs(item) item = Item(artist="Alice & Bob", title="song") assert ("Alice & Bob", ["song"]) in lyrics.search_pairs(item) assert ("Alice", ["song"]) in lyrics.search_pairs(item) item = Item(artist="Alice and Bob", title="song") assert ("Alice and Bob", ["song"]) in lyrics.search_pairs(item) assert ("Alice", ["song"]) in lyrics.search_pairs(item) item = Item(artist="Alice and Bob", title="song") assert ("Alice and Bob", ["song"]) == list(lyrics.search_pairs(item))[0] def test_search_artist_sort(self): item = Item(artist="CHVRCHΞS", title="song", artist_sort="CHVRCHES") assert ("CHVRCHΞS", ["song"]) in lyrics.search_pairs(item) assert ("CHVRCHES", ["song"]) in lyrics.search_pairs(item) # Make sure that the original artist name is still the first entry assert ("CHVRCHΞS", ["song"]) == list(lyrics.search_pairs(item))[0] item = Item( artist="横山克", title="song", artist_sort="Masaru Yokoyama" ) assert ("横山克", ["song"]) in lyrics.search_pairs(item) assert ("Masaru Yokoyama", ["song"]) in lyrics.search_pairs(item) # Make sure that the original artist name is still the first entry assert ("横山克", ["song"]) == list(lyrics.search_pairs(item))[0] def test_search_pairs_multi_titles(self): item = Item(title="1 / 2", artist="A") assert ("A", ["1 / 2"]) in lyrics.search_pairs(item) assert ("A", ["1", "2"]) in lyrics.search_pairs(item) item = Item(title="1/2", artist="A") assert ("A", ["1/2"]) in lyrics.search_pairs(item) assert ("A", ["1", "2"]) in lyrics.search_pairs(item) def test_search_pairs_titles(self): item = Item(title="Song (live)", artist="A") assert ("A", ["Song"]) in lyrics.search_pairs(item) assert ("A", ["Song (live)"]) in lyrics.search_pairs(item) item = Item(title="Song (live) (new)", artist="A") assert ("A", ["Song"]) in lyrics.search_pairs(item) assert ("A", ["Song (live) (new)"]) in lyrics.search_pairs(item) item = Item(title="Song (live (new))", artist="A") assert ("A", ["Song"]) in lyrics.search_pairs(item) assert ("A", ["Song (live (new))"]) in lyrics.search_pairs(item) item = Item(title="Song ft. B", artist="A") assert ("A", ["Song"]) in lyrics.search_pairs(item) assert ("A", ["Song ft. B"]) in lyrics.search_pairs(item) item = Item(title="Song featuring B", artist="A") assert ("A", ["Song"]) in lyrics.search_pairs(item) assert ("A", ["Song featuring B"]) in lyrics.search_pairs(item) item = Item(title="Song and B", artist="A") assert ("A", ["Song and B"]) in lyrics.search_pairs(item) assert ("A", ["Song"]) not in lyrics.search_pairs(item) item = Item(title="Song: B", artist="A") assert ("A", ["Song"]) in lyrics.search_pairs(item) assert ("A", ["Song: B"]) in lyrics.search_pairs(item) def test_remove_credits(self): assert ( lyrics.remove_credits( """It's close to midnight Lyrics brought by example.com""" ) == "It's close to midnight" ) assert lyrics.remove_credits("""Lyrics brought by example.com""") == "" # don't remove 2nd verse for the only reason it contains 'lyrics' word text = """Look at all the shit that i done bought her See lyrics ain't nothin if the beat aint crackin""" assert lyrics.remove_credits(text) == text def test_is_lyrics(self): texts = ["LyricsMania.com - Copyright (c) 2013 - All Rights Reserved"] texts += [ """All material found on this site is property\n of mywickedsongtext brand""" ] for t in texts: assert not google.is_lyrics(t) def test_slugify(self): text = "http://site.com/\xe7afe-au_lait(boisson)" assert google.slugify(text) == "http://site.com/cafe_au_lait" def test_scrape_strip_cruft(self): text = """<!--lyrics below-->  one <br class='myclass'> two ! <br><br \\> <blink>four</blink>""" assert lyrics._scrape_strip_cruft(text, True) == "one\ntwo !\n\nfour" def test_scrape_strip_scripts(self): text = """foo<script>bar</script>baz""" assert lyrics._scrape_strip_cruft(text, True) == "foobaz" def test_scrape_strip_tag_in_comment(self): text = """foo<!--<bar>-->qux""" assert lyrics._scrape_strip_cruft(text, True) == "fooqux" def test_scrape_merge_paragraphs(self): text = "one</p> <p class='myclass'>two</p><p>three" assert lyrics._scrape_merge_paragraphs(text) == "one\ntwo\nthree" def test_missing_lyrics(self): assert not google.is_lyrics(LYRICS_TEXTS["missing_texts"]) def url_to_filename(url): url = re.sub(r"https?://|www.", "", url) url = re.sub(r".html", "", url) fn = "".join(x for x in url if (x.isalnum() or x == "/")) fn = fn.split("/") fn = os.path.join( LYRICS_ROOT_DIR, bytestring_path(fn[0]), bytestring_path(fn[-1] + ".txt"), ) return fn class MockFetchUrl: def __init__(self, pathval="fetched_path"): self.pathval = pathval self.fetched = None def __call__(self, url, filename=None): self.fetched = url fn = url_to_filename(url) with open(fn, encoding="utf8") as f: content = f.read() return content class LyricsAssertions: """A mixin with lyrics-specific assertions.""" def assertLyricsContentOk(self, title, text, msg=""): # noqa: N802 """Compare lyrics text to expected lyrics for given title.""" if not text: return keywords = set(LYRICS_TEXTS[google.slugify(title)].split()) words = {x.strip(".?, ()") for x in text.lower().split()} if not keywords <= words: details = ( f"{keywords!r} is not a subset of {words!r}." f" Words only in expected set {keywords - words!r}," f" Words only in result set {words - keywords!r}." ) self.fail(f"{details} : {msg}") LYRICS_ROOT_DIR = os.path.join(_common.RSRC, b"lyrics") yaml_path = os.path.join(_common.RSRC, b"lyricstext.yaml") LYRICS_TEXTS = confuse.load_yaml(yaml_path) class LyricsGoogleBaseTest(unittest.TestCase): def setUp(self): """Set up configuration.""" try: __import__("bs4") except ImportError: self.skipTest("Beautiful Soup 4 not available") class LyricsPluginSourcesTest(LyricsGoogleBaseTest, LyricsAssertions): """Check that beets google custom search engine sources are correctly scraped. """ DEFAULT_SONG = dict(artist="The Beatles", title="Lady Madonna") DEFAULT_SOURCES = [ # dict(artist=u'Santana', title=u'Black magic woman', # backend=lyrics.MusiXmatch), dict( DEFAULT_SONG, backend=lyrics.Genius, # GitHub actions is on some form of Cloudflare blacklist. skip=os.environ.get("GITHUB_ACTIONS") == "true", ), dict(artist="Boy In Space", title="u n eye", backend=lyrics.Tekstowo), ] GOOGLE_SOURCES = [ dict( DEFAULT_SONG, url="http://www.absolutelyrics.com", path="/lyrics/view/the_beatles/lady_madonna", ), dict( DEFAULT_SONG, url="http://www.azlyrics.com", path="/lyrics/beatles/ladymadonna.html", # AZLyrics returns a 403 on GitHub actions. skip=os.environ.get("GITHUB_ACTIONS") == "true", ), dict( DEFAULT_SONG, url="http://www.chartlyrics.com", path="/_LsLsZ7P4EK-F-LD4dJgDQ/Lady+Madonna.aspx", ), # dict(DEFAULT_SONG, # url=u'http://www.elyricsworld.com', # path=u'/lady_madonna_lyrics_beatles.html'), dict( url="http://www.lacoccinelle.net", artist="Jacques Brel", title="Amsterdam", path="/paroles-officielles/275679.html", ), dict( DEFAULT_SONG, url="http://letras.mus.br/", path="the-beatles/275/" ), dict( DEFAULT_SONG, url="http://www.lyricsmania.com/", path="lady_madonna_lyrics_the_beatles.html", ), dict( DEFAULT_SONG, url="http://www.lyricsmode.com", path="/lyrics/b/beatles/lady_madonna.html", ), dict( url="http://www.lyricsontop.com", artist="Amy Winehouse", title="Jazz'n'blues", path="/amy-winehouse-songs/jazz-n-blues-lyrics.html", ), # dict(DEFAULT_SONG, # url='http://www.metrolyrics.com/', # path='lady-madonna-lyrics-beatles.html'), # dict(url='http://www.musica.com/', path='letras.asp?letra=2738', # artist=u'Santana', title=u'Black magic woman'), dict( url="http://www.paroles.net/", artist="Lilly Wood & the prick", title="Hey it's ok", path="lilly-wood-the-prick/paroles-hey-it-s-ok", ), dict( DEFAULT_SONG, url="http://www.songlyrics.com", path="/the-beatles/lady-madonna-lyrics", ), dict( DEFAULT_SONG, url="http://www.sweetslyrics.com", path="/761696.The%20Beatles%20-%20Lady%20Madonna.html", ), ] def setUp(self): LyricsGoogleBaseTest.setUp(self) self.plugin = lyrics.LyricsPlugin() @pytest.mark.integration_test def test_backend_sources_ok(self): """Test default backends with songs known to exist in respective databases. """ # Don't test any sources marked as skipped. sources = [s for s in self.DEFAULT_SOURCES if not s.get("skip", False)] for s in sources: with self.subTest(s["backend"].__name__): backend = s["backend"](self.plugin.config, self.plugin._log) res = backend.fetch(s["artist"], s["title"]) self.assertLyricsContentOk(s["title"], res) @pytest.mark.integration_test def test_google_sources_ok(self): """Test if lyrics present on websites registered in beets google custom search engine are correctly scraped. """ # Don't test any sources marked as skipped. sources = [s for s in self.GOOGLE_SOURCES if not s.get("skip", False)] for s in sources: url = s["url"] + s["path"] res = lyrics.scrape_lyrics_from_html(raw_backend.fetch_url(url)) assert google.is_lyrics(res), url self.assertLyricsContentOk(s["title"], res, url) class LyricsGooglePluginMachineryTest(LyricsGoogleBaseTest, LyricsAssertions): """Test scraping heuristics on a fake html page.""" source = dict( url="http://www.example.com", artist="John Doe", title="Beets song", path="/lyrics/beetssong", ) def setUp(self): """Set up configuration""" LyricsGoogleBaseTest.setUp(self) self.plugin = lyrics.LyricsPlugin() @patch.object(lyrics.Backend, "fetch_url", MockFetchUrl()) def test_mocked_source_ok(self): """Test that lyrics of the mocked page are correctly scraped""" url = self.source["url"] + self.source["path"] res = lyrics.scrape_lyrics_from_html(raw_backend.fetch_url(url)) assert google.is_lyrics(res), url self.assertLyricsContentOk(self.source["title"], res, url) @patch.object(lyrics.Backend, "fetch_url", MockFetchUrl()) def test_is_page_candidate_exact_match(self): """Test matching html page title with song infos -- when song infos are present in the title. """ from bs4 import BeautifulSoup, SoupStrainer s = self.source url = str(s["url"] + s["path"]) html = raw_backend.fetch_url(url) soup = BeautifulSoup( html, "html.parser", parse_only=SoupStrainer("title") ) assert google.is_page_candidate( url, soup.title.string, s["title"], s["artist"] ), url def test_is_page_candidate_fuzzy_match(self): """Test matching html page title with song infos -- when song infos are not present in the title. """ s = self.source url = s["url"] + s["path"] url_title = "example.com | Beats song by John doe" # very small diffs (typo) are ok eg 'beats' vs 'beets' with same artist assert google.is_page_candidate( url, url_title, s["title"], s["artist"] ), url # reject different title url_title = "example.com | seets bong lyrics by John doe" assert not google.is_page_candidate( url, url_title, s["title"], s["artist"] ), url def test_is_page_candidate_special_chars(self): """Ensure that `is_page_candidate` doesn't crash when the artist and such contain special regular expression characters. """ # https://github.com/beetbox/beets/issues/1673 s = self.source url = s["url"] + s["path"] url_title = "foo" google.is_page_candidate(url, url_title, s["title"], "Sunn O)))") # test Genius backend class GeniusBaseTest(unittest.TestCase): def setUp(self): """Set up configuration.""" try: __import__("bs4") except ImportError: self.skipTest("Beautiful Soup 4 not available") class GeniusScrapeLyricsFromHtmlTest(GeniusBaseTest): """tests Genius._scrape_lyrics_from_html()""" def setUp(self): """Set up configuration""" GeniusBaseTest.setUp(self) self.plugin = lyrics.LyricsPlugin() def test_no_lyrics_div(self): """Ensure we don't crash when the scraping the html for a genius page doesn't contain <div class="lyrics"></div> """ # https://github.com/beetbox/beets/issues/3535 # expected return value None url = "https://genius.com/sample" mock = MockFetchUrl() assert genius._scrape_lyrics_from_html(mock(url)) is None def test_good_lyrics(self): """Ensure we are able to scrape a page with lyrics""" url = "https://genius.com/Ttng-chinchilla-lyrics" mock = MockFetchUrl() lyrics = genius._scrape_lyrics_from_html(mock(url)) assert lyrics is not None assert lyrics.count("\n") == 28 def test_good_lyrics_multiple_divs(self): """Ensure we are able to scrape a page with lyrics""" url = "https://genius.com/2pac-all-eyez-on-me-lyrics" mock = MockFetchUrl() lyrics = genius._scrape_lyrics_from_html(mock(url)) assert lyrics is not None assert lyrics.count("\n") == 133 # TODO: find an example of a lyrics page with multiple divs and test it class GeniusFetchTest(GeniusBaseTest): """tests Genius.fetch()""" def setUp(self): """Set up configuration""" GeniusBaseTest.setUp(self) self.plugin = lyrics.LyricsPlugin() @patch.object(lyrics.Genius, "_scrape_lyrics_from_html") @patch.object(lyrics.Backend, "fetch_url", return_value=True) def test_json(self, mock_fetch_url, mock_scrape): """Ensure we're finding artist matches""" with patch.object( lyrics.Genius, "_search", return_value={ "response": { "hits": [ { "result": { "primary_artist": { "name": "\u200bblackbear", }, "url": "blackbear_url", } }, { "result": { "primary_artist": {"name": "El\u002dp"}, "url": "El-p_url", } }, ] } }, ) as mock_json: # genius uses zero-width-spaces (\u200B) for lowercase # artists so we make sure we can match those assert genius.fetch("blackbear", "Idfc") is not None mock_fetch_url.assert_called_once_with("blackbear_url") mock_scrape.assert_called_once_with(True) # genius uses the hyphen minus (\u002D) as their dash assert genius.fetch("El-p", "Idfc") is not None mock_fetch_url.assert_called_with("El-p_url") mock_scrape.assert_called_with(True) # test no matching artist assert genius.fetch("doesntexist", "none") is None # test invalid json mock_json.return_value = None assert genius.fetch("blackbear", "Idfc") is None # TODO: add integration test hitting real api # test Tekstowo class TekstowoBaseTest(unittest.TestCase): def setUp(self): """Set up configuration.""" try: __import__("bs4") except ImportError: self.skipTest("Beautiful Soup 4 not available") class TekstowoExtractLyricsTest(TekstowoBaseTest): """tests Tekstowo.extract_lyrics()""" def setUp(self): """Set up configuration""" TekstowoBaseTest.setUp(self) self.plugin = lyrics.LyricsPlugin() tekstowo.config = self.plugin.config def test_good_lyrics(self): """Ensure we are able to scrape a page with lyrics""" url = "https://www.tekstowo.pl/piosenka,24kgoldn,city_of_angels_1.html" mock = MockFetchUrl() assert tekstowo.extract_lyrics(mock(url)) def test_no_lyrics(self): """Ensure we don't crash when the scraping the html for a Tekstowo page doesn't contain lyrics """ url = ( "https://www.tekstowo.pl/piosenka,beethoven," "beethoven_piano_sonata_17_tempest_the_3rd_movement.html" ) mock = MockFetchUrl() assert not tekstowo.extract_lyrics(mock(url)) class TekstowoIntegrationTest(TekstowoBaseTest, LyricsAssertions): """Tests Tekstowo lyric source with real requests""" def setUp(self): """Set up configuration""" TekstowoBaseTest.setUp(self) self.plugin = lyrics.LyricsPlugin() tekstowo.config = self.plugin.config @pytest.mark.integration_test def test_normal(self): """Ensure we can fetch a song's lyrics in the ordinary case""" lyrics = tekstowo.fetch("Boy in Space", "u n eye") self.assertLyricsContentOk("u n eye", lyrics) @pytest.mark.integration_test def test_no_matching_results(self): """Ensure we fetch nothing if there are search results returned but no matches""" # https://github.com/beetbox/beets/issues/4406 # expected return value None lyrics = tekstowo.fetch("Kelly Bailey", "Black Mesa Inbound") assert lyrics is None # test LRCLib backend class LRCLibLyricsTest(unittest.TestCase): def setUp(self): self.plugin = lyrics.LyricsPlugin() lrclib.config = self.plugin.config @patch("beetsplug.lyrics.requests.get") def test_fetch_synced_lyrics(self, mock_get): mock_response = { "syncedLyrics": "[00:00.00] la la la", "plainLyrics": "la la la", } mock_get.return_value.json.return_value = mock_response mock_get.return_value.status_code = 200 lyrics = lrclib.fetch("la", "la", "la", 999) assert lyrics == mock_response["plainLyrics"] self.plugin.config["synced"] = True lyrics = lrclib.fetch("la", "la", "la", 999) assert lyrics == mock_response["syncedLyrics"] @patch("beetsplug.lyrics.requests.get") def test_fetch_plain_lyrics(self, mock_get): mock_response = { "syncedLyrics": "", "plainLyrics": "la la la", } mock_get.return_value.json.return_value = mock_response mock_get.return_value.status_code = 200 lyrics = lrclib.fetch("la", "la", "la", 999) assert lyrics == mock_response["plainLyrics"] @patch("beetsplug.lyrics.requests.get") def test_fetch_not_found(self, mock_get): mock_response = { "statusCode": 404, "error": "Not Found", "message": "Failed to find specified track", } mock_get.return_value.json.return_value = mock_response mock_get.return_value.status_code = 404 lyrics = lrclib.fetch("la", "la", "la", 999) assert lyrics is None @patch("beetsplug.lyrics.requests.get") def test_fetch_exception(self, mock_get): mock_get.side_effect = requests.RequestException lyrics = lrclib.fetch("la", "la", "la", 999) assert lyrics is None class LRCLibIntegrationTest(LyricsAssertions): def setUp(self): self.plugin = lyrics.LyricsPlugin() lrclib.config = self.plugin.config @pytest.mark.integration_test def test_track_with_lyrics(self): lyrics = lrclib.fetch("Boy in Space", "u n eye", "Live EP", 160) self.assertLyricsContentOk("u n eye", lyrics) @pytest.mark.integration_test def test_instrumental_track(self): lyrics = lrclib.fetch( "Kelly Bailey", "Black Mesa Inbound", "Half Life 2 Soundtrack", 134 ) assert lyrics is None @pytest.mark.integration_test def test_nonexistent_track(self): lyrics = lrclib.fetch("blah", "blah", "blah", 999) assert lyrics is None # test utilities class SlugTests(unittest.TestCase): def test_slug(self): # plain ascii passthrough text = "test" assert lyrics.slug(text) == "test" # german unicode and capitals text = "Mørdag" assert lyrics.slug(text) == "mordag" # more accents and quotes text = "l'Ă©tĂ© c'est fait pour jouer" assert lyrics.slug(text) == "l-ete-c-est-fait-pour-jouer" # accents, parens and spaces text = "\xe7afe au lait (boisson)" assert lyrics.slug(text) == "cafe-au-lait-boisson" text = "Multiple spaces -- and symbols! -- merged" assert lyrics.slug(text) == "multiple-spaces-and-symbols-merged" text = "\u200bno-width-space" assert lyrics.slug(text) == "no-width-space" # variations of dashes should get standardized dashes = ["\u200d", "\u2010"] for dash1, dash2 in itertools.combinations(dashes, 2): assert lyrics.slug(dash1) == lyrics.slug(dash2) ����������������beetbox-beets-01f1faf/test/plugins/test_mbsubmit.py�������������������������������������������������0000664�0000000�0000000�00000004464�14723254774�0022652�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# This file is part of beets. # Copyright 2016, Adrian Sampson and Diego Moreda. # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the # "Software"), to deal in the Software without restriction, including # without limitation the rights to use, copy, modify, merge, publish, # distribute, sublicense, and/or sell copies of the Software, and to # permit persons to whom the Software is furnished to do so, subject to # the following conditions: # # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. from beets.test.helper import ( AutotagStub, ImportTestCase, PluginMixin, TerminalImportMixin, capture_stdout, control_stdin, ) class MBSubmitPluginTest(PluginMixin, TerminalImportMixin, ImportTestCase): plugin = "mbsubmit" def setUp(self): super().setUp() self.prepare_album_for_import(2) self.setup_importer() self.matcher = AutotagStub().install() def tearDown(self): super().tearDown() self.matcher.restore() def test_print_tracks_output(self): """Test the output of the "print tracks" choice.""" self.matcher.matching = AutotagStub.BAD with capture_stdout() as output: with control_stdin("\n".join(["p", "s"])): # Print tracks; Skip self.importer.run() # Manually build the string for comparing the output. tracklist = ( "Open files with Picard? " "01. Tag Track 1 - Tag Artist (0:01)\n" "02. Tag Track 2 - Tag Artist (0:01)" ) assert tracklist in output.getvalue() def test_print_tracks_output_as_tracks(self): """Test the output of the "print tracks" choice, as singletons.""" self.matcher.matching = AutotagStub.BAD with capture_stdout() as output: with control_stdin("\n".join(["t", "s", "p", "s"])): # as Tracks; Skip; Print tracks; Skip self.importer.run() # Manually build the string for comparing the output. tracklist = ( "Open files with Picard? " "02. Tag Track 2 - Tag Artist (0:01)" ) assert tracklist in output.getvalue() ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������beetbox-beets-01f1faf/test/plugins/test_mbsync.py���������������������������������������������������0000664�0000000�0000000�00000006125�14723254774�0022317�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# This file is part of beets. # Copyright 2016, Thomas Scholtes. # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the # "Software"), to deal in the Software without restriction, including # without limitation the rights to use, copy, modify, merge, publish, # distribute, sublicense, and/or sell copies of the Software, and to # permit persons to whom the Software is furnished to do so, subject to # the following conditions: # # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. from unittest.mock import patch from beets.autotag.hooks import AlbumInfo, TrackInfo from beets.library import Item from beets.test.helper import PluginTestCase, capture_log class MbsyncCliTest(PluginTestCase): plugin = "mbsync" @patch("beets.autotag.mb.album_for_id") @patch("beets.autotag.mb.track_for_id") def test_update_library(self, track_for_id, album_for_id): album_item = Item( album="old album", mb_albumid="81ae60d4-5b75-38df-903a-db2cfa51c2c6", mb_trackid="track id", ) self.lib.add_album([album_item]) singleton = Item( title="old title", mb_trackid="b8c2cf90-83f9-3b5f-8ccd-31fb866fcf37" ) self.lib.add(singleton) album_for_id.return_value = AlbumInfo( album_id="album id", album="new album", tracks=[ TrackInfo(track_id=album_item.mb_trackid, title="new title") ], ) track_for_id.return_value = TrackInfo( track_id=singleton.mb_trackid, title="new title" ) with capture_log() as logs: self.run_command("mbsync") assert "Sending event: albuminfo_received" in logs assert "Sending event: trackinfo_received" in logs singleton.load() assert singleton.title == "new title" album_item.load() assert album_item.title == "new title" assert album_item.mb_trackid == "track id" assert album_item.get_album().album == "new album" def test_custom_format(self): for item in [ Item(artist="albumartist", album="no id"), Item( artist="albumartist", album="invalid id", mb_albumid="a1b2c3d4", ), ]: self.lib.add_album([item]) for item in [ Item(artist="artist", title="no id"), Item(artist="artist", title="invalid id", mb_trackid="a1b2c3d4"), ]: self.lib.add(item) with capture_log("beets.mbsync") as logs: self.run_command("mbsync", "-f", "'%if{$album,$album,$title}'") assert set(logs) == { "mbsync: Skipping album with no mb_albumid: 'no id'", "mbsync: Skipping album with invalid mb_albumid: 'invalid id'", "mbsync: Skipping singleton with no mb_trackid: 'no id'", "mbsync: Skipping singleton with invalid mb_trackid: 'invalid id'", } �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������beetbox-beets-01f1faf/test/plugins/test_mpdstats.py�������������������������������������������������0000664�0000000�0000000�00000005170�14723254774�0022662�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# This file is part of beets. # Copyright 2016 # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the # "Software"), to deal in the Software without restriction, including # without limitation the rights to use, copy, modify, merge, publish, # distribute, sublicense, and/or sell copies of the Software, and to # permit persons to whom the Software is furnished to do so, subject to # the following conditions: # # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. from unittest.mock import ANY, Mock, call, patch from beets import util from beets.library import Item from beets.test.helper import PluginTestCase from beetsplug.mpdstats import MPDStats class MPDStatsTest(PluginTestCase): plugin = "mpdstats" def test_update_rating(self): item = Item(title="title", path="", id=1) item.add(self.lib) log = Mock() mpdstats = MPDStats(self.lib, log) assert not mpdstats.update_rating(item, True) assert not mpdstats.update_rating(None, True) def test_get_item(self): item_path = util.normpath("/foo/bar.flac") item = Item(title="title", path=item_path, id=1) item.add(self.lib) log = Mock() mpdstats = MPDStats(self.lib, log) assert str(mpdstats.get_item(item_path)) == str(item) assert mpdstats.get_item("/some/non-existing/path") is None assert "item not found:" in log.info.call_args[0][0] FAKE_UNKNOWN_STATE = "some-unknown-one" STATUSES = [ {"state": FAKE_UNKNOWN_STATE}, {"state": "pause"}, {"state": "play", "songid": 1, "time": "0:1"}, {"state": "stop"}, ] EVENTS = [["player"]] * (len(STATUSES) - 1) + [KeyboardInterrupt] item_path = util.normpath("/foo/bar.flac") songid = 1 @patch( "beetsplug.mpdstats.MPDClientWrapper", return_value=Mock( **{ "events.side_effect": EVENTS, "status.side_effect": STATUSES, "currentsong.return_value": (item_path, songid), } ), ) def test_run_mpdstats(self, mpd_mock): item = Item(title="title", path=self.item_path, id=1) item.add(self.lib) log = Mock() try: MPDStats(self.lib, log).run() except KeyboardInterrupt: pass log.debug.assert_has_calls([call('unhandled status "{0}"', ANY)]) log.info.assert_has_calls( [call("pause"), call("playing {0}", ANY), call("stop")] ) ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������beetbox-beets-01f1faf/test/plugins/test_parentwork.py�����������������������������������������������0000664�0000000�0000000�00000013551�14723254774�0023221�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# This file is part of beets. # Copyright 2017, Dorian Soergel # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the # "Software"), to deal in the Software without restriction, including # without limitation the rights to use, copy, modify, merge, publish, # distribute, sublicense, and/or sell copies of the Software, and to # permit persons to whom the Software is furnished to do so, subject to # the following conditions: # # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. """Tests for the 'parentwork' plugin.""" from unittest.mock import patch import pytest from beets.library import Item from beets.test.helper import PluginTestCase from beetsplug import parentwork work = { "work": { "id": "1", "title": "work", "work-relation-list": [ {"type": "parts", "direction": "backward", "work": {"id": "2"}} ], "artist-relation-list": [ { "type": "composer", "artist": { "name": "random composer", "sort-name": "composer, random", }, } ], } } dp_work = { "work": { "id": "2", "title": "directparentwork", "work-relation-list": [ {"type": "parts", "direction": "backward", "work": {"id": "3"}} ], "artist-relation-list": [ { "type": "composer", "artist": { "name": "random composer", "sort-name": "composer, random", }, } ], } } p_work = { "work": { "id": "3", "title": "parentwork", "artist-relation-list": [ { "type": "composer", "artist": { "name": "random composer", "sort-name": "composer, random", }, } ], } } def mock_workid_response(mbid, includes): if mbid == "1": return work elif mbid == "2": return dp_work elif mbid == "3": return p_work @pytest.mark.integration_test class ParentWorkIntegrationTest(PluginTestCase): plugin = "parentwork" # test how it works with real musicbrainz data def test_normal_case_real(self): item = Item( path="/file", mb_workid="e27bda6e-531e-36d3-9cd7-b8ebc18e8c53", parentwork_workid_current="e27bda6e-531e-36d3-9cd7-\ b8ebc18e8c53", ) item.add(self.lib) self.run_command("parentwork") item.load() assert item["mb_parentworkid"] == "32c8943f-1b27-3a23-8660-4567f4847c94" def test_force_real(self): self.config["parentwork"]["force"] = True item = Item( path="/file", mb_workid="e27bda6e-531e-36d3-9cd7-b8ebc18e8c53", mb_parentworkid="XXX", parentwork_workid_current="e27bda6e-531e-36d3-9cd7-\ b8ebc18e8c53", parentwork="whatever", ) item.add(self.lib) self.run_command("parentwork") item.load() assert item["mb_parentworkid"] == "32c8943f-1b27-3a23-8660-4567f4847c94" def test_no_force_real(self): self.config["parentwork"]["force"] = False item = Item( path="/file", mb_workid="e27bda6e-531e-36d3-9cd7-\ b8ebc18e8c53", mb_parentworkid="XXX", parentwork_workid_current="e27bda6e-531e-36d3-9cd7-\ b8ebc18e8c53", parentwork="whatever", ) item.add(self.lib) self.run_command("parentwork") item.load() assert item["mb_parentworkid"] == "XXX" # test different cases, still with Matthew Passion Ouverture or Mozart # requiem def test_direct_parent_work_real(self): mb_workid = "2e4a3668-458d-3b2a-8be2-0b08e0d8243a" assert ( "f04b42df-7251-4d86-a5ee-67cfa49580d1" == parentwork.direct_parent_id(mb_workid)[0] ) assert ( "45afb3b2-18ac-4187-bc72-beb1b1c194ba" == parentwork.work_parent_id(mb_workid)[0] ) class ParentWorkTest(PluginTestCase): plugin = "parentwork" def setUp(self): """Set up configuration""" super().setUp() self.patcher = patch( "musicbrainzngs.get_work_by_id", side_effect=mock_workid_response ) self.patcher.start() def tearDown(self): super().tearDown() self.patcher.stop() def test_normal_case(self): item = Item(path="/file", mb_workid="1", parentwork_workid_current="1") item.add(self.lib) self.run_command("parentwork") item.load() assert item["mb_parentworkid"] == "3" def test_force(self): self.config["parentwork"]["force"] = True item = Item( path="/file", mb_workid="1", mb_parentworkid="XXX", parentwork_workid_current="1", parentwork="parentwork", ) item.add(self.lib) self.run_command("parentwork") item.load() assert item["mb_parentworkid"] == "3" def test_no_force(self): self.config["parentwork"]["force"] = False item = Item( path="/file", mb_workid="1", mb_parentworkid="XXX", parentwork_workid_current="1", parentwork="parentwork", ) item.add(self.lib) self.run_command("parentwork") item.load() assert item["mb_parentworkid"] == "XXX" def test_direct_parent_work(self): assert "2" == parentwork.direct_parent_id("1")[0] assert "3" == parentwork.work_parent_id("1")[0] �������������������������������������������������������������������������������������������������������������������������������������������������������beetbox-beets-01f1faf/test/plugins/test_permissions.py����������������������������������������������0000664�0000000�0000000�00000005730�14723254774�0023400�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������"""Tests for the 'permissions' plugin.""" import os import platform from unittest.mock import Mock, patch from beets.test._common import touch from beets.test.helper import AsIsImporterMixin, ImportTestCase, PluginMixin from beets.util import displayable_path from beetsplug.permissions import ( check_permissions, convert_perm, dirs_in_library, ) class PermissionsPluginTest(AsIsImporterMixin, PluginMixin, ImportTestCase): plugin = "permissions" def setUp(self): super().setUp() self.config["permissions"] = {"file": "777", "dir": "777"} def test_permissions_on_album_imported(self): self.do_thing(True) def test_permissions_on_item_imported(self): self.config["import"]["singletons"] = True self.do_thing(True) @patch("os.chmod", Mock()) def test_failing_to_set_permissions(self): self.do_thing(False) def do_thing(self, expect_success): if platform.system() == "Windows": self.skipTest("permissions not available on Windows") def get_stat(v): return ( os.stat(os.path.join(self.temp_dir, b"import", *v)).st_mode & 0o777 ) typs = ["file", "dir"] track_file = (b"album", b"track_1.mp3") self.exp_perms = { True: { k: convert_perm(self.config["permissions"][k].get()) for k in typs }, False: {k: get_stat(v) for (k, v) in zip(typs, (track_file, ()))}, } self.run_asis_importer() item = self.lib.items().get() self.assertPerms(item.path, "file", expect_success) for path in dirs_in_library(self.lib.directory, item.path): self.assertPerms(path, "dir", expect_success) def assertPerms(self, path, typ, expect_success): for x in [ (True, self.exp_perms[expect_success][typ], "!="), (False, self.exp_perms[not expect_success][typ], "=="), ]: msg = "{} : {} {} {}".format( displayable_path(path), oct(os.stat(path).st_mode), x[2], oct(x[1]), ) assert x[0] == check_permissions(path, x[1]), msg def test_convert_perm_from_string(self): assert convert_perm("10") == 8 def test_convert_perm_from_int(self): assert convert_perm(10) == 8 def test_permissions_on_set_art(self): self.do_set_art(True) @patch("os.chmod", Mock()) def test_failing_permissions_on_set_art(self): self.do_set_art(False) def do_set_art(self, expect_success): if platform.system() == "Windows": self.skipTest("permissions not available on Windows") self.run_asis_importer() album = self.lib.albums().get() artpath = os.path.join(self.temp_dir, b"cover.jpg") touch(artpath) album.set_art(artpath) assert expect_success == check_permissions(album.artpath, 0o777) ����������������������������������������beetbox-beets-01f1faf/test/plugins/test_play.py�����������������������������������������������������0000664�0000000�0000000�00000011337�14723254774�0021772�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# This file is part of beets. # Copyright 2016, Jesse Weinstein # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the # "Software"), to deal in the Software without restriction, including # without limitation the rights to use, copy, modify, merge, publish, # distribute, sublicense, and/or sell copies of the Software, and to # permit persons to whom the Software is furnished to do so, subject to # the following conditions: # # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. """Tests for the play plugin""" import os import sys import unittest from unittest.mock import ANY, patch import pytest from beets.test.helper import CleanupModulesMixin, PluginTestCase, control_stdin from beets.ui import UserError from beets.util import open_anything from beetsplug.play import PlayPlugin @patch("beetsplug.play.util.interactive_open") class PlayPluginTest(CleanupModulesMixin, PluginTestCase): modules = (PlayPlugin.__module__,) plugin = "play" def setUp(self): super().setUp() self.item = self.add_item(album="a nice älbum", title="aNiceTitle") self.lib.add_album([self.item]) self.config["play"]["command"] = "echo" def run_and_assert( self, open_mock, args=("title:aNiceTitle",), expected_cmd="echo", expected_playlist=None, ): self.run_command("play", *args) open_mock.assert_called_once_with(ANY, expected_cmd) expected_playlist = expected_playlist or self.item.path.decode("utf-8") exp_playlist = expected_playlist + "\n" with open(open_mock.call_args[0][0][0], "rb") as playlist: assert exp_playlist == playlist.read().decode("utf-8") def test_basic(self, open_mock): self.run_and_assert(open_mock) def test_album_option(self, open_mock): self.run_and_assert(open_mock, ["-a", "nice"]) def test_args_option(self, open_mock): self.run_and_assert( open_mock, ["-A", "foo", "title:aNiceTitle"], "echo foo" ) def test_args_option_in_middle(self, open_mock): self.config["play"]["command"] = "echo $args other" self.run_and_assert( open_mock, ["-A", "foo", "title:aNiceTitle"], "echo foo other" ) def test_unset_args_option_in_middle(self, open_mock): self.config["play"]["command"] = "echo $args other" self.run_and_assert(open_mock, ["title:aNiceTitle"], "echo other") # FIXME: fails on windows @unittest.skipIf(sys.platform == "win32", "win32") def test_relative_to(self, open_mock): self.config["play"]["command"] = "echo" self.config["play"]["relative_to"] = "/something" path = os.path.relpath(self.item.path, b"/something") playlist = path.decode("utf-8") self.run_and_assert( open_mock, expected_cmd="echo", expected_playlist=playlist ) def test_use_folders(self, open_mock): self.config["play"]["command"] = None self.config["play"]["use_folders"] = True self.run_command("play", "-a", "nice") open_mock.assert_called_once_with(ANY, open_anything()) with open(open_mock.call_args[0][0][0], "rb") as f: playlist = f.read().decode("utf-8") assert ( f'{os.path.dirname(self.item.path.decode("utf-8"))}\n' == playlist ) def test_raw(self, open_mock): self.config["play"]["raw"] = True self.run_command("play", "nice") open_mock.assert_called_once_with([self.item.path], "echo") def test_not_found(self, open_mock): self.run_command("play", "not found") open_mock.assert_not_called() def test_warning_threshold(self, open_mock): self.config["play"]["warning_threshold"] = 1 self.add_item(title="another NiceTitle") with control_stdin("a"): self.run_command("play", "nice") open_mock.assert_not_called() def test_skip_warning_threshold_bypass(self, open_mock): self.config["play"]["warning_threshold"] = 1 self.other_item = self.add_item(title="another NiceTitle") expected_playlist = "{}\n{}".format( self.item.path.decode("utf-8"), self.other_item.path.decode("utf-8") ) with control_stdin("a"): self.run_and_assert( open_mock, ["-y", "NiceTitle"], expected_playlist=expected_playlist, ) def test_command_failed(self, open_mock): open_mock.side_effect = OSError("some reason") with pytest.raises(UserError): self.run_command("play", "title:aNiceTitle") �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������beetbox-beets-01f1faf/test/plugins/test_player.py���������������������������������������������������0000664�0000000�0000000�00000114453�14723254774�0022324�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# This file is part of beets. # Copyright 2016, Adrian Sampson. # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the # "Software"), to deal in the Software without restriction, including # without limitation the rights to use, copy, modify, merge, publish, # distribute, sublicense, and/or sell copies of the Software, and to # permit persons to whom the Software is furnished to do so, subject to # the following conditions: # # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. """Tests for BPD's implementation of the MPD protocol.""" import importlib.util import multiprocessing as mp import os import socket import sys import tempfile import threading import time import unittest from contextlib import contextmanager # Mock GstPlayer so that the forked process doesn't attempt to import gi: from unittest import mock import confuse import pytest import yaml from beets.test.helper import PluginTestCase from beets.util import bluelet from beetsplug import bpd gstplayer = importlib.util.module_from_spec( importlib.util.find_spec("beetsplug.bpd.gstplayer") ) def _gstplayer_play(*_): bpd.gstplayer._GstPlayer.playing = True return mock.DEFAULT gstplayer._GstPlayer = mock.MagicMock( spec_set=[ "time", "volume", "playing", "run", "play_file", "pause", "stop", "seek", "play", "get_decoders", ], **{ "playing": False, "volume": 0, "time.return_value": (0, 0), "play_file.side_effect": _gstplayer_play, "play.side_effect": _gstplayer_play, "get_decoders.return_value": {"default": ({"audio/mpeg"}, {"mp3"})}, }, ) gstplayer.GstPlayer = lambda _: gstplayer._GstPlayer sys.modules["beetsplug.bpd.gstplayer"] = gstplayer bpd.gstplayer = gstplayer class CommandParseTest(unittest.TestCase): def test_no_args(self): s = r"command" c = bpd.Command(s) assert c.name == "command" assert c.args == [] def test_one_unquoted_arg(self): s = r"command hello" c = bpd.Command(s) assert c.name == "command" assert c.args == ["hello"] def test_two_unquoted_args(self): s = r"command hello there" c = bpd.Command(s) assert c.name == "command" assert c.args == ["hello", "there"] def test_one_quoted_arg(self): s = r'command "hello there"' c = bpd.Command(s) assert c.name == "command" assert c.args == ["hello there"] def test_heterogenous_args(self): s = r'command "hello there" sir' c = bpd.Command(s) assert c.name == "command" assert c.args == ["hello there", "sir"] def test_quote_in_arg(self): s = r'command "hello \" there"' c = bpd.Command(s) assert c.args == ['hello " there'] def test_backslash_in_arg(self): s = r'command "hello \\ there"' c = bpd.Command(s) assert c.args == ["hello \\ there"] class MPCResponse: def __init__(self, raw_response): body = b"\n".join(raw_response.split(b"\n")[:-2]).decode("utf-8") self.data = self._parse_body(body) status = raw_response.split(b"\n")[-2].decode("utf-8") self.ok, self.err_data = self._parse_status(status) def _parse_status(self, status): """Parses the first response line, which contains the status.""" if status.startswith("OK") or status.startswith("list_OK"): return True, None elif status.startswith("ACK"): code, rest = status[5:].split("@", 1) pos, rest = rest.split("]", 1) cmd, rest = rest[2:].split("}") return False, (int(code), int(pos), cmd, rest[1:]) else: raise RuntimeError(f"Unexpected status: {status!r}") def _parse_body(self, body): """Messages are generally in the format "header: content". Convert them into a dict, storing the values for repeated headers as lists of strings, and non-repeated ones as string. """ data = {} repeated_headers = set() for line in body.split("\n"): if not line: continue if ":" not in line: raise RuntimeError(f"Unexpected line: {line!r}") header, content = line.split(":", 1) content = content.lstrip() if header in repeated_headers: data[header].append(content) elif header in data: data[header] = [data[header], content] repeated_headers.add(header) else: data[header] = content return data class MPCClient: def __init__(self, sock, do_hello=True): self.sock = sock self.buf = b"" if do_hello: hello = self.get_response() if not hello.ok: raise RuntimeError("Bad hello") def get_response(self, force_multi=None): """Wait for a full server response and wrap it in a helper class. If the request was a batch request then this will return a list of `MPCResponse`s, one for each processed subcommand. """ response = b"" responses = [] while True: line = self.readline() response += line if line.startswith(b"OK") or line.startswith(b"ACK"): if force_multi or any(responses): if line.startswith(b"ACK"): responses.append(MPCResponse(response)) n_remaining = force_multi - len(responses) responses.extend([None] * n_remaining) return responses else: return MPCResponse(response) if line.startswith(b"list_OK"): responses.append(MPCResponse(response)) response = b"" elif not line: raise RuntimeError(f"Unexpected response: {line!r}") def serialise_command(self, command, *args): cmd = [command.encode("utf-8")] for arg in [a.encode("utf-8") for a in args]: if b" " in arg: cmd.append(b'"' + arg + b'"') else: cmd.append(arg) return b" ".join(cmd) + b"\n" def send_command(self, command, *args): request = self.serialise_command(command, *args) self.sock.sendall(request) return self.get_response() def send_commands(self, *commands): """Use MPD command batching to send multiple commands at once. Each item of commands is a tuple containing a command followed by any arguments. """ requests = [] for command_and_args in commands: command = command_and_args[0] args = command_and_args[1:] requests.append(self.serialise_command(command, *args)) requests.insert(0, b"command_list_ok_begin\n") requests.append(b"command_list_end\n") request = b"".join(requests) self.sock.sendall(request) return self.get_response(force_multi=len(commands)) def readline(self, terminator=b"\n", bufsize=1024): """Reads a line of data from the socket.""" while True: if terminator in self.buf: line, self.buf = self.buf.split(terminator, 1) line += terminator return line self.sock.settimeout(1) data = self.sock.recv(bufsize) if data: self.buf += data else: line = self.buf self.buf = b"" return line def implements(commands, fail=False): def _test(self): with self.run_bpd() as client: response = client.send_command("commands") self._assert_ok(response) implemented = response.data["command"] assert commands.intersection(implemented) == commands return unittest.expectedFailure(_test) if fail else _test bluelet_listener = bluelet.Listener @mock.patch("beets.util.bluelet.Listener") def start_server(args, assigned_port, listener_patch): """Start the bpd server, writing the port to `assigned_port`.""" def listener_wrap(host, port): """Wrap `bluelet.Listener`, writing the port to `assigend_port`.""" # `bluelet.Listener` has previously been saved to # `bluelet_listener` as this function will replace it at its # original location. listener = bluelet_listener(host, port) # read port assigned by OS assigned_port.put_nowait(listener.sock.getsockname()[1]) return listener listener_patch.side_effect = listener_wrap import beets.ui beets.ui.main(args) class BPDTestHelper(PluginTestCase): db_on_disk = True plugin = "bpd" def setUp(self): super().setUp() self.item1 = self.add_item( title="Track One Title", track=1, album="Album Title", artist="Artist Name", ) self.item2 = self.add_item( title="Track Two Title", track=2, album="Album Title", artist="Artist Name", ) self.lib.add_album([self.item1, self.item2]) @contextmanager def run_bpd( self, host="localhost", password=None, do_hello=True, second_client=False, ): """Runs BPD in another process, configured with the same library database as we created in the setUp method. Exposes a client that is connected to the server, and kills the server at the end. """ # Create a config file: config = { "pluginpath": [os.fsdecode(self.temp_dir)], "plugins": "bpd", # use port 0 to let the OS choose a free port "bpd": {"host": host, "port": 0, "control_port": 0}, } if password: config["bpd"]["password"] = password config_file = tempfile.NamedTemporaryFile( mode="wb", dir=os.fsdecode(self.temp_dir), suffix=".yaml", delete=False, ) config_file.write( yaml.dump(config, Dumper=confuse.Dumper, encoding="utf-8") ) config_file.close() # Fork and launch BPD in the new process: assigned_port = mp.Queue(2) # 2 slots, `control_port` and `port` server = mp.Process( target=start_server, args=( [ "--library", self.config["library"].as_filename(), "--directory", os.fsdecode(self.libdir), "--config", os.fsdecode(config_file.name), "bpd", ], assigned_port, ), ) server.start() try: assigned_port.get(timeout=1) # skip control_port port = assigned_port.get(timeout=0.5) # read port sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) try: sock.connect((host, port)) if second_client: sock2 = socket.socket(socket.AF_INET, socket.SOCK_STREAM) try: sock2.connect((host, port)) yield ( MPCClient(sock, do_hello), MPCClient(sock2, do_hello), ) finally: sock2.close() else: yield MPCClient(sock, do_hello) finally: sock.close() finally: server.terminate() server.join(timeout=0.2) def _assert_ok(self, *responses): for response in responses: assert response is not None assert response.ok, f"Response failed: {response.err_data}" def _assert_failed(self, response, code, pos=None): """Check that a command failed with a specific error code. If this is a list of responses, first check all preceding commands were OK. """ if pos is not None: previous_commands = response[0:pos] self._assert_ok(*previous_commands) response = response[pos] assert not response.ok if pos is not None: assert pos == response.err_data[1] if code is not None: assert code == response.err_data[0] def _bpd_add(self, client, *items, **kwargs): """Add the given item to the BPD playlist or queue.""" paths = [ "/".join( [ item.artist, item.album, os.fsdecode(os.path.basename(item.path)), ] ) for item in items ] playlist = kwargs.get("playlist") if playlist: commands = [("playlistadd", playlist, path) for path in paths] else: commands = [("add", path) for path in paths] responses = client.send_commands(*commands) self._assert_ok(*responses) class BPDTest(BPDTestHelper): def test_server_hello(self): with self.run_bpd(do_hello=False) as client: assert client.readline() == b"OK MPD 0.16.0\n" def test_unknown_cmd(self): with self.run_bpd() as client: response = client.send_command("notacommand") self._assert_failed(response, bpd.ERROR_UNKNOWN) def test_unexpected_argument(self): with self.run_bpd() as client: response = client.send_command("ping", "extra argument") self._assert_failed(response, bpd.ERROR_ARG) def test_missing_argument(self): with self.run_bpd() as client: response = client.send_command("add") self._assert_failed(response, bpd.ERROR_ARG) def test_system_error(self): with self.run_bpd() as client: response = client.send_command("crash") self._assert_failed(response, bpd.ERROR_SYSTEM) def test_empty_request(self): with self.run_bpd() as client: response = client.send_command("") self._assert_failed(response, bpd.ERROR_UNKNOWN) class BPDQueryTest(BPDTestHelper): test_implements_query = implements( { "clearerror", } ) def test_cmd_currentsong(self): with self.run_bpd() as client: self._bpd_add(client, self.item1) responses = client.send_commands( ("play",), ("currentsong",), ("stop",), ("currentsong",) ) self._assert_ok(*responses) assert "1" == responses[1].data["Id"] assert "Id" not in responses[3].data def test_cmd_currentsong_tagtypes(self): with self.run_bpd() as client: self._bpd_add(client, self.item1) responses = client.send_commands(("play",), ("currentsong",)) self._assert_ok(*responses) assert BPDConnectionTest.TAGTYPES.union(BPDQueueTest.METADATA) == set( responses[1].data.keys() ) def test_cmd_status(self): with self.run_bpd() as client: self._bpd_add(client, self.item1, self.item2) responses = client.send_commands( ("status",), ("play",), ("status",) ) self._assert_ok(*responses) fields_not_playing = { "repeat", "random", "single", "consume", "playlist", "playlistlength", "mixrampdb", "state", "volume", } assert fields_not_playing == set(responses[0].data.keys()) fields_playing = fields_not_playing | { "song", "songid", "time", "elapsed", "bitrate", "duration", "audio", "nextsong", "nextsongid", } assert fields_playing == set(responses[2].data.keys()) def test_cmd_stats(self): with self.run_bpd() as client: response = client.send_command("stats") self._assert_ok(response) details = { "artists", "albums", "songs", "uptime", "db_playtime", "db_update", "playtime", } assert details == set(response.data.keys()) def test_cmd_idle(self): def _toggle(c): for _ in range(3): rs = c.send_commands(("play",), ("pause",)) # time.sleep(0.05) # uncomment if test is flaky if any(not r.ok for r in rs): raise RuntimeError("Toggler failed") with self.run_bpd(second_client=True) as (client, client2): self._bpd_add(client, self.item1, self.item2) toggler = threading.Thread(target=_toggle, args=(client2,)) toggler.start() # Idling will hang until the toggler thread changes the play state. # Since the client sockets have a 1s timeout set at worst this will # raise a socket.timeout and fail the test if the toggler thread # manages to finish before the idle command is sent here. response = client.send_command("idle", "player") toggler.join() self._assert_ok(response) def test_cmd_idle_with_pending(self): with self.run_bpd(second_client=True) as (client, client2): response1 = client.send_command("random", "1") response2 = client2.send_command("idle") self._assert_ok(response1, response2) assert "options" == response2.data["changed"] def test_cmd_noidle(self): with self.run_bpd() as client: # Manually send a command without reading a response. request = client.serialise_command("idle") client.sock.sendall(request) time.sleep(0.01) response = client.send_command("noidle") self._assert_ok(response) def test_cmd_noidle_when_not_idle(self): with self.run_bpd() as client: # Manually send a command without reading a response. request = client.serialise_command("noidle") client.sock.sendall(request) response = client.send_command("notacommand") self._assert_failed(response, bpd.ERROR_UNKNOWN) class BPDPlaybackTest(BPDTestHelper): test_implements_playback = implements( { "random", } ) def test_cmd_consume(self): with self.run_bpd() as client: self._bpd_add(client, self.item1, self.item2) responses = client.send_commands( ("consume", "0"), ("playlistinfo",), ("next",), ("playlistinfo",), ("consume", "1"), ("playlistinfo",), ("play", "0"), ("next",), ("playlistinfo",), ("status",), ) self._assert_ok(*responses) assert responses[1].data["Id"] == responses[3].data["Id"] assert ["1", "2"] == responses[5].data["Id"] assert "2" == responses[8].data["Id"] assert "1" == responses[9].data["consume"] assert "play" == responses[9].data["state"] def test_cmd_consume_in_reverse(self): with self.run_bpd() as client: self._bpd_add(client, self.item1, self.item2) responses = client.send_commands( ("consume", "1"), ("play", "1"), ("playlistinfo",), ("previous",), ("playlistinfo",), ("status",), ) self._assert_ok(*responses) assert ["1", "2"] == responses[2].data["Id"] assert "1" == responses[4].data["Id"] assert "play" == responses[5].data["state"] def test_cmd_single(self): with self.run_bpd() as client: self._bpd_add(client, self.item1, self.item2) responses = client.send_commands( ("status",), ("single", "1"), ("play",), ("status",), ("next",), ("status",), ) self._assert_ok(*responses) assert "0" == responses[0].data["single"] assert "1" == responses[3].data["single"] assert "play" == responses[3].data["state"] assert "stop" == responses[5].data["state"] def test_cmd_repeat(self): with self.run_bpd() as client: self._bpd_add(client, self.item1, self.item2) responses = client.send_commands( ("repeat", "1"), ("play",), ("currentsong",), ("next",), ("currentsong",), ("next",), ("currentsong",), ) self._assert_ok(*responses) assert "1" == responses[2].data["Id"] assert "2" == responses[4].data["Id"] assert "1" == responses[6].data["Id"] def test_cmd_repeat_with_single(self): with self.run_bpd() as client: self._bpd_add(client, self.item1, self.item2) responses = client.send_commands( ("repeat", "1"), ("single", "1"), ("play",), ("currentsong",), ("next",), ("status",), ("currentsong",), ) self._assert_ok(*responses) assert "1" == responses[3].data["Id"] assert "play" == responses[5].data["state"] assert "1" == responses[6].data["Id"] def test_cmd_repeat_in_reverse(self): with self.run_bpd() as client: self._bpd_add(client, self.item1, self.item2) responses = client.send_commands( ("repeat", "1"), ("play",), ("currentsong",), ("previous",), ("currentsong",), ) self._assert_ok(*responses) assert "1" == responses[2].data["Id"] assert "2" == responses[4].data["Id"] def test_cmd_repeat_with_single_in_reverse(self): with self.run_bpd() as client: self._bpd_add(client, self.item1, self.item2) responses = client.send_commands( ("repeat", "1"), ("single", "1"), ("play",), ("currentsong",), ("previous",), ("status",), ("currentsong",), ) self._assert_ok(*responses) assert "1" == responses[3].data["Id"] assert "play" == responses[5].data["state"] assert "1" == responses[6].data["Id"] def test_cmd_crossfade(self): with self.run_bpd() as client: responses = client.send_commands( ("status",), ("crossfade", "123"), ("status",), ("crossfade", "-2"), ) response = client.send_command("crossfade", "0.5") self._assert_failed(responses, bpd.ERROR_ARG, pos=3) self._assert_failed(response, bpd.ERROR_ARG) assert "xfade" not in responses[0].data assert 123 == pytest.approx(int(responses[2].data["xfade"])) def test_cmd_mixrampdb(self): with self.run_bpd() as client: responses = client.send_commands(("mixrampdb", "-17"), ("status",)) self._assert_ok(*responses) assert -17 == pytest.approx(float(responses[1].data["mixrampdb"])) def test_cmd_mixrampdelay(self): with self.run_bpd() as client: responses = client.send_commands( ("mixrampdelay", "2"), ("status",), ("mixrampdelay", "nan"), ("status",), ("mixrampdelay", "-2"), ) self._assert_failed(responses, bpd.ERROR_ARG, pos=4) assert 2 == pytest.approx(float(responses[1].data["mixrampdelay"])) assert "mixrampdelay" not in responses[3].data def test_cmd_setvol(self): with self.run_bpd() as client: responses = client.send_commands( ("setvol", "67"), ("status",), ("setvol", "32"), ("status",), ("setvol", "101"), ) self._assert_failed(responses, bpd.ERROR_ARG, pos=4) assert "67" == responses[1].data["volume"] assert "32" == responses[3].data["volume"] def test_cmd_volume(self): with self.run_bpd() as client: responses = client.send_commands( ("setvol", "10"), ("volume", "5"), ("volume", "-2"), ("status",) ) self._assert_ok(*responses) assert "13" == responses[3].data["volume"] def test_cmd_replay_gain(self): with self.run_bpd() as client: responses = client.send_commands( ("replay_gain_mode", "track"), ("replay_gain_status",), ("replay_gain_mode", "notanoption"), ) self._assert_failed(responses, bpd.ERROR_ARG, pos=2) assert "track" == responses[1].data["replay_gain_mode"] class BPDControlTest(BPDTestHelper): test_implements_control = implements( { "seek", "seekid", "seekcur", }, fail=True, ) def test_cmd_play(self): with self.run_bpd() as client: self._bpd_add(client, self.item1, self.item2) responses = client.send_commands( ("status",), ("play",), ("status",), ("play", "1"), ("currentsong",), ) self._assert_ok(*responses) assert "stop" == responses[0].data["state"] assert "play" == responses[2].data["state"] assert "2" == responses[4].data["Id"] def test_cmd_playid(self): with self.run_bpd() as client: self._bpd_add(client, self.item1, self.item2) responses = client.send_commands( ("playid", "2"), ("currentsong",), ("clear",) ) self._bpd_add(client, self.item2, self.item1) responses.extend( client.send_commands(("playid", "2"), ("currentsong",)) ) self._assert_ok(*responses) assert "2" == responses[1].data["Id"] assert "2" == responses[4].data["Id"] def test_cmd_pause(self): with self.run_bpd() as client: self._bpd_add(client, self.item1) responses = client.send_commands( ("play",), ("pause",), ("status",), ("currentsong",) ) self._assert_ok(*responses) assert "pause" == responses[2].data["state"] assert "1" == responses[3].data["Id"] def test_cmd_stop(self): with self.run_bpd() as client: self._bpd_add(client, self.item1) responses = client.send_commands( ("play",), ("stop",), ("status",), ("currentsong",) ) self._assert_ok(*responses) assert "stop" == responses[2].data["state"] assert "Id" not in responses[3].data def test_cmd_next(self): with self.run_bpd() as client: self._bpd_add(client, self.item1, self.item2) responses = client.send_commands( ("play",), ("currentsong",), ("next",), ("currentsong",), ("next",), ("status",), ) self._assert_ok(*responses) assert "1" == responses[1].data["Id"] assert "2" == responses[3].data["Id"] assert "stop" == responses[5].data["state"] def test_cmd_previous(self): with self.run_bpd() as client: self._bpd_add(client, self.item1, self.item2) responses = client.send_commands( ("play", "1"), ("currentsong",), ("previous",), ("currentsong",), ("previous",), ("status",), ("currentsong",), ) self._assert_ok(*responses) assert "2" == responses[1].data["Id"] assert "1" == responses[3].data["Id"] assert "play" == responses[5].data["state"] assert "1" == responses[6].data["Id"] class BPDQueueTest(BPDTestHelper): test_implements_queue = implements( { "addid", "clear", "delete", "deleteid", "move", "moveid", "playlist", "playlistfind", "playlistsearch", "plchanges", "plchangesposid", "prio", "prioid", "rangeid", "shuffle", "swap", "swapid", "addtagid", "cleartagid", }, fail=True, ) METADATA = {"Pos", "Time", "Id", "file", "duration"} def test_cmd_add(self): with self.run_bpd() as client: self._bpd_add(client, self.item1) def test_cmd_playlistinfo(self): with self.run_bpd() as client: self._bpd_add(client, self.item1, self.item2) responses = client.send_commands( ("playlistinfo",), ("playlistinfo", "0"), ("playlistinfo", "0:2"), ("playlistinfo", "200"), ) self._assert_failed(responses, bpd.ERROR_ARG, pos=3) assert "1" == responses[1].data["Id"] assert ["1", "2"] == responses[2].data["Id"] def test_cmd_playlistinfo_tagtypes(self): with self.run_bpd() as client: self._bpd_add(client, self.item1) response = client.send_command("playlistinfo", "0") self._assert_ok(response) assert BPDConnectionTest.TAGTYPES.union(BPDQueueTest.METADATA) == set( response.data.keys() ) def test_cmd_playlistid(self): with self.run_bpd() as client: self._bpd_add(client, self.item1, self.item2) responses = client.send_commands( ("playlistid", "2"), ("playlistid",) ) self._assert_ok(*responses) assert "Track Two Title" == responses[0].data["Title"] assert ["1", "2"] == responses[1].data["Track"] class BPDPlaylistsTest(BPDTestHelper): test_implements_playlists = implements({"playlistadd"}) def test_cmd_listplaylist(self): with self.run_bpd() as client: response = client.send_command("listplaylist", "anything") self._assert_failed(response, bpd.ERROR_NO_EXIST) def test_cmd_listplaylistinfo(self): with self.run_bpd() as client: response = client.send_command("listplaylistinfo", "anything") self._assert_failed(response, bpd.ERROR_NO_EXIST) def test_cmd_listplaylists(self): with self.run_bpd() as client: response = client.send_command("listplaylists") self._assert_failed(response, bpd.ERROR_UNKNOWN) def test_cmd_load(self): with self.run_bpd() as client: response = client.send_command("load", "anything") self._assert_failed(response, bpd.ERROR_NO_EXIST) @unittest.skip def test_cmd_playlistadd(self): with self.run_bpd() as client: self._bpd_add(client, self.item1, playlist="anything") def test_cmd_playlistclear(self): with self.run_bpd() as client: response = client.send_command("playlistclear", "anything") self._assert_failed(response, bpd.ERROR_UNKNOWN) def test_cmd_playlistdelete(self): with self.run_bpd() as client: response = client.send_command("playlistdelete", "anything", "0") self._assert_failed(response, bpd.ERROR_UNKNOWN) def test_cmd_playlistmove(self): with self.run_bpd() as client: response = client.send_command("playlistmove", "anything", "0", "1") self._assert_failed(response, bpd.ERROR_UNKNOWN) def test_cmd_rename(self): with self.run_bpd() as client: response = client.send_command("rename", "anything", "newname") self._assert_failed(response, bpd.ERROR_UNKNOWN) def test_cmd_rm(self): with self.run_bpd() as client: response = client.send_command("rm", "anything") self._assert_failed(response, bpd.ERROR_UNKNOWN) def test_cmd_save(self): with self.run_bpd() as client: self._bpd_add(client, self.item1) response = client.send_command("save", "newplaylist") self._assert_failed(response, bpd.ERROR_UNKNOWN) class BPDDatabaseTest(BPDTestHelper): test_implements_database = implements( { "albumart", "find", "findadd", "listall", "listallinfo", "listfiles", "readcomments", "searchadd", "searchaddpl", "update", "rescan", }, fail=True, ) def test_cmd_search(self): with self.run_bpd() as client: response = client.send_command("search", "track", "1") self._assert_ok(response) assert self.item1.title == response.data["Title"] def test_cmd_list(self): with self.run_bpd() as client: responses = client.send_commands( ("list", "album"), ("list", "track"), ("list", "album", "artist", "Artist Name", "track"), ) self._assert_failed(responses, bpd.ERROR_ARG, pos=2) assert "Album Title" == responses[0].data["Album"] assert ["1", "2"] == responses[1].data["Track"] def test_cmd_list_three_arg_form(self): with self.run_bpd() as client: responses = client.send_commands( ("list", "album", "artist", "Artist Name"), ("list", "album", "Artist Name"), ("list", "track", "Artist Name"), ) self._assert_failed(responses, bpd.ERROR_ARG, pos=2) assert responses[0].data == responses[1].data def test_cmd_lsinfo(self): with self.run_bpd() as client: response1 = client.send_command("lsinfo") self._assert_ok(response1) response2 = client.send_command( "lsinfo", response1.data["directory"] ) self._assert_ok(response2) response3 = client.send_command( "lsinfo", response2.data["directory"] ) self._assert_ok(response3) assert self.item1.title in response3.data["Title"] def test_cmd_count(self): with self.run_bpd() as client: response = client.send_command("count", "track", "1") self._assert_ok(response) assert "1" == response.data["songs"] assert "0" == response.data["playtime"] class BPDMountsTest(BPDTestHelper): test_implements_mounts = implements( { "mount", "unmount", "listmounts", "listneighbors", }, fail=True, ) class BPDStickerTest(BPDTestHelper): test_implements_stickers = implements( { "sticker", }, fail=True, ) class BPDConnectionTest(BPDTestHelper): test_implements_connection = implements( { "close", "kill", } ) ALL_MPD_TAGTYPES = { "Artist", "ArtistSort", "Album", "AlbumSort", "AlbumArtist", "AlbumArtistSort", "Title", "Track", "Name", "Genre", "Date", "Composer", "Performer", "Comment", "Disc", "Label", "OriginalDate", "MUSICBRAINZ_ARTISTID", "MUSICBRAINZ_ALBUMID", "MUSICBRAINZ_ALBUMARTISTID", "MUSICBRAINZ_TRACKID", "MUSICBRAINZ_RELEASETRACKID", "MUSICBRAINZ_WORKID", } UNSUPPORTED_TAGTYPES = { "MUSICBRAINZ_WORKID", # not tracked by beets "Performer", # not tracked by beets "AlbumSort", # not tracked by beets "Name", # junk field for internet radio } TAGTYPES = ALL_MPD_TAGTYPES.difference(UNSUPPORTED_TAGTYPES) def test_cmd_password(self): with self.run_bpd(password="abc123") as client: response = client.send_command("status") self._assert_failed(response, bpd.ERROR_PERMISSION) response = client.send_command("password", "wrong") self._assert_failed(response, bpd.ERROR_PASSWORD) responses = client.send_commands( ("password", "abc123"), ("status",) ) self._assert_ok(*responses) def test_cmd_ping(self): with self.run_bpd() as client: response = client.send_command("ping") self._assert_ok(response) def test_cmd_tagtypes(self): with self.run_bpd() as client: response = client.send_command("tagtypes") self._assert_ok(response) assert self.TAGTYPES == set(response.data["tagtype"]) @unittest.skip def test_tagtypes_mask(self): with self.run_bpd() as client: response = client.send_command("tagtypes", "clear") self._assert_ok(response) class BPDPartitionTest(BPDTestHelper): test_implements_partitions = implements( { "partition", "listpartitions", "newpartition", }, fail=True, ) class BPDDeviceTest(BPDTestHelper): test_implements_devices = implements( { "disableoutput", "enableoutput", "toggleoutput", "outputs", }, fail=True, ) class BPDReflectionTest(BPDTestHelper): test_implements_reflection = implements( { "config", "commands", "notcommands", "urlhandlers", }, fail=True, ) def test_cmd_decoders(self): with self.run_bpd() as client: response = client.send_command("decoders") self._assert_ok(response) assert "default" == response.data["plugin"] assert "mp3" == response.data["suffix"] assert "audio/mpeg" == response.data["mime_type"] class BPDPeersTest(BPDTestHelper): test_implements_peers = implements( { "subscribe", "unsubscribe", "channels", "readmessages", "sendmessage", }, fail=True, ) ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������beetbox-beets-01f1faf/test/plugins/test_playlist.py�������������������������������������������������0000664�0000000�0000000�00000026635�14723254774�0022675�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# This file is part of beets. # Copyright 2016, Thomas Scholtes. # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the # "Software"), to deal in the Software without restriction, including # without limitation the rights to use, copy, modify, merge, publish, # distribute, sublicense, and/or sell copies of the Software, and to # permit persons to whom the Software is furnished to do so, subject to # the following conditions: # # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. import os from shlex import quote import beets from beets.test import _common from beets.test.helper import PluginTestCase class PlaylistTestCase(PluginTestCase): plugin = "playlist" preload_plugin = False def setUp(self): super().setUp() self.music_dir = os.path.expanduser(os.path.join("~", "Music")) i1 = _common.item() i1.path = beets.util.normpath( os.path.join( self.music_dir, "a", "b", "c.mp3", ) ) i1.title = "some item" i1.album = "some album" self.lib.add(i1) self.lib.add_album([i1]) i2 = _common.item() i2.path = beets.util.normpath( os.path.join( self.music_dir, "d", "e", "f.mp3", ) ) i2.title = "another item" i2.album = "another album" self.lib.add(i2) self.lib.add_album([i2]) i3 = _common.item() i3.path = beets.util.normpath( os.path.join( self.music_dir, "x", "y", "z.mp3", ) ) i3.title = "yet another item" i3.album = "yet another album" self.lib.add(i3) self.lib.add_album([i3]) self.playlist_dir = os.path.join( os.fsdecode(self.temp_dir), "playlists" ) os.makedirs(self.playlist_dir) self.config["directory"] = self.music_dir self.config["playlist"]["playlist_dir"] = self.playlist_dir self.setup_test() self.load_plugins() def setup_test(self): raise NotImplementedError class PlaylistQueryTest: def test_name_query_with_absolute_paths_in_playlist(self): q = "playlist:absolute" results = self.lib.items(q) assert {i.title for i in results} == {"some item", "another item"} def test_path_query_with_absolute_paths_in_playlist(self): q = "playlist:{}".format( quote( os.path.join( self.playlist_dir, "absolute.m3u", ) ) ) results = self.lib.items(q) assert {i.title for i in results} == {"some item", "another item"} def test_name_query_with_relative_paths_in_playlist(self): q = "playlist:relative" results = self.lib.items(q) assert {i.title for i in results} == {"some item", "another item"} def test_path_query_with_relative_paths_in_playlist(self): q = "playlist:{}".format( quote( os.path.join( self.playlist_dir, "relative.m3u", ) ) ) results = self.lib.items(q) assert {i.title for i in results} == {"some item", "another item"} def test_name_query_with_nonexisting_playlist(self): q = "playlist:nonexisting" results = self.lib.items(q) assert set(results) == set() def test_path_query_with_nonexisting_playlist(self): q = "playlist:{}".format( quote( os.path.join( self.playlist_dir, self.playlist_dir, "nonexisting.m3u", ) ) ) results = self.lib.items(q) assert set(results) == set() class PlaylistTestRelativeToLib(PlaylistQueryTest, PlaylistTestCase): def setup_test(self): with open(os.path.join(self.playlist_dir, "absolute.m3u"), "w") as f: f.write( "{}\n".format(os.path.join(self.music_dir, "a", "b", "c.mp3")) ) f.write( "{}\n".format(os.path.join(self.music_dir, "d", "e", "f.mp3")) ) f.write( "{}\n".format(os.path.join(self.music_dir, "nonexisting.mp3")) ) with open(os.path.join(self.playlist_dir, "relative.m3u"), "w") as f: f.write("{}\n".format(os.path.join("a", "b", "c.mp3"))) f.write("{}\n".format(os.path.join("d", "e", "f.mp3"))) f.write("{}\n".format("nonexisting.mp3")) self.config["playlist"]["relative_to"] = "library" class PlaylistTestRelativeToDir(PlaylistQueryTest, PlaylistTestCase): def setup_test(self): with open(os.path.join(self.playlist_dir, "absolute.m3u"), "w") as f: f.write( "{}\n".format(os.path.join(self.music_dir, "a", "b", "c.mp3")) ) f.write( "{}\n".format(os.path.join(self.music_dir, "d", "e", "f.mp3")) ) f.write( "{}\n".format(os.path.join(self.music_dir, "nonexisting.mp3")) ) with open(os.path.join(self.playlist_dir, "relative.m3u"), "w") as f: f.write("{}\n".format(os.path.join("a", "b", "c.mp3"))) f.write("{}\n".format(os.path.join("d", "e", "f.mp3"))) f.write("{}\n".format("nonexisting.mp3")) self.config["playlist"]["relative_to"] = self.music_dir class PlaylistTestRelativeToPls(PlaylistQueryTest, PlaylistTestCase): def setup_test(self): with open(os.path.join(self.playlist_dir, "absolute.m3u"), "w") as f: f.write( "{}\n".format(os.path.join(self.music_dir, "a", "b", "c.mp3")) ) f.write( "{}\n".format(os.path.join(self.music_dir, "d", "e", "f.mp3")) ) f.write( "{}\n".format(os.path.join(self.music_dir, "nonexisting.mp3")) ) with open(os.path.join(self.playlist_dir, "relative.m3u"), "w") as f: f.write( "{}\n".format( os.path.relpath( os.path.join(self.music_dir, "a", "b", "c.mp3"), start=self.playlist_dir, ) ) ) f.write( "{}\n".format( os.path.relpath( os.path.join(self.music_dir, "d", "e", "f.mp3"), start=self.playlist_dir, ) ) ) f.write( "{}\n".format( os.path.relpath( os.path.join(self.music_dir, "nonexisting.mp3"), start=self.playlist_dir, ) ) ) self.config["playlist"]["relative_to"] = "playlist" self.config["playlist"]["playlist_dir"] = self.playlist_dir class PlaylistUpdateTest: def setup_test(self): with open(os.path.join(self.playlist_dir, "absolute.m3u"), "w") as f: f.write( "{}\n".format(os.path.join(self.music_dir, "a", "b", "c.mp3")) ) f.write( "{}\n".format(os.path.join(self.music_dir, "d", "e", "f.mp3")) ) f.write( "{}\n".format(os.path.join(self.music_dir, "nonexisting.mp3")) ) with open(os.path.join(self.playlist_dir, "relative.m3u"), "w") as f: f.write("{}\n".format(os.path.join("a", "b", "c.mp3"))) f.write("{}\n".format(os.path.join("d", "e", "f.mp3"))) f.write("{}\n".format("nonexisting.mp3")) self.config["playlist"]["auto"] = True self.config["playlist"]["relative_to"] = "library" class PlaylistTestItemMoved(PlaylistUpdateTest, PlaylistTestCase): def test_item_moved(self): # Emit item_moved event for an item that is in a playlist results = self.lib.items( "path:{}".format( quote(os.path.join(self.music_dir, "d", "e", "f.mp3")) ) ) item = results[0] beets.plugins.send( "item_moved", item=item, source=item.path, destination=beets.util.bytestring_path( os.path.join(self.music_dir, "g", "h", "i.mp3") ), ) # Emit item_moved event for an item that is not in a playlist results = self.lib.items( "path:{}".format( quote(os.path.join(self.music_dir, "x", "y", "z.mp3")) ) ) item = results[0] beets.plugins.send( "item_moved", item=item, source=item.path, destination=beets.util.bytestring_path( os.path.join(self.music_dir, "u", "v", "w.mp3") ), ) # Emit cli_exit event beets.plugins.send("cli_exit", lib=self.lib) # Check playlist with absolute paths playlist_path = os.path.join(self.playlist_dir, "absolute.m3u") with open(playlist_path) as f: lines = [line.strip() for line in f.readlines()] assert lines == [ os.path.join(self.music_dir, "a", "b", "c.mp3"), os.path.join(self.music_dir, "g", "h", "i.mp3"), os.path.join(self.music_dir, "nonexisting.mp3"), ] # Check playlist with relative paths playlist_path = os.path.join(self.playlist_dir, "relative.m3u") with open(playlist_path) as f: lines = [line.strip() for line in f.readlines()] assert lines == [ os.path.join("a", "b", "c.mp3"), os.path.join("g", "h", "i.mp3"), "nonexisting.mp3", ] class PlaylistTestItemRemoved(PlaylistUpdateTest, PlaylistTestCase): def test_item_removed(self): # Emit item_removed event for an item that is in a playlist results = self.lib.items( "path:{}".format( quote(os.path.join(self.music_dir, "d", "e", "f.mp3")) ) ) item = results[0] beets.plugins.send("item_removed", item=item) # Emit item_removed event for an item that is not in a playlist results = self.lib.items( "path:{}".format( quote(os.path.join(self.music_dir, "x", "y", "z.mp3")) ) ) item = results[0] beets.plugins.send("item_removed", item=item) # Emit cli_exit event beets.plugins.send("cli_exit", lib=self.lib) # Check playlist with absolute paths playlist_path = os.path.join(self.playlist_dir, "absolute.m3u") with open(playlist_path) as f: lines = [line.strip() for line in f.readlines()] assert lines == [ os.path.join(self.music_dir, "a", "b", "c.mp3"), os.path.join(self.music_dir, "nonexisting.mp3"), ] # Check playlist with relative paths playlist_path = os.path.join(self.playlist_dir, "relative.m3u") with open(playlist_path) as f: lines = [line.strip() for line in f.readlines()] assert lines == [os.path.join("a", "b", "c.mp3"), "nonexisting.mp3"] ���������������������������������������������������������������������������������������������������beetbox-beets-01f1faf/test/plugins/test_plexupdate.py�����������������������������������������������0000664�0000000�0000000�00000011652�14723254774�0023200�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������import responses from beets.test.helper import PluginTestCase from beetsplug.plexupdate import get_music_section, update_plex class PlexUpdateTest(PluginTestCase): plugin = "plexupdate" def add_response_get_music_section(self, section_name="Music"): """Create response for mocking the get_music_section function.""" escaped_section_name = section_name.replace('"', '\\"') body = ( '<?xml version="1.0" encoding="UTF-8"?>' '<MediaContainer size="3" allowSync="0" ' 'identifier="com.plexapp.plugins.library" ' 'mediaTagPrefix="/system/bundle/media/flags/" ' 'mediaTagVersion="1413367228" title1="Plex Library">' '<Directory allowSync="0" art="/:/resources/movie-fanart.jpg" ' 'filters="1" refreshing="0" thumb="/:/resources/movie.png" ' 'key="3" type="movie" title="Movies" ' 'composite="/library/sections/3/composite/1416232668" ' 'agent="com.plexapp.agents.imdb" scanner="Plex Movie Scanner" ' 'language="de" uuid="92f68526-21eb-4ee2-8e22-d36355a17f1f" ' 'updatedAt="1416232668" createdAt="1415720680">' '<Location id="3" path="/home/marv/Media/Videos/Movies" />' "</Directory>" '<Directory allowSync="0" art="/:/resources/artist-fanart.jpg" ' 'filters="1" refreshing="0" thumb="/:/resources/artist.png" ' 'key="2" type="artist" title="' + escaped_section_name + '" ' 'composite="/library/sections/2/composite/1416929243" ' 'agent="com.plexapp.agents.lastfm" scanner="Plex Music Scanner" ' 'language="en" uuid="90897c95-b3bd-4778-a9c8-1f43cb78f047" ' 'updatedAt="1416929243" createdAt="1415691331">' '<Location id="2" path="/home/marv/Media/Musik" />' "</Directory>" '<Directory allowSync="0" art="/:/resources/show-fanart.jpg" ' 'filters="1" refreshing="0" thumb="/:/resources/show.png" ' 'key="1" type="show" title="TV Shows" ' 'composite="/library/sections/1/composite/1416320800" ' 'agent="com.plexapp.agents.thetvdb" scanner="Plex Series Scanner" ' 'language="de" uuid="04d2249b-160a-4ae9-8100-106f4ec1a218" ' 'updatedAt="1416320800" createdAt="1415690983">' '<Location id="1" path="/home/marv/Media/Videos/Series" />' "</Directory>" "</MediaContainer>" ) status = 200 content_type = "text/xml;charset=utf-8" responses.add( responses.GET, "http://localhost:32400/library/sections", body=body, status=status, content_type=content_type, ) def add_response_update_plex(self): """Create response for mocking the update_plex function.""" body = "" status = 200 content_type = "text/html" responses.add( responses.GET, "http://localhost:32400/library/sections/2/refresh", body=body, status=status, content_type=content_type, ) def setUp(self): super().setUp() self.config["plex"] = {"host": "localhost", "port": 32400} @responses.activate def test_get_music_section(self): # Adding response. self.add_response_get_music_section() # Test if section key is "2" out of the mocking data. assert ( get_music_section( self.config["plex"]["host"], self.config["plex"]["port"], self.config["plex"]["token"], self.config["plex"]["library_name"].get(), self.config["plex"]["secure"], self.config["plex"]["ignore_cert_errors"], ) == "2" ) @responses.activate def test_get_named_music_section(self): # Adding response. self.add_response_get_music_section("My Music Library") assert ( get_music_section( self.config["plex"]["host"], self.config["plex"]["port"], self.config["plex"]["token"], "My Music Library", self.config["plex"]["secure"], self.config["plex"]["ignore_cert_errors"], ) == "2" ) @responses.activate def test_update_plex(self): # Adding responses. self.add_response_get_music_section() self.add_response_update_plex() # Testing status code of the mocking request. assert ( update_plex( self.config["plex"]["host"], self.config["plex"]["port"], self.config["plex"]["token"], self.config["plex"]["library_name"].get(), self.config["plex"]["secure"], self.config["plex"]["ignore_cert_errors"], ).status_code == 200 ) ��������������������������������������������������������������������������������������beetbox-beets-01f1faf/test/plugins/test_plugin_mediafield.py����������������������������������������0000664�0000000�0000000�00000010240�14723254774�0024456�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# This file is part of beets. # Copyright 2016, Adrian Sampson. # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the # "Software"), to deal in the Software without restriction, including # without limitation the rights to use, copy, modify, merge, publish, # distribute, sublicense, and/or sell copies of the Software, and to # permit persons to whom the Software is furnished to do so, subject to # the following conditions: # # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. """Tests the facility that lets plugins add custom field to MediaFile.""" import os import shutil import mediafile import pytest from beets.library import Item from beets.plugins import BeetsPlugin from beets.test import _common from beets.test.helper import BeetsTestCase from beets.util import bytestring_path, syspath field_extension = mediafile.MediaField( mediafile.MP3DescStorageStyle("customtag"), mediafile.MP4StorageStyle("----:com.apple.iTunes:customtag"), mediafile.StorageStyle("customtag"), mediafile.ASFStorageStyle("customtag"), ) list_field_extension = mediafile.ListMediaField( mediafile.MP3ListDescStorageStyle("customlisttag"), mediafile.MP4ListStorageStyle("----:com.apple.iTunes:customlisttag"), mediafile.ListStorageStyle("customlisttag"), mediafile.ASFStorageStyle("customlisttag"), ) class ExtendedFieldTestMixin(BeetsTestCase): def _mediafile_fixture(self, name, extension="mp3"): name = bytestring_path(name + "." + extension) src = os.path.join(_common.RSRC, name) target = os.path.join(self.temp_dir, name) shutil.copy(syspath(src), syspath(target)) return mediafile.MediaFile(target) def test_extended_field_write(self): plugin = BeetsPlugin() plugin.add_media_field("customtag", field_extension) try: mf = self._mediafile_fixture("empty") mf.customtag = "F#" mf.save() mf = mediafile.MediaFile(mf.path) assert mf.customtag == "F#" finally: delattr(mediafile.MediaFile, "customtag") Item._media_fields.remove("customtag") def test_extended_list_field_write(self): plugin = BeetsPlugin() plugin.add_media_field("customlisttag", list_field_extension) try: mf = self._mediafile_fixture("empty") mf.customlisttag = ["a", "b"] mf.save() mf = mediafile.MediaFile(mf.path) assert mf.customlisttag == ["a", "b"] finally: delattr(mediafile.MediaFile, "customlisttag") Item._media_fields.remove("customlisttag") def test_write_extended_tag_from_item(self): plugin = BeetsPlugin() plugin.add_media_field("customtag", field_extension) try: mf = self._mediafile_fixture("empty") assert mf.customtag is None item = Item(path=mf.path, customtag="Gb") item.write() mf = mediafile.MediaFile(mf.path) assert mf.customtag == "Gb" finally: delattr(mediafile.MediaFile, "customtag") Item._media_fields.remove("customtag") def test_read_flexible_attribute_from_file(self): plugin = BeetsPlugin() plugin.add_media_field("customtag", field_extension) try: mf = self._mediafile_fixture("empty") mf.update({"customtag": "F#"}) mf.save() item = Item.from_path(mf.path) assert item["customtag"] == "F#" finally: delattr(mediafile.MediaFile, "customtag") Item._media_fields.remove("customtag") def test_invalid_descriptor(self): with pytest.raises( ValueError, match="must be an instance of MediaField" ): mediafile.MediaFile.add_field("somekey", True) def test_overwrite_property(self): with pytest.raises( ValueError, match='property "artist" already exists' ): mediafile.MediaFile.add_field("artist", mediafile.MediaField()) ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������beetbox-beets-01f1faf/test/plugins/test_random.py���������������������������������������������������0000664�0000000�0000000�00000006034�14723254774�0022303�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# This file is part of beets. # Copyright 2019, Carl Suster # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the # "Software"), to deal in the Software without restriction, including # without limitation the rights to use, copy, modify, merge, publish, # distribute, sublicense, and/or sell copies of the Software, and to # permit persons to whom the Software is furnished to do so, subject to # the following conditions: # # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. """Test the beets.random utilities associated with the random plugin.""" import math import unittest from random import Random import pytest from beets import random from beets.test.helper import TestHelper class RandomTest(TestHelper, unittest.TestCase): def setUp(self): self.lib = None self.artist1 = "Artist 1" self.artist2 = "Artist 2" self.item1 = self.create_item(artist=self.artist1) self.item2 = self.create_item(artist=self.artist2) self.items = [self.item1, self.item2] for _ in range(8): self.items.append(self.create_item(artist=self.artist2)) self.random_gen = Random() self.random_gen.seed(12345) def _stats(self, data): mean = sum(data) / len(data) stdev = math.sqrt(sum((p - mean) ** 2 for p in data) / (len(data) - 1)) quot, rem = divmod(len(data), 2) if rem: median = sorted(data)[quot] else: median = sum(sorted(data)[quot - 1 : quot + 1]) / 2 return mean, stdev, median def test_equal_permutation(self): """We have a list of items where only one item is from artist1 and the rest are from artist2. If we permute weighted by the artist field then the solo track will almost always end up near the start. If we use a different field then it'll be in the middle on average. """ def experiment(field, histogram=False): """Permutes the list of items 500 times and calculates the position of self.item1 each time. Returns stats about that position. """ positions = [] for _ in range(500): shuffled = list( random._equal_chance_permutation( self.items, field=field, random_gen=self.random_gen ) ) positions.append(shuffled.index(self.item1)) # Print a histogram (useful for debugging). if histogram: for i in range(len(self.items)): print("{:2d} {}".format(i, "*" * positions.count(i))) return self._stats(positions) mean1, stdev1, median1 = experiment("artist") mean2, stdev2, median2 = experiment("track") assert 0 == pytest.approx(median1, abs=1) assert len(self.items) // 2 == pytest.approx(median2, abs=1) assert stdev2 > stdev1 ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������beetbox-beets-01f1faf/test/plugins/test_replaygain.py�����������������������������������������������0000664�0000000�0000000�00000027727�14723254774�0023172�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# This file is part of beets. # Copyright 2016, Thomas Scholtes # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the # "Software"), to deal in the Software without restriction, including # without limitation the rights to use, copy, modify, merge, publish, # distribute, sublicense, and/or sell copies of the Software, and to # permit persons to whom the Software is furnished to do so, subject to # the following conditions: # # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. import unittest from typing import ClassVar import pytest from mediafile import MediaFile from beets import config from beets.test.helper import ( AsIsImporterMixin, ImportTestCase, PluginMixin, has_program, ) from beetsplug.replaygain import ( FatalGstreamerPluginReplayGainError, GStreamerBackend, ) try: import gi gi.require_version("Gst", "1.0") GST_AVAILABLE = True except (ImportError, ValueError): GST_AVAILABLE = False if any(has_program(cmd, ["-v"]) for cmd in ["mp3gain", "aacgain"]): GAIN_PROG_AVAILABLE = True else: GAIN_PROG_AVAILABLE = False FFMPEG_AVAILABLE = has_program("ffmpeg", ["-version"]) def reset_replaygain(item): item["rg_track_peak"] = None item["rg_track_gain"] = None item["rg_album_gain"] = None item["rg_album_gain"] = None item["r128_track_gain"] = None item["r128_album_gain"] = None item.write() item.store() class ReplayGainTestCase(PluginMixin, ImportTestCase): db_on_disk = True plugin = "replaygain" preload_plugin = False backend: ClassVar[str] def setUp(self): # Implemented by Mixins, see above. This may decide to skip the test. self.test_backend() super().setUp() self.config["replaygain"]["backend"] = self.backend self.load_plugins() class ThreadedImportMixin: def setUp(self): super().setUp() self.config["threaded"] = True class GstBackendMixin: backend = "gstreamer" has_r128_support = True def test_backend(self): """Check whether the backend actually has all required functionality.""" try: # Check if required plugins can be loaded by instantiating a # GStreamerBackend (via its .__init__). config["replaygain"]["targetlevel"] = 89 GStreamerBackend(config["replaygain"], None) except FatalGstreamerPluginReplayGainError as e: # Skip the test if plugins could not be loaded. self.skipTest(str(e)) class CmdBackendMixin: backend = "command" has_r128_support = False def test_backend(self): """Check whether the backend actually has all required functionality.""" pass class FfmpegBackendMixin: backend = "ffmpeg" has_r128_support = True def test_backend(self): """Check whether the backend actually has all required functionality.""" pass class ReplayGainCliTest: FNAME: str def _add_album(self, *args, **kwargs): # Use a file with non-zero volume (most test assets are total silence) album = self.add_album_fixture(*args, fname=self.FNAME, **kwargs) for item in album.items(): reset_replaygain(item) return album def test_cli_saves_track_gain(self): self._add_album(2) for item in self.lib.items(): assert item.rg_track_peak is None assert item.rg_track_gain is None mediafile = MediaFile(item.path) assert mediafile.rg_track_peak is None assert mediafile.rg_track_gain is None self.run_command("replaygain") # Skip the test if rg_track_peak and rg_track gain is None, assuming # that it could only happen if the decoder plugins are missing. if all( i.rg_track_peak is None and i.rg_track_gain is None for i in self.lib.items() ): self.skipTest("decoder plugins could not be loaded.") for item in self.lib.items(): assert item.rg_track_peak is not None assert item.rg_track_gain is not None mediafile = MediaFile(item.path) assert mediafile.rg_track_peak == pytest.approx( item.rg_track_peak, abs=1e-6 ) assert mediafile.rg_track_gain == pytest.approx( item.rg_track_gain, abs=1e-2 ) def test_cli_skips_calculated_tracks(self): album_rg = self._add_album(1) item_rg = album_rg.items()[0] if self.has_r128_support: album_r128 = self._add_album(1, ext="opus") item_r128 = album_r128.items()[0] self.run_command("replaygain") item_rg.load() assert item_rg.rg_track_gain is not None assert item_rg.rg_track_peak is not None assert item_rg.r128_track_gain is None item_rg.rg_track_gain += 1.0 item_rg.rg_track_peak += 1.0 item_rg.store() rg_track_gain = item_rg.rg_track_gain rg_track_peak = item_rg.rg_track_peak if self.has_r128_support: item_r128.load() assert item_r128.r128_track_gain is not None assert item_r128.rg_track_gain is None assert item_r128.rg_track_peak is None item_r128.r128_track_gain += 1.0 item_r128.store() r128_track_gain = item_r128.r128_track_gain self.run_command("replaygain") item_rg.load() assert item_rg.rg_track_gain == rg_track_gain assert item_rg.rg_track_peak == rg_track_peak if self.has_r128_support: item_r128.load() assert item_r128.r128_track_gain == r128_track_gain def test_cli_does_not_skip_wrong_tag_type(self): """Check that items that have tags of the wrong type won't be skipped.""" if not self.has_r128_support: # This test is a lot less interesting if the backend cannot write # both tag types. self.skipTest( "r128 tags for opus not supported on backend {}".format( self.backend ) ) album_rg = self._add_album(1) item_rg = album_rg.items()[0] album_r128 = self._add_album(1, ext="opus") item_r128 = album_r128.items()[0] item_rg.r128_track_gain = 0.0 item_rg.store() item_r128.rg_track_gain = 0.0 item_r128.rg_track_peak = 42.0 item_r128.store() self.run_command("replaygain") item_rg.load() item_r128.load() assert item_rg.rg_track_gain is not None assert item_rg.rg_track_peak is not None # FIXME: Should the plugin null this field? # assert item_rg.r128_track_gain is None assert item_r128.r128_track_gain is not None # FIXME: Should the plugin null these fields? # assert item_r128.rg_track_gain is None # assert item_r128.rg_track_peak is None def test_cli_saves_album_gain_to_file(self): self._add_album(2) for item in self.lib.items(): mediafile = MediaFile(item.path) assert mediafile.rg_album_peak is None assert mediafile.rg_album_gain is None self.run_command("replaygain", "-a") peaks = [] gains = [] for item in self.lib.items(): mediafile = MediaFile(item.path) peaks.append(mediafile.rg_album_peak) gains.append(mediafile.rg_album_gain) # Make sure they are all the same assert max(peaks) == min(peaks) assert max(gains) == min(gains) assert max(gains) != 0.0 assert max(peaks) != 0.0 def test_cli_writes_only_r128_tags(self): if not self.has_r128_support: self.skipTest( "r128 tags for opus not supported on backend {}".format( self.backend ) ) album = self._add_album(2, ext="opus") self.run_command("replaygain", "-a") for item in album.items(): mediafile = MediaFile(item.path) # does not write REPLAYGAIN_* tags assert mediafile.rg_track_gain is None assert mediafile.rg_album_gain is None # writes R128_* tags assert mediafile.r128_track_gain is not None assert mediafile.r128_album_gain is not None def test_targetlevel_has_effect(self): album = self._add_album(1) item = album.items()[0] def analyse(target_level): self.config["replaygain"]["targetlevel"] = target_level self.run_command("replaygain", "-f") item.load() return item.rg_track_gain gain_relative_to_84 = analyse(84) gain_relative_to_89 = analyse(89) assert gain_relative_to_84 != gain_relative_to_89 def test_r128_targetlevel_has_effect(self): if not self.has_r128_support: self.skipTest( "r128 tags for opus not supported on backend {}".format( self.backend ) ) album = self._add_album(1, ext="opus") item = album.items()[0] def analyse(target_level): self.config["replaygain"]["r128_targetlevel"] = target_level self.run_command("replaygain", "-f") item.load() return item.r128_track_gain gain_relative_to_84 = analyse(84) gain_relative_to_89 = analyse(89) assert gain_relative_to_84 != gain_relative_to_89 def test_per_disc(self): # Use the per_disc option and add a little more concurrency. album = self._add_album(track_count=4, disc_count=3) self.config["replaygain"]["per_disc"] = True self.run_command("replaygain", "-a") # FIXME: Add fixtures with known track/album gain (within a suitable # tolerance) so that we can actually check per-disc operation here. for item in album.items(): assert item.rg_track_gain is not None assert item.rg_album_gain is not None @unittest.skipIf(not GST_AVAILABLE, "gstreamer cannot be found") class ReplayGainGstCliTest( ReplayGainCliTest, ReplayGainTestCase, GstBackendMixin ): FNAME = "full" # file contains only silence @unittest.skipIf(not GAIN_PROG_AVAILABLE, "no *gain command found") class ReplayGainCmdCliTest( ReplayGainCliTest, ReplayGainTestCase, CmdBackendMixin ): FNAME = "full" # file contains only silence @unittest.skipIf(not FFMPEG_AVAILABLE, "ffmpeg cannot be found") class ReplayGainFfmpegCliTest( ReplayGainCliTest, ReplayGainTestCase, FfmpegBackendMixin ): FNAME = "full" # file contains only silence @unittest.skipIf(not FFMPEG_AVAILABLE, "ffmpeg cannot be found") class ReplayGainFfmpegNoiseCliTest( ReplayGainCliTest, ReplayGainTestCase, FfmpegBackendMixin ): FNAME = "whitenoise" class ImportTest(AsIsImporterMixin): def test_import_converted(self): self.run_asis_importer() for item in self.lib.items(): # FIXME: Add fixtures with known track/album gain (within a # suitable tolerance) so that we can actually check correct # operation here. assert item.rg_track_gain is not None assert item.rg_album_gain is not None @unittest.skipIf(not GST_AVAILABLE, "gstreamer cannot be found") class ReplayGainGstImportTest(ImportTest, ReplayGainTestCase, GstBackendMixin): pass @unittest.skipIf(not GAIN_PROG_AVAILABLE, "no *gain command found") class ReplayGainCmdImportTest(ImportTest, ReplayGainTestCase, CmdBackendMixin): pass @unittest.skipIf(not FFMPEG_AVAILABLE, "ffmpeg cannot be found") class ReplayGainFfmpegImportTest( ImportTest, ReplayGainTestCase, FfmpegBackendMixin ): pass @unittest.skipIf(not FFMPEG_AVAILABLE, "ffmpeg cannot be found") class ReplayGainFfmpegThreadedImportTest( ThreadedImportMixin, ImportTest, ReplayGainTestCase, FfmpegBackendMixin ): pass �����������������������������������������beetbox-beets-01f1faf/test/plugins/test_smartplaylist.py��������������������������������������������0000664�0000000�0000000�00000031233�14723254774�0023732�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# This file is part of beets. # Copyright 2016, Bruno Cauet. # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the # "Software"), to deal in the Software without restriction, including # without limitation the rights to use, copy, modify, merge, publish, # distribute, sublicense, and/or sell copies of the Software, and to # permit persons to whom the Software is furnished to do so, subject to # the following conditions: # # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. from os import fsdecode, path, remove from shutil import rmtree from tempfile import mkdtemp from unittest.mock import MagicMock, Mock, PropertyMock import pytest from beets import config from beets.dbcore import OrQuery from beets.dbcore.query import FixedFieldSort, MultipleSort, NullSort from beets.library import Album, Item, parse_query_string from beets.test.helper import BeetsTestCase, PluginTestCase from beets.ui import UserError from beets.util import CHAR_REPLACE, bytestring_path, syspath from beetsplug.smartplaylist import SmartPlaylistPlugin class SmartPlaylistTest(BeetsTestCase): def test_build_queries(self): spl = SmartPlaylistPlugin() assert spl._matched_playlists is None assert spl._unmatched_playlists is None config["smartplaylist"]["playlists"].set([]) spl.build_queries() assert spl._matched_playlists == set() assert spl._unmatched_playlists == set() config["smartplaylist"]["playlists"].set( [ {"name": "foo", "query": "FOO foo"}, {"name": "bar", "album_query": ["BAR bar1", "BAR bar2"]}, {"name": "baz", "query": "BAZ baz", "album_query": "BAZ baz"}, ] ) spl.build_queries() assert spl._matched_playlists == set() foo_foo = parse_query_string("FOO foo", Item) baz_baz = parse_query_string("BAZ baz", Item) baz_baz2 = parse_query_string("BAZ baz", Album) bar_bar = OrQuery( ( parse_query_string("BAR bar1", Album)[0], parse_query_string("BAR bar2", Album)[0], ) ) assert spl._unmatched_playlists == { ("foo", foo_foo, (None, None)), ("baz", baz_baz, baz_baz2), ("bar", (None, None), (bar_bar, None)), } def test_build_queries_with_sorts(self): spl = SmartPlaylistPlugin() config["smartplaylist"]["playlists"].set( [ {"name": "no_sort", "query": "foo"}, {"name": "one_sort", "query": "foo year+"}, {"name": "only_empty_sorts", "query": ["foo", "bar"]}, {"name": "one_non_empty_sort", "query": ["foo year+", "bar"]}, { "name": "multiple_sorts", "query": ["foo year+", "bar genre-"], }, { "name": "mixed", "query": ["foo year+", "bar", "baz genre+ id-"], }, ] ) spl.build_queries() sorts = {name: sort for name, (_, sort), _ in spl._unmatched_playlists} sort = FixedFieldSort # short cut since we're only dealing with this assert sorts["no_sort"] == NullSort() assert sorts["one_sort"] == sort("year") assert sorts["only_empty_sorts"] is None assert sorts["one_non_empty_sort"] == sort("year") assert sorts["multiple_sorts"] == MultipleSort( [sort("year"), sort("genre", False)] ) assert sorts["mixed"] == MultipleSort( [sort("year"), sort("genre"), sort("id", False)] ) def test_matches(self): spl = SmartPlaylistPlugin() a = MagicMock(Album) i = MagicMock(Item) assert not spl.matches(i, None, None) assert not spl.matches(a, None, None) query = Mock() query.match.side_effect = {i: True}.__getitem__ assert spl.matches(i, query, None) assert not spl.matches(a, query, None) a_query = Mock() a_query.match.side_effect = {a: True}.__getitem__ assert not spl.matches(i, None, a_query) assert spl.matches(a, None, a_query) assert spl.matches(i, query, a_query) assert spl.matches(a, query, a_query) def test_db_changes(self): spl = SmartPlaylistPlugin() nones = None, None pl1 = "1", ("q1", None), nones pl2 = "2", ("q2", None), nones pl3 = "3", ("q3", None), nones spl._unmatched_playlists = {pl1, pl2, pl3} spl._matched_playlists = set() spl.matches = Mock(return_value=False) spl.db_change(None, "nothing") assert spl._unmatched_playlists == {pl1, pl2, pl3} assert spl._matched_playlists == set() spl.matches.side_effect = lambda _, q, __: q == "q3" spl.db_change(None, "matches 3") assert spl._unmatched_playlists == {pl1, pl2} assert spl._matched_playlists == {pl3} spl.matches.side_effect = lambda _, q, __: q == "q1" spl.db_change(None, "matches 3") assert spl._matched_playlists == {pl1, pl3} assert spl._unmatched_playlists == {pl2} def test_playlist_update(self): spl = SmartPlaylistPlugin() i = Mock(path=b"/tagada.mp3") i.evaluate_template.side_effect = lambda pl, _: pl.replace( b"$title", b"ta:ga:da" ).decode() lib = Mock() lib.replacements = CHAR_REPLACE lib.items.return_value = [i] lib.albums.return_value = [] q = Mock() a_q = Mock() pl = b"$title-my<playlist>.m3u", (q, None), (a_q, None) spl._matched_playlists = [pl] dir = bytestring_path(mkdtemp()) config["smartplaylist"]["relative_to"] = False config["smartplaylist"]["playlist_dir"] = fsdecode(dir) try: spl.update_playlists(lib) except Exception: rmtree(syspath(dir)) raise lib.items.assert_called_once_with(q, None) lib.albums.assert_called_once_with(a_q, None) m3u_filepath = path.join(dir, b"ta_ga_da-my_playlist_.m3u") self.assertExists(m3u_filepath) with open(syspath(m3u_filepath), "rb") as f: content = f.read() rmtree(syspath(dir)) assert content == b"/tagada.mp3\n" def test_playlist_update_output_extm3u(self): spl = SmartPlaylistPlugin() i = MagicMock() type(i).artist = PropertyMock(return_value="fake artist") type(i).title = PropertyMock(return_value="fake title") type(i).length = PropertyMock(return_value=300.123) type(i).path = PropertyMock(return_value=b"/tagada.mp3") i.evaluate_template.side_effect = lambda pl, _: pl.replace( b"$title", b"ta:ga:da", ).decode() lib = Mock() lib.replacements = CHAR_REPLACE lib.items.return_value = [i] lib.albums.return_value = [] q = Mock() a_q = Mock() pl = b"$title-my<playlist>.m3u", (q, None), (a_q, None) spl._matched_playlists = [pl] dir = bytestring_path(mkdtemp()) config["smartplaylist"]["output"] = "extm3u" config["smartplaylist"]["prefix"] = "http://beets:8337/files" config["smartplaylist"]["relative_to"] = False config["smartplaylist"]["playlist_dir"] = fsdecode(dir) try: spl.update_playlists(lib) except Exception: rmtree(syspath(dir)) raise lib.items.assert_called_once_with(q, None) lib.albums.assert_called_once_with(a_q, None) m3u_filepath = path.join(dir, b"ta_ga_da-my_playlist_.m3u") self.assertExists(m3u_filepath) with open(syspath(m3u_filepath), "rb") as f: content = f.read() rmtree(syspath(dir)) assert ( content == b"#EXTM3U\n" + b"#EXTINF:300,fake artist - fake title\n" + b"http://beets:8337/files/tagada.mp3\n" ) def test_playlist_update_output_extm3u_fields(self): spl = SmartPlaylistPlugin() i = MagicMock() type(i).artist = PropertyMock(return_value="Fake Artist") type(i).title = PropertyMock(return_value="fake Title") type(i).length = PropertyMock(return_value=300.123) type(i).path = PropertyMock(return_value=b"/tagada.mp3") a = {"id": 456, "genre": "Fake Genre"} i.__getitem__.side_effect = a.__getitem__ i.evaluate_template.side_effect = lambda pl, _: pl.replace( b"$title", b"ta:ga:da", ).decode() lib = Mock() lib.replacements = CHAR_REPLACE lib.items.return_value = [i] lib.albums.return_value = [] q = Mock() a_q = Mock() pl = b"$title-my<playlist>.m3u", (q, None), (a_q, None) spl._matched_playlists = [pl] dir = bytestring_path(mkdtemp()) config["smartplaylist"]["output"] = "extm3u" config["smartplaylist"]["relative_to"] = False config["smartplaylist"]["playlist_dir"] = fsdecode(dir) config["smartplaylist"]["fields"] = ["id", "genre"] try: spl.update_playlists(lib) except Exception: rmtree(syspath(dir)) raise lib.items.assert_called_once_with(q, None) lib.albums.assert_called_once_with(a_q, None) m3u_filepath = path.join(dir, b"ta_ga_da-my_playlist_.m3u") self.assertExists(m3u_filepath) with open(syspath(m3u_filepath), "rb") as f: content = f.read() rmtree(syspath(dir)) assert ( content == b"#EXTM3U\n" + b'#EXTINF:300 id="456" genre="Fake Genre",Fake Artist - fake Title\n' + b"/tagada.mp3\n" ) def test_playlist_update_uri_format(self): spl = SmartPlaylistPlugin() i = MagicMock() type(i).id = PropertyMock(return_value=3) type(i).path = PropertyMock(return_value=b"/tagada.mp3") i.evaluate_template.side_effect = lambda pl, _: pl.replace( b"$title", b"ta:ga:da" ).decode() lib = Mock() lib.replacements = CHAR_REPLACE lib.items.return_value = [i] lib.albums.return_value = [] q = Mock() a_q = Mock() pl = b"$title-my<playlist>.m3u", (q, None), (a_q, None) spl._matched_playlists = [pl] dir = bytestring_path(mkdtemp()) tpl = "http://beets:8337/item/$id/file" config["smartplaylist"]["uri_format"] = tpl config["smartplaylist"]["playlist_dir"] = fsdecode(dir) # The following options should be ignored when uri_format is set config["smartplaylist"]["relative_to"] = "/data" config["smartplaylist"]["prefix"] = "/prefix" config["smartplaylist"]["urlencode"] = True try: spl.update_playlists(lib) except Exception: rmtree(syspath(dir)) raise lib.items.assert_called_once_with(q, None) lib.albums.assert_called_once_with(a_q, None) m3u_filepath = path.join(dir, b"ta_ga_da-my_playlist_.m3u") self.assertExists(m3u_filepath) with open(syspath(m3u_filepath), "rb") as f: content = f.read() rmtree(syspath(dir)) assert content == b"http://beets:8337/item/3/file\n" class SmartPlaylistCLITest(PluginTestCase): plugin = "smartplaylist" def setUp(self): super().setUp() self.item = self.add_item() config["smartplaylist"]["playlists"].set( [ {"name": "my_playlist.m3u", "query": self.item.title}, {"name": "all.m3u", "query": ""}, ] ) config["smartplaylist"]["playlist_dir"].set(fsdecode(self.temp_dir)) def test_splupdate(self): with pytest.raises(UserError): self.run_with_output("splupdate", "tagada") self.run_with_output("splupdate", "my_playlist") m3u_path = path.join(self.temp_dir, b"my_playlist.m3u") self.assertExists(m3u_path) with open(syspath(m3u_path), "rb") as f: assert f.read() == self.item.path + b"\n" remove(syspath(m3u_path)) self.run_with_output("splupdate", "my_playlist.m3u") with open(syspath(m3u_path), "rb") as f: assert f.read() == self.item.path + b"\n" remove(syspath(m3u_path)) self.run_with_output("splupdate") for name in (b"my_playlist.m3u", b"all.m3u"): with open(path.join(self.temp_dir, name), "rb") as f: assert f.read() == self.item.path + b"\n" ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������beetbox-beets-01f1faf/test/plugins/test_spotify.py��������������������������������������������������0000664�0000000�0000000�00000012640�14723254774�0022520�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������"""Tests for the 'spotify' plugin""" import os from urllib.parse import parse_qs, urlparse import responses from beets.library import Item from beets.test import _common from beets.test.helper import BeetsTestCase from beetsplug import spotify class ArgumentsMock: def __init__(self, mode, show_failures): self.mode = mode self.show_failures = show_failures self.verbose = 1 def _params(url): """Get the query parameters from a URL.""" return parse_qs(urlparse(url).query) class SpotifyPluginTest(BeetsTestCase): @responses.activate def setUp(self): super().setUp() responses.add( responses.POST, spotify.SpotifyPlugin.oauth_token_url, status=200, json={ "access_token": "3XyiC3raJySbIAV5LVYj1DaWbcocNi3LAJTNXRnYY" "GVUl6mbbqXNhW3YcZnQgYXNWHFkVGSMlc0tMuvq8CF", "token_type": "Bearer", "expires_in": 3600, "scope": "", }, ) self.spotify = spotify.SpotifyPlugin() opts = ArgumentsMock("list", False) self.spotify._parse_opts(opts) def test_args(self): opts = ArgumentsMock("fail", True) assert not self.spotify._parse_opts(opts) opts = ArgumentsMock("list", False) assert self.spotify._parse_opts(opts) def test_empty_query(self): assert self.spotify._match_library_tracks(self.lib, "1=2") is None @responses.activate def test_missing_request(self): json_file = os.path.join( _common.RSRC, b"spotify", b"missing_request.json" ) with open(json_file, "rb") as f: response_body = f.read() responses.add( responses.GET, spotify.SpotifyPlugin.search_url, body=response_body, status=200, content_type="application/json", ) item = Item( mb_trackid="01234", album="lkajsdflakjsd", albumartist="ujydfsuihse", title="duifhjslkef", length=10, ) item.add(self.lib) assert [] == self.spotify._match_library_tracks(self.lib, "") params = _params(responses.calls[0].request.url) query = params["q"][0] assert "duifhjslkef" in query assert "artist:ujydfsuihse" in query assert "album:lkajsdflakjsd" in query assert params["type"] == ["track"] @responses.activate def test_track_request(self): json_file = os.path.join( _common.RSRC, b"spotify", b"track_request.json" ) with open(json_file, "rb") as f: response_body = f.read() responses.add( responses.GET, spotify.SpotifyPlugin.search_url, body=response_body, status=200, content_type="application/json", ) item = Item( mb_trackid="01234", album="Despicable Me 2", albumartist="Pharrell Williams", title="Happy", length=10, ) item.add(self.lib) results = self.spotify._match_library_tracks(self.lib, "Happy") assert 1 == len(results) assert "6NPVjNh8Jhru9xOmyQigds" == results[0]["id"] self.spotify._output_match_results(results) params = _params(responses.calls[0].request.url) query = params["q"][0] assert "Happy" in query assert "artist:Pharrell Williams" in query assert "album:Despicable Me 2" in query assert params["type"] == ["track"] @responses.activate def test_track_for_id(self): """Tests if plugin is able to fetch a track by its Spotify ID""" # Mock the Spotify 'Get Track' call json_file = os.path.join(_common.RSRC, b"spotify", b"track_info.json") with open(json_file, "rb") as f: response_body = f.read() responses.add( responses.GET, spotify.SpotifyPlugin.track_url + "6NPVjNh8Jhru9xOmyQigds", body=response_body, status=200, content_type="application/json", ) # Mock the Spotify 'Get Album' call json_file = os.path.join(_common.RSRC, b"spotify", b"album_info.json") with open(json_file, "rb") as f: response_body = f.read() responses.add( responses.GET, spotify.SpotifyPlugin.album_url + "5l3zEmMrOhOzG8d8s83GOL", body=response_body, status=200, content_type="application/json", ) # Mock the Spotify 'Search' call json_file = os.path.join( _common.RSRC, b"spotify", b"track_request.json" ) with open(json_file, "rb") as f: response_body = f.read() responses.add( responses.GET, spotify.SpotifyPlugin.search_url, body=response_body, status=200, content_type="application/json", ) track_info = self.spotify.track_for_id("6NPVjNh8Jhru9xOmyQigds") item = Item( mb_trackid=track_info.track_id, albumartist=track_info.artist, title=track_info.title, length=track_info.length, ) item.add(self.lib) results = self.spotify._match_library_tracks(self.lib, "Happy") assert 1 == len(results) assert "6NPVjNh8Jhru9xOmyQigds" == results[0]["id"] ������������������������������������������������������������������������������������������������beetbox-beets-01f1faf/test/plugins/test_subsonicupdate.py�������������������������������������������0000664�0000000�0000000�00000011145�14723254774�0024052�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������"""Tests for the 'subsonic' plugin.""" from urllib.parse import parse_qs, urlparse import responses from beets import config from beets.test.helper import BeetsTestCase from beetsplug import subsonicupdate class ArgumentsMock: """Argument mocks for tests.""" def __init__(self, mode, show_failures): """Constructs ArgumentsMock.""" self.mode = mode self.show_failures = show_failures self.verbose = 1 def _params(url): """Get the query parameters from a URL.""" return parse_qs(urlparse(url).query) class SubsonicPluginTest(BeetsTestCase): """Test class for subsonicupdate.""" @responses.activate def setUp(self): """Sets up config and plugin for test.""" super().setUp() config["subsonic"]["user"] = "admin" config["subsonic"]["pass"] = "admin" config["subsonic"]["url"] = "http://localhost:4040" responses.add( responses.GET, "http://localhost:4040/rest/ping.view", status=200, body=self.PING_BODY, ) self.subsonicupdate = subsonicupdate.SubsonicUpdate() PING_BODY = """ { "subsonic-response": { "status": "failed", "version": "1.15.0" } } """ SUCCESS_BODY = """ { "subsonic-response": { "status": "ok", "version": "1.15.0", "scanStatus": { "scanning": true, "count": 1000 } } } """ FAILED_BODY = """ { "subsonic-response": { "status": "failed", "version": "1.15.0", "error": { "code": 40, "message": "Wrong username or password." } } } """ ERROR_BODY = """ { "timestamp": 1599185854498, "status": 404, "error": "Not Found", "message": "No message available", "path": "/rest/startScn" } """ @responses.activate def test_start_scan(self): """Tests success path based on best case scenario.""" responses.add( responses.GET, "http://localhost:4040/rest/startScan", status=200, body=self.SUCCESS_BODY, ) self.subsonicupdate.start_scan() @responses.activate def test_start_scan_failed_bad_credentials(self): """Tests failed path based on bad credentials.""" responses.add( responses.GET, "http://localhost:4040/rest/startScan", status=200, body=self.FAILED_BODY, ) self.subsonicupdate.start_scan() @responses.activate def test_start_scan_failed_not_found(self): """Tests failed path based on resource not found.""" responses.add( responses.GET, "http://localhost:4040/rest/startScan", status=404, body=self.ERROR_BODY, ) self.subsonicupdate.start_scan() def test_start_scan_failed_unreachable(self): """Tests failed path based on service not available.""" self.subsonicupdate.start_scan() @responses.activate def test_url_with_context_path(self): """Tests success for included with contextPath.""" config["subsonic"]["url"] = "http://localhost:4040/contextPath/" responses.add( responses.GET, "http://localhost:4040/contextPath/rest/startScan", status=200, body=self.SUCCESS_BODY, ) self.subsonicupdate.start_scan() @responses.activate def test_url_with_trailing_forward_slash_url(self): """Tests success path based on trailing forward slash.""" config["subsonic"]["url"] = "http://localhost:4040/" responses.add( responses.GET, "http://localhost:4040/rest/startScan", status=200, body=self.SUCCESS_BODY, ) self.subsonicupdate.start_scan() @responses.activate def test_url_with_missing_port(self): """Tests failed path based on missing port.""" config["subsonic"]["url"] = "http://localhost/airsonic" responses.add( responses.GET, "http://localhost/airsonic/rest/startScan", status=200, body=self.SUCCESS_BODY, ) self.subsonicupdate.start_scan() @responses.activate def test_url_with_missing_schema(self): """Tests failed path based on missing schema.""" config["subsonic"]["url"] = "localhost:4040/airsonic" responses.add( responses.GET, "http://localhost:4040/rest/startScan", status=200, body=self.SUCCESS_BODY, ) self.subsonicupdate.start_scan() ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������beetbox-beets-01f1faf/test/plugins/test_substitute.py�����������������������������������������������0000664�0000000�0000000�00000005442�14723254774�0023240�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# This file is part of beets. # Copyright 2024, Nicholas Boyd Isacsson. # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the # "Software"), to deal in the Software without restriction, including # without limitation the rights to use, copy, modify, merge, publish, # distribute, sublicense, and/or sell copies of the Software, and to # permit persons to whom the Software is furnished to do so, subject to # the following conditions: # # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. """Test the substitute plugin regex functionality.""" from beets.test.helper import PluginTestCase from beetsplug.substitute import Substitute class SubstitutePluginTest(PluginTestCase): plugin = "substitute" preload_plugin = False def run_substitute(self, config, cases): with self.configure_plugin(config): for input, expected in cases: assert Substitute().tmpl_substitute(input) == expected def test_simple_substitute(self): self.run_substitute( { "a": "x", "b": "y", "c": "z", }, [("a", "x"), ("b", "y"), ("c", "z")], ) def test_case_insensitivity(self): self.run_substitute({"a": "x"}, [("A", "x")]) def test_unmatched_input_preserved(self): self.run_substitute({"a": "x"}, [("c", "c")]) def test_regex_to_static(self): self.run_substitute( {".*jimi hendrix.*": "Jimi Hendrix"}, [("The Jimi Hendrix Experience", "Jimi Hendrix")], ) def test_regex_capture_group(self): self.run_substitute( {"^(.*?)(,| &| and).*": r"\1"}, [ ("King Creosote & Jon Hopkins", "King Creosote"), ( "Michael Hurley, The Holy Modal Rounders, Jeffrey Frederick & " + "The Clamtones", "Michael Hurley", ), ("James Yorkston and the Athletes", "James Yorkston"), ], ) def test_partial_substitution(self): self.run_substitute({r"\.": ""}, [("U.N.P.O.C.", "UNPOC")]) def test_rules_applied_in_definition_order(self): self.run_substitute( { "a": "x", "[ab]": "y", "b": "z", }, [ ("a", "x"), ("b", "y"), ], ) def test_rules_applied_in_sequence(self): self.run_substitute( {"a": "b", "b": "c", "d": "a"}, [ ("a", "c"), ("b", "c"), ("d", "a"), ], ) ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������beetbox-beets-01f1faf/test/plugins/test_the.py������������������������������������������������������0000664�0000000�0000000�00000004174�14723254774�0021606�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������"""Tests for the 'the' plugin""" from beets import config from beets.test.helper import BeetsTestCase from beetsplug.the import FORMAT, PATTERN_A, PATTERN_THE, ThePlugin class ThePluginTest(BeetsTestCase): def test_unthe_with_default_patterns(self): assert ThePlugin().unthe("", PATTERN_THE) == "" assert ( ThePlugin().unthe("The Something", PATTERN_THE) == "Something, The" ) assert ThePlugin().unthe("The The", PATTERN_THE) == "The, The" assert ThePlugin().unthe("The The", PATTERN_THE) == "The, The" assert ThePlugin().unthe("The The X", PATTERN_THE) == "The X, The" assert ThePlugin().unthe("the The", PATTERN_THE) == "The, the" assert ( ThePlugin().unthe("Protected The", PATTERN_THE) == "Protected The" ) assert ThePlugin().unthe("A Boy", PATTERN_A) == "Boy, A" assert ThePlugin().unthe("a girl", PATTERN_A) == "girl, a" assert ThePlugin().unthe("An Apple", PATTERN_A) == "Apple, An" assert ThePlugin().unthe("An A Thing", PATTERN_A) == "A Thing, An" assert ThePlugin().unthe("the An Arse", PATTERN_A) == "the An Arse" assert ( ThePlugin().unthe("TET - Travailleur", PATTERN_THE) == "TET - Travailleur" ) def test_unthe_with_strip(self): config["the"]["strip"] = True assert ThePlugin().unthe("The Something", PATTERN_THE) == "Something" assert ThePlugin().unthe("An A", PATTERN_A) == "A" def test_template_function_with_defaults(self): ThePlugin().patterns = [PATTERN_THE, PATTERN_A] assert ThePlugin().the_template_func("The The") == "The, The" assert ThePlugin().the_template_func("An A") == "A, An" def test_custom_pattern(self): config["the"]["patterns"] = ["^test\\s"] config["the"]["format"] = FORMAT assert ThePlugin().the_template_func("test passed") == "passed, test" def test_custom_format(self): config["the"]["patterns"] = [PATTERN_THE, PATTERN_A] config["the"]["format"] = "{1} ({0})" assert ThePlugin().the_template_func("The A") == "The (A)" ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������beetbox-beets-01f1faf/test/plugins/test_thumbnails.py�����������������������������������������������0000664�0000000�0000000�00000023634�14723254774�0023176�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# This file is part of beets. # Copyright 2016, Bruno Cauet # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the # "Software"), to deal in the Software without restriction, including # without limitation the rights to use, copy, modify, merge, publish, # distribute, sublicense, and/or sell copies of the Software, and to # permit persons to whom the Software is furnished to do so, subject to # the following conditions: # # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. import os.path from shutil import rmtree from tempfile import mkdtemp from unittest.mock import Mock, call, patch import pytest from beets.test.helper import BeetsTestCase from beets.util import bytestring_path, syspath from beetsplug.thumbnails import ( LARGE_DIR, NORMAL_DIR, GioURI, PathlibURI, ThumbnailsPlugin, ) class ThumbnailsTest(BeetsTestCase): @patch("beetsplug.thumbnails.ArtResizer") @patch("beetsplug.thumbnails.ThumbnailsPlugin._check_local_ok", Mock()) @patch("beetsplug.thumbnails.os.stat") def test_add_tags(self, mock_stat, mock_artresizer): plugin = ThumbnailsPlugin() plugin.get_uri = Mock( side_effect={b"/path/to/cover": "COVER_URI"}.__getitem__ ) album = Mock(artpath=b"/path/to/cover") mock_stat.return_value.st_mtime = 12345 plugin.add_tags(album, b"/path/to/thumbnail") metadata = {"Thumb::URI": "COVER_URI", "Thumb::MTime": "12345"} mock_artresizer.shared.write_metadata.assert_called_once_with( b"/path/to/thumbnail", metadata, ) mock_stat.assert_called_once_with(syspath(album.artpath)) @patch("beetsplug.thumbnails.os") @patch("beetsplug.thumbnails.ArtResizer") @patch("beetsplug.thumbnails.GioURI") def test_check_local_ok(self, mock_giouri, mock_artresizer, mock_os): # test local resizing capability mock_artresizer.shared.local = False mock_artresizer.shared.can_write_metadata = False plugin = ThumbnailsPlugin() assert not plugin._check_local_ok() # test dirs creation mock_artresizer.shared.local = True mock_artresizer.shared.can_write_metadata = True def exists(path): if path == syspath(NORMAL_DIR): return False if path == syspath(LARGE_DIR): return True raise ValueError(f"unexpected path {path!r}") mock_os.path.exists = exists plugin = ThumbnailsPlugin() mock_os.makedirs.assert_called_once_with(syspath(NORMAL_DIR)) assert plugin._check_local_ok() # test metadata writer function mock_os.path.exists = lambda _: True mock_artresizer.shared.local = True mock_artresizer.shared.can_write_metadata = False with pytest.raises(RuntimeError): ThumbnailsPlugin() mock_artresizer.shared.local = True mock_artresizer.shared.can_write_metadata = True assert ThumbnailsPlugin()._check_local_ok() # test URI getter function giouri_inst = mock_giouri.return_value giouri_inst.available = True assert ThumbnailsPlugin().get_uri == giouri_inst.uri giouri_inst.available = False assert ThumbnailsPlugin().get_uri.__self__.__class__ == PathlibURI @patch("beetsplug.thumbnails.ThumbnailsPlugin._check_local_ok", Mock()) @patch("beetsplug.thumbnails.ArtResizer") @patch("beetsplug.thumbnails.util") @patch("beetsplug.thumbnails.os") @patch("beetsplug.thumbnails.shutil") def test_make_cover_thumbnail( self, mock_shutils, mock_os, mock_util, mock_artresizer ): thumbnail_dir = os.path.normpath(b"/thumbnail/dir") md5_file = os.path.join(thumbnail_dir, b"md5") path_to_art = os.path.normpath(b"/path/to/art") path_to_resized_art = os.path.normpath(b"/path/to/resized/artwork") mock_os.path.join = os.path.join # don't mock that function plugin = ThumbnailsPlugin() plugin.add_tags = Mock() album = Mock(artpath=path_to_art) mock_util.syspath.side_effect = lambda x: x plugin.thumbnail_file_name = Mock(return_value=b"md5") mock_os.path.exists.return_value = False def os_stat(target): if target == syspath(md5_file): return Mock(st_mtime=1) elif target == syspath(path_to_art): return Mock(st_mtime=2) else: raise ValueError(f"invalid target {target}") mock_os.stat.side_effect = os_stat mock_resize = mock_artresizer.shared.resize mock_resize.return_value = path_to_resized_art plugin.make_cover_thumbnail(album, 12345, thumbnail_dir) mock_os.path.exists.assert_called_once_with(syspath(md5_file)) mock_resize.assert_called_once_with(12345, path_to_art, md5_file) plugin.add_tags.assert_called_once_with(album, path_to_resized_art) mock_shutils.move.assert_called_once_with( syspath(path_to_resized_art), syspath(md5_file) ) # now test with recent thumbnail & with force mock_os.path.exists.return_value = True plugin.force = False mock_resize.reset_mock() def os_stat(target): if target == syspath(md5_file): return Mock(st_mtime=3) elif target == syspath(path_to_art): return Mock(st_mtime=2) else: raise ValueError(f"invalid target {target}") mock_os.stat.side_effect = os_stat plugin.make_cover_thumbnail(album, 12345, thumbnail_dir) assert mock_resize.call_count == 0 # and with force plugin.config["force"] = True plugin.make_cover_thumbnail(album, 12345, thumbnail_dir) mock_resize.assert_called_once_with(12345, path_to_art, md5_file) @patch("beetsplug.thumbnails.ThumbnailsPlugin._check_local_ok", Mock()) def test_make_dolphin_cover_thumbnail(self): plugin = ThumbnailsPlugin() tmp = bytestring_path(mkdtemp()) album = Mock(path=tmp, artpath=os.path.join(tmp, b"cover.jpg")) plugin.make_dolphin_cover_thumbnail(album) with open(os.path.join(tmp, b".directory"), "rb") as f: assert f.read().splitlines() == [ b"[Desktop Entry]", b"Icon=./cover.jpg", ] # not rewritten when it already exists (yup that's a big limitation) album.artpath = b"/my/awesome/art.tiff" plugin.make_dolphin_cover_thumbnail(album) with open(os.path.join(tmp, b".directory"), "rb") as f: assert f.read().splitlines() == [ b"[Desktop Entry]", b"Icon=./cover.jpg", ] rmtree(syspath(tmp)) @patch("beetsplug.thumbnails.ThumbnailsPlugin._check_local_ok", Mock()) @patch("beetsplug.thumbnails.ArtResizer") def test_process_album(self, mock_artresizer): get_size = mock_artresizer.shared.get_size plugin = ThumbnailsPlugin() make_cover = plugin.make_cover_thumbnail = Mock(return_value=True) make_dolphin = plugin.make_dolphin_cover_thumbnail = Mock() # no art album = Mock(artpath=None) plugin.process_album(album) assert get_size.call_count == 0 assert make_dolphin.call_count == 0 # cannot get art size album.artpath = b"/path/to/art" get_size.return_value = None plugin.process_album(album) get_size.assert_called_once_with(b"/path/to/art") assert make_cover.call_count == 0 # dolphin tests plugin.config["dolphin"] = False plugin.process_album(album) assert make_dolphin.call_count == 0 plugin.config["dolphin"] = True plugin.process_album(album) make_dolphin.assert_called_once_with(album) # small art get_size.return_value = 200, 200 plugin.process_album(album) make_cover.assert_called_once_with(album, 128, NORMAL_DIR) # big art make_cover.reset_mock() get_size.return_value = 500, 500 plugin.process_album(album) make_cover.assert_has_calls( [call(album, 128, NORMAL_DIR), call(album, 256, LARGE_DIR)], any_order=True, ) @patch("beetsplug.thumbnails.ThumbnailsPlugin._check_local_ok", Mock()) @patch("beetsplug.thumbnails.decargs") def test_invokations(self, mock_decargs): plugin = ThumbnailsPlugin() plugin.process_album = Mock() album = Mock() plugin.process_album.reset_mock() lib = Mock() album2 = Mock() lib.albums.return_value = [album, album2] plugin.process_query(lib, Mock(), None) lib.albums.assert_called_once_with(mock_decargs.return_value) plugin.process_album.assert_has_calls( [call(album), call(album2)], any_order=True ) @patch("beetsplug.thumbnails.BaseDirectory") def test_thumbnail_file_name(self, mock_basedir): plug = ThumbnailsPlugin() plug.get_uri = Mock(return_value="file:///my/uri") assert ( plug.thumbnail_file_name(b"idontcare") == b"9488f5797fbe12ffb316d607dfd93d04.png" ) def test_uri(self): gio = GioURI() if not gio.available: self.skipTest("GIO library not found") assert gio.uri("/foo") == "file:///" # silent fail assert gio.uri(b"/foo") == "file:///foo" assert gio.uri(b"/foo!") == "file:///foo!" assert ( gio.uri(b"/music/\xec\x8b\xb8\xec\x9d\xb4") == "file:///music/%EC%8B%B8%EC%9D%B4" ) class TestPathlibURI: """Test PathlibURI class""" def test_uri(self): test_uri = PathlibURI() # test it won't break if we pass it bytes for a path test_uri.uri(b"/") ����������������������������������������������������������������������������������������������������beetbox-beets-01f1faf/test/plugins/test_types_plugin.py���������������������������������������������0000664�0000000�0000000�00000014004�14723254774�0023541�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# This file is part of beets. # Copyright 2016, Thomas Scholtes. # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the # "Software"), to deal in the Software without restriction, including # without limitation the rights to use, copy, modify, merge, publish, # distribute, sublicense, and/or sell copies of the Software, and to # permit persons to whom the Software is furnished to do so, subject to # the following conditions: # # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. import time from datetime import datetime import pytest from confuse import ConfigValueError from beets.test.helper import PluginTestCase class TypesPluginTest(PluginTestCase): plugin = "types" def test_integer_modify_and_query(self): self.config["types"] = {"myint": "int"} item = self.add_item(artist="aaa") # Do not match unset values out = self.list("myint:1..3") assert "" == out self.modify("myint=2") item.load() assert item["myint"] == 2 # Match in range out = self.list("myint:1..3") assert "aaa" in out def test_album_integer_modify_and_query(self): self.config["types"] = {"myint": "int"} album = self.add_album(albumartist="aaa") # Do not match unset values out = self.list_album("myint:1..3") assert "" == out self.modify("-a", "myint=2") album.load() assert album["myint"] == 2 # Match in range out = self.list_album("myint:1..3") assert "aaa" in out def test_float_modify_and_query(self): self.config["types"] = {"myfloat": "float"} item = self.add_item(artist="aaa") # Do not match unset values out = self.list("myfloat:10..0") assert "" == out self.modify("myfloat=-9.1") item.load() assert item["myfloat"] == -9.1 # Match in range out = self.list("myfloat:-10..0") assert "aaa" in out def test_bool_modify_and_query(self): self.config["types"] = {"mybool": "bool"} true = self.add_item(artist="true") false = self.add_item(artist="false") self.add_item(artist="unset") # Do not match unset values out = self.list("mybool:true, mybool:false") assert "" == out # Set true self.modify("mybool=1", "artist:true") true.load() assert true["mybool"] # Set false self.modify("mybool=false", "artist:false") false.load() assert not false["mybool"] # Query bools out = self.list("mybool:true", "$artist $mybool") assert "true True" == out out = self.list("mybool:false", "$artist $mybool") # Dealing with unset fields? # assert 'false False' == out # out = self.list('mybool:', '$artist $mybool') # assert 'unset $mybool' in out def test_date_modify_and_query(self): self.config["types"] = {"mydate": "date"} # FIXME parsing should also work with default time format self.config["time_format"] = "%Y-%m-%d" old = self.add_item(artist="prince") new = self.add_item(artist="britney") # Do not match unset values out = self.list("mydate:..2000") assert "" == out self.modify("mydate=1999-01-01", "artist:prince") old.load() assert old["mydate"] == mktime(1999, 1, 1) self.modify("mydate=1999-12-30", "artist:britney") new.load() assert new["mydate"] == mktime(1999, 12, 30) # Match in range out = self.list("mydate:..1999-07", "$artist $mydate") assert "prince 1999-01-01" == out # FIXME some sort of timezone issue here # out = self.list('mydate:1999-12-30', '$artist $mydate') # assert 'britney 1999-12-30' == out def test_unknown_type_error(self): self.config["types"] = {"flex": "unkown type"} with pytest.raises(ConfigValueError): self.run_command("ls") def test_template_if_def(self): # Tests for a subtle bug when using %ifdef in templates along with # types that have truthy default values (e.g. '0', '0.0', 'False') # https://github.com/beetbox/beets/issues/3852 self.config["types"] = { "playcount": "int", "rating": "float", "starred": "bool", } with_fields = self.add_item(artist="prince") self.modify("playcount=10", "artist=prince") self.modify("rating=5.0", "artist=prince") self.modify("starred=yes", "artist=prince") with_fields.load() without_fields = self.add_item(artist="britney") int_template = "%ifdef{playcount,Play count: $playcount,Not played}" assert with_fields.evaluate_template(int_template) == "Play count: 10" assert without_fields.evaluate_template(int_template) == "Not played" float_template = "%ifdef{rating,Rating: $rating,Not rated}" assert with_fields.evaluate_template(float_template) == "Rating: 5.0" assert without_fields.evaluate_template(float_template) == "Not rated" bool_template = "%ifdef{starred,Starred: $starred,Not starred}" assert with_fields.evaluate_template(bool_template).lower() in ( "starred: true", "starred: yes", "starred: y", ) assert without_fields.evaluate_template(bool_template) == "Not starred" def modify(self, *args): return self.run_with_output( "modify", "--yes", "--nowrite", "--nomove", *args ) def list(self, query, fmt="$artist - $album - $title"): return self.run_with_output("ls", "-f", fmt, query).strip() def list_album(self, query, fmt="$albumartist - $album - $title"): return self.run_with_output("ls", "-a", "-f", fmt, query).strip() def mktime(*args): return time.mktime(datetime(*args).timetuple()) ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������beetbox-beets-01f1faf/test/plugins/test_web.py������������������������������������������������������0000664�0000000�0000000�00000060644�14723254774�0021607�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������"""Tests for the 'web' plugin""" import json import os.path import platform import shutil from collections import Counter from beets import logging from beets.library import Album, Item from beets.test import _common from beets.test.helper import ItemInDBTestCase from beetsplug import web class WebPluginTest(ItemInDBTestCase): def setUp(self): super().setUp() self.log = logging.getLogger("beets.web") if platform.system() == "Windows": self.path_prefix = "C:" else: self.path_prefix = "" # Add fixtures for track in self.lib.items(): track.remove() # Add library elements. Note that self.lib.add overrides any "id=<n>" # and assigns the next free id number. # The following adds will create items #1, #2 and #3 path1 = ( self.path_prefix + os.sep + os.path.join(b"path_1").decode("utf-8") ) self.lib.add( Item(title="title", path=path1, album_id=2, artist="AAA Singers") ) path2 = ( self.path_prefix + os.sep + os.path.join(b"somewhere", b"a").decode("utf-8") ) self.lib.add( Item(title="another title", path=path2, artist="AAA Singers") ) path3 = ( self.path_prefix + os.sep + os.path.join(b"somewhere", b"abc").decode("utf-8") ) self.lib.add( Item(title="and a third", testattr="ABC", path=path3, album_id=2) ) # The following adds will create albums #1 and #2 self.lib.add(Album(album="album", albumtest="xyz")) path4 = ( self.path_prefix + os.sep + os.path.join(b"somewhere2", b"art_path_2").decode("utf-8") ) self.lib.add(Album(album="other album", artpath=path4)) web.app.config["TESTING"] = True web.app.config["lib"] = self.lib web.app.config["INCLUDE_PATHS"] = False web.app.config["READONLY"] = True self.client = web.app.test_client() def test_config_include_paths_true(self): web.app.config["INCLUDE_PATHS"] = True response = self.client.get("/item/1") res_json = json.loads(response.data.decode("utf-8")) expected_path = ( self.path_prefix + os.sep + os.path.join(b"path_1").decode("utf-8") ) assert response.status_code == 200 assert res_json["path"] == expected_path web.app.config["INCLUDE_PATHS"] = False def test_config_include_artpaths_true(self): web.app.config["INCLUDE_PATHS"] = True response = self.client.get("/album/2") res_json = json.loads(response.data.decode("utf-8")) expected_path = ( self.path_prefix + os.sep + os.path.join(b"somewhere2", b"art_path_2").decode("utf-8") ) assert response.status_code == 200 assert res_json["artpath"] == expected_path web.app.config["INCLUDE_PATHS"] = False def test_config_include_paths_false(self): web.app.config["INCLUDE_PATHS"] = False response = self.client.get("/item/1") res_json = json.loads(response.data.decode("utf-8")) assert response.status_code == 200 assert "path" not in res_json def test_config_include_artpaths_false(self): web.app.config["INCLUDE_PATHS"] = False response = self.client.get("/album/2") res_json = json.loads(response.data.decode("utf-8")) assert response.status_code == 200 assert "artpath" not in res_json def test_get_all_items(self): response = self.client.get("/item/") res_json = json.loads(response.data.decode("utf-8")) assert response.status_code == 200 assert len(res_json["items"]) == 3 def test_get_single_item_by_id(self): response = self.client.get("/item/1") res_json = json.loads(response.data.decode("utf-8")) assert response.status_code == 200 assert res_json["id"] == 1 assert res_json["title"] == "title" def test_get_multiple_items_by_id(self): response = self.client.get("/item/1,2") res_json = json.loads(response.data.decode("utf-8")) assert response.status_code == 200 assert len(res_json["items"]) == 2 response_titles = {item["title"] for item in res_json["items"]} assert response_titles == {"title", "another title"} def test_get_single_item_not_found(self): response = self.client.get("/item/4") assert response.status_code == 404 def test_get_single_item_by_path(self): data_path = os.path.join(_common.RSRC, b"full.mp3") self.lib.add(Item.from_path(data_path)) response = self.client.get("/item/path/" + data_path.decode("utf-8")) res_json = json.loads(response.data.decode("utf-8")) assert response.status_code == 200 assert res_json["title"] == "full" def test_get_single_item_by_path_not_found_if_not_in_library(self): data_path = os.path.join(_common.RSRC, b"full.mp3") # data_path points to a valid file, but we have not added the file # to the library. response = self.client.get("/item/path/" + data_path.decode("utf-8")) assert response.status_code == 404 def test_get_item_empty_query(self): """testing item query: <empty>""" response = self.client.get("/item/query/") res_json = json.loads(response.data.decode("utf-8")) assert response.status_code == 200 assert len(res_json["items"]) == 3 def test_get_simple_item_query(self): """testing item query: another""" response = self.client.get("/item/query/another") res_json = json.loads(response.data.decode("utf-8")) assert response.status_code == 200 assert len(res_json["results"]) == 1 assert res_json["results"][0]["title"] == "another title" def test_query_item_string(self): """testing item query: testattr:ABC""" response = self.client.get("/item/query/testattr%3aABC") res_json = json.loads(response.data.decode("utf-8")) assert response.status_code == 200 assert len(res_json["results"]) == 1 assert res_json["results"][0]["title"] == "and a third" def test_query_item_regex(self): """testing item query: testattr::[A-C]+""" response = self.client.get("/item/query/testattr%3a%3a[A-C]%2b") res_json = json.loads(response.data.decode("utf-8")) assert response.status_code == 200 assert len(res_json["results"]) == 1 assert res_json["results"][0]["title"] == "and a third" def test_query_item_regex_backslash(self): # """ testing item query: testattr::\w+ """ response = self.client.get("/item/query/testattr%3a%3a%5cw%2b") res_json = json.loads(response.data.decode("utf-8")) assert response.status_code == 200 assert len(res_json["results"]) == 1 assert res_json["results"][0]["title"] == "and a third" def test_query_item_path(self): # """ testing item query: path:\somewhere\a """ """Note: path queries are special: the query item must match the path from the root all the way to a directory, so this matches 1 item""" """ Note: filesystem separators in the query must be '\' """ response = self.client.get( "/item/query/path:" + self.path_prefix + "\\somewhere\\a" ) res_json = json.loads(response.data.decode("utf-8")) assert response.status_code == 200 assert len(res_json["results"]) == 1 assert res_json["results"][0]["title"] == "another title" def test_get_all_albums(self): response = self.client.get("/album/") res_json = json.loads(response.data.decode("utf-8")) assert response.status_code == 200 response_albums = [album["album"] for album in res_json["albums"]] assert Counter(response_albums) == {"album": 1, "other album": 1} def test_get_single_album_by_id(self): response = self.client.get("/album/2") res_json = json.loads(response.data.decode("utf-8")) assert response.status_code == 200 assert res_json["id"] == 2 assert res_json["album"] == "other album" def test_get_multiple_albums_by_id(self): response = self.client.get("/album/1,2") res_json = json.loads(response.data.decode("utf-8")) assert response.status_code == 200 response_albums = [album["album"] for album in res_json["albums"]] assert Counter(response_albums) == {"album": 1, "other album": 1} def test_get_album_empty_query(self): response = self.client.get("/album/query/") res_json = json.loads(response.data.decode("utf-8")) assert response.status_code == 200 assert len(res_json["albums"]) == 2 def test_get_simple_album_query(self): response = self.client.get("/album/query/other") res_json = json.loads(response.data.decode("utf-8")) assert response.status_code == 200 assert len(res_json["results"]) == 1 assert res_json["results"][0]["album"] == "other album" assert res_json["results"][0]["id"] == 2 def test_get_album_details(self): response = self.client.get("/album/2?expand") res_json = json.loads(response.data.decode("utf-8")) assert response.status_code == 200 assert len(res_json["items"]) == 2 assert res_json["items"][0]["album"] == "other album" assert res_json["items"][1]["album"] == "other album" response_track_titles = {item["title"] for item in res_json["items"]} assert response_track_titles == {"title", "and a third"} def test_query_album_string(self): """testing query: albumtest:xy""" response = self.client.get("/album/query/albumtest%3axy") res_json = json.loads(response.data.decode("utf-8")) assert response.status_code == 200 assert len(res_json["results"]) == 1 assert res_json["results"][0]["album"] == "album" def test_query_album_artpath_regex(self): """testing query: artpath::art_""" response = self.client.get("/album/query/artpath%3a%3aart_") res_json = json.loads(response.data.decode("utf-8")) assert response.status_code == 200 assert len(res_json["results"]) == 1 assert res_json["results"][0]["album"] == "other album" def test_query_album_regex_backslash(self): # """ testing query: albumtest::\w+ """ response = self.client.get("/album/query/albumtest%3a%3a%5cw%2b") res_json = json.loads(response.data.decode("utf-8")) assert response.status_code == 200 assert len(res_json["results"]) == 1 assert res_json["results"][0]["album"] == "album" def test_get_stats(self): response = self.client.get("/stats") res_json = json.loads(response.data.decode("utf-8")) assert response.status_code == 200 assert res_json["items"] == 3 assert res_json["albums"] == 2 def test_delete_item_id(self): web.app.config["READONLY"] = False # Create a temporary item item_id = self.lib.add( Item(title="test_delete_item_id", test_delete_item_id=1) ) # Check we can find the temporary item we just created response = self.client.get("/item/" + str(item_id)) res_json = json.loads(response.data.decode("utf-8")) assert response.status_code == 200 assert res_json["id"] == item_id # Delete item by id response = self.client.delete("/item/" + str(item_id)) res_json = json.loads(response.data.decode("utf-8")) assert response.status_code == 200 # Check the item has gone response = self.client.get("/item/" + str(item_id)) assert response.status_code == 404 # Note: if this fails, the item may still be around # and may cause other tests to fail def test_delete_item_without_file(self): web.app.config["READONLY"] = False # Create an item with a file ipath = os.path.join(self.temp_dir, b"testfile1.mp3") shutil.copy(os.path.join(_common.RSRC, b"full.mp3"), ipath) assert os.path.exists(ipath) item_id = self.lib.add(Item.from_path(ipath)) # Check we can find the temporary item we just created response = self.client.get("/item/" + str(item_id)) res_json = json.loads(response.data.decode("utf-8")) assert response.status_code == 200 assert res_json["id"] == item_id # Delete item by id, without deleting file response = self.client.delete("/item/" + str(item_id)) res_json = json.loads(response.data.decode("utf-8")) assert response.status_code == 200 # Check the item has gone response = self.client.get("/item/" + str(item_id)) assert response.status_code == 404 # Check the file has not gone assert os.path.exists(ipath) os.remove(ipath) def test_delete_item_with_file(self): web.app.config["READONLY"] = False # Create an item with a file ipath = os.path.join(self.temp_dir, b"testfile2.mp3") shutil.copy(os.path.join(_common.RSRC, b"full.mp3"), ipath) assert os.path.exists(ipath) item_id = self.lib.add(Item.from_path(ipath)) # Check we can find the temporary item we just created response = self.client.get("/item/" + str(item_id)) res_json = json.loads(response.data.decode("utf-8")) assert response.status_code == 200 assert res_json["id"] == item_id # Delete item by id, with file response = self.client.delete("/item/" + str(item_id) + "?delete") res_json = json.loads(response.data.decode("utf-8")) assert response.status_code == 200 # Check the item has gone response = self.client.get("/item/" + str(item_id)) assert response.status_code == 404 # Check the file has gone assert not os.path.exists(ipath) def test_delete_item_query(self): web.app.config["READONLY"] = False # Create a temporary item self.lib.add( Item(title="test_delete_item_query", test_delete_item_query=1) ) # Check we can find the temporary item we just created response = self.client.get("/item/query/test_delete_item_query") res_json = json.loads(response.data.decode("utf-8")) assert response.status_code == 200 assert len(res_json["results"]) == 1 # Delete item by query response = self.client.delete("/item/query/test_delete_item_query") res_json = json.loads(response.data.decode("utf-8")) assert response.status_code == 200 # Check the item has gone response = self.client.get("/item/query/test_delete_item_query") res_json = json.loads(response.data.decode("utf-8")) assert response.status_code == 200 assert len(res_json["results"]) == 0 def test_delete_item_all_fails(self): """DELETE is not supported for list all""" web.app.config["READONLY"] = False # Delete all items response = self.client.delete("/item/") assert response.status_code == 405 # Note: if this fails, all items have gone and rest of # tests will fail! def test_delete_item_id_readonly(self): web.app.config["READONLY"] = True # Create a temporary item item_id = self.lib.add( Item(title="test_delete_item_id_ro", test_delete_item_id_ro=1) ) # Check we can find the temporary item we just created response = self.client.get("/item/" + str(item_id)) res_json = json.loads(response.data.decode("utf-8")) assert response.status_code == 200 assert res_json["id"] == item_id # Try to delete item by id response = self.client.delete("/item/" + str(item_id)) assert response.status_code == 405 # Check the item has not gone response = self.client.get("/item/" + str(item_id)) res_json = json.loads(response.data.decode("utf-8")) assert response.status_code == 200 assert res_json["id"] == item_id # Remove it self.lib.get_item(item_id).remove() def test_delete_item_query_readonly(self): web.app.config["READONLY"] = True # Create a temporary item item_id = self.lib.add( Item(title="test_delete_item_q_ro", test_delete_item_q_ro=1) ) # Check we can find the temporary item we just created response = self.client.get("/item/query/test_delete_item_q_ro") res_json = json.loads(response.data.decode("utf-8")) assert response.status_code == 200 assert len(res_json["results"]) == 1 # Try to delete item by query response = self.client.delete("/item/query/test_delete_item_q_ro") assert response.status_code == 405 # Check the item has not gone response = self.client.get("/item/query/test_delete_item_q_ro") res_json = json.loads(response.data.decode("utf-8")) assert response.status_code == 200 assert len(res_json["results"]) == 1 # Remove it self.lib.get_item(item_id).remove() def test_delete_album_id(self): web.app.config["READONLY"] = False # Create a temporary album album_id = self.lib.add( Album(album="test_delete_album_id", test_delete_album_id=1) ) # Check we can find the temporary album we just created response = self.client.get("/album/" + str(album_id)) res_json = json.loads(response.data.decode("utf-8")) assert response.status_code == 200 assert res_json["id"] == album_id # Delete album by id response = self.client.delete("/album/" + str(album_id)) res_json = json.loads(response.data.decode("utf-8")) assert response.status_code == 200 # Check the album has gone response = self.client.get("/album/" + str(album_id)) assert response.status_code == 404 # Note: if this fails, the album may still be around # and may cause other tests to fail def test_delete_album_query(self): web.app.config["READONLY"] = False # Create a temporary album self.lib.add( Album(album="test_delete_album_query", test_delete_album_query=1) ) # Check we can find the temporary album we just created response = self.client.get("/album/query/test_delete_album_query") res_json = json.loads(response.data.decode("utf-8")) assert response.status_code == 200 assert len(res_json["results"]) == 1 # Delete album response = self.client.delete("/album/query/test_delete_album_query") res_json = json.loads(response.data.decode("utf-8")) assert response.status_code == 200 # Check the album has gone response = self.client.get("/album/query/test_delete_album_query") res_json = json.loads(response.data.decode("utf-8")) assert response.status_code == 200 assert len(res_json["results"]) == 0 def test_delete_album_all_fails(self): """DELETE is not supported for list all""" web.app.config["READONLY"] = False # Delete all albums response = self.client.delete("/album/") assert response.status_code == 405 # Note: if this fails, all albums have gone and rest of # tests will fail! def test_delete_album_id_readonly(self): web.app.config["READONLY"] = True # Create a temporary album album_id = self.lib.add( Album(album="test_delete_album_id_ro", test_delete_album_id_ro=1) ) # Check we can find the temporary album we just created response = self.client.get("/album/" + str(album_id)) res_json = json.loads(response.data.decode("utf-8")) assert response.status_code == 200 assert res_json["id"] == album_id # Try to delete album by id response = self.client.delete("/album/" + str(album_id)) assert response.status_code == 405 # Check the item has not gone response = self.client.get("/album/" + str(album_id)) res_json = json.loads(response.data.decode("utf-8")) assert response.status_code == 200 assert res_json["id"] == album_id # Remove it self.lib.get_album(album_id).remove() def test_delete_album_query_readonly(self): web.app.config["READONLY"] = True # Create a temporary album album_id = self.lib.add( Album( album="test_delete_album_query_ro", test_delete_album_query_ro=1 ) ) # Check we can find the temporary album we just created response = self.client.get("/album/query/test_delete_album_query_ro") res_json = json.loads(response.data.decode("utf-8")) assert response.status_code == 200 assert len(res_json["results"]) == 1 # Try to delete album response = self.client.delete("/album/query/test_delete_album_query_ro") assert response.status_code == 405 # Check the album has not gone response = self.client.get("/album/query/test_delete_album_query_ro") res_json = json.loads(response.data.decode("utf-8")) assert response.status_code == 200 assert len(res_json["results"]) == 1 # Remove it self.lib.get_album(album_id).remove() def test_patch_item_id(self): # Note: PATCH is currently only implemented for track items, not albums web.app.config["READONLY"] = False # Create a temporary item item_id = self.lib.add( Item( title="test_patch_item_id", test_patch_f1=1, test_patch_f2="Old" ) ) # Check we can find the temporary item we just created response = self.client.get("/item/" + str(item_id)) res_json = json.loads(response.data.decode("utf-8")) assert response.status_code == 200 assert res_json["id"] == item_id assert res_json["test_patch_f1"] == "1" assert res_json["test_patch_f2"] == "Old" # Patch item by id # patch_json = json.JSONEncoder().encode({"test_patch_f2": "New"}]}) response = self.client.patch( "/item/" + str(item_id), json={"test_patch_f2": "New"} ) res_json = json.loads(response.data.decode("utf-8")) assert response.status_code == 200 assert res_json["id"] == item_id assert res_json["test_patch_f1"] == "1" assert res_json["test_patch_f2"] == "New" # Check the update has really worked response = self.client.get("/item/" + str(item_id)) res_json = json.loads(response.data.decode("utf-8")) assert response.status_code == 200 assert res_json["id"] == item_id assert res_json["test_patch_f1"] == "1" assert res_json["test_patch_f2"] == "New" # Remove the item self.lib.get_item(item_id).remove() def test_patch_item_id_readonly(self): # Note: PATCH is currently only implemented for track items, not albums web.app.config["READONLY"] = True # Create a temporary item item_id = self.lib.add( Item( title="test_patch_item_id_ro", test_patch_f1=2, test_patch_f2="Old", ) ) # Check we can find the temporary item we just created response = self.client.get("/item/" + str(item_id)) res_json = json.loads(response.data.decode("utf-8")) assert response.status_code == 200 assert res_json["id"] == item_id assert res_json["test_patch_f1"] == "2" assert res_json["test_patch_f2"] == "Old" # Patch item by id # patch_json = json.JSONEncoder().encode({"test_patch_f2": "New"}) response = self.client.patch( "/item/" + str(item_id), json={"test_patch_f2": "New"} ) assert response.status_code == 405 # Remove the item self.lib.get_item(item_id).remove() def test_get_item_file(self): ipath = os.path.join(self.temp_dir, b"testfile2.mp3") shutil.copy(os.path.join(_common.RSRC, b"full.mp3"), ipath) assert os.path.exists(ipath) item_id = self.lib.add(Item.from_path(ipath)) response = self.client.get("/item/" + str(item_id) + "/file") assert response.status_code == 200 ��������������������������������������������������������������������������������������������beetbox-beets-01f1faf/test/plugins/test_zero.py�����������������������������������������������������0000664�0000000�0000000�00000016255�14723254774�0022010�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������"""Tests for the 'zero' plugin""" from mediafile import MediaFile from beets.library import Item from beets.test.helper import PluginTestCase, control_stdin from beets.util import syspath from beetsplug.zero import ZeroPlugin class ZeroPluginTest(PluginTestCase): plugin = "zero" preload_plugin = False def test_no_patterns(self): item = self.add_item_fixture( comments="test comment", title="Title", month=1, year=2000, ) item.write() with self.configure_plugin({"fields": ["comments", "month"]}): item.write() mf = MediaFile(syspath(item.path)) assert mf.comments is None assert mf.month is None assert mf.title == "Title" assert mf.year == 2000 def test_pattern_match(self): item = self.add_item_fixture(comments="encoded by encoder") item.write() with self.configure_plugin( {"fields": ["comments"], "comments": ["encoded by"]} ): item.write() mf = MediaFile(syspath(item.path)) assert mf.comments is None def test_pattern_nomatch(self): item = self.add_item_fixture(comments="recorded at place") item.write() with self.configure_plugin( {"fields": ["comments"], "comments": ["encoded_by"]} ): item.write() mf = MediaFile(syspath(item.path)) assert mf.comments == "recorded at place" def test_do_not_change_database(self): item = self.add_item_fixture(year=2000) item.write() with self.configure_plugin({"fields": ["year"]}): item.write() assert item["year"] == 2000 def test_change_database(self): item = self.add_item_fixture(year=2000) item.write() with self.configure_plugin( {"fields": ["year"], "update_database": True} ): item.write() assert item["year"] == 0 def test_album_art(self): path = self.create_mediafile_fixture(images=["jpg"]) item = Item.from_path(path) with self.configure_plugin({"fields": ["images"]}): item.write() mf = MediaFile(syspath(path)) assert not mf.images def test_auto_false(self): item = self.add_item_fixture(year=2000) item.write() with self.configure_plugin( {"fields": ["year"], "update_database": True, "auto": False} ): item.write() assert item["year"] == 2000 def test_subcommand_update_database_true(self): item = self.add_item_fixture( year=2016, day=13, month=3, comments="test comment" ) item.write() item_id = item.id with self.configure_plugin( {"fields": ["comments"], "update_database": True, "auto": False} ), control_stdin("y"): self.run_command("zero") mf = MediaFile(syspath(item.path)) item = self.lib.get_item(item_id) assert item["year"] == 2016 assert mf.year == 2016 assert mf.comments is None assert item["comments"] == "" def test_subcommand_update_database_false(self): item = self.add_item_fixture( year=2016, day=13, month=3, comments="test comment" ) item.write() item_id = item.id with self.configure_plugin( {"fields": ["comments"], "update_database": False, "auto": False} ), control_stdin("y"): self.run_command("zero") mf = MediaFile(syspath(item.path)) item = self.lib.get_item(item_id) assert item["year"] == 2016 assert mf.year == 2016 assert item["comments"] == "test comment" assert mf.comments is None def test_subcommand_query_include(self): item = self.add_item_fixture( year=2016, day=13, month=3, comments="test comment" ) item.write() with self.configure_plugin( {"fields": ["comments"], "update_database": False, "auto": False} ): self.run_command("zero", "year: 2016") mf = MediaFile(syspath(item.path)) assert mf.year == 2016 assert mf.comments is None def test_subcommand_query_exclude(self): item = self.add_item_fixture( year=2016, day=13, month=3, comments="test comment" ) item.write() with self.configure_plugin( {"fields": ["comments"], "update_database": False, "auto": False} ): self.run_command("zero", "year: 0000") mf = MediaFile(syspath(item.path)) assert mf.year == 2016 assert mf.comments == "test comment" def test_no_fields(self): item = self.add_item_fixture(year=2016) item.write() mediafile = MediaFile(syspath(item.path)) assert mediafile.year == 2016 item_id = item.id with self.configure_plugin({"fields": []}), control_stdin("y"): self.run_command("zero") item = self.lib.get_item(item_id) assert item["year"] == 2016 assert mediafile.year == 2016 def test_whitelist_and_blacklist(self): item = self.add_item_fixture(year=2016) item.write() mf = MediaFile(syspath(item.path)) assert mf.year == 2016 item_id = item.id with self.configure_plugin( {"fields": ["year"], "keep_fields": ["comments"]} ), control_stdin("y"): self.run_command("zero") item = self.lib.get_item(item_id) assert item["year"] == 2016 assert mf.year == 2016 def test_keep_fields(self): item = self.add_item_fixture(year=2016, comments="test comment") tags = { "comments": "test comment", "year": 2016, } with self.configure_plugin( {"fields": None, "keep_fields": ["year"], "update_database": True} ): z = ZeroPlugin() z.write_event(item, item.path, tags) assert tags["comments"] is None assert tags["year"] == 2016 def test_keep_fields_removes_preserved_tags(self): self.config["zero"]["keep_fields"] = ["year"] self.config["zero"]["fields"] = None self.config["zero"]["update_database"] = True z = ZeroPlugin() assert "id" not in z.fields_to_progs def test_fields_removes_preserved_tags(self): self.config["zero"]["fields"] = ["year id"] self.config["zero"]["update_database"] = True z = ZeroPlugin() assert "id" not in z.fields_to_progs def test_empty_query_n_response_no_changes(self): item = self.add_item_fixture( year=2016, day=13, month=3, comments="test comment" ) item.write() item_id = item.id with self.configure_plugin( {"fields": ["comments"], "update_database": True, "auto": False} ), control_stdin("n"): self.run_command("zero") mf = MediaFile(syspath(item.path)) item = self.lib.get_item(item_id) assert item["year"] == 2016 assert mf.year == 2016 assert mf.comments == "test comment" assert item["comments"] == "test comment" ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������beetbox-beets-01f1faf/test/rsrc/��������������������������������������������������������������������0000775�0000000�0000000�00000000000�14723254774�0016677�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������beetbox-beets-01f1faf/test/rsrc/abbey-different.jpg�������������������������������������������������0000664�0000000�0000000�00000110113�14723254774�0022424�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������˙Ř˙ŕ�JFIF��H�H��˙Ű�C�  !"$"$˙Ű�C˙Â�\\"�˙Ä���������������� ˙Ú�����ëś6 D0tz‡Źš1—EvZ­ŞN´Žä§ho\f`fC`”p¦(ä“7w[®ÄŕyuwÔů{Ńňn´á;ćé{«3�s00DpJQ.fsądPwť%Éň(u“3¤ŽŮp[áĚĚĚ0ć1ÄČb¦ˇŔÜÚď@§0‘@2X¸ -qeý "&Ě/Ö…răQ‚wŰŠfŠ˘’ŘfĹW<4bĄ§Ý˘"a)x°¸)“{Yĺ©7U,Ŕ*Ä* ékŠ-ÓEí>é0aĚRđ%`mŮ%eJ-‚Ş�RaLb4Őó´,ćÍî“WŔ/ŐąŠ»x›]úl9–ÂQĽŔLE1ŔŤiŞJÜĆb˘6ßs¸‚| WaÔ|ݱ9vňAщă8ţÄĚ«ÝűžČ;¨Âö÷q °8®+ťŰ%•ÖĹ»2’ĎX…}OCv/·1ç ÁR]ÎöżsâŕJĽŘëľČ®Wë™Ŕ4†ó,Mx 9š¸A[Â"a0€ŞđD»mvç ŻÄŃcĎt{G$~®©†9Řę°îíNä0¨‰đ%b‘łöÍ«=żJܢSUÇPr;Ý6×±neHPp­µÜÇĹ0q??ëۉLr<.â}Ô24”BÇl;1™ëó�q@qmwI„ĂO?«&s¤˛ű»sŁ6É™—<WŃ™¦Ů©�¸ l=ÁÝFĚ8â~Ö@`MEČ×»úUbkkžeĐ7–F‘G žaÓ8-owIĂ'źő–Ę:MŃş ‚'Ę=bhHŁěÍ„ Ĺ1˛ŕî“ć* O?k`(�¬˙�[»euu4ţ2nuĘ닉LAĂ\ťĚp„ çÝn)‰¤†8ˇ7; ˘\h÷Ń“<Ő੭‡�¸'ş{€ŕGóň´6­ęŻbî*çy7łI­Í§ŤDĹb S*‡k( ą'źUş©(„6M"Ş'2iÉë±­,9®‘Ůćń¦ ‚ląűtŕG8óö¶T˛4¸”MtYFęun¦°É·GLŠ«Ů«âéŇÜÚ1™˙�c›t8O?kL÷~›4÷b}kAw©Ú%¶Úóă4”>ËRk5«ŢL›Ú}: WRůů]Ěí«}@WÚvŻ$ĎŘm¶đ­îâ 2-úÍvŔőq ]Őű× TŔ_?«›wml¨«Cš#Űâ÷w-­Ăa˝pβ[TĐtí˘.]]ş`Ĺ„ĄŕŮŰůú/Qň˝¶y·o]Í}ł­‹>– «§Í˸Ëk§· PJ^®Iç˝ěĘ/ŇdmŠń+$#şŐ§ÓŁkąc4áŢšÉëC”—€+Ĺ1YGY\tź,°Ţ#°“¸—XOÚm5Č;KŽÔÚI,1ĂĹ/Ö»4ꛩîż +ĽřÓ—:˛Ž6ŇĹß)"ŔXŇE-kŘ* Č0b¶L{ŞSSqIT]â0bÉ·íué.–Hăa°w€Ú˘ËŰÝ8|LPăhcŤjtő¤¤1e6•U®%ȵŹmWYW޶ŤëN–nĽaŐFĂ( ^-ŻŰăžÄ‹Ŕú‚\gZČ®úPĚŮÍú¸»­‚uý•'Ř9źŢ"&P@ĽaX-Ő·'9öżžťv™74gëkV)'á6;~§śjµüšÍ^Źś–¸‘_Ł‚ €q7Ż,±W"‘Q×sÓÁ<űk¶FčźjkŠ·g-ďZî4Ë—ô’ľ˝ş ‘ćĂ™Ç 6đrݡo·t—˛ys>ë^U‡Ô÷UiĐťWĄö\ÄŮNčµQ×ć㙨yŚ’çy–u ‘¸8ó1¶’Oďű›ONseůĚYÝÖ w¨`››‡{Ь0AĚnTižNQ7}krfŠ«<uhö�Pś¬Ä“^ü[U©˘h†Ě:c˘M†0ć&ÚÄ ReÝGĹ΄۸śwqs4>y·(ôÇOC”ąÔ·Ga‰ÄŽ«áŘ/:Âë0 [ڦm˝ fq­&`S˝çăšFâěSĚ8:ţ>§ “w«Ň”Ç%†ôKa•o”ÉĎ»Ř፸Đý`1‡J8ói®×€÷Ęáp‘Ľ ů‚ßW» áp9śë*o_Ú4ä?«ß˝ÜFĄ79ÇK$…ŘÖo5O¤‘MÜ®Î�Ŕ`†dÚŞĽap{–;”á·}0B¸m§’7žYśîýłg3ë300HbáŠ!…ÁC030D¦ �˙Ä���������������˙Ú����Ŕň+be‰4Ť-•śĽN±z)Íd,Ds GŠÚö›·'kĐš)¨Ľ¨­Î×ĆÂ0Ý»3Feł™1Ü©Ś$‹‚i»vp)Q×ÇtILł[„]ŮÚ­KyN‹QÁ#ÚčT#·GNcĺKčŽăň¤L"·G^ä)Ad*Żt\MŰł÷3•‘Ą¤^î‹‚i»v~dEâE“#»—ˇáuÝ™ .¬Źd«®±t@bšUŢ,x“Đ‘¤–U5ŮV·ŤŽâ5aXEd¸-“ŕ`šeÝśqĆD{Ç(9âËŃTŃĐ4Ë­‘Î篱Êۨ•نµ’ť1§– @ę·çXn×ŐÇ»>%wᢕuťeÔřŐyF§WßßQeěvńh >~f]ücZnž¤x¨÷×ÝBK”)ˇLKŹŮŹ,şËD¦}7UT–ăÓsĄÉÉkbÉWŤ‰Ťl”iN7#ąˇ–Ł– šß˙Ä��������������˙Ú����íěŁ26dÔ;bˇpŰ@©ŘjQĹBž5(_Ď‘Ř{*ýá„fÇ—ť1Ćţ—:ż~›p‡LóŕÖb~p~쇅pvŕ–Żx`Ś”îUn�5w?>ěz�kޠ𦳛ô4ćjz9 bşň¤¦¸jzBg‰VĎ+AĆJ©Ży‘âÝQôĚü¶ôĽäé=GŤkíůÔÖBxtô9;#¦ys]ęä«óŮ$9wˇ¤ÜÁ›¤NÜ—ő'ĎÇÓEj7%KÁz9kŃJůtz ˘Wť©ˇhśöKň¸žˇQ.Ć×YŃL5zjŇ\Ö/ĹŃŤ ĄkĎדZĽˇşňmYހ٫ťhvŰşt ‘ŠśJlJ±Ç”¶t T9Ű˙Ä�F� ����!1AQ "0aq24R±#35BS‚‘’ˇ$@rÁCTbŃđDPsń˘˛á˙Ú��?í$–(ţ’F˛üĘôŞoó~¤éŕm±LÁqqÖRIHöłÄ§Ď @.•€;1s˝zM7ůżVŁSN xÁęQËźG#_ŕW¤Ó‹ő(ĺŠOّʷ"¤šÍź+{Ę’HăÎGµž%zU7ůżXFxCŚŃŘî8·ę5ŕŘĎ#ýI“BófJÇç'Ď…ÓF"äŇ.Ň?ĚÔŇĹPA’ůr+F@ʉ\Ů/`ŰäVhŽXÝÍŽÁWÖ–´vv<Ö‘öz?ü_ě˘X>N‘·pϲÔíźĘíctŤĆî«V‰Ât ěĆvaŢ{”XvďÇ ¦ä Ń{>ľ gC»y9©qUKS8ÜŃ/ý*fG[C’ýL˛Tđ1úAĐŕËKĆŘb§Ť—°ĹżÉGômđRaôůqĆdÝŐhÝ‘‘ű:gÂě;É%š|m©§.żÚ˝ż"´FÇ`á źż0îÍhşY ™Î� m˝iJY§ť®Ť €ŢkJÁ-C#±ć«(ç’vµŁ©f­Ąýá˙�ÇQĄ­e\“BŃ›ŤłT´•™é5_⣥®†W> =á¤LR¶K[fîTú,^r滓l´\ď{dhŔxߊ¦¦™šIÓ9˝B]ťÖ–§–}–É·µďź‚Śi@ćÜőoź«ą:šµ•Źšw…L+śç6¨üŮm˛˛lZF q¶ŇµÜI˙�uŁ)]LÇc=g#Y¤&†Ąń5±ŮĽüĘłű‘ţE|«?ąäWĘłű‘~ëĺYýČżuň¬ţä_şůVr/Ý|«7ąîľUźîăý×Ęłű‘ţčiIýČ˙�tÍ#3Ü�dy”*słVÝ܂ۻ[wr nîAmÝČ-»ą·w ¶îäÝ܂ۻ[wr nîAmÝČ-»ą·wşÝÜ‚ô‡{ˇ—·pnĘ2Z匂ůNOşjůVOągćľV“î™ůŞ= ůމ 7ět§Öů|® Ş˛¶ţć®A4†˙�,©Üâ,c#ż‡ňU€ěŤź„qËz»ŹĘţ:ôYţ>??‡cĄ}ľ_/€č«$5a<“ZC»Ôˇ’äđN•̵ĂM÷f†ďä$`ظ«f04}ę뫇v|őpZ.ŢťźĂ±ŇżXKĺđ ŇP˙�ÔzŁĹ1·f<[·«‹kŞaőËś8LKĺęő@îßŰ›,l÷Çć§u¤“ĽśOmYŮ E{s<ţŽ•úÂ_/€čŮuw‹®sXŤ¬Ł•ÍŤĚëd˝âSń棟fŇ[šeMîząoĚMŚv·‚„:ÁĎľ.]›±:ay\Ş–Ť„ťxŻmŘ”Ů3$ß{€X®ěNC7[}Ń+ËV‰öćyü;+ő„ľ_ĐîXr<D;’·o[÷+¸îM'rłś-TĐúÍsúĂŹMÄ4\îL—n‹Ŕaw$ÉâmÁmAq�îNn:ćµ÷#‘ʱˇŽ{Cp…xĆm»ëśŃÉ9FÜN˛—3~·žzôW¶łĎáŘé_¬%ňř a· ‰ßp±•´ąŤ´ű¤+ß%ť¬©Ş-a–-X)Ý%öFç­ä˘Š#sń5ˇ»·t§”µÎß»/*.,ěüV+n)ó».j°¦LřăsDĄťmÁ6WşLEîż;§»]ťÔÇŞŻrŽôĚ;Ýů)ëŃ>ÜĎ?‡cĄ~°—Ëŕ: Ăö‘ߪćÖBKpuIä€Č‹¦ďmĆĺM`5Âý×áŇ*i[Ô’—<;ÔuouŃLpk®E÷«őV#eĹ:#ŰYŕ{+ő„ľ_ĹÍ� :ţHoVTŐm°ŚGźw“ΗrUîc۵oč űť®ů«‹nÖ5hźogětŻÖů|:l6]Rm{#{jhżqďTLt_ŕľç‰˛ňčČć°]Ů?ŇĽ3q;µŤeŐ˘}˝ľ±ŇżXKĺđŐçÓ˝Xˇű(v®łCśćî¶%NĚ,ősč8Űz­Ć!.g ăX[|ČÝą ·+ę=Z#Ű›ŕ{)ő„ľ#áÓ�âżÉCQlźÇ+ŹÉDěmÜ+#­íik·*Ý â[+ÝMP–Ĺ€ó ĺÉJÜ29ş¬ŻŮčnoět§·Ëâ>1®ćČ*cf=xZů¦ …µşV4ظ%cCśžYő4¦Gâw+#Şsóηmˇýąľ±ŇźXKâ>€#ĽŁ’�îř”ŰqTŚap’şÇ{ ±\5U<Ç śjIóśG˝xÜ-ćŮ#–KÇršĺćă;ŁÚčnoět§·Ëăýşa[-QYäŹ�®©&`°™··Şyj+IN/˛üň^(­űŁŢZ¤?8îwYYní4?¶Źé=Ž“öů|·an¦ ue©ą-!s ŮN­vÔŘ ĎZëx;ʲ}ąý‘«yDőlIßč۱ĐŢÚ?¤ö:OŰĺńţÝw¦ŃüÎĐ’xVÎÉą\íÖÔ7¬. ĹÂöPşHŘpßż%Ďš Ťíßą4zŢ6ű¬›qň/,q‹Yś“Äü ‰nŹ ˘…ÁÉ8’nwô´7µţŘé?o—Çűt©ë$‰3pńRĽ>BŕĚŕ#Ŕ8\Đ|”‘68#wfdÍţJF_ !¸zŞ‘`ŔßĹ6hń±ĹŤsNnĚ*‰i_€€z©Łk˛alÁ·çu,„ś"âÜÖ <‘ęđNĹ}üÁuÁĎťó(ăĎŞ�;ÔŤ-vd¤e‰¶á¨ZC°˘ďEĽPÍéí±˛"Ý íż„ö:OŰĺńţÚ¸ ¶2[Ô+fáöJĂĚŁny)đľC3˙�Ҥ¦{Ş×3"¶$†ÄÇî·_k2Ţa8Ü&ßp[˛9·‘PČö_ggŽNŢť9ŘćHw+) ;=ˇînâÚÁ Yç¶6_[÷;*"}ż0śMÓśNó¨eą_­tƇĆKJâ8"ďäůąsŢ9)sv¸›‰ö˝¤Ë×>ÅᤜĆVZ řOc¤ýľ_í¬<ĂŐnţ)µrqk ˘ž ž#{\×Éi6Ľ[ ¶{˛ć­’k­Äy¦T•ă?˛tň„ŕÓÜ…mwX‰ő€ąšž[€f‘cdą×ńN‘ěh˛,¸¨ÍSOR'矨«¤xŹ­Ďí¸/G-AlK0ç{ŢęůÝ8ÝzSloʹͬ6Ńż4Áó=čâ¶Rř-ˇĆÇÝÖµ÷-ÓéL…Áětź·ËăýµC pÇÁ2&;7 é´1â/$täĎ|Ú ą*Š­¤{ Ë\ßQšHM5ŚsWA:ťŰ&9±’ě7}Đv{¬äďśfŤ;‡ÚjvŰŚĽáŕ. ĄÚ’üŠ"JyKd`üAmáuŹŮoXG4z1Ä1ÍF3ÜćÝUÄ#™Áą·† O5|îsZ*gľ« ݇—c¤˝ľ_íŞ . ÖLל;=ÜÁTÎ{)Ć2oĆčŐb›`X=^˛תp‡ěg˝7Dʵ5ü•Dn‰ć7udÜUµ7(ĚŇŹGa69ąÄî ŘÎ ~6đv ä°Ý1í§–îĎ+Ţś÷=řÜ븡„‹ąŁÄ##HĆîHFâlş0Éî;ňEĄy&»$󎆆ößÂ{%íŇř˙�m@Ž-M-ő^”ëzâţ Ňćľreŕ´8.’IŹżçí«L´cüş#T˛9‘ěÚď_Ö�V;fśâçPŢ©Îő†ç6w5r>~&˙�˛ľWµ’Ü‘‡Öhż4ćĆăën·rÜś1"ÓËV†ößÂ{%íŇřôBŃ­–ă˝ ů-1ę0÷ôB&Ŕ”ăs}CŐŢëę¦őĎ‚‹,q2í<Âq?âłń5lĂ›vXŽaŔmî·4"Tq—Ü b˙�t§Fqf,{ÖŠŚ¶®˙�é=Ž’öé|zîMĂĹ«D8lÝnśCsZS:F›[ŻŃ OW_Ź%¬SJż+ Jşt"řLgU.— 4PYX[Íq •ĺĄÖB`}`°ĆíŔy*f»-ÝŽŚšŮOzÂV ·¬VďAh¦ť‘p¶g5łÝŮŞÖm)+ŽNő¶°j“wB?Y±k9 ńÍbiűJŞî!ŤVC×QXq ęęŠG:L'—c\ŠŠßŞúé"ŘÂÇŠ7ŕ«ćÁLíś­ÇÜzOú=pťăUŻ˝9˘Ú„_1´Ď-ę?[UÇ%Ő䙇üµ<f÷ĺaą6ăqńQR8Ń™ďĺÜ˝.¬ťdKm -đW}®×b EľóŘďĂŘ×{Tž*ęúôdXę;™š©?4x(ät¤őÜGŠŇ–1޶])}UŁŰYҶéâĎ sQ_]˝2B <5 ŰąIôd/˛Sj‡csą_$-‚Ä\'ÓgŠnĺ˘Ü˙�KĂ#sÂ{÷'ŠĆ ®®´\mm6!˝ů•Ą¤ `oÚwěŚë<·Íiö”ŹćơŞá ÷fĄ$5G†Ů§Ýí˛Éd­•í–®=�X“ĎPřj‡ŞĹÍŢŻÉPşóův:Eî5OŐbM€ş˘ŁŮňg'˙�]MU3¶Üóä9އ óëä‚©ŹgPör+EC†-©ŢýŢĎ53ËÝďÔ†tNcă6Ç{îŕ§»©Úîü(IÚ§gäÚiA6~•3 RP6Ý’ m=L]h›Ť»ŃŁĄć/ܡOBNMţ˘Şččď�łÇîŽYŽĺĘăzĄĄ¤ť˝Y%ĹÄdľM‡ß—ö_'3ď^Ş)d‹~mć´x;}ü;!í˛x¦4ąŘZ.N਩�Äs“ź-dáeŐLĆi.wp §¬ ~Ň6żŢUôĆJÖ[sĆ~Hd,7kŻv Iu“÷ˇ˝ꦹĚuÚKH䙤&vOxęXß‹ř ě&ăzŇG»KZűüPŐK1‚`ńçŢćÉssiU´˝ľaE;™Ţ9*Ř[R6°ý ŢŢk4 ”R›b¨«D–dą?ź=m§k&Ú3.îĆŞ'ˤ^Ć î©iŮNÜłyŢuZQű:'wäŠ5ˇ_ŠłÜ)Ö<ŻĐÓÔŕsr~ýAPÁCś×“{eb˝g¤Ůi!Âü”±‰[„§dŞ3Ś÷j§ˇ™ěsÜ0�ÜŻĹn9­Q…ŰťęřęšťŻoUËc︅]KµŹŇ#oXŽły¬7 Ü\BŃň8Ň´É}öŹbĐâCEÝż[ś¬lť<,Ţö…ĄgŠX€Ť÷8µS6V™ŤĽ”51HDPCßM2u™„xô4Ëşěg!uëăĚ®ęÝĘ‚IQ€ÜeÁRŇş:˘÷›€Ű7T‘±ţłASPµĎËâťLáQ°¶iňI(Ĺ;d<¬‰ąąŐAQéçë·'j©¦ÚMÉËřŞ~›ůŞËí˶F<Y©šYlB×Ě*+™ňZRB#dw<Őx G9Ď‘í4ś¬lx Zçť×áß«† ´TXa2ďřt Ňn˝cłÝ’;î­«GVG˙�8ttłp†Ô7 ÇTÜ]=ďy»ÜNşIĚŹ>ôŇĐá¸ęÁÖ.)ńb˙�ĂÁi:Y0íqc ĺąSĘč$ÄĐ Ô’:GcvdęŃUs¶Ě[.îČŞ™Ű XĎć¤{žň÷›“Ф‹k3cüĐä:/8Ţ\wśőć¨}®/ęčÔF%…Ńž!gYĂ1ĐŃ?ôîü=%E‡çboSĺŻCű_á=‰Opcq8١UNéĺÄwp‘čh°Äd?oáŃ«~ΖGwjňEw(NO–—ڞ§ŽIáPÔzD7űcÖčéM‰Ú3čĎí«Cűgá=ʤŞvŻŮłÔoďŃ‚3,­Śq(�Ńa¸ttĂ­LÍÚł^z¸(Ťâi<GGIĹ´Ą'‹zÚ­®ŠsÁü8„sC†ăĐpmś. Ň›‰ąĆe˘=łđžĆ×޶0ýĚĄl!ű˙�JŘC÷1ţ•°‡îcý+cÜÇúScŤ¦ícG€é9ڮûÄ-Ś?sé[~ć?ҶýĚĄl ű˙�JŘĂ÷1ţ”2-„qé aÜGúBŘA÷ţ˝¸‹ô…°îcý(�Ńač¸ 8\w¦CvDĆžćö›)=<SŠş‹`ÄN4ĘéëdlŐ/ŔŃ„bÎęZ™ RČ&pkćÂÜ÷ůeG±|ăg]<¤g„“e_)Ž0Čţ–C…Ş9žÍQŤäľ79” q˘k\÷bs3wRKS<~—>ě/‰G<otŽ–şhşÝP ܤńĹé˛61,eŮ›ŻH‘‘Ôç|‘‹ayßtéĺu�ŹmvDß5¤¦”ąŃŔâŃ nň’.ôoIdň3Ş,ĐUX’š6^®[Ľ‹Üîć¨Ěo›ć륖Ůá('pTUšd'÷Ŕ9Y:_â'VÉ źŐOžcOLé%s1wJšWíÜČę<x $đPĚ×D şFV;RNú)ž$.{/ůňTM‘°Ťł‹žs7࢕ĆČę÷6Aöe¨¨tPăŮž'‘˝Dé`¬m3ä25âí'zŠŞCX$$ěňŔšjéŁmCŁ µ­ŕ¨e‘ĆHĄ7tf×çŰÁĹlóĽzÖ TqK&{š6ŹqvôĘJŰOhÚăÜzÜU;ŞKŽÚ&0węY'Ştł9Ń”xĄ˘¨Í}f˝ŕÜ»zŐFKK Îç*H¦Žžr@Ú˝ĹĂ5LÚŘ 6š3n%ę*rjj$ťŤ!ö jŤśp8^6M{ß쪊Y=?o17ÖµţŇnŹvÁĹŇ˵p»€vD§Á3é©b,őHÇź�«#ťŐQIaâ1ÄńTćrNÖÇĘĆ꽲ľ™Ě]ÎËzš€¶0a’G=„‡;$Č꣚g f<HëőśˇÔ1‘ĆąÂBňŰä7!ňT _ qaiqT²˘±Öâ\\rJ łslÎĎv¨¨Ź `spL ÚVΨ=•!k‡ Śľôřޤ{ŞkĂpĆŔďÝ;GźDÂ$“Ă‹«tßLeCćř‹Ŕż\rTPľ<rKm¤†ćÜ?îź˙Ä�)�������!1AQa qˇđ0‘±ÁŃńá@˙Ú��?2úކÜÇsüěç łh銼´Jv4}ŹFo‰Ä»Ák[Q&ĎËĘ+j Ż®ŚÄ�ŕjč`‰_pŞkPD±˛ŁĽ`DŮD±Fé‚Č7JČŘ˙�évĚFČ`Ţ\uK;1:eożiĎŻůŚIBÔeŻD<І¬Đ8´Áü~˘ďÂg\KŢ íÜÍKÖz¨ö,GnímĎůS˛)Î4ňU¶r©ě1˙�€ux— Tßžq D¨ó Ĺ`őÓµé˙�Şť`U·”ÎtŢ$ëtŚĽé4u04©HŃexá¶śŞ­Ąőđ»1}ćýŠF]tí3÷ZJÚ Ý/‡Ďh2DŠ7qˇ^×ţ!Ţ8F ŘĽ¨‚l¬>ž°BăQľąc‘c°nOĎţbŞŢCĚĂ»đŢ|c÷řßÔçSüďę•ýBüüŢsä˙�POçýE?Żúšre¶xF…ިzĽ ĹsďSîPčJtűTű Öńĺg´"e`«¸fÜŐÖŮĄ}ü±˙�±źé%ź—Âý)łĚĽŠ††ŢĺBôj÷4ď‡GO- �ŕŚÝßŃ—‡öoë+óBTţOçNc?łű+­Ř�Á´UĘÂŚAf˛ÖeŇV˝Ł?ŻăÉסO,Ƹ©–)Ů‚ĂV7µŢVÓzŤŤ,Ď⦠>÷ŇdTG‡iP;ĘŇTâ:3™Sű?ł1ڦޑYÇy† Č‹ňšAß_ą´Óýľ=Ç3[3,Ň^2Vś±QkGäAuó{JXnSíX„ŤkoĎXÔ•¤âúm+5‰='öă˘mc6ËKňŐŐKśâŤź´ĽTŮn<ăÝř^Ŕď=çzCžwíSZŻĚĎóqG{.ó.@dĆjYffžqęĹ®^’ډ­Jo産esĚ=ę.”µ»Sť.ogMŁĺŐ5óźŢ¦¤Ş—¦ţÂăÎ ßâ+Ý^RĆ‘MŮD ‰Ă¶JM—ô<ŕwžçެ°á«[`ďY>Y”󆲎sŐ+»(kËRćAĎ  GĽ»`°5‡~đýJźČ‘Śs¤sÚTŢZ@¦ű@ l¶öŢm•Y4"¶ŔbS9ˇÂ´˝ř™AÍF:1 µëR–ńk*Đ›Ooů>Ľ÷˝VXć!ł.ZÚXžbľoyK»UÂÚËHĐ+Ć[‡ń…ëźëßç C?Äg#¦Ë]Mf3ŠâyÄ}ş0śŐšąi—{±.2ń.P_Ţ%´Ňë–_"µ:ë‚sČo}剻ą‚QŐŽUVĐPŔö‚×!Ăw‰Ůl ŘÚĽş`…ËÇĐîú˘2â§tň›ąrŇpůË%ŮL¤&®Âń‹í#Ë&—-†®GÎzłďÓt1gŚs´ĎžKÚ^{ÎRąf÷śErś1 ůFôÁ˝Vî»ć>5˝IJm–é9ŠOâCÝţĚMc’'S ôͰŽJí»>ý)×´®€°ĐĽB‹Şw?ě×?lÇ´1 °a’€ÖVčľ­­â¬ÚPË1†±OÓŕ<>çđř^yoiŮŰMĆĐĂ.±ÜUĂŤ1ş«ô…Ńtj:uw—»-/ěů•Ď<(šŽ=b™=/¦ÝyBÍćő˙�á÷?Ąg„עrâ:•,g—3(&ŤGČ©˝ubDí= Ă$«—Żî¬˘Ń”- É5RŃ(Ý+¦ĐóJŽß °OśăŔx~S„zîfşÚńÖńݤŤ]ŠCNQxżű`4VgW0 Ľ 6ŕMĎ´˘7˙�)Ťŕň18peźb˘5ŢhÚ=ˇń\}ŚáÔŹVŤ’ńĐÁĽq5lË%š<Ů˙�𰞏éÄ}eË‹jéJčE‘m µAľŃűËÓ#ţEűKtęx6đ%nO‚ăčü'+–3EäveťŽř&bŐÜ'Ě!AŻÚˇËZŽň D.‚Ż#Ȁ޷vˇZ«ŃąIĺ<ŇsONŢ-Ľ?ÇŃöoĂÇIž•›Ę0ó›ˇLby†|˝Ąďx¬E¬ŔL«± ¸16ůäĚÂŕÄ–lK4EżüśöďĂŁ;éۇá cm1 jrJ®­ś*ŁEż2Ôś¬nő!­ĂČDkÍŁ‰‰ťćĄ·*¶ÁĎę0›Ń6š5ëU‰·„xđűWáálcĽW¸´±FiÚZ‹Ď–Šô6 űĘťwZćłŘ”óě%Uç1Ż”Đ.›főn!ŢńÄ4 ĎĺŘ ň¸ůmφźS#±¨ŢÖľXMř}‹đŚŰĄbú Ŕ÷éŚBme©+äo(i¬{ËXţŁyÍ’Ńo>ťŐ«+Š…ż˛T&iÚőľjdKćŞćą ś¦±Všy&™|1÷Ťyü Ť,“Mż‘ EĐfżK—–°t]Ô|đÓRrÎ]—)­%<ONš> đűáÔ ¶pß2:ďł4ľ!\â4ŕó‹LĆŮxŮ˝ńuë,~0ű°t43‡Sžq¬b56`.Cą‡´ŔX8ńMí§iˇ c`Ú˘ÚłQîg1n\×ć!*ËvËעU¤coôŚŠ9@…ä|K!WRŃHâ&:Ý zPĆ72čË ŢbłüĽ^AóđűáÓh8a­ţ ˝ŘłL‰ÖS ¶żř–2sq‘ě&”\n7y@oyR č)”çh—ĺ˙�eNŤ=—„r"VăĽ={qĺ0ǦÁú+ĘaҦˇK"/ż5‘´qPˇ7Ű«,Ęx:FĂA‚UZ«°;šoÚ*d…:¨#˙�f’Őŕ|>Ĺřt<°]9ŹžYJž`õ;ŤyćQjKÎ…Îäő‹bţÓG+ĚÓ ĽöiĎćX� o+€™–ďDLf”uł,ărOáĎäCďOăˇ�aé]˛ťŕuýDK.],lőľS˝ÂäŞú/kü:dŮwF” ˛ş¬ÎÁvÜq÷ĺw€öĚů ¬ŕýŕť™Ď 9öđ@ü/ĺRĹ b®]1çfČÂÚ˝›¶űEÍV ĄČăÔž¨#$Ů[Ęy •ŻÜÁ5MÁjÖĆX̰kŚ÷‰×Oĺ§ŃöżĂ¦Č|±.!W'GŘkíˇEn~`Ó1�-FݱýaĺRâőŰ ú[~ý%ŁdiëP©Ňo…38t·đ>fvŹ=c:<ÜÚ…»3… „ÉľâÜšuVsŘ ŻjţĐ;«Ë¦źËOŁíßÂ#ny>kcłşŇŇoŁ} ݧďUľšXf‘lukiäa^I>6 ÉĆŹOÔ™”éé´a†Śâ «Â¸ąk} §-eěLb4`,j}ˇŕ|Çř%:JžP\t,ňř»AÝÇ]FŃ—Ś1ăˇÄsUęiÓ$»â4 ńÔ3çH.¬EőŚC¶®{ä5ŕ'.ă ‹¬írU{’•łĽk9M>ŽŐ?ÍUćîdËŹ9†Š»MZ¬#©ŔňřÇTÚěš ĚĹęJNEă®°ÓM?YSr7íčäŹy¤¦mŽ™Ě6KŰ1eß.a`Ě®Ťě_Q豑¦][-çôp˙�—i[ŻÖ7¦˝X†\fö—ózĽÓ‰Ť�¨G^„ŇÖň뛓3he-ši,E¶A1WłĐ˛r‡Ň 2|ęQą^©fD|Pü¨ě°˛»‰mĘk tdÖyʧŕĎű©5ű7ëÂř6ţlv&a€›š÷—ÚzâUÝŰçi^V›ÝK–ÖsYb‡ĎN¸šôŐŠ0˛X¨”*¨CŤj{2şfć$¤a._@t›Ź4MˇűÂG…´¦»%…«ŤÓAşĘňú"dáŘŹŔčRS7š?Ôđ‰ýJđ~.‚rMË"R8Ěą›LßXŽŇúźtďPŃ<ş©L8~%ă4¨,ňJ JŚyĹLĚ›>ó�™ľjAU€Ý�ś–.Ößő3Ţ ^óPű«5@Wv~şzčÁxzňÚ]üŁçÚgżJ‚´5Ҭ4q ŤĄŕ"urOŇF2&ć­µGhŠÓÜ3†ëżĽX 5˙�i^ř3¶DUň¦‚aÚjt#�ĘŘö•č^żÄŁOn#zĽĽř»l˙�_Gáv!´ş‚PµýŰ ;p 4Ć1ĹÓx†n^'xăĆďj„ˇ®p«Ł×A™,&Đ„ŞIDp4żoä9¸¶¨7 T°ž‡A2ÇX8‹[&*±î˘ýĽcF›č ˛7: #gýôÖYŠ%;}>ŤŹň0eÚĄsôU´ň…±qĬjKWZ}˙�fÉBs×)çôłW˘Ě>ŕVąŽ(űź†fŻ"KÚő”ÎätvVeqQ´,;“ĽŞŰpůűčŕOa†ěQŠ­Ŕ÷ĺl˝ć‹a%ů©Ţk\˛ę›tŃôMq-îĚÍŁf˘Ď{Ć\ĘŞT§ˇ¬˛“€‡ĽÄóŻ‚üJOź´MĆ´J9>\Lő´µ¶:ŚŰ+č?÷f3Í7ď9»×ź(˙�yJZÝłŰX‹‘r¬ń Ď€ĎLĐn^Ś ě?ä…ĐyŢ%rś†¤4–ŐXv™7]ŻZÚ�3Boçýš~ŽřŽĂŰ.‘Dj7Úk1Ęh||Ň= m¦ůTíýš*u?PoĽĚ±”đ”Ěě>3.xŰ·\ĐA°±čáZ‘óQŘTiŃŤFÖRŔŤÓ•–×WwfĐúı6WNDÔP–^c<ç9]ˇ@6ăÁqíÖŞťćO9Kb,T7řxĽťŁł§Ż”#.żÉá˛öĎńí§Ćňú:ŤbÝQXëÇÂLmŃäţĚŰÇK„¸öaćâoXď8÷MEkŘŤyŚëâďśő5ýuÚdL‰ł]hüřĘK†wŹ.ź ĘAî™c6Űą›9śÄĚ7ĚŔFłÁ ŤOď\ÇüŹ„Ş řŤŁČ-ޏĹgŮ[ ýĽ57 ÷í=•é-ČŔŘYŕX@R1{x|Ż/˘@G™ţf‰źâçř™ţf^źĄ‰â¨Ŕi’§ů™nżg?ÇĎđóü„�€čô¸ä§O[l—ůř@ŔĐ1áBFÔ3ą$!úŤÂgŮ˙�#Óf« Ҷ+Î5„viZřď8îO»*—ełzĽ?˝AŠśÁ)Ôj«ţ2Ôcôf»qAꍓ 4$š>WöGňjK˝ĂÓ.‡ĎÔ Ú ż÷ÚŐç„î=ˇ[2«*´ýĆn‚ÖfŢčęZ|í,$0 wevڏf*¶+«Î"ŤZ—M|®‡Ç´ZŐ±öA+ &îRS˛ţm÷›DÚä¶Ôüḏ̌–˘m(ö©·×¨íżŕ•pö{–qSŹÁ05pެä9q[˛Á …b›żZą2Ćqˇą1ď G| ćVÔ¸ŞR»ę¬çâĺ1µ {׼©3¨ç0żqĽ`üFŻĆ„µđ•ŻW%5 †Š7‡żÇnۉŔ(@ 4{BüÔ1n$×NîY¸‡„JaĽ#Ŕ*Xí’*o,>±Ň6…€˛ń–›˛µzi÷„r^ÁH/~e„Áš;>݉ÄŰ˙�˙�“˙Ä�'�������!1AQaq‘ѡ±Áđáń ˙Ú��?!ř…}ľ„8ť'HńëôůŹŃ$ĹŇćßçóghô3“ĽjóHŠöąAD$ęu Ä• >xţă`éĘŤ‰rĂy9ËWZŠŇGŢ1‘ĚşÔŔ ­{©u†+›ˇ•č\Óüa>úĹYy¦XNb-$ý ‰{—3čëC¬ăl�ő 48DÜöžŰ‡•Źżź0ýĂĚăÄ|ĎoÄéô8ť>ŹżG÷ę8Š^Ş˙�;]P(M [Hr†şłY1ĽĹsŇuendYĆăűŽŮöŤÜN *Nđ·7Ý” \są×ζy„»=ŤÝżą@Ó°nŹŕ¬Śľü[EómyŠ˙�BI¦^&r‘}Ö/ęâ9er—5ĺ0śs"1XěźÔěĹżĎ$¤›ŃĹ4×ŮćçĚ!ÄăÄăÄtâ0Ţą„řúüNž“âC‰Ó¦#Ć9śj>śÇŇ.y– �yQâCT«»Ď¬¬ú‚W0\4kľr'ó´yŢăÝ»s§´RŮ8™ŔńŢç<WůÚ˝„ĹĘb˛Rşi/“‘pr¬˙�Ś "°w˛^°ť4Îr´R^®Ť˝Đ)Đžń|^Ą˘@Ţw ¤_•r¶Ć mŇÉT”ŘÓÖ7ĘbË”ľ\®t‡ś‘׎óë9ó:OOGl9ź޸ť1Ä鉜ŇOČ+©źÓôč9…“%Šú«4‹Đ!±ŞxÄ0ŮŘŚ˛±ň˘8I{;ë›§Ôýć-`q2ů[ů†;ËĐĚ;Űßć?wĚu~ČPt:3üS7~d{�ďß¶9zťćTww™»˝XcýóEd:ł|XäJ{4Ýkׯۺ— ę93«72Ú±`•úóݧ/¬'ÄřźJ·4^RL¬w€uŞo^9Ź+mfc©lÚhčc§ĽëٸÍϧ Ż"ćĄç±ţ± Węedv.N•Çß{ /Xő*˝e[z‡<•Ů«<ĽÍ¸(2óŠt@˛łá;Žąâw0e;é'9Šş ]ăĚŐcŹi]°KP¬”vń­#@Ѣ[ eleYE¬¸–´ÖY.;µ{ĚýN]îĽĹ‰×zźâ3îä»Ué6b»},VĽŠT­˘Î`S`qđK5Ä@˘ˇŮJČâ1Qśă ÄIDmťZěĹĚđiŞĄa揳É2!RĐۤŘĎ=fšĺ SŻxh©Ě˝ ŃXŽľnVkÎ=ełUw±T1WAYĂ,^ *Ž:Ě9b Ś>[‰ŮYÚSŠöüFp âZp3°żŢŐ3—c×™±aNąoűÚCĘÄç3ťó Á¨ó™×1ç3–~”˛ă(NgX)†4'€Ô pÇbrąĺtëߤ¦¶0–‘©hr]hć«=yÍky‘-z77ľŇ”ŃŞ´ ŔVŤ{R—]MÇ-ÍYĽbéÄĐQĽC“Ľ¶ ęʵe¸="]¨ÔŰZ­K<"SF“źt9Ăp®zÎ|ů•Ť:nŚŕeöŚ%~äŕ•{‰mŽvT"ëĆnö?żsžŘÜ<Tbťeó>~ś}#Ěëu=ü´ ;9éö¦Ąj»ç7®ž×é:cýá‹˝É+eOiFźźgĽąMĘֆꩀC\ŚîĹkĆ˛Ę u¤z’µÂčŠĺ«Ď/ý?W´”ŃÖłu÷•f©\á0sťĘ΄©Uቑ¦ŽcČśG7fę—ŞVúÂ憗ĚLÓ Ű®w‘ŽÜܰ¨EĚ»ÖĎiE±äŃ×Ä6e^1·QASŞâlV đç~‘P+Łş›[Š»Ü-mŹ´o÷Oq‰ń)zź3ÚuÔô>ą6FˇcťŤ“=—ú=ć¨}źÄÍ:teíQ2+Ńâ&áϤ§®†¨|»wßó1m ˛qF]Ś_IŮĂ#?ß~ ^¨Ĺp©ä އ‚Ů$(ěxIĚ5­ë1ÖDŘäj` ^  Ľć`1Ń«űÂg9G:ńď�ޞ•íBמ—č®}Zś®-eÍGoĽÝXŠru‰A!ÜÎŤűĂ6TŠ)“oEoĚŘë¤�Úűî#·Ń iÖńzÖ&ŘÄ˙�©ßńÜ'ÇŃ-ŐÇ÷=ŁĎÔIÄżV]UˇVuĐŮ‘,ÜŔĐu­ř†ŕą/K›-¤ËÖż1łkĘ­«>h·Ň}§Ţ:ôç«Ć czQ@¦šcÖ.t•StkŚúFO°fV?ú‚U™Kŕôéâi3čŠhßtM×IËQ–ťNϡLĹěŮ­>sÚŘaT§d%’öÜ{ÚóĄńuĹüőÚšD�7h×٦ł0ŇámqFרŚäŠÉÖav)ߏĽ¨VwVBE¦Ő©6đuׂâ��*�lV•FuQˇ«LĚNU™ţčNž“ÄLĎ™íąí9kř¶<ňń:0”„°pÇĽ(¦őëťoqź tąykőbIóOZé� »`ÝšâşÇčÔĎN+ŽŹ06ç `čúťB«‹r×Yw«ÖĄ2âWŮ(ćőUňÔp ´â~ÜźíęĐ-Ty›ë;q`·ÖXäĽŃ2ĺ˛fŔ%éyë„N6e‡·–¶ŕ<Ĺ$¶Ţ¸ë\u—% )„Îň6Čňł X±}nˇň…Ö\ŔŐ Í›Şło¦s†'Äĺ•:kpýĎó­˙�ŕÉ=&¸¦Ř,‹"äžm_ąÉßq«°’Ĺ­×ŢQŚ%‡z31…-{6ů} Rr¨Rq38V„·'7ŚXęöĆŘŠ}(VkuŘ\˛ö0Xz%/Dzń8î Ľj#z§4¸eG‰ŽCé)ˇɇ´¦»DĂUŤ@/-gs ńŢ Ă1¶EéÂL˛ËŽë+·˛ËCq©Ó$ë­ýTó8ć #3o]Ł2 pvĆ +BŤ°óé3ŚŚ/Ź‹ů‰µgĄ\�V×®PX3L#X,2x|rĄţćY ŃOIE÷¨LŃM`/EĺíÄŃMP®Čľ.(§ů*8qéâ…ť'!2rďű¤S¤%R8– ÂÄ#ÇçéĄF€Fî/ĐgP&ŽÓNťcËbţl?S®8śůú:C‰Ň|ý]Ť‰hĐŢ:˙�qő1.˝ĺ4´öŽó(ŕ1ÉҲšţ%Ř^?»ĆJT4ł)Ś:bRŢĽw·jľÖÍĘq2/F&lôĄˇ(q+e”`’ŞłŃ–™č\bg{ó9ľ‰’…Vžň•Ěęć0§ÄŰôuôăĎĐď㤷7^'U˙�|=8ťgŽf“¤6N“ć(źT¸[¸]G÷ĂnőÄĎXY=Ŧµ/UĹo M¤MO;řŚ6 öń—Ľ[˨󙽱6áë3„-&–Żwď)Š9`ç7€ß¬É×ÉëßľćëIKĹűŠl8çŇ#uŮźâ‰`Ě,ŹÓŹ3™űśKĹqô/Ťq<s9=bęCdé>~śŘÁs»éÄ´Ĺ k‹ňĘ%­É}˝C-mĹt^%’Rţ‡ ę_ Ä í}›CĐ]pá–Î%Ńm®łd×wc%0 ń8&}ŕŮĄpĂ=.·„l é–1ĽĹ]ĹMÔ[śNg_ˇ8–ŃŽݤđîzÎ Â'Ióô§ćvOŁç§ŃÄl:3U_^ÓCŢ?Š0 {×Rc8O2Ú;*Ę˝őyÉş—Ćř/?ářŹ©‚X)h¶Îd´hÖ5űL›˘††{üĚ˝«>Ń)¨Ż‡W^ä7@)Rú#ÂęĚb#x%ă˘<G_N!©_wÔŁyÇĎŐN'Ě6zËÖYń:ntť>…ĚN7ÎóvK›t<cq®Żxz˛Ąť…SźÔµC Ëč· »oŃÄJ`hPń­ą—°Ű…ę5Ţ:¨^Ť÷¤`zÝ„ŘĹá©jz3M7ĂŢ:цÍćÁ›•ŇąŤNÄ˝Ç_]Î#{ÎgözO3ťó:GŠźâtĚéô«[Ńëtú:Nj3yRµ AJ.;ú:‘ŐŞ*7wće`×bp4u–�«¬ŮÜ×Ř›XňęôôŚÔˇTć;*wnŞ©€–Ľ }˘E“ bnń訝eF»?Ě"ńYĚC‘‹ĚD!(ľFĺfUę)(Di'ˇôćXÝń>g'¬é8Nľ“âtôť>Ź\K9ř–\HÁŁeŻV;+žŹ4KWVć4v ™ĽÖó…ž zEBÂćź™m›˛ĂštcŹxµ0@VŻBUtô!É[čdÝD�(©l.…ďn!˛*ד§2ňná•*ç;•66uME&‹ćÚ· ц˘šj[yú@±ŇăˇU/¶ł‡­NźGń- -ń™Í#&Jo–ŔÖz‘x!SÖp}=a¤M§óú“ćO‰żó>'>>·rWűqńÚÔµ«Î:ņ’°Ł°Âc­h¦ĚBůńÚ1ĄžGg´ŢJµŮpO'ř•0 âU®(×f·šęń Ý!Í˝˝*^k*6–6đC.Ű×kuTsľ±‰Ŕ:]4Ł©„“ş]›˛›6Ä áb]®#ŻĎ0’‰y Ň卙»-ÝŢyôń.'íú—Őd·Y'á•ç#E¨:—ĄÖf9KhńÇh’Ő(ĺ´X4©3ű1áu››©›o*…ńř–ěü3ž7:OmMŁĎŃýNOĄŠú.q4¬»+-čďňµ Ć»0÷YŃ,ËB°9ă^Né†7€CXU÷”ł1=eŚšó {ŮÄZ'Zˇá¦ńŤ }RĂÓŁŢ=íŁö«›X߬ }«}7ĄMşń�WC#`4 i¸VŁMN7ÁVN ˛ť]‡Ý-FÖâia{«_LV*±§Q´ŃËůD›!<tq+nś®ă«ú‰AśóŤç(Ě!§…#ŤůH\Đ,'~ꏇL#FzÄĄ+ůp»ó:OmFŰŃ8ń>'>>™ĎXÍt%ł”ü?ąm} 5]ű#Î;ĆËP˶ő9łŤ Wpü0‹t’˙�I\+°łMóÄBL ö¨Qݤ}‘îëôP®˛6÷Ó¤p¶şŚÂ‘ÔŹx2:ŮtzÖsšçĄVş–Vď¨xĆ?ěA€n c ÍěůÜĄ§ÇpŽAëSP©N˝¦cJćD8Ŕ­?{•R’ÄG_"€r•aĆ?p–ÂÝů>Ű7)ď5Í"ËčJ‰íów©h ŞÄĺ¬rĚz éBîáż3¤ř›q>“âsâső1A`Ň-Ďń<Q!{Ŕ'çîAĆ[ç,5Ôá†e˘=Ř2ŇdÇY^\ şB ň‹>ń¬@âőčËś_ŃŽđľ„5‚ŃŁ°8{ é¨'Ćú˘#źń3Ą 晉W‡ęY¨%ę¶Ĺ·/»€ýt©~dŐl;8ĆȵĐáÉ÷CUČýÁö…৉–«z©_A/3´ĎŮďë*dea§5S™CM.Ő"ĹV^ł0UUk´7ćOÇ™ç‰ń<ńô óyŮÚ ďé)ú’Ţ=h>°›´´!d°ŕ_ĚĹ”˛ŽąËŘç‹!˛{#îÄ„­ž Ô”ĆOUzúAwmž˛‰MÜUŃÚ�kÁÎv`+Űë Ú=řąl9€Ăž•Ě[§˝ÇĚĸ«ČLW»÷ϡ‹ß„Żâňăî/Ұń…ź|=ĄŰő‰~ĺĘ“$~đ˛{Dźs\Ł]ÎŚRK»6¶ĆČ~–ěÄUďV'Äw¸ó™ĚóÄó:h>ćU§¨‡„iă’ČůBŮĎ…i|Y‰bU(ÎŹ/˛âŢYíŠPv,Ac÷GĘvfÜvq+ŇS…Pŕ¸úżpëq¦I–vŽ:űνÄA}V"Ý~ í’«Ě SďOYMw Ř­Űxńb*Ú§«¤Tˇ§îŕÉŠZűMKfĺp·Q–E]ZcŹűŰp‚âS¸Ç‡Z}P`ĚGĚë>'ÇĐRJ%o˛ŘJl˙�h׉sr­źÝnrzÚţéLj¸"&}âŰkqVŘJpYŢPmĄEŕş™¦˝*ě†S;?Ij”fʰ˙�°—Vµ Ł;Éqí¶íß©„ö…›Y˝ĎÄ{żˇňYň„fn§o¬ß;wB‹÷}ŘM%),á —K|T¬ś[. ztă—6§1¨ý4gY×ŇsâyЬmrĺâ?„j9™H´{JýŞňOĽC ßd &¸ qh3‰éN "ćuíôyŤ¸j ŽŘŽM<D€Đśuăč™q] ‰„ÁŤzŔioĽ Ŕ;Uİuv‚™żů’5”öź\rĚĂÖŹĚoH ć7Ęö=›D•´]–1­"eý›FĘGÖ 2€Y8‡8ń\yú|GrÚëHŮîůÁ*čâ ę´dB§ŤÍJWÝN»ŻnđeÚ‚áަH2żëUćaźxM*ř•V¦7lßçëć4ô×ę&ÉÄZůŰRü!oZZš!¬)jŤ™âŘžţr›a×7‚4¸µh”2miUţŚ‹á®qÉű€7ËP#ËŠĄ…łŰ¬±h7LbK}ŮëÔ~Ź8™Ěřź‘šĄaĆs´ë/öXѶ.đ hďŇ2˨رKY—‡źOaÚXÁgf KM~§�WiíŢeŰħ˘5ç|@qSÖ0\Ó—MÇ-çŇc7ą„aÍnxç#r!łÓJmŃ€f¬şaeö6ŚžČŐ0QHdłYçĹFĘÉÉéöŚ45tađNdË­V­îjÍÖ;1ţÇč҂޿AĐůč)ę9!Ţ ’¨sąÜ xŹÓ”ń>#ą€&Í«üĄ-(éŃ…E½eqęÄĚĆ÷Ě5LBÖ8ď2 †;w‡@y(„Çřšgń ŘŻHë®cÇ} ­J”JAŃçă D%b_X ś%Űť;ąKzĚuô̦±.„öZ«żÉr€Y,năťŔěN6«‹ä}¦)GJcűQeSé¦nuhbHiĽGÚŕA iK˙� őc9ý®^'kĚ!Ő‰ßâO}}ó‰Ö|GÓ‰W7^?hî˝u ö‚cq4=%Ű&ZBłC°>÷/=Ťuužńq8@ľ řŔłµrŻ&#č—µEP>Š Áó\V+ÂD=ŕ‹®{ÍŮv+\aáBţĽCFkśĺ—JýKľ»& .Iľ“6îA:%ß3 ¸‰|n(«ÁŢbĎk&d Ř{âY٬Ěϡ{®?lŔ^‚+.‘Čk–çÔ6~e¨ěú8ńGź§ÄSâIÁĎ_3ă¤u‚cŔVŘWIż“»Ű«Sî•U—ľ0;Kčüô#¶*ŢĚ] Łż1ÉAóűUTÁÔöý9\ŘŰ™Á…E^Ę—o+t_2ÂŁ=ŮhnşńŢ Č+~+!Zßâ¤zö‹]~ ßIJŁÄl‘+ e¦ťă†/|׸ÁˇÖ˘ó#´2ٵAępôţE ÔĂČĚË7X[mô`ě¬5Ü9ťá&^$ëŐë·ă/ă‡ý~IÇÂ<ý>'>%‚:ér 0nÁ«ńňçŇŕąĚŃFĺ67ęÄÍS‘_— zpŔ®˛ĺäkĂÂc\6{ޡĘ%.ÝîrĹ+OWÉ›5â ŤS3…ĽŘu5G©g^ő]|E sŹ'#Ţn˛ć¶ 4ŕłÔ¬zÇŁ´ę—|=Mßó—9Ôx~"¶š®ß¸–ĚxéĘĚ1űüéŕ0ŽÎŃ3Ůy"žşX=Ú ~»=ş}–;íĎÓ٬ôkZô„ăÇĐŹ8ž'Ž>Ą…şwď�öćV±yÍĘE0,Ě­4ďż°Ęa2÷—Á]‡Ý†ţ‰Ą˙�>ÓÔî]ñĚăňk±gä‡I™AqąW40ĂwІ ™ŢĂ{Ş;Ôľ » ŚĽuaWr§©©F-¬DĎJ\Ľún ´…#-˛ËV>GçĽuśPEa»ź¸4q÷ĂŃ”–LĹ?Ąsëł7çŐ›Č>cTŽä…µu”5űx>źšŹ„yÄçÇתȕ /Ä��^}Ł`)[ë(E”š¤ śŘÂĐóTĎB7·s¬Őwt÷€_”M¶ašáË)\Ş„t�ÁŢŕ'ęxúsyťrÔSPě ó‚_sŚŻ1 ÄĽ5śBc‡'­÷™j1ćŰPĽtóÓ]Vź–ćRD@Öůé,» Yi$î„°"‚ľ¨÷Y|G™s°W™QKF“ÓUCŐ¬Î|ڍl÷_Ɇ o¨/ÇYű"„IO 2óůŽY>C&úđĹbn¦›6“ ×â.î«™€Ë˝ß´řB†w˙�×Ë Ç‰ďôyúó§#3 «nWŢ`fek}żČŢiSpŮUSA:Şe̸Ȍ{N˝Ű}!u/Ľv5s‹e߯•4ěČ÷0 1{÷ăć>Ę9Í÷\ąyĎ9űB}BËęĽ~>ŹŇŁ{ŕ�Ę1Đ‘_«�ŔzNm?P­ô©zÜŻnĎ^Nä-监,ckŁšu¸řWÜ=D3/°»Ćν? ~{�›ýE—éüT~30ÓÚÉů|Tá8ńNżO}Oxi™Y„ąłŻ/=?™zkü8›— ľĺąDcĐÇ~tz°x ×dhä-¨~â Ĺ{ű·(;é¬ßöćácXh€zŰÉýí0ÖŘr¸=ÜyúWÔG)‚ůcjh®“gŇ€ëŇ5ÁŚîŞşďň§žßůÔÉöę?‡¦«ă‹ÜŘÎĎý âqľ>Ż3źăéŐ,bV¶;´Ţ!p‡•ţŹ/ůµŘŃ5Ď·´(č¶ĂóĽ–Säűp¨87›¦‹«Ţł«Íç„gŘî‘.ĚŔ˙�%ZŻIĂťăď2 ®Cű·yş0Ö^Ôô—Ś^Ł€7ŚÇ˙�;±zËĚüËmWź0ߎ^Ë!¤cň0˙�gˇßąÇŇĺÂ`‘0Ć[/®S—ŕřéiViÁý_ĐăÄ`Çź§Ä¸Đpx¨•ťŐÝŁţL»Ţ…ž˛ÎSĘŁ`¬|ËŕĚiáŔPhS}.9Ş÷FţđŁxÎ%1‘bö ů€!ľTᗡΞŇĹ'Ü.هó/ ş×ă÷klîŹüŃËçx?Ó؉–)í ±™¦ri7-eúÁŃöy6˛°ç#‘ŚuJđŹ>•[—üSüš×ţOóôëôřśĘĄJAv@«óŇiţ_  Ďó‰€ţ_-mţq'–AXĆ=hëŤĘ:íąŽ†çĚŢK§âżWňřŠ­K»řfsýiĹüľ‘zµÇ÷L«°�čEiĎŢ'?1_ć[ľúĚ€µ„tţ‹ń Ů®&.Ďő˙�ř•Úb&ă t n2Ëq™oYo^&—X;ŚâQâ.–ź„óÄ'OˇÄé:CYFŕÎašŐ룑˛Ţ°ĄË/eAÄmT1°äLńS•%ű4;áIrŘFę°çxúBqK/jQr^="uS}°ý‰¦đ-=kÄ´˘đ óN ˘$’ů˘g­äfm Qł:yĚĽU"(ť›ďš˙�eĽÝA=˘>vĂruvő,9:­¶$i]c4:bÎ÷w•Ôč1U 3üÂ)Y+†—7Ż~цŁnÖ})Ť *–ž–űw1ýîó>=+ó}Ąđ<Ŕ pş+»,W˛—…đ6Š/8¶¦î¦`…/V˝ŕ‹xě=aOżbíG8~ÓŹź0źOńľ!˝Î™âCŚĂ‰Ó0YűËâpâÂă#ü\§·„~ç0}ŞĺnD@ßcX»őÍq1DŃZëÁĆëĎ�A$čőĘYq§÷ö—s¶K•č]wFĆ\wyĂĐâX.–»Ś·ŢPsŻhę§=jRó÷6¸„´±['=)JęćĆźLt‚žŤŔrłé^cŕDŻF–ÜoĽŕ˛?0oŽÍ}áśDó0SžxŐěV]]áţtl;·]§ Ę‹řÄo˛~Řx _©…áí\Ç@I™_A;ăU 2Ĺ)V6ů‡F01ňMÝFŮfľçíë8٨ĎxžÚŹ?G~Ąţ'0u)ĂÖ|ĎëĚůź3ćsć¸që:N’§3âqâsŹÇĐýĎźˇŁŇqâpúG÷>a:N<G™ó'>Ś˙Ä�%�������!1AQaq‘ˇ±ÁđńŃá˙Ú��?đkLhŔÓÇś5€éל…S˙�2x:|ŕŘŹ.ýd×Ţ ŢpóÎ;Ľó†’”ŠĐˇKâľ›ŕT+şvzɤodTH¬ÜÂVQŤ—)y0[}UĐvÎŐĆ%`ň!B"#ŢWç&ä»F^cŤÂ*ŹHó‘ÚHQ |b6:”$;ăOŚfQŐ˘…»|áěĹ‹1DeŽśJ4ŹŚi|(…1_€–PZBńSÎ+“d»›Łw1kpq@ĚzŮŔÖ�ŞW�MyŢŰĽ‰Ĺ‘ΤzÉ·OřÁľöśĐ=Śt¶ăV0 ÁöČŤ`ža)¬ÓŽ7ź94kĽĺŁËĽ„áçÎCHăaÓÎriç.«Đ1‹Cžľ3vŹd\[HG!sŰđ%.řg<¸đ4räÄUÇŕ4�@Сs[ĎřČui(ŞÝńš‚ďHp‹m Aĺ6qCŞoXćACg ±łĆáT”l!ŕŁ8öcŁwRża¤ľÖP5ŕ-Ć9ŇëxÉĄ^É /čł±5ÓëB_‡@Đ9~ů T\¤9şšĚÚ„­ŘEP9"L±­t± ŘÔ^`’‘řfȉ˜xđ¸ŔĂ6׋Ć@ëĽtí3Ů߬駷8o¶=é4Ęúp°şs‡:Ç×=ś\gX?í—ňtâ}ĺŐ{tâ–ţLTvćéÎ<ó„R2ŤĘłŞwY64;ÄeĆ8é7ţ$R<·ÖŹ]t»Ťk ˝ŃŁžP0a:ŃFľ•”Ćň\ç"¬“`źŠĆ‡Ş‚«Ŕë pfĎB*ęŽń\B: ťŤL”p,�›H‹Aꦢŕźü8‰0˘jkĽfôžDJŢž&ĺÍ«ž\$uN|7ĆńőŠěHVJŽJez ýę,HxÜÍÓT"B„u@…î˛9Uŕ,¨Ţ’˝béĺ‚hś±n~\}Ă.h:jĺđü°ŐŔŰ߬8m›ŢÜ;ĹĐňpç™M¸[Łr×fŕq[`R 9MOśĐ§T5iËËŻg*¤áÉ…h‡�G#D§ ÍíŰ€´R»ĘđËE?*ý±J°Úu\<řaÉă;?ÍőŻ•ü89BÔ„PVDZ„VdxAQ¦‰{t@óÖ,Ľ�×wĐ˙�Ç €‰ …ŕă®3PĹWţ0Č ÁĂ>÷”Á«aćâëłS[â… ,ź÷Şyń‰SuoĆ÷…J<˝ďăŇŕE#źś®4kůÇIQĂ{ď¬F—�G}˝ßŰ9Ŕ'‘Ç:ąMpŃY®÷}bŁĎßÎóőó–6=ü}aá;ŠB»hމjőܬÝ`Zč„8\ B„�9ac1·\/ŁÁěĘAÓĎÉ€¤ď†$jO·Ć'PÓ/(€ËĐţ©kËĎÎSzg¶M?|z¬]x{ŔľQa€„D˘µ±Őîőóó—íëa6éɧ<G[×%ťÁ°Cm#Äń]Îö„ ‚ ĆŃĹT!vŹ&ž?|łź?ó48i5öaR<r|âL Jß|OĽS‚ŐD€Ë7®N"0mŇbXJ(©¶5Ś2Á :ŮC€."+ű8ŢčßľúË~FŤ±ńľ<ŕ)W“·ýĺU^'qŻpi‚F©żÎCŔWăţořő›R„ˇ+ż/y:—Eý?î'(IjOýÇ”$C_žgxÁaĐŕńý8Â4†îM“üʉWHářżß¬ĐHŞŐfżĽh<Ś×í×?ĆA¤č‘§;žOśŤ< €¦ŘçEüaďÁ0ę�-[a§ś ©eMXůŮĎěń•hŠ_ ëgś–ŽßűőŤŤ7Bżĺú{ÎŇČ2öp6ŰČźśNKÁŻ<1ĺ+ůdďo2ßľr(˘ŃRÖ0ţřäĐöł޲ڜvۦřë—¸›+·ÉůOžń2–* dy~]8ĹEwä‰ßńŚi>ňľ~>ňˇÜµ¶I»A[ĆÜ­@IZ o&÷n p•p4Ă şĂQŚâÔÓ¶cj¸đ˙�ßç!ďRN˝ä@ÚOc×é‚I ŤNvĎďÖV,OVřg÷¬U%Ťź¦2"wçůţ—8Śxżăď-(®ý5Í5<áL‡~9pwňCëÇxA"ßXHŽNž‹ńĽd | ůůÜÉ Ňüéý7ďĆ𜢜††o“'Ň`)Í.śI—4¨îůúúů©"ŞE4‚TP y¸˛ô±Tv?<xő”3nÎFµ-”ą €V\qgÁă;ůiś†Ž×(láw‹ĹřËż�b/Ľbáá÷…9𩡠Că $şď�@ Ű·ţŕŤŮ«7š*O ÚźäąîkáßN/8 ĆŚmI±äí‡Őśpôńđ¨Xć¶j+&ŐMŔ)śpSŻŘľJmŃHĐx-3MZ…ŞpR[Ţ´ËdĄ(E"µ&VjĚç˝Ţyßé?l¨ľ„őřüyÁôÇۧűď8Q‚‚íîQ¸��×oí뽎®ń­_ďĆP±vł­Ľä-vS]uÁĺÖNŃ<Oßµ�G™Çă�¬+S‘ÇçýĎ#Éýţý­a Ůî}]ăÖjÍNý?h%<ÜťóüřĂx6ĐEÓ‘ŘGÎ#}]†ŽşÖmŻÉ(đďľđfŔ6V1w>ٶńŤ—¬´Pi§zân–ć®�ëCü»ý0ĺٸÓ®đę~đ6Ó‡ m ĄŘšÄYzěáÁŔQ·X“Ź:řËoŻĽ®Ş"–ç €›(ß@˝\R 5ˇ)¦Eú<n$Ęôˇî,çµ·ĽRD!\uM>~rćž«4'kWĽBâ­6›ś]#wŽdÍDÝ áJÉ«ş*6"#AˇČ ±š¦˘&5;&Ç=ۢ´şS˘”ČÓqJR&� s{?~ąg†'W>Ąý\Te¤?·ó‚ő"şďŤÜużČ†ÚţřŔA·óşťŻßľ2#8'ÓxáVţíóK° >ăZD)ŃíµD.˝B>ĺQ,AŔŘÉČ6Ř×u8Çrč"kÍČCU)ŔT´„DI©ľ°¤ÁýäQçĄ.€ćD$xBâĺ›41@ŐďěTĺ]ýĺÁŠ…›öxůĂ“Fyh»úÎe>xPĺŁOÂ÷ĺ‹ěŮź¨ď"3Ő‘´6™çóŤ$*>𛡂›:ßž&X" ¶‚ÝŤéĽt’&€6î·˝¬ˇ#MʰýOyŻ`2‡SŢ:Ď“Đh¦†1ŹLuŹŁČ+*q @×&6äĘđě*SA�–¬k˘śŻép0� ČŮR2€E3 XN…IФESÇţ÷ç ¦ˇ¬ĽřĂ6D1=OĆ2¤�Ź÷űĽ đä|NŻľrä÷ţxńšĽ$äĄ=č>ŢśWŮÔ�|VĂ®ÜéË× »Đ«Iď„áuťn€Bqá‹É«łKhÓM4PđŐ˘ť¶SB.În©ۦ—h_—ćű¶ŹĆť^IŢştűɰÉĘ /lçAü‰ 4EBö]V˘“˘ěród(ŁĐ…‘RR¤šxÁĺNÎđ‹"„ҡÁšż'.ÍvďRte…ůł¶Ź¦MşägĂÎmŔqA»Îxooś;ňk獺lŰăŇ”Ë;Jčaľ× 7&˝;_ŮÇbq—¤Ű!_?8íÔ„¦µ#^ëuÖ  Yľ!»Ż&ĽˇîXőS 'w'"ҲO–ćŢ÷<vśŞ• €°5g^ÖÎ1L¤ hEŢń`˘Ů-ÎB«PÓi­rpÁTM˙�˝ŕŃLť?§®ćsC€­čšĺű™üÝk‹Ľó I6GóĎöŕŽ4®3n·Ą—N}ňžďJÓŕC,Äô×ä˝tI9ôd`‘ě4ş…Zĺ’ôÔ+ ß, ÝÍd96‹!˘•ĄŰ„9č+^čÚoo[`EU\\ˇb0ö݆3¤�] í€xëś*„EÜâńlłŮ{Š(X†ČÔÓv€Y‰ÂŐfÁ`w4­íąrŮ˙�1äźťĎçń…cK‡w˙�\ćk· Ŕywťµp`)GóŤňowíś^o8ÁCcĽöoƬ!äa&Ű";®ąŕÇúńčú_ß‘)xʼn3AtĺŃ:łďžMBPäă<ž^=:= ÷půăA8$ AŚlś8¤$qj+ŃA¬»¨«ŠŽŞ4!ej9G/˝së8›Vé'ŹßX÷]&.°C_3ŃńůĹ8h/ză.hQ 5ÇŤ:—*‹uPčf°.X;ĘGaˇóÚąďEŤ"űçůĹ$$NĎwŢXbkYŞ{ďçŢMŢQ6»?[Żj/D/šoţł. ĘÖDâ‘ ¨LJ Őâôk†˙�8d±0 9¨p56Ó bZ_ľ±ęH#uŻżxyX•<ž ŁeUJűČÎĎ^»â÷睬–k_®<)¬˘•Á;öŕo=äß7HÍfĘŰ�±fIL\<°şłbđĚ×µóJźÝâ×–˝\RNżX‚!Ĺ u­oÇż8 ‹]/ěLbň#|ĺžć˘JD l]±@B.ó¶Ź—^u‹!»âpŮ�Ţۨ&‘ä&0„uÁ®PhA·ăŰń‚#שuÖ�a’xťÝňőë^ÜK@ţďŻ<ŕV9íŻżźÇĂžŮ^thó®ŕbź8J*şd&ÄxLthv?Ĺ(5QלM4Q% ”Z{yßŐJ�çg^}aFő*ÓSź®5óď6±O0guă~ń·Qx|'¨ź:ĆŘhú|eő‡€ĐôAfük"ËQş(ÝĆ»={ɬECO`;+ž¤˙�¬Ţ5¨dň©Ď÷®ZŚF4»çąýđ‚ď~ ÂÖŽň7·<ivâ{†ŰřŔÝůgä8ŮäS�»syç÷Á ŹSŢI7sůÖ1€h &µż÷šÖp¸“aŞ´Uz÷®ék�ˇ5HWe»×±°Ů5v+{Ú·0*Q®¶:‰Ń”@ĂjZu0訝€^čŇa†¶®ďž˙�ÜKJ7KĆ˝žpGdĺ+˙�ł*‰PőŤ@ŔSwŤ~5ń’OP8žR·´Ö":ěF ˇB ¤v(A9›|ĺ…•%ťřď"t!Á%mârwůdJ"d»ýzOgrBbQm[Ő]XúçO"z#BXň}áŐAĹ磮LŁWľ8ÂëBłQ˙�sŽl©ĽłIAă@×Ć J4ëůŢ9g@ÔćsŚÖ1ŻôyÖ i˝yŕÓ'.zwűdř÷šđŰŚťcdĽśĆŰpÁV7Ćť ˛VěáĂúbńń)‡žO&4Ş´ßé‘Có%' Żľń*vWt¸tp˛-4ééüf‡ÓMóéßßźy˛H¤.žY¶í*EÄmEČš&˛G Şđ 6yNôć冝!Nü`$˝oW瀧=îk#đ tăh_¸m.¸” ň0y¸üČMĐ4ĎVG©‚EmŘaˇ/{߸Z$€ť;ţúĘ7Íhň¶čt{牜EŽkÎÝŹśh´#ŕyôě:ĂW(m ~˙�^É©Ś8ÎťqŹ~±żnHhGwśNĚŚ•püŻ×Oă![g~1†“Ćq@i‡ˇĂťP3ăç8 ŮAń†6b DĽŢń-JĽŻ{ÂUćoů/ŕÂĽ7>&ÔËAµ~áGôaPiäu‚Zö!áî?ÇĆo¬ňąC`h‘"ýÜSGBŐAÂz ‘dj ?ô6%M”a"ď/źH˙�ł*zČpĎű¬.ÓĚ)ť '“& ü\(xłhu…` ¤D\¬S˝ącˇŇ�5ęZk"…ľaÓxpé§Q®Ž-Ö˘H®őĎzńëC[TőĆëóűá%„{«ůĆt°Â´”Ó8ç4˘ä—žrk)|°Ľw¬Đůď-§Łń‡2ĹM`mműaËnľ<ĄpÂ:;b�őýë*hXţŮŕś=ăşžąŔĐĄŢH[ Îf˛ ˝üfěN){Ä­[^^ňÁFe ÷řÍŚ©§‘äű5îÇynÂU¬ ýßcŘ,{ýqM(^Mžs‚7u H×q×'Ô%ć%ä¬-G’ä#+ER݆šČqÖPŘŻĚ l§oěƉ›Ő×߼ŻVÖ šD‡Ŕ•?& ÎŘV×§@ëűŃf nŘ{>ďéŠIÂ4@›úóďÑ߫ /Çťä+Ňöîýýer® Ľź\áRęsůËĽ5áť·»ŚŐv®şÖpoŚđćů<,—[·`îkÇŹ&RÝľ2qĂżxŰčwÎUŃĺŢ;Ř5çéϧ>>{ĎŐg‘câdŠ‚8Ŕ_Ć5€ '{NÓŰF �]}k ď®á gdă W8  Ň{ßx›ě–Î5HŢÍ‚Ң(”uo;ˇNŹ8)-&[ai=Š  µ}žźÉ$”ľfŽ|ţrs–éSCĘoĄ‡"ş¬b§d Bëŕa`ŔsđÇĽ2' QP\ŢçO…ůŁŞđ");äđĺe1G‚áŇAá<™dz®pp¦ĚHędĐJ˙�çó»‰ŘCŐç­ăeÎcP ó€[JI|)Ď\âKŢ-—{ď�ŁT“xJ+0IjĂ ;ZŇ5ČŇőß“śBÜľţO÷ś)]yäÎEyc†ĽáÇyWŢ|95łk‘űŢ ”Qé2qFú1ďY¤Wë%‹÷ÉŁĆç8VÜa@±D+`[¸‡‰Ąxq€@pĘ«lę·I"[°]˘‚ĘŮş=oX4ŐTH`jˇd)ÖK¶µŤď›ď ¤h6{ÂKťçM›)(r‘–ť,°QçÓÖM°HiÝ ÓZŢj¨H+Ŕůţzrě(Ű+¤’Nö{ż3ëČ”Ľ!5´ęb"[v—ăűăBFÔo°Ý?ç;Ŕ‚&T©łń—˘ŘhÄTß®q5ĚŢŻś8q+ÖG?®&śśL.Ŕ4ż9µkĽ©ˇ§˛őügNÝ\8`s·śi±ă¬ŃvëĂ&Ü2ř/›ßYJÚy˙�·y°…{~�1jĹ·ă[Ç|: ™¤Şąq[k5ÇóĆ #ĐjŠôřlNˇ-ŤÍ3ëŘZOW7Ś1Ak’{ -!�cxNąýŻÄmĂ;U-šďinBÂ<»˘iĺ§T¬ yĽ†Ýßß ˛śę‰8ѤŢ5ľKż” µę>f 3KrÝöEŢ‚‰§kîqŢţĚ€‰Ć řôĆuíŕ5˘ë–ĆÇ–X‚©87Ť‚Ń-ŮŽˇ®@\xU8_>®W–I9ďE-ßÖ�Tx2É4‰×¬]•µŢuőś72V'Žr˙�8őuúqţpy)±Ö ŃË|8rÖ/ń‘Wé„»żLRířgtůë Uçeȉłë(hŁĐţěákÎY9Â)zAŘÁ Ň Fí°*Ĺ%K"¤^5–±gc±ĹBbP‚$#p¨Pr‘ś·“ vÁ‚ť«ŔyqŻe´U9+,¸y4„0ŽŔšÎ=!@’}KŇaI9¨ă®n ýuŕR»žLUAU/–ţŢĚ“KT ±šň…çHÉ*C•Ąň¦î±ÍůŔŃ€8˝›‚¦=bŕ„š×“x‚|?ś[5•ÓTÜŔ�ŁC5€ŢśÎgĽQMšsđ Ä˝ë¬o&ÔŞľÖ«‰vĚdú›Ŕ J8<ś|etzĂÉßő×ó y0…TĺĂIg,Řháś9qăsLˇ‚R=kYĎšĘQ®ňHŻ…Űăëśauü0�]«]ë ˇĚ0ÂôŁ`pŠ@ĘÇüMíTl Đ™˛q‹Bš·Q%~DäŹŇá+8‹đť¬řQďă“6=° ZC—”BÂÖ°ę<ŁNsY„ŕ3aĄą»Ü»úÚ¤IŁ}ÁŁB °”ٰ S“!'dîŔĄ˛jU¸§VG(l Ě®ß!…¬áĹ]˝ľÓ"8…OZ×;wöŃŽ­´ŠíZňúKňŹŹę릤J‡ćŤë‡-t‘ZîĹ4@ěŐŽą;ůĆK7ťś›Ľ€iÝ űŹZŢ™âFÂťÎpÖ $RëTćúĂâŇ*oznG@)/Yo´O¶ęÉณB»Oą°UčÄĹĽa“ä0sĂĽëQ˙�¬·IĆ*ră/…uăů~™˝c×8h&j çÎ6I«ă�‚âČĽĽą¨„×Ţ€†řß8‡ZŠi‘r—_nʤ3KsË‘c 0IUłł,ĆĄXś ʵÉUÓl÷¸‚K&hu€C€’´k8îľö1CĘ^Ă‘ş]kŻľęăm+®gŻędcŮň'€Ň"ă:U~ˇ€;‡mU†!MdIÓ%‰ 'ЦÚ"˘ @PŢ0ŽĹE…rŁl]*ŘĐ}3#7_ ;Ö9 ŃZ OXĚćCˇ EŹ·Îm“ŕ0ř(vt‹}3‰�°B•É,ZwĄŃó?śkŻ4"jŽĐR/ŢÔ4 /¦Ť›Ă@‡şëÓĽ)ô¦ź<?Ţój›8=a6ä©A˘~âř©/fí,&€0Ž‹Co7 bf"PŢ]@V ř‡ŔÝ ţڇ#%Ňś9Őţ—*„ö őë¦úf®–áHʦ]ŤşČűd-Mł@tmß<ŕ€Ą"¦x%#«P!Ćň/ęúc©ÓOĘ–VŁxXi9#ŐĐšÜë#Ą»çä×ŢZSUč=pY¨—€ ő٬Ӡ6JzńĽ6Ł‚NÎ{=řČôkEÚ ®‰Ęńˆ)âzÇ0•Cwôřţď3zŤŇ!·P6–®3¨,Q>Ó˙�0˝…kcŞBúç|aÄ]–âm­‚‚CătDŰ÷¤)Çz 4"-eé´`ą˘ sş�Ő=üOÂ?|6¬×xb@h‘ÝŽ±î¤ ¬ÜXčx*ŞîŕšŰE(ˇb'Ň>óv1`păBBĐÄ»űmŞÄXVŤN5RŰ»f*Ű‚ ć ÷Nľ€2Péś“SńÓPË TA¸ljÍPT±ČYF­ô¦!Âŕ´ëžpiĽ­#sńŠú;ĸÝZkÉÎă}=etüxş{ĆÓŚŔŠ:]÷¬, M$gۡňçzŻuÄ(ôĆó›fFçY"b>�ëBLŁj¨oIĆŹš†˘§júvş=ąu‹w7űsdOC8ůĘ*^Aľ¬ăF¸ÇۙȖőçŚJtm§3_ŻY¦*m ő¬Ŕr§Ď}Ţsa„Á¶…,9 ‹ß! »ĄâT]~ęîĐŻó Ž*Â{ZqçŚVn„˝Żó¸>řNb®\jcpŠĐóŇŽSF(D Jś†,05^ą {ś÷Ćhęz†ŮȾߦQ°äNżŮ㊅DÖýţŮ{Mb€ŕ Tüŕ4(WRN"Ç :°XČ!Ő ËÜX1şÖ:T4A˝/ÜqRÔÁŻ>ËÎîQ{"�†86evNZ<pýň®¦pÝăV‰Ë]¸e 冦úS„řu&Y •<¤’«uvó] «cš4]xrÎé *Öq»ZÉŇÓ•äöÉ&Ó?I¨`TvŞ0BůNű˝«KOŤóŹăçŞ+´4÷ż@Žů58ľl-'ŰŚDżÔúă%OŠŹA<d$ň, ő…ntfŰ?źśUt®G¨c�*pŽÉGi˛žJ `¨ú;BóŢ$R1<ŕZ5 o[8Ęěi¶’żŤá\[@ź;Tލą¦[ĺ�"� mä$ĐŰXôĆ1Xôš ó$–©`Ý?«ůrÎe¤ćahqÚ‡íĹĂa\´m’roŚ] ?®›Rű‡fŽśł_­ĚěűłÁ !‚té3ŔgŁ|˝ó‚Cń‡ Űš)8s[ă‡ď“Đ}f§ł…ą¬eC¤28ÄĽ—,'ńĽ§äŐ?›ńĄ/M¶u…Ť]jë”ďÓŮĆđT€'Ł2§iĎ^xÓExQ_ĹG¸:]ŕÝ’ż!đe¶%Řt×f]ť“dý}u€;٢őĂŠ ňńÇśŐh®ţśńďśV×qŮËŢk“`‚(ňl'–AuOüĘ.-Cłs]8 ŢŃť9üŕ!fčC…ń˙�3w % �<żŢňÚ‚Ŕ ŕ€Ăç–ľEíÂ[[–_»Ťk~58Q=|%¨Ř~tG·ĹĆ’Ç9*/O1Éf¦ÚJ.5› ŰE;űnŠ—łčhYßYi dśŞlRh%H8Ń‚“ăsőĆŁÂP‡ŞC\ą±ěl¦łšćËłv5Bś˛µÇ,C|pw—nĎÎ-KĽ]íŃ‹¶8]Ť>™»»ńf 7>q ü`–#\bzjb˘*őšŁF<łí}&]Ń6Ax+·.�Ĺ·Ň Ú¤îŹżŁ„+H’NÖšÍŔŽŐ5ĺóýďĘđţ?÷(]µN¸Ŕűá ˙�yýł‚ŠůöÎ<ä\Ŕ4A Ö!­yąM{#űw÷ť¤ÎgţzÍv]đ;Â%‘”ĺó†ůgŢJŽ‘üąÚČHĄC˙�Áß5ăMľë2Őş¤}Âöěňo&«€Â.ę»tZö “Śuó+Ę3ĐáÂnb+Á©čcÇs)QôëĽ^ÓҢ$"CXë@Mŕ„D¨Ş…ĺVąĚ#t-6XµÖ§xŚqg{Űç®\1-ÝČ©§8L/{˙�ĚQŹ.pŕvÎŽ j»O‚âzzÇh×ݡ†W‰›ŇžłAĆűřŔ˘ĹŚ6w‚Śţ^0ú‡ńšH‚ꑦż8ĺĚ9cŤ·ľÇśmĹ­ŇubšříňáÇr‰ZčŠíđŻ<ăó¦µĚůÄĐG™ůç­á°(Rüy㯜JŐ(÷ăôý1Ev]oüÖzJ�›D×ăsťôkś ţzË4ŢNď DŐŐŕ•Đ©˝Ž—˘˙�gŚ7&H© FÁ|.ť C­9;8GăŚM?|Iä"Ľúë{3lČ«ů%%ĽŮrQ'C´±ŹG‹yĘ-ß‘ÄWő»BAFđTÝO ?9ĆHÁbv)ŮƦăřĄJ2Ş Ńß3¨¶*8¶&Í-ŢŚtZ ť‰×sĽĺL…u­ţ&*ť@nš{;ĹYĂECL"a;R"Ą˝rţqH·�+ŽűĘ.ĽjŕÚĺC yH:Ć×_–phăÎ^ţÝ7çqĚ>Ü‚Ů%/ęőÄ÷š�Ĺő5çó…µęµŞo}ýbvŔWč8ńÇśSĂnQ‰Ô‰Ęăšě@N…sí^^2c9H¤UرˇĚŇ‚×÷ďó—DňOŘČ*M|ő÷ťíł®áůýî$#˲ě´ś5Śn'‡řůňŔëĆhv›ËZUvéţ1Ŕa ŤSöżŰ1Q®f×ăŹé€ "ó˙�¸Ťś‚¸Űľ+ĂŽŁŤ;<CĚÄl‰€®ÍoŻZ–™09śl×L|cKK-üv3ÄŽµ7´’8ëVŁkß6÷˙�Ąí0ę»ŕę uĽ”éT?Řć«ä+·Gď~±f‹FÇnůë ŮF±ô‚…é?Ź(ZFîrmçxÝ.˛$ť±Ł ÄŢźß 5LvÓŻÎ#lü±{ňĹSHÖÓÍvf…łr‚Ζ9¦Î˛Q¬ŇEM^Îts”`Ôo‘üóýń@Eűň"~xp±h.B˛W×—,„´ ˝~Črz޵ó€čzş*˘ďťť&2PGCRc­Ľ4_¶n"Ö¤W®(E«#Ĺ{Čú<‡^źŽ3b'OŹŻŚWGXŽöÖéŇ =é?ÔB¦‘ňđMsçSŚ®˘vXqĎyŻĐ´Ó‹ëÁřĹ=Q”pmâD­CČQAŤÖďTńńŤ¤5i«®ăĺÄHťŃ©ĹůÝóś4@'ł{éŽ śśřçvřŕshTěŮż8GI ˘ŃĘgo|¤RED ¦”6 ˝ąGDㇻʠ<,7‘EěÓg·ĺÄžĂŢ /ĄÄ đk#Đ8?‘™Âháu\€‚Ŕחń€šâôç–8­#Dś…'[]őç7@›Ř=~˝Wś´áRŹwEíŹóƉŕ |Áľ<a rŔľuy3ˇŇD´jmçŽ&–E ΞO8…íŔ«J™(—ČaŠâĐĐMuĄů<äú5ˇď ˇĆCÁeoŔi ç9' –ő­ľ<~řdÖ˛G!úÓĎsź5Ç…**¤ŞĆQ8vŚ ä“xrT´jŽô3ĎšÎEoO:ŐﮝäCĎ'1şżżxî" µVOĎGůŚńřÄ+b&áŞl�‰łĹHAł—űý1§E*5ŻI†ťš±;ŕžLŘl85"üksS*©iTďhJ'+uęáŁ{Íá!R—žĹÄÖĘđ©VŢôĘOj@PnŠ|LczŕĐ9kE÷ĂŚ$©AăŽČ]r†;ä‰*µŃŔso"ŢrťRˇ˛u¦ŹĆ5PJ[Ť˙�ëŻz_ç9ÁćW«ęóă�đ?yGx…t}°vňÎÇ©ľD8ůđxÍ`ZI­y”ćóqŹQ&É'“‹÷ühvŮ 6oŹî°a4ĎÓW Y°µBňŐt Á18řŇYŘÁ˘V¶!<@ř$^*w€‚*ŽŁ´óŰšĂPU ‹ \ţsG˛ř|u—ëR†—éçŹÎY“ˇÓ*ş8ßr×âĽoJ(šó‰"!˘xq6śh˝[xűŕ>!âŇ \pđčÂăŮ­{ž0Ř®Ş‘ăUÖůţĐ›QQÎŻ8FWĘőzĘ[¨Ł}|b°x.ß8H€^Ľuůń‰Aăl=ţřó|+úuĽ­H$îÄľ?łărËi�ď}+«ăĎŚť(F„äoLďŚE­Ě1Řj–4»Ż –WZ©`UM/d6ĄëQEA/Ł yWă ʶÄ'ę|{Ćű%3iŮ}Ą‡{ç�čĚ€¤zŔ6ůńś ďĎ8…uąç;uůd^?,a—( ÔUÚXìş]^9}ýk‹elž&î=ë,oë…†›_/Î=ŠÓdXđ!łĚ{«€5š;kUŕęť×Yžt°ö夂1ى-­;~~®$vM ÖÍďbŚu˛, ÚŃ/żŚN DÜ ’4×OCuEĺkË/°Ę0s;Č.=ö>˝ă! IE'•6›\BZľnF¦Ôű Â‡Ô||໪µ2;´Ë5¤lUö<ž’áLĎh$m)´|Ěr†,—¤Ő˘�â8Ó2T#júeŤ+Öę‘vő‡‡PRś–ŚŰŠŁ)‚5řÂ: DIXG‚^ŔâńÜ A)ÓăűÎ4x¸QYĘ u„Ď•ó‹5)(Q˝@[pŇMN �U\ ÝŤÝdťK ˛îß?‰Ö ÷ť´ŕ%ÓÉŢ.oś"qÇ˝ncÍ…öÉFż,j…]ˇÖĂ㏎růźyŃ�µ€Uq6ř(ÍŘŇUtFiÇL¸6NrRŤŁE7Ó‡ńd1Ęß-µ;ś�Wg*A˘|3±gKß#;¸ţWd-V2áˇPÔ ,đ}ë%Źüńý¦Y%Ů}5„1ă†ćňŃ€÷Ś$UbĽN‚ű¦ç~'1Fj Ę>FbćĄh)şЉIMÇĂPî.éćY§$@Ż×\Ţ}—BĆRŕ+ŞÓgś©VđŮ9ÇŁtMüě™ku¦Yé`‹ˇa$r°{6+b#ƨµZWĐyjp0Sd+h»¸ľ˝cŽźFt$PH]Ň(€d{ĹŞqC˛:É@Uc'‰ÉĹóâ<šă+‰śQéµĐWČe€€˘=9¨’Q ¤•wŘ„` ÷wżlJşy7ťąç*W#ÎnľF<űbl¤Ňs–š˙�AŘ V-€* ©— dÝý`Ҥj Źß?÷Ćâď›xĆŢŚŚ±^ľ3”mĆËĐőąŤÎ+FŽ\+¦ÄN÷¬]ŔPěű–^Ö:śŔö >ăĆX©Aş’\ÖË·ă ·—ČÖKĹ5ŻĄü™iĄIhÔĎ; ˘‡·±cA °lĄęf¸>.˘ëF×�Ľć¸Ś±]éMŹ?FÚ€pډ¦’`˝›KhŰę)÷“†ËÄ®ńNB„{ˇ¤ŮŘÍ:t2v#±'&đŽy;đ†´›Â5';Îqâ7ćâxŇŽÂŤ5äOn �V¦('1ŁŇfXJ+Â1~pm««Ţ*sńúd˛jĂuŚK4HJo`Ĺvş]cńx}äkOeąđscÓÎ=0]ŕüap-5ÖËă Ě(FO! P:6¸€nÝ;®ëĎĎ®p‚ŠŢ‹űäb.+§Á7„Ö€rmŢŻňUyK*š—őČZ^‡SšüfÄ ��Q‹¸o¬ÂeÓ`i 2lťµIqQŰU@‘a)Y°Ý4űÉ5Ýë]â””Çč8rßXP¬z·@�V�+ło@>ÂŻP ËuĽÝČ‹»Ź®úŔîT°Uď\\Ýc†ŕ*U/8 €Ž<§Ó”%»ĺÓł…3DŮŹ QČ�+Sn±ą¸4)j襝AiÝ]ă1@ŘCÍb=“ ‹vOEH@ěvĽy0·ŔŇAĄ@›Ť�ťârnŹ’MĂ2´@E’®H;PnM*‚^t,(QÖ‘ÍdKDĂ @®Ě\If)ŕűŕ5q�Á]¸_ˇť6ňůĂŮĂŢúdµŢp)ÖńÖviă:sơĺ^Ďży--‹ü[WŰ[źŹé†4v6%´^<’Ş`! p űÝ~5ŠŃ>—OÔý>ćN˝@8YŻžż9Č@Ş (ź=ý`´j…MSU®o sĆ#šwçĆnŠ©R?·ŹX„T$čWÖ¸ď×"µ ú)ĺŮđëˇ]#fĽž± sůŃ HÎŻď‹ ś˘‹ÍXô“·Žrb$BË]µÓËď$}gđĆ:KÄÄ_áĹýë k® ]·.#ä€ď*Ö6GDŁĹ»ţ4d T�Uţ˙�ć˛mć>@ŐOŕ Kx »ĆŇ&T‰÷ŤděĐ­† ó.ń$HŔГϖLőeÚ5W1v\'+Ś‚r"aľqśňz kXEăzîź—Ww†(h/P’Ţp<źĎ#{čÎŰďĆ<RńĽ-ďŚ4ÍđÄZrş+˘ş<Caň‡x˛lşSÓĄ‹«§cźýŻG€<6ÝátňOâ9Ü�K®ůśĽáSČ řţ˙�n2…0E=b<BÎ2 hČĐ诌*†é ÚëíÖ^ÁtďŢ6ş·‘޲˘@gŽś'đ‘uBfÄ 0”sQ{€yvĄ„Č=đ „×Á­äĹĘŤpZqN„o#ŠćLxň¦·ăNŮŹ˝ć­ŇäňIiÉňŹŃ–aE:ŘŹóŁ60Uj^4Ö3źż¬Đ*ř(ńgT9ńďŃ{+_‡ţůÁ°q0oŕ=©»šđfŽ<řČ®ĹúħŢ;pä‡ÜďĽîy/ňď�‘>j›ľ?\‚l©˘^I8˙�ĚbR÷—鯎rĹß~r.Ś#UĹ)·¦Ţ˝P—lj_>đÍ2k*Ú�Ug8*h-um‡/ŁW¨îtř›ú÷†ĺá§Hřţď äzßÉrj%’Í{Çj‚Ç@;(g {ÖćĄ Ô×ëk˘mI¨YşF~ŘJĂĹʶDBŁ]{˙�pD GĄ915-śš Frß<yŽn)&Č„Šo…ďö�™ĐN: EOĘ®'Z!¦í~Ž\Jť†­8óśľ7—XÍ8éľ0uWďPP3S||ů!ŔňN˙�Vź‹Ž¨ĘÓ·—ö2XŘjÍźŰúaë‘„Aťä6Φmˇ#ˇŘ«%ó}ćýłkÖ5â AŇ#Čř÷Ź:JKĘź‹x”‚Öľ×űĽL­Čh×'ôÎSś=±°ďĆ&ös‹É5řńľűg#ž<cM�«®}OíĹÍ©›6îwK·¸6ĂÓĹź“üĆIТGť…Őă�@Źí€° QM6Ć0ĄCÎ;”�ô�·—Ë©ýľyüă-°Aěq÷¬K MhřďďýĆ!mŰ'ăôÍ <Ř-×-“$śl¤J§(_\Wó‹7ĺ-QůŤÖkP�Őš©đLŔ;p«ĺg-'ZqÖ/&Ă"W¶·Öˇ´ŐÇc:Çë#ë7g]áÚ»H"‡ŢČ<ŕ´…É{?O›®ók°3˝U1ŕm éŻüńÁ›9۱«ľsRĐktX,Ôl6E‡Q h.ö&_42 @ Ü·´ő‚"Ž\WăoÂvy9áŽńĚ`ŻŘţžp¨nó…”Lü]aÝ{Ĺä<”Îôňgf9ëĆ7DN„"#ČńŹ2 1bŢhč<a=łiż 3KŢ °í`sĎčý1.qjRTžž şx|äiÓY�‡‡5ߎň¶)Ż.3ž Ţ’čeś<2’Čdŕ-źlŢ’ +¬íZŠŹ ď–&Żź!‚Íäŕü°ôčÄ�Đ��qś™Ć8ĹxĆÖŢL4ç�75R áL{Đ™B‡ÍŢmęŹ‹Ś¬U”ąda†Éśŕl~�ôfáŘŽóÎnÜ4mĆLvá†6ĺä Oś ~;Řć4k8}g_ń•ľ:Ĺ.»Ë¤ÁlŮÇŚ9ă K;Ęy/s—¬g)Ď‹‡Ł ˘@nö´¸ÔbßăT€8Ř0 KW”™] ^ušhó!= —l#ĽVRëš t'±ş\¨W”†D˛şŚ©®×b�ŽĹÔxÍ®9 ŕü¨îęÍhÓĎł5b)VÔ]}pĚ«dąÂiú¤Ťő‰J#U˘ěn× Îy C¨ĚYă… ě¸č`É9°ŻP4¶Á7p,JüŰĘAm‹‹T|�ą˘yE@!ŃÍ7‚ŚM— R -ž1É/(9qCŘ]҆q' ¸ŹVƲi„}dÁŻ’l‘ZĘÂÍq”5PĂ�@uRŘZű¤-hažŁ!: ďBúČaUŹ9=;KyžC»Đf)¦Ë]pĚŤęŞŇ욥çjćÓýąěĐxSMTU|Śňv]g|Ů6rŕşă–±hńÁŠV‡š}1;G2´FŘ5xĹĆ1yśąľ®|a3"-ž‡eăĺĂet‘):6řŃsşŮM®řwŻ9Kňé¤N¶>gĽŇ7Kůr™¨i‚”I=9*>zV.ć îó`Öűď>°őŮŽ›>t8ű €Ţ°Ö´ř=ܰśÉK9e*Ź Ôä-ŕ!ŠŁ@C`e9ć<ÍţRjˇT+ L[h!¶á•°ă)!4 PA¨`=b-]�ů'y»RÇ{E”J†ăzĆî# ] Xö\¦4˛‘V’üw‘v[ŰAŰÁ*nW ^ č‘—lp˝E´¤Śü IZ*ʂ̔˝"Şs^­LȺ蜼áÉY\ôIIb–@—żżÓŃŰ ¨'·Ú2°(ŇĹşă…Tp©ŕ2Ć©č4kĘ;ęH¬ˇ9aFřë,zÂŮN¸ćÄ%R˝biđbiĆď*ś}0t÷q—†‹Ť¦·qŢ/XéˇËösÔ` k·ďś A¨týóZŃŤ#Łüá̆�Ťł€ČÖBéŰ4q;ţr'N@‡Ŕ®¦ÇĂĺĂž°ięNvňwśąLĄywÁ^q°˝śŻ~0«»¦wř[„Ó}¸~× ĘÖ‡ŚňřĂŞ‡.0Z4±‘Ń×ď‘x?˙Ä�7�������!1AQqa±đ"23‘ˇ#4ÁŃá $B˙Ú�?�AąCDÝUµ­ ´KÜâ#\•>íCӑ׹7†0S$ďËcŢB±´mÎ,Dä‡ g´sq€?_˛ĄĂÚú®a$4F±żŮR°¤ěaî ¶g%qlÚt™U†qOŘ«ŽĘ4ńÉ9NŃç*ď‡6…,m$÷íëŻdvk8UĹF®zŻô÷"rűˇÁ®cOş"ç—ÝRá÷”鹀wz§i|0�ą¦|ů¦đ~&X¦Â3׿Ĺ[Źq;|XXÓ<Ďö‡â˛Ics”ţ›Šµ¤Sc[<NŕśPąîŔßxAĎűLŕśM´…#I¤ ÔŤüU^ Äę¶IĽ¦DůŞĽŠŐaf‰ÖĎîżńN%?ú…[üzúŹĆĐ<B˙�StNCîNuMĄÎđ·G¶Ô~C:$sM‚$n¨0bŰš´®ÇŔâHćĄL©RĄJ őLKĚu\JŁ*U÷3űúđMŽJďä?ˇňD˘Nݶź!ť’(äĄî9*`Ť‰ Ś•­ý*l3żŻăÍSľmWá¦'że‰±f±,JżŁEĹ„ćź~űÚ@˝ ,Nä‚»lţ‡ÉlŠŽËOÎɉÇD ơRÂčv‰”ź ÷i樴Sn!,JŻkŞg¨AúňU8ť@Ń„gë’¸sßW·Žš«ŕČ)Úd©ČUîvďč|»4ěÉZ|†tH Ř2‡zöMÔ Č*ŐěÁ Ů®®  0T"aç)­5 !žĐť0Ż?Nţ…Pě´?Î˰"2„[ˇX‚·Şg eë»÷XĆČT•wR]@§)OôLnp˘vWŁ˙�^§BŽŞT¬Ő§ČgAĺŘ4í *@¶JĆ«]˛›yžI°€ňťîĽ¸?˛Ŕv(˙�›ďÓ?ˇGTOd+_Î˰v4ÉXµR{`ÂŞó5*0č‚{I{Hďýtdó}úgô(„uí´ů č<»iÜSŞHiŃŕ'…hÜĹ)ŤÎI”Á%RşcrŞuEX*!˙�˙�¦CŘňhöZ|†t]‚eLy¨Ń™Wüá#ŻÝU~?y…š€ű±;ŞaÔ˝Ů$÷ý<»ţÔ5 4™Đ÷&2¤�>ľľą«*uZ}đIýÓAŁ2…ä»ýp0{./hۨa]ÜS©lü'cű˘‚ŽËOÎÉ=íjShĹ ŇĄrŮŞ ĎŻáT¶§XI ¶ţĹZ*t Ě*µIŕ»)?¶ţµUi8ŚmČ«zŚî»=4ęťsJÚž*Źń^ŐĐ DŽsý&şs)­Ćęµ­:ŔŤ:úŮQ»‰uł˛ŚĆň=VÔĽ‚NťË[Ôm[ ôőÜ;Űh!ť’} Ż`⩲LtőőM­¨J.F+< Î|Qn ­»8VÖĺć|*čÓÄś“.ß9óő껬><U;Ç“l}Ő;f=řÝ’®ÁIĺ’Ż˙�Lţ…NIňˇh?!ť—cž‰{Ä‘Ýäl'ćłD«ě„ťP!] ¤Bppr^Â1 ďßרTj:ˇŽúëüY&5Ús\fî˝;·¶F^A\ńLtŤ8‚D(É=‚µ®öŇlňHU|Jk‰Rnâa0‡Ž![čUa-”ÇAÍ5Ŕ‰ Ú2 !¦cÍ:šsJ·h§TŞ^Ň´-uS@Uz”îëŚrľµü§;#Ě'¨ě·¦]I™l<dlš!T¤j¸ż Lntl‰G9RÝő¨mqi;ŤvEîhĂ*TćsF“ZIhŐ�śÂSXCćł§pÖą˙�ü™Q˘ćć5U®*kÄĺâ¶Oě•k ˘ŢÉ^ń )ÁäÔsśr�ů« OgŠŁţ'}»•W m;zý•ĹË«Ő/•DąĎ 5Mˇ­�*Ě[ˇU(TĄ“ĎÝXc§Z$S¶+{jĆ×*ŤĹG7r·­í@;˘Đń ôDĂ—¤áEġśŹe~ ÷Sm&h�M&`n¸u:t[„EÄLf�F hÉq+üTÝGŁtÖ°¦]pÔĂ�]řšp*öłŞáĎáÝRâ ¦}đrWĽD†µ´Äá—ŽźÜŞ´EZx^5UhÔ č •c�ł'wŞWę1¤ĎzĽľ©íŮlÍN~…}úwĎ$tN(•=ś2ĎŰż´[¬ŐG†4“˛¸®kĽ¸î™™Ě®Ŕjr~Ši*$§‚2 ĚĹ‘\00#Ţďu@TlĆaV &Věď4îÍQ4Ş»ÚďDJâ�~ý§vÚZşć¶ĺĚň ť6ŇhkFAl¸µÉc0s>K2éL&s\!¸ißä?´$ y«†–¸˘ ‘ ÖŻ˛Ş éŘÝzvN{#’ˇri89şn®Ş6ĄŁÜ9-“»!5îoÂHT1Ő™¨DgşeŤÉ1Á9ďý Ą»ÍaNI'śŹ5VÓŮI'CĽˇĂdś.ŤŽú&ŘÖ€˙�1©ŹA6ŇŕĽ3çs´gă9*T*Tó3$ĎmUzn¤ě$ÉńhpęËIĐNç»nôîU­Ä\7籍UK[†»�y'–yÇ)ŐRŁZˇpmMď?©mXüĆq'N|ż¤ëŤ0÷FSżHëš«MÔź€”đ•˛pXQä©Ô,qľŠťűÚ\`gבů&\–<ą€ ęU[ÓU°öőîű—ăIʼn É}†[¦ßÔiĐmá/öU@GŮ6őÁŔŔßžúî›V*†Ś¶Í!R \őÝ ÜJ«†`N|÷ń„ë§:§´ �çĎFâĄînAúOň…ăŃ:o§-tŰśnżP´Š€8sĽä«=ŐŤË Đ'/˙Ä�.������!1A2Qđ"q a‘ˇ±ÁŃ˙Ú�?�|Đą6ÓI%6™Ü։˦Žî‡]$9=+ÉŰi‘Čۢ·\ďťňˇäŠt,±hř$LężÜyđŢ丬R÷†ĽźQ‡Ë‡D}F&îظŚ)ݱq8S»>łą#¶gv(Y ôE rDýLNŤŃZ É= ‘kZţ|ăóúäČĆRua‹Ś~áęCÔ¨erDýLBLŃ!żaĆ̸dݢPqVţoÉ;Ďë•|ţL|4ňj¶'Ă®'LeŻú$““Ôó©ŤÜĹż(“ő1n$–ăB˝ŤR´K5-Q99;bĎč†'%gӫ܇›w±ÂcŠŤ§˛č˙�ŃéîşwfďRJŮŹI!dIú‹´xĐRd]™Ł$ő+–u=v6ŘE+ZMî7Gä‹ű÷Śž¦SBbŘRR"˝ĚĐňPŁóř8hihkÁKROA»ceÇęCĂ“ÖĹÉvM'°Łóř1âs~ƉÜPľč6ý×ý:–Ă•ţś^´2†#'­•É/aŞ[ %«$­‘ŤďónOOÉ”ř˙�ŁŤę7úqzĐůŁ'©ňDđO¶5Ö’Bű™Đčz!¦ŇdáŐ:F\RÄź’ÍŠUÉ# űĐĹż$dő3ŘHsÉ(t·ˇŠ7~%ŤTŃśrNZĄăoĎăňg–Ť<pé_›1ÁĹ>ŞĄŻçö;\­čqyq¸ý­˛MxKZźIżެkS “2¸ †ž6ś†-ů"~¦)$cÇiF^ #Ób9%‰Đň,Ť7ą<–¨Á…ĺM/jpnŃ“˝­?«Đ‡ <ňĽpÓçůŢ®‰B•"R–—ŕĂÄäÂŰ‹2p˛xŻ]÷!Ä¨ŞŻě\N9AĆŞ˙�ćź5c$MjČJĄŐÉ 4ŰÓ˙�„¸Ś‚ŠůcÇÖúž‚‚HH†yakC»Üm˛ZŇĚąűjâé_T©“ĂąŮĆôlź´Ĺ›"ÇÚ˝ą‹Ö‡ÍÝť>ć<V4¨Ó˛;śT´Q0y?›“ł‡ŇBŘxÔ˝:"ŁęEŇ0c„˘­Ó>šľë4<1{¨ů)"y\ĄŁĆ"ÔGmŁ©Q%{ =GŽxÚęUzŠ^â“oBqY1IżŮ|ţ XžM"$ńčŃ žÍuäîY9T[1Ë·F}JŘżbŇF>#ĘSŹR­…Ę]~ţ FGhyç‘%7u˘…%ärTp\KÂĄďßÇă÷Ô´cÇNĐĆů"NŮ®¦GöŇňfËŞ;˝MíTAܨČú§¨®x¶ˇÉJ$]–KFHLë’ňcĘžŹ“ĺe-Č®¦8Íľ¦©/qcęZ‘ĂŇú쓳¬śnMˇAÁ;$ŇzĹÜtť~LMąlžŻÇĎaµzLD±´ŢšáÓ‡[z 5%ÉóŤ·C} ÷ÂŤŁČö#j#Ź’Oíb,îIÁ+ĐcÔ„Üv%ÄNQč“Đr’ŹJzzŹĐźLmŽ]Nب‚´$4¨Ý"ұr‡·&ISСŇH{ŹoŃtĹ‘P§öŘĄgpîŻaä[ĐçWˇšł­$´Uě,‘jčsJ´:—±Ö˝…ORąPÖ… 6΄8Z§¨ˇOFt{1ăGj6vŐUť:Sgin°ĄäXéU’Ƥő;^,ě­Đ˘’˘Š(˙Ů�����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������beetbox-beets-01f1faf/test/rsrc/abbey-similar.jpg���������������������������������������������������0000664�0000000�0000000�00000243337�14723254774�0022135�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������˙Ř˙ŕ�JFIF��H�H��˙Ű�C�    ˙Ű�C   ˙Â�ęô"�˙Ä������������� � ˙Ú�����óŔÂÎq9jaOČ‹)źă‚b÷gk¶O©.4ÄhK“HéÇ–.‹‹THÓp߉+ŕâĚHÁr8g‘Á<¸ ` Růt3ŚŚ.)Ţ_% Óú!vźb,łĂ ŕEĹć ŢpČ&žńŚłfř1âř}qNkIJĺ­1çcťäŚďíŇ›¸­°+�$˙�0÷¦‚©J„÷&ľÁŮ©ŠZŮ5{Á.řµ=~Ž­çl‡ F#t• ×uŰ'˘Ą”r&×UT|!§ďziŞOᨎĎÎŮ"˙�¦§Ňę\›ľčľ0©#ĺzu™:mŽ «}ŠT /™ÄâHŠź[ŞúpO$Âxřić©íyie·2ÍatH˘á§ëá]4µ•ŮŇůdV<Öť#Ă‹«D¦M•Ë·ösşbú|gĹF|%† pÜĹdÔ7ޡ-şŢ˙�Ą 4GÂďÂUÂ)y>dß[%‘2?;wŹ#cśJ¤úŻH2ÉÍ{ŇŕĄqŁDc"ßË}_çď!in[Ž—…đ‡đ8)|’& AÉľ U•!źGŻ4΋Ńmëp‚»ň1üŃWG˛śö]OÜŞlz%Ú}¶2˝/bSo/ kĘě|č»÷*pČ q…­[@ćú]­~Ű6sş”ÖىAŢšVYÉR(r#’ @uĹÖś™&|ŽZđĹpÂVf›ÎteśťîĹ’RlîS„RWUóú‚CčŐĆGÉűŢ|śÁ…=Wڎ˛0*—F,vSĄuh Ť»ßPúÁíšiEuŃ�„yüiĎ ŤęO•ą&C5Ń>‚O›ľř)ů÷_( —^)q‚Ď#–ôĹ[1–ťńÝ-ŽŤDŞ‹ś TŹ©D{»)JE`Hwî”Gđ“÷ /♪DÚ8ëĂ3LÎjţŽ7H2tç3H”…’A ëOüŚľ RXÂuî{˛Ľ3Ü+A39k˝O8a‰Iz×0ăłž�lâzěŮ�n:¬`̶ôˇz¦Ąé{÷N5 ůĺ[:U—ě)ÉŐ¶µ5v‰ô‚D›˘E“Ŕ@<Ô¬J‡-°­Hós…m!ŤĹ!‰O,Ô"\‘í Qs‚$Ś%{HŃľÍĆŔdÇiY6\ôAŢblŠÁ?«ÂŞnřÖ5 5[qˇĂčKc:*–b0�pvŚ”XzY xáŹňҧÍĎ–¦đś1€¤ŤÎŘ€¨JUĎ�Š;Î`1E®k8Y‹R¤ç�Ż—J ŕyÂŐIb=~´ÎňęĽďÄeʎOón>·čÜÎ\ÎĄś&Ś±ĆšŠi{D€±¸"–�<1(�.ZR$-A]"ăäŃşěŮÖÁ@-1ż)ÄŘŞŻ–ĄŃ1č«jҢƷ4?J«0ďľRç„Ĺü3x>§+ĽLř2Řë�.¸‹Ą±˘µlś´ĺpćĘwĎć†6H†¶Łť#q—ĆN7 JÖ^qHIęoś‹4?Ď€zp¨ř¤|ë‚·%©$WL7Ć„R€Ł!ů»«Ůµ#Ľă-°‡eMéařśŤXř� \zWň`ó‹¤R•Đs˝+«m$ćTJ;-¤Ý]QÝźW0’§1Ö®ł&čľ’IoŤPÍüŰšVÄe„ż×I“…vdů’a;ŘÚ¤¤ßCĽĂhY0Şű?ßÔă”Ů| ÎOĹ$”šrČ yjql/®.ŻÖÂ>Ć.žŘB¨Ň/¶łmÓ¤·|ŻĽÖ ßĐśÇY4şuoVîÚŇs»RdüÁÓĹ/Ü>§@%ˇvmmçÁŕ•ĽŮŇü˝%´jŻź­+oŃŞN5çů3Z2Ab†…6?býŠşHž‰.%uÔ¤ °ăiÉ�Śu›í<i[|±áĘ‹ˇáťëôŰDfšÝĆU·~pő[ÍĆ(fÇĄÇhY•ˇµÔdsŠV)}sŽĘblvÔR2ú'9eĂĺŞpi>’9EšAňť.ŮM»ßQuŮMDŇxŤÖű˘ëc“‡|( łÚř_VđóŢg?DśK#‹* Ç{?ořF=—ĺ8‡ł*|ť“^W\gtŚźO`«áPŞú´™ĚoTP‚ZÓQ’Z!–Mk{§QÓ5ĺwa>Ő˘? %śçxXĚł%Đă'÷Ů:ň¨¦&1-?^L& óM˛ôÁdV…IÝ{YŇŐ*•úťľŻÄ×5XâÉiŁ®č5đ|0ÂĹkÖ•lW‡¤™Ł±ě-Ŕß Ď÷ă”R_YqďŚn’Eŕ.ěv–”©Ľ˛K·_ňÖß­ĐÔř—@R(!öväAŃ}aŔ¤4ŹbőÄ U?—Ď\í›Ţ”Ç3f¤&ŔeÁÇ:ŇîŞéËröŻĽŰ\Ĺ_HćÍśĚ×YŇť<Ę«/ş0tV/`ůýęćŃíŢsd—9Ó&dʆĎö <U®I"¬ăÓË…®ÓWÖ´Ňo€®ŠŚ”H ŃUT)µ0DHÁ,q…ó˝čW[Zež«4MrËĚ-gG'3·űĐśQâg-€×ÄX¦çLń.kŐ°­†Ă”ĺ°uÚĘK•(d˙�%”Łî]ÎţÍć —.A™gČ^‚ňj,\ÁŰ2ĽĚ±Ť˘q>:Ő0řŐčżźn7;˛)›"`žć˘¨n(n @ZP*K“đď„i–¶©ý¤·KąmŘKlř�­;G˘÷&X˘xë.žNĎčFę.ůô'2ljbrrČmß +;ş)fÔ0‚#liH-z ‹S ż©Viż¨­¸vWŽQšźÇw©+ĆŞ˘ăh\]l Ő°ćěăéćťÉ¦Žę°Ľ ±APű 1Ä{P­;fiú#S-XˇfĽ)ş‡ŽWmÍV3U4üC\UÄ>z»5E–?Ěd /ŐÇZććĄěy‚ą{Š™ćٱŽ`óöD€„±&G—U|QúęKVKqMTŮ,EţÖ¶]&™UTűIÁ [,“:=]Ç ŐelŻ”<:ËťÔ§źęB—“ĺĹńnJl­vŻ ťYú.óĎ­”¬1"°ąHß]lË›,Ż´.Vg7—çDMEŚšˇ…l˝îJ˘K6x•i“–—ڞ#Ʀqád»8xĘ jűvé%CN–Rw;ĺşQ0SŚ_áÍpű»@(ZrűÖ-ä»*ÇmvŇÔ'ÇŮ|¦Bóji”.Íů‡ŚyjĄć_żÉŔšŕc®°žĺÖ80ĚȧSĄI’™\·3nó„ăÓď[#”ŹköČß]&/2…ZU5>Ö™őü<iĚľ‡şSůjrĆ„V]ŤoĆ+ČâÇąđ]‹!‹-ËES40liBÇC>śč˙�9Űg4±7} ”é-ôíí‰Ó2c)líÁ9ŽŮű=XyńŞRéjŞ˛µ˝�ŁkE˘jLPľ­sŐ¬×]´Ç¶‘ úµÔWo‘§58j9Z&â­óĐ˝+ĐłŃ5t BˇC‹ík…ä–u¸»2Ër[7´ ˛‚f˛Ml9®Ş¤mřŁH¤6ěśĂÜ Îyá{0Ţui,‹8{…çę8„2sć]"J¶LĄˇÉęI†&Á«¤Ë©c»&`΄—u #ĚşCŇúß^çććy¦gŠđ¤űAúąçäMI,µ5|ogľ†­Âk D{ë닆I‘·«QśÇ‹_¶ăP0«Ź¶Ç2îĘ˝p•Ě]Ü ÎE‚óPö‚Ąăe-ęr^,LĄ@ó—G6Wv†ů«Ýs äUiş36^@!Ó“^@cVŃ2wQ<¨”Â\PgV–ŃľÓ.Ś r7Ô[żkLJ#ó2ščR%"úFć…pŘaG“ÇksD·Ş?”.dłś˝ĺo¬k¸é—ńÔYM·ÓÍâŢýŘu”ËAń;Ďw«kk—Q>H”aâ×ZčŇ,q Ż!«eŻ ă'éA^”ýBy2ËUĂŐ˝Xţ\çů%=R>z›č‡™ŘݸWŹ5°ÓLýyźž12;8ůNbTŽ#Ó)+xc^rçUîŤI¶ö{/eéZűÇŘŰ=á˙�Wçúä˙�h®qx»dĎŕšm?W2Ś«}đ¸q·ť‘…^Ö\uë»ńg9$ĎţP´.th›oýQš|äcK!nOč¤Ă3G­=W4ń˘ą.?—Ąôţ ć‹SŰ%žHsStŤŢŇ÷_žhn˘÷ «ÄwoZ®Z?ÉËŁŐ±”ě™5?ĺT5é*4űż&,—oT™kCđΊťRŮťs,|¸ÖĆЦE<óÔ^„ůS‡1ä§ŐŤ çwś }‰Ú¸˙�GĎ ľ ÖsP6A‹ËB*kČřęgI#W]ŘŇX>ŁV)ovĚIOîYF`Íú’ζ‡˛4 ^ET¶ÖZU€xwUezďť–yo°;AÁ`|tÇÎWűU˘Ť5ż‚§ü–„)Ĺšٶ%é9ŕ*ŠÂ'çô"Ág´t†Š˝’ˇÎŘúáĐř“Ć[ä÷TŠ/í:Ěźĺü>KŞ˝W“y‰€&˛fčzŹ]5·ČĂ÷ŐďŤuú™*ß`,ü´î„4ĎŹČe¤ńźű$Ľ 0ĘżĎčSě¦`iľˇÝ˘ĎÍ“ůV/Ů™÷̵˙�|ĘÍ/ö*Ý%†IŢW~7BÖĆ6'¤î|w †xĚÎěâ—¤ ő[E'+€4˛Ľä˝K›ëg˘ú»ĄŢŠĽŔąĚ)€ęv‘gXâQ†ÁĽ§ˇ÷GˇÝďĆ ˛<o­Ö<©6®ßX~áuGŠň§óäŇmyđN;óiÜŁ[V>H}j™•ń"V>ŕ˝´XŤű§ËĽđO@€¤>°h®"¸ňǔ꾜4¸´G·'Ł,ΚrçIiËËł}o?äć­#eőWÇ„XGJZŮÁ!ŚŮľĚ ,Ţfbą:Áł»´ěőô„=rZ#ÇçV'1›˘†ćÍ…é0‰řáň¬ Nł“.˛"ş©Ć/őÇP‹źžô^4Ő+â/Ͳdçş{rŤZ“N7˝Żü@[óš’x‚?ě.…ř=ˇfžŁ§Ő3…i仺ˇ.qě÷ĂO~'Ŕ©Zx˛óÔ;'Iîŕ¤ńtb”ŮŚ·Ä�VSî±üçßWNF±‹ĹVwD©×+9\[Ö«őţ)ÍĵËâŻn¨śŰ}ô4tÓ„«?׫s@b1Áµ÷ŞďÜ©ť—-]çţq™Çb/)íAľë–JŤ[óHşCIôĘÍîÁ‚ęg¬ţ-3:1&@—ű0ŚÂą®˙�˙Ä��������������˙Ú����\ô/·= Ä—+ÄÁćdWMąč�ŽO=óëZ-Ž HBł2 dú`W #łY×űWC¤Ha!ŤçÇLD3� [™5Š=Ç2!9NMll«Ü ¸]ŔĄĘ§ŁkĐ‘qĹo •JßŃbg„bFGŻzC9žń4kć;‡†Y3Ă(YµžŠěKjř˛:ĂeĄ=ĂÄSĂˆljńN_śéâ6ôôGO wŞą«&Ż$5 šsĹ\CĂÝ !ťí6gy¤0 ĺ3ŃÝÂSQÝgNýđń °Úí&3»ş;†gş{‚µ‘ë[¸ą$6ÇŽ"fzc”‹ś'Ę…Ĺ•Ă+ó2"Ţ `ăşd ¤{•]™ÚI«ÄĘtY{IňŞ‹¸mžžŽ€­.«—¤XÖč˝Ö°  ˇ×ruŹ™Â58©_±]sťk>íWjXsi†‡LOGGž—%Űć®_NS´|Ć­Ö¬ÍTw {¦,háĺŮ7EVŇÎn’Ńś¶7SV…wľŃŐČĎŻOLë'>›=T]JęŇÜ«JŃą6=WťÍjݞ]›EAę#=ë®›vŞç[bËXKk §§Vc-H=ŮÍq÷ZĎ@ŚmŢÉĎĽ2Ň«]Zw§ űHąÔ+½F[‡×®DŔSP ĐycÚ±=E¶«đČf»i fîhČĐóÖöl˘ŤÜHŃěoCUĂU9›—jÖס›ŞĐÂŢónŐąOÉűO?sNjäú»¶%uôkĐK·pÚt˛u {PŹ!陑čTü«µhëÚę™ RÇFŘ×Ĺ úžÎőůËúTtt8śČŮ»GÚî_PĚ@3ŐUÄŞç_×)N"Â‹©z;±«-V˝`üţU‹:,‘«Ś§Î[~ޏôPɅƶ€ec\żjÝĐăͧ#ç­\ő éŹ.2ŁŢw fäŮżq;&L˙Ä�������������˙Ú����µůż-•‘í†Ćő‚Ęî’UFÍ^ AĄÁ­/Ňő“ t†@ß ¸°§žć+�íqöľÚ΢Â$hµ +ó^p# =ˇ§łďâó‰JޱŠ×b0Ż…äҸ‡OĽŮkj¶ěą G4Jţ۲íc$ŠAŤŞĄąüO0¶{ý¤¦Bd‘7çÖß´ŚŔ4’H˛Bd µeóś{Ž…ń¤ II ©<—Ž·ŞęA ’,2bA›Čňźč—4µ@‚4S+c#H*dŹ7čűrĽ@a…`U¸CĎh NŕýK™I–@˘HHc&ě÷‚ÉŃé.~V1wJŢ`ˇ`,pšµĺݱVÚ óť í<îŻ5QL{’«Ý o§MZ*äPt…éŮÁ�•…»ňŰv5íÓ—GBŚ˝Lř–ęžţ—–$4cęěÂö…mUmŰ›(×¶Ô§›Čé[+\ôî–čfDşî–śüVŃ~«ß•ĆÓą™*çŠ|ßoˇ-ľÜwn·'.lWŃ7.íą«±yĆrş=6ž|]»nçŕĺqą÷tn‚śŤfl;zŘ„Jőč~nU銖©ź©¬3W„g™ěĐ]ŇűŰKآ™ ¶"ŠĄ¦M€Ő­gĘŹ^‚µŐ«.µ‹«ŻQçťu†îp¬y–ŰňYu p©ě+Uů4>Áé‰ow›šc7uyU=7K»ŞZ,ErŽÉeűřŮöÝ–UŁ5ŹX©áŞËš  9•ę“;*łČ*&ł¨Ô]`f0\ô­Â„°Ľ¤5kÔ®ş‘ŔV„Ťą˘Úk;Ję2wqçˇÔK­[¦pZÂf˙Ä�:����!"12AQ #a03B$Rq4@CS‘5Tbr%˙Ú�� ů(cĺ©AŰ#xÎIňÉő%~PÄ~7ҧ˝OÔkCS zçÄśP­óŠęÓö¤pőť#Oj†wg@Ä*¤’\B©"mÂmç‚9VOů!ůů¸µÂľ¨{çl Ôsź[99AµlfF�|ˇ°>îņő˛úŕkÓ�o2´Ý› Ň6óŃOÖclg¸¬Ó V€uaŐBäT+ťÚ5Ó©»8]9 4đ˝üŘ}g˝ĺáůe˘±™çB^=üü«ą9ňK§5ÄîšP*âęhăbf©%i[Pď÷÷Ďt $d9BŮßçS¶íŢ•sťŔ ŚďK’1Óeˇ+Ź\ÔjČŐ€ŞŤb¦QűÝaÓŐÜčL u׫¦”6ľ™VzŠśâ·c L¨J[|H”HńŁŐÔÖćfC!U†$Š0±öüăůÜD!qFe†Á:ÝÝ·cšŰxÉ©»”Ż]ÇÎ6>†‰đżżećiéÝ[g­Yô¬.6Ém¶éą*݆ŔĄvŇ6Ęý6�)ÉąŹ—†ŃV†?ŠúľF´*„!.-˘úFUÍ_J’¶UJËÂîáŤ<±©Ą!ŢeÔmř8ď™eMATFŠŠ:O‹Ł,ŔT,¨Y<żäóŐ§ÔçŇŻĄq{°ą“s\Jń–3˛HÔä ÍN‘ĂĂ‚˛őŰCĚaŰ!AË8§Ôđ‚űS)oż€#N1żËéš´¨ŘúBeN)‰xs"Ńţš]Aşv(„•ŔęntŚÁČĐÂ-Y…´§Hé5Ô‘—d™íÂ$µH95qk™Á‡Pұnó+T%SX<ÔşezĄŤ8†ę#‚<ÇuI0®×*$’<u5ěpíWŇĺ„e*uÄéĚMqi °˙�&{±ÔÁ´ŻNž¦Q\MţťłjÍI˛ö&®TňÚ8¤hšQämLÄ÷­RFŁ;) A:­Éy´±Ű[4†ýŁűPů…ÇŰĆ,ëŰą8r5ę l%K‘†…›Dr6tďO‘Zmš-—Ô{Śőz÷lŐŚs)YŁ“8ÚT–[˛ÜŞÔ­•QĄĂ¦ż)Íşó$`O)-¤žĐ‰N‡KÉ­Ţă ­cá˘I-ľ¦™ÖÂ'oţBńŐ-&f;pé'žgŚHѬöܲŚXłI˙�j˙�Mú´™m$0ÉpěuŇtíVřĺôŕ›¬©ŇÝęÖúw©Đ*„ K2Ý«?7¦)Ć(gÓĂţj6ĆF­¶,:±QF&ýű–XžXó­OŔĆ?©FX lŻI­?I{cW×DşLpI+ëRLń0ÍGŞ]Z?S`¤śë-¤ďQ.™9,S1N]YXdÄäţc¸ÍO(ŤO]GúB”ä ć śď˝´ĽÁÔ0nćXbŐëlC =ĎŐü6ă ă† q9«÷Ź}ĺ˙�Č[ŇéÇÔČ@Ç#$+%^Óé Î‹§×ľOz?Ú­%XXtćŁD6ośiu@voźÖŤcµ.ťDň¨Č¦f–O/RÂäg7íĚh˘˛•q©äł‘‚ő©-ep?fh÷đEĚ‹žÓÄëeRh™J7\śÁ·Ć–ěĂę°Ľ\äęŇo _¤EK`Ú:Ky «đăTJ#ć{}$ţOŻ‚ş•&…ěfPŁË;sľ"6|;•*×úqť–yQG1f×R^k•ťŞĘŕE( IÄ%‰´/VnîžÔ4B5[űą\dš´w•¤sł`§:oŕ5«męgţaźVŹÎ—QZßJěiŃqĄX3$ŮsAbC·{›©%M'ş´Ř ~J·§ˇţŮ®ĄL‰1Q–/Ôŕ–¸ŇŠU‡0ť/)P9dÔw˛Łt˘»mÓ“ʇäéŔ!⢵Hô¸ôFUŐ§ęŮ–äĚ%Ź„Ek4ln|ö<řŔ•úâ]…,Z“��;~Y8­˘yŁS"—Iî“~˘°Ąc«¶’¬^Cî¬p٨ňětˇ4“2L\wGMŽK<· °h]«ܶjË_"M­çg0Ě÷ăÓPĹ\L$1§T€ą$¶i[Łîsđá˝d^ äRťŘÔÚĆ*'ŇuŮŐ݉Pqůü#$oZŚă ćŤ Yt!űuď˝*ú–´Âč¬v­1ĹvĘdRĺ~ JŃt-Č–DÔ‚Ć­ŐL˛AÉvHô=¦—‰äŤľľey¤(Ř«Qp·"$vY-®&ç$SE±ˇůe±ÜŐäâęípII-R!gî3ę”űgpHé}zÔČÎE5N˝;lź·yHÖÍč01«ĽW|©äęŮd/0ć®GÖÂ…�GŻą.K}ŁPď‘K…Śd®™Ąß`)Q‰ß¶° *ěŻËP}L2ľŹO,>:~qJĹ[>±Ë"¶TśĘěîY·o üAĂ*GÖsëßąˇŰ˝@qč.�Ç.1›y Ësq€W}O)2ćLÖ˝tl$%]´µj+ŰvŐ–\ŠdQ‰7&I°¤9\ŕR’ K—`§"°şuT¸©ĽĂµz ë>`H¬[i[Sś6Bî§zm+Ś×4äă°móXëÜÖţťż(x˙� ňžúʑގƔjě7ĺHqXÁę·µ@%qĆŃî¸ÉÓÂ~Ş+4Ű>sůfD'«‹G®Ę:µ´Ńäé¦ęŹëIG+rrCővĺITAM$‡~őžžˇQĆĆ »éwZčeÜőęA·¤ď„čîŇ1RµťĽ�͡ë$ě{+ŹčdĘV˝wŻţF+>ý×F=|C‘&ŘńÇČőŢŁ ŘęmĽ#ΰÍ6˝lĚÁä â0áG(6 ŚbpN+ÎŚŔyQč3yűëjDŤ#Ě4˛Gżryůý4ĺŻ k‡ëÓŕ&ţ&xĂĆ؉Yaxĺc—]„¸˛7#Zˇ§ęLbąŠ2mQŕf»¦ăfÝŰLdňT±ôÍ1]D;§š;űx#ŕ©u.°1+e¶5ś-jŢŽů4§Ô‡,kW‡cś|Ţź*mŐŠBżÓš#¨…Ţ·Vő““V÷1,yUíµÉ$wËrůcdc1#fĺhĎg-äÍ+cĄĽŞ¬FUN,-˘m<ŮůfŹOë#~\®Ą%Ź»»#IŇúJÇÖ˘ź!‡P'W¦j@TďQîŔÔg9ćyC2Ô_ĐPĎ›V۲äTŠsśä—ô®cvÍg5°őŁŢ¶Çđ>ކźcá«8ĺü˝Ľ$bí©ŽőŹ�Çk# (áϵ2a{î}ęcýÎ*ĘîÍW�†[«�¸…5T3‰J#ŹÉă2DUXm<:uwĂ]eTśEPâźý©‡Be¨ĆČřeL>Ĺ–0 ©ş&¤M—qHš}hČă8¦bOć v °¶Ô.P×HŘśü«ľkFUĎr~_OÓPÎŞě=üä ҆#Ď™†r6Ź ”WRćB1Kľ’®5Ú¤âWĺ(eµĘŤŘţDŻËPOe߱ۏYż7ť¦ĄáäÄ$iÂ×! •ç–g…×<ľµ12aź`ÚÍ$ÜŘεÖŮ~É&Q±Ţˇ ětŠE:}ębjaĄ»ř¶q·®Ô@˙�ź›;řf‡AňGjšnc)*(üýÍ)ĎIî{ř&1ś«ŞŞ®+ž zŃ]În|Ec Ńj–Mĺu2憙93úwŁňzřĘ‚TĐݡ˝äLb`tIÄ­Ćťv—‹[˛é{SQĘÜ®Qť$HÔßĚpŮe?¨‡l7t<€u rrs‘hR Ë!LčŢş‚Íslb›ÍO§;xF1ÔNĚsýľlř®wŰ?›é÷>ŤA€RHg4ĂzPHĄÔ˝śeŠŞŽáŕήڄϥ� K¨c‘pV+©ÔŤëŽŐçnń"ŻäqŽź«Ó‰ŁaşmMź\Ôm¤y6w}ŹÇđ›˙�PÂ>łş´„ěąŇ˛ďžŐĚëÜÖ{śŇ•îŐ‘ŹP}jli;oů €;>9C¶(2s7@@Fa·fwˇŠŠâ@:Ľ±ëc”},ŻĐ îa™5‚¦@lÝ*ĹPÇ ŹĄĺůELâ(˨]‰ pšD·ó›Śdom2mÍŞT/Ş‘şsš#¨ŕU“ç‡^ę$S6¦/ËÍFŚę]»#ifŃ˝7zŰ‘‘Ýr}>LíŠ261ťĽvÁůŹůĎ}ľ\|2i|ô‡Fz±Gę Ú’ĂbąŽ Ő€$S ŠĘA¨ôî_¦¦Ź:."!…ŤěQćtUA„� >3ÜEť·k©ąěCSĽ­űÎ^WĆ–Sź¤Ţáą`꤯Kn˛Ť-¨SM¶1ж°™Ię›éÇŁ ĄéÓR ąŤ Ť´ěE �ô—ÍŹĚýą­«·ů8ÍF3śhFÂuŤŁ ÚWS®ť&T*ĂlR’JiD>BH¤ĺĺÍäáżł29úexIJ@@ß>'a𻏠3.™ŘŽCÔŘŇ5˘‘¤ťqé"¬ŇÝÜsfd©•UއÍ&]Ű$ę–źzáŁU”§UiIy†śőÓSjÎűÔQë4`ĐÚŹ•ѲI­—űîäíżÍŹz>ĺ4ĚYł·Č­…Ć…4#Ű&™qťčăţ~tRN�Ý`TtĎ©_VjGv%śćµeÉŠB„śjęcĄŘ±ý_µ°@çRŮÚ˛Mη—T>Âń•-śą8¸1őHWM",ŘFÓ :%*[čŻO4˘i'u1ż›Ń÷a ­óY`u`Š· đkćĆüÁ¤µBŕČ QnŞňu§v“8É©$-Fđ? ů6 ¸ĎČÚ4¦CSŽÇ:Hó m8{„čÉÚŠýţjSÓQ°r6"ĄčŮM)¤ďI&–•eŇÇ SÍÍłŹR ;Ô(˛*°“ę[^I …¦Ţeš Ę~N7?:m ßNŢ%Ći#(rä°çFNť8LŞľZhŔň†éxŮT7¦ăj@K�*^–+ŻUBúl&Z‘TcDšë«ÁÂą_ŰĽ‹§Ŕîv¤LľÇjÇć63Űę[YA"”5yŞCĚúC©S†;ť¨wíOťłÜ“ůß˝ĄéďĽv¦Nëš·Á}Á#‘”b|ĎĚN‚qQťM†;}©2Wr¦¸t¦Ţ\•$+jP¸…Ů‚ É|ŻfŰaĺC|ŇŤzŽü¸Ů9Öü˝ĂhŘ%Űs¸X€[ 5Q‰^Ř2:çIţĄW ąˇŢ”}�Ś1Oˇ!’=�ą™Śz<QxVÉ«ť`¸+hÖ KŻF¬l€“öŤaĺ•ýĹ×§–‰¨-¤–DEĆg…ᤝóó(ɨmÝÓPÚ …!ł"s6/›‡hÎWíŕ2Oz]ňŕwŇGÍëŕîĂk W*ŮĹ\őĆžZžÜ®p(1�Đ–LcU,î|Ř5ą=]˘Ç˝Iľ4šőv9łŇ-c«ąqq#2Ó8Pzq¶?vdéŐµťÇó¨JQŘ­I#¶=ˇř{űÍĂÁZĚ$m*ÖĆI.žé¨!ě_MK+=»>h“ň/zcAŰÍrÔ’€Ä•Ć)cÉ8¨ř[Ď$’.5\ĚóĘ]űüŁŞŻŮ®-䲕eŤőPw·xô˛ÄJ|fĽ-zć˛=ĽcőÉ81Ź÷Fd]-ŤYůdŕT\9đ’9n¸t°(o5é¨óĄ8?Ô3Ó.2÷Ü<Ek±–đ^ô ÷䕇śjň9#eÖ¬*Ů2é©:%sű2hôżZŐë}@1E~ä (Ô_§‡ăâáŢM€Ć±DúBÍ’Ľ=Lľů!M9<Ş˛_˙�ŁtsV¶‚âSÍcFTžK#E%qň.ÇzďXÇzc¸"ˇŰ/ĺ‘‚ş(¦D8¦ÎŁW ‰E@3{clřtrżű¬mŕ(cÝ.ćXŮ˝<Ó®7;îçĺRŔť›8φ+AŁ›/ĄëMhm”EŃ\JŰâRTGë‹Jí$gW1q¤€*'můé•Ô.4뫼<M\IO¤pČkčÖů:s"+y[P? Żgmő*XM·/_(ŮÚ´vŤĚ‰M fRĹŘŐÍŞ+ĆĘş‡±‰`uĹk/đňÍϦúMoü—Ä)ęł’„�Ís˘Ë˛éÖŇőFIŚO"ND­­.ăř™ťßămŁ`bŚÓÉ­^g3Ň SZŻÂsuő°ÁĹ`ă>€@­%ÓW!ĄÝFLČQô‘ĂŁ^™E©š'ŰRTńĂţ›ŚĂóy»Ë•ĺ˝pČ„’a—"ú×@R ¨af|b®`xŔb6îv®[cµojŇŢÇňbE1d÷i6Ň6˝-\®źJĆÝëҬî%·-Ëj~+v5h`µqq,ďŞFÉýݱ\.îSdm6ćÜISÚÍ=ĺČPWHy©ţ¬L趫ڎŞQ;)-pÍČÄdą¶ś,Ayn)n¤’’Yc·Ţ!˝JŹËÔąW·ąŤ-ôβ %[¶“\L |ëYÝ ±¦IşóyjR4† ŐŇ)’65(űÍ,Š„˝7Ăż™w·Ó©“‚šhµˇ’9—F(đgá×ôŇ[‹VZőŃ­í±ú™˘ä®źF =ZÝF3Ëf–ůµÝĘŘ"¸séiܡÔîŘ Űó9˛ÇŇĂ.ÜQÄŠµÁcÄ<Â7ă_ †”źsNÄ®äřjosY>ćµ·żäŰ~•ćźÍBŻ{%GŚoMŁÓ¸F=”×)ČöĄ·ßyR’óţŁT1Ü2ÂsĂęśš±›\I{µŤÎ¸d¸ljá˛%âM$€ÔöÖÓ"Fá´ÜAQB±¦Śľ¬ź¶›­f?Ą§Q{îßC¶ôŔŁ T‹é‰f .­Ý±™'ż‘¤D’>]\ϢÚ'vÔmž6·g—ő$–ĽÝ'‡.n­Äe4/&N,tŰ·í~gµ‰ť2Ń[@˝’¸ÄQĄŚŽşµČçD• l{ř ?şĽĂ*ŢŐ(mgW{Kˇ¤ŐŁ$ĐsŔŇ9Öńę-*×Ôż—Dxĺq›(mxbŻš{y]tąÄ%ç˘ĆŞúąL»é`&^…4 ł` –…Ôé*C<l€RäŔ~ťô2NŰ›ny(΀ŠxDŤŤnµü€čtx]çŁĂRŮŢEú‘ą ÚcŇ’JÖŞË­†›—ť',f(`™//fĆÜ6ݢŽq>Iá6‰onŃwN1WŞ‘€şs_ ¦RyŤŕk4;Q9ŠŽĆˇ’®c·›©âÉĽ ¨bCŁđěŔZ°«ł4‘…`Z]\TI.4ń»¦¸FŘ·˝¶DEÉ+ő«śs1\NćÚK9Łi„\°V«É#—–Ë«]ü˘{§•cĐ´s TjŤ‹ÇŐÁ5ë_Kř#rĽÄ ťę3¦ejżâ‚ć8Ç( čM'bdVÝj[Ű9K†®2ń»Bc9ö­ośęl¦·` b47 8ůí­žl‘Ř zażk^G ¶I&¸?=­i?'î¶jŚYKł1ŚĆčë”`Ë$‹m$„âł[ÜÇŃFUőÖŁďQČęzM%Ýî:AÇń ăŹT±ô·rFđ&¸’[†t‘ÔĹw}é4ř|EÜ�ň‘źęîM9đĎMBŮŽeŢźµ+`Ň=)VW\5$şľ(·Á·(ł5MÜ¨ŽŞt_ä2桖;~ގMŧ“R}8ëހ΍i<„׆ć…mÎkI˘µľ+uňšw$ő”Î*;$j‘Ť:F[ ‘LŚ3źČ…ôuüůîůĹYÍĘrŐĂŁ~#3_ĄÄxtV°Ĺ"d׊ćâöWĺ|%ĎűUđ×ý +D€ŕ†đÄn/öŮĘU`v·ĚÖŞÍu ŽâU¬ŠŐZŰúާv*S´äĚ“oPȲLBBší1 ĄaE=š‹S˝k4‡Ţ—µZ–ř… FfR¬PÓR5#€ąŻ”¶W®˛Ü¨“JHĘC¦¤<ȢÇ|LřQ«Ki&‰b’ëĽÄIřťtëÁ +|MŚĄn˘gS~ ŐńS±éE«í_¬ă2(sĺE®@ÔrŰ!zjŐĂ&™;!‚¸¬<¶ŚŽßä3-EoY-ş3%qX#ŠŇ$[†iŇ8‚‚Ę äÂGé 6úµ-«Ť|Ä··†Ů[’škŠŘ‹ÇŚ™4TCb:rŐqĂmn.{č˝ŰIJkŁŠ›Ě*œɶŔlŃ^^bĺřІ;>^Q2(d¦ž<ů…“޵vÍFrhďKCg{µq/Őó(ÓH~(`íb>"`K•GŽÔŠÚ•ŠHĂa®Uăó6mGúŤ[]O G4ĽRčjý2$‘ÝË3ŰňôŹ÷ZŢéŽJЎ镪Tťś‰¨SgA5n¦IBŠe>\jÚ¸ô.¶P7í?>uRÝŞ4ë¨ŕÇÄ_—Ë8I\™ďŁ«»Í2pă6…Y#âňsyon5BKF xĘA'jąŤ3šc¦Ýžµë%Űľ}Ľ&ÎŮŻÂđÇË‘Ëf[Çx¤ňńI Ď/éočZ˝–·űR JĆŐ.É\Fhß“ˇ˛u oR¨ç“ÝmŚÚĄĐ=aíî [†Á[«•wÖŠEs#“δmíČÎ…ŁgnÓZ~™Ű–;Ćľż¦˘…ĽĐ˙�†— /ťQŠ…tÔ„2Ëoľe«°(*ßZĚ9`“<7™Ú7ń.$L ş7HˇC4mk0ďSÄGqInňy5$.­†:M#ÄxXđŁś(\źhŁćGĽŞřs®¤mX®1+O†»\[hŠUQ›P<ŕGe5ž‚Ep.d眇üY?.ÄB˝íçXmáî÷7Q˙�Ł Hľ• ×%ű‘QÇŘÖ¶†@ń6í…ÍśW,áb ÷¦ŚgµiÇĄ Nýé{ř:qO rřH¬`T“1ËiŽ{;{Ž˘í&ą¶Ä1©$懲šÉđu˘¸ˇE Č’Č/Uł_ź<=7Pj«iř™Ś.đB‘ UĂń h„Čť\=’t1mBâ3ĺŢŚ0±ÉfkuŰ—ôę{cžijnŮŮňĹż­E| ŐŚí/ ŤélĎ»V+?µb´× ŤQ9źąŽŢ”đĹ+ËKhÂmşDVĹNn‚´6Fh„Ď•·ř»@5Č4IwqíMĆnS?CĽO‰0Ů9v·RąMÄ—;�‚Łňg;Á)ç<⮜7Jžś;Ó­p@ł‹‹7¨ßTj}]÷¦ďŕ´”>Ő§j¶řLÉ[UÔܨżú;śúţ˝6÷BoŁÇüyÓHM+űÖŞÔh>ýé_ŢşMi”26Ąsí´�3VÖĐÂĚč€?„ĂTL(ňÔž­ähFŕeŤËd�ß `\ç#ˇ¶k—ţŞř·ő4÷:˝hĎýŤ|Cz Ĺb±[VjĚćÚ:“>”ŚÜć×ŰýD'ap:‰®ÇîĚÄa™ř»¬c⤺ą4ÎiÝßĚĚÔ„LJ»ŰÚÇŢi(´O˝$°–ÂÁąE?µknlµ!×÷¶“áď!UΔâPţÚ9ˇIźř=öˇÚ»× xŃ%Y Óľ;×lÝéôŁV“‹lČs)ďHw¤&˛h}űíď¸"4»ÖŢő>ž»Ĺîŕ7’Ç8ĺŠÉŞ'$‹XĂ[D÷ݬ¶y†…ŞéŇX‘6ę¸Ňj[sŇĂKŮËčmT–k§Ş'ÎŞÔ~N~‘ŰĆ1Ţá”ó4†NL}žáÇ9¨äÖ\şŃB:U +oj·ĺFJiö­…ĚŐúĺ4›ďOÚŻn5\Á7®AŚěµ&/­b–ô©Őíµ_ćĺˇF­›Lęh˙�j=čTu¨ęĄoľůÍ!ßÖ gµ&0)|˘‡#kň¶´Ýěn­›]¬„8«)Ńwi$·“ôĺńţŐĚ#ď\ß—5jßËäTg+W1j ş+o&ST«ő;o¦Ž<sIg# «ŕäą+uo˝F€yˇ ß—ZyWi‡óRЏŮkKútÝ©Ź­péNygµôŚ=(·OµűÚ¸RG!“R\R1Ă@ÚďüT´ľăë §ZĆÇ4tŹSzÚűŚŇ6ŰŇśwÍĄ+ „ŔđO:Tšv:·Ż˙�5q3.&ąá.»Y*;ë›fĺܡ5ռáđÇżŽ>R�ďQJ –ZµpQH¦lŻMrăćkťÔ%ÁŚéuÔhҡmÁZÓK¸ZŚ\µfăÔ-o• Gµ3ďÚłÓY_vÎď–$ú*‚µ/ö©×N*3†»ŕÖßňą«÷]¨6}xC˙�6Ë\gh•ý$:¤fĄďŕ ‘ÜT0˘ôhJiMźJ éŠö żqŽŚG:Źj^řđ˝$"âîEµo[Ô$%@ËuÂAŢÝčM{bŘ6÷đËł}7ŃëY˘ď@ŘQé5ĚoJĆ|Ć­sz�¦y"!¤@ŞR†ěבscŰĄ€a ‰WŻoÓZŘŮ€Ú÷ĺf”Î6UŇ5Üd씆\dâĺk×˝09©zT·¤G#㣞ŚQŢQSţĄ ŞÎBmc­ą†¶¬dŃôFmpŢ‹¸vŻÄ"ŕřÝ”Q\f]¨˙�ÝcÖ…Fh‘ZŰĐŇ˝[4©ĂǢö¦÷Ą;W—”"Ű5ńcQčzKŃëán˝Łrm˙�M¨\čzř”=µŠ7Ó"’.llĺŢŢ] Ěş˛rěśMńŐŢO¦Ř;Ö¶; ‰"2&8őí1¨ěÖąF t1ń¸FĎś!Ăöµ¸Iď7ÇŮG§éŤ&Pb:Yk–=4?ę›zŐ¶ä'?ŰÖ·Ű'lŤXÍ]·Ń5ęb°Ąsšâ=»€Fţ«Ęď¤f î+é~rëŰÔÖ3é[ęǡóÖNަe8ßńtÚlŁ‡ÖŹ€5m75pO_ľi»oíL(xI¤÷® mĘ‹˙�©âH^őĹWXFÎ)ÎŕT›żH p ~đú…‰=Y®Qߪµi4›îWMi_Ţ€‰8m›¶­8Ż…URMá¶ŐŰ´6±/›¨§-1Š’UZv×,’SŚ`ÔɦB+×ďasεFý÷%LMŻşťCsľřî3Ś~íôëŐ±m¨śÓn|Őn÷Śyx5Ů­XSDť_f>µ3Ô6˘’9ÜdŘs"YĆ)4ďŢ”{o[Żc[{VG×»wşý1GŔR1 ¨¤ŐešÚ¶|6Ú¸LsŰé|Ľ`‡PhVĆ7GjptޤÎ>ţÇ×ęĹJŚ}h% ý릢н°ĎőcN+&ęâłqćČ4uĺrŚÝ†�gÉ«–ꥫyJTâ·ŐęÇCgnýŽH­4»ě+Gq];ďOŹJÁďWλwmżżáËhe¶¤üK Gt’÷Je 0Ý[×|{Ť÷Łť÷Ú<ăď§|Pň}řŚűrG€ŁŢŹ‚X“2 `F¶Öëf°i.cĺLČköńő«ÉmΊŤÇĽ|Tó:ÓhäW<J>e«{¶uö5«5§–Úó]aşNk&îŮżŞ–C’eaľvVÎć¶>ô°(ŘŤGS…ŮFwo±N7¦9ÔŘ髦Ťąx9ąŢ*°`­¤ö“§5íětp6Ő¶ëšm¨ŕă=ş})ńą˘ŘíSËĄsŤŘ剮IJdbńk¶„ę¬Pí[“X߀v˘vl ÉŃÚŠŻ/r W±rć>ŁĺµndKď ŃT űŐË´ŽŇz`ă=ÇÍdśŰ•ʎFőđmĆ*VĺÜĽfµnry…‚ę]őéĹk'íQíMs7Ď`Äă}—šĄWM<«ťëżP}µt“Ş™±Š 7ÉÁąuä Ňě�Í?V”¨.õęi—Ł ›Ô/©t·rČ1¦Śą_eÔpÔθŮĹkjćٰÍzűT§-R¨;ÓĎ®ÖÖäcÚ˘|dm\ĚW3'Ş„łPýŮ™eÎË„űďBQ•«‚“.ĹJ9o¦ł]éü,eĺÉ٧ĹCŞn•+O®1Ą…jßµj޵ďÚµÔ*ŇR‡ 1_†ăĚROŹ äXbiá`mHĂń ®ÄٞËF`ĂWÄmBAŮ&qď9ßjçí‚3MrąéŽľ%~âľűýšřăśBkřoôŚPá|CT«Ëyít|AZ^ôşťÎžĹj哱jµ‹2é=ŽcsďĂ8yĽÍ’'ŕ¨Ýî$ŻŕqěK_Ŕŕ˙�zJţ mţäµüÓúĄ®-˝©HŕfçąîIɦŘU˘f\žŇ 3Cżmű:–FĐś3‡˛‡ăÂě?ő–ż…Řë-/±S•·Lţ!B‘G�T Uß§ŘŇÇ1w,|? AG;şÜo‡Ç=‹rŁQ-űwáü›‹fĺ%ra˙�i+ŠÚŁŘˢ%ćđőŁáŔ%ŠAđďëE 0ŁťC.#é}•Ć-ŤŐ®„=n9 ¶ĎšöŁćŔ˘íŘ%”Úcą‚>gŔŮ˙�ëG_g˙�ݞ˙�ÖŹäô®+0ą˝yčý™#d8Pµ÷"˛7č6–ďWcP_†nyrIo#á ŃűŇÝĽxĹč´·Űy·bYÎX÷Ĺ>†ç5ÂV†ťůH7Ť˛şZś wŻĂS‰xjGžżÄÍž$Ó$ăU Đjň]]>�dĽż…Ůy—�ÄVżÄ[Jü/s X%ľż«áĢä_J€asÚµ{Ö±ďVP5Ôâ8ęÎÚ+hôĆ>IżkPđâ¶Ý&|łHŤŤ«‡R1Ú˛6đ:H®Ää´>d†ŢhćŚ<N|nfŠŚ’¸Użâr\ecĘC†o.ôS©Öw¤ÖN3'˙�¬· á‚5çŢnJî«gľĄjµL.z˘@‹ár–¶í+ÔŇ=ĚŻ,‡-ĺ©}ęN¦¨×;TŹŤ…p8tDÓ7›‹ÁÉş.<ąČ«ie‚pđ¶–á—‰yFŇxq6×Ä.I5ŹCąą”$zGęx.Âż śqßĂń<ŢdjŤ™2ś7 ş–K/ďŻÄvů…nő.âÚ)'ťbŤrü*Í,í´ ŢŻ.b¶Ź\† ‘f‰dC•ŁÚ“u<RĹ.Ó>Y§ŽHd1H4¶ôąöŁé¶ÝŹj¶ą’Ömq·ăÍečzâ|F;o¦™<ď-Ěş¦“SzíY©ęÂ…�ˇ˛űÔzŢN™8O[UçĎ<’ý¸˘t —˝ś˙�ýŔëC˝•Ŕ™7ýI]#ŤťÎ‰^5ÝŃ}ô Šö¤8_¸ ÚďkžáS|v�×Ń­Ä}Ncr­^µepÖ× 2ŐĽ©4+,g++i‰ÚŁË>ˇ“NÂ!–îěYµőőđḌ ):dTŠ6Fíw ‚ćH[˝~Ľř[Ѩý•‘27ich&x›ÍĽ·L"oĂ,㳀*îőÄď#´·26íq<—Í‘˛xďĂĎÉýŰoáÄ­ę-Ç]Ô|©J0!µÎőśQęÓ–8?qZsą5Ĺ$™Ym:cÉďś×í;ť¶őaŮi¤›Ő¬S]9Žó\:Ę(s±–âç_Jěş±M‚=ťZȨ¤,™�•!†ÍÇo^áÄJ1 kѨ)Óżüzćzžŕc˝1É®µç«5šâ6Isl$‡ü@ľô5ö®q47K ‚ńń‰9\2á˝PtďÚęNc˙�óCżŤ›»‰ŤC+Fví ©'cżâël<wKăřf÷źoČýZă6sqFY[Gk.1ás2C Ë)Âq+§»ą26ŔPunűWżç‡”ý_ăĬŇîžć'·™˘—gˇ]ýi3§j żÓ»ő2ŽÔ¸�Đ©ŕ×´šîRa K{t-\\´˝=–;iöŇ!¶Ť7ĆZâŮXe6n!TśuÚ9cŇŢČ·S’˘NgŻ›"W˝ć Ú˘Ku?éŢpW]í¤O ľ˙�hť$L‡GZŕÖ_wˇ¶Co'— k ‹Z/0ĘęŇÚ»+§"` ëHjnvĐ !Y?LźĘőą| #Ĺ<Kťę$ĐFµÍMhĚ{ÖŹł\Ëα– zĽ Áf°óa“™6ˇśdgÇń÷ÄÜrŁ?F‚ćPĘńH®‡ ÂřŚW‘˙�LćâVqŢE†ŮŻ ’Úá˘qXô=7Ţź$ołe˛şFZúŃ •{•PËŁ®kśHĄRŰ@†ŇŮHĂ26š‚Ţ8·«ĆúŰśąS‰8G)Dt qĹřŞ@A¦In/îî6•‰PdvôÝCĐSkѽĤZ]ÝۢmiqĹcxČ{MIÂ-VKô= ‚6ą° úM¤ŢÚÜGťQäXµ¸Ó)(´÷piýU®5"™˘Á˘Řśł.Ą|j:<´Ł; Š(—:›'éŚi©dlUهµ‰ÇibIUĺ”5GÔ·@-ĆtěpOHŔ…ž6Č]řż:ôJóu$¨Ď¤6O¦ŐƸ¤čóŮ®�}©aŰ$Ö“˙� íŠxöČ«IšŢmb¸eß1jČůŻm˘ş‹D‚Ża{k‹ľkţč?ŁÔ,xÝ·H.í§\¤ŞhíWvÜcXŢâ‡éĆ�ů¸ĄâZA“ĽŚe‘šGc­QŠě=u`/Ą~ü‘±}GuÝÉjáPüWŚ”ůĄ·‚A‡‰ż„ŮjÔ#Ĺq…EâsF‰±Śűíˇ‘Áơ˘6m„ŠÖë– ĺT|ßN@§Î¶«‚đ»!ĂmîŘ3‚ŻkÂláýśĂđY˙� @ŹLXJĽK«g(Ȳ¸Žâ’3·áBę^rI¦P¸Č#î+8űŽäW­8÷« d·ťt×’i`7Kfµ/¸®l\ÍÁoŰh®˘Ń"Őĺ´–łĺ­č6WĐSg×á<ý3Ü®"X‚Ś)`4˙�ôiQTä ţKÉă¶€É!ÚîâK‹“3ÖzqKŰc[¨ěw®ý©Ćv_úŕvż f3úż;“ÚYk™%ĄűQ›ŰęPw ®şLs7ÄǦ鍿«đÉĎ -aY ˘čn#u{Ä‚+@ČĂf€][çĐwˇ˛ő ôjRÄÖ ŕ·\řt7ęŕQŠ3Ý•|ŞËymĚ&9W"ă‡\E)M%†¸/ ňĎr»|îÁł/şkë­żE| ú“ľŞô\ŻMű¶3ď[t`Să-¶ÜŠâíäqfŃĂ.[8¤ĆŁ»i ľµëŹď$ľť ¨†Ź›Î*~äݶѿoĂ2ë˛hý#ń¶¸…ŔďëYéŔě ±ŽĆ˝ Şď“P;C*Č›5śéq�‘?'‚pµ-ĹŔĚß?®kŽß-Äß Śy cěÚ…WOnÇďľrŰă;Ń='zŰÍmŽř®'‡&ĄĂźČüM NËęÓś�FäQ «ŞŁĘçPČĐ"äĘT± uiëW>Zü&Ů–čcĺĎ€đ¸ŚMĆÝĄŤ˘•Ł0>žžŢ•»5§PĄjő˘k†]›KŤGôІPĘr>|ţGâý ÚBÝH‡×a”’rNH/űsµv¸Ň[«Ą~ů8ϧâďŔoÓüźĹS5 ^ź±ŽvÝÔnâ´.T*ď€ď‡\­ń‰łd®‘ ěsLŞAęż ËŁ‰ŞüŢ€ůŰâDą›ÉśĐ˙�ç9ޱ˙�Hq¸±Ţ‰Ł_‡Żt?ÂČz? |Ürűá Đź®˝]ZލµuPŐ§>š‹z‚`«Ehozá›Ýzü=mđö:uţObüRV¤Â#ą-'“"·'RP— "Š‘s @Âś–Śn“tÔUEj)Ź,k„äí Çä_Â.-$Öc±_°Í{QAŽôÚpţśÖ7ďDQÁ.~"Ĺ56eů˝hx'v–vüĆݦ’[›†ťŘéСs©: Óß'‡Rý»úäůŹlZmÇ|Wµ[«őxż%Ž•-é“<˛6Úť´h@:őhŐÓ»÷ÜŠŔVŢ´őęŃŇ�ço¸‘S1¨ÍK+äj=.ţÝĘŕ~Wâ+vŽóšÓîŰPÔ2kGFuWĄ{ öŽlš9«+‡µşI–”ĺAůµ{¤0´˛%ĺËÝÜó$íŠWpŘČ ćŻ6t ŽŤđÝźRÍť¨’Łîů_µpKO…ł�Ź©K«'?‘ĤŘO!¨P°î)tëďLHs¬i9ë-ކŤ w4ú˛ Ő¦ČÁÍs*u`ž–Ą\Kvüľ#ÜZI =ö­öęŢnů¬ý<j­ńöÇjm$öˇ¤ é…p~&ń:C6đ|Ţľµé_ŠÉţTghXW¨ĄďOëáű{Ńý:NĆ­wâĂŇŹĘ~OÄ˙�řłVß§%Oţ)k‰y ›ŚmęOR˙�„úÍţ.˘˙�F§ÚőqWgůˇIĺ(ů şýy«ý'¨uGćÝčשŁâłÎ}i*K‹Ź÷䦹¸˙�~Jř‰ő~´•jîas_˙Ä�7������1!AQa 0q"2R@Brˇ±b‘PÁsŃ3˘˛áń˙Ú�� ?ç®%j–“zŻBĄu4ŞX–^:Ľ=Ćü«Ođ)Ţ·śvç\Č‹:K±ł“ŢkMîD˙�˘¨n•BřŘQŘíÉ[›twäöΗ4hÔRÎŁů®Gv•DČ–1Dđ„âUáxëńĽK ĺÖ¨˛–zÄîTDWznWÔ±Důa· ­H±u‡‘E{ÂŇŞĂ“r´řf«„źŞQ™S—'ĐrĚ÷Ob%aصh{c—Ś˙�‚‹Ň+ÂgGA@©VÔx`ę/Oî6Ä®EnÖ áS˝o&šŔŘ©ńždX3‹ z« Kłgŕ{ňTü,wZě<RZjĹṫ…ęPŠ*5L~š!ęĘ3*ź©NJÖSŢÇíXu䯲,ś×Ţy”K‡ÜeRô/¸ó$\<y ĺúôťG‚"ġ˙�Ô¨l‡ŔX ĎIÓOm>ćŃt´GÜÚ[ŤúѲäÔ^¬s+ęąĆ,ŠŮüÝpŕ©Ě#ĆßţâÚµWQĄH  Xţ^"ŔwCŇ\3×R#\‹”Ŕ‡$şš‘aA*ÂŮR$+žÖEeˇ¸Í ŚŐîš¶ä7d6‹3~´Vł–‚Ç aNĹk€óÔ»4bBó=Ťg”.č}Dl<3Sn•Đ_2!´5ĚY{Ż+ĺ[>wˇâ_„®4ĽHsëS#A_bÎNQŞvRk5Y-\ţň^™äůźŔ®MŚŮL](ş©ŐlP­Ť‡öY™t}ÍS˘şË—yfŃ™ ô'NŁ«’uţ üĆłekMM̦Ý_.ŇËťçź[1a€Ý"ý˘)…2f˛Ą«C*šC™ZV.śŹŰ°Ż±¤ż&FŇürż…W5’3ÜÇ´!iQEsе˘–BÂO2ÜTąs^ť=5ą[KcYě)]Ł˙�®LÖĄD7讂¶á7ĂĄ ÔËQ‘Z´~JYÉŁU'%מćmÍCCż&”3ŔwÜN…q„^Ą7čVQz–†ś'üf|'Ęl,jÉ#Cqü2TĂ«­.,…ňčSĚάĎzł6č1eqŞŃ+•Ó Ć™™Q| ř áËś–lO ţH±2§F&hśű¸®Ť4žü«ă;rSű4“ôěČž úbţM˘če ĂTF••ŤD=Ădfw–ĺrZäÖÜş!˙�F¤'äČĚrŽś;|Ëü ViwąZb,áĚŤVŰűfmͬ˛fu3t;Č‚%B›rođ[rjÍŇXŠŘI»!dĚťOž/’/——aĽ14/«fuWĺŞä‹ŕ©ćĆň|ąŽÝ-eí–X‹Ő+í=ůaŞ+â{‡f˘¦#“„l¦8ZoŕâÇkÂgĂ©JőóBÂnLË´™á­Kő+‰— ú‹řŔ®Ą§Ä;E §Ţy™ÉN1t+NçjĚďÉß§ą›±ĺyšęSRe33FĂx•“ń—âH‚ä/1Ç̤çYˇóţEgCôëâAUQ$J´†ÝG’>e¤ý¤(ků‘0ńíąE$qÄúHÚŚEJ ™3…5ĎI-LĺGňC™^–X^O“;¸óCĂĐĘҨđęÜŁVbƇŮ ţĆýBʦń.!G™R«kâ<jA ‘ Őd^”gX•*— [M‰ŘzKÚÇŞ?i_L™j?âśJŤlv’ĆŚ«Ĺś<PiŮŠ(”XXĐŠ%WCxH°UđŞßo±î3áX3‹‰'rń'€¬•†ńâŔŕĄČŁ’ŘXÜCɢŐÂĚSQMî[»3‡HXěquëŇŮPŢ3ŤĹĹö‡‰ZżäXŞ1xŠuĚí?vhɨ¨}DO'®~¦ŘţUjx4ĐÔ—ďhŃrDđŮ {› q»šöť…úďß/Ý Ú†°âą_‰SƇˉâ*Q<Ž,ĺÍ•·*sBŨŠüÄD b®âZ¸pŕSQq§ňš«Uáç(^BM14oŠ9w?ł·N<xs>ŁhŹŢŤ„E\Zň}'ôS'î3ˇű×5+ĘňˇLjD‡ yč7OHłQJ(+šĽű‹ˇ—G$Cwˇ‰ jď‰ňö;ťŤĐˇóîDęĹ㦿ˇE>çqY.ç‰x_ĘůâĂ9Łq|ČěA ¬SÓú~“ţ6qăŘ#t}$XŘŰ–/‚ĄĐŁXE.˛ě~×COTŠä Ţ!Ţ(p!Ď’E’<8¨˘Ę!ĹTëgÉÚk,pś'Ôât—r”Çäŕ?{Áťäą ’‹úř+6CďwEiĺŚsÔq ¬‰×‘ú y˘Őô8ťĄ™ ^ťqC7„úNܞȱFŞĆĆÄ&Č{ ć¤×Ş dž‚’vhń^=ŹÓň­Ĺ)ż*ł"^hwRľz,h?KD7OĄß{ŕĆą!I^ÚzÂjv;OéćŠOěTWFřÇ'›L4PŘđŐZ‰R¦¬ÚJqĂĹs>“´ĽOł)ŹMúžł¤»™Ë±Ú]ůÚrݝ廹ĄNĆňâś1TŻ?ĎAę"8cľ«ŕ ‰ŞäjŽÜ˝ŽÇ~jĘ•G›GŇŢ\FC“đˇ¨ú5ť~ÜŤ®j®iÔ†,d±>XS3ĺŢ}ćŘů.łyS¬÷ř ˝ÍI榬‡¬»Ďę橯"4Po­—#|ŢŘŤ"·öoˤ÷"· ŢYťŤ(lnliNgʰcĺ­ rWˇ¤˘#|˝Ź¤ŢLŹí)čĄÜj˘U2 _‘ň±O)«ËĂ|ióU$Uţ*čÇö\°Ë9~Ţm™¤&ü´ť˘2–ăÉôďYFąr.B©3ż<ĹÍĹ*ÉďĐî1ŢUäĎťĚĚĺśłäE]ň#TěhRZr)|ÜŃf+s»Ł¸ô|‹ą˘U7–RÎ]Ť¶&’}&8ޙdžę[ÉJ¦Ňyó,,,ú9ňŔô4¤»™Éż+Âq ú”»ňľ_ŠĽŐääµfÜíŃJŽQ®MĂ—yv7gÔ}dvIÖKąĽ‘®¦ÉÁüϨďé¬Îâ~e°×$wí-Ĺ:ĄŠ Ć)Ĺ´úDްć‡é~L¤ťQ\ľę\ěv;rŻ,2FóĚĽ5×–/Jß#/? Ę×$>ÚYË2‰Ň^'šAňÎë“7ˇ›×‘9«D,ŠkÉ˝Đů÷fĹl™¨µ2$WŇSÉj7ýsGb<f˝PĎŽ•2"äŠ KÁóVeŁZ9x~®Ó~¸µrÉjEĚ­ątÉ™«Éĺ§síIhŚäňYźüe ű Jč†ě‡Ńś¬~ˇMe©ń4%i(•%âaýĘ%BC쏚-eň­FF˙�§Î˝,SĘjń;˛¶-=Ěö)ćŽlV4 ÷g»‡š)ÁuîśoV[ś4E…n^#CB/,_ę~ÝJŃł7¬ˇ>Xtšł÷.ué‹›Që§óDň3Ö)no(n3î-^7z‘ʼníŹţϬ¶(…V)ę!úé(˝U"‰PwćâF’k¤Úó=9|?ýź$.¨W…ó«Eˇ“Ô¤Ö,PŐ逅“ˇ„űšľOÉá?5Nö=©Q Z*łŰď‰ââÖ›ri‘¶3ĄbŁ4䦂ĚJă…Kîf§ácK·;qçÍšÔŠÜů=m÷äMDÎňY®wč‡R/Sťe™š>}Ńł!I)~Oo E±°Ć(i=a?~'ŇlsCű¦6bć®ÇňĄičą2z=dţňĘâ;Ë^_ČţËAŚ{_’<bč"8Ş;UŽÚ•ÂJćĂ8źA߇ ŠÝŚĚą űË·7ŕÉĄsSĺđßűč+ú!˙�ey4Bá¤ÚףŔĺ¶cű _r‚Üyq]/Ű’)Â~:9CíčÂüÍ|̡«–†fF†ä~gŃŤĄ'‘ąšhf”4–َ ¤ťą˘ő!üőµ–ÂÄüĆY›Éy˘é@Ş˙�‘;RHďręS9xÓ¤ü±˘Ýčzm׍a¶ăwÔ°že0ئ2¸ň—‹ć{t—”‰ cŚť™ŘLfĐś}&°îg<ą Â.­ˇ‡R)F“×IC“ óEŇGŠę¸ŚČ°u+a杏Ŕň®ĂŤtüOĚ‘¤őźĚµCéÂd´FUľçâYĘÔÔ‹Ą—A@B®d©ZŽŇ‹%ňcŢ…o ¬‘ư]útŔ¤ôÓšŐÎťm*ô8×OŽpÂPá1“Ś©‡ä§Oőć\=¨úĎ­źQÜ˙Ä�(������!1AQaq ‘ˇ±ÁŃáđń0˙Ú��?2ô=3C^cŞ-3 ě [a™°)Ůqä*×>đSL@k{›Ý˛x/ř†0}@HĚAxąňîwŢB8XeF‰ztÚŢ /ţ|ʉM*­â±^H§ĚcQ+śCŐ„ă×l}>†W˘˝+1ńRť;® `ŠY=�álĐËő8W‚4ÄoÝ®u-ÄĄęĄĘĎžüĚĹ;ßÉGH7ľ)°âNŰ=Ńxffu{S Irł|KŢŇĘ`WPU™ě‰L5K+§ú”Ö�ÎëĚÔŞĂĆRRG÷3Ü&0ú$=X{z„g´>Žg?ü âV¦TfTµë”TW˘…¬Ł9‹ŚexXË6Éâ7ř=Ó]ÎŻRîmł†ŽT¬\¬y˛Uf^Ңđ;˛duW˛îRć#Z'%ňyLöy é™Ëö–Ľ•†¦c¨0ĺ0اÄĆf­QŹÉ®áĚÇP‡–ŘDiůMzV3P™•‰Qő^‡ˇ«źF]v­*%Ąn `ÂĽ÷(]Ěţë„Ă`AĽ—ąËŠú—$±ńÔ*ňĆÝÄŐ#)gTÎĹ**˛¨­DŤşEăČÜ$ěxI÷‰Ł6Ť×·ˇ”Ź8«7†×ŕOyŚ1dĎČy%&#<Ll¤.˙� ŽZ)˘ń ÂîUZńčŻJ°|˘ĺa[şźŻB,żMĂQŹő<Á‡ˇé^f—Ž'…×·É•Ńg¶ńoSvpK¶iČ÷Ďé3+{$.6¸żŢ=6.ýÍD§M'UÔEm3Hw@ †Üˇ‡6h¤µś.w€¬AV­‰×, 15(rśa!ÂóüűĚZ,o̵7Vßî%ÓKG~cĐmo7řäá¸Ě;‚Ëý‚ř~ĐłkŠ™Ý¤#ĽÄkYŃ5G5{kšĺ°â„đOy^ŚýúV=GžÓ™Pôâpškqç”őN/PJ¶ŕ",Aş]^ĺC™Ńe‰ÎĺYMŮť3Ěę7 ˘ýš¸‰Ű3—´V]_iĺ8úÄÂfŢďrÉf â[dfÄŘŘĚĄuů‹Să™×‰Ą˛/HŘ2¶jóv& –Łď4µYˇrś!çz!o†ö‡¸Ҳϱ4ˇ‹‘�ť.ét˙�¬ÉÇtÁ#•wâ`ˇô”8qŞ?¸úÓŻŻő ¸‡ż¦:W«Lu‰o‘݆Βv‰˙�Ř+™rôÍÂoq¶Ë »§¦ůdĹBâh˝a»˙�BqçębćĚ_…ŰxôŁđµ>ňŇC-J]ş#,CÄŹz›şź0Ű\Ý!)ĎSIăG Ű6đÉţBâě—ŮÚLĂ:1sI)ů‚ Sö:‹zt}âz¬žjć\Éw¤p Çň8ůŚl¬§łAô&g^µ™ÄRÚ]L »ŰÔŢ3·Ľrń(¬—•‡ĽĘĹź,´i2©ÁśŚ¬cř@XÚÚąŤ[Ľą[ŮĄg(Ýb8öW0·A9µ“gĽîÁňŕăbt”Jcéî9†ą[ÁD¶]őEş…š± -©XnkÁç »÷ ěS2?Ú*ź2“�Á ósRÎb·sMJ: ň2ł‰KĚ,é6Új9ŢĽ•h¬űˇíűjń+AUż•” „Ą¦)‰^7ĎłéŹGQăŃ÷‡Žă‘ S7ą¶ ‚2ÜŰ{me…ěĚ´ ¬5ŤË 8â$úp@ďlŘU±ž5’5ËCQE°o\J'ĽÍ·E:Ěkt;ţ mH­řQýĚ’g.ě&ˇŠnŮĘC=qŕ˙�ŕ{JGŻhˇňâ¬Ď«oÚfÝQr‹›Äą{k, cŢ,ĺyqëáĺ"lPáŻdĂ4Ú˘CbŁUr»cćYeĂo>%×)S¸ŚqmŇ&,o§Úgh(ôó´J˝ŕÁ-żOoBŞ\kŇń RíK¦ÝŹ„${şv¸LVg&||Júj­€°c1’/g/Ů PíE-YĘZTÇÁSŁňFdŐ• ť±Ś ú„Ä@}âŞk /D K5EÍŔfe7Éłî ‰Qě¨zEőč_)Q$ŽO˝źâ©y>'›ž`©§™™µć6‚˘>9YO›y‘C: y":ŐY±Ą×#7Ę0(4*žß'aż WË cZgî ŤN}9ú+ŃĚ2e*ÁÚ8R­é,n2Ö*Ľ|ŤÇxÍĹËÎ %¸đÎɱq€ÓCx`°TĚxś‰Ąţc˛íd©|%ę"Öťę bH"ŕk¨bďŔŚ*x.@ĂQ°ŻpŻw0Č©ČJ)vÜ•nţ«6}vzď9ÄwŹEÄŃË ´“ť›}3)c0ômÉYÁ›ź3/]E–Yă¸Ôq÷ E(Ş}ćRS„ŹC/3ęfj^<ÍĚߪÇŕ ÂJ«Ěí1™…«ß,ꩌŘĘŘ;�ŞŐN*QŽż ‘ uąs±f¨‹łS;nŽ"dŚř…3Wď.*Ulˇiń0ŻĚ —w«Üކ7 ĂŐÄcpŢŻ©¶Žž§«éÔ¸Ęmuă×+ŤÂl0ŐŞqB"3(tďy–ˇĄă×yÜtđŇq—.—Úą¨`=ÎbŇJwčúW¦*>†ĺ<Bó iň„ś˙�h9ÍC´ľÂŕÝ…”¬”{VŞ€ŞË›ę_í ¸¬lŔ$i°=ţҸ#nČĐ3ťĎQ ţB[ räö¨«^ŘJ0sĚ ˛Ű]@¬0mĄ•Ô0Ů…N=dČ´ý˝L mŽŮ1ZÔ[.ĄEj7|y‘/-˝łEL6çîąµšyK…€bľUŹŮITäGz-ő2ˇÓ�ôąún[Xđ…Ö)WĚsjâş%vÁźJ*1=¸ł°±%39U®*Ó‹'ÄľyAĚŽ»VP&őńŇv+‚!¨e×0‹v 59qŠöŚÜ_´_Z˝ŁP‰˛ §äC¶]j–冨ň˛˙�h®ýB€éÁQú§'ÓŹCQ*‰‘°Ś®Ď8bá9žc{$l:¸ÜcSů8‚ŐG– -‚—ëć6ŕ0‚Á–`L's@ä°Iů â é¦0Ť<ů*/ EĚ:~Ń%\Ż^bJ¸’ĽŇ¸!‘ ‹q0 Ľ9żšŽěC ŹąË8ęŰŹă–©p¬gśţˇÖŚYp AĐŞ¶ •/c‡,uŔćbVŇŞr:zfýeĹuরhrĚ9_BÄ(ÍJÍI\ÇĐCĚń5úâÜĄÔQżMmC‰„™2ÚáĹä]­1Ż…âZ×ô,2Äd8eş]_Ă©‹ËŁI®}ĄXÎćŁG×CÜtxđeuőĂnÓp_Ť’Ó80k˙�bbŘŕ¨L‚ĺFÁrâg,ŘoąÁ�S2™‹®"âţt z#CoÂ2ZËrź©ËôUµ?e'îfÁuŮg08ąô}F ϡ/és5ôŞÇ’‹űŁC.ŘéçÓ#.’ŁMŔ> XşvĐ7솱b/¸\ŇÄÚvKâ Ô˙�SËëü‘ô!2ćmęŔ˘ő.Ĺ(@=˘žhŃ5ůő‡j`?ăsČQ9 äKş…F¦Čňbuń0<\]~ MWUĚżvҲ*0 qRŻ©¬6ĘĐJ[ňÇč cp©j6b ŐĆ“ćoQ),ĎMúď4Ęľká~e-­}f•&[s)©ďR– ęîŹCň›˘Şša†d˛üšúZ`{k>j`Lśż@…ĚËŇ31čÁÔ(€ źőĐc—€}áŢ#źě†¶m˙�2őZ7ŇńĘĘ´›52ÖĄ6ša”ĐN « ÝîZOtp€“:[R´;ŹVÔÂPůĹ~ż>·¦_¦€»3‰EŘÁŽţ­Eú°CgÓkfĄř×q›¨A8yŚm¬Ó€)‘ď‰ ×M]@?lŤLD¬vĽGÎ5 Âł-ŔśFspg>‡ĽG s;SžDz›Ěި`´ů†í‹IČ7Ä!3—2!i> oLnĚşäľy™žˇŤ±l˙�ę[aĐQ;ú’‹ő87`¦¬~şĹß§>‡Ş«¶«łÚ[Î|¶źl’9Ú"§ar­Ę¶†Ń¸ŐŢT|FŔĘĽĘ“»:˘# ‡ŞÇ^‚4'ImôrŻÎw·¸ŘďÔcW"]5ŰŠŕ\AP_i“â7ńŮO˛\©ąJ°ŕ‹Ů\Ŕ» w2*Ő–QL}.QDaVýMŤń/ ¦>«8DôC˘ äÇĄý ČQÔóes+ĚŃlÇ­Ôg í®ˇBĹ·)Ňĺ®nějl˘3W/T¸÷vł©áń&QŮé íoç0͢ĎöĘp[:˘ůÂ-O2RUeäĆsWÇ8KwÚR8\[ ŰćŇŔŽAâęRĆÖ~ňđP8Š$=ŕO)�ĚţÓ*Ę1ôă˙�Ż•YŞ &PÔXzßÔF?F3¸)t7ÂQşř†MŘ3~¦ \Ćx„ĎL.V…Ę9¶żx­Žş3~ŮPú΢Őc•`źpa^†Ş&€ąBáÇIŢf™Ľ[÷/"§�Ľ@A•K“7śGáaYerÎ&ć xs1Ĺ˝­Ă…aÍG§4m X µ{–őK!ɶś'ѨÓč«Ć`©xŃÖôÜtf-ŻCpŐLa*űAH}Nń©˝ ËĚ-HYáQ>%kŰ0�Äćŕ%ž:ÂěąKŞyő+qȵó._ť'™*$Â;kΗĭlÁm;zTnú D¸=öF�nfe7{ëPŃ5fşŤh­Ěx•îQˇůs"X+‚¸Ü ëŰű‡C”„»’óéç2Ř{Š],(Ůő ÷ęhýú„h„dl/łď8Ť?BŚ•ĚfÝöK9ć(ý'S.†&”˛áŕ6˛ ]ôźą}@ľŹâ'ŻizŘy0ÍÂú·Żdn[˘8sžu,ç§—™˛6ĎPŻh­kŹ,žĚĺ` Z.#ă öR¬á ć¦:ę/Z&­°aÚL+Çw©ȵ)~ UŕŤl‚•gXŐşWřßiKTé©hÔ¶+?Eú0>ßA<áŽŰü!ů]d¬/ő:‚Ť¸”őé|Ć=(Ü'ÂîRĄ×Ó~„:JUwî‰y‚hůcfÝŔ Fę<°k©“M2LűÍ‘i,ěZV:úĺÂÖÎ[ń̵–a_Ź€™EÇ_)DVÓĹLD°&Č[i5G2úźő\˝!D…{–Ő4cĚ­Rö+–Â>{‚q§ĄŞ–Ö "Q¸Ă1ÄS>!K™Lkř"ł)›îu4YÎçF§Y‚śF/éJ ČŐĘuźŕq »Ů™Ěq*L•†˛!·Hű˛%L>€űÎ޶uŽáPy "¨·Ď5_Gsc­—)‚ď ĚÄž*"Qc®% g“dÖŻs™v ŻôŽ”Ľęó2µnźÜě–®Ě5p@´üĐ=÷ÄŁ€VËć .ŽŞsÎጎîHťM-ąŹŔ‡;Ś=HDĺŃ\Gsô 'r×É„ LĄĽo0zJ{LĂŠnZ{ÁOĄ¦ł…~ăî]x0zWц\Űű ±9†Ď0 Ëjţ2ć^„­ŹžbŰF٧l䜸¨nnAM’Ý+Ę  GĐ…±„ÄćţxĚeR§ ?Ţ67ć®eÜ»–ÖÂ|ľe=ˇ:Öwč/—Ä{ťńd+E EÔŁsÉTKîT¬Ç,*¬~ĺ+ ö–? K ‹ćSśäřbŔľů”î¶ ńîĂUş×&Ľ8ľň´˝Ú˘ ŘEě˝Çč)¶ÍTihćîăżV`Y–Ł�\S…ťń0+;˝‰“0úgĚ*”őL€(nㇴ …s—Äg%“±/"ĂĚL—9-đ¸ÇŔ|‰4§č¨…ŮîJó3ďDÇĽ˘0Č~ŐÄCŢ^†Ö€3 ‹/_¬řń{¬ÔA„•[/´Đ®`ińo¤×ôYßóČN+`íGĺŔsf-‘ŚĘ°Î/ó6‡ m §ßV‘kÜËř–ćiśÇ(٬¨i®ś_†Y@1™Hň­é¸ůč>ÔąÍÜ3şÉ+ H\ó>D®¬\;QŚpN×P¦=-v83™Ó̬+a´Ň¬Ĺ˘?žxlż1ë¸i.Hdr˘Ň&`PnĎ´ PčSěvTÄ,ăk ­HVM¦T$×ŐW­n»DŔŁĚÍ=Ábš~',tżo—jéWĎ9¶ąxnXŘř%…/µÄ! nc*íl‚Z`ϧ쀪*mo3�ŕ©Ú1ř¸k«ŮÔ}Âw/—»4ßĺ/H·BŠx€G6ą{Šôju8ŰáŕÔŁ«t·(=«ËÄ &„¤ć=_"ĘĽW'd¨¦q#ËŹzźxA/"KĆ̤ŰDáLqÔ ´n X5L ÷ Jźu(•Ó“PÎă­[É &mů‚¶ Ux—^¶Y©cŘwÄ÷ĂřE6}ČŔO»´Î G˝<żKő>Ź»îc´“śĚ¬?ňpśB‘řdYYĹŮô 7E żcč–OO4U˛»ŤLgBĺ‰ţ†ka Ôćý×3Ň\n:Dă+j|Kx“’!^¶M[4RŔh˝W´EY;·ĚÉ!ys b|WR×@H>X“`-„˝A* 5ž¬ČćíÜ©ĄV3věF)®^¦–çŘFT¶@_W›îÚŠÜZJ&Ř#§l\Dő©xć]„T§ý±+älLX–EDKîYÇo�ٸ1Ú15Y‰5’ýY”ú6f˙�x)…8 aţđUř„ŰQÁ NĆe~r2ťµýóa|ńQI€8…X\ŔżB8Ś€61}¬ěU]Îúś¦# µĘU©s=ŃíxžAčŹ!¸'´¨v˘nżş‘ß˦;h­Ó.—\Ú7Ădßłrd_8^źµ+¬ ç0%—Śű@†G$A­1óG%JíÓîR¬P.ŁŹ)gcÂ\iÔÎ*~žëԦʚÉSđĆSWlŞň¸ňUĆH!´Ź†~BZ=íŐ żző’ĺËô/�ó063şŤvĚ“ _áí=łĚµ)űQoď÷ňąĽĆ·Ä"AÆ ă>`±aÉ÷„<¤RéćŁ1­–ţŇó2 ńvÝL+°ýKĐŻT uIÜ�ÝwL@ÉVűHĹd\Żmă(8ň5űÎČü‘ô;]{ ‘ ¶Ó~jłĘÄmU±6űĎz˝˝2đ¨"¶SÚYÓ¶WSŚ^˘—/*‘NÓŹWč¶Ů]†.ż¬m›»ř[Ç™š? HŞ˘jČxJą>Â9ßb˙�©ě$PţcŘ ×HńŇ<•;’Ë-ʧÍsüĘü±~ЧWłŚĐxϙΟˇó%ľ‰ŃA™�eĬÄvn}ÄW„8±Ał43*Pć(M@™ÓI¸!b˙�Ć",Žá†3>ć"hŽ˘ą=Ë/ 7aâ«é ů©ăĂiâ$L ŻÝZx©ŕ&5äc Üžćog‡qÍ«TM3‡Đőâ-Î!Ż®ř•ż:•>3Ú˝Ĺ'oKRd¶®á˘Ď2í§iŮä.đܬż)´p.V3o#îk–ĐM—Ďp.ń?§Ě¦łďď6 ŚĄň—ĚĐľ=S�Ż+öEqS“˝żń …ĹŻijâŽ6R5Ä<ᙎŇ*ů€ pw6Ubr}ç`{śÂŰ‚Ćg˘ZÖŰU‹ĎQQ0Ľ â|y±‹|l«}=Acuč­řýŘ ŻĚßív oQ%zÝ}>ôaĄĂ&JJăŢQ([&*dńş¬eě‚°Pź‰dG •ßâ„Ŕ€ÉOşÁÎe˘ß™Ă>#ő˛ 8IFďU‚×&ĄJ@űđt~ iQÚţ!¤%2ÄV3N%›şŠG<ŮíRĹŁŻ{?ŢNpÎüÍ+«sÖĹçÁUšŚPžôL] Őb1rŞŁŞÜ ÇÎQAA=]~7qĘ2|DnćN@ Î‹áĄâłíÓoFę3O’WÄk$c5•,ÔÂWˇz‚ Y‰xß6T|rËNě ˇeLŢć[áľ„ZVkqŇJŠ“Rťw´đ ´ÄcÖGĽ5gÄ+˛«cď©k †ŮTłÂľňĄâŕđüĺ0Ćý:Ł3*ĚÝKRÔŔv¸ÜĽŮs°M¶™Kŕß‚*(˘-Ú4„ł˛Ţ JĂó*T®Ź$Sµęßľ,­Ë›J9cLJ™Z uÖ9ÄćÎÎb]ľ™k“ĂŇ :‰¬‹H@v÷* Č>˙�Iă�F\JKÜá<d0ç †\=ć~…–â<‘ůś¨˛>]®­•�qA3 ¸G”D|lěýç?†ĎiH'Pď ´8 îÇj+šĺ—¦‘Xä–iťzń[+^řŚ·şYp“»s#,G1 ŐÔ÷ÂvÇ1¬•[Y–—Y×ó2—ś0ó8˙�ćSą˘=nŢى›H•¤b˝Źď3*súyÎ2ĚŰĚYýŃ<Ę–Łň>T5•.ŤĂ"(.n>Č”nVŞYĄBµu4­$ľ 3(UŘ<1,�!WČ4Ĺ ;^÷ °ÇcĂVUˇ@ö~Ă z ţΡBĹă˛ß)DŢTŰŹ©x]đNaPZzb¦Ö˝´ţ0ŹÂ ýÜ,·® §7UPŘÜ[ÁĚDÖĆ „tí0ŃKÎW™cŢ%Ůe¶!¬bPűE?ěćţfBÂü˘w˘p„^#×@ó¦Ťz cŹF2J°ĺv)rćŻé(Ţ]Ň~ ĘŤ <›ĚĆ·Ü&śE<f)âo~„ε¤9 rD¶­ł[ŹĐ•©Ł‰¨Zb ú%Ž"˝̦îźíAěDÁt®('9E˘~ ÉAl˝âf ůľl–×¶ T ‰Ť—t–~đg ”*uĚ*˙�’XҦ¸ŚRÖÓ©fXęB㏛˛˝ĄŘENŹłx<L7/gĐ Ć/ŇRŢŚçĐÇý|«ú3+BľÉM}—ŢŕÖU3śŇ{_S~¤VŃâXC®83öĄrX¦BáňžpĆgîXĚ €Ă·0txÄ—Ů2eU9Ő‘ŃNŃęł·´©Ç.¨©ń>ń1’Ţ*P8Ú \: ˙�a-›h`ş €"’á…©ů¨ř‡RäbZÂú}W3P^pG*.Ąs5 ś#Ü#Rż,Íj±•“›š%iMúEd7ŠŚ)\ňL‹ů©řa‚†8ýÜĂt·=0×ň‹÷‚Vw2—11Š„çSÁ8.ât¸ä¦YRŢs¨ uµóď.őÔ˘U2Đ�î’÷\Ä&ß1]Tó¤ŃtzeÚ!ü3™Lń*Vî±d«Ŕ.($ÝGó8™¤Íđ…„Ăݨxy2JĄË¸>0ŞořAJţ Ěb¶îęĄkö†TŻ'SUTĚňĚĹč7.2ŞćżÝb8FQž¦Q-·÷euś¬˝L> ľa$]5^{ô:W¦8ňA,‚12ęQ.íń <‘ˇó-éľá÷Dü«Ú3g_Ľ·‘ŮĎŁBś± ~]ÂŘ35ŮsëgËV\Eh[\!áŰărřj›q2ZŐ»ÖĐ,•Ż€Ŕ©Ćă —=Ś"ľ|UDu§LFýĺ,˝Ó€ÝÝw4Âtc63‚+ˇÁ.˘űČÓEöÓî: J^ľ"a=·™ ř—Weݏ4ëď9f÷ĄÇ BŐÁŤ˘*¨2ś˛š˛Ę–Ž,ĺŹEěŤČC'â4w ::= §ŔËúŹ%=N\ÔÎyâFČřx3_rd÷NÖm´Ëb??á�—çÜ~8ާM<O‚!~b+'©»OhĆř`č„ŕ–K Ëő3:—lj€>Ý߼XCĚ&ˇĺď\¤f44#bçąĐżĽzŞQ™ůEĂŐLúí9IĐżË(vĐ;Ś\ˉ§fň‚,ĽËßěË’lr&6w2uoÜĎK±xĽ—ܧ]đŚxď“úśÁgĄ\Ę€UÉż3ěŞ'𖩡91~9ź„şgéÄ^@EOôĽĘEŽŐë33Ş­2›ď9áŚôëY9%…*µr‡=ŰQlvJŽíżťzŹ…ŽřKäÔĆ"(ę\Q~9›c Ž Ę"Á¬Çņ¸Lpcŕ¸x‹…ĆŃöaÁC}ĄUĚŔ´•[ń5wSZęŽîmtÄŚĆ�3Ô”5­ő�pŘę2ôę2+t‡:řÄÖ˘úî Y_ÄÂ[n¦F‰eŃŽ¦Ž;€4Q´ąDľnßżęm0#čŘË9![Ýăí*ÜŁt÷Sa>nťË¶×ĽSżČ:!ô%Îě`°߬DhâÖ?0 ý­¦P®8ĎG w+…+ŘüĹ{ŔĹjČ 3RĹ%sxŤĐEl•MŹ ĚUX ĚÁ¶ Ěd÷îY^NbgÄŤ®XÖÝ•ńô0p‡^n.ŰÓ«•5{·ěŠ4g„^SŃ@††jn˝™#ţx‚'UyNűU,]ěőčĘ*äŤjÓ!ÜĄŃVË3¨ťĄ…Â7ş‰Oůu8šzN.?+[dˇ~ďyµŔŮí96őďąĎ´ůf&]Ą7 Í.$–ü1®ř}.| ĚKBńXŔíŕĄ%*ÍŰ0\Ť?ĽyІ%îoÉŚé jć Pm¸¨ż3%ÖsđŔ*ýáOSÔ9»ěîX‡śîo»e8Ô�:Ł“·ŽČ>Ń]îŹÁş®IaC}L×óoҢ ÓžgGEéW“ćZćáŚíNšÂ7l¸%°ĺ™ŐjŕűŔdł–*‹ĺqŽOs˝¸w`é‰[—F6s–.}^}<L9 ±đwî&+ ×dnëJµ8ý@óIÇ —şÇ3 ěqµP·Ď qI@­#’ U axłwŮ ]Ž„ŹĚ„Ä •­W”ÄN4w66ĺ›Îş¸ŃDFR˝%pć)UO)ŕÄ5U72ÁŮ,ř&°i ‹›ď5Ă=źîZ˛ű ťLMý„95çŃ…·âő)—çQŇş —¦Ç ¬âŚa̵+q´¶‘q ľĐEŞőĚG'í,kP‹ü ÇoŁÔbXr˛ŮĆ" WL°RfÍÜĘĹÜĄćU±¸¨;ôĽ˙�NVö>Ř…‘qzGVŠe}©ßlDp¨¸ł»j§6J2fY]x%J/ SäŃč"s˝ÉNA:–BQ„śW÷#Öa<¦ĐaÍ\‚Ď- T®,yî1›wήcĄ>Ŕ—o¦)‘ݶ`>üL!,U.—€!2¸wpelń ËGLoUkÄZŇ­l®cc€UP‡ůßŇh×ů~=(B»P«\÷íbfĚw ŔeÔ Üă ¦űW ő ‰žQ=oaÚűĚł©×„Wľ«š±ąŕ3™n7Ś‹PË›�}j*çCÍâ2Ŕ}s,— Ŕż©*“e2„*ő˛bŽ{€šç‰Éćf[ ş7ź2z,LőÂJ!–}€Îtř„ýĘ ٽÁé~:•Ą„(Ş Đx¸z`¬2¦ş9¬?d¦Ňpaɸb¬^ `Ó,÷6ßUÇ@ü2˘TámżX5±#é˘G_ĎŮ‹M±_ŠVÇ$ŢjqyĘ;Yű*c aŰC‹‰ĎÇ\}žtŹ/ů/ň”Ľůó/˘X>Ş'źżL˙�r}ç0Ě ­mÎĎG$Ą<tÁUżhä´˘tŠey^ú˝Ďś×~Ťz§Š;ôĄ đĆ˝—Ś*R-o¸Ą9¶ćŇÖ˛pO‘ńöç'B=YÚą™€đ¶^*ő¤ 'kőó-@ű±˙�±Űí] ĐU4XbŠsţ^ŘRŞňµ µ•żoh˛ĽĎ¸ŹLŚ×ĽúśQĹ:&Gő;˘ĺİ@ľ^ú”ŽÜÄ%úĎ3—Kuţ/ž©‹šzV ÓŇ%·‘菠÷ˇ:ÎX} ÖoěŚAí°á—Ȧ<B9—ň—Âdϲ~„ĂËâ�Ż"QMąě•Ýz Dv=ž?㏴¸×:ńq§‡i]T7ŐĚ{ąµŁÉ�ŁÖú<&Phz´<桛óJ·7_´ęâÝ<F[đÁ¶*˝+ěöůó+#9ć…!đB·ö” Ťą-8ť1IĂ´î:/žŕ?qć`ůAšć[ĐB¦Čŕ…tB9ůP`„Ä,µ>䯗G$ˇÂ˛y§e°YŻ»™˘Ov´ŤözcżÄy›{Ńď sŔÓq?ëBŻÇ˘–źęá-wŔ~ů Ű|µ‡¶ vm2ý2 cµt^Łłë };ńl»uÓÔ ”žeđqÄVşÎ{…4¨=měu x?’]&�ĺr”·”LE·u¨V]/0A~6X<‡ütLŻqî!7UÄľÎ4Ŕo,§yŁé•”ţĎQdŕ«ď,6čTz"yš:”/„[dű~¦î0ÜqýĹ+î%›ą0>¦űR§ĺ÷ĂKL<M/G ]'¬Â-ŞÖ×3ď±l”“ů|ˇméawßč ›čťÂľG"î8%M}˛ŔÇXďIĂżţŽ}8żM=^ąţCŹÉÜ´®C‡L©y´™É5/tÚQ|µK6˙�”±UźćŚ®fJŕcű3KĘÚyšÜw.Ůűy|Oą”2#Ǧ^Ů8x…OWĽsÁśýú—׫žŻd·…ć]aAj8µĹ2E|˛ˇ[ű™…Â{®Ł)#@jßrÄ’×cëA8Ť7 îóRŐYbŽáŘ…ĽŐ÷—ŹĽk ̶a§ëÔafeT*SŮOľĄŹó50ZR»§Ď.'9ß2ń‘0ř×đâáÜ=9×ňüz8˝˘´“?ô%Ôâw<zýyů™„UŚŃŮ2°ČĄś<‘ ¸¸EČHŘą–fftšs�sťŔ×ÚW Ďą¸[?çz’Ć”aăÝ1­jtËĐM%¸48EL8ćbNV»ţeA)Ă·ăę1¶2ýňŔ!{đ3 )jźi«kéÜ*öŕ±]CrÝ/ŹFP±ř¦’’Ěäҧ�|ý˘8«ÄĚ˙�î1´łP»c+Ü)|%Ę‚iWtýÉŹí3ňž˘m›´4gŻÂ:Ş*®ö”XžľOř7ÇRjSĺčý>ůµ)ąµÁŤŻ8Üm@y­1YÄ2Yá„đżh=łRS�WË/?J®+Řţ§wâŐĘ´Ćw4bÍř–¨6d˝E˝e j WĹ„t^ăŠö¸>ţŻŞ\T!ÜvźŤD.!0ř˙�eŻÍ0BŢ56÷§¨EM şNˇýÄ1Ő`ĘĹ~h·9ŹQÁ9Şýăë™Î‰ełCrŚą‘{M‡nŁn! ˘&ńSđÓR˛µ§%YČŞ¬Ĺ¨‚˝¦óo ݇B(źűRŕ›ÉCËOÎű!Űť–ŹdcőĂ#9«M]±<Őż1ŕ…Ć4eSlaß)¸ŔôŔyŁ•Ń<ĂŽ¤Cü ­ŠlćaŢ´îŚ3 zR0…_K‹~. ¤á»ôzľŹ®¦ëc Ő><~!·úMLâŘŁ{ ŠÚSírźhvĆiV–ZĽ‡2% ’ď¨ĎkżÖó^¤B,măű‡2ĺĐ­NY^ß™˛ö,Ëmµu5*áŹĚä9šW˝0ąp­ńgÆ)˛nÔ~,ĎK—čO+˛Śĺ):gń703űźŇúK8¶ž%™Ľ‡üË*ó+™¶ßŹćfc[¸šŰˇ¸7oěL€žçs űË MţĎVKťaTÉUĄÜ,Ró|!rÂĚ«(&”.¦DY9ű%‡îĺ˙�dxj[WCľÎĄýX8éĚeăŇţ‘Ö’ŐÁíۧq"ňł2wVť&îďGRÄ >Ó-âČaŮŰ˙�ŠŠŘÚ˙�qa衹í>”d•˙�5(Á� °lg—Ďy#‘ŻLĘ@F(ĐŠże¬¸ąüŤÎż†~ľf See Ýnbk†Žežá•s4ď“�{]”÷`a|ĄĐWĂ çÄIj§mf^Bë÷Gč Ç^®Y˛ˇGQ)-ĺŽcu÷âŕ€©±Žę‰«˙�>€\_Í)`¬N}J‡Đĺ× ‰‰ŻD׉Ą‹ţ×0ŮEQ˝„,Űů™]BsGş,–Ü©NY/¸‚î˙�Ě vjWľ‹žŹű¨c/CNa;_&Ŕţb_V5Äl0ĘČűSÎN2«1÷‰Ťßs.ť×sŚ -Ô^âľ"°: ®}çGł÷Ůüú\\Á6Ľ@>šo˘ ľűÁj®ýĘEç2ÎĘĄ‘QÜŃ�ľY·Oućh÷+;EËčEć"sčF;Ď{qk˛°¬°qˇÄę±/Ś˘´[1 ĹíĘc>7pt«?tKHhäb8Ó)7°ŕ˙�âéÓÁţËŕf‰ťcÓ\«ĎŮ§Ţ Qąn (p'„ŇműLk­ăü› kń�íÍ}™¬o–QőÄýN6ú Ą…‚)T+=˵â­˙�äÂÍ«őGąâń2%˙�˙�p[5˝ĺ-ąˇµixč#„eĹ7-±Ě·ĺqŐfŰ KĄ>řć[HTŐă=ŮéżÜ¦~L° Sú!€+č=oĐÖŃlÂ76;e7ęđü¤ôAç‚;°·ÇR‚µę<BŞ •ŔÍP]ĘjĆżPÔ`ĐËs^§˙�ÄůJuÁŹ—•ÜÍVłvő ˇÁ÷1d {¸+:šÝĐ}č_Ôé’s`OĚ!3-¬Żµˇ}©†`'’^g2ŐâWeÓůN Ś�1u,ŐTĚW©ł‡0>-7®:‹Qŕđ”Eżţ>= ŃŮéqbËśBe8lw0:DăS.Ń“÷ r¨ăDËßĆ·#BŚĹ(q˛{@MńÉ/U1îěgl機žs üą‹˛éâ7ŢĆů<L�T;;—ĐĄÚX+Ox×ácË-3,9üĹşz€DúćŻţ*Ć˝~eŮ =ĺ75ÚŰ üć;ů‚7Lßń?çâ `R2·śĹÖ0D¶+‰›çBs8{ÂwM§^„iS_Ý2ł¸B?Úmra5ö6«+<!0üüîa‡!h'śDŘV:ŤEµ”~Ő×Ö·ąŁÄFţl›Á»âkžbW,żą¦eÍî Ż)*Q&a~ěpKć?˙Ä�'������!1AQaq‘ˇ±ÁŃđáń ˙Ú��?!§ăč8cĽpo´w…µMJ6:{;cHNf€§Úá\rÓ_ ÄUÁe>§kq#N#Ȃމqe\ĂĘ|1žĺĆPb‹âQ6ˇLđÄŘAX^}ÍĂ&U.á[§âa‹†‡4+”ÇĹÔÓ7:/S9•ÂdĽü;ů–YW˝O[b.qĽ˛Ľ%^¦4ÜÄľć8 ±–ŢsÔ×7S‚M“Ë–l Ů¸6÷ÜÂŻycş9”ž äÔ¶ŁŘź¸ő ~Đ/‹źŤ]NOx÷-„;äł“ĚP¶<Ĺ**„ˇUżsSWDn„%@ć‰h•0âFŻ‚QŞLL…éö@·Z‰Xű“<]Ýf^Ţ–a’uÎ"»‚ç•‘4‹�—ĺAÉyšqPú\AÎsj+…îWňęe˙�1ÇŠÚĽř”ů`Ë:|‡p;i7‰iđ"Żň™'G\E¦¶&AEě{řâ-´XôźdmůVZţĽ‘P»•Í*Š"§Ďę" ĺ:‚,ĚW¨F–Ď3e‹ŤM„™-WĎ0¶Í,nżŮ‡ĺš€«\VˇŔóďÄbĂÁ…¤¨ő…@©fĚď9ŚzpŔěMjű–ś21÷B.Ş:+ć-KÇAlj›˘1‘íŃé“~IY=0ç©{XÄh70&îa<ăHĘ=LfڏSMIWVYRL{cBĚ#ěíáĎ-VŞ.°fÁAŰâ{)syáŕeŽ5îVÝÁ9×Kwř¦qíÄbí\Y˛óRÍ’Ç–Ŕv@8ę;Ą6Ŕł]LSű(–ĚÂópăQ­M'5¤cpÁ(ú€q̡/Ä{ÓÇÓx 6©íz™`]ĘÉ9¸ść˛L’ßP…k[RöXŁeb©:4‹ ߍcÜęňWq…2°)(Bđ<ŐĂé’‘˘U|FŔŐěŕM„GŚ¬Ú Ut_ć]Ĺ#Wćiq•fľeű­}‰žüJ}’×´ő5K)ŐÓ Š@ŹuΡ'•6awřîf…^/;żRŤ–X(ŕň0R±‡#ÁŞŐŢ—ŐÂŢá]ĘľŮĚPÇ™Y8#y0’_—ź©YYŞk2/cŘŻ’ľÓbÎş *`ęe ÄV†‰V›xĄMJÉŔtÍ×r‡7 Äk„ Ż÷(Ú÷)–y†fKÎĺMLäĎl˝ ‚ůBŔínpcâMgĵşĽ„ Ĺ˙�,<şüÎŽŕpW^Ą5 « q–_)öfîbŤ‡o>cŇe‡35%1óîb(ľ58„Ü«ŞĚq“÷ ,ÉŐ8=ËTQŚÖôK8›°LŤqěj$ÔqIć\6÷u°Ľ‡w�m^¬)S=kw€Sé—¸Á±óąbäŔíÎŕPĘ'<e´Ae™‘±Ýukđbµ đyî›y…PذjXÝ@ĺNú„úĆ] [ćŔjáowZĂ1»°#ź’~z0%u\ÁŕΡIŽ^Ý×uŁ»™wR 8zaT[ÜÓq¤É›”nŚű•ŤUĄfv9XâVë4CŠź$ÇyŠH^¶á]ĂÇ2´—şˇ6ăĚóOŻs3`Ĺnţňąŕ*S ŕZG-[BW@%/lĺć.ĎYjÄPŤÍCČĘ2ŕçP+Ş˝m�ý‚”Ľń2ą´€­}*ŕZŠ×É*¶^ rŹÂX2–ÉYä…ĹäÔxpÚŞ×÷˙�qúm î̆PăŃ)¸g‰Üó4M7E1-ŠQ<·-“Í}× {[}Ę—ňŤ˙�ÄĹ +]ŕ­Eb8%x8|>eŃ ĺŃÔFkťNźĆŽńIĘŇáó‡ä Ż^ćÍ‚qń{ö™Qµi}ť>y÷ůB÷»g|EĘ] Öâ]ŢŢîů>Říę7şÇ3ö‹ŚÜćrĚV§†ˇGąÍę.<Ŕ镏ÔÎ Š4ÝÇS5™ÜÚ_-{‰Lĺ·¸Y$a„aÄ ý•8ląńkqi´Ę(-Ѱu3<¦e°dń;&ää†>Đ,/ď/:Ŕ¦7ĂWşÍ_W(®óřúĐçčT§3Ő Úëćg ‹SĦő/Őťńč™)ÄhŘέvőnĘ?ß×-˝ůcµ LN+CDmđp˙�.&•ž#ZŘUSџܺő‹:żwĚ!O•¬4Ľâ:ľéÄRő~ ˇˇu'¸kňh9Ç©Kui^[ç}L#�ŻŹ™^¨•‡¦ř•qZV3фՉä<­¤ę5YÜó=Ěىm›eZöú•l76RJ"^ń(ÓmÔ2LyÖâ´Ą ň‰j;Y¸ŁŘěŔó �7AŢ% OŠ)Ď8â,*­�Qf\Ďd6Ş|ę*"٦a"ŤĹ·ÎńŔ¨ŚVřĚi)ń÷.˘í+m÷_›Ş?f^T7µwÄ"×/qěăŘjşŁö†ţ¤şĐĚjľb¤h˝Lżu1~ô.˝ÎHz«˙�Ř=Öşłą]çU?ÁĚą¨©ň‚b.Ärp†4B±MI%ĺÝLó.®q(5i*/ScdQ©/C ŕ†Ś°o¤Ü—.›ü z×_¸8&€ćG' q.W "îmř€çä.\˝_X—Z�qVď TÚ°ˇ‡gěŻ źncłU„ĂYfK˛8.Ą1ÂQ{•đeT*÷·®ˇYV áőe­V#ÎŰĺęy¨wů @ź™Qćčq{T” ­Í›k °ás39îYPĚ:`W¸1•…QňŹDC»B‡€áÂîćĹđ^ĘdÂ…ă]E TáGĄťÄ–±»śW [¶,;Ż0 ¨ľö”Ti f=bPý›krúĚşŃ _ÓFq)ę=âűŤ‹ĺá-ŤiOw PŤŞůEŮĂA•y⢼V,é;zź50iń0­Ç`3S~G<ńú–CMŁÝÖ–áÜ)ŕ“t±¶žţe4ZYpŹćRh&~s… Ŕíznˇ,Ąl˝~#$–o…ţ*dp¶Äe/¬š~ e| âgĺŻÔl/ó 0 Ń™dQ5»–S™W¤Ŕ'¦.\>ÄeÁSesŻťGpÁK7†*±ËçĆ ŞJ€ÉĆx•…‚ý1*Ů2Ö [Ŕ9ftR˙�ćˇÔýËŠd˙�hÇńf“3!CŮzĚhôş, îĄŃwŕß3P¶ ·ĚUëg:&PQ„Ӣh?™U–<&l7%‡ÜVNś,. |đܶ¬ŕzú°Ż¦&ŐiNq¨±ZÓďsŁxÎ=úG¬`"Ć”#Ż—>%Wu8%Aô)Zę^xĎ1ĂÇȦěŇy–ĄA¬Řę]jŃç‚–úË™^ÝĚZę<WǦîĆ�3Őľ/ĺ}J:-Ç„ “U›đóöîd ëľśw2ćz=ÝLYů•‰rø¬â™·•§ń;,żP|•9Ë U›w­DjWZJÎěç\{ŠńŇĺŁÍsë)ZrŻRÚŇ€řjâ;:ó,HŘŰÁ�ąr˵%NźĽ·…î>áTĄ7~R©<| #?ĂńP«všĄ_ąu P•Ľď©TQä|ÓÔ ‡ ňkÜHŠ–\s‡o:ÍŞ–ńžź•XE(ńĽ“UĹŁ÷ Î*ŕRüµ­âj\JĂnÍÔŬÂĐ‹z‚e{x‡šËÜÎrĎ.fW\růŠxM…ÔąM˛űťlÉW<Ě9üĘ5â«a–:L‘ögQ´öĹUáQJŔŘcĺ ů:†ßg‡ícbTs¨©ĆI•÷nbŞs83N*bńKg˘Ł{arH[hu}’ĹŐVóë0HěG´ňřô|ŢČbv5˘Ą´ĄďË4UçŘśj>óÝC6ó¦9¶üJŇ+ Ş6I‹ňĂŻ.mî»…Ŕ&>,ZBÜ‹.5Ř‹…înuiŻí;p~'˙� ·Cjśg^f& !ńdąu7YUâ[ʱRţ—Í|s4ý1Diá"ĺ—żŁ(Ixb¶ńÜśľś•Ä·ĘZ[64żĚ`V®^śkS1fWQuŐäEÔ&l(ŤÜ–+ŐÇŔŠ7[Sń@§M ÇüCl#AĂö”Ť«Vw3Sć!uĎqRć|Î6\dđŻńÉ xuu(Ň=Ă |Äë”ĘáÝqp0-žWϨáZ±ő ţzŠXE®5Âş…¶ÇCÇRÖäWG0± @WzZĐWňŽs§)~�ăĚĂŰËqŰE#"á:« Ę1ľĽL.1ăN®V;L*ć4«ŽźQ5v {™‡€ďűAČW&®Pró :¸bJ4Ţż¸lş‡ŇÜ´\ çčoD(ŘĺLĺ†>¦ÄÁŁm:aQ—nŤL tôé˙�VŹNˇl\ĂŤ•~%[]Ib•Xź ą»ÝÁĂ“®žËĘ)ňňFĚ1Ż| {”ŤÂÝ ¦<Ěţŕěč•|ź‘üśSđuZ=·Ç˘Ģ6;‚9^qą@q¸nµńZc08†¬–g3—sĐń.ĂÉŹńaËă‘^ž"’ąŮUĆ|ÄĎ^›ę*Ƭżą�łşq™Ż‚^z /¤†Ĺ0R“«5Ň™<yłPŃ*·=§0c‚ÝČüß/J3\]sŻ–bNx?PśëĆ€UĽs‰«'}Ë'Íu)ü8ř¨†×ď'^g2ň#–ˇDJbA ¸,ĹÄîŕÎfe‹›±“čÖ›ú†÷@Ô é›<ŇżÚ±cÖ~˘[2;<Ű1Cm{WW‡Vř™ ”eÎ÷-élQgăéj«Ě:őŐ(f›ÜÎ4k«Áń ”+OSh[/ťŐÂôÔé-‚˘śî)_jÜ7v Yś÷1g›s<DËĽJ˝µ2eÄ2SŃbŤ”<3--ž˝y‡˝¨ĆŻŐĚ (IJ- ”vÚďqł–ăk¸K óműJ}FEíxc 4»˙�ĺS„›ż™ĘeT'&ôăšáüĆí⪕L˘ŃÓ+©ëŁ'*[¨Öôś){Š*řN g=Š R1\µT‘AuAâ)C}&ĺuC˛8KŔÇ'5W2YlűDâV>—Š©ćLŹć'™U5·ä?’ŤŠte–0vq.†cg śî;d[:¸%Ô sDŃ›ž:`rď¦_î<‹ËüJŤ|7+”»ů•> ř·`Ď;&są^xx†!s"qQ3ÝO…Ez–»cws•“âXŠ\*ct«qM<g 9NĆ—Ć&!ÄSŕ/\J hvżú›'6Óţ&(ʬ|(Ď-n1Ť%{ ĚÉ}˝’§öE)M1#{ 4;ЦZ®ÇŠŐó×ůpηPŇfKĹDń ›‹$fĄóC,[ůKsEŇy2ŽĎw5Ł‹Ł}CY‰jţŤęk‹÷ăĚQ8żôLąpĹúÖ/‰HĐůޕДÓ-UĎÉâ&‚Ź™Ě4#$G/AĽnX�Rʆ^ău•sâAňkĽ)Rëßžsá+ÉŮfÖFŕ­˝«iń»ć?9—ó-M#q㪭0UQDáÔ“Q™ďň ¤÷ĺy^ŕp5•ÄMWÔvv˝Ĺš;¦óĹ‹ţˇŐi¶«;%-¬eʇť‡ráˇmł, ř*WÉ<ň)Yęf̦`żÜş3ůé’Ťę†1(F^ qî, 9”H©öó‚ë,Kš!0�éÓ-[s¶«¨ ¨µÖĎ>Ó§×.¤ś=Ä­ĺpůź‚!©Pš¸ęăNµ1‡p-mę­z—®kAÔ"B‘,Ç©HQčÄĘŞ”UĹqsgy8ňvťŻÁg�v€űĘ!›1qĄţť>Φ 5dŰę*7‰ţTĂ’eŠ& ‹—\ęáîZ"Ń@vôŔČq7\1-ŁÍĚOÉÄgµÖ`ń$p‹¶™óĐAf¬,× rBăŽz&0UżMFµâąť§yaŰá…E‹˙�ıÁ2iĚ!hk'糲6·P´‰ÍTÄŔڼͨb'4Ô˛0«Í<Ă>>•�î=–čŁÁň뉖>bŤńÝ…‰ď¨Ç;Ügpµ>‰ÁüćbYęÝńS&ŚBC› 6ů– ©d�SÍć Cµ$«Đţŕ ht×]ýĺ$gU=zerĘäŢ@f«™€_mß‹Ůîĺ/ĎG’ÍT•>#üDö%vW©U.nỪę f«–ő ·tć˘E5Ńö’ÁAW&gz…!yQ˝“Ě߯“h§űćZh89˙�Č={±ÉâbÝŰ9%úZ€_ć× ÇXň€ł˘Ë_¤r´pĽĹkJÓ[śm¬7Ź )Bc>y÷vó9úłŻ2ą;ÍN~žJDĚľŔĘĆ J•*ŞTăy”µqµQ4•-ô¨Ł >&UůXf˛Ě7p©´Ä ~2ëâfpRß°•YqŚÚoš®Ąŕí"‰rŤzľH®vVĄÓ<EQBÝů *–=y˝.š )CÔZř‡‚˙�T+ĚJÓRá·|K5fă!€Ë�-ş|3÷‹”� ön,�ö[Ą/V\xń!·ëp€ĂÇÔ ˇLe&w8ĶZ.=Pňu§WÄë™NĽĆăYÁUy”FXŹ´ÔSó9¦:ŔiÍKu˘[Ü`Ň{•xQ ˇŃ�˛ŐW1ż'.ˇŮ‹9„¸ ,ľbkjń? /,c©ČĚ,UŮ•ˇÓďć&(Ćâ0,îą´©)*ËüŠăaę(IN/¤lÉčąÇÚŮ}iy+ľ#5¶D—ďÂ%}Î{FŤEeťb=|lćb_š´hžHĐ łŢ5ń2^nvńP°öŕ)Ł‹aę[K;![Y·ĽF–ÄLf¦­×ÄŔHk*ŚÓ0-šČô÷W:.Zyg(0 9Čř‡^jcÂÁĚl˛7U1úÝâW›śLÚq8ń:ŻŮ›CąZr÷8‹|“pvY,mÚĹzĎ©q~Đú«•6¸­Äµýx€3kŻrÔĹÍ^ŕ$¦DĽt„Jôshpb ËöŘćřőq¨XÎŕŹ8ŕUCź)V”⇮˘FĂxçkž+.ć{{f2ĘZŢSâ"Ż5 xĎFzQ+©šŇĎĚ=Çťë/ńŇj˘¬z”%ľÜgťô]l‹lŠÖr„%<>a…đru®íLŐíę? rËlşk±cŔy÷�şŚrs091™ŐĘ™ˇ`ş‡BÝbzz›°ň-z¶ú—ľUű‹_pNsô+™ĺó‹Ž Ąř„ŰĎ3(i†˘±6uę. ł† €ÂÇŽâąí–ęasŽ=G‰FlóŰüL$k…ŰÍz—c)´°¬Dfcë‰VÔŕţP™ˇc˘€xěOy’ŐŃEz…<¬Wú”çTëő.‰u€á~Ňŕ¨cô&«r¸+a¤ëÇÚv†VÎë*=T6mr Á_îlĆb­TÄłź‰ž'+ đůť” Ź˝“Gž"ľ!S’ýC'łŕˇţŕ”ş"w‰K][g ů•ô^·™pn ®Đw`đ¤ę’[ł¶– 7ăĚU`ŐpÇěCLËŠôĂ oŔ8…FńáµjPˇ*E)¦; /îE|UńBřó+HŔĹĆVôŞuâs,"K‘×8– ůa€n°ł üEžx€ŃiÓ¨Ţí{1â;˘p/Ô´ýX;ÔĹă˙�‹é̢/îDV—Nڍ™<URßÇÁÔnŰC´` G´t¦§(‹säćÄPŐ6y¸¬¬ń 9xŔ˛{~$%Í6BĘ©V®fBăkęWĚŃz]źiE ÷Ž˘°W‘"8y=rOúP�ś2÷¸ę+śâ5ŢqÉě–N×$§€–P{cĐeY¬ř„ŐŇâ€8ćW];ź#Ű1‘KŁR¤ÜűM“Ă„µ5+W$·@®%Útr“š,ż¸ŁClb#I?SŃPŃrń™“üË3pĆÇ;ú,KÝ.˛ľÎÂ|Ü?çl#‘]^‰VĚ 9kQŮj‹¬LŻ)'© `fÚ­ĘşŚ(t%ćmúŰZĹťÔ{i¦':;üÁPKs]<ć5Ďe¨Mś"bSŐó#U6ŃY•¶9DĽ1Kqî_ŹŤP8ęX˛† ţ:…c€&Áářđ©XÔjÇ%zŚöS^Díí0é1:ŻłĎRţĽ_°k}N ŽQ±ýٵ”2´âŃ~1÷“Ĺ›ů˙�˛Ń†g¦NÚ8 ă1"Ë,˝—8=T%Ţ*:ĎŘáÄ®ž%ťŕŃľ™ä¤Yj4[“óÔŞű1`›xč—é’—Ěá"P«q‡IšNîź?hnŇYp´`ŕ†x—ÄýýťWĽµ1¤ 3CsěôĘr§p6N•“şŤKä•Äj0zX_‡ÄyyNó.˙�\Á¸…â2‰p´l™;g‚8łhgńK¦TkÇ$ł>fh58?H±˘X8zŹXöGˇŠóŹ2ö–ČçÔF– C,Âh5¨ZµŔ?Ąn#(đó5ĎíČüó xĽ2 ôŠň „,%Čö{ ˙�Qé]™hš–̵‘0ж#¦ÎMĺ®đÜ1öŕ˙�÷ ‚?ęX†ÉŔ¦+ąOśGWçPĄµSaĄJ¶%Fá)´-YŽ´±�*¬!Đ–m{ŤĐ(ăěšÖˇ$“L)°ą•J;Š] ě5ĚşUg_A:úW,ČcYU˘»µ~§[€ťD †XĂ É–µcÍ/]f#"AG˛1 @m|™1\F–(t^˘Ç5ťőp1óU AjVą˙�ŕ­(ŽBśŹJčçÔĚ*–ŇŻĐő±/úöĐnň–>yľ<ż´XS;ŹRę8éÁ%6Ç-˛_~†vÄ "hÇŻqşďůXęĘÚk»™5­ĄcÁć7¦Ł·ő ”Ý9\Ô†~ĂĄoË;a‡ĘĽ # ŚJ'p‹ěRŻú›�CšÁ2–'(ż=|•Ť:OÉĽ&üT~‡^&'…ÍQźr›&7ťĚµ‚űO0m# ü3J(ůb.VŞć»Ę’™vŐ3x)P^e±UVé˝ Fg”.Şó4ȇŐş=jĄńS±pZŠ(9·oîăŹdŁ-ÁßÓ.¦˘á¸=* %:Ď(Ďęšą˛2÷7= aqNefŐ ÂP=Cž˙�s(Ę+a_3'nÉRęęS‡çĺÎ%Ť,7ÚßćŹň<-ž§PB9…~y†Žş ,ř®s) ÓÂ8ĆăţvîvÇí-aÖG°öLĺiTńŹRýĎiN”3®%aŇ'7/n€îgÉ<3íx&V2nz%b»"üJC—ö޸%ĺ Š‘‰óČ#X¨Č©Ž[FáóćƇ‰CuFË_Ç`® ®ˇP­C.5x%:ňůő‰Ůú–ů<ġ}4·ś!u¡Đů8•ümuríěk‡ŞWô#No‡âfŚÜęgBźÇć®˝´ËDö6Ë4N@Ć”ËS€%ڦžc)kŚ´<KŤqŻĄ}MĂq땇‚ŢXľâ±Ú¸JŮa5BSäÎt[¤‚f JŃlGźp˙�bµŠŠĹĘÓ@ş^DFĹŽlĘ+ďRňĄZĽź¸;ޤ­şHó˝Ě¨‡ ĽÄÇ€ a§„€,ĄˇŮ׍ÜĆoâ&ŐĹ7%˙�n5C@…™žđáł lÔÖ-žOYžnęЇőěÜbŃ V<+&ăÉĶ^;¶RE ”2K¨R«\(áůŚfâE÷”—|KUĹÖ¦2K©ĎžrCź`¬ĚîiŠňôĸU¬}ۉĽ Ib÷Ă˝rĎďRĄ­ß,\`ŚF3~ćŐZ{=@VpöÎmď‰a®XzšbŠ6ĹŹ.íüŁÎëĽSÔâUbÔMsE^tŃíDń˝Ă)4'¸Ö°\o‰oH|Čq9î_ŐlŘŽ>—2^JĚ#]2„p kÔᄾ;ĚV»Ě¬Z­K˛.ˇĄ1¨śöoÁź–kVĽř'łĄ†„ •'ˇ€Ż’\ Ó0 ŢXŔXŤyŔŚżĄkŞÍnZŹO‘U—hwĘ3\řęvqO$j̲RyçlAČ•&ŽÜ‡1p­üĐ‚Ź˛áëÖĆŻ‰.«†Aáϸr…°ľCŔÜĹ“ ŃćťÄL«G–Zo‰Č�DG>?q Á®vžć>“Xµ*ŻS”9Ą™‚ˇĂŘ'átŮ9+ÜkąćŮ›’ Ż3±‚-·7#t)dĆŇ,UWâ] ş:bĹËm}L Ě´ĺŻHŞąěBőP@Ô˘ĽFöqx¨ďJń…ŰůŽ8¨aE«^YBMó�á«Ä~ş…Ę´!«Ě]ĺ 3Ë“Đ@á ÍW¤Ň¦SLš ŹĂ` –ăŔŽ_ěŹÄŔĹîýĐ0íÔ¦á ľ�ć6ČşŮ*‹őpĹDL פçz˝¦Ž˛›NŠŔÍBâ=@L–ňc÷ăv™żůĐž-â!xç0Żř†{˘»ióÄĚf\eo?©tĐÁI¦”oĂ2O¶ŚŹ::Ů»·zŽRđč?2¶¦÷xy)ř‚’ŕýBEc:ŹşŔiŐűĚ1i}×Bu"ÜECĽËćPV1ĐP: śń[÷cbŕĹjˇ0oĽđÝUëăAčn3‡hßiÁf._IÝG̸¦»äýJěÄt˝¤¤É.šzf±sŕ§M 6‡&~đQ‡bŹ!,,[#]Ě_qxŤş–Ś J =«+芬v§„ó; ްëÇzY–±:ż‰hw“űa^Ú­ýc§śq-Dęŕy@ämKq|ěf:ŢJĂÔɉě)÷‡Ő,eýGśśň:…&ŽsŚ#lUTc L,z´Ç‹hç"č~`őŠdĚKé— Y™‡ß©źdŽA ĽKđŽ·7™s-C€EH/w‹´ŕ(z™5}yąAŕő-fdĽ,oÜ!’­Ö@4ÄŃ"IáćRü#h«˘[Eůç‡Kµcs5�ň?„jJ©ŰÁ&“ć†&yÚܧ¸Q—*‰RKaëÔ.iˇsť‘†ç¢ćST:ĂOžČ‘ä±˙�ćˇ\ËßgxNüMކ14v§â-™ö„TGR±ööüÇßźüÄÜźî"™žn}ŞG:„6&gxeAÍôü¤1@ŘĹűą_Ź*ń×đLŠĂÔÇë ó ·i?3[)9éŘ®"˝€˙�Ë-D窲sż±>v;@źĚѲgX\3zq=/¦Y*Ľ$ÔÎkPEw‹–ĆťĘóĽ˛ÎúÜšő©’.oˇ‹®ČÚŢf–k„÷&rŕ©ëůŔJÜh Ł÷˛¬¸©ŚZ®Śb2‹…\hßS‚Ď?ş4Çĺ0¤ě!”8ę]źsM" ¨óÜ&+¶,¶¨ízJ Ô1QA’ÇSŠV%K–Nayáëčó}/3™ŹĄC—ppöÖfÇ`iŮ[î&1O�[řŽ0\ŞŁ+ńUŔďŚ~‰-FµLr­¦ĽĄ+n7mYŻy°”.5 ęGňľăOş~ŃZ_ÁÂd2âć<©A{‘1•ÝvBĽ'>#“·p7ÚýĚÚqŚěĘüËďş`xOÉŽ[­Řk@É@uňF„‚“nů”E5ű…Czń3·ÜÚ×đܧL ˙�łn÷í-G⣻^ß0KűĽ·‡ˇ55Âď‰HaJ,/Ä_‚Ż‚,Ą~pé3p}2™SŢ~ă<Ť//…-0w[sÍĚd­†,"Ô•/Ň8i2ôíŽkLM ÖĄFŹrć莵.ž~…ĽJ™Ą{N‚ń)| @‰f]ĺµ ”cPŚ&ú®®\8ARz¸;fşÚŹqü&]â)»Ň˛§ žË»> €‹Óâ%š˙�·oÜu,0˘–đ„@ô\ äY”[*őRôɆXĹVgĄ!w2ŠżtmÖ]Ń侣gí óŁÄÇ\ ľk®…S’ěr…WĘúŹ\}™Ô«±čYţâ9FÎA­5pQÔ¬âČFÎBWŔ™Śó ľ;)1ö|K‡»Ű\Ěg™â(±c Ëó–—En7O¦řżIŻ} ¦ĽD-IL\m`ĚÎ"ęť3?勦|AË6˘=~>`8`ÔI/lĎX§üvŽiaqaÉć>K™˛|K®tRn.[ł‚॥uŔ‚ô®Š`«řŐ—ŞľˇW$Ő¬m:Yß ßśµń‰âŚźv™˝'ITý·,ćÂî¦Í’¦>ŕÓÄ#Ú®ˇ[ËÉŚŕ4»éěńĽ µ qřD­©JlÉ&¸T¶‚Ź3„'´ŃŮĎ0.WÔŇoĚłšf°“ *»![uôÂÇÁĹŔBŮ.˛ýĄčEÂă&y–”<Đö1•‚őްB ĽĚăgU*(żrąL;âUŘ®!=Ç•6ŤŔ=§öµ> qP ެŤ>ŮS˝V˝P‘ ¨U>uŕYZ„„ŔZˇ üĚ[$Ě}™01Kóë#-îzm"J¶…můÔHěѬĂ@ŇHđú´`i‰ *e;"ńrá[j“ oŃż-sG„cY ĐŽ@wń\]†›Ď�SňÂ^óJ)2Lí|^â90őíi01iˇŐáý&‘î]W4ç(˝âŠč‹/čmů‚mAż‹ÍG zLzâó2–˝é55ů}ĺRú<ą2iľŹÔW‰Ů˛ŹťKřZ|LC#Wu/Fž¦¨ k2§®+÷•GŹ™¦ 91Ą«+(ýYox4|×T´nßçäUš1Fś8JŔ Uśě0Ţ ‰­¶ˇł¦fQ6 ‘K«ď,ĄŇa:ŽžéäCĂéç—>ĚIBµá–%ÍéurU¤Üx  *5uGŢ�Ň`â9iWĚ•ÎţV‹°Va…Ź™FÜIĹK+o‰Ć°Ű¦T�Ů}A® ĽŰ˛…é÷ Łş·÷7soąhIđ°$S*˙�Q4§ú‚Qť8űjWŕűOŠ&^Ř›A=ż(^×ŔFĺU©q`€ J÷§‚U<îţŇ+¸(hŠľQKA©—9T•˘ç,XĐ…ós97,‡˝Äóepұ,䔜ű•<ą|‘đlG^ĄĎxŁ _§Ž{á”ÁaŰRúţyLÚvÔ«@XZ†�ČDŐaÝAT††Á9qeTĹ�#faÄu,Mý|P$AÂ׊•¶¸; Ф$` ¬ţ# «şÄLÂŁő1§đlEËMa.˛âĺĐÍŃ‚Z EzKUĄ÷oÔs¸Ďžcą¶>H$ńĂ.Cż9feP´őĂX@Ě„÷e_B¶ĎÜŞWÚąę˘üĘ9˛�ĎřFSA(LWJ á_mť 6÷‡ éDüGdŕŁ^"-Łx`:pYv±śsöŤ=ŁlËj|-ÂÂoP…ž°ńK2Î+§Á,.;TńÁ€Ź!«FÍËDŰÓ q,ÜdŻÝ Ű)­•65¨¬%M1@ÜšÁj»âëŢ)—aJ5›#.“ĄÉż©D…Có�r-ŔĽşˇŕ˙�žţ#¤FmŤńSv2L¬z™î§á‘ŕ'r˝>gF<M}âÁy–-ŽjYU1XŔďąP;Ǩ˘ëŠĹó-‹ĘżŮÔSE3(…·(ń¨jĘDB_’±Q SyĄÄą?Łď,ę`¨<µ†=B‚â»Č\®�2đ†-l>FŁ:¸§Ă/E¦oő ×âAçđEm(ĽÂ|Hö űFyI‰­·®áé~†Ń^ŤX;Axs7ńUş3�şł‹«…“Id]ż‰č0 }剣Ě�U3cŤ†ba ÚĽő6PpLňµ˝Ô Ś5¸†ź±jĎŹ ,Řý8JDM'?1¨n˝BJ4?Ů3�2}šÄÖFGN×ÔŐѤđć=d¨m˛S\J\@ĺń<ĺË„ĚŻç °ęĄ ňĘěBăa^ĹKßs4ÍWc˙�:‹’ç(+�B™sx}2ÁŔłIA#•Š’«ć {s‹Tł´ş bČLUÖ6.‘€ĂG”G»4pJŐîF–U Eî+H8ó„˝7˛Š-*h_Ę2M<Zř”‚—ÎIfs¨7(Ý_ŮSx¶Wý‡$N<Ĺ˙�Ą¬Ł.`ź2”’Ô x†ŃŠ_śĄe¬Y+#›+p]>Ěě””Ón'úžzăűF¬4ôDsÖłřJȲŚvLňÔĘŮ.á 8 uPM”ŻL!ľFz™g2…̶Ĺs:ř “}…ţŁbÖ?\˙�bűŻŤ"¦Eq˘°yŠ�Wü"łŇŤ±Š[«e•«ßó,k‘˙�ąK&ÖÖ‰(ÎŰ$±V …§L˝5FWď:ęcŮöÇĘEŮ@f„Ě!űH¬Ä>]\ş�éĄŰć%?g%ŢĺŮQ˝ŞĚ`¸Š~aŔÓ‘îX—ŁI䊡·s-Ě`¤đ¨ĺbŇ€ q­[mJ#¬Nb]5˘ŽĹŤCHô2ôE­X­/7™vSÇĎx.üB@‚·Jů–ćwPüń "şÉ˘%Q©îsÍňşXLŠë ׹ŚâŢIz´3^f+/™Ťä–¨5+Ä´íŔBŻť Ü#¦Ąč•Ž‹jsůŽ¦ŻśŕÉ0¸Sa«¸Em//Ů ť‘¶ëĺÂí-ľ4•y€śFW_™1@ÚQ«đB3,Ú˙�GÄJ°čĽzu)@đ`qNgd¦]đc…] pîěĘ <˛—Nů†'Š€Ţ‰Ť@˛Óáš>č6#żé±`ĺâÜZľUËÚ\ uÁţuJ.="ŇŐ6ţ:ŤË úů€ĺC«źŁ/ ĎZ w!OŚĹ]“'§Ô@čâáň+«Ă’ ‚ pq(Ł}ě4¨ĆŹć5$*ÇQcˇĚů4Ŕą‡ţcoŠ/.!Ĺ˙�$˛ˇŐĘ‹(ĘüĄ©@+PdTVňĽtűË5‡ő0˘÷řh«šěsX6MŞbE-ąex,±k’,Ż““2¦˙�0a¬ťÁHGcÔ|íú¦#ÂPůÔ2jq7H¬ő+K«¦§Ts7U¤ŕrÁĺÝyîZýlgUż‰ą‡FGŚĹlSŤ‰vVÔ¶ët{Ş:łŐ±ó){°Đš- ŻŃ Căü0jkZú‡„^ž«1”čMŤs¦Ą,ŔŹxc›@€Ř&ď›dÇ,ń” wx¨m ˛ô%…ŕ"¦=a»ä˘ô ŕ)j\Źěôq}B(±Ťg=.4ł„N5KáŮąA/q4i.Žś°ELN(ż¤Bt—ůD›‚aşćCÜq™J»Ç›…쪯©r€)c0}˛›ˇŐĂ;Ĺ’o1SşĽKóíáfę÷Y, ЉnÎurŁS5€6AŮM§}KϢw‰Ű†T0cĸ} hšrľ�ÔX`á¬ýJ€ »`STmŢźůŠĐ6PkxšKĎU†\ÇűíÓâ? p‰öhÓęT!ËgĆ#Ç–GĎň^Qß«“WląCĹJç×–õMÁäő(ňŤ=|Dłađ”1éąfeĎ›×’žŚ×–J|Ă›Áłl^L�_6w”š’ĽY5®S0,”KJD Ŕ§~fÚ «bĽţŃ„fň?0 ŃÇ߉^qZd†0’ Ë`WŹ1s«×ýpEFoď0\Ny¸54ViéÜÉŚąbň]ĂBgNďÂ~Ŕn7Äwb¶ĺĺß2Üxw=?hŇŻXřF0ÝőĹ×*y…¶Đ8÷ ZŠ€0Ů»¸|î~R»°ţlÓçz_ŃA7™w\NĄˇ@®«1žµľ•ĺ…HŰY«^ö­ĆmÇ–Š9˛ćŽĄH˘-T´íXźaExAësTö(=Fáűl¨ů€zŠ‚â8‰’űs¬ĚĄąP?ěm=ęč'Ł%;“ËRËú÷öÜ&®ź6 x«­pna@úË (ű?‰`ůŻ3Yç“ä%Ł—Ĺrq.‰5Éľ`‡­ş„)ŤZęUŰ=pHÎÓŞąŽË Lv.đë9‡dK¦Ă,űĹlşń´Xxe6D,ŔbpŇÍÚK’D'¨ŰŽvű€Ş€ĺůîu€:^fY›v#ćĺ]śĄŔŃ:Z,? gĂ>ĹĘ» Eă5ž2ôq r„+1lŻňnd *í.rđÜĹäüĘ,ÜŽ2ú_ľˇĹa(©h!ĺqsRŐGc˛/´¨Exţ&¸‘°ĆoĚ lĚ=°­˝äX ‹čškâŃ™ ĺÍF›‚»1Ěľ”ŕ+«…MµßöW0TxLJ &Ń‘ŞuíÖdI49:›ŔZ64˛é$ĺ›Á Ă±Ž©%±ZđęÜFUé˝0zKűĄťů?RĚÝtög2Ü”Ř#‡ÄŞÇL¸vóOŐ)AłFÉ­‹ [ýÄÝťy†¦ÁM÷!Ü  2pAr6Đě™ĹĐĺűřůŠClĽ _zŕĚů±Ó�§%":Ň4_¦%¬3Ö&őćÜMŔ3‹ÔÇ]…ů !Ř�8f‚®Ż´ś>—B‚đpbB6„8ťĚY<1Ď;[KDÎŐÍ7}âUËŐt€BŚ…>źuv~n!€"ÖpM1G஢ĺÇčî>ߨš+·0fů‚î÷§ŻÄ({ €ńl\ʩ߼ɢú…`ĘyľRŽ ,W2ę¤R­4»\‘ůŠ×~Ův´ëO±(09á‚4ŕ™`Ăőq·¤ĘË-ž ×3Ť–ç=F ěę?2„ÁÇ�ĽbĄîCâθaż]˛ ůc×ú«‘ ¨ ź'Ę!µ˘sËŰĘoâoƇňĘ–S{ŰÄ%oŔZ+ŮńşgąŇpĹÁń§S{„ţq€_–ë3Ě+-̬&č-ěAń.«1x#%ú2‹ĺ8ő7*ń?á8úVí¨č—ÚÁśąť¦Ýv\Ó ®ž?0ń]:)źĚBÎDl ś$MIČ•y|ĄE§d ”ËrÍ^®0m¨ßkŰćPçŐ"QîoĚW±á& 䆠řÇ46ş˛%{“µ1ó+’ŞşânWU¦ÖbTo8ÂÎW11ŮĺŇşëěfĹéŰt‡=ńąOIJU‘čD €KźcüBÓŁtܨ!‡ľâ¶Ş_«íkđ5…âq,V­†>3ű!CÍTüKË?ië=:ަ:ł•ßÁŞŤçwů„{ö힎.ˇżÔ¬dű”R“—U˙�ÉĎ<$ľ´,+_Šu/ 9ţŞ˘V"2źDAž¦ěÁD ˙�’&ŐŚ:ł¤]Ź/_E<öׂQ»U苉™f¦ę8_ű>a€ů™>Đ©;ÔR †<ÁgŮyY«ďs …XĄéńqĄ-îx ˘Ýżş÷µŠč•O1î5ßŰ­1"*OŚ˙� (rq˛b°´=Î@XK޵[ÄŁxaǧËiÄňö<MIuáéúV`ˇV‚rĽ%ź�ĺť1GçÁăďxŠ uŐËa†ZĐ]EeiÎÇöuHXp0mɩW•9ó˙�—s¦^ÇůâEB÷ĺXÂëÜĺ"Ú홾çD�ľ€Âő�DTb‘ŚWc!±Íő8‹Ăhą:jĆ—)Q&­‡ß?0�Úf1»Ń7IÉ*j¨î_gk†<C3(AkAĂáŚŘÁłř™mN—µâ5mnm˘|¶ËÖÓ”nţbŐÜÔžô?fţ& <NqaÂäŕ>ˇ3ýüĘË;;ôć5oOâ$hžH˘ťAÁĺ8ňś;5ĎôtD Ę ¸7ô9‡ôëÄK„áes ~OˇĚq?<w+®b†U»Ó929§¸-ęŔ3G•ćZ΂ů=ł˘WřÜ y«#ŘśAŽ4ćOÎżŤÂ€Č'PF\¨śmąV¦±†ů7ăGG}˙�U�¬ŹŤŽ'q„hőű"’ ŽgđBŞß~3µŘŮ2P¨Ś`Úű<ž%ÍótA˘"cňĆüj2Ľ‡kć>űĺUFq¨íÉťNbő_ĘWő0¨ •ÄĆ»—ÖÁ©kéÚő7ue]Věţ<ÂĂnőáó0&>Ö\x-my\î(ÂŻ$py–8VŁ(ˇĽCĺ ‹±ř ţ˘˙�Žxť`Ľ‰SsNýśľăésb\á˙�ŹO‰DŁbn] …8IDeéĂňS ŮłÓ°`çú%x‹pĚĎ_ňÁ+Ć-Ž&—)ÁôúuîJĽgC)¸ËÎú|xʆjčťűú15ÍS +ąŤ «Ă™P­4Ŕ«†^j6ĺ¬gŃí÷€QĐĂvĆ˙�™kBń珰żĽzűb9VfkŘ­%¨łKÁşś@eěý«$Ĺ·ýeł<×›†ç%“÷A' kĄ9;%§/ža,ewügaŃqÜ[Ĺj"ľ §17AÁż¦Rë`ĽÄMřˇ§KĎ0—梴rRď uřŽ>ÓÓ\ź$:MńËęšă/´žýíP‡8Í)˝7G.ü%_šâ2s4ѬL˝ÔE(Vőxţb3+©çp„4O˘pĎě>ßFOÄJB6ř^Ćźż3~Ą›&ąŰ)śműÉŚşd�Ź/€íś»łŻŰĎü–Y¬€~Ě«ą}ťÂŽś†‚3/‚î—Óß„ŢŐă€ř`·p Lá˘X¬c˘ x+ź0ú…»Şű[G9n&w}^Ę. T®p”÷úńú>ç!íĚKCĹśľĐµbíúEZ=Uţ&Čâż&% K—·÷Öëá~¶ĂíSüśś*jÓNćjČ |�ŢŔÎ/«Ĺů‰uđ^—ĚäüE(aa}aR—˛*¶>y” ZĎhŤ ŕ%äę•řÉkb‡ÇSÜŁf%ô†D-"8 Î1Ą‡ńżnÓC -™OÄŽ'P@Ş ă~X…/…ßÓń.ňôăíĂŞ9_xŮ‚¸:y–öę_gťŰOîP2ş®ßţYęćR]Ă@Óäh|˝N Öx§„ÔLĺtřu2˛źrä™5š} }ĄÔ§ÇťŹSŹ[Ľ*~Č…řeóÔĂsSEx…Ďg¸(Uđ[6Ě`RáéhĚó#ZnĄšüTŔ3§ű=Î<Úňî°vBÉ Ľźń)'<M3¸fŐś_Ú=u ćÓâ™”¨­µĽń�¦ä¸=ÖWď41·3·é_ HbĎË ÁÍ$sť1ŤÔ¬WŠ:”pä€ÖÜ ×söŚĽF?É’Ąą,x|3ć }#cPď´űŢ|Böµ[ń{ś˛°Vű@¶ôÝ`‡ŚMEŮę]űGąBÝcł›äűʏI¶(čđđôÄ3ŇŻîeŤ\—©™…jůŽ\P´Ď dFQX<şËPâ¸ĎsĐ+­§Í\§!Ô!ÚhŮP•»] Ôí}˝¬ĺÖFcÜÂĎ؇_S“‹ú"§/Ńú;Óá»ÄÉ\yžű8&W q¶OW1—*Ş`ĂťőSÇŘĂp>q´Ő ľ©›ôN»“«…5^®ËµŰ)kűĚT»úrBjšŤ˝yEöů‹Żů9ŔĄ6ßÇîÁň3dévA¶ÁAŻXÉdEŠôÔOHŚ«ÂľńYś‚[Ľű‹¬Ďűöb`ŚŢ:…îóng±-Ŕ—iO‰Tw…Ie÷„Zy6̨Â<¨6|ę[Ż7Úş÷2-&÷y‚>…ů2ŢZR—kÁűmVJ~Ł7î #w›ŤgĎ\łîß°h•.«cYdçH™ř}‘7_žUĺEşäs@G» öF¸[2ˇśJčÝâ‹îHBÖĺŮ ;?‰O©+ŔoÂľc$�uO76ŘÄ6ĆQŮ~!ô!˛ Ě䙏˙�ů%nmĺŹV@˛ďŃi—ţ—!OĘçöL1 épt·<ĚüŤŻP΢Nq(o–f˘L1źĄä›đÉ˙�Čyć: ‡uW7`<<Ú••í*=™ŹDŁ|D€=«ş˙�@ů~ŤÂoKĄ„>‰»ž Ě8ĂQʏŽŮŕ32%Íţ‚g)X{Kę-ŔzN( ?’R1•OŮÇĆ&¬Z~ĂŚčî fúŮ3Đ^Y†Ę7Ýú SYůę|Gg”FFo82= |§¸)¶Ć·ýMÖäÎUőęu�Ăvq2áRă׌Ăa8Cł«Ŕdż Yî,VtPÁă×Â<ţ81ŻŢ=ÁźsóÜ``ÉË"ˇ, ~2pÇł×Dŕ|>'Ť‹žeúÂϱţżÂ…8ŕ›Á3ÇĐťîićůŤ\m`x™^ŃŃǶs–c—»–cełŁ_U%_„B®‹NܤCŔ: �·Ăř‡ AŹóPµáŰOš·ÔĹJ‚~Ť›‰rŁW3CĄ* ă f?b­�7ŇÜFu™«‹ĺ6ÂßP�pŔŹňSý.VćĚ`/e·Š‚ŐČZ čÖ»‰4—9ryř†*ôŞ)ű¨˛Ç‚Yż˘ç3p•/×?ó+°Ü/ć˘*VnŚ>a„jŻ]ľŁw0Süw6[®Ýg*ËĐP é?+e§˛$ä|3Žź|=Çcy—™‰W¨`ôc¤ýřsúĘ ®sÜ+*Ť Id›…Ö]Í °«^&Ś„-.Çĺőu˝XQőöCEóÜĹ”ĎČ ˘ˇxHŻKu˛XŁdŔ1ýŁ©@®´90lÓŤfýĘl˝yč>©(Y�ó8Ä<Î&sÄ<÷Ä®‘Ýb•Ömkš ‘€CwďÄŮ:ŞŰz€Nć<öaKüv`Ú÷ć^öz!K@Ëpu\ÁD܉ÂŮK®Ô;HW‚±†ľ>ŚÖ¦™dŔ^Yhż?K‡żb’ĺ­'/ţJ~e®“J~~%†é¦0<6ŰŠ9śâK Ů Ng>yCź_´*QÍh&<ř8'dëĚĚ„µĂ_TáŻ1CRÖÚôM­ţg$iž~‚˛@m¶Beäúr>Ó™±‚ŚLô—ć=ĄXt—ŃŻZ”)¶¦c˙�Ä8†X»»8sî8[GůpWx7?…"(®! yśJÄçS*­KB5Ë<µ›ô’UĘ2ĺ]L@‰ZĄ7÷”ŘvTß73᯽ť™q-żˇş›´<ąf¨Źň7YIJôÚ€âŚÎlAYąž5Ăń./ˇ?]ôçü)ö”%Ą­ełŻ»@ ˇhŢ#ASĐŽ`׳̪ęŐZ©ąi‘Lr©¨äyŤCާîĽěűG'ŇŞZá‚D^¸â*éP*đŢ 7y–¸CcüCGtó?ŕćZX)Úű•G[ŔË0kCˇŚCrHűÉ`—ź™zġŕB�°˘ß*?)qŢą„(˛(H0«.PĎč›y—*Y’73=ĚJcĚdŢ Űö—P=µJ2#ßr¨dłňpÍHÎx Đ81Ľşµ`Ö†qÇǸ֎ˇédŻşÄkvpęudČm&ŁáV >î6up*8•Ô§?@öTZhĘűŔîP”Ň}ćZîf®+˘÷R‘Żşšß0ô"­ę‚ńU/~nŢ—ő+®‰•Äg•čĽĘ°Y29]ű�µËë¶ÜËe¨V9z€0ÄZÄŃIŰsÁşÉ«řglĽ¸C]`ţŕK@·–#“ą¦]察‰rçÝÚ¨^uwY:ŮJ< A2{wÚ'‚…iÍ2ŚÚ‡ýŠ~%D ţěŢâłĎ™ąéŹ-ž(…îâJ¬p÷T|E8ß°KüH9B9=ńU˘)©Âߎ"§ůBWRŐŢO›×ą…¶IU|2ÂĎ’\Łţćd8&śyŞ€ŠŞ/*—÷I\ÇÜPúźTŽP!ÇëŢĽP§ŕW1z‘ňeĎÇÄßĐolŐ´6ľ,Â�|łNhŰůڵ śŘGąrĽ›?ěÖ$ÓôÇŇĄyŠs¬AGąXϵk 9l‰0Şý{Žű‘9´ňň÷ę6Ű€·äď'pĂ ~©Ěłň=Ô�l+¤ţĺŞÓVňµ¨©2 ßľâ(‡gŽ=ĚĄÍë•‹wS„Ţ@éÝÜ;ÔŢu %ĆQĚ—{:b·‰iéf 0Ů›`€A쪼ÁCÂ@Z˛‰SÉĘgŔęöą,`ߦ^Ř-‚‘ďMĘéŤU¦̦ˇkÔ_äË�Úą¨`hńG™™í´·xášîűÄćŢ'ŕJ'2ń™pĚ>ŹÓŞg• ąÝĄŃpŢŢţ%r aŰ©ËŢuň™ťĚľčŕŔ ä¬ĆÓGVyçËÔ[QŁWĺń/So řÖ¸3Ďë‡%‘9úbYŇ=ÄüXŰŚóô Ęč…"vÁ!0e@4e …M5gqN1 q7[uRĚÓŽân^‡¸>`¶ů„kŻ"ĺ×ÇŃĽ?Św#üÍ0ý翢߬ ;Ćl]ú›w ą@a-Ô>ôÍLĆJgäë¨`Ś;y@*Ő9„*—}ÄFh¶«'5JŻšfÔwő…Îfż^bGl··Ţ`oĺ?'ąćbĆĹLÜ[›sĺŘű`·8ţRáN9`¬-¦iËÜŞq� }ŕ0/óÜ˙�>đAk˙�|ĉťŞĎ˙Ä�&������!1AQaq‘ˇ±ÁŃáđń˙Ú��?/XréËă9#oĽ}‚-Nof9"ŤşC¶ôo+h¦¸ř0™Ą*`Ź7çÇ;Ź+ró˘ĘD')– wŘL©<܇jĽ-nŠ" Čs r(F»v|oăÎl :ÎPÁŃŤŽ„Ţ)]ueţe,.ýžń(Ť MŻŮš‹ČrôßcbMÔ �šTşňç‚Ë1ŃyV3\á(”ŔP„ †ŰŔś˛§¤l@ ŢŞ[3‘›,d¬Ük€˘ť>05N ÉŁĆ ŽÍĂÄË Ć1…Ô¶b_2ŇžSĽ;AbßîNâē߼W„îy“Šü;LT0lRLJHďŽîNS®±Ŕ~Ż9ŞiŇ^1H"ľ'÷"ÁVŻń”ŔôĽb›�ÍbÔM¸ŽŰç‡l·ËhQáÉU(Íe˘ß…6}ŕ… ™×ś8”7ÎižńŤBž;¸Rů6KŁ‚ţx„x˝ąŢ˛ęeNĺ<âXź`RÄHoM- -[ľ&ýbŃśŔĂ2<ĚŮ»k2ŕ{„ű÷—›jŽQwó‰7a¨ŕöG†b- ĽyÍqŽ¶Ĺ¦Ň}É%ĹŃ1PŃŇä§ť`ŹSî@włdË´Xé~˝çC‚éöÝ0‰Ýó<!ő—ŤwŮr>`Nn©ăç¬×ŤvÁ«î0˛Č"§}.‹¬n)ÖhG´ăhňŞ۱oç$D’ď@čä;’|â]ĆX8„|÷ !w ;äÖHË™0|Â[/XÁZ¤OX Í˝ř÷Ľ1Y]¦ľµ/�ŮŹČ,÷¬Őµ^ \Ď{ʡ}–ď䛜1÷zĂmvÜhѸ÷Š WŻî�ÓFó/ÖA“*pÄ:p/;Ţ8Î!/múÄ ¸8’IŁ6č»ďڍ4=ë�4J9}Oó źŚd&… âĄ(<«t9stě€ 7R0żË…> Ő<¶ďĆ>Ac-uA˛ž;ʦŤĎłS6Iwž{ńĽóĺ�qĹżX˛=˘1yŐ¸ÖqďşŇĐąB|âMë8W•™T>¶dŐt‹Ý;p®˝Ú1ěâh ůĹ"ŇíľţrBXŮ:ęfüŔFKMëÇy»,m" #ç$9ńŘő8ć-•Ľ†ôcőŘIÂIĘ(fhˇ´©| ¶ÂŰhm±¬ŢZ,íçŰIĂ›J­›=ÝÇścK ÔŽ }pcć<!ö–¦Ü[Š]„ŚŤŘ8Ř⇄ŞÚÜ ‘\¤_Ă!#¨râ'ěŕb¨tĘřŔM3i4©şeˇ˘ńyrltë�7¤ŔäËŃŚň¬Ý0 �u®r—śäĆAŮÜ5KăŚäL^2· ëů„őçĄă i¬„ëÄ‚¸á˙�śZ~9Ë(¤ëůŃďPłĎX.„wĽiP*Ň`Xx˝;űĘ•Xě›Íě‹Ů=LdńŃĺËOŽň–I\aĎH<pxŔÇš„AĘš†8 ´pŞŁ I⸗—ŢşÇ=*8äúq:ĘP˝ŐÇ;rI+ЧĽ¤5&Z:/zČบ…üâ·*5ÓžwŔ”Oc׳RÁ( aš0b^1`‘Q›çű†k‰Î Č}e¤ú I&híÔr=·8Ýx”€ř{ç ˘‘˘sąŻx¸ „zmÚu€xZCňŔď·śW€Ź+ΠčýäZŮč şĺ[ÓEąâ'”i0×F"kÝ4•ă-PËąG’o ä8¨wb¤!D% ő“żŰ€oŠ·ÇXK¨ B§śŻč}  lNtâ&7ę¶@(‡9: äş›76·okĆ f§~qÔ^_úČJř+·Ö#)[˛|!{Öj•W—x°H.ËqŔĐ^nA {Bë"š8g8ŠćÜyűË‘ŘĚA°{§X.¬gXčJ¶řÁhjĽęg0 š†ľ0 ňďŚ5g?î©§\žr‹PéŢ ťO3ëßĎ;¨ŔţäjëA�żé¬¶i! <s˝`.D4Ń:ÉýőĎk•úÁëx-S˝š,ŔěI/ ®xĆ Ł@ ´y~°5$Ţ hG[´řÉÇÓx†Řt "amˇ)ĆOE®P ŮçŤ9*ŽĎŃ” ő}äµŢ(ĺŞßŁYPĄ|áGlŞ],śńŢ k L(š‹üŕ˝ôŕ»x# çłFqę9ŕŔ¸b Rť<~ňy*µI2bÄű)ŃEŚťá9Ś›f° ť·gńŠjVňq1J.ĹFŚ ĄçÖsĘJ´D……ž1CP5Wgsfn�ű‚Bu‘`•˝Ű‘”0¤Sf«ŹđcNŤl5¬cu(†1ĹŢ®=P……†BŃ!„łK#MĐůÎŇK‰Đ‘’·ś%: urUŠB–łČEăÂB/ÄŁ˝Đ¸4Řôvŕ¨;ŮłxĆĐ`?(;|úĘ�0éŢO'A~°‰łŔxÄ”=®"T•® R=đă ú;±lýePtÎ*ÂęÎ%Ž}ă•“úÍ}§~X9^800 ¬iU»ČÜżăÁÖ¶â&'ŽSéÎ@%S†ŠKÇŽ1娧…Eú14*%/žü`%žÓî3xÔÖá»L1ĚĚŤt'[.4)í±\®5Ńđ€Šq±úkJ'„ĄśŽü’h K4¦hšR€J©Š&űĂÁ[¨ý—ŕ5ĄŢi©ŃśűŔQűČoYˇ.üžqŘF°µÄ-(6đÜfx©7N+λÇç'?6 N‚ěz!ó‰MnI[íËFúENúŔU†4»p‚Ů>0덡8†ëpŽsUňkÜÄlč2[u¦®ďFU.�#9)§ăĂX´|iţá,´š@�źÎjĺ!ĺÇhţŕĤi ·ŹÎ˛H)¬‰•X§fŻmxž-±©ü‹óŤ‹Í�©Ć�ŕvi•ńЬŔôůtQŐ Íí$<A_…ď$W "7ZÂ06© ˇÚ*ĄměitM�´˘ ďRhÜ«Ęm{w‚/3î@ˇěČăSC�Ń@›ÁĂÉH÷ď�ؤśűĹŇŘŕ�7V¸†€_5µ/MÖD>{|elĽ˝0ç şńŚŕ{úŔ˘ó„¨Ž_参§n˝kĽˇ­ßM`H”ZQ˘yăďŐF ަ˘pѨGĺl3űŹĽÖ€ŞoX¸#A Ë>n }بiE›ćÜ-F¨I§âëÎNU͸ł„Ír\´ň,S~ľóŃăG¬ x¨Â<ˬ>&! ™(T× ÷2ĆCQ]—xÜ´á6>qÇšçĆ#hDń‚R¬‚KAó†VĂH~ů1_8B{–žqEÝ–W'Ć:®ušN=b÷ś'JĄňćőÁF včúČĽU›˘0Ľşă�á]ĽËł‡*M8ĆţĚ:­ăhZ3ŕńŤajô Ú¶b jQS†ő¶ęâJ”>ăž1Uh‹ŃşŔMŽžtŤ[:�Ůh�Rh®NŮĄś \óŹhÄIôé®|ä˘ôÖŃd'ułl¦L|5<Ó{s´"*<Ńŕ<â­5‡!–›ş¸ 4Ú٦rÝč  'Q›"=ć*ÎfΓŚ$MzNňUł§Ľ†P§)§�‰˘q’!Γ�k y 4;<řŔŠ˝§&#ăVRµĹD&š#Ix^°Şď’‚vsÖD%T:Oś$V1yżY.±D2Ľőµ KJpá IÎ^2Keť‡Žf_ę±Đ'ÇŚP.•Ű.D1“Ka,Ş,űĂĆC˘°ăďNš&Ś“™KDäă#ŠJŠÉ8U\WŁŰW•NnĄŔ}z4­h"ÖîÍbdj Kż—Ľ$™ŐŮ@Kś4'<áIHĆš-""O>ȦşqúŘ6öářÖ(ł¨\j#ĽPĄÁ©4śu…*Mó.ĎW" îĆů`g–‘PަeÚ»]ˇĺCž—ÖuŹ âo ¶÷ÎKĐů€Ö—ĆK ]@rŠ"ęťŕąĆőŕEŰş;ÍFńM 9 Çă ěŐ˛ĽŇ\2qQ¬Ľe\;5Š7ĺMHá!ĺMŔĹĺx+‚[Đt ­—ś^Ď*Ôl›ă¤&#°š*«v]b~’ę(ő¤ť<Ç{Ë,Rŕ Ę *HÝA§&Pyą~rB¨ ¨˘šJ^ř®Ú]!ˇ·Ž±0ËPŁał’E!h=XĆžÁÇZGżŚ /.=¤čśĚŐKSGŚű65¦b]ŻÎA®ÝŐĹrZqÇ~q .,®Ęt›Ľa˘ŇŻ’˝¦G`AĄ{Gšb\cS:Ń0˝@ w Ôü8hdł5€ŔGmˇpŤ¨€QMBďX»ˇTĺ 4fđl%rŐă¦Ýăt†ÝÓ«rNq˘rŘt¸rbS„» ˘ď"*lU+·ďm;DŻďA@]W˙�ą'öFŔčąVC­áxYŻůEČ;őŤ@› ­]ŻA¬P!Ąę+4ŤćŔ!ŔđÎŤŚĄ·$Î9đZâpVŠĎy ‘„.Î8ÄGűĘÓyÓÍĘTÁ¸˙�2š"żÚ Ö6°łbÉă…†”:éSŚ:‹(ĆúHÝň9Ä„đW~̉”6QRj›Î»¦p   <ĺ˘ ™Ř #¨Ď<%ŞËŹmâOxâČ'Î4ת řS%‘ŐTŹ ×¸j`Á‘ĺR¨,<YRî+ ÇŢ }ĄI8§.ĽxĘ­ő#UŘşÄ4Ţ ‡L6:Ź®±ĽeŤĄ®Ű2N;ÍW˘˛»˘čIÁ7„¤WVô!·(NŮÝň´wş”śŮ‘Jq8^Ôáł{ pouC­v{óĽd Ny_¬Ż ĽbŽťŚ˝yp×Ďyr �őnw¸nLRŠ &>A®® +Fâ€PŤ™Ű;źřÍE9)*ž†»:ŕÁ%&¤4ĽžĚ``šďČÓiâËřç°•řÄS0ŤŇ®S*§6iE—¶cS#ôÜŢÝ×’X¨ˇŃËŮĂŽ2]UTŁhĎk€?„˸ó„řp·9)ŃŐu˘bJTd*‘?&:XďŠ<śE>·‰<”5ź*}/’hŘ8U2&ĹHťřŔ«ÂmŁ\řn8Ö®6•Eb”éĽLú$¤P!RĆÎđXő™‘(ą=2Ó ĄTPXNµ®Ü˛†Ý ő¤ěÄŚ‚čEŕşsňĂxDP*Ŕ|ŕ ďă5Ţ.ŚSŰł"vťiç8ťbďKL×Ó“% lI0JiËJ:—“ĽŘ4eť÷ÎpK3Ř*ˇJV"ÝŕqxÖ•˛/ą«kŔnŹGxüÜT)sĐž..4üŃÓ~1|+@‚ÎŇwĆA1˘˘*|ęăšý[P47˘úńʤšŃ;§C±ťd+U)zB)CśŰźxŁBŔy'8DđôŁe› ó–żťlŘAH'M a0µ�ľÝ�C tßî×ŢóQ&¬ÓЉÁçlşbŁ ďÖPv_xzц± 6c|RŽL*Çxb›‚s…G#U.qqdfŦ,V(.~ jÍÁÁW)p řÄB ˇ:7|›0˘čTěóĆ2‰(A5�đ%¦±Cvź–±9HŘňIçĚR�ЇOuń›„)ËÓç ?HđĂox8—5Ŕ 1|÷u˘4#ř“|ŕ0�ńWŰ^‰ŹĚvŹ1ÓN§ŢÎiěSKŔ`LZ¸8ů_€QĘůĘ*b/!m é­{Á !a<ÖŻzóî)°Ť€ĺŰ’Ú*Żc®çśnDÁĺ Űáă$4‰ šçÎ\zWăö^D¤[¨Ő;zĘ6SŕĐ\,ÍÖ›^Ě’vď őŚl¤äÁťžLBŚĐlţĺŔ@z>Ś͵%µ4T»rĐÇFŤ cĆźŚtîcý΂�«­Ă#M#_]bŠ+9— |8ŰŠ;VŻfŽ&Ä'�Á°H¤ČçĘ:ő”Č2śUÍ"榫€žĘl»ő€ ¨‰ąxď÷„­Q)P§}ŕ-p€W"c"lr8<Ëx¸šôÚą§ í Şkç›@çŢ&�fąÉbë—X©ÎŚE«…uäÉŰqCŔÉ'CŽ€I2µt-ő–ĺ(*CCS}Y\«g)·Šű©‰Z«GS­˝¸ňÝÓôoŕęŕĎ…ˇ¤Űďµ<âDϨezá!‡jM K١ŻXÎ434'ţóŹ6Ţ…›lvs†Ú`ŕŢL(_z“v:çŢ HR×ěď˝c „,d ‘{_O\弾O]âîaÉ<R5€Übm´‰|€ 8Ň\°Ôěo'Chč_Őqj *’Z<מ7ŤŻĎ8ě˘a´N}`r( eT ®¬çW�.. Ý•ÇeccäÄ]UwĎ>ó±—Ţ8ëł7Đ—ç hýÎ+ŔŚ…(XSÓx«´Óäă*1 ő€¸đ#şôa|M=Gň9Ů1Nč`3~0Ô R)ë L%±:Tő¬,äätÎ~<ŕ9FhđF=ţđ€¤ ŕăç6¦¬ŇiµA¤Žä rŠfšEc|ÚfŻV&ň‰Íř0„äđoĆ ^Ž2ˇÝňŔ�:7’ ±&łm´±ÖUM¬®ŕWŢ1`§ż D�‡Gx‡°Úf䊢,çz3D vśľ1ŁGjÚPyă#ŽHĂ[^2ĂŠ=‘¶Dóă&Ů ş§;îó3KŐlÉ`h¦Ó*°@€B&ż¸č¬4~×®đ(Ż�Ą%"E†(©÷FhÓtćw1řrC%r'–ĂXňäőiTT9/–ńŽxdéŃ|>ěÚŻ7Î"Tžłůśžţ°4š pŢŚçł)Ý]dör×=^p°$:2ß§®3Sgh«˙�|âšž:9ĘňŽq̉Ó.MŘŻ’uŹŮń”©CĆőlRÍłdÝq¨§śÜÄHhŰxâ­Üלú şăĹIŕĘŚŃă©FwĐáňŔŚ=*„§>>W Ę˙�éë Yvˇ”Ę·EjřÁlÓ@ŁTס2v¨¤(ˇ˛ĺ]xąß©Ô€:Ű qͲ ¬KÄ ë/‰¨(lZë—XţąŕhŐžó ' mS_+ŹP?š)|Ě×>-l‡�NÍç xÖ˘ą¬x5Ţ C iµ ÍoÖM™;/ޱČBč'9[t­ó|a´'D4˙�ÎTM*s„ѲdÄׯ¨ť‹?ă#Q,.3kÓś0s•M­ęěl=âĆdM�ˇ۬�ÁHZbi˛ůÎţŹĂ‚­5.đݢb9[]łĽŢąNÁť$t8Š”uVŠ–/<bÔČsd‚ăÎ.–™ˇcśHß fo«]|ëŢ‹{G\gO˙�şŘ(–@źĽ�Sh°R„×~˛F'ĎgŽýĚ"׉M5˙�yzešÂ* xÔÍď¬|ˇ=ăô“'$‡ŹřŔnĹp| <÷–Y5>+áŤđô8ď~®UÎŁCŕő“íѰńń‡SP"�čë"Ë_;ĂťboáN¨·zúĹ«ä×Őq7şb¦—ěÉáÚL)‰MűM ÓŚšŢ˝cżď XŠ…š~kř1^퀎ęO,Ö/}Ç�đŹ ŔőŽÚyŘ~Çq2熢«»ľPÂ:OR׌KöÝ«‡„C¨ľ řÄEóO »Ąe2Ťčd€µa‹y®N´¬żŹs§żŚVw)]üw˝÷ĽŃ Tu5'W~ÜiJy‹<eĆăta„ÓX5*Y÷—([Ş4nŰ®2 _ĆTż† •0f˘:ÁXxn3 ž.G›ű9ęÍäWÖúÇĐp7ŞĺňŃVGxłO’DÓ>–˝áăÜHŔ‡śť|ٲsN˻ѱ´}aj¤Bľ ňň·Î jő‚hčâ—m‡bGßýÓB)ÁĘéuŢaݦËaäÖś�* ‰6qŃ‹XfŐµYcG\ăÂÜĘC ĺyó‘Ź»áúĘÎë|‘ĽIđ+Ń•ĆÜnŹŹś¶©ĺ±ă*mt_xĂn5.:hO €łXű®Ó4ĆŮľyŔÚA÷řĆ`µâőŻŚ˘xĘ=ÔŠqźPřŔA6e±ĎóQ>GźŚz0 N±§{ńÔdĆşdüd&Š•Ôž±Z=ďí›’˘čyĆߊQˇ9ľq CB‘:÷ąÂĽP3ŕçę4w?Ü’Ű)gă¬Ý„^}ă ¤v ăßY0çłZÂ9ÝNg¦!:‰Ëy Q^H!ćâhť äýĹ猱ňĄZÁ N`Yć´a „ŇďiËbŹ#H׬‰qŇ (V›Üťb9!ČËëĄđ¸¤-<઻ÓpUUżXlEä†Fé¬ýç�w%çűebłEëx†„\ DU:¸ÄQGcÁ[ľ&Śš^¤TQ]Ž˝áŻIÉtt ůĆ#UĽ7°…-ą,Ý–bQe|ď„T¤"iÓdw4,¤zu9ń†Z­AHĂ„%FzÇ«RÍY<^'x&°ŕČçŢ5”R&ą¶8 ů€ËÚß¶”ÝSĎ?Ył…Ă -ë4bϰáxÖl6‚ńŤVüáAäńŠ3śWN±7Ţ7Ŕ8ż"¨Ż(÷äp�$,Ż‹ÖVÚÂ�I[ˇap Z Ň}Ž6*łsXq\f=žqFąË‘÷É Úy#—„D §ÂVřw…ŘŮÇÎ"ş¸ysk’w†µ^.:@áˇĎśp‰­Ôę;p˛°¸Đ5ăĽDă ëC”¦ńĐ) P’VkĽł{ �ä/3]¸š%ߥŢő|8áaĘÖO ÚYća©Í�‚|ŔŐ1Z `lß”YĹ"Ńy�„ 5)ÓŞH>üáÝAu3uéƸŚu~L�Euk42„† 4ăXĂ”1ßÄŔŚ—`ÖĘeLÓ�Jhjnjë3š¶écë;/đçh¬ľ1&2© _°…;ÂGE`í±şzçŃŤ‰żOłÖ7Ü`:}ł¬ł°„R[ęŁXĹhŇYĺô÷Ś+|Đłľ|kˇÎźáCq§ç¤Ń@hřƉ©pü.řÄ#mÄBBt¬FZ˘YđxÁä1Ŕť˘ző€ěç!¬WFąÎË4xĆécä.Ęŕ8Ç@]ńĄŢ�ß8oĽżŤ}`6lŕí–|g E@şíÎdň¦ Ó«ÖĄq7ĹÎ"ąvZ&‡Ů‘ôAZ]ąí¶á©]ήNř]D&Ű}f´5čŔ{KŤ*MâS”Âů}ĽdHC(•Dâ˝ÜK)čc6lŇË——DAřóßXki“şÇŤÝÜŁHŔË{SX¬©;äáĚüe˛r±Ç©t÷=Ꭲ—ł—'�Î8Gìb˝`++Î@*ňĺŇáŃäůȰĂĂŠ¬yظűĆ*°Ľ|áX´Ë¦(‘ EÂ8âűy`‰8ł ½%@ˇßŮŽŐ� čň řpŔĚŮĺę°ľ˛¤’KÇ‚…DU—<ŐwłM7¬"$şý1PŞ;Đ•y6Öş®kŐ÷QÚŮeîălX‡ĐÝíÖ“©äPW‘8Wçśi‰ZS—Ö*Y ™{žNEťB»§ó8I’śţ0MĽmXůL7iĆ“Žy0Ę CĐ˙�nho8ŤtCéwÖ*E ďo @¶®đU´+Ż.Špő—t?8ČŚVĂłá<†vÓ4üŹ8ŔGśckzd]8‰Şgf/P±ŘúČXw‹JaÝf 5‰łuš(K…H ŻŁŰ~˛Qb˝dkTď ¨ś¸tç9WČôůÉSôÜďs€jľ;&‚%A÷·Ţ‚ úă#<ÔŰŮľµl|cŠXt¦Â04ňq10Ŕěüaíyd6Ö˘DĐc ;&±BĺlÔČć úpŃ®–3Jď“ă¨+qW4nȉUŢ)J4"ĚW4l‡ĺ‘E¤!ĺëU·”rťřqŔÉŚD°´@ßŤŁ°a;46¦ĽfärdwpÇ,„CfęN8µe«Á=h‰›Ŕ ‡?Ľm”O—ŁŚ'˙�A['§_ŚĐ(¨|ľ\…�ěRM{÷’‡‹‹˘_î$”*P^•őÄÁÖ=ŚŁ»Ă«ogg|Ů>�ę× Z’ť „븍ç4í¸Ź-°"4őŽ#gĽUV,ýLóMe'üŕę�‡ů$ż­¶†N Ľ×­0S·ŮI~1…°šLŕů;Á·.ąÄf¸ĹAśó„(N gÄ4C±ËŠş#ˇ|Ď›5â1F&§‡�Őë’+AFrL@,zĄX¦×Ł[1 hh§Ă‚×ÄÜ+éŢ]hŤG‹]aŔ‹§Z!ÇŽůŢl¬ĹČę¦Ďy�Ś)ľÓ dô9%ĺš“…Đ Ć›ÂhÂÇ '¬Ą­ľr†đ<ń’𝳨ä„E«1ú…iL­°­ď¨Ţ&ôĐŠŽ9Ĺč(|Č1ÝĹŕZNĆ=™FCjaĺ^Gx(lâšQ˘pew]6 x‰L6„$ Jš]ŮśĐÂ1ß=<ďJ T»=Ţ?xDä Ťl˙�—:.ϢxÉ©Ş"č9“Ľ Aµ=íăŚëŮtűçĽĘ57Á‚#´Űtałh]*÷řĘȒ݆¸ÎWĎś@›—`©0tE»ŮŠ ×}g­EěwŽ*ÇxĆk§ M†Ó—Î2 ČĎĽěóÖ”łoŢ,AT^pë bDë»”^f)9¸¬+XąŁúňůÂÓÍďSp_†kŇÜzůĘuö1Ł>qE˝uVÂŕW••K)ĆýdH­)O.´Šc2t'úßă ‡€QĐ0N´[ŽYa¤1fńŮV^Éy”L �Rí”Kă$ňY ¨r�ľ°ŕx´`'{ćŽôž[5C·qvŚ‹”$pĽş×ă�˘ĘGÁu´ÄÉł=°/bLCF§Âŕ?8¦ÚÓoBŔg0· nsŘŐW`pe'΀'łŤđ,' KWb4wrĎÄÂ!RY47'{‡đ_ÝT ¶˛Ţź<Ý`«Ľ šńÉ1wýMđMŢs;)†ěX .÷[ă ńŤl L@îw(ŕ{I€‚V˘›¬G™Ć˛ÁCtŻpf˘Ś.Üŕ|ď'Aî Ďc— ®ŃšŮ€D6 ĺÄ;i“x-;Ä<‡xŁ®§ś [O8GvŤź9(H‰ n@gfń<ź;źă�)NwÇ׬ŕă®űÜńÜÁfąrwdŮľ5„-zÍş>XаłNĎ-‰Ć:řxÄ_8 Ô0q( ă8YšUZd*‰µ˝róřç%±nK±˝ńŠ_"%Ä{< ›ÁhŽta„ÖÁź<áŕä w<«“¦QŔ e‡wZfę“b¤›ť´Ţ)!)i»,ľcJaűĂgD�O‘Héŕ;»ÝÂP�ŽÚKś‚ś=â¦ZyžqńäŐ§.BiŚ€W}kw8OžÉŃô…ńŠ’TÎŽéłx“Hą`×ßxŃĘBđHxśâ Îő[Öń!:·*R—¬ąËG>O3¦{ĂźP#Ĺ:Ăa%‰˘VťrJFH{%Ô“o¦éĂHţ/¬7HSBBfĐďÇśĐN Áô4Ľnń(„ÖkăŢ˙�ăŢ%(_-,ĽcPˇw‰ŕޏĘ!ŃxFM§ÁËŠ ľĚşŐś^±CYaP#°x0ĐšéäÁŠKZÖeA8¶©±ÄÜż>ć&5Ľ,ńŻ$űÄB’‡©Ç=`€y(hOî÷Ň AŹÉďµTŻýç% nT¦“q›ë)ńâYĽfĐĺ ÇÍxĂg vS„i{eÔŔAôă)ÔŹĆ!×éM=™EÄńŤ1Óď.$Ý»ÉŕBčŻ/<ë)I…_Ş')řÇq$˛ h żâ±;Śĺ��}b;ązL®ë q‘äĄ:Etň͢¬QĐŽ´đaőjd> Âw‰8Uŕě ÔŘAÓEębě#F˝P8ŔĂ]Šľđ‹¨Ô°U®ńq˛ áÍZĄ“ŞídřĹ«¨Ş‡aŢ…@ĽË…€WJ9¦<É<äe`…2BB´Ü ^AG¦› \ByÍVŚLă¶ňťŻő{ÎÝz ţTÁk鲏GŢ$ (LŽV$Ö°Ţ /ĘpëĽg{HĄęńÝJ×—'Ú*±«J)MîŐ™:Äpůş›¬ń‡ Ň„ &Ţ=$–U_ľ1‹5M9ÉĎSë8ďcţî �„CĽyçJuÖO1µĽ†én-ÍĐžpU§;Ţ=: ůÜă`Ţ×XŐ Ţ˛ÄÔÔ-!TÉ•PP/uă IŮ#ăi¬şAŽőÎK’ J÷Ö Ő(=ş^Ś "ŻeŇMd›hĹ}\ó€b…_?6mä¸%XŞë‰Š9ń‘6Ün¶öăĽ5#Ęi|ă±H%tňâî×DjóXďĆ,wE,i¦ŇŹÉ“®ńmÎFGgś5äbä�HńĽ"a&öÔb“b›NşL°„б b/ß=`ČÇ~vËĆ-=PP ™ÍŠd ä6,ňo$„żqÉP^#2ť ĄŔĺW'Ľ ÷^¶ őÎTâś!e˘µŤĄ]íÂüä„Bâ]:ď>´b<´€QaÝ}ŕPEŚv÷ŚŽUD)óĂ€´ţ”×ëś7•˘ś[ëO´)¶ŞŇ÷óŠ€!J§–™q1ťéguXy!“×hÄŮG‡x± ­;«çq‘Ľ'™–žÚNDŇ'x+m ˝™SÖ¦š:Í/#îQşžńMލŞ]~|Ó�9=-@ŚIŚä<7‘UC¦µŚ?<@@'źyČŠ@Ó_ŁłźT|`Q…âŐž?9}±fíyřń‚šĄTQHšY2&÷6Âućä¨!誚gťť-ö%šż8Ȣuóë*ät+C˝řĂÇMĺá=Ü–hRXâ:¦ô629%őT U Ł˙�ŕ SŢoß:˝ăÚ“|,Č)zuů÷.�gŘ;ýeL„˛ČÔÝőŚíŘ?Züa’ ‰ĹxŤýfŕ9ÔÖ °G™Ŕ± 9¦¬}ážh‡Ťń‡©E…ŽĎ üw…nŕÇCÓ[f?,§&ä' łO;r K˘ĎźX ţşŹ@v÷dı¶°tÜľL—ËFÚf§<Ü­;h†Đ;{ă ÖŃ÷ö ąAŤ ¶śxÄ´lE7G„eÜő—'Ŧ Č×Ú`Š´*Źhń¦î;Hî/FŰż(cš{¬FÎóĽě= bŞĘ› “˝‹5Ąx©zb_…Ŕů!�CÇĆ:81¨$S<aęb@ˇ ,›ăŚ ´°|ýç?pv##aĂ—VŤ’ęBzk ¤Ňěs騩“¨Ů1gĆS+ŮËR|˙�™ ™ň׾q@/@<dâŇAOLP ĹÎ(V Í”©óŠÝ3ů‡7W{Âä~±9.éŤ«Ţ G v…j‚»ÖnYlW!ŘÜÔéšă2ZCŽŁjcÚ;/ y<ă [:ąËÜTpk!¤ ¨Ţ›Ĺ}Ť )Ő»ŕ¥ظ|řČŠq„HúĽ`×&šäucâbU�UťĎĽ\t`?X`=ąpZ΢>«ńĽ¨/{m9Ö°W#vż‹CŃáĘnćńv—µ;ÁgY„ř_3ŁŚş}çg‡”őŹdĐcZ+’=dYˇ5=$ň\ˇŚvAď¤üă/ ˘tŃŐő„7BE )kuŢąëVo…[ĄZży*�ÓŚŤpćÓtD�†ŕ͸Ť@Şň‘ŁŁ‘‡;…ô%Ţ·}ć†�’ĘŘáÓżxEF eăZ¸źłMvŠ;߀F ‘>f5©ëłN :jë!•Wgˇ<9A…Ŕ±#eâ`Vë·yłÉsĽKűAdŐç„"N'X—nĂŚśˇ…hrSĆ#{ňóÄ[D·ú8 ą*ž×Î#' cÜ4ÇH‘áúó’ÓpŇÎűőŔˇi¨ĄÄ€‘2|<)‘éÇ~O˝ŹśŹ{ůÍľ2*ˇ‰ęe¸ä/ĆW¤ܢşĂjLpÇlTç—):�­›ś­µ…bSÝAŇY3ťę]°:1VĹŠ´‡ MSŠó„)X‹~ŃÍ Áó†°D†§+8}`B\EÓá>#÷—ž}Üĺ¦ ŞÚ™& •}ŕÍ^6ş ĄŠDkś.^Ű8iV7•©»tżZ®ŮŞtwó5Šř!xîśĺŕ„eŤşňĐXŘO/^·ŹőWRhm€vÇbßS4€DíͰJ§ŁµÜť`‘WŞŠjĎŁL)PĹ7|5Ö°ňì©'ncĆ#TP§‰Î)/©sEá]Ṣěě<|śřÁV a"~rQ2 ěęÜi›ŠHÔśyŃCUˇ~`“AyňmuÁ•Ł;! Á·vá¸&üá<ă‰ŰěáůÚIŹ ż‡9YşřlčMIń‘Žąďßanm}› ­ ĺÝÄ™wś?uÔĄ•ë.l’fńˇ•´čŠ6[\]k\p,,~ĂĽo,R“\n‡!ڦ\SźxłÝŔׄÂöľÁÄ=ÜŐ (¨©“"ctęěńď/ćp% ;¬ćĚE݇GŔ7Č<e…mń‘�‹Ţlţ,Ţ4ç¬tđ:†Ďeł®rYÄťŚű;®ŰáÖ×^±éç9jíÂE÷…c͸lâ ŽÜl ‡fŹ>{Ö#Dś?# x‡ď%–ś¨™^�°WQÁĄzˇúçúC&ž$śŤ·2®9d&Î8ÖnŰźNŇ ¦çă–…뫿ĆÉ…\ťŻÜr_:T€&éĎXăäLĹfĐI0Dm¨lĆé}-T;úâ`0VŻH›¤fśÉAhÇ8¨h©âc7'ËPI„xŇPM‘9HÝư–°ˇ Ă7C¬‚&Ö ş?Y¨Î?  n†7TN-QMŞÉ޸ĹIĺQ1µçI1)« •wś á!Ăydv$”qĎwL— ”¨_Ƨöse(Š€ţp5» p⣝s‰ś íf žPENąW‹âś Đme+Iáq<¸[7˘â+Č ˇ uďÎ_aaĄŢG·ĘSá›Ęy=ĺŔ…ŕňäqľ°–Q}ęĺ·ůDF➌žC§clźyÎĐt5ůNŕt 4ÎđQf”ŇS¤§‰w”ŞŘMč›ÁŔžvÍ˝…g¬Ř ç˘[ýÝş"Ń"8ßßx[J€ďÔ+bSˇ;ďX*ň€ć®jĐÄr°ŞČ>F?ĽV·đB;áŔšEÍ/ÎeśŕG›zĹnşÁA¬ c)¬Xň÷¨Óý1śkď# b¶®oĽi¬šß…ÁÉä¬hy÷GŮv{Äó8ŕđzIÎî˛Vőµ9‡ ë—’‚l4üW·mh\ĄďżX›y•+® Î&V2 S_ 9IĘ0 3ő›ň‹b P pë ôMaţ൝P]ť\C‚®E°‡ś$5™lÖÁ9ĘĘ×`RD-‡ťr¤2%E!ČÚ,_Ö!ÉRŠ•¨Ly2QżiKÚ| D6 jNŢX.ÁJ«˛¦ôNó^o”hGř1óşĐ�ÚŐI;»Ë"ĺLRÁ[€„Ř<Ó¤Cő‡vĹ XoŤĺěß[IßY¤€­% sŢĄo@ttóΧďZ{´%7ŕŔő+ @T]bÂ%ĚUĐÍĽ]x€MgĽď_ Ç”óŚ-đ±7áë+·D7Oeqľ\=źE"¦˛ŁĺÇAì6,ńĐ)`@T÷Šś°„8çŻ\fćh�Ň´TđÜ ÂiĐěăćh Ţşm˝ä«K€…±ç~1±á‡Ău”Ł�‚ę÷’»Ś‘Äá†öÔZŰvä' ňŽz1]q‚łÎl—YxjdŮ›'Ś ćoEV‰­zĂkß—#„8€rŔµÝq×°7ĺÚĺÚúLęűÖG»ŢV|m/8Wr•îë4‘( ŕĹČą=ĺ&?X2ÂVśuŐĘŇŹ% 0tjţrŰTż„ĆĎË‚äNѤŘPľłŠ53Ł1©×ÎŁ„N#„'#§�ôn¨«˝ľwĘő=ř0łĘB/e}c3:ÔŃË8Ţ?Z°ŕÝÍhóë45VŮGÉ˙�Śyˇ‡˙�#¬Ý&­V6˝ü`B™pXŔ+Ď>°— đ�*K׼|`,Ö�µâ»úÂJ›.ş% )ÉÍÉ*Ôo­~zÍ.vR‚yµ^ĘjđŕÓƩɖ††€!va…0¨b`äsp!ËĺÝnUцlü4Rň Ľ=Ç^qN´:Ô}Ú÷×Áî4 đOîY5Öąv×ó%uÓŮŤv6©L ]äĂô‚şŤíÖ><˘Ć«°č®2LóL‚Mt¤ĺĆAŔm‡ 7gáwűéŚ@›ŰńŹN zźOâYÁy?l„ľΕŘ} Öěßp\Q7˛űÎyp”řĹń­őńŠP)Ţ  ůÄN†8ž·9Nąö>Ü0ţ’ÓŔW®˛;ÖůšKháAW€ŐyÖ/gĚGCąŢ5XhGÖ‡ăҦ¸“ZńňLD ÚŹ!ÔŽđ‹ŠěĹ Ś˘ęˇ‰*]v_¸ĚCĄ&“vŽ| ĚYČŮçyBRŠŮěŽí`Ď dĐ ‡§“üÚ‰y¬L”Â`𮾬nµr~¬ĂS´čŕů«·Ö?E NLcH ľprâ= gŢoîZĹÜXřĆÉ<¦)ä1Q$é4ŔQŁ�âÜ�†ň%ĐŽ�ë4ďCX°ľq&E{†OĚŞh^u8;Q×m8Ľ4n›qxë›c¨äÇń˛Ç‡żxŕaľCFő|Ňɢđ¨ZőšĹˇGÄÁ #®M!–ŢE ,+ZqŤW đ©ŹcÄxÂ-®Âe‘ÉĐZ~0µČĐx”X "[‹çFŃĹ*ĘŚÍ/;74žQL&01lJd‡›0jÓ1‘U†žđ`ă›’Á¸Â¬Ď"lĹ1ž)\Ýe$ŔŠ.ĺ/ŚçđTľÄĂA>ń@7®p6•yÓť{ĘJë"sřÎdšPüşÂQ}oűüĆ…ěň©ÎM;ů—¨RVů™@÷Ť;>bc /ŕŔÄ-ËNńŇ—~pVČ‹gMđăľ±<6řĹĽ˘¤ĚŕÁ@úÍAĆ#x™°uÁüŕÇ— ŔL“ž -5ůW©:îń‰ą|ČDÜňÁëXľ&AyuŔŠ&M<T‡Ť.hńh A×[ü°9(ŕ‘˝ösĽfŞ7Â=¸KaŚľlÓSţŕ(ń÷…B­ •€MÜ׬Ş˙�Üó„BÝď:ŇukbÍÇZEtžSFs@/¤¨(©™¤„únRÂí2©­`Dçnça‹vÓ°-A!ĺńQRPI8 Ng˙� N…íxÜ[@Eüo'ÂV¬­JV×Ä'x#!˝¨}ĚC<Ô}uf2”ÚşăĘ÷ ăŚ%84LC#epzÇYˇşz+Ľ&´ó€vÁx7šS“ÉľLˇLM—Î%q›·€¨4ç€}cŁ_yGnąÁO FţÇčĆ´)CŤćËż}b3źťŕIf<Ď>±(ĺŕ‡iÂÇŔPžÁuď–ŕC@ś›o„yÉą˛ă§ç "”x+Á‹ŔNżÁáÁ ş˲~Ś'~6°Iď!eJ"ĽóçO-Äŕž|†zIWĐkífŕíśş ޶ŇĆ/P WďY,!§Ćn»úČxXZ­Ţ±¬†QŁŮŞů+Śt+5BDŕ9ÖVѬć·”a­Í+qú¨4Şy<qŤ9…ÚŻ9Ľzź?ůĘ0µ·D?ÎpCó–ľP0+ĚîS±ńëŢZŤ$ěď Đ(˝Ţ$E'›‚ę ­éL`1NwmwRç Qˇb«Ç|ÇŚ3Ę­ä5š‰B†ą…¸ 5“H!Ć&oăGZky›a˝2:l‡–>\›˛…j7*.o¬łb7˛·ŹŕÔˇYç­a‹eŤj9ǬjOŕłxë@ §“÷^ ˙�f)kep_\b¦±†f$Ó¶‚ż‚żYäí)qÓ«’"tâĽo鬑)9q™t9Í Ôő€X6ÝŕŤ&CH†�8.§ aĎxa4›ćc;2š ńQřś-µR«””şáË`�?,ĐîäÓC~5€5˘Ź>°ĄËzO¬pKV“Ă�…‹w›âü‰* Ś=H†Q] &¸Ţ&Đ^€SĚŕ;ÇÓh–ƵFŚ-•*Tô(X̵É780@Kď"@Ukz\j˛†E &‘Ň˝b„<Ą:yÜóÎPe Uňĺm‘śbóĹzďl‘/*Lüać7’LšG¤~đâ‚Zz\– NřŘÚɤ°UjĂ]ÂfDiÉ…˘–óÓśťp Ý81nŇQČś«ŁřZÍ€«şd÷ćn7"ÚóWűW@!˝qł6>ż6ă#2ÉIЦüŕ ¨atŤŰş÷‡ýŐ¬­€Ŕŕ: ĽćL^˘/RÇuë°ëŽ4´?q_ÄÂÂÚ?'<Í…Gä8÷EuĺëŕÁ: •ëľâôIý2'ěHfĽŔ$żg*ˇŕ¸¨AăđÎlk°ď"QÎ4Ńr9zÄç“_XtRŢĚ#®n±Cw‹¤\HŐ |;¸ŤtĂYç5CÇéŰm׬¬©Dů?˛—’Ňw¸~đ—çIY)MPfRý¬.‘'W…€Ü°$mŔ€ŕĹAXqĂ%đ‡Źśw™¨Uą _÷ŚW»YH>µ‰µY썟+}‘Ţ•O›\ݡӞL ĘŮTĄ«Ěu…[PÂé#gĆ9,6€ÖĂ˝Â]b^ÁPoÜŚk C…}ĺěÝ~ľ0#ŇÇŚ'RŞěAă˙�\e0;¬i—ťOX9ćíáĆ6Ţ´Ćż×%ˇF*/ ă ~¶ &‘yMßg٤g*?xVö&Šqç�&DE‡N(€ đ5çú#|=IŽ!({!Ô;`¬Tg%ăÔ†ËÝkµď—…P& ďśşlC 6|CÔ8ü • §]ŚÖ!ahĽß=â7Ö,Ş$!ů.Ij0éGŢđ: B:c×E˝EpĄ:gžţłĆß5Ź´pŹ>ë8(F×˝â}Ç–=ýćőŠM"™^PđóŤŔŐ{Á:5ň`˘(bńÓÄËXEÖ AÍś·żo@w†¦"‡ŕAÂ÷‹ŤXş"jđoů„  °éó9ÁÝŤˇ˘5­˘y1ĂÄo�JPńáĘQüűqCĄ©ő“ŚE»PP€Í|†«żb¸ÚńŤç,#ČçXD©đXđ˝25†ťETľđĂ4ťtJţ2â%ŕÇ%%”Ó­űÁčlç†cI¬ú9B‚Ć®pÓ`¤žÖíä:`ŁŠ‡{—zƱśfW]`ŠŐťŹ77V¨?č¨"ŐŕkÎM€l˝µżűĆ†ą’•áÇ^ xˇŰŠ:ú.ČŔóót€ŚĽ3/ÉgĄ#Á ÖéĹ›ÄMCÍžŔ#Ľ­ĘU[E´šď©‡„Á)zíÖ˝ćÝ* Žhăk'Źx˝€I¬5C´íÍ\EdQĆK˘‡‘:Éy:Oő–~4Ąúq:kźÎ…đĘüJOUśÍŕµ6µ/HžNÚq„BöŤVmÁ"SĂĽ C0 @gBí72ÉÇ"śC€âä�úúy4OŚT*_ąŢĹQut/Dó–"őŮS Ż­ŇőŞb<�R«ŰüKôß~Y.…O­ďÓ HüŽ%_->szµ9‰ĺ q€w(L\€˛Gű‘†*ŻŐ~°ď§…Ź&Íő‘5CŃĐOŚo×ŃA5ćřë ‘oW�÷íŔ;€‰Ć™ľpiŢÚúąŇŢaâď?W]j�8h¸çjPiP+Ću6Đ#WĐ˝Í&E� uqR[ˇš_ó-‡vîţrĆ1o>×dőÉĐ'űËü®ĆĚ)ÎŤüĺ;>çx ÍcJ˛B‹W`Çžq(k¶ä,Rцě—ăňżű KĺŢŚ˝ŞEA=ŽÝŔÝ8}â,M‚Uü˛¦X"żc…XÓ)JoďŰ0ž_ mĹA;ŽĂrÎL·.AŘâĺ=*S4ČĽwX\ła!›:Âl `C (¤˙�ĽÜNŘ[w‹ëBáđw‰�gĽ 4×IłŻţĺň([Ă÷˙�Xpýŕ’¸HxÂř¦»Uô»ÎU×UŻŠń‰„ç;pŃä'Ă4ţrQaŁO°Úô$t˙�1e_W:ŹCó|dëĎë’śPyIĎĆ%\R漛š˛Đő®ŢjMhU5O9ůvű0§>Çď8Čkx|aĄP6U!ĆÜ_DâpćŚ eq€ę©O ŰW!—Gz¸jÜJˇĆlÎĚ1 ˘¶íź3’:Î@lĹó˙�ěW‰u‡P3V^rd¤h%kć h?G#ihkăv?µč ·%ěÍä=?Óă7đHž¬cč€OëQ„«ď˘ó룅ŃCĚë˝+Č<ą?$PΰČś4C„ťCŹ~”?89@,éóĂ#滫ď×8šÄ€ňž<fń˙�c·ç»‡PhüyĹRŤ±ĄĽ8»Ç€}úĘńŮjl×=ÉŇ殣ă8TUt]~S Ąkď6dCEr"Îp{§"¨ ^ [ô7Ń3k%Řiw>yŢ55ѧárJ›z·É•1±&ľ÷‡w° Ë.1ŻyR‰°ťăéę¨ŇjďHóĽ´,MÔ:‡x~Ĺ7hĎîhŤ sç�˘/eü],Í[ťŇňŐRŮ3ő@5­ťâah˘C°\TjoĹx\UĽ"Cë˙�E)ěU~q:uřh š/ŠeŔ ^AÎ\1¨}ë4ňőřËNň„Łj[„"SŞ ˛5š8‡…B/\Ü -7q—ů¬ :¤DĄŠ“Ůî. 6-€Ů„â„5Ioë:A8uŚ‹Ălc5#žS[ă6H|2‹¸“&ˇ�‚/ÎMŐ5ÇŽ<e†<‘™BĚ!8ÖăPYďĎśëC Đý»É‚?WŤë1Č[!ÁJ#ŚŹś2?K)+ýd‘¨Á~Kď'ŤyÇţĽdű¤Â�ÖŁźî:őĂa_Ďë Ăp]Ľ ˇAŐ=ŻËÎ ŽĚ€kb ßq%p0 ) űĘ…{|w‘áb­8}ćîć8!Ő·ŽS«Sߌ)VÁˇzÖ/@´Eźó"l nśŚÍ 1ş3šÇř-ďcÇĆ4óK¨ńG“6•lŤWźĆEŐÜ|u̸ÂR"x˙�ĽŁ˝ď I`_ţ śÎ”1 Ď<ăfĐ źK°gĽB9ç`îęÖŢ ZµČ}u/)ń‘Ńv‹Ë“P5ď*¶€°ډp‡󀑛P“4HńŁśPŚنySŻŚQ8¶ą9P´Ľ8KÉ0Đ߬ ĆNۦ “…Ž…ęóĺ,đ8řÄm”.‚<Š[ÝÄS´ ŕó‰ž]>&&”B=äb ű/P§Ď.°Ú¸ j?Ś4<qx·Ć ń›˝›DÍ%Ź?Ź8Éś ăšťŕɦ‚řpĂäo!vüŕĄ>UĐ9UfËéÁB8~.O÷ jnµ?DĂ€t2ąZĽ9ż¦‰©Î݇śyJ+ýďšF-YÖń¦±*W?mhĺš‹ŻŹŚÓ1i˘Ź¨ł8ňůË Cü˵ˇŢJ¤Ř÷ŹPFĐĄWĎś,č`g˙�<cÖµ)î~p(7ŕ;D× Öşw«‡|Lş|'ĚÄLj(-çëŢjÖ &˘˛ż2OĽUZŮŚ>*I_ËăśfŁ(JÖdÄŔő‚]Zďă"T<˝ś(Ő(­=8ěôWc^ű€S4b'!łŢ4ŰnOÁůç>Łs =Zâăů’˘YŰŻĆqŐç4 ¶ś&˛yGL©8ďŕčÖ»ăyµ>yÍEŃ {FşÖđPˇôcŮvŠ&ô »ŰqĘPšDá͡Všs±3f¨+g¶íĆßXRFÄS|CłŁ×ÇHŞĹ�›.?ŔŁ]sqB«>‡*@ôŇ+ďeˇ�úőśŤÄňóĆľńNô×Âś=ŕWĐš^s‰qÜBáׇSl„ZŹŹ®xŢTBŐ‰“PňŇt=|¸soBDőŻĽŘŐćŹ4­Ź-ÉjŢ€Źł_x’#xŻF٨yýűČÍ­o„Ůď(íШNök}8rÂ5ZG˛sđáĚIVĂŞhÉ4BĐń¬ G án¶g{ĽÄOÔËF‡é†ŤÖ±�ăˇ\G-ËBW¤pP¤GŃ}xĂnĹ&——*JÛ谄6±çď•5™+ŁÉüĹ÷'Śo0 q¨˝Z}.°8Ščz¦Ü ˇ!Űţ0.wŮÁ÷€¨ŮšZłĘäţ±ęů/g{ç|ÎWš7ăÖ„Pm<ňş2™U‰ëɇQ9bš÷ă Ä=šc)ó‹pÔ›9…˛öŮ•óFń˛/ľ™c€ Oo‡sśb$P­;śH ăŔ™˛ŤŕÉXÚ:ĺ:Á)ÂSő†ÍJÂj}ô0haęB€Juľ'8ą‡LŔťś>q CrŮŮî'ÎCG0Ü)tá¬c?H˘řCŚOíY®żÜśşçő†Ł„ŇĹ3ĺÓ�*Ű[q?8ĘČŞé댗ćä¨Ú‹ż8:…­ŮŇxď$AÎ÷Ö±ż-näş<.<ó {›Ö> ’‚Űíg_8Ă‹gIż¬`ÚŐę~±`ŠÎŤĺĂżń‰g‰ˇÜz ŔXěq˙�Ü�>°Â'šŞüýbĂQË ž;Ć Ó�Äôůó’�`+Á›ÖlU¬P€Ż;m‡D â=ŞC�†¬2y€HzÂuQ”PŚđÇś*�6Ź ëZĘr‘?7 Šu=şN˛21@ń:ÂÁJ,ĽŰϬp5¬ĄźMŢ<Ć×D}řÁ/Ą�ú?›¸Čj•4.żď%Kč<ëĐqîŕî'™ĺ{]á”á‹–Đĺ;3BŧŚC|>@ď‰÷Šş ěŘúĂ+ŕÎxżnäg”ĄŢVŁ-höĽ ᱚbÚsńŻXrŔÓĄĂŐÎřJD^ ‰Ân«‚âµŘŻĎh˝pIpŞĘŰŃŇ.:®˛ö™jgŰ=ťř{wXôfŕĹ´Úń&.˘Ľ‘îA1 ÖĆ rĆĺąÁK¬<hą1‰ĹĹ…á9­]a:¸†ôn<đë”Í]Ďy[oťŹ2ڍąÓÔđ®ňěYZ.Ó7Ę—[ÄT#@ë]Ľ#ˇČŞg—Mí=d‚ ·Ŕ}ú3z”ZĄŔóuĆ–„€’v}yqŤ‰‹AEQ¤ăĵ]†Ľ1Ň´(ĄHŹľć^ď†`Ë€ăÁJ‡}ë�T=Ëí}NńŃMACÄÍáŠř:0–�;@ôâ´ăŚZvXů1î—‘xŹ[¦‰C[ÓÎ�&€ Ňţ˛uBěĄĺśú0ɢ÷ !ępo�Hz«Ŕw ĎZP©ő}ŕ-yŔ8Ů”:×y ’">\žń¦ĆÚ˛ XS“¬ínźąŁŻ}dh»bŰ‹HÝúűČ҆Žăł53@ áűĘSB­~ó–¨!Eç—óő› ó {Â\´EĺĎěYƱ.ŽłCNĹ+ŻX®Ľá2Âł€ÖT·ËšűpmAyzćâ0¬0R«Ńë#wb «ŁŚě 7^N®0€†©G•zď‚CÁ˛X%0¤0˛ßŚn–ÖůŹć/vŐCÜX.6PÉ^×đbľřu©\99…đś¸� V#-É×fl@…ěq{Äz©¬vÚë‹‘¶ ‡* ĺu–p@Î ¬"Fý‰„>~°˙�ÔĆ"4žrIś#=…ZNńP‹ ˝ü,ĺÁ<Q|—Béđɬô¦kÂüáyMzuďó„<*ÝĆübz ß\É -đ©#Âńś{ ¤<ŮÉ�ýăŔTN…ĺn˝:"o97i…ËĎŰĽpŔ´ř…8_BŐÚÉ,"2<1›Ü›ç Ř]±¶rů]jŰ´)Uń†đíŞxöů…PŁ·•AŢ쮲Q( NPńĎÇXm.8â6rdWY4Ľý:Ís€ëlüůÁPâ Á˘|ĽZ¬=`˘w„ęv¤Ż|&A^!éá;†SÄĆô N°„©Ęř»Ć)t–(đ¦=OL6GϬCVĆ_AÁ‚�“—÷ŚN(ZĄőŃĆ™„жÎÔŻŁ|ŕ� ¬Ý˙�öpďeËeí·Ź8ÂccŠr Ă»p¶I!Ť›őë q\sĺ1$\G`”ťľđXCŐˇ|™jŤŢoĽLjÜ…N‘Ď:Š3eÚ÷ţaĺ “Đoă.±NÄá$„#€óß9khÜ{•9Äd@HąäţY®R‹@/öq†ć6A➇Ś%˘µÇ—Ě÷ň›YkĚŻű‚  \ŠŠ¦î,’{:,#°ášs=A&¶ŇńÇ9`ćśX< ëÔ 9 5`ˇ"$Ńbl'ťLâb7Xč9éĂđŰfW@®ÝńóŤ \Ó“Z/N&ś5âuľtÓ Ă±ÍoľpŤ¶kťř¸iá¨b#‚ÍŚÍĐ‚«{ËCĚ‘HsöĆIŚüľqHlj˙�xşŢ4·Ąś-Ţ?J�8 ë׼Ő, iJüĽcŚ 5«°Öáë °ŹĄv1uĎ80-ZäÔČč`Ěĺ>{ĆĄ¶™łu<ňúÖ;®ó·lLJ\P·ł;<d(ŔËYÓžĚŃÜ„-mî¶§Ľć»…†ź/ĂTsUçYO5xçö¶ňťZô$?w뤾ŔcŁ„Ţ–n˝ŕÓO цCśŠşPEŘA/9jŽ]ĐëJő<¸OôžDXžpúbqtęž„¦7¨`‹Ú-•ŻŚ^lŘR7‡ŚŁÍ’›…ÂĂţś*‘yin`GŚŰ–H€FsŁx Ĺ –Ő-‡zÎr0€QÇ®đ9´C¤çn«†ĚŞ‚ŕ ÁRŚMş:׬zřL<+ŕ}ŕu.ń2i: Čĺ‘pI*´t|0ş Ą~8Ćjv´ÇśŹÇŔNř Oč› RFݬ ąIó Ż® ¸=CÎăPQ"„äă—yT0Gl,C†ůÎhHN|űÄs§•}>łXˇ�{ćńŽ38W‰ň±ďË []/§ÇŚkE‚5Q“ůřÄI#q|ŁzřÄ\�µŠ…Óż!„Pé&WǬ] ‚ÎŕQkŤĺaô"nvřĹ•° ęoaÖ%EłăŕŮšLlU6˙�Á‘@4ÎŹŹtm5@;<:—ńŹ{•ňţńšÎÇJ»ÄzĽáŮĽlCž˝¦MůŻ1#Zůčă4ţ»ŘbGÎňë߉¬`ŽŢC¬‰ş÷ŤŰ›‚ÄSâăîŇT„Şl¤G,‡”N»|çr‘¤}árŻCĎő€/!�×Ëő•‰Fڤ=šçlŐ NĆr’‹ŻŕĆ ™Ö†üľpČ<˘«^|eŐ•zOŕÄç˛ qď�îh}ĺ�çťecJ¸B'ď ¬ GëNŘľ&X.Ę6µüÍŐ);3Ż9OhZ€oPś Fhµ§/Ţ:�·n®ń•O’�qľrg%ż›¦‹řÖCËĂI˘qá�Ćg#Ä{˝ó†5fÁKeŮęţ˛Â€Žs&ő†Pëóv’m>zĂ_뙸« SäQĆĆ'\ଓSŁÎk@]Ţ5r÷lN{rsÚ:-aKfę@‘-řÖC0'JKęxćcđđDRtÇžţ± L�DŃ›Ă!÷’¦–šéď7 v–ÓĹ® ! v¸yň5»ś.(a«’‹R>ÜdđÚ^FŔ›~°ů EŰźîiOt€ňřËčľ1Ű bĄŤu…Ń!�_ôÄť-%ç'ś]qĐGćä܉7ÉÁ ł7ß»OŽ]á}Ě$t]ä8 A·Ž ÍâV‰éq+ÓP×8˘™­«´Ě â ś´Ńßśqd ä‚=ëAW‰¬}­Yľňh *—W˙�Î�y/÷™EHď|Ç+„´69»ľđąÂ DđEoÖmŤš€^ĂÇ$Ę^<;ćĽ ń°ž^µ‚‹tpůČ=’¤6*ńš3@Nč“ó„ éŐ°˙�ęd‡‡˙�ŇĐgT"/cřͲÄBDö±Ĺ†‰şvó˙�Ěáđ•ämžnP JÂmŔŢn „6äkŐő›dźUwҫIJ`‚ÜîAp$×9iŃi/cƵ“5ů€Í±Ę jöÖóŠŢ‚ ŻY¬+Oe¬â ó›&VkŤŰ $rR06»f˛°Ń~äËÍíRż:¬x Ý‰ sç9:J ëjĺ \’¨˘ZČ^GĽăě¶č—|žśČáş­»!*72…óy眤şUaŘ?ë(SW—‡ÔĹ8‘' öQś3NOŕďCŔŰ €ĆÎÉ_kÎŤ)đW®ň˧XüŰfü‹–AdQzĘo%R =ú˙�q†j>Ů:qptÔŞâÔĆůË÷A_,E$ZŁgŰ‘ "‚â !%mÎÇ$‰çÓ€ˇÝŐV*Â%‘íň äg@9.ô{Ŕn¸Ä#@2 vcŚ^‚Ou ˙�2?b]׸`…»Ä/I‹ˇŹX$–$OIÓš u‚ VôčaĹ ‡ŚxđppÁşŘţ0CŻf& “ćP#ŮG¬|b$Ż…śi˘up ŕ sÁ„ŞĽň÷ç9,7¨[ ‡2¤eüŔ÷śIWnK•'<⎲=qÓ5ďśj­cIŃôÇrdAWPyó­Źc‰bÍ%•>@‘ÔřÍ_¤»Žů<L Š?őŮqć€&}’vĆé¸&Ťß7\ĺ?îÄh®Tď«Ć$ﯬq‚Ŕ'Wą÷kG»„ë4Uýĺ™!+˘WČ,±ŐŔ çF .ťťťłłĆ$ę2ˇ+5č8óŠ’©(Ť�PđoYgo:7nkŁlŔ’Ź/ë\´Yʢ盛e;b]aŰűž1FkZ˘hFü7ľŘRţÍm”ĺcŕŃ Ő�bťň;őĆ*— Ŕ˙�\_ż„/ÖČOŢÚě ŘSŽľÁ„Â_ycMÜ˝iě/ÖTĚH¬QňŢŰŤ-1�j99śg;PjcZ›T «/žp˛„YŞ*‡§Ë»Ž¶‰hlUĐőŠŇhmłç›‹p5{sSűĄŃCĺańqÖ7~ŚÔŽ—5żâAÍŇčeŘVvzĄ©,ߌ�Îř2„–DŘNĚĎɦĐđ�zÖ(UčhWFů C  ßÔ•"Ú4U˙�0 ćžŢe;ONGV ďü\+§ŢÍW+Š,+p…$Ż |c`Δ™C}äÜş ‡Ú÷Ét¬%z‹‚k`(śţLaÍŰF8O^r(”4‚ő;ľ±j(¶úÄ>Ą”޸Á‰Š ć‹HŰ]ŢcĽ†ČăoŚyŹ.�ýŞÜ ÉÜN…¶tUĂą.U(˝—G~]a?.€r ůÍÚ}‘.\ęăĐ3€&Ş|bDľ‘nĂ©´ęë ˇÇÖiExÖi“i�›9GŹAy^Úчěx€ĂéÖ”j\iţ:M¨ĽS·ĚËinĽůľ3ťGi1ăA–Çň˝đrÜú€řHź*÷—~đŕ˙�rŠ.¸pŁkiĐx‹mJ6—Ţ,Bŕ:9‡çu¸éVUwÔŔČHŃđ?Ńťä&ópŹŤf¸ËŘůdwś°Ă‘ţĐý hl/(=ââ$+ÜĐt×9˛Îéx^|EĽs„"Ú%/„µőŹÔ•Uĺ{ŔOb° ž˘–Zëeydŕďőś„”Žb• źĆm¤¸$ŁrŤéëłN°•Qn2g†Č1§ĂĽLč8Šą |.8Ól9ňĺ\ )Ěó„+# 8ąWÁT1µ†Y‹ij) 0ĺpŽ0€żdŻ|L�WěĽ#¤ő–i0iQA†Ŕg^qă$1§fÁŃŮâÓ’&2khž4mÓů™I$ßnKE`�ŮVýIóšJĂ…hŤ*%ůÁ+(¦‚Ŕ8~q‰ĘT'7ŁŮ¦$şuŰ|—~přpMĚ©yŁ` ŠÖśQČZ‰…éŰĺfs[şBU}ëśČ;Y¤oĂ€\C7¶řóŹ]ôĄ­â^Xęl=íÓ®2 ŽÄ7ćűʵKŻë–d€|B;äëXUC§ňŠă_>Ł©g§/ˇ@ëŘŢŘ‹P,MŐţ¸$¨Ń–yRě$¬R!P¤ <_x4›$qpĎa@ńěÁäÄŐ»•nŮ3Ĺ™ěąŐ×Ůścwńs ëśŢŐˇńôń÷‰VŁwL¤4Ő©ňe_a¸”N›<«€=w\‡AŇzÁhhľoţ2~aV…ÓU‡ś˘Ş"rȸÝI€®" ľ¬ łśŢѸ:# ĆńJ&ŃĆľc]JÚÎQäŢZ‹/îW­5ÇěOĘç ™EfćCĘ ů5‰°ŤŘŽJ¸ ţp%ń(éÇď°P!#ëó9k`BÚ!ČkČ^80Řf•Á# č«˛ękGËŚ@Ş”>Ť(⇕Č$Ýń€,O¬ BT„9ÎtÎ#”şq!91946¸_‡ŚăU˛'i;?ą0ˇX€ Őť Ž‘¤UÜ…°<¬¸ &˘…ŤÎsip'J<Áç«1ú°ÔMuăĚÖ"rö6*QdŁ€.ş Z•úć·ó€±Ć<&ś|c˙�„.Îčě „ĘÄkv«č¦#ˇSčv k~ńK—đĂt@š5p‡:«°č|µn-GśťřzçŤD˘şW âwąéT)śLvŰ6PvÓŮüq $;ŢMÁz˘ŠĆă7N{=i\g…ŘyŰP·r|đŁÍ”śáŮčh> ýď/©PŻśáBşÉ§UŔ É<—ţđQY5�uŤ„A¸Î ”.:4l´ťËh GxxA4«W¤vU-4°H»˘;$õ&Ż>q9+n´SąˇB—ĂąÖíeýä™4Ý%‚VxÄĐ´˘KěMOĘL^Ŕ—$�4đ;!ÖSéáK<Qń”Ŕ¤}G¦pâIüAěö`ęm@(ńw˝ßL ďźxéĐ˝Q§É–ţđl»|ňńчaÁ™Á1˛ů™ĽŰĘŻä—ÇŁ; ¨\W¬ĽYh&Ô€WJŇhÓĂPŁ–€d¶ďO§±+„xĐůß(>D} :óDĽŻXď¶n3¦˙�ĚŘ7ń‚Ú»řř;;űÍS26ę'˝Óá90j�_%”đ*á‡@§CU~¦méh¤ŘřĹ r&EĺZŻ—s~Ď€;těpVR†ĎHnwś# ĺÜíÂÔ ›jÎ]ܬIŞĽçŻüa!k{¬ŹÍÖč- w€Ëk¨� éŢÓŮ}üRG:7üÂJ–žŕs†Óč_‚ůĆ~ÓM5ŘőÁńĆň\Iv�ZN1?ť0>ĹČÎSŚJEAZŠ9%Z+ÝĎ Ke‘iNt°ë•¤(ăRČmĽž¦Ьx…÷‡w´üwoÖ1˘ČTőőpnŞhAé`^`Íě2Ş!¦ďĽ1-E #smT łŹ'¬j˝i4GE ±rŢşËÂ4‘čĂßłRpĽoďŢ<yşp]d‘H _ya®p,DĘ8ő­ ťb+mďß÷!"şžLS¶§ÇÎ aÔGízůˇ¨ M·˘ă'`´`óÓÉ…O*ďë1ŃÇry@#Î c˛2<>šd—`P=ř˝g> Dí°^Xa—b5ÔGŠ®.ń~0IíŚ]€mW7ÂÉhŃG`zUčYDm;ůʼn–×'Č—e}᪶ÎEGbćîB‡OtééXě¤vcá٬bZ\ß3+ś«ô' _QÁPýl'2”~•¬C´9‹®ÜÝ1ę.Ţp@usŠ`–`Ü”]ő]w(âçń‘}*­�äkŢ7l¨$Ĺ|E"93©ßŮzů޶w Ë@ă}âm©PyËĘłn5‚TyWőĽŃ[㯦_sÖQZ©Ë… /¦3Á řčé‹K&!¸Ü–‡J'#ŤJ�#­eVG+ÖmĘ„3b wăĽqh°éhć8Ę%–V›˘JKÎçś Ď5C…Ń4c;řÎGĄOĆi¸stQ¦ ŹĆ(d˝>Š« ’ � rŔš |@ Łäü0<Đ‚a°ôz;Ů>;Í.éUXE×ó >0ĽlśË{é3‘x›ŕ'ŔżxŠq›ŇîyČdjɆu©ů¸¶°hzü6¸ďĺXEZť˙�Ć µ€ěŮ­5Ţlv6űÎjľ¸żÍ8tx9HtőĽt ŽĘYN|¸ĹWŘ_És–NhÜK§ íŔ…SˇŢOXö˛ÝĄŘ=ýu–#C‘÷Žc!őąč :Ö"(€CRHGăřš0F•7iĆ ±p‚^ć2‰ĂCdyoóHC@oéőé\ŽÝ#±˙�ś4wh)÷&o ŐÎ(ˇwĆ}`Ů9ď<ÍÜc{ÖŃéĎŮĂŢpŘ—ü€Ó±k˘$Ň Ľătaö需|†  ©<g#0ŻŢlľýťL \EýbVłňŐGĆhVrJö_8�AßÉČ!ZűĆźďŚÖÁ7ĎĽW|xÂä5RÁ•kj„}nB”…„Ŕ>Tn X›ôż+€¨P0Ś6r]ăž%�D±Ćůáf<Űl`¦ôu‡e°ć¦ÂµŔ ĆÉC cýj°*¤řÂyÎ�‡Łuxó‚ˡuë&ąiúĂQZÚÜ=tꙑîőµđaőŠ’‡‡*˘—zĆŽřŽÇh]Íx|e$đ”đĂl;K¬aFÚT| 5ÎâňCGKÄ8ÂöŹ XłwŽ2•C›ę— qyf)ěâFé`Ző‘t ›±\ěă‡!�půěń”ŚŔĽ!>ŮŹÄ<ÎŁÚsŠ.2U§|dĄ-đëfCgľ›¨šBúČŚ�§Ó2Š—&ŔŁÄ¦˛`ŁŃ‚J{ç• …;°ŘŁFoĚ5Ö¶ť„|xĆŰ <[]?x…·SĎ6ş Nž±‹XއŢ€ĎţĺYü@­ĐW¬P!ČżőĹąĎ'yş™§•*ĺ¨8ó®2˛5úŔÄđńÂiç@ČQi<SiěŠe)ĺß8ÄŐ’l 0!Ź=XşR|Şő‚ĹR°ěWeŘQí "Á¶ ńµW îYAü0ęJŢąë �'|jć‚#Ś©ónM"ŤĐŕ¬zeĽ+´ţ9uŤkFú€ž‹˘·Ä2Ë 6ׄr. Ě…b(Ź^ůĹ„=Š€6aΖW]ÜC§Ťq|ŕ0*,$/:äk ´I&ţ:]fËICIj†śď}wš´aď*~Ýáµ"cňĽŇ`VŞsĆr {Ö"„Fé‡pKŕxZSď1  Ňţ2Ł­<Ó˝!©Ź9aĆé·ťŤďĆhŐ* C{ř†˛É*\QśńLŽŇTHŘn“h™˛OKe1]s=P/=ť–c @z˛¬7Iă7Ú.Hů­í¸&› ě~ň„ŃŔ÷‚Ŕqşý…¸%(ÄKNĎŚAË_r†Ě]Ąu!E|)©ËjŁ(ß 6]íyÄěě ¤Ř€÷¬QhbÍk‡&»† őh+îÄĐ﮲$PÔ.‹ÚŕÔÇŽk)Ć>+ÉĹHeE.śăÍ8Öáͨ{Đíő†ÂşAn -Ű»ĎÖI.üĂx®¤r*(> §ÜSĽc�ŇňŔîĺűĎrÉÁľ‰á0~6[NwiˇçCŽÔUŰ{ GŁ—pÁ  ë¨}ë4Nă ‘jŘś¸-8°¨˝`Y€árhpUUÇŃ –ËLä{i J X^ŕ‡ÜÁ¸NéŔsŕ›ăVúŤ°«˘su\_8&¬_. ľŇV˝( ¸@$+hşf‰˝83ľ íßmL)va`2h±î€�jhĹR9jşrK u¬…G!k¬ëĚőG ĺĂę9Ë Ëp*a¸› m¨śL WiM%v™8>#B HxÁé{B–ŽÎ†p şAËÉ$.2&˛·í‚őq16Ä» Ĺćg$8Jí€Ţ™“€…0ÜO(ŕŚÉÖv�Ô:S"s• ·»—ÎzpŁ’ůÍą[ţŐşMyHnőí‰$/ ·řÂK‘¸Ş‚ěi­g± şóđôҢÄ_ŻŠuŹ2ÖDÜ!üÎ4ń…jWh;šß»ď5är0´ë@úÇ…p†źÚ 'ßXb·_ŤŇ?¬ŻMš>0’hxÄRó‚•!ýrDNÝçÎń Áöa9!c{šžU8«QŰĹÄeoÖ:{÷”SM®QŠń®9 Ůă‘8 ›;‚ĐßXB”óˇíęM>!µ©W°’ľňáÍx»=Ŕ»;;Â7¸&‚†‰É=9 ’UDV°ŞqÎđÓ0áAvO#m[‹`w«hŤŐ|fćŇmöhqśf`UVOë%`Ţ4¤|`†ő·$ë ĽfY�öç @ĺ8g2lKHüńçƱçŃ+eZĚz\F¨�šęď˙� FD €L䨍n€ r4ŃÔ2¤x/¤5Ť‹â=ߌ6M8k§¬ :〆’†iŃÓ›÷i5Žž^L'É×YIu‡*pĚçWńŠ–Î-w2�J'ĆçĆXŠ č‘?Ś9aAZ‹ĺ‰ą-V:+Y>pZ@ˇÖĽÎŽřĆ0H!°kMj÷‰¨›Śj Ż<ŕőUŇÉů7$Ú ×É‘|w“˙�<ščĹÁ"P¨x‘˝7Ůžóe/ mŻGuzĎ Ţ.ůË2Ł_óČŞ•ś˝×:\”I+ĄŇ§¬8Š=^fF‰ÉŠhá.Ó(GŞ °Ć¦Ăĺ3c˘žT…Üń89ÁźiĹŚ ~s~š‚‡˛łGÎGú IIzÜĹ~†P$ÜőůÂ:|¸‘ayĂ#=ůN”:9iŁ)Ň… Ü HO`¸äCÎLt:×F$„kÁŻŚYWDSßyJć…†±čđć•51Ň ŠašĚgÁ?%EzžC…ó¬Ł-€îĹŞÖłp[Ë…YÖ -@E/5€ó˛p·*G BĆG ©NVËô¶fĹ?.Îś¶ ­ĺlJŃPŰM2ëĹEúd:kďü|ăG†Îp„ŞżŢ4]p™Ľ-Ľ>ĽÜ—»2ćűÄů–×§í¸ŻKčÂa“Ź#ĂŤ&#^÷, ˇ>ţ7“ć@÷w˝îůuŽI0VDp9ă“Mä JkͲcQ3CO`Ĺ€I–¸}QĄQ«]%đÄj`wëW“#ËVoĆLX/dÄ,/—1)Ű~0ş‚ŐÍéRh|ŕV¤Î4oźśA(˘n2ĺ*…Ö¦šőAÂĎŘô(ÓXC›VĽŕ„ĐŁ@ťĄzyÖ2ÚŃ`\y5wŰ«&/źť€±á¶M®şÉ…ĺÉ[Gś]P-îr5Ňo9U:…ÜBhó‰ ·Ô˘?żYJ(shÖiâai:l ‡¸ŮŞŻ5ÎT>SśĽFî `cĄÉ°ËŇuť­7‚pa8p¦§xîńzn–ďő—«JŞ(âkÎĺ‡A�¦§«>K˝ĽŤňeŚdNA!ÇÎynpgyŚRÓŮš?®iň6»Ś0x¶˝1¤čuĺrŕŁV:KmÁUŢđMŠ]ďn¬óë1F…•:@p2dpçśÝš|bY©‘Â&.ó”Šđ8ŻŘGQ-lć15PˇHšM÷ÖŐ‘­•ŕ7Ç\ŕuúwš™ĂCÇ8…’–ąÇ$MMŢqHX‰lż—ză%ĆŞÖ´u0˝™( ‰7ďE ` ‚p‰±4ŕKu 9T¸†Gn¦QDŢ#zŢ€¦{?XÜwĚß„Îd5†Uĺ}â„hůç Žą™@8Nň´ăy§E›áč" 4Uă+k‚a…« jŔLČÔ;§ WG竣C3“ÂS¦Wą–ŘEčćúĘRLBÝ]më—+őbÝ7„řď¶–·ť0e?vĐ[YŕĂ2%­ G´ŻqŽ™€& ŤĆ |85Úq€ŘE8Ä€lXK¬FE>L6S™ZŢ/gÂţ0çČs…7„śiĆ Ł–ÎöšÖ¶ď�3ůLÚ«´Óš ¸Ń¸4Ż#”%-$± [ňś´^3tňlůĆ–8çäviľ3|ţŰ”+o.§EĆ‘jƨź·YŇ�ŹC:S?ń“É»Ň˙�pG_®%‰Nß©/g8pbośKďç;ł é;ĘwĂqĐÔ4„ę@”\­ĐĆÜDX őůrÁbQI6.ŢţóÄŔHm´Ô«|hÂC Á»Á©ę8rHY6í|łśśV‡'ÍĆVh 7P AČő\ЧŘď÷¦÷đK ”áUKä´żśvú\çчĺ”ď=Y9{[Ń‹ĄI€›Şňlŕßö Đóq)ásŽĘË—=ńÁ¦;%n:1]ᢺŞDélĂÚřHĄĄk^?Xő+ŽĹŘTŘ\αTݤŘ19r«Ćî?Âiś’ŻLć4 VŮŔbcň´{YĄ%ŢKŰ"TŽśž˛tş¶ĘM¤sĘ`7Ä´#w$Úv—ś]u®˝ĺ˭ś7č!ç ËŢĎ™ űŔë—|`“rÉŇăB¤W±Ę ö&”A(×bďi–HeŠq- Ű?Sî) rĐiSµ“ " -YňLQ˘RŘŘ;|xpWş÷+iK‘wÖ%)�:”\Łă@'~Š .0ý?DRZTŐŰšÎ]9 áuŢnňßÖiśçĽ{k.ŔŃ”„ăL-‰a”p2Sáň‰Çf%kä¸{ÎPG‘šS–·Ţ8ŃÁ� “Ő8ŕÇ@ŞÉćŤ^}fâÁ R@öÍďřî– §V˝ňh)pµç…é‚„â…ÝŔWFî ¤ecµĽóqö¨ę"«AĽRˇ. Ȥ{ą´4ďÖ=›0y ůŔ3÷šÝ÷ÎfďťüĺĄyÜLż-ŹQáxČ„KéÄ}‹,H3xu´R*-K’0L†ŰűÁěA˛l1ę@ĂzĽ`pSµMŮŹ*5\żL\�!Żś�G– ´´?!ă;=ěŔ^oţMQţgî9Çé€^2F«śY‡­10ŰÁśŚÔl3¬× !lisĆ�c°ů{ó‡9&ËT¬0ĚőqŰđ_yŢ5{k/ú0,ÚH¨ďµ÷ŚäĺÝ—ËŁ•“G9ÇĂ´É ŚęužĎ9ž#ĎĽÝÝ®Çáśwźß(ŮĎśMĺ|âľzÁgorĂ€ţŘ�€&8=9»7Şé~°€äŢx€ćýdž·b·ŁQŐp2. ńŤ1ÚQƵ¨ĄŇâ�@M©ă÷ Ë@—«^‡>A­ěËŮŁc7ŞVM®˙Ä�*�������!1"AQ #2a$3˙Ú��ĘĘÄ%hçă:°Â¨+†˙�丫 H ä$%d—^d•'ÉCa'ľňűĂ€çx÷‡÷íʆ' Š ś˙�Íj†á›Ą.]AZ?µĺűň$Öxď j̤(¦Sküg n8•‰Sf¨žŞ€Ţs[¬';đ á˙�«ç/ĆłYy<É rvőx$R‰B3xÍßÇyx=Žü2Ţ3őś ö]V Řm¦hřbńO±ŹzPo#ő)Ęöwv&ú =ě<^^pt0ţłń…Žő ¶•GÝy˘@ŁŘˇçŞŻ{VVPÂkĽäO—dč§ě˛$źęEśĄo©Päžd…"•ŢłOET†aЬł—ÖY¬$Ö>Ôk@ío°ú őĘň żÎJ;^˘ń±íLĚ ’S0ă XýOxXxËË8čFV09ĹXçÂĽř˘7|çYfb1$~LŕťWš¬azQY_śá”yÍ™’Ë;L$ěÝŕ4l‡*’/5ă]>@Ć8••¤â:aßC¬‡°÷8|ĺ8p0ÂF/éîłÓĺrädEHËŔk,{;*ŽýSćůŠH<XP«ŮU#˰Ëä(FX‹Áźó(×@>×ě?ˇó”nĹ߉y(°>`o{˛q$(Ô_Ť8ŕn»/€śÖo„—]‘ŢVĘěZđr@G…Ś{ě)0€p�0ĺ~rđô/:ö?҇śĺřÄ๸ä7Ö›;`ů>0qľíęĚ`€Ń–'­Řij‚r=ß wߌżsíßµ`Ă€ý,xÉ[>0ťIÓť_ę}rÇăÇbRĄiŕő9@(&&AÜËĹyç Ţx“ŘUjîłŔĹ7ßµá÷Ľ/ő ŮăüňBh'ÔŮÔ…PÚ(ł•YµęPÁőÍýą6Řq ŚĄ ŘúćĎ˙�/°bRŐ@ň(ŚľýşËţ×íĘĽÝ˙�F îÜ Fó’¶+˘·ŰP Şß©eřб°Alvb{äÜH+ÚćĐo„ HĘŢs5A~©5—ÖQÂükŻÍĺ`ţŚ/¬uŁŃE¬OA˙�YŁ´cn-.ÂDśŰÔw¤"1n]|iU‰!RA±ŽC:*<ĺ‘Öüan#®XHU yxŇ ‹|„ĺeg@ŕ7í$Él˛+‹Y¸’0l*®T~…€%Ű3ĐČí”pĄúL›ÜX„S”Ă#°ĽčÇ"şň}ŻŹYYv1h ś¬¬hÔůĽ{żcdd©ĚVA‘ŢJ-¨Ú ĂqÄŹcšŠŤKÎlHüË_‚i`dŮ”FĂŚ|2ËSnH!Šô­c/1b ű1M¶F Bńň1W±×µű•„÷Ź*GŰťípr=˝v=rýHÎXBÇ#Íţ<žWŤ`ÚĂ#FśWýX€ż$#”z˙�Éš–R¸şę]Śä€+ôĽ¨Y°0ĺY˝¤˛Ó,^—?2¨Twi ÚHĂ˝w`˙�I/‰§ŢŽ9V'X˘´§_Yşę˘J¨° ŤŚ`‡Z3ŃĄ‰I6Ž—\rčĆrDäEJőł§ů]I'-ńČ!L…Tç<y$SK»`snY&ż¨$®{VUĺ ď/ŮŤf–¶ŇLZTł0Î.żęŃl“ó®ł2É ´Ç=Wna9ÖĎN…×T,‚53€0ČÎÔ5ŐÂö¤ߨňu«łłäăÔăaL›pU¨Üž'bD�şíŞŘbMdzŇG°ŽUÁńČ]eŕ>ÇŰ[_ć.múp¶#h×íühl Ž0­`F�XSň}Š=BĹÖÚ}t—‰*_•uÜě0°ü¤ŚIQ9M7¦Â–°Uqx+n¦đIbš)äŚýuJMÁ Q© ±‡(NŻŽĹ2x#g?ö:9Ę_Kj”©žâ`Nó,†ÔçY˙�FXŮ•BTQŠÜH9$|Lhd§ífEŻŹ­ä Ć%,|Ö±VĆK ůÇ×$ mzşăG=4žLą %I;d+Á ŚI*žÝÜśYç_rkÁí­ ŽK,«<YµD0á`<»K˧™¬űôqŕ3Ľ"Ö°€,c˘ŮÎ!»Ć  €„�ŃńIǬx†47ŤŻšŠ‘Fç4$W„qßrňqÁČě¸ú·ĺ?FŤý‚ŔEaj»†T3�!ŰhśĆŢ·ęš©2…M€âńłŠśda#$Śő—†Eid\`Ž4Ȣů$^W‡Ćpuč­ś®° ¬ÖqVż‹'<icvfVTnńDCËé6x}HÚa•üaVsŢ”J®NljÍßM;‘}tµ_] ˝~Éď°,ôŠYÚÚ]hö¶ÂĎ‘L„ă*‚ÎÁ$]iŔRÜűzźŞA®QLL®”yĘý!UČç iź‰Ň"«Đ~#8o6V°Ńąěüâł7D]fŻ+7µ#-*ëÉd6OY ú˘Žđ!Ŕ˘ókZ=ŚOˇ¦u!ř‰ŤŻłâsŃh¨ÂĚTŻmÝŘú˛DcP‹G�> “éťË"¸¸Bg@ÔI‚ÍâŔF–$¶°=Ą‹ę ą°HÍĆ 17‘·sr`Wy9tv%ş˘VnL0ô1hőž ˛ů#K=ćI$ŕ3±¬ ‹>˘ä@@ôť“ÜG}9ĆQ†G/5 ,ă'Č8“ E<_nĄcS§Ő&ĺő 㪟öxřś*·‘DÎ@zÁ{i¤â(~ňIŤq]ITő=2$ĹŰ@ +Ź-Ű'ä$« ÝÓ19tôԢ͑§ČÖOŚÚRиň¦VÄ ¨#_ă—’út€©LV$P–TÍŢEď GUąB·˛xLs[`H)ŻĽ=çÄ24Xú J dÎ.ËČÎiHq›*$ő(˘ŽĄŹh5ä܆©#Ű‚SIęËř‡Ë j3:źŚúŽä0ÇÁý8‰"µU (”ŻĄUXĐôŮ CÄďq0›ÖúČ,€E$pŞ7#›Đ÷]3Jzśd˛°ŚA:űúkî˝®˛[ž!ÁŰÔ 3»á#ŔTbŔĐW<E!>3cZ7’ď“H & ÝŘ đĚ侌Qèć+'bݰÝL´íš;í ©Ş!4­3lMm«+yÂ×ŕyÁG˘eŹUi¦š=€�qŸâIÄŃÖÚ-Y,©–m‰NÁ·X•|úb€zČ!€pž°/ďóYĽô><`8ă(ó€�r$S ú<E9ú�°W—[ýNŘHče…Ĺ#óŞWä› háɢ/hÂt@ŕf®ˇP źĺH:É÷gäČÄŽ1Ë#Τ"ľFęěůΆ3q™$çŢP&đń8OGBÄ ňZ } @ŁęęĂbŔj…Ź„RF„ůŤ‹'`aV9·¬$^că6F,``ë/"kďŕ7šzß#snżI‹¬ŢźŹřÁ&úçĹ€ŮÁwCÓcâĚřlŽ@á Ö/Ř ő”bČqdj.ň5K¬y ѬëÎyĎą<�5çŕDVWĚĘ-zď5㸌ń)ýáë¬#±•C$™šÜ‡°0±¬f&ÉSg4Ô,,ŕ˝HŁ śŻBWŽ ¬Özˇä‘’ çÖEV�cKÇjWŹn c"‹%„XbkĎ˙Ä�.��������!1 AQađ0qˇ±ÁŃ2‘á@ń"˙Ú� ?�ě™s7O÷»ý®Ů“=پŠ]uű1%©!Őß­űËdëٱ9|że[eűsaČŞĘ|gČö˘ëĽ…Îć(kuY¬tÇ-îű' m¸~…óľµC2śřŽs^ąEkZđśW±ľţBpZżˇ¨ëSôőŃ‹Z¦XkÝŔT Ę=úôyJ‘ktT_śŘ˛[i]›ÉÓS‚ÝCöíˇAK8ő¸şyąÓą];ŮÔR(]ŢúUŐFüĆ.ń˙�YSŞžž»h°éd%ä]5ę„8ţµďÎŻăU%~O•^¨_Ô°ňRąŘĂ2……NCeWĹWg»ěŃAĐcĄÎĽr˛~˶q#żůěő8Wz%’–‡_«ŮšTňâ§âký;–{pĘ‘š|ź_žă…{îUXĆŁŚů î·ŕ«—Q†qOÁüNZä·Ű‰üM>-Íô*.cḣ˛pbő1©ëŚgŠÓçCŐIó‡>»…B«·…¬[đő0Ó®P_Ű+1g{O!¶řĺü@ô\űśXu|íţLwTůË ümą†PŁ%›ś)XµF>A†|E"É5<t­.Ď™Š«ß¨ăé´’Ü7ř×% ¤ďoÚŕQďˇdšĂUk;ż#ÔźäÂÖN3ŚI(ó#Ôáe+¸Çš1/(™qËĺŽ>©őeárąqĐŕräT˙�'0öqu’ÍŠ&â>×_’ÎK=×ÁdË~–K,LžĂu]VŐúĺŠ}IC­,Qă^řÉÖ^şďŘ*—Wď†Ň> ZG)î'.ôˇT* JŞ~µEÇŮb˙�—qJ0ÇŠ•\ĚK'BĺǰŐ{¦|†_4ܸQĚßËl”<_ŕňß5-ů.¶Сó,Ę,ʡ˝Vzlř]ľ«×4Tľxe ¦Ť¬]űd„<‘|îŚF&bpąçt7(ĆĆŕuĎĂÜS7ÎËsgä;—É Q‡*ł|î¨[rŮŮtđ7>Öl[.+1Qž]ŽclŻy÷hYY*8cžFĐăžĆ4üPĄîb«*ťą/fÎ7Ďt]z ss‹ŽHÝy¬ďë“Ëľ.žŢ‚˙�Ż!ĺqĘáĎĽÂŚ6đĎţ—?‘âŻsđآŚŰäâ)Oóű0¸ÓŔĽC\4oc§ňż®L¶ČYßCçšÍ $Ř[ć…›…aFo±ŘßGă+Žý1ĄŘÝhz>Ý>9,öëŘUŰ®á•sř8{ćµŰVĺ–›˝7ëäÜŘĄü…e&˙�¬÷ŻĄfüżg˙Ä�,������!1 "AQ2a#qB0˙Ú��ö0ěT lKßě˝FUÉŁĚůsvÁ‡“ń9GaÜ�\Ř•¨/Ä ůŚ�Üńä�ޱă|ą(˙�ĂąýIĆăŻâGµ>3ďPöŚŐ;ŤŤźRË›.EĆr*u9Ź"ŮÉ€đäę^T<ÁwA¬ Q`Ü#öŮżŻ§úfn^aŤ1˙�â<ž&UÍ“&WÎĆ?ĄkÄ{\¸a¸ĚD�ťŽłPqrü„­š?ŔÂâ‹ú?ô=;ŤÇAóą#3�ż÷ţJŰDĘ•p DôMLĽrůx~fOVf/ńË Ń`/LÝ\@Ŕ‹öąp\˝@oË7č82ť‚kČ=—°î˝m†u/ÖřÍ–çú“0ęŤćP"P&Pş€ncâeÉ}x^’2Ěމ”0S“Ń[+źčI› śľźŔ\cřřĆÁĹřŰŐqŞ–0łsÄd,uđ©żÔ¦$ΠJR [Q˝ţ<üy°ąlNŮ Ű3_’goĐł6łŹó8Tâa†ŃTXŞ”Ăcb‹ÔbÍń·hyŮN>ł/!˛žÎ[qŽ·˛(÷Č!éˇwě Ől0'OŕߪŕOŠĆU`ĐÜ"QöT.~ßGL'tŃ6ÔD:»´íŰ~çĚ?ďIö�¤’!ń "}őĽh1‘K2śXÖϨů{›(ĆŞś.ëťYpă\ ő˙�V…NżŞŞźô5íR˝čBŐô ď蹨F§c[Ď…ޢ1é…Ŕ۲X ;š_Fř‡"śU\ °*Ä'ô>~­BĐ}x‡p˛2aě€Qâ61ز}Љwç ?`Pzg(\Ż‹ŽE"i…ŹmO÷ŘKú._˙�Ťš–Č f˛*“GÔr04ě@†Éś?IÍśv>›ééĹS5@Mř‰eÁ%kŢý€úÄ©»ö©Rˇ M�*hěr˛9á‹ڤťŕĂňdUި*„#[©ŤmÄ$ĂőíR˝ÎĄKâT7ć Ź ĹţÂ0Z€Ł\›9Ü%ËŹ˛áâeĎ—ă_Hŕ|äȵB BżÔ� RnÁćçćyź÷ăŘ‚ †T&cfA0hQ÷#öćžĆ5gú)ڎ…N”ł‹Ŕ~Ć6:&şYt¶¸ťŃIÇé¸ţEřů:}ą°¶6ęŐ?ŮB*Ř•¨PA6NčJ÷ˇ?Ö·EÇŁ¨1Ĺă$DÂYŞdátb N�S„u�Ś.ŕßE�L8°© R–8' §*( ¸PE°Łî™Să 8Ü`ČN˛–l‚f 3*öÓy†kŘ{B�O%CŤĆŹRf5·ÇÚdfÄK®,­“(ą*Ž\ä3h¨·ÇŹ!§Črń…Sň(0ĺČŮÜéke…ě!Ł3Ňçśřm_TĘéÔ]€Hăqßs2Ş?PJ@ˇćc�°íđ‡ŁšűVŘyÂÁ®Ý­A Î@‰ŚçUR8AX†/ÔOł@Úe4naĚŻ@ňřJÎUl®ýËŃĐ[­L8QÖÎ\J˘ÔăoǦń0äČF^ŁżË/^÷^ ±ú1‹a|„`¬yUş‡ŤçîĹ‘t<Ĺ�Śhhź¶zgTěĽĚźóZüą Âě°l¶NČ;Sců1öäńńf�ýnAŕđ3ŢĎ ŃGaýŞĽVO&<K÷z‡;~1U¨TŐĘú?>‰ N/9¨ü_ĺo·çä" cĺśł-žĘ­läP¦†,퉉ŻQ,Ě“ŕ ąÉĘPuN/;6âff˛Čú…ő�żxčő|§lŞ»łn6Bč,äý;a"Ë/pâ_P[Ćăŕ‰ÄFOµ«ńěbäb`♍1�ě/a´k"aaÖ‚°üş¨rFf핌Ûâce¨ĚyšŞ/ DĚŕÉ©ęŞ)7™Ćěl/ÍÔSőĆăA6$;ź Ăä'd ńdś|źňP)®‚˘ÖÇ-T�@cn}Ťq` Gj©µÔFoä•,ÖÄÂeK0;DĘjâć$o›•˛2‰”Ó„”˝Ł(>_ŹF×äÉŹűüŞĂ]„7�ą›.2[/r Čľ—ÇĘŞ~GÂWŔCćĐdb�.ŕçčB Lg˘€ŤĆŮß'ěpFĄĹ Ť5ÖŤÁŘ—)U;ŠaGř”˝cüČ|äăvîHąřĺćgJĽ<¬x2)ËĘ͇'_·čoÁp#d±@-čPÔ B#U(ÔhŤfůY3‰ĆÍć:˛’¬|@Oĺń»›™8워˙�ńE˛,“BÉewÂŹ�­ÎK-�8Ȭ-ą8‡R§“ŽäÁ…–j¦§ ÷ž@ĚÁ‚¸­®UşŤť äcň3އ%=¸ăÇű—&Q“!ČäęX®)±šV ŕĺ<l…lAČů?“@�s›±ü‘ ‰KS0^ÔGYť~Ă×�”@Ý@Ż`,ÂŤK€Đ&)Q32“J‹{Ž˘®!Łn2ô5ěî€0¨bP©*×±`ťCp“;˛hd=DÂ…Ť’ŃäkôÉŤ‰N71˛(Ć÷«$ŢĚ[&†N ţ7@QZŕń2?QB®(Ř�© j#—keÍDFs®€ _ć=v4ŚFÁ°7ąB‚LÇĄ y ęąńXţ+3Z„‘Ťš¨ăuž5+÷d̬'+Ž´^.ëŘ2`vf,l¬Ć­Ř0Ź‘÷.|LŤ±ćYˇ ŔÍĺѱv,T |˛…]!căŮY•¬wČ+ TŘ'¶ŕ-éą^¤3ş»ň2dÉ“łk(ěX°˛7Č6(ŮL ,€y$VÝŚ‹ňĐ)YPX6*ĆĄ\ÇiýNF>Ony€KźĚ?�Ë—čE· �,[,ĹŔ‹�ö:ŠÄčŠ'`.2ţEźjČ:ě‡Ęb"GuPlř>%PŠ™Şóěłä"Ţ‚ŻV˛¤blě$U&0P6H¨ < ąbĚm\-pű2ę0ŐʨÍ�˝źöŁů�űľ%A˝EęRĚÚÜ:$” ĚÄBL'۰Dř#÷ُ‚\ą—§ş¨äŤÁŕ öTY¨Ţj5*̡p(Ű˙�  ÂF'iŇŞÜś]S´eu†TPC/­Řůť«sä7ÁťgIÖ˙Ä�.�������!1 A0Qaq±đ@ˇÁ"‘Ńá2ń˙Ú� ?�ŮČ _ý ăb2Ü”p·5aNĂfx,%ő iţ"ôżdő_P0/®Š0á;I>J1˝Öó’“ąű,7ÓžB ¶AÜëŔ&�!XO¶ĚÍ ˛1+U†çó˝@=sa˛ŇátřWŹő÷ܡÇÁEČ"ÜÂĹům2Ö¨BPË(ćP¸žh^–ˇ”uŢŚäj'®`‡>h:×d6a+ eÓ˛ úTzKŐŹ\´ů[!ôĄ—ť2]aüßMž¤yÇJŔî‹“ęĎžËĐ]FO#ÓÝ_őÝ?*ëý;kěL­¤©' †ů)Řźd ţĺ˝7TQ˛eů+ tmÚ€ÓĹbü÷Ž«ęµőrľ¨#pŠ>-[¬Rx.(ş‚V·ۧ˛ąA•ůŁ*đáĺ=“|ţ•›«Ň lQŹ”SęŹoę–O–Č»!š(đ˙�(JĆ&Ú¬R¦šW o,ŻŃŮ丬L±?JIŃĂvS’ËNz,4ęŠblxg§~*xńÖ†—[ÂÄŕÉcrńŹŰ,a#‚ĹŃ9fEŹ+ň Í}(ÇšA!‘e(°!ŹśýČŠ,QüŔż±ŕö⌣PŹ*\Gńí\UĐ­ĘůĂU˝XĆAAw®WC ŐKđŽ](±7Â0)­ á‚(Ch®ŚŁ(e7řV¦›!˘Š* ĹAPřL–d3Ănł]* «Ś’7Šh‚l®Čç+ Ce1[îŃ%–©ˇ¨ÉŞmFČßľ‹JÂ(×JYXĘ22ÜwZVőÓÁžÝłÚš(FŐ Ś á^ş(;wdżdQWÉ„GOfWAÚµ.Š2ŁuaBˇ©EŰ®n›¶Ň–AΞ †şĐúSµŃž9‡Ą6Z®čí˙�˙Ů�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������beetbox-beets-01f1faf/test/rsrc/abbey.jpg�����������������������������������������������������������0000664�0000000�0000000�00000027476�14723254774�0020503�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������˙Ř˙ŕ�JFIF������˙Ű�C� ( %"1"%)+... 384,7(-.,˙Ű�C  ,$$,,,4,4,,4/,,,,,,,,,,,,,,4,,4,,,,,,,,,,,,,,,,,,,,,,˙Â��á�á"�˙Ä��������������˙Ú�����É=âŇđ^qŮűv‹îsŢę»Ď{ľÇޤŔ«¦§’QŁ•7r˝Î{ťW}îř E±áÖĄşóÎĘ8dÝܶ×Îu~ďX‚ČŰŹ*ßuç%žÉrää.Î{žň»]¨b¶ľ©éB‚\ÔĽťÔŢ{Đ•¤ş:ąKjB&-—8·ĎŚ,·,úŻ˝ďCćń­č´I€ĄÎs¤ł6lDî«-ď{<¨ ŇÁ§>X®¨WXaöş· :SC˛űŤă!7)ÚĚzQÇSŧÄ1Ĺ–›Ťâ×ďWs¶‘"Ç;Ä+ŢWş“솝hnˇI‰_ ÖšWÓŽű‡ßdĄÇ/Yť¨çíĂ…E†3žë\zlĘáŔ6łěoŮ/yŕND9Ń΋áň—-Ř—–Íą‰jé6wŕămEWęđŇG(Šó>÷d° ýŇTČ´ ¦*©E™*Ň}nRÉź+ľ´HmˇđŢI„pVŹ�AÔüś Ó‘[×}°zÎq'? DśaDWŮ9`X(‚˛”Čľő”lP"ľ$ŔdĚŔqÉS”»Ôr8@„ĎXÓSDąĘôáW*öç ”śľ@QžK9š»%3éoG^·”@´ Źś"ët¨ĹŠ’W¦Y®in B&mţÖ«öID¶źÁśë�ĸ ć¸†`gG%ŃŞAßŇŇlµbš÷‹ç¬@çŠ^¨îaZvÇ©Ô)”ť¶‰®ä+ŮNć*‹5­•śń»Jfˇé@]*št&fţń…nT°Ö‰Ľ…›â‡_zĂl]j¶É×úÔJĄŹK¦si¬bö}ÁȬt5ŽţÔë™! Őg=Šf‹8ąí}‹Ö“čÜd4ëÓ} !o“ZŰ~Że¤rń{âłÚO‹×d˝A¸Jy8čžoW°ŁÔj[.ŮôÔ¬L1őźłsĘ˝Ękýôćľ×»—ÖÜĺŹTCÍ«žjó˘§Ěř<)÷†Ň.éKäY�Ű5tŐ)Ö*Kcčd{˙Ä��������������˙Ú����®�Ż˘ŘlĽ P%Ź_AÂ[,vÔp ’ôOvÓ—’}ł –}§ŠtˇŰ gŻÎ¬\í¶YíĄv“˛Xs‹L„°K×H%$´Ś™ËŮŇ&±›bö<Ý O^aú9ę&/�ÓÝhR±ť´— ňŇüŻ}Ę×J^|ĄíDçk-m¸˙Ä���������������˙Ú����C”qď.ÄäŠOG\ŇŔ&ś•^˝‚›i¤TÔěŘe”GC3V–ç­ťÁŇ”@éŠ,ë#iĂiB–ظ5sv!ťEpDť(†’l!i»<ť±ŃqYĘĚš™ě±Ý 2äL× Ą(©©‘uÇ˙Ä�#��������� 0˙Ú��íŁăá”YŮ3g*&˙�W}]Ôě*%Ż‚!lč×ôťŰ×ÝQeÎ:đťj‘¦0Ő1řŚ»oÉ oLÇ _o®˘ť2uňÉĄ`–ę¶#t:°!ţôĎ~Ć˝q V´!öż  ŞÔ°ÔŞ«=<ÚŘŐk~,i[Ň6ăi_Ó´kÖš¨+۵í\Ą^BI[‰~ÔtcŁíů˝vŃÍŚ÷ÚÍRĚź*3IŮ;3ŔPjôëP( LŰŤ«LAĚU™¬¨‰šBź»Ę K¦”Ąâ±ŚŞF…ţČćşRŻĎl®ĽŻ#_"fęéŘr1ŕL»ťčصµú»ĚóRo*2gý»y¤:±¬±¤‰;z6ćLç”&hÁÔĘĽşféÔÔWŔě™V„!ÂYvW!¶6şö¬U?Úô)¨§NÝE©Ďbśd8IÓ “@" á\úş7­[5IX Bc ľ^%@şwńč' »·ý€Ž|n a51ĺ<™­B.N6jłF¶esScV0ńů¦™J›Ô~<\­”ĘF›ČŃ#ŽŤ-7$C<rĚçęüîĽxϤ(Cxú¶˘m°HŤ'C+Ŕ—ň.]›ĺ 4šAÎú޵ě}ŽĽ0ň­SĽz„÷ĹŁ©kÜL'šÄ}Š°Í•?Żô ”ćVo Cńf†tCł.Iof2ČKPT#d÷ HŚŃLŞ›áűĄśÄJżĎ÷÷Y®VpýqŃ€®ĽĐ®xdäw‹ĆP ‰M†·„ň˝ý<¸Ů{ÓFD®0B.öĄ8ť QAź7h8EFxÚ[ÖˇbŔ]︞4C аňN÷%nĽk 2’ŘşE“'ę¨B'ŽŇŚš żŽĐeMěU}Ţo-&¸¤ŇiZʬßů$ťe¨O’i|ź7ÝŁŘ×č ĄPŞůş6Śľ,I˝DO]ś®ú4Aói•®Íušźř+K`ĹyŃÍŇžmÎA§7idŠÝn=©ż;–C ëÔăúzô°0őůKZ­Vu›·ýŤŃDÇR·ťVµ«ž*a/Jľ~áĹŐĘ©É׺ä{&˝rM´(Ü«ç×Č Y+[FPu7äýZ®ŐÇ^+ŤJĺHd}^@%1q3IŮ_Łvšňóz»hçeç´ź­ůłş”¸‘{ĺ5™Ů ™×z˝Sţc>ŹSžť˙�dWŔ¦ýň™ş“®.îý]¬ÎĎç2ó:óç¦[úqtŇƨ˙�ŽBRwÇôkąÉ+ůg“ńÍ_Íä4ĘJ+‰'éşŘCU–'|‘e.\„¤¦†Ąß˙Ä�B������!1AQ"aq‘ 2ˇ±ÁŃđ#BRáńbr’$03S˘˛c‚ÂCTŁŇ˙Ú��?Ň;h$™0EA)—Ś5ŢŢžĎ\ ’ I¸ZaÝč?޵űDkM`€RĹšŚ'>€5-˛.‰ăŽ#6ő„ëľ·A-yDď Fř#Ůý©ÜS6xPcŽ^<`¬6ŞqlË–żXCj]a—ńlŔ{éć!íT &sÂŇą‚ynĄ^? {?ů臜=áĂcµëăąĘj¨­\ŤĐ’�Ď”P”ŇŁî‹©Ö“ü3Çt 4˝™˝,¤ů>‘u™ÉütŤ?‚ &BqgqNăTÔl…-ě€Ú}a°&Źĺę“IzúĆţŹÎct$«ZrŔ÷EËş˘îîfČ$GRBÁÖúyBŠŻ*w‹Fqđâ BĐ©•…ÜźľT…üŮH™đ9ď…Z®ă¨ _x � ?vÍGşł.™)ť[6ARa3ŔEŕÎEÚ‡ÇÂP˘DçGĆqtŢpď7ŹôÇ®€ŇJO?8i›EŘ|kju(łâ;Řď€:¶©VÉ‚xc1bĚxq‚vsł|lKDźÂU«ˇµsĽďZőǢ”6öx34•ŰHŐďcfˇ7űĂřX~˘pÇ(Ľ‚%ކ”]Kq#dg7>3jĹŐlkŮ„�V…E«_M ů ­A)©§~†ŇDÄ&é`AěYB˝z¦Čş]) -ęTÎ {ÍřeŮ đ PJyöƲMő‚Cg»‡HYś^w8‡nĆŚ$ć)xηwQáśći Ż8kĂ^š ™ ą'\ �ÓRśK žPÄ»7Ëç�8ž{!_?öý4ývíč8•r‰±În[·6“i=vPoޞp#8 Č=g A”ůĎś*Ń7”›Ł‰§ýÄcÓAřDü8ăŰôśvĆ7‰¬4™ŕ”–ě‚ó„Ęs9Ňű DŢEű ^Ŕí…úxŐÁ¤9}áK{ _(mŤ>oŹ8REHŮHĎń1Ŕ@I˝J/–�˙�ą_Jt$bň”­eeŞ}˛şĆ°f Ěóôń&xü­KŚgżˇ'űÁĚč—ź†Ý$ JN>šÁjÇďW�ą.üýVdׇ@ŮÉ=aÝş/ˇ LçQŮŚ_*Ką)Q˝,(ǵ5Ł7žČAž?YAĄcť`¤9©ěŇÚOn6”^wsźÚ˘Ŕ“V™J0ęšbĎĺHq7 d©3}‡-!Ě„VčÜIđ‚ş›­GĚPĹďĚ>YŐsĺ2;@Ö8dő9Fł™Ă´ç šôéëśO(Řđ6»<lŇřŽu‚$.‡eS%s§ş«®oQ“JĽł|MNr#XyčJRIue”;ąóÎ�¨-&őH}`§Ţý`^Q`âÍLÔ¦P•ËłśY Qß9´;Ł!>_,ó!6căH5*™ĺ„„o‰#‡Ú}ś„Ý7…L!™@ľbSSí¤(I#8(ąuśĚůEĺ•—ň€@/‹;™JĽęęěÄŇ5Ž˘ëňʶ0ˇyK•FÜ”;°ăŽŮśá˝a|€ŁÎ&䂝šŽ¬©Żm=Ç@H"čRvÔdÇ6Ť…{tO8śŘnúE/,Š”›Fz§Ćp”Z+†i»ŰŚ ”¤†.Á…xľč÷ uŘ/t‘t5ęËqh{‹z¦•„ŕ’“x>8ň…‚îýaF߲ J�Ęvŕ%ľ€IK)Ĺ•G Ľ;8ďđhłÖ•âęĽH=S„öÁ.¤†H“c˛ZpÖĘ=˘‹Đcĺľ/,¤ Špqă ĽEÓ,>Đćś 9ŚÎč¦qh«ş‹¤őLhFđ| =`Y¨`ŻřKBn, 0MĹTŢ#ëOz[}JŔkjďËÁvƵăS+ßçĘ Ć»~bcĹ1íMĄ×ěüŰa@=âÁ7‹âyŠĐĹŕ•-D¸rś) M±EšXČ»bî&*áĂŤĆ n⍰¸§‹s”öE'ާůe3AÇö±R@–xEĄőX–‘…n,`•ÝQP‘Ç” ˙�öôBŮ(ţdÝčŢiű4¬Đhćň™Ř �;ăö”őěďĘGhŹÝĹ©OZWLÇÚY$Ó™wČřZśbˇĺ´älď‰LľÝ“°ŽŮyCB˛ěŐ‹c˝Á‚§™ n«Ů<e˛ 2Eé­Éŵ7R=ĺĺ…&̲ŤçbŐ¤)*ł§Ďô…%Ůśjś~°Ą-%îMÁű“ySgrO|ˇW‚ŻÂ…âßú‡ňĐ”€.ú0Ź—´t &Ĺ+ W«”°;"ĺ•ő'¬Ł$ĘŻÝŃ µGAłu])Éč;`Ň:’’˘X8Łś8hM˘×zR“ă»h€«ˇ µÝčÎ[TóŔhžŃ™ÚyŞ…(€n—}Q'ň‹dÍ6ŹŞ"ůL¶Hďhj©IŰ+˝Ýđˇ0§x%f¦R`đ•%hÖ{„X"˘G¦ 4p'/, ăj­Ö–a]Ô€¦ăµ Ź˝f”� d»HȬ[Z[„=ŕ|a# Á¨áqE�ˇ¸Źă†m fá_ q€P« ^ަ…bP8šFoĎ@1Ć̉K×íOHa3™†RmGZŹ:_kEšľĎÂ,ľSXB3W-D›|(•‡¬)ťÓgŽ®˙�Ýߌ5Äš�G÷ý$\ ŁUU˝>Q”^„ÄN/ k›ÎÜyÖ-×Mńó#­ĹP…Í,xLoFâ6Â'«X_úÝ´lă„ęËt_�Ą‰ĂÖpqŁaî†ĎžŠ@cí‡H9Ť  vů#đŢđđĐn§w@¨ßŔSiĆ[ŕ€N“ńł´.d¬ +ž1jŚ­“±‚Çś!}UOĹăaä`žŞ@2ür}n„ §lLÓ&gh’’uUM†Ł”!S[ˇ¬!IJJ’ÁT}őtÎ5Rź•缾‰eŢجQ€‡ŐâpĄ›€0ya¤­A"¦$"QvĐ~_xO٤˘ÎÓ¬™ć$®b?Ő¶ýGCSCĹŇ »ő·ůCŽţ#`¬{[ˇ]d¨LâP4^A�úĘEĸÜ|!É9ĎJí-S¬©N¦qŤD+"ÇŹÔvč7ÁĘą­Ň2ÄůEŹĘQ‹ $\žĚĎ8ă�(*’Ľ6Gěę˘rŐ‹é#”%J÷iJlÄ}¦0ĆtknÄÂ=˛BÂJT[:ŤŮq‹/é#ôJ}š̆LÝđ—1}U™ĆżŮAB�RP.ŠęĚm”'ŰÝ]Ň•&Wš¸6ÚÂUd¤Ą c]Ĺ޶ XöiIH´mk¸Ăď-lĘpP¬*ÍWV'Ţ3,É´MŐŕo=˛1cň«ő±É_¨čMžŞYKěN˙�(RŐyEÎ߀ŠCiláł8ţm›"bҸ -+§ľ‘xű$|g3–ěô4Ě2 UW]Ř(7HŐ=S‹ă٬ë©zŚ·{oĘ‘çĎá†bő…Ä RŹel´d];ŚĂzÂ=˝'®™/~ŚŰ€uć€6Ô Ć†şŘÚv'vݱuW,Ř‘Ö>ŕZ -4=™”&Ő7UŔâ!VJşşá·hĐ«=[Mdpó‹ęŘ+-gŞŚńW‡!#0¬伶Zď´µţÔqÄ–oZŞy8J’É {ýg łA]˛™# rśY+VĹ *TAs΂,K9'â,• âovĂŢ&ŇČMW\Źś$Д씠éŔÔžČŢ.éůľ —hŘ’ű„Ľ"pٞÁ&̲=đĄX‘ÁĆ¶ĹťŠ–m¤–˙�kmĹŕ*ęŰ 8b®ę@˛C$űÓ!řv&plÖŹqUŘsó€ ŕ¸‰Âm“uUřNFd«Şŕp#1Ł×ˇŮ ľźg5ľ¨‹KŁÚ]˝Ť×h`Á€Ŕ Ĺ9¬őG‰Ř!ŐxÍU$×O˛łźYZĘđ¶é˛KRŞŰ( «Ŕw“ěűč÷j/ŔB-Suiq–I–ČRW}*qB– “E™µ%!^Ńýŕ4¤›łCÂ6eR¨ž;#gn„Ű&ę¸A…Y*ę¸ѡV‹@uwfNČMů–jŻ,†”Ů ­Xa™Ŕ6Š+WXödÍ>ÖÔ?U:ÇŔsńč7ěëüLžgÉôń0âÔmImŕŹ‡VĐÂŁţ:JK‰HČĽ+E Ž”Ú¦ę¸Žb-?©gúUç ±MÔń8źYi D°&n·˘GPxńÓ(övZÝuë+Ŕrč-’®RńŠ·Ţ7ż¬!öOďý{Qâˇí,ÔŚÄ·áŰńߎźchđ™,lúC‡=?j „˙�,źů ´{kYőQ¬ŻřŽ}Ǣöíň¤&~0Ŕ{#Öq¶Ź9ÇďŞ öiJ:ÄŁÂX-$šN.[^X~?ct)`¶ĘĚ˙�Äřt˝Ň˙�)î‰XD‡ĺŃ!Ť[]é˙�Źď6žľÝăń3ş?yGĺ?â4űˇůĽ {ë-ţq+=çş%ÇĂDŁ^Ďó§üÇC˙Ä�'������!�1AQaqˇ‘±đÁŃáń ˙Ú��?0úF¶%'§ k÷Ľ^ÁEAŤ¦ŕÜ_Î,CGVÄuu<㇑<óë‘`źZˇŃS§[Ŕ°*aďŃńß9 9íŃ7+]µ‡ 5 ”D3$$^ ęý1˙�Č˙�č2!n‰˘_·9P‰‚Fg¶ ›Č ) :"¶Ě‰âţűäaÖđH@zȱ¸ă×ué’”@˛ąh]˛B02JĹMĹ q ŰÖĆ(¤>lIčśeŘü¨ž»C×4†ú˙�žţ‘Š›A71|}Ü4ŘŕŮ…[đů19YĐdBÁČšĹ!¦ą-łÂćrp˙�҉ -]Őŕ¶(˛¤ @ ĚUĺ7*b™$/“}Ć~Ł#hW śOÓZľ";ž>řÉ:—üřrŘ4ćçr&°¬D“AHd&zŘ3şÉ "*‹°(“ăÁŽHC0›±~玌Ţ.ŃIQŠv3Ś)“Úš‰ě7ĂĄĚ&1 ¤˛4+ÄI¶¦nyO®Q�»Ç®F900ČŔĘęJAh%kĆXĆť üĺ@V4¤>F•śPX`¶J‘!ş“śU"ÔXŇČŐß:g§áýaŠ,Qá+çZąÁdBjüż»Âp�#ʦ&9fă%Ň”•#Î’ú;sî„Ń™AʆL–DʢT›î}8qjlM©ç[CZŤmP Ĺ …@†YŤĺĨ6ˇ$M‰óžx‘QE˛L$HW<dŕĂ1O`kZVÍyÉÚŽĚ»–ý~©‘Šcý×Ň0/Ťť±Q˙�;¬ňÂĂČ�o˘Ř NŢÚôqPŠ2…y˘ŽŁy8(6…ETPaQ "ŞHwtoFGýśVúË&č]@Lń°ěW‡Ę5¬‘`Ž‚šR“ÓEˇnąE ăm`JDM-4XF÷€bF˝BŞ$ŇüřĂG U…ła`Bm âDÍ 8©Ő2k9”ÎÂlÉwt;\h�ffVݦĹń.ŹüF<­\D6ýµ’ƨ%•Č÷q®Ä.¤3dŁxć·ě…şw@m©^˛P, Ŕ€´P˝:rSnmݵ„ !D»F•˛ ŻľJŔ»b ăfX ­[%ĄfBRë#˛*d¦`•¤…UÖx_?ÖC8 n¨pDŽĹQ‡CLě\ÇgQ[ś!ĎŮŢ*ŞAÇüöűăD,DąHÂZ§hă b©·ěŻáśŇ„ŠÓ*$ sr™V& ­®^vÄŐdćmQĚ, #Ł×Ő$’ÜÔn®_¶hMĽżůž&•·O¦ď&ëQ°0©U,Vů˝dvęn&.ý"L€ŔdhW'p˛”$MÜűeĺ&f>Ôýâ˝9Ë$"@J»ÔĚ1f#r GŠš/F�‚@^Ç®ý2p•"˝í6›Ôd‰ Ó©±O_śŽŚhZf8©źŃ„ÄYeĎŕŞçÜ{dYzáŹÓ$dŰ…>‘yV�aA¸g€ď(f%$i×Ürz–tYĐF–9ľ±™1™ÜL$lďxfČ‹„.ĺn†«!…8%ś1*oü¤©łN22łN/Ó’„DH%tŔdľĎŚU–´X±J)ľűrdŢ)d$ŹĆ� UÄkUY9Aě|_Ű#W•›"ßű‰‚FŕŇy¨N}pLRF9H �Łkďyč)s:Ľ3ĐLAąŞ>3Äţý1¬‚n‹1×ĺŠ@yBéňkĆJ,ľ:ľyűüď ĵĐKýĆ'ëŚW1ţâ"(łmú˘ďĐÍ łg”ŚMŮ?8ňÚe˘¤Úyż/[ČĆTWMY©ßQ=d 5!7Ë $UkTĽ`|‹ ”Áo)/˙�0, ×qú02j•âGSW’ĘD;Ňy.}WY1:@uVĹőďY]ź&ŽŽß~Ś”JÓ~_űë0^UIđ&)H“Ň?śťÍÎÔ1j=qOđX!@đŐšŢĂšóŢŢs‰“ząÖőď—n‘EvŮÖżŢ5˙�>ŤČ™7űIČBĎOŢ|` ÜKŞ%>ů Cѱő†ţřË *’ş,,±+1ł şMa0ixś“ěčáJÔ˘«ť˙�ŕ¬@…[JŹ łŰJ3DČĘÝNň"ŁDL‚Hő`¦�\gîíŐä,ęÓľĂTUř dŤn<V)Zf©¨¶hڞZN9NžzÖ9\…­ň˙�'źăzĎ×ěäg‹?~ßIÁ?üÎ_ÁĂĎ÷ŚÓ@C• Žp"e}*杪<dŔ"!HŰî߬#¦ .ÚZŢѢ©ŰTüII_Gܬź H”ň?Ś<&%6B ň@ďę'$Ą‹6oůÂÎfđ€X¬»ó'@˘–č=ďŽđęgÓĹdafÂ$y=rŤwO˝1笇¦BuŐţ\Ô대ş›ó D|ţ™ĐrFA!–"|oY¸A A·1×®+ —¬^(A=Br×3‘-Ź÷‹L )ĹqçűÉb°Z@JÍv6µDë°¨‚’y&UÍ"…•ˇ ±‹âśş¦i–,˛xŕ4ÇË]Đrá�dŇRw pşÂ†VPZ†–)IQW€Ŕ˘� ¨†Ěˇ†ŕŘđ»÷ó” �ŇYâťĆH0řŐ]“† ś®Ő™·ď—Źxć˙�Ź˘ěźŢńřŚ)6ąčOí}Q@ZqĺŮH2 É{ś»}•¤č$Íśd #¨oÓ)¤Ń¶bĂĂ|e˝ý)‰ C…męŤáÂÍřď]šĎ>(Šd˝ÚzwBNČČ{óR&\0 #<CuëůČ?bYĹ<Şý±·5p‚ đ|ąĎpvKRÉpą}).ĄaůŐĺ53"KX…&ĘŹlśę˛WVC‹‚ Z*•<›-iŰšÉ3Kň‹‡Ű ᡠÉćfĎă%4`¸műd†ţ!2QĄ™ëĽ•öˇK+]Gą‘(¸[áľ<d7FłÁ€h†űc… B¬'_Ö4KŻžŕĹDK~?˘†ŤBńM^˛lZó€`©UQÜkÁ’ Z· {Î! Q#’¨Ś•Śň‘N“ÄE`@č —“fň`!SDšůa Q0�F O�¸a8®ńxŇRQËżŚ Dd`즻Ăú'FĐD_nďSs‚Í^…!!V“ç ‡&ĆŻ 8ĹH#aᬉôˇiłEâŇzN·Ž¨™\gË®|c¦°˘ťÂHĽo#Ü­!„JäRüe!(‹ER}p Ů[aš_ŽńnĽe#NGů1úRN˙�Ć�>‹_däeú“ÁĆ ÍEžâAó—Ť�/Ö_ĆZrYJŕ#ävT'p"ůă@$¬ “š’ËĽ„ !´ÂBÚw­^)¸’ŐDD™űd2”<#™`éŔLG °"0LŐÂá$‰IUeЉH“%Äă * b’EŢ"¤H,ŁsúäâF*ĺ€ŇÜjńQ ™!#†ńyđgVĺŘ)äĹhŃ©@—ŁÇ*@ÚŁ~pg'"‚ąf «yqÜpp·ÔĄÎ§%Í´6µĚłŮ”l5*j̤^yŻ\  –Z•µ5ăNĚQť…±2Yó—AË=Q|(¨©"Ä˝Ńűc˛DÄ•¤Íp˙�1Ü“v+÷ă7e“s'-!K„JK“ôź0Q’»Ď[Fťn/ĺ’!ł§$VĤl@7Ío! ­J(%•'n÷„á›]˛G@y7’�P ®ö ݉˛Ăk’{juŽ0ÖŠ$ iqš€‚ĚČ…ď%‡PKVa eÖ>C©H˘ĐČСVťŕ®ŠÁ�bUX± Öyż§ŚpĂ X?xĘŮ‘$ě “%ŃżYČ’K¨ť•@ťËšOa1ňyv€Š ĐL őŔG%lľKde=¸™› ˝OľrÉQD‚@üc˝o÷mĂA-„S˛Lő†) $ąµGDŃ&N¶ HG ’-|Ě=¶HÉ2k¸bĚß8•(Ô ŹHőç.y–¦‘$hČĉ\“Űw‹Í -?~ă6Áę'ç°®G]âőď źL÷|`{Ă2<‘‰b2ÜAáĆĹ…°‹=ŹL‘@jĎ‹ĚN UŕnOľ®„ ę™đA„˘1)i(ŠuŁŞĂ{¨XMOŚpb)ëˇ?µ©JH@ˇú `®2 ĘÉWĐ™D|bč ëµi9Ććš•KDZŰÖEŰď„’d§^¸ f€Hj äX¸ődľ- <‹ÔőÁ„x�¸O‘|Ä‚÷‹ŤÚ§˛7ďŠZn.W„×µ‚ióósö””ÍěĎůoď´`śýľ“> ěŁm ă®e`‹Ĺ´=«ýpf r¸8íËÂţáMq^`ÍÇB^Ó>Í#”?_i&z&U¸Íw‹D6Ä-YPGB·m»Ţń T"#Ň2'ŁxäŘ ę’çë˝ă)6Îć2f'dkżÝćă?—�0HD "‰Ín|`ő?¸Âe‡t>Azá2>Ř>é)â¸öĎĐ˙�LN_sž˘;šűă’v“äJb$V”#š‰ů1(čÜ™ťÁá1\äµ´ć>Đ`î®”ůOńť"{ómŕÉ^Tý÷Ŕ+Çëüá+î_ă<D7ö“ÜĂű8…ńö żt…ë)řĹpńYbÜ11}GŹř z˝‰×äka†¶˛üAp˙�ą `ÓúS‚Áä«Ô‰ŹmăĐČÚÔŹĆOőż®Ą;WŘřÄI/™đuéŞ>µGŔ8+ "ŤG±Ě¸žZ%Ru4~řx­%“‘y|řÁ4˙�ż9>ŻŰ ýţ~Ů tüÝúb„đ倌/NňÚ’¸DŤőă&?䢓ŔL{ą׼„Ą„2®ŞÜ9ç¸:âRŹ861Âd ”źX˛/đXGkč ŕç/o•mőŔTţůÇ%¶ö+QýáI9ăOř^3aúřzłówď– U}Źś‡î~p§ŰĎç�mn™ësd—Üü|ŕĄâf=ŕűŕŔÚ»zšŽ#"Ő„Ż.źŢr2^˙�gş<˙�X3W#î%#í±uÝjÇGÄ>S4§_Üý!„ńVÎlŁe—{xsł}u—zb4GB…©ÜÇ󚔓‹eŹ˘Śè{ňÁď‡΂ˇž  ĎŮ‚ŹÁŤß©­k(ho¦ńŕOßĚTbHŇOďśó>ßî@?PRsűöăPQYDlwŕÄ€fĽ±ĄŤ]äV@‘7űŢ5 -ˇZ^¦çó‰—…Ą Ä)¨ÂŠ©€-_đűŕĘSŁ«ć·ŽŰz?Ľď†˘*łz0ű`ˇ ’NC¶ úź 8�­[}±ˇzôzଋŔ©˙�lVZŕ \Ąîô ŕ”nä"9Á‰@tŽá=ńD¤¦ “ŔyŔ@0bi©y{âg*1ÂyŹ™$÷Äz”!(ěo‘1ybjJNľNrc©ő›Ëƿꆞ:ËdÇ÷çďÎ(şMĎ­6ťwĘcEż2ŔP»pAHx î(őóŕŢs"w ôď¬ežÄ×(ÔG“]¤DŻ×´á± 4˝†|šáÎxŐÁËÄa%_»ý&$仉>O9"×- a'łNVT® .b|Ąđäؤ Óoóń‰Gů}\ľvߏ+@¬ť‚ á xä:NOŃ1\řdÚ&‹zë'‰Ă•—ÜĐ“:Ä:]‹oÉ ÜÜţ)˘ĺ!r6ń%u§FaÚŇyöÄÓ“űŻŚ…–{{<vsŽK˛4& 8yëN‰‡UXŮ&™_F&ÉÓa®łţoôÇŚňiş„ţďLR–‚ Şź âç ‚$‰¨ůÇBĘČ"C·:8ď-x@ëü}ń€€€ŕtĎ?ő‚]MȤĘđN÷y˛ş˘6÷$ĺ€űT¤()°ÖôÖ˘Y.b*QcŔ¨Ć”ŇĂaă6—�;čŚ óYHD‚Î…f «ď!ŕH rÄ;ŮOÎmýdTyŻlTP0´Âx¨ěĂhKÔ‹oÉ#_Ň „-,Q¨r şçZQ Ęx1ŮD¸E— Ŕ–٬¤1Ěł*ÚÎÝďÎ;áŮŐCÖ2Tťěüý#˘_qŮŮÎ-8MŇOɱĎřüg«�ł§Ü­ęĎÎTI†!Fz˘.ĺŠŮŠŃłP YŞžp"ˇ�z}&dK??č,䔬¶iÄ0ńţćB^Ł×yöqAí/Đ1ËÔHMörŤ CŘjL�F‹µ ˝‘­MÔ]_ĎÄd·®'ĘÎFűŮč–{aˇŃ,źußľBĘ Z‡)ě âLŁň�jgÉc‚‘{ČpD$źĎ®’ŤPT˘Č–™§ËŮźfE]Yđ˘OäÓŤÎö{?#gË.$ö"òúZÝ=G”=žě¬ź˘ZžáËńnLť4cŃ Öĺ]ăôżűž/!bxÚw Âą¨L2ZŔš'}íŹMJTŻ<~LT*ĽC7Éw÷U‘‰PÜÄŮň3–Ž‚’I k_|rpČQ/Üź>ŮúżĆBĽűű`•(á;ü'9\�Ůš&=éú?3Đ'ŠM'Ódy¶ßokřaô2CQ@\aycŐ3q©=W$#דÇć°;ýűć!E‚ŃŹ_ăçŃ čWĹ ň¸˛~“śČůĺA 32­}Čărd¤Č¨0<’¦ŽÜŤť‘1ą˝Ţku$ zÎ_żĐź 1Ôžż`fŕB(: Q#�7ĚýűâĘzĂŐ\Ę~ç8 ‚&‘$G¨ÁúŻĐdŻú<]¤đOMW´y'xT®š:­>ělÎúťE-dH&+)†SÂ'ÉŮŽ\_ˇ›$FäW©�‰ďĽb b�Zoď0ϵPđđB:FŘb}ň«S¸D‹BĚD-ůś ! z ¨ňÓčxšˇ(—qöĂš@,úw†â˘ÇÝ$w.Ô¸ž_Mg'X ÓᪿśŤ1<±±Ňu.98}?OŰ?O«?Q×>řÍ^ŻÖCô™řYk“ůż9🣧éń|×ăżéź•ŤßÎ~O¤Ý߯˙Ä�.��������!1A"Q 2đaq±3BR‘ÁŃń˙Ú�?�ďŔĄî2 ą Úů›H”ťž˝]ř}!CúŽDs$©!7\ézeΠčSy”‹iň\őjÍ’něő/ĎďňmóÁ QŽ’šŠ¶K<¤<ŹÜ۸Kbŕ«äŰ÷öľVĄá‰űĺkOÄľ(ăČÓ}ăHˇ˘ľV褺1}$ňFŽn]őű sB…vWČÄí IvŻLY}<™”¬‡Šo*b#EVŽš'Â"ĺT„68ɲ8ĺĆÉM©U ~ă’GĆŽ˛”·;čr^ÄĺČäÔö˘© u-ŁČź“zňÉNJJ‡ľĐŐ”´Ŕ–ó<ômHţd‰«$G±ĹĘ,\ro7łrF)ܸ?š{בɲä´ůC"˝VK«(ť"+š:7Ż%ÇÜrF’rÝ™˝/ ŠâĹÂ.żR3RtD“ŽH;D$šŕ›¤E«6¦Kjá—żúGą•¨Ĺ$G&ŮY7 OtWúHĄÉ –ȸĘohÚF7Rŕ~‰~Dc»Ő!ĂlOkŻ&Ź…rQDczd~ !Âŕĺ’U!úQ%j†štĚy6ţ‡Ä‡żî>Č-ĚŞŕnąe·ËşZO˝1:tôË V´˝1tĆfúD#Çödű!ő"_ÄB< O˙Ä�%���������!1A Q2"Á˙Ú�?�żB’1¬¬¨±*EK.Ë~Šu˛‡ÝŤŠ<^5gâµÚě˙�,´TžÇʉ&‡&óýŠ!äIi“nÇł‡ĆIÖ†ílx„[bKT5cG‹Ń(ˇŞbM‘ЉG±EběX}ěcNČÇDbÇEü˝–qŚ™%cŽČ˘â?"–‘B‚jě§z)śxŞUŮĹý’Ť=W xdĽiE1Di‘‚âěLĽ[?!żÖ~?‘µ·g"ŰDGŃ)hŚ…±ô44V<©qŮŕđF0{ßü(±ZěKeꊼAY.±ÄâĹĎ$6‘5´ĹâżeSs’˛Pâ»,IĆJ‰E§˛(kE±&ÖŽ2%?˘ĺl”•58ÂĄţľĹú%‘%%Č«$őłúC~‘v‡HÚg6F2N±JËŮG¬>ˡ2QłőËt=‘VčcĘëęńZřOÖ<_Öď ˇôGůcř˙Ů��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������beetbox-beets-01f1faf/test/rsrc/acousticbrainz/�����������������������������������������������������0000775�0000000�0000000�00000000000�14723254774�0021717�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������beetbox-beets-01f1faf/test/rsrc/acousticbrainz/data.json��������������������������������������������0000664�0000000�0000000�00000374502�14723254774�0023536�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������{ "tonal":{ "thpcp":[ 1, 0.638657510281, 0.293813556433, 0.259863913059, 0.21968896687, 0.218203336, 0.252398610115, 0.22969686985, 0.447383195162, 0.749422073364, 0.580664932728, 0.310822367668, 0.238883554935, 0.178785249591, 0.194924846292, 0.299323320389, 0.282649427652, 0.18946044147, 0.181915551424, 0.231100782752, 0.554247200489, 0.831909179688, 0.589426040649, 0.387799620628, 0.422363936901, 0.429372549057, 0.408978521824, 0.326897829771, 0.266663640738, 0.429461866617, 0.633336126804, 0.477401226759, 0.261826515198, 0.238164439797, 0.287726253271, 0.690547764301 ], "chords_number_rate":0.00194468453992, "chords_scale":"minor", "chords_changes_rate":0.0445116683841, "key_strength":0.636936545372, "tuning_diatonic_strength":0.495492935181, "hpcp_entropy":{ "min":0, "max":4.48086500168, "dvar2":0.867867648602, "median":2.02990412712, "dmean2":1.14721953869, "dmean":0.68769723177, "var":0.635742008686, "dvar":0.33780092001, "mean":2.00384068489 }, "key_scale":"minor", "chords_strength":{ "min":0.24240244925, "max":0.793840110302, "dvar2":9.58399032243e-05, "median":0.586153388023, "dmean2":0.0106231365353, "dmean":0.00929547380656, "var":0.00910324696451, "dvar":6.61800950184e-05, "mean":0.576524615288 }, "key_key":"A", "tuning_nontempered_energy_ratio":0.721719145775, "tuning_equal_tempered_deviation":0.0515233427286, "chords_histogram":[ 56.2445983887, 8.10285186768, 1.79343128204, 0.0864304229617, 0, 0.605012953281, 2.20397591591, 12.1650819778, 0.0216076057404, 0.0216076057404, 0, 0, 0, 0, 0, 0, 2.67934322357, 0.21607606113, 10.8686256409, 0, 2.07433009148, 0.0864304229617, 0.648228168488, 2.1823682785 ], "chords_key":"A", "tuning_frequency":441.272583008, "hpcp":{ "min":[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ], "max":[ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 ], "dvar2":[ 0.159377709031, 0.118723139167, 0.0969077348709, 0.0841393470764, 0.0857475027442, 0.0681946650147, 0.0922033339739, 0.0763805955648, 0.08953332901, 0.134413808584, 0.117157392204, 0.0784328207374, 0.0576078519225, 0.0540019907057, 0.0537950210273, 0.0788798704743, 0.0758254900575, 0.0659088715911, 0.0595937520266, 0.0897909551859, 0.117696471512, 0.141075149179, 0.116812512279, 0.143778041005, 0.157332316041, 0.206293225288, 0.187901929021, 0.16186593473, 0.119209326804, 0.107413217425, 0.119033068419, 0.101279519498, 0.102868333459, 0.108767814934, 0.105039291084, 0.133028581738 ], "median":[ 0.200053632259, 0.138681918383, 0.0352100357413, 0.0198299698532, 0.0150980614126, 0.0205171480775, 0.026256872341, 0.0286095552146, 0.0424337461591, 0.0602139532566, 0.0744276791811, 0.0494470596313, 0.0350396670401, 0.0212194472551, 0.0196186862886, 0.0201223343611, 0.0170610919595, 0.0120322443545, 0.0105668697506, 0.0191436801106, 0.0846247002482, 0.135070502758, 0.113241240382, 0.0424886606634, 0.0378469713032, 0.02946896106, 0.0286043733358, 0.0198593121022, 0.0253958441317, 0.0672319456935, 0.0957452505827, 0.0639808028936, 0.026175301522, 0.0180807597935, 0.0324410535395, 0.125212281942 ], "dmean2":[ 0.353859990835, 0.294070899487, 0.217189013958, 0.180236533284, 0.162956178188, 0.144071772695, 0.172789543867, 0.171253859997, 0.22391949594, 0.284728109837, 0.274570196867, 0.198656648397, 0.154297873378, 0.124744221568, 0.124510832131, 0.157537952065, 0.158150732517, 0.135058999062, 0.123808719218, 0.177607372403, 0.277576059103, 0.324962347746, 0.298212528229, 0.308668285608, 0.311420857906, 0.344731658697, 0.335885316133, 0.283822774887, 0.227637752891, 0.252195805311, 0.288448363543, 0.249307155609, 0.212308287621, 0.205825775862, 0.223324626684, 0.315198987722 ], "dmean":[ 0.206140458584, 0.16949801147, 0.121237404644, 0.101308584213, 0.0920756608248, 0.0820758640766, 0.0983714461327, 0.0957674309611, 0.130757495761, 0.167563140392, 0.159212738276, 0.113431841135, 0.0875298455358, 0.0698468312621, 0.0709747001529, 0.092557400465, 0.0927894487977, 0.0761438235641, 0.0697580873966, 0.0992580577731, 0.162719354033, 0.194310605526, 0.174188151956, 0.171439617872, 0.174783751369, 0.191449582577, 0.185274213552, 0.155063658953, 0.124152831733, 0.143824338913, 0.167870178819, 0.144021183252, 0.116702638566, 0.112653404474, 0.125104308128, 0.184472203255 ], "var":[ 0.143021538854, 0.0637424811721, 0.0320195667446, 0.037381041795, 0.0286979582161, 0.0301742851734, 0.0303927082568, 0.0223984327167, 0.052778493613, 0.13409627974, 0.0731517747045, 0.0302681028843, 0.0220995694399, 0.0194978509098, 0.0208596177399, 0.0510722063482, 0.0462048053741, 0.0236530210823, 0.0288583170623, 0.0295663233846, 0.0644663274288, 0.119045428932, 0.0609758161008, 0.0493184439838, 0.0607626289129, 0.070556551218, 0.0664848089218, 0.0493576526642, 0.0335861295462, 0.0447917319834, 0.0859667509794, 0.0526875928044, 0.0304508917034, 0.0315008088946, 0.0328483134508, 0.0794815197587 ], "dvar":[ 0.0686646401882, 0.0436623394489, 0.0358338914812, 0.0332863405347, 0.0316647030413, 0.027725789696, 0.0338032282889, 0.0273791830987, 0.0345646068454, 0.0615020208061, 0.0457257218659, 0.0297911148518, 0.0221415478736, 0.0201385207474, 0.0201426595449, 0.033235758543, 0.0318886972964, 0.0243560094386, 0.0229729004204, 0.0327679589391, 0.0460740588605, 0.0626438856125, 0.0435314439237, 0.0521778166294, 0.0578555390239, 0.0772548541427, 0.0728902295232, 0.05926451087, 0.0424739196897, 0.0389089211822, 0.0483759790659, 0.0381603203714, 0.036982499063, 0.0398408733308, 0.0387278683484, 0.0517609193921 ], "mean":[ 0.366864055395, 0.234300479293, 0.107789635658, 0.0953347310424, 0.080595985055, 0.0800509601831, 0.0925959795713, 0.084267526865, 0.164128810167, 0.274936020374, 0.213025093079, 0.114029549062, 0.0876377895474, 0.0655898824334, 0.0715109184384, 0.109810970724, 0.103693917394, 0.0695062279701, 0.0667382776737, 0.0847825706005, 0.203333377838, 0.305197566748, 0.216239228845, 0.142269745469, 0.154950141907, 0.157521352172, 0.15003952384, 0.119927063584, 0.0978293046355, 0.157554119825, 0.232348263264, 0.175141349435, 0.0960547402501, 0.0873739719391, 0.105556420982, 0.253337144852 ] } }, "rhythm":{ "bpm_histogram_second_peak_bpm":{ "min":167, "max":167, "dvar2":0, "median":167, "dmean2":0, "dmean":0, "var":0, "dvar":0, "mean":167 }, "bpm_histogram_second_peak_spread":{ "min":0, "max":0, "dvar2":0, "median":0, "dmean2":0, "dmean":0, "var":0, "dvar":0, "mean":0 }, "beats_count":577, "beats_loudness":{ "min":4.48232695405e-09, "max":0.181520029902, "dvar2":0.00434844521806, "median":0.0302296206355, "dmean2":0.0709233134985, "dmean":0.0362482257187, "var":0.0014705004869, "dvar":0.00124853104353, "mean":0.0407853461802 }, "bpm":162.532119751, "bpm_histogram_first_peak_spread":{ "min":0.164835140109, "max":0.164835140109, "dvar2":0, "median":0.164835140109, "dmean2":0, "dmean":0, "var":0, "dvar":0, "mean":0.164835140109 }, "danceability":1.14192211628, "bpm_histogram_first_peak_bpm":{ "min":161, "max":161, "dvar2":0, "median":161, "dmean2":0, "dmean":0, "var":0, "dvar":0, "mean":161 }, "beats_loudness_band_ratio":{ "min":[ 0.00683269277215, 0.00988945644349, 0.00177479430567, 0.000523661612533, 0.000248342636041, 0.00070228939876 ], "max":[ 0.970164954662, 0.725397408009, 0.739950060844, 0.658194899559, 0.676319360733, 0.622089266777 ], "dvar2":[ 0.0699004009366, 0.0290632098913, 0.035205449909, 0.0226975940168, 0.0168868545443, 0.0086750369519 ], "median":[ 0.619332075119, 0.176913484931, 0.0926762372255, 0.0303400773555, 0.0398489944637, 0.0262520890683 ], "dmean2":[ 0.369491040707, 0.230186283588, 0.192849993706, 0.106605380774, 0.107576675713, 0.0616580061615 ], "dmean":[ 0.207789510489, 0.129003107548, 0.110624760389, 0.0578341074288, 0.0602345280349, 0.035002540797 ], "var":[ 0.0565228238702, 0.0165011454374, 0.0193302389234, 0.0086074648425, 0.00634109321982, 0.00296737346798 ], "dvar":[ 0.0250691790134, 0.00962078291923, 0.0126609709114, 0.00782771967351, 0.00567667419091, 0.00305109703913 ], "mean":[ 0.577607989311, 0.195795580745, 0.138790622354, 0.0610357522964, 0.065284781158, 0.04240244627 ] }, "onset_rate":5.17941665649, "beats_position":[ 0.383129239082, 0.789478421211, 1.16099774837, 1.53251695633, 1.90403628349, 2.27555561066, 2.6470746994, 3.01859402657, 3.39011335373, 3.76163268089, 4.13315200806, 4.5046710968, 4.87619018555, 5.24770975113, 5.60761880875, 5.9675283432, 6.33904743195, 6.71056699753, 7.08208608627, 7.45360517502, 7.81351470947, 8.17342376709, 8.54494285583, 8.91646194458, 9.287981987, 9.64789104462, 10.0194101334, 10.379319191, 10.7508392334, 11.110748291, 11.4822673798, 11.8421764374, 12.2136955261, 12.5852155685, 12.9567346573, 13.3166437149, 13.6765527725, 14.0364627838, 14.3963718414, 14.7678909302, 15.1394100189, 15.5109291077, 15.870839119, 16.2307472229, 16.602268219, 16.9621772766, 17.3220863342, 17.693605423, 18.0535144806, 18.4134235382, 18.7733325958, 19.1332416534, 19.4931507111, 19.853061676, 20.2245807648, 20.5844898224, 20.9560089111, 21.3159179688, 21.6874370575, 22.0473461151, 22.4072551727, 22.7787742615, 23.1386852264, 23.4985942841, 23.8701133728, 24.2416324615, 24.6131515503, 24.984670639, 25.3561897278, 25.7277088165, 26.0992279053, 26.4591369629, 26.830657959, 27.2021770477, 27.5736961365, 27.9452152252, 28.316734314, 28.6766433716, 29.0481624603, 29.4196815491, 29.7912006378, 30.1627197266, 30.5342407227, 30.9057598114, 31.265668869, 31.6255779266, 31.9854869843, 32.3453979492, 32.6704750061, 33.0071640015, 33.3554649353, 33.7153739929, 34.0868911743, 34.4351921082, 34.8067131042, 35.1898422241, 35.5961914062, 35.9793205261, 36.362449646, 36.7339668274, 37.1054878235, 37.4886169434, 37.8601341248, 38.2316551208, 38.6147842407, 38.9863014221, 39.3578224182, 39.7293434143, 40.1124725342, 40.4723815918, 40.8438987732, 41.2154197693, 41.5869369507, 41.9584579468, 42.3299751282, 42.7014961243, 43.0730133057, 43.4445343018, 43.8160552979, 44.1875724792, 44.5590934753, 44.9306106567, 45.3021316528, 45.6736488342, 46.0335578918, 46.3934669495, 46.7533760071, 47.1132888794, 47.4848060608, 47.8563270569, 48.2394561768, 48.6225852966, 48.994102478, 49.3656234741, 49.748752594, 50.1318817139, 50.5033988953, 50.8749198914, 51.2580490112, 51.6411781311, 52.0126991272, 52.3842163086, 52.7557373047, 53.1272544861, 53.4987754822, 53.8702926636, 54.2534217834, 54.6365509033, 55.0080718994, 55.3795890808, 55.7511100769, 56.122631073, 56.4941482544, 56.8656692505, 57.2487983704, 57.6203155518, 58.0034446716, 58.3749656677, 58.7464828491, 59.1180038452, 59.4895210266, 59.8610420227, 60.2325630188, 60.6040802002, 60.9756011963, 61.3471183777, 61.7186393738, 62.0901565552, 62.4616775513, 62.8331947327, 63.2163238525, 63.5994529724, 63.9709739685, 64.3424911499, 64.714012146, 65.0855331421, 65.4570541382, 65.8285675049, 66.200088501, 66.5716094971, 66.9431304932, 67.3146438599, 67.686164856, 68.0344696045, 68.4175949097, 68.8007278442, 69.1722412109, 69.5321502686, 69.8920593262, 70.2635803223, 70.6351013184, 70.995010376, 71.3549194336, 71.7264404297, 72.1095657349, 72.481086731, 72.8642196655, 73.2357330322, 73.6072540283, 73.9787750244, 74.3502960205, 74.7218093872, 75.1049423218, 75.488067627, 75.859588623, 76.2311096191, 76.6026306152, 76.9741516113, 77.345664978, 77.7171859741, 78.1003189087, 78.4834442139, 78.85496521, 79.2264862061, 79.5979995728, 79.9695205688, 80.3410415649, 80.712562561, 81.0840835571, 81.4555969238, 81.8271179199, 82.198638916, 82.5701599121, 82.9416732788, 83.3131942749, 83.684715271, 84.0678405762, 84.4509735107, 84.8224945068, 85.1824035645, 85.5423126221, 85.9138336182, 86.2853469849, 86.656867981, 87.0283889771, 87.3999099731, 87.7714233398, 88.1429443359, 88.514465332, 88.8859863281, 89.2575073242, 89.6290206909, 89.9889297485, 90.3488388062, 90.7203598022, 91.0918807983, 91.451789856, 91.8116989136, 92.1832199097, 92.5547409058, 92.9262542725, 93.2861633301, 93.6460723877, 94.0175933838, 94.3891143799, 94.760635376, 95.1205444336, 95.4804534912, 95.8403625488, 96.2002716064, 96.5717926025, 96.9433059692, 97.3148269653, 97.6863479614, 98.0578689575, 98.4293823242, 98.8009033203, 99.1724243164, 99.532333374, 99.9038543701, 100.263763428, 100.635284424, 101.006797791, 101.378318787, 101.738227844, 102.098136902, 102.469657898, 102.841178894, 103.21269989, 103.584213257, 103.944122314, 104.304031372, 104.675552368, 105.047073364, 105.406982422, 105.766891479, 106.138412476, 106.509933472, 106.881446838, 107.252967834, 107.624488831, 107.984397888, 108.355918884, 108.715827942, 109.087341309, 109.458862305, 109.818771362, 110.17868042, 110.538589478, 110.898498535, 111.270019531, 111.641540527, 112.001449585, 112.361358643, 112.732879639, 113.104400635, 113.464309692, 113.82421875, 114.184127808, 114.544036865, 114.915550232, 115.287071228, 115.658592224, 116.006889343, 116.366798401, 116.726707458, 117.086616516, 117.446525574, 117.794830322, 118.15473938, 118.514648438, 118.886169434, 119.269294739, 119.652427673, 120.035552979, 120.418685913, 120.813423157, 121.2081604, 121.602897644, 121.997642517, 122.380767822, 122.763900757, 123.147026062, 123.530158997, 123.92489624, 124.319633484, 124.69115448, 125.085891724, 125.480628967, 125.863761902, 126.246894836, 126.630020142, 127.013153076, 127.396278381, 127.779411316, 128.17414856, 128.568893433, 128.963623047, 129.346755981, 129.729888916, 130.113006592, 130.496139526, 130.867660522, 131.239181519, 131.610702515, 131.982223511, 132.353744507, 132.725265503, 133.09677124, 133.456680298, 133.816589355, 134.188110352, 134.559631348, 134.931152344, 135.30267334, 135.674194336, 136.045715332, 136.417236328, 136.788757324, 137.160263062, 137.520172119, 137.880081177, 138.251602173, 138.623123169, 138.994644165, 139.366165161, 139.737686157, 140.109207153, 140.480728149, 140.852249146, 141.223754883, 141.595275879, 141.943572998, 142.315093994, 142.675003052, 143.034912109, 143.406433105, 143.777954102, 144.149475098, 144.520996094, 144.89251709, 145.264038086, 145.635559082, 146.007064819, 146.378585815, 146.750106812, 147.121627808, 147.493148804, 147.8646698, 148.236190796, 148.607711792, 148.979232788, 149.362350464, 149.745483398, 150.117004395, 150.488525391, 150.860046387, 151.231567383, 151.603088379, 151.974594116, 152.346115112, 152.717636108, 153.089157104, 153.460678101, 153.832199097, 154.203720093, 154.575241089, 154.946762085, 155.318267822, 155.689788818, 156.061309814, 156.432830811, 156.804351807, 157.175872803, 157.547393799, 157.918914795, 158.290420532, 158.661941528, 159.033462524, 159.404983521, 159.776504517, 160.148025513, 160.519546509, 160.891067505, 161.262588501, 161.634094238, 162.005615234, 162.37713623, 162.737045288, 163.096954346, 163.468475342, 163.839996338, 164.211517334, 164.58303833, 164.954559326, 165.326080322, 165.674377441, 166.045898438, 166.417419434, 166.788925171, 167.160446167, 167.531967163, 167.903488159, 168.275009155, 168.646530151, 169.018051147, 169.389572144, 169.772689819, 170.155822754, 170.52734375, 170.898864746, 171.270385742, 171.641906738, 172.025024414, 172.408157349, 172.779678345, 173.151199341, 173.522720337, 173.894241333, 174.265762329, 174.637283325, 175.032012939, 175.415145874, 175.798278809, 176.169799805, 176.541305542, 176.9012146, 177.272735596, 177.632644653, 178.004165649, 178.375686646, 178.747207642, 179.130340576, 179.513473511, 179.884979248, 180.256500244, 180.62802124, 180.999542236, 181.382675171, 181.754196167, 182.137313843, 182.508834839, 182.880355835, 183.251876831, 183.623397827, 183.994918823, 184.378051758, 184.761169434, 185.13269043, 185.504211426, 185.875732422, 186.247253418, 186.618774414, 186.99029541, 187.361816406, 187.733337402, 188.10484314, 188.476364136, 188.847885132, 189.219406128, 189.590927124, 189.96244812, 190.345581055, 190.72869873, 191.100219727, 191.471740723, 191.843261719, 192.214782715, 192.597915649, 192.981033325, 193.352554321, 193.724075317, 194.095596313, 194.478729248, 194.861862183, 195.23336792, 195.604888916, 195.976409912, 196.347930908, 196.719451904, 197.0909729, 197.474105835, 197.857223511, 198.228744507, 198.600265503, 198.971786499, 199.343307495, 199.714828491, 200.086349487, 200.457870483, 200.829391479, 201.200897217, 201.572418213, 201.943939209, 202.303848267, 202.663757324, 203.03527832, 203.406799316, 203.778320312, 204.149841309, 204.521362305, 204.892883301, 205.264389038, 205.647521973, 206.030654907, 206.402175903, 206.773696899, 207.145217896, 207.516723633, 207.888244629, 208.259765625, 208.631286621, 209.002807617, 209.385940552, 209.757461548, 210.128982544, 210.500488281, 210.883621216, 211.255142212, 211.626663208, 212.009796143, 212.392913818, 212.776046753, 213.170791626, 213.565536499, 213.971878052, 214.378234863 ], "bpm_histogram_second_peak_weight":{ "min":0.163194447756, "max":0.163194447756, "dvar2":0, "median":0.163194447756, "dmean2":0, "dmean":0, "var":0, "dvar":0, "mean":0.163194447756 }, "bpm_histogram_first_peak_weight":{ "min":0.659722208977, "max":0.659722208977, "dvar2":0, "median":0.659722208977, "dmean2":0, "dmean":0, "var":0, "dvar":0, "mean":0.659722208977 } }, "lowlevel":{ "spectral_complexity":{ "min":0, "max":51, "dvar2":34.109500885, "median":15, "dmean2":5.03252983093, "dmean":3.31208133698, "var":108.135475159, "dvar":16.1623840332, "mean":15.1260938644 }, "silence_rate_20dB":{ "min":1, "max":1, "dvar2":0, "median":1, "dmean2":0, "dmean":0, "var":0, "dvar":0, "mean":1 }, "average_loudness":0.815025985241, "erbbands_spread":{ "min":0.907018482685, "max":163.113571167, "dvar2":511.106719971, "median":34.9861183167, "dmean2":17.4016342163, "dmean":11.4227361679, "var":572.351501465, "dvar":220.769592285, "mean":39.7502365112 }, "spectral_kurtosis":{ "min":-1.22816824913, "max":77.2247085571, "dvar2":28.6044521332, "median":4.62249898911, "dmean2":4.51546525955, "dmean":2.82785224915, "var":33.1767959595, "dvar":13.1654214859, "mean":6.00768327713 }, "barkbands_kurtosis":{ "min":-1.96124887466, "max":732.31427002, "dvar2":588.577392578, "median":2.02856016159, "dmean2":8.05669307709, "dmean":5.11299228668, "var":374.321380615, "dvar":247.842544556, "mean":7.06718301773 }, "spectral_strongpeak":{ "min":1.71490974199e-10, "max":16.1200447083, "dvar2":3.3518705368, "median":0.39955753088, "dmean2":0.941311240196, "dmean":0.545010268688, "var":2.96148967743, "dvar":1.33243703842, "mean":0.93408870697 }, "spectral_spread":{ "min":1388334.875, "max":41575076, "dvar2":1619247235070.0, "median":3420763.25, "dmean2":983456.875, "dmean":732694.4375, "var":4945877663740.0, "dvar":854983901184, "mean":4104934.25 }, "spectral_rms":{ "min":3.15950651059e-12, "max":0.0132316453382, "dvar2":3.71212809114e-06, "median":0.00425734650344, "dmean2":0.0013317943085, "dmean":0.000880118692294, "var":5.96411746301e-06, "dvar":1.66359927789e-06, "mean":0.0039903158322 }, "erbbands":{ "min":[ 1.77816177391e-22, 1.16505097519e-21, 1.25622764484e-20, 2.42817334007e-20, 1.54258117469e-20, 1.0398112018e-19, 1.45132858053e-19, 2.45289269564e-19, 4.01068342766e-19, 8.21050273636e-19, 3.81530310848e-19, 8.81511903137e-19, 1.07499647945e-18, 8.4783438187e-19, 1.00474413342e-18, 2.63416224414e-18, 2.7020495774e-18, 3.62243276547e-18, 3.19863277569e-18, 3.43176763428e-18, 6.46998299903e-18, 5.15079866686e-18, 8.52004633608e-18, 1.02257547977e-17, 1.00997123247e-17, 1.05472856921e-17, 1.00685656659e-17, 1.10003920706e-17, 1.51110777052e-17, 1.5046216819e-17, 1.31580720189e-17, 1.30844504628e-17, 1.13508668418e-17, 1.02681982621e-17, 8.04728449187e-18, 4.73097927698e-18, 2.75997186582e-18, 1.08864568334e-18, 2.8601904839e-19, 3.5584382605e-20 ], "max":[ 2.05614852905, 10.580906868, 60.5060195923, 152.843505859, 450.042755127, 175.338409424, 237.807418823, 238.884460449, 443.897521973, 525.930541992, 518.611022949, 1031.67468262, 954.232910156, 1123.45935059, 556.857299805, 356.125640869, 342.635467529, 1293.60327148, 1103.80981445, 1287.36242676, 2169.76928711, 1266.18579102, 601.754516602, 1258.96850586, 444.097320557, 240.62600708, 303.504180908, 56.5521354675, 72.2374343872, 85.4121780396, 25.407957077, 19.8146400452, 46.2547264099, 16.3226490021, 16.8903636932, 5.15064907074, 0.893250703812, 0.284754753113, 0.106889992952, 0.00452945847064 ], "dvar2":[ 0.0137821119279, 4.26913785934, 97.7259292603, 622.611999512, 3656.42504883, 376.053527832, 307.56942749, 320.293243408, 2445.90405273, 1300.15063477, 729.621582031, 2693.6159668, 2013.25512695, 3353.13598633, 573.952941895, 269.22253418, 457.528259277, 1406.78710938, 1557.82128906, 793.165527344, 5019.24316406, 4039.60839844, 970.312316895, 1538.21313477, 721.875244141, 195.41003418, 88.4793777466, 31.4306335449, 42.7237434387, 32.8801956177, 6.04226541519, 1.52690029144, 1.47338938713, 0.542356073856, 0.211627364159, 0.0269440039992, 0.00465576630086, 0.000269315525657, 1.98406451091e-05, 4.38383267465e-08 ], "median":[ 0.00466703856364, 0.101253904402, 1.64267027378, 5.13034963608, 6.49109458923, 4.258228302, 3.18711066246, 4.03815889359, 7.81102657318, 7.54452610016, 5.75877952576, 8.21393203735, 11.6316785812, 9.69087696075, 4.23464298248, 1.69191777706, 2.67072510719, 4.43968248367, 4.24516201019, 7.75287914276, 16.0199928284, 12.5332527161, 9.28721427917, 9.18504428864, 6.87661838531, 4.15781497955, 3.11850094795, 0.707400023937, 0.422007799149, 0.326348185539, 0.171605303884, 0.0764799118042, 0.0530340671539, 0.0369812250137, 0.0170799326152, 0.00388540537097, 0.000607917027082, 0.000106923354906, 2.54503829638e-05, 2.41674279096e-05 ], "dmean2":[ 0.0468679144979, 1.00080478191, 4.60071754456, 11.530172348, 27.0302009583, 10.1726131439, 8.18716049194, 9.02602672577, 21.6493034363, 17.5054721832, 11.9851293564, 23.2072429657, 23.1678962708, 24.8249855042, 9.62737751007, 5.9968290329, 8.08820819855, 12.5483970642, 11.0777654648, 12.6770420074, 31.1724281311, 22.2209968567, 15.6441850662, 17.6994724274, 11.4936075211, 6.59135293961, 5.08022689819, 2.30506944656, 2.95904040337, 2.46585559845, 1.15297198296, 0.531184136868, 0.382049500942, 0.275081396103, 0.141834661365, 0.0498343594372, 0.0225136969239, 0.00503297196701, 0.000977287418209, 5.82068387303e-05 ], "dmean":[ 0.0350424982607, 0.580041825771, 2.61035442352, 6.80757236481, 16.318693161, 6.28967905045, 5.23286628723, 5.70099258423, 13.4230766296, 10.8768568039, 7.74926900864, 15.0034389496, 15.0271100998, 16.6018199921, 6.02632713318, 3.79855132103, 5.38237333298, 8.43670463562, 7.43314123154, 8.2191324234, 20.6921386719, 13.7538080215, 9.77100944519, 11.3139772415, 7.25735664368, 4.1218457222, 3.17433810234, 1.45581579208, 1.87883663177, 1.61556243896, 0.779599308968, 0.371349811554, 0.264476120472, 0.193502560258, 0.100708886981, 0.0353026203811, 0.0144239943475, 0.00325388251804, 0.000670222449116, 3.95594870497e-05 ], "var":[ 0.0116662262008, 1.27503490448, 25.6204109192, 206.069885254, 1430.30371094, 234.442382812, 206.473526001, 288.805267334, 1444.05480957, 817.272949219, 625.690795898, 2697.92749023, 2397.11669922, 7130.13330078, 515.86315918, 346.633789062, 809.448486328, 2584.76660156, 2616.26757812, 3138.03320312, 20583.1132812, 3381.78515625, 1288.27880859, 2477.11401367, 682.099060059, 216.604751587, 97.8326339722, 13.7823019028, 19.9818115234, 16.6690158844, 4.43811273575, 1.2346996069, 1.27807950974, 0.517578363419, 0.177246898413, 0.0190207827836, 0.00211605615914, 0.000168058744748, 1.65621640917e-05, 3.32350325039e-08 ], "dvar":[ 0.00734147289768, 1.59334981441, 34.9658050537, 236.386993408, 1519.1640625, 157.498901367, 126.366111755, 127.708183289, 921.020629883, 497.140319824, 303.813903809, 1122.60107422, 868.916992188, 1545.74194336, 241.721923828, 118.562904358, 218.458129883, 734.227844238, 762.639587402, 405.900024414, 2714.4777832, 1520.69287109, 428.032836914, 696.146972656, 272.255096436, 80.3479690552, 38.2512245178, 12.4188299179, 17.8483753204, 13.7868928909, 2.77658462524, 0.731714189053, 0.67433899641, 0.263227701187, 0.108624838293, 0.0132785160094, 0.00197272375226, 0.000125091624795, 1.03948887045e-05, 2.18264126772e-08 ], "mean":[ 0.0470404699445, 0.51214581728, 2.94917726517, 8.56711673737, 16.8939933777, 9.74743175507, 8.58794498444, 10.7102499008, 23.4056625366, 18.6381034851, 13.9866991043, 27.1086997986, 28.1797733307, 34.376159668, 10.7114057541, 6.6013917923, 10.7804918289, 16.5257949829, 15.1251926422, 19.3569641113, 49.2880210876, 27.8654499054, 20.180316925, 22.9017887115, 15.1088733673, 8.45034122467, 5.79618597031, 1.91187810898, 2.04077577591, 1.82269322872, 0.964613378048, 0.475511223078, 0.359998822212, 0.270887732506, 0.138232842088, 0.0431671403348, 0.0138024548069, 0.00314460648224, 0.000714557303581, 5.64505244256e-05 ] }, "zerocrossingrate":{ "min":0.00244140625, "max":0.525390625, "dvar2":0.000216632659431, "median":0.04736328125, "dmean2":0.0121012274176, "dmean":0.0100563038141, "var":0.00131242838688, "dvar":0.000186675912119, "mean":0.0536084286869 }, "spectral_contrast_coeffs":{ "min":[ -0.981265366077, -0.957678437233, -0.97364538908, -0.967074155807, -0.96257597208, -0.963937044144 ], "max":[ -0.232828617096, -0.450794398785, -0.464783608913, -0.367899239063, -0.556100070477, -0.624147236347 ], "dvar2":[ 0.0063711241819, 0.00358002260327, 0.00270585925318, 0.0017158848932, 0.00113679806236, 0.00189194327686 ], "median":[ -0.592801511288, -0.736532092094, -0.745844006538, -0.781389117241, -0.797656714916, -0.772757589817 ], "dmean2":[ 0.0916584655643, 0.074229337275, 0.0648957416415, 0.0500396862626, 0.0380141288042, 0.0374387130141 ], "dmean":[ 0.0592447705567, 0.0465519614518, 0.0414465926588, 0.0322540737689, 0.024834824726, 0.0243127625436 ], "var":[ 0.00911337323487, 0.00655136583373, 0.00618056347594, 0.00577284349129, 0.00380114861764, 0.00231571029872 ], "dvar":[ 0.00270068016835, 0.00148758781143, 0.00118501938414, 0.000784370582551, 0.000524090020917, 0.000761664705351 ], "mean":[ -0.594863533974, -0.735448241234, -0.745020091534, -0.775078713894, -0.790849328041, -0.779701292515 ] }, "dissonance":{ "min":0.155567497015, "max":0.500000119209, "dvar2":0.0011708199745, "median":0.469446510077, "dmean2":0.0329371914268, "dmean":0.0200165640563, "var":0.00120261416305, "dvar":0.000480501999846, "mean":0.460001558065 }, "spectral_energyband_high":{ "min":7.11060368084e-21, "max":0.00607443926856, "dvar2":2.99654345781e-07, "median":0.000152749445988, "dmean2":0.000292699754937, "dmean":0.000196773456992, "var":2.3810963512e-07, "dvar":1.35860290129e-07, "mean":0.000318794423947 }, "gfcc":{ "mean":[ -75.1009368896, 108.134063721, -114.922393799, 35.9068107605, -53.9456176758, -7.74362421036, -37.9716300964, 9.9239988327, -24.244802475, -10.4905223846, -23.3989257812, -9.61326217651, -9.66184806824 ], "icov":[ [ 8.00217167125e-05, -0.00014524954895, 0.000159682429512, -0.000167002261151, 9.01917956071e-05, -0.000181695446372, 0.000171542298631, -0.000129780688439, 0.000197500339709, -0.000278050283669, 0.000315256824251, -0.000206819575396, 0.000114160277008 ], [ -0.00014524954895, 0.00178359099664, -0.000247094896622, 0.00111800106242, -0.000636783661321, 0.000774645654019, -0.000396145915147, 0.000103466474684, -0.00112135277595, 0.00229536322877, -0.00234188255854, 0.00232740258798, -0.00131408020388 ], [ 0.000159682429512, -0.000247094896622, 0.00116660131607, -0.000234406528762, 0.000154832057888, -0.00112980930135, 0.000650760543067, -5.60496459912e-07, 0.000167754784343, -0.000856044876855, 0.0014209097717, -0.00128671491984, 0.00078388658585 ], [ -0.000167002261151, 0.00111800106242, -0.000234406528762, 0.00312983314507, -0.000794405816123, -0.00058439996792, 2.24104460358e-05, 0.000424961413955, -0.00152624503244, 0.00239069177769, -0.00202889670618, 0.00110484787729, -0.00148455286399 ], [ 9.01917956071e-05, -0.000636783661321, 0.000154832057888, -0.000794405816123, 0.00397709663957, -0.000828172138426, -0.000291677570203, -0.000975954462774, 0.00101370830089, -0.00107421039138, -0.000894718454219, 0.00066822959343, -0.000943991879467 ], [ -0.000181695446372, 0.000774645654019, -0.00112980930135, -0.00058439996792, -0.000828172138426, 0.00597975216806, -0.00177403702401, -0.000384801533073, 0.00153620564379, -0.00195281521883, 0.00158954621293, 0.000321330269799, 0.000257747073192 ], [ 0.000171542298631, -0.000396145915147, 0.000650760543067, 2.24104460358e-05, -0.000291677570203, -0.00177403702401, 0.0080866701901, -0.00180288043339, 7.330061635e-05, 0.00111615261994, -0.0011511715129, -0.000984402606264, 0.000181279159733 ], [ -0.000129780688439, 0.000103466474684, -5.60496459912e-07, 0.000424961413955, -0.000975954462774, -0.000384801533073, -0.00180288043339, 0.00851836241782, -0.00320849893615, 0.00141180318315, -0.00124999764375, 0.000833041733131, -0.00159861764405 ], [ 0.000197500339709, -0.00112135277595, 0.000167754784343, -0.00152624503244, 0.00101370830089, 0.00153620564379, 7.330061635e-05, -0.00320849893615, 0.0134673845023, -0.00816399604082, 0.00590222747996, -0.00234723254107, -0.000930359237827 ], [ -0.000278050283669, 0.00229536322877, -0.000856044876855, 0.00239069177769, -0.00107421039138, -0.00195281521883, 0.00111615261994, 0.00141180318315, -0.00816399604082, 0.0195522867143, -0.0122134555131, 0.00327054248191, 0.000577625120059 ], [ 0.000315256824251, -0.00234188255854, 0.0014209097717, -0.00202889670618, -0.000894718454219, 0.00158954621293, -0.0011511715129, -0.00124999764375, 0.00590222747996, -0.0122134555131, 0.021111080423, -0.00721056666225, 0.00365835893899 ], [ -0.000206819575396, 0.00232740258798, -0.00128671491984, 0.00110484787729, 0.00066822959343, 0.000321330269799, -0.000984402606264, 0.000833041733131, -0.00234723254107, 0.00327054248191, -0.00721056666225, 0.015932681039, -0.00565396249294 ], [ 0.000114160277008, -0.00131408020388, 0.00078388658585, -0.00148455286399, -0.000943991879467, 0.000257747073192, 0.000181279159733, -0.00159861764405, -0.000930359237827, 0.000577625120059, 0.00365835893899, -0.00565396249294, 0.0153731228784 ] ], "cov":[ [ 21821.4921875, 1302.12060547, -2790.62133789, 583.293151855, 63.9840736389, 26.9333572388, -119.419998169, 277.984161377, -110.454193115, -76.0569458008, -16.7917041779, -162.89515686, 121.933448792 ], [ 1302.12060547, 1445.02355957, -661.351135254, -311.094665527, 156.139587402, -306.730987549, 60.9609375, 92.3673324585, 3.47471880913, -108.692207336, 66.6088409424, -147.776809692, 75.2410125732 ], [ -2790.62133789, -661.351135254, 1889.47399902, 92.8338088989, -92.8102874756, 368.803192139, -78.5678482056, -95.1785125732, 24.1375102997, 86.254699707, -94.2749252319, 117.902046204, -80.0259933472 ], [ 583.293151855, -311.094665527, 92.8338088989, 515.187255859, 60.5850448608, 128.642654419, -2.62003684044, 6.46807575226, 13.7108755112, -3.07335114479, -7.74705600739, 28.8581352234, 29.7626171112 ], [ 63.9840736389, 156.139587402, -92.8102874756, 60.5850448608, 339.564758301, 28.9691524506, 45.5025405884, 54.3213920593, -9.63641166687, 12.0036411285, 43.2324829102, -22.4280166626, 29.361246109 ], [ 26.9333572388, -306.730987549, 368.803192139, 128.642654419, 28.9691524506, 324.149993896, 20.1203899384, -3.79410982132, -15.0688257217, 43.0340042114, -26.3511829376, 28.6324996948, -22.8170604706 ], [ -119.419998169, 60.9609375, -78.5678482056, -2.62003684044, 45.5025405884, 20.1203899384, 155.001724243, 42.1054992676, -1.47882866859, -8.76181983948, 19.2513332367, 2.83816099167, 11.5606393814 ], [ 277.984161377, 92.3673324585, -95.1785125732, 6.46807575226, 54.3213920593, -3.79410982132, 42.1054992676, 155.586120605, 33.8702697754, -2.8296790123, 9.17068958282, -6.04741716385, 28.1404056549 ], [ -110.454193115, 3.47471880913, 24.1375102997, 13.7108755112, -9.63641166687, -15.0688257217, -1.47882866859, 33.8702697754, 114.661575317, 34.3392486572, -7.06085252762, 10.0253458023, 15.4272651672 ], [ -76.0569458008, -108.692207336, 86.254699707, -3.07335114479, 12.0036411285, 43.0340042114, -8.76181983948, -2.8296790123, 34.3392486572, 112.38079071, 43.6220817566, 14.6831378937, -20.7214431763 ], [ -16.7917041779, 66.6088409424, -94.2749252319, -7.74705600739, 43.2324829102, -26.3511829376, 19.2513332367, 9.17068958282, -7.06085252762, 43.6220817566, 99.527053833, 15.1529960632, -6.4773888588 ], [ -162.89515686, -147.776809692, 117.902046204, 28.8581352234, -22.4280166626, 28.6324996948, 2.83816099167, -6.04741716385, 10.0253458023, 14.6831378937, 15.1529960632, 101.876480103, 16.7505264282 ], [ 121.933448792, 75.2410125732, -80.0259933472, 29.7626171112, 29.361246109, -22.8170604706, 11.5606393814, 28.1404056549, 15.4272651672, -20.7214431763, -6.4773888588, 16.7505264282, 91.9189758301 ] ] }, "spectral_flux":{ "min":6.40516528705e-11, "max":0.355690479279, "dvar2":0.00278266565874, "median":0.0625253766775, "dmean2":0.0423415489495, "dmean":0.0282820314169, "var":0.00376007612795, "dvar":0.00135926820803, "mean":0.0749769806862 }, "silence_rate_30dB":{ "min":0, "max":1, "dvar2":0.0354892387986, "median":1, "dmean2":0.0246406570077, "dmean":0.0123189976439, "var":0.00654759025201, "dvar":0.0121672395617, "mean":0.993408977985 }, "spectral_energyband_middle_high":{ "min":1.27805372214e-21, "max":0.0389622859657, "dvar2":9.42645056057e-06, "median":0.00293085677549, "dmean2":0.00200005969964, "dmean":0.00133119279053, "var":2.5883437047e-05, "dvar":4.14980877395e-06, "mean":0.00445337453857 }, "barkbands_spread":{ "min":0.167884364724, "max":139.821090698, "dvar2":253.175750732, "median":18.0125980377, "dmean2":10.9470348358, "dmean":6.75650119781, "var":180.234161377, "dvar":101.147232056, "mean":20.320306778 }, "spectral_centroid":{ "min":111.030769348, "max":11065.2148438, "dvar2":701335.5625, "median":1143.22497559, "dmean2":567.022827148, "dmean":354.07824707, "var":626410.0625, "dvar":292997.4375, "mean":1266.39624023 }, "pitch_salience":{ "min":0.103756688535, "max":0.928133249283, "dvar2":0.013650230132, "median":0.569122076035, "dmean2":0.124187774956, "dmean":0.0767409279943, "var":0.0128469280899, "dvar":0.00529233785346, "mean":0.566493272781 }, "erbbands_skewness":{ "min":-8.09688186646, "max":6.17588758469, "dvar2":1.00871086121, "median":0.283587485552, "dmean2":0.786845207214, "dmean":0.512230575085, "var":1.42545306683, "dvar":0.427418738604, "mean":0.235957682133 }, "erbbands_crest":{ "min":2.31988024712, "max":34.2482185364, "dvar2":16.2832355499, "median":8.70411396027, "dmean2":4.28936433792, "dmean":2.70263242722, "var":24.3617858887, "dvar":6.85065603256, "mean":9.92293930054 }, "melbands":{ "min":[ 1.7312522864e-24, 4.69779527492e-24, 4.46513652814e-24, 3.32025441336e-24, 6.6967940523e-24, 5.54350662457e-24, 5.62380004294e-24, 7.24197266845e-24, 4.37425935743e-24, 6.31036856572e-24, 4.53679113075e-24, 3.08454218323e-24, 4.01757944901e-24, 5.05271286808e-24, 5.25895581858e-24, 3.18568913964e-24, 7.13208118891e-24, 4.5636159514e-24, 5.04583400098e-24, 6.17228950832e-24, 6.96855978959e-24, 4.44096109684e-24, 6.9472211021e-24, 7.30462636813e-24, 7.82425851273e-24, 7.32260214157e-24, 5.87433201125e-24, 6.79884662102e-24, 6.23345462746e-24, 6.36714722381e-24, 5.56132344254e-24, 7.90887173342e-24, 7.08392990812e-24, 8.85545433259e-24, 6.01856418372e-24, 6.31963413149e-24, 6.85279050744e-24, 7.75657976909e-24, 8.50479856047e-24, 7.94873918585e-24 ], "max":[ 0.0150078302249, 0.0248268786818, 0.0372853949666, 0.0202775932848, 0.00924268271774, 0.00459495186806, 0.00642714649439, 0.00673051225021, 0.00483322422951, 0.0058145863004, 0.00487699825317, 0.00474451202899, 0.00216139364056, 0.00089383253362, 0.000890302297194, 0.00114067236427, 0.00265396363102, 0.00179244857281, 0.00171541213058, 0.00327429641038, 0.00261263479479, 0.00140622933395, 0.000604341796134, 0.000546872266568, 0.00119282689411, 0.000385722087231, 0.000307112553855, 0.000185638607945, 0.000196822540602, 7.07383733243e-05, 3.59257646778e-05, 4.41324045823e-05, 5.90648887737e-05, 5.03649462189e-05, 1.98958878173e-05, 1.04367582026e-05, 1.49458364831e-05, 3.54613039235e-05, 2.11343995034e-05, 1.33671064759e-05 ], "dvar2":[ 1.97027497961e-06, 1.79839098564e-05, 3.41721388395e-05, 6.39068775854e-06, 4.12703258235e-07, 1.60991760367e-07, 5.84672534387e-07, 1.35801229817e-07, 5.67447884237e-08, 9.04100758703e-08, 5.12682660769e-08, 6.00754361813e-08, 8.33282598478e-09, 2.19900075926e-09, 2.22529794591e-09, 2.68628164157e-09, 6.38194253e-09, 4.48827552901e-09, 1.61791646747e-09, 5.6524527281e-09, 7.00124225261e-09, 5.1307971205e-09, 1.8114484357e-09, 6.22804863237e-10, 1.41800715614e-09, 5.03870334345e-10, 3.42071621029e-10, 8.80266623482e-11, 5.10801435871e-11, 1.6358601973e-11, 1.09966497713e-11, 1.34551406475e-11, 2.12651927317e-11, 8.70376774126e-12, 2.70498077062e-12, 8.23377390574e-13, 6.18267915163e-13, 8.94622212682e-13, 5.74140724113e-13, 4.04918747187e-13 ], "median":[ 6.78846045048e-05, 0.000686653598677, 0.00100371893495, 0.000425793463364, 0.000121567500173, 8.67325506988e-05, 0.000119830452604, 7.64572032494e-05, 4.73126528959e-05, 4.95156273246e-05, 5.10508471052e-05, 3.72932154278e-05, 1.39631702041e-05, 4.18296076532e-06, 3.36458288075e-06, 5.95153596805e-06, 7.06666241967e-06, 5.36886500413e-06, 6.55584017295e-06, 1.27752928165e-05, 1.80421066034e-05, 1.10708388092e-05, 9.13756139198e-06, 6.40786538497e-06, 6.87108331476e-06, 5.39385519005e-06, 3.31015212396e-06, 2.2452804842e-06, 2.11795463656e-06, 8.45357135404e-07, 1.55302529947e-07, 2.16630468231e-07, 2.0936421663e-07, 1.59076819273e-07, 1.03014848207e-07, 5.60769883862e-08, 4.16746956944e-08, 3.91173387015e-08, 3.08294119122e-08, 2.99958919925e-08 ], "dmean2":[ 0.000655197829474, 0.00202016695403, 0.00265697692521, 0.00127012608573, 0.000302697066218, 0.000204388590646, 0.000336306344252, 0.000177521447768, 0.000107648374978, 0.000137986353366, 0.000116639275802, 0.000105825478386, 3.63973231288e-05, 1.78256286745e-05, 1.63039130712e-05, 1.98472516786e-05, 2.55192408076e-05, 1.86820070667e-05, 1.45677859109e-05, 2.93159864668e-05, 3.81224999728e-05, 2.55198028754e-05, 1.86311717698e-05, 1.29478112285e-05, 1.63034928846e-05, 1.03988468254e-05, 6.90801107339e-06, 4.38494134869e-06, 3.91557023249e-06, 1.95062216335e-06, 1.26417035062e-06, 1.61554555689e-06, 1.957419272e-06, 1.21927041619e-06, 7.50478704958e-07, 4.08925643569e-07, 3.15493565495e-07, 2.87596407134e-07, 2.50877462804e-07, 2.38469112901e-07 ], "dmean":[ 0.000433199049439, 0.00115317711607, 0.00158822687808, 0.000778516754508, 0.000194163410924, 0.000129944470245, 0.00020776965539, 0.000110655011667, 7.02645556885e-05, 8.88201902853e-05, 7.56665394874e-05, 7.11579850758e-05, 2.26343472605e-05, 1.1214566257e-05, 1.06519555629e-05, 1.3082231817e-05, 1.72533091245e-05, 1.2507844076e-05, 9.44854855334e-06, 1.93181331269e-05, 2.54146216321e-05, 1.58707625815e-05, 1.14972417578e-05, 8.10282745078e-06, 1.0418824786e-05, 6.57239706925e-06, 4.29072770203e-06, 2.71895851256e-06, 2.42956934926e-06, 1.20713775686e-06, 7.98163171112e-07, 1.02098283605e-06, 1.22477297282e-06, 8.0222929455e-07, 4.99395412135e-07, 2.76074587191e-07, 2.18049038381e-07, 1.94722304059e-07, 1.73989079144e-07, 1.64999249819e-07 ], "var":[ 1.04273829038e-06, 4.88182786285e-06, 1.22067667689e-05, 3.09852111968e-06, 2.95254579896e-07, 1.60118688086e-07, 3.36397903311e-07, 9.02441499306e-08, 5.12204714198e-08, 8.87765096991e-08, 6.13447141973e-08, 1.30928043518e-07, 7.16093628839e-09, 2.90166846106e-09, 3.30743610277e-09, 3.75769282357e-09, 1.11862910046e-08, 7.38158290048e-09, 3.41670158832e-09, 2.24571348184e-08, 3.01072198283e-08, 4.89972462603e-09, 1.57648072374e-09, 7.79673825502e-10, 2.14637396745e-09, 5.14293607701e-10, 2.73522648975e-10, 8.45902653479e-11, 5.53298171169e-11, 1.09233455614e-11, 4.45680905375e-12, 6.09628753728e-12, 8.53084356628e-12, 4.52617804347e-12, 1.82034248786e-12, 6.15677430912e-13, 4.4449592119e-13, 6.71954570979e-13, 5.48820374997e-13, 3.48484424911e-13 ], "dvar":[ 8.33708043046e-07, 6.49838420941e-06, 1.36113221743e-05, 2.76258151644e-06, 1.75387782519e-07, 6.53309797372e-08, 2.18858644985e-07, 5.17116269805e-08, 2.40723956324e-08, 3.69722101823e-08, 2.22425278196e-08, 2.84932291095e-08, 3.64641872252e-09, 9.5759922214e-10, 9.86244308443e-10, 1.3016818734e-09, 3.31316440949e-09, 2.19745110996e-09, 7.73902275597e-10, 3.32642535739e-09, 3.88597554135e-09, 1.99106442444e-09, 7.00074609394e-10, 2.79730322239e-10, 6.32938867984e-10, 2.10190226335e-10, 1.22236526456e-10, 3.66416376407e-11, 2.22450686344e-11, 6.52528248449e-12, 4.34595848892e-12, 5.58480735616e-12, 8.46765019213e-12, 3.64534357561e-12, 1.20417478072e-12, 3.81005014864e-13, 2.8428255301e-13, 3.87186458025e-13, 2.78560404994e-13, 1.87431545436e-13 ], "mean":[ 0.00047609579633, 0.00126094208099, 0.00185862951912, 0.000971358967945, 0.000324478023686, 0.000246531388257, 0.000361267186236, 0.000190427192138, 0.000126440427266, 0.000159403600264, 0.000136641858262, 0.000143978060805, 3.71072310372e-05, 1.87067053048e-05, 1.93349878828e-05, 2.34964645642e-05, 3.1896517612e-05, 2.36660616793e-05, 1.83704396477e-05, 4.34660541941e-05, 5.81043132115e-05, 2.89009076369e-05, 2.15983145608e-05, 1.50616651808e-05, 1.95628890651e-05, 1.24749267343e-05, 8.04845058155e-06, 4.90500542583e-06, 4.20188916905e-06, 1.82525320724e-06, 8.70177814249e-07, 1.09259167402e-06, 1.25471365209e-06, 9.23780646644e-07, 6.02008753958e-07, 3.45971272964e-07, 2.69976595746e-07, 2.55056221476e-07, 2.36791535713e-07, 2.26877631349e-07 ] }, "spectral_entropy":{ "min":4.61501169205, "max":9.81467628479, "dvar2":0.184129029512, "median":7.38190746307, "dmean2":0.352754920721, "dmean":0.243219792843, "var":0.300432950258, "dvar":0.0902535244823, "mean":7.3010840416 }, "spectral_rolloff":{ "min":64.599609375, "max":21037.9394531, "dvar2":3396222, "median":861.328125, "dmean2":1057.10974121, "dmean":604.693481445, "var":2179523.25, "dvar":1425528, "mean":1383.6763916 }, "barkbands":{ "min":[ 6.93473619499e-25, 5.19568601854e-24, 1.02416202049e-23, 4.28463410897e-24, 3.04471689542e-23, 2.72335547301e-23, 3.08580014027e-23, 1.15517745957e-23, 5.01556722243e-23, 3.01743335215e-23, 3.20834273992e-23, 6.39242832255e-23, 4.96037188707e-23, 6.11980114915e-23, 7.73605723709e-23, 1.27850093686e-22, 8.33913319498e-23, 1.55698133196e-22, 1.80609161514e-22, 2.27535985033e-22, 3.03975723224e-22, 3.99622470031e-22, 4.5185483121e-22, 7.15649816942e-22, 1.01532623561e-21, 1.53862207745e-21, 2.31706194078e-21 ], "max":[ 0.00229191919789, 0.047907166183, 0.0691591277719, 0.0995827168226, 0.0820566862822, 0.0232072826475, 0.0308443717659, 0.0302771702409, 0.0344947054982, 0.0269099473953, 0.0134304724634, 0.00715320091695, 0.00838674418628, 0.0218261200935, 0.019664183259, 0.0362881943583, 0.0182446800172, 0.0173479039222, 0.0146563379094, 0.00540218688548, 0.00442544184625, 0.00217473763041, 0.0013186649885, 0.00208773952909, 0.00195873784833, 0.000584891589824, 0.00050722778542 ], "dvar2":[ 2.09710808718e-08, 5.00840687891e-05, 9.50076937443e-05, 0.000209878431633, 8.73877215781e-05, 3.97830672227e-06, 1.24859725474e-05, 1.86764350474e-06, 3.62891637451e-06, 3.01378167933e-06, 5.96735674208e-07, 1.57074438789e-07, 2.46402947823e-07, 4.87388831516e-07, 4.96838595154e-07, 1.52867119141e-06, 1.2727361991e-06, 3.34951494096e-07, 4.58745859078e-07, 1.02385989464e-07, 2.90899002664e-08, 3.47254207611e-08, 1.03255741735e-08, 3.98843491567e-09, 4.08773503935e-09, 1.11437570283e-09, 4.70612881998e-10 ], "median":[ 1.01329169411e-06, 0.000326245411998, 0.00187892094254, 0.00155476410873, 0.0016892075073, 0.000451811589301, 0.000548189680558, 0.000192572828382, 0.000307663634885, 0.000414336362155, 9.44759449339e-05, 2.73611021839e-05, 5.38471249456e-05, 6.47374472464e-05, 8.72917589732e-05, 0.000232024758589, 0.000200124268304, 0.000144036966958, 0.000164192693774, 8.08068361948e-05, 4.40170915681e-05, 1.11471890705e-05, 6.5616086431e-06, 3.2568784718e-06, 2.92447043648e-06, 4.78593108255e-07, 7.76246977807e-08 ], "dmean2":[ 5.63530775253e-05, 0.00340586039238, 0.00441808719188, 0.00632638018578, 0.0048057041131, 0.00103038607631, 0.00156902591698, 0.000596007099375, 0.000905426510144, 0.000906587869395, 0.000307931040879, 0.000146102538565, 0.000193360538105, 0.000240257213591, 0.000224526840611, 0.00052686111303, 0.000436997273937, 0.000281702930806, 0.000314143690048, 0.000150212654262, 8.88355425559e-05, 8.39097774588e-05, 4.62743046228e-05, 2.28419812629e-05, 2.21090576815e-05, 1.11555727926e-05, 5.03497130921e-06 ], "dmean":[ 4.36054288002e-05, 0.00207182555459, 0.00250360206701, 0.00372918182984, 0.0029777479358, 0.00065627245931, 0.000966914638411, 0.000378262484446, 0.000581912638154, 0.000613346113823, 0.000192098785192, 9.08574875211e-05, 0.000126861719764, 0.000160341092851, 0.000145415237057, 0.00034682394471, 0.000273586803814, 0.000176411645953, 0.000198787456611, 9.40177123994e-05, 5.56063205295e-05, 5.36341467523e-05, 3.11664407491e-05, 1.60014496942e-05, 1.57560625667e-05, 7.42050679037e-06, 3.44123100149e-06 ], "var":[ 1.73105885182e-08, 1.84976615856e-05, 2.50716802839e-05, 7.04820486135e-05, 4.76136665384e-05, 3.67056259165e-06, 6.74153125146e-06, 1.57238355314e-06, 3.58573174708e-06, 5.54312009626e-06, 5.51931407244e-07, 1.78970466891e-07, 4.17550438669e-07, 9.57703605309e-07, 8.59389047037e-07, 6.6176985456e-06, 1.22271069358e-06, 4.2584204607e-07, 5.58315321086e-07, 9.85875061588e-08, 2.47446703128e-08, 1.64356386279e-08, 7.04793423623e-09, 3.46884565516e-09, 3.82823417411e-09, 6.34027108593e-10, 3.8906944333e-10 ], "dvar":[ 1.15370024645e-08, 1.92330826394e-05, 3.36813718604e-05, 8.24065355118e-05, 3.870560613e-05, 1.55360748977e-06, 4.66681467515e-06, 7.47769490772e-07, 1.50442451741e-06, 1.46062268414e-06, 2.81505151634e-07, 6.65834889446e-08, 1.17960382795e-07, 2.50351632758e-07, 2.41593681949e-07, 8.20324487449e-07, 5.11046778229e-07, 1.56924727435e-07, 1.97066171381e-07, 4.1844121057e-08, 1.20861924913e-08, 1.45208129965e-08, 4.66174743252e-09, 1.88047488692e-09, 2.07477035552e-09, 5.05100294923e-10, 2.44846420916e-10 ], "mean":[ 5.19759996678e-05, 0.00199001817964, 0.00309076649137, 0.00392009504139, 0.0039071268402, 0.00123883492779, 0.00161516538355, 0.000634168100078, 0.00102682900615, 0.00122780457605, 0.000289549614536, 0.000142228382174, 0.000236654945184, 0.000305703491904, 0.000276061356999, 0.000822361966129, 0.000508801313117, 0.000334064679919, 0.000397378782509, 0.00017829038552, 8.88610738912e-05, 5.81649801461e-05, 3.79984667234e-05, 2.1309551812e-05, 2.22307517106e-05, 7.82890947448e-06, 3.57463068212e-06 ] }, "melbands_flatness_db":{ "min":0.00437760027125, "max":0.607957184315, "dvar2":0.00331180845387, "median":0.219427987933, "dmean2":0.0528259426355, "dmean":0.034728333354, "var":0.00492821913213, "dvar":0.00150177872274, "mean":0.230123117566 }, "melbands_skewness":{ "min":-2.44428515434, "max":15.3962888718, "dvar2":2.92571592331, "median":2.28337860107, "dmean2":1.4999755621, "dmean":0.987386882305, "var":4.25560426712, "dvar":1.34734094143, "mean":2.84234952927 }, "barkbands_skewness":{ "min":-6.20005989075, "max":18.9707603455, "dvar2":2.02687716484, "median":1.45346200466, "dmean2":1.15091514587, "dmean":0.750691831112, "var":2.42782378197, "dvar":0.866045475006, "mean":1.71697402 }, "silence_rate_60dB":{ "min":0, "max":1, "dvar2":0.0371209047735, "median":0, "dmean2":0.0337187945843, "dmean":0.0168575756252, "var":0.179377868772, "dvar":0.0165733974427, "mean":0.234251752496 }, "spectral_energyband_low":{ "min":4.89638611687e-23, "max":0.116083092988, "dvar2":0.000277574028587, "median":0.00443472480401, "dmean2":0.00833203457296, "dmean":0.00496239587665, "var":9.57977899816e-05, "dvar":0.000101656405604, "mean":0.00667642848566 }, "spectral_energyband_middle_low":{ "min":2.98093395713e-22, "max":0.171243280172, "dvar2":0.000569899275433, "median":0.0082081919536, "dmean2":0.0117948763072, "dmean":0.00737277884036, "var":0.000299356790492, "dvar":0.000242799738771, "mean":0.0127554573119 }, "melbands_kurtosis":{ "min":-1.94107413292, "max":380.680023193, "dvar2":1038.95019531, "median":7.13754844666, "dmean2":17.6919975281, "dmean":11.5336751938, "var":1275.55053711, "dvar":493.653686523, "mean":19.2873706818 }, "spectral_decrease":{ "min":-4.64023344193e-08, "max":6.78438804261e-19, "dvar2":6.20802821665e-17, "median":-4.53350113006e-09, "dmean2":4.26796598063e-09, "dmean":2.69736544212e-09, "var":3.69683427013e-17, "dvar":2.61311086979e-17, "mean":-5.59147705914e-09 }, "erbbands_kurtosis":{ "min":-1.86338639259, "max":171.201263428, "dvar2":23.2438850403, "median":-0.320037484169, "dmean2":2.39585089684, "dmean":1.52739417553, "var":35.0008544922, "dvar":11.0155954361, "mean":1.24987971783 }, "melbands_crest":{ "min":1.85922825336, "max":32.9627304077, "dvar2":27.1548709869, "median":12.5910797119, "dmean2":5.60343551636, "dmean":3.39730739594, "var":20.3299713135, "dvar":10.5175638199, "mean":13.3324451447 }, "melbands_spread":{ "min":0.26147004962, "max":299.135498047, "dvar2":1027.73010254, "median":17.4329528809, "dmean2":16.3484096527, "dmean":10.0459423065, "var":529.263366699, "dvar":403.605499268, "mean":23.2355747223 }, "spectral_energy":{ "min":1.02320440393e-20, "max":0.179453358054, "dvar2":0.000923860818148, "median":0.0185781233013, "dmean2":0.0165870357305, "dmean":0.0105188144371, "var":0.000572183460463, "dvar":0.00039060486597, "mean":0.0224339049309 }, "mfcc":{ "mean":[ -715.191650391, 133.878646851, -2.74888682365, 38.7127075195, 3.85699295998, -10.4443674088, -5.89105558395, 4.36424779892, 2.37372231483, 1.66640949249, -7.12301874161, -9.74494552612, -3.86338329315 ], "icov":[ [ 7.98077235231e-05, -5.86888636462e-05, 0.000145378129673, -9.86643281067e-05, 3.03972447e-05, -0.000187377940165, 0.000134168396471, -0.000129314183141, 1.27186794998e-05, -7.31398395146e-05, 1.38320649512e-06, 0.000111114335596, -8.96842757356e-05 ], [ -5.86888636462e-05, 0.000914695789106, -1.55892048497e-05, 0.000330161477905, -0.000239893066464, 0.00115360564087, -0.000499361369293, 0.000324924068991, -1.15215043479e-05, -0.000243278453127, 0.000191972227185, -9.64893956734e-07, 0.000270225456916 ], [ 0.000145378129673, -1.55892048497e-05, 0.0013757571578, 7.94086445239e-05, -0.000468786456622, -0.00135524733923, 0.00098228675779, -0.000316469959216, -0.000591932330281, 0.000769039965235, -0.000745072495192, 0.000638137804344, 0.000274067162536 ], [ -9.86643281067e-05, 0.000330161477905, 7.94086445239e-05, 0.00385635741986, -0.00140862353146, 0.000500513473526, 0.000532710750122, -0.00153480307199, 0.000852926750667, -0.000837227795273, 0.00151946756523, -0.00070260866778, 0.000143392870086 ], [ 3.03972447e-05, -0.000239893066464, -0.000468786456622, -0.00140862353146, 0.0056857005693, -0.00172490451951, -0.00111165409908, 0.000201825780096, 0.000247466086876, -0.00217686733231, 0.00112909392919, -0.000958321907092, -0.000166401950992 ], [ -0.000187377940165, 0.00115360564087, -0.00135524733923, 0.000500513473526, -0.00172490451951, 0.00837340857834, -0.00406587868929, 0.00216831197031, -0.0023798532784, 0.00346444058232, -0.00286303297617, 0.00123248388991, -0.000868651026394 ], [ 0.000134168396471, -0.000499361369293, 0.00098228675779, 0.000532710750122, -0.00111165409908, -0.00406587868929, 0.00986782740802, -0.00542975915596, 0.00409825751558, -0.0044681020081, 0.00282385596074, -0.00354772782885, 0.00168551225215 ], [ -0.000129314183141, 0.000324924068991, -0.000316469959216, -0.00153480307199, 0.000201825780096, 0.00216831197031, -0.00542975915596, 0.0144132943824, -0.00864001736045, 0.00550767453387, -0.00397388311103, 0.00314126117155, -0.00206855195574 ], [ 1.27186794998e-05, -1.15215043479e-05, -0.000591932330281, 0.000852926750667, 0.000247466086876, -0.0023798532784, 0.00409825751558, -0.00864001736045, 0.0176015142351, -0.0112102692947, 0.0083336783573, -0.00373222492635, 0.00208288733847 ], [ -7.31398395146e-05, -0.000243278453127, 0.000769039965235, -0.000837227795273, -0.00217686733231, 0.00346444058232, -0.0044681020081, 0.00550767453387, -0.0112102692947, 0.0205363947898, -0.0105180032551, 0.00535045098513, -0.00213157944381 ], [ 1.38320649512e-06, 0.000191972227185, -0.000745072495192, 0.00151946756523, 0.00112909392919, -0.00286303297617, 0.00282385596074, -0.00397388311103, 0.0083336783573, -0.0105180032551, 0.0160211343318, -0.00672314595431, 0.00358490552753 ], [ 0.000111114335596, -9.64893956734e-07, 0.000638137804344, -0.00070260866778, -0.000958321907092, 0.00123248388991, -0.00354772782885, 0.00314126117155, -0.00373222492635, 0.00535045098513, -0.00672314595431, 0.0181408431381, -0.00800348073244 ], [ -8.96842757356e-05, 0.000270225456916, 0.000274067162536, 0.000143392870086, -0.000166401950992, -0.000868651026394, 0.00168551225215, -0.00206855195574, 0.00208288733847, -0.00213157944381, 0.00358490552753, -0.00800348073244, 0.0156487096101 ] ], "cov":[ [ 18717.6230469, 1395.88500977, -2566.72802734, 557.851989746, -126.109024048, -374.194091797, 26.1053237915, 164.080841064, 80.1878204346, 192.214401245, -182.145751953, -47.5592041016, 152.686767578 ], [ 1395.88500977, 1649.59350586, -550.320007324, -49.1516876221, -56.1948204041, -331.535217285, 9.86240959167, -34.6940498352, -6.41311454773, 74.1020736694, -56.9916381836, -16.3797187805, -19.4202880859 ], [ -2566.72802734, -550.320007324, 1546.81799316, -94.7429580688, 133.984313965, 339.89239502, -54.6255912781, 10.0923881531, 34.4428405762, -69.080619812, 85.0990753174, -36.5973091125, -56.1265907288 ], [ 557.851989746, -49.1516876221, -94.7429580688, 342.6902771, 83.1379013062, -15.0537748337, 17.4024486542, 46.2917900085, 21.9955101013, 12.8328580856, -40.3507461548, 8.21014022827, 19.1214065552 ], [ -126.109024048, -56.1948204041, 133.984313965, 83.1379013062, 261.195220947, 98.0570144653, 69.749458313, 21.4776115417, 23.9828796387, 28.7067451477, 2.99768400192, 18.3751049042, 10.1154251099 ], [ -374.194091797, -331.535217285, 339.89239502, -15.0537748337, 98.0570144653, 288.139526367, 71.6913833618, 15.3370637894, 9.08637428284, -21.2720279694, 42.7490844727, 11.0748538971, 0.872315227985 ], [ 26.1053237915, 9.86240959167, -54.6255912781, 17.4024486542, 69.749458313, 71.6913833618, 187.006271362, 49.2006340027, 4.36477804184, 25.2697525024, 7.5669798851, 29.0018577576, 7.81967544556 ], [ 164.080841064, -34.6940498352, 10.0923881531, 46.2917900085, 21.4776115417, 15.3370637894, 49.2006340027, 122.378990173, 54.7077331543, 6.68141078949, -6.7650809288, -0.911539852619, 7.60771179199 ], [ 80.1878204346, -6.41311454773, 34.4428405762, 21.9955101013, 23.9828796387, 9.08637428284, 4.36477804184, 54.7077331543, 120.954666138, 41.4050827026, -25.8017272949, -5.81494665146, -0.236202299595 ], [ 192.214401245, 74.1020736694, -69.080619812, 12.8328580856, 28.7067451477, -21.2720279694, 25.2697525024, 6.68141078949, 41.4050827026, 102.835891724, 31.3814048767, -2.45175004005, -1.74627566338 ], [ -182.145751953, -56.9916381836, 85.0990753174, -40.3507461548, 2.99768400192, 42.7490844727, 7.5669798851, -6.7650809288, -25.8017272949, 31.3814048767, 120.884010315, 22.8159122467, -8.79971408844 ], [ -47.5592041016, -16.3797187805, -36.5973091125, 8.21014022827, 18.3751049042, 11.0748538971, 29.0018577576, -0.911539852619, -5.81494665146, -2.45175004005, 22.8159122467, 87.9682235718, 38.346157074 ], [ 152.686767578, -19.4202880859, -56.1265907288, 19.1214065552, 10.1154251099, 0.872315227985, 7.81967544556, 7.60771179199, -0.236202299595, -1.74627566338, -8.79971408844, 38.346157074, 87.662071228 ] ] }, "spectral_contrast_valleys":{ "min":[ -27.644701004, -27.7437572479, -27.5804691315, -27.6421985626, -27.3466243744, -27.4174346924 ], "max":[ -5.21189641953, -4.43200492859, -5.37789392471, -5.91939544678, -5.84870004654, -8.253657341 ], "dvar2":[ 0.418128162622, 0.42821636796, 0.406443417072, 0.387645244598, 0.499291837215, 0.697984993458 ], "median":[ -7.62850189209, -6.92876195908, -7.67359733582, -8.07751655579, -7.46400737762, -11.0671672821 ], "dmean2":[ 0.747626721859, 0.710296332836, 0.67908090353, 0.625626146793, 0.595694363117, 0.680769026279 ], "dmean":[ 0.493877381086, 0.47190451622, 0.458809673786, 0.433981686831, 0.418061226606, 0.529967188835 ], "var":[ 2.56318879128, 2.56686043739, 2.68657803535, 2.71048474312, 2.86386156082, 2.03321886063 ], "dvar":[ 0.185303419828, 0.210249379277, 0.205063581467, 0.197276696563, 0.240655809641, 0.327577888966 ], "mean":[ -7.9825963974, -7.30778980255, -8.13263988495, -8.62220096588, -8.10116863251, -11.1167650223 ] }, "barkbands_flatness_db":{ "min":0.00845116842538, "max":0.463767468929, "dvar2":0.00153901893646, "median":0.152025014162, "dmean2":0.0385256558657, "dmean":0.0259312130511, "var":0.00293617043644, "dvar":0.000700293399859, "mean":0.161552548409 }, "dynamic_complexity":5.97568511963, "spectral_skewness":{ "min":-0.258594423532, "max":7.78090715408, "dvar2":0.501113593578, "median":1.41668355465, "dmean2":0.598508477211, "dmean":0.382409095764, "var":0.599681735039, "dvar":0.224964693189, "mean":1.57063114643 }, "erbbands_flatness_db":{ "min":0.0321905463934, "max":0.434577912092, "dvar2":0.00118384102825, "median":0.181439816952, "dmean2":0.0333808884025, "dmean":0.024810899049, "var":0.00294912024401, "dvar":0.000594827288296, "mean":0.178649738431 }, "hfc":{ "min":1.12110872149e-16, "max":96.712890625, "dvar2":109.576454163, "median":11.9836702347, "dmean2":6.97959280014, "dmean":4.67007303238, "var":197.084396362, "dvar":50.9854736328, "mean":14.6673564911 }, "barkbands_crest":{ "min":2.1863629818, "max":25.4534358978, "dvar2":11.5983400345, "median":8.56367301941, "dmean2":3.74820661545, "dmean":2.30216050148, "var":11.5966396332, "dvar":4.54739236832, "mean":9.14883136749 } }, "highlevel":{ "timbre":{ "all":{ "dark":0.0808309540153, "bright":0.919169068336 }, "version":{ "essentia":"2.1-beta1", "extractor":"music 1.0", "gaia_git_sha":"857329b", "models_essentia_git_sha":"v2.1_beta1", "essentia_git_sha":"v2.1_beta1-228-g260734a", "essentia_build_sha":"8e24b98b71ad84f3024c7541412f02124a26d327", "gaia":"2.4-dev" }, "value":"bright", "probability":0.919169068336 }, "ismir04_rhythm":{ "all":{ "Rumba-American":0.0406456775963, "VienneseWaltz":0.338310062885, "Samba":0.0297329928726, "Rumba-Misc":0.0135653112084, "Rumba-International":0.0278510767967, "Tango":0.330144882202, "Waltz":0.00898563489318, "ChaChaCha":0.0889096781611, "Jive":0.11033257097, "Quickstep":0.0115221142769 }, "version":{ "essentia":"2.1-beta1", "extractor":"music 1.0", "gaia_git_sha":"857329b", "models_essentia_git_sha":"v2.1_beta1", "essentia_git_sha":"v2.1_beta1-228-g260734a", "essentia_build_sha":"8e24b98b71ad84f3024c7541412f02124a26d327", "gaia":"2.4-dev" }, "value":"VienneseWaltz", "probability":0.338310062885 }, "voice_instrumental":{ "all":{ "instrumental":0.999981045723, "voice":1.89501206478e-05 }, "version":{ "essentia":"2.1-beta1", "extractor":"music 1.0", "gaia_git_sha":"857329b", "models_essentia_git_sha":"v2.1_beta1", "essentia_git_sha":"v2.1_beta1-228-g260734a", "essentia_build_sha":"8e24b98b71ad84f3024c7541412f02124a26d327", "gaia":"2.4-dev" }, "value":"instrumental", "probability":0.999981045723 }, "gender":{ "all":{ "male":0.108683988452, "female":0.891315996647 }, "version":{ "essentia":"2.1-beta1", "extractor":"music 1.0", "gaia_git_sha":"857329b", "models_essentia_git_sha":"v2.1_beta1", "essentia_git_sha":"v2.1_beta1-228-g260734a", "essentia_build_sha":"8e24b98b71ad84f3024c7541412f02124a26d327", "gaia":"2.4-dev" }, "value":"female", "probability":0.891315996647 }, "genre_rosamerica":{ "all":{ "hip":0.070330247283, "rhy":0.225707545877, "jaz":0.0771619826555, "dan":0.0574826933444, "roc":0.270465612411, "cla":0.0938607081771, "pop":0.175827592611, "spe":0.0291636306792 }, "version":{ "essentia":"2.1-beta1", "extractor":"music 1.0", "gaia_git_sha":"857329b", "models_essentia_git_sha":"v2.1_beta1", "essentia_git_sha":"v2.1_beta1-228-g260734a", "essentia_build_sha":"8e24b98b71ad84f3024c7541412f02124a26d327", "gaia":"2.4-dev" }, "value":"roc", "probability":0.270465612411 }, "mood_electronic":{ "all":{ "electronic":0.339881360531, "not_electronic":0.660118639469 }, "version":{ "essentia":"2.1-beta1", "extractor":"music 1.0", "gaia_git_sha":"857329b", "models_essentia_git_sha":"v2.1_beta1", "essentia_git_sha":"v2.1_beta1-228-g260734a", "essentia_build_sha":"8e24b98b71ad84f3024c7541412f02124a26d327", "gaia":"2.4-dev" }, "value":"not_electronic", "probability":0.660118639469 }, "genre_electronic":{ "all":{ "house":0.187250360847, "trance":0.185409858823, "dnb":0.00702595943585, "techno":0.0184047427028, "ambient":0.601909101009 }, "version":{ "essentia":"2.1-beta1", "extractor":"music 1.0", "gaia_git_sha":"857329b", "models_essentia_git_sha":"v2.1_beta1", "essentia_git_sha":"v2.1_beta1-228-g260734a", "essentia_build_sha":"8e24b98b71ad84f3024c7541412f02124a26d327", "gaia":"2.4-dev" }, "value":"ambient", "probability":0.601909101009 }, "mood_sad":{ "all":{ "not_sad":0.700305402279, "sad":0.299694597721 }, "version":{ "essentia":"2.1-beta1", "extractor":"music 1.0", "gaia_git_sha":"857329b", "models_essentia_git_sha":"v2.1_beta1", "essentia_git_sha":"v2.1_beta1-228-g260734a", "essentia_build_sha":"8e24b98b71ad84f3024c7541412f02124a26d327", "gaia":"2.4-dev" }, "value":"not_sad", "probability":0.700305402279 }, "tonal_atonal":{ "all":{ "atonal":0.125749841332, "tonal":0.874250173569 }, "version":{ "essentia":"2.1-beta1", "extractor":"music 1.0", "gaia_git_sha":"857329b", "models_essentia_git_sha":"v2.1_beta1", "essentia_git_sha":"v2.1_beta1-228-g260734a", "essentia_build_sha":"8e24b98b71ad84f3024c7541412f02124a26d327", "gaia":"2.4-dev" }, "value":"tonal", "probability":0.874250173569 }, "mood_party":{ "all":{ "party":0.234383180737, "not_party":0.765616834164 }, "version":{ "essentia":"2.1-beta1", "extractor":"music 1.0", "gaia_git_sha":"857329b", "models_essentia_git_sha":"v2.1_beta1", "essentia_git_sha":"v2.1_beta1-228-g260734a", "essentia_build_sha":"8e24b98b71ad84f3024c7541412f02124a26d327", "gaia":"2.4-dev" }, "value":"not_party", "probability":0.765616834164 }, "moods_mirex":{ "all":{ "Cluster2":0.0673071071506, "Cluster3":0.397048592567, "Cluster1":0.061667304486, "Cluster4":0.190215244889, "Cluster5":0.283761769533 }, "version":{ "essentia":"2.1-beta1", "extractor":"music 1.0", "gaia_git_sha":"857329b", "models_essentia_git_sha":"v2.1_beta1", "essentia_git_sha":"v2.1_beta1-228-g260734a", "essentia_build_sha":"8e24b98b71ad84f3024c7541412f02124a26d327", "gaia":"2.4-dev" }, "value":"Cluster3", "probability":0.397048592567 }, "danceability":{ "all":{ "danceable":0.143928021193, "not_danceable":0.85607200861 }, "version":{ "essentia":"2.1-beta1", "extractor":"music 1.0", "gaia_git_sha":"857329b", "models_essentia_git_sha":"v2.1_beta1", "essentia_git_sha":"v2.1_beta1-228-g260734a", "essentia_build_sha":"8e24b98b71ad84f3024c7541412f02124a26d327", "gaia":"2.4-dev" }, "value":"not_danceable", "probability":0.85607200861 }, "genre_dortmund":{ "all":{ "raphiphop":4.74844455312e-05, "electronic":0.984485208988, "jazz":0.000787914788816, "pop":0.000125292601297, "folkcountry":0.00235203420743, "rock":0.0010081063956, "alternative":0.00961782038212, "funksoulrnb":6.0458383814e-05, "blues":0.00151570443995 }, "version":{ "essentia":"2.1-beta1", "extractor":"music 1.0", "gaia_git_sha":"857329b", "models_essentia_git_sha":"v2.1_beta1", "essentia_git_sha":"v2.1_beta1-228-g260734a", "essentia_build_sha":"8e24b98b71ad84f3024c7541412f02124a26d327", "gaia":"2.4-dev" }, "value":"electronic", "probability":0.984485208988 }, "mood_acoustic":{ "all":{ "acoustic":0.415711194277, "not_acoustic":0.584288835526 }, "version":{ "essentia":"2.1-beta1", "extractor":"music 1.0", "gaia_git_sha":"857329b", "models_essentia_git_sha":"v2.1_beta1", "essentia_git_sha":"v2.1_beta1-228-g260734a", "essentia_build_sha":"8e24b98b71ad84f3024c7541412f02124a26d327", "gaia":"2.4-dev" }, "value":"not_acoustic", "probability":0.584288835526 }, "mood_happy":{ "all":{ "not_happy":0.910523295403, "happy":0.0894767045975 }, "version":{ "essentia":"2.1-beta1", "extractor":"music 1.0", "gaia_git_sha":"857329b", "models_essentia_git_sha":"v2.1_beta1", "essentia_git_sha":"v2.1_beta1-228-g260734a", "essentia_build_sha":"8e24b98b71ad84f3024c7541412f02124a26d327", "gaia":"2.4-dev" }, "value":"not_happy", "probability":0.910523295403 }, "mood_aggressive":{ "all":{ "not_aggressive":0.922077834606, "aggressive":0.0779221653938 }, "version":{ "essentia":"2.1-beta1", "extractor":"music 1.0", "gaia_git_sha":"857329b", "models_essentia_git_sha":"v2.1_beta1", "essentia_git_sha":"v2.1_beta1-228-g260734a", "essentia_build_sha":"8e24b98b71ad84f3024c7541412f02124a26d327", "gaia":"2.4-dev" }, "value":"not_aggressive", "probability":0.922077834606 }, "genre_tzanetakis":{ "all":{ "hip":0.154464527965, "jaz":0.308918893337, "bl":0.0514711923897, "roc":0.0772226303816, "cla":0.0343172624707, "pop":0.0617748275399, "met":0.0441242903471, "co":0.102957598865, "reg":0.0617764480412, "dis":0.102972343564 }, "version":{ "essentia":"2.1-beta1", "extractor":"music 1.0", "gaia_git_sha":"857329b", "models_essentia_git_sha":"v2.1_beta1", "essentia_git_sha":"v2.1_beta1-228-g260734a", "essentia_build_sha":"8e24b98b71ad84f3024c7541412f02124a26d327", "gaia":"2.4-dev" }, "value":"jaz", "probability":0.308918893337 }, "mood_relaxed":{ "all":{ "not_relaxed":0.87636756897, "relaxed":0.123632438481 }, "version":{ "essentia":"2.1-beta1", "extractor":"music 1.0", "gaia_git_sha":"857329b", "models_essentia_git_sha":"v2.1_beta1", "essentia_git_sha":"v2.1_beta1-228-g260734a", "essentia_build_sha":"8e24b98b71ad84f3024c7541412f02124a26d327", "gaia":"2.4-dev" }, "value":"not_relaxed", "probability":0.87636756897 } }, "metadata":{ "audio_properties":{ "analysis_sample_rate":44100, "length":214.866668701, "downmix":"mix", "bit_rate":0, "codec":"flac", "md5_encoded":"2b46dab358c1b79a3decd5bd93d7221f", "equal_loudness":0, "replay_gain":-14.4778690338, "lossless":1 }, "version":{ "lowlevel":{ "essentia":"2.1-beta1", "extractor":"music 1.0", "essentia_build_sha":"50a0fbec89d6a9cedea3d45b6611406f7e8c7b1a", "essentia_git_sha":"v2.1_beta1-7-ge0e83e8-dirty" }, "highlevel":{ "essentia":"2.1-beta1", "extractor":"music 1.0", "gaia_git_sha":"857329b", "models_essentia_git_sha":"v2.1_beta1", "essentia_git_sha":"v2.1_beta1-228-g260734a", "essentia_build_sha":"8e24b98b71ad84f3024c7541412f02124a26d327", "gaia":"2.4-dev" } }, "tags":{ "albumartistsort":[ "Various Artists" ], "disctotal":[ "1" ], "file_name":"04 La Grange.flac", "artists":[ "ZZ Top" ], "musicbrainz_workid":[ "42722fe8-9de7-3729-a506-3c7f41c617a9" ], "releasecountry":[ "DE" ], "totaldiscs":[ "1" ], "albumartist":[ "Various Artists" ], "musicbrainz_albumartistid":[ "89ad4ac3-39f7-470e-963a-56509c546377" ], "composer":[ "Dusty Hill", "Frank Beard", "Billy Gibbons" ], "catalognumber":[ "491384 2" ], "tracknumber":[ "4" ], "replaygain_track_peak":[ "0.999969" ], "engineer":[ "Terry Manning", "Robin Hood Brians" ], "album":[ "Armageddon:The Album" ], "asin":[ "B000024C3A" ], "replaygain_album_gain":[ "-9.32 dB" ], "musicbrainz_artistid":[ "a81259a0-a2f5-464b-866e-71220f2739f1" ], "producer":[ "Bill Ham" ], "script":[ "Latn" ], "media":[ "CD" ], "label":[ "Columbia" ], "artistsort":[ "ZZ Top" ], "acoustid_id":[ "3ed3441e-facc-4fcd-9ef7-9fbc68c206a2" ], "replaygain_album_peak":[ "0.999969" ], "lyricist":[ "Dusty Hill", "Frank Beard", "Billy Gibbons" ], "musicbrainz_releasegroupid":[ "f51d56e4-0211-3533-a9a5-08c02d8bb04a" ], "compilation":[ "1" ], "barcode":[ "5099749138421" ], "releasestatus":[ "official" ], "composersort":[ "Hill, Dusty", "Beard, Frank", "Gibbons, Billy" ], "date":[ "1998" ], "isrc":[ "USWB10505222" ], "discnumber":[ "1" ], "musicbrainz_recordingid":[ "cd3f5efa-bc5e-4064-a765-960494ad4bb4" ], "tracktotal":[ "14" ], "originaldate":[ "1998-06-23" ], "language":[ "eng" ], "artist":[ "ZZ Top" ], "title":[ "La Grange" ], "releasetype":[ "album", "soundtrack" ], "musicbrainz_albumid":[ "cfc31187-aebd-309f-a92f-7138c17df7c2" ], "work":[ "La Grange" ], "totaltracks":[ "14" ], "replaygain_track_gain":[ "-9.38 dB" ], "musicbrainz_releasetrackid":[ "befe2741-462b-3568-ba06-c8cc8e4f6eaf" ] } } } ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������beetbox-beets-01f1faf/test/rsrc/archive.7z����������������������������������������������������������0000664�0000000�0000000�00000004533�14723254774�0020607�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������7zĽŻ'�xi¨á������Z�������vć|ăŕ ËŮ]�&”_%7{ŘÜ!ďQ4FUţ>Ë筢ߓ:S7ŢşE¨˛0)ž~ÁŇŢHNÔFş#ČH9âüa˘ ů0!µźş vŮo°j˙â|~ÄF3ĺ#9|o]{&ĹÓ촹㪼ůRrFFŰ>–†őĹ¶ŻĄÔÚ>)…në1Sw/釄Ň›" ő4Žu¦rkLÇ[Ř«ąXbí0ŕŇ>׏ŹPĂď˝Ďf“J;µ+řËUMŰ řl˛YŁ×;UĘń±PVdńĽ×rŻŹâĺ¸t„Z HéJeÄĹđŹT ÉĘj!µó˝|v·çn!uŠgióŐω®ď¸ŞŃöh r†?E}c˙8ŞĎěv˛:€o-aÎȵçV¬łŹ9˝9Ň)o»U`áp×nűč'ůTŞI?ůĽ*xľ„Ţ]4@z×`DlooĽ5´LUg–Ťź,.ľűl‘°–_$Ź^EV^OZN˝©`”=!ë):™ŹeŮi ·ĄC~Du>�cźJUÜeÍ®¦ž¤ËąfoŢ8V?ű—\2“Ň#IK+Ń6tŐŇç]+ Ée=>‚ĄÖYŠĚíö\@yęYw –Ś 4|#ÜŽ'WqěÜŕšü˛-ă âsľ"Fţ›RÇ/*¬Ă†Ň˙ßdýÉ—ňą3“�\ÖY¬čň2Šal×](cˇGÉ/1óW’«6+bdCÜ(ĽIĘËŻŹßă!#rMПƏÍý̰P66„Äm´e$ç>“Ú1Yr8nH¨·;=őW¨Ë{N'ß<8‚ę÷ţÓÁ"Z ]ć/ÝéHY™ĚU!ţ€gîňşgЍ�@Ž-CýV(„äńâ¤IN$zĹř,bzŤ.Ş_{vŠŔ%—ôwőËpď7ĽćÍ„¦Ęľ,+ÎgÝGv™Ť;ŽĘŃMó‰ýŔłzŐ°ë‹UĽ•¸Ż9&ů·ŤÂÚš ¬Â}†śćęÍţ×Üů„�'.+éšqk±NîŻ.JąL>†m̉x;g,{óh7Iť,[M©"{.Ż­Jgě …n}ĹoˇŚń;ą¨yx}ĘçČw÷N &~`¬˙–Ŕ˛ďO$Ľ‚ńA E*Fv’ź@NŠ[O¶CŮŤÝÝ ŘWRîĹÎüeľh˘/ÂÜÓt"ЉG$ř_ňZ] Ŕş!d(<´ső€˙6¨µ†ž”=&GE BÎ ZŃťeăůµHLąËh]¸KÓÁĺMŽD¸ďQŮš›O<ŰIËÉŁíńű€áóJÜÁźŹ—Šzü’Ý(‹ o ”€#揷ޖ  )#¦3°e5©ˇűX[}ćM†çúWMŽ7µ(`Q«†Ké™ú>‡pŇO…„¦Ă7/őÔ~Á=Łüś+b-}—nđÉ%‚pŔ9Ő_ý:;ö̆śźhPcű)‰ě÷p_-ĘYÎ.ă”^IŁábŃ źNÂÝŤMµK8Í*ćq‘_˙(µEaë˱†Š&Flx Gy¬ôŚ#›ĺ—[�MÇż\'#ezNżxk–ł/č“F Pţ˝“.ZŰqť�F©+Ń$ĚŚĺČĹÖQF~‰SěĐÍő!ÖßŐ:ëňŹýŤxq5zjѤAśY¤!1}#R®ąĂŐOµÂűlkôMŽ"°6¶Íuó-;5§f‡1<ŐIm?ý×ŰŁ;HQĄc°ĘÖäŃśŘó~—s A/e=¸żcÚbo&ąĘ¬ YU¶fŻ®ĄÎCí FĄ® A‘űIi:~«0b?Kr!šnU"Őq5…™‚cWâ6"!¦ŤwAL}Ő•T—““ř «‰»f¤Ł– ˙ćťčŸrôŰS*śZyüŽEsĽôLdŰJiű©1e±:DĚ™EÂEލ•UJđzçCŠŤß¬ďŐŞ48w3oLݶćM!6D^Ů+xˇä a ĹÁ|HG1Zµ¸Áę .ÔLăułÇÁBÉŞxă˛5¤¤V‘oíťĹ{ð[1v¶ýŔ €™ëáŐuÓ—ă*Í řća¤‰•)`íŐÍGˇ÷Lća ĹË#%a$šëŚý™ł^»˘“ŘľÓÂŐ?Á �•:dhËÁ……`ÂYŞ:űLEš˝ży éZ{C×ă58,ĺ2L,3iFŔýŁTF(Ť9Ó‘’†PâN?ŇjżĚżĹ‘Wb˝č µÓٱr;ňĚő5ĺŘĎÜ.jJ«Ő¤tĚ šÍęKtŕ =Yväâ\W\wš‚›Ćć g»o‰^ÚaNâŁÍú„yŃĆžőüáS{Ş—ľęŞĂE#@Žš˙UĘ˝­by79\»1–łMT*,3ş!8"•ääZVAť\řłĎŠ».ĂIŘŐ‰2”č23÷óŁňé=@rÁš¨Y‹îµôş«˘ó«Nˇ=oEÜŽZŚmź‡&Ľ=şX;B˛`ô ÷ŽCY5Ś&řűbËJ,fp?­NÖt“Ǣ|¨1Ó‘âÉOPŢ!ńe-q1ÍüVT7ä ¦p­żhř®”»XaĎKś¦�·JČsŤ RóYťŚ}qLżáóŁ űď0wŢ“ $}ű÷ 04Ý §st“č:Łü‚ÎYt /ŠĂckAúľŢÍEˇÓ\÷SeŤŘi‡[Ť­ĽVą˝ÝŮđ\żgŃűŰqŘŢÁŐéŞôME”•PčBO†|Ei ]Ć˙#3Kż‚îŔ‰ČpIKDšoŘő‰ç’Q-îHĹ3=0CSĂ ř˙\xĐ÷[´Ń ™TĽłkŔjĄČ@oŤĺ„GX1­Ôgß ¸3ćÓŐ͸rI¸˙–ß™Î<sr9ŐÎ}:•L’ ä +ŇB� 笠ÚfŠRÎÇĎ;Y•Ř�ŘâTÔ8á•TüФ+šŹĐĺR<ëĄäÄ!\u^`°VM—0Š×áwŚŰ6}ÂwĆ»�� á� �!!� ‹Ě� mŃłq�� �����������f�u�l�l�.�m�p�c���� �÷‚çź ×� ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������beetbox-beets-01f1faf/test/rsrc/archive.rar���������������������������������������������������������0000664�0000000�0000000�00000004465�14723254774�0021037�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������Rar!�Ďs�� �������ůlt *�đ��… ��úˇxĄčXŚD3�¤��full.mpc�Ŕ•XČŤ<űě’(&î»$ł6ÚęŁůBŐk ą]Ş]R—7h˂Ȓ´–ĎT®Ó6U×ásMĘâÉtŞă7j7E ®Óuu`šău Xŕ5řRFŰ„„„ůwŘ3w7éžôçyÎóťçyß<ďgó\÷Î|}ď<ç{ć~ýűžó“ßs'đžÄÖ§¦ěŻÜoÇľYOŕ˝!šć†‰ęôěn/ŔĐa…ŹfC©/‹ÔÓ9ĄĽ},–Ł4KÔË]śÉóíA†C XńBöĺ0Đw:·¤Uô‡«\C Ő #čىMa>Ź€Ią{Aj�¨.äŢŤ3jśúěT}ő±۬Ť›AYXśµ®XUŇ{)·ö!ÁYn`».Đ/٧łCł¸ŁWiqş'bŽęĆç•;×ŐĺČö}i5› o˶`ă:ŞőŠLM×»^f%j=źÍEUÄx/ŮŘŃę·Ü”w;ű˙IUŇ*G“äĄGţÍő~]éeźŇŠŁáíî-»˘›»ąĺăV™›ń¨›ú©•ö1dľ)íIň^ÖpöEuú~ű‰®ęńłşČŔ 2…ăĚŚźôą }žđá Ö…S QŽíźŕîf'n\·ńhI›ďĆ2ë†i¨ÚśjĄQF):#xôö ×äIíżw×{ĄÇîä ·¶3č+áĹî /0[ö–-»ÂˇDä!-ô!˘,ÇO"¦ŃŹ»MžđřDŰjýó™îđжáIŹ âg¸<©wč2<búÖžFOŢ\hćń“řţĽ.ˇlVlŞeŻ —{Xx’Đć˘0w&Š­“uFý!»îÝ严-RüBďť($ Ç=˛¨o~&ă©ň,1óĚ6€ Vž˝ü8\KŕfÇM3-řƱ‹†hŃşşŔ'dÂe‡ ©Ă¶ąÎ+z߸hZ¢żŇˇ¤­^–-ô!­ěčíďĄôc h^dŃmčި«Š Ů\@Tzë=ađ3śÎ=í«ŢCiU"J¸7ýëš,o“•ŤÍárSôŐ^jŐxÂ,R¸Ácŕ?őé†Qđ*YXlî¬n¸î¬.ŞB\Î^$틳“ ){ĹŮĐ?č~ — µ"Ü�Fńxtc«tCs|VÚ|*k›´Ďd"ëÔ¨˛l¤É7’m˙Ă؆ôćX\±rďęŐ9Ĺ+Xâ>}Š™ŁäŹ‹()0Ă;ůLJđ×µqxDĎň›¬ TŐŃyq8ťßí‡]€čÝî’X;ËłÜű޻܇dlĂłŐZIÖÄď2–ëŽüÎçbMĘŔ2Ü´`Ýx›Ł,÷´,µžŽ‰düĚÜ–ë×Y›ß,ç{qyĘě…é{^‡}ţzź7‘8°6KL™zĎÁ©ŤăWÚăÎś2ÁA&5EÄ3†ľ7 €«jŔ°Nô‡pP7]Ă&TŢ&ěHŁŇŻ?Pz^u’HŹ´&„L8'3ţtq_~®^đĐP"AD˝´]~=˙’rčšä+ë÷sS˘* ŃLGź= óOsĚć™ÁëŕfźqhgŢyŻČŐ¦âÖ3DĂ\<ĺXňâü˝Y"±čą¬őľ.z­1îGŮ÷Ďqm‚kě#ąß”2Ăα9Ť?ş‚€dUwÇxq*E*Ó¦ŘWAe‰éŚ}ŹDˇŤáČËëh[vAóďĹ6ćó˝˙# ś¸Ź6Â9§…4€ýQ0mÂŢ´/‹¦1}ˇJ5ŃGąűrkő˘Čjľ8®%g úu–ş~ß&FŐG7‚pá]6^ůëÄSúô O§Ň/č~‘ÍÍŮ6¤ˇÂťŃ˝Â:†ip@ $Ę@Âbrk˘<oFĹýQ|öŐ`ł´Â\ťŻ‚ë‹Ô3TKżŚQz a!/Ľ”ĘĄÔ3¶îęs»ż7]f0ď ™=-ŞjPł$â2@6 Ň r°‰rî•t¸,QŚSÝŠ|(Đ ĄW4@ßßłÓQdi•dGUíL¨ťľ(;ëšÁ�—!ȰSFQôÉ ˝.‘hetvUŃQ0ęďB; =0°·ˇPS$\Ť¸®8n §ŕXÝŘůMČěú¸´ŁŢáZFŢ+Š)ź€ÇÖ€LçŢ‹GŢő‡D'D‚sŽ|ÇťżJ#¸˛™ń ú\ÝŔ.Š1Ö{5*Ţýg&<Ž<­sʤů˛GÄł7°Ľ)3Ö®Âgü–,Ś››üłz}’Î×ďŕěŚgj}šĺ“‹5Ţ˝BÎrĎgőăh>źŐÁö¸ÚηçĆ×rűZ—Gůňţ+_ľ7çŻţ¤a…\, oĐf¦/ZrtćĐ4|Q‚ä™ŕéŮ $}Şa­O†ű†Á…yTäí[J›8B}Ĺ™ľL™JrńP <~TzűÚŻEîťb&Ż´­ałD Ë-R^Řy|ŁzÚÁ[Š2�1臻X,jÓ÷&@xô80ď‚;Ţířřle%żB”–‘ö­ śě˝§É­čk;öZÜżŞµ«>ă›»^Ů[’Ô?‡ĆsS:ůŤL϶Ľ<\l@«ąB¸5Ţ }žHŚppPR%źŚ3HiÓ6ô;7y$Ű“SˇźŮşň›nÚ…ăAĄ‚˘ jßuô‰‘;ŤB$^|BjdČQ=ZaĽčó‘Ňs03đůÄżőŻs$<řv–ˇő ?šxź¦F’‘âi)©AĄ˙´|ő ÓmÔĐŇÓľu5 Ş!Ń‚şaóԛ⩿efE•Ž36ß¶Ôôš /€”ď‚J›2ă Ăô•Čťg?šśmş‡ÝŁ|VcÄţbi|ći)’ů^gü¸ ´˘§¦¨­-|ĘďĚßQÔ%|Ľó˙—ľ‚¤ Ůă§cäĄ$ŁcގË$ddŁăPĘ9c^Ü!ŇÉHiv2´?ůun»Ó˛’dĄc^5 ŽQ)+=*ëĆÉ!•yÇwC±“Ź:ă˙† )?±ŽˇxĘä›Ě7(ĐdşÔ1±ňOC“‘;*éˇ˙ŕÖ ·đ@Ä={�@������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������beetbox-beets-01f1faf/test/rsrc/beetsplug/����������������������������������������������������������0000775�0000000�0000000�00000000000�14723254774�0020671�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������beetbox-beets-01f1faf/test/rsrc/beetsplug/test.py���������������������������������������������������0000664�0000000�0000000�00000000752�14723254774�0022226�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������from beets import ui from beets.plugins import BeetsPlugin class TestPlugin(BeetsPlugin): def __init__(self): super().__init__() self.is_test_plugin = True def commands(self): test = ui.Subcommand("test") test.func = lambda *args: None # Used in CompletionTest test.parser.add_option("-o", "--option", dest="my_opt") plugin = ui.Subcommand("plugin") plugin.func = lambda *args: None return [test, plugin] ����������������������beetbox-beets-01f1faf/test/rsrc/bpm.mp3�������������������������������������������������������������0000664�0000000�0000000�00000031024�14723254774�0020076�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������ID3����4TIT2������full�TPE1��� ���the artist�TRCK������2/3�TALB��� ���the album�TPOS������4/5�TDRC������2001�TCON��� ���the genre�COMM���h���engiTunNORM� 80000000 00000000 00000000 00000000 00000224 00000000 00000008 00000000 000003AC 00000000�TCMP������1�TENC������iTunes v7.6.2�COMM������eng�the comments�TBPM��� ���128 BPM�TIT1������the grouping�COMM������engiTunPGAP�0��COMM�����engiTunSMPB� 00000000 00000210 00000A2C 000000000000AC44 00000000 000021AC 00000000 00000000 00000000 00000000 00000000 00000000�USLT������eng�the lyrics�TCOM������the composer�TPE2������the album artist��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������˙ű`Ŕ�� F=��íÉ‘A#ô‰ą˙˙˙˙F0Ăß˙C ˙_ůď˙˙˙˙óĎŁ)çÚa”2ůŠz<‚žyő=xů=O `ĐĘÇŤ  ĄŕĆyé>`řÜá ĐŔXč" z)ăBś"Ŕ$)‚ A„ ��˙˙˙˙˙ř˙×?˙ű˙˙˙˙ţľÝvbŁ×Bť¬¦ ł4Ŕă°˛+¸µÂ¤ÍrąÁÔR°Ä%;ťîRŁQ[˙˙˙ďÖÜî˙łľÚ7UW6dTUT!ub8´Çř çčx?ń.Ě€Ź˙ÚÚ˘ł<ŚaeźDY/˙˙˙˝˙PśĽ·$D¬(躡ďPŚi§˙ű`Ŕ€Š9ŕ… éâH#4�•¸‹RŇAbÉuĎ&Ž ü:<cŠĚIÁij#Ř„`4㔑H‘qeu§šj2^ĺmłÚ•żúiżH­äşĚDĎC’âjÚĺn_«ąšşůEkwwŮÝ~˘îÚZ†6ë GMˇ$1ÉDČ㦩CňŢĆCB‰ÂŔ�€ ¶€ç˙˙ď˙˙ţú÷çwبE:ŽabŘę‚c˘ő>=ÄÝL´2!Ô¶#9îŽÔ#"5ż˙˙˙ú­˙fE$ěȉ,Ş1HËez˛1’Ş!ĨR ‰Ě§)˘((â˘,Ęc°š)ÄÜX˘¬,qE„T9N@řt���˙˙Ňţ÷˙?˙ű`Ŕ� ň= mÁ¸Č#tŤ¸ż˙˙óËäÜŻđÉ÷INˇćiŢ˙{ŢžYg§S—ÜëdE/'˙˙˙˙˙˙˙çţ§Ě¸g Wëď&%ÍirÄDuń6.ÝítÜ‘mtP&ę,Ž«˘’Duhâb¨D›ŔN±˙˙˙˙˙ý`Z˙ý{˙˙˙˙˙çĚímŢóĄ/ĂžjľGH„<&ńÎCĄ˙ü„žl÷˙˙˙˙˙˙˙ýţ˙çJÎÉPŤ1•ĄI’ňÔä»ň9Ł3łZHFĘ;ŽÔr/viĂ1  Ü‰…Ł2‡ �ěÁ˙˙˙˙˙Öő˙ţy˙?˙˙ůz\¨Ű+dDÎŞŹ1ĆckTIw˙űbŔ� ć% „MɬH#t‰¸™¨…¶R˛¶Í´ö˙˙˙˙dÝꓳĐ÷1ęr© ĘĎłîŇ»łlÖ• lΆmC‘Uę÷c,ě$(tČ4BK˙˙˙˙˙ëĺý˛ëZS˙˙˙˙üąQRĹ­ÝŚ‡>łĚl®®č§1ڍËf:V¨m&ľĺDGB;?˙˙˙˙˙ů­RĚé+9‡}•+Ďł+±ÍUĐčĘbPSú;1Ś%Ü; Š…R•Ěwt”€Î9B�˙˙˙˙ţ¶˙ë˙•˙˙˙˙äěúRwó5ťlŞ„s>ʸő#ĚäşuFlëĐęɕћ˙˙˙Ú˙ł«ÔŞFJČŞyŚEus–˙ű`Ŕ"� ? „­Á¬Ć#t�•ąa2ft*3άVwVl̸â•ęc@@Ç‚o2ÇĺQÇ;> ˘Ł€��?˙˙ős˙˙˙˙˙ü»µ®ĺ:=¨îďv:şş•VB/Îlęčö-ĘZ$Ű™5O˙˙˙ţÍ'*MDŁť'» Č.–zą›S«”ĚęW!„Ça溡Z!3#NǞ#!˘âcH5VA¸ň¨`†�`±óőůţż˙˙˙˙®•g;RWTťŮUŐ˝ÔÝQeŃŃTÄľÎB%O˙˙˙őůŃgęŐ9F F‘ťT‡j9"Î(cJb#!ÇxŐ3ł ŞE˙ű`Ŕ,� Ú= „­ÉŻČ#4‰¸vcśqL8áaŕĘPq˘Ń2$48˙˙˙˙ţÖŻu˙ş˙˙˙˙˙?Ł‘Ý”„5›Rl›ogGĐŚŞöi7şŐŽ·ěKw˙˙˙˙˙ÚŇ"1Ý_™Ü§8¤T„Dgťä;YśFä%PěĆ \ČäRŠF €‚äS3: ÁČQBŞŕ•Ü(˙˙˙˙ţÍ—˙/üˇßźő˙˙˙o‘śíbnYŘz(‰‘ëu×J:ä·IdŁďZĐÔŮ˙˙˙˙˙ţźJ'»J Ff®ď©Ne+ Č 8ä*&*†q2 0Ě AŠAD…0’ŤQ!2Ş”ĄA§aQE˙ű`Ŕ6€ ¶A „­Á¬EŁtŤą0�˙˙˙ý°/ó׿üż˙˙˙˙ň]sąśĐô… 仪Dţ9FzÓ¦[7‘tĄ+Mą2äł˙˙˙˙˙˙˙é˙9ćÓ*FE‘D‰»ë2ł×r™9“Ň·I­|ĽUčĘă±Áz°spÂ+† JŰŘ8˙˙˙˙ţŤ©üĽňgRfŇĎĺ˙˙ýţ•$íc1üý§ş!Yz:d÷#ę×*ş%Pú?{Ż˙˙˙˙˙čɦ·5\ÎRŁľě‚ŠTv{Ä$A™ RkŃi†!ٱĹĘÖ(|¨rpđé˘î<ÂÁŔ+)h��0Ŕ�5"üż˙˙˙˙˙dî˙űbŔB�VA „­ÁÄÇŁ4�•ąŹF±:ĽŰ>yę­)Ýě¬ĺşQň˛+7cfT˙˙˙ëŮ[óÉWŁ•hr<AU8‹=¬ąQćg"Ž*­Ž9EXEæRDqŚ(Ă„`˘śĚ T1Jç R‡\pđ¨ăT�˙˙˙˙˛˙Żň˙÷˙˙˙˙ˇ2;Ůß)mW3™‘«Ą“ś×–ĄUW¶¦pÇłłşZs]×˙˙˙˙NŞ–±z™ Ëłî†j˛ěů 6Ç$ÁHČÄ!ĚI2 ›„ę¦*Łłşž SĂ(áDÄp`�Ŕ´Śę˙ż˙˙˙˙ŐQ&^Ęeý‰WłśĘDYŃÝŇvSŞ˙ű`ŔH€ –= „MÉËČ"ô•¸ĘtR2™K®Ž÷Ń»˙˙˙ţę”T˘˛ÓJJfiÎÄW̨ńd%'"Ĺ2Ş8ä1Ů,$,4Î$DŤ.qA a˘¬„1Đ8pš E.Qp�˙˙˙ţ±ţ{Ď$ĚĄ—˙˙˙˙ë^ËVe)_fJęuyĄŁfzŻvUWB5gßgąU§˙˙˙˙ŮQ)*«dCÔ­yněÎmČvr%ëWśŠd(SąLr" 1UF»”VA†B ¦0ă2F ęq€�˙˙˙Ű˙ůçü˙_˙˙˙˙Ó˛µ‹ŮĘwvť™¤ek ôIچT4‹˛ľěé:˝Ń˙ű`ŔP€FA „­Á·Č#t•¸—!?˙˙˙N©źg2ä9Ň…5ÄÝ.»¬ŽkQÝŮGĘ,îYČFd1ČQ©……ĐĄvAE@˛,äQě&‹ť‚$˙˙˙˙˙Ö…źß˙˙ĺ˙˙˙ţý^ŢěéG!ÎŚŮ,UąŐUg)héu1WVĽ”s5•4*?˙˙ţľ›{+f"zYЇg/LδRÎŚ¨â¨÷aČânĄ´GR:Čç9Ŕsšbr(ŇD$A…BD˙˙˙˙ţŘ?_Żç˙˙˙˙˙ďýϬ†›ům<í}ËHDíű+ÉÔŃožĘÍ Ú¬SÉmż˙˙˙˙˙˙ůΚOň˙3gb…šě˙ű`ŔX€ š7 „­É˛H#tŤ¸ňH[˝‡ů™`ŘÉŇ6n\¶UCł…U.ÁFpřò7°*$Íť€€�˙˙˙˙Ö˙çë˙˙˙˙˙˙ŢíXŽuř’"ĂňfF„B VČ“%תG‡ĘcSż˙˙˙˙˙˙˙˙?)Ăé}ëŐý׋N퓝©”™Ą}­sś6SX‚ Ęş¤OE+ ´SX\lCľć '�˙˙˙ţŘ˙?˙^uú˙˙˙çe“t;˛šîg%|çgçR7cѬ®SrŮ „Ů ÎéGy˙˙˙˙¦Š×k6ĺ.EŞ!]R~†uĚ}h‡Qhey•ěĆB)]˙ű`Ŕc€ š= „mɩƣt‰ąÎcŁťr �Ś,ŐÓ��˙˙˙˙öú˙”ţ_>ż˙˙˙˙ŹiO‰Ť&׊aҶ§ö’ŃşkL“ÔW0÷?ÜpµpÝ˙˙˙˙˙˙˙1˙×ýúÚuz÷¤sň8ë›~9Ťç^;Ž’…ěar:¦hZ—Ro|†AwÚÖěXňo®Ü**>@Ö0˙˙˙˙˙ëËWuűJŤFŃYěîĺňYŮrć\îjí»*"±Hß˙˙˙˙~î‰m¨—g*:…+"ofi•ŢÂ#EŹ ¨˛)JçWACę,$aqĘÇ)„€p1Ś"Pč‘ĹH‡(ѡ`8˙űbŔo€5 … É˝H"Ě�•¸��3čň˙Ż˙˙˙Ö×ÝzčĽě×»Čr/Ȣ Ę®”dŁ3Ń—yN¤q˙˙˙÷ä{"Zĺu#ĚeR ¸ńŽÄsî®e-ESi‘pŐ( ›0“„bŽ3‡Ŕăs†yNEr”ÓŠ¨€ ŚŔ˙˙˙˙˙íyď˙éß˙˙˙ýz.Ą1,o5ä+9·Ežuvf±Îőąv}ČČ–N˙˙˙˙Dű}lłµ+3UTŠCoĽĄ>%čOyS!ťXڇ‘Ći;ť\ę8#)Ĺ:‰cĹ•Ęt(P‘Ŕ��?˙˙˙Ň˙ů˙ó˙˙˙˙ů´š÷f˙ű`Ŕx� 9 ­Ů¤GŁt‰ąb$„vşftgdű©kU-™śčƤĆVäfŰ˙˙˙˙ěźęR>Ó­ŐK5(i5; Îń“•ŃŠwiÇ*ą¤"âŠ*.*1â¦c´x´çRH@“#•bŔ�˙˙˙ö@ż˙-»<ŽÄjµ=]ÖÓTޤ®Ć96Kä<îäsnłĽó—˙˙˙ýv"Ńő{Ń]LëgRťg©ŞF5ŽFuőZÇQ3ŹQÓ ‡Ç°›”a‡¨Ô µĚ.tP‰GQCĺ<ĂŠ�˙˙˙˙Öţ]˙ţ«Ż˙˙˙ţŢy‘žr3ÚŰg§ź¤m4U++!d“Ö¶_J´Í{˙ű`Ŕ� nA „­ÁŞGŁ4ą˙˙˙˙˙˙˙˙öů7ČqyiHsC0¤Er_?* –gĐNP‰îF¸v#UCŞ9ŽB!* jčŤA‡1Y(0�˙˙˙˙ý`_óňţ_˙˙˙˙üßíŃžďzJ_ąK&´išôE3Šeu!&>ţF˙˙˙˙éĚý<s±Q#•ŢcÚď39’Ŕś“Ůś[%ÜŃÝÄÇ8s¸Îd†; á�Xä …C���0��­˙ůZőşťVýi ­bą*ŞOf3U§,ÍD=®E%^ÝË˙˙˙˙óHęíł=UÎ®Ż­]ť™žŘ⮪9ŐŚfd#9˙ű`Ŕ� ňA „mÁžČ#t‰¸ŃťÔ¤C»Tpř…Ĺ”HbČ s‘ť E,*Q˙˙˙˙˙ý`9oËęk˙ýý˙˙˙Îë"ĺE§KQîÖÝťJî·<†aG TGČŞW›F}]i˙˙˙˙ęŢŞú#f«˘Ě¨ĹUTH̵EçĚŞ·;f¤€ÜâŔŮŃŐN]DÂśr¬¨�qNČa�Ý(�˙˙˙ţ˛űçţ}~_]˙˙ëďôěĚÇűčw.šQŢrŘĚĚäb-lzčŇŇš˙˙˙˙ßé­ÝíSUĐćg«DL†îBÓśxÝť ¶QE§™c±îD €Ń3‡Pá˙űbŔś� 2A €­Á˛H#4‰¸$)ź1Qd‚��� Łý˙ç˙˙˙˙ýÖţ—tÍT!¦ČwS5îęëfşťĐĹ*”ťlýŘŠß˙˙˙˙ôşµYĘĘKžÓĘÄB˛˘ťĎvj±•QXĚB *”a”٤q3”Á7 áşˇb±Î!¬k **6H��˙˙˙˙¶üżżËĺ_˙ňůß%é,«żuÜöećBĘěöJąMwş®í­]Őr«7˙˙˙ű˘Ú®d+Tµ#!•Deś‡ć)Řě…F•®q`łťQËĘ·K ‡!:ÁÎÂĂur‹C®ŕ˙˙˙˙ú@ľ_âü˙ű`Ŕ©€ ž; „­ÉÁG#4•ąŻ˙˙˙˙çď˙%2óĄxzžőËa©—ÝßČͶMźšĂ™);ţe­Ű˙˙˙˙˙˙˙˙ů~~KrS¬[uvzźzYq˘ń ňlQwŃß#}¨\‰jZ |‹T ¸Ä!ĚéŔŔŕ�˙˙˙˙˙Ťźž Řç —Ne9ź˙˙ç eR!áçÖVę˘őś}!wÂOí3UUÚZWU>Ń˙ÜW˙˙˙˙˙˙˙˙?íńó÷+kLÖĂůŽ*Řëk‘s*—gJŹ4`žąEĆÇviF"‡$z©†9‚XőÂaË”9pčP·XT]Ç˙˙˙˙˙c`qîy˙ű`Ŕ˛€ .A „MÁ­GŁtŤą‰#±’ż%ľ§˙ţY¸tűîú㙬ꙉyĄ†ýwŠőÚ©¦÷ŢŇĄ©S»éşć˙˙˙˙˙˙˙ţ˙ż˙űżŽąškŻ«YO›­˛<6<’±…&TÉë˘�Ć!wB±E2 ¸öłJ‡4\V…K1`°’ކ6Ëq1Š@˙˙˙˙ý±Í#WĺżĚ«˙˙˙ý:*\ČŞuo™ŹťH(έb+JB{1¨¦î·±•jj˙˙˙ţť7Ą®·tT2+9#ÝÖ<–şśČ©*­ ayŚQ1§3•Ú&=ĄB:``ë‘‚˘§Š ‰Zpđ[JQ�˙ű`ŔŔ�ŠA … ÂČ"´ˇ¸ €�k?ýţ˝˙˙˙˙˙ţŐdR•¨ÉżWDJ2oR›±ÎÓęUd©Šf3¦Óo5ź˙˙˙˙ňKk­Ę„y™bęěęt3–S’Č5‹Ž «Ź,† 1 áňŽrťJâŠ0˘„#(@Xęč$,⊏8Š4xD*…„�˙˙˙˙˙’Ëäű×˙˙˙˙űνey5v}úş*±K÷U2‘ÖgR™–tK"µoE˙˙˙˙˙éîíLĘŚE:2+Ů’}Hî8†fc ‹ł)†ĹĚîěBaÇds¸“!…„XPrxńĘ,Š($0<*5� †��4lü˙ű`Ŕł€˛A „­ÁŰH"ô•¸Ö|˙˙˙˙˙˙ôM‘Ű[V۲NI”¬ÚÖçbk3›ß˘ŞŐ‘Z~ż˙˙˙őˇj‡»µ¶g*•Ř”ssB«Ąr”¬QGމ ”„)ĺ10‚ädĆw<)]Ě($aE8pj‡ĐhŔłŔ˘¦‰€˙˙˙˙ţŤßü˙F—=ĆÚţż˙ůĺ®›±÷őĚk*"§f5§˘*YŐC^bşę§»LÜŐ˛˙˙˙˙˙ëî‰wcަUÎ<Ę;0Ô=QÖ«žŽ¤Ź:<H±9 šaęH™aD‰Â˛,yR—(V('aÂÄFŠAM4óGÄAx¤âŔl�Ăa†Ń˙űbŔµ�= „­ÉÚH"ô•¸°~Sţźě˝üż˙ř’ť=ŘÓË˝űuÍł˘©43ˇšęÔi˛Fµż˙˙˙Ż{zGj”Ç»ŃJÖ!Hj$Ő\ŔW§(X÷aŞŃb„ÁÄFş !…UČ &)Î&ěqaÇ śpx"0HD =@˙˙˙˙ţ‘łĂčę^ék<’—/˙˙ĹůLM t;2ѬΨçEŞs܉\ú”‰Đčä=f{اu_˙˙˙˙éŁhßyr'G:Ëłť f‹ -ÝQĺt3 ‰ťśŁŚ,Ž`č|Xs™…™ÄĘÁŃ¡12\ä&đ˘(Ś`�Ă ˙ű`Ŕą€nA „íÁóČ"´•¸¶hŘőçůßËő˙˙˙ő<u(ë7q]M]ő×=CGŢüýňÔ“ĚsTŇ‹÷;JĹ×}_ß˙˙˙˙˙˙˙˙˙]OŐOvĐňÓ ÇňŚĐ´5¸}’ŻčmÚśs–4“{e‡=$u0ˇfLÜ, °T?i(MHÇ‡Źž(�˙ű˙ţ±üüÖ~µ‰÷?˙˙ö˝Ń¶©E[d[Ý‘QČîuC*ĄÝT»»F!ňĐäR¦BĽŮŐ ß˙˙˙űďÜ›Lއ2«ó•ÖŠ[¸€›łL”ˇÄŃXiH‚Ň !”Ś"Áŕ›ŕ@ř°±„Ăę4TT©QbbDÝÎ�˙˙ű`Ŕµ�Ň= „­ÉčǢ´ˇą˙˙ö˛˙–^ż7óę˙˙ţ˝¶dĘďEŁu+Í3’y˛«îęŠEwT¬ĆUťŠ¨W˙˙˙ý~ÄűHűŽőTg+T¦C”Ą"˛ŠRśŠu2N§ĄĘ9\aq) R Ž28Ä2‹qĹČs‰D8á$*��ü¤>łóż˙˙˙Ďéą&q…+T©µ'ŮTU•ŚgeR á‡B32)—ýž·D|ď˙˙˙˙Ó]«MÉş+YŚ[Ö¦0Ň)ĘbŽĄŽ5EQ„ÄEa!\hAÂ!ذ łBĂîg Ŕ(ńá‚ÂC &P 8|DV&Q˙˙˙˙˙ű`Ŕ´€ÂA „­Á×ÇŁ4•ą˙éä^¤–ú,‹˙ź˙˙žŇ÷f»‘×d[Ňş˝ VąLës ws)P§z^¬t252]ko˙˙˙ď§Ł÷]»ÍĘS2•gsƙٞDkaśÔ9Ç#Â"%F âDL*Ł˘ (B" ‡XÂŽAěbĽ>˙˙˙˙˙±…ĺţY//˙˙˙üć©J$î”Y?RşŃôVT}ŢŠrQKjßZmu}v˙˙˙ţ«ëzttz%¦TS•1ä#ĚĆ˝j=‘ŇŞ”ŞQ!0c ÓH,˘ĺ ŁÄÄĂŠ,ˇŃw‡Č 8ç1Đĺ†0¸Đ�Ű �ĂjĐö˙űbŔ¶€A ­ŃŮČ"ô•¸}s—˙ţż˙˙Ä@÷:XČ®M*¨Ccą§r”ü­#çDQz­Şý§µ)˙˙˙˙ýţ–ŃNbç›-LQl„0׳ )ťÜk ;Â,5ÜŁĹ‹;"Š 8ЦA¨$ÁŃqĆ\M…EAB@Áç( *�˙˙˙ţŤ˙-ă4Ń—pţR˙˙˙ł˘îVlěU·K&öu+<Ď™‡tLěěv3ˇŠÔłI{×˙˙˙˙úűű•YŃ Ą*«“ęçŞîR†Đ<Ž=ÇÔLU…aĹqáL (s”ń¨§D4P€Č‚Ä !€8˙˙˙˙˙ű`Ŕ·€’A „­ÁńČ"´•¸˙ěŚ w"šçĎ’–«˙˙ţM˙ÇýwóóŻ÷Üu]fFĚŐH÷SR‰őô±×KŻ×˙˙˙˙˙˙˙˙˙Ďw§qóßLň°»OĄO3r˛1GÔ­mÍ»±ĺ;=šSĘ8yŁ2lĎ#•0ŃÄĂHpň ˘ĄŹ% « €˙˙˙˙˙˛źĎ_ĺËz˙˙˙ţţŠŚ÷Z2>ô±• ŚĚ]ŞC\ťČ„VGGuyŐ(Ęr˘J´˙˙˙˙ű[BUÝźaj«öŁ[‹Ő™HŠčăLR«yڇś¬C%Üq]Qbě->A!Yâ†`Ł1\Xâ@��`Ň y?˙ű`Ŕ·�6A „­ÁâČ"ôˇ¸—#˙˙˙˙Ę­ä&ĘŞËťŐčęĘ}V¤1ÖłFnną:ű[Ż˙˙˙ëmjĚësˇŇŐF!ŠbŠę®Ć)†‹‚*‘LŚc°jˇ¬*‰˘ď0pĄ>$av!śXč4DHÁÁ5‡ĹĨU(p@1@�˙˙˙ţ­üŚ˙ĄÔËďë˙˙˙7ęěµR!ź#ٵŠč¨~ܦf5ćfb1ŢŽÝS"Ű˙˙˙ţßčÍišŁP®vJÖ®d1qčq”2ÚČ0:Ĺ+‘T6aÂbEl?2‡DD…‡‡\M"B Ł˘† ”Ác4\�6Ă �˙ű`Ŕµ€.A „­ÁçČ"´�•¸´ýą~ö˙˙˙ţT«Ű"Ł—ÜÇž—ŃsŮŃhę®DÎmYd˝îdSnŰ˙˙˙˙éíľ­:Ş3š§š•c%®5HQU<§!ĹEHcDH@8â(‡E`=‡(ąĂ§qR¸˛:$&<(ją‹ĆЏ  h���Ř�6­óćż˙˙˙˙#í%)sMŞŘí1ćrÚě·IUkwG‘rîGtTe÷Ń—˙˙˙˙ŢŐęŰ++Jζ%ƱŢČÄĚ<‹;‘XÇ8q‚:‹ Aa6qe- @ĘAA¦`‘Da"ÄĆÄ„@đ™”:?˙˙˙˙ý$˙ű`Ŕ·€A „­ÁďČ"´•¸/ţN_¬™ôë˙˙ä ÷Gő>ľJĄÓ:˛ť—G4¸±Q(„tC\î{+©•®gO˙˙˙˙쮕˝Ôł!O<®ë!¨Žd,ŠćR1>‚%f0°¬@ę>qâˇô (¨ĺś4ÖQ!0ř€şX‚eQC€˙˙˙˙˙Ňäţgě?˙˙˙˙˙-‘ś¦ŽI"*ĚDŁ™•̧Y§In–jH~­’Vkg»T®Š¬‹˙˙˙˙•úŃĚŽäRËŐ®¬–K¦í[ÝF"Ψpá®ĺs ”<îáC‡A r$ň 0×R‹ŽĚW€˙˙˙˙Ű˙˙űbŔµ�^A ­ÁéÇâô•¸˝˙˙ß›˙˙üż->ůŞó/ß&@şŇzšşË)˘Ô-^źđĽŽ˝Ďmű˙˙˙˙˙˙˙˙ĺ‘ßće ·îV›E‚6ŤťŢő$4Cą»Ľ­dPŔŽŠ$B¬ŽAĹŁE "Ł,†8SW©9 € €�`5‘ç˙Ëćkúë˙˙ň˙ZšçTCŁú^5Ýő‘ś¨bT©‘Üz-®–™=¤˝ź˙˙˙ýiCž”čËB)©au”‚o,ŃČ8‡c‡ ""@”AĐDĄ<IĆ €(ÁaŔ0XT¨W ÁË‹ 9ÄŔPvĆW B€˙˙˙˙Ł@Ý4˙ű`Ŕ¶€fA „­Á˝G#tŤąŘe:€ýź˙˙Ď}ęš”ę‡ôQŞërˇ¶:#›:9ćécŮ’g!Ö«×˙˙˙˙ý;ěĄK_˛ö(‰ÄČńŐś¤‰ )ÝŽ$ę†(ä1EŘç9ĹŔRDP\fŽ ,$",ax°u„„EET8px˙˙˙˙ţ±őż˙—ć]Ď˙˙ţ}-W晍f»Ő)wK%OJąčT#š1K{Ż?9™ě¬§˙˙˙˙ű“ŰMí4čŇUŚçU!N¬Lk‘Ô®îRŁę̌rŠŹ3 …;‡äA1Ą0¨Ńx |MÄ <H"a! #¸)J.?˙˙˙˙˙¤˙ű`Ŕ˝�Ę= „­ÉńČ"´•¸/ůl‹7ËůŻ˙˙˙<ť‹v}‡W­SN¨y‘Ë«ŞUť ÔźY™•ŽJ{5g˙˙˙˙öőˇVµUµ]Ő§"ËšrÔŤBŞ‘™Ąäh‰„tŁ S#ĚC2XĄB:Î&a(qĺÄN�˙˙˙˙ţ‘«ęYáŃHöô\ç?˙˙îoö»¦ćőŽb:K­f””ĺôžfi‘siáˇxîW©ž«ę?˙˙˙˙˙˙˙ű˙řůřý*ˇx«žišő˝‡#’´,] ŞĘ ĂD N*/L<Ć"áÄŔ¤f0Ë4PZ”łX••mm)„T¦X˙˙˙˙ô˙ű`Ŕ·€ŢA „­ÁČG#4•ąŚ§ŁbŇďĺ:Í˙˙˙ŃţFDĄďJh†T Š$ެWČŞ’t$Ë뤷U3äs?˙˙˙·ďÝQ4:—bÖ¨y'Fˇ&yÍ8”MFEŠíČc˘ŽĂX@yŽ."‡DŚ'aa[‰8:R˘eqG0˙˙˙˙ţ‘óžüü˛ľč˙˙˙˙˙­ĐŠÍGňÝUUMçyJďIŠcىJH䪓nŞ‹˙˙˙˙§ÍÓ«ÄŞ§rąĚbUĚfQqÚ›Ő v#ŤR ?Q"…]JÂ&)Ä@¦ ;•ĹPXP¤1F‚JAp@& *ńŔ`0˙űbŔ»�öA … ÁđH"ô•¸�lŇ0~~˙˙ůń˙˙ţý4iŃşú­UU\ŻeB$čÄ#č…5m¦M.•T5jż˙˙˙ŇĆŃ_ÔŽŽ§3ÎňˇUămTb©„Ęg1HÄĘ,Ç(±ÄŐEÚ¦ H&eAŁÇX@EB†ŠHEGŤ(÷+CÂÄ $`w�˙˙˙ý­ůů/˙řÉEů˙˙ó2˙Bmv6UsrLŚŹ«Ä{ü¤[÷uݬď[˙˙˙˙×Ů=Vd=ČZgŁČS1¬E]VK‡ť„ŐŤ*†31@ńGpčÁ�ŕ r qŚěSb«‡<`±˙˙˙˙˙´`ň˙ű`Ŕµ€îA „­ÁâČ"ô•¸˙úóą˙˙˙ŻôśĆ1î@FvYŤ±BşŁ?fě–1l¤DĄť—J¦ô‘S˙˙˙÷ôő;ě_Ů·1™+¦®qgTaśçLĄpçr¤D�Îdáŕč)B2ȇ3ęv tT8Ql&�˙˙˙˙ým©OzM'ęőšďëţSŤť/˙„é&˙_I¨×űőŇluBßǧµţ—\üwĎÝüĹ˙˙˙˙˙˙˙˙˙7˙ÚUÜ7;Ç­Ęßs#Zń×<`Ů č& l•yTb‰˛$ÚÄE`Čń:VE‡dž"ňpŁÂ1ä É& *y ŁĎ¶4J��m¶Ŕm[˙˙ű`Ŕµ€JA „­Á¶ÇŁ4‰ąĎÖ¦kťW˙˙˙ű+•ĘĄ*F›+ÜŐt1 ÜşSV#™Y\ś¶zTňó©•ż˙˙˙˙îjnĘŇ™“[;ÜŽÇAĹ;IĚD;!ĚÇ9(ěeŽ8MĆX^ Q!Ŕ hLH>*<:*‡ °x:@ůDEEH8:.څЇ†��˙˙˙˙¶üżůëď˙˙˙˙©˙˙–ŽÍ­îGH˘’°»Nޱ™ÄW%˛zŐóŘřDo“eŢ˙˙˙˙˙˙˙ţ_ü?)ąleyX†żç)ÎśzRĚź"]Ë•L´'dxČĐtS 3j±Ű é ŕ˙˙˙˙˙Ŕ~ds˙ű`Ŕ˝€>A … ÂH"´•¸ć˝ ˙#˙˙˙˲µĐŐc0‰&V‰ZâBĹrĚdpëFEyFm Ą*ł¬Îľë×˙˙˙˙˙żżmYĘŠVĘĆ}LęĆ+>j=‚¨,ĺ-*UcĹB˛=ť QSÎPčŃÎ"ĘYH  ˙˙˙˙˙˙˙˙˙˙˙˙Ż˙˙˙˙˙Ż˙ý~Ć(`JEŞ*!ŮŮĘbŞ”Vs ` #ł±Š©˙ł”ÁB(�����������������������������������������������������������������������������˙ű`Ŕł€ n? „mÁŃF˘ô•ą������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������˙űbŔ»€&@Ţ�MŔ�[Ŕ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������˙ű`Ŕ˙€���Ţ������Ŕ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������beetbox-beets-01f1faf/test/rsrc/convert_stub.py�����������������������������������������������������0000775�0000000�0000000�00000001305�14723254774�0021770�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#!/usr/bin/env python3 """A tiny tool used to test the `convert` plugin. It copies a file and appends a specified text tag. """ import locale import sys # From `beets.util`. def arg_encoding(): try: return locale.getdefaultlocale()[1] or "utf-8" except ValueError: return "utf-8" def convert(in_file, out_file, tag): """Copy `in_file` to `out_file` and append the string `tag`.""" if not isinstance(tag, bytes): tag = tag.encode("utf-8") with open(out_file, "wb") as out_f: with open(in_file, "rb") as in_f: out_f.write(in_f.read()) out_f.write(tag) if __name__ == "__main__": convert(sys.argv[1], sys.argv[2], sys.argv[3]) ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������beetbox-beets-01f1faf/test/rsrc/coverart.ogg��������������������������������������������������������0000664�0000000�0000000�00000021075�14723254774�0021227�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������OggS���������ţ–ç=����Ý[vorbis����D¬��€»��€»��€»��¸OggS����������ţ–ç=���Âjy&˙ ˙˙˙˙˙˙˙˙˙˙˙˙˙2vorbis���Xiph.Org libVorbis I 20050304���Ů���COVERART=iVBORw0KGgoAAAANSUhEUgAAAAIAAAADCAIAAAA2iEnWAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH3gIKDgwQSgEWmQAAABl0RVh0Q29tbWVudABDcmVhdGVkIHdpdGggR0lNUFeBDhcAAAAVSURBVAjXY/z//z8DAwMTAwMDggIAORsDA9GFfycAAAAASUVORK5CYII=vorbisBCV���cT)F™RŇJ‰s”1F™b’J‰Ą„BHťsS©9לk¬ąµ „SP)™RŽRic)™RKI%t:'ťc[IÁÖk‹A¶„ šRL)Ä”RŠBSŚ)Ĺ”RJB%t:ćSŽJ(A¸śs«µ––c‹©t’Jç$dLBH)…’JĄSNBH5–ÖR)sRRjAč „B¶ „ ‚ĐU���Ŕ@˛ �P��ЎŠ„†¬�2�� (Žâ(Ž#9’cI˛ �����ŔpI‘ɱ$KŇ,KÓDQU}Ő6UUöu]×u]×u 4d���@H§™Ą  d Y� ���F(ÂBCV����b(9&´ć|sŽf9h*Ĺćtp"ŐćIn*ććśsÎ9'›sĆ8çśsŠrf1h&´ćśsf)h&´ćśsžÄćAkŞ´ćśsĆ9§qFçśsš´ćAj6Öćśs´¦9j.Ĺćśs"ĺćIm.ŐćśsÎ9çśsÎ9çśsާspN8çśs˘öćZnBçśs>§{sB8çśsÎ9çśsÎ9çśs‚ĐU����A6†q§ HźŁEiȤÝŁĂ$h r ©GŁŁ‘Rę ”TĆI)ť 4d����!„RH!…RH!…R!†bČ)§ś‚ *©¤˘Š2Ę,łĚ2Ë,łĚ2ë°łÎ:ě0ÄC ­´KMµŐXc­ąçśkŇZi­µÖJ)Ą”RJ) Y�€��dAF!…R!¦śrĘ)¨ BCV�€�����<ÉsDGtDGtDGtDGtDÇs<G”DI”DI´LËÔLOUŐ•][ÖeÝömavÝ÷uß÷uă×…aY–eY–eY–eY–eY–eY‚ĐU����€B!…RH!ĄcĚ1ç “PB 4d��� ���ŔQĹq$Gr$É’,I“4Kł<ÍÓ<MôDQMÓTEWtEÝ´EŮ”M×tMŮtUYµ]Y¶mŮÖm_–mß÷}ß÷}ß÷}ß÷}ß÷u Y�H��čHޤHФHŽă8’$ˇ!«�����(ŠŁ8ŽăH’$I–¤IžĺY˘fj¦gzިˇ!«��@�������(šâ)¦â)˘â9˘#J˘eZ˘¦j®(›˛ëş®ëş®ëş®ëş®ëş®ëş®ëş®ëş®ëş®ëş®ëş®ëş.˛ ���БɑI‘I‘ÉBCV�2���p Çɱ,KÓ<ÍÓ<MôDOôLO]ŃBCV�€�������0$ĂR,Gs4I”TKµTMµTKUOUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU5MÓ4M 4d%���ŔbŤÁĺ !%%ĺŢ“ž1&!µ^!‘’Ţ1ž2˘ rŢBă Y�D��Ć ÇsČ9G©“9ç¨t”祎Rg)ĹbÍ(•ŘR¬ŤsŽRG­Ł”b,-v”RŤ©Ć����,„BCV�Q��„1H)¤bŚ9§śCŚ)çs†1ćsŽ9ç tR*çśtNJÄsŽ9§śsR:'•sNJ'ˇ��€��€� ˇĐ@ś�€A’<Oň4Q”4OESt]Q4]×ň<ŐôLSU=ŃTUSUmŮTUY–<Ď4=ÓTUĎ4UŐTUY6UU–EUŐmÓuuŰtUÝ–mŰ÷][vQUmÝT]Ű7U×ö]Ůö}YÖucň<UőLÓu=ÓteŐum[u]]÷LS–MוeÓumŰ•e]weŮ÷5Ót]ÓUeŮt]ŮveW·]Yö}Óu…ß•e_WeYv]÷…[וĺt]ÝWeW7VYö}[×…áÖua™<OU=Ót]Ď4]Wu]_W]×Ö5Ó”eÓumŮT]YveŮ÷]WÖuĎ4eŮt]Ű6]W–]Yö}W–uÝt]_WeYřUWöuYוáÖmá7]×÷UYö…W–uáÖuaąu]>Uő}Sv…áteß×…ßYn]8–Ńu}a•máXeY9~áX–Ý÷•et]_XmŮVY†_řťĺö}ăxu]nÝçĚşď Çď¤űĘÓŐmc™}ÝYf_wŽá:żđă©ŞŻ›®+ §, żíëĆłűľ˛Ś®ëűŞ, ż*۱ëľóüľ°,ŁěúÂj˰ڶ1Üľn,żpËkëĘ1ëľQ¶u|_x Ăótu]yf]ÇöutăG8~Ę��€�€�Ę@ˇ!+€8�Ź$‰˘dY˘(Y–(Š¦čş˘hş®¤i¦©ižiZšgš¦iŞ˛)š®,išiZžfšš§™¦hš®kš¦¬Š¦)˦jʲiš˛ěş˛m»®lۢiʲiš˛lš¦,»˛«Ű®ě꺤Y¦©yžijžgš¦jʲiš®«yžjzžhŞž(ŞŞjŞŞ­ŞŞ,[žgššč©¦'ŠŞjަ­šŞ*˦ŞÚ˛i޶lŞŞm»Şěú˛mëşiŞ˛mަ-›ŞjŰ®ěę˛,Űş/išijžgššç™¦iš˛lšŞ+[ž§šž(ŞŞć‰¦jŞŞ,›¦ŞĘ–癪'ŠŞę‰žkšŞ*˦jÚŞiš¶lŞŞ-›¦*Ë®műľëʲnŞŞl›Şjë¦jʲlËľďʪ)˦ŞÚ˛iŞ˛-۲ď˲¬ű˘iʲiŞ˛mŞŞ.˲młlűşhš˛mަ-›Ş*۲-űş,ŰşďĘ®o«Ş¬ë˛-űşîú®pëş0Ľ˛lűެúş+Űşoë2Űö}DÓ”eS5mŰTUYveŮöeŰö}Ń4m[UU[6MŐ¶eYö}Y¶ma4MŮ6UUÖMŐ´mY–ma¶eáveŮ·e[öuוu_×}ă×eÝ溲í˲­űŞ«ú¶îűÂpë®đ ��p��0ˇ ˛���ŚaŚ1ŤRÎ9ˇQĘ9ç dÎA!•Ě9!”’9ˇ””2ç ”’Rˇ””Z !””Rk��8��Ř )±8@ˇ!+€T��ăX–癢jÚ˛cIž'ŠŞ©Ş¶íH–牢iŞŞm[ž'Ц©Ş®ëëšç‰˘iŞŞëęşhš¦©Ş®ëşş.š˘©ŞŞëş˛®›¦ŞŞ®+»˛ěë¦ŞŞŞëĘ®,űÂŞş®+˲më°ޮëʲl۶oÜş®ëľďű‘­ëş.üÂ1 G�ŕ �@6¬ŽpR4XhČJ� �€0!B!„RJ!Ą”��0ŕ��`B(4dE�'��C)¤”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RH)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ©¤”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)•RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”R �Šp�z0ˇ ˛�H��ŚQJ)Ćś1ćcĐI()bĚ9Ć”’Rĺ„Ri-·Ę9!¤ÔRm™sRZ‹1ć3礤[Í9‡RR‹±ćškVk®5çZZ«5לsÍą´k®9לsË1לsÎ9çsÎ9çśsÎ�ŕ48�€ذ:ÂIŃX`ˇ!+€T��ĄsÎ9čRŚ9ç„"…sÎ9!TŚ9çtB¨sĚ9!„9ç„B!s:č „B„Bˇ”ÎA!„J(!„B!„:!„B!„B!„RJ!„B ˇ”P��`�@€ «#śŤ˛���€– R΄AŽAŹ AĘQ3 BL9Ń™bNj3S9ťtjAŮ^2 ��€ � Ŕ (řB1��AĚ …U°Ŕ  ćŔD„D� H»¸€.\ĐĹ]BB‚X@ 88á†'Ţđ„ś STę ����� �ŕ�ŕ �""š«°¸ŔČĐŘŕčđ������ř��8>€ć*,.02468:<��������€€€�����@���€€OggS��@–������ţ–ç=���ý‚ĺ'02310276;:?CCBl…‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹¬Ř%ô� B8FkF#ëśzĘýýžĺJI`ź76�BU5Ŕ: š†îK.Ţ[/IŔ´�€Ž€ ~ĘýýžĺJĽçŰJŮ�@!TŐ€ €z wP{@Ű‚š_,ß5đ�č@ �4�>Şý˙üĺ žíkĄ°�@!TUč�40ŃMtô™ŕÉĆ+ÚŮÁ…WÇŃ�ŕĐ>Şý˙üšäâvy¨��…PUˇô…|G×OӱЂ††ŤĘpB�Ŕ&€>Şý˙üšäîô4Ş��…PU'�Đ@$¸O}Ç%ĺMŇ;†:€ �žä>Şý˙üšäŇ´đ&”��ŞĘ* @tđ¨�´ŻůçaďĄ)t (�4 �šý˙Ore1éM0�BU � 1>ŢY(ĄÚsYŔ&-RJgčˇŐMkňŘ�Ŕ‚Všý˙OrĄ2ŘĺIŘ��UUĹ@EO5š‚nE~IôĆÓ´JĘqŘ)d˘U)°�$ @šý˙OrĄ28ĺMč��ŞĘj� ĚY¬"ćeç…d×Ôdţ‘ňcw˘ ±“¦‘�€d �:šý˙OrĄ`°Ű›¸�TU:�Ě]ą¤ż\Ďôł\ĎŇ{QF<Q©š5¬ĐŔ`MX�Ţyýök”‹2íMQ#�€B¨¨˘Đ@§ŰŢçµNUďáM őNůf 3 óéęĐÝlC–ž >H4¬T05�Ţyýök”‹2¸ŰŰXaE��…P + &EŇć^ ô#©”¶&Ú!ir}Ď””Ωaku¨“)lY ‚& H�Ţyýök”‹2íMP�*jT�¦Ţ1{Ă1^_Y•˘~vçt×Zź}ą.󭡣Ş9“5\1á)č�ŕÁ_#�Ţyý˙ţĺR Ny“�B1ę,�r(¶Řňł4ptmp sR›¦ŁL/g9‚čŢ?®Í0—M)�°”�,Z�ýÎuą‘Ţ8� 4´X‰ h©u Ŕ÷ßŇ‹ö~>SŁČü†ý$=cr¦Čß7˛üĽöŽĄĎĂŚóů0Ę«®bĄ*Żß·ţQť˙Šá¨Y4®#öO¤Ĺ± Š«=ˇ P{údX]ÎÔýÎ59 ŁĽ �„f)6đÖ”łA`fś“€˝^ťÄ!<©¬i0/dĄ·?/Ôhł=é«m¤ň¶Ţ­™žŹŤwŰŐZ:Ý–˙‰^®¦×Ö‘&uVwű’CĹ_Ĺjđ-mŚ����������������������������ýÎu9 %˝ ��Đ"++IŔ@âĘAGŐŤrý5]ó�o”X€Ż/săÎŹ\ú çHÇ‘ŮqoĹóČĐ3ú.+Ľđ¬qÖ\Eť¬SF’ż1R-ŕ"ç ÜI¦ĎlÂjÂÉĚěć���������������������������������ýÎwŮ4-=� ĤĽĚdŔ˘ĐI�ŕ,1ć8‘qü]âj­_č÷⥧Թéô''ďsçőu|‰ěwűćąĹÇ덳Î勝/óˇ<:Ť0ŃŰĺ$ˇÍ˝'—3Śí®Źý ��������������������������������ýÎu9q­< B�ˇˇËÂÉgPŕ€~57kg§Pżd´]ťŮŰüąśÎoż’_ÚąJs)ęă5÷Ňä¤Ĺp–ô•őÚc\^;ÇĄQĺtFôb[D®áě §­“������������������������������������ýÎ5ąVŢ�h’ʦˇ�°@vvż´ÄNľd ¦˛˛Ki*Żu67[`&· 'nV‡§Ż‹“ËČcşt“źq’C>-VţFźÄĽVŮ ]ęďtGNĘy49ŞéaśŮMă����������������������������������������ýÎ5ąäŤ�ˇXd‰‘��\%`8Ź'\ŕÁÁű5ôŹ7‰M?/'x‘,ׯ×}YżŽ˝<S«oÍYI¤Ű0î­< V_R†]ŠíÍžJ];揅śŢí·BłHűń·•8NŔŮ˝Dä�����������������������������������ýÎ5ą8‘Ţ�B“„eŔ_™h$�áž''Đ„Y8˘ŘEŃ´Ç˝^‹ďç«Ü.pNë°Ä-’CaĂ+83|©űŐźŤ-Éśť¶ĎUxY¶]FVS”ijúP[éFÂR/P:Ű»r��������������������������������������ýÎ7ąVľ�BłE R€�Ŕz+ó´¶źëž•ĘĘőŠa߉xR=¬±7ćL‰Ű &‡Ü‡nʬőúÖVŢk\ńÖďá šŰŻŕ´şBĄ:ť1Í“‘±‚¦bDţö1Í ‘Ş®���������������������������������ýÎuąž8� ”Ôe…1ŔŇ,�0™ŽŽG­;H`5ŰrŹbR;VsĎÜlËŚjąPĎFlôn3¦°íúuPéˇ)`,V*ýxuC=ľ™rN:Ś…ý¬=Oä}w�ôöúEad•”cb�������������������������������ýÎ~7Ů8Yľ„�ÄËJ84ÜcPČąëlbĐźĄBâŔ(ŰŘ,·âŠĺ$ł¦ÂĐ›. üdĂĆy/”cův€~ý—:lϬ™Ńk8Ĺuoć”HȆ¨$Ĺ ö˙óŞÁžą˝Ď�����������������������������ýÎuąáiH��Z„®0rea�X¸¶(cp˛KÓëćtŹ#)s:.ĆhëřL¬^ł)Z Km“IJyś’c#Qďln2t6]ň~’]¦NĘ „*đ<´o1X¨ŔźÇŤGSz��������������������������������������ýÎuąŢ��B“¬<•@ ľÁq] ŘÔÜ‹ć$"93Ë’zlć3Ĺ—ŕ”S©Űď“á˙ąz)ĺLżĄě. ›&ukbgÄ÷Ę_:`…HňcĽď0D64úb‘˙ďŕ 9ű!r�������������������������������ýÎuą(‘ž„*�B“˘Ś«Š:�>nmcfFU)¸¸đ“‡0ŮëŢľŐŃřÚťť·•ęP[©+T˛űëĽý2š\ÍüÓSś ÂG.WąčX©;·ĺUFŔhkôo†FŘ/÷ŕÂu cĘ:����������������������������ýÎ7Ů–Ţ8�€š• ›0Řd7•\´×vÎ}ií˝E«MÝ M3‹Š"äđşĎ~�µ˝bł¶#Ah©'ňR‚ŃÖ&y,]~ŹMG“-9¶#8…ţůäëĽ#s/y����������������������������������ýÎ7Ů8Qľ1�•Ö%8�€iup;š¤_I5…ŔÖ˙?»ŘÄ&ňҵäduˇn±{˝ď…é!‰ź»:•¬Ď…úzĹU.25žBÉ–+Ęčq0ďîQ®*/CUë&^ܱé-��������������������������������ýÎ59q#} �„&ĺ68ZŔ€ôĐ»t3ü´WÜ™ohm™äP»-¨Y?Ç{ž°ęŽLXř8&MöŘ f%ŞO©›÷NÉ”‰ÍÍŻ®b˘ě˝›ěÎČPwˇëWI;–���������������������������������ýÎwŮ(–ž„*�hRdÁPL�ŹKşá?7śŹI—»BťŹÁ¦Ń|_O&±arúäşvQŕ$ąk×°d­Áĺ˛l­Lc/["°R#lcú,čVŔÝťŠ…«äŕHďbz¬Ű{8T�������������������������������������ýÎ7Ů8QŢ1�ÉJQ3=H�Đi—Ť ‚©ćĘě•ä¸óM6żÝRmöµ|fę «ö’ô<xQ[‚ Ł§Ý˝űjTż03Ѹů-ťao8*Ę.B¶ŘrI…Z™Řyůۙ�����������������������������ýÎ~5ąQľ1�ˇu‰8{,�U¦»k,ż,¦żW}IŤŠZGv›Ü ý±gěÉVöCA’ô—”4#ŤâwLqu;Ž‚óÄ~BH Iţ҉fDaďŚne›������������������������������������ýÎwŮ8–žHb -ŞˇŔě�@cŰweł¬fô“'ŹI»#ł:«/Ősâ§ LNao—Ĺ’üłęÎhrŻ‹K5«ŚámŃ {Ř–ÔnČ[oČcőQ˝‚€ď šî*§ťÚč˘ŢŢ���������������������������������ýÎ7ŮâŤ�ˇ˘`đ&@ ď~Ë˝r8zôľóG­řHä·5ż^yÚxńé\™JfŇŹ}ŔmťŁvŐľËôLa!9'ěbŰ>ŃZ*Ěü(NbÎŃŮ&ç˛����������������������������������������ýÎuą–ž�@-jIÁ�…&&č�h‹o\ś‰[˘�ĆüJx2ÝĎÚ ^Cí7^:«YGcF¦ňO}žÝşž‡žŹ™‰°±<É~ľ¬2Pâü"SÔމíőŞŔö™!Gć&ŰóS’‰ťÎI…¨��������������������������ýÎuą8QŢ(�BĚR¬H¦Đ�‡YB,’˛-oĹx3CT§XČčňßŃ| éČÓůĆű<Z>Ńł^^Ş„^‰)5=‚%G˝pďb€{fy](€>(Ö÷:;ubjŮ:kGG:�����������������������������OggS�D¬������ţ–ç=���‡ż˛¬‹‹‹‹‹‹ýÎ59 %˝ ‚��”. :�>Źţč]µ¤?÷TăĚxL€çsÔžM›PĎXšÁ„—+q!^#§ĂaâčŃ|S«ă.Ź„™ăµ•«Q®%(Îsnĺ¶©lő]”N-{~����������������������������������������ýÎwŮ8™ž0�B“b…qÄń–�X9vń)l}¬ĹĽŹb‘Id=!ű…t,W˘7Ĺć˙9±¸őń­Ű˙â® Rńçf©ÄŔoé<ďËÓ—·§ľUÚe±”׫üpő0Úé[(wÉÎ"…��������������������������ýÎuą8–Ţ„2�h–˘"hrđČ)=|éP›Ű"ýr:Iň"qŤö°Ě蹨šŻŕŽľRß"ŕ€"«•uŢ˝‰ ţąÚ‡żš.řAsń©Ďnµďn7…Ł'ňčeŽ~ ���������������������������������ýÎuąŢ0�B Í c@2 �°8čä>h»ď_6zyu—YĆü|$Ô•»ąŽëö¬ĘŔĹ…÷ńSěŮ n'E˛0!¸¶˛ö>~v3üayč™zź›9óMÔk ¦1×5„éW7ń#ŢežČi����������������������������ýÎwŮ8‘ž1�h–bŢFh‚�¦zmśňp4<Żĺ˛é*é´ ´ÚäÂśÉ"N¨„Şń|oâSđjl’ĚôřśxŽMqIß–Š'S´Î—WřKw®˛:&U0TĚn;Ť‘bbž ĆPČe�������������������������������������ýÎݞ 8��P‹<Üŕ0P8ťÓą­j� �x�z}]Ü��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������beetbox-beets-01f1faf/test/rsrc/date.mp3������������������������������������������������������������0000664�0000000�0000000�00000031024�14723254774�0020235�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������ID3����4TIT2������full�TPE1��� ���the artist�TRCK������2/3�TALB��� ���the album�TPOS������4/5�TDRC��� ��1987-03-31�TCON��� ���the genre�USLT������eng�the lyrics�COMM���h���engiTunNORM� 80000000 00000000 00000000 00000000 00000224 00000000 00000008 00000000 000003AC 00000000�TCMP������1�TENC������iTunes v7.6.2�COMM������eng�the comments�TBPM������6�COMM������engiTunPGAP�0��TIT1������the grouping�COMM�����engiTunSMPB� 00000000 00000210 00000A2C 000000000000AC44 00000000 000021AC 00000000 00000000 00000000 00000000 00000000 00000000�TPE2������the album artist�TCOM������the composer��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������˙ű`Ŕ�� F=��íÉ‘A#ô‰ą˙˙˙˙F0Ăß˙C ˙_ůď˙˙˙˙óĎŁ)çÚa”2ůŠz<‚žyő=xů=O `ĐĘÇŤ  ĄŕĆyé>`řÜá ĐŔXč" z)ăBś"Ŕ$)‚ A„ ��˙˙˙˙˙ř˙×?˙ű˙˙˙˙ţľÝvbŁ×Bť¬¦ ł4Ŕă°˛+¸µÂ¤ÍrąÁÔR°Ä%;ťîRŁQ[˙˙˙ďÖÜî˙łľÚ7UW6dTUT!ub8´Çř çčx?ń.Ě€Ź˙ÚÚ˘ł<ŚaeźDY/˙˙˙˝˙PśĽ·$D¬(躡ďPŚi§˙ű`Ŕ€Š9ŕ… éâH#4�•¸‹RŇAbÉuĎ&Ž ü:<cŠĚIÁij#Ř„`4㔑H‘qeu§šj2^ĺmłÚ•żúiżH­äşĚDĎC’âjÚĺn_«ąšşůEkwwŮÝ~˘îÚZ†6ë GMˇ$1ÉDČ㦩CňŢĆCB‰ÂŔ�€ ¶€ç˙˙ď˙˙ţú÷çwبE:ŽabŘę‚c˘ő>=ÄÝL´2!Ô¶#9îŽÔ#"5ż˙˙˙ú­˙fE$ěȉ,Ş1HËez˛1’Ş!ĨR ‰Ě§)˘((â˘,Ęc°š)ÄÜX˘¬,qE„T9N@řt���˙˙Ňţ÷˙?˙ű`Ŕ� ň= mÁ¸Č#tŤ¸ż˙˙óËäÜŻđÉ÷INˇćiŢ˙{ŢžYg§S—ÜëdE/'˙˙˙˙˙˙˙çţ§Ě¸g Wëď&%ÍirÄDuń6.ÝítÜ‘mtP&ę,Ž«˘’Duhâb¨D›ŔN±˙˙˙˙˙ý`Z˙ý{˙˙˙˙˙çĚímŢóĄ/ĂžjľGH„<&ńÎCĄ˙ü„žl÷˙˙˙˙˙˙˙ýţ˙çJÎÉPŤ1•ĄI’ňÔä»ň9Ł3łZHFĘ;ŽÔr/viĂ1  Ü‰…Ł2‡ �ěÁ˙˙˙˙˙Öő˙ţy˙?˙˙ůz\¨Ű+dDÎŞŹ1ĆckTIw˙űbŔ� ć% „MɬH#t‰¸™¨…¶R˛¶Í´ö˙˙˙˙dÝꓳĐ÷1ęr© ĘĎłîŇ»łlÖ• lΆmC‘Uę÷c,ě$(tČ4BK˙˙˙˙˙ëĺý˛ëZS˙˙˙˙üąQRĹ­ÝŚ‡>łĚl®®č§1ڍËf:V¨m&ľĺDGB;?˙˙˙˙˙ů­RĚé+9‡}•+Ďł+±ÍUĐčĘbPSú;1Ś%Ü; Š…R•Ěwt”€Î9B�˙˙˙˙ţ¶˙ë˙•˙˙˙˙äěúRwó5ťlŞ„s>ʸő#ĚäşuFlëĐęɕћ˙˙˙Ú˙ł«ÔŞFJČŞyŚEus–˙ű`Ŕ"� ? „­Á¬Ć#t�•ąa2ft*3άVwVl̸â•ęc@@Ç‚o2ÇĺQÇ;> ˘Ł€��?˙˙ős˙˙˙˙˙ü»µ®ĺ:=¨îďv:şş•VB/Îlęčö-ĘZ$Ű™5O˙˙˙ţÍ'*MDŁť'» Č.–zą›S«”ĚęW!„Ça溡Z!3#NǞ#!˘âcH5VA¸ň¨`†�`±óőůţż˙˙˙˙®•g;RWTťŮUŐ˝ÔÝQeŃŃTÄľÎB%O˙˙˙őůŃgęŐ9F F‘ťT‡j9"Î(cJb#!ÇxŐ3ł ŞE˙ű`Ŕ,� Ú= „­ÉŻČ#4‰¸vcśqL8áaŕĘPq˘Ń2$48˙˙˙˙ţÖŻu˙ş˙˙˙˙˙?Ł‘Ý”„5›Rl›ogGĐŚŞöi7şŐŽ·ěKw˙˙˙˙˙ÚŇ"1Ý_™Ü§8¤T„Dgťä;YśFä%PěĆ \ČäRŠF €‚äS3: ÁČQBŞŕ•Ü(˙˙˙˙ţÍ—˙/üˇßźő˙˙˙o‘śíbnYŘz(‰‘ëu×J:ä·IdŁďZĐÔŮ˙˙˙˙˙ţźJ'»J Ff®ď©Ne+ Č 8ä*&*†q2 0Ě AŠAD…0’ŤQ!2Ş”ĄA§aQE˙ű`Ŕ6€ ¶A „­Á¬EŁtŤą0�˙˙˙ý°/ó׿üż˙˙˙˙ň]sąśĐô… 仪Dţ9FzÓ¦[7‘tĄ+Mą2äł˙˙˙˙˙˙˙é˙9ćÓ*FE‘D‰»ë2ł×r™9“Ň·I­|ĽUčĘă±Áz°spÂ+† JŰŘ8˙˙˙˙ţŤ©üĽňgRfŇĎĺ˙˙ýţ•$íc1üý§ş!Yz:d÷#ę×*ş%Pú?{Ż˙˙˙˙˙čɦ·5\ÎRŁľě‚ŠTv{Ä$A™ RkŃi†!ٱĹĘÖ(|¨rpđé˘î<ÂÁŔ+)h��0Ŕ�5"üż˙˙˙˙˙dî˙űbŔB�VA „­ÁÄÇŁ4�•ąŹF±:ĽŰ>yę­)Ýě¬ĺşQň˛+7cfT˙˙˙ëŮ[óÉWŁ•hr<AU8‹=¬ąQćg"Ž*­Ž9EXEæRDqŚ(Ă„`˘śĚ T1Jç R‡\pđ¨ăT�˙˙˙˙˛˙Żň˙÷˙˙˙˙ˇ2;Ůß)mW3™‘«Ą“ś×–ĄUW¶¦pÇłłşZs]×˙˙˙˙NŞ–±z™ Ëłî†j˛ěů 6Ç$ÁHČÄ!ĚI2 ›„ę¦*Łłşž SĂ(áDÄp`�Ŕ´Śę˙ż˙˙˙˙ŐQ&^Ęeý‰WłśĘDYŃÝŇvSŞ˙ű`ŔH€ –= „MÉËČ"ô•¸ĘtR2™K®Ž÷Ń»˙˙˙ţę”T˘˛ÓJJfiÎÄW̨ńd%'"Ĺ2Ş8ä1Ů,$,4Î$DŤ.qA a˘¬„1Đ8pš E.Qp�˙˙˙ţ±ţ{Ď$ĚĄ—˙˙˙˙ë^ËVe)_fJęuyĄŁfzŻvUWB5gßgąU§˙˙˙˙ŮQ)*«dCÔ­yněÎmČvr%ëWśŠd(SąLr" 1UF»”VA†B ¦0ă2F ęq€�˙˙˙Ű˙ůçü˙_˙˙˙˙Ó˛µ‹ŮĘwvť™¤ek ôIچT4‹˛ľěé:˝Ń˙ű`ŔP€FA „­Á·Č#t•¸—!?˙˙˙N©źg2ä9Ň…5ÄÝ.»¬ŽkQÝŮGĘ,îYČFd1ČQ©……ĐĄvAE@˛,äQě&‹ť‚$˙˙˙˙˙Ö…źß˙˙ĺ˙˙˙ţý^ŢěéG!ÎŚŮ,UąŐUg)héu1WVĽ”s5•4*?˙˙ţľ›{+f"zYЇg/LδRÎŚ¨â¨÷aČânĄ´GR:Čç9Ŕsšbr(ŇD$A…BD˙˙˙˙ţŘ?_Żç˙˙˙˙˙ďýϬ†›ům<í}ËHDíű+ÉÔŃožĘÍ Ú¬SÉmż˙˙˙˙˙˙ůΚOň˙3gb…šě˙ű`ŔX€ š7 „­É˛H#tŤ¸ňH[˝‡ů™`ŘÉŇ6n\¶UCł…U.ÁFpřò7°*$Íť€€�˙˙˙˙Ö˙çë˙˙˙˙˙˙ŢíXŽuř’"ĂňfF„B VČ“%תG‡ĘcSż˙˙˙˙˙˙˙˙?)Ăé}ëŐý׋N퓝©”™Ą}­sś6SX‚ Ęş¤OE+ ´SX\lCľć '�˙˙˙ţŘ˙?˙^uú˙˙˙çe“t;˛šîg%|çgçR7cѬ®SrŮ „Ů ÎéGy˙˙˙˙¦Š×k6ĺ.EŞ!]R~†uĚ}h‡Qhey•ěĆB)]˙ű`Ŕc€ š= „mɩƣt‰ąÎcŁťr �Ś,ŐÓ��˙˙˙˙öú˙”ţ_>ż˙˙˙˙ŹiO‰Ť&׊aҶ§ö’ŃşkL“ÔW0÷?ÜpµpÝ˙˙˙˙˙˙˙1˙×ýúÚuz÷¤sň8ë›~9Ťç^;Ž’…ěar:¦hZ—Ro|†AwÚÖěXňo®Ü**>@Ö0˙˙˙˙˙ëËWuűJŤFŃYěîĺňYŮrć\îjí»*"±Hß˙˙˙˙~î‰m¨—g*:…+"ofi•ŢÂ#EŹ ¨˛)JçWACę,$aqĘÇ)„€p1Ś"Pč‘ĹH‡(ѡ`8˙űbŔo€5 … É˝H"Ě�•¸��3čň˙Ż˙˙˙Ö×ÝzčĽě×»Čr/Ȣ Ę®”dŁ3Ń—yN¤q˙˙˙÷ä{"Zĺu#ĚeR ¸ńŽÄsî®e-ESi‘pŐ( ›0“„bŽ3‡Ŕăs†yNEr”ÓŠ¨€ ŚŔ˙˙˙˙˙íyď˙éß˙˙˙ýz.Ą1,o5ä+9·Ežuvf±Îőąv}ČČ–N˙˙˙˙Dű}lłµ+3UTŠCoĽĄ>%čOyS!ťXڇ‘Ći;ť\ę8#)Ĺ:‰cĹ•Ęt(P‘Ŕ��?˙˙˙Ň˙ů˙ó˙˙˙˙ů´š÷f˙ű`Ŕx� 9 ­Ů¤GŁt‰ąb$„vşftgdű©kU-™śčƤĆVäfŰ˙˙˙˙ěźęR>Ó­ŐK5(i5; Îń“•ŃŠwiÇ*ą¤"âŠ*.*1â¦c´x´çRH@“#•bŔ�˙˙˙ö@ż˙-»<ŽÄjµ=]ÖÓTޤ®Ć96Kä<îäsnłĽó—˙˙˙ýv"Ńő{Ń]LëgRťg©ŞF5ŽFuőZÇQ3ŹQÓ ‡Ç°›”a‡¨Ô µĚ.tP‰GQCĺ<ĂŠ�˙˙˙˙Öţ]˙ţ«Ż˙˙˙ţŢy‘žr3ÚŰg§ź¤m4U++!d“Ö¶_J´Í{˙ű`Ŕ� nA „­ÁŞGŁ4ą˙˙˙˙˙˙˙˙öů7ČqyiHsC0¤Er_?* –gĐNP‰îF¸v#UCŞ9ŽB!* jčŤA‡1Y(0�˙˙˙˙ý`_óňţ_˙˙˙˙üßíŃžďzJ_ąK&´išôE3Šeu!&>ţF˙˙˙˙éĚý<s±Q#•ŢcÚď39’Ŕś“Ůś[%ÜŃÝÄÇ8s¸Îd†; á�Xä …C���0��­˙ůZőşťVýi ­bą*ŞOf3U§,ÍD=®E%^ÝË˙˙˙˙óHęíł=UÎ®Ż­]ť™žŘ⮪9ŐŚfd#9˙ű`Ŕ� ňA „mÁžČ#t‰¸ŃťÔ¤C»Tpř…Ĺ”HbČ s‘ť E,*Q˙˙˙˙˙ý`9oËęk˙ýý˙˙˙Îë"ĺE§KQîÖÝťJî·<†aG TGČŞW›F}]i˙˙˙˙ęŢŞú#f«˘Ě¨ĹUTH̵EçĚŞ·;f¤€ÜâŔŮŃŐN]DÂśr¬¨�qNČa�Ý(�˙˙˙ţ˛űçţ}~_]˙˙ëďôěĚÇűčw.šQŢrŘĚĚäb-lzčŇŇš˙˙˙˙ßé­ÝíSUĐćg«DL†îBÓśxÝť ¶QE§™c±îD €Ń3‡Pá˙űbŔś� 2A €­Á˛H#4‰¸$)ź1Qd‚��� Łý˙ç˙˙˙˙ýÖţ—tÍT!¦ČwS5îęëfşťĐĹ*”ťlýŘŠß˙˙˙˙ôşµYĘĘKžÓĘÄB˛˘ťĎvj±•QXĚB *”a”٤q3”Á7 áşˇb±Î!¬k **6H��˙˙˙˙¶üżżËĺ_˙ňůß%é,«żuÜöećBĘěöJąMwş®í­]Őr«7˙˙˙ű˘Ú®d+Tµ#!•Deś‡ć)Řě…F•®q`łťQËĘ·K ‡!:ÁÎÂĂur‹C®ŕ˙˙˙˙ú@ľ_âü˙ű`Ŕ©€ ž; „­ÉÁG#4•ąŻ˙˙˙˙çď˙%2óĄxzžőËa©—ÝßČͶMźšĂ™);ţe­Ű˙˙˙˙˙˙˙˙ů~~KrS¬[uvzźzYq˘ń ňlQwŃß#}¨\‰jZ |‹T ¸Ä!ĚéŔŔŕ�˙˙˙˙˙Ťźž Řç —Ne9ź˙˙ç eR!áçÖVę˘őś}!wÂOí3UUÚZWU>Ń˙ÜW˙˙˙˙˙˙˙˙?íńó÷+kLÖĂůŽ*Řëk‘s*—gJŹ4`žąEĆÇviF"‡$z©†9‚XőÂaË”9pčP·XT]Ç˙˙˙˙˙c`qîy˙ű`Ŕ˛€ .A „MÁ­GŁtŤą‰#±’ż%ľ§˙ţY¸tűîú㙬ꙉyĄ†ýwŠőÚ©¦÷ŢŇĄ©S»éşć˙˙˙˙˙˙˙ţ˙ż˙űżŽąškŻ«YO›­˛<6<’±…&TÉë˘�Ć!wB±E2 ¸öłJ‡4\V…K1`°’ކ6Ëq1Š@˙˙˙˙ý±Í#WĺżĚ«˙˙˙ý:*\ČŞuo™ŹťH(έb+JB{1¨¦î·±•jj˙˙˙ţť7Ą®·tT2+9#ÝÖ<–şśČ©*­ ayŚQ1§3•Ú&=ĄB:``ë‘‚˘§Š ‰Zpđ[JQ�˙ű`ŔŔ�ŠA … ÂČ"´ˇ¸ €�k?ýţ˝˙˙˙˙˙ţŐdR•¨ÉżWDJ2oR›±ÎÓęUd©Šf3¦Óo5ź˙˙˙˙ňKk­Ę„y™bęěęt3–S’Č5‹Ž «Ź,† 1 áňŽrťJâŠ0˘„#(@Xęč$,⊏8Š4xD*…„�˙˙˙˙˙’Ëäű×˙˙˙˙űνey5v}úş*±K÷U2‘ÖgR™–tK"µoE˙˙˙˙˙éîíLĘŚE:2+Ů’}Hî8†fc ‹ł)†ĹĚîěBaÇds¸“!…„XPrxńĘ,Š($0<*5� †��4lü˙ű`Ŕł€˛A „­ÁŰH"ô•¸Ö|˙˙˙˙˙˙ôM‘Ű[V۲NI”¬ÚÖçbk3›ß˘ŞŐ‘Z~ż˙˙˙őˇj‡»µ¶g*•Ř”ssB«Ąr”¬QGމ ”„)ĺ10‚ädĆw<)]Ě($aE8pj‡ĐhŔłŔ˘¦‰€˙˙˙˙ţŤßü˙F—=ĆÚţż˙ůĺ®›±÷őĚk*"§f5§˘*YŐC^bşę§»LÜŐ˛˙˙˙˙˙ëî‰wcަUÎ<Ę;0Ô=QÖ«žŽ¤Ź:<H±9 šaęH™aD‰Â˛,yR—(V('aÂÄFŠAM4óGÄAx¤âŔl�Ăa†Ń˙űbŔµ�= „­ÉÚH"ô•¸°~Sţźě˝üż˙ř’ť=ŘÓË˝űuÍł˘©43ˇšęÔi˛Fµż˙˙˙Ż{zGj”Ç»ŃJÖ!Hj$Ő\ŔW§(X÷aŞŃb„ÁÄFş !…UČ &)Î&ěqaÇ śpx"0HD =@˙˙˙˙ţ‘łĂčę^ék<’—/˙˙ĹůLM t;2ѬΨçEŞs܉\ú”‰Đčä=f{اu_˙˙˙˙éŁhßyr'G:Ëłť f‹ -ÝQĺt3 ‰ťśŁŚ,Ž`č|Xs™…™ÄĘÁŃ¡12\ä&đ˘(Ś`�Ă ˙ű`Ŕą€nA „íÁóČ"´•¸¶hŘőçůßËő˙˙˙ő<u(ë7q]M]ő×=CGŢüýňÔ“ĚsTŇ‹÷;JĹ×}_ß˙˙˙˙˙˙˙˙˙]OŐOvĐňÓ ÇňŚĐ´5¸}’ŻčmÚśs–4“{e‡=$u0ˇfLÜ, °T?i(MHÇ‡Źž(�˙ű˙ţ±üüÖ~µ‰÷?˙˙ö˝Ń¶©E[d[Ý‘QČîuC*ĄÝT»»F!ňĐäR¦BĽŮŐ ß˙˙˙űďÜ›Lއ2«ó•ÖŠ[¸€›łL”ˇÄŃXiH‚Ň !”Ś"Áŕ›ŕ@ř°±„Ăę4TT©QbbDÝÎ�˙˙ű`Ŕµ�Ň= „­ÉčǢ´ˇą˙˙ö˛˙–^ż7óę˙˙ţ˝¶dĘďEŁu+Í3’y˛«îęŠEwT¬ĆUťŠ¨W˙˙˙ý~ÄűHűŽőTg+T¦C”Ą"˛ŠRśŠu2N§ĄĘ9\aq) R Ž28Ä2‹qĹČs‰D8á$*��ü¤>łóż˙˙˙Ďéą&q…+T©µ'ŮTU•ŚgeR á‡B32)—ýž·D|ď˙˙˙˙Ó]«MÉş+YŚ[Ö¦0Ň)ĘbŽĄŽ5EQ„ÄEa!\hAÂ!ذ łBĂîg Ŕ(ńá‚ÂC &P 8|DV&Q˙˙˙˙˙ű`Ŕ´€ÂA „­Á×ÇŁ4•ą˙éä^¤–ú,‹˙ź˙˙žŇ÷f»‘×d[Ňş˝ VąLës ws)P§z^¬t252]ko˙˙˙ď§Ł÷]»ÍĘS2•gsƙٞDkaśÔ9Ç#Â"%F âDL*Ł˘ (B" ‡XÂŽAěbĽ>˙˙˙˙˙±…ĺţY//˙˙˙üć©J$î”Y?RşŃôVT}ŢŠrQKjßZmu}v˙˙˙ţ«ëzttz%¦TS•1ä#ĚĆ˝j=‘ŇŞ”ŞQ!0c ÓH,˘ĺ ŁÄÄĂŠ,ˇŃw‡Č 8ç1Đĺ†0¸Đ�Ű �ĂjĐö˙űbŔ¶€A ­ŃŮČ"ô•¸}s—˙ţż˙˙Ä@÷:XČ®M*¨Ccą§r”ü­#çDQz­Şý§µ)˙˙˙˙ýţ–ŃNbç›-LQl„0׳ )ťÜk ;Â,5ÜŁĹ‹;"Š 8ЦA¨$ÁŃqĆ\M…EAB@Áç( *�˙˙˙ţŤ˙-ă4Ń—pţR˙˙˙ł˘îVlěU·K&öu+<Ď™‡tLěěv3ˇŠÔłI{×˙˙˙˙úűű•YŃ Ą*«“ęçŞîR†Đ<Ž=ÇÔLU…aĹqáL (s”ń¨§D4P€Č‚Ä !€8˙˙˙˙˙ű`Ŕ·€’A „­ÁńČ"´•¸˙ěŚ w"šçĎ’–«˙˙ţM˙ÇýwóóŻ÷Üu]fFĚŐH÷SR‰őô±×KŻ×˙˙˙˙˙˙˙˙˙Ďw§qóßLň°»OĄO3r˛1GÔ­mÍ»±ĺ;=šSĘ8yŁ2lĎ#•0ŃÄĂHpň ˘ĄŹ% « €˙˙˙˙˙˛źĎ_ĺËz˙˙˙ţţŠŚ÷Z2>ô±• ŚĚ]ŞC\ťČ„VGGuyŐ(Ęr˘J´˙˙˙˙ű[BUÝźaj«öŁ[‹Ő™HŠčăLR«yڇś¬C%Üq]Qbě->A!Yâ†`Ł1\Xâ@��`Ň y?˙ű`Ŕ·�6A „­ÁâČ"ôˇ¸—#˙˙˙˙Ę­ä&ĘŞËťŐčęĘ}V¤1ÖłFnną:ű[Ż˙˙˙ëmjĚësˇŇŐF!ŠbŠę®Ć)†‹‚*‘LŚc°jˇ¬*‰˘ď0pĄ>$av!śXč4DHÁÁ5‡ĹĨU(p@1@�˙˙˙ţ­üŚ˙ĄÔËďë˙˙˙7ęěµR!ź#ٵŠč¨~ܦf5ćfb1ŢŽÝS"Ű˙˙˙ţßčÍišŁP®vJÖ®d1qčq”2ÚČ0:Ĺ+‘T6aÂbEl?2‡DD…‡‡\M"B Ł˘† ”Ác4\�6Ă �˙ű`Ŕµ€.A „­ÁçČ"´�•¸´ýą~ö˙˙˙ţT«Ű"Ł—ÜÇž—ŃsŮŃhę®DÎmYd˝îdSnŰ˙˙˙˙éíľ­:Ş3š§š•c%®5HQU<§!ĹEHcDH@8â(‡E`=‡(ąĂ§qR¸˛:$&<(ją‹ĆЏ  h���Ř�6­óćż˙˙˙˙#í%)sMŞŘí1ćrÚě·IUkwG‘rîGtTe÷Ń—˙˙˙˙ŢŐęŰ++Jζ%ƱŢČÄĚ<‹;‘XÇ8q‚:‹ Aa6qe- @ĘAA¦`‘Da"ÄĆÄ„@đ™”:?˙˙˙˙ý$˙ű`Ŕ·€A „­ÁďČ"´•¸/ţN_¬™ôë˙˙ä ÷Gő>ľJĄÓ:˛ť—G4¸±Q(„tC\î{+©•®gO˙˙˙˙쮕˝Ôł!O<®ë!¨Žd,ŠćR1>‚%f0°¬@ę>qâˇô (¨ĺś4ÖQ!0ř€şX‚eQC€˙˙˙˙˙Ňäţgě?˙˙˙˙˙-‘ś¦ŽI"*ĚDŁ™•̧Y§In–jH~­’Vkg»T®Š¬‹˙˙˙˙•úŃĚŽäRËŐ®¬–K¦í[ÝF"Ψpá®ĺs ”<îáC‡A r$ň 0×R‹ŽĚW€˙˙˙˙Ű˙˙űbŔµ�^A ­ÁéÇâô•¸˝˙˙ß›˙˙üż->ůŞó/ß&@şŇzšşË)˘Ô-^źđĽŽ˝Ďmű˙˙˙˙˙˙˙˙ĺ‘ßće ·îV›E‚6ŤťŢő$4Cą»Ľ­dPŔŽŠ$B¬ŽAĹŁE "Ł,†8SW©9 € €�`5‘ç˙Ëćkúë˙˙ň˙ZšçTCŁú^5Ýő‘ś¨bT©‘Üz-®–™=¤˝ź˙˙˙ýiCž”čËB)©au”‚o,ŃČ8‡c‡ ""@”AĐDĄ<IĆ €(ÁaŔ0XT¨W ÁË‹ 9ÄŔPvĆW B€˙˙˙˙Ł@Ý4˙ű`Ŕ¶€fA „­Á˝G#tŤąŘe:€ýź˙˙Ď}ęš”ę‡ôQŞërˇ¶:#›:9ćécŮ’g!Ö«×˙˙˙˙ý;ěĄK_˛ö(‰ÄČńŐś¤‰ )ÝŽ$ę†(ä1EŘç9ĹŔRDP\fŽ ,$",ax°u„„EET8px˙˙˙˙ţ±őż˙—ć]Ď˙˙ţ}-W晍f»Ő)wK%OJąčT#š1K{Ż?9™ě¬§˙˙˙˙ű“ŰMí4čŇUŚçU!N¬Lk‘Ô®îRŁę̌rŠŹ3 …;‡äA1Ą0¨Ńx |MÄ <H"a! #¸)J.?˙˙˙˙˙¤˙ű`Ŕ˝�Ę= „­ÉńČ"´•¸/ůl‹7ËůŻ˙˙˙<ť‹v}‡W­SN¨y‘Ë«ŞUť ÔźY™•ŽJ{5g˙˙˙˙öőˇVµUµ]Ő§"ËšrÔŤBŞ‘™Ąäh‰„tŁ S#ĚC2XĄB:Î&a(qĺÄN�˙˙˙˙ţ‘«ęYáŃHöô\ç?˙˙îoö»¦ćőŽb:K­f””ĺôžfi‘siáˇxîW©ž«ę?˙˙˙˙˙˙˙ű˙řůřý*ˇx«žišő˝‡#’´,] ŞĘ ĂD N*/L<Ć"áÄŔ¤f0Ë4PZ”łX••mm)„T¦X˙˙˙˙ô˙ű`Ŕ·€ŢA „­ÁČG#4•ąŚ§ŁbŇďĺ:Í˙˙˙ŃţFDĄďJh†T Š$ެWČŞ’t$Ë뤷U3äs?˙˙˙·ďÝQ4:—bÖ¨y'Fˇ&yÍ8”MFEŠíČc˘ŽĂX@yŽ."‡DŚ'aa[‰8:R˘eqG0˙˙˙˙ţ‘óžüü˛ľč˙˙˙˙˙­ĐŠÍGňÝUUMçyJďIŠcىJH䪓nŞ‹˙˙˙˙§ÍÓ«ÄŞ§rąĚbUĚfQqÚ›Ő v#ŤR ?Q"…]JÂ&)Ä@¦ ;•ĹPXP¤1F‚JAp@& *ńŔ`0˙űbŔ»�öA … ÁđH"ô•¸�lŇ0~~˙˙ůń˙˙ţý4iŃşú­UU\ŻeB$čÄ#č…5m¦M.•T5jż˙˙˙ŇĆŃ_ÔŽŽ§3ÎňˇUămTb©„Ęg1HÄĘ,Ç(±ÄŐEÚ¦ H&eAŁÇX@EB†ŠHEGŤ(÷+CÂÄ $`w�˙˙˙ý­ůů/˙řÉEů˙˙ó2˙Bmv6UsrLŚŹ«Ä{ü¤[÷uݬď[˙˙˙˙×Ů=Vd=ČZgŁČS1¬E]VK‡ť„ŐŤ*†31@ńGpčÁ�ŕ r qŚěSb«‡<`±˙˙˙˙˙´`ň˙ű`Ŕµ€îA „­ÁâČ"ô•¸˙úóą˙˙˙ŻôśĆ1î@FvYŤ±BşŁ?fě–1l¤DĄť—J¦ô‘S˙˙˙÷ôő;ě_Ů·1™+¦®qgTaśçLĄpçr¤D�Îdáŕč)B2ȇ3ęv tT8Ql&�˙˙˙˙ým©OzM'ęőšďëţSŤť/˙„é&˙_I¨×űőŇluBßǧµţ—\üwĎÝüĹ˙˙˙˙˙˙˙˙˙7˙ÚUÜ7;Ç­Ęßs#Zń×<`Ů č& l•yTb‰˛$ÚÄE`Čń:VE‡dž"ňpŁÂ1ä É& *y ŁĎ¶4J��m¶Ŕm[˙˙ű`Ŕµ€JA „­Á¶ÇŁ4‰ąĎÖ¦kťW˙˙˙ű+•ĘĄ*F›+ÜŐt1 ÜşSV#™Y\ś¶zTňó©•ż˙˙˙˙îjnĘŇ™“[;ÜŽÇAĹ;IĚD;!ĚÇ9(ěeŽ8MĆX^ Q!Ŕ hLH>*<:*‡ °x:@ůDEEH8:.څЇ†��˙˙˙˙¶üżůëď˙˙˙˙©˙˙–ŽÍ­îGH˘’°»Nޱ™ÄW%˛zŐóŘřDo“eŢ˙˙˙˙˙˙˙ţ_ü?)ąleyX†żç)ÎśzRĚź"]Ë•L´'dxČĐtS 3j±Ű é ŕ˙˙˙˙˙Ŕ~ds˙ű`Ŕ˝€>A … ÂH"´•¸ć˝ ˙#˙˙˙˲µĐŐc0‰&V‰ZâBĹrĚdpëFEyFm Ą*ł¬Îľë×˙˙˙˙˙żżmYĘŠVĘĆ}LęĆ+>j=‚¨,ĺ-*UcĹB˛=ť QSÎPčŃÎ"ĘYH  ˙˙˙˙˙˙˙˙˙˙˙˙Ż˙˙˙˙˙Ż˙ý~Ć(`JEŞ*!ŮŮĘbŞ”Vs ` #ł±Š©˙ł”ÁB(�����������������������������������������������������������������������������˙ű`Ŕł€ n? „mÁŃF˘ô•ą������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������˙űbŔ»€&@Ţ�MŔ�[Ŕ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������˙ű`Ŕ˙€���Ţ������Ŕ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������beetbox-beets-01f1faf/test/rsrc/date_with_slashes.ogg�����������������������������������������������0000664�0000000�0000000�00000020562�14723254774�0023074�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������OggS���������ţ–ç=����Ý[vorbis����D¬��€»��€»��€»��¸OggS����������ţ–ç=���­9s„@˙˙˙˙˙˙˙˙˙˙˙˙˙2vorbis���Xiph.Org libVorbis I 20050304������date=2005/06/05vorbisBCV���cT)F™RŇJ‰s”1F™b’J‰Ą„BHťsS©9לk¬ąµ „SP)™RŽRic)™RKI%t:'ťc[IÁÖk‹A¶„ šRL)Ä”RŠBSŚ)Ĺ”RJB%t:ćSŽJ(A¸śs«µ––c‹©t’Jç$dLBH)…’JĄSNBH5–ÖR)sRRjAč „B¶ „ ‚ĐU���Ŕ@˛ �P��ЎŠ„†¬�2�� (Žâ(Ž#9’cI˛ �����ŔpI‘ɱ$KŇ,KÓDQU}Ő6UUöu]×u]×u 4d���@H§™Ą  d Y� ���F(ÂBCV����b(9&´ć|sŽf9h*Ĺćtp"ŐćIn*ććśsÎ9'›sĆ8çśsŠrf1h&´ćśsf)h&´ćśsžÄćAkŞ´ćśsĆ9§qFçśsš´ćAj6Öćśs´¦9j.Ĺćśs"ĺćIm.ŐćśsÎ9çśsÎ9çśsާspN8çśs˘öćZnBçśs>§{sB8çśsÎ9çśsÎ9çśs‚ĐU����A6†q§ HźŁEiȤÝŁĂ$h r ©GŁŁ‘Rę ”TĆI)ť 4d����!„RH!…RH!…R!†bČ)§ś‚ *©¤˘Š2Ę,łĚ2Ë,łĚ2ë°łÎ:ě0ÄC ­´KMµŐXc­ąçśkŇZi­µÖJ)Ą”RJ) Y�€��dAF!…R!¦śrĘ)¨ BCV�€�����<ÉsDGtDGtDGtDGtDÇs<G”DI”DI´LËÔLOUŐ•][ÖeÝömavÝ÷uß÷uă×…aY–eY–eY–eY–eY–eY‚ĐU����€B!…RH!ĄcĚ1ç “PB 4d��� ���ŔQĹq$Gr$É’,I“4Kł<ÍÓ<MôDQMÓTEWtEÝ´EŮ”M×tMŮtUYµ]Y¶mŮÖm_–mß÷}ß÷}ß÷}ß÷}ß÷u Y�H��čHޤHФHŽă8’$ˇ!«�����(ŠŁ8ŽăH’$I–¤IžĺY˘fj¦gzިˇ!«��@�������(šâ)¦â)˘â9˘#J˘eZ˘¦j®(›˛ëş®ëş®ëş®ëş®ëş®ëş®ëş®ëş®ëş®ëş®ëş®ëş.˛ ���БɑI‘I‘ÉBCV�2���p Çɱ,KÓ<ÍÓ<MôDOôLO]ŃBCV�€�������0$ĂR,Gs4I”TKµTMµTKUOUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU5MÓ4M 4d%���ŔbŤÁĺ !%%ĺŢ“ž1&!µ^!‘’Ţ1ž2˘ rŢBă Y�D��Ć ÇsČ9G©“9ç¨t”祎Rg)ĹbÍ(•ŘR¬ŤsŽRG­Ł”b,-v”RŤ©Ć����,„BCV�Q��„1H)¤bŚ9§śCŚ)çs†1ćsŽ9ç tR*çśtNJÄsŽ9§śsR:'•sNJ'ˇ��€��€� ˇĐ@ś�€A’<Oň4Q”4OESt]Q4]×ň<ŐôLSU=ŃTUSUmŮTUY–<Ď4=ÓTUĎ4UŐTUY6UU–EUŐmÓuuŰtUÝ–mŰ÷][vQUmÝT]Ű7U×ö]Ůö}YÖucň<UőLÓu=ÓteŐum[u]]÷LS–MוeÓumŰ•e]weŮ÷5Ót]ÓUeŮt]ŮveW·]Yö}Óu…ß•e_WeYv]÷…[וĺt]ÝWeW7VYö}[×…áÖua™<OU=Ót]Ď4]Wu]_W]×Ö5Ó”eÓumŮT]YveŮ÷]WÖuĎ4eŮt]Ű6]W–]Yö}W–uÝt]_WeYřUWöuYוáÖmá7]×÷UYö…W–uáÖuaąu]>Uő}Sv…áteß×…ßYn]8–Ńu}a•máXeY9~áX–Ý÷•et]_XmŮVY†_řťĺö}ăxu]nÝçĚşď Çď¤űĘÓŐmc™}ÝYf_wŽá:żđă©ŞŻ›®+ §, żíëĆłűľ˛Ś®ëűŞ, ż*۱ëľóüľ°,ŁěúÂj˰ڶ1Üľn,żpËkëĘ1ëľQ¶u|_x Ăótu]yf]ÇöutăG8~Ę��€�€�Ę@ˇ!+€8�Ź$‰˘dY˘(Y–(Š¦čş˘hş®¤i¦©ižiZšgš¦iŞ˛)š®,išiZžfšš§™¦hš®kš¦¬Š¦)˦jʲiš˛ěş˛m»®lۢiʲiš˛lš¦,»˛«Ű®ě꺤Y¦©yžijžgš¦jʲiš®«yžjzžhŞž(ŞŞjŞŞ­ŞŞ,[žgššč©¦'ŠŞjަ­šŞ*˦ŞÚ˛i޶lŞŞm»Şěú˛mëşiŞ˛mަ-›ŞjŰ®ěę˛,Űş/išijžgššç™¦iš˛lšŞ+[ž§šž(ŞŞć‰¦jŞŞ,›¦ŞĘ–癪'ŠŞę‰žkšŞ*˦jÚŞiš¶lŞŞ-›¦*Ë®műľëʲnŞŞl›Şjë¦jʲlËľďʪ)˦ŞÚ˛iŞ˛-۲ď˲¬ű˘iʲiŞ˛mŞŞ.˲młlűşhš˛mަ-›Ş*۲-űş,ŰşďĘ®o«Ş¬ë˛-űşîú®pëş0Ľ˛lűެúş+Űşoë2Űö}DÓ”eS5mŰTUYveŮöeŰö}Ń4m[UU[6MŐ¶eYö}Y¶ma4MŮ6UUÖMŐ´mY–ma¶eáveŮ·e[öuוu_×}ă×eÝ溲í˲­űŞ«ú¶îűÂpë®đ ��p��0ˇ ˛���ŚaŚ1ŤRÎ9ˇQĘ9ç dÎA!•Ě9!”’9ˇ””2ç ”’Rˇ””Z !””Rk��8��Ř )±8@ˇ!+€T��ăX–癢jÚ˛cIž'ŠŞ©Ş¶íH–牢iŞŞm[ž'Ц©Ş®ëëšç‰˘iŞŞëęşhš¦©Ş®ëşş.š˘©ŞŞëş˛®›¦ŞŞ®+»˛ěë¦ŞŞŞëĘ®,űÂŞş®+˲më°ޮëʲl۶oÜş®ëľďű‘­ëş.üÂ1 G�ŕ �@6¬ŽpR4XhČJ� �€0!B!„RJ!Ą”��0ŕ��`B(4dE�'��C)¤”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RH)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ©¤”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)•RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”R �Šp�z0ˇ ˛�H��ŚQJ)Ćś1ćcĐI()bĚ9Ć”’Rĺ„Ri-·Ę9!¤ÔRm™sRZ‹1ć3礤[Í9‡RR‹±ćškVk®5çZZ«5לsÍą´k®9לsË1לsÎ9çsÎ9çśsÎ�ŕ48�€ذ:ÂIŃX`ˇ!+€T��ĄsÎ9čRŚ9ç„"…sÎ9!TŚ9çtB¨sĚ9!„9ç„B!s:č „B„Bˇ”ÎA!„J(!„B!„:!„B!„B!„RJ!„B ˇ”P��`�@€ «#śŤ˛���€– R΄AŽAŹ AĘQ3 BL9Ń™bNj3S9ťtjAŮ^2 ��€ � Ŕ (řB1��AĚ …U°Ŕ  ćŔD„D� H»¸€.\ĐĹ]BB‚X@ 88á†'Ţđ„ś STę ����� �ŕ�ŕ �""š«°¸ŔČĐŘŕčđ������ř��8>€ć*,.02468:<��������€€€�����@���€€OggS��@–������ţ–ç=���ý‚ĺ'02310276;:?CCBl…‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹¬Ř%ô� B8FkF#ëśzĘýýžĺJI`ź76�BU5Ŕ: š†îK.Ţ[/IŔ´�€Ž€ ~ĘýýžĺJĽçŰJŮ�@!TŐ€ €z wP{@Ű‚š_,ß5đ�č@ �4�>Şý˙üĺ žíkĄ°�@!TUč�40ŃMtô™ŕÉĆ+ÚŮÁ…WÇŃ�ŕĐ>Şý˙üšäâvy¨��…PUˇô…|G×OӱЂ††ŤĘpB�Ŕ&€>Şý˙üšäîô4Ş��…PU'�Đ@$¸O}Ç%ĺMŇ;†:€ �žä>Şý˙üšäŇ´đ&”��ŞĘ* @tđ¨�´ŻůçaďĄ)t (�4 �šý˙Ore1éM0�BU � 1>ŢY(ĄÚsYŔ&-RJgčˇŐMkňŘ�Ŕ‚Všý˙OrĄ2ŘĺIŘ��UUĹ@EO5š‚nE~IôĆÓ´JĘqŘ)d˘U)°�$ @šý˙OrĄ28ĺMč��ŞĘj� ĚY¬"ćeç…d×Ôdţ‘ňcw˘ ±“¦‘�€d �:šý˙OrĄ`°Ű›¸�TU:�Ě]ą¤ż\Ďôł\ĎŇ{QF<Q©š5¬ĐŔ`MX�Ţyýök”‹2íMQ#�€B¨¨˘Đ@§ŰŢçµNUďáM őNůf 3 óéęĐÝlC–ž >H4¬T05�Ţyýök”‹2¸ŰŰXaE��…P + &EŇć^ ô#©”¶&Ú!ir}Ď””Ωaku¨“)lY ‚& H�Ţyýök”‹2íMP�*jT�¦Ţ1{Ă1^_Y•˘~vçt×Zź}ą.󭡣Ş9“5\1á)č�ŕÁ_#�Ţyý˙ţĺR Ny“�B1ę,�r(¶Řňł4ptmp sR›¦ŁL/g9‚čŢ?®Í0—M)�°”�,Z�ýÎuą‘Ţ8� 4´X‰ h©u Ŕ÷ßŇ‹ö~>SŁČü†ý$=cr¦Čß7˛üĽöŽĄĎĂŚóů0Ę«®bĄ*Żß·ţQť˙Šá¨Y4®#öO¤Ĺ± Š«=ˇ P{údX]ÎÔýÎ59 ŁĽ �„f)6đÖ”łA`fś“€˝^ťÄ!<©¬i0/dĄ·?/Ôhł=é«m¤ň¶Ţ­™žŹŤwŰŐZ:Ý–˙‰^®¦×Ö‘&uVwű’CĹ_Ĺjđ-mŚ����������������������������ýÎu9 %˝ ��Đ"++IŔ@âĘAGŐŤrý5]ó�o”X€Ż/săÎŹ\ú çHÇ‘ŮqoĹóČĐ3ú.+Ľđ¬qÖ\Eť¬SF’ż1R-ŕ"ç ÜI¦ĎlÂjÂÉĚěć���������������������������������ýÎwŮ4-=� ĤĽĚdŔ˘ĐI�ŕ,1ć8‘qü]âj­_č÷⥧Թéô''ďsçőu|‰ěwűćąĹÇ덳Î勝/óˇ<:Ť0ŃŰĺ$ˇÍ˝'—3Śí®Źý ��������������������������������ýÎu9q­< B�ˇˇËÂÉgPŕ€~57kg§Pżd´]ťŮŰüąśÎoż’_ÚąJs)ęă5÷Ňä¤Ĺp–ô•őÚc\^;ÇĄQĺtFôb[D®áě §­“������������������������������������ýÎ5ąVŢ�h’ʦˇ�°@vvż´ÄNľd ¦˛˛Ki*Żu67[`&· 'nV‡§Ż‹“ËČcşt“źq’C>-VţFźÄĽVŮ ]ęďtGNĘy49ŞéaśŮMă����������������������������������������ýÎ5ąäŤ�ˇXd‰‘��\%`8Ź'\ŕÁÁű5ôŹ7‰M?/'x‘,ׯ×}YżŽ˝<S«oÍYI¤Ű0î­< V_R†]ŠíÍžJ];揅śŢí·BłHűń·•8NŔŮ˝Dä�����������������������������������ýÎ5ą8‘Ţ�B“„eŔ_™h$�áž''Đ„Y8˘ŘEŃ´Ç˝^‹ďç«Ü.pNë°Ä-’CaĂ+83|©űŐźŤ-Éśť¶ĎUxY¶]FVS”ijúP[éFÂR/P:Ű»r��������������������������������������ýÎ7ąVľ�BłE R€�Ŕz+ó´¶źëž•ĘĘőŠa߉xR=¬±7ćL‰Ű &‡Ü‡nʬőúÖVŢk\ńÖďá šŰŻŕ´şBĄ:ť1Í“‘±‚¦bDţö1Í ‘Ş®���������������������������������ýÎuąž8� ”Ôe…1ŔŇ,�0™ŽŽG­;H`5ŰrŹbR;VsĎÜlËŚjąPĎFlôn3¦°íúuPéˇ)`,V*ýxuC=ľ™rN:Ś…ý¬=Oä}w�ôöúEad•”cb�������������������������������ýÎ~7Ů8Yľ„�ÄËJ84ÜcPČąëlbĐźĄBâŔ(ŰŘ,·âŠĺ$ł¦ÂĐ›. üdĂĆy/”cův€~ý—:lϬ™Ńk8Ĺuoć”HȆ¨$Ĺ ö˙óŞÁžą˝Ď�����������������������������ýÎuąáiH��Z„®0rea�X¸¶(cp˛KÓëćtŹ#)s:.ĆhëřL¬^ł)Z Km“IJyś’c#Qďln2t6]ň~’]¦NĘ „*đ<´o1X¨ŔźÇŤGSz��������������������������������������ýÎuąŢ��B“¬<•@ ľÁq] ŘÔÜ‹ć$"93Ë’zlć3Ĺ—ŕ”S©Űď“á˙ąz)ĺLżĄě. ›&ukbgÄ÷Ę_:`…HňcĽď0D64úb‘˙ďŕ 9ű!r�������������������������������ýÎuą(‘ž„*�B“˘Ś«Š:�>nmcfFU)¸¸đ“‡0ŮëŢľŐŃřÚťť·•ęP[©+T˛űëĽý2š\ÍüÓSś ÂG.WąčX©;·ĺUFŔhkôo†FŘ/÷ŕÂu cĘ:����������������������������ýÎ7Ů–Ţ8�€š• ›0Řd7•\´×vÎ}ií˝E«MÝ M3‹Š"äđşĎ~�µ˝bł¶#Ah©'ňR‚ŃÖ&y,]~ŹMG“-9¶#8…ţůäëĽ#s/y����������������������������������ýÎ7Ů8Qľ1�•Ö%8�€iup;š¤_I5…ŔÖ˙?»ŘÄ&ňҵäduˇn±{˝ď…é!‰ź»:•¬Ď…úzĹU.25žBÉ–+Ęčq0ďîQ®*/CUë&^ܱé-��������������������������������ýÎ59q#} �„&ĺ68ZŔ€ôĐ»t3ü´WÜ™ohm™äP»-¨Y?Ç{ž°ęŽLXř8&MöŘ f%ŞO©›÷NÉ”‰ÍÍŻ®b˘ě˝›ěÎČPwˇëWI;–���������������������������������ýÎwŮ(–ž„*�hRdÁPL�ŹKşá?7śŹI—»BťŹÁ¦Ń|_O&±arúäşvQŕ$ąk×°d­Áĺ˛l­Lc/["°R#lcú,čVŔÝťŠ…«äŕHďbz¬Ű{8T�������������������������������������ýÎ7Ů8QŢ1�ÉJQ3=H�Đi—Ť ‚©ćĘě•ä¸óM6żÝRmöµ|fę «ö’ô<xQ[‚ Ł§Ý˝űjTż03Ѹů-ťao8*Ę.B¶ŘrI…Z™Řyůۙ�����������������������������ýÎ~5ąQľ1�ˇu‰8{,�U¦»k,ż,¦żW}IŤŠZGv›Ü ý±gěÉVöCA’ô—”4#ŤâwLqu;Ž‚óÄ~BH Iţ҉fDaďŚne›������������������������������������ýÎwŮ8–žHb -ŞˇŔě�@cŰweł¬fô“'ŹI»#ł:«/Ősâ§ LNao—Ĺ’üłęÎhrŻ‹K5«ŚámŃ {Ř–ÔnČ[oČcőQ˝‚€ď šî*§ťÚč˘ŢŢ���������������������������������ýÎ7ŮâŤ�ˇ˘`đ&@ ď~Ë˝r8zôľóG­řHä·5ż^yÚxńé\™JfŇŹ}ŔmťŁvŐľËôLa!9'ěbŰ>ŃZ*Ěü(NbÎŃŮ&ç˛����������������������������������������ýÎuą–ž�@-jIÁ�…&&č�h‹o\ś‰[˘�ĆüJx2ÝĎÚ ^Cí7^:«YGcF¦ňO}žÝşž‡žŹ™‰°±<É~ľ¬2Pâü"SÔމíőŞŔö™!Gć&ŰóS’‰ťÎI…¨��������������������������ýÎuą8QŢ(�BĚR¬H¦Đ�‡YB,’˛-oĹx3CT§XČčňßŃ| éČÓůĆű<Z>Ńł^^Ş„^‰)5=‚%G˝pďb€{fy](€>(Ö÷:;ubjŮ:kGG:�����������������������������OggS�D¬������ţ–ç=���‡ż˛¬‹‹‹‹‹‹ýÎ59 %˝ ‚��”. :�>Źţč]µ¤?÷TăĚxL€çsÔžM›PĎXšÁ„—+q!^#§ĂaâčŃ|S«ă.Ź„™ăµ•«Q®%(Îsnĺ¶©lő]”N-{~����������������������������������������ýÎwŮ8™ž0�B“b…qÄń–�X9vń)l}¬ĹĽŹb‘Id=!ű…t,W˘7Ĺć˙9±¸őń­Ű˙â® Rńçf©ÄŔoé<ďËÓ—·§ľUÚe±”׫üpő0Úé[(wÉÎ"…��������������������������ýÎuą8–Ţ„2�h–˘"hrđČ)=|éP›Ű"ýr:Iň"qŤö°Ě蹨šŻŕŽľRß"ŕ€"«•uŢ˝‰ ţąÚ‡żš.řAsń©Ďnµďn7…Ł'ňčeŽ~ ���������������������������������ýÎuąŢ0�B Í c@2 �°8čä>h»ď_6zyu—YĆü|$Ô•»ąŽëö¬ĘŔĹ…÷ńSěŮ n'E˛0!¸¶˛ö>~v3üayč™zź›9óMÔk ¦1×5„éW7ń#ŢežČi����������������������������ýÎwŮ8‘ž1�h–bŢFh‚�¦zmśňp4<Żĺ˛é*é´ ´ÚäÂśÉ"N¨„Şń|oâSđjl’ĚôřśxŽMqIß–Š'S´Î—WřKw®˛:&U0TĚn;Ť‘bbž ĆPČe�������������������������������������ýÎݞ 8��P‹<Üŕ0P8ťÓą­j� �x�z}]Ü�����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������beetbox-beets-01f1faf/test/rsrc/discc.ogg�����������������������������������������������������������0000664�0000000�0000000�00000021162�14723254774�0020464�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������OggS���������ţ–ç=����Ý[vorbis����D¬��€»��€»��€»��¸OggS����������ţ–ç=���^¸µ˙@˙˙˙˙˙˙˙˙˙˙˙˙˙2vorbis���Xiph.Org libVorbis I 20050304������ALBUM=the album���ARTIST=the artist���BPM=6���COMMENT=the comments ���COMPILATION=1���COMPOSER=the composer ���DATE=2001���DISC=4���DISCC=5���GENRE=the genre���GROUPING=the grouping���LYRICS=the lyrics ���TITLE=full ���TRACKNUMBER=2 ���TRACKTOTAL=3 ���YEAR=2001vorbisBCV���cT)F™RŇJ‰s”1F™b’J‰Ą„BHťsS©9לk¬ąµ „SP)™RŽRic)™RKI%t:'ťc[IÁÖk‹A¶„ šRL)Ä”RŠBSŚ)Ĺ”RJB%t:ćSŽJ(A¸śs«µ––c‹©t’Jç$dLBH)…’JĄSNBH5–ÖR)sRRjAč „B¶ „ ‚ĐU���Ŕ@˛ �P��ЎŠ„†¬�2�� (Žâ(Ž#9’cI˛ �����ŔpI‘ɱ$KŇ,KÓDQU}Ő6UUöu]×u]×u 4d���@H§™Ą  d Y� ���F(ÂBCV����b(9&´ć|sŽf9h*Ĺćtp"ŐćIn*ććśsÎ9'›sĆ8çśsŠrf1h&´ćśsf)h&´ćśsžÄćAkŞ´ćśsĆ9§qFçśsš´ćAj6Öćśs´¦9j.Ĺćśs"ĺćIm.ŐćśsÎ9çśsÎ9çśsާspN8çśs˘öćZnBçśs>§{sB8çśsÎ9çśsÎ9çśs‚ĐU����A6†q§ HźŁEiȤÝŁĂ$h r ©GŁŁ‘Rę ”TĆI)ť 4d����!„RH!…RH!…R!†bČ)§ś‚ *©¤˘Š2Ę,łĚ2Ë,łĚ2ë°łÎ:ě0ÄC ­´KMµŐXc­ąçśkŇZi­µÖJ)Ą”RJ) Y�€��dAF!…R!¦śrĘ)¨ BCV�€�����<ÉsDGtDGtDGtDGtDÇs<G”DI”DI´LËÔLOUŐ•][ÖeÝömavÝ÷uß÷uă×…aY–eY–eY–eY–eY–eY‚ĐU����€B!…RH!ĄcĚ1ç “PB 4d��� ���ŔQĹq$Gr$É’,I“4Kł<ÍÓ<MôDQMÓTEWtEÝ´EŮ”M×tMŮtUYµ]Y¶mŮÖm_–mß÷}ß÷}ß÷}ß÷}ß÷u Y�H��čHޤHФHŽă8’$ˇ!«�����(ŠŁ8ŽăH’$I–¤IžĺY˘fj¦gzިˇ!«��@�������(šâ)¦â)˘â9˘#J˘eZ˘¦j®(›˛ëş®ëş®ëş®ëş®ëş®ëş®ëş®ëş®ëş®ëş®ëş®ëş.˛ ���БɑI‘I‘ÉBCV�2���p Çɱ,KÓ<ÍÓ<MôDOôLO]ŃBCV�€�������0$ĂR,Gs4I”TKµTMµTKUOUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU5MÓ4M 4d%���ŔbŤÁĺ !%%ĺŢ“ž1&!µ^!‘’Ţ1ž2˘ rŢBă Y�D��Ć ÇsČ9G©“9ç¨t”祎Rg)ĹbÍ(•ŘR¬ŤsŽRG­Ł”b,-v”RŤ©Ć����,„BCV�Q��„1H)¤bŚ9§śCŚ)çs†1ćsŽ9ç tR*çśtNJÄsŽ9§śsR:'•sNJ'ˇ��€��€� ˇĐ@ś�€A’<Oň4Q”4OESt]Q4]×ň<ŐôLSU=ŃTUSUmŮTUY–<Ď4=ÓTUĎ4UŐTUY6UU–EUŐmÓuuŰtUÝ–mŰ÷][vQUmÝT]Ű7U×ö]Ůö}YÖucň<UőLÓu=ÓteŐum[u]]÷LS–MוeÓumŰ•e]weŮ÷5Ót]ÓUeŮt]ŮveW·]Yö}Óu…ß•e_WeYv]÷…[וĺt]ÝWeW7VYö}[×…áÖua™<OU=Ót]Ď4]Wu]_W]×Ö5Ó”eÓumŮT]YveŮ÷]WÖuĎ4eŮt]Ű6]W–]Yö}W–uÝt]_WeYřUWöuYוáÖmá7]×÷UYö…W–uáÖuaąu]>Uő}Sv…áteß×…ßYn]8–Ńu}a•máXeY9~áX–Ý÷•et]_XmŮVY†_řťĺö}ăxu]nÝçĚşď Çď¤űĘÓŐmc™}ÝYf_wŽá:żđă©ŞŻ›®+ §, żíëĆłűľ˛Ś®ëűŞ, ż*۱ëľóüľ°,ŁěúÂj˰ڶ1Üľn,żpËkëĘ1ëľQ¶u|_x Ăótu]yf]ÇöutăG8~Ę��€�€�Ę@ˇ!+€8�Ź$‰˘dY˘(Y–(Š¦čş˘hş®¤i¦©ižiZšgš¦iŞ˛)š®,išiZžfšš§™¦hš®kš¦¬Š¦)˦jʲiš˛ěş˛m»®lۢiʲiš˛lš¦,»˛«Ű®ě꺤Y¦©yžijžgš¦jʲiš®«yžjzžhŞž(ŞŞjŞŞ­ŞŞ,[žgššč©¦'ŠŞjަ­šŞ*˦ŞÚ˛i޶lŞŞm»Şěú˛mëşiŞ˛mަ-›ŞjŰ®ěę˛,Űş/išijžgššç™¦iš˛lšŞ+[ž§šž(ŞŞć‰¦jŞŞ,›¦ŞĘ–癪'ŠŞę‰žkšŞ*˦jÚŞiš¶lŞŞ-›¦*Ë®műľëʲnŞŞl›Şjë¦jʲlËľďʪ)˦ŞÚ˛iŞ˛-۲ď˲¬ű˘iʲiŞ˛mŞŞ.˲młlűşhš˛mަ-›Ş*۲-űş,ŰşďĘ®o«Ş¬ë˛-űşîú®pëş0Ľ˛lűެúş+Űşoë2Űö}DÓ”eS5mŰTUYveŮöeŰö}Ń4m[UU[6MŐ¶eYö}Y¶ma4MŮ6UUÖMŐ´mY–ma¶eáveŮ·e[öuוu_×}ă×eÝ溲í˲­űŞ«ú¶îűÂpë®đ ��p��0ˇ ˛���ŚaŚ1ŤRÎ9ˇQĘ9ç dÎA!•Ě9!”’9ˇ””2ç ”’Rˇ””Z !””Rk��8��Ř )±8@ˇ!+€T��ăX–癢jÚ˛cIž'ŠŞ©Ş¶íH–牢iŞŞm[ž'Ц©Ş®ëëšç‰˘iŞŞëęşhš¦©Ş®ëşş.š˘©ŞŞëş˛®›¦ŞŞ®+»˛ěë¦ŞŞŞëĘ®,űÂŞş®+˲më°ޮëʲl۶oÜş®ëľďű‘­ëş.üÂ1 G�ŕ �@6¬ŽpR4XhČJ� �€0!B!„RJ!Ą”��0ŕ��`B(4dE�'��C)¤”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RH)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ©¤”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)•RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”R �Šp�z0ˇ ˛�H��ŚQJ)Ćś1ćcĐI()bĚ9Ć”’Rĺ„Ri-·Ę9!¤ÔRm™sRZ‹1ć3礤[Í9‡RR‹±ćškVk®5çZZ«5לsÍą´k®9לsË1לsÎ9çsÎ9çśsÎ�ŕ48�€ذ:ÂIŃX`ˇ!+€T��ĄsÎ9čRŚ9ç„"…sÎ9!TŚ9çtB¨sĚ9!„9ç„B!s:č „B„Bˇ”ÎA!„J(!„B!„:!„B!„B!„RJ!„B ˇ”P��`�@€ «#śŤ˛���€– R΄AŽAŹ AĘQ3 BL9Ń™bNj3S9ťtjAŮ^2 ��€ � Ŕ (řB1��AĚ …U°Ŕ  ćŔD„D� H»¸€.\ĐĹ]BB‚X@ 88á†'Ţđ„ś STę ����� �ŕ�ŕ �""š«°¸ŔČĐŘŕčđ������ř��8>€ć*,.02468:<��������€€€�����@���€€OggS��@–������ţ–ç=���ý‚ĺ'02310276;:?CCBl…‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹¬Ř%ô� B8FkF#ëśzĘýýžĺJI`ź76�BU5Ŕ: š†îK.Ţ[/IŔ´�€Ž€ ~ĘýýžĺJĽçŰJŮ�@!TŐ€ €z wP{@Ű‚š_,ß5đ�č@ �4�>Şý˙üĺ žíkĄ°�@!TUč�40ŃMtô™ŕÉĆ+ÚŮÁ…WÇŃ�ŕĐ>Şý˙üšäâvy¨��…PUˇô…|G×OӱЂ††ŤĘpB�Ŕ&€>Şý˙üšäîô4Ş��…PU'�Đ@$¸O}Ç%ĺMŇ;†:€ �žä>Şý˙üšäŇ´đ&”��ŞĘ* @tđ¨�´ŻůçaďĄ)t (�4 �šý˙Ore1éM0�BU � 1>ŢY(ĄÚsYŔ&-RJgčˇŐMkňŘ�Ŕ‚Všý˙OrĄ2ŘĺIŘ��UUĹ@EO5š‚nE~IôĆÓ´JĘqŘ)d˘U)°�$ @šý˙OrĄ28ĺMč��ŞĘj� ĚY¬"ćeç…d×Ôdţ‘ňcw˘ ±“¦‘�€d �:šý˙OrĄ`°Ű›¸�TU:�Ě]ą¤ż\Ďôł\ĎŇ{QF<Q©š5¬ĐŔ`MX�Ţyýök”‹2íMQ#�€B¨¨˘Đ@§ŰŢçµNUďáM őNůf 3 óéęĐÝlC–ž >H4¬T05�Ţyýök”‹2¸ŰŰXaE��…P + &EŇć^ ô#©”¶&Ú!ir}Ď””Ωaku¨“)lY ‚& H�Ţyýök”‹2íMP�*jT�¦Ţ1{Ă1^_Y•˘~vçt×Zź}ą.󭡣Ş9“5\1á)č�ŕÁ_#�Ţyý˙ţĺR Ny“�B1ę,�r(¶Řňł4ptmp sR›¦ŁL/g9‚čŢ?®Í0—M)�°”�,Z�ýÎuą‘Ţ8� 4´X‰ h©u Ŕ÷ßŇ‹ö~>SŁČü†ý$=cr¦Čß7˛üĽöŽĄĎĂŚóů0Ę«®bĄ*Żß·ţQť˙Šá¨Y4®#öO¤Ĺ± Š«=ˇ P{údX]ÎÔýÎ59 ŁĽ �„f)6đÖ”łA`fś“€˝^ťÄ!<©¬i0/dĄ·?/Ôhł=é«m¤ň¶Ţ­™žŹŤwŰŐZ:Ý–˙‰^®¦×Ö‘&uVwű’CĹ_Ĺjđ-mŚ����������������������������ýÎu9 %˝ ��Đ"++IŔ@âĘAGŐŤrý5]ó�o”X€Ż/săÎŹ\ú çHÇ‘ŮqoĹóČĐ3ú.+Ľđ¬qÖ\Eť¬SF’ż1R-ŕ"ç ÜI¦ĎlÂjÂÉĚěć���������������������������������ýÎwŮ4-=� ĤĽĚdŔ˘ĐI�ŕ,1ć8‘qü]âj­_č÷⥧Թéô''ďsçőu|‰ěwűćąĹÇ덳Î勝/óˇ<:Ť0ŃŰĺ$ˇÍ˝'—3Śí®Źý ��������������������������������ýÎu9q­< B�ˇˇËÂÉgPŕ€~57kg§Pżd´]ťŮŰüąśÎoż’_ÚąJs)ęă5÷Ňä¤Ĺp–ô•őÚc\^;ÇĄQĺtFôb[D®áě §­“������������������������������������ýÎ5ąVŢ�h’ʦˇ�°@vvż´ÄNľd ¦˛˛Ki*Żu67[`&· 'nV‡§Ż‹“ËČcşt“źq’C>-VţFźÄĽVŮ ]ęďtGNĘy49ŞéaśŮMă����������������������������������������ýÎ5ąäŤ�ˇXd‰‘��\%`8Ź'\ŕÁÁű5ôŹ7‰M?/'x‘,ׯ×}YżŽ˝<S«oÍYI¤Ű0î­< V_R†]ŠíÍžJ];揅śŢí·BłHűń·•8NŔŮ˝Dä�����������������������������������ýÎ5ą8‘Ţ�B“„eŔ_™h$�áž''Đ„Y8˘ŘEŃ´Ç˝^‹ďç«Ü.pNë°Ä-’CaĂ+83|©űŐźŤ-Éśť¶ĎUxY¶]FVS”ijúP[éFÂR/P:Ű»r��������������������������������������ýÎ7ąVľ�BłE R€�Ŕz+ó´¶źëž•ĘĘőŠa߉xR=¬±7ćL‰Ű &‡Ü‡nʬőúÖVŢk\ńÖďá šŰŻŕ´şBĄ:ť1Í“‘±‚¦bDţö1Í ‘Ş®���������������������������������ýÎuąž8� ”Ôe…1ŔŇ,�0™ŽŽG­;H`5ŰrŹbR;VsĎÜlËŚjąPĎFlôn3¦°íúuPéˇ)`,V*ýxuC=ľ™rN:Ś…ý¬=Oä}w�ôöúEad•”cb�������������������������������ýÎ~7Ů8Yľ„�ÄËJ84ÜcPČąëlbĐźĄBâŔ(ŰŘ,·âŠĺ$ł¦ÂĐ›. üdĂĆy/”cův€~ý—:lϬ™Ńk8Ĺuoć”HȆ¨$Ĺ ö˙óŞÁžą˝Ď�����������������������������ýÎuąáiH��Z„®0rea�X¸¶(cp˛KÓëćtŹ#)s:.ĆhëřL¬^ł)Z Km“IJyś’c#Qďln2t6]ň~’]¦NĘ „*đ<´o1X¨ŔźÇŤGSz��������������������������������������ýÎuąŢ��B“¬<•@ ľÁq] ŘÔÜ‹ć$"93Ë’zlć3Ĺ—ŕ”S©Űď“á˙ąz)ĺLżĄě. ›&ukbgÄ÷Ę_:`…HňcĽď0D64úb‘˙ďŕ 9ű!r�������������������������������ýÎuą(‘ž„*�B“˘Ś«Š:�>nmcfFU)¸¸đ“‡0ŮëŢľŐŃřÚťť·•ęP[©+T˛űëĽý2š\ÍüÓSś ÂG.WąčX©;·ĺUFŔhkôo†FŘ/÷ŕÂu cĘ:����������������������������ýÎ7Ů–Ţ8�€š• ›0Řd7•\´×vÎ}ií˝E«MÝ M3‹Š"äđşĎ~�µ˝bł¶#Ah©'ňR‚ŃÖ&y,]~ŹMG“-9¶#8…ţůäëĽ#s/y����������������������������������ýÎ7Ů8Qľ1�•Ö%8�€iup;š¤_I5…ŔÖ˙?»ŘÄ&ňҵäduˇn±{˝ď…é!‰ź»:•¬Ď…úzĹU.25žBÉ–+Ęčq0ďîQ®*/CUë&^ܱé-��������������������������������ýÎ59q#} �„&ĺ68ZŔ€ôĐ»t3ü´WÜ™ohm™äP»-¨Y?Ç{ž°ęŽLXř8&MöŘ f%ŞO©›÷NÉ”‰ÍÍŻ®b˘ě˝›ěÎČPwˇëWI;–���������������������������������ýÎwŮ(–ž„*�hRdÁPL�ŹKşá?7śŹI—»BťŹÁ¦Ń|_O&±arúäşvQŕ$ąk×°d­Áĺ˛l­Lc/["°R#lcú,čVŔÝťŠ…«äŕHďbz¬Ű{8T�������������������������������������ýÎ7Ů8QŢ1�ÉJQ3=H�Đi—Ť ‚©ćĘě•ä¸óM6żÝRmöµ|fę «ö’ô<xQ[‚ Ł§Ý˝űjTż03Ѹů-ťao8*Ę.B¶ŘrI…Z™Řyůۙ�����������������������������ýÎ~5ąQľ1�ˇu‰8{,�U¦»k,ż,¦żW}IŤŠZGv›Ü ý±gěÉVöCA’ô—”4#ŤâwLqu;Ž‚óÄ~BH Iţ҉fDaďŚne›������������������������������������ýÎwŮ8–žHb -ŞˇŔě�@cŰweł¬fô“'ŹI»#ł:«/Ősâ§ LNao—Ĺ’üłęÎhrŻ‹K5«ŚámŃ {Ř–ÔnČ[oČcőQ˝‚€ď šî*§ťÚč˘ŢŢ���������������������������������ýÎ7ŮâŤ�ˇ˘`đ&@ ď~Ë˝r8zôľóG­řHä·5ż^yÚxńé\™JfŇŹ}ŔmťŁvŐľËôLa!9'ěbŰ>ŃZ*Ěü(NbÎŃŮ&ç˛����������������������������������������ýÎuą–ž�@-jIÁ�…&&č�h‹o\ś‰[˘�ĆüJx2ÝĎÚ ^Cí7^:«YGcF¦ňO}žÝşž‡žŹ™‰°±<É~ľ¬2Pâü"SÔމíőŞŔö™!Gć&ŰóS’‰ťÎI…¨��������������������������ýÎuą8QŢ(�BĚR¬H¦Đ�‡YB,’˛-oĹx3CT§XČčňßŃ| éČÓůĆű<Z>Ńł^^Ş„^‰)5=‚%G˝pďb€{fy](€>(Ö÷:;ubjŮ:kGG:�����������������������������OggS�D¬������ţ–ç=���‡ż˛¬‹‹‹‹‹‹ýÎ59 %˝ ‚��”. :�>Źţč]µ¤?÷TăĚxL€çsÔžM›PĎXšÁ„—+q!^#§ĂaâčŃ|S«ă.Ź„™ăµ•«Q®%(Îsnĺ¶©lő]”N-{~����������������������������������������ýÎwŮ8™ž0�B“b…qÄń–�X9vń)l}¬ĹĽŹb‘Id=!ű…t,W˘7Ĺć˙9±¸őń­Ű˙â® Rńçf©ÄŔoé<ďËÓ—·§ľUÚe±”׫üpő0Úé[(wÉÎ"…��������������������������ýÎuą8–Ţ„2�h–˘"hrđČ)=|éP›Ű"ýr:Iň"qŤö°Ě蹨šŻŕŽľRß"ŕ€"«•uŢ˝‰ ţąÚ‡żš.řAsń©Ďnµďn7…Ł'ňčeŽ~ ���������������������������������ýÎuąŢ0�B Í c@2 �°8čä>h»ď_6zyu—YĆü|$Ô•»ąŽëö¬ĘŔĹ…÷ńSěŮ n'E˛0!¸¶˛ö>~v3üayč™zź›9óMÔk ¦1×5„éW7ń#ŢežČi����������������������������ýÎwŮ8‘ž1�h–bŢFh‚�¦zmśňp4<Żĺ˛é*é´ ´ÚäÂśÉ"N¨„Şń|oâSđjl’ĚôřśxŽMqIß–Š'S´Î—WřKw®˛:&U0TĚn;Ť‘bbž ĆPČe�������������������������������������ýÎݞ 8��P‹<Üŕ0P8ťÓą­j� �x�z}]Ü���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������beetbox-beets-01f1faf/test/rsrc/empty.aiff����������������������������������������������������������0000664�0000000�0000000�00000254276�14723254774�0020704�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������FORM�X¶AIFFCOMM������¬D�@¬D������SSND�X�����������������˙˙�˙ţ�˙ţ�˙ţ�˙˙�˙ţ�˙ţ���˙ý�˙ü�˙ţ�����˙˙�˙ý�˙ţ�˙˙�˙˙���˙˙���˙ý�˙ű�˙ý�˙ţ���˙ü�˙ý���˙ţ�˙ý�˙˙�˙˙�˙˙�˙˙�˙˙���˙˙�˙˙�˙ţ���˙˙�˙ţ���˙˙�˙ţ�˙ţ����˙ţ�˙ţ��˙ţ�˙ţ�˙˙�������˙ţ�˙ü�˙ű�˙ý�˙˙���˙ţ�˙ţ�˙ţ�˙ţ�˙ţ�˙ţ�˙ý�˙˙�˙˙���˙˙�˙˙���˙ý�˙˙���˙ţ�˙˙���������˙ţ�˙ţ�˙ü�˙ý��˙ţ�˙ţ�˙ý�˙ý���˙˙�˙ţ���˙˙���˙˙�˙ţ�����˙˙�˙ţ���˙˙�˙ţ���˙˙�˙ţ���˙˙�˙ţ�˙˙˙˙�˙ţ���˙˙���˙˙���˙˙���˙˙���˙˙�˙ţ�˙˙���������˙˙�˙ý�˙ý�˙˙�����˙˙�˙˙�˙ý�˙ű�˙ü���˙˙�˙˙�˙˙�˙˙�˙˙�˙˙�˙˙���˙˙�˙˙���˙˙���˙˙�˙˙�����˙˙�˙ţ�˙ţ���������˙˙���˙˙�˙˙���˙˙�˙ţ�˙ü�˙ý�˙˙�������˙˙���������˙ţ�����������˙˙�˙ţ�˙˙�������˙ţ�˙ý�˙˙���˙˙�������˙ţ�˙ţ�˙ý�˙ţ�˙ţ�˙˙�����˙˙�������˙˙���˙˙�˙ý�˙ţ���˙˙���˙˙�˙˙���˙˙�˙˙���˙ý�˙ű�˙ţ�˙˙�˙ý�˙ţ�����˙˙�˙ü�˙ţ���˙ţ�˙ý�˙ü���˙˙�˙ý�˙ý�˙ý�˙˙���˙ţ���˙˙�˙ţ�˙˙���˙˙�˙ý�˙˙˙˙�˙ţ��˙ţ�˙ü�˙ý���˙ţ�˙˙���˙˙˙˙�˙˙������˙˙�˙˙��˙˙�˙ý�˙ú�˙ü�˙˙˙˙�˙ý�˙ű�˙ý��˙ý�˙˙������������˙˙�˙ţ�˙ţ�˙ţ�˙˙�˙ţ�˙ţ�˙ý�˙˙���˙ţ�˙ţ�˙˙�������˙˙���˙˙�˙ý�˙ý�˙ţ���˙ý�˙ţ���˙ţ�˙˙�˙ţ�˙ý�˙˙���˙ţ�˙˙���˙˙�˙˙�˙˙�˙ţ���˙˙�˙ţ���˙˙�����˙˙�˙ţ���˙˙�˙ţ�˙˙�˙˙�˙˙���˙ţ�˙ý�˙ţ�˙˙�˙˙�˙˙���˙˙�˙˙˙˙�˙ý�˙ý�˙˙���˙˙���˙˙�˙ý�˙˙���˙˙���˙ţ�˙ţ���˙˙���˙ý�˙ţ���˙˙���˙˙���˙˙���˙ţ���˙˙�˙˙���˙ý�˙˙���˙˙�˙ţ�˙ű�˙ú�˙ţ���˙˙���˙˙�˙ţ��˙ý�˙ý�˙ţ������˙ţ�˙˙�˙ţ�˙ý���˙ţ�˙ű�˙ý���˙˙�˙˙���˙ţ���˙ţ�˙ý���˙˙��˙ý�˙ý�˙ý�˙˙���˙ţ�˙ý�˙ţ���˙˙�˙ţ�˙ü�˙ý���˙˙�˙ţ�˙ý�˙ţ�˙˙�������˙ţ�˙ý�˙˙�˙˙�˙˙�����˙˙�˙˙�˙ý�˙ý�˙˙����������������������������������˙˙���˙˙�˙ý���˙ţ�˙ű�˙ü�˙˙���˙˙�˙ţ�˙ü�˙ű�˙ţ���˙˙�˙˙�˙ý�˙ű�˙ţ��˙ţ���˙ţ�˙ű�˙ű�˙˙�˙ţ�˙ü�˙ý�˙ţ�˙˙��������������˙˙�˙ţ�˙ý�˙ţ���˙˙�˙ţ�˙ý�˙ý��˙ţ��˙ţ�˙ţ�������˙˙�˙ý�˙ű�˙ý��˙ý�˙ü�˙ý�˙ţ�˙ţ�˙ţ�������˙˙���������˙˙�˙ţ���������˙˙�˙ţ�˙ţ�������˙˙�˙ý�˙ü�˙ţ���˙˙�˙ţ�˙ţ�˙˙�˙ţ�˙ţ�˙˙�˙˙�˙ţ���˙˙���˙˙�˙˙�˙ý�˙ü��˙ţ�˙ţ�˙ţ�˙ý�˙˙�������˙ţ���˙˙�˙ý�˙ý�˙ţ���˙ţ�˙ţ���˙˙���˙˙���˙˙�˙ý�˙ţ�˙˙�˙ý�˙ý�˙ţ���˙˙�˙˙���˙˙�˙˙���˙˙�˙˙�˙˙�˙˙�˙˙�˙ý�˙ý�˙ý�˙ý�˙ý�˙˙���������˙ţ���˙˙�˙˙˙˙�˙ü�˙ý�˙˙�˙ţ�˙ý�˙˙�����������˙ţ�˙ţ�˙˙���˙ţ�˙˙�����˙ţ�˙ý�˙ý�˙ý�˙ţ�˙˙�˙ţ�˙ý�˙ü�˙ű�˙ű�˙ý�˙ţ�˙˙���˙ţ�˙ý�˙ü�˙ý�˙ţ�����˙ý�˙ţ���˙ý�˙ý�˙ű�˙ü�˙ţ�˙ý�˙ű�˙ü�˙ü�˙ţ�˙ţ�˙ţ��˙ţ�˙ţ�˙ţ���˙˙�˙ţ���˙˙���˙ţ�˙ý�˙˙���˙˙���˙ü�˙ý���˙˙���˙ţ�˙ţ���˙˙�˙ţ�˙˙˙˙�˙ü�˙ý�˙˙˙˙�˙ü�˙ţ˙˙�˙˙˙˙�˙ý��˙ţ��˙ţ�˙ý���˙˙�˙ý�˙ý�˙ü�˙ý������˙ţ�˙ţ�˙˙�������˙ţ�˙ü�˙ü��˙ţ�˙ţ˙˙�˙ü�˙ű�˙ü�˙ű�˙ý��˙˙���������˙˙�����˙ţ�˙ű�˙ü���˙ţ�˙ü���˙˙�˙˙�����˙˙�˙˙�˙ţ�˙˙�˙˙�˙ý�˙ý�˙ţ���˙˙�������˙˙�˙˙���˙˙���˙ţ�˙ý�˙ţ�˙˙˙˙�˙ü�˙˙˙˙���˙˙�˙ţ�˙ý�˙ţ��˙ý�˙ü�˙ţ�����������������˙˙�˙ţ���˙˙�˙ţ��������˙ţ�˙˙���˙˙���˙˙�˙˙�˙˙�˙˙�˙ţ�˙ü�˙ý�˙ţ������˙ţ�˙ţ�˙ü�˙ű�˙ý���˙˙�˙ţ���˙˙�˙ü�˙ý�˙˙�����˙ţ�˙ţ�˙˙���˙ţ�˙˙���˙˙�������˙˙�˙ţ�˙ţ�˙ţ�˙ü�˙ű�˙ű�˙ü�˙ţ�˙˙�����������˙ţ�˙ţ�˙˙����˙˙��˙ý�˙ý�˙ţ���˙ţ�˙ű�˙ţ�˙˙�˙ţ�˙˙�˙˙���˙˙�˙ý�˙ţ�����˙˙�˙ý�˙ü�˙˙˙˙�˙ü�˙˙˙˙�˙ţ�˙ţ������˙ţ������˙˙��˙˙�˙ü�˙ű�˙ý�˙ý�˙˙���˙˙˙˙�˙ü�˙˙������˙˙��˙ţ�˙ţ��˙ţ�˙ţ�˙ţ�˙ţ�˙˙˙˙�˙ü�˙˙˙˙�˙ý���˙˙��˙ţ�˙ţ���˙ý�˙˙���˙˙�˙ţ�˙ý�˙˙���˙ţ�˙˙�˙˙�������˙˙�������˙ţ�˙ü�˙ý�˙˙�˙ţ�˙ý�˙˙�˙ţ�˙ţ�˙ý�˙˙���˙ţ�˙˙���˙ţ�˙˙���������˙ţ�˙ý�˙ü�˙ý��˙ţ�˙˙�˙ţ�˙ý�˙ý�˙ý�˙ü�˙˙��˙ý�˙ü�˙ü�˙ý�˙ţ���˙˙���˙˙�˙˙˙˙�˙ţ�˙˙�������˙ţ�˙ţ������˙ţ�˙ţ���˙ţ�˙ţ���˙ţ�˙ţ�˙ţ���˙˙���˙˙�˙˙�˙˙�˙ü�˙ţ���˙˙�˙˙���˙˙���˙ţ�˙ü�˙ţ�˙ý�˙ű�˙ü�˙ţ���˙ý�˙ţ�����˙˙�˙ý�˙ţ���˙˙�����������˙ţ��˙ţ�˙˙���˙ţ�˙˙���˙ţ�˙ü�˙ü�˙ü�˙˙˙ţ�˙ü�˙˙˙˙�˙˙���˙ţ�˙˙������������˙˙�˙ţ�˙ţ���˙˙�˙ý�˙ü�˙ţ���˙ţ�����˙˙�˙ý�˙ţ�˙ţ�˙˙������������������˙ţ��˙ţ�˙ü�˙˙���˙ţ�˙˙�˙ţ�˙ű�˙ú�˙ý��˙˙�˙ţ�˙˙�˙˙�˙˙���˙ý�˙ţ���˙ý�˙ý�˙ţ�˙ţ���˙ţ�˙ţ�����˙˙�˙ţ�˙ţ���˙ý�˙ü�˙˙���˙ý�˙ý���˙˙�˙˙�˙˙���˙˙�˙˙�����˙˙���˙ţ�˙ý�˙ý�˙ţ�˙˙�˙ţ�˙ţ���˙ţ���˙˙���˙ţ�˙ý���˙ý�˙ü�˙ý�����˙˙�˙ţ���˙˙���˙˙�˙˙���˙˙�˙˙�˙˙�˙˙�˙˙�˙˙���˙˙�˙˙�˙˙�˙ý�˙ý���˙˙�˙ý�˙ţ���˙˙�˙ţ�˙ý�˙ţ��˙ý�˙ü��˙ţ�˙˙˙˙�˙˙�����˙ţ�˙˙˙˙�˙ü�˙˙���������������������������˙ţ�˙ţ�˙˙�˙ţ�˙ţ�˙˙���˙ţ�˙˙���˙ý�˙˙���˙˙���˙ţ�˙ţ�˙˙�˙ţ�˙˙�˙˙�����˙˙���˙ţ�˙ý�˙˙���˙ţ�˙ţ���������˙˙�˙ţ�˙˙�˙ţ�˙ţ�˙ý�˙ţ�˙ý�˙ţ��˙ţ�˙ý�˙ü�˙ü�˙ţ�˙˙����������˙˙�˙ţ�˙ý�˙ţ�˙ţ�˙ţ��˙ţ�˙˙˙˙�˙ü�˙ü�˙ý�˙ţ�˙ţ�˙ţ���������˙˙��˙ý�˙ů�˙ű��˙ý�˙ţ��˙ţ�˙ţ�����������˙˙���˙˙�˙ü�˙ţ��˙ţ�˙ţ������˙ţ������˙ţ�˙ţ�˙ţ�˙ţ�����˙˙�˙ý�˙˙˙˙�˙ţ��˙ţ�˙˙˙˙�˙˙˙˙�˙ţ���˙˙�˙˙�˙˙�˙˙���˙ý�˙ý���˙˙�˙˙�˙˙�˙˙�˙˙���˙˙�˙ţ�˙ţ�˙ý��������˙ţ�˙˙˙˙�˙ţ�˙ü�˙ţ�����˙˙���˙ţ�˙ű�˙ý�˙ý�˙ý�˙ý�˙ţ�˙ţ�˙˙�˙˙˙˙�˙ţ�����������˙˙�˙ü�˙ü�˙ý��˙ý�˙ű�˙˙�����˙ý�˙ý�˙ţ�˙ý�˙˙˙˙�˙ű�˙ý��˙ý�˙ý��˙ý�˙ü�˙˙��˙˙�˙ţ�˙˙˙˙��˙ţ�˙˙˙˙���������˙˙�˙ţ�˙˙�˙˙����˙˙�˙˙˙˙�˙ü�˙˙˙˙�˙ţ�˙ţ���˙˙�˙ü�˙ţ��˙ţ��˙ţ�˙ţ��˙ţ�˙ţ�˙ţ�˙˙���������˙ţ�˙ţ���˙˙�˙ţ�˙ý�˙ü�˙ü�˙ţ�˙ţ�˙ţ�˙ţ�˙ü�˙ý�˙ţ�˙˙���˙ţ�˙˙�˙˙�˙˙�˙˙�˙˙�˙˙�˙ý�˙ţ���˙ý�˙ţ�˙˙���˙˙�˙ţ���˙˙�˙˙�˙ý�˙ý�˙ţ���˙˙���������˙˙�˙ţ���˙˙�˙ţ���˙˙���˙˙�˙ţ���˙ţ�˙ţ�˙˙�˙ţ�˙ý���˙ţ�˙ý�˙ý�˙ü�˙ţ˙˙�˙ü�˙ü�˙ü�˙ý��˙ý�˙ü�˙ü�˙ţ�˙˙�˙ý�˙ű�˙ű�˙ü���˙ţ�˙ü�˙ţ�������˙˙�˙˙���˙ţ�˙˙�˙˙��˙˙�˙ţ�˙ţ���������˙˙�˙ţ�˙ý������˙˙˙˙�˙ţ�˙˙˙˙�˙ţ��������������������˙˙�����˙ţ�˙ý�˙˙�˙˙�˙˙�˙˙�˙˙�˙˙���˙ţ�˙ý��˙ţ�˙ý�˙ţ�˙ţ�˙ţ�˙˙���˙ţ�˙ţ�˙ţ�����˙ţ�˙ú�˙ű�˙ü�˙˙�˙˙�˙ţ����������������˙ţ�˙˙�������˙˙�˙˙���˙˙�˙˙�˙ţ�˙ý�˙˙���˙ţ�˙ţ�˙ţ���������˙˙�˙ţ�˙ţ���˙˙�˙ý�˙ţ���˙ü�˙ü���˙ţ�˙ý�˙˙���˙˙�˙˙�������˙˙�˙˙���˙ý�˙ţ���˙˙�����˙˙���˙˙�˙˙���˙˙�˙˙���˙˙���˙˙���˙˙�˙ţ���˙ý�˙ü�˙ý�˙ű�˙ü�˙˙˙˙���˙ţ�˙ţ�������˙˙�˙˙�˙˙�˙˙�˙˙�˙˙�˙ţ�˙ţ�˙˙�˙ţ�˙ű�˙ţ���˙ý�˙˙���˙˙�˙˙���˙ţ�˙˙�˙˙���˙ţ�˙ţ���˙˙���˙˙���˙ţ���˙ţ�˙ý�˙˙�˙˙�˙˙�������˙˙��˙˙�˙ý�˙ý��˙˙�˙˙�˙ţ�˙ý�˙ţ���������˙˙�˙ţ�˙ţ���˙˙�˙ţ�˙ţ�˙ţ�˙˙���˙˙�˙˙�˙˙�˙˙�˙˙�˙˙���˙˙�˙˙�˙˙�˙˙�˙˙���˙ţ�˙ý�˙˙�����˙ţ�˙ţ�˙˙�˙ţ�˙ü�˙ü�˙ţ�˙˙�˙ţ�˙˙˙˙�˙ţ���������˙˙�˙˙˙˙�˙ţ�˙˙�����˙ţ�˙˙���˙˙�����˙˙�˙˙�����˙˙�˙ţ�˙ý�˙ý�˙˙���˙ţ�˙ý�˙ý�˙˙���˙˙�˙˙�˙˙�˙˙�˙˙�˙ţ�˙ý�˙ţ���˙˙���˙ţ�˙ţ���˙˙�˙ţ���˙˙�˙ţ���˙˙���˙˙���˙˙�˙ý�˙ý�˙ý���˙ţ�˙ü�˙ű�˙ý���˙ţ�˙ý���˙˙�˙˙���˙˙�˙ý�˙˙�˙˙�˙ţ�˙˙������˙˙�˙ü�˙ý����˙ţ�˙ý�˙˙�˙˙�˙ţ��˙ý�˙ü�˙ý������˙ţ�˙ţ�˙˙���������˙ţ�˙ü�˙ű�˙ű�˙ţ���˙˙�˙ţ�˙˙���˙ţ�˙˙����˙˙�˙ţ�˙ţ���˙˙�˙ü�˙˙�����˙ý�˙ü�˙ţ�˙ţ���˙˙�����˙˙�˙ý�˙ţ���˙˙�˙ţ���˙ý�˙ţ���˙ţ�˙˙�˙˙�˙ţ���˙˙�˙ţ�˙ţ�������������˙˙�˙ü�˙ü�˙˙�˙ţ�˙ţ�˙˙���˙ţ�˙˙���˙ţ�˙˙���������˙ţ�˙ţ�˙ţ��˙ţ�˙ţ�������˙˙�˙ü�˙ý��˙ţ�˙˙˙˙�˙ţ���˙˙���˙˙���˙˙�˙ţ���˙˙�˙ţ�˙ý�˙ü�˙˙˙˙�˙ţ���˙˙���˙˙�˙ţ��������˙ţ��˙ţ�˙ţ�����˙˙�˙˙�˙ţ�˙ý�˙ţ�˙˙�������˙ţ�˙˙�˙ţ�˙ý�˙ý�˙ţ����������˙˙���˙˙���˙˙���˙˙���˙˙���˙˙�˙ţ������������˙˙���˙˙�˙˙�˙˙�˙ý�˙˙�˙˙�˙ţ�˙˙���˙ţ�˙ý�˙˙˙˙�˙˙�˙ţ���˙˙�˙ý�˙ý�˙˙˙˙���˙˙�˙˙�����˙˙�˙ţ��˙ţ�˙ţ�������˙˙�˙ý�˙ű�˙ţ�˙˙�˙ţ�˙ý�˙ü�˙ý�˙ţ���˙ţ�˙ţ���˙˙���˙ý�˙ű�˙ű�˙ţ�˙˙���˙˙�˙ţ�˙˙�˙˙�������˙˙���������˙ţ������������������������������˙ý�˙ü�˙ţ�˙ţ�˙ý�˙ţ�˙ţ�˙ţ�˙ţ�˙˙�˙ţ�˙ţ�˙ý�˙˙�������˙ţ�˙ü�˙ý�˙ý���˙ţ�˙ű�˙ü�˙ý���˙˙�˙˙���˙ý�˙˙�˙˙�˙ý�˙ţ������������˙ţ�˙ü�˙ý�˙ý�˙ý�˙ý�˙ţ�˙ţ���˙˙�˙ţ�˙˙�˙˙���˙ý�˙ű�˙ţ���˙˙���˙˙�˙ţ�˙ý�˙ţ���˙˙�����˙˙�˙˙���˙˙���˙˙�˙˙���˙ý�˙˙�˙˙�˙˙���˙˙�����˙˙�˙ý�˙ü�˙ţ�������˙˙�������˙˙���˙˙�˙˙���˙˙���˙˙���˙˙�˙ţ���˙˙���˙˙���˙ţ�˙ű�˙ü�˙ţ�������������������������˙˙�˙ü�˙ü�˙˙˙˙�˙ţ�˙ţ�˙˙�˙ý�˙ý�˙ţ���˙ţ�˙ý�˙ý�˙ý�˙ţ�������������������������˙˙�˙ţ�˙ý�˙ţ��˙ü�˙ü���˙ţ�˙ű�˙ţ���˙ý�˙ţ���˙˙�˙ţ�˙ţ�˙ţ�˙ţ�˙˙�˙ţ�˙ţ�˙ý�˙ý�˙˙�˙˙�˙ţ�˙ţ�˙ü�˙ţ���˙˙���˙˙�˙ţ�˙ţ�˙ţ�˙ý�˙˙�˙˙�˙˙���˙ţ�˙ţ���˙ţ�˙ý���˙ţ�˙˙���˙˙�����˙ý�˙ý���˙˙�˙˙�˙˙�˙ý�˙ţ�����˙˙�˙ţ�˙ţ�˙ý�˙ü�˙˙˙˙�˙ţ��˙ţ�˙ţ��˙˙���˙ý�˙ý�˙ţ�˙ţ���˙˙���˙˙�˙˙�����˙˙�˙ü�˙ü���˙˙���˙˙�˙˙���������˙˙�˙ţ�˙ţ�˙ţ�˙ţ�˙ţ�˙˙˙˙�˙ý�˙ţ�˙˙���˙ţ�˙ţ�����˙˙�˙ý�˙ű�˙ý�����������˙˙�˙ţ�˙ý�˙ý���˙˙�˙˙���˙˙�˙ý�˙ţ�����˙˙�˙ý�˙ţ���˙˙�˙˙�˙˙�˙˙�˙˙�˙˙���˙ţ�˙ü�˙ý�˙˙���˙˙�˙˙�˙˙���˙ý�˙ý�˙˙�˙˙���˙ţ�˙ý�˙˙���˙˙���˙˙�˙˙�˙˙���˙ţ�˙ü���˙ţ�˙ţ���˙˙�˙˙�˙ţ�˙ţ���˙˙�˙ţ�˙ţ�����������˙˙�˙ţ���˙˙�˙˙�˙˙�˙˙�˙ţ�˙ý�˙˙�����˙ţ�˙˙���˙˙���˙˙�˙˙���˙ţ�˙ý�˙˙�����˙ţ�˙ü�˙ý��˙ţ�˙˙�������˙ţ�˙ý�˙˙���˙ţ�˙ý�˙ý�˙ţ�˙ű�˙ű�˙ţ�˙˙�˙˙���˙ţ�˙ý�˙ţ�����˙˙���˙˙�˙ý�˙ţ���˙˙�˙ţ�������������˙˙�˙ţ�˙ţ�˙˙�˙ţ�˙ý�˙ţ���˙˙�����˙˙���������˙˙�˙ţ�˙˙����˙˙�˙˙˙˙�˙ţ�˙ţ�˙ý�˙ü�˙ü�˙ü�˙ý�˙˙�������˙˙��������������������˙˙�˙ţ��˙ý�˙ţ�����˙˙�˙ý�˙˙˙˙�˙ý��˙ý�˙ü��˙ý�˙ţ�����˙ţ�˙ý�˙ţ���˙˙���˙ü�˙ý���˙ţ�˙˙���˙˙���˙˙���˙ţ�˙˙���˙˙�������˙˙�˙ţ�˙˙���˙˙���˙ţ�˙˙���˙ţ�˙ü�˙ű�˙ý�˙˙�˙˙���˙˙���˙˙���˙ţ�˙ý�˙ý�˙ý�˙ţ�˙ţ���˙˙�˙ţ���˙˙�����˙˙�˙ţ��˙ţ�˙˙���˙˙���˙ţ�˙˙���˙ţ�˙˙���˙ţ�˙ý�˙˙�˙ţ�˙ü�˙ü�˙˙�˙˙�˙˙�˙˙�˙ý�˙ţ���˙ý�˙ý�˙ţ�˙˙�˙˙���˙˙�˙ţ���˙˙�˙ý�˙ý�˙ý�˙˙�˙ý�˙˙���˙˙�����˙˙�˙˙�˙˙�˙ţ�˙ü�˙ü�˙ü�˙ü�˙ü�˙ü�˙ý�˙˙�����˙ţ�˙˙�˙ţ�˙ţ��˙ţ�˙ţ�˙ţ�˙˙���˙ţ�˙˙���˙˙����˙˙�˙˙˙˙�˙ý�˙ţ�����˙˙�˙˙�˙˙�˙˙���˙˙���˙˙�˙˙�����˙˙�˙ţ�˙ü�˙ü���˙˙�˙˙�˙˙�˙ý�˙ţ�˙˙�˙˙���˙˙�˙ý�˙ţ���˙˙�˙ţ�˙ţ���˙ţ�˙ţ�����˙˙�˙ý�˙ü���˙ţ�˙ű�˙ü�˙ţ���˙ţ�˙ţ���������˙˙�˙˙������˙˙�˙ü�˙ü�˙ý��˙ţ�˙ţ����˙ţ�˙˙�˙ţ�˙ţ��˙ý�˙ú�˙ý�˙˙�������������˙ţ�˙˙���˙ţ�˙ý�˙˙�˙˙�˙ţ�˙ţ���˙ţ�˙ý�˙ý�˙ý�˙ţ�˙˙�˙ţ�˙ţ�˙ţ���˙˙�˙ý�˙ý���˙˙�˙˙���˙˙���˙ü�˙ű�˙ý�˙˙���˙˙���˙ţ�˙˙���˙˙�˙ţ�˙ý�˙˙�˙˙���˙˙�˙˙���˙˙�˙˙�˙˙�˙˙�˙˙�˙ý�˙ţ���˙ý�˙ţ���˙ý�˙ţ���˙ý�˙ţ�˙˙�˙ţ�˙ţ�˙˙���˙˙�˙ý�˙ý�˙ţ���˙ý�˙ţ���˙ţ�˙˙���˙˙�˙ý�˙ý�˙˙�˙˙�˙ţ�˙˙���˙ý�˙˙�˙˙�˙ý�˙ţ�˙˙�˙ý�˙ţ���˙ý�˙ţ�˙˙�˙ý�˙ţ���˙ţ���˙ü�˙ü�˙ţ������������˙ţ�˙ü�˙ý�˙˙˙˙�˙ý�˙˙˙˙�˙ý�˙˙���˙˙���˙ţ�˙ţ���˙˙���˙ý�˙ý�˙ý�˙ţ���˙˙�˙˙�˙ţ���˙˙�˙˙�����˙˙���˙ý�˙ű�˙ü�˙ţ�˙˙�˙ţ���˙ţ�˙ű�˙ű�˙ţ�������˙ţ�˙ü�˙ý�˙˙���˙ţ�˙ü�˙ý�˙˙�������˙ţ�˙ü�˙ý��˙ţ�˙˙�����������������˙ý�˙ţ��˙ý�˙ü�˙ü�˙ü�˙˙�����˙ý�˙ü�˙ţ˙˙�˙ý�˙ý�˙˙�˙˙�˙˙�˙˙���˙˙�˙˙���˙ţ�˙ü�˙ý�˙ţ�˙ţ�˙ţ�˙˙�����������˙ţ�˙ţ���������˙˙�˙ý�˙ý��������˙ţ�˙ţ�˙˙˙˙�˙ý���˙˙�˙˙����˙˙�˙ţ�˙ţ�˙ţ�˙˙˙˙�˙ü�˙˙��˙˙�˙ý�˙ţ���˙ţ���˙˙�˙ţ�˙ţ���˙˙�˙ű�˙ü���˙˙�˙˙�˙˙�˙˙�˙ţ�˙ü�˙ű�˙ý�˙˙�˙˙���˙˙�˙˙���˙˙���˙ý�˙ű�˙ý����˙ţ�˙ţ��˙ý�˙ü�˙ţ���������˙˙�˙˙���˙ţ�˙ţ�˙ü�˙ý�˙˙�������˙˙�˙˙�˙ţ�˙ű�˙ý���˙˙���˙˙�˙˙���˙˙�˙˙���˙ţ�˙ý�˙˙���˙ţ�˙˙���˙ţ�˙ţ�˙˙�˙ţ�˙˙˙˙�˙ý���˙ţ�˙˙�˙ţ�˙ý�˙˙˙˙�˙ţ�˙ţ�����˙˙�˙˙�����˙˙�˙˙�˙ţ���˙˙�˙ý�˙ţ�˙˙�˙ý�˙ű�˙ü�˙ţ�˙˙�˙˙�˙˙���˙˙�˙ţ��������˙ţ���������˙˙���˙˙�˙˙���˙ţ�˙ý���˙ţ�˙ý�˙˙���˙˙���˙ţ�˙ţ�˙ý�˙ţ����˙ţ�˙˙˙˙�˙ü�˙ý����˙ţ�˙ü�˙ü�˙ű�˙ţ���˙˙���˙˙�˙˙�˙˙�˙˙���˙˙�˙˙���˙ý�˙ţ�˙ý�˙˙���˙˙�����˙˙�˙˙�˙˙�˙˙�˙ý�˙ü���������˙ţ�˙ţ�˙˙���˙˙������������������˙˙���˙˙�˙˙�˙˙�˙ţ�˙ţ���˙ý���˙ţ���˙ţ�˙ü�˙˙���˙ţ�˙˙�������˙˙�������˙ţ�˙˙�����˙ţ�˙ü�˙ţ���˙ţ�˙˙������������˙˙�˙ţ�˙˙�����˙˙���˙ţ�˙ý�˙ţ�˙ü�˙ý�˙˙�����˙˙�����˙˙�˙ţ�˙˙�˙˙�������˙˙�˙ţ�˙ü�˙ý�˙˙�������˙ý�˙ű�˙ý�˙˙�˙˙�˙˙�˙˙���˙˙�˙ţ�����˙˙�˙˙�����������˙ţ�˙˙���������˙ţ�˙ý�˙ý�˙ţ�����˙˙�˙ü�˙ü�˙˙���˙ţ�˙˙���˙ţ�˙ţ�˙ţ�˙ţ�˙ţ�˙ţ�˙ţ�˙ţ���˙˙�˙ý�˙ţ���˙ţ�˙ý�˙ý�˙˙���˙ü�˙ü�˙ţ���˙ţ���˙˙���˙˙�˙ţ�˙ţ�˙ţ�˙ý�˙˙�˙˙�˙˙���˙ţ�˙ţ���˙ţ���˙ţ�˙˙���˙˙���˙ý�˙˙�˙˙�˙ü�˙ý���˙ţ�˙˙�˙ţ�˙ü�˙ţ���˙ţ�˙˙���˙ţ�˙˙������������˙˙�˙ý�˙ţ���˙˙�˙˙�˙ţ�˙ţ�������������˙˙�˙ţ�˙ţ�˙ţ�˙ţ�˙ţ�˙˙���������˙˙���˙ţ�˙ţ�˙˙����˙˙�˙ţ�˙ţ�����������˙˙�˙ü�˙ý����������˙ţ�˙ţ�˙˙�˙˙�˙˙�˙˙�����˙˙���˙˙�˙ţ���˙˙�˙ţ�˙˙���˙˙�˙ý�˙ţ���˙ţ���˙˙�˙˙�˙˙�˙˙���˙ţ�˙ţ�����˙˙�˙˙�˙ţ�˙˙�����˙˙�˙˙�˙˙�˙˙�˙ţ�˙ý�˙ý�˙˙�˙˙�˙ţ�˙ţ���˙˙���˙˙�����˙˙�˙˙���˙˙�˙˙�������˙˙�˙˙�����˙˙�˙˙�˙˙�˙˙���˙˙�˙˙���˙˙�˙˙�����˙˙�˙ţ���������˙˙���˙˙�˙ý�˙ţ���˙ý�˙ţ���˙˙���˙˙�˙˙���˙˙�˙ý�˙ü�˙ţ�����˙˙���˙˙�˙ţ���˙ý�˙ţ�����˙ţ�˙ţ��˙ý�˙ţ�����˙˙�˙ý�˙ü�˙ţ���˙˙�˙ý�˙ţ���˙ţ�˙˙�˙˙�˙ţ���˙˙�˙ţ�������˙˙���˙˙�˙ý���˙˙�˙˙���˙˙���˙ţ�˙ü�˙ű�˙ü��˙˙�˙ţ���˙˙�˙˙���˙˙�˙˙�����˙˙�˙ţ��˙ţ��˙ý�˙ü�˙ü�˙ţ����˙ý�˙ű�˙ú�˙ü�˙˙˙˙�˙˙�����˙˙�˙ý�˙ţ�����˙˙�˙ţ��˙ý�˙ú�˙ű���˙ţ�˙ý���˙˙�˙˙���˙˙���˙ţ�˙˙���˙ţ�˙˙���������˙ţ�˙ţ�˙˙���˙ţ�˙ţ�˙˙����˙˙�������˙˙���˙˙�˙ţ��˙ţ�˙ţ��˙˙���������˙ý�˙ü�˙ý������˙ţ�˙ü�˙ý�˙˙�˙ţ�˙˙���˙ţ��˙ţ�˙ţ�˙˙���˙˙���˙ý�˙ţ�˙˙�˙˙���˙˙���˙˙˙˙�˙ú�˙ý˙˙�˙ý���˙ţ�˙˙�˙˙�����˙ţ�˙ű�˙ů�˙ý��˙ţ�˙ţ�˙˙�������˙ţ�˙˙���˙ţ�˙˙�����˙ţ�˙˙˙˙�˙ý��˙ţ�˙˙���˙˙���˙˙���˙˙���˙˙���˙˙�˙ý���˙˙�˙ý�˙ý�˙ý�˙ű�˙ű�˙ţ�˙˙�˙ý�˙ţ�����˙˙���˙˙�˙˙�����˙ţ�˙ţ��˙ý�˙ü�˙ý�˙˙�����˙ý�˙˙�����˙˙���˙ţ�˙ţ���˙˙�˙˙�˙˙�˙ý�˙˙���˙˙�˙˙���˙˙�˙ţ�������������˙˙�˙ţ�˙ţ���������˙˙�������˙˙�����˙˙�˙ţ�˙ţ�˙ţ�˙ý�˙ţ���˙ý�˙ý�˙ţ�˙˙�˙˙�˙˙�˙˙�˙˙�˙˙�˙˙���˙˙�˙ý�˙ţ���˙˙�˙ý�˙ü�˙ţ�����˙˙�˙ý�˙ű�˙ű�˙ţ���˙˙��˙˙�˙ţ�˙ţ��˙ţ�˙˙˙˙�˙ţ�˙˙˙˙�˙ý�˙ý�˙ţ�˙ţ���˙ţ�˙ţ�˙˙�˙ţ��˙ţ�˙˙˙˙�˙ü�˙ţ�����˙˙�˙ţ�˙ţ����˙ţ�˙ţ�˙˙�˙˙���˙ü�˙ú�˙˙˙ţ�˙ű�˙ű�˙ú�˙ý���˙ţ���˙˙�˙˙�˙˙�˙˙�˙˙�˙˙���˙˙�˙˙���˙˙�˙ţ�˙ý�˙ý�˙˙���˙˙�˙˙�����˙˙�˙˙���˙ţ�˙ý�˙˙���˙ţ�˙ü�˙ý�˙ţ���˙˙���˙˙�˙˙�˙ţ�˙ý��˙ţ�˙˙˙˙�˙˙���˙ţ�˙˙��������������˙˙�˙ţ���˙˙�˙ţ�˙ý�˙˙˙˙�˙ţ�˙ţ�˙˙�˙ţ�˙ý�˙ţ�˙˙���˙˙�˙ý�˙ţ�����˙˙���˙˙���˙ţ�˙ű�˙ţ���˙˙�˙ţ�����˙˙�˙ţ�˙ţ���˙˙�˙ü�˙ý���˙˙�˙ü�˙ü�˙ţ�˙ţ�˙ţ�˙˙˙˙�˙ţ���˙˙�˙ţ�˙˙�����˙˙�˙ý�˙ţ�����˙˙���˙˙�˙ţ���˙ţ���˙˙�������˙˙�˙ý�˙ü��˙ý�˙ü���������˙˙�˙ţ�˙ý�˙ţ���˙˙�˙˙���˙ý�˙ý�˙˙�˙˙�˙˙���˙ţ�˙ý�˙ü�˙ý���˙˙�˙ý�˙ţ���˙˙�����˙˙�˙ý�˙ţ���˙˙�˙ý�˙ţ�˙ţ���˙˙�˙ü�˙˙˙˙���˙˙�˙ţ���˙ý�˙ţ���˙ý�˙ý���˙˙�˙˙���˙˙�˙˙���˙ţ�˙ý���˙ţ�˙ý�˙ţ�˙˙�˙˙�˙ţ�˙˙�˙ţ�˙ţ���˙ţ�˙ţ���������˙˙�˙˙˙˙�˙ţ�˙ţ������˙ţ��˙ţ�˙ü�˙ü�˙ý�˙˙˙˙�˙ţ�˙ý�˙ţ��˙ţ�˙˙���˙ý�˙ű�˙ü�˙˙�˙ý�˙ţ���˙ý���˙˙�˙˙���˙˙������˙ţ��˙ţ�˙˙����������˙˙�˙˙�˙ü�˙ý�˙ţ���������˙˙�����������˙˙�˙ţ�˙˙���������˙ţ�˙ü�˙ý���˙ţ�˙ţ�˙˙�˙˙�������˙˙�˙ţ�˙ţ�����˙˙�˙ý�˙ţ�˙˙�˙ţ���˙˙�˙ý�˙ý�˙ţ���˙˙���˙˙���˙ţ�˙˙���˙ý�˙ţ��˙ý�˙˙���˙˙�˙˙�˙˙�����˙˙�˙˙�˙ţ�˙˙�˙ý�˙ü�˙ţ�������˙˙�˙ţ���˙˙�˙ý�˙ü�˙ţ�˙˙�˙ý�˙ţ���˙˙�����˙˙�˙ý�˙ű�˙ţ���˙ü�˙ý�˙˙�������˙ţ�˙ţ�˙˙���˙ţ�˙˙�����˙ţ�˙˙�����˙ţ�˙ý�˙ü�˙ý�˙ý�˙˙���˙˙�˙˙�˙ţ�˙˙���˙ţ�˙ţ�˙˙�����˙ţ�˙ü�˙ý�˙˙�˙ţ�˙˙���˙ţ�˙ţ�˙˙˙˙�˙ü�˙ý�˙˙���˙˙�˙ţ�˙ţ�˙ý�˙˙˙˙�˙ţ�˙˙�������������˙ţ�˙ţ�˙ţ�˙ţ�˙ţ�˙ţ�˙ţ�˙˙˙˙�˙ţ��˙ţ�˙ţ��˙ţ�˙ţ�˙˙�������˙˙���˙˙���˙˙�����˙˙�˙ţ�˙˙�˙˙���˙˙�����˙˙���˙ţ�˙ý�˙˙�˙ţ�˙ý�˙˙�����˙ţ�˙ý�˙˙�����˙˙���˙ý�˙ű�˙˙���˙˙�˙˙�˙˙�����˙˙���˙ţ�˙ű�˙˙���˙˙���˙˙���˙˙���˙˙���˙ţ�˙ţ����˙ţ�˙˙���˙ţ�˙ţ�˙ü�˙ü�˙ý�˙ţ��˙ý�˙ü�˙ţ�˙˙�˙ý�˙ű�˙ü�˙ţ�˙˙�˙ţ���˙˙���˙˙�˙˙���˙˙�˙˙���˙ý�˙˙���˙˙�˙˙�˙˙�˙˙�˙ý�˙ý�˙˙���˙˙���������˙ţ�˙ţ�˙˙����˙˙�˙ý�˙ţ���˙ü�˙ü���˙˙�˙˙�˙ý�˙ţ���˙˙���˙˙�˙ý�˙ţ���˙ý�˙ü�˙˙�˙˙�˙˙���˙ţ�˙ţ�˙ý�˙ţ�˙˙�˙˙���˙ţ�˙ý�˙ü�˙ý�˙ý�˙ý�˙˙�˙˙�˙ý�˙˙�˙˙�˙ý�˙˙���˙˙���˙ţ�˙˙���˙˙���˙˙�˙˙�˙˙�˙˙���˙˙�����˙ţ�˙ü�˙ý�˙˙���˙ţ�˙ý�˙ý�˙˙�˙ţ�˙ţ���˙ü�˙ţ����˙ţ�����������˙˙�˙ý�˙ý�˙ý�˙ý���˙ţ�˙ü�˙˙˙˙�˙ý���˙˙�˙˙�����˙˙�˙˙���˙˙�˙˙���˙˙���˙˙�˙ţ�����˙˙�˙ţ���˙˙�˙ţ���˙˙�˙ţ�˙ţ���˙˙�˙ý�˙ţ�˙ý�˙˙��˙ý�˙ý�˙ţ���˙˙���˙˙�˙ţ�˙˙�˙ţ���˙ţ�˙ű�˙ü�˙ý�˙ý�˙ţ�˙˙���˙˙�˙˙�����˙˙�˙ţ�˙ţ�˙˙�˙ţ�˙ü�˙ü�˙˙�˙˙�˙˙���˙˙�˙˙���˙˙���˙˙�˙˙���˙ţ�˙˙�����˙ý�˙ý�˙˙�˙ý�˙ý�˙ţ���˙˙���˙˙�˙˙�����˙˙�˙˙�˙ţ�˙ţ��˙ţ�˙ü�˙ü�˙ý�˙˙˙˙���������˙˙�˙˙˙˙�˙ü�˙ý��˙ý�˙ü�˙ţ���˙˙���˙˙�˙ţ�˙ţ�˙ý�˙ţ�˙ý�˙ý���˙˙�˙ü�˙ţ���˙˙�˙˙�˙˙�˙˙�˙˙�˙ţ�˙˙���˙˙���˙˙���˙˙���˙˙���˙ü�˙ý�˙˙�����˙ţ�˙ţ�˙ţ�˙ü�˙ý�˙˙�˙˙�˙˙�˙˙�˙˙���˙ţ�˙ý�˙˙˙˙�˙ý�˙ý���˙ţ�˙ý�˙ţ�˙˙�˙˙���˙˙�˙˙���˙ţ�˙ü�˙ű�˙ý�����������˙˙�˙ţ�˙˙˙˙�˙ţ�˙˙��˙˙�˙ţ��˙ý�˙ü�˙ţ�˙ţ�˙˙˙˙�˙ţ�˙˙˙˙�˙ţ�����˙˙�˙˙�˙ţ�˙ý�˙ý��˙ţ�˙˙˙˙�˙ü�˙˙˙˙�˙ü�˙ţ��˙ţ���˙˙�˙ý�˙ţ�˙˙�˙˙�˙˙�����˙ţ�˙ü�˙ý�˙˙������������������˙˙��˙ý�˙ű�˙ţ���˙˙���˙ý�˙ý�˙˙˙˙�˙ţ�˙˙�������������˙ţ�˙ţ�˙˙�˙˙�������˙˙������˙˙�˙ţ�˙˙��˙˙�˙ţ�˙˙˙˙�������˙˙���������˙˙�˙˙���˙˙�˙ý�˙ţ�˙˙�˙ý�˙ü�˙ţ�˙˙�˙ý�˙ý�˙˙���˙˙�˙ţ�˙˙�˙˙�˙ţ�˙ý�˙˙���˙ü�˙ű�˙ü�˙˙���˙˙�˙˙���˙˙�˙˙�˙ý�˙ţ���˙ý���˙˙�˙˙�˙˙�˙ý�˙˙�˙˙�˙˙�����˙ţ�˙ü�˙ţ�����˙˙�˙˙���˙˙�˙ý�˙˙���˙ţ�˙˙���˙ű�˙ů�˙ü�˙˙�˙˙���˙ţ�˙ţ�˙˙�˙˙�˙˙���˙˙�˙˙�˙˙�˙˙�˙ţ�˙ţ�˙ţ���������˙˙���˙˙�˙˙�˙ý�˙ý�˙ţ�˙˙�����˙˙�˙˙�˙ţ���˙˙�˙ţ���˙ţ�˙˙˙ţ�˙ü�˙ý�˙ţ�˙ü�˙ý��˙ü�˙ý��˙ţ�˙˙˙˙�˙ü�˙˙˙˙�˙ü�˙˙˙˙�˙ű�˙ú�˙ű�˙˙˙˙�˙ţ�˙ţ���˙˙�˙ţ��˙ý�˙ü�˙ţ�����˙ţ�˙ý�˙ý�˙˙�˙˙�˙ţ�˙ţ���˙˙�˙˙�˙˙�˙˙�˙˙�˙˙�˙˙���˙˙�˙˙˙˙�˙ý�˙ý��������˙ţ��˙ţ��˙ý�˙ý�˙ţ�˙ţ��˙ý�˙ü�˙ý���˙˙�˙ý�˙ţ�˙ţ�˙ü�˙ü�˙ţ�����˙ţ�˙ý�˙˙���˙ţ�˙ü�˙ţ���˙ţ�˙˙�˙˙�˙ţ�˙ţ�˙˙�������˙˙���˙˙�˙ţ���˙˙�˙ţ�˙ţ�˙ţ�˙˙�����˙ţ�˙ţ�˙˙�����������˙ţ�˙ţ�˙ţ�˙ţ�˙˙����˙˙�˙˙������˙˙�˙ţ�˙ţ�˙˙���˙˙���˙ţ�˙ý�˙ţ��˙ţ�˙ţ���˙˙�˙ţ�˙˙���˙˙���˙˙�˙ý��˙ţ�˙ţ��˙ţ�˙ü�˙ý�˙˙�������˙˙�˙ţ�˙˙�˙˙�˙ţ�˙ý�˙˙�˙˙�˙˙���˙ţ���˙˙�˙ţ�˙ţ���������˙˙���������˙˙�˙ţ�˙˙˙˙�˙ţ���������˙˙���˙ý�˙ţ�˙˙���˙˙�˙ý�˙ţ���˙˙�˙ý�˙ý�˙ý�˙ý�˙˙���������˙ţ�˙ţ������˙ţ������˙˙���˙˙�˙˙�˙˙�˙ţ���˙˙�˙ţ�˙ţ�˙ý�˙ţ�˙˙�˙ţ�˙ţ���˙ţ�˙ý�˙ý�˙˙�˙˙�˙˙�˙ţ�˙ý�˙ţ���˙˙���˙˙�˙ý�˙ţ��˙ţ�˙ý�˙ţ�����˙˙�˙ý���˙˙�˙ţ�˙˙���˙ý�˙ü�˙˙�˙˙�˙ű�˙ű�˙ý�˙˙�˙˙�˙ý�˙ý�˙ý���˙˙�˙ý�˙ý�˙ü�˙ţ�˙ý�˙ű�˙ý�˙˙�˙ý�˙ů�˙ú�˙ü�˙˙�˙ţ�˙ţ���˙˙�˙ü�˙ţ���˙˙���˙˙���˙ţ�˙ţ�˙ţ��˙ý�˙ü�˙˙���˙ţ�˙ü�˙ý�˙˙���˙ţ�˙˙���˙ţ�˙˙�˙ţ�˙ý�˙ü�˙ý���˙ý�˙ý�������˙˙�˙ţ�˙ţ�˙˙�˙ţ���˙˙�˙ţ�˙ţ�˙ţ���������˙˙���˙˙���������˙˙�˙˙����������������˙˙���������˙˙�˙ţ���˙˙�˙ý�˙ü��������������˙ţ�˙ţ�˙ţ��˙ţ�˙ţ��˙ţ�˙˙���˙˙�����˙˙�˙˙�˙˙�˙ü�˙ü�˙ţ�����˙˙�˙ţ�˙ý�˙ý�˙˙���˙ţ�˙˙�˙˙���˙ţ�˙ý�˙ţ���˙ý�˙ţ���˙ű�˙ü�˙ţ������˙ţ�˙ţ�˙˙���˙ţ�˙˙���˙ţ�˙ý�˙ý�˙˙�˙˙�������˙ţ�˙ţ�˙˙˙˙�˙ţ�˙ţ�˙ţ���˙˙���˙˙�˙˙�˙˙�˙˙�˙˙���˙˙�˙˙�����˙˙�˙ý�˙ţ�˙˙�˙ţ�˙˙���˙ţ�˙ţ�����˙˙�˙˙���˙˙���˙ţ�˙ţ�˙ý�˙˙���˙˙���˙˙�˙˙�˙ţ�˙ý�˙˙���˙ţ�˙ý�˙˙�����˙˙�˙˙�˙ý�˙ţ���˙ý�˙ű�˙ű�˙˙˙˙�˙ý���˙˙�˙ý�˙ţ���˙ţ�˙ţ����˙˙�˙˙���˙˙�˙˙�˙˙���˙ţ�˙ý�˙˙���˙ţ�˙ý�˙ý�˙˙���˙ţ�˙˙�˙˙�˙ţ�˙ţ�˙˙�˙ţ�˙˙���˙ţ�˙ý�˙ţ��˙ţ�˙ţ��˙ţ�˙ţ�˙ţ�˙ţ�˙ţ�˙˙˙˙�˙ţ�˙˙�����������˙ţ�˙˙˙˙�˙ú�˙ű�˙ý�˙˙���˙˙�����˙ţ�˙ü�˙ý�˙ţ��˙ţ�˙ü�˙˙���˙ţ�˙˙���˙˙���˙˙�˙˙�˙˙�˙˙�˙˙�˙˙�˙˙�˙˙�˙˙�˙˙�˙˙���˙˙���˙˙�˙ţ����������˙ţ�˙ţ�˙ţ�˙˙�����˙˙�˙ý�˙ý�˙ţ���˙˙���˙˙���˙˙���˙˙�˙ţ���������˙˙��˙ţ�˙ţ���˙ţ�����˙˙�˙ţ����������˙ţ�˙ý��˙˙���˙ţ�˙ý�˙ý�˙ý�˙ţ�˙˙�˙˙�˙ý�˙ţ�˙˙���˙˙�˙ü�˙ý�˙ţ���˙˙�˙˙�˙˙���˙˙�����˙˙�˙ý�˙ţ�˙ţ���˙ţ�˙ü�˙˙˙˙�˙ţ�˙˙�����������˙ý�˙ü���������˙˙�˙˙�˙ý�˙ý�˙ţ�˙˙�˙˙�˙ý�˙ţ���˙˙�˙˙�˙ţ���˙ţ���˙ţ�˙˙���˙˙���˙˙�˙ţ�˙˙���˙ţ���������˙˙�����˙˙�˙ý�˙ý�˙ý�˙ý�˙ý�˙ý���˙˙���˙ţ�˙ü�˙ţ�������������˙˙�˙˙���˙ţ�˙ţ�˙˙���˙ţ�˙˙�˙˙���˙˙���˙ţ�˙ý�˙˙���˙˙�������˙˙�˙ţ�˙˙�˙˙�˙ţ�˙˙�˙ţ�˙ý���˙ţ�˙˙�˙˙���������˙ţ�˙ü�˙ý���˙˙�˙˙�˙˙�˙ţ�˙˙�����˙˙�˙˙�˙ý�˙ý�˙ţ�˙˙�˙˙�˙˙�˙˙�˙ţ�˙ü�˙ý�˙˙�������˙ţ�˙ţ��˙ţ�˙ţ��������˙ý�˙ţ�����˙˙�˙˙�˙˙�˙ý�˙ý�˙˙�˙˙�˙˙˙˙�˙ü�˙ü�˙˙�����˙˙���˙ý�˙ý��˙ţ�˙˙�������˙ţ�˙ü�˙ý�˙ý�˙ţ�˙ţ���˙ý�˙ű�˙ý��˙˙�˙ţ�˙ü�˙ű�˙ý�˙ý�˙˙���˙ţ�˙ý�˙˙�����������˙ţ���˙˙�˙ü�˙˙˙˙�˙ţ���������˙˙�˙˙�˙ţ�˙ý�˙ţ��������˙ţ��˙ţ�˙˙˙˙�˙ü�˙ü�˙ý�˙˙�����˙˙�˙˙���˙˙�˙˙�˙ţ�˙ü�˙ý�˙˙��˙˙�˙ţ�˙ţ���˙˙�˙ţ��˙ý�˙ý��˙ţ�˙ţ�˙˙���˙ţ���˙˙�˙˙���˙˙�˙ý�˙˙���˙ý�˙ü�˙ý�˙ý�˙ţ�˙ý�˙ý���˙˙�˙ý�˙ý�˙ţ�˙˙�˙˙�˙ý�˙ý��˙ý�˙ý���˙˙���˙˙�˙˙�������˙˙�˙˙���˙˙�˙ý�˙ý�˙˙���˙ţ�˙ţ��˙ţ�˙ţ�˙˙�������˙˙���˙ý�˙ý�˙˙�������˙ţ�˙ý�˙˙���˙˙�˙˙���˙˙�˙ü�˙ý�˙˙�˙ţ�˙ý��˙˙�˙ţ�˙˙���˙ţ��˙ţ�����˙˙�˙ý�˙˙˙˙�˙ţ�˙˙�˙ţ�˙ý�˙ţ���˙˙�����˙˙�˙˙�˙˙���˙˙�������˙˙˙˙����˙˙��˙˙�˙˙���˙ţ�˙ý�˙˙���˙ţ�˙ü�˙ű���˙ţ�˙˙���˙ý�˙ý���˙˙���˙˙�˙ý�˙ţ���˙ţ�˙ű�˙ű�˙ý�˙˙���˙ţ�˙ý�˙ţ�˙˙�˙˙���˙ý�˙ý�˙˙�˙˙�˙˙���˙˙�˙ý�˙˙���˙˙�˙˙�˙ý�˙ű�˙ü�˙ţ�˙˙�˙ţ�˙ţ������˙ţ�˙ţ�˙˙˙˙�˙ţ�˙˙˙˙�˙ű�˙ý���˙ű�˙ü�˙ţ�˙ý�˙ţ���˙ţ�˙ű�˙ü�������˙˙�˙ý�˙ý��˙ý�˙ý�����˙˙�˙ü�˙˙�������˙ţ�˙ý�˙˙�˙˙�˙˙�˙˙�˙ţ���˙˙�˙ţ�˙˙�˙˙���˙˙���˙ţ�˙˙���˙˙�������˙˙����������˙˙�˙ţ���˙˙���˙˙���˙˙���˙˙�˙ţ�˙ý�˙ţ���˙˙���˙˙�˙˙�˙ţ���˙˙�˙˙��˙˙�˙ý�˙ý�˙ţ��������˙ţ��������˙ý�˙ţ�˙ţ�˙ý�˙˙���˙ţ�˙˙�˙˙���˙˙�˙ü�˙ű�˙ú�˙ý�˙˙�˙˙���������˙ý�˙ü�˙ü�˙ţ�˙ţ���˙˙�˙ü�˙ţ��˙˙���������˙˙���˙˙���˙ţ�˙ý�˙ý�˙˙���˙ţ�˙ţ�˙ţ�����������˙˙�˙ü�˙ý�������������˙˙�˙ţ�˙˙˙˙�˙˙�˙˙˙˙���˙˙�˙ţ���˙ţ�˙ý�˙˙���˙˙���˙ţ�˙ý�˙ý�˙˙���˙ü�˙ý�˙˙�������˙˙˙˙�˙ü�˙ý���������˙˙�˙ţ�˙ţ�������˙˙�˙ţ���˙ţ�˙ű�˙ů�˙ű�˙ţ�˙ţ�˙˙�˙˙���˙˙���˙˙���˙ü�˙ţ˙˙�˙ý�˙˙�����˙˙���������˙ţ�˙ţ�˙ţ�˙ţ�˙ţ�˙ţ�˙˙���˙ţ�˙˙�˙˙���˙ţ�˙ý�˙˙�˙ţ�˙ţ�˙˙��˙˙���������˙˙�����������˙˙�˙ţ���˙˙�˙ţ�˙˙�˙ţ�˙ţ�˙˙���������˙ý�˙ů�˙ú�˙ţ�˙˙�˙ţ�˙ý�˙ý���˙ţ�˙ţ���˙˙�˙˙�˙ţ�˙ý�˙ţ�˙ý�˙ý�˙ţ���˙ü�˙ü���˙ţ�˙ţ���˙˙�˙ý�˙ü�˙˙˙˙�˙ý�˙ü�˙ü�˙ţ�˙ţ�˙ţ�˙ţ�˙ţ�˙ţ�˙ţ�����˙˙�˙ý�˙˙˙˙�˙˙�˙ţ�˙ţ�����˙˙�˙ţ�����˙˙�˙ţ�������˙˙�˙ţ�˙˙�����˙˙���˙ţ�˙ý�˙ý�˙ü�˙ű�˙ţ�����˙˙���������˙ý�˙ţ�˙ţ���˙˙���������˙˙��˙ý�˙ü�˙ţ���˙ţ�˙ý�˙ý�˙ţ�˙˙�˙ţ�˙˙���˙˙�����˙ţ�˙ű�˙ý�˙ý�˙ý�˙˙�˙ţ�˙ý�˙ý�˙˙���˙˙���˙˙�˙˙�˙˙���˙˙�˙ţ���˙˙�������˙˙���˙ţ�˙ţ���˙ü�˙˙˙˙�˙ü�˙ý�˙˙���˙˙�������˙˙�˙ţ�˙˙���˙˙�˙˙�˙ţ�˙ţ���˙ü�˙ü�˙ţ�˙˙�˙˙���˙˙�˙˙�˙˙�˙ý�˙ü�˙˙˙˙�˙ü�˙ţ�˙˙˙˙�˙ý�˙ü�˙˙˙˙�˙ţ�˙˙��˙˙�˙ý�˙ţ���������˙˙�˙˙�˙ţ�˙ý�˙ű�˙ú�˙ý�������˙˙�˙ţ�˙ý�˙ü�˙ü�˙ţ�˙ý�˙˙˙˙�˙ý�˙ţ���˙˙�˙ţ��˙ý�˙ü�˙ü�˙ţ��˙ý�˙ţ���˙˙���˙˙�˙˙�˙˙�˙˙�˙ţ�˙˙�˙˙���������˙˙�˙ý�˙ý���˙ţ�˙˙���˙˙���˙˙�˙ţ�˙˙�˙˙�������˙ţ�˙ţ�˙ţ�����˙˙�˙ű�˙ü�˙˙���������˙˙�˙˙�˙˙���˙˙�˙˙�˙ý�˙ý�˙ý�˙ý�˙ý�˙ý�˙ţ�˙˙�˙ţ�˙˙˙˙�˙ü�˙ű�˙ý���˙˙�˙ţ�˙ţ���˙ţ�˙ţ�˙˙�˙˙�˙ý�˙˙�˙ţ�˙˙�����˙˙�˙ţ�˙˙˙˙�˙ý�˙ú�˙ü���˙˙�˙˙���˙˙���˙ţ�˙ý�˙˙�����˙ý�˙ű�˙˙�����˙ţ�˙ý�˙˙���˙˙���˙˙���˙˙�˙˙�����˙ý�˙ý���˙˙���˙ţ�˙ý�˙ţ���˙˙���˙ţ�˙ţ���˙ţ�˙ý�˙˙�˙˙��˙˙�˙ü�˙ú�˙ű�˙˙������˙˙�˙ţ���������˙˙�˙ţ�˙˙˙˙�˙ţ��˙ý�˙ü�˙˙˙˙�˙ţ�˙ţ�˙ţ���˙˙�˙ý�˙ţ���˙˙��������������������˙ţ�˙ţ�˙˙˙˙��˙ý�˙ű�˙ý������˙ţ�˙ţ�˙˙���˙ţ�˙ţ�˙˙˙˙�˙ü�˙ü�˙ý��˙ţ�˙˙�����˙ý���˙ţ�˙ü�˙ţ���˙˙�˙˙�˙ţ�˙˙�˙˙�˙ý�˙ű�˙ü�˙ţ���˙˙�˙ý�˙ţ���˙˙��������������������˙ţ�˙ţ�˙˙�������˙ţ�˙˙���������˙ţ�˙˙���˙ţ�˙˙�����˙ţ�˙ţ�˙˙�����������˙ţ�˙ü�˙ý�˙˙���˙ţ�˙˙������������˙˙�˙ţ�˙˙�˙˙�˙˙�˙˙���˙˙�˙˙���˙˙�˙˙�˙ţ�˙ý�˙ý�˙˙�˙˙�˙˙���˙˙�˙ý�˙ű�˙ü�˙ţ�˙˙�������˙˙�˙ţ�˙ü�˙˙���˙ţ�˙˙���˙ţ�˙ý�˙ý�˙˙�˙ţ�˙ü�˙ú�˙ú�˙ű�˙˙�����������˙ţ�˙˙���˙ţ�˙ü�˙ű�˙ű�˙ý���˙ţ�˙ý�˙ţ�˙˙���������˙ţ�˙˙���˙ţ�˙ý�˙˙���˙˙���˙ţ�˙˙������˙˙�˙ţ���˙˙�˙˙���˙˙�����˙ţ�˙ţ�˙˙�������˙ţ�˙ţ�˙ý�˙˙���˙ü�˙ű�˙ü�˙˙���˙˙�˙˙���˙˙�˙˙�˙˙�˙ý�˙ý�˙ý�˙ţ���˙ţ�˙ý�˙ü�˙˙�˙˙�˙˙�˙˙�˙ţ���˙˙�˙ţ�˙˙�˙˙�˙˙�����˙ţ�˙ú�˙ű�˙ţ�����˙˙�˙˙�����˙˙�˙˙�˙ý�˙ţ�˙˙�����˙˙�����˙˙�˙ţ�˙ý�˙˙�˙˙�˙˙���˙˙���˙˙���˙˙���˙˙���˙ţ�˙ü�˙ü�˙˙���˙˙�˙˙�˙˙�˙˙���˙˙�˙˙���˙˙�˙˙�˙˙�˙˙�˙˙�˙˙�˙ţ�˙ý�˙ý�˙˙���˙˙�˙˙˙˙�˙ü�˙ý��˙ý�˙ü�˙ü�˙ü�˙ţ�˙˙˙˙���������˙˙��˙ý�˙ü���˙˙���˙ţ�˙ý�˙ţ�˙˙��������������˙ţ�˙ý�˙ţ���˙˙���˙˙���˙˙�˙ţ�˙ţ���������˙˙���˙˙�˙ý�˙ţ���˙˙�˙˙���˙˙�˙˙���˙˙�˙ţ�˙ţ����˙ţ�˙ţ�˙ţ�˙ţ�˙˙�����������˙ţ�˙ü�˙ü�˙ü�˙ű�˙˙˙˙�˙ü�˙ý�˙˙�������˙˙���������˙ţ�˙ţ�˙ţ�˙˙˙˙�˙ţ�˙˙˙˙�˙ý�˙ý����������������˙ţ�˙ý�˙˙�˙˙�˙ţ���˙ţ�˙ú�˙ţ˙˙�˙ú�˙ü���˙ţ�˙ţ���˙˙�����˙˙�˙˙�˙˙˙˙�˙˙�˙ţ�˙ţ�˙ţ�˙ţ�˙ţ�˙˙��˙˙�������˙˙���˙˙�˙ţ���˙˙�˙ţ�˙ţ��˙ţ�˙ţ�˙˙�˙˙�������˙ţ�˙˙˙˙�˙ţ�˙ţ�˙ý�˙ţ���˙ţ�˙ý�˙˙�����˙ţ�˙ţ�˙ţ�˙ţ�˙˙˙˙�˙ţ�˙ü�˙ü�˙ý�˙ţ�˙ü�˙ü�˙˙˙˙�˙ű�˙ű�˙ü�˙ţ���˙ü�˙ü�˙ţ���˙ţ���˙˙�˙ţ��������������˙ţ�˙˙�����˙ţ�˙ü�˙ý�˙˙˙˙�˙˙���˙ţ�˙ţ�˙˙�˙˙�˙˙�����˙˙�˙ü�˙ü�˙ţ�����˙˙�˙ţ���˙ý�˙ţ�˙˙�˙ţ�˙ţ���˙ţ�˙ý�˙ţ�˙˙�˙˙�˙˙���˙˙�˙ţ���˙ü�˙ý�˙ţ�˙˙�����˙ý�˙ý�˙˙˙˙�˙ţ�˙ü�˙ţ�˙˙��˙˙����˙˙��˙˙�˙˙���˙ţ�˙ţ�˙˙�˙ţ������˙ţ���˙˙�������˙˙�˙˙�˙ţ���˙ý�˙ţ�˙˙���˙˙�˙ý�˙ţ�˙ý�˙ü�˙ý��˙˙�˙ţ�˙ţ�˙ü�˙ý�˙˙���˙ţ�˙ý�˙˙���˙˙���˙ţ�˙ţ���˙ţ�˙ý�˙ţ�˙ţ�˙ţ�˙˙�������˙˙���˙ţ�˙ý�˙ţ�˙ý�˙˙˙˙�˙ý�˙˙˙˙�˙˙�˙˙�����˙ţ�˙ü�˙ý�˙ţ�˙ţ�˙ţ�˙˙˙˙�˙ţ�˙˙˙˙�����˙˙�˙˙���˙ţ�˙ý���˙˙�˙˙���˙ţ�˙˙�����˙˙�˙ţ�˙˙�����˙˙�˙˙�˙˙�˙ý�˙ţ���˙˙�˙˙�˙ţ�˙˙�˙˙�˙˙���˙ý�˙˙�����˙˙�˙ţ�˙˙�˙˙�˙ţ�˙ü���˙˙�˙˙˙˙�˙ţ�˙˙˙˙�˙ţ�˙˙���������˙ţ�˙ü�˙ü�˙ü�˙ü�˙ţ�����˙˙�˙ý�˙ü�˙ţ���˙ý�˙ţ���˙ţ�˙ý�˙ý���˙ţ�˙ý�˙ţ�˙˙�˙˙���˙˙�˙ý�˙ţ�����˙˙���˙˙�˙˙���˙˙�����˙ý�˙ý�˙˙˙˙�˙ţ�˙ţ�˙ţ�˙ţ�������������˙˙�˙ţ�˙˙���������˙˙�˙˙�˙˙�˙˙�˙ý�˙ý�˙ţ�˙˙�˙˙�˙˙���˙˙���������˙ţ��˙ý�˙ú�˙ţ˙˙�˙ü�˙ý�˙˙���˙˙�˙˙�˙ţ�˙ý�˙ý�˙˙�˙˙�˙ţ�˙ý�˙ý���˙ţ�˙ý�˙ţ�˙˙�˙ý�˙ü�˙ţ�˙˙�˙˙�˙˙�˙˙���˙˙�˙˙���˙˙�˙˙���˙˙�˙˙�������˙ţ�˙˙���˙˙���˙ţ��˙˙�������˙˙�˙ţ���˙˙�˙˙�˙ţ�˙ý�˙ý�˙˙���˙ý�˙ý�˙˙�˙ţ���˙ţ�˙ü�˙ý�˙ý�˙ű�˙ü�˙ţ�˙˙���˙˙���˙˙���˙ţ�˙ţ�˙˙�˙˙�˙ţ�˙˙�˙˙���˙ţ�˙˙���˙˙�˙ţ�˙ý�˙˙���˙ý�˙ţ�˙˙�˙˙˙˙�˙ţ���˙ţ�˙ţ�˙ţ�˙ü�˙ý���˙˙�˙ţ�˙ţ���˙˙�˙˙������˙˙�˙ü�˙˙˙˙�˙ţ�˙ţ�˙ţ���������˙˙�˙˙�˙˙�˙˙�˙ţ�˙ý�˙˙���˙˙�˙ţ�˙ý���˙˙�˙ţ�˙˙�˙ţ�˙ý�˙˙���������˙ţ�˙˙���������˙ţ�����������˙˙�˙˙�˙ţ�˙˙�˙˙�˙ý�˙ţ���˙˙�˙˙�˙ţ�˙ý���˙ţ�˙ý�˙ý���˙˙�˙˙�˙˙�˙ţ�˙ý�˙ü�˙ý�˙ý����˙˙���������˙ţ�˙ţ�˙ţ��˙ţ�˙ţ����������˙ţ��˙˙�˙˙�����˙˙�˙˙�˙ý���˙ţ�˙ü�˙ţ�˙˙�˙ţ���˙˙�˙˙���˙˙�˙˙���˙˙�˙˙�˙˙�˙˙���˙ý�˙ţ�����˙˙�����˙˙�˙ţ�˙ý�˙ü�˙ü�˙ű�˙ý���˙ţ�˙ý�˙ý�˙ý�˙ű�˙ý�˙ý�˙ý�˙ý���˙ţ�˙ű�˙ţ�˙˙�˙ţ�˙˙�˙˙�˙ţ�˙˙���˙ü�˙ű�˙ý�˙˙�˙ţ�˙ü�˙ý�����������˙˙�˙ý�˙ý�˙ý�˙ţ�˙˙��˙˙�˙ý�˙ý���˙˙�˙ţ�˙˙��˙ý�˙ý�˙ý�˙ý�˙ţ�˙ţ�˙ţ��˙ý�˙ţ���˙˙�˙˙���˙˙�˙˙�����˙˙�˙ý�˙ý�˙ţ�˙ţ�˙˙�˙ý�˙ű�˙ý���˙˙�˙˙��˙˙�˙ý�˙ý���˙˙�����˙˙�˙ý�˙ţ���˙˙���˙ý�˙ý�˙˙���˙˙�˙˙�˙ţ�˙ű�˙ů�˙ú�˙ţ���˙˙���˙ţ�˙˙���˙ţ�˙˙�˙ţ�˙˙�˙ţ�˙ţ�˙ü�˙ú�˙ű�˙˙����������������������˙˙�˙˙����˙˙��˙ţ�˙˙˙˙�˙ţ�˙˙�˙ţ�˙˙���˙˙�������˙˙�˙ý�˙ý�˙ý�˙ţ�˙˙���˙ţ��˙ţ�˙ý��˙ţ�˙˙������˙˙�˙ţ�˙˙˙˙�˙ţ�˙˙���˙ţ�˙ý�˙ţ�˙˙�˙˙�˙ý�˙ţ�˙˙�������˙ţ�˙˙���˙ţ�˙˙�������˙ţ�˙ü�˙ű�˙ţ���˙ý�˙˙���˙ţ�˙ţ�˙ý��˙ý�˙ý��˙ý�˙ű�˙ü�˙ţ�������˙˙�˙ţ�˙ţ�˙ý�˙ú�˙ú�˙ú�˙ű�˙˙��˙˙�˙ţ�˙˙˙˙��������˙ý�˙ü�˙˙������˙˙�˙ţ���˙˙�˙˙�˙˙�˙ţ�˙˙���˙ý�˙ý�˙˙˙˙�˙˙���˙ţ�˙ţ�˙˙���������˙ţ��˙ţ�˙˙˙˙�˙ţ���˙˙�����˙˙�˙ý�˙ü�˙ü�˙ţ��˙ý�˙ü�˙ý��������˙ţ�˙˙�������˙˙�˙ü�˙ű�˙ţ���˙ü�˙ý�˙ý�˙ţ�˙ý�˙ý�˙˙�˙ţ�˙ü�˙ý�˙˙�������˙ţ�˙ü�˙ý�˙ţ�˙ţ�����˙˙�˙ý�˙ţ���˙˙�˙ý�˙ţ�˙ţ�˙˙���������˙˙�˙ţ�˙ţ�˙ý�˙ü�˙ţ�˙˙���˙˙�˙˙���˙˙�˙ţ�˙ü�˙ţ����������˙ţ���˙˙�˙ü�˙ý���˙˙�˙ý�˙˙�˙˙�˙˙�˙˙�˙ţ�˙˙������������˙˙�˙ţ�˙ü�˙˙��������������˙˙�����˙˙�˙ý�˙ţ�������˙˙�������˙˙�˙˙�˙ţ�˙˙�������˙˙�˙˙���˙˙�˙˙���˙ý�˙˙�˙˙�˙ţ�˙ý�˙˙���˙ţ�˙ţ�˙ţ���˙˙�˙ţ�˙˙�˙˙�˙ý�˙ţ���˙˙�˙ý�˙ý�˙ü�˙ý�˙˙����˙˙�˙ţ�˙ţ�����������˙˙�˙ţ�˙˙˙˙�˙ý�˙ü�˙ü�˙ü�˙ý�˙ţ�˙ý�˙ý��˙˙�˙˙���˙ţ�˙ý�˙ý�˙˙���˙ţ�˙ý���˙˙�˙˙�˙˙���˙ţ�˙ý�˙˙˙˙�˙ű�˙ű��˙ű�˙ű�˙ý�˙ţ�˙ţ�˙ţ�˙˙�˙˙�˙ý�˙ý�˙ţ�����˙˙�������˙˙�˙˙�˙ţ�˙˙���˙˙�˙ý�˙ţ���˙˙�˙˙�˙˙�˙˙���˙˙���˙ţ�˙˙�˙ţ�˙ţ�˙˙�˙ţ�˙ü�˙ţ�˙˙�˙˙�˙˙�˙˙�˙˙�����˙ţ�˙ü�˙ţ�˙˙�˙˙�˙˙�˙˙�˙˙�˙˙�˙˙�����˙˙�˙ý�˙˙�˙ý�˙ü�˙ţ�˙ţ�˙ţ�˙ý�˙˙���˙ţ�˙ý�˙ý�˙ţ���˙ý�˙ţ�����˙ţ�˙ü�˙ţ���˙ý�˙ý�˙ţ�˙˙�����˙˙�˙˙�˙ţ�˙˙���˙˙�˙ý�˙ű�˙ü�˙ţ�˙˙�˙˙���˙˙���˙˙�˙ý�˙˙�˙˙�˙ţ�˙ý�˙˙���˙˙���˙˙���˙˙���˙ţ�˙ý�˙˙���˙˙�˙˙�˙ţ�˙ý�˙ý�˙˙���˙˙���˙˙���˙ţ�˙ý�˙ü�˙ý�˙˙�˙˙�˙˙�˙˙�˙˙���˙˙�˙˙�˙˙�˙˙�˙˙���˙˙�˙ý�˙˙˙˙�˙ţ��˙ý�˙ü�˙ü�˙ü�˙ü�˙ü�˙ü�˙ý���˙˙�����˙˙�˙ý�˙ţ�����˙˙�˙˙�˙˙˙˙�˙˙�˙ţ�˙ţ���˙˙�˙ţ��������������˙ţ�˙˙�˙ţ�˙ü�˙ţ�����˙˙�˙ţ�˙ý�˙ý���˙˙�˙˙�����˙˙�˙ţ�˙ţ�����������˙˙�˙ţ�˙˙�������˙˙�˙˙���˙˙���˙˙�˙˙���˙˙�˙˙���˙˙�˙ţ�˙˙���˙ţ�˙˙���˙ţ�˙ţ���˙ţ�˙ţ�˙˙���������˙ţ�˙ţ�˙ţ�˙˙����˙˙�˙ţ�˙ţ�˙ţ�˙˙�˙ü�˙ű�˙˙˙˙�˙ý�˙ţ���˙˙�����˙˙�˙ţ�˙ű�˙ű�˙ţ�����˙˙�˙ţ�˙˙�����˙ţ�˙ţ�˙˙�˙˙���˙˙���˙ţ�˙ű�˙ü���˙˙���˙ţ�˙ű�˙ü���˙˙�˙˙���˙˙�˙˙���˙˙���˙ü�˙ü��˙˙���˙˙���˙ü�˙ţ���˙ţ�˙ý�˙˙���˙˙���˙ţ�˙ý�˙ý�˙˙�˙˙���˙˙�˙˙�����˙˙���˙ţ�˙ý�˙ţ�˙ý�˙˙���˙˙�����˙˙���������˙ţ�����˙˙�˙ý�˙ü�˙ţ��˙ţ�˙ţ��˙ţ�˙ü�˙˙���˙˙˙˙�˙ý�˙ý�˙˙�˙ý�˙ý�˙˙���˙˙�����˙˙�˙˙���˙ý�˙ý�˙ţ�˙˙�˙˙�˙˙�˙˙�˙˙�˙˙���˙˙�˙˙���˙ţ�˙ý�˙ý�˙˙���˙˙���������˙ţ�˙ţ�˙˙�˙ţ�˙ý�˙ý�˙ţ���˙˙���˙˙�˙ţ���˙˙�˙˙˙˙�˙ý�˙ü�˙ţ�˙ţ�˙ţ�˙˙���˙ţ�˙˙˙˙�˙ü�˙ü�˙˙���˙ţ�˙ţ�˙ţ�˙ý��˙ţ�˙˙˙˙�˙ý�˙˙�����˙ţ�˙ý�˙ţ���˙˙�˙˙���˙ţ�˙ý�˙˙���˙˙�˙˙�˙˙���˙˙�˙ý�˙ţ�˙ţ�˙ţ��˙ţ�˙ţ�˙ţ�˙ţ�˙ţ������˙ţ�˙˙�˙˙�����˙˙�˙˙�˙˙�˙ţ�˙ú�˙ů�˙ü�˙ţ�˙ý�˙ý�˙˙�����˙ţ�˙ý�˙˙�������˙ţ�˙ü�˙ý�˙˙�����˙ý�˙ú�˙ý��˙ý�˙ű�˙ű���˙˙�˙ţ�˙ý�˙ű�˙ü�˙ţ���˙ţ���˙˙��˙ý�˙ü�˙ţ�˙˙�˙ţ�˙ý�˙ţ��˙ý�˙ţ�����˙˙�˙˙���˙˙�˙˙�˙˙�˙˙�˙˙�˙˙�˙˙���˙˙�˙˙�����˙˙���˙ţ�˙ü�˙ü�˙ý�������������˙˙�˙ţ��˙ý�˙ü�˙˙˙˙�˙ţ�˙ţ�˙ţ�˙ţ��˙ţ�˙ý�˙ü�˙ü�˙ţ�˙ţ�˙˙˙˙�˙ţ�˙˙˙˙�˙ý�˙ţ�˙˙�˙˙���˙ü�˙ű�˙ý�˙ţ�˙ţ�˙ţ������˙ţ�˙ţ���˙ţ�˙ü���˙ţ�˙ţ�˙˙����˙˙�˙ţ���˙˙���˙˙�˙ý�˙ţ��˙ý�˙ý�˙ţ���˙˙���˙˙�˙ţ���˙˙�˙ţ�˙ý�˙˙˙˙�˙ţ��˙ý�˙ţ��˙ý�˙ý�˙ţ���˙ţ�˙ű�˙ů�˙ú�˙ý����������˙ţ�˙ţ��˙ţ�˙ţ��������˙ţ�˙˙�����˙ţ�˙ý��˙ţ�˙ý�˙˙�˙˙�˙˙���˙˙�˙˙�˙˙�˙ý�˙ţ�����˙˙���˙˙�˙ý�˙ţ���˙˙��������˙ý�˙ű�˙ú�˙ü���˙˙�˙ý�˙ţ�˙˙�����˙˙�˙˙�˙ý�˙ý�˙ţ�˙˙�˙˙�˙˙���˙˙���˙˙�˙ý�˙ü�˙ţ�����˙˙�˙˙�˙ţ���˙˙�������˙˙�����˙˙�˙˙�˙ţ�˙˙���˙˙�˙ý�˙ţ�˙ý�˙ü�˙˙˙˙�˙ü�˙ý�˙˙���˙ţ�˙ú�˙ů�˙ţ���˙˙���˙˙���˙˙���˙ţ�˙ý�˙ţ���˙ý�˙ţ���˙ý�˙ţ�˙ţ�˙˙���˙˙�˙ţ���˙˙���˙ţ�˙ý�˙˙���˙˙�˙˙���˙˙�˙˙�˙˙�˙˙�˙˙�˙˙�˙˙�˙ţ�˙ü�˙ü�˙ý�˙˙˙˙�˙ý�˙˙�����˙ţ�˙ţ�˙ţ������˙ţ������˙˙���˙ţ�˙ý�˙ý�˙˙�˙˙���˙˙�˙˙�����˙ü�˙ů�˙ü�����˙˙�˙ý�˙˙��������˙˙�˙ţ�˙ý���˙˙�˙ü�˙ý�˙ţ����˙ţ�˙ý�˙ý���˙˙�˙˙������˙˙�˙ü�˙ţ�˙ţ�˙˙�����˙˙�˙˙�˙ţ�˙ţ���˙˙�˙ţ���˙˙�˙˙�˙˙�˙˙���˙˙���˙˙���˙ţ�˙ţ�˙˙���˙˙�˙˙�˙ţ�˙ţ�˙˙��˙˙�˙ý�˙ü�˙ţ�˙˙�˙ý�˙ü�˙˙˙˙�˙ţ�˙˙˙˙�˙ţ�˙˙˙˙������˙ý�˙ý��˙ü�˙ţ�����˙˙���˙ý�˙ý�˙˙�������˙˙���˙ţ�˙˙���˙˙���˙ţ�˙ţ���˙˙���������˙˙�˙˙�˙˙�˙ţ�˙ţ�˙ý�˙ţ���˙˙�˙ţ�˙˙�˙˙�����������������˙ţ�˙˙˙˙�˙ú�˙ű�˙˙��������������˙˙�˙ü�˙ý����˙ţ�˙ý�˙ţ�˙ü�˙ý�˙˙���������˙ţ�˙ü�˙ű�˙ý�˙ý�˙˙���˙˙���˙ţ�˙ý�˙˙���˙ý�˙ţ���˙˙���������˙ţ�˙˙���˙ţ�˙ý�˙˙�˙˙�˙ţ�˙ý�˙˙���˙ý��˙ţ�˙ý�˙˙���������˙˙˙˙�˙ţ�˙˙�������������˙ţ��˙ţ�˙˙�����˙ý�˙ý�˙ţ�˙˙�˙ý���˙˙�˙ý�˙ţ�˙˙�˙˙�������˙ţ�˙ü�˙ý�˙˙�����˙ţ�˙ý��˙ü�˙ţ���˙ý�˙ý�˙ý�˙ţ�˙˙�˙˙�˙˙�˙˙�˙˙���˙˙����������������˙ţ�˙ţ�˙ţ�˙ţ�˙ţ�����������˙˙�˙ü�˙ý�˙ţ�˙ý�˙˙���˙˙���˙ţ�˙ý�˙ţ�˙ý�˙ţ�˙˙�˙ţ�˙ü�˙ţ���˙ý�˙˙�˙˙�˙ţ�˙ý�˙ý�˙ţ�˙ţ�˙˙���˙˙�˙˙�˙˙�˙ţ�˙ý�˙˙���˙ţ�˙ţ�˙ţ������˙ý�˙ţ�˙˙�������˙ţ�˙˙���˙ţ����˙ţ�˙ű�˙ý���˙˙���˙˙�˙ţ�˙ý�˙ý�˙ü�˙ű�˙ţ�˙˙�˙ţ�˙ţ�˙˙���˙˙�˙ü�˙ű�˙˙˙ţ�˙ţ�˙ţ�˙˙�����˙ţ�˙ú�˙ţ���˙ţ�������������˙˙�˙˙���˙ţ�˙ţ�˙ý�˙ţ�˙ţ�˙ý�˙˙�����˙˙���˙ţ�˙ű�˙ý�˙ý�˙ý�˙ţ�˙˙���˙˙�˙˙�˙˙�˙˙���˙˙���˙˙���˙˙���˙˙�˙ý�˙ţ�˙ý�˙ű�˙ý���˙˙���˙ü�˙ţ��˙ţ���˙˙�˙ü�˙˙����˙˙�˙ţ�˙ü�˙ű�˙ú�˙ü���˙˙�˙ü�˙ú�˙ţ���˙ţ�˙˙�˙˙�˙ţ�˙ý�˙ý�˙ţ���˙ü�˙ű�˙ý�˙˙���˙ţ�˙ý�˙ý�˙˙���˙ý�˙˙���˙˙�˙˙���˙˙���˙ţ�˙ý�˙ţ���˙˙���˙˙�˙˙�������˙˙���˙˙�˙ţ�˙˙�˙˙�˙˙�˙ţ�˙ú�˙ů�˙ý���˙˙�����˙ţ�˙ü�˙ý�˙˙���������˙˙�˙ţ�˙˙�˙˙�˙˙�˙˙���˙˙�˙˙���˙˙�˙˙�˙˙�˙ţ���˙˙�˙ü�˙ü�˙ü�˙ý�˙˙�����˙ţ�˙ü�˙ý��˙ý�˙ţ�˙ţ�˙˙�˙˙�˙ţ���˙ü�˙ý���˙ý�˙˙˙˙�˙ţ�˙ţ�˙ţ�˙˙���������˙ţ�˙ü�˙ţ���˙ý�˙ţ�˙˙���˙˙�˙ţ���˙˙�˙ţ���˙˙�˙ý���˙˙���˙˙�˙ý�˙ţ�˙˙˙˙�˙ý�˙ý���˙˙�˙ţ���������˙˙�˙ý�˙ţ���˙ý�˙˙���˙˙�˙˙�˙˙�˙˙�˙ý�˙ţ�˙˙�˙˙�˙˙�˙˙�˙˙�˙˙�˙˙�˙˙�˙˙���˙˙���˙˙�˙ţ���˙˙���������˙˙��˙ţ�˙ţ���������������˙˙�˙˙�����������������˙ţ�˙ţ�˙ţ�˙ý�˙ţ��˙˙����˙˙�˙˙˙˙�˙ü�˙˙�����˙ţ�˙ţ�˙ţ�˙˙˙˙�˙ţ�˙ţ�˙ţ�˙˙˙˙�˙ü�˙ý�˙˙�������˙˙�������˙˙�������˙ţ�˙ý�˙ţ�˙ţ�˙ü�˙ü�˙ý�˙ţ�˙ü�˙ű�˙ţ�˙˙�˙˙���˙ý�˙ü�˙ý�˙ý�˙ţ�˙ý�˙˙���˙ţ�˙ý�˙˙���˙˙�˙˙�˙˙�˙˙�˙˙���˙˙�˙˙�˙˙�˙˙���˙ý�˙ý���˙˙�˙ý�˙ý�˙ý�˙ý�˙˙���˙˙�˙˙�˙˙�˙˙���˙˙�˙˙���˙˙�˙˙�˙ý�˙ý�˙ţ���˙ţ�˙ţ���˙ţ�˙ý�˙ý���˙ţ�˙˙���˙ţ�˙˙���������˙ţ�˙˙�����˙ţ�˙˙���˙ţ�˙ţ���˙ţ�˙˙���˙˙�˙˙�˙˙�˙˙�˙˙�˙˙���˙˙�˙˙���˙˙�˙ţ�˙ţ�˙ý�˙ţ��˙ţ�˙ţ��˙ţ�˙ţ�˙˙�������˙˙�����˙˙���˙˙�˙˙�˙˙�˙ţ���˙˙�˙ţ�˙˙�˙ţ�˙˙����˙ý�˙ý��˙ţ�˙˙�˙˙�˙ý�˙ţ�����˙˙�˙ţ�˙˙˙˙�˙ý�˙ü�˙˙˙˙�������˙˙�����˙˙�˙˙�˙ţ�˙˙���˙˙�˙ý�˙ţ�˙˙�˙ţ�˙ţ�����������������˙˙��˙ţ�˙˙���˙ý�˙ţ�˙ţ���˙˙�˙ý�˙ý�˙ţ���˙˙�˙˙���˙˙�˙ţ�˙ţ���˙ţ�˙ţ�˙ţ�˙ü�˙ü���˙ý�˙ý���˙ţ�˙ţ�˙˙���˙ţ�˙ü�˙ü�˙ý�˙˙�����˙ý�˙ű�˙ű�˙˙���˙ţ��˙ţ�˙ţ�˙ţ�˙ţ�˙˙��˙˙�˙ţ�˙ţ�������������������������˙˙�˙ţ�����˙˙�˙ý�˙ţ���˙˙���˙˙�˙ţ�˙ý�˙ü�˙ü�˙ü�˙˙˙˙�˙ţ�����˙˙�˙ý�˙ţ���˙˙���˙˙�˙˙���˙ţ�˙˙�˙˙�˙ţ�˙ţ���������˙˙�˙ţ�˙˙�˙ţ���˙ţ�˙ü�˙ţ���˙˙�˙ţ�˙ţ�˙˙���˙ţ���˙ţ�˙˙���˙ţ�˙ý�˙ý���˙˙�˙˙�˙ý�˙ý�˙ţ�˙˙���˙˙���˙˙�˙ţ���˙ű�˙ý���˙ý�˙ţ�˙˙�˙˙�˙ţ�˙˙����˙ţ���˙˙�˙ü�˙ý��˙ý�˙ý�˙˙�������˙ţ�˙ţ�˙˙˙˙�˙ý�˙ü�˙ý�˙ü�˙ý�˙ţ�˙ţ������������������˙ţ�˙˙˙˙�˙˙���˙ţ�˙˙�˙ţ�˙˙�˙˙�������˙ţ�˙ţ�������˙˙���������˙˙�����˙˙�˙˙�˙ţ���˙˙�˙ţ�˙ţ�˙ţ�˙ü�˙ţ�����������˙˙��˙ţ�˙ü���˙ţ�˙˙���˙˙�����˙ü�˙ý�˙˙���˙ţ�˙ý��˙ý�˙ţ���˙ý�˙ű�˙ü�˙ý�˙ý�˙ţ���˙˙�˙˙���˙˙�˙ü�˙ú�˙ý���˙ţ��˙ý�˙ţ���˙˙�������˙˙������˙ţ�˙˙˙˙�˙ü�˙ý��˙ţ�˙ţ�˙˙�������˙ţ�˙ţ�˙˙���������˙ţ�˙˙˙˙�˙˙˙˙�˙ţ��˙ţ�˙ţ�˙˙�����˙ţ�˙ü�˙ý���˙ţ�˙ý���˙˙���˙˙���˙˙�˙ý�˙ü�˙ţ���˙ý�˙ţ���˙˙�����˙˙�˙ý�˙ţ���˙˙�˙˙�˙˙���˙˙��������˙ý�˙ü�˙ý�˙ţ�˙ţ�˙ţ�˙ţ�˙ü�˙ü���˙ţ�˙ü�˙˙���˙ţ�˙ţ�˙ý�˙˙���˙˙�˙ţ�˙ý���˙ţ�˙ý���˙ţ�˙ü�˙ý��˙ý�˙ţ�˙˙�˙ţ�˙ţ���˙˙�˙˙�˙˙�˙ţ�˙ý�˙˙˙˙�˙ü�˙ý�˙ţ�˙ý�˙˙���˙˙�����˙ţ�˙˙���������˙˙����������������˙˙�˙ţ���˙˙�˙˙˙˙�˙ţ�˙˙˙˙�˙ü�˙ü�˙ý�˙˙˙˙�˙ý�˙ý�˙˙�˙˙�˙ţ�˙ý�˙˙�˙˙�����˙˙�˙˙���˙˙�˙˙�˙ý�˙ý�˙ý�˙ű�˙ü���˙˙�˙ţ�˙˙�������˙ţ�˙ţ���˙˙���˙˙�˙ţ�˙ţ�˙ţ�˙ţ�˙ţ�˙ý�˙ţ�˙˙�˙ý�˙ý���˙˙�˙ý�˙ý�˙ü���˙ţ�˙ű�˙ý�˙ţ�˙˙���˙ý�˙ű�˙ü�˙ţ���˙˙�˙˙�������˙˙���˙ţ�˙ý�˙ý�˙ţ���˙˙�˙˙�˙˙���˙ţ�˙ű�˙ů�˙ú�˙ý����������������˙ţ��˙ţ�˙ý��˙ţ�˙ý�˙˙�����˙˙���˙ţ�˙˙���˙˙���˙˙�˙˙�˙˙�˙˙�˙˙�˙˙�˙˙�˙˙�˙˙���˙ý�˙ý�˙ý�˙ý�˙ţ�˙˙�˙˙�˙˙�˙˙�˙˙�˙˙�˙˙���˙ţ�˙ý�˙ţ�˙˙���˙˙�������˙˙�������˙˙���˙˙�˙ý�˙ţ�˙˙���˙ţ���˙ţ�˙ü�˙ý�˙ţ���˙ţ�˙ý�˙ý�˙ý�˙ý�˙ý�˙ý���˙ţ�˙ý��˙ţ�˙ü�˙ţ�˙ţ�˙ţ���˙˙�˙˙˙˙�˙ý�˙ü�˙ü�˙ý�˙˙�����˙ţ�˙ü�˙ü�˙˙�˙ţ�˙ú�˙ú�˙ţ�����˙ţ�˙ü�˙ý�˙˙���˙˙�˙˙�˙˙�˙˙�˙˙�˙˙���˙ý�˙˙���˙ý�˙ý���˙˙�˙˙�˙˙�˙ţ�˙ü�˙ý��˙ţ�˙˙�˙˙�˙˙���˙˙�˙ţ���˙ţ�˙ü�˙ţ�����˙˙�˙ţ�˙ţ�˙˙���˙ţ���˙˙�˙ţ�˙ţ�˙ţ�˙ţ�˙˙�˙˙�������˙˙�˙ţ�˙ţ���˙˙���˙˙�˙˙���˙˙�˙˙�˙˙���˙ţ�˙ý�˙˙���˙˙�˙ţ�˙˙�˙ţ�˙˙���˙ţ�˙˙�����˙ţ�˙ţ�˙˙�˙ý�˙ű�˙ţ���˙˙�˙˙�˙ý�˙ý�˙ý�˙ý�˙ý�˙˙���˙˙���˙˙�˙˙���˙˙�˙˙�˙ţ�˙ý�˙ý��˙˙�˙˙���˙˙�˙˙�˙˙���˙˙�˙˙���˙˙���˙˙�˙˙���˙˙�˙˙�˙˙�����˙˙�˙˙�˙ţ�˙˙�˙ţ�˙ţ�˙˙���������˙ţ�˙ţ�˙˙����˙˙�˙ý�˙ü�˙˙�˙ţ�˙ţ���˙ţ�˙ţ�˙˙�˙˙�˙˙�˙˙���˙˙�˙˙�����˙˙���˙˙�˙ţ���˙ü�˙ű�˙ý�˙˙���������˙ţ�˙ţ�˙˙�����˙ţ�˙ţ�˙ü�˙ů�˙ů�˙ý��˙ţ�˙˙���˙ţ�˙˙�˙˙���˙˙�˙ü�˙ű�˙ü�˙˙�˙˙���˙ţ�˙ý�˙˙�˙ţ�˙ý�˙ý�˙˙���˙ţ�˙˙���˙ţ�˙ţ�˙˙������˙˙�˙ý�˙ý���˙˙��˙ý�˙ü�˙ţ�������˙˙�˙ţ�˙ţ�˙ţ��˙ţ�˙ţ�˙˙�˙ţ��˙˙�˙ţ�˙˙���˙˙���˙ţ�˙˙�˙˙�˙ţ�����˙˙�˙˙���˙˙�˙˙�˙ý�˙ý�˙ţ���˙˙���˙˙�˙˙���˙˙���˙˙�˙˙���˙˙���˙ţ�˙ý���˙ţ�˙ý�˙ţ�˙˙�˙˙���˙˙�˙˙�˙ý�˙ý�˙ţ�˙˙���˙˙�˙˙�˙˙�����˙˙���������˙ţ�˙ţ�˙ý�˙ţ���˙˙�˙˙�����˙˙�˙ţ�˙ţ�˙ţ��˙ý�˙ţ�����˙˙�������˙˙�������˙˙�˙ý�˙ü�˙ý���˙˙�˙˙������˙˙�˙ü�˙ü�˙ý�˙ţ�˙ţ�˙˙˙˙�˙˙����˙˙�˙ţ�˙ü�˙ţ�˙ţ�˙ý�˙ţ�˙ţ�����˙˙�˙ý���˙˙�˙˙���˙˙�˙ţ�˙˙���˙ţ�˙ţ�˙˙˙˙�˙ü�˙ý��˙˙�����˙ţ�˙ý�˙˙�����˙ý�˙ü�˙ý�˙ý�˙ţ��˙ţ�����˙˙�˙˙���˙˙�˙˙�˙˙�˙˙�˙ţ�˙ý�˙ý��˙ţ�˙ý�˙ţ�˙ţ�˙˙����˙˙�˙ţ�˙ý�˙ţ���˙ţ�˙ţ���˙˙���˙˙���˙˙�˙˙�˙˙�˙˙���˙ý�˙ý�˙ţ���˙˙�˙ý�˙ţ���˙ţ�˙ţ�˙ţ�����˙˙�˙ý�˙ţ�˙˙�˙ţ�˙ţ��˙ţ�˙ţ�˙˙����˙˙�˙ţ���˙ţ�˙ű�˙ű�˙ţ���˙˙�˙ţ�˙ţ�˙ţ�˙˙���˙ţ�˙ü�˙ű�˙ý�˙˙������������������������˙˙�˙ţ��˙ý�˙ţ���˙ü�˙ü�˙ţ��˙˙˙˙���˙˙�˙ü���˙˙��˙ý�˙ý�˙ţ�˙˙�˙˙�˙˙�˙˙�˙˙���˙ţ�˙ý���˙ţ�˙ü�˙ý�˙˙�����˙ţ�˙ý�˙˙���˙˙�������˙ţ�˙ţ�˙ţ�˙ţ�˙ţ���˙˙�˙ţ�˙˙�˙˙�˙˙���˙˙�����˙˙�˙˙�����˙˙�˙˙���˙˙�˙˙���˙˙�˙˙�˙˙���˙˙���˙˙���˙˙�˙ţ�˙ţ�˙˙�˙ţ�������˙˙���������˙˙�˙ţ�˙ţ�˙ţ�˙˙���������˙ţ�˙ţ�˙˙�����˙ţ�˙ý�˙˙���˙ţ�˙˙������˙˙�˙ý�˙˙�����˙˙���˙ţ�˙ţ�˙˙����˙˙�������˙˙�������˙˙�������˙˙�˙˙�˙ţ���˙˙���˙˙�˙ý�˙ý�˙ý�˙ý�˙ý�˙ý�˙ţ�˙ý�˙ţ����˙ţ�˙˙˙˙�˙ý�˙˙���˙˙�˙ţ�˙ţ�˙˙����˙˙�˙ţ���˙˙�˙ţ�˙ţ�˙ţ�˙˙���˙ţ�˙˙���˙ţ�˙ţ�˙ţ�˙ý�˙ý�˙ý�˙ý�˙˙���˙˙���������˙ţ�˙ţ��˙ţ�˙ţ�˙ţ�˙ţ�˙ţ�˙ţ�˙ţ�˙ţ�˙ţ�˙ţ���������˙˙�˙ţ��������˙ý�˙ţ�˙˙˙˙�����˙˙�˙ý�˙ý����������˙ţ�˙ţ�˙˙����˙˙�˙ý�˙ţ��˙˙���˙ţ�˙ý�˙˙�˙˙�˙˙���˙˙�˙˙���˙ý�˙ý�˙ţ�˙˙�˙˙�˙˙�˙˙���˙ţ�˙˙�����˙ţ�˙ţ�˙˙�˙˙˙˙�˙˙�˙˙˙˙�˙˙���˙ţ�˙˙�˙ţ�˙ü�˙ţ�����������������������˙˙�����˙˙�˙˙���˙ţ�˙ý�˙ţ�˙ţ�˙ţ�����˙˙���˙˙�˙ý�˙ţ����������������˙ţ�˙ţ�˙˙�����������˙ţ�˙ţ�������˙˙���˙˙�˙ý�˙ţ���˙˙�˙˙�˙ý�˙ý��˙ý�˙ţ���˙ű�˙ü���˙˙�˙˙���˙˙�˙˙�˙ţ�˙ý�˙ý�˙˙�˙˙�˙ţ�˙ý�˙ý�˙ý�˙ý�˙ý�˙ţ���˙˙�˙˙�����˙˙�����˙ţ�˙ü�˙ű�˙ý�˙˙�˙ţ�˙ý�˙˙�˙˙�˙˙�����˙˙�����������������˙˙�˙ü�˙ü�˙ţ�˙˙���������˙ţ�˙ţ�˙˙����������������˙˙���˙˙�˙ý�˙ţ���˙˙���������˙˙���˙˙�˙ţ���˙˙�˙ý�˙ü�˙˙˙˙�˙ţ��˙ý�˙ü�˙ý���˙ţ���˙˙��˙ý���˙˙�˙ţ���˙˙�˙ţ�˙ţ�˙ţ�˙˙���˙ţ�˙˙���˙˙���˙ţ�˙ý���˙˙�˙ý�˙ý�˙ü�˙ţ���˙ü�˙ú�˙ü�˙ţ�˙ý�˙ţ��˙ü�˙ü���˙˙���˙˙�˙˙�˙˙�˙ý�˙ţ���˙ý�˙ţ���˙ţ�˙ý�˙ý�˙˙���˙˙�˙˙���˙˙�˙˙�˙˙���˙˙�˙ţ�˙˙���˙ţ�˙˙���˙˙�˙˙�˙˙�˙ý�˙ű�˙ý�����������˙˙�˙˙������˙˙�˙˙������������˙˙�����˙˙�˙ţ�˙ţ�˙˙���˙˙�˙ý�˙ý�˙ţ�˙ţ�˙˙���˙˙�˙˙���˙ţ�˙˙���˙˙���˙ţ�˙ý�˙ý�˙ý�˙ţ���˙ý�˙ý�˙ý�˙ű�˙ü�˙˙˙˙�˙ţ��˙ţ���˙˙�˙ţ������˙ţ���˙˙�˙ţ���˙˙�˙ý�˙ü�˙ý�˙˙���˙˙���˙ţ�˙˙˙˙�˙ţ�˙ţ�˙ţ�˙ţ�������˙˙�˙ý�˙ţ�����˙˙�˙ţ�˙ţ�˙ţ�˙ţ��˙ý�˙ü�˙ţ�˙˙�˙˙���˙˙�˙˙�����˙˙�˙˙�˙ţ�˙˙���˙˙�˙ý�˙ü�˙ţ�����˙˙�˙ý�˙˙˙ţ�˙ű�˙ý���˙˙����˙ţ�˙˙�˙ţ�˙ý�˙ţ�˙˙�˙˙�˙˙�˙˙���˙˙�˙˙�����˙ţ�˙ü�˙˙���˙˙˙˙�˙˙���˙ţ��˙ý�˙ü�˙ţ���˙˙�˙ţ���˙˙�˙ţ�˙˙���������˙˙�˙ţ�˙˙���˙ţ��˙˙���˙ţ�˙ţ�˙˙�˙˙���˙˙�˙˙�˙˙�˙˙���˙˙�˙˙���˙˙�˙˙���˙ý�˙˙���˙˙���˙˙�˙ţ�˙ý�˙ţ�����˙˙�˙ý�˙˙˙˙�˙ţ��˙ţ���������˙˙���˙˙�˙ţ���˙˙���˙˙�˙ý�˙ý�˙ý�˙ý���˙ţ�˙ţ���˙ü�˙ű�˙ý�˙ý�˙ý�˙ý�˙ţ���˙˙���˙ţ�˙ţ�˙˙���˙˙�˙ý�˙˙�˙˙�������˙˙˙˙�˙ţ�˙˙������������������˙˙�˙ý�˙ý��˙ţ�˙ţ�˙ý���˙ţ�˙ű�˙ţ���˙˙�����˙˙�˙˙�˙ţ�˙ý���������˙˙��˙ý�˙ú�˙ţ˙˙�˙ü�˙ţ˙˙�˙ţ�˙˙��˙˙�˙ţ�˙ţ�˙ţ�˙ţ���������˙˙������˙ý�˙ü�˙˙˙˙�˙ü�˙˙˙˙�˙˙������˙˙�˙ü�˙ű�˙ý���˙˙���˙˙�˙˙�˙ý�˙ţ�����˙˙�����˙˙�˙ţ��˙ý�˙ţ��˙ţ�˙ý�˙ţ�˙˙˙˙�˙ţ���������˙˙�����˙˙�˙ý�˙ý���˙˙�˙ţ�˙˙�˙˙�˙ţ�˙˙���˙ţ�˙˙˙˙�˙ü�˙ý�˙ţ�˙ţ�˙ý��˙ý�˙ű�˙˙˙˙�˙ü�˙˙˙˙�˙ţ���˙˙�˙ý�˙˙˙ţ�˙ű�˙ü�˙ý���˙˙�˙ţ�˙ţ�˙ţ�˙ý�˙ý�˙˙���˙˙�˙˙�����˙˙�����˙ţ�˙ý��˙ý�˙ţ�����˙˙�˙ţ�˙ţ����˙ý�˙ü�˙ý�����������˙˙�˙ý�˙ü�˙ţ������������������˙ţ�˙ţ�˙˙����˙˙�˙ý�˙ţ�˙˙�˙˙���˙˙�˙˙���˙ţ�˙ü�˙ű�˙ý�˙˙���˙˙�˙˙���˙˙�˙˙�˙˙�˙ý�˙ý�˙ţ�˙˙�˙˙�˙ý�˙ţ���˙˙�˙ţ�˙ý�˙ţ���˙˙���˙˙�˙ý�˙ű�˙ý��˙ţ��������˙ţ�����������˙˙�˙ţ�˙˙�����˙ţ�˙ý�˙˙���˙˙���˙ţ�˙ý�˙ý�˙˙�˙˙�˙˙�˙˙���˙˙�˙˙���˙˙�˙˙���������˙ţ�˙˙˙˙�˙ţ�˙˙������˙˙�˙ţ�˙ţ�������˙˙�˙ü�˙ý�˙˙�˙ţ���˙ţ�˙ý�˙ţ���˙˙�˙ţ�˙˙�˙ţ���˙ţ�˙ű�˙ý�˙˙��������������������˙˙�˙ý�˙ţ�˙ý�˙ý�˙˙˙˙��������������˙ţ�˙ţ�˙ý�˙˙���˙˙���˙˙�˙˙�˙˙�˙ţ�˙ý�˙ý�˙˙���˙ţ�˙˙�˙ţ�˙ü�˙˙��������˙˙�˙ü���˙ý�˙ű�˙ý�˙ý�˙ü�˙ý�˙ü�˙ü�˙˙�����˙ý�˙ý�����������˙˙�˙ţ�˙ý�˙ý�˙˙˙˙�˙˙�˙˙�������˙˙�������˙ţ�˙ü���˙ţ�˙ý�˙˙�˙˙���˙ţ�˙ý�˙ţ���˙ţ�˙˙�˙˙���˙ţ�˙ü�˙ţ���˙ţ��˙ý�˙ý���˙˙�˙˙�����˙˙�˙˙���˙˙�˙˙�˙˙�˙˙�˙˙�����˙˙�˙˙�����˙˙���˙ţ�˙ý�˙ţ�˙ý�˙˙˙˙�˙ý�˙ţ�˙ţ�˙ý�˙˙˙˙�˙ţ�˙˙��˙˙�˙ţ�˙˙˙˙�˙˙�˙˙�˙˙���˙˙���˙˙���˙˙�˙˙�˙˙���˙˙�˙ý�˙ţ�˙˙�˙˙���˙ţ�˙ţ�˙ý�˙ý��˙ý�˙˙����˙˙�˙ţ�˙ţ���˙ţ���˙˙�˙˙���˙˙�˙˙���˙˙���˙˙���˙˙�˙ý�˙ţ�˙ţ�˙ţ�����������˙˙�˙ţ�˙˙˙˙�˙˙���˙ţ�˙˙��˙˙�˙ţ�˙ţ�˙˙�˙˙�˙˙�˙˙�˙˙���˙˙�˙˙�˙˙�˙ý�˙ý�˙ü�˙ű�˙ü�˙˙˙˙�˙ý�˙ţ���˙˙���˙˙���˙˙�˙ţ���˙ţ���˙˙���˙˙���˙˙�˙ţ���������˙˙���������˙˙�˙˙�˙ţ�˙˙���˙˙���������˙ţ���˙˙�˙ü�˙ţ��˙ţ�˙ý�˙ý��˙ţ�˙˙˙˙�˙ü�˙ý��˙ţ���˙˙�˙ţ�����˙˙�˙ű�˙ű��˙ţ��˙ü�˙ű�˙˙˙˙�˙˙���˙ţ�˙ţ�˙ý�˙ţ�˙ţ�˙ü�˙ţ���˙˙�˙˙���˙˙���˙˙�˙ţ�˙ţ�˙ţ�˙ţ�˙ü�˙˙�����˙ţ�˙˙���˙ţ�˙ü�˙ţ���˙˙���˙˙���˙˙�˙˙�˙ý�˙ý�˙ţ���˙ţ�˙ü�˙ý�˙ţ�˙ü�˙ű�˙ţ���˙˙�˙˙�˙˙�˙˙�˙˙�˙˙�˙˙���˙˙���˙ţ�˙ý�˙ü�˙ý���˙˙�˙˙���˙˙���˙˙�˙ý�˙ü�˙ý���˙˙�˙˙˙˙�˙ý�˙˙�����������˙˙���˙ţ�˙ý�˙ý�˙˙���˙˙���˙˙���˙˙���˙˙���˙˙���˙˙�˙ţ�����˙˙�������˙˙�����˙˙����˙ţ���˙˙�˙ý�˙ţ�˙ţ�˙ý��˙ţ�˙ý��˙ţ�˙ţ�˙ţ�˙ţ�˙ý�˙ü�˙˙�������˙ţ�˙ü�˙ű�˙ű�˙ţ���˙˙���˙ţ�˙˙���˙ţ�˙ţ�˙ţ�˙ţ�˙ţ�˙˙�˙ţ���˙ţ�˙ú�˙ü�˙˙�����˙˙�˙˙�����˙˙�˙˙���˙˙�˙˙�˙ý�˙ű�˙ü���˙˙�˙˙�˙˙�˙ý���˙˙�˙ţ���˙ţ�˙˙�����������˙ţ�˙ţ�˙ţ�˙˙˙˙�˙ü�˙ý�˙ţ�˙ţ�˙˙��˙˙�˙ü�˙ü�˙ţ˙˙�˙ţ���˙ţ�˙˙���˙˙�˙˙���˙ý�˙ţ���˙ý�˙ţ�����˙˙�˙ţ��������˙ţ�˙˙˙˙�˙ý�˙ý��˙˙�˙˙���˙ţ�˙ţ���˙ý�˙ţ�����˙˙�˙ý�˙˙�˙˙�˙˙���˙˙���˙ü�˙ű�˙ţ���˙ţ�˙ţ�˙ţ���˙ţ�˙ţ��˙ţ�˙ţ�˙˙�˙ţ�˙˙���˙ţ�˙ţ�˙˙������˙˙�˙ţ���˙˙�����˙˙�˙ý�˙ü�˙ý��������˙ţ�˙ţ�˙ţ�˙ţ�˙ý�˙ý�˙ţ�˙˙�˙˙�˙˙�˙˙�˙˙�˙˙���˙˙���˙ý�˙˙�����˙ţ�˙ý�˙ý�˙ý�˙ű�˙ű�˙ü���˙ţ�˙ý�˙ý�˙ţ�˙ţ�˙ţ�˙ý�˙ţ���˙ý�˙ţ�˙˙�˙ţ��˙ý�˙ü�˙ţ�˙ý�˙ü�˙ţ����������������������˙˙���������˙˙�����������������������������˙ţ�˙ţ���˙ţ�˙ţ�˙ţ�˙ü�˙ü�˙ţ��˙ý�˙ý�����˙˙�������˙˙������������˙˙�˙ü�˙ţ��˙ţ�˙˙��˙ţ�˙˙����˙ţ���˙˙�˙˙˙˙�˙ý�˙ü�˙ü��˙ţ�˙˙˙˙�˙ü�˙ý�˙ý�˙ţ�˙ý�˙ţ�˙˙�˙˙˙˙�˙ţ�˙ý��˙ý�˙ü�˙˙˙˙���˙˙�˙ý�˙ţ���˙ý�˙ý�˙˙�˙ý�˙˙���˙˙�˙˙�˙˙���˙ţ���˙˙�˙ţ�˙ý�˙ú�˙ü�˙ý�˙ţ��˙ţ�˙ţ��˙ţ���˙˙�˙˙�����������˙˙˙˙�˙˙�˙˙˙˙�˙˙�˙ý�˙˙�˙ţ�˙˙�����˙˙���˙˙�˙ý�˙ţ�˙˙�˙ý�˙ţ���˙ý���˙˙�˙˙�����˙ţ�˙ý�˙ý�˙ý�˙ţ�˙˙�˙˙�˙ţ�˙˙�˙˙�˙ý�˙˙���˙˙�����˙ţ�˙ý�˙˙���˙ý�˙ü�˙ű�˙ţ˙˙�˙ű�˙ţ�����˙˙�˙˙���˙˙�˙˙���˙˙�˙˙�����˙˙���������˙˙˙˙�˙ý�˙ý�˙ţ��˙ţ�˙ý�˙˙���˙˙�˙˙���˙˙�˙ţ�˙ü�˙ţ˙˙�˙ý�˙˙�����˙˙�˙˙�����˙˙���˙ţ�˙ý�˙ţ���˙ţ�˙ű�˙ţ�����˙ţ�˙ü�˙ţ�˙ý�˙ü�˙ţ�˙ţ�˙ţ��˙ţ�˙ü�˙ý�˙˙���˙˙���˙ţ�˙ţ���˙˙���˙ţ���˙ţ�˙ý�˙ü�˙ü�˙ý�˙ţ�˙˙�˙˙�˙˙�˙˙���˙˙�˙ý���˙ţ�˙ţ���˙˙���˙˙�˙˙�˙˙���˙˙�˙˙�����˙˙�˙˙�˙ý�˙ý�˙ţ�˙ţ�˙ý�˙˙���˙˙�������˙ţ�˙˙˙˙�˙ţ�˙ţ�˙˙�˙˙�˙ý�˙ţ���˙˙���˙˙�˙ý�˙ţ�˙˙���˙˙�˙˙���˙ţ�˙ý�˙ţ�˙˙���˙ţ��˙ý�˙ý�˙ţ���������˙˙�����˙˙�˙ý�˙ü�˙ű�˙ú�˙ú�˙ý���˙˙�˙ţ�˙˙���˙ţ�˙ü�˙ű�˙ý�˙˙�˙˙�˙˙���˙˙�˙˙�˙˙�˙ţ�˙ü�˙ý�˙˙���˙˙�˙˙���˙˙�˙ý���˙˙���˙ţ�˙ü�˙ţ�˙ţ�˙ţ�˙ţ�˙ţ�����˙˙�˙ţ���˙ý�˙ţ�˙˙���˙˙���˙ţ�˙ţ�˙ý�˙ţ��˙ţ�˙ü�˙ţ�˙˙����˙˙�˙˙˙˙�˙ý�˙ţ�����˙˙�˙˙�˙˙˙˙�˙˙�˙ţ�˙ý�˙ţ���˙˙�˙˙�˙˙������������������˙˙�˙ţ�˙ü�˙ţ��˙ţ�˙ţ���������˙˙�˙ý�˙ü�˙ü���������˙˙���˙˙�����˙˙�˙˙���˙˙�˙ţ���˙˙�˙˙˙˙���˙˙�˙ţ�˙˙�˙˙�˙ţ�˙˙���˙˙�˙ý�˙˙��˙˙����˙ţ�˙˙��˙ý�˙ý���˙˙�˙˙�˙˙�˙˙�˙˙���˙˙���˙˙���˙ţ�˙˙�����˙ţ�˙ü���˙ţ�˙ý�˙˙�˙˙�˙˙�˙˙�˙ý�˙ü�˙ý�������˙ţ�˙˙�˙˙�˙ţ�˙ý�˙ţ�˙˙���˙ţ���˙˙���˙ý�˙ţ���˙˙�˙˙�˙ţ�˙ý�˙˙�����˙ü�˙ű���˙ţ�˙ý���˙˙�˙˙�����˙˙�˙ţ���˙˙��˙ý�˙ű�˙ű�˙ú�˙ţ�����˙˙�˙˙���˙˙�˙˙�������˙˙�˙˙�˙ţ�˙ţ���˙ţ�˙˙���˙ü�˙ű�˙ţ���˙˙�����˙˙���˙˙�˙ţ���˙ý�˙ü�˙ý�˙ţ���˙ý�˙ţ˙˙�˙ţ���˙ţ�˙˙���˙ţ�˙ţ�˙ţ�˙ţ�����˙˙�˙ţ��˙ţ��˙ţ�˙ţ��˙ţ�˙ţ�˙ţ�˙ţ��˙ţ��˙ý�˙ü�˙˙�����˙ţ�˙ý�˙˙���˙ý�˙˙���˙˙�˙ţ�˙ý�˙ţ�˙˙�˙ţ���˙˙�˙ţ���˙˙���˙˙�˙˙���˙˙�˙˙���˙˙���˙˙�˙ý�˙ţ���˙˙�˙˙���˙˙�˙ý�˙ţ���������˙˙�˙ţ�˙ý�˙ű�˙ű�˙ţ����˙˙���������˙˙˙˙�˙ű�˙ű�˙ţ�˙ý�˙ţ���˙ý�˙˙�˙˙�˙ţ�˙ţ���˙ţ�˙ţ���˙˙�˙˙�˙˙�˙˙���˙ţ�˙ý�˙˙˙˙�˙˙���˙ý�˙ţ���˙˙���˙˙���˙ţ�˙ű�˙ü�˙ţ�˙˙�˙ţ�˙ý�˙ţ���˙ţ�˙ţ�˙˙���˙˙�˙˙�˙ţ�˙˙���˙ü�˙ý�˙ţ�˙ţ�˙ţ���˙˙�˙ţ�˙˙�������˙˙�˙˙���˙˙�˙ý�˙ü�˙ţ����˙ţ���˙˙�˙ü�˙˙���˙ţ�˙ý���˙˙�˙˙�˙˙�˙ţ�˙ý�˙ý�˙˙�����˙˙����˙˙�˙ţ�˙ţ���˙˙�˙ţ�˙ţ������˙ţ������˙ţ�˙ý�˙ü�˙ţ���˙˙�����˙ý�˙ý���˙˙���˙˙�˙ý�˙ü�˙ţ���˙˙���˙ţ�˙ű�˙ý��˙ţ�˙ţ���˙˙�˙ţ�˙ţ�˙ţ�˙ý�˙˙���˙ý�˙˙�˙˙�˙ţ�˙ý�˙˙���˙ý�˙ý���˙˙�˙ý�˙˙���˙˙���˙˙�˙˙�˙˙���˙˙�˙ţ������˙ţ�˙ţ�˙ü�˙ű�˙ű�˙ţ������˙ţ�˙˙���˙˙���˙˙���˙˙�˙ý�˙ý���˙ţ�˙ţ���˙˙���˙˙���˙˙�˙ţ�˙ţ�˙ý�˙ţ��˙ţ�˙ţ�˙˙���˙ţ�˙ý�˙ý�˙˙���˙˙�����˙ţ�˙ý�˙˙�˙˙���˙˙�˙˙���˙˙���˙˙�˙ý�˙ü�˙ţ���˙˙�˙˙�˙˙�˙˙���˙˙�˙˙�˙˙�˙ý�˙ţ�˙˙�˙ţ�˙ţ��˙ý�˙ű�˙ý���˙˙��������������˙ţ�˙ţ�˙˙����˙˙�˙ţ���˙ţ�˙ü�˙ţ���˙ţ���˙˙�˙ţ�˙˙�˙ţ���˙˙�˙˙�˙˙�˙ý�˙˙�˙˙�˙ü�˙ű�˙ü�˙ü�˙ý�˙ţ�����˙˙�˙˙�����˙˙�˙˙�˙˙�˙˙�˙˙�����˙˙���˙ţ�˙˙���˙˙�����˙˙�˙˙�˙˙�˙ý�˙ţ���˙˙�˙˙�˙˙�˙˙�˙˙�˙ţ�˙ý�˙ý�˙ý���˙ţ�˙ű�˙ű�˙ţ���˙˙���˙˙�˙ţ����������������˙ý�˙ű�˙˙˙˙�˙˙˙˙�˙ţ�˙ý�˙ý�˙ü�˙ý�˙˙�˙˙�˙˙���˙˙�˙ţ�˙ý�˙ý�˙˙�˙ţ�˙ý�˙ý�˙ţ�˙ý�˙˙˙˙�˙ü�˙ü�˙ý���˙˙�˙ţ�˙˙���˙˙�˙ý�˙˙���������˙ţ�˙˙���˙ţ�˙ţ�˙ţ�˙ý�˙˙�����˙˙�˙˙˙˙�˙ü�˙ú�˙ű�˙ý�˙˙���˙ţ�˙ý�˙˙�����˙ý�˙ý�˙˙�˙˙�˙ý�˙ţ�˙ţ�˙ý�˙˙�˙ţ���˙˙�˙ţ���˙˙���˙˙�˙ţ���˙ý�˙ý��������˙ţ�˙ţ�˙˙˙˙�˙ţ�˙˙�������˙ţ�˙ý�˙˙�˙˙�˙˙�˙˙�˙˙�˙˙�˙ü�˙ý�˙˙�˙ý�˙ý��˙ű�˙ú�˙ü��������˙ţ�˙ţ�˙˙���˙ţ�˙˙���˙ţ��˙ţ�˙˙˙˙�˙ţ�˙ţ�˙ţ�˙˙˙˙�˙ü�˙ý�˙˙�˙ţ�˙ý�˙ý�˙˙�˙˙�˙˙�˙ţ�˙ü�˙ů�˙ű�˙ţ�˙˙�˙˙���˙˙���˙˙�˙ţ���˙ý�˙ü�˙ţ���˙˙�˙˙�˙ţ�˙˙���˙ţ�˙ţ���˙ý�˙ý�˙ţ���������˙˙�˙ţ���˙˙���������˙˙�˙ţ���˙˙�˙ý�˙ý�˙ý�˙ý�˙ý�˙ý�˙ţ�˙˙���˙˙�˙ý�˙ţ���˙ý�˙ű�˙ţ�˙˙�˙ý�˙ţ���˙ü�˙ý������˙˙����˙˙�˙ţ�˙ţ�����˙˙�˙ý�˙ú�˙ü�˙˙�����˙ţ�˙ţ�˙ý�˙˙���˙ţ�˙˙���˙ţ��˙ţ�˙ţ�˙˙���������˙˙�����˙ţ�˙ü�˙ý�˙ü�˙ú�˙ű�˙ý�˙˙�����˙ţ�˙ý��˙˙���˙ţ���˙ţ�˙ü�˙ý�˙˙�����˙ţ�˙ý���˙ţ�˙ý�˙˙���˙ţ�˙ţ�˙˙�������˙ţ�˙ţ�˙ţ��˙ţ�˙ű�˙ý�˙˙�����˙˙���˙ţ�˙ý�˙ý�˙˙�˙˙�˙ţ�˙ý�˙˙���˙ţ�˙˙�˙ţ�˙ţ�˙ý�˙˙���˙ţ�˙˙���˙˙˙˙�˙ý�˙˙���˙˙�˙ţ�˙˙�˙˙���˙˙�˙˙�˙˙�˙ý�˙ý�˙ý�˙ţ���˙ý���˙˙���˙˙���˙˙�˙˙�˙˙�˙˙�˙˙�˙˙�˙ü�˙ű�˙ü�˙ý�˙ý�˙ţ�˙˙�����˙ý�˙ý�����˙˙�˙ý�˙˙˙˙�˙ţ���˙˙�˙ţ�˙ý�˙ţ�˙ţ�˙ţ��˙ţ��˙ü�˙ű���˙˙�˙ţ���˙˙�˙ţ�˙ý�˙ý���˙˙�˙˙�˙˙�˙˙�˙˙���˙ţ�˙˙���˙ţ�˙ţ�˙ţ�������˙˙�˙˙���˙˙�˙˙�˙˙�˙ţ�˙˙���˙ý�˙ý��˙ţ�˙˙���˙˙�˙˙���˙ý�˙˙���˙˙�����˙ţ�˙ţ��˙ţ�˙ţ����˙˙���˙ţ�˙ý�˙ý�˙˙���˙ü�˙ü�˙ţ���˙˙�˙ü�˙˙˙˙�˙˙˙˙�˙ţ�˙˙�˙ţ�˙ü�˙ű�˙ţ���˙˙���˙ţ�˙ý�˙˙˙˙�˙ţ�˙˙˙˙�˙ţ��������˙ţ�˙˙�����˙˙�˙˙���˙˙�˙˙�˙˙�˙˙�˙˙�˙˙�˙˙�˙˙�˙˙�˙ţ�˙ý�˙˙���˙ţ�˙ţ�˙ý�˙˙�������˙ţ�˙ţ�˙ţ�˙ţ���˙˙�˙ţ�˙ý�˙˙˙˙�˙ţ��˙ţ�˙ţ������˙ý�˙ţ��˙ý�˙ţ���˙ţ�˙ý�˙ţ���˙˙���˙˙���˙ţ�˙ý�˙˙�˙ţ�˙ü�˙ý�˙ý�˙ý���˙ţ�˙ű�˙ű�˙ű�˙ü�˙ţ�˙˙�˙ţ�˙ţ����˙ţ�˙ţ���������˙˙�˙˙�˙ý�˙ţ���˙ý�˙ý�˙ý�˙ţ�˙˙�˙ý�˙ü�˙˙˙˙�˙˙˙˙�˙ý�˙ü�˙ü�����˙˙�˙ý�˙ü�˙ţ�˙ţ�˙ţ�˙ţ�˙ţ�˙ţ�˙ţ�˙ý�˙ţ�˙ţ�˙˙�˙ţ�˙ý�˙˙˙˙�˙ý���˙˙���˙ţ�˙ý�˙ţ�˙ý�˙ý���˙˙�˙ý��˙ţ���˙ţ�˙ý���������˙˙�˙ţ�˙˙˙ţ�˙ű�˙ű�˙ţ�˙ý�˙ý�˙˙˙˙�˙ý�˙ţ�˙˙�˙ý�˙ü�˙ý���˙ý�˙˙˙˙�˙ý�˙˙˙˙�˙˙���˙˙�˙˙���˙ţ�˙ű�˙ý�˙˙�˙˙�˙˙���˙˙�˙ţ�˙ţ�˙ý�˙˙�����˙ţ�˙ü�˙ü�˙ú�˙ü�˙˙˙˙�˙ţ�˙ţ�˙ţ�˙ţ�˙ţ��˙ţ�˙ţ��˙ţ�˙ü�˙˙���˙ţ��˙ţ�˙ţ�˙ţ�˙ű�˙ý���˙˙�˙˙���˙ţ�˙ű�˙ü�˙ý�˙ţ�˙ţ�˙˙�˙ţ�˙ţ��������������˙ţ�˙ý�˙ý���˙ţ�˙ý�˙˙�����˙˙���˙˙���˙ţ�˙ţ��˙ý�˙ý�˙ţ�˙ý�˙ý�˙ý���˙˙�˙ţ���˙ţ�˙ý���˙˙���˙ý���˙ţ�˙ý�˙˙���˙˙���˙ţ�˙ü�˙ý��˙ü�˙ý�˙˙�˙ü�˙ů�˙ú�˙ţ�˙˙���˙ţ�˙ü�˙ü�˙˙�����˙˙���˙ţ�˙˙˙˙�˙˙������˙˙�˙ü�˙ü�˙ü�˙ý�˙˙�˙ţ�˙ý�˙ţ���˙˙�˙˙�˙˙�˙˙���˙˙���˙ţ�˙˙�����˙˙���˙ţ���˙ţ�˙˙���˙˙���˙˙���˙˙�˙˙�����˙˙�˙˙���������˙˙˙˙�˙ţ�˙˙˙˙�˙ţ�����˙˙�˙ý�˙ţ���˙˙�˙ţ���˙˙���˙˙�˙˙�˙˙�˙˙�˙˙�˙˙�˙˙�˙˙�˙˙�˙˙�˙ý���˙˙���˙ţ�˙ţ���˙ý�˙ý���˙˙�˙˙���˙˙�˙˙�˙˙���˙˙�˙˙���˙ţ�˙ü�˙ú�˙ű���˙ţ�˙˙˙˙�˙ü�˙ý�˙˙�������˙ţ�˙ü�˙ý���˙˙�˙ţ�˙ţ�˙˙�˙ţ�˙ţ��˙ţ�˙ý�˙˙�˙ţ�˙ţ�˙ţ���˙ý�˙˙��������˙˙�˙ü�˙ý���������������˙˙�˙ţ�����˙˙�˙ţ��˙ý�˙ü�˙˙˙˙�˙ţ���˙˙�˙ţ�˙ý�˙ţ���˙˙�˙ý�˙ý�˙ţ�˙ý�˙ű�˙ý�˙ţ�˙˙�˙ţ�˙ţ�����˙˙�˙ý�˙ý�˙ý�˙ţ���˙˙���˙ţ�˙ý�˙˙���˙˙˙˙�˙ţ�˙ţ������˙ý�˙ţ�˙˙���������˙ţ�˙˙�˙˙�����˙˙�˙ţ�˙˙����������������˙˙�˙˙�˙ţ�˙˙���˙ţ���˙ü�˙ý���˙˙�˙˙�˙ü�˙ţ���˙˙���˙˙���˙˙�˙˙�˙˙���˙˙���˙˙�˙ý�˙ţ���˙˙�˙˙�˙˙�˙˙���˙˙�˙ţ�˙ţ�˙ý������˙ţ�˙ţ��˙ý�˙ú�˙ý��˙ţ��˙ţ���˙˙�˙˙˙˙���˙˙�˙ű�˙ú�˙ý���˙ý�˙ű�˙ű�˙ţ�˙˙�˙˙�˙˙�˙˙�˙˙�˙˙���˙˙�˙˙���˙ý�˙ţ���˙˙�������˙˙�˙˙�����˙˙���˙˙�˙ţ���˙ţ�˙ý�˙ü�˙ý�˙˙�˙ţ�˙ű�˙ű�˙ý���˙ü�˙ý���˙˙���˙˙���˙˙���˙˙�˙ý�˙ý�˙ű�˙ý���˙ű�˙ţ������˙˙�˙ü�˙ţ���˙˙�˙ý�˙ű�˙ű�˙ý���˙ý���˙˙���˙˙��������������˙ţ�˙˙�˙˙���˙˙���˙ţ�˙ţ�˙˙�˙˙�˙ţ�����������������������˙˙�˙ţ��������˙ý�˙ü�˙ý�˙˙�����˙ţ�˙˙�˙˙�����˙˙�˙ţ�˙˙�˙ţ�˙ü�˙ý�˙ţ�˙ţ�˙˙˙˙�˙ý�˙˙��˙˙�˙˙���˙˙���˙ţ�˙ű�˙ü���˙˙�˙˙���˙˙�˙ţ�˙˙�˙˙�����˙˙�˙˙���˙ţ�˙ű�˙ű�˙ü�˙ţ�������˙˙�˙ü�˙˙˙ţ�˙ü�˙ţ���˙˙�˙ţ��������˙ţ���˙˙�˙ţ�˙˙�˙˙���˙˙�˙˙�����˙˙�������˙˙�˙ţ�˙˙���������˙˙�������˙˙�����˙ţ�˙ü�˙ţ���˙ţ�˙ý�˙ý�˙ý�˙ý�˙˙�˙˙�˙ţ�˙˙���˙˙�˙˙�˙ý�˙ţ�˙˙�˙˙�˙ý�˙˙���˙ý�˙˙���˙ý�˙ţ���˙ţ�˙ý�˙ţ��˙ý�˙ü�˙ý��������˙ý�˙ţ�˙ţ�˙˙�����˙˙�˙˙���˙˙�����˙˙�˙ţ��˙ţ���˙˙�˙ţ�˙˙���˙˙�˙ý�˙ü�˙ţ�˙ţ�˙˙���˙˙�˙ü�˙ţ���˙ý�˙˙�˙˙�˙˙���˙ţ�˙ý�˙˙�����˙ţ�˙˙���������˙˙��˙˙�˙ü�˙ű�˙ţ�˙˙������˙˙�˙ü�˙ţ�����˙˙�˙ý�˙ü�˙ţ�˙˙���������˙ţ�˙˙�����˙ţ�˙˙���˙ü�˙ü�˙ý�˙ţ��˙ţ�˙ý�˙ý�˙ý������˙˙�������˙ţ�˙˙���˙ţ�˙ü�˙ú�˙ű�˙˙������˙˙�˙˙��˙˙�˙˙�˙ü�˙ý�˙ý�˙ű�˙ű�˙ű�˙ű�˙ý�˙ţ���˙ţ�˙ý�˙˙�˙ţ�˙ü�˙ţ���˙ţ�˙˙�˙ţ�˙ţ�˙˙��˙˙�˙ţ�˙˙˙˙�˙ü�˙ý�˙˙�˙ţ�˙ü�˙ý�˙˙˙˙�˙˙�˙ţ�˙˙���˙ţ�˙ţ���˙ţ�˙ü�˙ţ�˙˙�˙ţ�˙˙�˙˙�˙ţ�˙˙�˙˙�˙ţ�˙˙�˙˙�˙ţ�˙˙�˙ţ�˙ţ�˙ţ�˙ţ�˙˙����˙˙�˙ţ�˙˙˙˙�˙ü�˙˙���˙˙˙˙�˙˙�����˙ţ�˙˙˙˙�˙ý��������˙ţ���������������˙˙�˙˙�˙ţ�˙˙���˙ý�˙ý�˙˙�˙˙�˙˙�˙˙�˙˙�����˙˙�˙˙�˙ý�˙ţ���˙˙��˙ý�˙ü�˙ţ�˙ţ�˙ý�������˙˙�������˙˙���������˙˙�˙ţ���˙˙�˙ţ���˙˙�˙ţ���������˙˙�˙˙�˙ţ�˙ţ���˙ţ�˙˙˙˙�˙ţ�˙ü�˙ţ�˙˙�����������˙ţ�˙ţ�˙˙�����˙˙���˙ţ�˙ý�˙ý���˙ţ�˙ţ���˙˙���˙ü�˙ü�˙ű�˙ý�˙˙�˙˙�˙˙�˙˙���˙˙���˙˙���˙ţ�˙ű�˙ü�˙ý�˙ţ���˙˙�˙ţ�˙ý�˙ţ�����˙˙�˙˙���˙˙�˙ý���˙ţ�˙ţ���˙ü�˙ţ���˙˙���˙ţ�˙ţ�˙ü�˙ü�˙˙������˙˙�˙ţ�����˙˙�˙˙���˙˙�������˙ţ�˙ü�˙ţ���˙ý�˙ý�˙ţ���˙ý�˙ü���˙˙�˙ţ�˙˙�˙ý�˙ţ���˙ý�˙ţ�˙˙�˙ý�˙ü�˙ţ�����˙˙�˙˙���˙˙���˙˙�˙ý�˙ý���˙˙�˙˙���˙˙���˙˙�����˙˙�˙ţ������˙ţ�˙ý�˙˙˙˙�˙ý�˙ű�˙ú�˙ţ���˙ţ���˙˙�˙ţ�����˙˙�˙ý�˙ü�˙ţ�˙ţ���˙˙�˙ü�˙ţ���˙ţ���˙˙�˙ý�˙ý�˙ţ���˙˙���˙ţ�˙ü�˙ü�˙ű�˙ý�˙˙���˙˙�˙˙���˙ý�˙˙���˙ţ�˙ü�˙ţ���˙ţ���˙˙�˙˙�˙˙�˙˙���˙˙�˙˙�˙˙���˙˙���˙˙�˙ţ���˙˙�˙ý�˙ý�˙ý�˙ý�˙ý�˙˙���˙˙�˙˙���˙˙���˙˙�˙ý�˙ţ���˙ý�˙ţ���˙ý�˙˙�˙˙�˙˙���˙ţ�˙ý�˙ü�˙ý�˙˙������������������˙˙����˙ţ�˙ý�˙ý�˙ü�˙ü�˙ý���˙˙�˙ţ�˙˙�˙˙�˙ý�˙ü�˙ű�˙ü�˙˙���˙˙�˙˙���˙ţ�˙ý��˙ű�˙ů�˙ű�˙ý�˙˙���������˙ţ�˙ţ�˙˙�������˙ţ�˙ý�˙˙���˙ý�˙ý�˙ţ�˙˙�˙˙�˙˙�˙˙�˙˙�˙ţ�˙ţ�˙ü�˙ü�˙ţ�˙ţ�˙ţ�˙˙��������������˙˙�˙ý�˙ţ���˙˙�˙˙�˙˙�˙˙�˙˙�����˙ţ�˙ý���˙ţ�˙ý�˙˙���˙˙���˙˙�˙˙�˙˙�˙˙�˙ţ�˙ý�˙ţ���˙˙�˙ý�˙ý�˙ý�˙ý�˙ţ���˙ţ�˙ü�˙ţ�����˙˙�˙˙����˙˙�˙˙�˙ţ�˙ţ�˙ü�˙ý�˙˙��˙˙�˙ţ��˙ý�˙ü�˙˙˙˙�˙ţ�˙˙��˙˙���������˙˙���˙˙�˙ţ�˙ţ�˙ţ����˙ţ�˙ý�˙˙˙˙�˙ţ�˙ţ�˙ý�˙ü�˙ţ���˙˙�˙ý���˙˙�˙ý���˙˙�˙ý�˙ţ���������˙˙�˙ţ��˙ţ�˙˙˙˙�˙ý�˙ý�˙ý�˙ţ�˙˙�˙˙�˙˙�˙ţ�˙ý�˙˙˙˙�˙ü�˙ý��˙ţ�˙˙�����˙ţ�˙˙�˙˙�����˙˙�˙ţ�˙ý�˙ţ��˙ţ�˙ţ�˙ţ�˙ţ�˙˙����������˙˙�˙ü�˙ü�˙˙˙˙�˙ý�˙ü�˙ţ���˙˙�˙ţ���������˙˙���˙˙�˙ý�˙ü���˙˙�˙ý�˙ţ���˙˙�˙ţ�˙˙�����˙˙�˙˙���˙˙�˙˙�˙˙���˙ţ�˙ü�˙ţ�˙ţ�˙ý�˙ţ���˙ý�˙ţ���˙˙�˙˙�˙˙���˙ţ�˙ü�˙ý�˙˙�����˙ţ�˙ý�˙ý�˙ý�˙ţ�˙˙���˙˙�˙˙�����˙˙���˙˙�˙˙���˙˙���˙˙���˙˙�˙ţ���������˙˙�˙˙�˙ţ�˙ý�˙ţ�˙ţ�˙ţ���˙˙�˙ţ���˙˙�˙ý�˙ţ���˙˙�˙˙���˙˙�˙˙�����˙˙�˙ţ�˙˙���˙ţ�˙˙���������������������������˙ţ�˙ý�˙˙���˙˙�˙˙���˙˙���˙ţ�˙ý�˙˙��������������˙˙�˙˙���˙˙�����˙ţ�˙˙�����˙˙���˙˙�˙˙���˙˙�˙˙�˙˙�˙˙�˙˙���˙ţ�˙ý��˙ţ�˙ý�˙ţ���˙˙���˙ţ�˙˙˙˙�˙ý�˙ţ�˙ţ�˙ţ�˙˙����˙˙�˙˙���˙ţ�˙ţ�˙˙���˙ţ�˙ţ�˙ţ�˙ü�˙ü�˙ý�˙˙���˙ţ�˙˙�˙˙�������˙˙���˙ý�˙ü�˙ý������������˙ţ�˙ü�˙˙��������������������������˙˙�˙˙�˙ý�˙ý�˙ţ�˙ţ�˙ţ�˙˙������˙˙�˙ţ�˙˙�˙ý�˙ü�˙˙�����˙˙�˙ţ�˙˙�˙˙�˙ţ�˙ý���˙ţ�˙ý�˙ý�˙ü�˙ű�˙ű�˙ţ���˙˙�˙˙�˙˙���˙ţ�˙ý�˙ţ���˙˙���˙˙�˙˙�˙˙�˙˙�˙˙�˙ţ�˙ţ���˙˙��������˙ý�˙ţ�˙˙��˙˙�˙ţ�˙ţ�˙ţ�˙ý�˙˙˙˙�˙ű�˙ţ�˙˙�˙˙���˙ý�˙ý�˙ţ���˙˙���˙ţ�˙ý�˙˙�����˙ý�˙ü�˙ü�˙ţ���˙˙�˙ţ�˙ţ���˙˙�˙ţ���˙˙�˙˙���˙˙�˙˙���˙ý�˙ý�˙˙���˙ţ�˙ü�˙ţ���˙ü�˙ý�˙˙�˙˙�������˙ţ�˙ţ�˙ü�˙ý�˙˙˙˙�˙˙�˙˙�˙˙���˙˙���˙˙�˙˙�˙ţ�˙ý���˙˙�����˙ţ�˙ü�˙ý������˙ţ�˙ţ�˙ý�˙ţ�˙ý�˙ý���˙˙�˙˙���˙˙�˙˙�˙ý�˙ţ���˙ţ���˙˙���˙ý�˙˙�˙ţ�˙ý���˙ţ�˙ü�˙ý�˙ý�˙˙���˙˙�˙˙�˙˙˙˙�˙ţ���˙˙�����˙ţ�˙ý�˙˙���˙˙�������˙˙���˙˙�˙˙���˙˙���˙˙���˙ý�˙ü����������������������˙ţ�˙˙˙˙�˙ü�˙ý�˙ţ�˙ţ�˙˙˙˙�˙ý�˙ü�˙ţ�˙ţ�˙ţ��˙˙�˙ţ�˙ţ�˙˙˙˙�˙˙����˙˙��˙ţ�˙ü�˙ţ�˙ţ�˙ţ��˙ţ�˙˙�����˙ţ�˙ý�˙ţ�˙˙�����������˙ý�˙ú�˙ű�˙ü�˙˙���˙ţ��������˙ţ�˙ţ�˙˙������������˙˙�˙ý�˙ü�˙ü�˙ţ�˙ţ�˙ţ���������˙˙�������˙˙�����˙˙�˙ţ���˙˙�˙ţ�˙˙�˙ţ�˙ţ�������˙˙�˙ý�˙ţ�˙˙�˙ý�˙ý�˙˙�˙˙�������˙ţ�˙ý�˙˙���˙˙�˙˙���˙˙���˙ţ�˙ý�˙˙�˙˙�����˙˙�˙ý�˙˙���˙˙�˙˙�˙˙�˙˙���˙˙�������˙˙���������˙ţ�˙ü�˙ţ�˙ţ�˙ţ���˙˙�˙˙���˙˙�˙ţ�˙ý�˙ý�˙ţ�˙˙�˙˙�˙ţ�˙˙�˙˙���˙ţ�˙ý�˙ý�˙ţ�˙ţ�˙ţ�˙ý�˙ţ���˙ý���˙ţ�˙ý�˙˙�������˙ţ�˙ţ�˙ţ�˙ţ�˙ü�˙ý�˙˙�����˙˙������������������˙˙���˙˙�˙ţ���˙ü�˙ü�˙ţ�˙˙���˙˙�˙˙���˙˙���˙˙���˙˙�˙˙�˙˙�˙ţ���˙ý�˙ţ���˙˙�˙˙�˙˙���˙˙���˙ţ�˙ý�˙ţ�˙˙�˙˙���˙˙���˙˙�˙ţ�˙˙���������˙ţ�˙ţ�˙ţ�˙ţ�˙˙�����������˙ţ�˙ţ�˙ţ�˙ţ�˙ţ���˙˙�˙ţ�����˙˙�˙ţ�����˙˙�˙ý�˙ţ��˙ý�˙ü�˙˙˙˙�˙ü�˙ţ�������˙˙�˙ţ�˙ţ��˙ţ�˙ű�˙ű�˙ý�˙˙�˙˙���˙˙�˙˙���˙˙���˙˙���˙ü�˙ţ���˙ţ�˙˙������������˙˙�˙ü�˙ü�˙˙���˙ý�˙ý�˙˙���˙˙�˙˙�˙˙�˙ý�˙ý�˙ý�˙ţ�˙˙����˙˙�˙˙���˙ţ�˙ţ�˙˙����˙˙�˙˙��˙˙�˙ţ���˙˙�˙ţ�˙˙�����˙˙�˙ţ���˙˙���˙˙�˙˙�˙ţ�˙ţ�˙˙�˙˙���˙˙�˙˙�˙˙�˙˙���˙ý�˙ý���˙˙�˙˙�˙˙�˙˙���˙˙�˙˙���˙˙�˙ý��˙ý�˙ú�˙ý��˙ţ�˙ţ�������˙˙�˙˙�˙˙�˙˙�˙ţ�˙˙���˙ţ�˙˙�˙ţ�˙ţ���˙ţ�˙˙���˙˙���˙ý�˙ý�˙ý�˙ý�˙˙�����������˙ţ�˙˙���˙˙���˙˙���˙˙�˙ý�˙ţ���˙ý�˙ý�˙˙�˙˙�˙ţ�˙ý���˙ţ�˙ý�˙˙������������˙˙�˙ţ���˙˙�˙ţ�˙ý�˙ý������˙˙�˙ý�˙ü�˙ý�˙˙�˙˙���˙˙�˙ţ�˙ý�˙ţ���˙ý�˙ý�˙ý�˙ţ�˙˙�����˙ţ���˙ţ�˙ţ�˙ţ�˙ý�˙ţ���˙˙���˙˙�˙˙�˙˙�˙˙�˙ü�˙ú�˙ü�˙˙���������������������������������˙ţ������˙ţ�˙ţ�˙ţ�˙ţ�˙˙˙˙�˙˙˙˙�˙ü�˙ţ�˙ţ�˙ţ�������������˙˙�˙ţ�˙ţ���˙ţ�˙˙�˙˙�˙ý���˙ţ�˙ű�˙ű�˙ü���˙˙�˙˙�˙ţ�˙ű�˙ý�˙˙�˙˙���˙˙�˙˙�˙˙�˙˙�˙ý�˙ý�˙ţ���˙ţ�˙ü�˙ţ���˙˙���˙˙�˙ţ���˙˙�˙ý���˙˙���˙ţ�˙ţ���˙ü�˙ü�˙˙�˙˙�˙ý�˙˙�����˙ţ�˙ý�˙ţ���˙˙�˙˙���˙˙�˙˙���˙˙�˙ý���˙˙�˙ţ���˙˙���˙˙�˙˙�˙˙�˙ý�˙ţ�����˙˙�˙ţ���������˙˙�˙˙���������˙ý�˙ý�˙˙˙˙�˙˙���˙˙���˙˙���˙ý�˙ű�˙ţ���˙˙���˙ý�˙ţ���˙˙�����˙˙�˙˙���˙˙���˙˙���˙ţ���˙ţ�˙ý�˙ý�˙ü�˙ű�˙ű�˙ý���˙˙�˙˙�˙˙�˙˙�˙˙�����˙ţ�˙ţ�˙˙���������˙ţ�˙ţ��˙ţ�˙ý�˙ţ�˙ţ��������������������˙ţ�˙ţ�˙ţ�˙ţ�˙˙���˙ý�˙ý�˙˙��˙˙�˙ü�˙˙˙˙�˙ţ��˙ý�˙ü�˙ţ�˙ţ�˙ţ�˙˙�������˙ý�˙˙���˙˙���˙˙���˙ţ�˙˙���˙ţ�˙ţ�˙ý�˙˙���˙ţ�˙˙�˙ţ�˙ţ�˙˙�˙ţ�˙ü�˙ý�˙ţ�˙ţ�˙ý�˙ţ�˙˙�˙ý�˙ý�˙˙�˙˙�˙˙���˙˙�˙ţ���˙ý�˙ý���������˙˙�˙ţ���������˙˙������˙ý�˙ý�˙ü�˙ű�˙ý�����˙˙�˙ţ��˙ţ�˙ţ�˙ţ�˙˙�˙ţ�˙ý���˙ţ�˙ý�˙ý���˙˙�˙˙�����˙˙�˙˙�˙˙�˙˙���˙ţ�˙˙�����˙ţ�˙ţ�˙˙�����˙ţ�˙ý�˙ţ�˙˙�˙˙�˙˙�˙˙�����˙˙�˙˙�˙˙�˙ţ��˙ţ�˙ţ���˙˙�˙ü�˙ţ���˙ý�˙ţ��˙˙���������˙ţ�˙ţ�˙ţ�˙ţ�˙ţ�˙ţ�˙ţ�˙˙���˙˙�˙ţ�˙˙�˙˙�˙ţ�˙˙���˙ü�˙ű�˙ţ�˙˙�˙˙���˙˙�����˙˙�˙ý�˙ţ���˙˙�˙˙���˙ţ�˙ţ���˙ţ�˙ű�˙ü���˙˙���˙˙�˙˙���˙˙�˙˙���˙ţ�˙ű�˙ü���˙˙�˙˙�˙˙���˙˙�˙˙˙˙���˙˙�˙ţ�˙˙�˙˙���˙ţ�˙˙���˙ţ�˙˙�˙˙�˙˙�˙˙���˙ţ���˙ţ�˙ý�˙ý�˙˙�����˙ţ�˙˙���˙ý�˙˙�˙˙�˙˙�˙˙�˙˙�˙˙�������˙˙�˙ţ�˙ţ�˙ý�˙ý�˙ý�˙ý���˙ţ�˙ű�˙ý�˙˙�˙˙�˙˙�˙ţ�˙ű�˙ü�˙ţ�˙˙���������˙˙�����˙˙�˙ţ�˙˙�˙˙�˙ý���˙˙�˙ü�˙ţ���˙˙���˙˙�˙ý�˙ý�˙ý�˙ţ���˙˙���˙˙�˙˙���˙˙�˙˙�����˙˙���˙˙�˙ý�˙ű�˙ü�˙ţ�������˙˙�������˙˙���˙˙�˙ý�˙ý�˙ý�˙ű�˙ţ���˙ý���˙˙�˙˙�˙˙�˙˙���˙ţ�˙˙˙˙�˙˙˙˙�˙ü���˙ý�˙ű�˙ý�˙˙�˙˙�����������˙ţ�˙ţ�˙˙�˙ţ�˙ý���˙ţ���˙ţ�˙ý�˙˙���˙˙�˙ü�˙ú�˙ű�˙˙�˙ţ�˙ű�˙ţ���˙˙�˙˙�˙˙�˙˙�˙ý���˙ţ�˙ý�˙˙�˙˙���˙˙���˙˙���˙˙�˙˙�˙˙�˙˙�˙˙�����˙ü�˙ű�˙ţ���˙˙�˙˙�˙˙�˙˙���˙ý�˙ý���˙˙�˙ý�˙ţ�˙˙�˙˙�˙ý�˙ý�˙˙�˙˙�˙˙�����˙˙�˙ţ�˙˙���˙ţ�˙˙�����������˙˙˙˙�˙ű�˙ú�˙˙˙˙�˙ý�˙ţ�˙˙���˙ţ�˙ý�˙ý���˙˙���˙˙�˙˙�����˙˙�˙˙�˙˙�˙˙�˙ý�˙˙���˙ü�˙ű�˙˙���˙˙˙˙�˙ý�˙ý�˙ţ�˙˙���˙ţ�˙ţ�˙ý�˙ý�˙˙�˙˙���˙˙�˙˙�˙˙���˙˙�˙ý�˙˙˙˙�˙ý�˙˙˙˙�˙ý�˙ü�˙ţ���˙ţ���˙˙�˙ţ�˙ţ�˙ţ�˙ţ�˙ţ�˙ţ���������˙˙���������˙˙�˙ý�˙ü�˙˙˙˙�˙ţ���˙˙���˙ţ�˙ý�˙ý�˙ţ�˙ý�˙ý�˙˙���˙˙�˙˙���˙˙�˙˙�˙˙�˙˙�˙˙���˙˙�˙ţ�˙ý�˙ý�˙˙�˙˙�˙ţ�˙ý�˙ţ���˙ţ�˙ý�˙˙���˙ţ�˙˙�˙ţ�˙˙�˙˙�˙˙�˙˙���˙ţ�˙˙������������˙˙�˙ţ�˙ţ���������������˙˙�˙ţ�˙ţ����������˙ţ�˙˙���˙ţ�˙ü�˙ű���˙ţ�˙˙���˙˙�˙˙���˙˙�˙˙�˙˙�˙ý�˙ý�˙ţ���˙˙�˙˙�˙ţ�˙ý��˙ý�˙ü��˙ý�˙ţ��˙ţ��������˙ţ��˙ţ�˙˙˙˙�˙˙�����������������������������˙˙�˙ţ���˙ţ�˙ü�˙ţ����˙˙�˙ý�˙˙˙˙�˙˙�����˙ý�˙ý�˙˙�����˙ţ�˙ţ�˙˙˙˙�˙˙�˙ţ�˙˙�˙˙�˙˙�˙˙�˙ý�˙ý�˙ü�˙ţ�������˙˙�˙ţ�˙ţ�˙ţ��˙ţ�˙˙˙˙�˙˙���˙ţ�˙ţ�˙ţ�˙˙���������˙ţ�˙˙�˙˙�������˙˙�˙˙���˙ý�˙ý�˙˙�˙˙�������˙˙�������˙˙�˙˙���˙˙�˙˙���˙ţ�˙ţ���˙˙�˙˙�˙˙�˙˙�˙˙�˙˙�˙ý�˙ý���˙˙�˙˙�˙˙�˙˙���˙˙�˙˙�������˙˙�˙˙�˙ţ�˙ý�˙ý�˙ţ�˙˙���˙˙�˙˙���˙˙�����˙ţ�˙˙���������˙ţ�˙ţ�˙ý�˙ţ�˙ţ�˙ý�˙ü�˙ý���˙ţ�˙ţ�˙ţ�˙ţ���˙˙�˙ţ�˙ţ�˙ţ�˙˙˙˙�˙ý�˙ţ�˙˙�˙˙���˙˙�˙ţ�˙ý�˙ý�˙˙���˙˙���˙˙�˙ţ���˙˙���˙˙�˙ţ���˙ý�˙ţ���˙ý�˙ý�˙ţ�˙˙�˙˙�˙ý�˙ű�˙ý��˙ţ�˙˙�˙ţ�˙ý�˙˙���˙ţ�˙ţ�˙ţ���˙˙�˙ý�˙ü�˙ţ���˙ý�˙ü�˙ý�����˙˙�˙˙���˙˙���˙ţ�˙ü�˙ü�˙˙����������������������˙˙�˙ţ�˙ü�˙ţ�����������˙˙�˙ţ�˙ţ���������˙˙����˙ţ�˙˙�������˙˙���˙˙���˙˙�˙ţ�����˙˙�˙˙�˙ţ�˙˙���˙ţ�˙ý�˙ý��˙˙�˙˙�����˙˙�˙˙�������˙ý�˙˙���˙˙�˙˙�˙ţ���˙˙�˙˙˙˙�˙ý�˙ü�˙ý�˙ţ�˙˙�˙˙�˙˙�˙˙�˙ţ�˙˙�˙ţ�˙ü�˙ý�˙˙�˙ţ�˙ý�˙ý�˙ý�˙ű�˙ý���˙ţ�˙ţ�˙ý�˙ü�˙ý�˙˙�����˙ţ������˙ţ�˙ţ�˙ý�˙˙���˙˙�˙˙�˙ţ�˙ü�˙ú�˙ű�˙˙˙˙�˙˙˙˙�˙ţ�˙ţ�˙ţ�˙ţ���������˙˙���������˙˙�˙ţ�˙˙����˙˙�˙˙����˙˙�˙ţ�˙ţ���˙˙�˙˙�˙˙�˙˙�����˙˙�˙˙���˙˙���˙˙�˙ü�˙ü�˙ü�˙ţ�˙ý�˙ú�˙ú�˙ű�˙ý��˙˙�˙ţ�˙ü�˙ű�˙ý�˙ý���˙˙�˙ý�˙ţ�˙ý�˙ý�˙ţ�˙ţ�˙ű�˙ü��˙ý�˙ü�˙˙˙˙�˙˙˙˙�˙ţ�������˙˙�˙˙���˙˙�˙˙�˙ţ�˙ű�˙ţ���˙ţ�˙˙�˙˙�˙ý�˙ţ���˙ü�˙ý�˙ý�˙ţ�˙˙����˙˙�˙˙˙˙�˙ű�˙ý��˙ý�˙ü�˙ý��˙ţ�˙ý�˙ţ���˙˙��˙ý�˙ţ���˙˙�˙ý�˙ü�˙ţ���˙ý�˙ü���˙˙�˙ţ���˙˙�������˙˙�˙˙�˙ű�˙ü���˙˙���˙˙�˙ţ�˙ţ�����������˙˙�˙ý�˙ü�˙ţ�����˙˙�˙ţ�˙ţ������˙ţ�˙ý�˙ý�˙ţ���˙˙�˙ý�˙ü�˙ţ�˙˙�˙ý�˙ü�˙ý�˙ý�˙ý�˙ý�˙˙˙˙�˙ü�˙ü�˙ţ�˙ţ�˙˙�����˙˙�˙˙�˙ţ��������������������˙˙�����˙˙�˙˙���˙˙�˙˙�˙˙�˙ţ�˙ý�˙ý�˙˙���˙ţ�˙ý�˙˙�����˙ý�˙ú�˙ú�˙ý�˙ţ��˙ţ�˙ţ��˙ţ�˙ţ�˙ţ�˙ţ�˙ţ�������˙˙���������˙˙�˙˙˙˙�˙ü�˙ý�˙ţ�˙ţ�˙ţ�˙ý�˙ţ�˙ţ������˙ý�˙ü�˙ý��˙ţ�˙ţ�˙˙�˙ţ�˙ý�˙˙���˙ţ���˙ü�˙ü���˙˙���˙˙���˙˙�˙ý�˙ü�˙ű�˙ü�˙˙˙˙�˙ţ�˙ţ�˙ţ�˙ţ�˙ţ�˙˙˙˙�˙ý�˙ý��˙ý�˙ű�˙ý�˙ü�˙ű�˙ü�˙˙���˙˙�˙˙���˙˙�˙˙�˙˙�˙˙�˙˙�˙˙���˙ü�˙ű�˙ţ���˙˙�˙˙�˙˙���˙˙�˙ý�˙ţ�˙ţ�˙˙�����˙˙���˙˙�˙ý�˙ű�˙ű�˙ü�˙ý�˙˙�˙˙�˙˙���˙˙���˙ţ�˙ý�˙ţ���˙ý�˙ţ�˙˙�˙ý�˙ü�˙ţ�˙˙���������˙ţ�˙ţ��������˙ý�˙ţ�����˙˙�˙˙���˙˙�˙˙���˙˙�˙˙���˙˙�˙˙���˙˙���˙˙�˙ý���˙˙�˙˙�˙˙�˙ý���˙˙�˙ý�˙ý�˙ţ���˙ü�˙ű�˙ý�˙˙���˙ţ�˙ţ�˙˙������������������������������������˙˙�˙ţ�˙ţ�˙ý�˙ü�˙ţ��˙ţ�˙ţ�˙˙�˙ţ�˙ý�˙˙�˙ţ�˙˙���˙ű�˙ý�˙˙˙˙����˙ţ�˙ţ�˙ţ�˙ţ�˙ţ�˙ţ�˙˙˙˙�˙ţ�˙˙�˙ţ�˙˙���˙˙���˙ţ�˙˙���������˙˙���˙ţ�˙ý�˙˙���˙˙���˙ţ�˙ý�˙ţ�˙ţ�˙ţ�˙ţ���˙˙�˙ţ�˙ţ���˙˙�˙˙���˙ţ�˙ú�˙ú�˙ţ�˙˙�˙ý�˙˙���˙˙�˙˙�˙ý�˙ü���˙ý�˙ý��˙ţ�˙ţ�˙ţ�˙˙˙˙�˙ţ�˙˙�����������˙ţ�˙ü�˙˙�����˙ţ�˙ţ�˙ţ�˙˙�����˙ý�˙ú�˙ű�˙˙˙˙�˙ý�˙ţ���˙ţ���˙˙�˙˙���˙˙�˙ţ�˙˙���˙ý��˙˙���˙ţ���˙˙�˙˙�˙˙�˙ü�˙ű�˙ű�˙ü�˙ţ���˙ţ�˙ü�˙ü�˙ţ�����������˙ţ�˙˙��������������������˙˙�˙ý�˙ü�˙ü�˙ţ�˙ţ�˙ţ�˙ţ�˙ý�˙ü�˙ü�˙ţ�˙ý�˙ü�˙ý������˙ţ�˙ţ�˙˙������������˙˙�˙ý�˙ý�˙˙�����˙ţ�˙ţ��˙˙���˙ţ�˙ţ�˙ţ�˙˙���˙˙�˙ü�˙ü��˙ü�˙ü���˙˙�˙ý�˙ţ�˙˙�����˙˙�˙˙���˙ţ�˙ű�˙ű���˙˙�˙ţ�˙˙���˙˙�˙˙���˙˙�˙˙�˙˙���˙ţ�˙ü�˙ţ�������˙˙�˙ý�˙˙˙˙�˙ţ���˙ý�˙ü�˙ý�˙ţ�˙˙�˙ţ�˙ű�˙ű�˙˙���˙˙˙˙�˙ţ���˙ý�˙ý�˙ţ�˙ţ�˙ţ�˙˙�˙ý�˙ý�˙ţ�˙˙���˙ţ�˙ý�˙ţ�˙ţ�˙ý�˙ý��˙ţ��˙ţ�˙˙˙˙�˙ü�˙˙������˙˙�˙ţ�˙ý�˙ţ���˙ü�˙ü�˙ţ�˙˙�˙ţ�˙ţ�˙ţ�˙ţ�˙ţ�˙ü�˙ú�˙ü�˙˙�����˙ü�˙ý���˙˙���˙ţ�˙ý�˙ý���˙ţ�˙ţ���˙˙��˙ţ���˙˙�˙˙˙˙�˙˙�˙˙�˙ţ���˙ţ�˙˙˙˙�˙˙�����˙˙���˙˙�˙ţ���˙ý�˙˙˙˙�˙˙���������˙˙������˙ţ������˙ţ�˙ý�˙ű�˙ý�˙˙�˙ţ�˙ý�˙ţ���˙ţ�˙ý�˙ý�˙ý�˙ý�˙ţ�˙˙���˙ý�˙˙˙˙�˙ý�˙ţ�˙˙���˙ţ�˙ý�˙ý�˙˙�����˙˙�˙ý�˙ű�˙ý�˙ű�˙ű�˙ý�˙ý�˙ý�˙ý�˙ý�˙˙���˙ţ�˙ţ�˙ý�˙ü�˙ý��������˙ţ�˙˙�������˙ţ�˙ü�˙ý�˙˙�˙ţ�˙˙�˙ţ�˙ü�˙ţ˙˙�˙ű�˙ý�˙˙�˙˙���˙ý�˙˙���˙˙�˙ţ�˙˙˙˙�˙ü�˙ü�˙ü�˙˙������˙˙�˙˙���˙ţ�˙˙���˙ü�˙ű�˙ţ���˙ţ�˙ý�˙˙�������˙ţ�˙ý�˙˙���˙ý�˙ý���˙ţ�˙ý�˙ý�˙ý�˙˙���˙˙�������˙ţ�˙ü�˙ü�˙˙���˙ý�˙ű�˙ţ�˙˙�˙˙�˙˙���˙ţ�˙ý�˙˙���˙˙�˙˙�˙˙�����˙˙�˙ý�˙ţ���˙˙�˙˙���˙˙�˙˙�˙˙�˙˙���˙ţ�˙ý���˙ţ���˙ţ�˙ű�˙ü�˙ţ�˙ţ�˙ţ�˙˙�����������˙ţ�˙ţ�˙ţ�˙ü�˙ý�˙ţ�˙ý�˙ű�˙ü�˙ţ�˙˙˙˙�˙ü�˙ý��˙ţ�˙ý�˙˙���˙˙�˙˙�˙˙�˙˙���˙˙�˙˙�˙˙�˙ţ���˙˙��˙ţ�˙ű�˙ý�˙˙˙˙��˙ţ�˙ý�˙ţ�˙ţ�˙˙�˙ý�˙ü�˙ý�˙ý�˙ü���˙˙�˙ţ���˙˙�˙ţ�˙˙�˙˙�˙ý�˙ü�˙ţ�˙˙�˙˙�˙ţ�˙ţ�˙ţ�˙ţ���˙˙���˙˙���˙˙�˙˙���˙ý�˙ü�˙ü�˙ţ���˙ý�˙ţ�˙ţ���˙ý�˙ü�˙ţ���˙˙�˙ý��˙ý�˙ű��˙ţ���˙ţ�˙ü�˙ý�˙ý�˙ţ���˙ţ�˙˙�˙˙�����˙ţ�˙ü�˙ý�˙˙˙˙���������˙ţ�˙ü�˙ţ�˙˙�˙ü�˙˙˙˙�˙ţ�������˙˙�˙ţ�˙ţ�����˙˙�˙ţ���˙˙��˙ý�˙ü�˙˙˙˙�˙ý�˙ü�˙ţ���˙˙�˙ý�˙ü�����˙˙�˙ý���˙˙���˙ü�˙ű�˙ţ���˙˙�������˙˙�˙ý�˙ţ�����˙ţ�˙ţ���˙˙���˙˙�˙˙�˙˙�˙˙���˙˙�������˙ţ�˙ţ�˙ţ�˙ü�˙ý��˙˙˙˙�˙ý�˙˙�����˙˙�˙˙�˙˙�˙˙�˙˙���˙˙���˙ý�˙˙�˙˙�˙ţ�˙ý�˙˙���˙˙���˙˙�˙˙���˙˙�˙˙���˙ţ�˙ü�˙ý�˙ţ�˙ţ���˙˙�˙ý�˙˙���˙˙�����˙ţ�˙ý�˙˙�˙˙���˙˙���˙ü�˙ű�˙ý�˙ţ���������˙ţ�˙ţ��������������˙ţ�˙ţ�˙ţ�˙ý�˙ü�˙˙˙ţ�˙ű�˙ţ��˙ţ�˙ţ��˙ý�˙˙˙˙�˙ü�˙˙�����˙˙���˙ţ�˙ţ�˙˙�����˙ý�˙ü�˙ý�˙˙���˙˙�˙˙���˙˙�˙˙�˙ţ�˙ý���˙˙�˙ţ�˙˙�˙˙����˙˙�˙ţ�˙ţ��������˙ţ�˙˙���˙ý�˙ţ�˙˙���˙˙�˙ý�˙ţ���˙˙�˙ţ�˙˙���˙˙�˙˙�˙˙�˙˙�˙ţ�����������������˙˙�˙ţ���˙˙���˙˙���˙˙�˙ý�˙ţ�����˙˙�˙ý�˙ţ�˙˙���˙˙�˙˙�����˙˙�˙ţ�˙ţ�˙ţ�˙ţ�˙ţ���˙˙�˙ţ���˙ţ���˙˙�˙˙˙˙�˙ţ�˙ţ�˙ý�˙ü�˙ű�˙ű�˙ý�˙˙�˙˙���˙ţ�˙ý�˙ý�˙ü�˙ý���˙˙�������˙˙�˙ţ�˙ţ�˙ţ���˙ţ�˙ţ�˙ţ�˙ý�˙ý�˙ţ�˙ý�˙ü�˙ţ�˙˙�˙˙�˙ý�˙ý�˙ţ���˙˙���˙ü�˙ý�˙˙�������������������˙ý�˙ü�˙ý���˙˙�˙ţ�˙ţ���������������˙˙�˙ţ���˙˙���˙˙���˙˙�˙ý�˙ü�˙ţ�����˙˙���������˙˙���˙˙�˙ţ���˙ţ�˙˙˙ţ�˙ű�˙ţ�˙˙���˙˙�˙ţ���˙˙���˙˙�˙˙�˙˙�����˙˙�˙˙�˙˙˙˙�˙ţ�˙˙˙˙�˙ü�˙ü�˙˙˙˙�˙ű�˙ü�˙ý������������˙ţ�˙ţ�˙ý���������˙˙�˙ţ���˙˙�˙ý�˙ţ�˙˙�˙˙�˙ý�˙ý�˙ý�˙ü�˙ý�˙ţ�˙ý�˙ţ�˙ţ�˙ţ�˙˙���˙˙�˙˙�˙˙���˙˙�˙˙�����˙ţ�˙ý�˙ţ�˙˙���˙ý�˙ý�˙˙�˙ü�˙ý���˙ţ�˙ý�˙ý�˙ý�˙ý���˙˙���˙ý�˙ý��˙ý�˙ü�˙˙˙˙�˙ţ���˙˙���˙˙�˙ý�˙˙�˙˙�˙˙���˙˙���˙˙���˙˙���˙ţ�˙ţ���˙˙�������˙˙�˙˙�˙ţ�˙˙���˙˙�˙˙���˙ţ�˙ý�˙˙���˙ţ�˙ű�˙ů�˙ý�˙˙����˙˙�˙˙��˙˙�˙ý�˙ü�˙ţ�˙ţ�˙ţ�˙˙�˙˙���˙˙���˙ţ�˙ý���˙ţ�˙ü�˙ý�˙ţ�˙ţ�˙˙�˙ţ���˙ţ�˙ü�˙˙˙˙�˙ţ��˙ţ�˙˙˙˙�˙ţ�˙˙˙˙�˙ţ�˙˙˙˙�˙ţ�˙˙���������˙ţ�˙ý�˙ý���˙˙�˙˙�����˙ţ�˙ý�˙ţ���˙˙�˙˙�˙˙�˙˙�˙ý�˙ţ���˙ý�˙ţ�˙˙�˙˙�˙˙�˙˙�˙˙�˙˙�˙˙���˙˙�˙˙�����˙ţ�˙ý�˙ţ�˙˙���˙ţ�˙˙�˙˙�˙ý�˙ţ���˙˙�˙ý���˙˙�˙˙���˙ý�˙ý�˙ţ���˙˙�˙˙�˙ţ�˙ý�˙˙���˙˙���������˙ţ�˙ţ�˙ţ�˙ţ�����˙˙�˙ý�˙ţ�˙˙�˙˙�˙ţ���������˙˙�����˙˙�˙ý�˙ü�˙ţ�˙ţ�˙˙���˙˙�˙˙���˙˙���˙˙�˙ţ�˙˙�˙˙���˙˙�˙˙�˙˙�˙˙���˙˙���˙˙���˙˙���˙ý�˙˙�˙˙�˙˙�˙˙�˙˙�˙˙�˙˙�˙ý�˙ţ�������������������˙˙�˙ţ�����˙˙�˙ý�˙ţ���˙˙�˙˙�˙˙�˙ý�˙ţ���˙ý�˙ü�˙˙�˙˙�˙ý�˙˙�˙˙�˙˙���˙˙�˙˙�˙˙���˙ý�˙ţ�˙˙�˙˙�˙ţ�˙˙���˙˙�˙ý�˙ţ���˙ţ���˙ţ�˙ü���˙ţ�˙ţ���˙ü�˙ü���˙ţ�˙ţ���˙ţ�˙ý�˙˙˙˙�˙ý�˙˙˙˙�˙ü�˙ý��˙ţ�˙˙���˙ţ�˙ţ���˙˙���˙˙���˙ü�˙ý���˙ţ�˙˙˙˙�˙ý�˙ţ�˙ţ�˙˙�˙˙���������˙ţ�˙ţ��˙ü�˙ý��˙ţ�˙˙���������˙ţ�˙˙˙˙�˙˙���˙ü�˙ý�˙˙�˙˙�˙ţ���˙ţ�˙ý��˙ü�˙ţ���˙˙���˙˙�˙˙�˙˙�˙˙�˙˙�˙˙�����˙˙�������˙˙�˙ţ�˙ţ��������������˙˙�˙˙���˙ý�˙ű�˙ű�˙ţ���˙˙���˙ý���˙ţ�˙ţ�����˙ţ�˙ţ���˙ý���˙˙�˙˙���˙ý�˙ý�˙ţ���˙˙�˙˙���˙˙�˙ţ�˙ý���˙ţ�˙ţ�˙˙�˙˙�˙˙���˙ţ�˙ý�˙ţ�˙ţ�˙˙������������������˙˙�˙ţ�˙ţ��˙ţ�˙ţ�˙˙�����������˙ţ�˙˙˙˙�˙ţ�˙˙˙˙�˙ý�˙ü�˙˙˙˙�˙ý�˙ţ���˙˙���˙˙�˙˙���˙˙�˙ţ�˙˙�˙˙���������˙˙���������˙˙���˙ţ�˙˙�˙ţ�˙ü�˙ý�˙˙�������˙ţ�˙ü�˙ú�˙ű�˙ţ���˙˙�˙˙���˙˙�˙˙�˙ţ�˙ý���˙ţ�˙ý�˙ý�˙ý�˙ţ�˙˙���˙˙�˙˙���˙ţ�˙ý�˙ý�˙ý�˙˙���˙˙�˙ţ�˙˙���˙˙�˙˙�˙ý�˙ţ�˙˙�˙˙�˙ý�˙ý�˙ý�˙ţ���˙˙���˙˙�˙˙�˙ý�˙ü�˙ű�˙ü���˙˙�����������˙˙�˙˙���������˙˙˙˙�˙˙�˙˙�����˙˙�˙˙�˙˙�˙˙���˙˙�˙˙�˙˙�˙˙�˙˙�˙ţ���˙˙�˙ţ�˙ý�˙ţ�˙˙�˙˙���˙˙�˙˙���˙˙�˙ţ�˙ü�˙ű�˙ţ�˙˙��˙˙�˙ţ�˙ţ�����˙˙�˙˙�����˙˙���˙˙�˙ý�˙ţ���˙˙���˙˙�˙ţ���˙˙�˙ţ���������˙˙�˙ţ��˙ý�˙ü���˙˙�˙ţ�˙˙�������˙˙�˙ţ�˙ţ�˙ý�˙ü�˙ü�˙ţ��˙ý�˙ü�˙ţ�˙ý�˙ţ���˙ű�˙ţ���˙ý�˙ý�˙ţ�˙˙���˙˙�˙ý�˙ţ�˙˙�˙˙���˙˙�˙˙���˙˙���˙ţ��˙ý�˙ú�˙ý�˙˙˙˙�˙ţ�˙ý�˙ţ��˙˙����˙˙�˙ţ�˙ţ�˙ţ�˙ţ�˙ţ�˙˙�˙ţ�˙˙���˙˙���˙˙�˙˙���˙˙�˙ý�˙ý�˙ý�˙ý�˙ý�˙ü�˙ý���˙ţ�˙ý���˙ţ�˙ý�˙˙�˙ţ�˙ü�˙ý�˙˙������������˙˙�˙ţ�˙ţ������˙ţ�˙˙���˙ţ�˙˙�˙˙���˙ţ���˙˙�˙ü�˙ý�˙ű�˙ü�˙ý�˙˙�˙˙�����˙ţ�˙˙˙˙�˙ü�˙ţ˙˙�˙ü�˙ý������˙ţ�˙˙��˙ţ���˙˙�˙ţ������������������˙ţ�˙˙���˙˙˙˙�˙ý�˙˙�����˙ţ�˙ý��˙˙�˙ţ�˙ţ�˙ý����������˙ţ�˙ţ������˙ţ�˙ý�˙˙����˙˙�˙ţ�˙ţ���˙˙���˙˙�˙ý�˙ý�˙ü�˙ţ�����������˙˙�˙ţ��˙ý�˙ü�˙ţ�˙ţ�˙ý�˙ü�˙ü���������˙˙�������˙˙�˙˙�˙ý���˙ţ�˙ű�˙ű�˙ü�˙ţ�˙˙�˙˙�˙˙�˙˙�˙˙���˙˙�˙ţ���˙ű�˙ű�˙ţ��˙ţ�˙ţ��˙ţ�˙ý�˙ü�˙ü�˙˙�������˙ţ�˙ţ�˙˙�����������˙ţ�˙˙���˙ţ�˙ü�˙ü���˙ţ�˙ý�˙ţ�˙˙���˙˙���˙˙�˙˙�˙˙�˙˙���˙˙�˙˙�˙˙�˙ţ�˙ţ������˙ţ�˙ţ�˙˙������˙˙�˙ü�˙ý��˙ţ���˙ţ�˙˙�˙˙�˙ţ�˙ţ�˙ţ�˙ű�˙ű�˙ţ�˙˙�˙˙�˙˙���˙˙�˙˙�˙˙�˙˙�˙˙�˙˙�˙˙�˙ţ�˙ü�˙ý�˙ţ�˙ţ�˙˙���������˙˙�����������˙ţ�˙ţ�˙ţ�˙ű�˙ú�˙ü�˙ţ�˙ý�˙ű�˙ű�˙ý�˙ţ�˙˙�˙˙˙˙�˙ü�˙ý��˙˙˙˙�˙ţ�˙ţ�˙˙�˙ţ���˙˙���˙ţ�˙ý�˙ţ���˙˙���˙ţ�˙ý�˙ţ���˙˙���˙ţ�˙ţ���˙ţ�˙ţ�˙˙�˙˙�˙ţ�˙ý�˙ý�˙˙�˙˙���˙˙�˙˙���˙˙�˙ý�˙ý�˙˙�˙˙�����������˙ţ�˙ţ�˙ţ�˙˙���������˙ţ�˙ţ�˙ü�˙ü�˙˙˙˙�˙ţ�˙ţ�˙ţ�˙˙���˙˙�����˙˙�˙˙���˙˙���˙˙������������˙˙�˙ţ���˙˙�˙ţ�˙ţ�˙ţ����˙ţ�˙ű�˙ý��˙ţ�˙ţ��˙ţ�˙ţ�˙ţ�˙ţ�˙˙��˙˙������˙ü�˙ü�˙ý����˙ţ�˙ý�˙ţ���˙˙�˙˙�˙˙�˙ţ�˙ţ���˙ü�˙ü���˙˙�˙ţ���˙˙���˙˙�˙˙�˙˙�˙˙���˙˙�˙ţ�˙ý�˙˙�˙˙�˙ţ�˙ý�˙˙���˙˙�����˙ţ�˙ü�˙ý�˙˙���˙˙�˙ţ�˙˙���˙ţ�˙ţ�˙˙�˙ţ�˙ţ����˙ţ�˙ý�˙ţ�˙ü�˙ţ���˙ţ�˙ţ�˙˙�˙ţ���������˙˙�˙ţ���˙˙�˙ý�˙ţ���˙˙���������˙˙�˙ţ�˙ţ�˙ý�˙ü�˙ţ�˙ý�˙ý���˙˙��˙ţ���˙˙���˙˙�˙ý�˙ţ�˙˙�˙˙�˙ý�˙ý���˙˙�˙˙���˙˙�˙ü�˙ý�˙˙�������������������������˙˙�˙ţ�˙˙�˙ý�˙ţ�˙˙���������������˙ţ�˙ţ�˙˙����˙˙�˙ü�˙ý����������˙ţ��˙ţ�˙˙˙˙�˙˙������˙˙�˙˙���������˙˙˙˙�˙ü�˙ü������˙˙�������˙ţ�˙ţ�˙˙���������˙ţ�˙ţ�������˙˙�˙ţ��˙ý�˙˙������˙˙��˙ţ�˙ţ���˙˙�˙ü�˙ü�˙˙�����˙ţ�˙˙˙˙�˙ý���˙˙�˙ţ�˙˙���˙˙���˙˙�˙ý�������˙˙���˙˙�˙ý�˙ý�˙ý���˙˙�˙ý�˙ý�˙ţ���˙˙�˙˙�˙ţ���˙˙��˙ý�˙ý��˙ý�˙˙���˙ý�˙ü�˙ý�˙ţ�˙ţ�˙ţ�˙ý�˙ü�˙ţ�˙˙�˙ý�˙ü�˙ţ�����˙˙�˙˙�˙˙�˙˙���˙˙���˙˙�˙˙��˙ý�˙ţ��˙ţ���˙˙�˙ţ�˙˙�˙˙�˙˙�˙˙���˙ý�˙ü���˙˙�˙ý�˙ý�˙ţ���˙˙�˙˙���˙ţ�˙ý��˙ý�˙ű�˙ý�˙˙�˙˙�˙˙���˙˙�˙˙�����˙ţ�˙ü�˙ü�˙˙˙˙�˙ý�˙ü�˙ü�˙ü�˙˙˙˙�˙ü�˙ţ���˙ý�˙ý�˙ý�˙ý�˙ţ�˙˙�˙˙�˙ţ���˙˙���˙˙�˙˙�˙˙�˙˙�˙˙���˙ţ�˙ý�˙˙���˙˙�˙˙���˙ţ�˙ý�˙˙�����˙˙�˙˙���˙˙�˙˙����������������˙˙�˙ţ�˙˙˙˙�˙ţ�˙ţ���˙˙�˙ý�˙ű�˙ü���˙˙���˙˙�˙˙�˙ţ���˙˙�˙˙˙˙�˙ţ��˙ţ�˙˙�˙ţ�˙ü�˙˙���˙˙�˙˙�˙˙�˙ţ�˙ý�˙ţ�˙˙˙˙�˙˙�˙ý�˙ý�˙ţ�˙˙�˙˙�˙˙�˙ý�˙ý���˙˙�˙˙���˙ý�˙ý�˙ý�˙˙���˙ţ�˙ţ�˙˙���������˙ţ�˙ţ�˙˙���˙˙���˙ţ�˙ţ���˙ý�˙ţ�����˙˙�˙ý�˙ý���˙˙�˙ü�˙ţ���˙˙�˙˙�������˙˙����������˙˙���˙˙�˙˙�˙˙�˙˙���˙ý�˙ý�˙˙�˙˙�˙˙���˙ţ�˙ý�˙˙�˙ţ�˙ü�˙ţ���˙ţ�˙˙�˙ţ�˙ţ�˙ü�˙ü�˙˙�����������˙ţ�˙ţ�������˙˙�˙˙���˙˙���˙ţ�˙ü���˙ţ�˙ţ���˙ţ�˙ý�˙ý���˙ţ�˙ý���˙ţ�˙ţ���˙˙���˙˙�����˙˙������˙ý�˙ü�˙˙˙˙�˙ţ���˙˙���˙˙�˙ý�˙ţ���˙˙�˙ţ�˙ţ�˙˙���˙ţ�˙ţ�˙ţ�˙ţ���˙˙���˙˙�˙ý�˙ý�˙ţ���˙˙�˙˙�˙˙���˙˙�˙˙�����˙˙�˙˙�˙ţ�˙˙���˙˙�˙˙�˙˙�˙ţ�˙ý�˙˙���˙ţ�˙ü�˙ű�˙ü�˙ţ�����������˙˙�˙ţ�˙˙˙˙�˙ţ�˙ţ�˙ý�˙ü�˙ţ�����˙˙���������˙˙�����������˙˙�˙ţ�˙˙˙˙�˙ţ�˙˙˙˙�˙˙���˙ţ�˙˙�������˙ţ�˙ţ�˙ţ�˙ţ�˙ţ�˙ţ�˙ţ�������������˙˙�˙ţ�˙ţ�����˙˙�˙ý�˙ü���˙ţ�˙ü��˙ü�˙ű�˙ý�˙ý�˙ţ�˙ţ�˙ý�˙ţ�˙˙�˙˙���˙˙�˙˙�����˙ţ�˙ü�˙ý���˙ţ�˙ü�˙ü�˙˙���˙˙�������˙ü�˙ü�˙ý�˙ţ�˙˙���˙˙�˙˙�˙˙�˙˙�˙˙�˙˙�˙˙�˙˙�˙ý�˙ű�˙ü�˙ţ�˙˙���˙˙�˙˙�˙˙�˙˙���˙˙�����˙˙���˙˙�˙ý�˙ţ�˙˙�˙˙���˙˙�˙ý��˙ý�˙ű�˙ü�˙ţ�˙˙�˙ţ�˙ţ���������˙˙�˙˙��˙˙�˙ţ�˙˙˙˙�˙ţ���˙˙���˙˙�˙ý�˙ţ���˙˙�˙ţ�˙ý�˙ţ��˙ý�˙ý�˙ţ���˙˙���˙˙���˙ţ�˙ű�˙ý�˙˙�˙ţ�˙ţ�˙˙����˙˙�˙ţ�˙ü�˙ţ�����˙˙�˙ý�˙ţ���˙ţ���˙ţ�˙˙˙˙�˙ü�˙ú�˙ü��˙ý�˙ü�˙ţ���˙˙�˙ţ��˙ü�˙ů�˙ű�˙ţ�˙˙�˙ţ���˙˙���˙˙�˙ý�˙ý�˙˙˙ţ�˙ű�˙ţ���˙˙�����˙ţ�˙ţ�˙˙���˙ţ�˙ţ�˙ý�˙˙���˙˙���˙˙�˙ţ�˙ý�˙ý�˙˙�˙˙���˙ţ�˙ţ�˙ţ�˙ţ��˙ý�˙ü�˙ü�˙ţ�˙˙����˙˙�˙ü�˙ý�˙ţ���˙˙�˙ű�˙ţ�����˙˙�˙ý�˙ý��˙ţ�˙˙�����˙ţ�˙ü�˙ű�˙ý��˙˙�˙˙�����˙˙���˙ţ�˙ţ�����˙˙���˙ţ�˙ý���˙˙�˙˙�˙˙�˙˙�˙˙���˙˙���˙ţ�˙˙���˙˙���˙˙���˙ü�˙ů�˙ü�˙˙�˙˙����������˙˙�˙ţ�˙ý�˙ţ���˙˙�����˙˙�˙ý�˙˙˙˙�˙ţ���˙˙�������˙˙����˙ý�˙ü�˙ý��˙˙���˙ţ�˙ý�˙ý�˙ý���˙˙�˙˙�˙˙�˙˙�˙˙�˙˙�˙˙���˙ţ�˙ţ�˙ţ�˙ý�˙ü�˙˙���˙˙�����˙˙���˙ţ�˙˙�˙˙�˙ü�˙ű�˙ţ���˙ţ�˙˙���˙˙���˙˙���˙˙�˙˙�˙ţ�˙ý�˙˙�����������˙ţ�˙ţ�˙˙�����˙˙�˙ţ��˙ţ�˙ý�˙˙�����˙˙�˙˙�˙˙�˙˙�˙˙���˙˙�˙˙���˙ý�˙ű�˙ü�˙ţ�����˙˙�˙ţ�˙ţ����˙ý�˙ý�˙˙�������˙ţ�˙ü�˙ţ�˙ţ�˙ý�˙ý�˙ţ���˙˙�˙˙���˙ţ�˙ű�˙ý��˙ţ�˙ţ�˙ţ�˙ţ�˙ţ�������˙˙�˙˙���˙˙�˙ţ���˙˙�˙˙��˙˙�˙ý�˙ý���˙ţ���˙˙�˙˙�˙ý�˙ţ���˙˙���˙˙�˙˙�˙˙�˙ţ�˙˙�����˙˙�˙ý�˙ü�˙ţ�����˙˙�˙ý�˙ţ���˙˙�������˙˙�˙˙���˙ţ�˙˙�������˙ţ�˙ţ��˙ý�˙ü�˙ü�˙ţ�˙ţ������˙ý�˙ű�˙ý���˙ý�˙ý�˙ý�˙˙���˙˙�˙˙�����˙˙�˙ý�˙ü�˙ţ���˙˙�˙˙�˙ý�˙ý���˙˙�˙˙���˙˙�˙˙�˙˙�˙˙���˙˙�˙˙�˙˙�˙ý�˙ţ�������˙˙�˙ţ���˙˙�˙ý�˙ý�˙ţ�˙˙�˙˙���˙ü�˙ű�˙ţ���˙ü�˙ü�˙˙�˙˙�˙ţ�˙ý�˙˙˙˙�˙ý�˙ţ�˙ţ�˙˙���˙ţ�����������˙˙���˙˙�˙ý�˙ţ�˙ţ���˙ý�˙ţ�˙˙�˙ü�˙ü���˙˙�����˙˙�˙ţ�˙˙˙˙�˙ţ�˙˙˙˙�˙ţ��˙ţ�˙˙˙˙�˙˙���˙ý�˙ţ�˙ü�˙ý�˙ü�˙ü�˙ţ�˙ţ�˙ü�˙ü�˙ű�˙ü���˙˙�˙˙�˙ý�˙ű�˙ţ���˙˙�����˙˙�˙˙�˙ţ�˙˙�˙˙�˙˙�˙ý�˙ţ���˙˙�˙ţ�˙˙�����˙ý�˙ú�˙ű��˙ţ�˙ý�˙˙�����˙˙�˙ţ�˙ţ�˙ý�˙ţ�˙ţ�˙ţ�˙˙��˙˙�˙ţ�˙˙˙˙�˙ţ���˙˙�˙ý�˙ý�˙ţ���˙˙���������˙˙�˙ţ�˙˙�˙˙�˙ý�˙ý�˙ţ�˙ţ�˙˙�˙˙�˙ý�˙˙���˙ţ�˙˙�˙˙�����˙˙���˙ţ�˙˙�����˙ý�˙ý��˙ţ�˙˙˙˙�˙ţ�˙ţ�˙ý�˙ü�˙˙˙˙�˙ü�˙ý�˙˙��������������˙˙���˙˙�˙ý�˙ý�˙ý���˙ţ�˙ý�˙˙���˙ý�˙ý�˙ţ���˙˙���˙˙�˙ý���˙˙�˙ý�˙˙���˙˙�˙ţ�˙ý�˙ý�˙ý�˙ţ���˙ţ���˙ý�˙ţ�˙˙�˙ý�˙ţ�˙ţ�˙ţ�˙˙���˙ţ�˙ü�˙ű�˙ý�˙˙�˙ţ�˙ý�˙˙���˙˙���˙˙���˙ţ�˙ý�˙ţ�˙ţ�˙ţ���˙˙�˙ţ���˙˙�˙ţ�˙ţ��˙ţ�˙ü�˙˙���˙˙���˙ţ�˙ý�˙ý���˙˙�˙˙�˙˙�����˙˙�˙˙�˙ţ�˙˙���˙˙�˙˙�˙˙�˙˙���˙˙�˙˙���˙˙���˙ţ�˙ý���˙ţ�˙ţ���˙˙��˙ý�˙ý��˙ý�˙ţ�˙ţ�����������˙˙�˙ü�˙ý��˙ţ�˙ý�˙ţ�˙˙�˙˙�������˙ţ�˙ý����˙ţ�˙ü�˙˙��������������������˙˙�˙ý�˙ü�˙ű�˙ý�˙ţ�˙˙�˙ţ�˙ţ�˙ý�˙ý�˙˙˙˙�˙˙�������˙˙�˙ţ��˙ţ�˙˙˙ţ�˙ű�˙ű���˙ţ�˙ü�˙ý�˙ý�˙ţ���˙ţ�˙ý�˙ý���˙˙�˙˙�˙˙�˙ý�˙ţ���˙˙�˙˙�˙ţ�˙˙�����˙˙�˙˙�˙ţ�˙ý�˙ţ�˙ţ�˙˙���˙˙�˙ý���˙˙�˙ţ����������˙ý�˙ü�˙ý�����������˙˙�˙ü�˙ý��˙ţ�˙ý�˙ţ��˙˙�˙ţ����˙˙�˙ţ�˙˙���˙˙�˙ţ�˙ü�˙ý���˙ý�˙ţ���˙˙�˙˙�����˙˙�˙ý�˙ţ���˙ý�˙ý�˙ţ�˙˙�˙ţ�˙ý�˙ü�˙ü�˙˙������˙˙�˙ü�˙ţ��˙ý�˙ü�˙ţ�����˙˙�˙˙�˙˙���˙˙�˙˙���˙˙���˙ţ�˙ű�˙ü�˙ý�˙ý�˙ý���˙ţ�˙ú�˙ü���˙ţ�˙ţ���˙˙�˙ţ�˙ţ�˙˙���˙ý�˙ý�˙˙˙˙�˙ţ�˙˙˙˙�˙ţ�˙ţ�������˙˙�˙˙�˙ţ�˙˙���������˙˙�˙ý�˙˙���˙ţ��˙ţ�˙ü�˙ý�˙˙���˙ţ�˙ü�˙ý�˙ý�˙ţ�˙ţ�˙˙���˙˙�˙ţ��˙ý�˙˙˙˙�˙ü�˙ţ�˙ţ��˙ý�˙ü�˙ţ��˙ţ���˙˙�˙ü�˙ţ�˙ţ�˙ý�˙ü�˙˙˙˙�˙ţ���˙˙�����˙ţ�˙ý�˙ţ�˙˙���˙˙�˙˙���˙˙�˙ý�˙ţ���˙ţ�˙ţ������������˙˙���˙ţ�˙˙˙˙�˙ú�˙ü�˙˙�����������������˙ý�˙ü�˙ţ�˙ţ�˙ţ�˙ţ�˙ţ�˙ţ�˙ţ�˙ţ�˙ţ�˙ţ�˙˙�����˙ţ�˙˙������˙˙�˙ü�˙ţ�����������˙˙�˙ţ�˙ţ���˙˙�˙ý�˙ţ�����˙˙������˙ý�˙ý�˙ü�˙ý�˙ţ�˙˙�˙˙�˙˙���˙˙���˙ţ�˙˙���˙ţ�˙ý�˙˙˙˙�˙ý�˙˙˙˙�˙ü�˙˙�����˙ý�˙ű���˙ţ�˙ű�˙ý�˙˙�˙˙���˙ţ�˙˙˙˙�˙ü�˙ý�˙˙���˙ţ�˙ţ�˙˙���˙˙�˙˙�����˙ţ�˙ý�˙˙���˙ţ�˙ý�˙˙���˙ţ�˙ý�˙˙�˙ţ�˙ý�˙ţ���˙ţ�˙ý�˙ţ���˙˙�˙ţ�˙˙�����˙˙�˙ţ�˙ţ���˙ţ�˙˙˙˙�˙˙����˙˙���������˙˙�˙˙���˙˙�˙˙�˙ţ�˙ý�˙˙˙˙�˙ý�˙˙���˙˙�����˙˙�˙˙���˙˙���˙˙�˙˙�˙˙���˙˙�˙˙�����˙˙�˙˙�˙˙�˙˙�˙˙�˙ţ�˙ţ���˙ţ�˙ţ�˙ţ���������˙˙�˙˙���˙ý�˙ü�˙˙������˙˙�˙ţ�˙ý�˙ü�˙˙˙ţ�˙ţ�˙ţ���˙˙�˙ý�˙ý�˙ţ�������˙˙�˙ý�˙ű�˙ţ�˙˙�˙ü�˙ü�˙˙���˙˙�˙˙�����˙˙�˙˙���˙ý�˙ý���˙˙�˙ý�˙˙���˙˙���˙ü�˙ü���˙ţ�˙˙�˙ţ�˙ü�˙ý�˙˙���˙ţ�˙ý�˙ý�˙ţ���˙˙�˙ţ���˙˙���˙˙�˙ý�˙ý�˙ţ�˙ý�˙ű�˙ü�˙ţ���˙˙���˙˙�˙ţ���˙˙�˙ţ���˙˙�˙ţ���˙˙���������˙˙�˙ţ�˙ţ���˙˙�˙ý�˙ţ���˙ý�˙ţ�˙˙�˙ý�˙ţ���˙ü�˙ţ��������������������˙ý�˙ű�˙ý�˙˙�˙˙���˙˙�˙˙���˙ţ�˙˙�˙˙�����˙˙���˙ý�˙ý�˙ţ�˙ţ���˙ţ�˙˙˙˙���������������˙˙�˙ţ�˙ţ�˙ţ�˙˙�˙ü�˙ü��˙ý�˙ý�����˙˙�˙ü�˙ý��˙ý�˙ű�˙ý����˙˙���˙ý�˙ý�˙˙˙˙�������˙˙�˙˙���˙ţ�˙ü�˙ý�˙ţ�˙ţ�������˙˙�˙ý�˙ţ�����˙˙�˙ţ�˙ţ�˙ý�˙ú�˙ű�˙˙˙˙�˙˙������˙˙�˙ţ���˙˙�˙ý�˙ý�˙ü�˙ţ�����˙˙�˙ţ�˙˙�˙˙�˙ý�˙ý�˙ţ�˙˙�˙˙�˙˙�˙ţ�˙ý�˙ţ���˙ţ��˙ý�˙ü���˙˙�˙˙�˙ţ�˙ý�˙ý�������˙˙����˙ţ�˙ţ�˙ţ�˙ţ�˙ţ��˙ý�˙ý��˙ţ��˙ţ�˙ü�˙ý�˙˙���˙ţ�˙ü�˙ý���˙˙�˙ý�˙ý���˙˙�˙˙�����˙˙���������˙˙�������˙˙�˙˙�˙ţ���˙˙�˙ý�˙ü�˙ţ�����˙˙���˙˙�˙ý���˙˙�˙˙�����˙˙���˙ţ�˙ý���˙˙�˙ý�˙ţ�˙˙�˙˙���˙˙�˙˙���˙ţ�˙˙���˙˙�˙˙�˙˙�˙˙�˙˙�˙˙�˙ţ�˙ý�˙ţ�˙ý�˙ţ����˙ý�˙ü�˙ý�˙ţ�˙ţ�˙ţ�˙ţ�˙ţ�˙ţ�˙˙˙˙�˙ü�˙˙������������˙˙�˙ţ�˙ţ����˙ţ�˙ţ�˙ţ�˙ţ�˙ţ�˙˙���˙˙�˙ý�˙ý�˙ý�˙ţ���˙˙���˙ý�˙˙���˙ţ�˙ţ�˙ţ���˙ţ�˙ü�˙ţ��˙˙˙˙�˙ţ�˙˙��˙˙�˙ű�˙ú�˙ý�����˙˙�˙ţ��������˙ý�˙˙������������˙˙���˙˙�˙ţ�˙˙�����˙˙�˙˙���˙ţ�˙ý�˙ţ���˙˙���˙˙�˙˙�˙˙�˙˙�˙˙�˙˙�����˙ţ�˙ü�˙ý�˙˙����˙˙�˙˙�����������˙ţ�˙ţ�˙ţ�˙˙���������˙˙�˙ţ�˙˙���˙˙���˙ţ�˙˙���˙ţ�˙˙���˙˙���˙ţ�˙ţ�˙˙�����˙˙�˙ţ�˙ý�˙ţ�˙˙�˙ţ�˙ţ�˙˙�˙ţ�������˙˙�˙ý�˙ů�˙ú�˙ü�˙ţ���˙˙�������˙˙�˙˙�˙ţ�˙˙���˙ţ�˙ţ�˙˙�˙ü�˙˙˙˙�˙ü�˙ţ�˙ü�˙ü�˙˙���˙ţ�˙ý���˙˙���˙ţ�˙ý�˙ý�˙˙���˙˙���˙˙�˙˙�����˙˙����˙˙�˙˙˙˙���������˙˙�˙˙˙˙�˙ý�˙ý���˙˙�˙ţ���˙˙�˙ţ���˙˙�˙ţ�˙ý�˙ţ���˙˙�˙ý�˙ý�˙ţ�˙˙�˙˙�˙˙���˙˙���˙ţ�˙˙���˙ţ�˙ţ�˙˙�����˙˙���˙˙���˙˙���˙ţ��˙ţ�˙ý�˙˙���˙˙�˙˙�˙˙���˙ţ�˙ü�˙ű�˙ű�˙ű�˙ú�˙ü�˙ţ�˙ý�˙ü�˙ţ�����������˙˙�˙ý�˙ü�˙ű�˙ü���˙˙���˙˙�����˙˙�˙ý���˙˙�˙ý�˙ý�˙ţ�˙˙�˙˙�˙ý�˙ţ���˙˙���˙˙���˙˙���˙˙�˙˙���˙ý�˙ý���˙˙���˙˙���˙˙���˙˙�˙˙�˙˙�˙˙�˙˙�˙˙�˙˙�˙˙�˙˙���˙ţ�˙ü�˙ý��˙˙���˙˙���˙˙���˙ţ���˙˙���˙ý�˙ű�˙ű�˙ü�˙ý�˙ţ���˙ţ�˙ű�˙ú�˙ţ�˙˙�˙ţ���˙˙���˙˙�˙˙���˙˙�˙˙���˙˙�����˙˙�˙ý�˙ü���˙˙�˙ý�˙ý�˙ý�˙ţ�˙˙�˙˙�˙ý���˙˙�˙˙���˙˙�˙˙�˙˙�˙˙�˙˙�˙˙�˙˙�˙˙���˙˙���˙ţ�˙ý�˙ý�˙˙���˙˙�����˙ţ�˙ü�˙ý�˙˙˙˙�˙ţ�˙˙˙˙�˙ţ���˙˙���˙˙�˙˙�˙˙�˙˙�˙˙�˙˙�˙ý�˙ţ������������������˙ţ�˙ţ�������˙˙�˙ý�˙ü�˙ţ�������˙˙�˙ý�˙ü�˙˙������˙˙�˙ý�˙ţ�˙˙�����˙ţ�˙ü�˙ý�˙ţ�˙˙���������˙˙�������˙˙�˙ţ�˙˙�˙˙�����˙˙�˙ţ�˙˙���˙˙���˙ţ�˙˙�����˙ţ���˙˙�˙˙˙˙�˙ý�˙ü�˙ü�˙ü�˙ü�˙˙˙˙�˙ý���˙˙�˙˙���������˙ţ�˙ţ��˙ţ��˙ý�˙ű�˙ţ�˙˙���˙ţ�˙˙�����˙ţ�˙ý��˙ţ�˙ý�˙˙���˙ţ�˙˙˙˙�˙ý�˙ü�˙ü�˙ţ���������˙˙�˙ý�˙ý�˙ţ�˙˙���˙ţ�˙˙�����������������˙ţ�˙ţ�˙˙��������������˙˙�˙ý�˙ü�˙ţ�����������˙˙�˙˙���˙ý�˙ü�˙ű�˙ý�˙˙�˙˙�˙ţ�˙ý�˙˙˙˙�˙ţ�˙ţ�˙ţ��˙ţ�˙ţ��˙ţ�˙ţ��˙ţ�˙ţ�˙˙���˙ţ�˙ü�˙ţ�˙ţ�˙ü�˙ü�˙˙�������˙˙�˙ţ�˙ţ��˙ţ���˙˙�˙ţ��˙ý�˙ü�˙ý���˙˙�˙ü�˙ţ���˙˙�˙˙���˙˙�˙˙�˙˙�˙˙���˙ţ�˙ý�˙ţ���˙˙�˙˙���˙˙���˙ţ�˙ý�˙˙�˙˙�˙ţ�˙˙�˙ţ�˙ţ�˙˙˙˙�˙˙���˙ţ���˙ţ�˙ü�˙ű�˙ú�˙ű�˙ý�˙ţ�˙˙�˙ţ�˙ý�˙˙���˙˙�˙ţ�˙˙�˙ţ�˙ü�˙ý���������˙˙�˙ý�˙ý�˙ţ�˙ţ�˙˙���˙ţ�˙ţ�˙ţ���˙˙���˙ţ�˙ý�˙˙�˙ţ�˙ý�˙˙�˙˙�˙˙���˙˙�˙˙�˙˙�˙˙�˙ţ�˙˙�����˙˙�˙˙�˙˙���˙˙�˙˙���˙˙�˙˙�˙˙�˙ý�˙ý�˙ţ���˙˙���˙˙�˙ţ�������˙˙�˙˙�˙ţ�˙˙�˙˙�˙ý�˙ű�˙ů�˙ú�˙ţ���˙˙�˙˙���˙˙�˙˙���˙ý�˙˙���˙˙���˙˙�˙˙�˙ţ���˙˙�˙˙˙˙�˙ţ�˙˙���˙˙�˙ţ���˙˙�˙ţ�˙˙�˙˙���˙˙�˙ü�˙ţ���˙ţ�˙˙�˙˙�˙ţ�˙˙�˙˙�˙ţ�˙˙�˙˙�˙˙�˙˙���˙˙�˙˙���˙ţ�˙ü�˙ý��˙ţ�˙˙˙˙�˙ý�˙˙���˙˙�˙˙���˙˙���˙ý�˙ý�����˙˙�˙˙���˙ţ�˙ü�˙ţ���������˙˙�˙ţ�˙ţ�˙˙�˙ţ���˙˙�˙ţ���˙ţ�˙˙˙˙�˙˙�����˙˙�˙ţ�˙ţ�˙ý�˙˙˙˙�˙ü�˙ý�˙˙�˙ţ�˙˙�˙˙���˙ţ�˙ţ���˙˙���˙ý���˙ţ�˙ý�˙˙���˙˙�˙˙���˙ý�˙ü�˙˙˙˙�˙ý��˙˙���˙ţ��˙ţ�˙ţ������������������˙˙���˙ţ�˙ü�˙ţ�˙ţ�˙˙�˙˙�˙˙���˙˙�˙ţ������˙ţ�˙ţ�˙ü�˙ý�˙˙�����˙ţ�˙˙���˙ţ�˙ţ�˙˙�˙˙�������˙˙�˙ţ�˙˙˙˙�˙ü�˙ý�˙ţ�˙˙������������˙˙�˙ý�˙ý�˙ý�˙ý�˙ý�˙ý�˙ü�˙˙˙˙�˙ţ�˙˙���˙˙���˙˙���˙ţ�˙ý�˙ý���˙ţ�˙ý�˙˙�������˙˙�˙ţ�˙˙���˙ţ�˙ü�˙ű�˙ý��˙ţ�˙ţ�˙ţ�˙ţ��˙ý�˙ý��˙ý�˙ý�˙˙˙˙�˙ţ�˙ţ���˙˙�˙ý�˙ţ�˙ý�˙ű�˙ü���˙˙���˙ţ�˙ý�˙ţ�˙˙�˙ţ�˙ý�˙ţ���˙ţ�˙ü���˙ţ�˙ý�˙˙����˙˙�˙ţ���˙˙�˙ţ�����˙˙�˙ý�˙ü�˙ý�˙ţ���˙ţ�˙ü�˙ţ���˙ý�˙ü�˙ţ������������������������˙ţ�˙˙���˙˙���˙˙���˙˙�˙˙�����˙˙���˙˙�˙˙���˙˙�˙˙�˙˙���˙ţ�˙ý�˙˙���˙ţ�˙ý�˙˙�˙˙�˙˙���˙˙�˙˙�˙˙�˙˙�����˙˙�˙˙�˙ţ�˙˙���˙˙�˙˙���˙ţ�˙ü�˙ý�˙ţ���������˙˙���������˙˙���������˙˙�˙ţ�˙˙���˙ţ�˙ý�˙˙�˙ţ�˙ý�˙˙���˙˙���˙ţ�˙˙�˙ţ�˙ű�˙ů�˙ű�˙˙˙˙�˙ü�˙ü�˙˙�������˙ţ�˙˙���˙˙˙˙�˙ü�˙ü�˙ü�˙ü�˙ü�˙ý���˙ý�˙ű�˙ü�˙ţ�˙ý�˙ý�˙ţ�˙ý�˙˙�����˙˙���˙ţ�˙˙���˙˙���˙ţ�˙˙���˙ţ�˙ü�˙ý�˙˙�˙ţ�˙ţ�˙˙����˙˙�˙ţ�˙ţ���˙˙�˙ţ������������˙ţ�˙ţ�˙ý�˙ü�˙ý��˙ü�˙ű���˙ţ�˙ý�˙˙�����˙ţ�˙ţ�˙ţ��˙ý�˙ü�˙ţ�˙ţ��˙ţ�˙˙˙˙�˙ý�˙˙���˙˙�˙˙�˙ţ�˙ý�˙ţ���˙˙���˙˙�˙˙���˙˙�˙˙���˙ü�˙ű�˙ý�˙˙�˙˙���˙ţ�˙˙�˙˙��˙˙�˙˙���˙ý�˙ü�˙ý�˙ţ�˙ţ�˙˙���˙ţ�˙˙���˙ü�˙ý�˙ý�˙ý���˙˙�˙ý�˙ý�˙ţ���˙˙�˙˙�˙˙�˙ü�˙ű�˙ţ�����˙˙���������˙˙���˙ţ�˙˙�˙˙�������˙ţ�˙ţ�˙ý�˙ţ�˙ţ�˙˙���˙˙�˙˙�˙˙���˙˙�˙˙��˙ý�˙ţ��˙ţ�˙ţ�˙ţ�˙˙���������������˙ţ�˙ţ�˙ţ�����˙˙�˙ü�˙ü���˙˙�˙ţ�˙˙���������˙˙��˙ţ�˙ţ����˙ţ��˙ý�˙ý��˙ţ�˙ţ�˙˙���˙ţ�˙ý�˙ü�˙ü�˙ű�˙ý��˙ţ�˙ţ���˙˙���˙ý�˙ţ���˙˙�˙˙�˙ţ�˙ţ���˙˙�˙˙�˙˙���˙ţ�˙ü�˙ü���˙ţ�˙ţ���˙˙�˙ţ�˙ţ����˙˙˙˙�˙ţ�˙˙���˙ţ�˙ţ�˙ý�˙ţ�˙˙�˙ţ����˙ţ�˙ü�˙ţ��˙ţ�˙˙�������˙˙�˙ý�˙˙��������˙˙�˙ü�˙ý�������������������˙˙�˙ţ�˙ţ�˙ý�˙ý��˙ü�˙ý�˙˙���˙ţ�˙ý��˙ţ�˙ý�˙˙���˙˙���˙ţ�˙ý�˙ý�˙˙�˙˙�˙˙���˙˙���˙˙�˙ţ���˙˙�˙ý�˙ý�˙ţ�˙˙�������˙˙���˙˙�˙ý�˙ý�˙˙���˙˙���˙ţ�˙˙������˙˙�˙ü�˙ü�˙ý�˙˙���˙˙�˙ţ�˙˙�˙˙˙˙�˙ţ�˙ý���˙˙�˙ü�˙ű�˙ü�˙˙˙˙�˙ţ��������˙ţ�˙ţ���������˙˙�˙˙�˙˙�˙˙�˙ü�˙ű�˙ü���˙˙�˙˙�˙˙�˙ý���˙˙���˙˙�����˙˙�˙˙�˙ţ�˙˙�����˙˙�˙ţ���˙˙�˙ţ�˙ý�˙ţ�˙ý�˙˙˙˙�˙ţ�˙˙�˙ţ�˙ü���˙˙�˙ţ�˙˙�˙ý�˙˙���˙˙�˙˙���˙ţ�˙ü�˙ü�˙ü�˙ţ��˙ý�˙ţ��˙ý�˙ü�˙ü�˙˙˙˙�˙ţ�˙ţ�˙ý�˙ţ���˙˙�˙˙�˙˙�˙˙�˙˙�˙˙���˙˙�˙ý�˙ý�˙ţ���˙˙���˙ţ�˙ű�˙ü�˙˙�˙˙�˙ý���˙˙�˙ý�˙ţ�˙˙���˙˙�˙ý�˙ţ���˙ý�˙ţ�����˙˙�˙ţ�˙ţ�˙ţ��˙ý�˙ű�˙ý���˙˙�˙ţ�������˙˙�˙˙�˙ý�˙ý���˙ţ�˙ý�˙˙���˙ţ�˙ü�˙ý�˙˙�����������˙ţ�˙ü�˙ý���˙˙�˙˙�˙˙�˙ý�˙ü�˙ţ���˙ţ�˙ţ���˙ţ�˙ţ�˙ý�˙ú�˙ý��˙˙˙˙�˙ţ�˙˙����˙˙�˙ü�˙ü��˙ţ�˙ý�˙ý�˙˙˙˙�˙ý�˙˙���˙ţ�˙ţ�˙ţ��˙ţ�˙ţ��������˙ý�˙ű�˙ú�˙ü�˙ţ�˙˙�˙ţ�˙˙���˙˙�˙ý�˙ţ���˙˙���˙˙�˙ý�˙ţ�˙˙�˙˙���˙˙���˙ţ�˙ü�˙ý����˙ţ�˙ü�˙ý�˙ţ�˙ţ�˙ţ�˙ţ�˙ţ�˙˙�˙ü�˙ü�����˙˙�˙ý�˙ý�˙˙�˙˙���˙ţ�˙ü�˙ű�˙ű�˙˙�˙˙�������˙˙���˙ţ�˙ý���˙ţ�˙˙�˙ţ�˙ý��˙ý���˙˙���˙ţ�˙ü�˙˙˙˙�˙ý�˙ţ���˙˙���˙˙�˙ţ�����������������˙˙�˙˙�����������˙˙�˙˙�˙˙�˙ţ�����˙˙�˙ý�˙ý���˙ý�˙ý��������˙ţ�˙ţ�˙ţ�˙ţ���˙˙���˙˙�˙ý�˙˙�˙˙���˙ţ�˙ţ��˙ü�˙ţ�˙˙�˙ţ��������˙ţ�˙ţ��˙ţ�˙ý������˙ţ�˙ţ�˙ţ�˙ü�˙ý�˙˙���˙ţ�˙˙���˙ţ�˙ţ�˙ţ�˙ţ�˙ý�˙ý�˙ţ��˙˙���������˙ţ�˙ţ�˙ţ�˙ţ�����˙˙�˙ý�˙ý�˙ý�˙˙���˙˙�˙˙�˙ţ�˙ţ���˙ţ���˙˙�˙ţ���˙ţ�˙ü�˙ţ���˙ţ�˙ţ�˙˙���������������˙ţ�˙ţ�˙˙˙˙�˙ţ��˙ý�˙ü�˙˙������˙˙�˙ü�˙ţ���˙˙���˙˙�˙˙�����˙˙�˙˙�˙ý�˙ý�˙˙�˙ţ�˙ţ�˙ţ�˙ţ�������˙˙�˙ý�˙ü�˙ţ�˙ţ�˙ţ�˙˙˙˙�˙ü�˙ý��˙˙�������˙˙���˙˙�˙˙�˙˙�˙˙���˙ý�˙ü�˙ű�˙ü���˙˙���˙˙���˙˙�˙ý�˙ţ���˙˙���˙˙�˙ý�˙ţ���˙˙�˙˙���˙ţ�˙ý�˙ţ�˙˙���˙ý�˙ý���˙˙���˙ţ�˙ý���˙ţ�˙ý�˙˙���˙˙���˙ţ�˙˙�����˙ţ�˙ý�˙˙���˙˙�˙˙�˙˙�˙ý�˙ţ��˙ý�˙ü�˙˙˙˙�˙ţ�˙ţ������˙ţ�˙ţ�˙ţ�˙˙����˙˙�˙ý�˙ü�˙ţ�˙˙�˙ý�˙ţ���˙˙�˙ý�˙ý�˙ü�˙ű�˙ţ�����˙˙��������˙ý�˙ý�˙˙�����˙ţ�˙ţ�˙ţ�˙ţ�˙ţ�������������������������˙˙�˙ţ�˙˙���˙˙�˙ü�˙ú�˙ý�����˙˙�˙ý�˙ü�˙ţ�˙˙�˙ý�˙ý������������˙˙�������˙˙�˙ţ�˙ý���˙˙���˙ţ�˙ţ���˙ţ�˙ý�˙ţ�˙˙�������˙ţ�˙ţ�˙ţ�˙˙�����˙˙���˙˙�˙˙�˙˙�˙ţ�����˙˙�˙ý�˙˙�˙˙�˙ý�˙˙�����˙˙�˙˙�˙ý�˙ý�˙ý�˙˙���˙ţ�˙ţ�˙˙�����������˙˙�˙ţ�˙ý�˙ţ�˙˙�����������˙ţ�˙˙˙˙�˙ý�˙ţ�˙ü�˙ú�˙ý���˙ý�˙ü�˙ý���˙˙���˙˙�˙˙�˙˙���˙˙���˙ţ�˙ü�˙ý�˙ý�˙ý�˙˙�����˙ţ�˙ý�˙˙�����˙˙�����˙˙�˙ü�˙ý���˙˙���˙ţ�˙ý�˙˙���˙ü�˙ű�˙ţ���˙ý�˙ý���������˙˙�˙˙���˙˙���˙ţ�˙˙�˙˙�����˙˙�˙ţ�˙˙���˙ţ�˙˙���˙ţ�˙˙�����˙ţ�˙˙������˙˙�˙˙����˙˙�˙ţ�˙ü�˙ý�˙˙���˙ţ�˙ţ�˙˙���˙ţ���˙ţ�˙ý�˙ţ���˙˙�˙ý�˙ü�˙˙˙ţ�˙ú�˙ű�˙˙���˙˙�˙ţ�˙ü�˙ű�˙ý��˙˙�˙˙���˙˙�˙˙���˙ý�˙ü�˙ţ�˙˙���˙˙�˙ţ�˙ţ�˙ý�˙ţ�˙ţ�˙ü�˙ý��˙ý�˙ü�˙ţ�����˙˙���˙˙�˙ű�˙ý��˙ţ�˙ţ�˙˙���˙ţ�˙ý�˙˙�˙˙�˙ý�˙˙�˙˙�˙˙���˙ý�˙ý�˙ţ���˙˙�˙˙�˙˙�˙˙���˙˙���˙˙�˙˙�˙˙���˙ţ�˙ý���˙ţ�˙˙���˙ţ�˙˙���������˙˙���˙ţ�˙ý�˙˙���˙˙�˙˙�˙ţ�˙ý�˙ü�˙˙�����˙˙�������˙˙�����˙˙�˙˙˙˙�˙ý�˙˙˙˙�˙ý�˙˙˙˙�˙ü�˙ű�˙ţ���˙˙�˙˙�˙ţ�˙ý�˙˙���˙ý�˙˙�˙˙�˙ý�˙˙���˙˙�˙˙���˙˙�˙ý�˙ţ�����˙˙�˙ţ�����˙˙�˙ý�˙ţ���˙˙�˙˙�˙˙�˙˙���˙ţ�˙ý�˙ü�˙ý�˙ţ���˙˙�˙ý�˙ý���˙˙�˙˙�˙˙�˙˙���˙ţ�˙ý�˙ý�˙ü�˙ţ�˙˙���˙˙�˙˙���˙˙�˙ţ�˙ţ�˙˙�������˙˙�˙˙���˙˙�˙˙�˙ţ���˙ţ�˙ű�˙ý���˙˙�����������˙˙�˙ţ���������˙˙�˙˙�˙˙���˙˙���˙ţ�˙ú�˙ü�˙˙�˙ţ�˙ý�˙˙���˙˙���������˙ţ���˙˙�˙˙������˙˙�˙ý�˙˙˙˙�˙ü�˙ý����˙˙�˙ţ���˙˙�˙ţ�˙ý�˙˙˙˙�˙ü�˙˙˙˙�˙ý�˙ţ�˙ţ���˙˙���˙˙���˙˙�˙˙�������˙ţ�˙˙��˙ý�˙ý�˙˙�˙ţ�˙˙�˙˙�˙ý���˙˙���˙ţ�˙ý�˙ţ���˙ţ���˙ţ�˙ü�˙ţ���˙ţ�˙ý�˙ý�˙ý�˙˙���˙ý�˙ý�˙˙�˙ý�˙ţ���˙ţ���˙˙�˙˙�˙ţ���˙˙�˙˙˙˙�˙ţ�˙˙���˙ţ�˙ţ�˙˙�˙ţ�˙˙���˙˙���˙ţ�˙˙������˙˙�˙ý�˙ţ���˙˙���������˙˙�˙ţ�˙ţ��˙ý�˙ü�˙ü�˙ü�˙ý��˙ý�˙ý�˙ţ�˙ţ�˙˙˙˙�˙˙���������˙˙���˙ţ�˙ý�˙˙�˙ţ�˙ý�˙˙���˙˙�˙˙���˙˙���˙˙�˙ý�˙ţ��˙ţ���˙˙���˙˙�˙ţ�˙˙���˙˙�˙ý�˙ţ���˙ý�˙ý�˙ý�˙ý�˙ţ���˙˙���˙˙���˙ý�˙ţ���˙ý���˙˙�˙ý�˙ý�˙ţ�˙˙�˙ý�˙ţ���˙˙�������˙˙������˙ţ��˙ţ�˙ý��������˙ý�˙˙˙˙�˙ţ�����������������˙˙�˙ţ�˙ţ��������������˙˙���������˙ţ�˙ţ��˙ý�˙ü�˙˙˙˙�˙ţ�˙˙˙˙�˙ţ�˙ţ���˙˙�˙ý�˙ţ�˙˙�˙ý�˙ý�˙ţ�˙˙���˙ţ�˙ý�˙ţ���˙˙�˙˙�˙˙�˙˙���˙˙���˙˙�˙˙���˙˙�˙˙�˙˙�˙˙���˙˙�˙˙���˙˙�˙ý�˙ü�˙˙���˙ţ�˙ţ���˙ý�˙ü�˙ý���������˙˙�˙ţ��������������˙ý�˙ü�˙ü�˙ý�˙ü���˙˙���˙ţ�˙ţ���˙ý�˙ţ�����˙˙�˙˙���˙˙�˙˙�����˙ý�˙ý�˙˙�˙˙�˙˙�����˙ü�˙ű�˙ý�˙˙�˙ţ�˙ý�˙˙˙˙�˙˙������˙˙�˙ý�˙ţ�˙ţ�˙˙�����˙˙�˙ţ���������������������������������˙˙�˙ţ��˙ý�˙˙˙˙�˙˙˙˙�˙ý��˙ţ�˙ý�˙˙�˙˙�˙˙�˙˙���˙ý�˙ý���˙˙�˙˙���˙˙�˙˙�˙ţ�˙ý�˙ţ���˙ţ�˙ţ���˙ţ��˙ý�˙ţ���˙ţ���˙˙�˙ý�˙ý�˙ţ�˙˙�˙˙���˙˙�������˙˙�˙ý�˙ü���˙˙�˙˙�˙˙���˙ü�˙ü��˙ü�˙ű�˙ű�˙ţ���˙˙�������˙˙�˙˙���˙ü�˙ű�˙˙�����˙ţ�˙ţ�˙˙�������˙˙��˙˙�˙˙�˙˙˙˙�˙ţ�˙ţ�˙ţ�˙ý�˙ţ�˙ţ�˙˙�˙ý�˙ü�˙ţ���˙ţ���������˙˙��˙ţ�˙ý�˙ü�˙ţ�����˙˙���˙˙�˙ţ���˙˙�˙˙�˙˙���˙˙���˙ý�˙˙���˙˙���˙ţ�˙˙˙˙�˙ý���˙ţ�˙˙���˙ü�˙ü�˙ţ���������˙˙���˙ţ�˙ü�˙ű�˙ý�˙˙���˙ý�˙ý��˙ý�˙˙���˙ţ�˙˙���˙ţ�˙ý���˙ţ�˙˙���˙˙�����˙˙�����˙ţ�˙ý�˙˙���˙˙���˙ţ�˙ý�˙˙�˙˙�˙ţ�˙˙���˙ţ�˙ţ����˙ţ�˙ý�˙ţ�˙ţ�˙˙�˙˙�˙ý�����˙˙�˙˙�˙˙�˙˙���˙ţ�˙ý�˙ţ�˙˙�˙˙�˙ţ���˙˙���˙˙�˙ţ�˙˙�������˙˙���˙˙�˙ţ���˙˙���˙˙�˙ţ�˙˙���˙˙�˙˙���˙˙�˙˙�˙˙�˙˙�˙˙�˙˙���˙˙�˙˙���˙ţ�˙˙�˙˙�����˙˙�˙˙���˙˙���˙ţ�˙ü�˙˙˙˙�˙ű�˙ý���˙˙�˙ţ�˙ý�˙ţ�˙ţ�˙˙�����˙˙�˙˙���˙˙�˙˙���˙˙�˙˙���˙˙���˙ţ�˙ü�˙ý�˙˙���˙ţ�˙ţ�˙˙���˙ý�˙ü�˙ý�˙˙˙˙�˙ý�˙ţ˙˙�˙˙�˙˙�˙ý�˙ý��˙ý�˙˙˙˙�˙ţ��������˙ý�˙ü�˙ű�˙˙���˙ţ�˙ţ�˙ţ�˙ţ�˙ţ���������˙˙�˙ţ���˙˙�˙ţ�˙ţ�����������˙˙�˙ţ�˙ý�˙ý�˙ü�˙ţ���˙ý�˙˙˙˙�˙ţ���������˙˙�����˙˙�˙˙�˙ý�˙ý�˙ţ�˙˙���˙˙�˙ţ��˙ý�˙ü�˙ü�˙ý�˙˙�����˙˙���˙˙���������˙˙�����������˙ţ�˙ţ�˙ţ�˙ţ��˙ţ�˙˙�����������˙˙����˙˙�˙˙�˙ţ�˙ţ�˙˙���˙˙�˙ţ�˙˙���˙ţ�˙˙�˙ţ�˙ţ�˙˙���˙ý�˙ü�˙ý���˙˙���˙˙���˙˙���˙˙�˙ţ�����˙˙�˙ţ�˙ţ��˙ý�˙ü�˙ü�˙ü�˙˙˙ţ�˙ü�˙ý�˙ţ�˙ü�˙ů�˙ř�˙ű�˙˙˙˙�˙˙���������˙˙˙˙�˙ţ�˙ţ�˙ý�˙ţ��˙ţ����˙ţ�˙ü�˙ţ�˙˙���˙˙�˙˙�˙˙�˙˙���˙˙�˙˙�˙˙�˙˙���˙ü�˙ű�˙ý�˙ţ�˙ý�˙˙���˙˙�˙ţ�˙˙�˙˙�������˙˙����������˙˙�˙ý�˙ű�˙ý�˙˙�˙˙���˙ţ�˙ý�˙˙�˙˙�˙˙�˙˙�˙ý�˙ţ���˙ý�˙˙���˙˙�˙ţ�˙˙�˙˙�˙˙�˙˙�˙˙�˙ý�˙ý�˙ű�˙ţ���˙ý�˙˙�˙˙���˙ü�˙ű�˙ý�˙˙���˙˙�˙ţ�˙ţ�˙ý�˙˙���˙ţ�˙ý�˙ý���˙ţ�˙ű�˙ü�����˙˙�˙ý�˙ţ�˙˙�˙˙�˙˙�˙ü�˙ü�˙˙˙˙�˙˙�˙ý�˙ű�˙ţ���˙˙�˙˙�˙˙�˙˙���˙ţ�˙ţ�˙ţ�˙˙�����˙˙���˙˙�˙ý�˙ţ�˙ţ���˙˙�˙˙˙˙�˙ţ�˙˙˙˙������˙ý�˙ű�˙ü�˙ţ�˙ý�˙ü�˙ţ�˙ţ�˙˙˙˙�˙ţ�����˙˙�˙ţ�˙˙�˙ţ�˙ţ������˙ţ�˙ţ�����������˙˙�˙˙�˙ţ�˙ý�˙ţ���˙˙�˙˙�˙ţ�˙˙�˙˙�˙ý�˙˙˙ţ�˙ü�˙˙˙˙�˙˙����˙˙�˙˙����˙˙�˙ţ�˙ţ��������������˙ţ�˙ü�˙˙���˙˙�����˙ţ�˙ý�˙˙����������������˙˙�˙ţ�˙˙˙˙�˙ţ��˙ţ���˙˙�˙ţ�����˙˙�˙˙���˙ý���˙ţ�˙ű�˙ü�˙˙˙˙�˙ţ�˙ţ�˙ţ���˙˙�˙ţ�˙˙�����˙˙�������˙˙�˙ţ�˙ţ�������˙˙�������˙˙�������˙˙�����˙˙�˙ý�˙ţ���˙ü�˙ű�˙ý�˙˙˙˙�˙ţ�˙ţ�˙˙���˙ţ�˙˙���˙˙���������˙ţ�˙ü�˙ý�˙ţ�˙˙�˙˙�������˙˙�˙˙�������˙˙���˙ý�˙ú�˙ű�˙˙�����������˙ţ�˙˙�����˙˙�˙˙���˙˙���˙˙�˙˙�����˙˙�˙˙���˙˙�˙˙�˙˙���˙ţ�˙ţ�˙˙�˙ţ�˙ü�˙ţ�˙˙�˙ţ�������˙˙�˙˙�˙ţ�˙ý�˙ü�˙ý����˙ţ�˙ţ��˙ţ���˙˙�˙ţ���˙˙�˙ţ�˙ţ�˙˙���˙ţ�˙ý�˙ý�˙˙���˙ţ�˙ü�˙˙���˙ţ����˙˙�������˙ţ�˙ý�˙˙���˙˙�˙ţ�˙˙�˙˙�������˙ţ�˙ü�˙˙˙˙�˙ü�˙ü�˙ü�˙ţ�˙ü�˙ţ��˙ţ��˙ý�˙ú�˙ü��˙˙���˙ü�˙ű�˙ţ���˙˙�˙˙�˙ý�˙ţ���������˙˙�˙ţ�˙˙˙˙�˙ţ���������˙˙���˙˙�˙ţ���˙˙�������˙˙�˙ţ�˙ý�˙ü�˙ü�������˙˙�˙ý�˙ű�˙ű�˙ü�˙ý�˙˙���˙˙�˙˙�˙˙���˙˙�˙ý�˙˙���˙˙�˙˙�˙˙�˙ţ�˙ţ�˙˙�˙ţ�˙ţ�˙˙���˙˙���˙˙���˙˙�˙˙�˙˙�˙˙���˙ţ�˙ý�˙ý�˙ţ�����������˙˙�˙ţ���˙˙�˙ţ�˙˙�˙ţ�˙ü�˙ţ�˙ţ�˙ü�˙ü�˙ü�˙ý��˙ý�˙ţ�˙ţ�˙ţ�˙ţ�˙ţ�˙ý���˙˙�˙ü�˙ü�˙˙�����˙ţ�˙˙���˙˙���˙˙���˙ý���˙ţ�˙ý�˙˙���˙ţ�˙ţ�˙ţ�˙ü�˙ţ��˙ţ�˙ţ��˙ý�˙ü�˙˙��˙˙�˙ţ�˙˙�����˙ţ�˙˙�����˙ţ�˙˙˙˙�˙ü�˙ü�˙˙���˙ţ�˙˙�˙˙�������˙˙˙˙���˙˙�˙ü�˙˙˙˙�˙ţ���������˙˙�˙ý�˙ü�˙ţ���˙˙�˙ý�˙ţ���˙ý�˙ü�˙ţ������˙ţ�˙ţ�˙˙�˙ţ�˙ţ�˙˙������˙˙�˙ţ�˙˙�����˙˙���˙˙�˙˙���˙˙�˙ţ�˙˙�˙ţ�˙ţ�˙˙��˙˙�˙˙���˙ý�˙ú�˙ű�˙ţ˙˙��˙ţ�˙ü���������˙˙���������˙˙�˙ý�˙ü�˙ý������˙˙�˙ţ���˙˙�˙ý�˙ý�˙ţ���˙˙�˙˙�˙ý�˙ű�˙ü�˙ţ�˙˙�˙˙�˙˙�˙˙�˙ţ�˙˙�˙˙�˙ţ�˙˙���˙ý�˙˙�˙˙�˙˙�˙˙�˙˙�˙˙���˙˙�˙˙���˙˙�˙˙�˙˙�˙˙�˙˙�˙˙�˙˙���˙˙�˙˙���˙˙�˙˙�˙˙�˙˙�˙˙�˙˙�˙˙�˙˙���˙˙�˙˙�˙˙�˙ý�˙ý�����˙˙�˙ü�˙ü���˙ţ�˙ţ�˙˙���˙˙�˙ý�˙ý�˙˙�˙˙�˙ý���˙˙�˙ý�˙ţ�˙ţ�˙˙�˙ţ�˙ý�˙ţ�˙ţ���˙˙�˙ý�˙ţ���˙˙�˙˙���˙˙�˙˙���˙˙���˙˙�˙˙�˙˙˙˙�˙˙���˙˙�˙˙�˙ţ�˙ý�˙ý�˙˙���˙ý�˙˙���˙˙���˙ý�˙ţ�����˙˙�˙ţ�˙ţ���˙˙�˙ţ�˙ţ�˙ţ��˙˙���˙ţ�˙˙���˙˙�˙ţ�˙ý�˙ţ�����˙˙�˙ý��˙ý�˙ü�˙ţ�˙˙������˙˙�˙ü�˙ţ�˙˙�˙ý�˙ý�˙ţ�˙˙�˙˙�˙˙�˙˙�˙˙���˙˙�˙˙�����˙˙���˙ţ�˙ü�˙ţ���˙˙�˙˙�����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������beetbox-beets-01f1faf/test/rsrc/empty.alac.m4a������������������������������������������������������0000664�0000000�0000000�00000015344�14723254774�0021346�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000��������������������������������������������������������������������������������������������������������������������������������������������������������������������������� ftypM4A ����M4A mp42isom������ ćmoov���lmvhd����ÍĎáÖÍĎáÖ��¬D��¬D��������������������������������������������@��������������������������������îtrak���\tkhd���ÍĎáÖÍĎáÖ���������¬D���������������������������������������������@�������������Šmdia��� mdhd����ÍĎáÖÍĎáÖ��¬D��¬DUÄ�����"hdlr��������soun����������������@minf���smhd�����������$dinf���dref���������� url �����stbl���Xstsd����������Halac���������������������¬D�����$alac��������( �˙��¬��UF��¬D��� stts���������� �������� D���(stsc����������������������������@stsz����������� ��#���·��9���ß���Â���ç���É��¬��I��9���R���stco������������´��’�� „udta�� |meta�������"hdlr��������mdirappl�������������ilst�� Ffree��ňfree���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������� ěmdat����� řÇ˙„�˙€ňĆÝ0�Ŕ-ÇqŤŢ�Ç]ż(ť+¦����?ŕُ�˙€+F?ŕø�8Â;ť˝��řŚa×®ó��î��ňŚ8ŔăŃ„D ;�Â; íî@��tŚ#ş�0ë×y€��úFÜíµĎ ���Ç]ŕ�7x�ývď��Gvď� ŰĘ'P�u”���Ľ�ć:ď��ŔGpúĆÜ�y»~€��đĂŻ]ŕ��}ăq‡q‡^ŕ7o@��Ý˝���ŔGp˙�{Ś8R0„v»~€��üĚuŢ��ţĂ®đ�üľ0Žŕţp˙€¤ŕţ]Gq{€�đŢ�����˙ů{˙đ�2˙€ňĆ×ŢĽĄ���˙�]Ś8~q„w�đ.Á×oŃĘ���˙�IŚ#»w€�ţGp˙�˝Ś8Ŕ"ă»Ŕ�đ Á×x�ţ©Gp¤aĂţ�p˙€rŽŕţAp˙€*ŽŕţýGvď�zď��˙�dŚ#¸�HÇűĆzíú���Ü�˝Ŕ� uŢ˝���,:ď��Ŕăđn�����qú6�\� Hčç=��ć9Ď@�?´bč €˙ť€¤aŔG;�Ć.€˙€0Ž€”aŔÇ+{€�Ś]nđ»^€��ĆüHs°�`s°�}Ł@~C­Űk¨���óŚ uŘô�Ł@Ŕ&Ç[·��Ŕ0Ł ŕç`�ÎŔ˙�ZŚ/řlbčř bëw€� sglÉ@��ţ'şÝĽ€�ýG@vě�Ľbĺ=v��>ń…˙�· �xnŢ@�˙�A˙�GŚ]ÎŔç �Ěsž€�~ĂťŻ\RqŃĄ���úĆü�€s°�yFü�Śs°�Ŕ$‡;�üäs°�ŔAă ţ<9Ř�?AÖíł©T�…€�Ý€�Ś]nŢ@�����™úľ�ň�ű‡;�Ö0ÎĆě��˙€9Fü1t€Ň0ÎŔéąO@�ţc­Ď@^vTF�hR�5<s���´bč ;�¬bčř<b‰a`çkÎ`��˙€-Ć.€ÄaŔ=c ‘‹­Ř�8Â˙Q‹”ô�řTbëv�Öçśy8Ŕ��ü0s°�|ă ÷N0żŕ…ţv�ř$b‡ütr×–��˙ŞF9 ţ¤9kË^@��˙€AFüLrדĐ��Ă ?qĐđ Đđ~�����łűHR�Ń_đŘÂ&·`�@i_đ !É ührhó°ţQˇ˙�›ŚP˙€EĆR_Cţ%Jđ xÂ˙€@Füň1Cţ�Ŕ9`üśr×–��XĹřxč‚h˙�jŚ/ř ôaŔăÂ˙€6€˙€[Ž€üٰţ˙_đ XĹ9`ă ;€ůFŕčřŚb‡äb‡ü�Ş1HˇÁĐđńË€�����ű˛’�LaB˙€JF!úŚ˘˙€Ak-h�ôŚ/ř b‘@‚żŕ P˙€2B…˙�"!˙�L‚ި¨(Ŕ @‡üd(Z!˙�Z…(_đ((/řĚ?ŕ '˙€0AU!ŔD?ŕBđŔ!`N˙�‚‡ülCţ�Ż˙€"ŔFÄŠ¨Ŕ ˇ˙�$‚żBB˙€z‰ ţ�É˙€ACţ�J$/řt ţ�b!˙�RŔ? @żŕ‚=‰˙€Â…˙�Lŕ�����üÄ�ć8ü�– đČŔŕN˙�'˙€CŔř�  ý (_đx _ěCˇ!Ŕ _pĄ ţ�< ACţ” Ä?ŕ°/řüf đq!Ŕ„?ŕpP ¤/ř ěRü�„A‰ ţ�ţ$˘‡üZŕ_đ Á!ŔÄ„ ţ�< ACţd$ Hˇý ¤/řČŞ¨p/řDP˙€7Ŕ!Ŕ ÄŠ?ŕś�����‡üdů˙ö˙h!Ŕ!ŕ @żŕĐ/؇üLCűđč _đÁřô(ˇ˙�˙€Ŕżŕ B˙€Ŕ‡ü8CňÜ pPţ„*?ŕ*˙€,(ÂP H_đ!!P!ö ĄࡱÄ*?-ÁA‰ ÁA!P/‚Bp" *Bˇ !;´-¶†š,°� E–�Ö¬ečĐ4ÄĐĹ’`5‚Ńc��ˇ HĂ­˘,cX�h‹Ö´X-ĆZ h�4LĂw“,cX–… �ÖĽan¤Đ�!ŚKF  BĐ,cD4kCXµ‰h5h°Z�1!ä @śĚCbAJb ę2ň '$ˇ3»ÚĐZа�XÄ�4śßP&BđÁ 7Áˇ ±:„¨hCţ�g!÷uż ÎÁřÄ ¬B R ďř¤ť˙�Vx)Mř%P›ů˙€Áť˙�0Ŕ Dďřś ţ�d%Aö'ŔÄďřŔ‡ü. @›ţ�•Ŕ�����Çý¬ń˙ Î!üżŕĐ»ţ�Űná‚đ!1bb‚đŃ1AhB ŕ[˙-Ŕ oŘ…@ąý~Ĺd+P‡ű˙€ŠŢBÄ$$}@Ľt!PęˇhXp‡ @›Đ”żˇřÜ @· A hVúÔ,VСVˇŔŕQwÁ+&ˇ Ŕś.PĘßđŘd żŕâčV˙?P/B˘ŔE EęŻřşü8¦$BĹ˙�'T żĐřđŠźđČ![ţ�ě ďřěüÎŻřx‹ţ�ľ!˙�rŔEoř$_đ!ř‰t( ďřŕżŕâ*Ŕ ‰˙� _POř,đüî Mţ*˙€żŕ%(Ŕ!źđó•˙�@‡ü�w€�����ýţţ�˙€GÔý_đëú)A2DuÍ˙�]żŕŐHĘý?ŕ%2ść˙€@ČŽ˙€-”ˇLŔiÎJżŕ>•Oř LÉÜÉ˙�('ü4Ą»řR›KeußăÉĘ ˙�UżŹř|ćR´R¨.˛ŘŠ„! DRJ…BˇR•)˝É!ę‘×'&J(‚ZÄ ‰@¤,¨T3”¤¸™DV*đ×ů3’AĨŠS'l‘*SŔŐ…["(+‚9¨BPP%1"!A›ţ�Nw’#)‰"I R™.P!ÁS"*”r I˛±(BSŔ ‡Ŕ $Çü�ňęD«…)şŔ JSŔ S]r#żŕr'·ü�¶xäBSŔďř������őţ ţ� ýr!CżŕÓcţ�ĐT;“#Ŕ¦Oř ĚŇoř}ű’M˙�á‘Ô&wđŇŠ?ŕ'üxĺv§ü�ľ˙‚‘p��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������beetbox-beets-01f1faf/test/rsrc/empty.ape�����������������������������������������������������������0000664�0000000�0000000�00000032154�14723254774�0020531�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������MAC –��4�������������Ü3����������Ů0' 0éoĹ|Íţqôčî�����D¬�������D¬��P���('¸�������č+6ąD€»…›gzżă˙nč±`ČéŻCÔC-]áXUÚ˝ŰÎý–Ěą€ËRôa‹% –ýŇń0ó©'lOéŘ[&ď(ďďHő)ś"i%6aMJO1FĹ@…D8Ůqlpů¤ŚE ]“ą$ę ĺýwëvy0äĎ\ţv7®Ś±I®Ć ̇m7Ą.uMŇÉč$Ĺý #­×x|‰Sźúgb;<·|:}|{H?k¬ň"ÁÓ{äěćwß áÝéݡ"vútŽ`OM".�˙Ä+.Ë÷"q=f“; ĺF&8e*’<ű´íH4ŠČiçŢ-w»´7rý:¤ŰśJ \ó!¶Ü$W'8[4“ě«„ËAvěMXFĘUÄÍťHU®qta!;{;Şëą ĘÁ­ŕG^ô�ad¶Gę™gŚľPĄ­)«Ś·Č úîŘ™ÜzĐšSŽřg_ęQ¤Ü=ĎS Şů¶¸âčMż‡nć”– ­u%ă÷ú˘ÓÁ8˝ Ě úx«=j]m6“aú@Ť“I›L]ö~çęn0.*§ăĎćcžânQjŠ0‚ôęí‰6j®ń1ÚC%g§4ß\-GhŮ3Ý•[čŕH"’—ó@ů>Ż3°+©ŮÎ˙äÜG˝ů§W3r<b…ŘC3D:n•8ZŃ ž}ě؜׎BĹ‚Ţč&±č-ňěđäą:kň_+y‰ĐyAZ(>ÚKn>śŇÚ+{<zţĎÔ„ĽuϸlD +cbŰÄc„>ç˛ňô·Š¬´ &…ń6uż_zęmeS)K+2ňkň± éŘ8ôÝ‘cĽ3L®ÝÚ퍎Şv˛WܸÚßčŃ‘¶Yŕş“@§1"kâr}őqIĆţu ¸ ˇ©[#ŘČôđ¦‘w“f•—C?s·”b Ś[XAĹĘ Ă=‰‹\ ú„yäüťvJeą•ŮׂfÁ$0˛¤yŁž}:ŽÎ Ř­ô[čr‰zŘ>Q†Áx „“(® ~ 9Ĺ5QţKť¸ÓÓČÓ&)±¶‘9jÜaĄ«`ľę‹A8¤Ě×– ”©żŐ"“ŕ†;bR3¸ “D©ťW vćÇ)ăĎłS›ÎŁŽŹÚĘ̲A}ĎĐꂇTÖ‹ľDcXHwüWE>Čŕ‘'•:ЕȜÄqqoie4>#Ź÷ -&}ě#}FŹí5pĹ㏱ăł*"•(taŕˇÂŕĹ“"Ń)®BÜ -·,AEexjĄźňÚ†›űu®�RŹH×n_¶n¶(ö¶Îâg'v9HĂ^%Ü!?ľ"XÎ@µŰÚ6×›R÷…­‰ŻĚÎyÔĆá=ŔĎ‚öŻ–8/Ö3VőMć»8ż±˙u!đ™rP@ÄÔŻ ›“řqSzá~ĺjwń¤xTčv¨TÚ.'˙î†>ĐäŇš?Î`XşůĘT÷:™—ýĚ Aă?”1˛\@đd±Fâ|ɶĆD˝‹°6ő Xőś“4„,đÁMÝRż#÷°ŚĄ&ěeőľ­E„›źväĘýS˛š+vWČăsJ˙Ëžýxń˘Ěśă{ö[·>¦pQ… t˙łňŽ)®Ě ¤Ç`ămnX±^xőÚ-Ç®“©=®ćŤ´|ťt˘Ŕr…βţ‰ą!ɰvČ ^Tż ßN‘k1˝9Ą#śŢiÔ[@ÝöSÉ‘’č&ŔĎvš¶¦_Ö.ŻoţUśťć÷ŮŐt÷xLÚ˝ęř‰H/ ‚Ďm…ŇfčżĂÉ‹.y Ć8Ô÷KüQE¶p:•7'§Nnľ„ě 8lhËŻîo=›}Żľ•9P}Ví{sÔč˙ŽŤďnsŔXiV<Yô‘ľąÜÂú.ă«Ëö­4·ˇpDf&¶ ÷^°"–IXâíEwŔ°<Éýň&U¸Búħ:üşť°{“S™«´ UqXÓ_ZËŃő„)ćmţ}85äżÖÉ:gë}‹Ŕ^ňĽw5»PGH/űíúy‰<Ş~‡~WŐź=%Jg(ýj2i™ÁRf™hq5Ô[á:'b‰:ćhzhgM °€ÇŹĄ‰ŢÚŢĺŇ´+fä1‰óo „śĺ€ÔĄÓý§/ýčQţ´ů4QRĄăᣎ_ęS0>Ó”íEI€íH=ŇősşK—Î{ÍÁ»ŘŇcyO€g›ˇPű‘–ő‰żÉĹţµ\3yy8#;çtş2‡˙ę[mš ©í‡˙‰y0ÚäQv{Dź×®ú3@ä ă­h˝lXÂqKž!®!őĐ�Öąú§çÚ‡źîŔř–%5Pú]×|ąő´††#i2‰öíÍý Eäůě›ęL˙î­ 7XĄ˘)E»[Ýá[Z$ѾƺŖ˝żîKšW¤ĄŇĂśíeš�Y#ńŮ,‰íŤŢ„ě.eŔŇ<Š[čÍ؆,ŤI#ŚôŇ·5´BÚ ˘6Ěĺ)ĄŮůMP™12]ž Bäă'‘i"’-,ąXQ3Á™€Ř_@řĺ©ĆY5ÎŚDdťm P Uëe*zď =ý…,ňc‡-żgCó˝6a‘˘1ucô°,a$őÖ×zܤ ćV‚ňŔRĺ “”$}ôÇĹ~ď¸9EZç1°H.1.–j¸˘îżź{—N]aL·.7 ¦Ż€DQ‰l¨°Ůń‘±89Ĺgg­vDmę^ĹĽäžďđDUV‰.’‹ „OX“EV˝ńúĹŻĹż>qázŻ™Q}(Ż#¸MoNŞţ‚,–-ç~bűc–97ţŰ W_ýÚ 'sžNYO j]feČu´Ę6óÝ3¬ĄNâ‘+1g¸`äjÄ;Vn6ý˛pf_OBdkŕžpěuŐqž\HM…kŹűÇbµY2}¶PĎe'ĽŇqCGˇ[Jě7‰P~Oëę ’k¬™‘Ő3“/—A ]%†°h@ o<V¤Š/lÜŻDÍĚrä;Ś’B®ÓĽó€Če9óŮťÇN‡‘ëČP—díş«ö-GLjc¶íDNäçs­_Íî\\Č^˙Oöť.˝]ň´ ńd*jxÜť_Ĺ{?ň żÍ:4~ ëCŃfÖ$ u—V€T5•Ě4—|@‘#F2ç�xZ:Ú“¶)ŮäZLU˛ŠYńxź9­˝ĎÇ*¶^w23p(×kz0íť2NYT„Íö‡<1_íŢĂ{FTă© ëz–ú€Ěżs­PźtłÉŇú1 –Ű# ˝6#´»”› 0 ł2ýJáPX"<7Ę·ĆOáJ—čwCâj˙h*Ą}áVBřńďXŁŁ˙ŘÝűÂţ9ňסڬż‚ŕ.ę' ;h|Ĺnůf˝.ËťŢW|ú{­¬žžn™żýÓÁET˘”(Ań!¬ ŮfĺnŠnŔźW¦éî—ŕ·ń¸B÷rfŻÁ”O}7S|ąsľ[(7üe@aóÉý+¦§"Ü´¨Ŕ±3ć­3˘=DÄG|ÚŁSá\űËËŐ»GޤjfĆsé‚śŚˇcOýů5«Ö4ŔčAľ^Ď·Ţă�če>zWÇlýŇ0—uąrî[˙KŸ}–ún˛gţĚ@)E˘ …/ď׿•A ă썚¦›źÝéFz ‹ěӓ͸B‰@[Ë %Ű×á!Ę~nÂ8o¦®H#7±=ŞđG źöżqÍĐóО`Ka˙„íHĂPXŮóú{ ŚŻŢz“đ|\+Čç|+Nn{W´*ă›Ňý2†usá1\$«J÷‰µ®E3Ő˙°Ű (ÓÁň‰ĄQl µÓâű¦\ő€V 6°ě¶†RĐ-]ú–SŃM¬´«}.ŇZ+í)BĎw{”ű]ňŹ×ĽBôŽ Ł[Ú~š–’‹©<nľV,ë}#ŃL;ś5¸˝ťĘ�tf[ČVŞÔÔ8¨_TŃ7<ŘÓŢ»……ótu-ŽŔ'DB[XŔŻĹ?›Î:<Űn^}ĽłPČÄ —O·=Ř“y˛T-óĎ_uXXö‡JhËy ź´;ŤS’ t•ŠRňż×›ŽSŕ‘ܲ¸Ďf é,MĚ ÎnŇ­šß.‚ĆŮ>–uÖ<e ˇĆŚcUŐű äöŻÂáçł:ß á~*Ň:A1ž«¦1L–•PוÉ€‘¸Đ_¦âw¤MkyŇ‘>ęk‹§*ë[“Réů˝ĽČKń^˙Wń.ýŹyx^čSĄ`ąÇXń Tť9 Á‰}­ ,f%cň¶z¤Cdđ°‰/oűXfÍÁgî/ňŽćÖLb ăząIŃ9z,çik߸m2Ă�Hßč:ÚČ™U3±6ę›ĐÔVŕ-­!S)­Tµţňt —Ăߊq‚üăÎ4zM’î=03mcí(f4E×Ů9¨Ji#çŘ$Ö€˛‡.Ľ¶˝î—fąOŰša—żÇFaöćoËFVą68qc•ň:u[PŻ_«eŇžfŕëf›ŰÖ�»$;RľíźÄ=§Ś‘!MŹĆ—íS±(ŮbéLIŹçoWŮ>ňź“î铳ŇÓ¶—S{Iî=C.…ŠŰÍ'ĂÎ?^“Hut†q§ą›Yn—¦´®1o2˛ňz.˝ :LŤ?ŃÁmt‘¦×x) Lěĺ\číö󬍩K•aż´¬p\!)ó §÷ŻP#ű…?#™đĆ<wIµÉő«Ż�gŰEqŠó>ÎęX58ć»ů޵¸*l¦&#ŕp(müL‡3–B±AóéV$®ň÷\Ö^›G4–Řď”yłŢ°"Q’©s¬Żaň—R%VŞwPRő±űoĆuŇÍ‘?şËèđÔř| ŃjĹĆ ßFZ© Uíçíw¦łĘ�ĺÝLeňt—ŢˇŁ±ß\®]ťnfČ yř*e6úŚ.ü¦”PŤčZ Öâ‚ ş!ýi…ď«{ď¤Ý†S¶hÚëęŘgeyäÎy&úń —Ę6#ýF(;`;S÷íţX·ŕ‡«¶~Nţ>^„Ćjß˝#䬭VĎ%ކ„Řd8tLíĄÚF:änś_ îőČxrr«ĺô± „ŕŽVˇ˙—;ś!ŃĆüHŮĄ©˝IÄAş_\d™ęŢYšštIËľ§ăzŇ!¦%,Ľ"{–( –ĆiđĹĹŻÓďI«»öŤe˛˘mnřqĄŚÉ…włËX!qőüm­~"ś.Ă5Ě08ˇ8’ëA&›k÷y»pĚÝg¸ţäˇ!O úĆ5Ň/#Fv˛Ńľ»AßaI$¶öůIM$éV]ĆŽÎtőş ą\€Ť^ö*­B§X/e`{Ćh ÎLóęĄ5ÎV4híE@ňEÁhŚŤ2˛H1C|ggÍĘłĎß® :O­ —Bż ¸9’Ś0˛ďXwTl}Š$4¨éť]¸ô‘“l“ÎôFÝެ˝0ĆeSbŽ+ż’í‰<ŃL¸J‘ő^‹!hŤňďˇSiűA Źfa‚B$?é×ZAt¸# – =š üżÎGú—çTűÔűőUľ‘‘+FčX §L7˙»-áăx»µżN`ŻÁ„6$cöÓ ÁÂBÚő:—ţN’mkCŘ=ŽăF/}8âÖă^ÚDđAsą´®#x_™•C«KT]ÖƲÔľ˝mřa×á}üo† Vú´âä‚€ +Z*łN÷·Gť°.™ IĘ›ęáS \†Ä?ΤRý}-JzăˇÖbÍ­węµŘµ„ …Čť™_’ä7ĄĆóĄsRáÁĹtÍ­n—’ëT;×ńŚl CŽS÷Q’ëĄŐ>Tˇă‹#A陡Ż:^<k»äŢ‘m˘Ę>ŻhőQ”ŠĺÚĆĐĚU fB\˙śm\ŐMrYó^ů`—A‡ô–śEëQďZ‹’Ź˙ĚŞ™ľĐ8#”Ú:V:ŢgJo�’C‚ß0~×}”ćtž4$ĄşóÔň°Ái“@b8epÚµÁµíĆE+b÷y–ßb´čłň„«.ÓĐîv0ů›™H9ű('Ť%™ÎqÓÁ‰‡N«)†{ś‰b˛§?›,-nţ x˝k Î?UT…áʰ4vl”ĄP{%ŞÝ‹vÄuݦBnđCżjI÷4CLJ§ŹěłXk?fCŻĺjpőßżh×q”«ě50RHU*‡ŇŘĚ-m‹:Á|ă5´;Q•›µß&Ľůě¶PérçŐnŽŚL‘Uçň×5÷©yéšI,OYˇ*„áůŽ“)XLĐN5 8đ‡L¨Vá6«łW 9Q R#+ކ K˘ěýäXÜg.náá2Šá»í2KŇŘ—ďTőÂŢ�ËÇÝ„ĄF0XC‡,S_ç-ĎÔ,ŢÇńäÄÜŕ–¸‚ ö3"¤m˛wr•‰kBÇR‰žÎręK¦Éĺ.yú°V:±Ü;â *ť 0čk˝—VĽ!ľ�“ŽBçýę ÔŠ¬±ěż´Šú祽ň´D…¶ďbhtÄ·+ǵ!dĆŻ˘t'UNa¤…‘řĂČĆłR�V»3J†™˘ńqôňď(ÖôŹ<xm%cw†8Ňž gVlz/ZŔφ“é{ꇕ�őGď@îvŤ•“pذU×ML-wmŐ<ő[ü ‰ŽęiAĺ8\0"k87Ęž??—…Ź Zî"¶B'ěŤry/ ڬG’ń)Ę•Hśjľd/¦EŢLŃĆ˙4×8éG©Vä^LÁĘr1˙›¤äP =ŁżgŚ(F݇VĹŮY´ü4Ĺd•SĐL×ZÄČ˙@,Ú†qń­Ű÷sUwOéB©ŘŤ‹đáińŽ$Bżü(Ü»/ŢśSŔŃůdńëŽ3{Y¨¸¶ŰAňĄ ŮS'¦ęçX*=eB3;näŚ1 îr§ír™‚ĘG۶šăÍ®TóÄÍ <ĽĚžá—‰šyłsWűCáÜ=ŞÝĂIjĐyg!=°Š¤mŻ\2Łö}bD!bëŻ}ÂWśš˘ĺĐÓÝ=XŘj´:×…*hýřA81 #$ÄcŘWfš'çF]iJo·âY…˙d çmdĂß B~!=9- •ÔµţŢ yMMfŞżlŤp‹ŮŢuÁßá°ć•ą6ą•]«Ůřk˛¨6´%{둞: µ·Ĺłë₍ďŤű`çśş!TĚşp=ygxć(q–®ČZnĽ@ŠEľűö˝aśeŻc6ho†UčŹĹ[óuř"Đď¤}†aĚäť“ĐÉ>:óŽ)ß»^]Q¬#őy«‰Câ«9ĺÜĹÎŢŃĹ…˙GąDZjśłí&⥎Aŕ=䲽um©q>©jJF¨’!Ů<uő´«®™ [·cPFéwä=CăĐV‹ä–ďšĐ§ !9<Ç?Ѷ7K_ĐÎÇM+ž3ËžŻëŮĄ¶Ővvţó{şŕFßC´zÜź đ8<Č ćŹ;©hhöŢI$bÍŽ5ëF»‡]SFôxĺő©QÉł˝"v1ßs©žfźxI9Ă{hoŘw"~•Ż”6Ď"ů1ˇlâ>ÍŔZ »{Ę^Š©óf!— o:Kç\tÚJ8™X@ —úÎj Ü4«ş;07:MóŃ!©çń3Vď‚€dܱ MÝppËTýµ]÷?lťŹórÔÔV/b „Ö†68¨ş.'“[±$<בiﮂ¶ćŽż4 Í®§đú€1ÜŹń.†úôÂ~pç”d>?ŘŐľćw‰8­±PąLçbđ\8Q·Ě˛mďťf\ e+‰ő˝Í® ů?ĘěŞd)dś“ÉóŽŤĄµ„Ź…®W™�®´ŃIr$YB8v@Ř 06ŹŠf%¸.ý·Ę+1_ĘŁ€Ěî^Ѥŕ <•şîp[GépĎŚwĚ ‚ěŮ´EâÔq6ŰŞńË´×›ý_-/Ľ‰kç �qNúăKŽńLDu÷ďá’ c_DM!ĚâçwsV·¨ą -đUÓ÷ăŘźAŻŠ´Ľq®g@Ukwíěp~SE9˝tݲdu�ÍŹt˙}*Á&ďYeŹzÂîŰSŮńEóÝâ=|çÁ Ʀ ;ďĐ<_ŹoâLÜŤňÔ|’'Y÷űČá=‡2¶y˘nď˛Ě}VśúRětOćĽ ţ•A0gÎ:KÉ{‡ěµÂ]č­I‰}/ŕŔjóŇŹah•_t ®I$5‘uĚ&.µŇq¤2ÍÎ^żw…dMMOč1-)zŕuńĂX<Ű]ÖIâ ŽČ" Ý,/g® €ă|őŻ>éźu¦¶ËŔ9ĂV˙ŹűžÉŇ/ň5k€ě-!“[Äř&ţńŚ FÖ Ö{ëńq±ŘőwhŽĄ ćéyő3˛]—ćäéŰŰP\5NDůěŮžožcAČSéâVm>[ţâ}kE:Ž"üşÍTĆ ?Wź î'aď3QíjŠuŘx�gÇúnrj–čMQyµäçźĎťhřV´W†–&Ţ˙ŰEŮ)ó–Č«<ŞýLÓm˙ŵJEöíÄDâ|řdćCjąžßA§+ä׉Ôw,Ý rcĆýMÜ­«M“ĎפŤkFKgÓÇ üˇÇZcxé ěĘ)lQYŽ*RuątO’2˘Ľ^˝ňµW@ýäŢ‚˝ÄĐ`ţÜÂTťŻšÜ† fCpK:©Ţl)Ü9Řc––Mž¸¦ß®&)n,•ě‹Nń%ů»ß4Ťç{Ä?3ťĐ^@>čČJ„.ňE| 0ˇ×\Ť�ě§=e÷7üŘsÉZ$ 5Ţ«w? \."<$Ó)´ťßpäRwčĚß6Î;ně\+@°ĂiÍő”ë–RxĂŁ7Öî.V€~傦Bmk4ń@áé‚%?-f$1ż˛ÍFbńçšă•˛ßPďeCS r:ýJ(üqŔf‹µ9âç�ä¶L iáŘŐÁNS(†î)»%w–yűyđS(Ô$("­Ť¤-­ŁH2Á‹˛f>šËŮVďŕ$xb<A±—öáđî Kd|v^E“7”°ŻSöZ©[đ¶v×+ô{©î–°ŔyĂÄŐ“®ÍÎäYqXĹč†é|e´’vbÚ’źCˇ8YGŮÄ]‘O˘ĽĐ×ߤğ˝˘¸Ä {P„ĹBúč§V)&Šą Ž;U03µ}˛v;XäR…eď–[OIĚz†ŕČŽłŕŽß®Ú?†ľÖ2 č¸ä A<›4eôcM ­Ę}rţrg„^ ÜŢďtËŠÝ”á]ă“úÉ�€"!Ľ?$5’â !©A^ý©I·[ŁŘîvOy(™ßÖ»řĆO–�#„ÜüM@)ţFő}XöJ`7kŃć4eg´R÷Ć!o5"ĐćQ¸‚ĂŢĘ·Ďŕ—ö.řÝG(Ĺz§{âBh‚úGD¶řJ¤öOm`3olśkË6âß ^ófxvž€Ů×ÖúëÇřidŔ h ·A×űR0×)Ó\HXCH/UĂu};›8ĆńĂcăľ?ě˛t_/úäŻöňřĹ�[Ńô•‰ąö’Žö ŤNËYĆďxĄźÜĐ Vݸ̲]Ţ…y -ÍGČqAĚÉAĹ·źvxÚčLÖŃަš0~˘­/żż­Ź żë~9pľúlŁ�iˇ(¨as¦Ç" ‚ä\Ý_ Ц1]/JšźŞÓˇˇq÷@Ýl3lŐ ý„rămâńK [ńCĆ7íĎbI”ˇ^ďń‰N`ďwhg‚ü1Šg˙ˇ?Xýéý‘GŤ[Ľ¦¸5F-dLŻ%d^OGŰe÷Ď ‘ —ź‡Ž‰ą:DĹ˝«ß9ěŻäE/xźňüNŰ­5 p_·'PSî_cÔŘ ş�%9kŤ LGrôcŞI[ŠîLýH(e¶i—ŮOOĄ]âţ€l|żă:őęŞJ:đ«š_‹Ď~ŞŘ)9F§Î߉ÁŐ+ą”[ěj‰ü‹‘€%€. w6Ú5<çY'đ8^ĺABb8´¶ÚŁv`¬P§`5i§ťú#+Le€\6fO`÷™đŻ<ĚF"_™äÝőµţ®ßśSőjxYý&–v,¸¦MväŻ Ę+­[DZ‚üšKE ĚP›É¬ÂĂ™)˛ô_‹v:Ľq¨(<áa'L`R÷÷Í ~=CĆ;É`/Éúp{…@ĆZßńâëjÉrĹ8ŐÎîT—‰ąťŻl+ü`Ő‰^ֲۡŃĚŻm{sk¦`oC0ŃčĂČßîç<Íű#éě$VcCuAé%µSú÷“\,çJőű9 L”čź6 I4‰¦¶�´%ŇCwa4ËŤ ÷l3�b5¤ÔźŃ« T%ä8Ĺę!ř(©Í+‘#yűDźj†±1{`?1E^–?fé÷÷î->‹žsĆhĂ%uÎIíňm+'đNÝçdëź?żpőË—8}ÖÖ�ĘŕpÝbŤčî§‘™ß謏ŕĚŠPÄ«Ż(KL.Íg ĽkHĽa+Śxé‚3�ü1›+éOéçĚ…ar±7& ëÁvődVóůOÉËę6!ď3ŢB4˙ř,F|sÄ·ˇž¦ëżőw@*B`Đ_]ćq>ů/ èĂńť˝ř×Ô"ęÎÇNiK&”…=.˙hľŔ-JÓěćÎ;RvÓő¨„Ŕ;YDŻźđÂpâšôT>JmŃŘ„§3ażŐS ¬J+`Ő=*ăŚ1o05ÖYĽň1¨kŻ2eîÉŇÚĂ:ĹŽň5IrÝ Źĺ¬WÚ1Ë1(Ł X=~É`ä5ř;B,d—´5ŠB KZOů>üS.ŹTÝ Š é%*¸ŚĹşţíoOú4yJÔ8"B‘¶`G YSôâ#ź‰ U¨I• 1ú÷nůäîy)~Nj7Ź÷•ZÜgą\|˘˘EĐ <Z*îđ_™„î‚˙BŇ‹KÖĎ€‰zĂäWpťŔšŤ‡›şy2cʧŕZ×xŰgeźţ”ŽRŽß8§f†÷·Źţ¤@ţ–ÎăHš@NK„V¦şĆQ@ű¶Ń„Ľđ÷iÚ§gߤ!×÷<*ˇó"¤!—XżŮÎó¤pás2x…IFuisn›Ű˝t'YÚ7ŚŁ +`gý 0kN.ˇť„á|&VńWE˝žĘĆTTŔ8]U5ßH°›Ő¬ Řf~dŻI<Ů,“;¬ygÇgE0¸÷ŽĹ<¸0YöiMßHÖw(ÄTý•ŕÍŮ`î-|Ő@V‰8é˝°=%šU¸#joę3ćîKţÖhÉhÚŞ 3¤•/ä‹ë)Źi=d?xcǶŔcéăFx ?Ćr2•°$Hx=žă퍍Ńz;Ů9Bw8A”šbsÝĄÔf÷U^ťÎ�gß+c˘z [\“A«¬é6µĄ–®“Ů‘›?§ÓĆéŻ03® źoý©F[ `6!ǵ˙)ÇŐMT&čoţ˝Ť]6ˇËŐň‘DSdÎ:ÔÓěĹŕ°±Rş@Â6 ŕłiŢŚ)Öá ŇQ$^† —?ęPź1ľ.(Uł˘-Üž\m`1ó•ýKµÉ�–ĂíÍ*o¤‰ď-đ_c¸ó R—I,ŠĽ%żüÖÚąÚ›ő4V‘/•ĎÖďkIp›»PQˇZS“ìÉď¸-c·Üż7÷k9u#FŮHc&­.2/N&¦ş‚h¬Tîž §ă¶mÓ¬Ŕ <^ÂRá'M±.Ď2ú<Ě'ŢćČg2PM—]ś@ČăY z_\®vVŠČ5ĹuÂĘ÷ Xhŕ\Ô J4,ł^\ NcĄ(ľjHŚ–Ół¦:k¸ŤĚ`ďň„hÝÚßvű8A=…ÁmBŠ©/@dű}8çëI®«»¨Äçľ~ú†úĺź´§D°™îćŇ6•A4Čł-—° %¨ĐFy‡eä—´pµnłD*DŰyřŕ.ďš3*^˙©„żNâ2!Č©/gÍü:RCá#h,ň“6eç‡S㨻©]d¦^5ß`đĹŻw4V˛źÓEGKź¬:ʉ|ó$Kć€~ÜŮÉżI±Y§ćđuíż1µ±ÔÁƨµşĽ'\—«ňŽ6];ĺ˙FÎEÚęÓ{Ă‘”[ęMĹ**Lţš_0P'¸¸$üëâ„_ŐżxŚ»©Żé’dF˘4zÎÓAć3t\â `é\Jňy–Ě™p"ś^ĽÚr^8ýÓmFöH!ÜËŽ1;GŮ®Łµëx2dť őASČ—†xFöAÚÄ*,ߑÆ™× =»0ZT!¦Í!ä—|¶ľMú‚šŐj9vJÁˇM©üšŽĽ"űI7ły iŻé™\Ň.¦7ŚřU*°ĚuĐ/((BńmYY̰ÇÜČ%@ąąŔ/ôAČĚ\nľÍAĘ/×3<“`¶v2+‰„ľ˘zÇb<­nr¤ŮÜ'âÚę?#…OgMČÍ ;¨ŽEdĺÝÜ ËnqÓQ»–1±Kú{_ŰÄ„ők˛¬ř łˇë%Ǣ˙ErúXˇ[/Ľ8bvdô^őĹ*{/sŁţ:ô´0ÄśĘD ŇÇK–l’ĽŢ.~ŹÜ^©#,Ć  ×ŕ2ŁźW`¸HhŃ”¦Ę´Šö{úÎł/ĺ˙ĚŞ˝Ć©"e›(ů iłÝř•˝$.Č>«˙«0‚ŽínSîĹŤŔĽ“UúP �Rf=˛ÎŇòUEą!ż_’šďŰ?~ ĺŽ+ćΰÔ[Ł!W;ţ#„µ‘ú%‚tqšeŠ*ÚĹö»ĘŽLđ ű‹ňY9WTd Ę+żvsÓ~Vň!@śş’šÝ ;éWÉďlťŃÚUéf€ć6€ŇGľŤ´y ˛ü–Ą%rŽŠLb@çĺ-ŹŹ,ßÚy ÁĄÝ MxR 伝o^‹ÉüdçN¬źôf¤ĎóÄĺmŕ% Ű1÷[ňKÇ;‹.žúBşĹ,ý˘á�:ţŁţ^§ÁĚ&µ> ę/!†3|Ö«SŢ—yTßF?řË0E22˘>¤jQu?ěžx€©’űî~$[Ó«™8ôŻů{Üw!?I‰’a ×)!ö@ŕ°˝Č@¶ jTz\žů+ţUĽ•řĐęĂŞĆü߼–T˝Ś‘ŁşBŚ+.ú€Ý%Ű'±Zú oMO™ś8b(şÔßg.ŮŘE·ťácGZŇËęźB—ćľWx‚»5őÔ0¤'śç##�9]!@&ĚóŹÝ˘FýL†FŠĐ*ß•Âa¤6ď`­Ł 1\j¶Ś©űܦ#ÂôĘÁ«ĚŔ5~µZ ś¬;s–íąŘźŻo9­5 :!Ď$˝—ˇ`môŽJşŇ¨ŃC+śţH;q !LO‚ńŞá’bŤ5­O~ 6D„o§őÉňŃkCZĺą@ȶD›©t†xĹř Űőż4‡ď¬Ž<śŚU§9ë—žćgů˛ź•ë–zŰ"rUŻ BśŚ‡,ż|Ľ?;Éůńń0sę@aŠ€Ëł�ŤéYňó:‰’@5¦1ńq±0ĆYDăc:âPoBę.Ő­˛WZ˛ÍDL÷·ŢÜ ĹěZ­°ezž> \_Ľ7tm–îÓë9&˘DütÉđ—Ü•hI >kć…F€‡ţŇLöŃqWPBX`á#¦´«ÚćAßůţ#ÎÓXnł$u1ŤY ŔuĂ|ţE)čx†Hżő.®-ż\tż<7¶dĆ®(ěTüľ%ž(Í‚%Çę–¨«¸™Ç¨-’7ď±=Ş�aß’Ö±ü@†?Ŕ‘ČTˇ}ý‰öó·p ÷§ŕp(¶ŤRW‚»\źT@˛ ž˝îâ˛;)BhôR9ďţ÷ďß®ťă«´ßě?}_™‰lż &°Y–čę˝odŠ‘ô*ďđĘżdD«´âí€ŤĚ łűŞ]›őëha >ôyTMp´2‹¸e q¬±Ť8/ßźjČ ÚǫɿŔĎ kł Y® ľ m’čş].Ę}űß`ć4Ę1Sy;€ŕ,ÉŻt �r!Mő1Á:°/i)¸XŁŽźçĚa‘Xö–ř 4đRš(Ś”ŽŐŐxşäIfQ3sĺ/ŚŔ†Ńöž5ŇŐĂ',ö÷,˝Ç˛Şu[rwÝţ ÖĎn i á‰Tç†Ó9XĎs§łŠč \·Ř =şśôî1 "¸coäĹ/ęˇă……¶“ éžk?'-ÍHWŞk …ukČ1 0f›-ÝŹĐ”¬5˛gńéăŞřÖc» q;ZŔî5\ÂÓY5*÷[ŕŘx–ŔńŻß R»Y b˘úÄ_(%…×L™ëa h˛§5(·yÓn>ť‘›éDŠ-¤Á»/HňŇ{uäef/TCIÇ·R+FlÁ±řÄ» ÷UăÚ8ďúfBűa¦Ţ<š’</T¤őGćşlŤ>9 ˇŚUvnɆŚ`g Kł1–ŠNťDß·¬ ÇL§? :ÚYS3oÜ 2b~ôg ó(š/¨y'ć”´¸ç@I%ťJšZĐö3 lülŠY±=± ^!uaŻĽeAöč(†ź34ţܵ˙ĎSşaţ^ŔrŠŃrmgG`+n]8Ôīż‹ů<Aú%Wg{Ć6|AŔ6’t„Ř đRšGÝą.Uěî±SW0fľ‰pjCń ş]ljĹę¶ĂzóyxQR!äST´úȬxN˝łŰ>¨_Ă`ń řHpn]áĽ.2Děň{µ¨đDáÁí¶|’>– ÇÓö~­mWĚÚxIť#Żg—zžČ;‡*Ő~¶ďĹ ¦ëŢziť9§�6@ű<ű˛ÂÔĆşI&o /Ţ…tŻÔ,=?«v•s»ÜĄŇÎ @™R)e~3ŰŃëŤűcęhiI—·Ëť¸Ą9«ľ9Ü|F{˙C2V1ŤŤ°÷ŢSYęôše@µ 9_, í ń9%¶óŁůÂúĂbź…#ć2ä`O\úČf+e^ěϦŹę«_~úĆaŐgŰÖ;¸i † ď‘:§3mmŘÁÚţ„ ź{w©!dP-ĆćSęgŁ0şÂÁ[Ü{efOŠ –s@¶ôĎŃ_!”um˛hńM}ő!Źĺ‹ë+Ň&‚6YH C‹DµŐ{_ĹĹЧ0T7m`)C/4 Ő/a‡ •uâ©}XŰăcF ‰›Çš“SéşÄGÔĂ€bý�ťzđóąEísÚ‚XĄçs Ú˙Ů‘ž˙:°÷dţ%ÎN—fÉóá ôVÍó ç掶¤CXĎIýóę +{A:3ťÎ×\4ń&�0]-k•Č�M$8ŁÄúâţ…éă§Ťjë]at ¸˙±/ć‰h& Ě(ŢŚj MÎśűĘ+«ó˝�ăĎąˇÇÉ8,'ÂC˛Y0‚ůnů­eCÄ«®"ˇĹ«B¨ű1řęĺŚůN>R.pń1ü§)L­©Ó…WRW‡ŰZű°Ĺl˙ç„ŁŞ–ÝŚµé¤ &Ě‘ÔÜ+UáĚ&Ú«u´ýÝÖ~XŞĎ"cÁ"l@u¸Ń0ľůňČý×Ó'Ř ‚S˘XŠŔăZ¨\%téÔrŠ‹n¤Ą5Ěç*¨\QzáĆ=@%â|żřŃű.TÂO"µŘ¸dGúE"sXYŕ!ľäx­}M§ÖÎAdLąÖ\•«üó�Í˝lö·¦˛ńeQMYĽ’ăĆÓŃęěÎâĐň^§źşŠÚđ­)č›ňę)—*ńćö} q ç‡ 0‹ăe¨ÇpwvšÓ ćĄIÂŻĽŘW‡ô,2_FüZlŤŕ:Ô%,,ťÜ!ş9ľ…yÓ”)[Ź~CZD,ÚëM“ß|NŔů_¨ĂJ[‘1hëäÔnĄĹ-Ş^÷rsĚ«§ymLBÁ÷Py‡…<Z÷Ă6Ó¦CAĹ–%G”ꍕJtćľ'(©{ŮŁő ÷›¶úŘ2d±Ţ§-MÉ9ŢšëÇd˛VôxÁYů‡­­ ÷8 Ô˝.Ä-ë:ŇÔ‡hľ„¦ ľĺ+xE/Ř<ŠG n{VŇłYzVÝ>˙k#”ŃQ{óxĹôgü[ ŘH¬­aĽ ůݤw˘wGŐźů^;Ś<ó?ó¨<ˇÝ!ĆbNmÖđ<^Ë´™É ýPÖËťŇ6»y^‹ůˇĐ†¤ż•;Ń„@Š˘¤vf+]ű˛2{ćˇÜVFŞÚó¨e¦&™k1ŐVîj6Ľ_Ł™f˝×L2bJŻSĚŮmýť«3QIÜ]ą»ÜÖ�ć_źŁ‡đ8řź<u›x|~é.2„»”ŹoůóHęsM˘üť4Śx]Św7u…%Vą`‰ERyX¨őĆH˙…zďZ^ÎßSXĂźşďŁĎ›8[Žžw´ć.zPł+íĹď_G}ĽKŤű±.M·É´'(˘*˙…l‹1)číÁ®±Ř+B.çeÚupxW±đČ·m,´ŇŃü<ńľŘVôŚ 2•s ‰ŽěŤFŚŇ6úŔ+®#KWzGĆ?“wèPh<!츬bŢŰBŢ,jđp=zŇŢ _~&§~Éá—ďG2` !ű‡©|/i›íÉ”/R\JľßĹ'¬.qíW’ŻAmşą¶éJ9msұÂX~‡¶%AéŮĘžoWUfďč¨ßČÇ` 8G“ˇTűŢ|´¬­5ő\7vôUB»gĐrtŔ ±1WZcć�Vźž,~Ş˛#+0®÷^_Ş„Ĺ2QU.‚Ł‚/ß–;ŮWȬԝ¦@7 şÔ°”X;îŐňŽ×qĽĂÖµTa_Â'ČnPń®f˝&ň´ríőQÓLHf=¤Ŕ'ÚRő‡ŮŞŻäLó?;ť}dn9¶6`°é¦ŹëmŰőm piŮ’ď`™ăď@Âűď;¦­ŤŚnrÙč–~á $€_¨jÍ3Z őíÜŕŃŘ­Č8ŇüˇŽŁ×’ ‘°ber[ááM˝kuć}üžĆćö%L˛\‰!IHČY¤î,Ů'ąywŻČ'\ô:�?$ÉໆZSU ÍÉÜ@:ŇgŕŰeھƮţA%a-d˙qÓĆX?gçË»ăČąŠ ń� iBâ˛Ć=é¦Ĺ‹"Ą*˙cdőSb3ç´p¦Ř#ČçYýÂ/ă†ÄôZâ+<˘?s1Ň®Iďs5ÜÚe˙4Rx˛ŕÉŻ{ŁüRß‚FÔ¶OŽüý7!R$_Šô…×ôŁ gľť˝üĘ[ŞLOŮ7Ę0±}5OËĂ4Ę ę{Ć:PÁŽ$¸^­ S ’Ńó{‚ݤ=vĂUŐ‡pPůµě~')Ęŕ#—¬C;}4âŐş5 N?z(X°¸:Pčá4u ÁÇ $?KŘŢhCŚluI†Ő=·< 7ŢR”á॑‰‡”.đŞě7L­—í‰MłúÜuŻ-7s@Gqż˘ďŇ`4ŕ,ä|__S5‘@ę1áül]���_����APETAGEXĐ�� ���������� ��������APETAGEXĐ�� ����������€����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������beetbox-beets-01f1faf/test/rsrc/empty.dsf�����������������������������������������������������������0000664�0000000�0000000�00000010134�14723254774�0020532�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������DSD �������\��������������fmt 4���������������������+�������������������data ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������beetbox-beets-01f1faf/test/rsrc/empty.flac����������������������������������������������������������0000664�0000000�0000000�00000052602�14723254774�0020671�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������fLaC���"����÷ Ä@đ��¬D!îÄvoWe×l°÷� B��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������˙řÉ�•@��µÇ!̡'†hPćhRs%… $ˇaäĚ̔Ô9C9)™<ˇC™”ĚĚĂ“%8Y”ź)…% aś(r’‡49CL9C%RY’Ě<ž… dÓ ”)™™śÎd¦M 礧2…&yůgĐÉĺgĘaá94ť% Lł9ś(Xp¤¤ˇĂB…3™e% B‡ D™ˇ= ˇ@ł ) !ý Ě’s(NN’…2P¤ˇĺe&faʤĐĺ3$C'3™ g$ł 3”%8RS%™"Bś,’” IC„By— °aN”„Lä‘ ”Ę”ˇ”’yC“2i…9˙˙¤ˇa))™B‡ f,0©@¦„ˇIÉţ†Re ˇ(XSť …†Ng:OCž†sˇC™C<3Ě4ÉL(hĚ9ĺ”)“"))(Rp¦Jg3™™S39ňĘI’‡?)“)9C3'(e äžPĘI,)(S L”)'&p¦r†śˇĚáae ”2P)2HD%B†”†Je2yB„P‘‡‡(~y@¤ˇ4)“L<™2“¦K(y(”2“‡3ýPç)3™™”)<ĘaCRˇg)“)3ćS0Ą äź”ÉŇ2d@°ĺ$晡C…†s,ÎC“™ XzBś)™’a@ҦĚĘĘI伡I’‡%0ćRe'ĘPÉB’PĚĂI”'&RP”2yNe2 ‘ 9ILÂ$2™)"% LˇIš”ţXRe&P¦p(L¤ţ†r…Pć…$ň–,2’P”2’g JÉ43šˇCL)™Ěó”3' Đĺ$C„ÉMRS%330%3 pˇI”2“†IBr’a˙ůL’™2“ 3”É)'śˇĚ§= ’x|)C9'če$Δ dćsŇ$"I…9ô” 0§2̦g4) )(J¦g p¦fs)†ś¤ˇ…™¤LĚ“ÂPçĐ)’„@ĐäBr D…„ˇᡒ¤¤ô<'BP,(g(aaNĚź)ĘC930¤“ĂĚĚĚ)(RrPĺ0¦L¤ô9C””9śˇÂ!™HD fJ ç ÉB r“L‘$čg= 4Í ”9˙<ˇÉ™™Ę<Ě™L%”’JL9ü¤ó9™C™L硜ˇIB!(D9™ä§ L¤ŇS%2RRPСáI…†i4ĚĚĘ” fr’r„§9HD93ç@Í Ę3ĐÎPĚÉ™™™ĚĚĚ‘r™% 8dˇĎBPÉĘLó9)C9”3’S32†JdLˇĘ¤¤ˇLáˇĚ")Ě,™Ič””90áÉL¦O”(g e&Rt3” D99IˇIL4)(y‡(g?@§ L™I”ź)ś)ÎIB’‡))(r†PáC””9Ë sL2’O)%(O<ćJJJJůĚĚ,™I$C!L9B„ʦMçB…‡(e$”ˇ2Â…8y”“çĺź˙‘L¤™™%0ç”)'IáNL)3ĺ I™’r‡™ ÂśĘ)“L””ádĚ”š9ť'IL”””9II)3?ýś(g…2R†fL˙ô2“!(D ™C32Jaś™L,ĘJrfH„ȇ“<)™…&hfPáC…8r!9ĘJrPJ”ÉčffdˇIĺ9ˇB§Đfx˙řÉ’@�µÇh!)œ“4)2™™śˇI™™ÂśśˇĚćfHˇ™L3”3źC”ĚĘĺ&s9CśˇIB“ĘĘŕS‡2D2RRP¤Ęý đĘL¤Ňz:†e Ě™ç˙ˇÂ$Í B…Đ"LÄ2S„I„Iśĺ&|ĘLĺ&PˇĚśç(fdša¦M™ĘL¤Ę”(JLĚ)(S'2PÓ g!d–JfP¦ffLĺ$§ fL¤ĎBP¤¤¦fK3̡BP¦aIĚ”)2†hg4(hD Lˇśô' S% r\3„@°Ďĺ&PäĘ"žILĺ p§8D9™ĘLĘśÎffaIB’…% NPžaL”)(y(S“)0˛aIÂ’…' J ™ň…&P9@ćr„§ fL¦¦M… LáČN(RfP¤Ď(RP¤˙‘& Îd"rfs9B“™“ňśô(Jr™(Pť Ě” Jˇś D”)™”(s'3ś¤§‡3Đ™L)3ĚĘYśÎPćRe2JJ’Pĺ ć™4Ă”)8P¤ÎfJĺž’™""ÎS’’IIBp§33%8\9žff4<By˙ŇS2„ˇa ¤óĺ'(R¤ţ†Nr‡…&s̤ź2…&y9“3%hOĘO”ˇ<Đ)Ă)2™™™śˇI‘ ć ”9IC3LÉ”¤"LádÂ$Â$”) Re'ô)2g?C33'ÎS'C9ô JyL“3&’‡(y(RPĺ39Ni‡“L̡ś™BS%’śĎ™ćfd¦JdĐ礡IB’™)’…f’†…&b“4(s(RPóPçIBÂÎPˇ’!†™’™48XD3™"9…2hRf‡I@p§2D™Iň™2™:N’‡ püd¦P¤ÉI–Éś)9ÎhH@áO(D Éĺ&Rr’ˇIśÉIIBÂ…3 páIšJdÄ& ™ÂĚ”‘ ”Ě,’™(sô3IŇtšPš(S'333% LˇC™ˇB„Ą ˇś)(dˇBs!$°¤ YšÉL“„@Đ"0áĺ332S%(g3<ÎPćP¤’™)”4)(L¤˙C”2ˇžIÉB“4”‘Ă(sC9@¤Ąś™’‡(y4)3ICC”ť CCĐĘL§ rfH† ™IˇI”)'(”4)(S…9śˇÂ!“xf†sĐ”3™aB™2† L) JÉI–ź¦Éž“čdůB“™'̧4)8r…2Sś¤Č“É)™4Ç3ˇĘ9“)4(XP‰3ĘdĘNRr†RNfe&S'(Rd¤Ęd Rfe0ĺ źĘfffJ)™<ç<ĘIĎ9C’‡9Cśä@ĐĚÎK( ç2Xg%&S dđs… LáĘJs8Y%30§$Ľ‘2t)(D% aI””)(PŇg)‡’r†RLäÂÉ…™ĘLćfsĘ™$C aš™,úĚá8P8S&™)’’‡49C”3”)%’2e%D Ndó9)Br… I™)’’ś? rÉ””ɦÂ…2e'ˇ(”"§2xg !”Ě̤Îsś"Lĺ3 L™™Ę g†Nd¦H…Sčdˇ“C„C“% ”9¦BP¤ XP,ÂĘśÎH…&é……9™C'ň™'ϡ“@ą˙řÉ›@�µĆŔ!%'dé’…' áNfRLˇĚ")%2S JĚ,™“9šdˇd‘&@áI’!9śĚĚ39é)…2ri‡(hd8RP"J” …!’… ĺf!ś™BS333™ fś°°NpdĄĚ)˛‡2…!JI”''IC”32r™?)™“ˇ= Éśˇđ§(y)(XáB“<ĘI”93‡(g='ˇ’‡=…'% LˇLÎ|§2ĚćfaĐÉĘ”3“2†JdˇI…)3B‡†“&BĚ)ś(SJ……$ĘC¤(D…9Ęg‡ ˇI™I”’”2D(Jt3Ă”39é)= ˙Ičr… Ęg p¤Ď30L¤Ą ćRNPÉB’fe2e&PÎO”ÉC3””Ě̔ɡIĂášJs”Ă™ĚĘĘÉL)(P§…… JÉśĚĺe ä‘$Ęd”™CáNĚĚ”2~RP“3<ˇIáBĂ)3™™™™™™)(sĘa<”)“)))(sŇĐć‡<ˇˇ’!™“Ry„I%32RhRND2~D4=0¦NaILĘB$"LćRež“B™™ś)"3ô&S&RNds>e$ĘIC„I?(RaLĂɦÉĚ̡IžS…9Č“4Ě)Éš Nr!(RyB’xJDPĘ99”9ůe‡=0ĘLĺ…%™3™ĚĘ™ˇśˇÂ!(S&RćaJIáś(S'0§' 3ÉL”)̧ dó‘ žPćg0‰2PĐĘL°łBˇ’PÉ”šg>i'Bee I…8RP¤ČNd‘ śáNLćIL(Rg ćS% ”&JLICˇBPJhPäĚĘB”“ś¤ĎźĐĺ%%% ¤ĘJaĘB„ó–r$™He$ÉB!†™?"ÉĘ~s”3™LçĐ))ú(Iˇ Rg(g0‰3Í’ž†sˇČBĚ4“śáśü¦ffL¤śˇÉLšOIô9CĂ2|%PÎ ”Ă)%2PJ ”(pˇC9333š…0ÎhsCť śĺ Ę,ü.)=!ĺ ¦NRg” &rPÎIfg0Ó'aš…“3ćRfe B!3™)Pá@˛J8PÉ |ôĂ(PáN†PÎJB…&PáÎae%32’~dBag2’|¦s@‰0ů™’S33' ” ”&D'0˛M ”(P§9CśˇĚç3Âś)")’!2!ÉĐ(RPô9čdˇÂ!†g(Re'ü¦JB!HS)“)’L”)8XDe0ó ™”)2“ţS'”ź)2“”ÉůB“2“úfs9™C93% ™ÉĐÂĂ)2†rPˇL”?)2… ć'ĘLˇĚĺ'ˇĘsĘdňs†JIL”Ě)™2!†“3) RLňaILšaIɲR‡&sśćH… LĚĚ”ÉLĚ̡śĚĐ)(D39%'¤"‘ śź”2S%% 礡I”ÉL”3’™™?(S0Đ Rgź”Ě”2Pˇ)üˇÉ”„@Ś€D…„ĘM0”Ă33&“¤ˇĘ™'ĘJ”™ç)™’†ÉÂś(2e„ʡĎĘNP¤Ę)’g%„ĘJśˇ2!“™’†IÉČ„ĘY&’t&S$ł,¤é†…'% IC% ¦™”8“˙řÉś@˙˙µĆP!9BR‡™“ÎPđĺ”$ň…'3% ”šdp§&†NfaI”ĂĚág2’eB„ˇ’†O”)™ĚСĘ”ˇś"9’…&i)Ď"(fg?C'ĚĚ™9śü°@¤Ě̡ś™C8D2pˇÎfs9śĎ$Iś°ů”…2†s rP<“9))"xL¤ĚˇĚ.Iˇ‰RR†r„ô śôĚĘ’” JH|¤ůgÓ0¤¦e áLĘI4ź”Îç–ÉL,"8r™)’™ś)3IĄ ¦Â!™…“3„@°ĺ$ĘL"ÉB’źB„ŇIˇBR‡‰™“Bae2S’…2O …(M ”š™aNfe…% r“)%$¦)(ú¤)(d”™ó"y2†…'ˇ’†Lˇ“\žRe'”(pˇfS „ˇ¤ĚčfP)™(hd¤äC‡ˇĘfRdBe ”ÉňÂ’‡)4(g0d§)<Â!„I<3’…&yB“śĐ¤Ę“ÉčfPç%2RĚňĘáBÉ™”źĐÂĂý&…&tť0ňtÉNLˇĚ¦r‡RS aä:ϡ’… Ěś3C9C9$C…3$C”„™Ěó dÍ'L<”)2™śĺ$¦IBćfrD)ffg% !JCL9”3% LÄP¦NM0ňPˇIśÉfLˇĚç9C™™C9śÉĂäĚĚ”ĚĚ”) "™™’”š™¦g(sň’P¤,Ę™43źC'™Îg3™I”™L„Lź)™:ž, ™IĄś3C')’s$@¤ˇI)(S%2XD$Cś"B”9śˇśśĎ9C 9ž~YˇCS… Đ3§$ąś)“™™śÎPÎgĺ2J“)(r„ˇC“2e%ś2™†…%™")śˇĚĚÉB’g fg2’rS2áaśĺrD)3‘„B„PĘ™C”„Cś2““4ÉLĚĚćyĘÉOĐááC38R†rdˇ’…'8D9ĚćS!°"dčr’…&2e%PĘB‡“™2 P¤ÂžS…9ĘNPСə)Rf’…&Rr!C”(Rri…2s2RPˇĚÓRtšaäˇI(p RfP"9Ě‘ ”)(RO% 2D9™ĚĚĚ̡L™@ç)śË3”3“™á,(hRC (žJP”ˇ=ÉäÓ ĺS3śĺ će2hPç (dˇ’“"r!3ÎRg(L¤ô̦B”“˙)(ICý’’‡…&|ˇC“™ĘÂä/”™C“(s …|ĐÉü°¤äB| (Iň…&r†)™9™™)?”ĚÎ}“‡3”<<’…!BÉ”3Ây™”9”ÎdI'”™dˇ¤Ą Ę9BrP„BO’ F,ý ”ĂĚ9’Ę“2rS3%%%% IBĄfP,:R†B%…JI”3%‡3(g0ł3™")’™:JfP"\ˇIź2’fs9™‡(̙ʔ"IćD) ™>P¤(RrRĄ!BÂ’…$”(JPçˇIBĚĚĚ)9™™™™)(r“Ňçô'ü¦ađô%Ô̔32P)3Ďź(y…2pˇĂś)I<9”9™™™)™Âś(S3™™™)’S&D&D3„I”ź)9I”ČDÄÇ˙řɉ@�µĆč!339śĘađ§ dóÎfaĘ!s LĐÉB‡ N“L<”)8g?Ň…ÎP¦L¦JP”ˇ4(JBa&P¦Lä@Í °¦fM ç˙ˇÍ L¤ˇ”38Pć…&RS‡g=De É”„I”3…0¦NJJHD)9‡’Y@¤ˇ™ĺ2Pˇáäś˙)(L¤”“ĺ9ŇzM0ĺ Ě,Ϥ(X&xD™”4 J)9’™’„@ááe'I”” JÉĺ2|¤ˇ(p¦NP¤Â!Â’’…P¤ĘM Jfd¤äC'30§!a”Ă™”8D8D)(Dśˇ)™†R NĎ)>čr„ˇ’L™@ÎLˇÉćs0¦aIB‡“L¤ˇ™™‡’áBhD $đˇI”Ďśˇ™)™’ P¤ÂźË 9ÎRg33338S3'333$C'%'IŇP¤đä@ňdC338D4(Y3”% “2D(JJBP˛…&fffpł p¦e J“2Pä@ĺ%(OPĚĚ“ÉJ™)’™Ę̤”™IĘd‘…3 fg3™ĚʡśĚÉL””ÉLɦ“ĺ2t °”ČDÉ32S&’†„C™ň™9L(D% ™I9„C%'(rPÎe&S'ˇBRs:ś)’Xe&r’e!¦JaL™IL–g9C„C'2S dä¦yůL<…2hfp‰3IŇt(dĄ čÉĚ”'C &RM% s)2™(”<ˇÎ¤ź 4ÉL̡š<ÉĐĘLL¤¤¦g dĘ™“IB“†p¦N‡"8e Ě™B‡3˙čp‰(ćJB‰“™™’!Â!IśˇĚůLĚĺ!9ćrS%2S9’’†xs¤ô32|¦NPćRz9Ędé;&ɦ¤ˇLĚĎ’’ĚĘdčr†fL¤ĘfS3™ůNĚĚĚĚçˇÎD’RhP”ň™:Ă”93™Â™9™ĘNS$Đ“Be$ˇH_üó9CĚć’…&)“ňśé(Re0§$ D”)ĚĘÉ”332S<ň…dˇÉ™4źčäź)“ţ’…&RzJˇC… ‘R…PĚÉ”™Ę(SPÉI„”))RP¤Ą ä”ÉLćRIILÂ…!Oˇ= ý&”'S$ňS”™Ę™(S0¤Â‡ (s,ˇĘ2fr’†fsˇIš™Â!Îd¦M0ňPˇĚФ"!JIpĘNfs<ˇaš,"ʡ“ćRe‡(Nd¦fJd§ 3™C%% B‡ =%RP¦g„ˇäĚÉ”‘ ćOš?ô(JĂ„@°ĘM0ËHD  R š™ĎI‘ Đ)HR’Rf“C„I…)$ˇĎIˇN¤™I”” Naˇ4(9Bžˇ43™žyC@¤Í äd§ĐˇĚ˙)“ y'3”(s39™…2s Lˇ”™IB…!BÎYô2r‡3”3“3%9ź””2zÎI’d¦g˙B’S!IůL9śĚĘ̤”,>`A“)%8r!)ô2„Ę|ůBŔrddĄ$ĺ B– J™’™™ćyĘ y‡(dĐÉBLćK32P¤ç)”%čf‡)’™))(RLçĐÉBe&†y™śĚÂe&)2’’†‡))(sŇzaňî’˙řÉŽ@˙˙µĆČ!ĺ” 0Ą B’IBĘĐ" XAPˇ2y…™L””33 Re BśÉL)’”)8PáLĚĚÎdađ§2Pˇ’”% Có”3™Îd¤¤ˇÍ'I¤–P<ĂB†…32‡(PÎĺ&S3<9 0ćrS%0ˇIB™…… ź”Ě” NdĐ)d"LΔ)3Cś¤ f†r„ˇIIBÉśáá‘ 43…2JL¦OC”š2S”ĚĘdćfg8Y’!™…2Pg„ĐÉB“‡‡%gţ~˙”ČDĚ)Ě"pŢ Y…Č\’Ę… Đ)(“ĂÉ40Đp¤ˇIBś…™™Ă”2džáe ůˇ&„…ňi(Rp¤ˇaNH†fNaćg”9…(p…™ĚćyůˇBxPćg2…!9Ěó$C%(‘’‡&pˇI™Ęś4ˇ”ÉÉ32P°Js3%2i…2hs>RPĎ0¤ĘJžg(y2Â!<ôš2†pĄ$B‡2fJfP¤ĚĚ”,)"&fsCB“Ě‘0řP˛N„ˇIša)†Re3˙ˇ…Sž… Đô”šdˇĘ’‡y(RIr8S39ÎRr…ä̇É&PÉáË JfPç†áLš¤"ĂB’śĎ™”&DD8e&’†…% H)I”Ě㇠p¦g ‡PćfPÉĂ”9IL”ÉIB“)’…&zB„¦OÍ!))(Y2„Č„Ęfr‡(L‰2’’ Rg ¤Č„ó2“’M ”<ť2PˇĚ–P,2‡ fPÎsĐÎPÎe™JË…8RPˇĚ¤P§(faĚ,™CS… 3™™(s)…(JfRfK0§„„ł%)‡Â™2†s<ˇÉĚćJ9”ĚÉL“†pR¤ü¦N‡"(r‡‡)Y0dŇIfrS3<ĺ&y™…&S32†gNICźČˇBPĘaňg2†rPĎP)’”<”)”™LÜͅ&fPç rap’Čr‡)(hP”ĺ2PÉĺLĚĚ"L‘ @‚ś¦L¤Ë J™(™(g&e s%% ĘađÍ ĎC”ž‡(S33'% J,8RP))ž†sBf¦ĘL°¦OžP¤”)39CC”„B“9C„C™LžS8P‰ ç(g&PćRJa̤Í™”“™S2P°¤ł330dćO…8hR9C)&p°") PÎĚÎRe3ť2S2“"…&g 3Ęp§'(g0LĘ… C$ˇC…9C”2Pç)9śĚĚĚĚĘĘaĎ)™ĘHPáNdI„IC)2’’!ÉB’gLÉś3’śôÂ’…3' (J3CB‡&ffe$đĘB……”<3… Č)čd%% ”$dR) d¦HHYśĘaÎP¦aC33…8P°Ë f!Jdś“Ă9ůC8D9ÎdL"d”Τ",,"ÎP¤ D™IC’!BSś¤ˇ=™“% ”ĚÎr“ś¤"Lç3ĚüĐÉCš™C””ĚĚĚĚÂÂ’!(S…9™Ěĺ ”’IžP¤Ą đΤ%0”) RSúĐááC”2“(S$ˇH|3IB™’™"Í ćD’|¤že'ý0řhD!0‰ s™™)(Re39”ĂL§aBÂĘ’áˇNĚĚćp¤ĘN™6‡C˙řɇ@�µĆŘ! (e&J2„"d¤¤ˇL)Đ,Ę)XRR~RNdNaĎţ‡9Éś¤že$ˇIĚ”) 2RD JćRO3” dĚč™Cś˛‡™)“IĐÉóç”) PĚÎL¤’Rs 0ł JI„C%9O&™)48YCÉB‡'’„HP˛fH‡9Ió PŇO@ł4(rO”ś¤Â&O P“LůI”(RPô&0¦g$ĄJ…)%2hd RdˇI”$¤’!2 s‡y‡xffN†J†hNRP4ĺźÓ…933&Re!äËɤô9C”9é†hdˇ…’~S'9C8D%8DĂ™32RRD NLˇ”™¦J B$)ĘaN‡4ÉrD) ÉB†… Jś(s4ÉfáNsĚł)HPˇCB‡(sB‡'(Pĺ d§(Sd”áfP¤)HS3%3%%P¦frJd¤ˇIB’…“9C)3˙I)BJdĄ0Ą ”Ěĺ ĚůLžPćÉĚĚĘaNLÉţ…&S39""ÎffPůB™™2 s4'BJaIš"(dŇzJś”¤(fPćP°ˇaB“=” ̡̜Ę% NRO2‡Â™™?@§ 9”9é(PСI”<šd¦r…“)(RS3”)3”32rS&’™,ÉCIÓ ¤™ĘˇI”ćy”™I˙ůC™śĺ¦K‡ÉIL”ĚÎPСä,¤ y™:)“)S&e$ůȆJ|ˇa˙úaL“2z8s)&Pá̡ś)™’!’™)™™Be'"I432 FáL™Iç"̡śˇĘd§ $R"Lź)…ČS3%™”8\ˇIL¦™)…% 8Y(pRNIL%RbĎśÎd°ˇˇ˙”” NdˇC“8e2Pť!BÉś)9CÉů)<2ÎS'ô(g&sŇtšˇáśčRp§'ĘĚç(ry…’S3ź?(Y3”ÉJ“y’!žC% ‡<¦f™Ďţ’†„Bg%2Y’™†…%39’!“™)(důLĚź”ĚÎPć‡4)0p Yť áB!3'3śá¤ţN™™S<ť ”ÉĐšˇäĐĺ%2„ćJfp§ fd¦J@°ˇNtĚĘL¤”ČD™Â™śü¤Ě””)(S“(psúˇ”%3"ňs(dˇLÉđŇ„ĐÉL”ĚÂ!HS˙ţY”ĚÉBP,™ĎĐÍ0°ÉB™@ł'33 dČĘdĄ!BÂ’!(D%&IdĘd§0°ˇĘJf™ˇĎB|Lˇ3%… °¤Ë%9ÎR ĘĚ)™žS'ĘpĄ$çü¦fLLĐ,(prfO 3ś¤Ęa̤”É(Rs)8S“9†… OCž“"a”…48S2J"™4Ěĺ')“”2s3ĚĚ8S” (ĘfffLĘI”D8s%&…&S$Bd@¤ˇIL”É)(S9šĄ$ĺ&RLÎPÎd‰0˛ff|Ęa™ś"™3(™Éé4<„HeS2e%2S&’… LΓ¤ˇLÂÂ……39IBP, s'ň™%!ćaˇ’aLĚÂÂ!ÉĚ””Ěχ fg pˇIĚ)Éže&R~hD(Lˇś))“LĚ,Â!ˇšy2k»˙řÉ€@˙ţµĆh aśśĚÉBˇHPˇĘˇ™™śôž…9IĎ ćg332R†re ”ˇÉ"Hy™ĘOčg?ˇśň“(PĐ"LÎfaL™L2Ŕh&rÂ’„˙ţe!¤-”'"9L”ĂĚĚ)‡3ˇ™Â’„ĘfO”“ÉNäůó9ś§)™™…8S3%2tĂ)“Ě生)‡e&39C3%É9”3’Y‡)(hRP¦NaBˇI(s)śˇC”(|4ÉIˇ)˙čaf} 礗'IICšaň†hg™đł3P”(y9™”’…&JY„L2“9fSfe ”(J™ĎIčdčN†r™ś”ÉIB!'%™2†PˇÉÂ…$¦M’áBPçL”Ę…0¤ˇO'BP"B„C% )‰2…PˇˇC”ť0"J”3źB‡3C9”ˇ<ˇCś(y™Ę9<šaĎůćs3(g2‡9)ĂÂś)™śé= @¤će$ćD2P¦IChD% JJ2“ ™™(P”"% ”’™)™™Â!™™(RO&’… dňg9C% N9'Ęs‘&i’™)’™)(XS0°ˇL”"̦O˙ţ†eaL’‡%&He$¦ffJ%&r“ç(s9CĚÎPÎS$@J,’PŕA B™’†Oʦdä@¤ˇB“"BśĘL*"B™„C<…(S!|¤˙čPÎL̡ś”2S)“úç)Ă”Ây„BP „ˇB“ÉL‘„PňS™™"3ˇHPСä嚡‚™áLžI2„ˇaI@°ô2’„Bg ĚĚ(Ráae á™HYBS0Ň„)“IC@) dô™fRĚ”2R†fdđ˛ä’!„˛hP˛äĚÎe2†Jr“Ę™)% B dáfe%™ĂĐ™I„C…(sĘÂś"ś’äP”Â…‡3”9™ś”)2™)™’!Â$"&LÉůL“”’P)(PćffJf¤(D†R¤ž<ĂB†y…s„šdł8R„Ę“B‡2…¨‘Ě”¤žP¤ĘO9Ŕ°ˇÎ†sC”„C… Ę44,,"2“ˇI„ĘJfsĺ3 J2S39ÂÉ”9śÎg(Rs”,Ď(rs„C’^Sź”̡ aN!@°ç(O…)33™”8Pđť ç)0¤äˇI”<”ĚĂźčr’’…&hRrS&†fg(hr„ˇ™C)ł$C2“ ̦”&†xe!Ě)’ś"L˙ȆJPÎC”””(s4ž’‡"a¦JfM0”2P)ÂIO”ĂOC”)9…3$¤™“32S&!“Âś„I„C38Y”"(r’…“9ÎdL¤é‡’™"ĘC:B‡&dśÉá<“Ă‘…”2PÉC'Ę™1 ”ĚĚ)(Rs332S’!“ĂĚ)2……8S%3(s?)™Í Na¤é4(ry)’Ě<ÉLÎsś,™™™…&Đć…&S0¤ˇCI…“9™Â“y(RRX B!(D%(rĘ% Lˇ)Î&%Ě“ä‰Ŕ‰(R|¤ˇ0‰2e!Đ"BśĚĚĚšN†só8P§ p°JH„ˇaNÉ”ćPáśÎPó'ţ†p¦OĘJ(PáB“,Îe$üüĺ$ˇIžPˇI…3™”8D32rS$C0ęŕ˙řÉ­@�µĆŕ!™”’S NaLÉ”””2P¤ú愞ffJffRˇ“™)’” °“C9¦M% L¤ˇĘô8D4(y:”3D9)(… däˇIĚ”ç,"†PĂáčffd”™ĚćĚ̤Â!Â!ÉB“?ˇśĘL¤ś¤(D¤ňyI@¤"%2ä,ˇĎň“4r††S <§2”‡C9IBs>‡‡&PÍIÓ L™ĚĚÉC@JaNˇś<¦OĘI"%†NJJfd¦aśĘdô J„I2„Đ)Ă™ĚĚĚ̡IĚź?”Ě"ÉB„ÎI RPĐĎ 2D2S$C%2D&D'2†rs9…9?”Ěź)…8PĚÉžg9ţ‡(frP‰ áLĚšL‚HĄ&S2„ˇ”(i’™śĘ™C™ĺ L̤’&INJRBĘáĎC”)2“Đ̡ÉBR˛’…'0§ s“?ţe$.Oň™:%<°¤ˇ= ó@ˇ"gĘdůNt8D‡¤)Ę8r’Ą%% Ě)ś(S&SR B$2!“™”9‡3LÎd¦B$ÂĘfpˇˇB™)śˇˇ9“9žP§ 38P,ˇLĚśĂL”ĚÎPćg(s™Ě˙ĐÉC9B…% Jaʇ †™†RIL)“™…8S…3’DÉň™3”% JJáB$2„óúJfp§&r‡% B”’S‡ĺ9Ó%$@ĐJPÎ L”šLpˇĘJ™C2e39IB™)™’™4ĂÉLĘĘ2f} ”3(s4ž“čdˇ’„ĘO)™™śý ć… O0ňNg”)0P”Ď„Lś™™™™P¤Ď!32Ri(Rs% !Iś3ž„ĐÉC””)2†Ra&RRD 2S'C 3B’‡"ˇ9@s(p‚!N¤ˇL„@°§”ɔÙ̡śś¦g†rNIĺ0Í …'(Pĺ ó”%™")™śĺ&s„@°¤ţ†Re&…!C”ť$dˇĘś)™™ś¦ećH’aá‘&s™”9ůLśË ćffJpˇI™ˇś‘ žP˛e2S9@˛…33ť2Pĺ ˇBD9L))’™‡“B‡3B“):)<§†rs"„B™É)“B‡2S…9™™)RgC”)“ś"(D%&PĺĚ™gŇP¤šś"…&†J(p¤ˇ2$…Ă90¦(S I¦f!JND HCśĘaćdĐÉC”)“)=0¦Iće$ó”śJ”3(p RgúJÎaL™IBÂ…’R†fJś)&dĘ9’‡3śˇćr‡(g Ę™“L<”ĚÉL”‘ ”„BP‰'>IB‡’|ˇ¤ˇa˙С–IĘS!Lť‘„D2R†p¦aI”ĂɦI<”,(sȆJ}’…†Y2’JP2D)2™4)(S' ˇś=Ě"Í3™Ě‘“™Â D…„Ę“"(dˇśé‡ÉФ2Â…&e Iá9(p‰ “„CY@˛e9Idä¦J”3ź9I@#0˙‘LĚĚĚĚś¤ô 3čg(Rf“"aäćs”™C”P,94fNRP)3(O3ĘĘĘ”2R†JaäŇ„Ą% Ňzd¦Pĺ™ÉL–fs9)™™BdBNz"= ˇÂ!'(Pžt(XpJ@‚ż˙řÉ Ş@��µĆ g™Âś(Y%„BP‰% LĘ“(r’…2J™””9C”9Ičg(RP¤áI@ł?ä@ćNg)'ś)’hd¤ţ†gM!) RP¤äBe'L‘É4𙎙@¤ˇBzˇL9šaLśĚˇ’‡†…2†pé%33„BP°ˇI™B’”)™4ž“¦ˇC™¤ˇaO9C… Nd§™™™”<>dćffJ‡ô3’™™äˇLÉNaĐ™9††M!šäHICɦH†Jd¦fg$B“:ň™“ćRg)2“9By)CÉ)’YĐ D™™IÓ†x !NC…$C9śĎ= IčJ%(r‡(e&P¦Ng9I”ÉÎs–9™śˇC3dˇĚú2’…%’…… dć™IIINáfK33339B’‡2XrÂ…2rd@ˇˇĂ””ÉL”š4(S2s s3339’!śĎ(Re ”9ILĚĚ)3ü§:9ť =ÉĘśź>e$ó)3”ś˛„ˇC38P°§ĺ8XD $ĘNPС̤ˇC9ž… É”źň™?СĚú"s&Pĺ ÎáNÉ”) ’t3…”8D2RfS%32D2S9Ę™I)’„ĘB!C)3’Pće$ˇIOB„ćL¦r’!ÉC“9™y4Îg30¦L¤ˇÎy… ”)śÎg3) hĚ4ĂĚ”"C"I”“)&g3”)(RP9™Îá¦HĚô3Ă”42PśĘaĘ&JIĘś,"ĚáC…C–çůʆ’fJĘaIś¤Ędź2’IÉý ffa†LćH†p‡<ĘI"9B™ś"„Be ĺ IB’…$C'(dĄB!·(sB‡“"(Pćň“9Ěó™ĚÎPćPäÎffM0ňS$B“ĎIé= ™C’‡3”’„@ĺ 8yIš33&@Éažç)3”™B“%2t3”9ňe%! D9Ęg‡' J32†Jd¦Jps')'BdC'2e&$Î)’™D8YůĘIň™I<”Ě”ČDS2”ÂY„BPˇäô…9,"…2e ¦ˇBS…%3(JfdP”"(g&†L¦Idˇf'Bú%?čfP”“2†…g(g&d¤ˇĘÉäŮ…9ś¤ô dĚĘIˇ†” RaCś§=30dˇLÂÂ’“L>JRˇáĘĺB”ł3”)śÂ$Â!ś(fRe339…2e$@¤¤ÂÂ…'% J™ˇ™Â!HR‡:aNe LüˇI…3 ˇš” :4 P™@ćS8SśPćpňP¤ĘJśÉI2RP¤ćfxĐЧ'(PÍ ó”8D’†aI8P¤ĘJ˙B~faÍ !BS”Ěĺ% ')ŔŚ’YĘpˇ¦S0¤Ě¤Ęfe áůLÎhS9™”&D…)…!Jd˙)’‡‡@ł))“L)ÉLÎt(rfJ%śĚΡ’…%% L¤ˇI”ÉÉáNPáćr“”9™RaáC9”Ěť áI”)†Xf“‘ ś¦fs˙ĐňP)ˇCś(J|ů”8RD%(y<¦dç30ŇP˛adšaL%Ă<źC'ĺ%Â…†C%))™)"™ÎPćKDYCD8P§B@­ý˙řy CÔ@��µÇ(©i*”´ĄĄ-*ZUd«(´Ą-IKDĘJYeDÂŇĘRË*YJ”¨µ˘©KQT˘ŇÉ•)ZXš,µ%,©(ą)QjJZ”©IjRL¨šRŇŞT˛Ą)K(­%rWJV*X©bĘ‹R–T˘ä˘ĺ)R‹JVT¬´Ą©J–RŇKQj‹R•µ*KRZÉU%©-EI‹E­*¬©U’T¤š-,©JŞ\˛UbĄ‹KJZ-eKZJÔ––¤©¤‰ŞER‹‹I”–´L´ZĘRĄ©–Tµ,–RRÔ’Ň•-*˛Ň–•YJ‰”Ą%“%JK%T©eIeJRĄ%–”\´˛©,´˛ÔĄ%©.-,¸˛Ô•”´«J\YZ¤ŃeJެĄJŞURŐ*,¤˛•,´¨µ%I2’ÔĄ)--IjR’Ô–YQKR#)-U•*©2•Ee¤ÉeĄ+RZ¨µIJ˘eJK*Y*˛ŇU%eJŞŞŞ¬¨š,–+,©)J‹TZĄ)e%”¬¨šYRËRT¤´«,¬µ*&I‰”¨ĄÉZ˘eeKRU*É+)JZJ˘Ę–TŞŃk*R´˛µ)R˘ĺ*ZX¨šQ5"ęT˛Ę”¤ÉdŃKJYQ4ĄR©eJT˛¤ĄĄ-RZŇU(¸­JRL¤©EKK*’­,©ieRZ’ÔĄ)R•-%R•*Yqbhµ”´•IZ’Ő)RĄ–”Ą©)2’–)&II’ɢʕ&QYKR”©U•,ĄJ«)j*Yh˛ĄV•¨µ%IeDĘRRĄJŞUTµ)QiUJ¬’Ô”Ą¤“IjJL¤µĄĄĄ•+)Q2”¨Ş˘É•Y*Ąe)iEÉe˘©JRĄ)J‰•**R–’ŞUU–\¤©eIJQieĘRŇR”˘Ô\YrTL©JRÔTĄ”¨´˛˛ŇĄ(™(µ\Z”ĄKR©+K-%K%”–”™iIeIIeJʢ•)j•V˘–”µJ-J˛U”©UK.)T˛ĄUU”–T\˛´µEÄĹĄR•,ŞJҬĄJ´«J´¬´±kE,Ą%-)UK-*IU)U*LZYUKJ”©R•JTĄ)&J–*TT«QK)--QrĘ–R’–RĄ,¸ĄÉU,©JZJ©eĹ‹R¤\˘âbŃ2˘eE©EĄ*˛’Ę•ZYRҬĄ”¬«,˛T”Ą)R‹’Z”R¨ĄRZZZ”©e))UeK*Š.J-JK&’”Ą¬Z˘ĄĄ*T\L©KR‹”Z˘eEe%KJ––-QKIU+KTŞË*RËK,©JZ’Z’«)*TĄJZKRVJLZQ4YyUĄVYIjRŃe©)j,´ĄJR–’–”\”Ą)IjKR”Ą*RĄ)JR”¤µ(µEdÉdÉT˘Ő%ZJ˘–’Ą–”µ%©--Dµ%©e*Qr•”¬ĄK)JŠ–”Ą”––\&T’«*-IR”ĄJU*YhĄ¨€q¦������������������������������������������������������������������������������������������������������������������������������beetbox-beets-01f1faf/test/rsrc/empty.m4a�����������������������������������������������������������0000664�0000000�0000000�00000013346�14723254774�0020447�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000��������������������������������������������������������������������������������������������������������������������������������������������������������������������������� ftypM4A ����M4A mp42isom������ Šmoov���lmvhd����ÄLé_ÄLď���¬D��¸���������������������������������������������@��������������������������������trak���\tkhd���ÄLé_ÄLď����������¸����������������������������������������������@�������������mdia��� mdhd����ÄLé_ÄLď���¬D��¸�UÄ�����"hdlr��������soun����������������Óminf���smhd�����������$dinf���dref���������� url �����—stbl���gstsd����������Wmp4a���������������������¬D�����3esds����€€€"���€€€@����x��ú�€€€€€€���stts����������.������(stsc����������������������������Ěstsz�����������.���������2���2������"���"���'���-���+���1���/���+���&���0���'���'���&���%���)���(���-���,���*���)���.���)���(���*���%���)���-���4���&���6���.���*���,���$���,���3���/���'���&��� ������stco���������Ć��é���� •udta�� Ťmeta�������"hdlr��������mdirappl���������°���ilst�� Wfree��free��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������(mdat�Đ�ě\«11;Ä<Ŕ�ř¤4„ `Ifŕe—^ŕďŹP0#Ü·›ťÁbÄ»VOŘŮ(*ľJ€q8�ň„q�©-@>Ťč[-3ţ!‹rtgĽ<bžl¶‹ĺÔ‘ô]đNLŹÉ “€�řÂPD€TĹm8|P¶Hü>Ľ(Îą¶q]bĄ¸�úÄPEŮ€€ ްDăŮ‘>JUl3ËA$Ö)i)Ŕ�úÄA#€+:3ď`Xa¶™ÉůQ;«Ŕ±čŕ�ö˘" „Î Ž×� óf{q wZ“Ä 3zżf#)NŮq'�ř˘" „kËr˝ŮÖŁ gʧý'”ę )y*¦Úö»,°Îµxˇ�ú˘" „r †BXVFěě7nhϦNž|z%ôe0�Ue®«Ś ś�ö‚B„_`9ĺo˘đJ­Dz0Ż)ňřáŰ1F8‘ú·ĺÉ>7t·#‹Uŕ�ř¤4#ŚXŰőŢ™ľáŕŇ`¸Ž‚Ţ×xT†aG~fäHv<ý¶ÁŔ�úÄ`?� &×63Đ”l:äGŕë’ 莵š„ÇÓëĺN‚ŕ�ú˘R#A�ש9Ď<łÔiÇ%ÖřꦗµŠÍÎę®yfžÎ�ô‚T„ `®!I}uhnV$?‹+äˇ(Z«„ÁˇÜtaĄVi±+±l‰8�úÄ3#PŤ�0Ľń•T`’3ąčžÓ#?}ĄŐőÚ»ěÔ‹€�ú¤Q#Hl`µÁ˝ĄËK§)Q)E‚ńő>O˛ÂôŻSÔ¦�úĆ"#P ŰdpËźżŰ2é­~sŰČÓďŤ'Pîě=Í&ü!�úÄPG�<Ž0NÝÍEř™_ő'1…:ő‹ĺ\�ř˘ ,l qĽÔş<¬>čđÍ&ďĂž¦J3}•]dQ€€8�ú˘`F  8I+–¶:aá–;0ĄÇ>m>;ßMľíŰŹź× Ą/g�ř¤p>€�ŢbSci›”¤L z:~HŤ5M3©'°A+č&„f ‡Ö(�p�ö‚0!dŔ˘’Ż:%˘C°9Błî+]ţ3Z†ąčcJr†Â¶Ź�ö¤`E€a,]µ)\BiwČ,yç@ĂşD¸ůťü'ħĄ¨Ü�ř˘ ĐŔ€0qóŕ=bžmBĹAwůH°×! šDSŞ 8�ö‚5 !`Â4†§ĺ^ńíŞ^:$9µĎ…gs`M%…ĘZZtŞ^UŁY(o€�ú˘"¤09ś’#NŃ·#̬zÚW›;t A-1dyß”3¤^ ŕ�ú˘" "ě�FâwěĽú„,µ¸rŠţ¬js%”źŇísŮfžep=Ľ�ř¤`G €@ŔăŇ5?ŕĽ`•ŘÉÍM˛ČöĆýYB^×;C€�ř˘`BČ ĆăiRř—‡iéŽ60 9üľ<zäŽ_ężĺŔ�ö¤`E€%MĚŰFZÇ…‹÷x&)íS%–Ć5r꽡lÇq<�ř˘(E,?€"<0Ąŕ~WŢ€/‹Ďµţîĺ˙€y2tvĹ^O)! Ŕ8�ř¤p„p °ŕéËtCf€Ş<ŽÜYnÍPaüRísµٶ†1śžiĆ, ¸�ř˘`Gążzz>ĺÓvë…(Ü˙˙GYJ´…=˘oçlÇW�ř¤3 BĐ3ÜQhLÖĆ$/ę‡b<÷~łµ8ëűŰΚSŤ­n*jőXeĹ×7’#ŕ�ô‚"(„@Ř Ad¨*Â`óŮ'á¬Á®co·>Vűça &µ% �ř¤`F‚ ˘±äßxă}¸<ëĘüfP[2ÚqXä.Š'Iµp�ú˘" „¤*ÓŘšÖ'ęŠÇÉR™z[oÝÓ¬ía‚„ľ”XÇS2p'Ŕ�řĆ2! éZđŢx˛µľůěú$©żVn´%j(óVŔ�ö¤`F‚€öŽťF†@›c}}đCÂ2>Ű<ĆçO5Ł!ąQŢ/grDŹđ�ú¤aBŕô‡2‰´S(^®ZŹdđ{¦P¤ź~‰Ůۦ‘6 �Sşś�ö˘ „,`Ľ-1wNŢ�AŔÍ”XPKhˇwKŰ#0R¬Y±Xť-Ŕ�ř˘`E "ĹImq¤>w˘Č1s5{!ÁXş[Ż/ćŰ?ÓGŹĄx�ř˘2"%€Xä  aÔž®ŽćĎű©ĆĎçż÷şrŻLaŔě\ŞRi"?pě\¬ „ô@Ŕ\������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������beetbox-beets-01f1faf/test/rsrc/empty.mp3�����������������������������������������������������������0000664�0000000�0000000�00000020567�14723254774�0020470�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������˙űPÄ�����������������Info������(��!w�  &&,,,33999@@FFFLLSSSYY```fflllssyyy€€†††ŚŚ“““™™   ¦¦¬¬¬łłąąąŔŔĆĆĆĚĚÓÓÓŮŮŕŕŕććěěěóóůůů˙˙���9LAME3.97 Ą����-ţ��@$|B��@��!w_»ő^�������������������������������˙űPÄ�1Şü@TŘ5"L�‹‰€TÇ˙˙˙˙˙BhNs‡y„}@�3žÇ;çBú2żúźď!?úţ}Cˇ„'B‰�¨ŔbĘ€,=°�˛ 7›âýcE`M.¦2 :şJwť f}w§[?˙˙˙˙®Ť‘ŇŤW:®Ş…F Ś�ř·BŇ5ĄĘ纡Ö^ł­*+R:kB·ßżŰ®ź˙ß˙ĺ™üľ[&8—Ub0'QX`€hˇe˙˙ź˙Ů2yŐYä˙űRÄ!)± @} 86`€łˇµŕv"6‡¬F” łäÄÓ”šŁźĂ=ŘŠ˙Ă˙íçŢvó?VaVÓŻSä¤ËŚIůóläe÷M[#@~¸¨H'ŠÝzŁU0¤f˙˙˙úZc{˛™Î¤d îî‰tÖÇi1ާ+Ó>b—WnŻ›ľ·˙˙˙ü˙újÝkv}I*shěCÚ #9§™„�Ä! �×u»kŃ{µjk¦¨™É+Q@·ăÚ·]šŮ+¶Žşm˙öéF˙żnďâçĂWőČóŻ˙űRÄ;­µ @×Ŕ÷¶a N#ś4ěśHšW®JU?¦^gÍç=żĄv„y*g^M)\ŚčŃZż˝W$4>ÚdLN[Íűĺú>ĄS3őę*Ööß G_ĘxV#łB)ë´eT¶H Ś–šN@Ńk%©§GI,™eU]¦zQ.ű«,¶-Ż¬ŞŁ"Ůőó·˙ő¤~ů}ßJWňť}ŢÖTE¤>é ČMŠB#–¤D™?Č˙ď–\dáO=!›źbtph˝'r†PŚ·¨X.vlČK˙űRÄW ­µ =¶!'ˇ˝…Ö.™|ź˙÷şůănµXNkíÝbß aWIvȇ˘'HT'@ifłł"rŮ•µz:ö˛íŁ<ęŕŠZZVlĄVC¦žĚ´dOŻ÷G+>F{)M¨¦łk-«ˇ‘§!M.hńqő•Ň'ĆfŚz0@ú˙˙úďťşYďmV¶ígÔÖwµT†‘ x…!G!'GtŐ ĚgůÖŹ2éK.ŞüÖµ«mŢ“şŠkśR1U¦cë’ë&ČĘ ÄhÓF…˙űRÄn‰Y± = ¶ Ä'ˇ’�#¤´UdVißrŚ —N^’1·|ÓÜQęĺZ=ŢÎëO7Z¶ĆŇŤ}ůăűł«×ĆwáńCĽ[fB˘Ż> ’†R*Š(Ł ?ÎŚŽ˙k;L¨Źf*nSˇSyĚsfiÝT…*2ŮŠ&®ă;+Č•ď˙¦Ťµ żî‚YSőcY4FÚ™s­Î(M 4q°Č¬4EXuŕ(ĐfJ8 vhă dznE"p zł3¬Ó˘z˙ţr|í…˙űRÄ„ Áµ@ 7Á#¶`Č' ÷oň°×«¬Übß—w.Lű7eŠ’@K”Ą)um.W§[MF˘‹vR6"î‘mÚvbÖf±QYŞGuJdłýľş~ü2[q޵öźŹšŠÜfĘBýIŚ“Ů+q Ń áY�¤űľčŕl;4·pĚ2!äęŁfŚKËCŽ…I¤«»Y%>®#ŢľŰřţ?ŢŻ‰Źy—ł,¸bˇAY‡ž Ť,‘+Ť…” %*Ś?˙˙˙Ë˙˙˙˙éíçX‘©˙űRÄ– ł =�ý¶aH�› Ň+ڧş…-zÎv»c±¸F”ř1ŢV”Ôß?Břü'‘ßČ‹űßţoë$–R[ ĹdtŹŻ# 4± KY$íq:4s˛#M’ŔĘŔŐ= „gědLt2•f;^«écµ]Lô[;ŁŐS–Őß˙úßD÷ľ˝ú~"‚i]1N^ w/jÉO)Ş MFW+MČť6_+iĽoĚŠ¬ó%Čěéô™Á® )Ë $'¬ž\W'5ł’źź6?'˙ţpüĎ&Ý™Ţăq˙űRİÉŻ =9¶ Č�ŁXôˇŮě”<ö =}aÔwĄ“ꫜL»©ä�¤NýÝgfˇV X‚Š÷uQNkŘJ$&¨gČ©s©ĐŞčČ„\ôôµ?M?Ý˙’ŰXßéCe+0Ylö¦Ńi4,b`ř=@DW˙˙˙-oU¦—IT;fĘ·j‘ ÇŐ’:ětŃ… ąčs5KÝ[$Ú?Ó#D §Źřţ?ţxç“ëĚô¬-5ń d_e°ŮSä9Ç$, ˙űRÄÄJÍł Ť= 6!'ˇŘaţ˙?éö”`ÂI‰‚”]ĎKi»eóf;éi#R ¶/;:FSś}LÄ×s ÓÔĎW¶óŁ–=h¸c ¤4\jŚ" Eî A 8. `&ˇ r đäŕ‡ĺŹĄ?ôő˙ńň‡¬ż-ŽBĽ}ĺřĺVAT÷QŢ>™Z­v_˙WS›QW3µPÍÇ ›;ŮóŮN~U~í˙ëĺxJ§nŃ,ą!h,č–Ôš ŇdjŤ-i˛ żDH\”˙űRÄŐ ˇŻ ť5ᡌËA€ J ŠĆ@�ß˙˙˙EV"ُĚkĚeR‘÷w)ЬÇtzŻ.ćE!ęK©nčĆvząW[şűţúµu—ö¶ëĆ7”…NťJ(ô"5±›Öó@ÁaL=#ŽĹ @˙üżü R‡˙˙˙䉕ŕt˝wiŇ«UGQlwE˛‰ě¬#GđPŕ‘°şâNÜ#›±Î_ůSĚžž‘ś¬?Wý˘Ź“c‚ĂNBĄ%©ëÎn_XčaĺŃ0˛Áej±˙űRÄč 5µ`‡¶ŕ!¸I¶Çď˙˙˙˙iňĚ—31[šĎ-ľy?ď ś””󤊮nK6‘â–ŞÔŐ{±†yŐN~;sůU/µľöĽ˝ÍŻŠ¸»kˇśĐMl0>Př`ůŃP 8XLĆT,aLq€˙˙˙ţĄY˙˙u­>yń˛ďr‡•vá†Ďů¸JK×u|â•1ÚEf®´0’ rl즽ţŐĄéö6\on-dfY”š·®ßi ŐěŃ*˘'m9Ĺ!D##ŕ98ů˙űRÄč eł� -ÉA¶ˇ &ř˙˙˙Ż˙˙˙ŢüŹ"1µ“Y2 ĘiR+E˝‹'Ç  N�ŕÍOJä(aŘ™dÚ%‡y©µţű3;%fr®g,«źXPąüKÔ1¬´ĽŐîS&!§“I…ó—–—ްĐ˙˙˙üç˙˙˙b]Í�aI¤ÝŃÂŕšW„=Ë»¨K=\i“ĹńlÁIus?W;źńÉeűĺß8g—UYńCcÂIęË®)M–VX`‰L3b3EŘ„$TV˙űRÄěLeµ�@ŤžÁw¶ �¦¨Ś<Â�/˙˙˙˙˙˙ţwËMr:jdZ‘—uĘ@śÍĺW‚ÉÉWŠĂ‰G’=A ‚Á.ŤyXžÎ<˛,·ÎçîWQ`®f[:ş+gdr´’—2vŐ02+#`PH"(ř•Đ˙îM©4D6±Ď‹2•ŐJůݲn¬0^ŚgZ"‘ClV¤±ÔŞŇîEy~źóﱾ˛ü ‰ú…Ča5TŠ…W¦djj¨a#ÉÉTDL@Đl”\ҶĀ˙˙˙ëđ˙˙űRÄęKqł@Ť= ‚¶ łŮó‘vľY™©¶q‰'ţj»%#»+Ńž„l�2Ĺ$hś^´ Ń‘PIÄĺ/é^S4ŕ?Ě›Ë3ѱ1WO\+uîŚý”† —¤:!H¸€N /Š„sµ?@�ů>˙˙˙˙˙˙ź;Ô)*_ŞąjdJŚĺ$Ç™Ya´ LŚę Ę „Ď8Ů«cľďąç}» Q©űÝđm™ˇQ†çXËďTQŮŘ7(ĎB˘ŁÄäm2• ˙ůK˙˙˙űRÄé ™±@Ť= q6 H¦ůú˙˙˙çlAŻű÷l|őżTęmš(ţ™ýę-r đm±Ôe¶ďȧ9Y™ĺ¦â›ţţcwßo¬÷OĎ̆awV­“¨)“dK‹ž°ŕđĆĺ“&Î<D„4-x4&š—Qâ�>x˙˙˙ů”ޞ˙˙ëáÚľË ŤśŹú]ą˛ńľę­\)„›q„]»LBĺÂU—RĐó†eNŢ~ţ~W™ĎßžľR»±;'––ZĆ×Ŕ\µx©,ť·ŮKÂßM·Ž˙˙űRÄë }µ@ ;A”6ŕłŘ˙˙ţ¦/—˙˙Ç9sIBéˇ EËu;#XŠĘV˘‚…ąÎ—D8TuGtF[Š‘.Ôďň+»0–őÍęl˛ŃÇQęAŽj$¦?~ę•(@MNV'ž -Ś–©�6p˙˙˙ďúú˙˙‘ś™P\Т/?ôéB%ŻZÖ*Jt˘a.fćýÁšÖÔ’p‰µ-s˙.ß˙ý9żôżî?K=Ť¶ÓK^gϬÁ•Ö3‹ :--€Üý:ö„› Ű`@˙űRÄě %µ` 7Á’6 3ŘŃ� Ë×˙úµ6Úň˛ä4Ź"Ş(‡)ťčŠ˘ęCŐ°ą$3ÝXî-,t9XĆ»4çeSU˙ú©Őf¨š?Y“jM¸ń đß%\ȸm ‰`dşND ČVĹꀽW˙˙őÝď5’©î›k3·SH ~śëÍX);“ZÔźQ‘{ů­- JIxeŢçů·Wýüůó+Ö{ż°ňőLäo&ŹłI1ŠźÔÄI˘ČěD`5ĂŔh6 •A1@•˛˙űRÄëK™µŔŤť…6 HłŘĘ$DëŞ~Ç˙˙‹<÷cb4Ĺs\\ĎB·ĐérZĺ^ď™n†ĺMÄÚSĽ™RgĚşěĎÎßz˙ü÷×z¬uWľŘÂĄµJB‰VsŠÁjá`jĄ ˇ‰ČFŠś˝��.ábD °ňŕ0»/˙ő)ť_ Í ĽhŢecöÔ»éäcE3łÔŃdŮďhĂ(·h÷ÚÎňî>Ż˙ů‰^?˙˙żűţ»íüuEŚeĆ ©ÔQ …””Â:   Đá˙űRÄę yµŔŤžÁj6`Ś&ů�˙˙ţ_š@;vż˙©­D üC0đu"ť,gĽC„¦ #6†ţś|Ż“Ĺ5„ůnÍŇ7ŹąĎÎźŢgržnßůĽ´4ö7őŰ]ĺ¬+Ţ‚ćaCÔČO]:HOX×0�>heŐ,Ź­úěÝŐ(˝Tšn‰5Şâ4u˝é–ýźľZ<öŐëłűă°gZ٤ăńsźą.îMózffvźóťütďbíł[Ő6TzäjY:˙ Dâ*qŐ8†˙űRÄí =µ@ 5Ak6a(�˘at$;^‚Z,–HÚş­ ť˙˙˙÷H(©¸Dzś†3EĐ®e§3¦;&QÂä9ou%Ĺn C”‚d=+_˙˙ţÝčT»X™%8á< bHĂG‹ ‚01C‚ ��Q�˙˙ţť53{˙ú˘°ÚsÉ–Ą~ß|…™AŃ4͏˱Sá:Ě#rm%di?2łČďô˝´üżçc_űt×™¶Ne [|óLL,ť%é8&Y7™lő:1ąŁ±ě˙űRÄě‚ ±µ @A}¶ ¨ł ‚?˙˙ţͨVÝÁż˙řźln4 Ť‘ň8őTĹű,EYŻ «ÉN57C“5TlÉ‚n{ Ó€¬Ą=gu˙źéČęYňĽeP–•‚~ŮU (i–q &®pH@)H„©ŠpB�˙˙˙ý×%—7˙µü)ýM>©-;c®k9r/©Ä…ä‡ áb]C‚†AÉëŠ$0äLYpŰő˙˙ś§oŞ˝dV’{8Ştú‘Q 1#moHt´YerRDL(˙űRÄě ‘±�Ŕ “=¶ˇŚ�–  ĹÓ�]˙° 4zbŁČfËţ|¤0 Ą  ‚�ŞC•¤>ÁH›IVľ{¦×°ĺ2l“)M1 R3˙˙˙T·eٱŽčŠŠ ĘU,1ŃŽ–$Á Ő��`Ň µ¬Ő\u"˙ôż™桅?«m8ëT),ůK_űľ^UQó3ő­H†©ň”>‰ň2%~#ŻŹřůţ~:ŽŰt(s8ע¤¨†« ń2F ݱ`ôĐwkąĆÉ(€˙űRÄěKőł@Ťťw6`ڧi˙˙ţi3m EM˙˙ĽČ_nLDWcIĹ3“Ú ¨G!)\ĘyűE:ŽÇpÖĂ&CmMZŮ7‡¤˙–ţ|ş™˙¨ Čě&ÚŮžL™‰*(ˇ"bLŐ$"]!*©•��Q�˙˙˙ S¬e˙ůFg/’ł4&g§őŃ Ě蔿J1¬ TĂ1ëK;„nĺ\Äšc(0ŁN´,ňŰsüçsź)<wß­´˘ľT‹ ­FÂEr"č´t,BLŹRëx˙űRÄë Uµa =?6ˇô�Šh ĽH(¨�˙˙ô_ĆDđ ż˙ňôŃEśúhZ«“/ĹĽ\ÓđIţLYěĘęŞĆÚ>%“‰ S*Dsźü‡óüóuťűb¤ĹF䉜¤ŕüĆ\Â(ćVZa”BŠ ,`vŤ˛JŠ•��� ´@Ň·˙îŮgs{˙ů•©<Ć&Nr=îyÎĆľĽvě8ż™ć’ö«XŰ;G们Şç˙Ó·íëĘ®ŁQJěŠĘ ÎěcÄ1ĹP&4˙űRÄď }±@ ‰}¶ ڧi85a«d?˙˙ňm?Ŕ2ÓZźýĘfyÝĂá•QźĎ)%[+lg®VŁ#‘ˇ^¬ŕ*Ýş©ť×ęěŢ÷űŰż/!rČT®}÷'.Ť˘Uć’¦Ňç˘mŔ›Q\V`}f€��Q�˙˙ţN×Lůż˙şń–¤^CžęŢÎwÓ `ÖVq%KĂ yݢC=Zó$Ô‹:Ęt˘Ňňú^\ŮçĹŞ*é‹ É,m' ś2F ¤š¤˙űRÄë‚ uł@Ť>É‚¶ ¨§hL›ŔR)\Úú�áš�˙˙ýSŮÎÇ&‹7˙Mś"ŚŢ‡–kŠ: ”a!Á<QâşČc˘&hÉ« ±%ô÷˙ťßáąµýdń¨±%úĘ\b–ö‰R)d `ş3ńer­Ź���Ýô@‘C±úKă<UÝ›·HÉ9©°Şr“n …·˘ŢžŐR=*Úh…¬Š˝ÖGÝ˙ő×MKr˝*/W9Ěć«A Ă5Ę 9B#ë�ĺq��˙˙˙űRÄç Q±   łÉs5ŕĚ'ˇ˙îŠk•‘áRĺO˙˙aĽ˛y|o0R×ű’Â:ĆX~ó›%:iëÂaQ ŐpgÇŹíKřV$Ď+•ËţË0züĽ13Z3–X„Ăhą×âNÁű§ć’,CÇÜRY˛$RXęVR5���^3D˙˙˙·b[` ‹=ź˙ňâ˝4Vů¤EÉJ`ÉNą‚3ÍóÚbńtŞš246ĺe'WKzśőĎóç˙˙É+îý˙vókoR…ěS4”NZŇ<?˙űRÄé }µ@Ť=d¶!4 §Ůâ’dŐRu�ŁoüH$/D‘&|Č•®˙/8m¬f3/<ĽâÁţnjiĐgy]ĎÜ‘×{A;CŢUý¨íŞ–‡ě_ź˙çů˙ó?żś%"=Ě] $S`BBm(€ˇr hŐ���rKcI˙˙ęĆ82JKśĄş†U#«ľR}?ďÎ3ĎNă-/´ćÚÇS¸#˘Ł’0;s5ĄIĺÎ$jâ?b}Üż˙˙˙ţÍčW•Qhc”¬„şfşF` ’Č˙˙đ˙űRÄč‚ A±  ±Áź6`h!łi ź“»RkYA 4˙˙üľ?IßNo-öI-ůżâvú!ĚČŰĐHĄPA C=ŐSx”^¤=Dmô˙˙˙˙ô­÷Duu%§yčTIÇq` �ކ€�}|˙w˙¦á`0µ`˙ţéŽ[ŹSE¶ĺőĎřßdßó/ţľŤęXŤ v͆†h|„sv—¶Mž�ˇéˇŹÝďăĹeK?÷ßö{˙?6YŁ ^†«TËŠlˇđĺIšËő“QX#BJÄ!™ńËPô;>%-Đ˙űRÄé‚ IŻ ˇ ;IX6a´�Ťč› ˙˙˙±ĚĹp†@َ,Ěß˙ś%čw*LSł3ÁËěÝ209TÄžs)ʦEtşąT΋S©čVf{nĎFű˙˙˙µ:ŽŢB۶'¨bZÔ(–HË €™í“Ě”¨ ‹jľu��[ki�ţî—˙ŽŚŁe aöżůezŕ’<Ző2UÎE3-P˘p˘˛;™Ó1•Tňˇ*­IĘĂv:5ŐŐ¦–Wb˙˙˙˙KF˝mK^áĆŘ#�.•��˙˙ý˙űRÄëJń±ˇ UŃ?µâtŠ»ť<Čć(˛äb+‘=í5‰—١j˙ýŤjB5Śt{·ŤJĽb"ěđŇĹ $/7“Ű­Ę_˙‘fÜëćěËťť‰•›{ľ{9ď|Ú8>0 ë `…e���KkI?‹˛žrŘĂłçmŚ«Üó±)’Ň2ţÎÔĆ<ĄŘĎ•s9‰ş!YÔyĚbM™† **:Ě×mŃU[˙˙˙˙Ý™¨ö»:µdaö•ŹC (ąŠ9»î �?˙Ő˙űRÄőNµ�ŕ™ť¶ ô!&řŁ':¸—8µc…3¶ôéďsAĐ«őŞ'ßűŻ4÷{2ÓKcŐ>Č…X™‹eÄ43B3Ýn…ůź—ĺ˙oJ§ąSś™Śc$‰4šFĚWU®ˇ"M3ĄĂž*j(Cp ˘���rÝ« ˙˙Řd<ͱ¦ŚD8L».˙ Y“€¬=Ť{ą?]T.sž]…u¦m©9ÚťĘÝ *»ć°›ß_üżďçńő^_Ýő:­Vá[4®a÷’» Đ/*B„Ô ˙űRÄę m§  ­Ů”¶ ´!łˇŁËcDś§Ç󑔡Đ@ß*´żâ˛źK˙4#őp”[ˇkUI„G$7)ˇgÖÚ$ÍlÖˡÎł?˙˙űw§g»3vA„©ÂÜŞ@3ˇîF$Ę���rďł@˙˙ٲODd9ÂJ7"5ĎüĄţ×WBMsé[Ae[íCPĐ )ÓËË´ĎrSR6*O3z›1ůÄËŽ^ÇY˙˙óż;źg<„÷!MŐ]Ďfžá¦QCęi$zR˛Ó`˙űRÄë‚ éł  ±Ń“6 ¨!§ŮJá€6µ�˙˙˙˝ĐŠ1Ź~„¦˙ů&GIĐäćZ Î+×·ŇzZ®U$·s…)iw*•·78YäQÖ‘U«ń!žó˙ţţrGÜsn[Ň„çßä‚^eÚiśĚU‚ŻÉĐ.K*��߬E˙úô*ĺ“3Yħ˙ě’"§íöš)śłČŚó¶  ŞAjĄhÜP Ł ę*9`˘ j˙źźç˙ňżý·{łç­2eÎ@ÎÇNh˘˙űRÄë‚ ± Ť>ÉN6ačŠz´†Őł‚‰‘$ ·mcŚ�ttäŐ3ŢŢ^ó?żţw§G/>}Š«źĂ6őÚWMH(€çB1#Ş‹F‰Ć#ÖÎŽŁ˘Ŕ6˙ű?L‚V@���‚ďóH€äľnR…•2»d‹·gŹš÷‹[zľŕšÝ=Ć)ú,Ä˝>el×î‹h›ę©šŻźůëľú˙ţ8žőiżAšIĚÂ�łąĆ F<É$ń p†â„(żr4I˙ţ˙űRÄď -ݎ 7ɶ`ô!§h( L ”ă·t?Ă“ţűčşV¶áČs5B¶U)8Ť&°őH?pÄ  Ú۫ uŇ/5amë>ĺů˙˙˙˙ţß;3¶únôY#šKC˛ţ-RzĄşĚŔ±i˝��ľHŃ$ eąîÖ8fBŢ-vtď˙yŃfś…Dµł… óë=B“ĐszI›ba’Ş\‰T’“]–_??ď˙˙˙·÷;™{~uÚ˛cÓIUP%€ŚC+‡0ç8W-Ś€˙˙Ę-˙űRÄç‚ ]± ˇ Ý ˇă´�ŤŇ{™ Ó¤rěF‡,ČŇĎ'=AĽC—'émĺP™ Ş›ĘĂ[T¶łŰ‡&”ŹcŰňn•5-~Égů_˙‡3úiZĽ­÷ŚbŠŕÖ4ÉĹ g¬tÉH,Ś2†¦!ipM4 ‘ 4ĚŚŔ×—˙äÖË™ÉHČŐ–ˇ‘“YŤZËPÉ•”'#Y&˛Ë!”ą¬ś˛öJGüż˙ö‘¬ż#“,˛Ô5k,ŽFłĎ˛ć_ůË–( NŽFˇ¬‚šŠf\˙űRÄń Ył  Á‡¶!hšúrnŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞLAME3.97ŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞ˙űRÄě‚ ő± @oٶ`h¦řŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞ˙űRÄěË]°Ú €t��4€��ŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞ�����������������������������������������������������������������������������������������������������������������������������������������beetbox-beets-01f1faf/test/rsrc/empty.mpc�����������������������������������������������������������0000664�0000000�0000000�00000004224�14723254774�0020540�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������MP+'����� \����������@”7–�sĚ˙ţ ˙ŇĹDfŃç�ü˙˙���Ü3Ď?óĎ<óĎ>÷Ě>÷üó˙üsĎüóĎ?EDDţ�ńđ>@Čń_éŇá �@� Ń ��@;��8��şo´����č�€€Ţ ����‘˙Eľ|‘_Dä¤Č—DDDDů""_|ľ""ED""""‘""Čŕ‹���P� 8şŃh ˝ŃŤ¶m۶ӵ]×K۶m^UŐ6Ş^ő÷jĺ/ú{ŻŞ‹Uݶm»ň—mkšg{¦KŰ[Ú,H’łmŃĄçç&źŰli۶łsmŮ"Ň”(ŤŞş­lu~ĺ‚˝˙ÝgUÝÓ·ő—U÷U±o Úó5„4ĆiŚß@NĄjT=ť’¦SŇ©N�˙˙Đ˙˙˙˙ůD&Ľ�|/]ŕ˝|_�ţ˙8y�č˙˙AW˙˙˙˙üú˙�DŔ˙üCx/ţ‹źŕ˙]˙˙˙˙@bň˙çyŠÄáď1řëýÁ!ôóé€?A_OŘ�˙˙?�˙˙˙˙>Ŕ˙˙ş˙˙˙˙ýv˝ä`oôôyCŔŹWě€Ážpř öú‚@źŔ§�˙˙AW˙˙˙˙Đ—üűů‚˝Ŕ /觇~żžĎč úův>ßô��ż˙˙˙đ˙˙˙®pń�˙˙˙ů˙˙˙đ2."âĺß?€/ż‹��˙˙˙˙�ř˙˙AW¸u˙˙˙˙ü˙˙�ř� EDä�˙D@ŕw��đ˙˙�@˙˙˙˙ç�˙˙?č ˙˙˙˙tů“˙ ňż€ń "�|ůŕEी|˙şÂ˙˙˙˙Č—ă˙ €˙ä€|ľ�n�˙Đ˙˙˙˙â?,˙" �|ů ‘˙;€ŔÜŔ€mSş(›˙ ��ţ˙����9öźťçěÓgőڵőĽžł|ÎźßKź÷�ń‹ŔD�DÄ�"m›vů´k۶ۦmŰ´Űvm۶mŰmۦ]¶kŰ6���Đŕ ���8�������ŔxŔ���Đ �h����Đ���h���� �����ĐŁ��oŔ¸Ŕáp��иŁ�poŔ"Ŕá" """""‘/""H€DDä‹PDD€7€���í���Đ�� đ@�������čntŁ��ކŔč��ÝŤp´�Ý@ĂŃ׸m۶k¦mŰuŰ.]۶]»nŰ®kŰéÚ6m˙Űv˙ż˙˙˙ż˙ß˙ż˙˙ß˙˙Şq*ůN%+ŞcEU5ŤJŞ ’ŞÂX=WŁýĐsŢ!§Đçr ýW8y�˙˙˙Aü˙˙˙?ô^ţţř]/ŕä{€�@˙˙˙˙�ţ˙˙˙�˙˙˙˙´ŕ˙č]Ě˙˙˙?˙˙˙Ý tźŁ� 8€���Ťî:� �8p4����tt ˙˙˙Ę˙˙˙0ôí?ß 0ô?ÁŻřçű…? ° ×ë ]ź˙˙˙ň˙˙˙}Ă__Żô…»Ľď×čú|‚˝q0ţü‚ľ+ŕS�˙˙˙ ţ˙˙˙aĐ·K}>@PČóń|{<0°Çăú‚Á_PÇ…“€˙˙t˙˙˙˙ň»üÉ�ůŔ‡ß/_ä‹‹€üÎ� ˙Đ˙˙˙˙ůň'˙DäË/âÁ Čů "c�˙t…˙˙˙˙‘Í˙€|/_ŕ äË/ _D{�˙t˙˙˙˙€öÎ˙whŔ@ĂĐO�¸˙®€˙˙˙˙ /ů˙@ż /8ŘűýxĽť//,ůz@ß_±ô*�|=.ĐĹL˙˙˙đ˙˙˙ŕŢs��€8€ŕáđ�� �ŢŔ���p� ��ŕ AWPË˙˙˙˙°ü˙˙óÔgˇÁ�ÜwđŔ|ţîۧĎ{íőĐ_>âűoź?úÁëGŽ�˙˙Đ˙˙˙˙ńß@>Hţů|ů_˙ďż��ŕ˙˙˙ŕ˙˙˙ŔĚd�¶m˙`�€˙˙����źÓg�“çú<gý<źý¬źÓ|ţůg<ź×ó�íŔţ�î�� ��Ú��hp�p� ��  h¸Ł á���€áh¸Ł €��m۶®m۶۶m›¶Ý¶m۶mŰmŰtm´m۶����€n�������p����Ŕ ����€F@���€hěF����8�4�4�Ŕ��Ŕn�������€�ŕ��������Ŕ��€7��������Ž@���Ŕ�������MÓv ¶mş6۶m۶۶mÓµišm۶m¶mŰ¶Ş ^ßEŐŁđú˘ŞŞUUU=(5ŞŢ©ęťjvQŁŞWŞSJuŞÂŞQZő(­RťV©NŐ)őS@S@V*V*)ő«·„ľę-ˇŻą €˙  t˙˙˙˙-ü˙ţ—/]�ř"_ü�Ŕúď=ů_D_ľtĐż|ń�€/"�đß{AW¸x˙˙˙˙źü˙˙đ¤Ëů â<t‹|€˙˙˙�˙˙˙˙:�ü˙˙ +\˙˙˙˙lţ˙đż" "ů" ‹Čä@ä˙]á˙˙˙˙ďżó˙_�äË ňżďä|éE�@@�ńât…%˙˙˙˙›ó˙˙ő‹B‹p˝ĂţüŔ˙ Ü˙űOĹNí¸O˙űŇżţúéţúëŻú믿믿ţ�@”ú����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������beetbox-beets-01f1faf/test/rsrc/empty.ogg�����������������������������������������������������������0000664�0000000�0000000�00000020537�14723254774�0020542�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������OggS���������ţ–ç=����Ý[vorbis����D¬��€»��€»��€»��¸OggS����������ţ–ç=���8/Ď-˙˙˙˙˙˙˙˙˙˙˙˙˙2vorbis���Xiph.Org libVorbis I 20050304����vorbisBCV���cT)F™RŇJ‰s”1F™b’J‰Ą„BHťsS©9לk¬ąµ „SP)™RŽRic)™RKI%t:'ťc[IÁÖk‹A¶„ šRL)Ä”RŠBSŚ)Ĺ”RJB%t:ćSŽJ(A¸śs«µ––c‹©t’Jç$dLBH)…’JĄSNBH5–ÖR)sRRjAč „B¶ „ ‚ĐU���Ŕ@˛ �P��ЎŠ„†¬�2�� (Žâ(Ž#9’cI˛ �����ŔpI‘ɱ$KŇ,KÓDQU}Ő6UUöu]×u]×u 4d���@H§™Ą  d Y� ���F(ÂBCV����b(9&´ć|sŽf9h*Ĺćtp"ŐćIn*ććśsÎ9'›sĆ8çśsŠrf1h&´ćśsf)h&´ćśsžÄćAkŞ´ćśsĆ9§qFçśsš´ćAj6Öćśs´¦9j.Ĺćśs"ĺćIm.ŐćśsÎ9çśsÎ9çśsާspN8çśs˘öćZnBçśs>§{sB8çśsÎ9çśsÎ9çśs‚ĐU����A6†q§ HźŁEiȤÝŁĂ$h r ©GŁŁ‘Rę ”TĆI)ť 4d����!„RH!…RH!…R!†bČ)§ś‚ *©¤˘Š2Ę,łĚ2Ë,łĚ2ë°łÎ:ě0ÄC ­´KMµŐXc­ąçśkŇZi­µÖJ)Ą”RJ) Y�€��dAF!…R!¦śrĘ)¨ BCV�€�����<ÉsDGtDGtDGtDGtDÇs<G”DI”DI´LËÔLOUŐ•][ÖeÝömavÝ÷uß÷uă×…aY–eY–eY–eY–eY–eY‚ĐU����€B!…RH!ĄcĚ1ç “PB 4d��� ���ŔQĹq$Gr$É’,I“4Kł<ÍÓ<MôDQMÓTEWtEÝ´EŮ”M×tMŮtUYµ]Y¶mŮÖm_–mß÷}ß÷}ß÷}ß÷}ß÷u Y�H��čHޤHФHŽă8’$ˇ!«�����(ŠŁ8ŽăH’$I–¤IžĺY˘fj¦gzިˇ!«��@�������(šâ)¦â)˘â9˘#J˘eZ˘¦j®(›˛ëş®ëş®ëş®ëş®ëş®ëş®ëş®ëş®ëş®ëş®ëş®ëş.˛ ���БɑI‘I‘ÉBCV�2���p Çɱ,KÓ<ÍÓ<MôDOôLO]ŃBCV�€�������0$ĂR,Gs4I”TKµTMµTKUOUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU5MÓ4M 4d%���ŔbŤÁĺ !%%ĺŢ“ž1&!µ^!‘’Ţ1ž2˘ rŢBă Y�D��Ć ÇsČ9G©“9ç¨t”祎Rg)ĹbÍ(•ŘR¬ŤsŽRG­Ł”b,-v”RŤ©Ć����,„BCV�Q��„1H)¤bŚ9§śCŚ)çs†1ćsŽ9ç tR*çśtNJÄsŽ9§śsR:'•sNJ'ˇ��€��€� ˇĐ@ś�€A’<Oň4Q”4OESt]Q4]×ň<ŐôLSU=ŃTUSUmŮTUY–<Ď4=ÓTUĎ4UŐTUY6UU–EUŐmÓuuŰtUÝ–mŰ÷][vQUmÝT]Ű7U×ö]Ůö}YÖucň<UőLÓu=ÓteŐum[u]]÷LS–MוeÓumŰ•e]weŮ÷5Ót]ÓUeŮt]ŮveW·]Yö}Óu…ß•e_WeYv]÷…[וĺt]ÝWeW7VYö}[×…áÖua™<OU=Ót]Ď4]Wu]_W]×Ö5Ó”eÓumŮT]YveŮ÷]WÖuĎ4eŮt]Ű6]W–]Yö}W–uÝt]_WeYřUWöuYוáÖmá7]×÷UYö…W–uáÖuaąu]>Uő}Sv…áteß×…ßYn]8–Ńu}a•máXeY9~áX–Ý÷•et]_XmŮVY†_řťĺö}ăxu]nÝçĚşď Çď¤űĘÓŐmc™}ÝYf_wŽá:żđă©ŞŻ›®+ §, żíëĆłűľ˛Ś®ëűŞ, ż*۱ëľóüľ°,ŁěúÂj˰ڶ1Üľn,żpËkëĘ1ëľQ¶u|_x Ăótu]yf]ÇöutăG8~Ę��€�€�Ę@ˇ!+€8�Ź$‰˘dY˘(Y–(Š¦čş˘hş®¤i¦©ižiZšgš¦iŞ˛)š®,išiZžfšš§™¦hš®kš¦¬Š¦)˦jʲiš˛ěş˛m»®lۢiʲiš˛lš¦,»˛«Ű®ě꺤Y¦©yžijžgš¦jʲiš®«yžjzžhŞž(ŞŞjŞŞ­ŞŞ,[žgššč©¦'ŠŞjަ­šŞ*˦ŞÚ˛i޶lŞŞm»Şěú˛mëşiŞ˛mަ-›ŞjŰ®ěę˛,Űş/išijžgššç™¦iš˛lšŞ+[ž§šž(ŞŞć‰¦jŞŞ,›¦ŞĘ–癪'ŠŞę‰žkšŞ*˦jÚŞiš¶lŞŞ-›¦*Ë®műľëʲnŞŞl›Şjë¦jʲlËľďʪ)˦ŞÚ˛iŞ˛-۲ď˲¬ű˘iʲiŞ˛mŞŞ.˲młlűşhš˛mަ-›Ş*۲-űş,ŰşďĘ®o«Ş¬ë˛-űşîú®pëş0Ľ˛lűެúş+Űşoë2Űö}DÓ”eS5mŰTUYveŮöeŰö}Ń4m[UU[6MŐ¶eYö}Y¶ma4MŮ6UUÖMŐ´mY–ma¶eáveŮ·e[öuוu_×}ă×eÝ溲í˲­űŞ«ú¶îűÂpë®đ ��p��0ˇ ˛���ŚaŚ1ŤRÎ9ˇQĘ9ç dÎA!•Ě9!”’9ˇ””2ç ”’Rˇ””Z !””Rk��8��Ř )±8@ˇ!+€T��ăX–癢jÚ˛cIž'ŠŞ©Ş¶íH–牢iŞŞm[ž'Ц©Ş®ëëšç‰˘iŞŞëęşhš¦©Ş®ëşş.š˘©ŞŞëş˛®›¦ŞŞ®+»˛ěë¦ŞŞŞëĘ®,űÂŞş®+˲më°ޮëʲl۶oÜş®ëľďű‘­ëş.üÂ1 G�ŕ �@6¬ŽpR4XhČJ� �€0!B!„RJ!Ą”��0ŕ��`B(4dE�'��C)¤”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RH)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ©¤”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)•RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”R �Šp�z0ˇ ˛�H��ŚQJ)Ćś1ćcĐI()bĚ9Ć”’Rĺ„Ri-·Ę9!¤ÔRm™sRZ‹1ć3礤[Í9‡RR‹±ćškVk®5çZZ«5לsÍą´k®9לsË1לsÎ9çsÎ9çśsÎ�ŕ48�€ذ:ÂIŃX`ˇ!+€T��ĄsÎ9čRŚ9ç„"…sÎ9!TŚ9çtB¨sĚ9!„9ç„B!s:č „B„Bˇ”ÎA!„J(!„B!„:!„B!„B!„RJ!„B ˇ”P��`�@€ «#śŤ˛���€– R΄AŽAŹ AĘQ3 BL9Ń™bNj3S9ťtjAŮ^2 ��€ � Ŕ (řB1��AĚ …U°Ŕ  ćŔD„D� H»¸€.\ĐĹ]BB‚X@ 88á†'Ţđ„ś STę ����� �ŕ�ŕ �""š«°¸ŔČĐŘŕčđ������ř��8>€ć*,.02468:<��������€€€�����@���€€OggS��@–������ţ–ç=���ý‚ĺ'02310276;:?CCBl…‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹¬Ř%ô� B8FkF#ëśzĘýýžĺJI`ź76�BU5Ŕ: š†îK.Ţ[/IŔ´�€Ž€ ~ĘýýžĺJĽçŰJŮ�@!TŐ€ €z wP{@Ű‚š_,ß5đ�č@ �4�>Şý˙üĺ žíkĄ°�@!TUč�40ŃMtô™ŕÉĆ+ÚŮÁ…WÇŃ�ŕĐ>Şý˙üšäâvy¨��…PUˇô…|G×OӱЂ††ŤĘpB�Ŕ&€>Şý˙üšäîô4Ş��…PU'�Đ@$¸O}Ç%ĺMŇ;†:€ �žä>Şý˙üšäŇ´đ&”��ŞĘ* @tđ¨�´ŻůçaďĄ)t (�4 �šý˙Ore1éM0�BU � 1>ŢY(ĄÚsYŔ&-RJgčˇŐMkňŘ�Ŕ‚Všý˙OrĄ2ŘĺIŘ��UUĹ@EO5š‚nE~IôĆÓ´JĘqŘ)d˘U)°�$ @šý˙OrĄ28ĺMč��ŞĘj� ĚY¬"ćeç…d×Ôdţ‘ňcw˘ ±“¦‘�€d �:šý˙OrĄ`°Ű›¸�TU:�Ě]ą¤ż\Ďôł\ĎŇ{QF<Q©š5¬ĐŔ`MX�Ţyýök”‹2íMQ#�€B¨¨˘Đ@§ŰŢçµNUďáM őNůf 3 óéęĐÝlC–ž >H4¬T05�Ţyýök”‹2¸ŰŰXaE��…P + &EŇć^ ô#©”¶&Ú!ir}Ď””Ωaku¨“)lY ‚& H�Ţyýök”‹2íMP�*jT�¦Ţ1{Ă1^_Y•˘~vçt×Zź}ą.󭡣Ş9“5\1á)č�ŕÁ_#�Ţyý˙ţĺR Ny“�B1ę,�r(¶Řňł4ptmp sR›¦ŁL/g9‚čŢ?®Í0—M)�°”�,Z�ýÎuą‘Ţ8� 4´X‰ h©u Ŕ÷ßŇ‹ö~>SŁČü†ý$=cr¦Čß7˛üĽöŽĄĎĂŚóů0Ę«®bĄ*Żß·ţQť˙Šá¨Y4®#öO¤Ĺ± Š«=ˇ P{údX]ÎÔýÎ59 ŁĽ �„f)6đÖ”łA`fś“€˝^ťÄ!<©¬i0/dĄ·?/Ôhł=é«m¤ň¶Ţ­™žŹŤwŰŐZ:Ý–˙‰^®¦×Ö‘&uVwű’CĹ_Ĺjđ-mŚ����������������������������ýÎu9 %˝ ��Đ"++IŔ@âĘAGŐŤrý5]ó�o”X€Ż/săÎŹ\ú çHÇ‘ŮqoĹóČĐ3ú.+Ľđ¬qÖ\Eť¬SF’ż1R-ŕ"ç ÜI¦ĎlÂjÂÉĚěć���������������������������������ýÎwŮ4-=� ĤĽĚdŔ˘ĐI�ŕ,1ć8‘qü]âj­_č÷⥧Թéô''ďsçőu|‰ěwűćąĹÇ덳Î勝/óˇ<:Ť0ŃŰĺ$ˇÍ˝'—3Śí®Źý ��������������������������������ýÎu9q­< B�ˇˇËÂÉgPŕ€~57kg§Pżd´]ťŮŰüąśÎoż’_ÚąJs)ęă5÷Ňä¤Ĺp–ô•őÚc\^;ÇĄQĺtFôb[D®áě §­“������������������������������������ýÎ5ąVŢ�h’ʦˇ�°@vvż´ÄNľd ¦˛˛Ki*Żu67[`&· 'nV‡§Ż‹“ËČcşt“źq’C>-VţFźÄĽVŮ ]ęďtGNĘy49ŞéaśŮMă����������������������������������������ýÎ5ąäŤ�ˇXd‰‘��\%`8Ź'\ŕÁÁű5ôŹ7‰M?/'x‘,ׯ×}YżŽ˝<S«oÍYI¤Ű0î­< V_R†]ŠíÍžJ];揅śŢí·BłHűń·•8NŔŮ˝Dä�����������������������������������ýÎ5ą8‘Ţ�B“„eŔ_™h$�áž''Đ„Y8˘ŘEŃ´Ç˝^‹ďç«Ü.pNë°Ä-’CaĂ+83|©űŐźŤ-Éśť¶ĎUxY¶]FVS”ijúP[éFÂR/P:Ű»r��������������������������������������ýÎ7ąVľ�BłE R€�Ŕz+ó´¶źëž•ĘĘőŠa߉xR=¬±7ćL‰Ű &‡Ü‡nʬőúÖVŢk\ńÖďá šŰŻŕ´şBĄ:ť1Í“‘±‚¦bDţö1Í ‘Ş®���������������������������������ýÎuąž8� ”Ôe…1ŔŇ,�0™ŽŽG­;H`5ŰrŹbR;VsĎÜlËŚjąPĎFlôn3¦°íúuPéˇ)`,V*ýxuC=ľ™rN:Ś…ý¬=Oä}w�ôöúEad•”cb�������������������������������ýÎ~7Ů8Yľ„�ÄËJ84ÜcPČąëlbĐźĄBâŔ(ŰŘ,·âŠĺ$ł¦ÂĐ›. üdĂĆy/”cův€~ý—:lϬ™Ńk8Ĺuoć”HȆ¨$Ĺ ö˙óŞÁžą˝Ď�����������������������������ýÎuąáiH��Z„®0rea�X¸¶(cp˛KÓëćtŹ#)s:.ĆhëřL¬^ł)Z Km“IJyś’c#Qďln2t6]ň~’]¦NĘ „*đ<´o1X¨ŔźÇŤGSz��������������������������������������ýÎuąŢ��B“¬<•@ ľÁq] ŘÔÜ‹ć$"93Ë’zlć3Ĺ—ŕ”S©Űď“á˙ąz)ĺLżĄě. ›&ukbgÄ÷Ę_:`…HňcĽď0D64úb‘˙ďŕ 9ű!r�������������������������������ýÎuą(‘ž„*�B“˘Ś«Š:�>nmcfFU)¸¸đ“‡0ŮëŢľŐŃřÚťť·•ęP[©+T˛űëĽý2š\ÍüÓSś ÂG.WąčX©;·ĺUFŔhkôo†FŘ/÷ŕÂu cĘ:����������������������������ýÎ7Ů–Ţ8�€š• ›0Řd7•\´×vÎ}ií˝E«MÝ M3‹Š"äđşĎ~�µ˝bł¶#Ah©'ňR‚ŃÖ&y,]~ŹMG“-9¶#8…ţůäëĽ#s/y����������������������������������ýÎ7Ů8Qľ1�•Ö%8�€iup;š¤_I5…ŔÖ˙?»ŘÄ&ňҵäduˇn±{˝ď…é!‰ź»:•¬Ď…úzĹU.25žBÉ–+Ęčq0ďîQ®*/CUë&^ܱé-��������������������������������ýÎ59q#} �„&ĺ68ZŔ€ôĐ»t3ü´WÜ™ohm™äP»-¨Y?Ç{ž°ęŽLXř8&MöŘ f%ŞO©›÷NÉ”‰ÍÍŻ®b˘ě˝›ěÎČPwˇëWI;–���������������������������������ýÎwŮ(–ž„*�hRdÁPL�ŹKşá?7śŹI—»BťŹÁ¦Ń|_O&±arúäşvQŕ$ąk×°d­Áĺ˛l­Lc/["°R#lcú,čVŔÝťŠ…«äŕHďbz¬Ű{8T�������������������������������������ýÎ7Ů8QŢ1�ÉJQ3=H�Đi—Ť ‚©ćĘě•ä¸óM6żÝRmöµ|fę «ö’ô<xQ[‚ Ł§Ý˝űjTż03Ѹů-ťao8*Ę.B¶ŘrI…Z™Řyůۙ�����������������������������ýÎ~5ąQľ1�ˇu‰8{,�U¦»k,ż,¦żW}IŤŠZGv›Ü ý±gěÉVöCA’ô—”4#ŤâwLqu;Ž‚óÄ~BH Iţ҉fDaďŚne›������������������������������������ýÎwŮ8–žHb -ŞˇŔě�@cŰweł¬fô“'ŹI»#ł:«/Ősâ§ LNao—Ĺ’üłęÎhrŻ‹K5«ŚámŃ {Ř–ÔnČ[oČcőQ˝‚€ď šî*§ťÚč˘ŢŢ���������������������������������ýÎ7ŮâŤ�ˇ˘`đ&@ ď~Ë˝r8zôľóG­řHä·5ż^yÚxńé\™JfŇŹ}ŔmťŁvŐľËôLa!9'ěbŰ>ŃZ*Ěü(NbÎŃŮ&ç˛����������������������������������������ýÎuą–ž�@-jIÁ�…&&č�h‹o\ś‰[˘�ĆüJx2ÝĎÚ ^Cí7^:«YGcF¦ňO}žÝşž‡žŹ™‰°±<É~ľ¬2Pâü"SÔމíőŞŔö™!Gć&ŰóS’‰ťÎI…¨��������������������������ýÎuą8QŢ(�BĚR¬H¦Đ�‡YB,’˛-oĹx3CT§XČčňßŃ| éČÓůĆű<Z>Ńł^^Ş„^‰)5=‚%G˝pďb€{fy](€>(Ö÷:;ubjŮ:kGG:�����������������������������OggS�D¬������ţ–ç=���‡ż˛¬‹‹‹‹‹‹ýÎ59 %˝ ‚��”. :�>Źţč]µ¤?÷TăĚxL€çsÔžM›PĎXšÁ„—+q!^#§ĂaâčŃ|S«ă.Ź„™ăµ•«Q®%(Îsnĺ¶©lő]”N-{~����������������������������������������ýÎwŮ8™ž0�B“b…qÄń–�X9vń)l}¬ĹĽŹb‘Id=!ű…t,W˘7Ĺć˙9±¸őń­Ű˙â® Rńçf©ÄŔoé<ďËÓ—·§ľUÚe±”׫üpő0Úé[(wÉÎ"…��������������������������ýÎuą8–Ţ„2�h–˘"hrđČ)=|éP›Ű"ýr:Iň"qŤö°Ě蹨šŻŕŽľRß"ŕ€"«•uŢ˝‰ ţąÚ‡żš.řAsń©Ďnµďn7…Ł'ňčeŽ~ ���������������������������������ýÎuąŢ0�B Í c@2 �°8čä>h»ď_6zyu—YĆü|$Ô•»ąŽëö¬ĘŔĹ…÷ńSěŮ n'E˛0!¸¶˛ö>~v3üayč™zź›9óMÔk ¦1×5„éW7ń#ŢežČi����������������������������ýÎwŮ8‘ž1�h–bŢFh‚�¦zmśňp4<Żĺ˛é*é´ ´ÚäÂśÉ"N¨„Şń|oâSđjl’ĚôřśxŽMqIß–Š'S´Î—WřKw®˛:&U0TĚn;Ť‘bbž ĆPČe�������������������������������������ýÎݞ 8��P‹<Üŕ0P8ťÓą­j� �x�z}]Ü������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������beetbox-beets-01f1faf/test/rsrc/empty.opus����������������������������������������������������������0000664�0000000�0000000�00000020063�14723254774�0020746�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������OggS���������też/����ä\ OpusHeaddD¬�����OggS����������też/���ĎřČEOpusTags ���libopus 1.1���&���ENCODER=opusenc from opus-tools 0.1.2 OggS��€»������też/���dYoá3˙@š™›——‘™–ť•™”“––—“—–—–™•š›š™—’”™™–“–š—“”—•——•–řřř™9Ü�>ř-ůčE˝BSu53ś¬ů€$�/bĘ0•Ó”:ę'X*[|äŕR‚űc»} :Ł$ÖzŘúŐě§sD“÷INŔrF›wĺˇóß.!^x*iVńş}´üŢđ×]ë{ŹŻŕpT ĚŽŘ2ĂâÝ*©Ŕ`������ţKůŃkd1¤ś%ÝŁˇč\ý“źčŠË|±‹]­(hÎŇ®Źä¨’)4ľ‰±«ŔFž•[ =Lj„ąnĺü{Ě>žor3±@Žlc¬ý7 ěVU«§ĐÔ qx[»Žăt]{ĹąbęС‘1ăDt>í¨çרoŤÖB«ľKĘź’�qÓAm·Ď]ή¬·1%6ŔÎ ao»¤iâĆĄe¦¤,]”Ř“ďt߉Bs…F¸mFŚyó;&řaß:¶WrĚŰÖČr‡0–ľ i1U·…ž_-@X)/C’®ÎŤ{.jÝŰ*ç§‘>‰LÖ`/±Ţ!8®ů‡=­—}‰Ĺµމć�řD®ř#ňÂVmR Z›Řŕ;ÁÄ Ąě{ŽT>¸vSBű ›Ľ<ű‡­c‹Î>+ëđáŤh¸}«¶´LU$#rą!Q¦\řaŐ™ăÓŹŘe¦¨X,ŢR.bn˛o<“Nďđî\«ăq JŞ–űš|®Ł2ÔÝpĽQűahG —L”oEĎ5€qžŽ¸¬')„Ó˝ąĹ˝”Q<5¸"ŞöŽ‚t”nÁ;5úç8óüËfű 3â)ň˝‘…´±Ă\ł))…ňŞąAŕ’⪫ţi>|5|/9ÇŢBfłĎ1Ž-7/5,ă…řaŐ¬w訿ńÔžüJŕrU•íŇĎ’=Ż7eŽÜ#ţ$zÔž ď¦Ň…7 Ą´^6r•âĘoUâZË ›®X*ÖŘęe4^č>ňG®íŰßä^ŕ|¨é1ˇMěŮu'r]o1HŻŔrü%¸­´ 0ć,3VeŞŞ—xD®?·1­K+,IqŤ 3YĎw,kżr Ť�eLřaŐ±›ŔŕvťŤâÄB Ýś[Őűüaő†Pcž»,DY°ˇ©µKuć?‡öŰ^kůé˙Ďý0<ŹőňFWŇă¤~•ăşx›vř†¦Ä#Ž˝ěq䞦×Ę´Á ţvŢĹ ţů§Gúî+bGŮ©VeöŕIT`ěÜ)‡őäL¦đř3ŐÍĽÁÉŻ3¦Ë÷ČjŚřaŐÔÁsc0fxŹ‹ë{Šžâ##˝ňY˙é i„[•~€ é¤×Pz Ŕżň6 _ýĘ<y¸–®} ŮÍyglČ"u[írx%qîăDŚ7zp…«·›~ČbA‘ečAÔŠŤĹ»y˘YLpŤĄ‚׿ý –/RXFŐç–mîf˲ęĽřLb>ţ4 řaŇŤÚ§ÁçĚXZ1§gşW·b°úĚÚł‚]­Ť*ß~ŇSä„$“Ă­śfĎŃ+3ju[#v›ňÝ$XHe\n§§ÁjŁ’»*ß5zźş™c— Ą[Éęo®„eësż‘ágɇžÂÝöą ÷ů€˛| ľ›|–ĺ»-¨O# b±k0úó¤ţ)BřaŐ­]xúë|°ňdˇŰʑы,–0ä]ZŐšIë­"”1"¬0jť;Zj#ŰRť�-—٧ż7Ę·ăYC˝â¶ş+Í/8ďůęíŰ/őŕćţmíOžůGĎZňĺ^Zfe ˘5,k¸tÍ…—`\€@ĚňăŚQűgşJěfËž™o=âp,ް FŹ“HĽNVŽ”ůL<ĂřaŐ°żë+…'µ˛cÇěůcI]ż}(ń»ŞŤq6#ŤghóJđwÖMQjˇq)H6$HćzyXW«8‰9vý˘¸]_YóoDóő§ţm6PŢć<}WÎ\Oµđ%îh‡:ă¨.¨ťÜ"®6Ńs ŃKeG»îvd@9‚Ӹ󟕞ŐÝ/­řJ|2˙T=/řaŐ¬Öé÷‡†8©z #<öśCݸEČé|Ń&ökČĎáT{SXîwŐˇ•´ałäőTÚ-ÚEy S/ó^:9‚@ÜůŃĄăM Ů©±ÓŮ•¬°EBFŇ—<°ôx>–2°źť|<dCA»čÂ{ W ´Nµo—phĚŹôF_¸ćn»_›Sv©ĚG’,Şš$�ç:’­ŤކLJLÖ´Đ‘%řaŐ¬ťuź(K='ÁÍcÎěëT–ś…>łšÔÝŤ‚mŮ öoČŇj໫‡Ćµđ ‰3µG7ýlŽ*lçÍčD‡öKĹ%śŐ݉šBÖ…ÖUk”@7‹µůpD(›ÓŁ iOEÖc ‚'˙ł€«ŤZ68'˘ůXfW—7ZmáčMô(|Ö+ (ĹaÝ5,ţ EůřaŐŮᏠő-Ď׆S—ťŽZí|*c(Ă‚ČCż'€!0ásʦད˘A BŤ˝¤ł,Č/&Xŕţ ŞÁ»™młsČ­HĐDv…˙—M˙÷edyf˙5ůŠáŃŰjEwŚ‹7ľ#3%, pÉQyë¨qś€q&ąŐ@Ŕŕžń>ó'íĄv„ýWg*uaôR–�76AřaŢíŰ�¬!ÂŔŤ‹­łôżł7­*Šs 0of Ł´ÄH!Ţvćwꑊţp•ĆyE#U Lô%í�.µG.#žť µIŽ4I(j g^ލţf"~÷Ăoć +µsW(š—4<ń~íŢĺ4ŃfÄviĆ:§¶LΕZΞ'üÔpĆĺ.ŤśL1Ý-54UNü] ÎřaŢĚĐ[J2ÉŽ]ďF·ń@Ü<bÉ•+Mt…Đ|Ą Ç*sBÜn^4ó2Ϋ>1r\¨ýÓe(lţ¨<¸i t[ćąŘë˙ölg5ÁiF`éq­IÁíĚyP±71ŕa-Ýď>B\6™&vÎUő;ú HŽgŇm‹Zrç#±XFÄĂ»í (qËń\˙dřaŇf/1@÷qŽ#'•zíűú.Bޞ%C€‰yG¬ňKŻöRfQk*Ą˙1ͬősËb°6űOĂ8á) TP¨pNřeď0!óĆ1îŘ™˝‚î٧LédĐNâ,Š<19d«Đ"ĹŃlďÔÜó•&aÇËęo1»±Ş vJd ĘÁ˘ęž|o,/bĽ bl%\;Ď“vĆřaŐŮ)[" áX‡GIměpßŮ?…!ŢŃűđÉBçˇű2<·PůŹ•Ď?¨$9îNß[śţř°ß¶ą¤ęmĐńsľ <=ýú š˛=U‰—o»‚zĆľäe äž[ż¨dÉ‚ÝXšŽp †f á Wđ [^fSăú®LxŮ?ŹAâŃ÷6ů,ë`ŐÄĄ{l?@ů�#fřaß(ůí÷÷cďťźv°TŐf^Şě“Y.ŮXĂFĺŰ‘o«ÚÍn„Y�)o-üżT˙¤�6AÎ&c@ŽôĎ*@ń+Dˇ-úD}N  ¤<–…)ţϤżz˛ŰÍî–±‘ŤłhpŹ—˘˝.¬7’ąAš7S‰<źq=·=zŇ˙aSsZÁ,…:ď’Đ[Q ¬‘â ëřaß&ľłń4DP{uömÄ€N—ůÜéŔĆ}śzIXĽ¬Ľi«ô‡s ˘&éŰqŘeXŻ >!B¬Q—A‹1ú”GäaÉs¦(a?ÚţUş_Ě 8d~?űˇdZÝ5Đć?Ó@mâopÔťDnB`t·Úž;ú yűÎéaŚ?2X"Ą§ő™M;ŹlȰ,Ć™kĺířaŐÔťŇě”ÁÁ›08n"Oj ĹEő¶'ضz=ŽÁr®ŻÝzĆ.HtŇ0Âu/÷}ëŽ<{ŇMú� đioň†é@€“S«öčx8‚+÷ŔˇŤuĂ𾊚NJ1ő'¨Jäîb; `Ű´<�—G$&Ë5ˇ,ţ*ńíëŻ3ó˙űÖ„ěÖł3Ëç"ᛟv×Ô�Ç\ řaŐ¬ťď±PžŞş*›oqŢ6Q [3!ľ˙F@)P{°˛áű€Ď‘gÇtţ™ŇsY…rÉ(/?B„ y rŢxËm_ôŢÁč«`–/ďăšZQ?´ e ú?FggýňčęćAxß-G&ݦcIkČŤçNá7;ť”&)ŕFj0'6óP´Ô;*řaŐ›Y@´«Š•ć ž ă†c t¦Ą¬/Ó/ĘVŕfLŞ•ĹqkIÓy‚ęBđŤ+Đ?¨„z§¸Cż®έ(ÚżäýĹ»S˘¸‰$Ájz{ĺAŕ€ŽŇŇm<T Ě“eî¬Ë�C67Ćxä˙ߎă6kŔ—«ö‹™Á&šn¦±´Ť˘ĎšşšÜî±hřaŐ±Ć%ýḚ́lC_2éUjŕ€pÚ«_�ź4ř‚]Ç Çö*¸€ďńŢ­#ŻQSŤ=ö@Fc˝ ĘšîHč|cvSŤďv@üţs]xG¸Îß…6Î`´]Ĺ'`<>Ďw?Ě_v÷;üůA©ÚÜwĂť’ď'CMşĚt…u đÇć·áśAÁm‰tĺűJw®¤%`­řaŐ«GĄÄáA0ŕ^.aʶE*čŚ)@çďţ”ĂXbů3ËXrDđ–)–-6«B\!ž¸Ř¤‘‹Ď1Bݡ€�qŻRmj>Ý0’M„‹âí…{6‚QSńVչè Şëm¤§,ŢúŰLőď±Ă[Âţ ˇ‘8}•¦ąąî€Ž *·whŤ8;)ßŰśf ÝNˇxÓ†2JűřaŐ¬žŘé”W"דą0"J­®>ňX‹iäă‰U&A3J¶ź?7Ł ¸S4âv"Ł9Ů›úŽŞ×âńáNn=Í™pŰóÖ;pUŹ| cpŽZĺ !+ď“‘¦ąšx,V%Ţ.:żĆĹČ L,ŕz1ŕç·ĚٻѰďČaOS«ĆĆŤCîě¦Č¬r4Ľŕm„ŘĽřaŐ›b`®éđŕÝ÷'ł#p' H{™¸Ć^ŮďĂŰq±ßw2ząýţř\ŃzÇ&ꓺB"LŞkĘó*8ËM1P’çņ›h<Éî»ŕđA‹hMM<‘ŠMű†ŞĂUÄ•¶=¸a-y#ŻhףSÓ šłSç<üŞď4xţŮ´“ËXP '"ĄđÖjÚ›ăVĹUóŽR–”˙µžřaÓ,{Qwšs¶µq˛ţcšAşÔ!<ŔE{mݤëý0•OęĐ_QAY§ß' ŹoěÔDßJDvęÍČÚ„÷đ7mĐ`fźÍî'ŃúŕB‰AçĄR:N°e>7ŤŐ!Ö÷-ŹčźŤŕM™Śc†Z1ĹĎ÷Ń?TŘf˘DPčJ3 f›‡áEx *—śÝ|͵ĄŞ©.RżářaŢĚ0§Ü \}$—ë-§d’‡Ž«ŮÝ€őWů‡CTNI´Ň3Śşł\Ž˝ř2pg&˘WvÔ-¨Ĺ4ىí÷2ăe?ňŠĺ­MG®×vn‹ę™u›śH˙¦á˘żi0Ą%Çnj¶‹Ł–®ńźrü6ŕU‡& Z¬ť’QCRĚŁŐh L=µZŕřu�Ŕă=TÜ˝±pŚĽ4óčřaŐ›ö˙Ş­•–ұ3,öD©¨:¬Â€]\ÓËü˛6ßk Ę‹?GoÁ»ďCiôÄőh’‹› Őłk «Ł–ë™-´¸CSÄŤąˇOťěç‹·�¬Äµ´(@ •±„6O׾l6K.ňĐíŚKťAĎ|�ÚżQ[gďpöUаułŞĹy ¦±8eďí2˝ň ÷˝´řaÖt\ntţŇQ•ĘşÇU Ă`Ž#î,mÂ+ic¶ôÚGŕ>úAă é#…Ý€F÷ňőś:lžÇâç٤͵‘O3UCÁ[z÷ŇÝY§.Bß# 1oĺ ĺ»>áÇŻŁ1>e˛…{x=G Â*bťżş�ŐT×ͧÓ|˝|¦%ę?Ô3Ǭ6Eiç/“ďŞbTá›ËAhwТ/dÜřaŢňűčr^|ůxh¶´%ťăŕ'•ŘGJC» _Ö6ˇÚK5(Ą”]Řnˇ§Ű-OrŠd 7ŹMµ•vrĽUüŽMܸ!5„M;ÓĹŹwřVY‘7±ű˝äÜ.öÎť)’´Öúxz‡“z�Qśţ:ŔßŕŮň´„şâć(:áúşjŠÉ%×# Ńwś3”n44·.}A-hěřaŢěg×îÜgµR=ŘţĽgŁĎľš9dť t©´/-ŘVx*ޡŤ+ă{ŽíűŰĂ·l›(R2›öMĬéÔOę@Ćeł‹î¤Ů Ćą’řc˙<É.e#čäOP�8ČáJĹň=Ř­ľJ€µÝńřßU8ż [^îl»¬9¦îíDKľ\ËÁD#‡řaÖtq0Ô‡†gĺŕśúĚKŘdąĚ.Čďý›^źŕýR6H Ĺ"*ŞŢ!ŹÍ—ŠăŞvOŻîQçN™YßÇ@„ăÚ�Âqĺ ßXŇ(ďÔřD®b& üz>Ú¦[™S;9•L‹tNöÔ•‡'#ŤeĄHۆ]“öś\eL°.ůţÚň@<Bm]j±ÇrµŻ±lD;řaÖt\n~O\~'&2‚˛ç@uvd"´iz•ĺ‰ŢMŞ7”›tt3´„C˙ôŽÇú‡ÚŤ@'ş_ąaĂgűáRt\`|‡ŔÝE+ôiŠ‹/FĆk»oÍh‘n‚rpž‰úZůD p^ĂýšĆrĽOó/^€’»¤eŁuqeËá` řµÂš,đ¨¸ÁPÔäÔVŽ6Ń”řaŐ¬ˇTz —ËŘ1ü‹‡—Ë4Ň1j96./ë źl¬żĘ quxNĽ°%1B»çSwq4: °…(Č&±™Šd«ÇŽQü1i-uójc‹Rwaňš@˝Äe˛MMÂá_ÔŞp$Šźx7…L‡·Äqčř¨Ěz‹n„¤ú�…’â.p2-D ru©2ë�0 Ąó†'PĹÜ“ ¨JřaäŽ0‡– ôĂěý¸â6PůŤôşďěšyĚ·ś»MٶSśËň•0z.kCĘŮ{-ÔŹ`iňi ű{1®ŤńOŁj 9Ś˙ R?妄ĎCů öÓ¶˛!�V ,đ[ Śd5€Śs=t)¨*±â¤´,Á„!LżěÍQT—ÓdFhG¬Z™df„'ÉÄVřaŐŞŻŕ(YňŻ/ß5,ąxKĘľ8npřĹ\%ú’Í BËĹX[›Ct‰¬ŹŃ�먍ŘHąĎ‚ŤÎÔ˛ýő ĆăłčK„¶gżŻ—çÎÎĘ) 7 ÝěŠ1§{4~éŐ«¸§g‘žÖĺ<#Xü(„‰±ő—-ă´ÖčÚX%ęgüyńäÉi×<—ÍY©P.řaŐҡźˇáâ˛|H0Ľ$é:H÷`ú,™®ę6łâĹ|• Ŕ™Ą326ť őwc RĹCoD–üOBóĺÜČ…)+ąß‹a8zýĹvżĄŕ§8+LdÍš_f Iö�¦îB䤶*ôI<˝|źw X— {/Űţôżpímlm絪ő…B žś[_‰ ŚÝ[Ł řaŐś!Ć•oPţP”X«ĎeŮÄ=ŰG®,ĺŻđR˙ěíV+ťżÝ@lÓÄ@¨¤¶Wźxx;í,+IÔ€°ůBăy{_ňqy4ř‘ 0ŰČ~2xSÖóč±öm XőŐ,7”Ĺt¬ä8‡_wż”¶üŔS..ѨQe RnvS١t¸QĹx¦%‚l!Jµţ$řaÖr&`eęĎ­o}ŰÄ}W+] µ´ ÔsrŢ%Ä rjc§*˙gPżźiaź×L!`P‘E " Ó@ ŇŁ[y©A;ANL‘ cj9ŘÉ‘‘uB4pY‰Dź¸*Ąĺ>h˙T¶Ů›DGAźčOeÝ[­Bá$:ŘnË^áě$$?§Oâ›ÝÚ&*čôŃYěřaÖqę¨*űń:ľÝôýťRą†ę¨Ř&ý)˙0)ó1L¤ďąşúe -Ü`Ýś›ý«ľv"ZÄn,9tŁüŻHp°˛ÖC Žá%¸ß¨/F¨öÄKŠ+Îą˝2}ď(,#B¬çŚú×i4Řű]ĹxçYż˛Ó ±Ë˝—lQ-éBŇ aÖe8‚9ŢxMÝýY˛ŕ.b˝LC\@euřaÓYŔ®vĂۤtě|¦ ĺŢďar·°×M ž{Ŕq†$¨2ŻŇC4T’]şOµŕřw†Ľ mÎĎL—D3WöŘŮS¦@9uăݶV®l5a—PăŐG™ aEH^Ż"kňpHnşńw×°÷©™5GćÇF®e…ąeÄÔŐôÄŤXaŐź  L¶[‘q„­řaŐŞşh8Óü:ˇÄQ}6‘(aćf4¬\Á;Š_YĽ­´đţOT±H˙•,îĐ\mÚś+WWť]ŮpW=Ă™.‡ĄÓí¶°ä6żK§ŠĘ»5~ůŞ8śd”ô7OŤ,Ëú],Í3ťzršąOSóžPÔÁźyiÚCÁ˘WŤÝĹŹ[#m~a!X[×UÁ‰zň0řaŐ¬É&ö®NEoJ5ťă_¬ä.‚m{(%™Ëű, ,YÄŕóŞEôŚ3÷Iő3M(ßpLŘr&}t®ôżçNë5Rł›ĹŔÓ‘ýĆĺŚ`f .Iźs®ý6°©öt±Ľş €ívé'3‹¬)˛Ď%Nł°z›páRo\› ´çŹžvň÷íĎęÉlĚ/Ś7 CřaÖt Zx’Q‡D‚W9I¦sĹ-÷Ĺ0ďčiirźŘ^:jűÎ�?ĹPT-ô7ŐN p˙ť›XYÉs€ŞÚúÔuo—żOAŠa"ÉB--g©đ¸ÍšnÜŞ‘*śä8Ó–ÔŃjRtL-/jyÜWŽQĆ• u‚~….ţĚ!`hĽ>BĐ<éŕđ)#,~ł)řaÓLuŰ*Du=fÍQÍpĺůay±GB鯸BݍEéůr/Ł (Ł­%č<™űÜܴؓ˛‚ŤáΙ<´üçP şg· ďďHa�ob–iŞU/ÁÁ,§ËÖţ c&ç‹ ¸Ąęz¦â•÷űZĐšÜT�ÚéĄo‰řßöźęśAőÝyQĺ*+öVýó&ŞŃÂřaŐŞĐĚLŮíÄLĚĄlëNoŚg"ü©é\=ZJńňŠ ĎôąÔ¦ ßiÍ+vÜb/:túŮš ~^kçY¨D/5Íl�T·�˝č$k}ŐqĽż1—˛n”ł’ňĹ QŰ_঺Ű*Ź"µúNoĆŤÇcjNÁşg4fDĆß7Ń»­Ęt“ç×bH˘Ďüt0Ž ŠTSřaŐ¬ťuPúÄä.ŢĐ#m‡¶ěŘ;†ĺžy,J•"ĉ)ěá¶i�pÝą8ݧ"®ý1…¨Wü€QcCŤS_t7Éř°ý÷‚./ą?]Čž÷ɰ«%Ż /±¶n†…9ŹHă% Ľç‡Y—99u63[Ó¸‚ü\hř‹'Ž—ÇMfžt˝Q›ŮÁXMsR˝ŃA/ĎřaŐŞŮĽăZ Ö†˙I!vŠu›dH®ĹÇ<˝ ‘ź{o¨ Ä<@ĺ<@ěj†C%¨TáäDs[kâ­oĂńÝĎŕtł˙÷ěÖpŞÁ~“?lÎ1Ne$Ä}úE�‚Âth9ü˙•ŐEÖt@VNU}qŽ&._+#ň°s˙Z´Čé‘>Zäűky°ľFwÝź–…Ž`…OęZřaŐ°jß ˝Ee m›ţ¤żň0Ú^;Ł(87ů—Ć ×µ”k˘ühP(N©ň–@_#äÔśŔśmű§¬–(¬?T‹eá“ÁË�2Żl Đ’¤n"-–lĎđźYXě_ü›Ž¬Ľ-o^ť|4� BĎęŹL“W=Tz +HŹ„ĺ.E­^fk<NËɵuÖG‘W Ě![řaÖu2(Ä…C*rçôŮp/Iş¬Ăéű`¶ŽŹP5—ŮTNŚř")ţ9c°„TăÄ8+]ÇĎĽŃ�Ű—Š n…ö86ĐĆú®šdUP#lęKl-@3ˇ}‹!Ha^šşułÉ&ügéĚČĆĚ ë´í%-ó_^ăK¸˘ű-±Zç•Ř áŘŹŠĆ„‹}OggS�äĽ������też/���7cö2˙ř}ö2u‡Žô¤ß#źžE±ÔŁéѨBR†.ź‚;¦â…mBŇÁŁ0ŐRLaçÁ˝'<‘şŁgŮ8fɸ?†�}´a'Ş˛/0B5ˇ#ęË޸áD6¨™iVĂ utľgVmËáőĆ������������������������������������[,t»|(‚´ľÉďW×\Á°q´şőŐóA…°Ť·Ţ3Ä©˘ĹŞé41‰‹^ífŰe!]ĺ°ńMjiďÁ˘V@Đ÷R´/ËZž’“ů¨Ű˘aŮNň‰Ž’sAÔ÷ĽĽ•lčŕDŽţűŐ…?ľZůú ¨ź Đ$»i°#ó|đ »;˙ŕ×4"’â51�����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������beetbox-beets-01f1faf/test/rsrc/empty.wma�����������������������������������������������������������0000664�0000000�0000000�00000056070�14723254774�0020553�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������0&˛uŽfϦŮ�Ş�bÎlú���������ˇÜ«ŚG©ĎŽä�Ŕ Seh�����������������������ÄQ�������€>Őޱť�������Đt����ĐĘ›����� ���������€ ��€ ���ô�µż_.©ĎŽă�Ŕ Seb�������ŇÓ«ş©ĎŽć�Ŕ Se�4���ęËřĹŻ[wH„gŞŚDúLĘ���������”#D”ŃIˇANEpT���������3&˛uŽfϦŮ�Ş�bÎl"�����������������@¤ĐŇăŇ—đ� É^¨P���������‘Ü··©ĎŽć�Ŕ Ser�������@žiřM[Ϩý�€_\D+PÍĂżŹaĎ‹˛�Ş�´â �������������������a�D¬��€>��ç� ����������çç��@Rц1Đ٤� ÉHöd�������ARц1Đ٤� ÉHö�����W�i�n�d�o�w�s� �M�e�d�i�a� �A�u�d�i�o� �V�8������ah�e� �l�a�b�e�l����D�E�S�C�R�I�P�T�I�O�N������t�h�e� �c�o�m�m�e�n�t�s����D�I�S�C�T�O�T�A�L������5����C�O�M�P�I�L�A�T�I�O�N������1���(�M�U�S�I�C�B�R�A�I�N�Z�_�T�R�A�C�K�I�D�����J�8�b�8�8�2�5�7�5�-�0�8�a�5�-�4�4�5�2�-�a�7�a�7�-�c�b�b�8�a�1�5�3�1�f�9�e����T�o�t�a�l�T�r�a�c�k�s������3����W�M�/�G�e�n�r�e������t�h�e� �g�e�n�r�e���(�W�M�/�E�n�c�o�d�i�n�g�S�e�t�t�i�n�g�s������L�a�v�f�5�4�.�2�9�.�1�0�4���$�W�M�/�B�e�a�t�s�P�e�r�M�i�n�u�t�e������6���‘Ü··©ĎŽć�Ŕ Ser�������@žiřM[Ϩý�€_\D+PÍĂżŹaĎ‹˛�Ş�´â �������������������a�D¬��€>��ç� ����������çç��@Rц1Đ٤� ÉHöd�������ARц1Đ٤� ÉHö�����W�i�n�d�o�w�s� �M�e�d�i�a� �A�u�d�i�o� �V�8������ah�e� �l�a�b�e�l����D�E�S�C�R�I�P�T�I�O�N������t�h�e� �c�o�m�m�e�n�t�s����D�I�S�C�T�O�T�A�L������5����C�O�M�P�I�L�A�T�I�O�N������1���(�M�U�S�I�C�B�R�A�I�N�Z�_�T�R�A�C�K�I�D�����J�8�b�8�8�2�5�7�5�-�0�8�a�5�-�4�4�5�2�-�a�7�a�7�-�c�b�b�8�a�1�5�3�1�f�9�e����T�o�t�a�l�T�r�a�c�k�s������3����W�M�/�G�e�n�r�e������t�h�e� �g�e�n�r�e���(�W�M�/�E�n�c�o�d�i�n�g�S�e�t�t�i�n�g�s������L�a�v�f�5�4�.�2�9�.�1�0�4���‘Ü··©ĎŽć�Ŕ Ser�������@žiřM[Ϩý�€_\D+PÍĂżŹaĎ‹˛�Ş�´â �������������������a�D¬��€>��ç� ����������çç��@Rц1Đ٤� ÉHöd�������ARц1Đ٤� ÉHö�����W�i�n�d�o�w�s� �M�e�d�i�a� �A�u�d�i�o� �V�8������a���t�h�e� �c�o�m�m�e�n�t�s����D�I�S�C�T�O�T�A�L������5����C�O�M�P�I�L�A�T�I�O�N������1���(�M�U�S�I�C�B�R�A�I�N�Z�_�T�R�A�C�K�I�D�����J�8�b�8�8�2�5�7�5�-�0�8�a�5�-�4�4�5�2�-�a�7�a�7�-�c�b�b�8�a�1�5�3�1�f�9�e����T�o�t�a�l�T�r�a�c�k�s������3����W�M�/�G�e�n�r�e������t�h�e� �g�e�n�r�e���(�W�M�/�E�n�c�o�d�i�n�g�S�e�t�t�i�n�g�s������L�a�v�f�5�4�.�2�9�.�1�0�4���‘Ü··©ĎŽć�Ŕ Ser�������@žiřM[Ϩý�€_\D+PÍĂżŹaĎ‹˛�Ş�´â �������������������a�D¬��€>��ç� ����������çç��@Rц1Đ٤� ÉHöd�������ARц1Đ٤� ÉHö�����W�i�n�d�o�w�s� �M�e�d�i�a� �A�u�d�i�o� �V�8������a���t�h�e� �c�o�m�m�e�n�t�s����D�I�S�C�T�O�T�A�L������5����C�O�M�P�I�L�A�T�I�O�N������1���(�M�U�S�I�C�B�R�A�I�N�Z�_�T�R�A�C�K�I�D�����J�8�b�8�8�2�5�7�5�-�0�8�a�5�-�4�4�5�2�-�a�7�a�7�-�c�b�b�8�a�1�5�3�1�f�9�e����W�M�/�G�e�n�r�e������t�h�e� �g�e�n�r�e���(�W�M�/�E�n�c�o�d�i�n�g�S�e�t�t�i�n�g�s������L�a�v�f�5�4�.�2�9�.�1�0�4���‘Ü··©ĎŽć�Ŕ Ser�������@žiřM[Ϩý�€_\D+PÍĂżŹaĎ‹˛�Ş�´â �������������������a�D¬��€>��ç� ����������çç��@Rц1Đ٤� ÉHöd�������ARц1Đ٤� ÉHö�����W�i�n�d�o�w�s� �M�e�d�i�a� �A�u�d�i�o� �V�8������a5����C�O�M�P�I�L�A�T�I�O�N������1���(�M�U�S�I�C�B�R�A�I�N�Z�_�T�R�A�C�K�I�D�����J�8�b�8�8�2�5�7�5�-�0�8�a�5�-�4�4�5�2�-�a�7�a�7�-�c�b�b�8�a�1�5�3�1�f�9�e����W�M�/�G�e�n�r�e������t�h�e� �g�e�n�r�e���(�W�M�/�E�n�c�o�d�i�n�g�S�e�t�t�i�n�g�s������L�a�v�f�5�4�.�2�9�.�1�0�4���‘Ü··©ĎŽć�Ŕ Ser�������@žiřM[Ϩý�€_\D+PÍĂżŹaĎ‹˛�Ş�´â �������������������a�D¬��€>��ç� ����������çç��@Rц1Đ٤� ÉHöd�������ARц1Đ٤� ÉHö�����W�i�n�d�o�w�s� �M�e�d�i�a� �A�u�d�i�o� �V�8������a�1���(�M�U�S�I�C�B�R�A�I�N�Z�_�T�R�A�C�K�I�D�����J�8�b�8�8�2�5�7�5�-�0�8�a�5�-�4�4�5�2�-�a�7�a�7�-�c�b�b�8�a�1�5�3�1�f�9�e����W�M�/�G�e�n�r�e������t�h�e� �g�e�n�r�e���(�W�M�/�E�n�c�o�d�i�n�g�S�e�t�t�i�n�g�s������L�a�v�f�5�4�.�2�9�.�1�0�4���‘Ü··©ĎŽć�Ŕ Ser�������@žiřM[Ϩý�€_\D+PÍĂżŹaĎ‹˛�Ş�´â �������������������a�D¬��€>��ç� ����������çç��@Rц1Đ٤� ÉHöd�������ARц1Đ٤� ÉHö�����W�i�n�d�o�w�s� �M�e�d�i�a� �A�u�d�i�o� �V�8������aT�R�A�C�K�I�D�����J�8�b�8�8�2�5�7�5�-�0�8�a�5�-�4�4�5�2�-�a�7�a�7�-�c�b�b�8�a�1�5�3�1�f�9�e����W�M�/�G�e�n�r�e������t�h�e� �g�e�n�r�e���(�W�M�/�E�n�c�o�d�i�n�g�S�e�t�t�i�n�g�s������L�a�v�f�5�4�.�2�9�.�1�0�4���‘Ü··©ĎŽć�Ŕ Ser�������@žiřM[Ϩý�€_\D+PÍĂżŹaĎ‹˛�Ş�´â �������������������a�D¬��€>��ç� ����������çç��@Rц1Đ٤� ÉHöd�������ARц1Đ٤� ÉHö�����W�i�n�d�o�w�s� �M�e�d�i�a� �A�u�d�i�o� �V�8������a����W�i�n�d�o�w�s� �M�e�d�i�a� �A�u�d�i�o� �V�8������a6&˛uŽfϦŮ�Ş�bÎl2K�����������������������������‚�� ]“����‹�„����ç�� ��ç“˙@�� ÓĂĎ6Ó‚}mŕ ;€¦Jŕ$´ć“$ťÜ$›ě ?�'qU1Ů1%¦4©€` -I$Ŕ�IPťc Ô I¨R„UI10U5R’I‰¸IjR` Ů$Ä@«JTBTÓI�Ą1,E4” ¦�”Ň’ŇŇ€I:˘„bfĄ1&C�›°�` łQR@H@1Vš`𠆀H “�:ÓP@¨ţĄKĚI^o'»@*@�7C˛@ •ÂL Ŕ$¨Ä %©--¦ši!T€H””żHEPůjH@&B4ŠJJÁóä"„0Ň•…ý Sƶ°|µALU2”’k?ßí/¨/ßżĄ,B(âýqôŠ1BŇĐ~¶¶š-ÜKlřż!+t„H˘„„ĺ8Ť H¨‰ˇh-Űߥ)B Űô-P_”PVéCäQný"—év0{eýIBŇŇŞ¸V‚i~·ĆüPč6ňµA \@ŃĹA KTżÚŰëpăĄk‰ż, ńřRѤ ­żvŮFPµB(âó^®oă§ŹÍů»yZŔUÁ”P„ă(Ŕ\KYFP…»}ż÷o¤ÓM–Ę?_Ą Rýmřâ·> ·× p?,ŁŠÝúđ´ŕ*ŕ}Ćý–­ËKx)~üţ°óyKďŇoéăýұ~ěůĽ§(đďΚĆó\yďű§ó¬sXß•?´­-˙µ´[–¨}BŢSJŇ8řlj9ďůĺ>Á·÷úýqş_ͧ‹őEľ±ř˙'<!\üë‡÷ŕ|dHZýeNÝű±nýq§ń‹ucgµce ŇŐŚ˙Â9ďHý×R˙ŽśöʸÁ6óTŕŞÜúXř%[ü­ĎéĘ0p~ýqŰíŮě\C¸‡ăEżÍń§÷X˙˛iŁV7čNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN����ç��J ��ç–˙@���üYż�pánI›+çâ«$@ ,ťDĂaa $�N3Śë�2�B’�R¨ddLˇ!# MÉŞ$±jmD BED U5 $ B{AI„›  „™e‰0[ˇ/Ę ‚ ’ţPR‰Â ÂP±jI� ) !%'e .@„‚*Ä„şÁ(0T€–€h' H¬„Čl’P�ş5&CHA$Ť];ĺş +Č2g`H‚@*$j‚¦˘�™ ¤ΔI ŁJi.¤„†‚„_aĄ4Ĺ(U•PŔˇ%$E™@/а@)„R€ú•ިRţ„¤ˇn)·%!ř4PŠiH”?¤˘·@(|¶V’_”(Đ•‚Őßo $ÓI¤!ľXyčŠhˇ…şYHJךĘCĺ‚]¸|ůóđ¶ůcĹů!iňe]! JC ńq[ĐíÜx ‚ú‡ĎŹ›}Äž.?ÓńGšˇn€•Ľˇő˝mö?ŻÍ÷ĺnă[ăü“E’×çůq'ʉinÜűÓH¦•·ř+Zˇi(ă·ˇkóü˙I·ţ©Zü¨Zó_˘š_¬8–éü¨ýżvčÁ]uŚŚĄmn•ŻŰĺşŕ޸ĄÄTR´íÖ­é·Eü–‘”í‚Dâ·ţÍżhýĺ+BÝXőŽ·E8[źäKĆů)üß~FŠhČ“ňtżä“ć˙4ŹÝąÄJOź­V2ŐcˇŇô`$[ë=żYě·ć«Ţ˙P‡˙›ĄßţoíŹĎ–ŚöüíĂňČŘ nßűđ�,«ľ/çř† ź‘\2˙Ç?:ŕĘOçűý-¦śöókoň—ő ţ,JÓďĐ~µ\?›ěˇkŹ[ť˝cq[ňśÇĹGpńŕ“ô˙ô·gçHˇj±–čˇŰçżóÔţ–‹ĄÖĘŘŔUŚNNNNNNNNNNNNNNNNNNNNNNNNN����ç��x ��ç–˙@��o�ćt,Ď�,el˝ŤP0uÉÍŠXq3±2Ľ1 j , ™Đ Č@ ‰™i^Ŕj†PhAˇ%€H!°@�Hé‚D0QT–—Z P %jD €d°‘$$ RQ%BĹ‚uĄ(Ii’S2˘ZA0%†II$))2 (HW”ĹBAH1YĆH ĹD&RJĎ@†¨N@b„ ćć±%S-P é­“ÎK;Ť˛DH-;cJ†4CBćČo I É«,`-S,Y%(��ĆĄ)¨)BVÉ4%2 `Va(”ÔŠ¦—١YÔ Rý1@AL�‚)[9”Ąý.ÚŃV„n(~ü¬KäľZĄ)ý‡ô¶‚„>~ú—č·ŃÄ·ŮH˘š_%úiÝM.Ę]źŮK°´V˛ž+sô~_§`;t㎟ߛü°c[źOšă[t-·AĄ ·?üż|T>ˇűúBŇÓęr•Ąˇ”­ńńń-żý¦ŢýŮ·[–Çý>ót~ż'A·Đ„%lÓE(?ş0çÔń­şi )·ŰéĄňßîŢ·C÷ÉnŔ@­ĺ4qşR#)O˝k(ZvĎż:< żĘhŔT"±ź-­­yĽĄ6úr‘Ĺ@[Á*ŤXČů­~ë«3Xöü§=˙tţ¸ż\nÇÇ‚WÜi⦊Ć=©(YťXůşÇ[[¨Ś˙)ýůş?+{ !öt˝/żKhó\vőłÇO„Q•+yĘ-ŹŔUŹ‚[ucĺYň[Đ0ňřqŕ?5€˙tŕ”ĄÄ\úŔN‚‡Á<A? í­üI4?viZ/¨ăEp>§`0ţ¸2š2•Ą†{Qŕx6š>ŔT—ô%ů¤ş[ţđü_y»{7öáć폷ńe)Ę_y â × şŘ즚ŕâşĆ|NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN����ç��§ ��ç–˙@���đ%,V,XU2Ľť˘ d†´µ°DôZb`°D€�« ’ZŇÄ’XŞpÍR/¸I… &`c †˛X¬ŔH1…$�Ś)1´ĚJI,ˇ•�2"MK˘ A2˘B`’*!&©JADŐ€ë$Ši–„¤‰ E&®‘(hŘ+D¤„Ą!¤Á$‰``:–h�&FÄꤸl¶�¶ ¬ÜôF”aŘ…E„–ą‚Cv[0¤á@™Ť00Š&¬“‡)EděEBť�MCQ 3P ľ4R EB( ±JKôżBPPB0č@„„[‘M/ĹĄ‰ Ű¨J€°¤¬Rüˇ`ËTţęOT©QcMĽ°–ż-ZZĄhŇý nĘ-ôń:\ka<i| 4śRš_ŹÝ<_—íb<kOÖ–ź§ňZŁó)â 6V’|ŃŁňˇő˝?®%˛‚ŹŕŇC˛ůňŇÜ­2Ş-ŇFQ YJÓ˙Đ·Űßq&„—Ü_Ş˙ŠźÖPVŹäţÝ”y¤ĐúÝćß:˛M¸'÷€Çî”ůĄŞE>úüÖÓůSÇĹćź~ż×OóÔ~Íp; yHZĘ ˙̡q!ŇƸ8风ßůÓG®/É ĄŻÉn‡ď˙kHóHŔ_µĽýőpşZ…§ÂÝ”şSÄźĎóăüߥm÷xÂiĘ_;t8µn®§ňO8‹ęĆ}”ń~¸Đit˛ŢSźż[¬`˙`޸pD†¸„sĺBŢP_× P”Ö=Ž·BŐc'(ăŹŐcy®'ŐÁ€˙očˇ˙p›cóĺÁ^vkLŁţ_“÷KWQĆ·‚WŘ ­Űź×�ÁV}F §=đ_×ŢSBŐ4ľđ‚,Ą(Ŕ~oóÁ*ÓúǬlů-ÜvÇľqNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN���������������������������������������������������������������������������������������������������������������������������������������������������‚�� ]“ą���Ś�„����ç��Ő ��ç–˙@���“ąĆ_­mšV˙ b†­°&�6˘ H7#@&RI($L™&ú»’@(ÓŁ" h&gQ‡0c "`éě “†2QyJB%,Dš€&`–ŠČ¦S„ 2Ě@0’i jH”Ă*J 1 (‚i¤”  $˘©Ę ÂJa0BJI%Ą­$X €H–„ëD§ X©łB «-#@Á!¨JoV ]G@ĐcĽ«-TËNÉéŞÁł ±Ą†®ŞI1Q‘ąBJ… LÁ’…€Ŕ$„îši¦Ą@“JÄ- �.ËôŠQB@~„HDBŐ%4ST(@JIJء…Ąˇň�XżDS+eóä!/Č~•şBĂŠ”#ö´r¦„QNâ}Jj—ÁŰż·­ŰźŇ‡Ď‘@vxŠ-Łö4>~mĹŰ-SÄům%J-ëU_qţü)4&»)ZVô‡n´·H~·ůŽ?ŇVŠÚ?'ă(O@}ú®Č»gĎŇx·ůŰ­ô¬Vhúâ yíúâÍţ5żÉˇa€é·ţNÚś!mú+źÝ(óKĎ’HĘCëzVČB?YHâC˙4űŹôűЏO›ĄmbŹË•˝Ňďíß´Ž5¤ľOéÄ>SůŰ˙*P¶ĎÉőްĎ|€©ókvî%ľ/αčˇ8Í­Sű§\täKůţ‹ĄóÝëq|·B7K­WOçĹÄyľ#ű}ůůş_Ł=Ę“üĺ˙›ZĄo(·Óo¬zśCś÷·ţřřÖ¸ü#ž˙ľ$ŰÖ–đéóúÓOšĘyĽrŠĆ[Ęx‡šűĘi·×QÇű§Í[¸˙!űüż^t¶{~ř––‡šĎl‚Kupe<o­Ř)JÚŃů¸‰Mc˙>µů­QNP·ů¦ŞŢ%ů˘źÎ¸ Ą€NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN����ç�� ��ç–˙@���üđ-TŔ×ß˝GĂ©=ő�¶4C $š DΠ&I:MOü�`áÄ´€6 ‚FČuaĚTAc áA@a@-2E@i0–,P I$¤ÔŔh“:I‰-/ŤB(, áá´¤ĄcRL Љ(H©JMDÉi@š‚�ŃIQ¬6  Š†KL’™‰‚PL„ÂJRH4Ë@YP 5€" $�†ŔÄ iJH`�$°fb`liĚC`)ŰĽŔçżťŽ¶ënć6€¬Ë ¤†5@Lµ hĂÉ$‚€Ä¦ŠĄ`+:H„¬QE dÁŠQA¦…€E-|ů4�ýúh B‡ô‹ú(BA@Ú)<tĄ/–ťąi~ě?Gé@vV蜎°˘—Đ’‡lú«÷Ďß&„sú mĆŐ2µBŇVßĐ·EĽ¦‚•˛š-ÓúĄ Iâ/‡íoň||Őą[ŃG1ůM¸RŠRVĂęÓo~ţßME4”%oŠ•ľ0i|´”RµÄ¶-Ô-‡Č·#ţ˙IFă<HvP¶üŃĆVß'ÓTQ€ź­…ľ'ČŁ‰6úüQűˇ(ŔK_·Öţ+ć(~š+…ő˝tľ˘Ś~–Ăő†Péd?|?/?*nŰň®AZ¬e¤ůżŇ:ŰËî.$śö§Üg˛xť›vS”RţŢ·GZM6î?× ‹gŹÍÓXĹőcľˇ˙ĺ”ńńĺtř‘ć––«8%ü˛śoBqeżÎÜ˙× |źÍŇůJÝż‹Ť4-QĹ\+UĂCä~UŔ°üŃ€łŰóŔtŰë5Ŕź4˙öÝ-HăX`“ůĽˇóţ:Ć[ýżđŽ~ţŚ˙ňýÖ>{ˇkÍW 8–ň‹cÜCeµżÝEx o)Á1”»/¸«ő€©t˙ź%câG€€¸żóv˙5ćżo©KęÇ(Ę^NNNNNNNNNNNNNNNNNNNNNNNNNNNN����ç��2 ��ç–˙@���b—•aEDŘ%­iBLA2�d 5Ôf¬�vî‰ ”Ő Kd™ @ :„€&*’’!¤!bÔ"f@‘.%©˘¬‰¨ŔZ JR a,’CP†! %3U RDI%×Q�‚ŞÂ$HA@“0™(X�VPÁ 0¦¬¦¬,Ś"€*‚°‰@0RK¨ОІÔL†„TH‰U% ± (Ř ‚ĂľLŠ˘ĂUkI ͬnTbe‚.ÔČÂTs!¦éDư€ ¶Z 1`‚  Ja$¬°Ů k%Ôś±~ý�™5|¤Ąm4;jΗéM)$iH ”>R"˘Â“Y ŇE @[¦€Ä)( %ý55–ÖĘ2J¨·Qý 4!(?·Đ‡ô¦ŢŃ&*>âvÜkOź,)BR’µBi ˇi`·JRůú©OŢčEDŇ(đŠ?K||PýúpńXüD`>'ůN}oăý?Oäč+T-¤ź ·&Ţűe?Ľ˘ßúă‡ĹG¸ńRřľâ§óţ OpřUSoĘ-ő(+\h}O〟ż¬rrŽ7AFP¶˙">„e]ľ[âü‘á,¦ŹÉĄinś§)â¤V7[ź~¸éE[ř©âGQo|CĄřť’VÇża÷Q€żiGëöâ˙ä]>Uľ%·ëeoŔ@T�ë…o)[[|íčýĺ'Â%·>óVÇţÎ _`’¸JŰţ+{®ÁĎtńţíÔ„‹š@óx6’˙ňZ·ĄmOďÍ­%kÂ.źźŕ‘k‰ńZJ|×ć+öSoý­ľŁ÷ćśDqö·ćň‹c­Ç÷”ĺ Ăźy˛·űĘ2šŕŰć©®˙’ŁÂ+VúŕĘ<HJŰ˙ĎőnŁÍŰ«óvVđmáN—[NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN����ç��a ��ç–˙@���µđ" \jŽŽ)™ťFćËZX đZ`Âť±X ËÜ.&©‚j·bÝH–ť€"6˘ˇBHH @J D2 T A$ ŃH$ÓHŞ`ś"NČÚLaU5v "¨’^ŞĂP ”’Ö$еACâ0 BIŞ`Ô(’ZŮ0BA�D�k%ĂH~JBX„%$€AXšpę ”0!)ł$dhë "T)dh–Ů�93¦•'d\KzÓ@lCTd“-3vpć @%„$E@‚Ě"$–Ő�LD˛€ű äBR”ˇiJJ*Đ) ŃT† ˛)%AABBš(M a ¶ž:e$aR)BP_?"«ňZ’€ěŃIH ń'ŚŐJV’ýń„żˇ!SkkBŠżI@ ¬úvŮ+e¤˘›wô[¨â_µŞdţäĐSů-ĘŢ‚x­ŕ~­áú×mÉB_1™Kę8ż@RŠPšľiűĺżŘĘâ Ň€ż* ĐZZKţ3ů#ąB_ FÖ˙ÚÚ×ćűőE9HůËi‡ďż*xÖżO«„>¸Ö˛•şŕÇú[/żYO|šióTŃOčÓ”S””ńŐ„ç·éin•żĘÝ”qۇínÝáŇrůűĘiˇ6ôŹ×ę±ß‡mlv{qńńĺ/ß[ió\kYBÖů?4ĺ±ŇµÄůi˙äůBp°IÇů?~µžČĎn5 śĄ6ęĆĘşü펥öQ€©/řźŰčŔ^iŰĎę‡ůRZ`%ŞĆĎ|‚[uż‹=|#žß»~R˙>J©ý[ň‡ř”-q×óUڵoĎ5”¦ÝžÎ—·`•–Ę+śš”[–­Ďßľ¬u§mć˙ÜNꍮÚŐÄ;çáÓđ}‚QoüÍ»Í�NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN���������������������������������������������������������������������������������������������������������������������������������������������������‚�� ]“s��‹�„ ����ç��Ź ��ç–˙@��Ä�ň˛AP{ő¶ges@ ˛ZŐ5¶Á�ÉJ ĆŘĂƄČ'´„•ź H5 Ť„&*Nżä‚‚’¤T`b J †BRM ’„ŔD/HIŞ*%t. "ЏA2iT"DH Ş™€„’‘†[Q%• HŞ-RL€ @hD ¦pB6@Ŕ"óe KPI ş,DÍB`D@… =ŢÜŰŚAUĂ•dʆb eÄ@D˛HiŇBI’)nä,$JDˇ¦—ÉX€ @BL�ł4‡Č%“)&’”KúJ%lÓĆHĄôˇ!mىvÜa4ŃI/‘ űđřĄnˇ˘ŞVĐŠÁ}ÇHCőĄ´-ˇú8ź…¤P‡ÍĄő’ú‚’·Ć„ĐŚŚQEÉK°?°ů ·Ük°ú ń:´-­§Ź)X>Z[âĘKyO…­QÇKűzŰçŔR•ĄŹäµEĐ8ź?h·ř“úĘ2—éâA·ľ+t¸‚ĘVźţO˙TÖ„mߢž;z×îŘăXŐŠRţ¸E->E»Ľ±—Ďé··ľ·›}»óŁÂ˙B„P˙Í;~%ż7\=¸©Łř_í÷íkň[›ësဩ #=©ĘÚެĎÂűu4ľ·e."Pr…»}4­­3Ůýpĺ’›wâ/˙+{ünýq-ç˝)•ŻŘüż\|x%ü–ÖĽÓçĺ/«ŹÍľ—ÇÍţÖ‹Ąµ”e9C§Ţ­?°µnóuÔ۰?´~¸ëLĺ/¸śC`.7ĎýkôégÔWŢ{~­ůGäšáŔb±řÝ/\JĚí˝ýpţíÔ­ńV[EOËÍ×Mpe6ď[źćźçÉnĘłÝk=ň•ŞRâ"?_·ř ÍÇ{->tűńe`/7ű¬{pNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN ����ç��ľ ��ç•˙@��O3  -đ9˛ÄÉ×X᫣ZŤ¶Aˇ’Ö):�Ő’T««Ŕ%A€$Şd„!±LA’‰ŠĚD IJĐaIJŔ¤™lĄ)% BHLŐ ˘Q%0&Ş%0‘I šHX¬RŠQ‡ I4˘RDĐCe IiŠPł‘&Š„S5$„–�š‚˛ŠP”‚* e! �A;©Ů”š‡DTMPIh"PfÓ2 ‘†XVy“¶@� Íć$Ŕ‘`4s0W–X[$á†HbgD“˝$UlŔ„*$€‰«R’¶DBQBhA4”?&Ś© „¦¬aż/šQAD´¤”š"„ˇű°ţ„-x\‚R*ĐěłIXRí¤RA|š_ r‡Ä qˇjßJGJh‡Ôż¦Š©(·daGHČâmÄŐBBŐ/¤¬¶âMąřZ}VÜ·ĹBhâÍ~IZ)§(ă˘ßM ˇňM [®ĎŤ­ľ ®Aş˙’Ý8ö(ZŁŠ—Ä[˙TëN€“űýżóX ÝnüŁóş?/ÝąPiM?´Đ) vĺ®*ë¸B/Ý˝óühX§őG愾 Iü‚h·×Ďwţz/7ĹűđşÇ ­ŰżvŕźÉ÷…q[ß[ë‚ŢůŔ˙7ů„qľ(âŁöů´Śü[¸đ ż 0Th·~GÍ%˙ZĘ-ţi¬ů&‡˙§Ţl>[Ŕo˙<ý87żË͢—öęVĽ×€€Ř?ýřGőů"śZeŇř$Ąýąý¸Ö7íőpq>q ”g±ă·`•?¬öýWç”çĘú¸2—˙´Đł8·€łŰ=ßŕ7őŹo·ÓĹĹ€­čEců¬§ň®汣ŹÍş}¸đqú ¶ü…¸ ¶ęÇĂćźV™q Xßž® ÷ZŔh‘'äř¸‰jÚĹţ 2•¤á NNNNNNNNNNN ����ç��ě ��ç–˙@���őő1ŕ E.üĆÍĐwa(,± ­ÇB utL(fcDÁ`. Ť�‚%ÉÚI¨€-”ˇ H$„H�’ŘI’S&$…RŕI,™)¦ZR¬6˘ÄŠ`BÔ”„‚)Am)HF¨(C&MH u(”ĚĘT$‚tH )A((�Q5S'üɆÂAQ�ĄI�”3q†`$QD a·S57$Ŕł-Đ%¦ŘÜFŘ,ŽË&o­2ĽhĚho`…„2I4Ča«’ČIĄ a楇ŠS‘蔬IŞěŐJj!4Š · $-ĄôBÚ Ő Jjżă(ŽFO•ŠPŠV…/¸‹çÉ·„„P_Ë'ȡij‡îÇţ.$:)âG[H(Z·-:RÔÓÇBÁő˝l[ĐůnßA§ŠÝJŇx–ę%(+@|˙)+’]šhZKě%/¸że?şZ v˙ČPµnăĘV+šÂÜé|Ą—őŚh§ń…ş€[¸čX iĄő‘-U¤~­ţ$ń~Tż~µűŻŐc?ŔiĄöS”PţßCáNâýůĽ2źŕ­;`>ýľđ‡R-Î"Ű©tsďĐů&ܶ˙)Z[3”żŔX%ýq­ĺ/©MüŁöÓůO„Vč9E4ĺ/ßů¤g·›®ůRšá«ůŇ˙(¬l÷|°[.‚•·ŮFUŇůŠ)ŇČĎj_×ęÜ·ÇűŔhđµ«vPµćßWQůşíoŤýuĂáX ő»ŚÓ‘.P¶éd~·-׸ůŻ6•«vPét~uŽâ'č[“ĹĹů-Ąmúp.—KěöOęßMĽxA÷ě')E6ňâ/š)Ę?#năvË_«r?\N–}€­Ëx*Ę?UÂř× Ýpş^±Ý-H}XŢn´ÇćéI”çÉžç(˘„eĂćźţ`NNNNNNNNNNNNNNNNNNNNNN ����ç����ç–˙@���ç�%ř,HżA~”’Ĺđ!˝ťf%°ÖťĹíz ŔXd€ $¸uŐ€PČ- ’P ’ ™©1 ’J ˇ!5!µ %‰L€I Ś3 ŞDCúĄ¨–ja†“�˘Ş‚ś$ `©…T’$–¤„ �´ ™0 ‰ ‡Y(L ™aP2Á-;*j@ É–U2PvÖT@Ř€€Č&bÉhh €I Ř�´Á*†ZŔş[- ˛ˇ�Ä•6I6É��]XD‚Ђ٬ň’YC¤¬… �S PxŠPP MRH¦TÚ•¤ ˇhJĤPěľ ˇl-?B—ô»)ÂŮ„I¦ŠŹ˙oË ËúM (ˇ&š2…¤>-%+eb”ń?ă§Šßú˘“JĂ(ĹÇ/ß­/Ú]—Ë|r)ˇ FRý%`éz™Yž4')˘š-çŤ"ŠxżIâˇúÇĹnŔAńZZAl>}C÷čn[ý-Łô‚´°?§á÷Rů˙.7ő_ĺ?’+Íe  żĘ8‚ßhóUŤÄř>ÇGě>Ŕ_—ę±–gV’·GçűŔTŁ`'ß–SO·ţĎđS沅Ľöý?ýe9AŁň|!Çćę­äIn¬d»wëC‰§ôýňŢPµ@Jźĺ˙n!Ý,µ”-ľăüÖg_ńŇ_  ”[Ń\cÍq`*-öŕ2ŠmŹÁR8đK揚[ăü˙'ůí€ßÖ3©óO¨~´|"·ů­Q”q¬g_~O‘ŕT¤V6 (BŢ{ y·?¦ŘŕJڤ[˙+{Ą_Ďß-V7ęÜśĄ6˙ËźˇĆ˙>Jk+O˙/ÎÝÄýóĄĽŔÝĹ€Ý/ů× ˝ilŕ,Ćé~,řF‹xüë(§)§Ź)AŁ´ţ––ĽÝce.—ZÁ"�NNNNNNNNNNNNNNNNNNNNNNNNN���������������������������������������������������������������������������������������������������������������������������������������������������‚�� ]“-��‹�„ ����ç��I��ç–˙@��_�<bË7öT•ú>×I°Ć44˘{`ŮRAL—TĨÉ€RĆK@*L)H-`‚ Lfgb*T 1 DÄ�)0™„IEC%0"0¶üeé@v%ţv+"”TŔ$Ś9J*T‰dDĚJBh%auÉ�5)(5!"ę l"©’P„[´ ˇ%ÖM!¤Í I(©8A•Ih5jBN  ™č‰Ń ć[T4oa€ďLlŃŔ“ŰNÎş0fÁ‰$3¶‰)‘TĹ‚H¨v4B*0ŤDJ�)(Ja"­C@(`)!4‚iJA|˛‡ôĂó oŔ‚ŠtÔ,_ŃJčˇI|„RâůŰSCäŇA)|ů)˘”şRí˙B_ŇJ·n[Ąin(Ąn—Öţ:V“”>|ú©6íSúL­»)EDÓů V–ř±đ»z4?|µGĺ”Ón«s÷Ď©[GJ€RÇć˙€ţ±‘NR] _ŞĆ·~ř€âJßęŽ4­>|‹u (·­#)¬oÍ÷çEo–đV¶2Š*üŐ4ş ý:RĐRř$~źŰ’ţ¸˙^k‹‰÷qÍľZvüKKO˙Tľ[ˇ.ŕ|h·~Ń~‰âqö„QX߯ßçnُ‚âýţUŹ”q­,żÍˇmúŢSNSůQ”W (Ę?*ǡo=ßş2ź`‘nßNZă[Ďgë\HJßš}”V2ж_-Űß­­"Ź ˘´Ĺľ›v Ź–ż+rŐ˙F±íĂ=ň•§Ţmý!5Ă€ň‡ůC±mąÄ:?o¸“Ç€˛—ôţN‚JÇýţßÍÄUŻÍ––˙čüÂ×…-ŕ;cÂĆ)â§őŤGď)·ŰłÚŞŰúmÁ4x �+vúÓ—çć˙.*”ľ?żĺV=EJŕ|ű‹ŽŘďÉbšŕý~¨ăt°NNNNNNNNNNNNNNNNNNNNNNNNNNNNNN����ç��w��ç•˙@���5řYŕ J•ţ{lŻż˘Í*'ţXľ 4Ý�µ2€ ¦I €Ölť(‚ŘCś8 A’tMY$Rś3(IK“N’ɨÂA: ,L�LAiÔ(&BAĂ2€Đ0PU( “LŐaI@$B+2„’š€Ş %™(š*U$Ĺ&I5R€DЍ%�Ňě‘%!¨B‰‚P!'ĄR„ŠŁÄ % 8S!�AA�AfXPµF†FťPŇ"Ű:Ëš¤9ʡ—¶&H*–b ¨iB@HBH ÂH@4”¬ŕUv@j0@@ %‰h„PýúIĄňÚ(ĄŘ«I¤…´Š$Ë÷áÓDЍ�GÂŞÝ?[&•ľ7d»4%nŢŠ*Ň”˘ Ďí`…‹ţ*_¤Đ¶8Ňýihq­ˇinÝú lZĄ˙đ´­->©ĹEş˘(éă¦HJxÖčâ[ĺŞSJń$Ł�ú„q->·ÓOď÷o}ćźäKK÷˙™E/ŠŇmëO˙>4SMřéJ×ěeHNĄú]ÝF –Š?5µ«sëz_Ńoói âBş c­›}půşPŹÓńúĄĐ+Íe#)üźŚ˘Ü´¶üľţ<FŽ.>:ý McŰÖĐűŠ”[˙iFRµGďőXůíOäź7žé Ć—á˙š[ă ăZBmâÝű·»o5žä?ń'őůţożOňŠp?•6ńlzkR‘ćĎ們Y[Â\DüËęŕ¬t&±źńeĺżËľűÍçµ.–AĘk÷›tłü"ţŢ˙ôx°é÷›ýůĽ÷/‘Ĺůŕ.?×ĺ€˙\IĘ-î—[˘Ü˙ŹŕđŽPúšĆ}Io>L¦žô×�Ęű®H×ÉÁ/šŔUĂ\5Ťů`<ŁxBŚ”~˙vÇ‚éoÎÝ€­Ř%tąŔN!˙_µŻËÂÎ{eíÇZak=‹ěHN����ç��¦��ç–˙@���bđ#ŐŘňsgˇuÍ"HÚ›]˛ËĂ&;b&#`ť™¦‰,n€h$Ő«±I"%2CĚ Âpŕ5; ¤™I&jÁhh@I-5Ą0Ą „H�J" ©‡EM”ĘH�ČKeŐ)(4Ő‰TH"”dVDK ¤B@NA¦fU€�d¤‚RN’dP–Ą«B`Č„¤ÄI %’Ě&\’K"Ú°7 T¨lžî…XK™¸HřcR%Qˇ…›�É1 !&bZL@YŔ¤“2Ň�HJ*! ™šP±Á¤¤˘“ XŇPL>J""ˇ „±4ĐE¤Rš( „>�4Ő4¦‚ůlńĐ’°©Ä”Rú i Kô? ‘ BÓőş2KńRšh[âĐJVç(ľ¸ź~ÎPP„~Ëę}oĄŮâD”ż(ŁŠÜěRšÎx±|ě%Đic\ XŠGR·FDĄđ[ęÇvÔŰËô[ĘRě-ńţ’ţßBQGíij_~O…ş)Ďk}?°ů9M5QÇoˇŘ~‡é¬z´¶´ýńĹGä< mĎĐ·Ĺot¤óŃńQáúMâvĽ(ŰßţżIZZHĘ0(}‚»~®Ń ć©Ę,Xëv÷ëyJGéiţ•Ş?<hĘ_ĺ6ě÷âăŔyFăü’kk~$;/¸°çů`ŮúĎ{wąŰ~UŽ˙ő\[\�Ö5ąţ -ô,Ę Â| _—Ζý-§(Zü–˙ŽÝ‘- ¸ź~bŻ?;u¸řEóđú‹aÂŇÖS€żKKK3–÷HÚ;}přCň[˘±¸ß罺ܵ斖ř°·>Łňýŕ4>¬t?Ćż*x°KűOďő”:~ĎwŢoµÂ·eĄĄ«çKţĎ…­ř+_ŔăZ®ČNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN����ç��Ô��ç–˙@���ďđ*|¨ßWŻ!˘e‚BŞ( +ÚlI¶D5"ÎŔ2AVTŃH10dACDÁKpŠhĂŞ[$NŃ �Ú€I*ÔBI"Ä Ą¨‚F©Ş  DĚ  [ (ˇa ˇPH/é�‚$Č2PA‰I(�A‚@! „§¬[Q`�L‚° @¨·PDU ťÁI Ń”…I $A2�CZLČ2‘ �� I€2bR©Z4nľÎúu†††BŞŮ`›•l)"[«Ł¸Â@’&!bL8`Ą¦PH1NŘR”Ř:Š�Kö @Ą Th%úLŐE, JP]‡eZ|č<E)} S„SE6đš&˘VĘV¤Ń@[ˇő&‚·oăJŇÝ'Ťú_~«7ďÁ~šíö÷Ŕ>ˇ­ľvô"Źŕq>?› TZăvôš8¨4~°î‡ô Š/Öź˘źŰ·|“\/ĘÚŰę Ó÷ëX*(JÚ”ń!úKoŤ‰ BÝĆíé4ĄúŐ?Ą¸Ŕ\Aň\?ŻČ Ň”[‘A·Űß­ĺOR+… Tˇâ§÷”бý~T[ż_§ütůşEGô­Bm˙¤ľ}n·~x [|ž$V5şŠ–Ľ#ĹžĎݶ -Ö÷ÖęQ\ Ŕ´·ć­éZZ˘ßKţ5żĘßnĘż'ŐŽě­e)[üĐţźŐąŘoŠÝGć|ŢPµ\ۨĘ?Đ´ýnÝžéýqĐ´µůŕ”Qá׼>üňšŕŔ^jŹĎ"9jÜúž:áĘ~–“”ůŁžŘ ?—ĺůĺ6˙ÚÝż­$'ôŠÇ·­[żhĄk=˙4W„<Ó«TŁŽ‡ďłäâ[˘ź×ë=ë¸UÝąőżÍÖ1ý8‡Áµ$Zq _ľ® zاͭҊkŢýĐV’ýn±«„Q”yş_× ĎpűÍţ°Lŕ'Ňú…ż~te/ÝŠŕÁ¶¸2€NNNNNNNNNNNNNNN���������������������������������������������������������������������������������������������������������������������������������������������������‚�� ]“ç��‹�„����ç����ç–˙@���tÇ€€ +WŐ7÷ Žř;!Ą„@č$I¬Ó*ĆÎÚ–€Â„bál.dD”�P"K�a$Ў!¤Č0X„Č€X„ I" RQ)&DÓ �J ‚V “`RŃÂ(EILŇVx1TÔAL A& €°BeŘŮbDPł„¬@T¤¦$ŔŮd´B!"˘V0:5Du¸"J$CH’@fˇ›$LôÖImELG`ă™,h,föĽI!®»€„�f‚„j2‚ B Ă’P@ŞtŚ*•Z)4&Zů&¬PV)JjP‡ácBÚ’ ”>&‡Č ˘ŞÔ—d-ˇů¤Đ•ŠęŠ)ă~VńŰÓĆ�ĄůăĄđ BP?r‡ô5JŘă~„SQl|•µŞVđT˙#;4-”¨@ '‹ŚCę´Ąő!ŰŇů&šhă„~T}~ÓoÁRĆš8Ň‹zݸ¦€_,S”[ź~Ö˛š8¸íÖ쥨ŁÍżK÷ö˙ÍĐ(·ŕ'ď˙+ćëň)óO©Zt·›4­%#öů AĐV<hâ+tR_~ÝŤi(·?~ž%ľ'΀„ŁľNQgíuÂůkÍV9I® ÷â˘ßůżýż ţPŠçúâ[·~Ş~UŹ€¸˙oč·Ö7żôú¸_ľÂâü˛„Ń”!iÄ7ćégčŔkn"żă·‚‡n·”ľ@NRhú}K÷°>oyĄ¤ţgŽßXÉâ6즜ö§"T5ůů´qŰđKJ­?·‡ßżĚż[üÂ+Ť$Ł~k_ť)ýqe ˘Řrú‹tŕ/ŰĄĹđąoo‹őżĎňtýnÁXŐĂžţh×p>ótţht»üŁś^n˘0šB+¸ť?řFśˇ(ĄňßšĄ.–Źáŕ8¸xčMc­y·ÉOš@NNNNNNNNNNNNNNNNNNNNNNNNNNNNNN����ç��1��ç–˙@���đ*˙—ÚŢĹŃłň.ÔcŐŤ Ăgm Ęł53($”˛2A!˛0Á«!—6–ȆŔEI  Ë SwT$,D¤T€K�ÔŠ¤0 ¶€)‚”ˇ´@!˛ hM@‰¨Ó„ Âp’u �Š’ "“%5@ťHHË‚BP†msI€Ů� )HAĐĂu˛RÂ@:%AJp H%¦"MÚd¶a¬ŮDÉ`$Ŕąa[ˇF64\á­ąŤcPeI$’Đť-iI� „Ą(¨„:BA€±BBP/Â#dĐ„Ňţh ŃCĺJhĄ»*,@|—A)ăM)BÔ"”ş�M.ÉYë\KA# ń4%ŮĘúVŤCO…—ĆŢ•[E<KtqŔ¦˘(·żqCęě§Á_¤?~…¤qR% óâvéBRč!ő6çȨýkőJ2:VéBß4ŰżT-»e¤Đ·o@vÖňr…żĚľYÉ[Ęx“ÇůŰßM?~·FS”~tţ–¸’źŇEšÝ¤Âۨ¦‡ăÍŠá·§ő€˙oáKűpâ·qş[‰l---HŔoĘ×ć([Ł=íĎí•®?ŇÚĹn®PţŢŠ+ňĘ?vçůK÷üN!†kt~i– ň€éä­ÓXĹ/–ë/üÝp`'JDeţë÷Ţ·ţx(Č—ňFRVčt¶ S@ZŁÍ~Ž{[ň®––ź8‚EpŰ’í­î–·ń-ÓnOš®ŢkYCďĐt«c°ŇŰî5§˙şŕ·?ăt»çÉü«ÓöRâ- ©·›up[Ťą%6ăE±˙żĘ¸ ŇE_ťcŰđl¬oÍöQXÜg­­ŕ:ĆÁ*ßéiň<!‚JĆ.ĆQ\6ă‚\ţŹĎ(üżUÁűqoýyşá¬jśT xđW”y¤eIX:[ŚNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN����ç��_��ç–˙@���Bđ)«$1ß©jýh°a‰5ueHj˘€B**¤ ÁHidT“1 ĺŃ`h&H…R�5A3-C ’Ň”fPDIJpIT„I‚Âe$ˇ$2 ”°Re•b[4ś"(“PIBEí 2 NČ%( 2Jd”Bb¬A`�Ŕ@H�ĘDČB–ĄIĐ()Tf’& 1)`$©Vv&C0LkA†@ Ů‘V“•`ą‰ ô7*Ş!1 €Đ¬3pI‚I¨  ę¬µ3Q€ĐM)j$Ą4’jP@ )Lš””şL€­ż„I¤‚µHH¨Š’‡Ëˇúb«˛_!cB)Ąm/ŔHXČ}?´Ň)~@I|’Š*ÜKh¬‚dSD¦š˝m…˛J ?¤&łă"mk‹ňMŕ%¤dJh@ˇŮźď#ĺFDy·q-P—ČXĎéî%®>+đ( [–ß"޵EąĐq[Đű(¤Ű˛”şš xÜ•¤Đ_Úi}űZĎ|?ń%«zÚ?hMŻÝ N–~ýiőcŰĘŢ{RýjÝůű¦š“n®?ń?5Ś+±mŘ cćé·-żŔ®$Ňżt`‘ů.HZĘV¨ýľý-!."e%kB ďËŤ/ňźŐ<Oßy¬«żÉúx–“ /íÖę8ü/‹)üđßäů%ţ Ąź>ŁÂ+n‚JÇâ<F¸)4ţ^iî/6µ”ţ_ź›â}nýeOëŤB�Gpgµż)ü˙7Ö˙ÉŇÜT~Xţtţ˛ś€«ÝĹXřőĹůĺ"ßćż~o‰˙éńŔT~vÇ-e%oÜ˙ö…Ľ‰_~uŽéžř&Sů×iž,¦ź+řĘ?,_ś%óĄ"Űä-ÓXĎ˙yď”ËÂ?‘qÍ`<űüëNNNNNNNNNNNNNNNNNNNNNNNNNNNNN����ç��Ž��ç–˙@�� ¶ ź�—ŘÉÝi,“$ÎŻ#M)&Hh3ZkIŃ-:2�&Ai„\�u™hB±I„1$ڍ PPS¦%€$Ât*¦YŠd¤B‚Ń-!! Č’�MPI™$B€Š0i ! ”‘)‚!(–€�„‚P’%€€e«Џl‚„0K0REA– ! ¤U€S2¤ĚČae!Š0 €2aÖB&FČ_[W,l‘ôó•ëń5˝ČbäEF„í,C C(A¨�ŞŔgE Ą­&˘Â$   ˘Vh/‹­+eÖ„ĄiRi(„BhJBP¶ÓDĐĄúR-É Ňţ„$?)/ )ˇmmŘII|‚”Ą>¨)}nŞú·qń‡ët% ŁŤň„‚;n7ÔĺÉ Ć•«pM ĺ°(C°ěˇ4&…ş <`R·\CJméK· Ň‡ř+·%ů·?|¶EKv “ĹĹRßůŹěźŐ OĘŮ@Ę)"šr…Ż7Jh|µMľ„۸lj¸ăń-P•ĄĄ´ľ·ĺ!ý!h`*ŕý¤ŠršŕGî…ľ%®1úĘ2‘ű|—ŢjŚh}oZ⦗Ͽvëy}ćx°ęś g·P•µ‡żňóYFXţIYśG€€xҶ¶·G÷ŮMżŤ˙ä ?iĘ0ćK§ţ$[©q”¦‡kúM±ĂŤ/ř¨·ŕ>5™Űr-ŐŽŹßä‹zÝľ‡ôń—ĺm (ŔX6 Ö7cŕ0ţŘ÷Ëo–©ó_ŻÎ—Kń­żĘ2ś˘ß€ť,·‚ZőĆŠŕ·çµ([F{-ěĄŘ­1Eż=¸‹¸$tµ»ůňmô˙>é{zE9­źÓ˙­çşŰĄ’ţ¸ršát˙\"ÜxßľüÝ,@â}luľÝ”qţéFQ‚L§šá[ZHNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN���������������������������������������������������������������������������������������������������������������������������������������������������‚��]‚ ��/�‚����ç��Ľ��ç–˙@�� ®q~�&<ńrňX$é|N¶t 2ćîňł˛Y �ÁÂ- şfC«�¶IE@A &L$"©  &`j”Š‚ŞJ ()J�&¤CHJ*%W 2P’d B¬Ua P(!%"ŞID%�!ÔA0SQ2LH!-~Ij¤DT‚$0t€€Â „ŕ±*‚,HĐÁ!Öšˇ°Ęş©-“ČŮBv2u˛DÁ ­Ş�ĐĐ –ÁÖ¤‰Ťjf*Fľ ęR€ ™|ĘJHK* š„$Ę[Q(X„€™Z(€)% 4Š©AL� Ňü‘8TÓV�~•‰ˇ@I \O€)…·š•M)JSJhĄ|Iˇl%˙˛K÷Ĺ)ÓÇE%nÍ©%ő%&ß–)+UiK˛’x’±·ÓK˙Úßő®4¦ŢOé+o„­HĄ+gńńCďŇüżÓ”ĄöSÄ´ý4ÓĆE4AZKôĺ?—Qo·%+iâ6ôŰÖÔ·ĺ$ŇC˙7€íëa>mkŹňă[¤ĹÇú¦Šxß`.?Úk…m+x (}”->Ęîšů­ţ_ˇú|‘*׾śV=+’ŢĽč/˛•ĄŻŮĄ_żĎ‰ĐV˛•şá[·�'tŃ”?·ŕ<§ÍţN–t¶Qů[üŐ¸ľý!jP)G›@ćíÜ·ÖúĆĘ|_’ŰĄ˙_µŞhŽ*ż·eĽß6ăć–ü#ÇĆţŢ—ÜyM;86-SůţKtSCôˇú_Űź8†Á-›Ą“oć­Î”µŔü¦¸?Vőľ>3O瀏›ýşYOş—Ôż|’Ś«Š_¬kůçüž r„xA<oĐšk…Ňün–śĄmfpş^‹}cţĽÖQůń?ŔN‘¨Ďt`Úr”qg·ćţ±¸˛šm˙“Ž"ńVńŃnâ}ů[ÓnˇŇďüßš5ŚNNNNNNNNNNNNNNNN����ç��ë��ç–˙@���ŕˇÇËŔ@¦ëľ »Ţö5|Tc@kC L †™bflČ %F- ÓZ¬1$‰-H0ŇJĎŰ0@A‘ ’ĆA,”3,ś4!)c*C*D ”�B Z�×RT@¨FV"e�’ ”˘™(©$ KFĐj¶©RH  �BX AQ"¬ ‹8‚H�„˘`L)Bp@‚* ‚ŠAfŚÁ“3«aCŰ!’Ă-•I#¸-]tĚ kWôYbč)9v±¤ËA(Ă`•  ( �˘jÁÔV BE# .¤jQ)BB J´,)A/ß&˘hZš (~D>v_·H¤-,iŞ  R…ˇ —Á/ÍeOˇúŔ¦š ô…ŞiKňM4ŰŤ(Ş´ŃA AâKńB-ďĄń|$#ö¶€V¨©üĘŠj?hăýÓJRVݲŃK˛ě% k@P´¶ű÷ň´ŠR—A+i·­ŃžÉĄqźĐ<u)ăGŔKżŰ÷ÎŰö”ń“ůR—ţć’µć-ďż#MGČ?—›âýŕ/ÉőEĽˇaÇHˇöüĂűrÝ®?Ý(˘šąB)Ę2—eő¸šöä[ÂxčK4ľZĄý[kóýqqRQ‚µˇůŁ(óX !mo)ˇj‡é|´-ď­ř ž,ŁŹÍ>Ę)ŔVü÷}…” ĺ(~•·ô~c‹=íȢś‘Xč'ŠšĆýŠ_>¬jÓ+O°ńžé 8 Š˙(§Íe/Ęň” QCäҵZf‡öűwŰî,ű)}űŁ‹őúEőŻ5\4ŁŠŘî/ óyGçBOço·~Yďž˙ŻÖĘ<ŢS”ţ_“ĽuŤXö˙Őc'‰."šĆş0ÖĘK¦¸-é/łä§=żYJ8‘X˙Ş+†ßXÔ8†˘±ř’ůmÄ7NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������beetbox-beets-01f1faf/test/rsrc/empty.wv������������������������������������������������������������0000664�0000000�0000000�00000031050�14723254774�0020412�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������wvpk 2����D¬������D¬��Ľâ¶é!RIFF¬X�WAVEfmt �����D¬��X���dataX�BWWGVH�CĎßďë �����–��e����ŠŮ�Ŕt@Ą¬ ‘ɦfŢ1/°¤% �k^‹™«\ÁĄ”Eăž7h… <–°ÍeŔD*b=p™bE‘ÎÁ¨¸‰±I(€šxŔ‰Ŕ(N%ŞTŁ´°TéćÂé6™r¬‰ťS(Đ`xtÔMI)gŮxL¨Up M ”ľˇhŚ`pH /Bš×`0â@ 7K6ePÉ=J8(p˛Íü†Bd h �sr`ÁMybáhÎn2÷T ŁŹđ6Á:ŞűrÇ`…ŠĆŽ’Ń�%"39>JL9Ć� uě)<ÚL;=&B8GAářkŮFhF”›;n@+A¬P`ĂH›AŠFÖ$FlŠfľ÷Ŕ} f©S›áDś4/°Q¬RŔ+žÓŇ"(â°¤ Ť� WéÄÜ÷´),˛0±µĐMP„Ţ·$Ë„ÇÄÁqŽŇŹFŕd-Ŕ×?©µfsh"¤ aXjŞŚ`˛<˝†Ă•°9Śy1xŽÁ‡�ęrV�śu°<Ő.�™î¤˘Ü>˘q±Ä-8np#á8ŚÜąâC#yT†1=Ć ťÔÚ@ČĹNňc$ŵ’Álrc U˘«‰ $¸O–JŞFQ ó&łQd™Ž4/ z @j�Q g(l ĆÖ43ůJ~ñfĐŚ1¦Š%€¤ĂhfTÔ�ŕôN¸ŕc“8HŕLaD R+‡9QT~„şDĂŚ2:Q›IP�vjŽńÁë atÚXťÖ!�HŞFÜ °�†ĂË)Ě15ŐqK P |–MáŘI=ŤŠÁJ`Ő±ęíĂ+�D™™X­ĐC=2™7TŽJ3)�‹cđáDÍ,É"¤ÇńQ'#z¸PúDü—‰6ř RYÎ;ĐŽB¨¤�4�Ž']'�T ¬Ŕ‚D3@…ÖśX J|pĺS ‰ šŤ‹Db2Ćń|! xgľpČ�ćš˝=��˘^jc'ɸeÜ`ä&Ŕ©Đ DP+ÔČŕĆŽńXČĂś% ™jŞY–ôjX†ěj‘�lˇ:dŮ’Ű[µ>4& Ŕá>Ä ĉŇNĐřáhŚĺĄ€,d N˘łq<^@b…>Ögľ�ÁÓŠQĎ:X-č`“FřˇAŘ\Ť¸~˘µfs µI”Ĺ’Ĺ óNŠăĘń˘'v‚nBŕ„žąĂřP.”ăA9؇2Ęic"Y čŚ4FXžŞa!ŤŚP§"řř�cb˘ă(đ’ľA6fČůÍČĎ@q*.¨`Ż[kj®Ň� ł€‘c¨YĘń+ěx„j‚áX‰ľB$ěâ\R+ČD× R‡€fŃD6ŘTł,é"l,ŔRÂ’éŽW€PxS="=i t 7©ĺT3A—0`d„ÜĂÄQ0$Ž2îH;-îÁb5Ń`´ ČGN4ŹÁÓCO†P–°¬ě†M! ¤ YpJ…”ŚD  m�€ŘĽ,x ´1.Â0 MĆČĂĘ•DÔś€´Ńz")6÷Á± P,®hôŔFŐ\�Ňťš–t?Š‹F¦‘6˛ŽxAkF—XäjM P#Ţĺ¨X`dI4Ś=ř(Ł 9» 0óĺ·Xš�€—tě`¶h AŔĚŮŘ#G€¤ÖO†Ö 4šĂó% ÁNvş¨%„N ZD3”ˇ-97 .!í˘ÎäÄ÷iA”‘ŚÔ X‰ŕ;#QŔ&܄쓭:LtnT‹ ŕhé¤mP¬„IÁ(ÍK Đ‚¤Ĺ-Z¶GO‚d‚7ŚP@gŹY=ž (‘ą‚p%l!HLJ|ÂʤfédÎĂA8>�@k0'-?c¬Í‘ Ž‹, f ÎĄ“ „dłĂ’jŘ)2q ˇ‘…ej0§ČhAPë©®=a–Ś2`©…Pň\wçńP�0UĘĽˇP!ŕgĘňŔ± €§¶X~śµ¨ąäl8şH,fłü:‹¬ŕL;ĆÔą$vÂCHŞ@x?V  Sí0ű�#"ł:X3ĽŤ ó’ŕVźŕĆCEÁŔ4:bqL$`<¦¦„?L5/•ieââ8{�`hňbXs¤á`�€©…+CŔ”T€Ú2żĆR(™ş¦šj”Ç*™x„yXňŽ­ #Mb‹p8‘‚E5Ŕcş¨ă@#+QRčHH>ş@áŇ@4ĽŁޤ“ĹŮQŹ%óRH®°ôpSť*-¦ń—h ŘŔHä Ď5Ę�6Ř9; LT>•$FŽ5hYdaŔ”â2CyěÇGÉ0‘A®A>A#•�©őÁ:`@=c“.Tx™1 ;dX%‘ >âĆFEćN€–˛@H‰ČĚťÁ™ŘÁ­Á �`ćĘÜŕ@ňX% łĂX€‹ňŚ©b €Xę ŮÜ™š AfS(�ë1ú…‘ŮŚ  Ł™€gfň…YDËx aP¤J4@"h„Bą ó2Ă/ Č‰ŠŚáćĘ| ­ ŽÓČ Ç†m*ń46Í—ŞÇžđ�Í ĺx‘ˇ Ô´ŤGp#áx/Ôbă[€ 4¸@@ -ć$°TF9n€‰g˛Ö xXöꌍˇÂšąZł Ël a#©*�čhęT¨yAŮ#ŠqÁÓYO°ŽpHdJÓNbÁZ‚zşńŚiʧe“Ę«ÇĐĄ2Ö™„Ł`Ň×|Ň•%‚I°‰™'(‡ F†*’ĘTó�Ö¬Žň�H+ZÁA” &Ŕ K^ n( I†ťŚkŚ`čĐ8°ÜŔČ#‰t2ÇÂ%ăůaŢitŹąA’AAU¨fýĆe"„±8Ôä˛NT�˛ ‹FŃc=•Ę��ŰpJCʰ˘‘Âř .A<Ć&&ŽĆĚŔŁY:ÇM‘‘€‰Ŕžő1yfK,(Ź�$ŁĘ á<ŕŐŔ kʔ߄‰Zk$AÍ’?AĚK@AÇŃ3%f6” HSٵ)ÇkŔ °ä g;Dµ{”‡ÄÁĺĐ8Ň@$p ÉŃ$ Ł�VÜMŔ<Ő-" S‰; G4M¤g=AsMzMćEe.ˇť 0zbeÔi¬;óöá¡dî ĐD °Ĺĺ´L¸ŕ0F X9• ýRŁÂw±‡¨â—) �Ů1& ŘyŘWr”ešmŚ6łMÔ,¤çľČ´ΡW‰Ĺ˛§˛rŔLd“zęĄă˛y1B6o' Ę‹—ŽłQ–Ŕ*KĐ"04EÄ´1qů¸ˇŰAš@őA›í0¦î©†ě®K`%(·5'‡›ž˘\ÓĺPgý›±&´™’€R@3 N•Âu4Fářř¨xA<zrÉ’Ą¬|0KX¤2Ń„d:%2Ď ˘ ;Ö†T›ĐĘ´M�s‰™PÔSŔ,OZd´$‚q˘=GYE¨Ëtű Ę)EcÂ�„¦> ‘kFđ(;šă×đÄKJÔ�Ŕ4ŁŽ“GŇ8 DHŚÓ ccͰ ±2ÁÂúĄÁ�bś…=];p™PÓepj@Ź RâČóH)Lö Éu¦«sÉĽÄhB6°0fŚI)VćŢ2HÉL’`Źp| Ł%3„JÁXT€ňŐá×x´ŁÄĽ Ú”3É Ů@ôĂ"GȤ j�„6M ˇ(^kFá1yfgŕ´L´d:*ă%¨Ý€=XÄňÖMAćƇó …l¨Š*E ™JěŘŔ@qD¦»á†#PZ#ĘĆDGXś�i €/�Í7šKÉ–ŹtAŃ bŤÚS@t\P9�n ѡ°�k1V(Tr18ʰŤDŽt”%�€‘i2E&¬Çü†(ś†s±Ď°Ę­€>â–ŕ…‘4ąF� “DÍ/ŢűP ´Çä±â̵#EX2yĚ•Dd”%3L řOÍŃx… {Äp Xçř‹PAj¶ćč"Ô©‚ŽKN4uŞ @X6Ĺ'Rp ”T„é Z€XŠŕÜ  K`‡ŠF&­D†!ĽCI‰ ÖŚGt„F6ˇŔuŞĂ9Ŕ@2›„FرĹ@b¸ĆdA&¸Nu”$c1ŮS„,W Ş�sâŐ�ˇŤr7@˛°ÂGĂp4ř€<€‰šĚM5E`\Xd€ŘŁ$ĺ1¸ĂD­5’ pĽ,Oěá:�6âˇ'†‡”*/Š"Ě3ó€Pˇ^´%Če"‹­h"uC‰i�Í ež’ÖF=ĆʸÄH¤ă!„KČ_˛uH$ÍĄÔZ 3ř0HřqZś6Š”Iü  \'V rePNĄ�†fă%$R‚ń-s‡pʱž8„»`ÎĐ=�˘©0_4˘h¤cýETŘ‹ĚAŚą´ÚP™_Ŕ€š/1ŁÁ- D5Ą–Ž€™2/ˇŮĐR@ç!ŕG'ÄxâbÍ<Ë%Č1–Á8l8pzp€ Ě*ŃłŚ1q‘PŽ• Ĺ 9ÖˇA�¤Ę•ą‚D(PĽľ A H˘W„ă"Š%�"GÄىxi‰ÇhűdN•őăŻ?T@łld A›Ęšĺ\‘a 0i4ѱ.B>Ëbî3sx8ĂIż 8\ “wŔ;Š !ĺF «‰—:xSSJjńŐÜO…”!Č2îPy0„WăŃX $Ó@†Ż7Rjčź;Ŕ|á#e®˘T¬˛ú@‰j*/€‰ŤaşaáD ��l˛ÇşĐ8ZĆÚ5SYx„x4ŚRI—ÉlÁâ"kFhÍ×"ŮDC©R{4‘ěC±€‰Rd�xf=R"ĆTŇ f Ź2†ăCmp›daa#ĄŁ‹ŁqL6ŕ�ĺ‹5z”AR 09Á­�Ŕs‚} dŁ©dbAĚWćŇăín˘¬–ŚĘ3±bGÂ1/tAŤq4�ŽhŠä;#ş§rŃk.9ć.(€´ĐpĐXŘbëá7Ő¸łk¬9šńŃ:‚ŽX|r¸tġÂü@¬`ÁˇK€Ń®q–„ �„+�¦{Â%đrŚC–%Çî\r e‚aF˛Ă(K' K ‹=–D#§B÷č-Ĺ“’𢂦Ŕ˝˘ĘB.ĘqˇHd9äŚ.ßČŽĘ…0pfa˛!‡{XTt0ˇk!BJînäW6Ą• W�€ŮHÔNâ °IAËT‚F�&ˡÂ4ó‹ň©¤©B´Ó T¬�ě¤6]iq#l ˛„›–HO„@˘Ń‡QFËcwđ¨2—��ĚŮř’H@-Ń:Šđ a#B‹ě¤Rľ#@Őŕ�É«C…Ögâbâhbä,ć¤`“aaCą*b \B«(n@S a2Rh&—FĚ’C2eŢ€°P¨đx*”%ŹýcXd`F“…– ŕoRzͤOt(GcPŰ7HDČžĐčý„ĽOLÖńŇĘŽb(D k�Iâ ŹD…—Sb2ÉŔFdÎC/RćH<BÁD8`Ś›ęĂ5@§ĹG`€ďi™jŞăvëŚKL>Ô– Ăr°Á|Á’n06"¸“ ”ă5 Qţ%PęaŘSO� ”aYb‰ÍŐ!yžn„,\jĄĹädť«K°!AQJÓ37c-T)'–ÄS(Ś …ds¦L]łÄq4Ę€÷1t45˛z€ŠÎ|ŕşp´�Á5/ UBcŚ•ÁB;@雀U$…»ŕŇĚ/!TéŽü Sđ—â!Tâf̦{pťtN@Gb˛eÂFaއ€ÇY‚ö0™şN=©z°2Ę`٧0�Iń $Łéř’H…Ł%žQĂ_ |+ŕPÖ�tM5ĺ¸Aäč7âă—"e(ŕP"ć¦těΕą fh�[€c-Ź0+›)P,ăTÎV8\:2,U�`Ź}@˘™¦ćNĽ ‘Jad"TÓžtC Ě×€€UëO\€€3‘‡Đ‰2ó°´ĆĹ'8Ó:Š ôaŁ!‡%ż, —^(±t<ĂPy8‡$•$° (©Ć�ĐPXRsę�Ř‘[D^Ą‚1%FT6ŇFf¨.IA‹ąa`F<×\j´Ě[#žŤ&ć“<}0‚ b˘�E˛Ŕ( ±žő°f)OuÄGž*¶îó¬ań ˘c. (ż(µä ń‘ąY&h‘‡R 3_=Çí§đ).>Ú@«hĐ›/qŽ…9ĹČŕ´‚úĆk��w`¦ŹqX`Á’á>ŚO y�@T$šI‰�#…qfád(čˇxťqɉ€LÄ€‘–L´6هĄ€� S{„‚Ćš°Bć©ÁĽ8ÎŃŚ)`)0RĚ­AŇN5K5µCÍ5Ő©Ň© €™jČśŻ%‚:őAÖ:âB8$Â%\’8„J*IM‘&Ч%CĽĄí¨žĽ ühscQáň#\B)ę4Z|Iňť–` Đé† ­č`¨9’ËiPé0•bL$Á)RŮpBL‰ş<ĚĂHá2zÄc‘€M&3úŕuĐ… u¬1:.<Đ‚Đt zQ—ĺCj˛>Ŕ7Yzć$� ‘a€‰¤¨B;Ç�„8ŕě!™hŞ©)Ë ČĆ7ÄŠ% Kf?Č\ĂĹęAŮÂϵnÁÔ,7X �Č\MŮ =o“Ąµş ŮIK��€•ÁÚzEĄq ł9H…ŠEŕHˇć P^”ΦȠ™©ž�šh,¬’ĂŔžú Ů–ń0ÚHyęb˘R!5 ďq ďŮhâ‚4�ĐaĹ\9AŔŇV€MY‡C9¨H–Ř0nŠ!0YČ z°´4ČI3*ŹV , b¬8ţ…DĐ#ˇ”RH' ¦4µŕ†�<9r<Ć©ÁM!‡ß&IX8"�"3Ť1S 7W'‡d˛OGĐ ŻÂgÇﵪ10ÖÄQˇËô‰b„ÂN5Ąc•‰ę¨ ŽnÂ"&逥”ŽIeć-Ń5_¦Ń“Ď%jşgŞ<ŽYÂdl@gáç”IOkÁB±gĚPÁ aQvÚ�ÍTr $ÁËKplFcĚ2¸4Ň™bs±˝0ѨŇY “g @R©‘Ţ”ě#JsŞ&f7=\Řč¤o¦S{ E¦@@sňśTaadP”;.×°Ĺ`  ‘!ăŃ@‰ghE�%"6š6@šG‚p@ö`–‘82y E•Ěś'Ö–ˇ” Hf¶a â‘BcSdŢ#Ĺ'[Ş%%nP'€hĽcşX Ő˝20–4Ŕ¨F#IŔQ¬$š z(ŞŘĹn&p¸.PĂK aŃĂ],ƢAs6˙(r2Zh§Â t*ĚĄ™^Ă`Fž|)Ą%1¸U™Ŕ=4 ;8dx¬ˇÜ�Z3‹EX<|RaáEŃ�ŤRŽ/IŮeIá°ůĺ«a,Ą”Ía(‰×HÚŮPĆ=Ô­łv8�B¢ÖTŽO˛ ‰Ŕ\(/…/BĆfEp,1đČ W±Č JŐlˇ^ŠďŽBL% AŁĄ00üqŔÎŐp)�€N}3‡sCŤkÂĘ@‚ ĽĆ°µ ¸�60`q‰‡˛E=ô|€ X&ě°Ň‘Š[Eř2…›ÓÁ#ĂFuĚIu$.Ą^,]<5šK  Qbăťđ—uxM$Ý~Ť)ĎFĂLŃ&ů�…•Důi_�ib�0Ó; –0¨Sti(ÚŮ€J\‹®˛”É™ľuŔ˘‘)…ť#A¤PHdbÔ´{ 6��lÂK¦łă3RI…;G Xc¤E ľK*§d°n¦čȨ7T*ă‚Ć%¬ĹkŚ>' �ĺĹF¦ŁöA @˘ÁÚ(ůI· ěŠŕyGůeD‰ ©ŽFIž®€Ľ7ČoI\ƉͽŠŮ( Î;4Đ+BńEX0yŕFsĹ1RPaéPjşYplžh,7‡ćč$Ä#¶ĺSÂďP…| {\u†¶Pä6Ą‰ÖPa40óµă (ËB$:˝9ĽÎÁI4‰Pĺ°4„yahâ€ô`ZŔÔúđ2ŇÁ¨âQÜř˝Ć¸˛‘Š (dOQ…tÍ•DeC>ťéH*5Ú0ž´Ć P±ŐB;@S8l^–Ă „Ł€péęx˛„ÖJ.±Đ"¸�ÇGQŠÍ›"&gdŇĽ›In0ÚE‘čEĂ—Â/lÂ#,0âĄaZě‡A ™9��ť Xó¬Ii;ô—�Ç` Äŕ�Đ… a^ŕŐ „ć»1Îč3Ó@-Ť�2ŮQfy‘u(gĐ02lˇͰ�|f´Ě,M¦i4®8;ŽH4ŠĂ`&ĆÔń¤DÜÉĺ €X·`"ĽÁÍ©§`ě4¤�謷"ę‡/» ˘čń= ‚Ž8ŕ� ·m S+3k")KŘseJŹW�ĺŃ1A�Yd&Ť :©v JÄł*If)§:@ň„K5‰‚rśeŮ Zě!&J“SÍ'`6Ö+Ŕ „‚�°6ŘtŹůŠBďÓ÷ ń\¨™`ĚZe‰tZŽG/M*B�•«Ă=¸ŠŽJ‚Ä`ÔŔRH š‰Ć¸‰9†rő@Ááö„üXa‘ŽyĚ<˘’f~ß Ďh;A‰­V µh¨-źˇ<¨Aęđ…##Hś(Ö03€§Ó’ZFŚý -Hiwč˛óQö‰nZC»cÍSŠ,AŚí�ś7ľ ŕ˘č N!ň¸~ÔŠăh(s€Ä L˛1ŽŃ‘8sÝi±pVĺ…@X2¤17/ž—Qˤ;eĄCp ¸”%40Ś6”uAšÁ2gyäĹÄě1ű, p ĚŃ'6đ6côŚ/#€ZźdŠň’,® DŁp$ɲ$"TЬ ô–Í%HŃ'©M]b Ç âůăDňA�4`1×(âT24H9 ń‘Ă:Q13(/’‚wú P3žcAp‡" ók™ě ÉÁJnĆ%Ř`\r´WµÖCZá’he¨ákR  Ä‹3Óbľp>ĆHÖô-é�źĐ‚7¸2}IĘ=M™i\D˛uX’ZŕpÝ®ˇN•±# ´Óś�(·€Kµp$4űAF0‘Ł«ög„ Ľ8®‚Ńa[:*äF‘5V±Âw"dF$Rj€Ą„ŤČ¤p#ńób/<Ű3BÄR`Âŕ€Ë&CÂÓ+ÄŞĽ4bĆęBˇY q®3—ŚĐ2ci@‚MŃiç´ÍDgšĘšÔ~HS!)7—€E8őARJ‡ŃÄh€r¬:Ħč”#>ÇšB�µW�&}č0tŕŽ‹>Ĺ ,—�–ČłűO€%莴€c�(eS�$(@­ŽFŤO˛hě �%tD41ëuġI‹ŔÎ0¦Fć%,@D&4fĘTæš!ŢC€¨†$âlîĚ´��`Ž5%RˇbTs}ću ŞP8űAő‰qL�nžJŤ(‡$’yłŮ@C§;n�’jłt±·€ĆŔť M4@ Δ›�ô<Ý …*ĂqY ­h×Ŕ\jÂhxm= Ľ˘'ÎjôÇČ u±÷ŕěŇ É`8 ڏtl�yŢqp�ŔĽäXH1ó �‡ŐŃpG T–Ť…©9Áx©Ř$€(ŔT”Ă $ČFÇ”ăkD-Â2Ń· P»s5Q‚Ó×DsGB4x óâŔxpU8 Pľ�˝<SÍË/]Ź ‘0K+óZ�F íI�R4ć—‚IFn69ŞÁ@L­9;ăa `䨆ć8P¸3磋o š!±U�ˇą>ÜCa¦ši‡çÍ!0�6Úđ@ů +‰ĂĐ�Ă@˘Ĺ ř0?ě× ”T#:'·BXÁ58KOëőô&lŽQ+`�ެ¨‰ FŕX ‰“ˇ%ęîÁűÉÔV`‰TÉě*Rľ‘¬aŽľ(R$Ë@6¬Ŕ!5ÖćÎ\9NŹk� �pŞ` ¸Č6|F�MyZ4Ë©Y˛đ ‘©QrĽ§‰W3{¨Ąµ&ŔŚ @ö膰M…ădv�ŘÔĚ;©bÝ óÔZ��L±lN+á6Ŕ •hŁ9@—-ó±őŁŰ+�ťě·‚D¶Ęh€Ü4™<:ćj�śˇ5 Ł\I\}T "€Ére.hŚĐh°`3.f#ţY&ĹÎŹ¤DáTkHL@ Ň0 hNm’˘¸@`gj2P!=XăLCŔrgĆŔ|»d’H =ÄĘ0Đ©2«ˇŘ€– Äcş'uŇa4 �@ EŁĚAÄšĐ=˝3ŕ4.Ö‡;ăŠ� O#20J´§íÇE„&hŽC÷‰;ŃńşĐ-!n ��Ý#z^u˝ P^jfŹV€DsHD„ l4(4Ő©&ÄG<PsuŞXĺ5�€c|(ŔT3m†4VąÇp<'˛ćČ@´§0 L˘ć Ńzé(.1Ś,$ŠFjćcŁČl�‡ĆÉ�%2¨I5�&&ŇM0—Ť(ŇX &°7qrĆ�Ťj( Çő˛&›.5ۇ!r6jfTŔ€¨r‡«@ićEÖä©ĂŠe0đ�ó˘ę‡yJAFŮ<q8&HMăFĺ3LŐąA˘ rC‘AQϵ F6Śr¬b^ í@Ql"°Ňa 8Ľ™kH>ĉ¨5ÎŽ=- &� < § A㬄EgXĚ‚EĂÇQÂ<#-…3Hlc0­O¦™;jŤ0/Bi´ĚKzHJŚ&ăh26`'ăđZ™FHÄ3W�<(Őce¨Í@ĘĐg^�‰†GÜ<"Üġ%ŢZQꔇE ;hdŕ™G,!×ŘŚ1· Č9OmŽď„ýHDÔcExΡ]�¬4ŕŔŁ!źŮ$€”jŞL ,­çY0AfM$…Š ¬§Äă4�Z—$ ŞÄĄa#aŔNuŠ (˘‡ÜѦi:Ôy”Śî\ú´r„/E60®‹$4â‹ŔăÔ>Ľ>ŁD‚ŕF`z6%ćů R¤Ů�ŤGs|¨Â]0h”u(”—Z�.Íř0“dƤ3R9t¸ &´"y ‰ZH&• ¦®ŁńĐá‘5§šJE=¸‚YÂKˇ`^tt?%yŕ°8đ]Y4Č9î «ç†ś>ȱaNđ~$űa¬h ”l”Ś —€ D†°čYEpÜ�n"âDA6=X®€Ř9^š™ŽÁ±�v*›˛8Ů † í©€t'5Wg¶�8Ĺú‰šGĂ\ű˘‚(%öÁŐB­oĚa<ŚdŤ‘�ó† /‹Ş,5W†â‚€B/Žč•=úТ 9ÖŚ¬±ľ@Qš>Tâ óaP�ś•° i1Ň, ÉËsŠ„Éş (X84ĆCiÚcpň‚•¨$<�&…¦+ˇ\"@±.iŃ8Â5‘č5W§a2 ˘[BŁń0Đ�Ë­Sr ,Oh‘9@•h ',A@â‰pN‘Łý´'YMö ś(ˇ,±^Äăđ]Đ(@M…—ŘĹ(@Zť €ëXi™Báx„‡e*W‚”ˇš«3žÝOs"YČ Oľhž‚E!e8…PĆĆOY0Ńi±`6I@Ľ#ň5�3dĐ]SʡR.:“žě?b•fG¸RÚą0W¬Ě•pCˇD8˘Ô„¤ćJ¸aĐTGsLĐ‚8Ş×\c凬k”hB"),ŇĐ0Dřkb( ©[>ĽB¨¦}đµÝ€ ™ÇĂ–óÂq+đ „h€4ČótK ÁE8\B•DKQx‚š(#€6e "ĄKćcpćÔi –6ô”<o9Ë0=żÔ0Q8‘H†˛qXmŕŕk‚^cĐký„)ŮČeę ¸9`Ôii$@�hĘ7ňś“`=X^ ć­dF8.˛¸ „ ć’TNF*–yM4xQ0Jo"S3Ő\ĹńB-Ŕ0Ť‘bfxĂ÷:h%"łp!Aj}h‰8ŢSM^Ký!ö”Z¬`m:úË©Đ\㼬˛EB`G› _}� &6@lž>$¬8žÂĂöÇA†Ę 2 SĹŢOÚ†Jt=D%ڇiMމ�pśF?„ŤS#%‚B_~˘‰&Ů’` ď °Ăʉ˛©tŘP´CöPo€ŔO` ZóĹg‚dB,HТ{†�Ęs÷ńŞ8�Gbá(đşRD*kxŽŽyq°�ćă+€¨5ČÔšc2U†\‘Ł1,ňRdŁ,ěDaĹć aëP±‚ˇUŚ^Á3d‰KŃÔ¸�KJ(¨BäY'ö•8TŘج™»rŔÁ*G@‰F  M‡*ĽÇŔĚ•ˇđ•ąŔq<0z�ň Ńa�¨l4ć ©˛č:�şÄ©2)<mF`4Őŕ7ä¸XăŃ3jBx:J¦®YČŚ`¦}™4W@x\,FA††§s‡ăë°°Ń2®…”!ŤxFFHŔM11PäŹXG;Hil€E:ŻRŁć>82NMIžŠ8i°đsC0*�µ`šó`©2nBŃř5 °ô‚f(c¨pĘŐ„śđ"ĺŇ´�e†×8 {ęŚ[„ĹDĐ”r†Iä¬1j6@ęlˇ…[†gÂ:ÁĆŠPłĽ0WG#‚h<Ś â©!qú‘!PgéŁcČ*¤äŘş¦ć´Č0ëHG H$OŚ7ôd%‰Ä#2Ą±!´Ř‚­ h᪠—„ŕ5&Ě|6p ŕŔH5—�‚‘’ (Nâ€$ü‚<X^&8‹dc�Ůc–Đa3ŐAě „"{Ŕ‡‘ht„Ćr H*$ÖÁ2Âŕë@%{P*„ŔÍ•)ěTłŃx€řÔ˛1”‰”cćYM+ëPp2<7X;ôÓK"‚„&‚pô‰j.1pĐ9ŕ!{hWC-h¦Ç0 kÝ|Ň#:JŤʢz–Ga äÄN8źułz´T bŕp• =Ú! ˛A…›pŮů\KÁ(o5Z Ô–A,E4¨‰ ŔÜ`~"0gźĚ¦ŽŐ�‰d–ĹŮ™ ¬e9QÔˇ!yJyŽR wČR…§đ¬‡KO©ŮÖÄ«ä LѸ|*hyđŚ/‘Rzh83˘Ń‡ ś xĆ=€,8$’<–>pěÜ ‚č*FBĂĂh%¦u°L80�™LŽ,«PěąRä67�CDć #{´}ś\­fŚUlM`=`A™â0C‘řĐĘARK#Ť‰p ň €ěhޏ5đ‰tjńM‡*Ř�śy1¤YĆ?@‘Ź’§X0ŐĎ‘�¬¸)M–€�\§($‘SM�…q‚YE1µ ş�§Ś!Ł#-S­E,˝2„Łi¦Áh˘PqEBR) ň˛h â‚Ę%0<xĘh`yPćşôĐĄ¦CBŁĚŃŔr)¬ Ĺ’ieµ ‡(N˘ą‚â ¨đ‚ˇx2L ™R 9ăŰŔ{Łe^XŚ:ÔC\0b�·Ew tIŕ"đß8,8Pćc(mČŔ\9>‰7U‚˛©<śÇ°§‘C2¤(ĄPłń4*\:ş`é—eRAš�řo ’jĆ/¤˛µ . ÖĚ=¸X—M€<Č�šŐHX Á±:–K eĆkÂd€ â5ĹGcl� —�ćĐL tŔ†Jô:ŕ�YCŃ.¤ă  &iTŁ1(z˘Śť«C¨ńžę HËÄ­´$ %€ś7„đkc—Äj #îT™ x�‰“ÝDpLŚ Ą@Ďtbäl^ /0Ç@Őç!h𨺣‹RÎi“čp Ľ0ư*ŔÄúđ22€ł… ¨iM!¨Q Żf•°Ć‘ޞHčâH�Y�ŔăÉCe˘1 6!™>5E0a“L �ŔiA,`ą ¨’ł>0t 7W¦%č€l€*9…*aÁä{í2l�Ě™qöB ĽČ¸Ń,<"Ăre\!°Ű/ŹpQŘPÄVpŘ’Ě!N)Vćę”dTŹ.“.AěÁKÂŚŁ€dÁq±ĄÁIę8ă ¦ĐTOëđN§~j‰#Ś&ôŘťNc’Ę$RC–űˇăť@IÉA Ňă7&5WFĉă’a<!GŁ €xÔeů¨#ЦH€4–ZÔAK S%š+˘Ă× iŽÁ1• HVÂ$m�2Ht)>ŘHcóÄé§ęĂ !Ć+Pś›J>P,AĎ„¦.U˘J"14ˇq3R¬§”R1J‘ ˇ9np …9 D l¤Ś<ÂČO>ś’qVâťŃ… ‡+Á-'´<Đ€v3DH Ňt <q¶–@S ¸Bë‰`¨™šy3„q3U­…Čô|g”>h˛^€f�OëjćµRšy,)UöQ?âgŤ)Ŕ©ŁDP\L!Á28Q pe `Nęq€&ŤDc,b¨ÂˇÔѡTQkčŔR ó"(H8¨2d:bą�b°,‹d h< \Re¦˘Z şiÖľÄhq'°Ś)i^U°Ř�Żš—"ź� ć‡öQ ŕÄî\ [‡YN%Ša [NXc–U f”S%Ô3Ş©ĹC×ńě8 h‡¦šÖ¦jx Ŕ&T‘BĹÎc:\B>eX$‰őˇkOax”�ŞXÁ‘a`¸ŞÂ R”c}†˝¦©m(ł Q@4g‘ř�Ř(a=˘‡…ŤŚăP î°ÔT覚)ó¦ če€lŚ%Oě$¤čÁĚÎAçctÖ@TcQA<=úN)I~*żN:ś�Š#(­2lP‹Śá ’›'´ÇX€LÇi G ·†Ä Y¸ Ż1µ3™@Ą_$źJŔŘČŕóh¦ZG@&�Bd ąš8=S"b€`Ř8;˘ÉĚ,ŐÔ5Ő¤ĂaeVG'Äš,th`Ű ApaájŔŽUN…Ť=zÇ5Ę7�¸Ą=Ą¸“�8ÔCC‰Ťh٦8DnS"Ú<ž(d}¸”©˝Ôh?€`!š řa/„Đä{'I‚ń)0°Ł}ĆŤ$¬gö…÷XĐ!VPŔ“DžW?Eaţf°�ĂÄ&vK,LěN…zP"·!2RńŤ<Ç‹8 †`J±Ę%Mĺ™Ő ňĽL`qޞLµ ˛B@gHĘ3d%ë>p˘l„/C Y^ ĄJB9pL$ĚeAƤÇ$± ™Ĺ",zÉ‘�M u$‰ Ďťů ‚ßxL6đ�‚ŽĆ ňBj®Br2D­”ÔA�ťúŃŠŇXJĚ‹*F#/XpDhiĄ™�Ż6©ŕŤąt„Č nZ4C<@J€C‡”ąš¸>Ĺ�*TJ×±>ŘÜ��dGsŘV@¶™Ö�ě 4Ec 6ű=Z A–Ř óÄ5ĺćMAT`”ZŔSĺ Ž 6źPó ý :w‹C!Ź’Ů×lG^@Ęń~z$4Ľ^ÁЧtj2e"OˇkÂÇÍAĘn�DÚM(…SX^ ĆÂ, -Ć ‰ Zk� ř«cű�;†e $‡GëT.č桨  Sh ľŠ‡†Áq‚FO€ÂHâ,/€hĂ3o ¶ ň:ŕK#@ki ¤ťbŹ $3ťČŔÚ�™óŇ��L5ő¤Dü<5Çţ k„'KNĆé ™ 3öŕd†ŐTł”±Šśr–¸ĂÁJšĚŽqÚa (%«Ńˇ€-–ĆKň0™˙����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������beetbox-beets-01f1faf/test/rsrc/emptylist.mp3�������������������������������������������������������0000664�0000000�0000000�00000020000�14723254774�0021342�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������ID3����gTSSE���4���LAME 32bits version 3.98.2 (http://www.mp3dev.org/)TIT2���#��ţ˙�J�u�m�p� �I�n� �T�h�e� �P�o�o�lTPE1�����ţ˙�F�r�i�e�n�d�l�y� �F�i�r�e�sTALB�����ţ˙�F�r�i�e�n�d�l�y� �F�i�r�e�sTCON�����ţ˙TRCK������1TLEN������217040˙űd��������������������������������Xing����� w�]čż� "$'*,/1469<>ACFIKOQTWY\^adfiknpsvx{~€…ŠŽ‘“—™ťźˇ¤Ą¨Ş­Ż±´¶ą»˝ŔÂĹČÉĚÎŇÔ×ÚÜŕâĺčëîďňő÷úüţ���dLAME3.98rĂ��������4 $¸M�ô�]čżüĺ»:���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������˙űd�đ��i����� ����¤��� ��4€��LAME3.98.2UUUUUUUUUUUUUUUUUUUUUUULAME3.98.2UUUUUUUUUUUUUUUUUUUUUUUUU˙űd"đ��i����� ����¤��� ��4€��UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUULAME3.98.2UUUUUUUUUUUUUUUUUUUUUUUUU˙űdDđ��i����� ����¤��� ��4€��UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUULAME3.98.2UUUUUUUUUUUUUUUUUUUUUUUUU˙űdfđ��i����� ����¤��� ��4€��UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUULAME3.98.2UUUUUUUUUUUUUUUUUUUUUUUUU˙űdđ��i����� ����¤��� ��4€��UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUULAME3.98.2UUUUUUUUUUUUUUUUUUUUUUUUU˙űdŞđ��i����� ����¤��� ��4€��UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUULAME3.98.2UUUUUUUUUUUUUUUUUUUUUUUUU˙űdĚđ��i����� ����¤��� ��4€��UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUULAME3.98.2UUUUUUUUUUUUUUUUUUUUUUUUU˙űdîđ��i����� ����¤��� ��4€��UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUULAME3.98.2UUUUUUUUUUUUUUUUUUUUUUUUU˙űd˙Źđ��i����� ����¤��� ��4€��UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUULAME3.98.2UUUUUUUUUUUUUUUUUUUUUUUUU˙űd˙Źđ��i����� ����¤��� ��4€��UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUULAME3.98.2UUUUUUUUUUUUUUUUUUUUUUUUU˙űd˙Źđ��i����� ����¤��� ��4€��UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUULAME3.98.2UUUUUUUUUUUUUUUUUUUUUUUUU˙űd˙Źđ��i����� ����¤��� ��4€��UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUULAME3.98.2UUUUUUUUUUUUUUUUUUUUUUUUU˙űd˙Źđ��i����� ����¤��� ��4€��UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUULAME3.98.2UUUUUUUUUUUUUUUUUUUUUUUUU˙űd˙Źđ��i����� ����¤��� ��4€��UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUULAME3.98.2UUUUUUUUUUUUUUUUUUUUUUUUU˙űd˙Źđ��i����� ����¤��� ��4€��UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUULAME3.98.2UUUUUUUUUUUUUUUUUUUUUUUUU˙űd˙Źđ��i����� ����¤��� ��4€��UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUULAME3.98.2UUUUUUUUUUUUUUUUUUUUUUUUU˙űd˙Źđ��i����� ����¤��� ��4€��UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUULAME3.98.2UUUUUUUUUUUUUUUUUUUUUUUUU˙űd˙Źđ��i����� ����¤��� ��4€��UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUULAME3.98.2UUUUUUUUUUUUUUUUUUUUUUUUU˙űd˙Źđ��i����� ����¤��� ��4€��UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUULAME3.98.2UUUUUUUUUUUUUUUUUUUUUUUUU˙űd˙Źđ��i����� ����¤��� ��4€��UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUULAME3.98.2UUUUUUUUUUUUUUUUUUUUUUUUU˙űd˙Źđ��i����� ����¤��� ��4€��UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUULAME3.98.2UUUUUUUUUUUUUUUUUUUUUUUUU˙űd˙Źđ��i����� ����¤��� ��4€��UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUULAME3.98.2UUUUUUUUUUUUUUUUUUUUUUUUU˙űd˙Źđ��i����� ����¤��� ��4€��UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUULAME3.98.2UUUUUUUUUUUUUUUUUUUUUUUUU˙űd˙Źđ��i����� ����¤��� ��4€��UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUULAME3.98.2UUUUUUUUUUUUUUUUUUUUUUUUU˙űd˙Źđ��i����� ����¤��� ��4€��UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUULAME3.98.2UUUUUUUUUUUUUUUUUUUUUUUUU˙űd˙Źđ��i����� ����¤��� ��4€��UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUULAME3.98.2UUUUUUUUUUUUUUUUUUUUUUUUU˙űd˙Źđ��i����� ����¤��� ��4€��UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUULAME3.98.2UUUUUUUUUUUUUUUUUUUUUUUUU˙űd˙Źđ��i����� ����¤��� ��4€��UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUULAME3.98.2UUUUUUUUUUUUUUUUUUUUUUUUU˙űd˙Źđ��i����� ����¤��� ��4€��UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUULAME3.98.2UUUUUUUUUUUUUUUUUUUUUUUUU˙űd˙Źđ��i����� ����¤��� ��4€��UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU�˙˙˙˙Ä�ř>�G˙˙˙9˙˙˙˙˙ŕ˙(& ¦˘™—ś˙űd˙Źđ��i����� ����¤��� ��4€��*ŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞvŇXf<w†i÷~ő}Ěžµ63б.Ő/4q.oÓx`f;˙űd˙Źđ��i����� ����¤��� ��4€��‚@kžĹ;Ě��hÖbf,`›ź˘$ĽĐ˘”ŃźT›L`Ěí"AGI‰ânÖŰö^ô4Ŕ0ŚaHŚelc€z®¦˙űd˙Źđ��i����� ����¤��� ��4€��ó˘pÝvNŕlO<Ľf% Úű–íĎ·ŚŇ<ş#  X ’Eű0l!µž[ţ{đŢ@LX#u � €â°˙űd˙Źđ��i����� ����¤��� ��4€��Á0$ĹaPĹA�[Âľů®÷™PżoŰt`Žk®â>Nˇ0ÁđÄ!`8ţM™:Húö×1Ď·?ó÷rJý¶ńŽ^˙űd˙Źđ��i����� ����¤��� ��4€��ĘY éC!ŚCń‘‘b8°‚ćb 0`0a€$YŹËźŻĎűŹ9ž[wŠ÷îXú{ôWż/¨ 7ĺă Áa˙űd˙Źđ��i����� ����¤��� ��4€��„Ŕ 4 ×AŔA�`8ĐI˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙úĂ˙˙ç˙˙˙˙˙˙üă/†ßąű0útE8°ë.ęDV˙űd˙Źđ��i����� ����¤��� ��4€��tSEjfVtůî÷;-ޞŻ"WL$ `yýôŻ8"ýض !äĎľ”P,ŻáÚm8”Ďć{«¬o^żz>ĺĹóţÚďçť/ű˙űD˙ŤŔ��i����� ��< t�� E�#Ž€�ż/ť†÷s˙ńƧfé·#`ů`îLŰÇóî;µ®cÜłn÷l9nü˙Ôć{Ď *ŘĂ;—íß±~1,ΕѬ"Äiź†Żľ~]˙ű°d˙€ °‰Lţ{¤‚ÇLÉŹĚŕ*r%UůĚ€ Ś+?4€�Ő>U'e|Ďyĺ¶^ÖÚ{L˝…&6ĺůëń§wÜ? ˙–óĎ»üżśĎĽÖµoyX« ĹęX쾞݌˙˙˙ü¦]ŕ‚q%�‘ŚíÜŮtD„H“#2¤D΀çxF]!™üBb±Ů„L#$“ ¦‰Jr‚€l¶˘ˇF¨B.* €»8×Iˇ,hĐi5…LT)6ĹŠ…f)FŕëD�—ĐÉ|Ł;oä¤x�ŕ,lł‡ĄÓ”/ts]Kĺ&ÜyĹUŃV-+·˝w­‡@šKi/ĂФŘŐ7rTQ‰©»Ý1oY^zSĺr-ćá—÷´,9?ä?K†1gI`h“slJÉĄńŚRś4ył8ěýVFeÎʶ7'aôpעűK7/‰¦a^EYÖM‚ž»~î08¤ýčXý¤”–čĘ`ő2j¬˛WE7MmŠ,x*öă´ŤŢ ś{ďĚÝĂęgZ˙yßżÍgŹy˙˙˙˙˙˙öĄz˝Ě·—sˇżů÷˙˙˙˙˙ţƧ? ęýZöv’‡ČH ÁŮÉŠ@D€7jîĂ”“±‘ÎňSs/4_Ę"ôÄ/Ü@ŠQsmOSQÝŐW_üHʉŽ{kŞZül~ŠŁ‘І+7,řĽŹył­-9iZŠôě "ŘF�C ��2 ÍÂP!łˇPđ(’%y\@Á`kČâť1�†E•Y§™1E°}e„ČCńäU€0«ö{,*?łĚMĎw%Ěmł·i ©ękÔ• śÖ61wńÂÖ˙ű°d,‚myS÷k�S©Š~í�áUÍ<yČzlĽd„€Źo&QjĚ5�±ŮF§đ†—„3Ě_é´ëâP;MUóĄÜ·E'Şĺž\ąÚąĎKˇçR[ AçnČ�Xu@Ý™Ô�áĂŃ\ŕç©ěnÉHĽ›şY*“OƦ)ëRE_eČ|P]#o™W0[‰R^±Of¦Ya1Ë›ą„˘ě¶]ů:±:y©¸ËŻĺ¦·c/űüÝŚű«UyÝÚ˝„öWűĎĂjţ˙<ńÂýŚ˙_ö°çóvč–z�(��¸Ë‚ŕ‚S@ď|·çe1ţ˙7eŇâpŃÇ˙Ą›M§˙™óźźĐ™ĆőTÚY•‡¬ożţÖŘhaQĽś×1˝ĘšTÝu÷ťCÁF˙śe&6–��śŘYKč,�ßT5ĺË"Ć<˝ 4Ŕ FPX™r‚$5e<Í‹¨ů@_ĘŽ4îßâăPFâTuX¶ĺRČŃľň(u·öÇTÍK5ău™MrLÄÜm—bD¨™“L%~<"ÜUůqĐŃ=Ł0DUUşÚ·ÇÝ$Ă%ô‘©Kjčţ<εó.ډI­×HbĘtĐ-¦:ňů˝â ˛/Ş“*$qëU1Đ?–'J?3×? ÷ţĐ÷W¬%˝RŔŻvË8QDR±Ć‡_Ľ_âÚ7_†fąE.Őţ›eAŔË:ľSHˇ`H–äô  ß4<kĺţS#˙˙˙˙ł˙˙˘ŮU�44€/˘’7şN@Çŕč‹Ă}'MH’ 156”ČâáąÔ,90śŁřeW‘ę’Âý‡âd°űŃŰS˙ű d!YwUsOr-č{0":=éWÍ=wŔŤí|0 TE„G”šnĚ|e-›ął1|ˇ«ôű1ôŕ•DŚ;6¸=ś¬¶°ÜĘ: ë niC˛g©ŤV_ÍźŚâ ěÜו"´ž'ˇţdt멞†Tuý%ńD8Gě&Uաҝól¬q!4ˇě­Ę4-n*Ű;Tq!]‚lR±ëZÄ„ôâeF, PZ×+¤|(–¦?ž ë;¤(»ű¬XÖ–`n™ő‹efťś!r]\Ć}3��Ş€�X´Ű®;^ ďÍ˙ý(Ůi˙ĆęÉ˙˙ű˙˙˙˙ĘË×Ič:$y ë7�d0@Čź3Ś(Ó˛ś2@Â*d,xTňë™4B"ĹmĆZPső-i dx�2a›Ďtó¶÷oŹ|ŰüˇŠ®6 JâÚz«Ź+@Ü{ÚŞŚMoşťŠ”ëjČîĎ2Ús·19ąx ĚUs<Tg[Ýj§V„”6ص˙Ĺq‰ß˛32ŕ‘ˇŇťÁ¡PÇj™Q ĺj\›§5$]»¬Ňn-ë™Á„ÚŁ!ŕiى‘FŁzW>qイŞ^`‚•RdÓCeŠůg ¶(©d\ çjŇĎᓵ·n˙ţY]?8Č/ô´JG,�N��#â0LľĂú’ŧäŻüxňˇA~Ż˙˙8ÄďČUŘW�S4@-ˇ˙ű°d,xUóOer1zŢa"ÍĺUÍ=ž‚křđ¤�HLĂJ)"@fG�€O˛Î0,ô ř`P ‡‚EĂHŠ˘<}—Ý€lB+|&“™|¸±Â¬Ěj5Ǧéo¸Yď†CńúÓ÷‰ł¤ý9\εSSői ŔŕÔéá7O!ôŠ•"–ďŇFž”Älâ°źzŞ9,§‘C < ¨ěâä SąÜr!Ĺ͇ Ř’›ÖśZ¨ä'¦LXA0…bĆqk‹/5ťq©‚îѵڑ‡kŢ?^Ľ±DQgÎŐÇv7Z«ďlÄÍ+\ŮîťśéěĘ|ôîOkkVÁs`�$B�ŔĂ^ĺ^r+JˇźA=ŐöţźúöÝnY?túüdô˙†÷ű$™ö8_wţîł�C��Đ 8pôojXhČšp@ĺ‹‚$=q4Ȅ؆šiZAá륝9*JĚŘ‘wc¸\ÓJ>ĎdĹFMI$ŽÎąp|^OËo˙soďÓŰ”Xtd–ęCŇéŮ×)B8 …Jbw¦\d{‹ă‹ć“ˇJ%Uqc8Ö>2ĺń#Ů1¸‘ŞˇW ÍVôÁś«Ä9‡h{öĽUŞšŠřĚ)'÷“rkn2X8€^ đČÔ` „Ié9­ök.ÓÓÂ^“Q“‡µřYpt`BJ·+Io“Ĺôîý\Wńomsë˙“6¦ß~ß3˙Ó,MŮcNCîr��„ �ŕĆI‚ŕJx¦ř˝•ß˙îůÚ2˙˙ë˙˙ˇŮU�B�OĄ6Ž0´ÎAă €1‡4L Z¬‚#ńIä]Gö=&@Ś”fż˙ű°djŐóOerEçŞĎeg)éYÍ=7ČĘký”ńÍSRů;[ŹSővđÄš<8FÉű–řń/çÎś—!ŻŐ±F{IW1”W<«™y—'*Ž8\¤Nł+bµCŹ[˝.ţ6ćčŰCŁŕń ĄN#|í\ąpXgŽ˘8D ‘óŘ+šĹt¬ÓĹr)XŐ(î~l;–\\t†Ä0Ňöj÷aż•ŹB“uśJ]dC›Ő0^×Íac­¬9 Őý]ňËĽ*ý[˝ËŐçŚß;đH�˘@@�.9Ęi4ɡ5JăEHť38¨A_ÖĽĘ'đ°¸Äg»uű˙u<«{ô™×zz˙Ôô*PťC O¦“˙IlçţCéX�AŔŐň�ĹÄś]FX†(©;ŠŁÂ EGJ ŇRÓ z_d^W×2‘|»qMeqşKőE–ăö–Ş´žą©¦Ź—­u}a/âÚTŰ y ~őóăaeěhŠs)¨E*ŤČ¬ŻŐjW’âŮô´}ĕƕ,‹Łt¬R/«ÉlvŘŠÔ,çCSÇůIĎ3Ú¤”đˇ»Zbz~Ť´éôSĄßBYĚ*Ö™Öí:Ů,l.6�…ń–!8d~9 oR¦¬´ťY6ZÝRĽŻe[ź?¬ů÷>üľťJÚůŁúg�@��V Uv-QbíäNĎG_ţí˙OűŃÝ‘}zőűżäe»ňßíaţµíXC4€�S%< �eŚPÓNQoŔúë R^e�p}ŹwZ„>­ÍŚŽv©ůý-ó„JpŢ–E(®ŔíĚľ˙ű°d"€ jVsOerIJ ˙a……ĺSÍ<yČý«ý¬0HĄˇÄÜŚ‘ž°â2«zSCvGE»öH1k^_‡+#)ۉm\aťîĎÂţń8c2Ąć|Ě·mbŢ&±LÁŤ>•ݧSýá9!Iĺ·QŹôŠ˘âu¨v/…{Ku5tűcl¨9ŹĄó2XPąZ;ßkýMbz6łŹMMŠĄö pz.q˙ó¸}.8‘ę)ŤŰ2Ü»ű÷ĐóWĹÔßŐř+#™P� Č*˙Ă AJE‹źÄ;úî$.˛€†6±dţżé˙ĘîČÎy?/ďý¶·˙˙Zo/űz˙ěő0á‚Á5Xď/ČúöŔ�LD�� |P¨TĚšl(p&H±ŹŐް�$Ĺ!× ›•o%čÚ:<jŰźˇ–a=Ž2ą¸ĺ=±©V—;~~BR¶Gı^(Óď›ÇLTÚmę± ‰bŔv€Ś%ŐěçP*ŔXŽt‘oHQbV''Çĺ{ŐŇxqš;ťÉ’!Eô=%Ô ĂŃ+ä)<e“łI nc%dÝXBKu0NĎ­ńvé*‰W“v\”Čr4¶(‹lăbŤ;Öř7¬}ßĚVĄT3Nö ÚatŔd3DÄ]Ĺ«$řŤVÝ)WG CU?Ę–µUmyi5thŐ�(R€�Ź� cŹņ€„ XNšŹ鱢”KŚbeetbox-beets-01f1faf/test/rsrc/full.aiff�����������������������������������������������������������0000664�0000000�0000000�00000256320�14723254774�0020500�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������FORM�\ČAIFFCOMM������¬D�@¬D������SSND�X�����������������˙˙�˙ţ�˙ţ�˙ţ�˙˙�˙ţ�˙ţ���˙ý�˙ü�˙ţ�����˙˙�˙ý�˙ţ�˙˙�˙˙���˙˙���˙ý�˙ű�˙ý�˙ţ���˙ü�˙ý���˙ţ�˙ý�˙˙�˙˙�˙˙�˙˙�˙˙���˙˙�˙˙�˙ţ���˙˙�˙ţ���˙˙�˙ţ�˙ţ����˙ţ�˙ţ��˙ţ�˙ţ�˙˙�������˙ţ�˙ü�˙ű�˙ý�˙˙���˙ţ�˙ţ�˙ţ�˙ţ�˙ţ�˙ţ�˙ý�˙˙�˙˙���˙˙�˙˙���˙ý�˙˙���˙ţ�˙˙���������˙ţ�˙ţ�˙ü�˙ý��˙ţ�˙ţ�˙ý�˙ý���˙˙�˙ţ���˙˙���˙˙�˙ţ�����˙˙�˙ţ���˙˙�˙ţ���˙˙�˙ţ���˙˙�˙ţ�˙˙˙˙�˙ţ���˙˙���˙˙���˙˙���˙˙���˙˙�˙ţ�˙˙���������˙˙�˙ý�˙ý�˙˙�����˙˙�˙˙�˙ý�˙ű�˙ü���˙˙�˙˙�˙˙�˙˙�˙˙�˙˙�˙˙���˙˙�˙˙���˙˙���˙˙�˙˙�����˙˙�˙ţ�˙ţ���������˙˙���˙˙�˙˙���˙˙�˙ţ�˙ü�˙ý�˙˙�������˙˙���������˙ţ�����������˙˙�˙ţ�˙˙�������˙ţ�˙ý�˙˙���˙˙�������˙ţ�˙ţ�˙ý�˙ţ�˙ţ�˙˙�����˙˙�������˙˙���˙˙�˙ý�˙ţ���˙˙���˙˙�˙˙���˙˙�˙˙���˙ý�˙ű�˙ţ�˙˙�˙ý�˙ţ�����˙˙�˙ü�˙ţ���˙ţ�˙ý�˙ü���˙˙�˙ý�˙ý�˙ý�˙˙���˙ţ���˙˙�˙ţ�˙˙���˙˙�˙ý�˙˙˙˙�˙ţ��˙ţ�˙ü�˙ý���˙ţ�˙˙���˙˙˙˙�˙˙������˙˙�˙˙��˙˙�˙ý�˙ú�˙ü�˙˙˙˙�˙ý�˙ű�˙ý��˙ý�˙˙������������˙˙�˙ţ�˙ţ�˙ţ�˙˙�˙ţ�˙ţ�˙ý�˙˙���˙ţ�˙ţ�˙˙�������˙˙���˙˙�˙ý�˙ý�˙ţ���˙ý�˙ţ���˙ţ�˙˙�˙ţ�˙ý�˙˙���˙ţ�˙˙���˙˙�˙˙�˙˙�˙ţ���˙˙�˙ţ���˙˙�����˙˙�˙ţ���˙˙�˙ţ�˙˙�˙˙�˙˙���˙ţ�˙ý�˙ţ�˙˙�˙˙�˙˙���˙˙�˙˙˙˙�˙ý�˙ý�˙˙���˙˙���˙˙�˙ý�˙˙���˙˙���˙ţ�˙ţ���˙˙���˙ý�˙ţ���˙˙���˙˙���˙˙���˙ţ���˙˙�˙˙���˙ý�˙˙���˙˙�˙ţ�˙ű�˙ú�˙ţ���˙˙���˙˙�˙ţ��˙ý�˙ý�˙ţ������˙ţ�˙˙�˙ţ�˙ý���˙ţ�˙ű�˙ý���˙˙�˙˙���˙ţ���˙ţ�˙ý���˙˙��˙ý�˙ý�˙ý�˙˙���˙ţ�˙ý�˙ţ���˙˙�˙ţ�˙ü�˙ý���˙˙�˙ţ�˙ý�˙ţ�˙˙�������˙ţ�˙ý�˙˙�˙˙�˙˙�����˙˙�˙˙�˙ý�˙ý�˙˙����������������������������������˙˙���˙˙�˙ý���˙ţ�˙ű�˙ü�˙˙���˙˙�˙ţ�˙ü�˙ű�˙ţ���˙˙�˙˙�˙ý�˙ű�˙ţ��˙ţ���˙ţ�˙ű�˙ű�˙˙�˙ţ�˙ü�˙ý�˙ţ�˙˙��������������˙˙�˙ţ�˙ý�˙ţ���˙˙�˙ţ�˙ý�˙ý��˙ţ��˙ţ�˙ţ�������˙˙�˙ý�˙ű�˙ý��˙ý�˙ü�˙ý�˙ţ�˙ţ�˙ţ�������˙˙���������˙˙�˙ţ���������˙˙�˙ţ�˙ţ�������˙˙�˙ý�˙ü�˙ţ���˙˙�˙ţ�˙ţ�˙˙�˙ţ�˙ţ�˙˙�˙˙�˙ţ���˙˙���˙˙�˙˙�˙ý�˙ü��˙ţ�˙ţ�˙ţ�˙ý�˙˙�������˙ţ���˙˙�˙ý�˙ý�˙ţ���˙ţ�˙ţ���˙˙���˙˙���˙˙�˙ý�˙ţ�˙˙�˙ý�˙ý�˙ţ���˙˙�˙˙���˙˙�˙˙���˙˙�˙˙�˙˙�˙˙�˙˙�˙ý�˙ý�˙ý�˙ý�˙ý�˙˙���������˙ţ���˙˙�˙˙˙˙�˙ü�˙ý�˙˙�˙ţ�˙ý�˙˙�����������˙ţ�˙ţ�˙˙���˙ţ�˙˙�����˙ţ�˙ý�˙ý�˙ý�˙ţ�˙˙�˙ţ�˙ý�˙ü�˙ű�˙ű�˙ý�˙ţ�˙˙���˙ţ�˙ý�˙ü�˙ý�˙ţ�����˙ý�˙ţ���˙ý�˙ý�˙ű�˙ü�˙ţ�˙ý�˙ű�˙ü�˙ü�˙ţ�˙ţ�˙ţ��˙ţ�˙ţ�˙ţ���˙˙�˙ţ���˙˙���˙ţ�˙ý�˙˙���˙˙���˙ü�˙ý���˙˙���˙ţ�˙ţ���˙˙�˙ţ�˙˙˙˙�˙ü�˙ý�˙˙˙˙�˙ü�˙ţ˙˙�˙˙˙˙�˙ý��˙ţ��˙ţ�˙ý���˙˙�˙ý�˙ý�˙ü�˙ý������˙ţ�˙ţ�˙˙�������˙ţ�˙ü�˙ü��˙ţ�˙ţ˙˙�˙ü�˙ű�˙ü�˙ű�˙ý��˙˙���������˙˙�����˙ţ�˙ű�˙ü���˙ţ�˙ü���˙˙�˙˙�����˙˙�˙˙�˙ţ�˙˙�˙˙�˙ý�˙ý�˙ţ���˙˙�������˙˙�˙˙���˙˙���˙ţ�˙ý�˙ţ�˙˙˙˙�˙ü�˙˙˙˙���˙˙�˙ţ�˙ý�˙ţ��˙ý�˙ü�˙ţ�����������������˙˙�˙ţ���˙˙�˙ţ��������˙ţ�˙˙���˙˙���˙˙�˙˙�˙˙�˙˙�˙ţ�˙ü�˙ý�˙ţ������˙ţ�˙ţ�˙ü�˙ű�˙ý���˙˙�˙ţ���˙˙�˙ü�˙ý�˙˙�����˙ţ�˙ţ�˙˙���˙ţ�˙˙���˙˙�������˙˙�˙ţ�˙ţ�˙ţ�˙ü�˙ű�˙ű�˙ü�˙ţ�˙˙�����������˙ţ�˙ţ�˙˙����˙˙��˙ý�˙ý�˙ţ���˙ţ�˙ű�˙ţ�˙˙�˙ţ�˙˙�˙˙���˙˙�˙ý�˙ţ�����˙˙�˙ý�˙ü�˙˙˙˙�˙ü�˙˙˙˙�˙ţ�˙ţ������˙ţ������˙˙��˙˙�˙ü�˙ű�˙ý�˙ý�˙˙���˙˙˙˙�˙ü�˙˙������˙˙��˙ţ�˙ţ��˙ţ�˙ţ�˙ţ�˙ţ�˙˙˙˙�˙ü�˙˙˙˙�˙ý���˙˙��˙ţ�˙ţ���˙ý�˙˙���˙˙�˙ţ�˙ý�˙˙���˙ţ�˙˙�˙˙�������˙˙�������˙ţ�˙ü�˙ý�˙˙�˙ţ�˙ý�˙˙�˙ţ�˙ţ�˙ý�˙˙���˙ţ�˙˙���˙ţ�˙˙���������˙ţ�˙ý�˙ü�˙ý��˙ţ�˙˙�˙ţ�˙ý�˙ý�˙ý�˙ü�˙˙��˙ý�˙ü�˙ü�˙ý�˙ţ���˙˙���˙˙�˙˙˙˙�˙ţ�˙˙�������˙ţ�˙ţ������˙ţ�˙ţ���˙ţ�˙ţ���˙ţ�˙ţ�˙ţ���˙˙���˙˙�˙˙�˙˙�˙ü�˙ţ���˙˙�˙˙���˙˙���˙ţ�˙ü�˙ţ�˙ý�˙ű�˙ü�˙ţ���˙ý�˙ţ�����˙˙�˙ý�˙ţ���˙˙�����������˙ţ��˙ţ�˙˙���˙ţ�˙˙���˙ţ�˙ü�˙ü�˙ü�˙˙˙ţ�˙ü�˙˙˙˙�˙˙���˙ţ�˙˙������������˙˙�˙ţ�˙ţ���˙˙�˙ý�˙ü�˙ţ���˙ţ�����˙˙�˙ý�˙ţ�˙ţ�˙˙������������������˙ţ��˙ţ�˙ü�˙˙���˙ţ�˙˙�˙ţ�˙ű�˙ú�˙ý��˙˙�˙ţ�˙˙�˙˙�˙˙���˙ý�˙ţ���˙ý�˙ý�˙ţ�˙ţ���˙ţ�˙ţ�����˙˙�˙ţ�˙ţ���˙ý�˙ü�˙˙���˙ý�˙ý���˙˙�˙˙�˙˙���˙˙�˙˙�����˙˙���˙ţ�˙ý�˙ý�˙ţ�˙˙�˙ţ�˙ţ���˙ţ���˙˙���˙ţ�˙ý���˙ý�˙ü�˙ý�����˙˙�˙ţ���˙˙���˙˙�˙˙���˙˙�˙˙�˙˙�˙˙�˙˙�˙˙���˙˙�˙˙�˙˙�˙ý�˙ý���˙˙�˙ý�˙ţ���˙˙�˙ţ�˙ý�˙ţ��˙ý�˙ü��˙ţ�˙˙˙˙�˙˙�����˙ţ�˙˙˙˙�˙ü�˙˙���������������������������˙ţ�˙ţ�˙˙�˙ţ�˙ţ�˙˙���˙ţ�˙˙���˙ý�˙˙���˙˙���˙ţ�˙ţ�˙˙�˙ţ�˙˙�˙˙�����˙˙���˙ţ�˙ý�˙˙���˙ţ�˙ţ���������˙˙�˙ţ�˙˙�˙ţ�˙ţ�˙ý�˙ţ�˙ý�˙ţ��˙ţ�˙ý�˙ü�˙ü�˙ţ�˙˙����������˙˙�˙ţ�˙ý�˙ţ�˙ţ�˙ţ��˙ţ�˙˙˙˙�˙ü�˙ü�˙ý�˙ţ�˙ţ�˙ţ���������˙˙��˙ý�˙ů�˙ű��˙ý�˙ţ��˙ţ�˙ţ�����������˙˙���˙˙�˙ü�˙ţ��˙ţ�˙ţ������˙ţ������˙ţ�˙ţ�˙ţ�˙ţ�����˙˙�˙ý�˙˙˙˙�˙ţ��˙ţ�˙˙˙˙�˙˙˙˙�˙ţ���˙˙�˙˙�˙˙�˙˙���˙ý�˙ý���˙˙�˙˙�˙˙�˙˙�˙˙���˙˙�˙ţ�˙ţ�˙ý��������˙ţ�˙˙˙˙�˙ţ�˙ü�˙ţ�����˙˙���˙ţ�˙ű�˙ý�˙ý�˙ý�˙ý�˙ţ�˙ţ�˙˙�˙˙˙˙�˙ţ�����������˙˙�˙ü�˙ü�˙ý��˙ý�˙ű�˙˙�����˙ý�˙ý�˙ţ�˙ý�˙˙˙˙�˙ű�˙ý��˙ý�˙ý��˙ý�˙ü�˙˙��˙˙�˙ţ�˙˙˙˙��˙ţ�˙˙˙˙���������˙˙�˙ţ�˙˙�˙˙����˙˙�˙˙˙˙�˙ü�˙˙˙˙�˙ţ�˙ţ���˙˙�˙ü�˙ţ��˙ţ��˙ţ�˙ţ��˙ţ�˙ţ�˙ţ�˙˙���������˙ţ�˙ţ���˙˙�˙ţ�˙ý�˙ü�˙ü�˙ţ�˙ţ�˙ţ�˙ţ�˙ü�˙ý�˙ţ�˙˙���˙ţ�˙˙�˙˙�˙˙�˙˙�˙˙�˙˙�˙ý�˙ţ���˙ý�˙ţ�˙˙���˙˙�˙ţ���˙˙�˙˙�˙ý�˙ý�˙ţ���˙˙���������˙˙�˙ţ���˙˙�˙ţ���˙˙���˙˙�˙ţ���˙ţ�˙ţ�˙˙�˙ţ�˙ý���˙ţ�˙ý�˙ý�˙ü�˙ţ˙˙�˙ü�˙ü�˙ü�˙ý��˙ý�˙ü�˙ü�˙ţ�˙˙�˙ý�˙ű�˙ű�˙ü���˙ţ�˙ü�˙ţ�������˙˙�˙˙���˙ţ�˙˙�˙˙��˙˙�˙ţ�˙ţ���������˙˙�˙ţ�˙ý������˙˙˙˙�˙ţ�˙˙˙˙�˙ţ��������������������˙˙�����˙ţ�˙ý�˙˙�˙˙�˙˙�˙˙�˙˙�˙˙���˙ţ�˙ý��˙ţ�˙ý�˙ţ�˙ţ�˙ţ�˙˙���˙ţ�˙ţ�˙ţ�����˙ţ�˙ú�˙ű�˙ü�˙˙�˙˙�˙ţ����������������˙ţ�˙˙�������˙˙�˙˙���˙˙�˙˙�˙ţ�˙ý�˙˙���˙ţ�˙ţ�˙ţ���������˙˙�˙ţ�˙ţ���˙˙�˙ý�˙ţ���˙ü�˙ü���˙ţ�˙ý�˙˙���˙˙�˙˙�������˙˙�˙˙���˙ý�˙ţ���˙˙�����˙˙���˙˙�˙˙���˙˙�˙˙���˙˙���˙˙���˙˙�˙ţ���˙ý�˙ü�˙ý�˙ű�˙ü�˙˙˙˙���˙ţ�˙ţ�������˙˙�˙˙�˙˙�˙˙�˙˙�˙˙�˙ţ�˙ţ�˙˙�˙ţ�˙ű�˙ţ���˙ý�˙˙���˙˙�˙˙���˙ţ�˙˙�˙˙���˙ţ�˙ţ���˙˙���˙˙���˙ţ���˙ţ�˙ý�˙˙�˙˙�˙˙�������˙˙��˙˙�˙ý�˙ý��˙˙�˙˙�˙ţ�˙ý�˙ţ���������˙˙�˙ţ�˙ţ���˙˙�˙ţ�˙ţ�˙ţ�˙˙���˙˙�˙˙�˙˙�˙˙�˙˙�˙˙���˙˙�˙˙�˙˙�˙˙�˙˙���˙ţ�˙ý�˙˙�����˙ţ�˙ţ�˙˙�˙ţ�˙ü�˙ü�˙ţ�˙˙�˙ţ�˙˙˙˙�˙ţ���������˙˙�˙˙˙˙�˙ţ�˙˙�����˙ţ�˙˙���˙˙�����˙˙�˙˙�����˙˙�˙ţ�˙ý�˙ý�˙˙���˙ţ�˙ý�˙ý�˙˙���˙˙�˙˙�˙˙�˙˙�˙˙�˙ţ�˙ý�˙ţ���˙˙���˙ţ�˙ţ���˙˙�˙ţ���˙˙�˙ţ���˙˙���˙˙���˙˙�˙ý�˙ý�˙ý���˙ţ�˙ü�˙ű�˙ý���˙ţ�˙ý���˙˙�˙˙���˙˙�˙ý�˙˙�˙˙�˙ţ�˙˙������˙˙�˙ü�˙ý����˙ţ�˙ý�˙˙�˙˙�˙ţ��˙ý�˙ü�˙ý������˙ţ�˙ţ�˙˙���������˙ţ�˙ü�˙ű�˙ű�˙ţ���˙˙�˙ţ�˙˙���˙ţ�˙˙����˙˙�˙ţ�˙ţ���˙˙�˙ü�˙˙�����˙ý�˙ü�˙ţ�˙ţ���˙˙�����˙˙�˙ý�˙ţ���˙˙�˙ţ���˙ý�˙ţ���˙ţ�˙˙�˙˙�˙ţ���˙˙�˙ţ�˙ţ�������������˙˙�˙ü�˙ü�˙˙�˙ţ�˙ţ�˙˙���˙ţ�˙˙���˙ţ�˙˙���������˙ţ�˙ţ�˙ţ��˙ţ�˙ţ�������˙˙�˙ü�˙ý��˙ţ�˙˙˙˙�˙ţ���˙˙���˙˙���˙˙�˙ţ���˙˙�˙ţ�˙ý�˙ü�˙˙˙˙�˙ţ���˙˙���˙˙�˙ţ��������˙ţ��˙ţ�˙ţ�����˙˙�˙˙�˙ţ�˙ý�˙ţ�˙˙�������˙ţ�˙˙�˙ţ�˙ý�˙ý�˙ţ����������˙˙���˙˙���˙˙���˙˙���˙˙���˙˙�˙ţ������������˙˙���˙˙�˙˙�˙˙�˙ý�˙˙�˙˙�˙ţ�˙˙���˙ţ�˙ý�˙˙˙˙�˙˙�˙ţ���˙˙�˙ý�˙ý�˙˙˙˙���˙˙�˙˙�����˙˙�˙ţ��˙ţ�˙ţ�������˙˙�˙ý�˙ű�˙ţ�˙˙�˙ţ�˙ý�˙ü�˙ý�˙ţ���˙ţ�˙ţ���˙˙���˙ý�˙ű�˙ű�˙ţ�˙˙���˙˙�˙ţ�˙˙�˙˙�������˙˙���������˙ţ������������������������������˙ý�˙ü�˙ţ�˙ţ�˙ý�˙ţ�˙ţ�˙ţ�˙ţ�˙˙�˙ţ�˙ţ�˙ý�˙˙�������˙ţ�˙ü�˙ý�˙ý���˙ţ�˙ű�˙ü�˙ý���˙˙�˙˙���˙ý�˙˙�˙˙�˙ý�˙ţ������������˙ţ�˙ü�˙ý�˙ý�˙ý�˙ý�˙ţ�˙ţ���˙˙�˙ţ�˙˙�˙˙���˙ý�˙ű�˙ţ���˙˙���˙˙�˙ţ�˙ý�˙ţ���˙˙�����˙˙�˙˙���˙˙���˙˙�˙˙���˙ý�˙˙�˙˙�˙˙���˙˙�����˙˙�˙ý�˙ü�˙ţ�������˙˙�������˙˙���˙˙�˙˙���˙˙���˙˙���˙˙�˙ţ���˙˙���˙˙���˙ţ�˙ű�˙ü�˙ţ�������������������������˙˙�˙ü�˙ü�˙˙˙˙�˙ţ�˙ţ�˙˙�˙ý�˙ý�˙ţ���˙ţ�˙ý�˙ý�˙ý�˙ţ�������������������������˙˙�˙ţ�˙ý�˙ţ��˙ü�˙ü���˙ţ�˙ű�˙ţ���˙ý�˙ţ���˙˙�˙ţ�˙ţ�˙ţ�˙ţ�˙˙�˙ţ�˙ţ�˙ý�˙ý�˙˙�˙˙�˙ţ�˙ţ�˙ü�˙ţ���˙˙���˙˙�˙ţ�˙ţ�˙ţ�˙ý�˙˙�˙˙�˙˙���˙ţ�˙ţ���˙ţ�˙ý���˙ţ�˙˙���˙˙�����˙ý�˙ý���˙˙�˙˙�˙˙�˙ý�˙ţ�����˙˙�˙ţ�˙ţ�˙ý�˙ü�˙˙˙˙�˙ţ��˙ţ�˙ţ��˙˙���˙ý�˙ý�˙ţ�˙ţ���˙˙���˙˙�˙˙�����˙˙�˙ü�˙ü���˙˙���˙˙�˙˙���������˙˙�˙ţ�˙ţ�˙ţ�˙ţ�˙ţ�˙˙˙˙�˙ý�˙ţ�˙˙���˙ţ�˙ţ�����˙˙�˙ý�˙ű�˙ý�����������˙˙�˙ţ�˙ý�˙ý���˙˙�˙˙���˙˙�˙ý�˙ţ�����˙˙�˙ý�˙ţ���˙˙�˙˙�˙˙�˙˙�˙˙�˙˙���˙ţ�˙ü�˙ý�˙˙���˙˙�˙˙�˙˙���˙ý�˙ý�˙˙�˙˙���˙ţ�˙ý�˙˙���˙˙���˙˙�˙˙�˙˙���˙ţ�˙ü���˙ţ�˙ţ���˙˙�˙˙�˙ţ�˙ţ���˙˙�˙ţ�˙ţ�����������˙˙�˙ţ���˙˙�˙˙�˙˙�˙˙�˙ţ�˙ý�˙˙�����˙ţ�˙˙���˙˙���˙˙�˙˙���˙ţ�˙ý�˙˙�����˙ţ�˙ü�˙ý��˙ţ�˙˙�������˙ţ�˙ý�˙˙���˙ţ�˙ý�˙ý�˙ţ�˙ű�˙ű�˙ţ�˙˙�˙˙���˙ţ�˙ý�˙ţ�����˙˙���˙˙�˙ý�˙ţ���˙˙�˙ţ�������������˙˙�˙ţ�˙ţ�˙˙�˙ţ�˙ý�˙ţ���˙˙�����˙˙���������˙˙�˙ţ�˙˙����˙˙�˙˙˙˙�˙ţ�˙ţ�˙ý�˙ü�˙ü�˙ü�˙ý�˙˙�������˙˙��������������������˙˙�˙ţ��˙ý�˙ţ�����˙˙�˙ý�˙˙˙˙�˙ý��˙ý�˙ü��˙ý�˙ţ�����˙ţ�˙ý�˙ţ���˙˙���˙ü�˙ý���˙ţ�˙˙���˙˙���˙˙���˙ţ�˙˙���˙˙�������˙˙�˙ţ�˙˙���˙˙���˙ţ�˙˙���˙ţ�˙ü�˙ű�˙ý�˙˙�˙˙���˙˙���˙˙���˙ţ�˙ý�˙ý�˙ý�˙ţ�˙ţ���˙˙�˙ţ���˙˙�����˙˙�˙ţ��˙ţ�˙˙���˙˙���˙ţ�˙˙���˙ţ�˙˙���˙ţ�˙ý�˙˙�˙ţ�˙ü�˙ü�˙˙�˙˙�˙˙�˙˙�˙ý�˙ţ���˙ý�˙ý�˙ţ�˙˙�˙˙���˙˙�˙ţ���˙˙�˙ý�˙ý�˙ý�˙˙�˙ý�˙˙���˙˙�����˙˙�˙˙�˙˙�˙ţ�˙ü�˙ü�˙ü�˙ü�˙ü�˙ü�˙ý�˙˙�����˙ţ�˙˙�˙ţ�˙ţ��˙ţ�˙ţ�˙ţ�˙˙���˙ţ�˙˙���˙˙����˙˙�˙˙˙˙�˙ý�˙ţ�����˙˙�˙˙�˙˙�˙˙���˙˙���˙˙�˙˙�����˙˙�˙ţ�˙ü�˙ü���˙˙�˙˙�˙˙�˙ý�˙ţ�˙˙�˙˙���˙˙�˙ý�˙ţ���˙˙�˙ţ�˙ţ���˙ţ�˙ţ�����˙˙�˙ý�˙ü���˙ţ�˙ű�˙ü�˙ţ���˙ţ�˙ţ���������˙˙�˙˙������˙˙�˙ü�˙ü�˙ý��˙ţ�˙ţ����˙ţ�˙˙�˙ţ�˙ţ��˙ý�˙ú�˙ý�˙˙�������������˙ţ�˙˙���˙ţ�˙ý�˙˙�˙˙�˙ţ�˙ţ���˙ţ�˙ý�˙ý�˙ý�˙ţ�˙˙�˙ţ�˙ţ�˙ţ���˙˙�˙ý�˙ý���˙˙�˙˙���˙˙���˙ü�˙ű�˙ý�˙˙���˙˙���˙ţ�˙˙���˙˙�˙ţ�˙ý�˙˙�˙˙���˙˙�˙˙���˙˙�˙˙�˙˙�˙˙�˙˙�˙ý�˙ţ���˙ý�˙ţ���˙ý�˙ţ���˙ý�˙ţ�˙˙�˙ţ�˙ţ�˙˙���˙˙�˙ý�˙ý�˙ţ���˙ý�˙ţ���˙ţ�˙˙���˙˙�˙ý�˙ý�˙˙�˙˙�˙ţ�˙˙���˙ý�˙˙�˙˙�˙ý�˙ţ�˙˙�˙ý�˙ţ���˙ý�˙ţ�˙˙�˙ý�˙ţ���˙ţ���˙ü�˙ü�˙ţ������������˙ţ�˙ü�˙ý�˙˙˙˙�˙ý�˙˙˙˙�˙ý�˙˙���˙˙���˙ţ�˙ţ���˙˙���˙ý�˙ý�˙ý�˙ţ���˙˙�˙˙�˙ţ���˙˙�˙˙�����˙˙���˙ý�˙ű�˙ü�˙ţ�˙˙�˙ţ���˙ţ�˙ű�˙ű�˙ţ�������˙ţ�˙ü�˙ý�˙˙���˙ţ�˙ü�˙ý�˙˙�������˙ţ�˙ü�˙ý��˙ţ�˙˙�����������������˙ý�˙ţ��˙ý�˙ü�˙ü�˙ü�˙˙�����˙ý�˙ü�˙ţ˙˙�˙ý�˙ý�˙˙�˙˙�˙˙�˙˙���˙˙�˙˙���˙ţ�˙ü�˙ý�˙ţ�˙ţ�˙ţ�˙˙�����������˙ţ�˙ţ���������˙˙�˙ý�˙ý��������˙ţ�˙ţ�˙˙˙˙�˙ý���˙˙�˙˙����˙˙�˙ţ�˙ţ�˙ţ�˙˙˙˙�˙ü�˙˙��˙˙�˙ý�˙ţ���˙ţ���˙˙�˙ţ�˙ţ���˙˙�˙ű�˙ü���˙˙�˙˙�˙˙�˙˙�˙ţ�˙ü�˙ű�˙ý�˙˙�˙˙���˙˙�˙˙���˙˙���˙ý�˙ű�˙ý����˙ţ�˙ţ��˙ý�˙ü�˙ţ���������˙˙�˙˙���˙ţ�˙ţ�˙ü�˙ý�˙˙�������˙˙�˙˙�˙ţ�˙ű�˙ý���˙˙���˙˙�˙˙���˙˙�˙˙���˙ţ�˙ý�˙˙���˙ţ�˙˙���˙ţ�˙ţ�˙˙�˙ţ�˙˙˙˙�˙ý���˙ţ�˙˙�˙ţ�˙ý�˙˙˙˙�˙ţ�˙ţ�����˙˙�˙˙�����˙˙�˙˙�˙ţ���˙˙�˙ý�˙ţ�˙˙�˙ý�˙ű�˙ü�˙ţ�˙˙�˙˙�˙˙���˙˙�˙ţ��������˙ţ���������˙˙���˙˙�˙˙���˙ţ�˙ý���˙ţ�˙ý�˙˙���˙˙���˙ţ�˙ţ�˙ý�˙ţ����˙ţ�˙˙˙˙�˙ü�˙ý����˙ţ�˙ü�˙ü�˙ű�˙ţ���˙˙���˙˙�˙˙�˙˙�˙˙���˙˙�˙˙���˙ý�˙ţ�˙ý�˙˙���˙˙�����˙˙�˙˙�˙˙�˙˙�˙ý�˙ü���������˙ţ�˙ţ�˙˙���˙˙������������������˙˙���˙˙�˙˙�˙˙�˙ţ�˙ţ���˙ý���˙ţ���˙ţ�˙ü�˙˙���˙ţ�˙˙�������˙˙�������˙ţ�˙˙�����˙ţ�˙ü�˙ţ���˙ţ�˙˙������������˙˙�˙ţ�˙˙�����˙˙���˙ţ�˙ý�˙ţ�˙ü�˙ý�˙˙�����˙˙�����˙˙�˙ţ�˙˙�˙˙�������˙˙�˙ţ�˙ü�˙ý�˙˙�������˙ý�˙ű�˙ý�˙˙�˙˙�˙˙�˙˙���˙˙�˙ţ�����˙˙�˙˙�����������˙ţ�˙˙���������˙ţ�˙ý�˙ý�˙ţ�����˙˙�˙ü�˙ü�˙˙���˙ţ�˙˙���˙ţ�˙ţ�˙ţ�˙ţ�˙ţ�˙ţ�˙ţ�˙ţ���˙˙�˙ý�˙ţ���˙ţ�˙ý�˙ý�˙˙���˙ü�˙ü�˙ţ���˙ţ���˙˙���˙˙�˙ţ�˙ţ�˙ţ�˙ý�˙˙�˙˙�˙˙���˙ţ�˙ţ���˙ţ���˙ţ�˙˙���˙˙���˙ý�˙˙�˙˙�˙ü�˙ý���˙ţ�˙˙�˙ţ�˙ü�˙ţ���˙ţ�˙˙���˙ţ�˙˙������������˙˙�˙ý�˙ţ���˙˙�˙˙�˙ţ�˙ţ�������������˙˙�˙ţ�˙ţ�˙ţ�˙ţ�˙ţ�˙˙���������˙˙���˙ţ�˙ţ�˙˙����˙˙�˙ţ�˙ţ�����������˙˙�˙ü�˙ý����������˙ţ�˙ţ�˙˙�˙˙�˙˙�˙˙�����˙˙���˙˙�˙ţ���˙˙�˙ţ�˙˙���˙˙�˙ý�˙ţ���˙ţ���˙˙�˙˙�˙˙�˙˙���˙ţ�˙ţ�����˙˙�˙˙�˙ţ�˙˙�����˙˙�˙˙�˙˙�˙˙�˙ţ�˙ý�˙ý�˙˙�˙˙�˙ţ�˙ţ���˙˙���˙˙�����˙˙�˙˙���˙˙�˙˙�������˙˙�˙˙�����˙˙�˙˙�˙˙�˙˙���˙˙�˙˙���˙˙�˙˙�����˙˙�˙ţ���������˙˙���˙˙�˙ý�˙ţ���˙ý�˙ţ���˙˙���˙˙�˙˙���˙˙�˙ý�˙ü�˙ţ�����˙˙���˙˙�˙ţ���˙ý�˙ţ�����˙ţ�˙ţ��˙ý�˙ţ�����˙˙�˙ý�˙ü�˙ţ���˙˙�˙ý�˙ţ���˙ţ�˙˙�˙˙�˙ţ���˙˙�˙ţ�������˙˙���˙˙�˙ý���˙˙�˙˙���˙˙���˙ţ�˙ü�˙ű�˙ü��˙˙�˙ţ���˙˙�˙˙���˙˙�˙˙�����˙˙�˙ţ��˙ţ��˙ý�˙ü�˙ü�˙ţ����˙ý�˙ű�˙ú�˙ü�˙˙˙˙�˙˙�����˙˙�˙ý�˙ţ�����˙˙�˙ţ��˙ý�˙ú�˙ű���˙ţ�˙ý���˙˙�˙˙���˙˙���˙ţ�˙˙���˙ţ�˙˙���������˙ţ�˙ţ�˙˙���˙ţ�˙ţ�˙˙����˙˙�������˙˙���˙˙�˙ţ��˙ţ�˙ţ��˙˙���������˙ý�˙ü�˙ý������˙ţ�˙ü�˙ý�˙˙�˙ţ�˙˙���˙ţ��˙ţ�˙ţ�˙˙���˙˙���˙ý�˙ţ�˙˙�˙˙���˙˙���˙˙˙˙�˙ú�˙ý˙˙�˙ý���˙ţ�˙˙�˙˙�����˙ţ�˙ű�˙ů�˙ý��˙ţ�˙ţ�˙˙�������˙ţ�˙˙���˙ţ�˙˙�����˙ţ�˙˙˙˙�˙ý��˙ţ�˙˙���˙˙���˙˙���˙˙���˙˙���˙˙�˙ý���˙˙�˙ý�˙ý�˙ý�˙ű�˙ű�˙ţ�˙˙�˙ý�˙ţ�����˙˙���˙˙�˙˙�����˙ţ�˙ţ��˙ý�˙ü�˙ý�˙˙�����˙ý�˙˙�����˙˙���˙ţ�˙ţ���˙˙�˙˙�˙˙�˙ý�˙˙���˙˙�˙˙���˙˙�˙ţ�������������˙˙�˙ţ�˙ţ���������˙˙�������˙˙�����˙˙�˙ţ�˙ţ�˙ţ�˙ý�˙ţ���˙ý�˙ý�˙ţ�˙˙�˙˙�˙˙�˙˙�˙˙�˙˙�˙˙���˙˙�˙ý�˙ţ���˙˙�˙ý�˙ü�˙ţ�����˙˙�˙ý�˙ű�˙ű�˙ţ���˙˙��˙˙�˙ţ�˙ţ��˙ţ�˙˙˙˙�˙ţ�˙˙˙˙�˙ý�˙ý�˙ţ�˙ţ���˙ţ�˙ţ�˙˙�˙ţ��˙ţ�˙˙˙˙�˙ü�˙ţ�����˙˙�˙ţ�˙ţ����˙ţ�˙ţ�˙˙�˙˙���˙ü�˙ú�˙˙˙ţ�˙ű�˙ű�˙ú�˙ý���˙ţ���˙˙�˙˙�˙˙�˙˙�˙˙�˙˙���˙˙�˙˙���˙˙�˙ţ�˙ý�˙ý�˙˙���˙˙�˙˙�����˙˙�˙˙���˙ţ�˙ý�˙˙���˙ţ�˙ü�˙ý�˙ţ���˙˙���˙˙�˙˙�˙ţ�˙ý��˙ţ�˙˙˙˙�˙˙���˙ţ�˙˙��������������˙˙�˙ţ���˙˙�˙ţ�˙ý�˙˙˙˙�˙ţ�˙ţ�˙˙�˙ţ�˙ý�˙ţ�˙˙���˙˙�˙ý�˙ţ�����˙˙���˙˙���˙ţ�˙ű�˙ţ���˙˙�˙ţ�����˙˙�˙ţ�˙ţ���˙˙�˙ü�˙ý���˙˙�˙ü�˙ü�˙ţ�˙ţ�˙ţ�˙˙˙˙�˙ţ���˙˙�˙ţ�˙˙�����˙˙�˙ý�˙ţ�����˙˙���˙˙�˙ţ���˙ţ���˙˙�������˙˙�˙ý�˙ü��˙ý�˙ü���������˙˙�˙ţ�˙ý�˙ţ���˙˙�˙˙���˙ý�˙ý�˙˙�˙˙�˙˙���˙ţ�˙ý�˙ü�˙ý���˙˙�˙ý�˙ţ���˙˙�����˙˙�˙ý�˙ţ���˙˙�˙ý�˙ţ�˙ţ���˙˙�˙ü�˙˙˙˙���˙˙�˙ţ���˙ý�˙ţ���˙ý�˙ý���˙˙�˙˙���˙˙�˙˙���˙ţ�˙ý���˙ţ�˙ý�˙ţ�˙˙�˙˙�˙ţ�˙˙�˙ţ�˙ţ���˙ţ�˙ţ���������˙˙�˙˙˙˙�˙ţ�˙ţ������˙ţ��˙ţ�˙ü�˙ü�˙ý�˙˙˙˙�˙ţ�˙ý�˙ţ��˙ţ�˙˙���˙ý�˙ű�˙ü�˙˙�˙ý�˙ţ���˙ý���˙˙�˙˙���˙˙������˙ţ��˙ţ�˙˙����������˙˙�˙˙�˙ü�˙ý�˙ţ���������˙˙�����������˙˙�˙ţ�˙˙���������˙ţ�˙ü�˙ý���˙ţ�˙ţ�˙˙�˙˙�������˙˙�˙ţ�˙ţ�����˙˙�˙ý�˙ţ�˙˙�˙ţ���˙˙�˙ý�˙ý�˙ţ���˙˙���˙˙���˙ţ�˙˙���˙ý�˙ţ��˙ý�˙˙���˙˙�˙˙�˙˙�����˙˙�˙˙�˙ţ�˙˙�˙ý�˙ü�˙ţ�������˙˙�˙ţ���˙˙�˙ý�˙ü�˙ţ�˙˙�˙ý�˙ţ���˙˙�����˙˙�˙ý�˙ű�˙ţ���˙ü�˙ý�˙˙�������˙ţ�˙ţ�˙˙���˙ţ�˙˙�����˙ţ�˙˙�����˙ţ�˙ý�˙ü�˙ý�˙ý�˙˙���˙˙�˙˙�˙ţ�˙˙���˙ţ�˙ţ�˙˙�����˙ţ�˙ü�˙ý�˙˙�˙ţ�˙˙���˙ţ�˙ţ�˙˙˙˙�˙ü�˙ý�˙˙���˙˙�˙ţ�˙ţ�˙ý�˙˙˙˙�˙ţ�˙˙�������������˙ţ�˙ţ�˙ţ�˙ţ�˙ţ�˙ţ�˙ţ�˙˙˙˙�˙ţ��˙ţ�˙ţ��˙ţ�˙ţ�˙˙�������˙˙���˙˙���˙˙�����˙˙�˙ţ�˙˙�˙˙���˙˙�����˙˙���˙ţ�˙ý�˙˙�˙ţ�˙ý�˙˙�����˙ţ�˙ý�˙˙�����˙˙���˙ý�˙ű�˙˙���˙˙�˙˙�˙˙�����˙˙���˙ţ�˙ű�˙˙���˙˙���˙˙���˙˙���˙˙���˙ţ�˙ţ����˙ţ�˙˙���˙ţ�˙ţ�˙ü�˙ü�˙ý�˙ţ��˙ý�˙ü�˙ţ�˙˙�˙ý�˙ű�˙ü�˙ţ�˙˙�˙ţ���˙˙���˙˙�˙˙���˙˙�˙˙���˙ý�˙˙���˙˙�˙˙�˙˙�˙˙�˙ý�˙ý�˙˙���˙˙���������˙ţ�˙ţ�˙˙����˙˙�˙ý�˙ţ���˙ü�˙ü���˙˙�˙˙�˙ý�˙ţ���˙˙���˙˙�˙ý�˙ţ���˙ý�˙ü�˙˙�˙˙�˙˙���˙ţ�˙ţ�˙ý�˙ţ�˙˙�˙˙���˙ţ�˙ý�˙ü�˙ý�˙ý�˙ý�˙˙�˙˙�˙ý�˙˙�˙˙�˙ý�˙˙���˙˙���˙ţ�˙˙���˙˙���˙˙�˙˙�˙˙�˙˙���˙˙�����˙ţ�˙ü�˙ý�˙˙���˙ţ�˙ý�˙ý�˙˙�˙ţ�˙ţ���˙ü�˙ţ����˙ţ�����������˙˙�˙ý�˙ý�˙ý�˙ý���˙ţ�˙ü�˙˙˙˙�˙ý���˙˙�˙˙�����˙˙�˙˙���˙˙�˙˙���˙˙���˙˙�˙ţ�����˙˙�˙ţ���˙˙�˙ţ���˙˙�˙ţ�˙ţ���˙˙�˙ý�˙ţ�˙ý�˙˙��˙ý�˙ý�˙ţ���˙˙���˙˙�˙ţ�˙˙�˙ţ���˙ţ�˙ű�˙ü�˙ý�˙ý�˙ţ�˙˙���˙˙�˙˙�����˙˙�˙ţ�˙ţ�˙˙�˙ţ�˙ü�˙ü�˙˙�˙˙�˙˙���˙˙�˙˙���˙˙���˙˙�˙˙���˙ţ�˙˙�����˙ý�˙ý�˙˙�˙ý�˙ý�˙ţ���˙˙���˙˙�˙˙�����˙˙�˙˙�˙ţ�˙ţ��˙ţ�˙ü�˙ü�˙ý�˙˙˙˙���������˙˙�˙˙˙˙�˙ü�˙ý��˙ý�˙ü�˙ţ���˙˙���˙˙�˙ţ�˙ţ�˙ý�˙ţ�˙ý�˙ý���˙˙�˙ü�˙ţ���˙˙�˙˙�˙˙�˙˙�˙˙�˙ţ�˙˙���˙˙���˙˙���˙˙���˙˙���˙ü�˙ý�˙˙�����˙ţ�˙ţ�˙ţ�˙ü�˙ý�˙˙�˙˙�˙˙�˙˙�˙˙���˙ţ�˙ý�˙˙˙˙�˙ý�˙ý���˙ţ�˙ý�˙ţ�˙˙�˙˙���˙˙�˙˙���˙ţ�˙ü�˙ű�˙ý�����������˙˙�˙ţ�˙˙˙˙�˙ţ�˙˙��˙˙�˙ţ��˙ý�˙ü�˙ţ�˙ţ�˙˙˙˙�˙ţ�˙˙˙˙�˙ţ�����˙˙�˙˙�˙ţ�˙ý�˙ý��˙ţ�˙˙˙˙�˙ü�˙˙˙˙�˙ü�˙ţ��˙ţ���˙˙�˙ý�˙ţ�˙˙�˙˙�˙˙�����˙ţ�˙ü�˙ý�˙˙������������������˙˙��˙ý�˙ű�˙ţ���˙˙���˙ý�˙ý�˙˙˙˙�˙ţ�˙˙�������������˙ţ�˙ţ�˙˙�˙˙�������˙˙������˙˙�˙ţ�˙˙��˙˙�˙ţ�˙˙˙˙�������˙˙���������˙˙�˙˙���˙˙�˙ý�˙ţ�˙˙�˙ý�˙ü�˙ţ�˙˙�˙ý�˙ý�˙˙���˙˙�˙ţ�˙˙�˙˙�˙ţ�˙ý�˙˙���˙ü�˙ű�˙ü�˙˙���˙˙�˙˙���˙˙�˙˙�˙ý�˙ţ���˙ý���˙˙�˙˙�˙˙�˙ý�˙˙�˙˙�˙˙�����˙ţ�˙ü�˙ţ�����˙˙�˙˙���˙˙�˙ý�˙˙���˙ţ�˙˙���˙ű�˙ů�˙ü�˙˙�˙˙���˙ţ�˙ţ�˙˙�˙˙�˙˙���˙˙�˙˙�˙˙�˙˙�˙ţ�˙ţ�˙ţ���������˙˙���˙˙�˙˙�˙ý�˙ý�˙ţ�˙˙�����˙˙�˙˙�˙ţ���˙˙�˙ţ���˙ţ�˙˙˙ţ�˙ü�˙ý�˙ţ�˙ü�˙ý��˙ü�˙ý��˙ţ�˙˙˙˙�˙ü�˙˙˙˙�˙ü�˙˙˙˙�˙ű�˙ú�˙ű�˙˙˙˙�˙ţ�˙ţ���˙˙�˙ţ��˙ý�˙ü�˙ţ�����˙ţ�˙ý�˙ý�˙˙�˙˙�˙ţ�˙ţ���˙˙�˙˙�˙˙�˙˙�˙˙�˙˙�˙˙���˙˙�˙˙˙˙�˙ý�˙ý��������˙ţ��˙ţ��˙ý�˙ý�˙ţ�˙ţ��˙ý�˙ü�˙ý���˙˙�˙ý�˙ţ�˙ţ�˙ü�˙ü�˙ţ�����˙ţ�˙ý�˙˙���˙ţ�˙ü�˙ţ���˙ţ�˙˙�˙˙�˙ţ�˙ţ�˙˙�������˙˙���˙˙�˙ţ���˙˙�˙ţ�˙ţ�˙ţ�˙˙�����˙ţ�˙ţ�˙˙�����������˙ţ�˙ţ�˙ţ�˙ţ�˙˙����˙˙�˙˙������˙˙�˙ţ�˙ţ�˙˙���˙˙���˙ţ�˙ý�˙ţ��˙ţ�˙ţ���˙˙�˙ţ�˙˙���˙˙���˙˙�˙ý��˙ţ�˙ţ��˙ţ�˙ü�˙ý�˙˙�������˙˙�˙ţ�˙˙�˙˙�˙ţ�˙ý�˙˙�˙˙�˙˙���˙ţ���˙˙�˙ţ�˙ţ���������˙˙���������˙˙�˙ţ�˙˙˙˙�˙ţ���������˙˙���˙ý�˙ţ�˙˙���˙˙�˙ý�˙ţ���˙˙�˙ý�˙ý�˙ý�˙ý�˙˙���������˙ţ�˙ţ������˙ţ������˙˙���˙˙�˙˙�˙˙�˙ţ���˙˙�˙ţ�˙ţ�˙ý�˙ţ�˙˙�˙ţ�˙ţ���˙ţ�˙ý�˙ý�˙˙�˙˙�˙˙�˙ţ�˙ý�˙ţ���˙˙���˙˙�˙ý�˙ţ��˙ţ�˙ý�˙ţ�����˙˙�˙ý���˙˙�˙ţ�˙˙���˙ý�˙ü�˙˙�˙˙�˙ű�˙ű�˙ý�˙˙�˙˙�˙ý�˙ý�˙ý���˙˙�˙ý�˙ý�˙ü�˙ţ�˙ý�˙ű�˙ý�˙˙�˙ý�˙ů�˙ú�˙ü�˙˙�˙ţ�˙ţ���˙˙�˙ü�˙ţ���˙˙���˙˙���˙ţ�˙ţ�˙ţ��˙ý�˙ü�˙˙���˙ţ�˙ü�˙ý�˙˙���˙ţ�˙˙���˙ţ�˙˙�˙ţ�˙ý�˙ü�˙ý���˙ý�˙ý�������˙˙�˙ţ�˙ţ�˙˙�˙ţ���˙˙�˙ţ�˙ţ�˙ţ���������˙˙���˙˙���������˙˙�˙˙����������������˙˙���������˙˙�˙ţ���˙˙�˙ý�˙ü��������������˙ţ�˙ţ�˙ţ��˙ţ�˙ţ��˙ţ�˙˙���˙˙�����˙˙�˙˙�˙˙�˙ü�˙ü�˙ţ�����˙˙�˙ţ�˙ý�˙ý�˙˙���˙ţ�˙˙�˙˙���˙ţ�˙ý�˙ţ���˙ý�˙ţ���˙ű�˙ü�˙ţ������˙ţ�˙ţ�˙˙���˙ţ�˙˙���˙ţ�˙ý�˙ý�˙˙�˙˙�������˙ţ�˙ţ�˙˙˙˙�˙ţ�˙ţ�˙ţ���˙˙���˙˙�˙˙�˙˙�˙˙�˙˙���˙˙�˙˙�����˙˙�˙ý�˙ţ�˙˙�˙ţ�˙˙���˙ţ�˙ţ�����˙˙�˙˙���˙˙���˙ţ�˙ţ�˙ý�˙˙���˙˙���˙˙�˙˙�˙ţ�˙ý�˙˙���˙ţ�˙ý�˙˙�����˙˙�˙˙�˙ý�˙ţ���˙ý�˙ű�˙ű�˙˙˙˙�˙ý���˙˙�˙ý�˙ţ���˙ţ�˙ţ����˙˙�˙˙���˙˙�˙˙�˙˙���˙ţ�˙ý�˙˙���˙ţ�˙ý�˙ý�˙˙���˙ţ�˙˙�˙˙�˙ţ�˙ţ�˙˙�˙ţ�˙˙���˙ţ�˙ý�˙ţ��˙ţ�˙ţ��˙ţ�˙ţ�˙ţ�˙ţ�˙ţ�˙˙˙˙�˙ţ�˙˙�����������˙ţ�˙˙˙˙�˙ú�˙ű�˙ý�˙˙���˙˙�����˙ţ�˙ü�˙ý�˙ţ��˙ţ�˙ü�˙˙���˙ţ�˙˙���˙˙���˙˙�˙˙�˙˙�˙˙�˙˙�˙˙�˙˙�˙˙�˙˙�˙˙�˙˙���˙˙���˙˙�˙ţ����������˙ţ�˙ţ�˙ţ�˙˙�����˙˙�˙ý�˙ý�˙ţ���˙˙���˙˙���˙˙���˙˙�˙ţ���������˙˙��˙ţ�˙ţ���˙ţ�����˙˙�˙ţ����������˙ţ�˙ý��˙˙���˙ţ�˙ý�˙ý�˙ý�˙ţ�˙˙�˙˙�˙ý�˙ţ�˙˙���˙˙�˙ü�˙ý�˙ţ���˙˙�˙˙�˙˙���˙˙�����˙˙�˙ý�˙ţ�˙ţ���˙ţ�˙ü�˙˙˙˙�˙ţ�˙˙�����������˙ý�˙ü���������˙˙�˙˙�˙ý�˙ý�˙ţ�˙˙�˙˙�˙ý�˙ţ���˙˙�˙˙�˙ţ���˙ţ���˙ţ�˙˙���˙˙���˙˙�˙ţ�˙˙���˙ţ���������˙˙�����˙˙�˙ý�˙ý�˙ý�˙ý�˙ý�˙ý���˙˙���˙ţ�˙ü�˙ţ�������������˙˙�˙˙���˙ţ�˙ţ�˙˙���˙ţ�˙˙�˙˙���˙˙���˙ţ�˙ý�˙˙���˙˙�������˙˙�˙ţ�˙˙�˙˙�˙ţ�˙˙�˙ţ�˙ý���˙ţ�˙˙�˙˙���������˙ţ�˙ü�˙ý���˙˙�˙˙�˙˙�˙ţ�˙˙�����˙˙�˙˙�˙ý�˙ý�˙ţ�˙˙�˙˙�˙˙�˙˙�˙ţ�˙ü�˙ý�˙˙�������˙ţ�˙ţ��˙ţ�˙ţ��������˙ý�˙ţ�����˙˙�˙˙�˙˙�˙ý�˙ý�˙˙�˙˙�˙˙˙˙�˙ü�˙ü�˙˙�����˙˙���˙ý�˙ý��˙ţ�˙˙�������˙ţ�˙ü�˙ý�˙ý�˙ţ�˙ţ���˙ý�˙ű�˙ý��˙˙�˙ţ�˙ü�˙ű�˙ý�˙ý�˙˙���˙ţ�˙ý�˙˙�����������˙ţ���˙˙�˙ü�˙˙˙˙�˙ţ���������˙˙�˙˙�˙ţ�˙ý�˙ţ��������˙ţ��˙ţ�˙˙˙˙�˙ü�˙ü�˙ý�˙˙�����˙˙�˙˙���˙˙�˙˙�˙ţ�˙ü�˙ý�˙˙��˙˙�˙ţ�˙ţ���˙˙�˙ţ��˙ý�˙ý��˙ţ�˙ţ�˙˙���˙ţ���˙˙�˙˙���˙˙�˙ý�˙˙���˙ý�˙ü�˙ý�˙ý�˙ţ�˙ý�˙ý���˙˙�˙ý�˙ý�˙ţ�˙˙�˙˙�˙ý�˙ý��˙ý�˙ý���˙˙���˙˙�˙˙�������˙˙�˙˙���˙˙�˙ý�˙ý�˙˙���˙ţ�˙ţ��˙ţ�˙ţ�˙˙�������˙˙���˙ý�˙ý�˙˙�������˙ţ�˙ý�˙˙���˙˙�˙˙���˙˙�˙ü�˙ý�˙˙�˙ţ�˙ý��˙˙�˙ţ�˙˙���˙ţ��˙ţ�����˙˙�˙ý�˙˙˙˙�˙ţ�˙˙�˙ţ�˙ý�˙ţ���˙˙�����˙˙�˙˙�˙˙���˙˙�������˙˙˙˙����˙˙��˙˙�˙˙���˙ţ�˙ý�˙˙���˙ţ�˙ü�˙ű���˙ţ�˙˙���˙ý�˙ý���˙˙���˙˙�˙ý�˙ţ���˙ţ�˙ű�˙ű�˙ý�˙˙���˙ţ�˙ý�˙ţ�˙˙�˙˙���˙ý�˙ý�˙˙�˙˙�˙˙���˙˙�˙ý�˙˙���˙˙�˙˙�˙ý�˙ű�˙ü�˙ţ�˙˙�˙ţ�˙ţ������˙ţ�˙ţ�˙˙˙˙�˙ţ�˙˙˙˙�˙ű�˙ý���˙ű�˙ü�˙ţ�˙ý�˙ţ���˙ţ�˙ű�˙ü�������˙˙�˙ý�˙ý��˙ý�˙ý�����˙˙�˙ü�˙˙�������˙ţ�˙ý�˙˙�˙˙�˙˙�˙˙�˙ţ���˙˙�˙ţ�˙˙�˙˙���˙˙���˙ţ�˙˙���˙˙�������˙˙����������˙˙�˙ţ���˙˙���˙˙���˙˙���˙˙�˙ţ�˙ý�˙ţ���˙˙���˙˙�˙˙�˙ţ���˙˙�˙˙��˙˙�˙ý�˙ý�˙ţ��������˙ţ��������˙ý�˙ţ�˙ţ�˙ý�˙˙���˙ţ�˙˙�˙˙���˙˙�˙ü�˙ű�˙ú�˙ý�˙˙�˙˙���������˙ý�˙ü�˙ü�˙ţ�˙ţ���˙˙�˙ü�˙ţ��˙˙���������˙˙���˙˙���˙ţ�˙ý�˙ý�˙˙���˙ţ�˙ţ�˙ţ�����������˙˙�˙ü�˙ý�������������˙˙�˙ţ�˙˙˙˙�˙˙�˙˙˙˙���˙˙�˙ţ���˙ţ�˙ý�˙˙���˙˙���˙ţ�˙ý�˙ý�˙˙���˙ü�˙ý�˙˙�������˙˙˙˙�˙ü�˙ý���������˙˙�˙ţ�˙ţ�������˙˙�˙ţ���˙ţ�˙ű�˙ů�˙ű�˙ţ�˙ţ�˙˙�˙˙���˙˙���˙˙���˙ü�˙ţ˙˙�˙ý�˙˙�����˙˙���������˙ţ�˙ţ�˙ţ�˙ţ�˙ţ�˙ţ�˙˙���˙ţ�˙˙�˙˙���˙ţ�˙ý�˙˙�˙ţ�˙ţ�˙˙��˙˙���������˙˙�����������˙˙�˙ţ���˙˙�˙ţ�˙˙�˙ţ�˙ţ�˙˙���������˙ý�˙ů�˙ú�˙ţ�˙˙�˙ţ�˙ý�˙ý���˙ţ�˙ţ���˙˙�˙˙�˙ţ�˙ý�˙ţ�˙ý�˙ý�˙ţ���˙ü�˙ü���˙ţ�˙ţ���˙˙�˙ý�˙ü�˙˙˙˙�˙ý�˙ü�˙ü�˙ţ�˙ţ�˙ţ�˙ţ�˙ţ�˙ţ�˙ţ�����˙˙�˙ý�˙˙˙˙�˙˙�˙ţ�˙ţ�����˙˙�˙ţ�����˙˙�˙ţ�������˙˙�˙ţ�˙˙�����˙˙���˙ţ�˙ý�˙ý�˙ü�˙ű�˙ţ�����˙˙���������˙ý�˙ţ�˙ţ���˙˙���������˙˙��˙ý�˙ü�˙ţ���˙ţ�˙ý�˙ý�˙ţ�˙˙�˙ţ�˙˙���˙˙�����˙ţ�˙ű�˙ý�˙ý�˙ý�˙˙�˙ţ�˙ý�˙ý�˙˙���˙˙���˙˙�˙˙�˙˙���˙˙�˙ţ���˙˙�������˙˙���˙ţ�˙ţ���˙ü�˙˙˙˙�˙ü�˙ý�˙˙���˙˙�������˙˙�˙ţ�˙˙���˙˙�˙˙�˙ţ�˙ţ���˙ü�˙ü�˙ţ�˙˙�˙˙���˙˙�˙˙�˙˙�˙ý�˙ü�˙˙˙˙�˙ü�˙ţ�˙˙˙˙�˙ý�˙ü�˙˙˙˙�˙ţ�˙˙��˙˙�˙ý�˙ţ���������˙˙�˙˙�˙ţ�˙ý�˙ű�˙ú�˙ý�������˙˙�˙ţ�˙ý�˙ü�˙ü�˙ţ�˙ý�˙˙˙˙�˙ý�˙ţ���˙˙�˙ţ��˙ý�˙ü�˙ü�˙ţ��˙ý�˙ţ���˙˙���˙˙�˙˙�˙˙�˙˙�˙ţ�˙˙�˙˙���������˙˙�˙ý�˙ý���˙ţ�˙˙���˙˙���˙˙�˙ţ�˙˙�˙˙�������˙ţ�˙ţ�˙ţ�����˙˙�˙ű�˙ü�˙˙���������˙˙�˙˙�˙˙���˙˙�˙˙�˙ý�˙ý�˙ý�˙ý�˙ý�˙ý�˙ţ�˙˙�˙ţ�˙˙˙˙�˙ü�˙ű�˙ý���˙˙�˙ţ�˙ţ���˙ţ�˙ţ�˙˙�˙˙�˙ý�˙˙�˙ţ�˙˙�����˙˙�˙ţ�˙˙˙˙�˙ý�˙ú�˙ü���˙˙�˙˙���˙˙���˙ţ�˙ý�˙˙�����˙ý�˙ű�˙˙�����˙ţ�˙ý�˙˙���˙˙���˙˙���˙˙�˙˙�����˙ý�˙ý���˙˙���˙ţ�˙ý�˙ţ���˙˙���˙ţ�˙ţ���˙ţ�˙ý�˙˙�˙˙��˙˙�˙ü�˙ú�˙ű�˙˙������˙˙�˙ţ���������˙˙�˙ţ�˙˙˙˙�˙ţ��˙ý�˙ü�˙˙˙˙�˙ţ�˙ţ�˙ţ���˙˙�˙ý�˙ţ���˙˙��������������������˙ţ�˙ţ�˙˙˙˙��˙ý�˙ű�˙ý������˙ţ�˙ţ�˙˙���˙ţ�˙ţ�˙˙˙˙�˙ü�˙ü�˙ý��˙ţ�˙˙�����˙ý���˙ţ�˙ü�˙ţ���˙˙�˙˙�˙ţ�˙˙�˙˙�˙ý�˙ű�˙ü�˙ţ���˙˙�˙ý�˙ţ���˙˙��������������������˙ţ�˙ţ�˙˙�������˙ţ�˙˙���������˙ţ�˙˙���˙ţ�˙˙�����˙ţ�˙ţ�˙˙�����������˙ţ�˙ü�˙ý�˙˙���˙ţ�˙˙������������˙˙�˙ţ�˙˙�˙˙�˙˙�˙˙���˙˙�˙˙���˙˙�˙˙�˙ţ�˙ý�˙ý�˙˙�˙˙�˙˙���˙˙�˙ý�˙ű�˙ü�˙ţ�˙˙�������˙˙�˙ţ�˙ü�˙˙���˙ţ�˙˙���˙ţ�˙ý�˙ý�˙˙�˙ţ�˙ü�˙ú�˙ú�˙ű�˙˙�����������˙ţ�˙˙���˙ţ�˙ü�˙ű�˙ű�˙ý���˙ţ�˙ý�˙ţ�˙˙���������˙ţ�˙˙���˙ţ�˙ý�˙˙���˙˙���˙ţ�˙˙������˙˙�˙ţ���˙˙�˙˙���˙˙�����˙ţ�˙ţ�˙˙�������˙ţ�˙ţ�˙ý�˙˙���˙ü�˙ű�˙ü�˙˙���˙˙�˙˙���˙˙�˙˙�˙˙�˙ý�˙ý�˙ý�˙ţ���˙ţ�˙ý�˙ü�˙˙�˙˙�˙˙�˙˙�˙ţ���˙˙�˙ţ�˙˙�˙˙�˙˙�����˙ţ�˙ú�˙ű�˙ţ�����˙˙�˙˙�����˙˙�˙˙�˙ý�˙ţ�˙˙�����˙˙�����˙˙�˙ţ�˙ý�˙˙�˙˙�˙˙���˙˙���˙˙���˙˙���˙˙���˙ţ�˙ü�˙ü�˙˙���˙˙�˙˙�˙˙�˙˙���˙˙�˙˙���˙˙�˙˙�˙˙�˙˙�˙˙�˙˙�˙ţ�˙ý�˙ý�˙˙���˙˙�˙˙˙˙�˙ü�˙ý��˙ý�˙ü�˙ü�˙ü�˙ţ�˙˙˙˙���������˙˙��˙ý�˙ü���˙˙���˙ţ�˙ý�˙ţ�˙˙��������������˙ţ�˙ý�˙ţ���˙˙���˙˙���˙˙�˙ţ�˙ţ���������˙˙���˙˙�˙ý�˙ţ���˙˙�˙˙���˙˙�˙˙���˙˙�˙ţ�˙ţ����˙ţ�˙ţ�˙ţ�˙ţ�˙˙�����������˙ţ�˙ü�˙ü�˙ü�˙ű�˙˙˙˙�˙ü�˙ý�˙˙�������˙˙���������˙ţ�˙ţ�˙ţ�˙˙˙˙�˙ţ�˙˙˙˙�˙ý�˙ý����������������˙ţ�˙ý�˙˙�˙˙�˙ţ���˙ţ�˙ú�˙ţ˙˙�˙ú�˙ü���˙ţ�˙ţ���˙˙�����˙˙�˙˙�˙˙˙˙�˙˙�˙ţ�˙ţ�˙ţ�˙ţ�˙ţ�˙˙��˙˙�������˙˙���˙˙�˙ţ���˙˙�˙ţ�˙ţ��˙ţ�˙ţ�˙˙�˙˙�������˙ţ�˙˙˙˙�˙ţ�˙ţ�˙ý�˙ţ���˙ţ�˙ý�˙˙�����˙ţ�˙ţ�˙ţ�˙ţ�˙˙˙˙�˙ţ�˙ü�˙ü�˙ý�˙ţ�˙ü�˙ü�˙˙˙˙�˙ű�˙ű�˙ü�˙ţ���˙ü�˙ü�˙ţ���˙ţ���˙˙�˙ţ��������������˙ţ�˙˙�����˙ţ�˙ü�˙ý�˙˙˙˙�˙˙���˙ţ�˙ţ�˙˙�˙˙�˙˙�����˙˙�˙ü�˙ü�˙ţ�����˙˙�˙ţ���˙ý�˙ţ�˙˙�˙ţ�˙ţ���˙ţ�˙ý�˙ţ�˙˙�˙˙�˙˙���˙˙�˙ţ���˙ü�˙ý�˙ţ�˙˙�����˙ý�˙ý�˙˙˙˙�˙ţ�˙ü�˙ţ�˙˙��˙˙����˙˙��˙˙�˙˙���˙ţ�˙ţ�˙˙�˙ţ������˙ţ���˙˙�������˙˙�˙˙�˙ţ���˙ý�˙ţ�˙˙���˙˙�˙ý�˙ţ�˙ý�˙ü�˙ý��˙˙�˙ţ�˙ţ�˙ü�˙ý�˙˙���˙ţ�˙ý�˙˙���˙˙���˙ţ�˙ţ���˙ţ�˙ý�˙ţ�˙ţ�˙ţ�˙˙�������˙˙���˙ţ�˙ý�˙ţ�˙ý�˙˙˙˙�˙ý�˙˙˙˙�˙˙�˙˙�����˙ţ�˙ü�˙ý�˙ţ�˙ţ�˙ţ�˙˙˙˙�˙ţ�˙˙˙˙�����˙˙�˙˙���˙ţ�˙ý���˙˙�˙˙���˙ţ�˙˙�����˙˙�˙ţ�˙˙�����˙˙�˙˙�˙˙�˙ý�˙ţ���˙˙�˙˙�˙ţ�˙˙�˙˙�˙˙���˙ý�˙˙�����˙˙�˙ţ�˙˙�˙˙�˙ţ�˙ü���˙˙�˙˙˙˙�˙ţ�˙˙˙˙�˙ţ�˙˙���������˙ţ�˙ü�˙ü�˙ü�˙ü�˙ţ�����˙˙�˙ý�˙ü�˙ţ���˙ý�˙ţ���˙ţ�˙ý�˙ý���˙ţ�˙ý�˙ţ�˙˙�˙˙���˙˙�˙ý�˙ţ�����˙˙���˙˙�˙˙���˙˙�����˙ý�˙ý�˙˙˙˙�˙ţ�˙ţ�˙ţ�˙ţ�������������˙˙�˙ţ�˙˙���������˙˙�˙˙�˙˙�˙˙�˙ý�˙ý�˙ţ�˙˙�˙˙�˙˙���˙˙���������˙ţ��˙ý�˙ú�˙ţ˙˙�˙ü�˙ý�˙˙���˙˙�˙˙�˙ţ�˙ý�˙ý�˙˙�˙˙�˙ţ�˙ý�˙ý���˙ţ�˙ý�˙ţ�˙˙�˙ý�˙ü�˙ţ�˙˙�˙˙�˙˙�˙˙���˙˙�˙˙���˙˙�˙˙���˙˙�˙˙�������˙ţ�˙˙���˙˙���˙ţ��˙˙�������˙˙�˙ţ���˙˙�˙˙�˙ţ�˙ý�˙ý�˙˙���˙ý�˙ý�˙˙�˙ţ���˙ţ�˙ü�˙ý�˙ý�˙ű�˙ü�˙ţ�˙˙���˙˙���˙˙���˙ţ�˙ţ�˙˙�˙˙�˙ţ�˙˙�˙˙���˙ţ�˙˙���˙˙�˙ţ�˙ý�˙˙���˙ý�˙ţ�˙˙�˙˙˙˙�˙ţ���˙ţ�˙ţ�˙ţ�˙ü�˙ý���˙˙�˙ţ�˙ţ���˙˙�˙˙������˙˙�˙ü�˙˙˙˙�˙ţ�˙ţ�˙ţ���������˙˙�˙˙�˙˙�˙˙�˙ţ�˙ý�˙˙���˙˙�˙ţ�˙ý���˙˙�˙ţ�˙˙�˙ţ�˙ý�˙˙���������˙ţ�˙˙���������˙ţ�����������˙˙�˙˙�˙ţ�˙˙�˙˙�˙ý�˙ţ���˙˙�˙˙�˙ţ�˙ý���˙ţ�˙ý�˙ý���˙˙�˙˙�˙˙�˙ţ�˙ý�˙ü�˙ý�˙ý����˙˙���������˙ţ�˙ţ�˙ţ��˙ţ�˙ţ����������˙ţ��˙˙�˙˙�����˙˙�˙˙�˙ý���˙ţ�˙ü�˙ţ�˙˙�˙ţ���˙˙�˙˙���˙˙�˙˙���˙˙�˙˙�˙˙�˙˙���˙ý�˙ţ�����˙˙�����˙˙�˙ţ�˙ý�˙ü�˙ü�˙ű�˙ý���˙ţ�˙ý�˙ý�˙ý�˙ű�˙ý�˙ý�˙ý�˙ý���˙ţ�˙ű�˙ţ�˙˙�˙ţ�˙˙�˙˙�˙ţ�˙˙���˙ü�˙ű�˙ý�˙˙�˙ţ�˙ü�˙ý�����������˙˙�˙ý�˙ý�˙ý�˙ţ�˙˙��˙˙�˙ý�˙ý���˙˙�˙ţ�˙˙��˙ý�˙ý�˙ý�˙ý�˙ţ�˙ţ�˙ţ��˙ý�˙ţ���˙˙�˙˙���˙˙�˙˙�����˙˙�˙ý�˙ý�˙ţ�˙ţ�˙˙�˙ý�˙ű�˙ý���˙˙�˙˙��˙˙�˙ý�˙ý���˙˙�����˙˙�˙ý�˙ţ���˙˙���˙ý�˙ý�˙˙���˙˙�˙˙�˙ţ�˙ű�˙ů�˙ú�˙ţ���˙˙���˙ţ�˙˙���˙ţ�˙˙�˙ţ�˙˙�˙ţ�˙ţ�˙ü�˙ú�˙ű�˙˙����������������������˙˙�˙˙����˙˙��˙ţ�˙˙˙˙�˙ţ�˙˙�˙ţ�˙˙���˙˙�������˙˙�˙ý�˙ý�˙ý�˙ţ�˙˙���˙ţ��˙ţ�˙ý��˙ţ�˙˙������˙˙�˙ţ�˙˙˙˙�˙ţ�˙˙���˙ţ�˙ý�˙ţ�˙˙�˙˙�˙ý�˙ţ�˙˙�������˙ţ�˙˙���˙ţ�˙˙�������˙ţ�˙ü�˙ű�˙ţ���˙ý�˙˙���˙ţ�˙ţ�˙ý��˙ý�˙ý��˙ý�˙ű�˙ü�˙ţ�������˙˙�˙ţ�˙ţ�˙ý�˙ú�˙ú�˙ú�˙ű�˙˙��˙˙�˙ţ�˙˙˙˙��������˙ý�˙ü�˙˙������˙˙�˙ţ���˙˙�˙˙�˙˙�˙ţ�˙˙���˙ý�˙ý�˙˙˙˙�˙˙���˙ţ�˙ţ�˙˙���������˙ţ��˙ţ�˙˙˙˙�˙ţ���˙˙�����˙˙�˙ý�˙ü�˙ü�˙ţ��˙ý�˙ü�˙ý��������˙ţ�˙˙�������˙˙�˙ü�˙ű�˙ţ���˙ü�˙ý�˙ý�˙ţ�˙ý�˙ý�˙˙�˙ţ�˙ü�˙ý�˙˙�������˙ţ�˙ü�˙ý�˙ţ�˙ţ�����˙˙�˙ý�˙ţ���˙˙�˙ý�˙ţ�˙ţ�˙˙���������˙˙�˙ţ�˙ţ�˙ý�˙ü�˙ţ�˙˙���˙˙�˙˙���˙˙�˙ţ�˙ü�˙ţ����������˙ţ���˙˙�˙ü�˙ý���˙˙�˙ý�˙˙�˙˙�˙˙�˙˙�˙ţ�˙˙������������˙˙�˙ţ�˙ü�˙˙��������������˙˙�����˙˙�˙ý�˙ţ�������˙˙�������˙˙�˙˙�˙ţ�˙˙�������˙˙�˙˙���˙˙�˙˙���˙ý�˙˙�˙˙�˙ţ�˙ý�˙˙���˙ţ�˙ţ�˙ţ���˙˙�˙ţ�˙˙�˙˙�˙ý�˙ţ���˙˙�˙ý�˙ý�˙ü�˙ý�˙˙����˙˙�˙ţ�˙ţ�����������˙˙�˙ţ�˙˙˙˙�˙ý�˙ü�˙ü�˙ü�˙ý�˙ţ�˙ý�˙ý��˙˙�˙˙���˙ţ�˙ý�˙ý�˙˙���˙ţ�˙ý���˙˙�˙˙�˙˙���˙ţ�˙ý�˙˙˙˙�˙ű�˙ű��˙ű�˙ű�˙ý�˙ţ�˙ţ�˙ţ�˙˙�˙˙�˙ý�˙ý�˙ţ�����˙˙�������˙˙�˙˙�˙ţ�˙˙���˙˙�˙ý�˙ţ���˙˙�˙˙�˙˙�˙˙���˙˙���˙ţ�˙˙�˙ţ�˙ţ�˙˙�˙ţ�˙ü�˙ţ�˙˙�˙˙�˙˙�˙˙�˙˙�����˙ţ�˙ü�˙ţ�˙˙�˙˙�˙˙�˙˙�˙˙�˙˙�˙˙�����˙˙�˙ý�˙˙�˙ý�˙ü�˙ţ�˙ţ�˙ţ�˙ý�˙˙���˙ţ�˙ý�˙ý�˙ţ���˙ý�˙ţ�����˙ţ�˙ü�˙ţ���˙ý�˙ý�˙ţ�˙˙�����˙˙�˙˙�˙ţ�˙˙���˙˙�˙ý�˙ű�˙ü�˙ţ�˙˙�˙˙���˙˙���˙˙�˙ý�˙˙�˙˙�˙ţ�˙ý�˙˙���˙˙���˙˙���˙˙���˙ţ�˙ý�˙˙���˙˙�˙˙�˙ţ�˙ý�˙ý�˙˙���˙˙���˙˙���˙ţ�˙ý�˙ü�˙ý�˙˙�˙˙�˙˙�˙˙�˙˙���˙˙�˙˙�˙˙�˙˙�˙˙���˙˙�˙ý�˙˙˙˙�˙ţ��˙ý�˙ü�˙ü�˙ü�˙ü�˙ü�˙ü�˙ý���˙˙�����˙˙�˙ý�˙ţ�����˙˙�˙˙�˙˙˙˙�˙˙�˙ţ�˙ţ���˙˙�˙ţ��������������˙ţ�˙˙�˙ţ�˙ü�˙ţ�����˙˙�˙ţ�˙ý�˙ý���˙˙�˙˙�����˙˙�˙ţ�˙ţ�����������˙˙�˙ţ�˙˙�������˙˙�˙˙���˙˙���˙˙�˙˙���˙˙�˙˙���˙˙�˙ţ�˙˙���˙ţ�˙˙���˙ţ�˙ţ���˙ţ�˙ţ�˙˙���������˙ţ�˙ţ�˙ţ�˙˙����˙˙�˙ţ�˙ţ�˙ţ�˙˙�˙ü�˙ű�˙˙˙˙�˙ý�˙ţ���˙˙�����˙˙�˙ţ�˙ű�˙ű�˙ţ�����˙˙�˙ţ�˙˙�����˙ţ�˙ţ�˙˙�˙˙���˙˙���˙ţ�˙ű�˙ü���˙˙���˙ţ�˙ű�˙ü���˙˙�˙˙���˙˙�˙˙���˙˙���˙ü�˙ü��˙˙���˙˙���˙ü�˙ţ���˙ţ�˙ý�˙˙���˙˙���˙ţ�˙ý�˙ý�˙˙�˙˙���˙˙�˙˙�����˙˙���˙ţ�˙ý�˙ţ�˙ý�˙˙���˙˙�����˙˙���������˙ţ�����˙˙�˙ý�˙ü�˙ţ��˙ţ�˙ţ��˙ţ�˙ü�˙˙���˙˙˙˙�˙ý�˙ý�˙˙�˙ý�˙ý�˙˙���˙˙�����˙˙�˙˙���˙ý�˙ý�˙ţ�˙˙�˙˙�˙˙�˙˙�˙˙�˙˙���˙˙�˙˙���˙ţ�˙ý�˙ý�˙˙���˙˙���������˙ţ�˙ţ�˙˙�˙ţ�˙ý�˙ý�˙ţ���˙˙���˙˙�˙ţ���˙˙�˙˙˙˙�˙ý�˙ü�˙ţ�˙ţ�˙ţ�˙˙���˙ţ�˙˙˙˙�˙ü�˙ü�˙˙���˙ţ�˙ţ�˙ţ�˙ý��˙ţ�˙˙˙˙�˙ý�˙˙�����˙ţ�˙ý�˙ţ���˙˙�˙˙���˙ţ�˙ý�˙˙���˙˙�˙˙�˙˙���˙˙�˙ý�˙ţ�˙ţ�˙ţ��˙ţ�˙ţ�˙ţ�˙ţ�˙ţ������˙ţ�˙˙�˙˙�����˙˙�˙˙�˙˙�˙ţ�˙ú�˙ů�˙ü�˙ţ�˙ý�˙ý�˙˙�����˙ţ�˙ý�˙˙�������˙ţ�˙ü�˙ý�˙˙�����˙ý�˙ú�˙ý��˙ý�˙ű�˙ű���˙˙�˙ţ�˙ý�˙ű�˙ü�˙ţ���˙ţ���˙˙��˙ý�˙ü�˙ţ�˙˙�˙ţ�˙ý�˙ţ��˙ý�˙ţ�����˙˙�˙˙���˙˙�˙˙�˙˙�˙˙�˙˙�˙˙�˙˙���˙˙�˙˙�����˙˙���˙ţ�˙ü�˙ü�˙ý�������������˙˙�˙ţ��˙ý�˙ü�˙˙˙˙�˙ţ�˙ţ�˙ţ�˙ţ��˙ţ�˙ý�˙ü�˙ü�˙ţ�˙ţ�˙˙˙˙�˙ţ�˙˙˙˙�˙ý�˙ţ�˙˙�˙˙���˙ü�˙ű�˙ý�˙ţ�˙ţ�˙ţ������˙ţ�˙ţ���˙ţ�˙ü���˙ţ�˙ţ�˙˙����˙˙�˙ţ���˙˙���˙˙�˙ý�˙ţ��˙ý�˙ý�˙ţ���˙˙���˙˙�˙ţ���˙˙�˙ţ�˙ý�˙˙˙˙�˙ţ��˙ý�˙ţ��˙ý�˙ý�˙ţ���˙ţ�˙ű�˙ů�˙ú�˙ý����������˙ţ�˙ţ��˙ţ�˙ţ��������˙ţ�˙˙�����˙ţ�˙ý��˙ţ�˙ý�˙˙�˙˙�˙˙���˙˙�˙˙�˙˙�˙ý�˙ţ�����˙˙���˙˙�˙ý�˙ţ���˙˙��������˙ý�˙ű�˙ú�˙ü���˙˙�˙ý�˙ţ�˙˙�����˙˙�˙˙�˙ý�˙ý�˙ţ�˙˙�˙˙�˙˙���˙˙���˙˙�˙ý�˙ü�˙ţ�����˙˙�˙˙�˙ţ���˙˙�������˙˙�����˙˙�˙˙�˙ţ�˙˙���˙˙�˙ý�˙ţ�˙ý�˙ü�˙˙˙˙�˙ü�˙ý�˙˙���˙ţ�˙ú�˙ů�˙ţ���˙˙���˙˙���˙˙���˙ţ�˙ý�˙ţ���˙ý�˙ţ���˙ý�˙ţ�˙ţ�˙˙���˙˙�˙ţ���˙˙���˙ţ�˙ý�˙˙���˙˙�˙˙���˙˙�˙˙�˙˙�˙˙�˙˙�˙˙�˙˙�˙ţ�˙ü�˙ü�˙ý�˙˙˙˙�˙ý�˙˙�����˙ţ�˙ţ�˙ţ������˙ţ������˙˙���˙ţ�˙ý�˙ý�˙˙�˙˙���˙˙�˙˙�����˙ü�˙ů�˙ü�����˙˙�˙ý�˙˙��������˙˙�˙ţ�˙ý���˙˙�˙ü�˙ý�˙ţ����˙ţ�˙ý�˙ý���˙˙�˙˙������˙˙�˙ü�˙ţ�˙ţ�˙˙�����˙˙�˙˙�˙ţ�˙ţ���˙˙�˙ţ���˙˙�˙˙�˙˙�˙˙���˙˙���˙˙���˙ţ�˙ţ�˙˙���˙˙�˙˙�˙ţ�˙ţ�˙˙��˙˙�˙ý�˙ü�˙ţ�˙˙�˙ý�˙ü�˙˙˙˙�˙ţ�˙˙˙˙�˙ţ�˙˙˙˙������˙ý�˙ý��˙ü�˙ţ�����˙˙���˙ý�˙ý�˙˙�������˙˙���˙ţ�˙˙���˙˙���˙ţ�˙ţ���˙˙���������˙˙�˙˙�˙˙�˙ţ�˙ţ�˙ý�˙ţ���˙˙�˙ţ�˙˙�˙˙�����������������˙ţ�˙˙˙˙�˙ú�˙ű�˙˙��������������˙˙�˙ü�˙ý����˙ţ�˙ý�˙ţ�˙ü�˙ý�˙˙���������˙ţ�˙ü�˙ű�˙ý�˙ý�˙˙���˙˙���˙ţ�˙ý�˙˙���˙ý�˙ţ���˙˙���������˙ţ�˙˙���˙ţ�˙ý�˙˙�˙˙�˙ţ�˙ý�˙˙���˙ý��˙ţ�˙ý�˙˙���������˙˙˙˙�˙ţ�˙˙�������������˙ţ��˙ţ�˙˙�����˙ý�˙ý�˙ţ�˙˙�˙ý���˙˙�˙ý�˙ţ�˙˙�˙˙�������˙ţ�˙ü�˙ý�˙˙�����˙ţ�˙ý��˙ü�˙ţ���˙ý�˙ý�˙ý�˙ţ�˙˙�˙˙�˙˙�˙˙�˙˙���˙˙����������������˙ţ�˙ţ�˙ţ�˙ţ�˙ţ�����������˙˙�˙ü�˙ý�˙ţ�˙ý�˙˙���˙˙���˙ţ�˙ý�˙ţ�˙ý�˙ţ�˙˙�˙ţ�˙ü�˙ţ���˙ý�˙˙�˙˙�˙ţ�˙ý�˙ý�˙ţ�˙ţ�˙˙���˙˙�˙˙�˙˙�˙ţ�˙ý�˙˙���˙ţ�˙ţ�˙ţ������˙ý�˙ţ�˙˙�������˙ţ�˙˙���˙ţ����˙ţ�˙ű�˙ý���˙˙���˙˙�˙ţ�˙ý�˙ý�˙ü�˙ű�˙ţ�˙˙�˙ţ�˙ţ�˙˙���˙˙�˙ü�˙ű�˙˙˙ţ�˙ţ�˙ţ�˙˙�����˙ţ�˙ú�˙ţ���˙ţ�������������˙˙�˙˙���˙ţ�˙ţ�˙ý�˙ţ�˙ţ�˙ý�˙˙�����˙˙���˙ţ�˙ű�˙ý�˙ý�˙ý�˙ţ�˙˙���˙˙�˙˙�˙˙�˙˙���˙˙���˙˙���˙˙���˙˙�˙ý�˙ţ�˙ý�˙ű�˙ý���˙˙���˙ü�˙ţ��˙ţ���˙˙�˙ü�˙˙����˙˙�˙ţ�˙ü�˙ű�˙ú�˙ü���˙˙�˙ü�˙ú�˙ţ���˙ţ�˙˙�˙˙�˙ţ�˙ý�˙ý�˙ţ���˙ü�˙ű�˙ý�˙˙���˙ţ�˙ý�˙ý�˙˙���˙ý�˙˙���˙˙�˙˙���˙˙���˙ţ�˙ý�˙ţ���˙˙���˙˙�˙˙�������˙˙���˙˙�˙ţ�˙˙�˙˙�˙˙�˙ţ�˙ú�˙ů�˙ý���˙˙�����˙ţ�˙ü�˙ý�˙˙���������˙˙�˙ţ�˙˙�˙˙�˙˙�˙˙���˙˙�˙˙���˙˙�˙˙�˙˙�˙ţ���˙˙�˙ü�˙ü�˙ü�˙ý�˙˙�����˙ţ�˙ü�˙ý��˙ý�˙ţ�˙ţ�˙˙�˙˙�˙ţ���˙ü�˙ý���˙ý�˙˙˙˙�˙ţ�˙ţ�˙ţ�˙˙���������˙ţ�˙ü�˙ţ���˙ý�˙ţ�˙˙���˙˙�˙ţ���˙˙�˙ţ���˙˙�˙ý���˙˙���˙˙�˙ý�˙ţ�˙˙˙˙�˙ý�˙ý���˙˙�˙ţ���������˙˙�˙ý�˙ţ���˙ý�˙˙���˙˙�˙˙�˙˙�˙˙�˙ý�˙ţ�˙˙�˙˙�˙˙�˙˙�˙˙�˙˙�˙˙�˙˙�˙˙���˙˙���˙˙�˙ţ���˙˙���������˙˙��˙ţ�˙ţ���������������˙˙�˙˙�����������������˙ţ�˙ţ�˙ţ�˙ý�˙ţ��˙˙����˙˙�˙˙˙˙�˙ü�˙˙�����˙ţ�˙ţ�˙ţ�˙˙˙˙�˙ţ�˙ţ�˙ţ�˙˙˙˙�˙ü�˙ý�˙˙�������˙˙�������˙˙�������˙ţ�˙ý�˙ţ�˙ţ�˙ü�˙ü�˙ý�˙ţ�˙ü�˙ű�˙ţ�˙˙�˙˙���˙ý�˙ü�˙ý�˙ý�˙ţ�˙ý�˙˙���˙ţ�˙ý�˙˙���˙˙�˙˙�˙˙�˙˙�˙˙���˙˙�˙˙�˙˙�˙˙���˙ý�˙ý���˙˙�˙ý�˙ý�˙ý�˙ý�˙˙���˙˙�˙˙�˙˙�˙˙���˙˙�˙˙���˙˙�˙˙�˙ý�˙ý�˙ţ���˙ţ�˙ţ���˙ţ�˙ý�˙ý���˙ţ�˙˙���˙ţ�˙˙���������˙ţ�˙˙�����˙ţ�˙˙���˙ţ�˙ţ���˙ţ�˙˙���˙˙�˙˙�˙˙�˙˙�˙˙�˙˙���˙˙�˙˙���˙˙�˙ţ�˙ţ�˙ý�˙ţ��˙ţ�˙ţ��˙ţ�˙ţ�˙˙�������˙˙�����˙˙���˙˙�˙˙�˙˙�˙ţ���˙˙�˙ţ�˙˙�˙ţ�˙˙����˙ý�˙ý��˙ţ�˙˙�˙˙�˙ý�˙ţ�����˙˙�˙ţ�˙˙˙˙�˙ý�˙ü�˙˙˙˙�������˙˙�����˙˙�˙˙�˙ţ�˙˙���˙˙�˙ý�˙ţ�˙˙�˙ţ�˙ţ�����������������˙˙��˙ţ�˙˙���˙ý�˙ţ�˙ţ���˙˙�˙ý�˙ý�˙ţ���˙˙�˙˙���˙˙�˙ţ�˙ţ���˙ţ�˙ţ�˙ţ�˙ü�˙ü���˙ý�˙ý���˙ţ�˙ţ�˙˙���˙ţ�˙ü�˙ü�˙ý�˙˙�����˙ý�˙ű�˙ű�˙˙���˙ţ��˙ţ�˙ţ�˙ţ�˙ţ�˙˙��˙˙�˙ţ�˙ţ�������������������������˙˙�˙ţ�����˙˙�˙ý�˙ţ���˙˙���˙˙�˙ţ�˙ý�˙ü�˙ü�˙ü�˙˙˙˙�˙ţ�����˙˙�˙ý�˙ţ���˙˙���˙˙�˙˙���˙ţ�˙˙�˙˙�˙ţ�˙ţ���������˙˙�˙ţ�˙˙�˙ţ���˙ţ�˙ü�˙ţ���˙˙�˙ţ�˙ţ�˙˙���˙ţ���˙ţ�˙˙���˙ţ�˙ý�˙ý���˙˙�˙˙�˙ý�˙ý�˙ţ�˙˙���˙˙���˙˙�˙ţ���˙ű�˙ý���˙ý�˙ţ�˙˙�˙˙�˙ţ�˙˙����˙ţ���˙˙�˙ü�˙ý��˙ý�˙ý�˙˙�������˙ţ�˙ţ�˙˙˙˙�˙ý�˙ü�˙ý�˙ü�˙ý�˙ţ�˙ţ������������������˙ţ�˙˙˙˙�˙˙���˙ţ�˙˙�˙ţ�˙˙�˙˙�������˙ţ�˙ţ�������˙˙���������˙˙�����˙˙�˙˙�˙ţ���˙˙�˙ţ�˙ţ�˙ţ�˙ü�˙ţ�����������˙˙��˙ţ�˙ü���˙ţ�˙˙���˙˙�����˙ü�˙ý�˙˙���˙ţ�˙ý��˙ý�˙ţ���˙ý�˙ű�˙ü�˙ý�˙ý�˙ţ���˙˙�˙˙���˙˙�˙ü�˙ú�˙ý���˙ţ��˙ý�˙ţ���˙˙�������˙˙������˙ţ�˙˙˙˙�˙ü�˙ý��˙ţ�˙ţ�˙˙�������˙ţ�˙ţ�˙˙���������˙ţ�˙˙˙˙�˙˙˙˙�˙ţ��˙ţ�˙ţ�˙˙�����˙ţ�˙ü�˙ý���˙ţ�˙ý���˙˙���˙˙���˙˙�˙ý�˙ü�˙ţ���˙ý�˙ţ���˙˙�����˙˙�˙ý�˙ţ���˙˙�˙˙�˙˙���˙˙��������˙ý�˙ü�˙ý�˙ţ�˙ţ�˙ţ�˙ţ�˙ü�˙ü���˙ţ�˙ü�˙˙���˙ţ�˙ţ�˙ý�˙˙���˙˙�˙ţ�˙ý���˙ţ�˙ý���˙ţ�˙ü�˙ý��˙ý�˙ţ�˙˙�˙ţ�˙ţ���˙˙�˙˙�˙˙�˙ţ�˙ý�˙˙˙˙�˙ü�˙ý�˙ţ�˙ý�˙˙���˙˙�����˙ţ�˙˙���������˙˙����������������˙˙�˙ţ���˙˙�˙˙˙˙�˙ţ�˙˙˙˙�˙ü�˙ü�˙ý�˙˙˙˙�˙ý�˙ý�˙˙�˙˙�˙ţ�˙ý�˙˙�˙˙�����˙˙�˙˙���˙˙�˙˙�˙ý�˙ý�˙ý�˙ű�˙ü���˙˙�˙ţ�˙˙�������˙ţ�˙ţ���˙˙���˙˙�˙ţ�˙ţ�˙ţ�˙ţ�˙ţ�˙ý�˙ţ�˙˙�˙ý�˙ý���˙˙�˙ý�˙ý�˙ü���˙ţ�˙ű�˙ý�˙ţ�˙˙���˙ý�˙ű�˙ü�˙ţ���˙˙�˙˙�������˙˙���˙ţ�˙ý�˙ý�˙ţ���˙˙�˙˙�˙˙���˙ţ�˙ű�˙ů�˙ú�˙ý����������������˙ţ��˙ţ�˙ý��˙ţ�˙ý�˙˙�����˙˙���˙ţ�˙˙���˙˙���˙˙�˙˙�˙˙�˙˙�˙˙�˙˙�˙˙�˙˙�˙˙���˙ý�˙ý�˙ý�˙ý�˙ţ�˙˙�˙˙�˙˙�˙˙�˙˙�˙˙�˙˙���˙ţ�˙ý�˙ţ�˙˙���˙˙�������˙˙�������˙˙���˙˙�˙ý�˙ţ�˙˙���˙ţ���˙ţ�˙ü�˙ý�˙ţ���˙ţ�˙ý�˙ý�˙ý�˙ý�˙ý�˙ý���˙ţ�˙ý��˙ţ�˙ü�˙ţ�˙ţ�˙ţ���˙˙�˙˙˙˙�˙ý�˙ü�˙ü�˙ý�˙˙�����˙ţ�˙ü�˙ü�˙˙�˙ţ�˙ú�˙ú�˙ţ�����˙ţ�˙ü�˙ý�˙˙���˙˙�˙˙�˙˙�˙˙�˙˙�˙˙���˙ý�˙˙���˙ý�˙ý���˙˙�˙˙�˙˙�˙ţ�˙ü�˙ý��˙ţ�˙˙�˙˙�˙˙���˙˙�˙ţ���˙ţ�˙ü�˙ţ�����˙˙�˙ţ�˙ţ�˙˙���˙ţ���˙˙�˙ţ�˙ţ�˙ţ�˙ţ�˙˙�˙˙�������˙˙�˙ţ�˙ţ���˙˙���˙˙�˙˙���˙˙�˙˙�˙˙���˙ţ�˙ý�˙˙���˙˙�˙ţ�˙˙�˙ţ�˙˙���˙ţ�˙˙�����˙ţ�˙ţ�˙˙�˙ý�˙ű�˙ţ���˙˙�˙˙�˙ý�˙ý�˙ý�˙ý�˙ý�˙˙���˙˙���˙˙�˙˙���˙˙�˙˙�˙ţ�˙ý�˙ý��˙˙�˙˙���˙˙�˙˙�˙˙���˙˙�˙˙���˙˙���˙˙�˙˙���˙˙�˙˙�˙˙�����˙˙�˙˙�˙ţ�˙˙�˙ţ�˙ţ�˙˙���������˙ţ�˙ţ�˙˙����˙˙�˙ý�˙ü�˙˙�˙ţ�˙ţ���˙ţ�˙ţ�˙˙�˙˙�˙˙�˙˙���˙˙�˙˙�����˙˙���˙˙�˙ţ���˙ü�˙ű�˙ý�˙˙���������˙ţ�˙ţ�˙˙�����˙ţ�˙ţ�˙ü�˙ů�˙ů�˙ý��˙ţ�˙˙���˙ţ�˙˙�˙˙���˙˙�˙ü�˙ű�˙ü�˙˙�˙˙���˙ţ�˙ý�˙˙�˙ţ�˙ý�˙ý�˙˙���˙ţ�˙˙���˙ţ�˙ţ�˙˙������˙˙�˙ý�˙ý���˙˙��˙ý�˙ü�˙ţ�������˙˙�˙ţ�˙ţ�˙ţ��˙ţ�˙ţ�˙˙�˙ţ��˙˙�˙ţ�˙˙���˙˙���˙ţ�˙˙�˙˙�˙ţ�����˙˙�˙˙���˙˙�˙˙�˙ý�˙ý�˙ţ���˙˙���˙˙�˙˙���˙˙���˙˙�˙˙���˙˙���˙ţ�˙ý���˙ţ�˙ý�˙ţ�˙˙�˙˙���˙˙�˙˙�˙ý�˙ý�˙ţ�˙˙���˙˙�˙˙�˙˙�����˙˙���������˙ţ�˙ţ�˙ý�˙ţ���˙˙�˙˙�����˙˙�˙ţ�˙ţ�˙ţ��˙ý�˙ţ�����˙˙�������˙˙�������˙˙�˙ý�˙ü�˙ý���˙˙�˙˙������˙˙�˙ü�˙ü�˙ý�˙ţ�˙ţ�˙˙˙˙�˙˙����˙˙�˙ţ�˙ü�˙ţ�˙ţ�˙ý�˙ţ�˙ţ�����˙˙�˙ý���˙˙�˙˙���˙˙�˙ţ�˙˙���˙ţ�˙ţ�˙˙˙˙�˙ü�˙ý��˙˙�����˙ţ�˙ý�˙˙�����˙ý�˙ü�˙ý�˙ý�˙ţ��˙ţ�����˙˙�˙˙���˙˙�˙˙�˙˙�˙˙�˙ţ�˙ý�˙ý��˙ţ�˙ý�˙ţ�˙ţ�˙˙����˙˙�˙ţ�˙ý�˙ţ���˙ţ�˙ţ���˙˙���˙˙���˙˙�˙˙�˙˙�˙˙���˙ý�˙ý�˙ţ���˙˙�˙ý�˙ţ���˙ţ�˙ţ�˙ţ�����˙˙�˙ý�˙ţ�˙˙�˙ţ�˙ţ��˙ţ�˙ţ�˙˙����˙˙�˙ţ���˙ţ�˙ű�˙ű�˙ţ���˙˙�˙ţ�˙ţ�˙ţ�˙˙���˙ţ�˙ü�˙ű�˙ý�˙˙������������������������˙˙�˙ţ��˙ý�˙ţ���˙ü�˙ü�˙ţ��˙˙˙˙���˙˙�˙ü���˙˙��˙ý�˙ý�˙ţ�˙˙�˙˙�˙˙�˙˙�˙˙���˙ţ�˙ý���˙ţ�˙ü�˙ý�˙˙�����˙ţ�˙ý�˙˙���˙˙�������˙ţ�˙ţ�˙ţ�˙ţ�˙ţ���˙˙�˙ţ�˙˙�˙˙�˙˙���˙˙�����˙˙�˙˙�����˙˙�˙˙���˙˙�˙˙���˙˙�˙˙�˙˙���˙˙���˙˙���˙˙�˙ţ�˙ţ�˙˙�˙ţ�������˙˙���������˙˙�˙ţ�˙ţ�˙ţ�˙˙���������˙ţ�˙ţ�˙˙�����˙ţ�˙ý�˙˙���˙ţ�˙˙������˙˙�˙ý�˙˙�����˙˙���˙ţ�˙ţ�˙˙����˙˙�������˙˙�������˙˙�������˙˙�˙˙�˙ţ���˙˙���˙˙�˙ý�˙ý�˙ý�˙ý�˙ý�˙ý�˙ţ�˙ý�˙ţ����˙ţ�˙˙˙˙�˙ý�˙˙���˙˙�˙ţ�˙ţ�˙˙����˙˙�˙ţ���˙˙�˙ţ�˙ţ�˙ţ�˙˙���˙ţ�˙˙���˙ţ�˙ţ�˙ţ�˙ý�˙ý�˙ý�˙ý�˙˙���˙˙���������˙ţ�˙ţ��˙ţ�˙ţ�˙ţ�˙ţ�˙ţ�˙ţ�˙ţ�˙ţ�˙ţ�˙ţ���������˙˙�˙ţ��������˙ý�˙ţ�˙˙˙˙�����˙˙�˙ý�˙ý����������˙ţ�˙ţ�˙˙����˙˙�˙ý�˙ţ��˙˙���˙ţ�˙ý�˙˙�˙˙�˙˙���˙˙�˙˙���˙ý�˙ý�˙ţ�˙˙�˙˙�˙˙�˙˙���˙ţ�˙˙�����˙ţ�˙ţ�˙˙�˙˙˙˙�˙˙�˙˙˙˙�˙˙���˙ţ�˙˙�˙ţ�˙ü�˙ţ�����������������������˙˙�����˙˙�˙˙���˙ţ�˙ý�˙ţ�˙ţ�˙ţ�����˙˙���˙˙�˙ý�˙ţ����������������˙ţ�˙ţ�˙˙�����������˙ţ�˙ţ�������˙˙���˙˙�˙ý�˙ţ���˙˙�˙˙�˙ý�˙ý��˙ý�˙ţ���˙ű�˙ü���˙˙�˙˙���˙˙�˙˙�˙ţ�˙ý�˙ý�˙˙�˙˙�˙ţ�˙ý�˙ý�˙ý�˙ý�˙ý�˙ţ���˙˙�˙˙�����˙˙�����˙ţ�˙ü�˙ű�˙ý�˙˙�˙ţ�˙ý�˙˙�˙˙�˙˙�����˙˙�����������������˙˙�˙ü�˙ü�˙ţ�˙˙���������˙ţ�˙ţ�˙˙����������������˙˙���˙˙�˙ý�˙ţ���˙˙���������˙˙���˙˙�˙ţ���˙˙�˙ý�˙ü�˙˙˙˙�˙ţ��˙ý�˙ü�˙ý���˙ţ���˙˙��˙ý���˙˙�˙ţ���˙˙�˙ţ�˙ţ�˙ţ�˙˙���˙ţ�˙˙���˙˙���˙ţ�˙ý���˙˙�˙ý�˙ý�˙ü�˙ţ���˙ü�˙ú�˙ü�˙ţ�˙ý�˙ţ��˙ü�˙ü���˙˙���˙˙�˙˙�˙˙�˙ý�˙ţ���˙ý�˙ţ���˙ţ�˙ý�˙ý�˙˙���˙˙�˙˙���˙˙�˙˙�˙˙���˙˙�˙ţ�˙˙���˙ţ�˙˙���˙˙�˙˙�˙˙�˙ý�˙ű�˙ý�����������˙˙�˙˙������˙˙�˙˙������������˙˙�����˙˙�˙ţ�˙ţ�˙˙���˙˙�˙ý�˙ý�˙ţ�˙ţ�˙˙���˙˙�˙˙���˙ţ�˙˙���˙˙���˙ţ�˙ý�˙ý�˙ý�˙ţ���˙ý�˙ý�˙ý�˙ű�˙ü�˙˙˙˙�˙ţ��˙ţ���˙˙�˙ţ������˙ţ���˙˙�˙ţ���˙˙�˙ý�˙ü�˙ý�˙˙���˙˙���˙ţ�˙˙˙˙�˙ţ�˙ţ�˙ţ�˙ţ�������˙˙�˙ý�˙ţ�����˙˙�˙ţ�˙ţ�˙ţ�˙ţ��˙ý�˙ü�˙ţ�˙˙�˙˙���˙˙�˙˙�����˙˙�˙˙�˙ţ�˙˙���˙˙�˙ý�˙ü�˙ţ�����˙˙�˙ý�˙˙˙ţ�˙ű�˙ý���˙˙����˙ţ�˙˙�˙ţ�˙ý�˙ţ�˙˙�˙˙�˙˙�˙˙���˙˙�˙˙�����˙ţ�˙ü�˙˙���˙˙˙˙�˙˙���˙ţ��˙ý�˙ü�˙ţ���˙˙�˙ţ���˙˙�˙ţ�˙˙���������˙˙�˙ţ�˙˙���˙ţ��˙˙���˙ţ�˙ţ�˙˙�˙˙���˙˙�˙˙�˙˙�˙˙���˙˙�˙˙���˙˙�˙˙���˙ý�˙˙���˙˙���˙˙�˙ţ�˙ý�˙ţ�����˙˙�˙ý�˙˙˙˙�˙ţ��˙ţ���������˙˙���˙˙�˙ţ���˙˙���˙˙�˙ý�˙ý�˙ý�˙ý���˙ţ�˙ţ���˙ü�˙ű�˙ý�˙ý�˙ý�˙ý�˙ţ���˙˙���˙ţ�˙ţ�˙˙���˙˙�˙ý�˙˙�˙˙�������˙˙˙˙�˙ţ�˙˙������������������˙˙�˙ý�˙ý��˙ţ�˙ţ�˙ý���˙ţ�˙ű�˙ţ���˙˙�����˙˙�˙˙�˙ţ�˙ý���������˙˙��˙ý�˙ú�˙ţ˙˙�˙ü�˙ţ˙˙�˙ţ�˙˙��˙˙�˙ţ�˙ţ�˙ţ�˙ţ���������˙˙������˙ý�˙ü�˙˙˙˙�˙ü�˙˙˙˙�˙˙������˙˙�˙ü�˙ű�˙ý���˙˙���˙˙�˙˙�˙ý�˙ţ�����˙˙�����˙˙�˙ţ��˙ý�˙ţ��˙ţ�˙ý�˙ţ�˙˙˙˙�˙ţ���������˙˙�����˙˙�˙ý�˙ý���˙˙�˙ţ�˙˙�˙˙�˙ţ�˙˙���˙ţ�˙˙˙˙�˙ü�˙ý�˙ţ�˙ţ�˙ý��˙ý�˙ű�˙˙˙˙�˙ü�˙˙˙˙�˙ţ���˙˙�˙ý�˙˙˙ţ�˙ű�˙ü�˙ý���˙˙�˙ţ�˙ţ�˙ţ�˙ý�˙ý�˙˙���˙˙�˙˙�����˙˙�����˙ţ�˙ý��˙ý�˙ţ�����˙˙�˙ţ�˙ţ����˙ý�˙ü�˙ý�����������˙˙�˙ý�˙ü�˙ţ������������������˙ţ�˙ţ�˙˙����˙˙�˙ý�˙ţ�˙˙�˙˙���˙˙�˙˙���˙ţ�˙ü�˙ű�˙ý�˙˙���˙˙�˙˙���˙˙�˙˙�˙˙�˙ý�˙ý�˙ţ�˙˙�˙˙�˙ý�˙ţ���˙˙�˙ţ�˙ý�˙ţ���˙˙���˙˙�˙ý�˙ű�˙ý��˙ţ��������˙ţ�����������˙˙�˙ţ�˙˙�����˙ţ�˙ý�˙˙���˙˙���˙ţ�˙ý�˙ý�˙˙�˙˙�˙˙�˙˙���˙˙�˙˙���˙˙�˙˙���������˙ţ�˙˙˙˙�˙ţ�˙˙������˙˙�˙ţ�˙ţ�������˙˙�˙ü�˙ý�˙˙�˙ţ���˙ţ�˙ý�˙ţ���˙˙�˙ţ�˙˙�˙ţ���˙ţ�˙ű�˙ý�˙˙��������������������˙˙�˙ý�˙ţ�˙ý�˙ý�˙˙˙˙��������������˙ţ�˙ţ�˙ý�˙˙���˙˙���˙˙�˙˙�˙˙�˙ţ�˙ý�˙ý�˙˙���˙ţ�˙˙�˙ţ�˙ü�˙˙��������˙˙�˙ü���˙ý�˙ű�˙ý�˙ý�˙ü�˙ý�˙ü�˙ü�˙˙�����˙ý�˙ý�����������˙˙�˙ţ�˙ý�˙ý�˙˙˙˙�˙˙�˙˙�������˙˙�������˙ţ�˙ü���˙ţ�˙ý�˙˙�˙˙���˙ţ�˙ý�˙ţ���˙ţ�˙˙�˙˙���˙ţ�˙ü�˙ţ���˙ţ��˙ý�˙ý���˙˙�˙˙�����˙˙�˙˙���˙˙�˙˙�˙˙�˙˙�˙˙�����˙˙�˙˙�����˙˙���˙ţ�˙ý�˙ţ�˙ý�˙˙˙˙�˙ý�˙ţ�˙ţ�˙ý�˙˙˙˙�˙ţ�˙˙��˙˙�˙ţ�˙˙˙˙�˙˙�˙˙�˙˙���˙˙���˙˙���˙˙�˙˙�˙˙���˙˙�˙ý�˙ţ�˙˙�˙˙���˙ţ�˙ţ�˙ý�˙ý��˙ý�˙˙����˙˙�˙ţ�˙ţ���˙ţ���˙˙�˙˙���˙˙�˙˙���˙˙���˙˙���˙˙�˙ý�˙ţ�˙ţ�˙ţ�����������˙˙�˙ţ�˙˙˙˙�˙˙���˙ţ�˙˙��˙˙�˙ţ�˙ţ�˙˙�˙˙�˙˙�˙˙�˙˙���˙˙�˙˙�˙˙�˙ý�˙ý�˙ü�˙ű�˙ü�˙˙˙˙�˙ý�˙ţ���˙˙���˙˙���˙˙�˙ţ���˙ţ���˙˙���˙˙���˙˙�˙ţ���������˙˙���������˙˙�˙˙�˙ţ�˙˙���˙˙���������˙ţ���˙˙�˙ü�˙ţ��˙ţ�˙ý�˙ý��˙ţ�˙˙˙˙�˙ü�˙ý��˙ţ���˙˙�˙ţ�����˙˙�˙ű�˙ű��˙ţ��˙ü�˙ű�˙˙˙˙�˙˙���˙ţ�˙ţ�˙ý�˙ţ�˙ţ�˙ü�˙ţ���˙˙�˙˙���˙˙���˙˙�˙ţ�˙ţ�˙ţ�˙ţ�˙ü�˙˙�����˙ţ�˙˙���˙ţ�˙ü�˙ţ���˙˙���˙˙���˙˙�˙˙�˙ý�˙ý�˙ţ���˙ţ�˙ü�˙ý�˙ţ�˙ü�˙ű�˙ţ���˙˙�˙˙�˙˙�˙˙�˙˙�˙˙�˙˙���˙˙���˙ţ�˙ý�˙ü�˙ý���˙˙�˙˙���˙˙���˙˙�˙ý�˙ü�˙ý���˙˙�˙˙˙˙�˙ý�˙˙�����������˙˙���˙ţ�˙ý�˙ý�˙˙���˙˙���˙˙���˙˙���˙˙���˙˙���˙˙�˙ţ�����˙˙�������˙˙�����˙˙����˙ţ���˙˙�˙ý�˙ţ�˙ţ�˙ý��˙ţ�˙ý��˙ţ�˙ţ�˙ţ�˙ţ�˙ý�˙ü�˙˙�������˙ţ�˙ü�˙ű�˙ű�˙ţ���˙˙���˙ţ�˙˙���˙ţ�˙ţ�˙ţ�˙ţ�˙ţ�˙˙�˙ţ���˙ţ�˙ú�˙ü�˙˙�����˙˙�˙˙�����˙˙�˙˙���˙˙�˙˙�˙ý�˙ű�˙ü���˙˙�˙˙�˙˙�˙ý���˙˙�˙ţ���˙ţ�˙˙�����������˙ţ�˙ţ�˙ţ�˙˙˙˙�˙ü�˙ý�˙ţ�˙ţ�˙˙��˙˙�˙ü�˙ü�˙ţ˙˙�˙ţ���˙ţ�˙˙���˙˙�˙˙���˙ý�˙ţ���˙ý�˙ţ�����˙˙�˙ţ��������˙ţ�˙˙˙˙�˙ý�˙ý��˙˙�˙˙���˙ţ�˙ţ���˙ý�˙ţ�����˙˙�˙ý�˙˙�˙˙�˙˙���˙˙���˙ü�˙ű�˙ţ���˙ţ�˙ţ�˙ţ���˙ţ�˙ţ��˙ţ�˙ţ�˙˙�˙ţ�˙˙���˙ţ�˙ţ�˙˙������˙˙�˙ţ���˙˙�����˙˙�˙ý�˙ü�˙ý��������˙ţ�˙ţ�˙ţ�˙ţ�˙ý�˙ý�˙ţ�˙˙�˙˙�˙˙�˙˙�˙˙�˙˙���˙˙���˙ý�˙˙�����˙ţ�˙ý�˙ý�˙ý�˙ű�˙ű�˙ü���˙ţ�˙ý�˙ý�˙ţ�˙ţ�˙ţ�˙ý�˙ţ���˙ý�˙ţ�˙˙�˙ţ��˙ý�˙ü�˙ţ�˙ý�˙ü�˙ţ����������������������˙˙���������˙˙�����������������������������˙ţ�˙ţ���˙ţ�˙ţ�˙ţ�˙ü�˙ü�˙ţ��˙ý�˙ý�����˙˙�������˙˙������������˙˙�˙ü�˙ţ��˙ţ�˙˙��˙ţ�˙˙����˙ţ���˙˙�˙˙˙˙�˙ý�˙ü�˙ü��˙ţ�˙˙˙˙�˙ü�˙ý�˙ý�˙ţ�˙ý�˙ţ�˙˙�˙˙˙˙�˙ţ�˙ý��˙ý�˙ü�˙˙˙˙���˙˙�˙ý�˙ţ���˙ý�˙ý�˙˙�˙ý�˙˙���˙˙�˙˙�˙˙���˙ţ���˙˙�˙ţ�˙ý�˙ú�˙ü�˙ý�˙ţ��˙ţ�˙ţ��˙ţ���˙˙�˙˙�����������˙˙˙˙�˙˙�˙˙˙˙�˙˙�˙ý�˙˙�˙ţ�˙˙�����˙˙���˙˙�˙ý�˙ţ�˙˙�˙ý�˙ţ���˙ý���˙˙�˙˙�����˙ţ�˙ý�˙ý�˙ý�˙ţ�˙˙�˙˙�˙ţ�˙˙�˙˙�˙ý�˙˙���˙˙�����˙ţ�˙ý�˙˙���˙ý�˙ü�˙ű�˙ţ˙˙�˙ű�˙ţ�����˙˙�˙˙���˙˙�˙˙���˙˙�˙˙�����˙˙���������˙˙˙˙�˙ý�˙ý�˙ţ��˙ţ�˙ý�˙˙���˙˙�˙˙���˙˙�˙ţ�˙ü�˙ţ˙˙�˙ý�˙˙�����˙˙�˙˙�����˙˙���˙ţ�˙ý�˙ţ���˙ţ�˙ű�˙ţ�����˙ţ�˙ü�˙ţ�˙ý�˙ü�˙ţ�˙ţ�˙ţ��˙ţ�˙ü�˙ý�˙˙���˙˙���˙ţ�˙ţ���˙˙���˙ţ���˙ţ�˙ý�˙ü�˙ü�˙ý�˙ţ�˙˙�˙˙�˙˙�˙˙���˙˙�˙ý���˙ţ�˙ţ���˙˙���˙˙�˙˙�˙˙���˙˙�˙˙�����˙˙�˙˙�˙ý�˙ý�˙ţ�˙ţ�˙ý�˙˙���˙˙�������˙ţ�˙˙˙˙�˙ţ�˙ţ�˙˙�˙˙�˙ý�˙ţ���˙˙���˙˙�˙ý�˙ţ�˙˙���˙˙�˙˙���˙ţ�˙ý�˙ţ�˙˙���˙ţ��˙ý�˙ý�˙ţ���������˙˙�����˙˙�˙ý�˙ü�˙ű�˙ú�˙ú�˙ý���˙˙�˙ţ�˙˙���˙ţ�˙ü�˙ű�˙ý�˙˙�˙˙�˙˙���˙˙�˙˙�˙˙�˙ţ�˙ü�˙ý�˙˙���˙˙�˙˙���˙˙�˙ý���˙˙���˙ţ�˙ü�˙ţ�˙ţ�˙ţ�˙ţ�˙ţ�����˙˙�˙ţ���˙ý�˙ţ�˙˙���˙˙���˙ţ�˙ţ�˙ý�˙ţ��˙ţ�˙ü�˙ţ�˙˙����˙˙�˙˙˙˙�˙ý�˙ţ�����˙˙�˙˙�˙˙˙˙�˙˙�˙ţ�˙ý�˙ţ���˙˙�˙˙�˙˙������������������˙˙�˙ţ�˙ü�˙ţ��˙ţ�˙ţ���������˙˙�˙ý�˙ü�˙ü���������˙˙���˙˙�����˙˙�˙˙���˙˙�˙ţ���˙˙�˙˙˙˙���˙˙�˙ţ�˙˙�˙˙�˙ţ�˙˙���˙˙�˙ý�˙˙��˙˙����˙ţ�˙˙��˙ý�˙ý���˙˙�˙˙�˙˙�˙˙�˙˙���˙˙���˙˙���˙ţ�˙˙�����˙ţ�˙ü���˙ţ�˙ý�˙˙�˙˙�˙˙�˙˙�˙ý�˙ü�˙ý�������˙ţ�˙˙�˙˙�˙ţ�˙ý�˙ţ�˙˙���˙ţ���˙˙���˙ý�˙ţ���˙˙�˙˙�˙ţ�˙ý�˙˙�����˙ü�˙ű���˙ţ�˙ý���˙˙�˙˙�����˙˙�˙ţ���˙˙��˙ý�˙ű�˙ű�˙ú�˙ţ�����˙˙�˙˙���˙˙�˙˙�������˙˙�˙˙�˙ţ�˙ţ���˙ţ�˙˙���˙ü�˙ű�˙ţ���˙˙�����˙˙���˙˙�˙ţ���˙ý�˙ü�˙ý�˙ţ���˙ý�˙ţ˙˙�˙ţ���˙ţ�˙˙���˙ţ�˙ţ�˙ţ�˙ţ�����˙˙�˙ţ��˙ţ��˙ţ�˙ţ��˙ţ�˙ţ�˙ţ�˙ţ��˙ţ��˙ý�˙ü�˙˙�����˙ţ�˙ý�˙˙���˙ý�˙˙���˙˙�˙ţ�˙ý�˙ţ�˙˙�˙ţ���˙˙�˙ţ���˙˙���˙˙�˙˙���˙˙�˙˙���˙˙���˙˙�˙ý�˙ţ���˙˙�˙˙���˙˙�˙ý�˙ţ���������˙˙�˙ţ�˙ý�˙ű�˙ű�˙ţ����˙˙���������˙˙˙˙�˙ű�˙ű�˙ţ�˙ý�˙ţ���˙ý�˙˙�˙˙�˙ţ�˙ţ���˙ţ�˙ţ���˙˙�˙˙�˙˙�˙˙���˙ţ�˙ý�˙˙˙˙�˙˙���˙ý�˙ţ���˙˙���˙˙���˙ţ�˙ű�˙ü�˙ţ�˙˙�˙ţ�˙ý�˙ţ���˙ţ�˙ţ�˙˙���˙˙�˙˙�˙ţ�˙˙���˙ü�˙ý�˙ţ�˙ţ�˙ţ���˙˙�˙ţ�˙˙�������˙˙�˙˙���˙˙�˙ý�˙ü�˙ţ����˙ţ���˙˙�˙ü�˙˙���˙ţ�˙ý���˙˙�˙˙�˙˙�˙ţ�˙ý�˙ý�˙˙�����˙˙����˙˙�˙ţ�˙ţ���˙˙�˙ţ�˙ţ������˙ţ������˙ţ�˙ý�˙ü�˙ţ���˙˙�����˙ý�˙ý���˙˙���˙˙�˙ý�˙ü�˙ţ���˙˙���˙ţ�˙ű�˙ý��˙ţ�˙ţ���˙˙�˙ţ�˙ţ�˙ţ�˙ý�˙˙���˙ý�˙˙�˙˙�˙ţ�˙ý�˙˙���˙ý�˙ý���˙˙�˙ý�˙˙���˙˙���˙˙�˙˙�˙˙���˙˙�˙ţ������˙ţ�˙ţ�˙ü�˙ű�˙ű�˙ţ������˙ţ�˙˙���˙˙���˙˙���˙˙�˙ý�˙ý���˙ţ�˙ţ���˙˙���˙˙���˙˙�˙ţ�˙ţ�˙ý�˙ţ��˙ţ�˙ţ�˙˙���˙ţ�˙ý�˙ý�˙˙���˙˙�����˙ţ�˙ý�˙˙�˙˙���˙˙�˙˙���˙˙���˙˙�˙ý�˙ü�˙ţ���˙˙�˙˙�˙˙�˙˙���˙˙�˙˙�˙˙�˙ý�˙ţ�˙˙�˙ţ�˙ţ��˙ý�˙ű�˙ý���˙˙��������������˙ţ�˙ţ�˙˙����˙˙�˙ţ���˙ţ�˙ü�˙ţ���˙ţ���˙˙�˙ţ�˙˙�˙ţ���˙˙�˙˙�˙˙�˙ý�˙˙�˙˙�˙ü�˙ű�˙ü�˙ü�˙ý�˙ţ�����˙˙�˙˙�����˙˙�˙˙�˙˙�˙˙�˙˙�����˙˙���˙ţ�˙˙���˙˙�����˙˙�˙˙�˙˙�˙ý�˙ţ���˙˙�˙˙�˙˙�˙˙�˙˙�˙ţ�˙ý�˙ý�˙ý���˙ţ�˙ű�˙ű�˙ţ���˙˙���˙˙�˙ţ����������������˙ý�˙ű�˙˙˙˙�˙˙˙˙�˙ţ�˙ý�˙ý�˙ü�˙ý�˙˙�˙˙�˙˙���˙˙�˙ţ�˙ý�˙ý�˙˙�˙ţ�˙ý�˙ý�˙ţ�˙ý�˙˙˙˙�˙ü�˙ü�˙ý���˙˙�˙ţ�˙˙���˙˙�˙ý�˙˙���������˙ţ�˙˙���˙ţ�˙ţ�˙ţ�˙ý�˙˙�����˙˙�˙˙˙˙�˙ü�˙ú�˙ű�˙ý�˙˙���˙ţ�˙ý�˙˙�����˙ý�˙ý�˙˙�˙˙�˙ý�˙ţ�˙ţ�˙ý�˙˙�˙ţ���˙˙�˙ţ���˙˙���˙˙�˙ţ���˙ý�˙ý��������˙ţ�˙ţ�˙˙˙˙�˙ţ�˙˙�������˙ţ�˙ý�˙˙�˙˙�˙˙�˙˙�˙˙�˙˙�˙ü�˙ý�˙˙�˙ý�˙ý��˙ű�˙ú�˙ü��������˙ţ�˙ţ�˙˙���˙ţ�˙˙���˙ţ��˙ţ�˙˙˙˙�˙ţ�˙ţ�˙ţ�˙˙˙˙�˙ü�˙ý�˙˙�˙ţ�˙ý�˙ý�˙˙�˙˙�˙˙�˙ţ�˙ü�˙ů�˙ű�˙ţ�˙˙�˙˙���˙˙���˙˙�˙ţ���˙ý�˙ü�˙ţ���˙˙�˙˙�˙ţ�˙˙���˙ţ�˙ţ���˙ý�˙ý�˙ţ���������˙˙�˙ţ���˙˙���������˙˙�˙ţ���˙˙�˙ý�˙ý�˙ý�˙ý�˙ý�˙ý�˙ţ�˙˙���˙˙�˙ý�˙ţ���˙ý�˙ű�˙ţ�˙˙�˙ý�˙ţ���˙ü�˙ý������˙˙����˙˙�˙ţ�˙ţ�����˙˙�˙ý�˙ú�˙ü�˙˙�����˙ţ�˙ţ�˙ý�˙˙���˙ţ�˙˙���˙ţ��˙ţ�˙ţ�˙˙���������˙˙�����˙ţ�˙ü�˙ý�˙ü�˙ú�˙ű�˙ý�˙˙�����˙ţ�˙ý��˙˙���˙ţ���˙ţ�˙ü�˙ý�˙˙�����˙ţ�˙ý���˙ţ�˙ý�˙˙���˙ţ�˙ţ�˙˙�������˙ţ�˙ţ�˙ţ��˙ţ�˙ű�˙ý�˙˙�����˙˙���˙ţ�˙ý�˙ý�˙˙�˙˙�˙ţ�˙ý�˙˙���˙ţ�˙˙�˙ţ�˙ţ�˙ý�˙˙���˙ţ�˙˙���˙˙˙˙�˙ý�˙˙���˙˙�˙ţ�˙˙�˙˙���˙˙�˙˙�˙˙�˙ý�˙ý�˙ý�˙ţ���˙ý���˙˙���˙˙���˙˙�˙˙�˙˙�˙˙�˙˙�˙˙�˙ü�˙ű�˙ü�˙ý�˙ý�˙ţ�˙˙�����˙ý�˙ý�����˙˙�˙ý�˙˙˙˙�˙ţ���˙˙�˙ţ�˙ý�˙ţ�˙ţ�˙ţ��˙ţ��˙ü�˙ű���˙˙�˙ţ���˙˙�˙ţ�˙ý�˙ý���˙˙�˙˙�˙˙�˙˙�˙˙���˙ţ�˙˙���˙ţ�˙ţ�˙ţ�������˙˙�˙˙���˙˙�˙˙�˙˙�˙ţ�˙˙���˙ý�˙ý��˙ţ�˙˙���˙˙�˙˙���˙ý�˙˙���˙˙�����˙ţ�˙ţ��˙ţ�˙ţ����˙˙���˙ţ�˙ý�˙ý�˙˙���˙ü�˙ü�˙ţ���˙˙�˙ü�˙˙˙˙�˙˙˙˙�˙ţ�˙˙�˙ţ�˙ü�˙ű�˙ţ���˙˙���˙ţ�˙ý�˙˙˙˙�˙ţ�˙˙˙˙�˙ţ��������˙ţ�˙˙�����˙˙�˙˙���˙˙�˙˙�˙˙�˙˙�˙˙�˙˙�˙˙�˙˙�˙˙�˙ţ�˙ý�˙˙���˙ţ�˙ţ�˙ý�˙˙�������˙ţ�˙ţ�˙ţ�˙ţ���˙˙�˙ţ�˙ý�˙˙˙˙�˙ţ��˙ţ�˙ţ������˙ý�˙ţ��˙ý�˙ţ���˙ţ�˙ý�˙ţ���˙˙���˙˙���˙ţ�˙ý�˙˙�˙ţ�˙ü�˙ý�˙ý�˙ý���˙ţ�˙ű�˙ű�˙ű�˙ü�˙ţ�˙˙�˙ţ�˙ţ����˙ţ�˙ţ���������˙˙�˙˙�˙ý�˙ţ���˙ý�˙ý�˙ý�˙ţ�˙˙�˙ý�˙ü�˙˙˙˙�˙˙˙˙�˙ý�˙ü�˙ü�����˙˙�˙ý�˙ü�˙ţ�˙ţ�˙ţ�˙ţ�˙ţ�˙ţ�˙ţ�˙ý�˙ţ�˙ţ�˙˙�˙ţ�˙ý�˙˙˙˙�˙ý���˙˙���˙ţ�˙ý�˙ţ�˙ý�˙ý���˙˙�˙ý��˙ţ���˙ţ�˙ý���������˙˙�˙ţ�˙˙˙ţ�˙ű�˙ű�˙ţ�˙ý�˙ý�˙˙˙˙�˙ý�˙ţ�˙˙�˙ý�˙ü�˙ý���˙ý�˙˙˙˙�˙ý�˙˙˙˙�˙˙���˙˙�˙˙���˙ţ�˙ű�˙ý�˙˙�˙˙�˙˙���˙˙�˙ţ�˙ţ�˙ý�˙˙�����˙ţ�˙ü�˙ü�˙ú�˙ü�˙˙˙˙�˙ţ�˙ţ�˙ţ�˙ţ�˙ţ��˙ţ�˙ţ��˙ţ�˙ü�˙˙���˙ţ��˙ţ�˙ţ�˙ţ�˙ű�˙ý���˙˙�˙˙���˙ţ�˙ű�˙ü�˙ý�˙ţ�˙ţ�˙˙�˙ţ�˙ţ��������������˙ţ�˙ý�˙ý���˙ţ�˙ý�˙˙�����˙˙���˙˙���˙ţ�˙ţ��˙ý�˙ý�˙ţ�˙ý�˙ý�˙ý���˙˙�˙ţ���˙ţ�˙ý���˙˙���˙ý���˙ţ�˙ý�˙˙���˙˙���˙ţ�˙ü�˙ý��˙ü�˙ý�˙˙�˙ü�˙ů�˙ú�˙ţ�˙˙���˙ţ�˙ü�˙ü�˙˙�����˙˙���˙ţ�˙˙˙˙�˙˙������˙˙�˙ü�˙ü�˙ü�˙ý�˙˙�˙ţ�˙ý�˙ţ���˙˙�˙˙�˙˙�˙˙���˙˙���˙ţ�˙˙�����˙˙���˙ţ���˙ţ�˙˙���˙˙���˙˙���˙˙�˙˙�����˙˙�˙˙���������˙˙˙˙�˙ţ�˙˙˙˙�˙ţ�����˙˙�˙ý�˙ţ���˙˙�˙ţ���˙˙���˙˙�˙˙�˙˙�˙˙�˙˙�˙˙�˙˙�˙˙�˙˙�˙˙�˙ý���˙˙���˙ţ�˙ţ���˙ý�˙ý���˙˙�˙˙���˙˙�˙˙�˙˙���˙˙�˙˙���˙ţ�˙ü�˙ú�˙ű���˙ţ�˙˙˙˙�˙ü�˙ý�˙˙�������˙ţ�˙ü�˙ý���˙˙�˙ţ�˙ţ�˙˙�˙ţ�˙ţ��˙ţ�˙ý�˙˙�˙ţ�˙ţ�˙ţ���˙ý�˙˙��������˙˙�˙ü�˙ý���������������˙˙�˙ţ�����˙˙�˙ţ��˙ý�˙ü�˙˙˙˙�˙ţ���˙˙�˙ţ�˙ý�˙ţ���˙˙�˙ý�˙ý�˙ţ�˙ý�˙ű�˙ý�˙ţ�˙˙�˙ţ�˙ţ�����˙˙�˙ý�˙ý�˙ý�˙ţ���˙˙���˙ţ�˙ý�˙˙���˙˙˙˙�˙ţ�˙ţ������˙ý�˙ţ�˙˙���������˙ţ�˙˙�˙˙�����˙˙�˙ţ�˙˙����������������˙˙�˙˙�˙ţ�˙˙���˙ţ���˙ü�˙ý���˙˙�˙˙�˙ü�˙ţ���˙˙���˙˙���˙˙�˙˙�˙˙���˙˙���˙˙�˙ý�˙ţ���˙˙�˙˙�˙˙�˙˙���˙˙�˙ţ�˙ţ�˙ý������˙ţ�˙ţ��˙ý�˙ú�˙ý��˙ţ��˙ţ���˙˙�˙˙˙˙���˙˙�˙ű�˙ú�˙ý���˙ý�˙ű�˙ű�˙ţ�˙˙�˙˙�˙˙�˙˙�˙˙�˙˙���˙˙�˙˙���˙ý�˙ţ���˙˙�������˙˙�˙˙�����˙˙���˙˙�˙ţ���˙ţ�˙ý�˙ü�˙ý�˙˙�˙ţ�˙ű�˙ű�˙ý���˙ü�˙ý���˙˙���˙˙���˙˙���˙˙�˙ý�˙ý�˙ű�˙ý���˙ű�˙ţ������˙˙�˙ü�˙ţ���˙˙�˙ý�˙ű�˙ű�˙ý���˙ý���˙˙���˙˙��������������˙ţ�˙˙�˙˙���˙˙���˙ţ�˙ţ�˙˙�˙˙�˙ţ�����������������������˙˙�˙ţ��������˙ý�˙ü�˙ý�˙˙�����˙ţ�˙˙�˙˙�����˙˙�˙ţ�˙˙�˙ţ�˙ü�˙ý�˙ţ�˙ţ�˙˙˙˙�˙ý�˙˙��˙˙�˙˙���˙˙���˙ţ�˙ű�˙ü���˙˙�˙˙���˙˙�˙ţ�˙˙�˙˙�����˙˙�˙˙���˙ţ�˙ű�˙ű�˙ü�˙ţ�������˙˙�˙ü�˙˙˙ţ�˙ü�˙ţ���˙˙�˙ţ��������˙ţ���˙˙�˙ţ�˙˙�˙˙���˙˙�˙˙�����˙˙�������˙˙�˙ţ�˙˙���������˙˙�������˙˙�����˙ţ�˙ü�˙ţ���˙ţ�˙ý�˙ý�˙ý�˙ý�˙˙�˙˙�˙ţ�˙˙���˙˙�˙˙�˙ý�˙ţ�˙˙�˙˙�˙ý�˙˙���˙ý�˙˙���˙ý�˙ţ���˙ţ�˙ý�˙ţ��˙ý�˙ü�˙ý��������˙ý�˙ţ�˙ţ�˙˙�����˙˙�˙˙���˙˙�����˙˙�˙ţ��˙ţ���˙˙�˙ţ�˙˙���˙˙�˙ý�˙ü�˙ţ�˙ţ�˙˙���˙˙�˙ü�˙ţ���˙ý�˙˙�˙˙�˙˙���˙ţ�˙ý�˙˙�����˙ţ�˙˙���������˙˙��˙˙�˙ü�˙ű�˙ţ�˙˙������˙˙�˙ü�˙ţ�����˙˙�˙ý�˙ü�˙ţ�˙˙���������˙ţ�˙˙�����˙ţ�˙˙���˙ü�˙ü�˙ý�˙ţ��˙ţ�˙ý�˙ý�˙ý������˙˙�������˙ţ�˙˙���˙ţ�˙ü�˙ú�˙ű�˙˙������˙˙�˙˙��˙˙�˙˙�˙ü�˙ý�˙ý�˙ű�˙ű�˙ű�˙ű�˙ý�˙ţ���˙ţ�˙ý�˙˙�˙ţ�˙ü�˙ţ���˙ţ�˙˙�˙ţ�˙ţ�˙˙��˙˙�˙ţ�˙˙˙˙�˙ü�˙ý�˙˙�˙ţ�˙ü�˙ý�˙˙˙˙�˙˙�˙ţ�˙˙���˙ţ�˙ţ���˙ţ�˙ü�˙ţ�˙˙�˙ţ�˙˙�˙˙�˙ţ�˙˙�˙˙�˙ţ�˙˙�˙˙�˙ţ�˙˙�˙ţ�˙ţ�˙ţ�˙ţ�˙˙����˙˙�˙ţ�˙˙˙˙�˙ü�˙˙���˙˙˙˙�˙˙�����˙ţ�˙˙˙˙�˙ý��������˙ţ���������������˙˙�˙˙�˙ţ�˙˙���˙ý�˙ý�˙˙�˙˙�˙˙�˙˙�˙˙�����˙˙�˙˙�˙ý�˙ţ���˙˙��˙ý�˙ü�˙ţ�˙ţ�˙ý�������˙˙�������˙˙���������˙˙�˙ţ���˙˙�˙ţ���˙˙�˙ţ���������˙˙�˙˙�˙ţ�˙ţ���˙ţ�˙˙˙˙�˙ţ�˙ü�˙ţ�˙˙�����������˙ţ�˙ţ�˙˙�����˙˙���˙ţ�˙ý�˙ý���˙ţ�˙ţ���˙˙���˙ü�˙ü�˙ű�˙ý�˙˙�˙˙�˙˙�˙˙���˙˙���˙˙���˙ţ�˙ű�˙ü�˙ý�˙ţ���˙˙�˙ţ�˙ý�˙ţ�����˙˙�˙˙���˙˙�˙ý���˙ţ�˙ţ���˙ü�˙ţ���˙˙���˙ţ�˙ţ�˙ü�˙ü�˙˙������˙˙�˙ţ�����˙˙�˙˙���˙˙�������˙ţ�˙ü�˙ţ���˙ý�˙ý�˙ţ���˙ý�˙ü���˙˙�˙ţ�˙˙�˙ý�˙ţ���˙ý�˙ţ�˙˙�˙ý�˙ü�˙ţ�����˙˙�˙˙���˙˙���˙˙�˙ý�˙ý���˙˙�˙˙���˙˙���˙˙�����˙˙�˙ţ������˙ţ�˙ý�˙˙˙˙�˙ý�˙ű�˙ú�˙ţ���˙ţ���˙˙�˙ţ�����˙˙�˙ý�˙ü�˙ţ�˙ţ���˙˙�˙ü�˙ţ���˙ţ���˙˙�˙ý�˙ý�˙ţ���˙˙���˙ţ�˙ü�˙ü�˙ű�˙ý�˙˙���˙˙�˙˙���˙ý�˙˙���˙ţ�˙ü�˙ţ���˙ţ���˙˙�˙˙�˙˙�˙˙���˙˙�˙˙�˙˙���˙˙���˙˙�˙ţ���˙˙�˙ý�˙ý�˙ý�˙ý�˙ý�˙˙���˙˙�˙˙���˙˙���˙˙�˙ý�˙ţ���˙ý�˙ţ���˙ý�˙˙�˙˙�˙˙���˙ţ�˙ý�˙ü�˙ý�˙˙������������������˙˙����˙ţ�˙ý�˙ý�˙ü�˙ü�˙ý���˙˙�˙ţ�˙˙�˙˙�˙ý�˙ü�˙ű�˙ü�˙˙���˙˙�˙˙���˙ţ�˙ý��˙ű�˙ů�˙ű�˙ý�˙˙���������˙ţ�˙ţ�˙˙�������˙ţ�˙ý�˙˙���˙ý�˙ý�˙ţ�˙˙�˙˙�˙˙�˙˙�˙˙�˙ţ�˙ţ�˙ü�˙ü�˙ţ�˙ţ�˙ţ�˙˙��������������˙˙�˙ý�˙ţ���˙˙�˙˙�˙˙�˙˙�˙˙�����˙ţ�˙ý���˙ţ�˙ý�˙˙���˙˙���˙˙�˙˙�˙˙�˙˙�˙ţ�˙ý�˙ţ���˙˙�˙ý�˙ý�˙ý�˙ý�˙ţ���˙ţ�˙ü�˙ţ�����˙˙�˙˙����˙˙�˙˙�˙ţ�˙ţ�˙ü�˙ý�˙˙��˙˙�˙ţ��˙ý�˙ü�˙˙˙˙�˙ţ�˙˙��˙˙���������˙˙���˙˙�˙ţ�˙ţ�˙ţ����˙ţ�˙ý�˙˙˙˙�˙ţ�˙ţ�˙ý�˙ü�˙ţ���˙˙�˙ý���˙˙�˙ý���˙˙�˙ý�˙ţ���������˙˙�˙ţ��˙ţ�˙˙˙˙�˙ý�˙ý�˙ý�˙ţ�˙˙�˙˙�˙˙�˙ţ�˙ý�˙˙˙˙�˙ü�˙ý��˙ţ�˙˙�����˙ţ�˙˙�˙˙�����˙˙�˙ţ�˙ý�˙ţ��˙ţ�˙ţ�˙ţ�˙ţ�˙˙����������˙˙�˙ü�˙ü�˙˙˙˙�˙ý�˙ü�˙ţ���˙˙�˙ţ���������˙˙���˙˙�˙ý�˙ü���˙˙�˙ý�˙ţ���˙˙�˙ţ�˙˙�����˙˙�˙˙���˙˙�˙˙�˙˙���˙ţ�˙ü�˙ţ�˙ţ�˙ý�˙ţ���˙ý�˙ţ���˙˙�˙˙�˙˙���˙ţ�˙ü�˙ý�˙˙�����˙ţ�˙ý�˙ý�˙ý�˙ţ�˙˙���˙˙�˙˙�����˙˙���˙˙�˙˙���˙˙���˙˙���˙˙�˙ţ���������˙˙�˙˙�˙ţ�˙ý�˙ţ�˙ţ�˙ţ���˙˙�˙ţ���˙˙�˙ý�˙ţ���˙˙�˙˙���˙˙�˙˙�����˙˙�˙ţ�˙˙���˙ţ�˙˙���������������������������˙ţ�˙ý�˙˙���˙˙�˙˙���˙˙���˙ţ�˙ý�˙˙��������������˙˙�˙˙���˙˙�����˙ţ�˙˙�����˙˙���˙˙�˙˙���˙˙�˙˙�˙˙�˙˙�˙˙���˙ţ�˙ý��˙ţ�˙ý�˙ţ���˙˙���˙ţ�˙˙˙˙�˙ý�˙ţ�˙ţ�˙ţ�˙˙����˙˙�˙˙���˙ţ�˙ţ�˙˙���˙ţ�˙ţ�˙ţ�˙ü�˙ü�˙ý�˙˙���˙ţ�˙˙�˙˙�������˙˙���˙ý�˙ü�˙ý������������˙ţ�˙ü�˙˙��������������������������˙˙�˙˙�˙ý�˙ý�˙ţ�˙ţ�˙ţ�˙˙������˙˙�˙ţ�˙˙�˙ý�˙ü�˙˙�����˙˙�˙ţ�˙˙�˙˙�˙ţ�˙ý���˙ţ�˙ý�˙ý�˙ü�˙ű�˙ű�˙ţ���˙˙�˙˙�˙˙���˙ţ�˙ý�˙ţ���˙˙���˙˙�˙˙�˙˙�˙˙�˙˙�˙ţ�˙ţ���˙˙��������˙ý�˙ţ�˙˙��˙˙�˙ţ�˙ţ�˙ţ�˙ý�˙˙˙˙�˙ű�˙ţ�˙˙�˙˙���˙ý�˙ý�˙ţ���˙˙���˙ţ�˙ý�˙˙�����˙ý�˙ü�˙ü�˙ţ���˙˙�˙ţ�˙ţ���˙˙�˙ţ���˙˙�˙˙���˙˙�˙˙���˙ý�˙ý�˙˙���˙ţ�˙ü�˙ţ���˙ü�˙ý�˙˙�˙˙�������˙ţ�˙ţ�˙ü�˙ý�˙˙˙˙�˙˙�˙˙�˙˙���˙˙���˙˙�˙˙�˙ţ�˙ý���˙˙�����˙ţ�˙ü�˙ý������˙ţ�˙ţ�˙ý�˙ţ�˙ý�˙ý���˙˙�˙˙���˙˙�˙˙�˙ý�˙ţ���˙ţ���˙˙���˙ý�˙˙�˙ţ�˙ý���˙ţ�˙ü�˙ý�˙ý�˙˙���˙˙�˙˙�˙˙˙˙�˙ţ���˙˙�����˙ţ�˙ý�˙˙���˙˙�������˙˙���˙˙�˙˙���˙˙���˙˙���˙ý�˙ü����������������������˙ţ�˙˙˙˙�˙ü�˙ý�˙ţ�˙ţ�˙˙˙˙�˙ý�˙ü�˙ţ�˙ţ�˙ţ��˙˙�˙ţ�˙ţ�˙˙˙˙�˙˙����˙˙��˙ţ�˙ü�˙ţ�˙ţ�˙ţ��˙ţ�˙˙�����˙ţ�˙ý�˙ţ�˙˙�����������˙ý�˙ú�˙ű�˙ü�˙˙���˙ţ��������˙ţ�˙ţ�˙˙������������˙˙�˙ý�˙ü�˙ü�˙ţ�˙ţ�˙ţ���������˙˙�������˙˙�����˙˙�˙ţ���˙˙�˙ţ�˙˙�˙ţ�˙ţ�������˙˙�˙ý�˙ţ�˙˙�˙ý�˙ý�˙˙�˙˙�������˙ţ�˙ý�˙˙���˙˙�˙˙���˙˙���˙ţ�˙ý�˙˙�˙˙�����˙˙�˙ý�˙˙���˙˙�˙˙�˙˙�˙˙���˙˙�������˙˙���������˙ţ�˙ü�˙ţ�˙ţ�˙ţ���˙˙�˙˙���˙˙�˙ţ�˙ý�˙ý�˙ţ�˙˙�˙˙�˙ţ�˙˙�˙˙���˙ţ�˙ý�˙ý�˙ţ�˙ţ�˙ţ�˙ý�˙ţ���˙ý���˙ţ�˙ý�˙˙�������˙ţ�˙ţ�˙ţ�˙ţ�˙ü�˙ý�˙˙�����˙˙������������������˙˙���˙˙�˙ţ���˙ü�˙ü�˙ţ�˙˙���˙˙�˙˙���˙˙���˙˙���˙˙�˙˙�˙˙�˙ţ���˙ý�˙ţ���˙˙�˙˙�˙˙���˙˙���˙ţ�˙ý�˙ţ�˙˙�˙˙���˙˙���˙˙�˙ţ�˙˙���������˙ţ�˙ţ�˙ţ�˙ţ�˙˙�����������˙ţ�˙ţ�˙ţ�˙ţ�˙ţ���˙˙�˙ţ�����˙˙�˙ţ�����˙˙�˙ý�˙ţ��˙ý�˙ü�˙˙˙˙�˙ü�˙ţ�������˙˙�˙ţ�˙ţ��˙ţ�˙ű�˙ű�˙ý�˙˙�˙˙���˙˙�˙˙���˙˙���˙˙���˙ü�˙ţ���˙ţ�˙˙������������˙˙�˙ü�˙ü�˙˙���˙ý�˙ý�˙˙���˙˙�˙˙�˙˙�˙ý�˙ý�˙ý�˙ţ�˙˙����˙˙�˙˙���˙ţ�˙ţ�˙˙����˙˙�˙˙��˙˙�˙ţ���˙˙�˙ţ�˙˙�����˙˙�˙ţ���˙˙���˙˙�˙˙�˙ţ�˙ţ�˙˙�˙˙���˙˙�˙˙�˙˙�˙˙���˙ý�˙ý���˙˙�˙˙�˙˙�˙˙���˙˙�˙˙���˙˙�˙ý��˙ý�˙ú�˙ý��˙ţ�˙ţ�������˙˙�˙˙�˙˙�˙˙�˙ţ�˙˙���˙ţ�˙˙�˙ţ�˙ţ���˙ţ�˙˙���˙˙���˙ý�˙ý�˙ý�˙ý�˙˙�����������˙ţ�˙˙���˙˙���˙˙���˙˙�˙ý�˙ţ���˙ý�˙ý�˙˙�˙˙�˙ţ�˙ý���˙ţ�˙ý�˙˙������������˙˙�˙ţ���˙˙�˙ţ�˙ý�˙ý������˙˙�˙ý�˙ü�˙ý�˙˙�˙˙���˙˙�˙ţ�˙ý�˙ţ���˙ý�˙ý�˙ý�˙ţ�˙˙�����˙ţ���˙ţ�˙ţ�˙ţ�˙ý�˙ţ���˙˙���˙˙�˙˙�˙˙�˙˙�˙ü�˙ú�˙ü�˙˙���������������������������������˙ţ������˙ţ�˙ţ�˙ţ�˙ţ�˙˙˙˙�˙˙˙˙�˙ü�˙ţ�˙ţ�˙ţ�������������˙˙�˙ţ�˙ţ���˙ţ�˙˙�˙˙�˙ý���˙ţ�˙ű�˙ű�˙ü���˙˙�˙˙�˙ţ�˙ű�˙ý�˙˙�˙˙���˙˙�˙˙�˙˙�˙˙�˙ý�˙ý�˙ţ���˙ţ�˙ü�˙ţ���˙˙���˙˙�˙ţ���˙˙�˙ý���˙˙���˙ţ�˙ţ���˙ü�˙ü�˙˙�˙˙�˙ý�˙˙�����˙ţ�˙ý�˙ţ���˙˙�˙˙���˙˙�˙˙���˙˙�˙ý���˙˙�˙ţ���˙˙���˙˙�˙˙�˙˙�˙ý�˙ţ�����˙˙�˙ţ���������˙˙�˙˙���������˙ý�˙ý�˙˙˙˙�˙˙���˙˙���˙˙���˙ý�˙ű�˙ţ���˙˙���˙ý�˙ţ���˙˙�����˙˙�˙˙���˙˙���˙˙���˙ţ���˙ţ�˙ý�˙ý�˙ü�˙ű�˙ű�˙ý���˙˙�˙˙�˙˙�˙˙�˙˙�����˙ţ�˙ţ�˙˙���������˙ţ�˙ţ��˙ţ�˙ý�˙ţ�˙ţ��������������������˙ţ�˙ţ�˙ţ�˙ţ�˙˙���˙ý�˙ý�˙˙��˙˙�˙ü�˙˙˙˙�˙ţ��˙ý�˙ü�˙ţ�˙ţ�˙ţ�˙˙�������˙ý�˙˙���˙˙���˙˙���˙ţ�˙˙���˙ţ�˙ţ�˙ý�˙˙���˙ţ�˙˙�˙ţ�˙ţ�˙˙�˙ţ�˙ü�˙ý�˙ţ�˙ţ�˙ý�˙ţ�˙˙�˙ý�˙ý�˙˙�˙˙�˙˙���˙˙�˙ţ���˙ý�˙ý���������˙˙�˙ţ���������˙˙������˙ý�˙ý�˙ü�˙ű�˙ý�����˙˙�˙ţ��˙ţ�˙ţ�˙ţ�˙˙�˙ţ�˙ý���˙ţ�˙ý�˙ý���˙˙�˙˙�����˙˙�˙˙�˙˙�˙˙���˙ţ�˙˙�����˙ţ�˙ţ�˙˙�����˙ţ�˙ý�˙ţ�˙˙�˙˙�˙˙�˙˙�����˙˙�˙˙�˙˙�˙ţ��˙ţ�˙ţ���˙˙�˙ü�˙ţ���˙ý�˙ţ��˙˙���������˙ţ�˙ţ�˙ţ�˙ţ�˙ţ�˙ţ�˙ţ�˙˙���˙˙�˙ţ�˙˙�˙˙�˙ţ�˙˙���˙ü�˙ű�˙ţ�˙˙�˙˙���˙˙�����˙˙�˙ý�˙ţ���˙˙�˙˙���˙ţ�˙ţ���˙ţ�˙ű�˙ü���˙˙���˙˙�˙˙���˙˙�˙˙���˙ţ�˙ű�˙ü���˙˙�˙˙�˙˙���˙˙�˙˙˙˙���˙˙�˙ţ�˙˙�˙˙���˙ţ�˙˙���˙ţ�˙˙�˙˙�˙˙�˙˙���˙ţ���˙ţ�˙ý�˙ý�˙˙�����˙ţ�˙˙���˙ý�˙˙�˙˙�˙˙�˙˙�˙˙�˙˙�������˙˙�˙ţ�˙ţ�˙ý�˙ý�˙ý�˙ý���˙ţ�˙ű�˙ý�˙˙�˙˙�˙˙�˙ţ�˙ű�˙ü�˙ţ�˙˙���������˙˙�����˙˙�˙ţ�˙˙�˙˙�˙ý���˙˙�˙ü�˙ţ���˙˙���˙˙�˙ý�˙ý�˙ý�˙ţ���˙˙���˙˙�˙˙���˙˙�˙˙�����˙˙���˙˙�˙ý�˙ű�˙ü�˙ţ�������˙˙�������˙˙���˙˙�˙ý�˙ý�˙ý�˙ű�˙ţ���˙ý���˙˙�˙˙�˙˙�˙˙���˙ţ�˙˙˙˙�˙˙˙˙�˙ü���˙ý�˙ű�˙ý�˙˙�˙˙�����������˙ţ�˙ţ�˙˙�˙ţ�˙ý���˙ţ���˙ţ�˙ý�˙˙���˙˙�˙ü�˙ú�˙ű�˙˙�˙ţ�˙ű�˙ţ���˙˙�˙˙�˙˙�˙˙�˙ý���˙ţ�˙ý�˙˙�˙˙���˙˙���˙˙���˙˙�˙˙�˙˙�˙˙�˙˙�����˙ü�˙ű�˙ţ���˙˙�˙˙�˙˙�˙˙���˙ý�˙ý���˙˙�˙ý�˙ţ�˙˙�˙˙�˙ý�˙ý�˙˙�˙˙�˙˙�����˙˙�˙ţ�˙˙���˙ţ�˙˙�����������˙˙˙˙�˙ű�˙ú�˙˙˙˙�˙ý�˙ţ�˙˙���˙ţ�˙ý�˙ý���˙˙���˙˙�˙˙�����˙˙�˙˙�˙˙�˙˙�˙ý�˙˙���˙ü�˙ű�˙˙���˙˙˙˙�˙ý�˙ý�˙ţ�˙˙���˙ţ�˙ţ�˙ý�˙ý�˙˙�˙˙���˙˙�˙˙�˙˙���˙˙�˙ý�˙˙˙˙�˙ý�˙˙˙˙�˙ý�˙ü�˙ţ���˙ţ���˙˙�˙ţ�˙ţ�˙ţ�˙ţ�˙ţ�˙ţ���������˙˙���������˙˙�˙ý�˙ü�˙˙˙˙�˙ţ���˙˙���˙ţ�˙ý�˙ý�˙ţ�˙ý�˙ý�˙˙���˙˙�˙˙���˙˙�˙˙�˙˙�˙˙�˙˙���˙˙�˙ţ�˙ý�˙ý�˙˙�˙˙�˙ţ�˙ý�˙ţ���˙ţ�˙ý�˙˙���˙ţ�˙˙�˙ţ�˙˙�˙˙�˙˙�˙˙���˙ţ�˙˙������������˙˙�˙ţ�˙ţ���������������˙˙�˙ţ�˙ţ����������˙ţ�˙˙���˙ţ�˙ü�˙ű���˙ţ�˙˙���˙˙�˙˙���˙˙�˙˙�˙˙�˙ý�˙ý�˙ţ���˙˙�˙˙�˙ţ�˙ý��˙ý�˙ü��˙ý�˙ţ��˙ţ��������˙ţ��˙ţ�˙˙˙˙�˙˙�����������������������������˙˙�˙ţ���˙ţ�˙ü�˙ţ����˙˙�˙ý�˙˙˙˙�˙˙�����˙ý�˙ý�˙˙�����˙ţ�˙ţ�˙˙˙˙�˙˙�˙ţ�˙˙�˙˙�˙˙�˙˙�˙ý�˙ý�˙ü�˙ţ�������˙˙�˙ţ�˙ţ�˙ţ��˙ţ�˙˙˙˙�˙˙���˙ţ�˙ţ�˙ţ�˙˙���������˙ţ�˙˙�˙˙�������˙˙�˙˙���˙ý�˙ý�˙˙�˙˙�������˙˙�������˙˙�˙˙���˙˙�˙˙���˙ţ�˙ţ���˙˙�˙˙�˙˙�˙˙�˙˙�˙˙�˙ý�˙ý���˙˙�˙˙�˙˙�˙˙���˙˙�˙˙�������˙˙�˙˙�˙ţ�˙ý�˙ý�˙ţ�˙˙���˙˙�˙˙���˙˙�����˙ţ�˙˙���������˙ţ�˙ţ�˙ý�˙ţ�˙ţ�˙ý�˙ü�˙ý���˙ţ�˙ţ�˙ţ�˙ţ���˙˙�˙ţ�˙ţ�˙ţ�˙˙˙˙�˙ý�˙ţ�˙˙�˙˙���˙˙�˙ţ�˙ý�˙ý�˙˙���˙˙���˙˙�˙ţ���˙˙���˙˙�˙ţ���˙ý�˙ţ���˙ý�˙ý�˙ţ�˙˙�˙˙�˙ý�˙ű�˙ý��˙ţ�˙˙�˙ţ�˙ý�˙˙���˙ţ�˙ţ�˙ţ���˙˙�˙ý�˙ü�˙ţ���˙ý�˙ü�˙ý�����˙˙�˙˙���˙˙���˙ţ�˙ü�˙ü�˙˙����������������������˙˙�˙ţ�˙ü�˙ţ�����������˙˙�˙ţ�˙ţ���������˙˙����˙ţ�˙˙�������˙˙���˙˙���˙˙�˙ţ�����˙˙�˙˙�˙ţ�˙˙���˙ţ�˙ý�˙ý��˙˙�˙˙�����˙˙�˙˙�������˙ý�˙˙���˙˙�˙˙�˙ţ���˙˙�˙˙˙˙�˙ý�˙ü�˙ý�˙ţ�˙˙�˙˙�˙˙�˙˙�˙ţ�˙˙�˙ţ�˙ü�˙ý�˙˙�˙ţ�˙ý�˙ý�˙ý�˙ű�˙ý���˙ţ�˙ţ�˙ý�˙ü�˙ý�˙˙�����˙ţ������˙ţ�˙ţ�˙ý�˙˙���˙˙�˙˙�˙ţ�˙ü�˙ú�˙ű�˙˙˙˙�˙˙˙˙�˙ţ�˙ţ�˙ţ�˙ţ���������˙˙���������˙˙�˙ţ�˙˙����˙˙�˙˙����˙˙�˙ţ�˙ţ���˙˙�˙˙�˙˙�˙˙�����˙˙�˙˙���˙˙���˙˙�˙ü�˙ü�˙ü�˙ţ�˙ý�˙ú�˙ú�˙ű�˙ý��˙˙�˙ţ�˙ü�˙ű�˙ý�˙ý���˙˙�˙ý�˙ţ�˙ý�˙ý�˙ţ�˙ţ�˙ű�˙ü��˙ý�˙ü�˙˙˙˙�˙˙˙˙�˙ţ�������˙˙�˙˙���˙˙�˙˙�˙ţ�˙ű�˙ţ���˙ţ�˙˙�˙˙�˙ý�˙ţ���˙ü�˙ý�˙ý�˙ţ�˙˙����˙˙�˙˙˙˙�˙ű�˙ý��˙ý�˙ü�˙ý��˙ţ�˙ý�˙ţ���˙˙��˙ý�˙ţ���˙˙�˙ý�˙ü�˙ţ���˙ý�˙ü���˙˙�˙ţ���˙˙�������˙˙�˙˙�˙ű�˙ü���˙˙���˙˙�˙ţ�˙ţ�����������˙˙�˙ý�˙ü�˙ţ�����˙˙�˙ţ�˙ţ������˙ţ�˙ý�˙ý�˙ţ���˙˙�˙ý�˙ü�˙ţ�˙˙�˙ý�˙ü�˙ý�˙ý�˙ý�˙ý�˙˙˙˙�˙ü�˙ü�˙ţ�˙ţ�˙˙�����˙˙�˙˙�˙ţ��������������������˙˙�����˙˙�˙˙���˙˙�˙˙�˙˙�˙ţ�˙ý�˙ý�˙˙���˙ţ�˙ý�˙˙�����˙ý�˙ú�˙ú�˙ý�˙ţ��˙ţ�˙ţ��˙ţ�˙ţ�˙ţ�˙ţ�˙ţ�������˙˙���������˙˙�˙˙˙˙�˙ü�˙ý�˙ţ�˙ţ�˙ţ�˙ý�˙ţ�˙ţ������˙ý�˙ü�˙ý��˙ţ�˙ţ�˙˙�˙ţ�˙ý�˙˙���˙ţ���˙ü�˙ü���˙˙���˙˙���˙˙�˙ý�˙ü�˙ű�˙ü�˙˙˙˙�˙ţ�˙ţ�˙ţ�˙ţ�˙ţ�˙˙˙˙�˙ý�˙ý��˙ý�˙ű�˙ý�˙ü�˙ű�˙ü�˙˙���˙˙�˙˙���˙˙�˙˙�˙˙�˙˙�˙˙�˙˙���˙ü�˙ű�˙ţ���˙˙�˙˙�˙˙���˙˙�˙ý�˙ţ�˙ţ�˙˙�����˙˙���˙˙�˙ý�˙ű�˙ű�˙ü�˙ý�˙˙�˙˙�˙˙���˙˙���˙ţ�˙ý�˙ţ���˙ý�˙ţ�˙˙�˙ý�˙ü�˙ţ�˙˙���������˙ţ�˙ţ��������˙ý�˙ţ�����˙˙�˙˙���˙˙�˙˙���˙˙�˙˙���˙˙�˙˙���˙˙���˙˙�˙ý���˙˙�˙˙�˙˙�˙ý���˙˙�˙ý�˙ý�˙ţ���˙ü�˙ű�˙ý�˙˙���˙ţ�˙ţ�˙˙������������������������������������˙˙�˙ţ�˙ţ�˙ý�˙ü�˙ţ��˙ţ�˙ţ�˙˙�˙ţ�˙ý�˙˙�˙ţ�˙˙���˙ű�˙ý�˙˙˙˙����˙ţ�˙ţ�˙ţ�˙ţ�˙ţ�˙ţ�˙˙˙˙�˙ţ�˙˙�˙ţ�˙˙���˙˙���˙ţ�˙˙���������˙˙���˙ţ�˙ý�˙˙���˙˙���˙ţ�˙ý�˙ţ�˙ţ�˙ţ�˙ţ���˙˙�˙ţ�˙ţ���˙˙�˙˙���˙ţ�˙ú�˙ú�˙ţ�˙˙�˙ý�˙˙���˙˙�˙˙�˙ý�˙ü���˙ý�˙ý��˙ţ�˙ţ�˙ţ�˙˙˙˙�˙ţ�˙˙�����������˙ţ�˙ü�˙˙�����˙ţ�˙ţ�˙ţ�˙˙�����˙ý�˙ú�˙ű�˙˙˙˙�˙ý�˙ţ���˙ţ���˙˙�˙˙���˙˙�˙ţ�˙˙���˙ý��˙˙���˙ţ���˙˙�˙˙�˙˙�˙ü�˙ű�˙ű�˙ü�˙ţ���˙ţ�˙ü�˙ü�˙ţ�����������˙ţ�˙˙��������������������˙˙�˙ý�˙ü�˙ü�˙ţ�˙ţ�˙ţ�˙ţ�˙ý�˙ü�˙ü�˙ţ�˙ý�˙ü�˙ý������˙ţ�˙ţ�˙˙������������˙˙�˙ý�˙ý�˙˙�����˙ţ�˙ţ��˙˙���˙ţ�˙ţ�˙ţ�˙˙���˙˙�˙ü�˙ü��˙ü�˙ü���˙˙�˙ý�˙ţ�˙˙�����˙˙�˙˙���˙ţ�˙ű�˙ű���˙˙�˙ţ�˙˙���˙˙�˙˙���˙˙�˙˙�˙˙���˙ţ�˙ü�˙ţ�������˙˙�˙ý�˙˙˙˙�˙ţ���˙ý�˙ü�˙ý�˙ţ�˙˙�˙ţ�˙ű�˙ű�˙˙���˙˙˙˙�˙ţ���˙ý�˙ý�˙ţ�˙ţ�˙ţ�˙˙�˙ý�˙ý�˙ţ�˙˙���˙ţ�˙ý�˙ţ�˙ţ�˙ý�˙ý��˙ţ��˙ţ�˙˙˙˙�˙ü�˙˙������˙˙�˙ţ�˙ý�˙ţ���˙ü�˙ü�˙ţ�˙˙�˙ţ�˙ţ�˙ţ�˙ţ�˙ţ�˙ü�˙ú�˙ü�˙˙�����˙ü�˙ý���˙˙���˙ţ�˙ý�˙ý���˙ţ�˙ţ���˙˙��˙ţ���˙˙�˙˙˙˙�˙˙�˙˙�˙ţ���˙ţ�˙˙˙˙�˙˙�����˙˙���˙˙�˙ţ���˙ý�˙˙˙˙�˙˙���������˙˙������˙ţ������˙ţ�˙ý�˙ű�˙ý�˙˙�˙ţ�˙ý�˙ţ���˙ţ�˙ý�˙ý�˙ý�˙ý�˙ţ�˙˙���˙ý�˙˙˙˙�˙ý�˙ţ�˙˙���˙ţ�˙ý�˙ý�˙˙�����˙˙�˙ý�˙ű�˙ý�˙ű�˙ű�˙ý�˙ý�˙ý�˙ý�˙ý�˙˙���˙ţ�˙ţ�˙ý�˙ü�˙ý��������˙ţ�˙˙�������˙ţ�˙ü�˙ý�˙˙�˙ţ�˙˙�˙ţ�˙ü�˙ţ˙˙�˙ű�˙ý�˙˙�˙˙���˙ý�˙˙���˙˙�˙ţ�˙˙˙˙�˙ü�˙ü�˙ü�˙˙������˙˙�˙˙���˙ţ�˙˙���˙ü�˙ű�˙ţ���˙ţ�˙ý�˙˙�������˙ţ�˙ý�˙˙���˙ý�˙ý���˙ţ�˙ý�˙ý�˙ý�˙˙���˙˙�������˙ţ�˙ü�˙ü�˙˙���˙ý�˙ű�˙ţ�˙˙�˙˙�˙˙���˙ţ�˙ý�˙˙���˙˙�˙˙�˙˙�����˙˙�˙ý�˙ţ���˙˙�˙˙���˙˙�˙˙�˙˙�˙˙���˙ţ�˙ý���˙ţ���˙ţ�˙ű�˙ü�˙ţ�˙ţ�˙ţ�˙˙�����������˙ţ�˙ţ�˙ţ�˙ü�˙ý�˙ţ�˙ý�˙ű�˙ü�˙ţ�˙˙˙˙�˙ü�˙ý��˙ţ�˙ý�˙˙���˙˙�˙˙�˙˙�˙˙���˙˙�˙˙�˙˙�˙ţ���˙˙��˙ţ�˙ű�˙ý�˙˙˙˙��˙ţ�˙ý�˙ţ�˙ţ�˙˙�˙ý�˙ü�˙ý�˙ý�˙ü���˙˙�˙ţ���˙˙�˙ţ�˙˙�˙˙�˙ý�˙ü�˙ţ�˙˙�˙˙�˙ţ�˙ţ�˙ţ�˙ţ���˙˙���˙˙���˙˙�˙˙���˙ý�˙ü�˙ü�˙ţ���˙ý�˙ţ�˙ţ���˙ý�˙ü�˙ţ���˙˙�˙ý��˙ý�˙ű��˙ţ���˙ţ�˙ü�˙ý�˙ý�˙ţ���˙ţ�˙˙�˙˙�����˙ţ�˙ü�˙ý�˙˙˙˙���������˙ţ�˙ü�˙ţ�˙˙�˙ü�˙˙˙˙�˙ţ�������˙˙�˙ţ�˙ţ�����˙˙�˙ţ���˙˙��˙ý�˙ü�˙˙˙˙�˙ý�˙ü�˙ţ���˙˙�˙ý�˙ü�����˙˙�˙ý���˙˙���˙ü�˙ű�˙ţ���˙˙�������˙˙�˙ý�˙ţ�����˙ţ�˙ţ���˙˙���˙˙�˙˙�˙˙�˙˙���˙˙�������˙ţ�˙ţ�˙ţ�˙ü�˙ý��˙˙˙˙�˙ý�˙˙�����˙˙�˙˙�˙˙�˙˙�˙˙���˙˙���˙ý�˙˙�˙˙�˙ţ�˙ý�˙˙���˙˙���˙˙�˙˙���˙˙�˙˙���˙ţ�˙ü�˙ý�˙ţ�˙ţ���˙˙�˙ý�˙˙���˙˙�����˙ţ�˙ý�˙˙�˙˙���˙˙���˙ü�˙ű�˙ý�˙ţ���������˙ţ�˙ţ��������������˙ţ�˙ţ�˙ţ�˙ý�˙ü�˙˙˙ţ�˙ű�˙ţ��˙ţ�˙ţ��˙ý�˙˙˙˙�˙ü�˙˙�����˙˙���˙ţ�˙ţ�˙˙�����˙ý�˙ü�˙ý�˙˙���˙˙�˙˙���˙˙�˙˙�˙ţ�˙ý���˙˙�˙ţ�˙˙�˙˙����˙˙�˙ţ�˙ţ��������˙ţ�˙˙���˙ý�˙ţ�˙˙���˙˙�˙ý�˙ţ���˙˙�˙ţ�˙˙���˙˙�˙˙�˙˙�˙˙�˙ţ�����������������˙˙�˙ţ���˙˙���˙˙���˙˙�˙ý�˙ţ�����˙˙�˙ý�˙ţ�˙˙���˙˙�˙˙�����˙˙�˙ţ�˙ţ�˙ţ�˙ţ�˙ţ���˙˙�˙ţ���˙ţ���˙˙�˙˙˙˙�˙ţ�˙ţ�˙ý�˙ü�˙ű�˙ű�˙ý�˙˙�˙˙���˙ţ�˙ý�˙ý�˙ü�˙ý���˙˙�������˙˙�˙ţ�˙ţ�˙ţ���˙ţ�˙ţ�˙ţ�˙ý�˙ý�˙ţ�˙ý�˙ü�˙ţ�˙˙�˙˙�˙ý�˙ý�˙ţ���˙˙���˙ü�˙ý�˙˙�������������������˙ý�˙ü�˙ý���˙˙�˙ţ�˙ţ���������������˙˙�˙ţ���˙˙���˙˙���˙˙�˙ý�˙ü�˙ţ�����˙˙���������˙˙���˙˙�˙ţ���˙ţ�˙˙˙ţ�˙ű�˙ţ�˙˙���˙˙�˙ţ���˙˙���˙˙�˙˙�˙˙�����˙˙�˙˙�˙˙˙˙�˙ţ�˙˙˙˙�˙ü�˙ü�˙˙˙˙�˙ű�˙ü�˙ý������������˙ţ�˙ţ�˙ý���������˙˙�˙ţ���˙˙�˙ý�˙ţ�˙˙�˙˙�˙ý�˙ý�˙ý�˙ü�˙ý�˙ţ�˙ý�˙ţ�˙ţ�˙ţ�˙˙���˙˙�˙˙�˙˙���˙˙�˙˙�����˙ţ�˙ý�˙ţ�˙˙���˙ý�˙ý�˙˙�˙ü�˙ý���˙ţ�˙ý�˙ý�˙ý�˙ý���˙˙���˙ý�˙ý��˙ý�˙ü�˙˙˙˙�˙ţ���˙˙���˙˙�˙ý�˙˙�˙˙�˙˙���˙˙���˙˙���˙˙���˙ţ�˙ţ���˙˙�������˙˙�˙˙�˙ţ�˙˙���˙˙�˙˙���˙ţ�˙ý�˙˙���˙ţ�˙ű�˙ů�˙ý�˙˙����˙˙�˙˙��˙˙�˙ý�˙ü�˙ţ�˙ţ�˙ţ�˙˙�˙˙���˙˙���˙ţ�˙ý���˙ţ�˙ü�˙ý�˙ţ�˙ţ�˙˙�˙ţ���˙ţ�˙ü�˙˙˙˙�˙ţ��˙ţ�˙˙˙˙�˙ţ�˙˙˙˙�˙ţ�˙˙˙˙�˙ţ�˙˙���������˙ţ�˙ý�˙ý���˙˙�˙˙�����˙ţ�˙ý�˙ţ���˙˙�˙˙�˙˙�˙˙�˙ý�˙ţ���˙ý�˙ţ�˙˙�˙˙�˙˙�˙˙�˙˙�˙˙�˙˙���˙˙�˙˙�����˙ţ�˙ý�˙ţ�˙˙���˙ţ�˙˙�˙˙�˙ý�˙ţ���˙˙�˙ý���˙˙�˙˙���˙ý�˙ý�˙ţ���˙˙�˙˙�˙ţ�˙ý�˙˙���˙˙���������˙ţ�˙ţ�˙ţ�˙ţ�����˙˙�˙ý�˙ţ�˙˙�˙˙�˙ţ���������˙˙�����˙˙�˙ý�˙ü�˙ţ�˙ţ�˙˙���˙˙�˙˙���˙˙���˙˙�˙ţ�˙˙�˙˙���˙˙�˙˙�˙˙�˙˙���˙˙���˙˙���˙˙���˙ý�˙˙�˙˙�˙˙�˙˙�˙˙�˙˙�˙˙�˙ý�˙ţ�������������������˙˙�˙ţ�����˙˙�˙ý�˙ţ���˙˙�˙˙�˙˙�˙ý�˙ţ���˙ý�˙ü�˙˙�˙˙�˙ý�˙˙�˙˙�˙˙���˙˙�˙˙�˙˙���˙ý�˙ţ�˙˙�˙˙�˙ţ�˙˙���˙˙�˙ý�˙ţ���˙ţ���˙ţ�˙ü���˙ţ�˙ţ���˙ü�˙ü���˙ţ�˙ţ���˙ţ�˙ý�˙˙˙˙�˙ý�˙˙˙˙�˙ü�˙ý��˙ţ�˙˙���˙ţ�˙ţ���˙˙���˙˙���˙ü�˙ý���˙ţ�˙˙˙˙�˙ý�˙ţ�˙ţ�˙˙�˙˙���������˙ţ�˙ţ��˙ü�˙ý��˙ţ�˙˙���������˙ţ�˙˙˙˙�˙˙���˙ü�˙ý�˙˙�˙˙�˙ţ���˙ţ�˙ý��˙ü�˙ţ���˙˙���˙˙�˙˙�˙˙�˙˙�˙˙�˙˙�����˙˙�������˙˙�˙ţ�˙ţ��������������˙˙�˙˙���˙ý�˙ű�˙ű�˙ţ���˙˙���˙ý���˙ţ�˙ţ�����˙ţ�˙ţ���˙ý���˙˙�˙˙���˙ý�˙ý�˙ţ���˙˙�˙˙���˙˙�˙ţ�˙ý���˙ţ�˙ţ�˙˙�˙˙�˙˙���˙ţ�˙ý�˙ţ�˙ţ�˙˙������������������˙˙�˙ţ�˙ţ��˙ţ�˙ţ�˙˙�����������˙ţ�˙˙˙˙�˙ţ�˙˙˙˙�˙ý�˙ü�˙˙˙˙�˙ý�˙ţ���˙˙���˙˙�˙˙���˙˙�˙ţ�˙˙�˙˙���������˙˙���������˙˙���˙ţ�˙˙�˙ţ�˙ü�˙ý�˙˙�������˙ţ�˙ü�˙ú�˙ű�˙ţ���˙˙�˙˙���˙˙�˙˙�˙ţ�˙ý���˙ţ�˙ý�˙ý�˙ý�˙ţ�˙˙���˙˙�˙˙���˙ţ�˙ý�˙ý�˙ý�˙˙���˙˙�˙ţ�˙˙���˙˙�˙˙�˙ý�˙ţ�˙˙�˙˙�˙ý�˙ý�˙ý�˙ţ���˙˙���˙˙�˙˙�˙ý�˙ü�˙ű�˙ü���˙˙�����������˙˙�˙˙���������˙˙˙˙�˙˙�˙˙�����˙˙�˙˙�˙˙�˙˙���˙˙�˙˙�˙˙�˙˙�˙˙�˙ţ���˙˙�˙ţ�˙ý�˙ţ�˙˙�˙˙���˙˙�˙˙���˙˙�˙ţ�˙ü�˙ű�˙ţ�˙˙��˙˙�˙ţ�˙ţ�����˙˙�˙˙�����˙˙���˙˙�˙ý�˙ţ���˙˙���˙˙�˙ţ���˙˙�˙ţ���������˙˙�˙ţ��˙ý�˙ü���˙˙�˙ţ�˙˙�������˙˙�˙ţ�˙ţ�˙ý�˙ü�˙ü�˙ţ��˙ý�˙ü�˙ţ�˙ý�˙ţ���˙ű�˙ţ���˙ý�˙ý�˙ţ�˙˙���˙˙�˙ý�˙ţ�˙˙�˙˙���˙˙�˙˙���˙˙���˙ţ��˙ý�˙ú�˙ý�˙˙˙˙�˙ţ�˙ý�˙ţ��˙˙����˙˙�˙ţ�˙ţ�˙ţ�˙ţ�˙ţ�˙˙�˙ţ�˙˙���˙˙���˙˙�˙˙���˙˙�˙ý�˙ý�˙ý�˙ý�˙ý�˙ü�˙ý���˙ţ�˙ý���˙ţ�˙ý�˙˙�˙ţ�˙ü�˙ý�˙˙������������˙˙�˙ţ�˙ţ������˙ţ�˙˙���˙ţ�˙˙�˙˙���˙ţ���˙˙�˙ü�˙ý�˙ű�˙ü�˙ý�˙˙�˙˙�����˙ţ�˙˙˙˙�˙ü�˙ţ˙˙�˙ü�˙ý������˙ţ�˙˙��˙ţ���˙˙�˙ţ������������������˙ţ�˙˙���˙˙˙˙�˙ý�˙˙�����˙ţ�˙ý��˙˙�˙ţ�˙ţ�˙ý����������˙ţ�˙ţ������˙ţ�˙ý�˙˙����˙˙�˙ţ�˙ţ���˙˙���˙˙�˙ý�˙ý�˙ü�˙ţ�����������˙˙�˙ţ��˙ý�˙ü�˙ţ�˙ţ�˙ý�˙ü�˙ü���������˙˙�������˙˙�˙˙�˙ý���˙ţ�˙ű�˙ű�˙ü�˙ţ�˙˙�˙˙�˙˙�˙˙�˙˙���˙˙�˙ţ���˙ű�˙ű�˙ţ��˙ţ�˙ţ��˙ţ�˙ý�˙ü�˙ü�˙˙�������˙ţ�˙ţ�˙˙�����������˙ţ�˙˙���˙ţ�˙ü�˙ü���˙ţ�˙ý�˙ţ�˙˙���˙˙���˙˙�˙˙�˙˙�˙˙���˙˙�˙˙�˙˙�˙ţ�˙ţ������˙ţ�˙ţ�˙˙������˙˙�˙ü�˙ý��˙ţ���˙ţ�˙˙�˙˙�˙ţ�˙ţ�˙ţ�˙ű�˙ű�˙ţ�˙˙�˙˙�˙˙���˙˙�˙˙�˙˙�˙˙�˙˙�˙˙�˙˙�˙ţ�˙ü�˙ý�˙ţ�˙ţ�˙˙���������˙˙�����������˙ţ�˙ţ�˙ţ�˙ű�˙ú�˙ü�˙ţ�˙ý�˙ű�˙ű�˙ý�˙ţ�˙˙�˙˙˙˙�˙ü�˙ý��˙˙˙˙�˙ţ�˙ţ�˙˙�˙ţ���˙˙���˙ţ�˙ý�˙ţ���˙˙���˙ţ�˙ý�˙ţ���˙˙���˙ţ�˙ţ���˙ţ�˙ţ�˙˙�˙˙�˙ţ�˙ý�˙ý�˙˙�˙˙���˙˙�˙˙���˙˙�˙ý�˙ý�˙˙�˙˙�����������˙ţ�˙ţ�˙ţ�˙˙���������˙ţ�˙ţ�˙ü�˙ü�˙˙˙˙�˙ţ�˙ţ�˙ţ�˙˙���˙˙�����˙˙�˙˙���˙˙���˙˙������������˙˙�˙ţ���˙˙�˙ţ�˙ţ�˙ţ����˙ţ�˙ű�˙ý��˙ţ�˙ţ��˙ţ�˙ţ�˙ţ�˙ţ�˙˙��˙˙������˙ü�˙ü�˙ý����˙ţ�˙ý�˙ţ���˙˙�˙˙�˙˙�˙ţ�˙ţ���˙ü�˙ü���˙˙�˙ţ���˙˙���˙˙�˙˙�˙˙�˙˙���˙˙�˙ţ�˙ý�˙˙�˙˙�˙ţ�˙ý�˙˙���˙˙�����˙ţ�˙ü�˙ý�˙˙���˙˙�˙ţ�˙˙���˙ţ�˙ţ�˙˙�˙ţ�˙ţ����˙ţ�˙ý�˙ţ�˙ü�˙ţ���˙ţ�˙ţ�˙˙�˙ţ���������˙˙�˙ţ���˙˙�˙ý�˙ţ���˙˙���������˙˙�˙ţ�˙ţ�˙ý�˙ü�˙ţ�˙ý�˙ý���˙˙��˙ţ���˙˙���˙˙�˙ý�˙ţ�˙˙�˙˙�˙ý�˙ý���˙˙�˙˙���˙˙�˙ü�˙ý�˙˙�������������������������˙˙�˙ţ�˙˙�˙ý�˙ţ�˙˙���������������˙ţ�˙ţ�˙˙����˙˙�˙ü�˙ý����������˙ţ��˙ţ�˙˙˙˙�˙˙������˙˙�˙˙���������˙˙˙˙�˙ü�˙ü������˙˙�������˙ţ�˙ţ�˙˙���������˙ţ�˙ţ�������˙˙�˙ţ��˙ý�˙˙������˙˙��˙ţ�˙ţ���˙˙�˙ü�˙ü�˙˙�����˙ţ�˙˙˙˙�˙ý���˙˙�˙ţ�˙˙���˙˙���˙˙�˙ý�������˙˙���˙˙�˙ý�˙ý�˙ý���˙˙�˙ý�˙ý�˙ţ���˙˙�˙˙�˙ţ���˙˙��˙ý�˙ý��˙ý�˙˙���˙ý�˙ü�˙ý�˙ţ�˙ţ�˙ţ�˙ý�˙ü�˙ţ�˙˙�˙ý�˙ü�˙ţ�����˙˙�˙˙�˙˙�˙˙���˙˙���˙˙�˙˙��˙ý�˙ţ��˙ţ���˙˙�˙ţ�˙˙�˙˙�˙˙�˙˙���˙ý�˙ü���˙˙�˙ý�˙ý�˙ţ���˙˙�˙˙���˙ţ�˙ý��˙ý�˙ű�˙ý�˙˙�˙˙�˙˙���˙˙�˙˙�����˙ţ�˙ü�˙ü�˙˙˙˙�˙ý�˙ü�˙ü�˙ü�˙˙˙˙�˙ü�˙ţ���˙ý�˙ý�˙ý�˙ý�˙ţ�˙˙�˙˙�˙ţ���˙˙���˙˙�˙˙�˙˙�˙˙�˙˙���˙ţ�˙ý�˙˙���˙˙�˙˙���˙ţ�˙ý�˙˙�����˙˙�˙˙���˙˙�˙˙����������������˙˙�˙ţ�˙˙˙˙�˙ţ�˙ţ���˙˙�˙ý�˙ű�˙ü���˙˙���˙˙�˙˙�˙ţ���˙˙�˙˙˙˙�˙ţ��˙ţ�˙˙�˙ţ�˙ü�˙˙���˙˙�˙˙�˙˙�˙ţ�˙ý�˙ţ�˙˙˙˙�˙˙�˙ý�˙ý�˙ţ�˙˙�˙˙�˙˙�˙ý�˙ý���˙˙�˙˙���˙ý�˙ý�˙ý�˙˙���˙ţ�˙ţ�˙˙���������˙ţ�˙ţ�˙˙���˙˙���˙ţ�˙ţ���˙ý�˙ţ�����˙˙�˙ý�˙ý���˙˙�˙ü�˙ţ���˙˙�˙˙�������˙˙����������˙˙���˙˙�˙˙�˙˙�˙˙���˙ý�˙ý�˙˙�˙˙�˙˙���˙ţ�˙ý�˙˙�˙ţ�˙ü�˙ţ���˙ţ�˙˙�˙ţ�˙ţ�˙ü�˙ü�˙˙�����������˙ţ�˙ţ�������˙˙�˙˙���˙˙���˙ţ�˙ü���˙ţ�˙ţ���˙ţ�˙ý�˙ý���˙ţ�˙ý���˙ţ�˙ţ���˙˙���˙˙�����˙˙������˙ý�˙ü�˙˙˙˙�˙ţ���˙˙���˙˙�˙ý�˙ţ���˙˙�˙ţ�˙ţ�˙˙���˙ţ�˙ţ�˙ţ�˙ţ���˙˙���˙˙�˙ý�˙ý�˙ţ���˙˙�˙˙�˙˙���˙˙�˙˙�����˙˙�˙˙�˙ţ�˙˙���˙˙�˙˙�˙˙�˙ţ�˙ý�˙˙���˙ţ�˙ü�˙ű�˙ü�˙ţ�����������˙˙�˙ţ�˙˙˙˙�˙ţ�˙ţ�˙ý�˙ü�˙ţ�����˙˙���������˙˙�����������˙˙�˙ţ�˙˙˙˙�˙ţ�˙˙˙˙�˙˙���˙ţ�˙˙�������˙ţ�˙ţ�˙ţ�˙ţ�˙ţ�˙ţ�˙ţ�������������˙˙�˙ţ�˙ţ�����˙˙�˙ý�˙ü���˙ţ�˙ü��˙ü�˙ű�˙ý�˙ý�˙ţ�˙ţ�˙ý�˙ţ�˙˙�˙˙���˙˙�˙˙�����˙ţ�˙ü�˙ý���˙ţ�˙ü�˙ü�˙˙���˙˙�������˙ü�˙ü�˙ý�˙ţ�˙˙���˙˙�˙˙�˙˙�˙˙�˙˙�˙˙�˙˙�˙˙�˙ý�˙ű�˙ü�˙ţ�˙˙���˙˙�˙˙�˙˙�˙˙���˙˙�����˙˙���˙˙�˙ý�˙ţ�˙˙�˙˙���˙˙�˙ý��˙ý�˙ű�˙ü�˙ţ�˙˙�˙ţ�˙ţ���������˙˙�˙˙��˙˙�˙ţ�˙˙˙˙�˙ţ���˙˙���˙˙�˙ý�˙ţ���˙˙�˙ţ�˙ý�˙ţ��˙ý�˙ý�˙ţ���˙˙���˙˙���˙ţ�˙ű�˙ý�˙˙�˙ţ�˙ţ�˙˙����˙˙�˙ţ�˙ü�˙ţ�����˙˙�˙ý�˙ţ���˙ţ���˙ţ�˙˙˙˙�˙ü�˙ú�˙ü��˙ý�˙ü�˙ţ���˙˙�˙ţ��˙ü�˙ů�˙ű�˙ţ�˙˙�˙ţ���˙˙���˙˙�˙ý�˙ý�˙˙˙ţ�˙ű�˙ţ���˙˙�����˙ţ�˙ţ�˙˙���˙ţ�˙ţ�˙ý�˙˙���˙˙���˙˙�˙ţ�˙ý�˙ý�˙˙�˙˙���˙ţ�˙ţ�˙ţ�˙ţ��˙ý�˙ü�˙ü�˙ţ�˙˙����˙˙�˙ü�˙ý�˙ţ���˙˙�˙ű�˙ţ�����˙˙�˙ý�˙ý��˙ţ�˙˙�����˙ţ�˙ü�˙ű�˙ý��˙˙�˙˙�����˙˙���˙ţ�˙ţ�����˙˙���˙ţ�˙ý���˙˙�˙˙�˙˙�˙˙�˙˙���˙˙���˙ţ�˙˙���˙˙���˙˙���˙ü�˙ů�˙ü�˙˙�˙˙����������˙˙�˙ţ�˙ý�˙ţ���˙˙�����˙˙�˙ý�˙˙˙˙�˙ţ���˙˙�������˙˙����˙ý�˙ü�˙ý��˙˙���˙ţ�˙ý�˙ý�˙ý���˙˙�˙˙�˙˙�˙˙�˙˙�˙˙�˙˙���˙ţ�˙ţ�˙ţ�˙ý�˙ü�˙˙���˙˙�����˙˙���˙ţ�˙˙�˙˙�˙ü�˙ű�˙ţ���˙ţ�˙˙���˙˙���˙˙���˙˙�˙˙�˙ţ�˙ý�˙˙�����������˙ţ�˙ţ�˙˙�����˙˙�˙ţ��˙ţ�˙ý�˙˙�����˙˙�˙˙�˙˙�˙˙�˙˙���˙˙�˙˙���˙ý�˙ű�˙ü�˙ţ�����˙˙�˙ţ�˙ţ����˙ý�˙ý�˙˙�������˙ţ�˙ü�˙ţ�˙ţ�˙ý�˙ý�˙ţ���˙˙�˙˙���˙ţ�˙ű�˙ý��˙ţ�˙ţ�˙ţ�˙ţ�˙ţ�������˙˙�˙˙���˙˙�˙ţ���˙˙�˙˙��˙˙�˙ý�˙ý���˙ţ���˙˙�˙˙�˙ý�˙ţ���˙˙���˙˙�˙˙�˙˙�˙ţ�˙˙�����˙˙�˙ý�˙ü�˙ţ�����˙˙�˙ý�˙ţ���˙˙�������˙˙�˙˙���˙ţ�˙˙�������˙ţ�˙ţ��˙ý�˙ü�˙ü�˙ţ�˙ţ������˙ý�˙ű�˙ý���˙ý�˙ý�˙ý�˙˙���˙˙�˙˙�����˙˙�˙ý�˙ü�˙ţ���˙˙�˙˙�˙ý�˙ý���˙˙�˙˙���˙˙�˙˙�˙˙�˙˙���˙˙�˙˙�˙˙�˙ý�˙ţ�������˙˙�˙ţ���˙˙�˙ý�˙ý�˙ţ�˙˙�˙˙���˙ü�˙ű�˙ţ���˙ü�˙ü�˙˙�˙˙�˙ţ�˙ý�˙˙˙˙�˙ý�˙ţ�˙ţ�˙˙���˙ţ�����������˙˙���˙˙�˙ý�˙ţ�˙ţ���˙ý�˙ţ�˙˙�˙ü�˙ü���˙˙�����˙˙�˙ţ�˙˙˙˙�˙ţ�˙˙˙˙�˙ţ��˙ţ�˙˙˙˙�˙˙���˙ý�˙ţ�˙ü�˙ý�˙ü�˙ü�˙ţ�˙ţ�˙ü�˙ü�˙ű�˙ü���˙˙�˙˙�˙ý�˙ű�˙ţ���˙˙�����˙˙�˙˙�˙ţ�˙˙�˙˙�˙˙�˙ý�˙ţ���˙˙�˙ţ�˙˙�����˙ý�˙ú�˙ű��˙ţ�˙ý�˙˙�����˙˙�˙ţ�˙ţ�˙ý�˙ţ�˙ţ�˙ţ�˙˙��˙˙�˙ţ�˙˙˙˙�˙ţ���˙˙�˙ý�˙ý�˙ţ���˙˙���������˙˙�˙ţ�˙˙�˙˙�˙ý�˙ý�˙ţ�˙ţ�˙˙�˙˙�˙ý�˙˙���˙ţ�˙˙�˙˙�����˙˙���˙ţ�˙˙�����˙ý�˙ý��˙ţ�˙˙˙˙�˙ţ�˙ţ�˙ý�˙ü�˙˙˙˙�˙ü�˙ý�˙˙��������������˙˙���˙˙�˙ý�˙ý�˙ý���˙ţ�˙ý�˙˙���˙ý�˙ý�˙ţ���˙˙���˙˙�˙ý���˙˙�˙ý�˙˙���˙˙�˙ţ�˙ý�˙ý�˙ý�˙ţ���˙ţ���˙ý�˙ţ�˙˙�˙ý�˙ţ�˙ţ�˙ţ�˙˙���˙ţ�˙ü�˙ű�˙ý�˙˙�˙ţ�˙ý�˙˙���˙˙���˙˙���˙ţ�˙ý�˙ţ�˙ţ�˙ţ���˙˙�˙ţ���˙˙�˙ţ�˙ţ��˙ţ�˙ü�˙˙���˙˙���˙ţ�˙ý�˙ý���˙˙�˙˙�˙˙�����˙˙�˙˙�˙ţ�˙˙���˙˙�˙˙�˙˙�˙˙���˙˙�˙˙���˙˙���˙ţ�˙ý���˙ţ�˙ţ���˙˙��˙ý�˙ý��˙ý�˙ţ�˙ţ�����������˙˙�˙ü�˙ý��˙ţ�˙ý�˙ţ�˙˙�˙˙�������˙ţ�˙ý����˙ţ�˙ü�˙˙��������������������˙˙�˙ý�˙ü�˙ű�˙ý�˙ţ�˙˙�˙ţ�˙ţ�˙ý�˙ý�˙˙˙˙�˙˙�������˙˙�˙ţ��˙ţ�˙˙˙ţ�˙ű�˙ű���˙ţ�˙ü�˙ý�˙ý�˙ţ���˙ţ�˙ý�˙ý���˙˙�˙˙�˙˙�˙ý�˙ţ���˙˙�˙˙�˙ţ�˙˙�����˙˙�˙˙�˙ţ�˙ý�˙ţ�˙ţ�˙˙���˙˙�˙ý���˙˙�˙ţ����������˙ý�˙ü�˙ý�����������˙˙�˙ü�˙ý��˙ţ�˙ý�˙ţ��˙˙�˙ţ����˙˙�˙ţ�˙˙���˙˙�˙ţ�˙ü�˙ý���˙ý�˙ţ���˙˙�˙˙�����˙˙�˙ý�˙ţ���˙ý�˙ý�˙ţ�˙˙�˙ţ�˙ý�˙ü�˙ü�˙˙������˙˙�˙ü�˙ţ��˙ý�˙ü�˙ţ�����˙˙�˙˙�˙˙���˙˙�˙˙���˙˙���˙ţ�˙ű�˙ü�˙ý�˙ý�˙ý���˙ţ�˙ú�˙ü���˙ţ�˙ţ���˙˙�˙ţ�˙ţ�˙˙���˙ý�˙ý�˙˙˙˙�˙ţ�˙˙˙˙�˙ţ�˙ţ�������˙˙�˙˙�˙ţ�˙˙���������˙˙�˙ý�˙˙���˙ţ��˙ţ�˙ü�˙ý�˙˙���˙ţ�˙ü�˙ý�˙ý�˙ţ�˙ţ�˙˙���˙˙�˙ţ��˙ý�˙˙˙˙�˙ü�˙ţ�˙ţ��˙ý�˙ü�˙ţ��˙ţ���˙˙�˙ü�˙ţ�˙ţ�˙ý�˙ü�˙˙˙˙�˙ţ���˙˙�����˙ţ�˙ý�˙ţ�˙˙���˙˙�˙˙���˙˙�˙ý�˙ţ���˙ţ�˙ţ������������˙˙���˙ţ�˙˙˙˙�˙ú�˙ü�˙˙�����������������˙ý�˙ü�˙ţ�˙ţ�˙ţ�˙ţ�˙ţ�˙ţ�˙ţ�˙ţ�˙ţ�˙ţ�˙˙�����˙ţ�˙˙������˙˙�˙ü�˙ţ�����������˙˙�˙ţ�˙ţ���˙˙�˙ý�˙ţ�����˙˙������˙ý�˙ý�˙ü�˙ý�˙ţ�˙˙�˙˙�˙˙���˙˙���˙ţ�˙˙���˙ţ�˙ý�˙˙˙˙�˙ý�˙˙˙˙�˙ü�˙˙�����˙ý�˙ű���˙ţ�˙ű�˙ý�˙˙�˙˙���˙ţ�˙˙˙˙�˙ü�˙ý�˙˙���˙ţ�˙ţ�˙˙���˙˙�˙˙�����˙ţ�˙ý�˙˙���˙ţ�˙ý�˙˙���˙ţ�˙ý�˙˙�˙ţ�˙ý�˙ţ���˙ţ�˙ý�˙ţ���˙˙�˙ţ�˙˙�����˙˙�˙ţ�˙ţ���˙ţ�˙˙˙˙�˙˙����˙˙���������˙˙�˙˙���˙˙�˙˙�˙ţ�˙ý�˙˙˙˙�˙ý�˙˙���˙˙�����˙˙�˙˙���˙˙���˙˙�˙˙�˙˙���˙˙�˙˙�����˙˙�˙˙�˙˙�˙˙�˙˙�˙ţ�˙ţ���˙ţ�˙ţ�˙ţ���������˙˙�˙˙���˙ý�˙ü�˙˙������˙˙�˙ţ�˙ý�˙ü�˙˙˙ţ�˙ţ�˙ţ���˙˙�˙ý�˙ý�˙ţ�������˙˙�˙ý�˙ű�˙ţ�˙˙�˙ü�˙ü�˙˙���˙˙�˙˙�����˙˙�˙˙���˙ý�˙ý���˙˙�˙ý�˙˙���˙˙���˙ü�˙ü���˙ţ�˙˙�˙ţ�˙ü�˙ý�˙˙���˙ţ�˙ý�˙ý�˙ţ���˙˙�˙ţ���˙˙���˙˙�˙ý�˙ý�˙ţ�˙ý�˙ű�˙ü�˙ţ���˙˙���˙˙�˙ţ���˙˙�˙ţ���˙˙�˙ţ���˙˙���������˙˙�˙ţ�˙ţ���˙˙�˙ý�˙ţ���˙ý�˙ţ�˙˙�˙ý�˙ţ���˙ü�˙ţ��������������������˙ý�˙ű�˙ý�˙˙�˙˙���˙˙�˙˙���˙ţ�˙˙�˙˙�����˙˙���˙ý�˙ý�˙ţ�˙ţ���˙ţ�˙˙˙˙���������������˙˙�˙ţ�˙ţ�˙ţ�˙˙�˙ü�˙ü��˙ý�˙ý�����˙˙�˙ü�˙ý��˙ý�˙ű�˙ý����˙˙���˙ý�˙ý�˙˙˙˙�������˙˙�˙˙���˙ţ�˙ü�˙ý�˙ţ�˙ţ�������˙˙�˙ý�˙ţ�����˙˙�˙ţ�˙ţ�˙ý�˙ú�˙ű�˙˙˙˙�˙˙������˙˙�˙ţ���˙˙�˙ý�˙ý�˙ü�˙ţ�����˙˙�˙ţ�˙˙�˙˙�˙ý�˙ý�˙ţ�˙˙�˙˙�˙˙�˙ţ�˙ý�˙ţ���˙ţ��˙ý�˙ü���˙˙�˙˙�˙ţ�˙ý�˙ý�������˙˙����˙ţ�˙ţ�˙ţ�˙ţ�˙ţ��˙ý�˙ý��˙ţ��˙ţ�˙ü�˙ý�˙˙���˙ţ�˙ü�˙ý���˙˙�˙ý�˙ý���˙˙�˙˙�����˙˙���������˙˙�������˙˙�˙˙�˙ţ���˙˙�˙ý�˙ü�˙ţ�����˙˙���˙˙�˙ý���˙˙�˙˙�����˙˙���˙ţ�˙ý���˙˙�˙ý�˙ţ�˙˙�˙˙���˙˙�˙˙���˙ţ�˙˙���˙˙�˙˙�˙˙�˙˙�˙˙�˙˙�˙ţ�˙ý�˙ţ�˙ý�˙ţ����˙ý�˙ü�˙ý�˙ţ�˙ţ�˙ţ�˙ţ�˙ţ�˙ţ�˙˙˙˙�˙ü�˙˙������������˙˙�˙ţ�˙ţ����˙ţ�˙ţ�˙ţ�˙ţ�˙ţ�˙˙���˙˙�˙ý�˙ý�˙ý�˙ţ���˙˙���˙ý�˙˙���˙ţ�˙ţ�˙ţ���˙ţ�˙ü�˙ţ��˙˙˙˙�˙ţ�˙˙��˙˙�˙ű�˙ú�˙ý�����˙˙�˙ţ��������˙ý�˙˙������������˙˙���˙˙�˙ţ�˙˙�����˙˙�˙˙���˙ţ�˙ý�˙ţ���˙˙���˙˙�˙˙�˙˙�˙˙�˙˙�˙˙�����˙ţ�˙ü�˙ý�˙˙����˙˙�˙˙�����������˙ţ�˙ţ�˙ţ�˙˙���������˙˙�˙ţ�˙˙���˙˙���˙ţ�˙˙���˙ţ�˙˙���˙˙���˙ţ�˙ţ�˙˙�����˙˙�˙ţ�˙ý�˙ţ�˙˙�˙ţ�˙ţ�˙˙�˙ţ�������˙˙�˙ý�˙ů�˙ú�˙ü�˙ţ���˙˙�������˙˙�˙˙�˙ţ�˙˙���˙ţ�˙ţ�˙˙�˙ü�˙˙˙˙�˙ü�˙ţ�˙ü�˙ü�˙˙���˙ţ�˙ý���˙˙���˙ţ�˙ý�˙ý�˙˙���˙˙���˙˙�˙˙�����˙˙����˙˙�˙˙˙˙���������˙˙�˙˙˙˙�˙ý�˙ý���˙˙�˙ţ���˙˙�˙ţ���˙˙�˙ţ�˙ý�˙ţ���˙˙�˙ý�˙ý�˙ţ�˙˙�˙˙�˙˙���˙˙���˙ţ�˙˙���˙ţ�˙ţ�˙˙�����˙˙���˙˙���˙˙���˙ţ��˙ţ�˙ý�˙˙���˙˙�˙˙�˙˙���˙ţ�˙ü�˙ű�˙ű�˙ű�˙ú�˙ü�˙ţ�˙ý�˙ü�˙ţ�����������˙˙�˙ý�˙ü�˙ű�˙ü���˙˙���˙˙�����˙˙�˙ý���˙˙�˙ý�˙ý�˙ţ�˙˙�˙˙�˙ý�˙ţ���˙˙���˙˙���˙˙���˙˙�˙˙���˙ý�˙ý���˙˙���˙˙���˙˙���˙˙�˙˙�˙˙�˙˙�˙˙�˙˙�˙˙�˙˙�˙˙���˙ţ�˙ü�˙ý��˙˙���˙˙���˙˙���˙ţ���˙˙���˙ý�˙ű�˙ű�˙ü�˙ý�˙ţ���˙ţ�˙ű�˙ú�˙ţ�˙˙�˙ţ���˙˙���˙˙�˙˙���˙˙�˙˙���˙˙�����˙˙�˙ý�˙ü���˙˙�˙ý�˙ý�˙ý�˙ţ�˙˙�˙˙�˙ý���˙˙�˙˙���˙˙�˙˙�˙˙�˙˙�˙˙�˙˙�˙˙�˙˙���˙˙���˙ţ�˙ý�˙ý�˙˙���˙˙�����˙ţ�˙ü�˙ý�˙˙˙˙�˙ţ�˙˙˙˙�˙ţ���˙˙���˙˙�˙˙�˙˙�˙˙�˙˙�˙˙�˙ý�˙ţ������������������˙ţ�˙ţ�������˙˙�˙ý�˙ü�˙ţ�������˙˙�˙ý�˙ü�˙˙������˙˙�˙ý�˙ţ�˙˙�����˙ţ�˙ü�˙ý�˙ţ�˙˙���������˙˙�������˙˙�˙ţ�˙˙�˙˙�����˙˙�˙ţ�˙˙���˙˙���˙ţ�˙˙�����˙ţ���˙˙�˙˙˙˙�˙ý�˙ü�˙ü�˙ü�˙ü�˙˙˙˙�˙ý���˙˙�˙˙���������˙ţ�˙ţ��˙ţ��˙ý�˙ű�˙ţ�˙˙���˙ţ�˙˙�����˙ţ�˙ý��˙ţ�˙ý�˙˙���˙ţ�˙˙˙˙�˙ý�˙ü�˙ü�˙ţ���������˙˙�˙ý�˙ý�˙ţ�˙˙���˙ţ�˙˙�����������������˙ţ�˙ţ�˙˙��������������˙˙�˙ý�˙ü�˙ţ�����������˙˙�˙˙���˙ý�˙ü�˙ű�˙ý�˙˙�˙˙�˙ţ�˙ý�˙˙˙˙�˙ţ�˙ţ�˙ţ��˙ţ�˙ţ��˙ţ�˙ţ��˙ţ�˙ţ�˙˙���˙ţ�˙ü�˙ţ�˙ţ�˙ü�˙ü�˙˙�������˙˙�˙ţ�˙ţ��˙ţ���˙˙�˙ţ��˙ý�˙ü�˙ý���˙˙�˙ü�˙ţ���˙˙�˙˙���˙˙�˙˙�˙˙�˙˙���˙ţ�˙ý�˙ţ���˙˙�˙˙���˙˙���˙ţ�˙ý�˙˙�˙˙�˙ţ�˙˙�˙ţ�˙ţ�˙˙˙˙�˙˙���˙ţ���˙ţ�˙ü�˙ű�˙ú�˙ű�˙ý�˙ţ�˙˙�˙ţ�˙ý�˙˙���˙˙�˙ţ�˙˙�˙ţ�˙ü�˙ý���������˙˙�˙ý�˙ý�˙ţ�˙ţ�˙˙���˙ţ�˙ţ�˙ţ���˙˙���˙ţ�˙ý�˙˙�˙ţ�˙ý�˙˙�˙˙�˙˙���˙˙�˙˙�˙˙�˙˙�˙ţ�˙˙�����˙˙�˙˙�˙˙���˙˙�˙˙���˙˙�˙˙�˙˙�˙ý�˙ý�˙ţ���˙˙���˙˙�˙ţ�������˙˙�˙˙�˙ţ�˙˙�˙˙�˙ý�˙ű�˙ů�˙ú�˙ţ���˙˙�˙˙���˙˙�˙˙���˙ý�˙˙���˙˙���˙˙�˙˙�˙ţ���˙˙�˙˙˙˙�˙ţ�˙˙���˙˙�˙ţ���˙˙�˙ţ�˙˙�˙˙���˙˙�˙ü�˙ţ���˙ţ�˙˙�˙˙�˙ţ�˙˙�˙˙�˙ţ�˙˙�˙˙�˙˙�˙˙���˙˙�˙˙���˙ţ�˙ü�˙ý��˙ţ�˙˙˙˙�˙ý�˙˙���˙˙�˙˙���˙˙���˙ý�˙ý�����˙˙�˙˙���˙ţ�˙ü�˙ţ���������˙˙�˙ţ�˙ţ�˙˙�˙ţ���˙˙�˙ţ���˙ţ�˙˙˙˙�˙˙�����˙˙�˙ţ�˙ţ�˙ý�˙˙˙˙�˙ü�˙ý�˙˙�˙ţ�˙˙�˙˙���˙ţ�˙ţ���˙˙���˙ý���˙ţ�˙ý�˙˙���˙˙�˙˙���˙ý�˙ü�˙˙˙˙�˙ý��˙˙���˙ţ��˙ţ�˙ţ������������������˙˙���˙ţ�˙ü�˙ţ�˙ţ�˙˙�˙˙�˙˙���˙˙�˙ţ������˙ţ�˙ţ�˙ü�˙ý�˙˙�����˙ţ�˙˙���˙ţ�˙ţ�˙˙�˙˙�������˙˙�˙ţ�˙˙˙˙�˙ü�˙ý�˙ţ�˙˙������������˙˙�˙ý�˙ý�˙ý�˙ý�˙ý�˙ý�˙ü�˙˙˙˙�˙ţ�˙˙���˙˙���˙˙���˙ţ�˙ý�˙ý���˙ţ�˙ý�˙˙�������˙˙�˙ţ�˙˙���˙ţ�˙ü�˙ű�˙ý��˙ţ�˙ţ�˙ţ�˙ţ��˙ý�˙ý��˙ý�˙ý�˙˙˙˙�˙ţ�˙ţ���˙˙�˙ý�˙ţ�˙ý�˙ű�˙ü���˙˙���˙ţ�˙ý�˙ţ�˙˙�˙ţ�˙ý�˙ţ���˙ţ�˙ü���˙ţ�˙ý�˙˙����˙˙�˙ţ���˙˙�˙ţ�����˙˙�˙ý�˙ü�˙ý�˙ţ���˙ţ�˙ü�˙ţ���˙ý�˙ü�˙ţ������������������������˙ţ�˙˙���˙˙���˙˙���˙˙�˙˙�����˙˙���˙˙�˙˙���˙˙�˙˙�˙˙���˙ţ�˙ý�˙˙���˙ţ�˙ý�˙˙�˙˙�˙˙���˙˙�˙˙�˙˙�˙˙�����˙˙�˙˙�˙ţ�˙˙���˙˙�˙˙���˙ţ�˙ü�˙ý�˙ţ���������˙˙���������˙˙���������˙˙�˙ţ�˙˙���˙ţ�˙ý�˙˙�˙ţ�˙ý�˙˙���˙˙���˙ţ�˙˙�˙ţ�˙ű�˙ů�˙ű�˙˙˙˙�˙ü�˙ü�˙˙�������˙ţ�˙˙���˙˙˙˙�˙ü�˙ü�˙ü�˙ü�˙ü�˙ý���˙ý�˙ű�˙ü�˙ţ�˙ý�˙ý�˙ţ�˙ý�˙˙�����˙˙���˙ţ�˙˙���˙˙���˙ţ�˙˙���˙ţ�˙ü�˙ý�˙˙�˙ţ�˙ţ�˙˙����˙˙�˙ţ�˙ţ���˙˙�˙ţ������������˙ţ�˙ţ�˙ý�˙ü�˙ý��˙ü�˙ű���˙ţ�˙ý�˙˙�����˙ţ�˙ţ�˙ţ��˙ý�˙ü�˙ţ�˙ţ��˙ţ�˙˙˙˙�˙ý�˙˙���˙˙�˙˙�˙ţ�˙ý�˙ţ���˙˙���˙˙�˙˙���˙˙�˙˙���˙ü�˙ű�˙ý�˙˙�˙˙���˙ţ�˙˙�˙˙��˙˙�˙˙���˙ý�˙ü�˙ý�˙ţ�˙ţ�˙˙���˙ţ�˙˙���˙ü�˙ý�˙ý�˙ý���˙˙�˙ý�˙ý�˙ţ���˙˙�˙˙�˙˙�˙ü�˙ű�˙ţ�����˙˙���������˙˙���˙ţ�˙˙�˙˙�������˙ţ�˙ţ�˙ý�˙ţ�˙ţ�˙˙���˙˙�˙˙�˙˙���˙˙�˙˙��˙ý�˙ţ��˙ţ�˙ţ�˙ţ�˙˙���������������˙ţ�˙ţ�˙ţ�����˙˙�˙ü�˙ü���˙˙�˙ţ�˙˙���������˙˙��˙ţ�˙ţ����˙ţ��˙ý�˙ý��˙ţ�˙ţ�˙˙���˙ţ�˙ý�˙ü�˙ü�˙ű�˙ý��˙ţ�˙ţ���˙˙���˙ý�˙ţ���˙˙�˙˙�˙ţ�˙ţ���˙˙�˙˙�˙˙���˙ţ�˙ü�˙ü���˙ţ�˙ţ���˙˙�˙ţ�˙ţ����˙˙˙˙�˙ţ�˙˙���˙ţ�˙ţ�˙ý�˙ţ�˙˙�˙ţ����˙ţ�˙ü�˙ţ��˙ţ�˙˙�������˙˙�˙ý�˙˙��������˙˙�˙ü�˙ý�������������������˙˙�˙ţ�˙ţ�˙ý�˙ý��˙ü�˙ý�˙˙���˙ţ�˙ý��˙ţ�˙ý�˙˙���˙˙���˙ţ�˙ý�˙ý�˙˙�˙˙�˙˙���˙˙���˙˙�˙ţ���˙˙�˙ý�˙ý�˙ţ�˙˙�������˙˙���˙˙�˙ý�˙ý�˙˙���˙˙���˙ţ�˙˙������˙˙�˙ü�˙ü�˙ý�˙˙���˙˙�˙ţ�˙˙�˙˙˙˙�˙ţ�˙ý���˙˙�˙ü�˙ű�˙ü�˙˙˙˙�˙ţ��������˙ţ�˙ţ���������˙˙�˙˙�˙˙�˙˙�˙ü�˙ű�˙ü���˙˙�˙˙�˙˙�˙ý���˙˙���˙˙�����˙˙�˙˙�˙ţ�˙˙�����˙˙�˙ţ���˙˙�˙ţ�˙ý�˙ţ�˙ý�˙˙˙˙�˙ţ�˙˙�˙ţ�˙ü���˙˙�˙ţ�˙˙�˙ý�˙˙���˙˙�˙˙���˙ţ�˙ü�˙ü�˙ü�˙ţ��˙ý�˙ţ��˙ý�˙ü�˙ü�˙˙˙˙�˙ţ�˙ţ�˙ý�˙ţ���˙˙�˙˙�˙˙�˙˙�˙˙�˙˙���˙˙�˙ý�˙ý�˙ţ���˙˙���˙ţ�˙ű�˙ü�˙˙�˙˙�˙ý���˙˙�˙ý�˙ţ�˙˙���˙˙�˙ý�˙ţ���˙ý�˙ţ�����˙˙�˙ţ�˙ţ�˙ţ��˙ý�˙ű�˙ý���˙˙�˙ţ�������˙˙�˙˙�˙ý�˙ý���˙ţ�˙ý�˙˙���˙ţ�˙ü�˙ý�˙˙�����������˙ţ�˙ü�˙ý���˙˙�˙˙�˙˙�˙ý�˙ü�˙ţ���˙ţ�˙ţ���˙ţ�˙ţ�˙ý�˙ú�˙ý��˙˙˙˙�˙ţ�˙˙����˙˙�˙ü�˙ü��˙ţ�˙ý�˙ý�˙˙˙˙�˙ý�˙˙���˙ţ�˙ţ�˙ţ��˙ţ�˙ţ��������˙ý�˙ű�˙ú�˙ü�˙ţ�˙˙�˙ţ�˙˙���˙˙�˙ý�˙ţ���˙˙���˙˙�˙ý�˙ţ�˙˙�˙˙���˙˙���˙ţ�˙ü�˙ý����˙ţ�˙ü�˙ý�˙ţ�˙ţ�˙ţ�˙ţ�˙ţ�˙˙�˙ü�˙ü�����˙˙�˙ý�˙ý�˙˙�˙˙���˙ţ�˙ü�˙ű�˙ű�˙˙�˙˙�������˙˙���˙ţ�˙ý���˙ţ�˙˙�˙ţ�˙ý��˙ý���˙˙���˙ţ�˙ü�˙˙˙˙�˙ý�˙ţ���˙˙���˙˙�˙ţ�����������������˙˙�˙˙�����������˙˙�˙˙�˙˙�˙ţ�����˙˙�˙ý�˙ý���˙ý�˙ý��������˙ţ�˙ţ�˙ţ�˙ţ���˙˙���˙˙�˙ý�˙˙�˙˙���˙ţ�˙ţ��˙ü�˙ţ�˙˙�˙ţ��������˙ţ�˙ţ��˙ţ�˙ý������˙ţ�˙ţ�˙ţ�˙ü�˙ý�˙˙���˙ţ�˙˙���˙ţ�˙ţ�˙ţ�˙ţ�˙ý�˙ý�˙ţ��˙˙���������˙ţ�˙ţ�˙ţ�˙ţ�����˙˙�˙ý�˙ý�˙ý�˙˙���˙˙�˙˙�˙ţ�˙ţ���˙ţ���˙˙�˙ţ���˙ţ�˙ü�˙ţ���˙ţ�˙ţ�˙˙���������������˙ţ�˙ţ�˙˙˙˙�˙ţ��˙ý�˙ü�˙˙������˙˙�˙ü�˙ţ���˙˙���˙˙�˙˙�����˙˙�˙˙�˙ý�˙ý�˙˙�˙ţ�˙ţ�˙ţ�˙ţ�������˙˙�˙ý�˙ü�˙ţ�˙ţ�˙ţ�˙˙˙˙�˙ü�˙ý��˙˙�������˙˙���˙˙�˙˙�˙˙�˙˙���˙ý�˙ü�˙ű�˙ü���˙˙���˙˙���˙˙�˙ý�˙ţ���˙˙���˙˙�˙ý�˙ţ���˙˙�˙˙���˙ţ�˙ý�˙ţ�˙˙���˙ý�˙ý���˙˙���˙ţ�˙ý���˙ţ�˙ý�˙˙���˙˙���˙ţ�˙˙�����˙ţ�˙ý�˙˙���˙˙�˙˙�˙˙�˙ý�˙ţ��˙ý�˙ü�˙˙˙˙�˙ţ�˙ţ������˙ţ�˙ţ�˙ţ�˙˙����˙˙�˙ý�˙ü�˙ţ�˙˙�˙ý�˙ţ���˙˙�˙ý�˙ý�˙ü�˙ű�˙ţ�����˙˙��������˙ý�˙ý�˙˙�����˙ţ�˙ţ�˙ţ�˙ţ�˙ţ�������������������������˙˙�˙ţ�˙˙���˙˙�˙ü�˙ú�˙ý�����˙˙�˙ý�˙ü�˙ţ�˙˙�˙ý�˙ý������������˙˙�������˙˙�˙ţ�˙ý���˙˙���˙ţ�˙ţ���˙ţ�˙ý�˙ţ�˙˙�������˙ţ�˙ţ�˙ţ�˙˙�����˙˙���˙˙�˙˙�˙˙�˙ţ�����˙˙�˙ý�˙˙�˙˙�˙ý�˙˙�����˙˙�˙˙�˙ý�˙ý�˙ý�˙˙���˙ţ�˙ţ�˙˙�����������˙˙�˙ţ�˙ý�˙ţ�˙˙�����������˙ţ�˙˙˙˙�˙ý�˙ţ�˙ü�˙ú�˙ý���˙ý�˙ü�˙ý���˙˙���˙˙�˙˙�˙˙���˙˙���˙ţ�˙ü�˙ý�˙ý�˙ý�˙˙�����˙ţ�˙ý�˙˙�����˙˙�����˙˙�˙ü�˙ý���˙˙���˙ţ�˙ý�˙˙���˙ü�˙ű�˙ţ���˙ý�˙ý���������˙˙�˙˙���˙˙���˙ţ�˙˙�˙˙�����˙˙�˙ţ�˙˙���˙ţ�˙˙���˙ţ�˙˙�����˙ţ�˙˙������˙˙�˙˙����˙˙�˙ţ�˙ü�˙ý�˙˙���˙ţ�˙ţ�˙˙���˙ţ���˙ţ�˙ý�˙ţ���˙˙�˙ý�˙ü�˙˙˙ţ�˙ú�˙ű�˙˙���˙˙�˙ţ�˙ü�˙ű�˙ý��˙˙�˙˙���˙˙�˙˙���˙ý�˙ü�˙ţ�˙˙���˙˙�˙ţ�˙ţ�˙ý�˙ţ�˙ţ�˙ü�˙ý��˙ý�˙ü�˙ţ�����˙˙���˙˙�˙ű�˙ý��˙ţ�˙ţ�˙˙���˙ţ�˙ý�˙˙�˙˙�˙ý�˙˙�˙˙�˙˙���˙ý�˙ý�˙ţ���˙˙�˙˙�˙˙�˙˙���˙˙���˙˙�˙˙�˙˙���˙ţ�˙ý���˙ţ�˙˙���˙ţ�˙˙���������˙˙���˙ţ�˙ý�˙˙���˙˙�˙˙�˙ţ�˙ý�˙ü�˙˙�����˙˙�������˙˙�����˙˙�˙˙˙˙�˙ý�˙˙˙˙�˙ý�˙˙˙˙�˙ü�˙ű�˙ţ���˙˙�˙˙�˙ţ�˙ý�˙˙���˙ý�˙˙�˙˙�˙ý�˙˙���˙˙�˙˙���˙˙�˙ý�˙ţ�����˙˙�˙ţ�����˙˙�˙ý�˙ţ���˙˙�˙˙�˙˙�˙˙���˙ţ�˙ý�˙ü�˙ý�˙ţ���˙˙�˙ý�˙ý���˙˙�˙˙�˙˙�˙˙���˙ţ�˙ý�˙ý�˙ü�˙ţ�˙˙���˙˙�˙˙���˙˙�˙ţ�˙ţ�˙˙�������˙˙�˙˙���˙˙�˙˙�˙ţ���˙ţ�˙ű�˙ý���˙˙�����������˙˙�˙ţ���������˙˙�˙˙�˙˙���˙˙���˙ţ�˙ú�˙ü�˙˙�˙ţ�˙ý�˙˙���˙˙���������˙ţ���˙˙�˙˙������˙˙�˙ý�˙˙˙˙�˙ü�˙ý����˙˙�˙ţ���˙˙�˙ţ�˙ý�˙˙˙˙�˙ü�˙˙˙˙�˙ý�˙ţ�˙ţ���˙˙���˙˙���˙˙�˙˙�������˙ţ�˙˙��˙ý�˙ý�˙˙�˙ţ�˙˙�˙˙�˙ý���˙˙���˙ţ�˙ý�˙ţ���˙ţ���˙ţ�˙ü�˙ţ���˙ţ�˙ý�˙ý�˙ý�˙˙���˙ý�˙ý�˙˙�˙ý�˙ţ���˙ţ���˙˙�˙˙�˙ţ���˙˙�˙˙˙˙�˙ţ�˙˙���˙ţ�˙ţ�˙˙�˙ţ�˙˙���˙˙���˙ţ�˙˙������˙˙�˙ý�˙ţ���˙˙���������˙˙�˙ţ�˙ţ��˙ý�˙ü�˙ü�˙ü�˙ý��˙ý�˙ý�˙ţ�˙ţ�˙˙˙˙�˙˙���������˙˙���˙ţ�˙ý�˙˙�˙ţ�˙ý�˙˙���˙˙�˙˙���˙˙���˙˙�˙ý�˙ţ��˙ţ���˙˙���˙˙�˙ţ�˙˙���˙˙�˙ý�˙ţ���˙ý�˙ý�˙ý�˙ý�˙ţ���˙˙���˙˙���˙ý�˙ţ���˙ý���˙˙�˙ý�˙ý�˙ţ�˙˙�˙ý�˙ţ���˙˙�������˙˙������˙ţ��˙ţ�˙ý��������˙ý�˙˙˙˙�˙ţ�����������������˙˙�˙ţ�˙ţ��������������˙˙���������˙ţ�˙ţ��˙ý�˙ü�˙˙˙˙�˙ţ�˙˙˙˙�˙ţ�˙ţ���˙˙�˙ý�˙ţ�˙˙�˙ý�˙ý�˙ţ�˙˙���˙ţ�˙ý�˙ţ���˙˙�˙˙�˙˙�˙˙���˙˙���˙˙�˙˙���˙˙�˙˙�˙˙�˙˙���˙˙�˙˙���˙˙�˙ý�˙ü�˙˙���˙ţ�˙ţ���˙ý�˙ü�˙ý���������˙˙�˙ţ��������������˙ý�˙ü�˙ü�˙ý�˙ü���˙˙���˙ţ�˙ţ���˙ý�˙ţ�����˙˙�˙˙���˙˙�˙˙�����˙ý�˙ý�˙˙�˙˙�˙˙�����˙ü�˙ű�˙ý�˙˙�˙ţ�˙ý�˙˙˙˙�˙˙������˙˙�˙ý�˙ţ�˙ţ�˙˙�����˙˙�˙ţ���������������������������������˙˙�˙ţ��˙ý�˙˙˙˙�˙˙˙˙�˙ý��˙ţ�˙ý�˙˙�˙˙�˙˙�˙˙���˙ý�˙ý���˙˙�˙˙���˙˙�˙˙�˙ţ�˙ý�˙ţ���˙ţ�˙ţ���˙ţ��˙ý�˙ţ���˙ţ���˙˙�˙ý�˙ý�˙ţ�˙˙�˙˙���˙˙�������˙˙�˙ý�˙ü���˙˙�˙˙�˙˙���˙ü�˙ü��˙ü�˙ű�˙ű�˙ţ���˙˙�������˙˙�˙˙���˙ü�˙ű�˙˙�����˙ţ�˙ţ�˙˙�������˙˙��˙˙�˙˙�˙˙˙˙�˙ţ�˙ţ�˙ţ�˙ý�˙ţ�˙ţ�˙˙�˙ý�˙ü�˙ţ���˙ţ���������˙˙��˙ţ�˙ý�˙ü�˙ţ�����˙˙���˙˙�˙ţ���˙˙�˙˙�˙˙���˙˙���˙ý�˙˙���˙˙���˙ţ�˙˙˙˙�˙ý���˙ţ�˙˙���˙ü�˙ü�˙ţ���������˙˙���˙ţ�˙ü�˙ű�˙ý�˙˙���˙ý�˙ý��˙ý�˙˙���˙ţ�˙˙���˙ţ�˙ý���˙ţ�˙˙���˙˙�����˙˙�����˙ţ�˙ý�˙˙���˙˙���˙ţ�˙ý�˙˙�˙˙�˙ţ�˙˙���˙ţ�˙ţ����˙ţ�˙ý�˙ţ�˙ţ�˙˙�˙˙�˙ý�����˙˙�˙˙�˙˙�˙˙���˙ţ�˙ý�˙ţ�˙˙�˙˙�˙ţ���˙˙���˙˙�˙ţ�˙˙�������˙˙���˙˙�˙ţ���˙˙���˙˙�˙ţ�˙˙���˙˙�˙˙���˙˙�˙˙�˙˙�˙˙�˙˙�˙˙���˙˙�˙˙���˙ţ�˙˙�˙˙�����˙˙�˙˙���˙˙���˙ţ�˙ü�˙˙˙˙�˙ű�˙ý���˙˙�˙ţ�˙ý�˙ţ�˙ţ�˙˙�����˙˙�˙˙���˙˙�˙˙���˙˙�˙˙���˙˙���˙ţ�˙ü�˙ý�˙˙���˙ţ�˙ţ�˙˙���˙ý�˙ü�˙ý�˙˙˙˙�˙ý�˙ţ˙˙�˙˙�˙˙�˙ý�˙ý��˙ý�˙˙˙˙�˙ţ��������˙ý�˙ü�˙ű�˙˙���˙ţ�˙ţ�˙ţ�˙ţ�˙ţ���������˙˙�˙ţ���˙˙�˙ţ�˙ţ�����������˙˙�˙ţ�˙ý�˙ý�˙ü�˙ţ���˙ý�˙˙˙˙�˙ţ���������˙˙�����˙˙�˙˙�˙ý�˙ý�˙ţ�˙˙���˙˙�˙ţ��˙ý�˙ü�˙ü�˙ý�˙˙�����˙˙���˙˙���������˙˙�����������˙ţ�˙ţ�˙ţ�˙ţ��˙ţ�˙˙�����������˙˙����˙˙�˙˙�˙ţ�˙ţ�˙˙���˙˙�˙ţ�˙˙���˙ţ�˙˙�˙ţ�˙ţ�˙˙���˙ý�˙ü�˙ý���˙˙���˙˙���˙˙���˙˙�˙ţ�����˙˙�˙ţ�˙ţ��˙ý�˙ü�˙ü�˙ü�˙˙˙ţ�˙ü�˙ý�˙ţ�˙ü�˙ů�˙ř�˙ű�˙˙˙˙�˙˙���������˙˙˙˙�˙ţ�˙ţ�˙ý�˙ţ��˙ţ����˙ţ�˙ü�˙ţ�˙˙���˙˙�˙˙�˙˙�˙˙���˙˙�˙˙�˙˙�˙˙���˙ü�˙ű�˙ý�˙ţ�˙ý�˙˙���˙˙�˙ţ�˙˙�˙˙�������˙˙����������˙˙�˙ý�˙ű�˙ý�˙˙�˙˙���˙ţ�˙ý�˙˙�˙˙�˙˙�˙˙�˙ý�˙ţ���˙ý�˙˙���˙˙�˙ţ�˙˙�˙˙�˙˙�˙˙�˙˙�˙ý�˙ý�˙ű�˙ţ���˙ý�˙˙�˙˙���˙ü�˙ű�˙ý�˙˙���˙˙�˙ţ�˙ţ�˙ý�˙˙���˙ţ�˙ý�˙ý���˙ţ�˙ű�˙ü�����˙˙�˙ý�˙ţ�˙˙�˙˙�˙˙�˙ü�˙ü�˙˙˙˙�˙˙�˙ý�˙ű�˙ţ���˙˙�˙˙�˙˙�˙˙���˙ţ�˙ţ�˙ţ�˙˙�����˙˙���˙˙�˙ý�˙ţ�˙ţ���˙˙�˙˙˙˙�˙ţ�˙˙˙˙������˙ý�˙ű�˙ü�˙ţ�˙ý�˙ü�˙ţ�˙ţ�˙˙˙˙�˙ţ�����˙˙�˙ţ�˙˙�˙ţ�˙ţ������˙ţ�˙ţ�����������˙˙�˙˙�˙ţ�˙ý�˙ţ���˙˙�˙˙�˙ţ�˙˙�˙˙�˙ý�˙˙˙ţ�˙ü�˙˙˙˙�˙˙����˙˙�˙˙����˙˙�˙ţ�˙ţ��������������˙ţ�˙ü�˙˙���˙˙�����˙ţ�˙ý�˙˙����������������˙˙�˙ţ�˙˙˙˙�˙ţ��˙ţ���˙˙�˙ţ�����˙˙�˙˙���˙ý���˙ţ�˙ű�˙ü�˙˙˙˙�˙ţ�˙ţ�˙ţ���˙˙�˙ţ�˙˙�����˙˙�������˙˙�˙ţ�˙ţ�������˙˙�������˙˙�������˙˙�����˙˙�˙ý�˙ţ���˙ü�˙ű�˙ý�˙˙˙˙�˙ţ�˙ţ�˙˙���˙ţ�˙˙���˙˙���������˙ţ�˙ü�˙ý�˙ţ�˙˙�˙˙�������˙˙�˙˙�������˙˙���˙ý�˙ú�˙ű�˙˙�����������˙ţ�˙˙�����˙˙�˙˙���˙˙���˙˙�˙˙�����˙˙�˙˙���˙˙�˙˙�˙˙���˙ţ�˙ţ�˙˙�˙ţ�˙ü�˙ţ�˙˙�˙ţ�������˙˙�˙˙�˙ţ�˙ý�˙ü�˙ý����˙ţ�˙ţ��˙ţ���˙˙�˙ţ���˙˙�˙ţ�˙ţ�˙˙���˙ţ�˙ý�˙ý�˙˙���˙ţ�˙ü�˙˙���˙ţ����˙˙�������˙ţ�˙ý�˙˙���˙˙�˙ţ�˙˙�˙˙�������˙ţ�˙ü�˙˙˙˙�˙ü�˙ü�˙ü�˙ţ�˙ü�˙ţ��˙ţ��˙ý�˙ú�˙ü��˙˙���˙ü�˙ű�˙ţ���˙˙�˙˙�˙ý�˙ţ���������˙˙�˙ţ�˙˙˙˙�˙ţ���������˙˙���˙˙�˙ţ���˙˙�������˙˙�˙ţ�˙ý�˙ü�˙ü�������˙˙�˙ý�˙ű�˙ű�˙ü�˙ý�˙˙���˙˙�˙˙�˙˙���˙˙�˙ý�˙˙���˙˙�˙˙�˙˙�˙ţ�˙ţ�˙˙�˙ţ�˙ţ�˙˙���˙˙���˙˙���˙˙�˙˙�˙˙�˙˙���˙ţ�˙ý�˙ý�˙ţ�����������˙˙�˙ţ���˙˙�˙ţ�˙˙�˙ţ�˙ü�˙ţ�˙ţ�˙ü�˙ü�˙ü�˙ý��˙ý�˙ţ�˙ţ�˙ţ�˙ţ�˙ţ�˙ý���˙˙�˙ü�˙ü�˙˙�����˙ţ�˙˙���˙˙���˙˙���˙ý���˙ţ�˙ý�˙˙���˙ţ�˙ţ�˙ţ�˙ü�˙ţ��˙ţ�˙ţ��˙ý�˙ü�˙˙��˙˙�˙ţ�˙˙�����˙ţ�˙˙�����˙ţ�˙˙˙˙�˙ü�˙ü�˙˙���˙ţ�˙˙�˙˙�������˙˙˙˙���˙˙�˙ü�˙˙˙˙�˙ţ���������˙˙�˙ý�˙ü�˙ţ���˙˙�˙ý�˙ţ���˙ý�˙ü�˙ţ������˙ţ�˙ţ�˙˙�˙ţ�˙ţ�˙˙������˙˙�˙ţ�˙˙�����˙˙���˙˙�˙˙���˙˙�˙ţ�˙˙�˙ţ�˙ţ�˙˙��˙˙�˙˙���˙ý�˙ú�˙ű�˙ţ˙˙��˙ţ�˙ü���������˙˙���������˙˙�˙ý�˙ü�˙ý������˙˙�˙ţ���˙˙�˙ý�˙ý�˙ţ���˙˙�˙˙�˙ý�˙ű�˙ü�˙ţ�˙˙�˙˙�˙˙�˙˙�˙ţ�˙˙�˙˙�˙ţ�˙˙���˙ý�˙˙�˙˙�˙˙�˙˙�˙˙�˙˙���˙˙�˙˙���˙˙�˙˙�˙˙�˙˙�˙˙�˙˙�˙˙���˙˙�˙˙���˙˙�˙˙�˙˙�˙˙�˙˙�˙˙�˙˙�˙˙���˙˙�˙˙�˙˙�˙ý�˙ý�����˙˙�˙ü�˙ü���˙ţ�˙ţ�˙˙���˙˙�˙ý�˙ý�˙˙�˙˙�˙ý���˙˙�˙ý�˙ţ�˙ţ�˙˙�˙ţ�˙ý�˙ţ�˙ţ���˙˙�˙ý�˙ţ���˙˙�˙˙���˙˙�˙˙���˙˙���˙˙�˙˙�˙˙˙˙�˙˙���˙˙�˙˙�˙ţ�˙ý�˙ý�˙˙���˙ý�˙˙���˙˙���˙ý�˙ţ�����˙˙�˙ţ�˙ţ���˙˙�˙ţ�˙ţ�˙ţ��˙˙���˙ţ�˙˙���˙˙�˙ţ�˙ý�˙ţ�����˙˙�˙ý��˙ý�˙ü�˙ţ�˙˙������˙˙�˙ü�˙ţ�˙˙�˙ý�˙ý�˙ţ�˙˙�˙˙�˙˙�˙˙�˙˙���˙˙�˙˙�����˙˙���˙ţ�˙ü�˙ţ���˙˙�˙˙�ID3 �� ID3�����TIT2�����full�TPE1��� ��the artist�TRCK�����2/3�TALB��� ��the album�TPOS�����4/5�TDRC�����2001�TCON��� ��the genre�TBPM�����6�TCMP�����1�TPUB��� ��the label�TCOM�����the composer�TIT1�����the grouping�TENC�����iTunes v7.6.2�USLT���������the lyrics�COMM���������the comments�TPE2�����the album artist�TXXX�����REPLAYGAIN_ALBUM_GAIN�0.00 dB�TXXX�����REPLAYGAIN_TRACK_GAIN�0.00 dB�TXXX��� ��REPLAYGAIN_ALBUM_PEAK�0.000000�TXXX��� ��REPLAYGAIN_TRACK_PEAK�0.000244�TXXX���;��MusicBrainz Album Id�9e873859-8aa4-4790-b985-5a953e8ef628�UFID���;��http://musicbrainz.org�8b882575-08a5-4452-a7a7-cbb8a1531f9eTXXX���<��MusicBrainz Artist Id�7cf0ea9d-86b9-4dad-ba9e-2355a64899ea�TXXX���C��MusicBrainz Release Track Id�c29f3a57-b439-46fd-a2e2-93776b1371e0�COMM���h��engiTunNORM� 000003E8 000003E8 000009C4 000009C4 00000000 00000000 00000007 00000007 00000000 00000000����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������beetbox-beets-01f1faf/test/rsrc/full.alac.m4a�������������������������������������������������������0000664�0000000�0000000�00000015344�14723254774�0021152�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000��������������������������������������������������������������������������������������������������������������������������������������������������������������������������� ftypM4A ����M4A mp42isom������ ćmoov���lmvhd����ÍĎáÖÍĎáÖ��¬D��¬D��������������������������������������������@��������������������������������îtrak���\tkhd���ÍĎáÖÍĎáÖ���������¬D���������������������������������������������@�������������Šmdia��� mdhd����ÍĎáÖÍĎáÖ��¬D��¬DUÄ�����"hdlr��������soun����������������@minf���smhd�����������$dinf���dref���������� url �����stbl���Xstsd����������Halac���������������������¬D�����$alac��������( �˙��¬��UF��¬D��� stts���������� �������� D���(stsc����������������������������@stsz����������� ��#���·��9���ß���Â���ç���É��¬��I��9���R���stco������������´��’�� „udta�� |meta�������"hdlr��������mdirappl������������›ilst���©nam���data�������full���"©ART���data�������the artist���$©wrt���data�������the composer���!©alb���data�������the album���!©gen���data�������the genre��� trkn���data�����������������disk���data���������������©day���data�������2001���cpil���data����������pgap���data�����������tmpo���data�����������%©too���data�������iTunes 11.0.3���N----���mean����com.apple.iTunes���name����Label���data�������the label���R----���mean����com.apple.iTunes���name����publisher���data�������the label���€----���mean����com.apple.iTunes���(name����MusicBrainz Release Track Id���4data�������c29f3a57-b439-46fd-a2e2-93776b1371e0���y----���mean����com.apple.iTunes���!name����MusicBrainz Artist Id���4data�������7cf0ea9d-86b9-4dad-ba9e-2355a64899ea���x----���mean����com.apple.iTunes��� name����MusicBrainz Track Id���4data�������8b882575-08a5-4452-a7a7-cbb8a1531f9e���x----���mean����com.apple.iTunes��� name����MusicBrainz Album Id���4data�������9e873859-8aa4-4790-b985-5a953e8ef628���g----���mean����com.apple.iTunes���name����Encoding Params���(data��������vers���acbf���vbrq�������˘----���mean����com.apple.iTunes���name����iTunNORM���jdata������� 00000000 00000000 00000000 00000000 000002B8 00000000 00000001 00000000 00000000 00000000���"©lyr���data�������the lyrics���$©cmt���data�������the comments���$©grp���data�������the grouping���(aART��� data�������the album artist��łfree���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������ňfree���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������� ěmdat����� řÇ˙„�˙€ňĆÝ0�Ŕ-ÇqŤŢ�Ç]ż(ť+¦����?ŕُ�˙€+F?ŕø�8Â;ť˝��řŚa×®ó��î��ňŚ8ŔăŃ„D ;�Â; íî@��tŚ#ş�0ë×y€��úFÜíµĎ ���Ç]ŕ�7x�ývď��Gvď� ŰĘ'P�u”���Ľ�ć:ď��ŔGpúĆÜ�y»~€��đĂŻ]ŕ��}ăq‡q‡^ŕ7o@��Ý˝���ŔGp˙�{Ś8R0„v»~€��üĚuŢ��ţĂ®đ�üľ0Žŕţp˙€¤ŕţ]Gq{€�đŢ�����˙ů{˙đ�2˙€ňĆ×ŢĽĄ���˙�]Ś8~q„w�đ.Á×oŃĘ���˙�IŚ#»w€�ţGp˙�˝Ś8Ŕ"ă»Ŕ�đ Á×x�ţ©Gp¤aĂţ�p˙€rŽŕţAp˙€*ŽŕţýGvď�zď��˙�dŚ#¸�HÇűĆzíú���Ü�˝Ŕ� uŢ˝���,:ď��Ŕăđn�����qú6�\� Hčç=��ć9Ď@�?´bč €˙ť€¤aŔG;�Ć.€˙€0Ž€”aŔÇ+{€�Ś]nđ»^€��ĆüHs°�`s°�}Ł@~C­Űk¨���óŚ uŘô�Ł@Ŕ&Ç[·��Ŕ0Ł ŕç`�ÎŔ˙�ZŚ/řlbčř bëw€� sglÉ@��ţ'şÝĽ€�ýG@vě�Ľbĺ=v��>ń…˙�· �xnŢ@�˙�A˙�GŚ]ÎŔç �Ěsž€�~ĂťŻ\RqŃĄ���úĆü�€s°�yFü�Śs°�Ŕ$‡;�üäs°�ŔAă ţ<9Ř�?AÖíł©T�…€�Ý€�Ś]nŢ@�����™úľ�ň�ű‡;�Ö0ÎĆě��˙€9Fü1t€Ň0ÎŔéąO@�ţc­Ď@^vTF�hR�5<s���´bč ;�¬bčř<b‰a`çkÎ`��˙€-Ć.€ÄaŔ=c ‘‹­Ř�8Â˙Q‹”ô�řTbëv�Öçśy8Ŕ��ü0s°�|ă ÷N0żŕ…ţv�ř$b‡ütr×–��˙ŞF9 ţ¤9kË^@��˙€AFüLrדĐ��Ă ?qĐđ Đđ~�����łűHR�Ń_đŘÂ&·`�@i_đ !É ührhó°ţQˇ˙�›ŚP˙€EĆR_Cţ%Jđ xÂ˙€@Füň1Cţ�Ŕ9`üśr×–��XĹřxč‚h˙�jŚ/ř ôaŔăÂ˙€6€˙€[Ž€üٰţ˙_đ XĹ9`ă ;€ůFŕčřŚb‡äb‡ü�Ş1HˇÁĐđńË€�����ű˛’�LaB˙€JF!úŚ˘˙€Ak-h�ôŚ/ř b‘@‚żŕ P˙€2B…˙�"!˙�L‚ި¨(Ŕ @‡üd(Z!˙�Z…(_đ((/řĚ?ŕ '˙€0AU!ŔD?ŕBđŔ!`N˙�‚‡ülCţ�Ż˙€"ŔFÄŠ¨Ŕ ˇ˙�$‚żBB˙€z‰ ţ�É˙€ACţ�J$/řt ţ�b!˙�RŔ? @żŕ‚=‰˙€Â…˙�Lŕ�����üÄ�ć8ü�– đČŔŕN˙�'˙€CŔř�  ý (_đx _ěCˇ!Ŕ _pĄ ţ�< ACţ” Ä?ŕ°/řüf đq!Ŕ„?ŕpP ¤/ř ěRü�„A‰ ţ�ţ$˘‡üZŕ_đ Á!ŔÄ„ ţ�< ACţd$ Hˇý ¤/řČŞ¨p/řDP˙€7Ŕ!Ŕ ÄŠ?ŕś�����‡üdů˙ö˙h!Ŕ!ŕ @żŕĐ/؇üLCűđč _đÁřô(ˇ˙�˙€Ŕżŕ B˙€Ŕ‡ü8CňÜ pPţ„*?ŕ*˙€,(ÂP H_đ!!P!ö ĄࡱÄ*?-ÁA‰ ÁA!P/‚Bp" *Bˇ !;´-¶†š,°� E–�Ö¬ečĐ4ÄĐĹ’`5‚Ńc��ˇ HĂ­˘,cX�h‹Ö´X-ĆZ h�4LĂw“,cX–… �ÖĽan¤Đ�!ŚKF  BĐ,cD4kCXµ‰h5h°Z�1!ä @śĚCbAJb ę2ň '$ˇ3»ÚĐZа�XÄ�4śßP&BđÁ 7Áˇ ±:„¨hCţ�g!÷uż ÎÁřÄ ¬B R ďř¤ť˙�Vx)Mř%P›ů˙€Áť˙�0Ŕ Dďřś ţ�d%Aö'ŔÄďřŔ‡ü. @›ţ�•Ŕ�����Çý¬ń˙ Î!üżŕĐ»ţ�Űná‚đ!1bb‚đŃ1AhB ŕ[˙-Ŕ oŘ…@ąý~Ĺd+P‡ű˙€ŠŢBÄ$$}@Ľt!PęˇhXp‡ @›Đ”żˇřÜ @· A hVúÔ,VСVˇŔŕQwÁ+&ˇ Ŕś.PĘßđŘd żŕâčV˙?P/B˘ŔE EęŻřşü8¦$BĹ˙�'T żĐřđŠźđČ![ţ�ě ďřěüÎŻřx‹ţ�ľ!˙�rŔEoř$_đ!ř‰t( ďřŕżŕâ*Ŕ ‰˙� _POř,đüî Mţ*˙€żŕ%(Ŕ!źđó•˙�@‡ü�w€�����ýţţ�˙€GÔý_đëú)A2DuÍ˙�]żŕŐHĘý?ŕ%2ść˙€@ČŽ˙€-”ˇLŔiÎJżŕ>•Oř LÉÜÉ˙�('ü4Ą»řR›KeußăÉĘ ˙�UżŹř|ćR´R¨.˛ŘŠ„! DRJ…BˇR•)˝É!ę‘×'&J(‚ZÄ ‰@¤,¨T3”¤¸™DV*đ×ů3’AĨŠS'l‘*SŔŐ…["(+‚9¨BPP%1"!A›ţ�Nw’#)‰"I R™.P!ÁS"*”r I˛±(BSŔ ‡Ŕ $Çü�ňęD«…)şŔ JSŔ S]r#żŕr'·ü�¶xäBSŔďř������őţ ţ� ýr!CżŕÓcţ�ĐT;“#Ŕ¦Oř ĚŇoř}ű’M˙�á‘Ô&wđŇŠ?ŕ'üxĺv§ü�ľ˙‚‘p��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������beetbox-beets-01f1faf/test/rsrc/full.ape������������������������������������������������������������0000664�0000000�0000000�00000033374�14723254774�0020342�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������MAC –��4�������������Ü3����������Ů0' 0éoĹ|Íţqôčî�����D¬�������D¬��P���('¸�������č+6ąD€»…›gzżă˙nč±`ČéŻCÔC-]áXUÚ˝ŰÎý–Ěą€ËRôa‹% –ýŇń0ó©'lOéŘ[&ď(ďďHő)ś"i%6aMJO1FĹ@…D8Ůqlpů¤ŚE ]“ą$ę ĺýwëvy0äĎ\ţv7®Ś±I®Ć ̇m7Ą.uMŇÉč$Ĺý #­×x|‰Sźúgb;<·|:}|{H?k¬ň"ÁÓ{äěćwß áÝéݡ"vútŽ`OM".�˙Ä+.Ë÷"q=f“; ĺF&8e*’<ű´íH4ŠČiçŢ-w»´7rý:¤ŰśJ \ó!¶Ü$W'8[4“ě«„ËAvěMXFĘUÄÍťHU®qta!;{;Şëą ĘÁ­ŕG^ô�ad¶Gę™gŚľPĄ­)«Ś·Č úîŘ™ÜzĐšSŽřg_ęQ¤Ü=ĎS Şů¶¸âčMż‡nć”– ­u%ă÷ú˘ÓÁ8˝ Ě úx«=j]m6“aú@Ť“I›L]ö~çęn0.*§ăĎćcžânQjŠ0‚ôęí‰6j®ń1ÚC%g§4ß\-GhŮ3Ý•[čŕH"’—ó@ů>Ż3°+©ŮÎ˙äÜG˝ů§W3r<b…ŘC3D:n•8ZŃ ž}ě؜׎BĹ‚Ţč&±č-ňěđäą:kň_+y‰ĐyAZ(>ÚKn>śŇÚ+{<zţĎÔ„ĽuϸlD +cbŰÄc„>ç˛ňô·Š¬´ &…ń6uż_zęmeS)K+2ňkň± éŘ8ôÝ‘cĽ3L®ÝÚ퍎Şv˛WܸÚßčŃ‘¶Yŕş“@§1"kâr}őqIĆţu ¸ ˇ©[#ŘČôđ¦‘w“f•—C?s·”b Ś[XAĹĘ Ă=‰‹\ ú„yäüťvJeą•ŮׂfÁ$0˛¤yŁž}:ŽÎ Ř­ô[čr‰zŘ>Q†Áx „“(® ~ 9Ĺ5QţKť¸ÓÓČÓ&)±¶‘9jÜaĄ«`ľę‹A8¤Ě×– ”©żŐ"“ŕ†;bR3¸ “D©ťW vćÇ)ăĎłS›ÎŁŽŹÚĘ̲A}ĎĐꂇTÖ‹ľDcXHwüWE>Čŕ‘'•:ЕȜÄqqoie4>#Ź÷ -&}ě#}FŹí5pĹ㏱ăł*"•(taŕˇÂŕĹ“"Ń)®BÜ -·,AEexjĄźňÚ†›űu®�RŹH×n_¶n¶(ö¶Îâg'v9HĂ^%Ü!?ľ"XÎ@µŰÚ6×›R÷…­‰ŻĚÎyÔĆá=ŔĎ‚öŻ–8/Ö3VőMć»8ż±˙u!đ™rP@ÄÔŻ ›“řqSzá~ĺjwń¤xTčv¨TÚ.'˙î†>ĐäŇš?Î`XşůĘT÷:™—ýĚ Aă?”1˛\@đd±Fâ|ɶĆD˝‹°6ő Xőś“4„,đÁMÝRż#÷°ŚĄ&ěeőľ­E„›źväĘýS˛š+vWČăsJ˙Ëžýxń˘Ěśă{ö[·>¦pQ… t˙łňŽ)®Ě ¤Ç`ămnX±^xőÚ-Ç®“©=®ćŤ´|ťt˘Ŕr…βţ‰ą!ɰvČ ^Tż ßN‘k1˝9Ą#śŢiÔ[@ÝöSÉ‘’č&ŔĎvš¶¦_Ö.ŻoţUśťć÷ŮŐt÷xLÚ˝ęř‰H/ ‚Ďm…ŇfčżĂÉ‹.y Ć8Ô÷KüQE¶p:•7'§Nnľ„ě 8lhËŻîo=›}Żľ•9P}Ví{sÔč˙ŽŤďnsŔXiV<Yô‘ľąÜÂú.ă«Ëö­4·ˇpDf&¶ ÷^°"–IXâíEwŔ°<Éýň&U¸Búħ:üşť°{“S™«´ UqXÓ_ZËŃő„)ćmţ}85äżÖÉ:gë}‹Ŕ^ňĽw5»PGH/űíúy‰<Ş~‡~WŐź=%Jg(ýj2i™ÁRf™hq5Ô[á:'b‰:ćhzhgM °€ÇŹĄ‰ŢÚŢĺŇ´+fä1‰óo „śĺ€ÔĄÓý§/ýčQţ´ů4QRĄăᣎ_ęS0>Ó”íEI€íH=ŇősşK—Î{ÍÁ»ŘŇcyO€g›ˇPű‘–ő‰żÉĹţµ\3yy8#;çtş2‡˙ę[mš ©í‡˙‰y0ÚäQv{Dź×®ú3@ä ă­h˝lXÂqKž!®!őĐ�Öąú§çÚ‡źîŔř–%5Pú]×|ąő´††#i2‰öíÍý Eäůě›ęL˙î­ 7XĄ˘)E»[Ýá[Z$ѾƺŖ˝żîKšW¤ĄŇĂśíeš�Y#ńŮ,‰íŤŢ„ě.eŔŇ<Š[čÍ؆,ŤI#ŚôŇ·5´BÚ ˘6Ěĺ)ĄŮůMP™12]ž Bäă'‘i"’-,ąXQ3Á™€Ř_@řĺ©ĆY5ÎŚDdťm P Uëe*zď =ý…,ňc‡-żgCó˝6a‘˘1ucô°,a$őÖ×zܤ ćV‚ňŔRĺ “”$}ôÇĹ~ď¸9EZç1°H.1.–j¸˘îżź{—N]aL·.7 ¦Ż€DQ‰l¨°Ůń‘±89Ĺgg­vDmę^ĹĽäžďđDUV‰.’‹ „OX“EV˝ńúĹŻĹż>qázŻ™Q}(Ż#¸MoNŞţ‚,–-ç~bűc–97ţŰ W_ýÚ 'sžNYO j]feČu´Ę6óÝ3¬ĄNâ‘+1g¸`äjÄ;Vn6ý˛pf_OBdkŕžpěuŐqž\HM…kŹűÇbµY2}¶PĎe'ĽŇqCGˇ[Jě7‰P~Oëę ’k¬™‘Ő3“/—A ]%†°h@ o<V¤Š/lÜŻDÍĚrä;Ś’B®ÓĽó€Če9óŮťÇN‡‘ëČP—díş«ö-GLjc¶íDNäçs­_Íî\\Č^˙Oöť.˝]ň´ ńd*jxÜť_Ĺ{?ň żÍ:4~ ëCŃfÖ$ u—V€T5•Ě4—|@‘#F2ç�xZ:Ú“¶)ŮäZLU˛ŠYńxź9­˝ĎÇ*¶^w23p(×kz0íť2NYT„Íö‡<1_íŢĂ{FTă© ëz–ú€Ěżs­PźtłÉŇú1 –Ű# ˝6#´»”› 0 ł2ýJáPX"<7Ę·ĆOáJ—čwCâj˙h*Ą}áVBřńďXŁŁ˙ŘÝűÂţ9ňסڬż‚ŕ.ę' ;h|Ĺnůf˝.ËťŢW|ú{­¬žžn™żýÓÁET˘”(Ań!¬ ŮfĺnŠnŔźW¦éî—ŕ·ń¸B÷rfŻÁ”O}7S|ąsľ[(7üe@aóÉý+¦§"Ü´¨Ŕ±3ć­3˘=DÄG|ÚŁSá\űËËŐ»GޤjfĆsé‚śŚˇcOýů5«Ö4ŔčAľ^Ď·Ţă�če>zWÇlýŇ0—uąrî[˙KŸ}–ún˛gţĚ@)E˘ …/ď׿•A ă썚¦›źÝéFz ‹ěӓ͸B‰@[Ë %Ű×á!Ę~nÂ8o¦®H#7±=ŞđG źöżqÍĐóО`Ka˙„íHĂPXŮóú{ ŚŻŢz“đ|\+Čç|+Nn{W´*ă›Ňý2†usá1\$«J÷‰µ®E3Ő˙°Ű (ÓÁň‰ĄQl µÓâű¦\ő€V 6°ě¶†RĐ-]ú–SŃM¬´«}.ŇZ+í)BĎw{”ű]ňŹ×ĽBôŽ Ł[Ú~š–’‹©<nľV,ë}#ŃL;ś5¸˝ťĘ�tf[ČVŞÔÔ8¨_TŃ7<ŘÓŢ»……ótu-ŽŔ'DB[XŔŻĹ?›Î:<Űn^}ĽłPČÄ —O·=Ř“y˛T-óĎ_uXXö‡JhËy ź´;ŤS’ t•ŠRňż×›ŽSŕ‘ܲ¸Ďf é,MĚ ÎnŇ­šß.‚ĆŮ>–uÖ<e ˇĆŚcUŐű äöŻÂáçł:ß á~*Ň:A1ž«¦1L–•PוÉ€‘¸Đ_¦âw¤MkyŇ‘>ęk‹§*ë[“Réů˝ĽČKń^˙Wń.ýŹyx^čSĄ`ąÇXń Tť9 Á‰}­ ,f%cň¶z¤Cdđ°‰/oűXfÍÁgî/ňŽćÖLb ăząIŃ9z,çik߸m2Ă�Hßč:ÚČ™U3±6ę›ĐÔVŕ-­!S)­Tµţňt —Ăߊq‚üăÎ4zM’î=03mcí(f4E×Ů9¨Ji#çŘ$Ö€˛‡.Ľ¶˝î—fąOŰša—żÇFaöćoËFVą68qc•ň:u[PŻ_«eŇžfŕëf›ŰÖ�»$;RľíźÄ=§Ś‘!MŹĆ—íS±(ŮbéLIŹçoWŮ>ňź“î铳ŇÓ¶—S{Iî=C.…ŠŰÍ'ĂÎ?^“Hut†q§ą›Yn—¦´®1o2˛ňz.˝ :LŤ?ŃÁmt‘¦×x) Lěĺ\číö󬍩K•aż´¬p\!)ó §÷ŻP#ű…?#™đĆ<wIµÉő«Ż�gŰEqŠó>ÎęX58ć»ů޵¸*l¦&#ŕp(müL‡3–B±AóéV$®ň÷\Ö^›G4–Řď”yłŢ°"Q’©s¬Żaň—R%VŞwPRő±űoĆuŇÍ‘?şËèđÔř| ŃjĹĆ ßFZ© Uíçíw¦łĘ�ĺÝLeňt—ŢˇŁ±ß\®]ťnfČ yř*e6úŚ.ü¦”PŤčZ Öâ‚ ş!ýi…ď«{ď¤Ý†S¶hÚëęŘgeyäÎy&úń —Ę6#ýF(;`;S÷íţX·ŕ‡«¶~Nţ>^„Ćjß˝#䬭VĎ%ކ„Řd8tLíĄÚF:änś_ îőČxrr«ĺô± „ŕŽVˇ˙—;ś!ŃĆüHŮĄ©˝IÄAş_\d™ęŢYšštIËľ§ăzŇ!¦%,Ľ"{–( –ĆiđĹĹŻÓďI«»öŤe˛˘mnřqĄŚÉ…włËX!qőüm­~"ś.Ă5Ě08ˇ8’ëA&›k÷y»pĚÝg¸ţäˇ!O úĆ5Ň/#Fv˛Ńľ»AßaI$¶öůIM$éV]ĆŽÎtőş ą\€Ť^ö*­B§X/e`{Ćh ÎLóęĄ5ÎV4híE@ňEÁhŚŤ2˛H1C|ggÍĘłĎß® :O­ —Bż ¸9’Ś0˛ďXwTl}Š$4¨éť]¸ô‘“l“ÎôFÝެ˝0ĆeSbŽ+ż’í‰<ŃL¸J‘ő^‹!hŤňďˇSiűA Źfa‚B$?é×ZAt¸# – =š üżÎGú—çTűÔűőUľ‘‘+FčX §L7˙»-áăx»µżN`ŻÁ„6$cöÓ ÁÂBÚő:—ţN’mkCŘ=ŽăF/}8âÖă^ÚDđAsą´®#x_™•C«KT]ÖƲÔľ˝mřa×á}üo† Vú´âä‚€ +Z*łN÷·Gť°.™ IĘ›ęáS \†Ä?ΤRý}-JzăˇÖbÍ­węµŘµ„ …Čť™_’ä7ĄĆóĄsRáÁĹtÍ­n—’ëT;×ńŚl CŽS÷Q’ëĄŐ>Tˇă‹#A陡Ż:^<k»äŢ‘m˘Ę>ŻhőQ”ŠĺÚĆĐĚU fB\˙śm\ŐMrYó^ů`—A‡ô–śEëQďZ‹’Ź˙ĚŞ™ľĐ8#”Ú:V:ŢgJo�’C‚ß0~×}”ćtž4$ĄşóÔň°Ái“@b8epÚµÁµíĆE+b÷y–ßb´čłň„«.ÓĐîv0ů›™H9ű('Ť%™ÎqÓÁ‰‡N«)†{ś‰b˛§?›,-nţ x˝k Î?UT…áʰ4vl”ĄP{%ŞÝ‹vÄuݦBnđCżjI÷4CLJ§ŹěłXk?fCŻĺjpőßżh×q”«ě50RHU*‡ŇŘĚ-m‹:Á|ă5´;Q•›µß&Ľůě¶PérçŐnŽŚL‘Uçň×5÷©yéšI,OYˇ*„áůŽ“)XLĐN5 8đ‡L¨Vá6«łW 9Q R#+ކ K˘ěýäXÜg.náá2Šá»í2KŇŘ—ďTőÂŢ�ËÇÝ„ĄF0XC‡,S_ç-ĎÔ,ŢÇńäÄÜŕ–¸‚ ö3"¤m˛wr•‰kBÇR‰žÎręK¦Éĺ.yú°V:±Ü;â *ť 0čk˝—VĽ!ľ�“ŽBçýę ÔŠ¬±ěż´Šú祽ň´D…¶ďbhtÄ·+ǵ!dĆŻ˘t'UNa¤…‘řĂČĆłR�V»3J†™˘ńqôňď(ÖôŹ<xm%cw†8Ňž gVlz/ZŔφ“é{ꇕ�őGď@îvŤ•“pذU×ML-wmŐ<ő[ü ‰ŽęiAĺ8\0"k87Ęž??—…Ź Zî"¶B'ěŤry/ ڬG’ń)Ę•Hśjľd/¦EŢLŃĆ˙4×8éG©Vä^LÁĘr1˙›¤äP =ŁżgŚ(F݇VĹŮY´ü4Ĺd•SĐL×ZÄČ˙@,Ú†qń­Ű÷sUwOéB©ŘŤ‹đáińŽ$Bżü(Ü»/ŢśSŔŃůdńëŽ3{Y¨¸¶ŰAňĄ ŮS'¦ęçX*=eB3;näŚ1 îr§ír™‚ĘG۶šăÍ®TóÄÍ <ĽĚžá—‰šyłsWűCáÜ=ŞÝĂIjĐyg!=°Š¤mŻ\2Łö}bD!bëŻ}ÂWśš˘ĺĐÓÝ=XŘj´:×…*hýřA81 #$ÄcŘWfš'çF]iJo·âY…˙d çmdĂß B~!=9- •ÔµţŢ yMMfŞżlŤp‹ŮŢuÁßá°ć•ą6ą•]«Ůřk˛¨6´%{둞: µ·Ĺłë₍ďŤű`çśş!TĚşp=ygxć(q–®ČZnĽ@ŠEľűö˝aśeŻc6ho†UčŹĹ[óuř"Đď¤}†aĚäť“ĐÉ>:óŽ)ß»^]Q¬#őy«‰Câ«9ĺÜĹÎŢŃĹ…˙GąDZjśłí&⥎Aŕ=䲽um©q>©jJF¨’!Ů<uő´«®™ [·cPFéwä=CăĐV‹ä–ďšĐ§ !9<Ç?Ѷ7K_ĐÎÇM+ž3ËžŻëŮĄ¶Ővvţó{şŕFßC´zÜź đ8<Č ćŹ;©hhöŢI$bÍŽ5ëF»‡]SFôxĺő©QÉł˝"v1ßs©žfźxI9Ă{hoŘw"~•Ż”6Ď"ů1ˇlâ>ÍŔZ »{Ę^Š©óf!— o:Kç\tÚJ8™X@ —úÎj Ü4«ş;07:MóŃ!©çń3Vď‚€dܱ MÝppËTýµ]÷?lťŹórÔÔV/b „Ö†68¨ş.'“[±$<בiﮂ¶ćŽż4 Í®§đú€1ÜŹń.†úôÂ~pç”d>?ŘŐľćw‰8­±PąLçbđ\8Q·Ě˛mďťf\ e+‰ő˝Í® ů?ĘěŞd)dś“ÉóŽŤĄµ„Ź…®W™�®´ŃIr$YB8v@Ř 06ŹŠf%¸.ý·Ę+1_ĘŁ€Ěî^Ѥŕ <•şîp[GépĎŚwĚ ‚ěŮ´EâÔq6ŰŞńË´×›ý_-/Ľ‰kç �qNúăKŽńLDu÷ďá’ c_DM!ĚâçwsV·¨ą -đUÓ÷ăŘźAŻŠ´Ľq®g@Ukwíěp~SE9˝tݲdu�ÍŹt˙}*Á&ďYeŹzÂîŰSŮńEóÝâ=|çÁ Ʀ ;ďĐ<_ŹoâLÜŤňÔ|’'Y÷űČá=‡2¶y˘nď˛Ě}VśúRětOćĽ ţ•A0gÎ:KÉ{‡ěµÂ]č­I‰}/ŕŔjóŇŹah•_t ®I$5‘uĚ&.µŇq¤2ÍÎ^żw…dMMOč1-)zŕuńĂX<Ű]ÖIâ ŽČ" Ý,/g® €ă|őŻ>éźu¦¶ËŔ9ĂV˙ŹűžÉŇ/ň5k€ě-!“[Äř&ţńŚ FÖ Ö{ëńq±ŘőwhŽĄ ćéyő3˛]—ćäéŰŰP\5NDůěŮžožcAČSéâVm>[ţâ}kE:Ž"üşÍTĆ ?Wź î'aď3QíjŠuŘx�gÇúnrj–čMQyµäçźĎťhřV´W†–&Ţ˙ŰEŮ)ó–Č«<ŞýLÓm˙ŵJEöíÄDâ|řdćCjąžßA§+ä׉Ôw,Ý rcĆýMÜ­«M“ĎפŤkFKgÓÇ üˇÇZcxé ěĘ)lQYŽ*RuątO’2˘Ľ^˝ňµW@ýäŢ‚˝ÄĐ`ţÜÂTťŻšÜ† fCpK:©Ţl)Ü9Řc––Mž¸¦ß®&)n,•ě‹Nń%ů»ß4Ťç{Ä?3ťĐ^@>čČJ„.ňE| 0ˇ×\Ť�ě§=e÷7üŘsÉZ$ 5Ţ«w? \."<$Ó)´ťßpäRwčĚß6Î;ně\+@°ĂiÍő”ë–RxĂŁ7Öî.V€~傦Bmk4ń@áé‚%?-f$1ż˛ÍFbńçšă•˛ßPďeCS r:ýJ(üqŔf‹µ9âç�ä¶L iáŘŐÁNS(†î)»%w–yűyđS(Ô$("­Ť¤-­ŁH2Á‹˛f>šËŮVďŕ$xb<A±—öáđî Kd|v^E“7”°ŻSöZ©[đ¶v×+ô{©î–°ŔyĂÄŐ“®ÍÎäYqXĹč†é|e´’vbÚ’źCˇ8YGŮÄ]‘O˘ĽĐ×ߤğ˝˘¸Ä {P„ĹBúč§V)&Šą Ž;U03µ}˛v;XäR…eď–[OIĚz†ŕČŽłŕŽß®Ú?†ľÖ2 č¸ä A<›4eôcM ­Ę}rţrg„^ ÜŢďtËŠÝ”á]ă“úÉ�€"!Ľ?$5’â !©A^ý©I·[ŁŘîvOy(™ßÖ»řĆO–�#„ÜüM@)ţFő}XöJ`7kŃć4eg´R÷Ć!o5"ĐćQ¸‚ĂŢĘ·Ďŕ—ö.řÝG(Ĺz§{âBh‚úGD¶řJ¤öOm`3olśkË6âß ^ófxvž€Ů×ÖúëÇřidŔ h ·A×űR0×)Ó\HXCH/UĂu};›8ĆńĂcăľ?ě˛t_/úäŻöňřĹ�[Ńô•‰ąö’Žö ŤNËYĆďxĄźÜĐ Vݸ̲]Ţ…y -ÍGČqAĚÉAĹ·źvxÚčLÖŃަš0~˘­/żż­Ź żë~9pľúlŁ�iˇ(¨as¦Ç" ‚ä\Ý_ Ц1]/JšźŞÓˇˇq÷@Ýl3lŐ ý„rămâńK [ńCĆ7íĎbI”ˇ^ďń‰N`ďwhg‚ü1Šg˙ˇ?Xýéý‘GŤ[Ľ¦¸5F-dLŻ%d^OGŰe÷Ď ‘ —ź‡Ž‰ą:DĹ˝«ß9ěŻäE/xźňüNŰ­5 p_·'PSî_cÔŘ ş�%9kŤ LGrôcŞI[ŠîLýH(e¶i—ŮOOĄ]âţ€l|żă:őęŞJ:đ«š_‹Ď~ŞŘ)9F§Î߉ÁŐ+ą”[ěj‰ü‹‘€%€. w6Ú5<çY'đ8^ĺABb8´¶ÚŁv`¬P§`5i§ťú#+Le€\6fO`÷™đŻ<ĚF"_™äÝőµţ®ßśSőjxYý&–v,¸¦MväŻ Ę+­[DZ‚üšKE ĚP›É¬ÂĂ™)˛ô_‹v:Ľq¨(<áa'L`R÷÷Í ~=CĆ;É`/Éúp{…@ĆZßńâëjÉrĹ8ŐÎîT—‰ąťŻl+ü`Ő‰^ֲۡŃĚŻm{sk¦`oC0ŃčĂČßîç<Íű#éě$VcCuAé%µSú÷“\,çJőű9 L”čź6 I4‰¦¶�´%ŇCwa4ËŤ ÷l3�b5¤ÔźŃ« T%ä8Ĺę!ř(©Í+‘#yűDźj†±1{`?1E^–?fé÷÷î->‹žsĆhĂ%uÎIíňm+'đNÝçdëź?żpőË—8}ÖÖ�ĘŕpÝbŤčî§‘™ß謏ŕĚŠPÄ«Ż(KL.Íg ĽkHĽa+Śxé‚3�ü1›+éOéçĚ…ar±7& ëÁvődVóůOÉËę6!ď3ŢB4˙ř,F|sÄ·ˇž¦ëżőw@*B`Đ_]ćq>ů/ èĂńť˝ř×Ô"ęÎÇNiK&”…=.˙hľŔ-JÓěćÎ;RvÓő¨„Ŕ;YDŻźđÂpâšôT>JmŃŘ„§3ażŐS ¬J+`Ő=*ăŚ1o05ÖYĽň1¨kŻ2eîÉŇÚĂ:ĹŽň5IrÝ Źĺ¬WÚ1Ë1(Ł X=~É`ä5ř;B,d—´5ŠB KZOů>üS.ŹTÝ Š é%*¸ŚĹşţíoOú4yJÔ8"B‘¶`G YSôâ#ź‰ U¨I• 1ú÷nůäîy)~Nj7Ź÷•ZÜgą\|˘˘EĐ <Z*îđ_™„î‚˙BŇ‹KÖĎ€‰zĂäWpťŔšŤ‡›şy2cʧŕZ×xŰgeźţ”ŽRŽß8§f†÷·Źţ¤@ţ–ÎăHš@NK„V¦şĆQ@ű¶Ń„Ľđ÷iÚ§gߤ!×÷<*ˇó"¤!—XżŮÎó¤pás2x…IFuisn›Ű˝t'YÚ7ŚŁ +`gý 0kN.ˇť„á|&VńWE˝žĘĆTTŔ8]U5ßH°›Ő¬ Řf~dŻI<Ů,“;¬ygÇgE0¸÷ŽĹ<¸0YöiMßHÖw(ÄTý•ŕÍŮ`î-|Ő@V‰8é˝°=%šU¸#joę3ćîKţÖhÉhÚŞ 3¤•/ä‹ë)Źi=d?xcǶŔcéăFx ?Ćr2•°$Hx=žă퍍Ńz;Ů9Bw8A”šbsÝĄÔf÷U^ťÎ�gß+c˘z [\“A«¬é6µĄ–®“Ů‘›?§ÓĆéŻ03® źoý©F[ `6!ǵ˙)ÇŐMT&čoţ˝Ť]6ˇËŐň‘DSdÎ:ÔÓěĹŕ°±Rş@Â6 ŕłiŢŚ)Öá ŇQ$^† —?ęPź1ľ.(Uł˘-Üž\m`1ó•ýKµÉ�–ĂíÍ*o¤‰ď-đ_c¸ó R—I,ŠĽ%żüÖÚąÚ›ő4V‘/•ĎÖďkIp›»PQˇZS“ìÉď¸-c·Üż7÷k9u#FŮHc&­.2/N&¦ş‚h¬Tîž §ă¶mÓ¬Ŕ <^ÂRá'M±.Ď2ú<Ě'ŢćČg2PM—]ś@ČăY z_\®vVŠČ5ĹuÂĘ÷ Xhŕ\Ô J4,ł^\ NcĄ(ľjHŚ–Ół¦:k¸ŤĚ`ďň„hÝÚßvű8A=…ÁmBŠ©/@dű}8çëI®«»¨Äçľ~ú†úĺź´§D°™îćŇ6•A4Čł-—° %¨ĐFy‡eä—´pµnłD*DŰyřŕ.ďš3*^˙©„żNâ2!Č©/gÍü:RCá#h,ň“6eç‡S㨻©]d¦^5ß`đĹŻw4V˛źÓEGKź¬:ʉ|ó$Kć€~ÜŮÉżI±Y§ćđuíż1µ±ÔÁƨµşĽ'\—«ňŽ6];ĺ˙FÎEÚęÓ{Ă‘”[ęMĹ**Lţš_0P'¸¸$üëâ„_ŐżxŚ»©Żé’dF˘4zÎÓAć3t\â `é\Jňy–Ě™p"ś^ĽÚr^8ýÓmFöH!ÜËŽ1;GŮ®Łµëx2dť őASČ—†xFöAÚÄ*,ߑÆ™× =»0ZT!¦Í!ä—|¶ľMú‚šŐj9vJÁˇM©üšŽĽ"űI7ły iŻé™\Ň.¦7ŚřU*°ĚuĐ/((BńmYY̰ÇÜČ%@ąąŔ/ôAČĚ\nľÍAĘ/×3<“`¶v2+‰„ľ˘zÇb<­nr¤ŮÜ'âÚę?#…OgMČÍ ;¨ŽEdĺÝÜ ËnqÓQ»–1±Kú{_ŰÄ„ők˛¬ř łˇë%Ǣ˙ErúXˇ[/Ľ8bvdô^őĹ*{/sŁţ:ô´0ÄśĘD ŇÇK–l’ĽŢ.~ŹÜ^©#,Ć  ×ŕ2ŁźW`¸HhŃ”¦Ę´Šö{úÎł/ĺ˙ĚŞ˝Ć©"e›(ů iłÝř•˝$.Č>«˙«0‚ŽínSîĹŤŔĽ“UúP �Rf=˛ÎŇòUEą!ż_’šďŰ?~ ĺŽ+ćΰÔ[Ł!W;ţ#„µ‘ú%‚tqšeŠ*ÚĹö»ĘŽLđ ű‹ňY9WTd Ę+żvsÓ~Vň!@śş’šÝ ;éWÉďlťŃÚUéf€ć6€ŇGľŤ´y ˛ü–Ą%rŽŠLb@çĺ-ŹŹ,ßÚy ÁĄÝ MxR 伝o^‹ÉüdçN¬źôf¤ĎóÄĺmŕ% Ű1÷[ňKÇ;‹.žúBşĹ,ý˘á�:ţŁţ^§ÁĚ&µ> ę/!†3|Ö«SŢ—yTßF?řË0E22˘>¤jQu?ěžx€©’űî~$[Ó«™8ôŻů{Üw!?I‰’a ×)!ö@ŕ°˝Č@¶ jTz\žů+ţUĽ•řĐęĂŞĆü߼–T˝Ś‘ŁşBŚ+.ú€Ý%Ű'±Zú oMO™ś8b(şÔßg.ŮŘE·ťácGZŇËęźB—ćľWx‚»5őÔ0¤'śç##�9]!@&ĚóŹÝ˘FýL†FŠĐ*ß•Âa¤6ď`­Ł 1\j¶Ś©űܦ#ÂôĘÁ«ĚŔ5~µZ ś¬;s–íąŘźŻo9­5 :!Ď$˝—ˇ`môŽJşŇ¨ŃC+śţH;q !LO‚ńŞá’bŤ5­O~ 6D„o§őÉňŃkCZĺą@ȶD›©t†xĹř Űőż4‡ď¬Ž<śŚU§9ë—žćgů˛ź•ë–zŰ"rUŻ BśŚ‡,ż|Ľ?;Éůńń0sę@aŠ€Ëł�ŤéYňó:‰’@5¦1ńq±0ĆYDăc:âPoBę.Ő­˛WZ˛ÍDL÷·ŢÜ ĹěZ­°ezž> \_Ľ7tm–îÓë9&˘DütÉđ—Ü•hI >kć…F€‡ţŇLöŃqWPBX`á#¦´«ÚćAßůţ#ÎÓXnł$u1ŤY ŔuĂ|ţE)čx†Hżő.®-ż\tż<7¶dĆ®(ěTüľ%ž(Í‚%Çę–¨«¸™Ç¨-’7ď±=Ş�aß’Ö±ü@†?Ŕ‘ČTˇ}ý‰öó·p ÷§ŕp(¶ŤRW‚»\źT@˛ ž˝îâ˛;)BhôR9ďţ÷ďß®ťă«´ßě?}_™‰lż &°Y–čę˝odŠ‘ô*ďđĘżdD«´âí€ŤĚ łűŞ]›őëha >ôyTMp´2‹¸e q¬±Ť8/ßźjČ ÚǫɿŔĎ kł Y® ľ m’čş].Ę}űß`ć4Ę1Sy;€ŕ,ÉŻt �r!Mő1Á:°/i)¸XŁŽźçĚa‘Xö–ř 4đRš(Ś”ŽŐŐxşäIfQ3sĺ/ŚŔ†Ńöž5ŇŐĂ',ö÷,˝Ç˛Şu[rwÝţ ÖĎn i á‰Tç†Ó9XĎs§łŠč \·Ř =şśôî1 "¸coäĹ/ęˇă……¶“ éžk?'-ÍHWŞk …ukČ1 0f›-ÝŹĐ”¬5˛gńéăŞřÖc» q;ZŔî5\ÂÓY5*÷[ŕŘx–ŔńŻß R»Y b˘úÄ_(%…×L™ëa h˛§5(·yÓn>ť‘›éDŠ-¤Á»/HňŇ{uäef/TCIÇ·R+FlÁ±řÄ» ÷UăÚ8ďúfBűa¦Ţ<š’</T¤őGćşlŤ>9 ˇŚUvnɆŚ`g Kł1–ŠNťDß·¬ ÇL§? :ÚYS3oÜ 2b~ôg ó(š/¨y'ć”´¸ç@I%ťJšZĐö3 lülŠY±=± ^!uaŻĽeAöč(†ź34ţܵ˙ĎSşaţ^ŔrŠŃrmgG`+n]8Ôīż‹ů<Aú%Wg{Ć6|AŔ6’t„Ř đRšGÝą.Uěî±SW0fľ‰pjCń ş]ljĹę¶ĂzóyxQR!äST´úȬxN˝łŰ>¨_Ă`ń řHpn]áĽ.2Děň{µ¨đDáÁí¶|’>– ÇÓö~­mWĚÚxIť#Żg—zžČ;‡*Ő~¶ďĹ ¦ëŢziť9§�6@ű<ű˛ÂÔĆşI&o /Ţ…tŻÔ,=?«v•s»ÜĄŇÎ @™R)e~3ŰŃëŤűcęhiI—·Ëť¸Ą9«ľ9Ü|F{˙C2V1ŤŤ°÷ŢSYęôše@µ 9_, í ń9%¶óŁůÂúĂbź…#ć2ä`O\úČf+e^ěϦŹę«_~úĆaŐgŰÖ;¸i † ď‘:§3mmŘÁÚţ„ ź{w©!dP-ĆćSęgŁ0şÂÁ[Ü{efOŠ –s@¶ôĎŃ_!”um˛hńM}ő!Źĺ‹ë+Ň&‚6YH C‹DµŐ{_ĹĹЧ0T7m`)C/4 Ő/a‡ •uâ©}XŰăcF ‰›Çš“SéşÄGÔĂ€bý�ťzđóąEísÚ‚XĄçs Ú˙Ů‘ž˙:°÷dţ%ÎN—fÉóá ôVÍó ç掶¤CXĎIýóę +{A:3ťÎ×\4ń&�0]-k•Č�M$8ŁÄúâţ…éă§Ťjë]at ¸˙±/ć‰h& Ě(ŢŚj MÎśűĘ+«ó˝�ăĎąˇÇÉ8,'ÂC˛Y0‚ůnů­eCÄ«®"ˇĹ«B¨ű1řęĺŚůN>R.pń1ü§)L­©Ó…WRW‡ŰZű°Ĺl˙ç„ŁŞ–ÝŚµé¤ &Ě‘ÔÜ+UáĚ&Ú«u´ýÝÖ~XŞĎ"cÁ"l@u¸Ń0ľůňČý×Ó'Ř ‚S˘XŠŔăZ¨\%téÔrŠ‹n¤Ą5Ěç*¨\QzáĆ=@%â|żřŃű.TÂO"µŘ¸dGúE"sXYŕ!ľäx­}M§ÖÎAdLąÖ\•«üó�Í˝lö·¦˛ńeQMYĽ’ăĆÓŃęěÎâĐň^§źşŠÚđ­)č›ňę)—*ńćö} q ç‡ 0‹ăe¨ÇpwvšÓ ćĄIÂŻĽŘW‡ô,2_FüZlŤŕ:Ô%,,ťÜ!ş9ľ…yÓ”)[Ź~CZD,ÚëM“ß|NŔů_¨ĂJ[‘1hëäÔnĄĹ-Ş^÷rsĚ«§ymLBÁ÷Py‡…<Z÷Ă6Ó¦CAĹ–%G”ꍕJtćľ'(©{ŮŁő ÷›¶úŘ2d±Ţ§-MÉ9ŢšëÇd˛VôxÁYů‡­­ ÷8 Ô˝.Ä-ë:ŇÔ‡hľ„¦ ľĺ+xE/Ř<ŠG n{VŇłYzVÝ>˙k#”ŃQ{óxĹôgü[ ŘH¬­aĽ ůݤw˘wGŐźů^;Ś<ó?ó¨<ˇÝ!ĆbNmÖđ<^Ë´™É ýPÖËťŇ6»y^‹ůˇĐ†¤ż•;Ń„@Š˘¤vf+]ű˛2{ćˇÜVFŞÚó¨e¦&™k1ŐVîj6Ľ_Ł™f˝×L2bJŻSĚŮmýť«3QIÜ]ą»ÜÖ�ć_źŁ‡đ8řź<u›x|~é.2„»”ŹoůóHęsM˘üť4Śx]Św7u…%Vą`‰ERyX¨őĆH˙…zďZ^ÎßSXĂźşďŁĎ›8[Žžw´ć.zPł+íĹď_G}ĽKŤű±.M·É´'(˘*˙…l‹1)číÁ®±Ř+B.çeÚupxW±đČ·m,´ŇŃü<ńľŘVôŚ 2•s ‰ŽěŤFŚŇ6úŔ+®#KWzGĆ?“wèPh<!츬bŢŰBŢ,jđp=zŇŢ _~&§~Éá—ďG2` !ű‡©|/i›íÉ”/R\JľßĹ'¬.qíW’ŻAmşą¶éJ9msұÂX~‡¶%AéŮĘžoWUfďč¨ßČÇ` 8G“ˇTűŢ|´¬­5ő\7vôUB»gĐrtŔ ±1WZcć�Vźž,~Ş˛#+0®÷^_Ş„Ĺ2QU.‚Ł‚/ß–;ŮWȬԝ¦@7 şÔ°”X;îŐňŽ×qĽĂÖµTa_Â'ČnPń®f˝&ň´ríőQÓLHf=¤Ŕ'ÚRő‡ŮŞŻäLó?;ť}dn9¶6`°é¦ŹëmŰőm piŮ’ď`™ăď@Âűď;¦­ŤŚnrÙč–~á $€_¨jÍ3Z őíÜŕŃŘ­Č8ŇüˇŽŁ×’ ‘°ber[ááM˝kuć}üžĆćö%L˛\‰!IHČY¤î,Ů'ąywŻČ'\ô:�?$ÉໆZSU ÍÉÜ@:ŇgŕŰeھƮţA%a-d˙qÓĆX?gçË»ăČąŠ ń� iBâ˛Ć=é¦Ĺ‹"Ą*˙cdőSb3ç´p¦Ř#ČçYýÂ/ă†ÄôZâ+<˘?s1Ň®Iďs5ÜÚe˙4Rx˛ŕÉŻ{ŁüRß‚FÔ¶OŽüý7!R$_Šô…×ôŁ gľť˝üĘ[ŞLOŮ7Ę0±}5OËĂ4Ę ę{Ć:PÁŽ$¸^­ S ’Ńó{‚ݤ=vĂUŐ‡pPůµě~')Ęŕ#—¬C;}4âŐş5 N?z(X°¸:Pčá4u ÁÇ $?KŘŢhCŚluI†Ő=·< 7ŢR”á॑‰‡”.đŞě7L­—í‰MłúÜuŻ-7s@Gqż˘ďŇ`4ŕ,ä|__S5‘@ę1áül]���_����APETAGEXĐ��°�������� ���������������BPM�6�������TRACK�2�������DATE�2001�������YEAR�2001�������TITLE�full�������DISCTOTAL�5�������DISCNUMBER�4�������TRACKTOTAL�3�������COMPILATION�1 �������ALBUM�the album �������GENRE�the genre �������label�the label �������ARTIST�the artist �������LYRICS�the lyrics �������publisher�the label �������COMMENT�the comments �������COMPOSER�the composer �������GROUPING�the grouping$�������musicbrainz_albumid�9e873859-8aa4-4790-b985-5a953e8ef628$�������musicbrainz_trackid�8b882575-08a5-4452-a7a7-cbb8a1531f9e$�������musicbrainz_artistid�7cf0ea9d-86b9-4dad-ba9e-2355a64899ea$�������musicbrainz_releasetrackid�c29f3a57-b439-46fd-a2e2-93776b1371e0APETAGEXĐ��°��������€����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������beetbox-beets-01f1faf/test/rsrc/full.dsf������������������������������������������������������������0000664�0000000�0000000�00000337626�14723254774�0020360�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������DSD �������–ż�����\ą�����fmt 4���������������������"V����€Ü�������������data ą�����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������ID3���� 0TIT2�����full�TPE1��� ��the artist�TRCK�����2/3�TALB��� ��the album�TPOS�����4/5�TDRC�����2001�TCON��� ��the genre�TBPM�����6�TCMP�����1�TPUB��� ��the label�TCOM�����the composer�TIT1�����the grouping�USLT�����eng�the lyrics�COMM�����eng�the comments�TXXX���;��MusicBrainz Album Id�9e873859-8aa4-4790-b985-5a953e8ef628�UFID���;��http://musicbrainz.org�8b882575-08a5-4452-a7a7-cbb8a1531f9eTXXX���<��MusicBrainz Artist Id�7cf0ea9d-86b9-4dad-ba9e-2355a64899ea�TXXX���C��MusicBrainz Release Track Id�c29f3a57-b439-46fd-a2e2-93776b1371e0���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������beetbox-beets-01f1faf/test/rsrc/full.flac�����������������������������������������������������������0000664�0000000�0000000�00000052602�14723254774�0020475�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������fLaC���"����÷ Ä@đ��¬D!îÄvoWe×l°÷�������������������� ���reference libFLAC 1.2.1 20070917��� ���TITLE=full ���TRACKNUMBER=2���ARTIST=the artist ���DISCNUMBER=4���ALBUM=the album ���TRACKTOTAL=3 ���DISCTOTAL=5 ���COMPILATION=1 ���DATE=2001���GENRE=the genre���COMPOSER=the composer���DESCRIPTION=the comments ���TOTALTRACKS=3���DISC=4���DISCC=5 ���YEAR=2001���BPM=6���lyrics=the lyrics���grouping=the grouping8���musicbrainz_trackid=8b882575-08a5-4452-a7a7-cbb8a1531f9e9���musicbrainz_artistid=7cf0ea9d-86b9-4dad-ba9e-2355a64899ea8���musicbrainz_albumid=9e873859-8aa4-4790-b985-5a953e8ef628���label=the label���publisher=the label?���musicbrainz_releasetrackid=c29f3a57-b439-46fd-a2e2-93776b1371e0�����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������˙řÉ�•@��µÇ!̡'†hPćhRs%… $ˇaäĚ̔Ô9C9)™<ˇC™”ĚĚĂ“%8Y”ź)…% aś(r’‡49CL9C%RY’Ě<ž… dÓ ”)™™śÎd¦M 礧2…&yůgĐÉĺgĘaá94ť% Lł9ś(Xp¤¤ˇĂB…3™e% B‡ D™ˇ= ˇ@ł ) !ý Ě’s(NN’…2P¤ˇĺe&faʤĐĺ3$C'3™ g$ł 3”%8RS%™"Bś,’” IC„By— °aN”„Lä‘ ”Ę”ˇ”’yC“2i…9˙˙¤ˇa))™B‡ f,0©@¦„ˇIÉţ†Re ˇ(XSť …†Ng:OCž†sˇC™C<3Ě4ÉL(hĚ9ĺ”)“"))(Rp¦Jg3™™S39ňĘI’‡?)“)9C3'(e äžPĘI,)(S L”)'&p¦r†śˇĚáae ”2P)2HD%B†”†Je2yB„P‘‡‡(~y@¤ˇ4)“L<™2“¦K(y(”2“‡3ýPç)3™™”)<ĘaCRˇg)“)3ćS0Ą äź”ÉŇ2d@°ĺ$晡C…†s,ÎC“™ XzBś)™’a@ҦĚĘĘI伡I’‡%0ćRe'ĘPÉB’PĚĂI”'&RP”2yNe2 ‘ 9ILÂ$2™)"% LˇIš”ţXRe&P¦p(L¤ţ†r…Pć…$ň–,2’P”2’g JÉ43šˇCL)™Ěó”3' Đĺ$C„ÉMRS%330%3 pˇI”2“†IBr’a˙ůL’™2“ 3”É)'śˇĚ§= ’x|)C9'če$Δ dćsŇ$"I…9ô” 0§2̦g4) )(J¦g p¦fs)†ś¤ˇ…™¤LĚ“ÂPçĐ)’„@ĐäBr D…„ˇᡒ¤¤ô<'BP,(g(aaNĚź)ĘC930¤“ĂĚĚĚ)(RrPĺ0¦L¤ô9C””9śˇÂ!™HD fJ ç ÉB r“L‘$čg= 4Í ”9˙<ˇÉ™™Ę<Ě™L%”’JL9ü¤ó9™C™L硜ˇIB!(D9™ä§ L¤ŇS%2RRPСáI…†i4ĚĚĘ” fr’r„§9HD93ç@Í Ę3ĐÎPĚÉ™™™ĚĚĚ‘r™% 8dˇĎBPÉĘLó9)C9”3’S32†JdLˇĘ¤¤ˇLáˇĚ")Ě,™Ič””90áÉL¦O”(g e&Rt3” D99IˇIL4)(y‡(g?@§ L™I”ź)ś)ÎIB’‡))(r†PáC””9Ë sL2’O)%(O<ćJJJJůĚĚ,™I$C!L9B„ʦMçB…‡(e$”ˇ2Â…8y”“çĺź˙‘L¤™™%0ç”)'IáNL)3ĺ I™’r‡™ ÂśĘ)“L””ádĚ”š9ť'IL”””9II)3?ýś(g…2R†fL˙ô2“!(D ™C32Jaś™L,ĘJrfH„ȇ“<)™…&hfPáC…8r!9ĘJrPJ”ÉčffdˇIĺ9ˇB§Đfx˙řÉ’@�µÇh!)œ“4)2™™śˇI™™ÂśśˇĚćfHˇ™L3”3źC”ĚĘĺ&s9CśˇIB“ĘĘŕS‡2D2RRP¤Ęý đĘL¤Ňz:†e Ě™ç˙ˇÂ$Í B…Đ"LÄ2S„I„Iśĺ&|ĘLĺ&PˇĚśç(fdša¦M™ĘL¤Ę”(JLĚ)(S'2PÓ g!d–JfP¦ffLĺ$§ fL¤ĎBP¤¤¦fK3̡BP¦aIĚ”)2†hg4(hD Lˇśô' S% r\3„@°Ďĺ&PäĘ"žILĺ p§8D9™ĘLĘśÎffaIB’…% NPžaL”)(y(S“)0˛aIÂ’…' J ™ň…&P9@ćr„§ fL¦¦M… LáČN(RfP¤Ď(RP¤˙‘& Îd"rfs9B“™“ňśô(Jr™(Pť Ě” Jˇś D”)™”(s'3ś¤§‡3Đ™L)3ĚĘYśÎPćRe2JJ’Pĺ ć™4Ă”)8P¤ÎfJĺž’™""ÎS’’IIBp§33%8\9žff4<By˙ŇS2„ˇa ¤óĺ'(R¤ţ†Nr‡…&s̤ź2…&y9“3%hOĘO”ˇ<Đ)Ă)2™™™śˇI‘ ć ”9IC3LÉ”¤"LádÂ$Â$”) Re'ô)2g?C33'ÎS'C9ô JyL“3&’‡(y(RPĺ39Ni‡“L̡ś™BS%’śĎ™ćfd¦JdĐ礡IB’™)’…f’†…&b“4(s(RPóPçIBÂÎPˇ’!†™’™48XD3™"9…2hRf‡I@p§2D™Iň™2™:N’‡ püd¦P¤ÉI–Éś)9ÎhH@áO(D Éĺ&Rr’ˇIśÉIIBÂ…3 páIšJdÄ& ™ÂĚ”‘ ”Ě,’™(sô3IŇtšPš(S'333% LˇC™ˇB„Ą ˇś)(dˇBs!$°¤ YšÉL“„@Đ"0áĺ332S%(g3<ÎPćP¤’™)”4)(L¤˙C”2ˇžIÉB“4”‘Ă(sC9@¤Ąś™’‡(y4)3ICC”ť CCĐĘL§ rfH† ™IˇI”)'(”4)(S…9śˇÂ!“xf†sĐ”3™aB™2† L) JÉI–ź¦Éž“čdůB“™'̧4)8r…2Sś¤Č“É)™4Ç3ˇĘ9“)4(XP‰3ĘdĘNRr†RNfe&S'(Rd¤Ęd Rfe0ĺ źĘfffJ)™<ç<ĘIĎ9C’‡9Cśä@ĐĚÎK( ç2Xg%&S dđs… LáĘJs8Y%30§$Ľ‘2t)(D% aI””)(PŇg)‡’r†RLäÂÉ…™ĘLćfsĘ™$C aš™,úĚá8P8S&™)’’‡49C”3”)%’2e%D Ndó9)Br… I™)’’ś? rÉ””ɦÂ…2e'ˇ(”"§2xg !”Ě̤Îsś"Lĺ3 L™™Ę g†Nd¦H…Sčdˇ“C„C“% ”9¦BP¤ XP,ÂĘśÎH…&é……9™C'ň™'ϡ“@ą˙řÉ›@�µĆŔ!%'dé’…' áNfRLˇĚ")%2S JĚ,™“9šdˇd‘&@áI’!9śĚĚ39é)…2ri‡(hd8RP"J” …!’… ĺf!ś™BS333™ fś°°NpdĄĚ)˛‡2…!JI”''IC”32r™?)™“ˇ= Éśˇđ§(y)(XáB“<ĘI”93‡(g='ˇ’‡=…'% LˇLÎ|§2ĚćfaĐÉĘ”3“2†JdˇI…)3B‡†“&BĚ)ś(SJ……$ĘC¤(D…9Ęg‡ ˇI™I”’”2D(Jt3Ă”39é)= ˙Ičr… Ęg p¤Ď30L¤Ą ćRNPÉB’fe2e&PÎO”ÉC3””Ě̔ɡIĂášJs”Ă™ĚĘĘÉL)(P§…… JÉśĚĺe ä‘$Ęd”™CáNĚĚ”2~RP“3<ˇIáBĂ)3™™™™™™)(sĘa<”)“)))(sŇĐć‡<ˇˇ’!™“Ry„I%32RhRND2~D4=0¦NaILĘB$"LćRež“B™™ś)"3ô&S&RNds>e$ĘIC„I?(RaLĂɦÉĚ̡IžS…9Č“4Ě)Éš Nr!(RyB’xJDPĘ99”9ůe‡=0ĘLĺ…%™3™ĚĘ™ˇśˇÂ!(S&RćaJIáś(S'0§' 3ÉL”)̧ dó‘ žPćg0‰2PĐĘL°łBˇ’PÉ”šg>i'Bee I…8RP¤ČNd‘ śáNLćIL(Rg ćS% ”&JLICˇBPJhPäĚĘB”“ś¤ĎźĐĺ%%% ¤ĘJaĘB„ó–r$™He$ÉB!†™?"ÉĘ~s”3™LçĐ))ú(Iˇ Rg(g0‰3Í’ž†sˇČBĚ4“śáśü¦ffL¤śˇÉLšOIô9CĂ2|%PÎ ”Ă)%2PJ ”(pˇC9333š…0ÎhsCť śĺ Ę,ü.)=!ĺ ¦NRg” &rPÎIfg0Ó'aš…“3ćRfe B!3™)Pá@˛J8PÉ |ôĂ(PáN†PÎJB…&PáÎae%32’~dBag2’|¦s@‰0ů™’S33' ” ”&D'0˛M ”(P§9CśˇĚç3Âś)")’!2!ÉĐ(RPô9čdˇÂ!†g(Re'ü¦JB!HS)“)’L”)8XDe0ó ™”)2“ţS'”ź)2“”ÉůB“2“úfs9™C93% ™ÉĐÂĂ)2†rPˇL”?)2… ć'ĘLˇĚĺ'ˇĘsĘdňs†JIL”Ě)™2!†“3) RLňaILšaIɲR‡&sśćH… LĚĚ”ÉLĚ̡śĚĐ)(D39%'¤"‘ śź”2S%% 礡I”ÉL”3’™™?(S0Đ Rgź”Ě”2Pˇ)üˇÉ”„@Ś€D…„ĘM0”Ă33&“¤ˇĘ™'ĘJ”™ç)™’†ÉÂś(2e„ʡĎĘNP¤Ę)’g%„ĘJśˇ2!“™’†IÉČ„ĘY&’t&S$ł,¤é†…'% IC% ¦™”8“˙řÉś@˙˙µĆP!9BR‡™“ÎPđĺ”$ň…'3% ”šdp§&†NfaI”ĂĚág2’eB„ˇ’†O”)™ĚСĘ”ˇś"9’…&i)Ď"(fg?C'ĚĚ™9śü°@¤Ě̡ś™C8D2pˇÎfs9śĎ$Iś°ů”…2†s rP<“9))"xL¤ĚˇĚ.Iˇ‰RR†r„ô śôĚĘ’” JH|¤ůgÓ0¤¦e áLĘI4ź”Îç–ÉL,"8r™)’™ś)3IĄ ¦Â!™…“3„@°ĺ$ĘL"ÉB’źB„ŇIˇBR‡‰™“Bae2S’…2O …(M ”š™aNfe…% r“)%$¦)(ú¤)(d”™ó"y2†…'ˇ’†Lˇ“\žRe'”(pˇfS „ˇ¤ĚčfP)™(hd¤äC‡ˇĘfRdBe ”ÉňÂ’‡)4(g0d§)<Â!„I<3’…&yB“śĐ¤Ę“ÉčfPç%2RĚňĘáBÉ™”źĐÂĂý&…&tť0ňtÉNLˇĚ¦r‡RS aä:ϡ’… Ěś3C9C9$C…3$C”„™Ěó dÍ'L<”)2™śĺ$¦IBćfrD)ffg% !JCL9”3% LÄP¦NM0ňPˇIśÉfLˇĚç9C™™C9śÉĂäĚĚ”ĚĚ”) "™™’”š™¦g(sň’P¤,Ę™43źC'™Îg3™I”™L„Lź)™:ž, ™IĄś3C')’s$@¤ˇI)(S%2XD$Cś"B”9śˇśśĎ9C 9ž~YˇCS… Đ3§$ąś)“™™śÎPÎgĺ2J“)(r„ˇC“2e%ś2™†…%™")śˇĚĚÉB’g fg2’rS2áaśĺrD)3‘„B„PĘ™C”„Cś2““4ÉLĚĚćyĘÉOĐááC38R†rdˇ’…'8D9ĚćS!°"dčr’…&2e%PĘB‡“™2 P¤ÂžS…9ĘNPСə)Rf’…&Rr!C”(Rri…2s2RPˇĚÓRtšaäˇI(p RfP"9Ě‘ ”)(RO% 2D9™ĚĚĚ̡L™@ç)śË3”3“™á,(hRC (žJP”ˇ=ÉäÓ ĺS3śĺ će2hPç (dˇ’“"r!3ÎRg(L¤ô̦B”“˙)(ICý’’‡…&|ˇC“™ĘÂä/”™C“(s …|ĐÉü°¤äB| (Iň…&r†)™9™™)?”ĚÎ}“‡3”<<’…!BÉ”3Ây™”9”ÎdI'”™dˇ¤Ą Ę9BrP„BO’ F,ý ”ĂĚ9’Ę“2rS3%%%% IBĄfP,:R†B%…JI”3%‡3(g0ł3™")’™:JfP"\ˇIź2’fs9™‡(̙ʔ"IćD) ™>P¤(RrRĄ!BÂ’…$”(JPçˇIBĚĚĚ)9™™™™)(r“Ňçô'ü¦ađô%Ô̔32P)3Ďź(y…2pˇĂś)I<9”9™™™)™Âś(S3™™™)’S&D&D3„I”ź)9I”ČDÄÇ˙řɉ@�µĆč!339śĘađ§ dóÎfaĘ!s LĐÉB‡ N“L<”)8g?Ň…ÎP¦L¦JP”ˇ4(JBa&P¦Lä@Í °¦fM ç˙ˇÍ L¤ˇ”38Pć…&RS‡g=De É”„I”3…0¦NJJHD)9‡’Y@¤ˇ™ĺ2Pˇáäś˙)(L¤”“ĺ9ŇzM0ĺ Ě,Ϥ(X&xD™”4 J)9’™’„@ááe'I”” JÉĺ2|¤ˇ(p¦NP¤Â!Â’’…P¤ĘM Jfd¤äC'30§!a”Ă™”8D8D)(Dśˇ)™†R NĎ)>čr„ˇ’L™@ÎLˇÉćs0¦aIB‡“L¤ˇ™™‡’áBhD $đˇI”Ďśˇ™)™’ P¤ÂźË 9ÎRg33338S3'333$C'%'IŇP¤đä@ňdC338D4(Y3”% “2D(JJBP˛…&fffpł p¦e J“2Pä@ĺ%(OPĚĚ“ÉJ™)’™Ę̤”™IĘd‘…3 fg3™ĚʡśĚÉL””ÉLɦ“ĺ2t °”ČDÉ32S&’†„C™ň™9L(D% ™I9„C%'(rPÎe&S'ˇBRs:ś)’Xe&r’e!¦JaL™IL–g9C„C'2S dä¦yůL<…2hfp‰3IŇt(dĄ čÉĚ”'C &RM% s)2™(”<ˇÎ¤ź 4ÉL̡š<ÉĐĘLL¤¤¦g dĘ™“IB“†p¦N‡"8e Ě™B‡3˙čp‰(ćJB‰“™™’!Â!IśˇĚůLĚĺ!9ćrS%2S9’’†xs¤ô32|¦NPćRz9Ędé;&ɦ¤ˇLĚĎ’’ĚĘdčr†fL¤ĘfS3™ůNĚĚĚĚçˇÎD’RhP”ň™:Ă”93™Â™9™ĘNS$Đ“Be$ˇH_üó9CĚć’…&)“ňśé(Re0§$ D”)ĚĘÉ”332S<ň…dˇÉ™4źčäź)“ţ’…&RzJˇC… ‘R…PĚÉ”™Ę(SPÉI„”))RP¤Ą ä”ÉLćRIILÂ…!Oˇ= ý&”'S$ňS”™Ę™(S0¤Â‡ (s,ˇĘ2fr’†fsˇIš™Â!Îd¦M0ňPˇĚФ"!JIpĘNfs<ˇaš,"ʡ“ćRe‡(Nd¦fJd§ 3™C%% B‡ =%RP¦g„ˇäĚÉ”‘ ćOš?ô(JĂ„@°ĘM0ËHD  R š™ĎI‘ Đ)HR’Rf“C„I…)$ˇĎIˇN¤™I”” Naˇ4(9Bžˇ43™žyC@¤Í äd§ĐˇĚ˙)“ y'3”(s39™…2s Lˇ”™IB…!BÎYô2r‡3”3“3%9ź””2zÎI’d¦g˙B’S!IůL9śĚĘ̤”,>`A“)%8r!)ô2„Ę|ůBŔrddĄ$ĺ B– J™’™™ćyĘ y‡(dĐÉBLćK32P¤ç)”%čf‡)’™))(RLçĐÉBe&†y™śĚÂe&)2’’†‡))(sŇzaňî’˙řÉŽ@˙˙µĆČ!ĺ” 0Ą B’IBĘĐ" XAPˇ2y…™L””33 Re BśÉL)’”)8PáLĚĚÎdađ§2Pˇ’”% Có”3™Îd¤¤ˇÍ'I¤–P<ĂB†…32‡(PÎĺ&S3<9 0ćrS%0ˇIB™…… ź”Ě” NdĐ)d"LΔ)3Cś¤ f†r„ˇIIBÉśáá‘ 43…2JL¦OC”š2S”ĚĘdćfg8Y’!™…2Pg„ĐÉB“‡‡%gţ~˙”ČDĚ)Ě"pŢ Y…Č\’Ę… Đ)(“ĂÉ40Đp¤ˇIBś…™™Ă”2džáe ůˇ&„…ňi(Rp¤ˇaNH†fNaćg”9…(p…™ĚćyůˇBxPćg2…!9Ěó$C%(‘’‡&pˇI™Ęś4ˇ”ÉÉ32P°Js3%2i…2hs>RPĎ0¤ĘJžg(y2Â!<ôš2†pĄ$B‡2fJfP¤ĚĚ”,)"&fsCB“Ě‘0řP˛N„ˇIša)†Re3˙ˇ…Sž… Đô”šdˇĘ’‡y(RIr8S39ÎRr…ä̇É&PÉáË JfPç†áLš¤"ĂB’śĎ™”&DD8e&’†…% H)I”Ě㇠p¦g ‡PćfPÉĂ”9IL”ÉIB“)’…&zB„¦OÍ!))(Y2„Č„Ęfr‡(L‰2’’ Rg ¤Č„ó2“’M ”<ť2PˇĚ–P,2‡ fPÎsĐÎPÎe™JË…8RPˇĚ¤P§(faĚ,™CS… 3™™(s)…(JfRfK0§„„ł%)‡Â™2†s<ˇÉĚćJ9”ĚÉL“†pR¤ü¦N‡"(r‡‡)Y0dŇIfrS3<ĺ&y™…&S32†gNICźČˇBPĘaňg2†rPĎP)’”<”)”™LÜͅ&fPç rap’Čr‡)(hP”ĺ2PÉĺLĚĚ"L‘ @‚ś¦L¤Ë J™(™(g&e s%% ĘađÍ ĎC”ž‡(S33'% J,8RP))ž†sBf¦ĘL°¦OžP¤”)39CC”„B“9C„C™LžS8P‰ ç(g&PćRJa̤Í™”“™S2P°¤ł330dćO…8hR9C)&p°") PÎĚÎRe3ť2S2“"…&g 3Ęp§'(g0LĘ… C$ˇC…9C”2Pç)9śĚĚĚĚĘĘaĎ)™ĘHPáNdI„IC)2’’!ÉB’gLÉś3’śôÂ’…3' (J3CB‡&ffe$đĘB……”<3… Č)čd%% ”$dR) d¦HHYśĘaÎP¦aC33…8P°Ë f!Jdś“Ă9ůC8D9ÎdL"d”Τ",,"ÎP¤ D™IC’!BSś¤ˇ=™“% ”ĚÎr“ś¤"Lç3ĚüĐÉCš™C””ĚĚĚĚÂÂ’!(S…9™Ěĺ ”’IžP¤Ą đΤ%0”) RSúĐááC”2“(S$ˇH|3IB™’™"Í ćD’|¤že'ý0řhD!0‰ s™™)(Re39”ĂL§aBÂĘ’áˇNĚĚćp¤ĘN™6‡C˙řɇ@�µĆŘ! (e&J2„"d¤¤ˇL)Đ,Ę)XRR~RNdNaĎţ‡9Éś¤že$ˇIĚ”) 2RD JćRO3” dĚč™Cś˛‡™)“IĐÉóç”) PĚÎL¤’Rs 0ł JI„C%9O&™)48YCÉB‡'’„HP˛fH‡9Ió PŇO@ł4(rO”ś¤Â&O P“LůI”(RPô&0¦g$ĄJ…)%2hd RdˇI”$¤’!2 s‡y‡xffN†J†hNRP4ĺźÓ…933&Re!äËɤô9C”9é†hdˇ…’~S'9C8D%8DĂ™32RRD NLˇ”™¦J B$)ĘaN‡4ÉrD) ÉB†… Jś(s4ÉfáNsĚł)HPˇCB‡(sB‡'(Pĺ d§(Sd”áfP¤)HS3%3%%P¦frJd¤ˇIB’…“9C)3˙I)BJdĄ0Ą ”Ěĺ ĚůLžPćÉĚĚĘaNLÉţ…&S39""ÎffPůB™™2 s4'BJaIš"(dŇzJś”¤(fPćP°ˇaB“=” ̡̜Ę% NRO2‡Â™™?@§ 9”9é(PСI”<šd¦r…“)(RS3”)3”32rS&’™,ÉCIÓ ¤™ĘˇI”ćy”™I˙ůC™śĺ¦K‡ÉIL”ĚÎPСä,¤ y™:)“)S&e$ůȆJ|ˇa˙úaL“2z8s)&Pá̡ś)™’!’™)™™Be'"I432 FáL™Iç"̡śˇĘd§ $R"Lź)…ČS3%™”8\ˇIL¦™)…% 8Y(pRNIL%RbĎśÎd°ˇˇ˙”” NdˇC“8e2Pť!BÉś)9CÉů)<2ÎS'ô(g&sŇtšˇáśčRp§'ĘĚç(ry…’S3ź?(Y3”ÉJ“y’!žC% ‡<¦f™Ďţ’†„Bg%2Y’™†…%39’!“™)(důLĚź”ĚÎPć‡4)0p Yť áB!3'3śá¤ţN™™S<ť ”ÉĐšˇäĐĺ%2„ćJfp§ fd¦J@°ˇNtĚĘL¤”ČD™Â™śü¤Ě””)(S“(psúˇ”%3"ňs(dˇLÉđŇ„ĐÉL”ĚÂ!HS˙ţY”ĚÉBP,™ĎĐÍ0°ÉB™@ł'33 dČĘdĄ!BÂ’!(D%&IdĘd§0°ˇĘJf™ˇĎB|Lˇ3%… °¤Ë%9ÎR ĘĚ)™žS'ĘpĄ$çü¦fLLĐ,(prfO 3ś¤Ęa̤”É(Rs)8S“9†… OCž“"a”…48S2J"™4Ěĺ')“”2s3ĚĚ8S” (ĘfffLĘI”D8s%&…&S$Bd@¤ˇIL”É)(S9šĄ$ĺ&RLÎPÎd‰0˛ff|Ęa™ś"™3(™Éé4<„HeS2e%2S&’… LΓ¤ˇLÂÂ……39IBP, s'ň™%!ćaˇ’aLĚÂÂ!ÉĚ””Ěχ fg pˇIĚ)Éže&R~hD(Lˇś))“LĚ,Â!ˇšy2k»˙řÉ€@˙ţµĆh aśśĚÉBˇHPˇĘˇ™™śôž…9IĎ ćg332R†re ”ˇÉ"Hy™ĘOčg?ˇśň“(PĐ"LÎfaL™L2Ŕh&rÂ’„˙ţe!¤-”'"9L”ĂĚĚ)‡3ˇ™Â’„ĘfO”“ÉNäůó9ś§)™™…8S3%2tĂ)“Ě生)‡e&39C3%É9”3’Y‡)(hRP¦NaBˇI(s)śˇC”(|4ÉIˇ)˙čaf} 礗'IICšaň†hg™đł3P”(y9™”’…&JY„L2“9fSfe ”(J™ĎIčdčN†r™ś”ÉIB!'%™2†PˇÉÂ…$¦M’áBPçL”Ę…0¤ˇO'BP"B„C% )‰2…PˇˇC”ť0"J”3źB‡3C9”ˇ<ˇCś(y™Ę9<šaĎůćs3(g2‡9)ĂÂś)™śé= @¤će$ćD2P¦IChD% JJ2“ ™™(P”"% ”’™)™™Â!™™(RO&’… dňg9C% N9'Ęs‘&i’™)’™)(XS0°ˇL”"̦O˙ţ†eaL’‡%&He$¦ffJ%&r“ç(s9CĚÎPÎS$@J,’PŕA B™’†Oʦdä@¤ˇB“"BśĘL*"B™„C<…(S!|¤˙čPÎL̡ś”2S)“úç)Ă”Ây„BP „ˇB“ÉL‘„PňS™™"3ˇHPСä嚡‚™áLžI2„ˇaI@°ô2’„Bg ĚĚ(Ráae á™HYBS0Ň„)“IC@) dô™fRĚ”2R†fdđ˛ä’!„˛hP˛äĚÎe2†Jr“Ę™)% B dáfe%™ĂĐ™I„C…(sĘÂś"ś’äP”Â…‡3”9™ś”)2™)™’!Â$"&LÉůL“”’P)(PćffJf¤(D†R¤ž<ĂB†y…s„šdł8R„Ę“B‡2…¨‘Ě”¤žP¤ĘO9Ŕ°ˇÎ†sC”„C… Ę44,,"2“ˇI„ĘJfsĺ3 J2S39ÂÉ”9śÎg(Rs”,Ď(rs„C’^Sź”̡ aN!@°ç(O…)33™”8Pđť ç)0¤äˇI”<”ĚĂźčr’’…&hRrS&†fg(hr„ˇ™C)ł$C2“ ̦”&†xe!Ě)’ś"L˙ȆJPÎC”””(s4ž’‡"a¦JfM0”2P)ÂIO”ĂOC”)9…3$¤™“32S&!“Âś„I„C38Y”"(r’…“9ÎdL¤é‡’™"ĘC:B‡&dśÉá<“Ă‘…”2PÉC'Ę™1 ”ĚĚ)(Rs332S’!“ĂĚ)2……8S%3(s?)™Í Na¤é4(ry)’Ě<ÉLÎsś,™™™…&Đć…&S0¤ˇCI…“9™Â“y(RRX B!(D%(rĘ% Lˇ)Î&%Ě“ä‰Ŕ‰(R|¤ˇ0‰2e!Đ"BśĚĚĚšN†só8P§ p°JH„ˇaNÉ”ćPáśÎPó'ţ†p¦OĘJ(PáB“,Îe$üüĺ$ˇIžPˇI…3™”8D32rS$C0ęŕ˙řÉ­@�µĆŕ!™”’S NaLÉ”””2P¤ú愞ffJffRˇ“™)’” °“C9¦M% L¤ˇĘô8D4(y:”3D9)(… däˇIĚ”ç,"†PĂáčffd”™ĚćĚ̤Â!Â!ÉB“?ˇśĘL¤ś¤(D¤ňyI@¤"%2ä,ˇĎň“4r††S <§2”‡C9IBs>‡‡&PÍIÓ L™ĚĚÉC@JaNˇś<¦OĘI"%†NJJfd¦aśĘdô J„I2„Đ)Ă™ĚĚĚ̡IĚź?”Ě"ÉB„ÎI RPĐĎ 2D2S$C%2D&D'2†rs9…9?”Ěź)…8PĚÉžg9ţ‡(frP‰ áLĚšL‚HĄ&S2„ˇ”(i’™śĘ™C™ĺ L̤’&INJRBĘáĎC”)2“Đ̡ÉBR˛’…'0§ s“?ţe$.Oň™:%<°¤ˇ= ó@ˇ"gĘdůNt8D‡¤)Ę8r’Ą%% Ě)ś(S&SR B$2!“™”9‡3LÎd¦B$ÂĘfpˇˇB™)śˇˇ9“9žP§ 38P,ˇLĚśĂL”ĚÎPćg(s™Ě˙ĐÉC9B…% Jaʇ †™†RIL)“™…8S…3’DÉň™3”% JJáB$2„óúJfp§&r‡% B”’S‡ĺ9Ó%$@ĐJPÎ L”šLpˇĘJ™C2e39IB™)™’™4ĂÉLĘĘ2f} ”3(s4ž“čdˇ’„ĘO)™™śý ć… O0ňNg”)0P”Ď„Lś™™™™P¤Ď!32Ri(Rs% !Iś3ž„ĐÉC””)2†Ra&RRD 2S'C 3B’‡"ˇ9@s(p‚!N¤ˇL„@°§”ɔÙ̡śś¦g†rNIĺ0Í …'(Pĺ ó”%™")™śĺ&s„@°¤ţ†Re&…!C”ť$dˇĘś)™™ś¦ećH’aá‘&s™”9ůLśË ćffJpˇI™ˇś‘ žP˛e2S9@˛…33ť2Pĺ ˇBD9L))’™‡“B‡3B“):)<§†rs"„B™É)“B‡2S…9™™)RgC”)“ś"(D%&PĺĚ™gŇP¤šś"…&†J(p¤ˇ2$…Ă90¦(S I¦f!JND HCśĘaćdĐÉC”)“)=0¦Iće$ó”śJ”3(p RgúJÎaL™IBÂ…’R†fJś)&dĘ9’‡3śˇćr‡(g Ę™“L<”ĚÉL”‘ ”„BP‰'>IB‡’|ˇ¤ˇa˙С–IĘS!Lť‘„D2R†p¦aI”ĂɦI<”,(sȆJ}’…†Y2’JP2D)2™4)(S' ˇś=Ě"Í3™Ě‘“™Â D…„Ę“"(dˇśé‡ÉФ2Â…&e Iá9(p‰ “„CY@˛e9Idä¦J”3ź9I@#0˙‘LĚĚĚĚś¤ô 3čg(Rf“"aäćs”™C”P,94fNRP)3(O3ĘĘĘ”2R†JaäŇ„Ą% Ňzd¦Pĺ™ÉL–fs9)™™BdBNz"= ˇÂ!'(Pžt(XpJ@‚ż˙řÉ Ş@��µĆ g™Âś(Y%„BP‰% LĘ“(r’…2J™””9C”9Ičg(RP¤áI@ł?ä@ćNg)'ś)’hd¤ţ†gM!) RP¤äBe'L‘É4𙎙@¤ˇBzˇL9šaLśĚˇ’‡†…2†pé%33„BP°ˇI™B’”)™4ž“¦ˇC™¤ˇaO9C… Nd§™™™”<>dćffJ‡ô3’™™äˇLÉNaĐ™9††M!šäHICɦH†Jd¦fg$B“:ň™“ćRg)2“9By)CÉ)’YĐ D™™IÓ†x !NC…$C9śĎ= IčJ%(r‡(e&P¦Ng9I”ÉÎs–9™śˇC3dˇĚú2’…%’…… dć™IIINáfK33339B’‡2XrÂ…2rd@ˇˇĂ””ÉL”š4(S2s s3339’!śĎ(Re ”9ILĚĚ)3ü§:9ť =ÉĘśź>e$ó)3”ś˛„ˇC38P°§ĺ8XD $ĘNPС̤ˇC9ž… É”źň™?СĚú"s&Pĺ ÎáNÉ”) ’t3…”8D2RfS%32D2S9Ę™I)’„ĘB!C)3’Pće$ˇIOB„ćL¦r’!ÉC“9™y4Îg30¦L¤ˇÎy… ”)śÎg3) hĚ4ĂĚ”"C"I”“)&g3”)(RP9™Îá¦HĚô3Ă”42PśĘaĘ&JIĘś,"ĚáC…C–çůʆ’fJĘaIś¤Ędź2’IÉý ffa†LćH†p‡<ĘI"9B™ś"„Be ĺ IB’…$C'(dĄB!·(sB‡“"(Pćň“9Ěó™ĚÎPćPäÎffM0ňS$B“ĎIé= ™C’‡3”’„@ĺ 8yIš33&@Éažç)3”™B“%2t3”9ňe%! D9Ęg‡' J32†Jd¦Jps')'BdC'2e&$Î)’™D8YůĘIň™I<”Ě”ČDS2”ÂY„BPˇäô…9,"…2e ¦ˇBS…%3(JfdP”"(g&†L¦Idˇf'Bú%?čfP”“2†…g(g&d¤ˇĘÉäŮ…9ś¤ô dĚĘIˇ†” RaCś§=30dˇLÂÂ’“L>JRˇáĘĺB”ł3”)śÂ$Â!ś(fRe339…2e$@¤¤ÂÂ…'% J™ˇ™Â!HR‡:aNe LüˇI…3 ˇš” :4 P™@ćS8SśPćpňP¤ĘJśÉI2RP¤ćfxĐЧ'(PÍ ó”8D’†aI8P¤ĘJ˙B~faÍ !BS”Ěĺ% ')ŔŚ’YĘpˇ¦S0¤Ě¤Ęfe áůLÎhS9™”&D…)…!Jd˙)’‡‡@ł))“L)ÉLÎt(rfJ%śĚΡ’…%% L¤ˇI”ÉÉáNPáćr“”9™RaáC9”Ěť áI”)†Xf“‘ ś¦fs˙ĐňP)ˇCś(J|ů”8RD%(y<¦dç30ŇP˛adšaL%Ă<źC'ĺ%Â…†C%))™)"™ÎPćKDYCD8P§B@­ý˙řy CÔ@��µÇ(©i*”´ĄĄ-*ZUd«(´Ą-IKDĘJYeDÂŇĘRË*YJ”¨µ˘©KQT˘ŇÉ•)ZXš,µ%,©(ą)QjJZ”©IjRL¨šRŇŞT˛Ą)K(­%rWJV*X©bĘ‹R–T˘ä˘ĺ)R‹JVT¬´Ą©J–RŇKQj‹R•µ*KRZÉU%©-EI‹E­*¬©U’T¤š-,©JŞ\˛UbĄ‹KJZ-eKZJÔ––¤©¤‰ŞER‹‹I”–´L´ZĘRĄ©–Tµ,–RRÔ’Ň•-*˛Ň–•YJ‰”Ą%“%JK%T©eIeJRĄ%–”\´˛©,´˛ÔĄ%©.-,¸˛Ô•”´«J\YZ¤ŃeJެĄJŞURŐ*,¤˛•,´¨µ%I2’ÔĄ)--IjR’Ô–YQKR#)-U•*©2•Ee¤ÉeĄ+RZ¨µIJ˘eJK*Y*˛ŇU%eJŞŞŞ¬¨š,–+,©)J‹TZĄ)e%”¬¨šYRËRT¤´«,¬µ*&I‰”¨ĄÉZ˘eeKRU*É+)JZJ˘Ę–TŞŃk*R´˛µ)R˘ĺ*ZX¨šQ5"ęT˛Ę”¤ÉdŃKJYQ4ĄR©eJT˛¤ĄĄ-RZŇU(¸­JRL¤©EKK*’­,©ieRZ’ÔĄ)R•-%R•*Yqbhµ”´•IZ’Ő)RĄ–”Ą©)2’–)&II’ɢʕ&QYKR”©U•,ĄJ«)j*Yh˛ĄV•¨µ%IeDĘRRĄJŞUTµ)QiUJ¬’Ô”Ą¤“IjJL¤µĄĄĄ•+)Q2”¨Ş˘É•Y*Ąe)iEÉe˘©JRĄ)J‰•**R–’ŞUU–\¤©eIJQieĘRŇR”˘Ô\YrTL©JRÔTĄ”¨´˛˛ŇĄ(™(µ\Z”ĄKR©+K-%K%”–”™iIeIIeJʢ•)j•V˘–”µJ-J˛U”©UK.)T˛ĄUU”–T\˛´µEÄĹĄR•,ŞJҬĄJ´«J´¬´±kE,Ą%-)UK-*IU)U*LZYUKJ”©R•JTĄ)&J–*TT«QK)--QrĘ–R’–RĄ,¸ĄÉU,©JZJ©eĹ‹R¤\˘âbŃ2˘eE©EĄ*˛’Ę•ZYRҬĄ”¬«,˛T”Ą)R‹’Z”R¨ĄRZZZ”©e))UeK*Š.J-JK&’”Ą¬Z˘ĄĄ*T\L©KR‹”Z˘eEe%KJ––-QKIU+KTŞË*RËK,©JZ’Z’«)*TĄJZKRVJLZQ4YyUĄVYIjRŃe©)j,´ĄJR–’–”\”Ą)IjKR”Ą*RĄ)JR”¤µ(µEdÉdÉT˘Ő%ZJ˘–’Ą–”µ%©--Dµ%©e*Qr•”¬ĄK)JŠ–”Ą”––\&T’«*-IR”ĄJU*YhĄ¨€q¦������������������������������������������������������������������������������������������������������������������������������beetbox-beets-01f1faf/test/rsrc/full.m4a������������������������������������������������������������0000664�0000000�0000000�00000013346�14723254774�0020253�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000��������������������������������������������������������������������������������������������������������������������������������������������������������������������������� ftypM4A ����M4A mp42isom������ Šmoov���lmvhd����ÄLé_ÄLď���¬D��¸���������������������������������������������@��������������������������������trak���\tkhd���ÄLé_ÄLď����������¸����������������������������������������������@�������������mdia��� mdhd����ÄLé_ÄLď���¬D��¸�UÄ�����"hdlr��������soun����������������Óminf���smhd�����������$dinf���dref���������� url �����—stbl���gstsd����������Wmp4a���������������������¬D�����3esds����€€€"���€€€@����x��ú�€€€€€€���stts����������.������(stsc����������������������������Ěstsz�����������.���������2���2������"���"���'���-���+���1���/���+���&���0���'���'���&���%���)���(���-���,���*���)���.���)���(���*���%���)���-���4���&���6���.���*���,���$���,���3���/���'���&��� ������stco���������Ć��é���� •udta�� Ťmeta�������"hdlr��������mdirappl���������°��ilst���©nam���data�������full���"©ART���data�������the artist���$©wrt���data�������the composer���!©alb���data�������the album���!©gen���data�������the genre��� trkn���data�����������������disk���data���������������©day���data�������2001���cpil���data����������pgap���data�����������tmpo���data�����������6©too���.data�������iTunes v7.6.2, QuickTime 7.4.5���N----���mean����com.apple.iTunes���name����Label���data�������the label���R----���mean����com.apple.iTunes���name����publisher���data�������the label���€----���mean����com.apple.iTunes���(name����MusicBrainz Release Track Id���4data�������c29f3a57-b439-46fd-a2e2-93776b1371e0���y----���mean����com.apple.iTunes���!name����MusicBrainz Artist Id���4data�������7cf0ea9d-86b9-4dad-ba9e-2355a64899ea���x----���mean����com.apple.iTunes��� name����MusicBrainz Track Id���4data�������8b882575-08a5-4452-a7a7-cbb8a1531f9e���x----���mean����com.apple.iTunes��� name����MusicBrainz Album Id���4data�������9e873859-8aa4-4790-b985-5a953e8ef628���˘----���mean����com.apple.iTunes���name����iTunNORM���jdata������� 00000000 00000000 00000000 00000000 00000228 00000000 00000008 00000000 00000187 00000000���Ľ----���mean����com.apple.iTunes���name����iTunSMPB���„data������� 00000000 00000840 0000037C 000000000000AC44 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000���"©lyr���data�������the lyrics���$©cmt���data�������the comments���$©grp���data�������the grouping���(aART��� data�������the album artist��^free����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������free��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������(mdat�Đ�ě\«11;Ä<Ŕ�ř¤4„ `Ifŕe—^ŕďŹP0#Ü·›ťÁbÄ»VOŘŮ(*ľJ€q8�ň„q�©-@>Ťč[-3ţ!‹rtgĽ<bžl¶‹ĺÔ‘ô]đNLŹÉ “€�řÂPD€TĹm8|P¶Hü>Ľ(Îą¶q]bĄ¸�úÄPEŮ€€ ްDăŮ‘>JUl3ËA$Ö)i)Ŕ�úÄA#€+:3ď`Xa¶™ÉůQ;«Ŕ±čŕ�ö˘" „Î Ž×� óf{q wZ“Ä 3zżf#)NŮq'�ř˘" „kËr˝ŮÖŁ gʧý'”ę )y*¦Úö»,°Îµxˇ�ú˘" „r †BXVFěě7nhϦNž|z%ôe0�Ue®«Ś ś�ö‚B„_`9ĺo˘đJ­Dz0Ż)ňřáŰ1F8‘ú·ĺÉ>7t·#‹Uŕ�ř¤4#ŚXŰőŢ™ľáŕŇ`¸Ž‚Ţ×xT†aG~fäHv<ý¶ÁŔ�úÄ`?� &×63Đ”l:äGŕë’ 莵š„ÇÓëĺN‚ŕ�ú˘R#A�ש9Ď<łÔiÇ%ÖřꦗµŠÍÎę®yfžÎ�ô‚T„ `®!I}uhnV$?‹+äˇ(Z«„ÁˇÜtaĄVi±+±l‰8�úÄ3#PŤ�0Ľń•T`’3ąčžÓ#?}ĄŐőÚ»ěÔ‹€�ú¤Q#Hl`µÁ˝ĄËK§)Q)E‚ńő>O˛ÂôŻSÔ¦�úĆ"#P ŰdpËźżŰ2é­~sŰČÓďŤ'Pîě=Í&ü!�úÄPG�<Ž0NÝÍEř™_ő'1…:ő‹ĺ\�ř˘ ,l qĽÔş<¬>čđÍ&ďĂž¦J3}•]dQ€€8�ú˘`F  8I+–¶:aá–;0ĄÇ>m>;ßMľíŰŹź× Ą/g�ř¤p>€�ŢbSci›”¤L z:~HŤ5M3©'°A+č&„f ‡Ö(�p�ö‚0!dŔ˘’Ż:%˘C°9Błî+]ţ3Z†ąčcJr†Â¶Ź�ö¤`E€a,]µ)\BiwČ,yç@ĂşD¸ůťü'ħĄ¨Ü�ř˘ ĐŔ€0qóŕ=bžmBĹAwůH°×! šDSŞ 8�ö‚5 !`Â4†§ĺ^ńíŞ^:$9µĎ…gs`M%…ĘZZtŞ^UŁY(o€�ú˘"¤09ś’#NŃ·#̬zÚW›;t A-1dyß”3¤^ ŕ�ú˘" "ě�FâwěĽú„,µ¸rŠţ¬js%”źŇísŮfžep=Ľ�ř¤`G €@ŔăŇ5?ŕĽ`•ŘÉÍM˛ČöĆýYB^×;C€�ř˘`BČ ĆăiRř—‡iéŽ60 9üľ<zäŽ_ężĺŔ�ö¤`E€%MĚŰFZÇ…‹÷x&)íS%–Ć5r꽡lÇq<�ř˘(E,?€"<0Ąŕ~WŢ€/‹Ďµţîĺ˙€y2tvĹ^O)! Ŕ8�ř¤p„p °ŕéËtCf€Ş<ŽÜYnÍPaüRísµٶ†1śžiĆ, ¸�ř˘`Gążzz>ĺÓvë…(Ü˙˙GYJ´…=˘oçlÇW�ř¤3 BĐ3ÜQhLÖĆ$/ę‡b<÷~łµ8ëűŰΚSŤ­n*jőXeĹ×7’#ŕ�ô‚"(„@Ř Ad¨*Â`óŮ'á¬Á®co·>Vűça &µ% �ř¤`F‚ ˘±äßxă}¸<ëĘüfP[2ÚqXä.Š'Iµp�ú˘" „¤*ÓŘšÖ'ęŠÇÉR™z[oÝÓ¬ía‚„ľ”XÇS2p'Ŕ�řĆ2! éZđŢx˛µľůěú$©żVn´%j(óVŔ�ö¤`F‚€öŽťF†@›c}}đCÂ2>Ű<ĆçO5Ł!ąQŢ/grDŹđ�ú¤aBŕô‡2‰´S(^®ZŹdđ{¦P¤ź~‰Ůۦ‘6 �Sşś�ö˘ „,`Ľ-1wNŢ�AŔÍ”XPKhˇwKŰ#0R¬Y±Xť-Ŕ�ř˘`E "ĹImq¤>w˘Č1s5{!ÁXş[Ż/ćŰ?ÓGŹĄx�ř˘2"%€Xä  aÔž®ŽćĎű©ĆĎçż÷şrŻLaŔě\ŞRi"?pě\¬ „ô@Ŕ\������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������beetbox-beets-01f1faf/test/rsrc/full.mp3������������������������������������������������������������0000664�0000000�0000000�00000031024�14723254774�0020262�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������ID3����4TIT2������full�TPE1��� ���the artist�TRCK������2/3�TALB��� ���the album�TPOS������4/5�TDRC������2001�TCON��� ���the genre�TBPM������6�TCMP������1�TPUB��� ��the label�TCOM������the composer�TIT1������the grouping�TENC������iTunes v7.6.2�COMM������engiTunPGAP�0��USLT������eng�the lyrics�COMM������eng�the comments�TPE2������the album artist�TXXX���;��MusicBrainz Album Id�9e873859-8aa4-4790-b985-5a953e8ef628�UFID���;��http://musicbrainz.org�8b882575-08a5-4452-a7a7-cbb8a1531f9eTXXX���<��MusicBrainz Artist Id�7cf0ea9d-86b9-4dad-ba9e-2355a64899ea�TXXX���C��MusicBrainz Release Track Id�c29f3a57-b439-46fd-a2e2-93776b1371e0�COMM���h���engiTunNORM� 80000000 00000000 00000000 00000000 00000224 00000000 00000008 00000000 000003AC 00000000�COMM�����engiTunSMPB� 00000000 00000210 00000A2C 000000000000AC44 00000000 000021AC 00000000 00000000 00000000 00000000 00000000 00000000��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������˙ű`Ŕ�� F=��íÉ‘A#ô‰ą˙˙˙˙F0Ăß˙C ˙_ůď˙˙˙˙óĎŁ)çÚa”2ůŠz<‚žyő=xů=O `ĐĘÇŤ  ĄŕĆyé>`řÜá ĐŔXč" z)ăBś"Ŕ$)‚ A„ ��˙˙˙˙˙ř˙×?˙ű˙˙˙˙ţľÝvbŁ×Bť¬¦ ł4Ŕă°˛+¸µÂ¤ÍrąÁÔR°Ä%;ťîRŁQ[˙˙˙ďÖÜî˙łľÚ7UW6dTUT!ub8´Çř çčx?ń.Ě€Ź˙ÚÚ˘ł<ŚaeźDY/˙˙˙˝˙PśĽ·$D¬(躡ďPŚi§˙ű`Ŕ€Š9ŕ… éâH#4�•¸‹RŇAbÉuĎ&Ž ü:<cŠĚIÁij#Ř„`4㔑H‘qeu§šj2^ĺmłÚ•żúiżH­äşĚDĎC’âjÚĺn_«ąšşůEkwwŮÝ~˘îÚZ†6ë GMˇ$1ÉDČ㦩CňŢĆCB‰ÂŔ�€ ¶€ç˙˙ď˙˙ţú÷çwبE:ŽabŘę‚c˘ő>=ÄÝL´2!Ô¶#9îŽÔ#"5ż˙˙˙ú­˙fE$ěȉ,Ş1HËez˛1’Ş!ĨR ‰Ě§)˘((â˘,Ęc°š)ÄÜX˘¬,qE„T9N@řt���˙˙Ňţ÷˙?˙ű`Ŕ� ň= mÁ¸Č#tŤ¸ż˙˙óËäÜŻđÉ÷INˇćiŢ˙{ŢžYg§S—ÜëdE/'˙˙˙˙˙˙˙çţ§Ě¸g Wëď&%ÍirÄDuń6.ÝítÜ‘mtP&ę,Ž«˘’Duhâb¨D›ŔN±˙˙˙˙˙ý`Z˙ý{˙˙˙˙˙çĚímŢóĄ/ĂžjľGH„<&ńÎCĄ˙ü„žl÷˙˙˙˙˙˙˙ýţ˙çJÎÉPŤ1•ĄI’ňÔä»ň9Ł3łZHFĘ;ŽÔr/viĂ1  Ü‰…Ł2‡ �ěÁ˙˙˙˙˙Öő˙ţy˙?˙˙ůz\¨Ű+dDÎŞŹ1ĆckTIw˙űbŔ� ć% „MɬH#t‰¸™¨…¶R˛¶Í´ö˙˙˙˙dÝꓳĐ÷1ęr© ĘĎłîŇ»łlÖ• lΆmC‘Uę÷c,ě$(tČ4BK˙˙˙˙˙ëĺý˛ëZS˙˙˙˙üąQRĹ­ÝŚ‡>łĚl®®č§1ڍËf:V¨m&ľĺDGB;?˙˙˙˙˙ů­RĚé+9‡}•+Ďł+±ÍUĐčĘbPSú;1Ś%Ü; Š…R•Ěwt”€Î9B�˙˙˙˙ţ¶˙ë˙•˙˙˙˙äěúRwó5ťlŞ„s>ʸő#ĚäşuFlëĐęɕћ˙˙˙Ú˙ł«ÔŞFJČŞyŚEus–˙ű`Ŕ"� ? „­Á¬Ć#t�•ąa2ft*3άVwVl̸â•ęc@@Ç‚o2ÇĺQÇ;> ˘Ł€��?˙˙ős˙˙˙˙˙ü»µ®ĺ:=¨îďv:şş•VB/Îlęčö-ĘZ$Ű™5O˙˙˙ţÍ'*MDŁť'» Č.–zą›S«”ĚęW!„Ça溡Z!3#NǞ#!˘âcH5VA¸ň¨`†�`±óőůţż˙˙˙˙®•g;RWTťŮUŐ˝ÔÝQeŃŃTÄľÎB%O˙˙˙őůŃgęŐ9F F‘ťT‡j9"Î(cJb#!ÇxŐ3ł ŞE˙ű`Ŕ,� Ú= „­ÉŻČ#4‰¸vcśqL8áaŕĘPq˘Ń2$48˙˙˙˙ţÖŻu˙ş˙˙˙˙˙?Ł‘Ý”„5›Rl›ogGĐŚŞöi7şŐŽ·ěKw˙˙˙˙˙ÚŇ"1Ý_™Ü§8¤T„Dgťä;YśFä%PěĆ \ČäRŠF €‚äS3: ÁČQBŞŕ•Ü(˙˙˙˙ţÍ—˙/üˇßźő˙˙˙o‘śíbnYŘz(‰‘ëu×J:ä·IdŁďZĐÔŮ˙˙˙˙˙ţźJ'»J Ff®ď©Ne+ Č 8ä*&*†q2 0Ě AŠAD…0’ŤQ!2Ş”ĄA§aQE˙ű`Ŕ6€ ¶A „­Á¬EŁtŤą0�˙˙˙ý°/ó׿üż˙˙˙˙ň]sąśĐô… 仪Dţ9FzÓ¦[7‘tĄ+Mą2äł˙˙˙˙˙˙˙é˙9ćÓ*FE‘D‰»ë2ł×r™9“Ň·I­|ĽUčĘă±Áz°spÂ+† JŰŘ8˙˙˙˙ţŤ©üĽňgRfŇĎĺ˙˙ýţ•$íc1üý§ş!Yz:d÷#ę×*ş%Pú?{Ż˙˙˙˙˙čɦ·5\ÎRŁľě‚ŠTv{Ä$A™ RkŃi†!ٱĹĘÖ(|¨rpđé˘î<ÂÁŔ+)h��0Ŕ�5"üż˙˙˙˙˙dî˙űbŔB�VA „­ÁÄÇŁ4�•ąŹF±:ĽŰ>yę­)Ýě¬ĺşQň˛+7cfT˙˙˙ëŮ[óÉWŁ•hr<AU8‹=¬ąQćg"Ž*­Ž9EXEæRDqŚ(Ă„`˘śĚ T1Jç R‡\pđ¨ăT�˙˙˙˙˛˙Żň˙÷˙˙˙˙ˇ2;Ůß)mW3™‘«Ą“ś×–ĄUW¶¦pÇłłşZs]×˙˙˙˙NŞ–±z™ Ëłî†j˛ěů 6Ç$ÁHČÄ!ĚI2 ›„ę¦*Łłşž SĂ(áDÄp`�Ŕ´Śę˙ż˙˙˙˙ŐQ&^Ęeý‰WłśĘDYŃÝŇvSŞ˙ű`ŔH€ –= „MÉËČ"ô•¸ĘtR2™K®Ž÷Ń»˙˙˙ţę”T˘˛ÓJJfiÎÄW̨ńd%'"Ĺ2Ş8ä1Ů,$,4Î$DŤ.qA a˘¬„1Đ8pš E.Qp�˙˙˙ţ±ţ{Ď$ĚĄ—˙˙˙˙ë^ËVe)_fJęuyĄŁfzŻvUWB5gßgąU§˙˙˙˙ŮQ)*«dCÔ­yněÎmČvr%ëWśŠd(SąLr" 1UF»”VA†B ¦0ă2F ęq€�˙˙˙Ű˙ůçü˙_˙˙˙˙Ó˛µ‹ŮĘwvť™¤ek ôIچT4‹˛ľěé:˝Ń˙ű`ŔP€FA „­Á·Č#t•¸—!?˙˙˙N©źg2ä9Ň…5ÄÝ.»¬ŽkQÝŮGĘ,îYČFd1ČQ©……ĐĄvAE@˛,äQě&‹ť‚$˙˙˙˙˙Ö…źß˙˙ĺ˙˙˙ţý^ŢěéG!ÎŚŮ,UąŐUg)héu1WVĽ”s5•4*?˙˙ţľ›{+f"zYЇg/LδRÎŚ¨â¨÷aČânĄ´GR:Čç9Ŕsšbr(ŇD$A…BD˙˙˙˙ţŘ?_Żç˙˙˙˙˙ďýϬ†›ům<í}ËHDíű+ÉÔŃožĘÍ Ú¬SÉmż˙˙˙˙˙˙ůΚOň˙3gb…šě˙ű`ŔX€ š7 „­É˛H#tŤ¸ňH[˝‡ů™`ŘÉŇ6n\¶UCł…U.ÁFpřò7°*$Íť€€�˙˙˙˙Ö˙çë˙˙˙˙˙˙ŢíXŽuř’"ĂňfF„B VČ“%תG‡ĘcSż˙˙˙˙˙˙˙˙?)Ăé}ëŐý׋N퓝©”™Ą}­sś6SX‚ Ęş¤OE+ ´SX\lCľć '�˙˙˙ţŘ˙?˙^uú˙˙˙çe“t;˛šîg%|çgçR7cѬ®SrŮ „Ů ÎéGy˙˙˙˙¦Š×k6ĺ.EŞ!]R~†uĚ}h‡Qhey•ěĆB)]˙ű`Ŕc€ š= „mɩƣt‰ąÎcŁťr �Ś,ŐÓ��˙˙˙˙öú˙”ţ_>ż˙˙˙˙ŹiO‰Ť&׊aҶ§ö’ŃşkL“ÔW0÷?ÜpµpÝ˙˙˙˙˙˙˙1˙×ýúÚuz÷¤sň8ë›~9Ťç^;Ž’…ěar:¦hZ—Ro|†AwÚÖěXňo®Ü**>@Ö0˙˙˙˙˙ëËWuűJŤFŃYěîĺňYŮrć\îjí»*"±Hß˙˙˙˙~î‰m¨—g*:…+"ofi•ŢÂ#EŹ ¨˛)JçWACę,$aqĘÇ)„€p1Ś"Pč‘ĹH‡(ѡ`8˙űbŔo€5 … É˝H"Ě�•¸��3čň˙Ż˙˙˙Ö×ÝzčĽě×»Čr/Ȣ Ę®”dŁ3Ń—yN¤q˙˙˙÷ä{"Zĺu#ĚeR ¸ńŽÄsî®e-ESi‘pŐ( ›0“„bŽ3‡Ŕăs†yNEr”ÓŠ¨€ ŚŔ˙˙˙˙˙íyď˙éß˙˙˙ýz.Ą1,o5ä+9·Ežuvf±Îőąv}ČČ–N˙˙˙˙Dű}lłµ+3UTŠCoĽĄ>%čOyS!ťXڇ‘Ći;ť\ę8#)Ĺ:‰cĹ•Ęt(P‘Ŕ��?˙˙˙Ň˙ů˙ó˙˙˙˙ů´š÷f˙ű`Ŕx� 9 ­Ů¤GŁt‰ąb$„vşftgdű©kU-™śčƤĆVäfŰ˙˙˙˙ěźęR>Ó­ŐK5(i5; Îń“•ŃŠwiÇ*ą¤"âŠ*.*1â¦c´x´çRH@“#•bŔ�˙˙˙ö@ż˙-»<ŽÄjµ=]ÖÓTޤ®Ć96Kä<îäsnłĽó—˙˙˙ýv"Ńő{Ń]LëgRťg©ŞF5ŽFuőZÇQ3ŹQÓ ‡Ç°›”a‡¨Ô µĚ.tP‰GQCĺ<ĂŠ�˙˙˙˙Öţ]˙ţ«Ż˙˙˙ţŢy‘žr3ÚŰg§ź¤m4U++!d“Ö¶_J´Í{˙ű`Ŕ� nA „­ÁŞGŁ4ą˙˙˙˙˙˙˙˙öů7ČqyiHsC0¤Er_?* –gĐNP‰îF¸v#UCŞ9ŽB!* jčŤA‡1Y(0�˙˙˙˙ý`_óňţ_˙˙˙˙üßíŃžďzJ_ąK&´išôE3Šeu!&>ţF˙˙˙˙éĚý<s±Q#•ŢcÚď39’Ŕś“Ůś[%ÜŃÝÄÇ8s¸Îd†; á�Xä …C���0��­˙ůZőşťVýi ­bą*ŞOf3U§,ÍD=®E%^ÝË˙˙˙˙óHęíł=UÎ®Ż­]ť™žŘ⮪9ŐŚfd#9˙ű`Ŕ� ňA „mÁžČ#t‰¸ŃťÔ¤C»Tpř…Ĺ”HbČ s‘ť E,*Q˙˙˙˙˙ý`9oËęk˙ýý˙˙˙Îë"ĺE§KQîÖÝťJî·<†aG TGČŞW›F}]i˙˙˙˙ęŢŞú#f«˘Ě¨ĹUTH̵EçĚŞ·;f¤€ÜâŔŮŃŐN]DÂśr¬¨�qNČa�Ý(�˙˙˙ţ˛űçţ}~_]˙˙ëďôěĚÇűčw.šQŢrŘĚĚäb-lzčŇŇš˙˙˙˙ßé­ÝíSUĐćg«DL†îBÓśxÝť ¶QE§™c±îD €Ń3‡Pá˙űbŔś� 2A €­Á˛H#4‰¸$)ź1Qd‚��� Łý˙ç˙˙˙˙ýÖţ—tÍT!¦ČwS5îęëfşťĐĹ*”ťlýŘŠß˙˙˙˙ôşµYĘĘKžÓĘÄB˛˘ťĎvj±•QXĚB *”a”٤q3”Á7 áşˇb±Î!¬k **6H��˙˙˙˙¶üżżËĺ_˙ňůß%é,«żuÜöećBĘěöJąMwş®í­]Őr«7˙˙˙ű˘Ú®d+Tµ#!•Deś‡ć)Řě…F•®q`łťQËĘ·K ‡!:ÁÎÂĂur‹C®ŕ˙˙˙˙ú@ľ_âü˙ű`Ŕ©€ ž; „­ÉÁG#4•ąŻ˙˙˙˙çď˙%2óĄxzžőËa©—ÝßČͶMźšĂ™);ţe­Ű˙˙˙˙˙˙˙˙ů~~KrS¬[uvzźzYq˘ń ňlQwŃß#}¨\‰jZ |‹T ¸Ä!ĚéŔŔŕ�˙˙˙˙˙Ťźž Řç —Ne9ź˙˙ç eR!áçÖVę˘őś}!wÂOí3UUÚZWU>Ń˙ÜW˙˙˙˙˙˙˙˙?íńó÷+kLÖĂůŽ*Řëk‘s*—gJŹ4`žąEĆÇviF"‡$z©†9‚XőÂaË”9pčP·XT]Ç˙˙˙˙˙c`qîy˙ű`Ŕ˛€ .A „MÁ­GŁtŤą‰#±’ż%ľ§˙ţY¸tűîú㙬ꙉyĄ†ýwŠőÚ©¦÷ŢŇĄ©S»éşć˙˙˙˙˙˙˙ţ˙ż˙űżŽąškŻ«YO›­˛<6<’±…&TÉë˘�Ć!wB±E2 ¸öłJ‡4\V…K1`°’ކ6Ëq1Š@˙˙˙˙ý±Í#WĺżĚ«˙˙˙ý:*\ČŞuo™ŹťH(έb+JB{1¨¦î·±•jj˙˙˙ţť7Ą®·tT2+9#ÝÖ<–şśČ©*­ ayŚQ1§3•Ú&=ĄB:``ë‘‚˘§Š ‰Zpđ[JQ�˙ű`ŔŔ�ŠA … ÂČ"´ˇ¸ €�k?ýţ˝˙˙˙˙˙ţŐdR•¨ÉżWDJ2oR›±ÎÓęUd©Šf3¦Óo5ź˙˙˙˙ňKk­Ę„y™bęěęt3–S’Č5‹Ž «Ź,† 1 áňŽrťJâŠ0˘„#(@Xęč$,⊏8Š4xD*…„�˙˙˙˙˙’Ëäű×˙˙˙˙űνey5v}úş*±K÷U2‘ÖgR™–tK"µoE˙˙˙˙˙éîíLĘŚE:2+Ů’}Hî8†fc ‹ł)†ĹĚîěBaÇds¸“!…„XPrxńĘ,Š($0<*5� †��4lü˙ű`Ŕł€˛A „­ÁŰH"ô•¸Ö|˙˙˙˙˙˙ôM‘Ű[V۲NI”¬ÚÖçbk3›ß˘ŞŐ‘Z~ż˙˙˙őˇj‡»µ¶g*•Ř”ssB«Ąr”¬QGމ ”„)ĺ10‚ädĆw<)]Ě($aE8pj‡ĐhŔłŔ˘¦‰€˙˙˙˙ţŤßü˙F—=ĆÚţż˙ůĺ®›±÷őĚk*"§f5§˘*YŐC^bşę§»LÜŐ˛˙˙˙˙˙ëî‰wcަUÎ<Ę;0Ô=QÖ«žŽ¤Ź:<H±9 šaęH™aD‰Â˛,yR—(V('aÂÄFŠAM4óGÄAx¤âŔl�Ăa†Ń˙űbŔµ�= „­ÉÚH"ô•¸°~Sţźě˝üż˙ř’ť=ŘÓË˝űuÍł˘©43ˇšęÔi˛Fµż˙˙˙Ż{zGj”Ç»ŃJÖ!Hj$Ő\ŔW§(X÷aŞŃb„ÁÄFş !…UČ &)Î&ěqaÇ śpx"0HD =@˙˙˙˙ţ‘łĂčę^ék<’—/˙˙ĹůLM t;2ѬΨçEŞs܉\ú”‰Đčä=f{اu_˙˙˙˙éŁhßyr'G:Ëłť f‹ -ÝQĺt3 ‰ťśŁŚ,Ž`č|Xs™…™ÄĘÁŃ¡12\ä&đ˘(Ś`�Ă ˙ű`Ŕą€nA „íÁóČ"´•¸¶hŘőçůßËő˙˙˙ő<u(ë7q]M]ő×=CGŢüýňÔ“ĚsTŇ‹÷;JĹ×}_ß˙˙˙˙˙˙˙˙˙]OŐOvĐňÓ ÇňŚĐ´5¸}’ŻčmÚśs–4“{e‡=$u0ˇfLÜ, °T?i(MHÇ‡Źž(�˙ű˙ţ±üüÖ~µ‰÷?˙˙ö˝Ń¶©E[d[Ý‘QČîuC*ĄÝT»»F!ňĐäR¦BĽŮŐ ß˙˙˙űďÜ›Lއ2«ó•ÖŠ[¸€›łL”ˇÄŃXiH‚Ň !”Ś"Áŕ›ŕ@ř°±„Ăę4TT©QbbDÝÎ�˙˙ű`Ŕµ�Ň= „­ÉčǢ´ˇą˙˙ö˛˙–^ż7óę˙˙ţ˝¶dĘďEŁu+Í3’y˛«îęŠEwT¬ĆUťŠ¨W˙˙˙ý~ÄűHűŽőTg+T¦C”Ą"˛ŠRśŠu2N§ĄĘ9\aq) R Ž28Ä2‹qĹČs‰D8á$*��ü¤>łóż˙˙˙Ďéą&q…+T©µ'ŮTU•ŚgeR á‡B32)—ýž·D|ď˙˙˙˙Ó]«MÉş+YŚ[Ö¦0Ň)ĘbŽĄŽ5EQ„ÄEa!\hAÂ!ذ łBĂîg Ŕ(ńá‚ÂC &P 8|DV&Q˙˙˙˙˙ű`Ŕ´€ÂA „­Á×ÇŁ4•ą˙éä^¤–ú,‹˙ź˙˙žŇ÷f»‘×d[Ňş˝ VąLës ws)P§z^¬t252]ko˙˙˙ď§Ł÷]»ÍĘS2•gsƙٞDkaśÔ9Ç#Â"%F âDL*Ł˘ (B" ‡XÂŽAěbĽ>˙˙˙˙˙±…ĺţY//˙˙˙üć©J$î”Y?RşŃôVT}ŢŠrQKjßZmu}v˙˙˙ţ«ëzttz%¦TS•1ä#ĚĆ˝j=‘ŇŞ”ŞQ!0c ÓH,˘ĺ ŁÄÄĂŠ,ˇŃw‡Č 8ç1Đĺ†0¸Đ�Ű �ĂjĐö˙űbŔ¶€A ­ŃŮČ"ô•¸}s—˙ţż˙˙Ä@÷:XČ®M*¨Ccą§r”ü­#çDQz­Şý§µ)˙˙˙˙ýţ–ŃNbç›-LQl„0׳ )ťÜk ;Â,5ÜŁĹ‹;"Š 8ЦA¨$ÁŃqĆ\M…EAB@Áç( *�˙˙˙ţŤ˙-ă4Ń—pţR˙˙˙ł˘îVlěU·K&öu+<Ď™‡tLěěv3ˇŠÔłI{×˙˙˙˙úűű•YŃ Ą*«“ęçŞîR†Đ<Ž=ÇÔLU…aĹqáL (s”ń¨§D4P€Č‚Ä !€8˙˙˙˙˙ű`Ŕ·€’A „­ÁńČ"´•¸˙ěŚ w"šçĎ’–«˙˙ţM˙ÇýwóóŻ÷Üu]fFĚŐH÷SR‰őô±×KŻ×˙˙˙˙˙˙˙˙˙Ďw§qóßLň°»OĄO3r˛1GÔ­mÍ»±ĺ;=šSĘ8yŁ2lĎ#•0ŃÄĂHpň ˘ĄŹ% « €˙˙˙˙˙˛źĎ_ĺËz˙˙˙ţţŠŚ÷Z2>ô±• ŚĚ]ŞC\ťČ„VGGuyŐ(Ęr˘J´˙˙˙˙ű[BUÝźaj«öŁ[‹Ő™HŠčăLR«yڇś¬C%Üq]Qbě->A!Yâ†`Ł1\Xâ@��`Ň y?˙ű`Ŕ·�6A „­ÁâČ"ôˇ¸—#˙˙˙˙Ę­ä&ĘŞËťŐčęĘ}V¤1ÖłFnną:ű[Ż˙˙˙ëmjĚësˇŇŐF!ŠbŠę®Ć)†‹‚*‘LŚc°jˇ¬*‰˘ď0pĄ>$av!śXč4DHÁÁ5‡ĹĨU(p@1@�˙˙˙ţ­üŚ˙ĄÔËďë˙˙˙7ęěµR!ź#ٵŠč¨~ܦf5ćfb1ŢŽÝS"Ű˙˙˙ţßčÍišŁP®vJÖ®d1qčq”2ÚČ0:Ĺ+‘T6aÂbEl?2‡DD…‡‡\M"B Ł˘† ”Ác4\�6Ă �˙ű`Ŕµ€.A „­ÁçČ"´�•¸´ýą~ö˙˙˙ţT«Ű"Ł—ÜÇž—ŃsŮŃhę®DÎmYd˝îdSnŰ˙˙˙˙éíľ­:Ş3š§š•c%®5HQU<§!ĹEHcDH@8â(‡E`=‡(ąĂ§qR¸˛:$&<(ją‹ĆЏ  h���Ř�6­óćż˙˙˙˙#í%)sMŞŘí1ćrÚě·IUkwG‘rîGtTe÷Ń—˙˙˙˙ŢŐęŰ++Jζ%ƱŢČÄĚ<‹;‘XÇ8q‚:‹ Aa6qe- @ĘAA¦`‘Da"ÄĆÄ„@đ™”:?˙˙˙˙ý$˙ű`Ŕ·€A „­ÁďČ"´•¸/ţN_¬™ôë˙˙ä ÷Gő>ľJĄÓ:˛ť—G4¸±Q(„tC\î{+©•®gO˙˙˙˙쮕˝Ôł!O<®ë!¨Žd,ŠćR1>‚%f0°¬@ę>qâˇô (¨ĺś4ÖQ!0ř€şX‚eQC€˙˙˙˙˙Ňäţgě?˙˙˙˙˙-‘ś¦ŽI"*ĚDŁ™•̧Y§In–jH~­’Vkg»T®Š¬‹˙˙˙˙•úŃĚŽäRËŐ®¬–K¦í[ÝF"Ψpá®ĺs ”<îáC‡A r$ň 0×R‹ŽĚW€˙˙˙˙Ű˙˙űbŔµ�^A ­ÁéÇâô•¸˝˙˙ß›˙˙üż->ůŞó/ß&@şŇzšşË)˘Ô-^źđĽŽ˝Ďmű˙˙˙˙˙˙˙˙ĺ‘ßće ·îV›E‚6ŤťŢő$4Cą»Ľ­dPŔŽŠ$B¬ŽAĹŁE "Ł,†8SW©9 € €�`5‘ç˙Ëćkúë˙˙ň˙ZšçTCŁú^5Ýő‘ś¨bT©‘Üz-®–™=¤˝ź˙˙˙ýiCž”čËB)©au”‚o,ŃČ8‡c‡ ""@”AĐDĄ<IĆ €(ÁaŔ0XT¨W ÁË‹ 9ÄŔPvĆW B€˙˙˙˙Ł@Ý4˙ű`Ŕ¶€fA „­Á˝G#tŤąŘe:€ýź˙˙Ď}ęš”ę‡ôQŞërˇ¶:#›:9ćécŮ’g!Ö«×˙˙˙˙ý;ěĄK_˛ö(‰ÄČńŐś¤‰ )ÝŽ$ę†(ä1EŘç9ĹŔRDP\fŽ ,$",ax°u„„EET8px˙˙˙˙ţ±őż˙—ć]Ď˙˙ţ}-W晍f»Ő)wK%OJąčT#š1K{Ż?9™ě¬§˙˙˙˙ű“ŰMí4čŇUŚçU!N¬Lk‘Ô®îRŁę̌rŠŹ3 …;‡äA1Ą0¨Ńx |MÄ <H"a! #¸)J.?˙˙˙˙˙¤˙ű`Ŕ˝�Ę= „­ÉńČ"´•¸/ůl‹7ËůŻ˙˙˙<ť‹v}‡W­SN¨y‘Ë«ŞUť ÔźY™•ŽJ{5g˙˙˙˙öőˇVµUµ]Ő§"ËšrÔŤBŞ‘™Ąäh‰„tŁ S#ĚC2XĄB:Î&a(qĺÄN�˙˙˙˙ţ‘«ęYáŃHöô\ç?˙˙îoö»¦ćőŽb:K­f””ĺôžfi‘siáˇxîW©ž«ę?˙˙˙˙˙˙˙ű˙řůřý*ˇx«žišő˝‡#’´,] ŞĘ ĂD N*/L<Ć"áÄŔ¤f0Ë4PZ”łX••mm)„T¦X˙˙˙˙ô˙ű`Ŕ·€ŢA „­ÁČG#4•ąŚ§ŁbŇďĺ:Í˙˙˙ŃţFDĄďJh†T Š$ެWČŞ’t$Ë뤷U3äs?˙˙˙·ďÝQ4:—bÖ¨y'Fˇ&yÍ8”MFEŠíČc˘ŽĂX@yŽ."‡DŚ'aa[‰8:R˘eqG0˙˙˙˙ţ‘óžüü˛ľč˙˙˙˙˙­ĐŠÍGňÝUUMçyJďIŠcىJH䪓nŞ‹˙˙˙˙§ÍÓ«ÄŞ§rąĚbUĚfQqÚ›Ő v#ŤR ?Q"…]JÂ&)Ä@¦ ;•ĹPXP¤1F‚JAp@& *ńŔ`0˙űbŔ»�öA … ÁđH"ô•¸�lŇ0~~˙˙ůń˙˙ţý4iŃşú­UU\ŻeB$čÄ#č…5m¦M.•T5jż˙˙˙ŇĆŃ_ÔŽŽ§3ÎňˇUămTb©„Ęg1HÄĘ,Ç(±ÄŐEÚ¦ H&eAŁÇX@EB†ŠHEGŤ(÷+CÂÄ $`w�˙˙˙ý­ůů/˙řÉEů˙˙ó2˙Bmv6UsrLŚŹ«Ä{ü¤[÷uݬď[˙˙˙˙×Ů=Vd=ČZgŁČS1¬E]VK‡ť„ŐŤ*†31@ńGpčÁ�ŕ r qŚěSb«‡<`±˙˙˙˙˙´`ň˙ű`Ŕµ€îA „­ÁâČ"ô•¸˙úóą˙˙˙ŻôśĆ1î@FvYŤ±BşŁ?fě–1l¤DĄť—J¦ô‘S˙˙˙÷ôő;ě_Ů·1™+¦®qgTaśçLĄpçr¤D�Îdáŕč)B2ȇ3ęv tT8Ql&�˙˙˙˙ým©OzM'ęőšďëţSŤť/˙„é&˙_I¨×űőŇluBßǧµţ—\üwĎÝüĹ˙˙˙˙˙˙˙˙˙7˙ÚUÜ7;Ç­Ęßs#Zń×<`Ů č& l•yTb‰˛$ÚÄE`Čń:VE‡dž"ňpŁÂ1ä É& *y ŁĎ¶4J��m¶Ŕm[˙˙ű`Ŕµ€JA „­Á¶ÇŁ4‰ąĎÖ¦kťW˙˙˙ű+•ĘĄ*F›+ÜŐt1 ÜşSV#™Y\ś¶zTňó©•ż˙˙˙˙îjnĘŇ™“[;ÜŽÇAĹ;IĚD;!ĚÇ9(ěeŽ8MĆX^ Q!Ŕ hLH>*<:*‡ °x:@ůDEEH8:.څЇ†��˙˙˙˙¶üżůëď˙˙˙˙©˙˙–ŽÍ­îGH˘’°»Nޱ™ÄW%˛zŐóŘřDo“eŢ˙˙˙˙˙˙˙ţ_ü?)ąleyX†żç)ÎśzRĚź"]Ë•L´'dxČĐtS 3j±Ű é ŕ˙˙˙˙˙Ŕ~ds˙ű`Ŕ˝€>A … ÂH"´•¸ć˝ ˙#˙˙˙˲µĐŐc0‰&V‰ZâBĹrĚdpëFEyFm Ą*ł¬Îľë×˙˙˙˙˙żżmYĘŠVĘĆ}LęĆ+>j=‚¨,ĺ-*UcĹB˛=ť QSÎPčŃÎ"ĘYH  ˙˙˙˙˙˙˙˙˙˙˙˙Ż˙˙˙˙˙Ż˙ý~Ć(`JEŞ*!ŮŮĘbŞ”Vs ` #ł±Š©˙ł”ÁB(�����������������������������������������������������������������������������˙ű`Ŕł€ n? „mÁŃF˘ô•ą������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������˙űbŔ»€&@Ţ�MŔ�[Ŕ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������˙ű`Ŕ˙€���Ţ������Ŕ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������beetbox-beets-01f1faf/test/rsrc/full.mpc������������������������������������������������������������0000664�0000000�0000000�00000005714�14723254774�0020351�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������MP+'����� \����������@”7–�sĚ˙ţ ˙ŇĹDfŃç�ü˙˙���Ü3Ď?óĎ<óĎ>÷Ě>÷üó˙üsĎüóĎ?EDDţ�ńđ>@Čń_éŇá �@� Ń ��@;��8��şo´����č�€€Ţ ����‘˙Eľ|‘_Dä¤Č—DDDDů""_|ľ""ED""""‘""Čŕ‹���P� 8şŃh ˝ŃŤ¶m۶ӵ]×K۶m^UŐ6Ş^ő÷jĺ/ú{ŻŞ‹Uݶm»ň—mkšg{¦KŰ[Ú,H’łmŃĄçç&źŰli۶łsmŮ"Ň”(ŤŞş­lu~ĺ‚˝˙ÝgUÝÓ·ő—U÷U±o Úó5„4ĆiŚß@NĄjT=ť’¦SŇ©N�˙˙Đ˙˙˙˙ůD&Ľ�|/]ŕ˝|_�ţ˙8y�č˙˙AW˙˙˙˙üú˙�DŔ˙üCx/ţ‹źŕ˙]˙˙˙˙@bň˙çyŠÄáď1řëýÁ!ôóé€?A_OŘ�˙˙?�˙˙˙˙>Ŕ˙˙ş˙˙˙˙ýv˝ä`oôôyCŔŹWě€Ážpř öú‚@źŔ§�˙˙AW˙˙˙˙Đ—üűů‚˝Ŕ /觇~żžĎč úův>ßô��ż˙˙˙đ˙˙˙®pń�˙˙˙ů˙˙˙đ2."âĺß?€/ż‹��˙˙˙˙�ř˙˙AW¸u˙˙˙˙ü˙˙�ř� EDä�˙D@ŕw��đ˙˙�@˙˙˙˙ç�˙˙?č ˙˙˙˙tů“˙ ňż€ń "�|ůŕEी|˙şÂ˙˙˙˙Č—ă˙ €˙ä€|ľ�n�˙Đ˙˙˙˙â?,˙" �|ů ‘˙;€ŔÜŔ€mSş(›˙ ��ţ˙����9öźťçěÓgőڵőĽžł|ÎźßKź÷�ń‹ŔD�DÄ�"m›vů´k۶ۦmŰ´Űvm۶mŰmۦ]¶kŰ6���Đŕ ���8�������ŔxŔ���Đ �h����Đ���h���� �����ĐŁ��oŔ¸Ŕáp��иŁ�poŔ"Ŕá" """""‘/""H€DDä‹PDD€7€���í���Đ�� đ@�������čntŁ��ކŔč��ÝŤp´�Ý@ĂŃ׸m۶k¦mŰuŰ.]۶]»nŰ®kŰéÚ6m˙Űv˙ż˙˙˙ż˙ß˙ż˙˙ß˙˙Şq*ůN%+ŞcEU5ŤJŞ ’ŞÂX=WŁýĐsŢ!§Đçr ýW8y�˙˙˙Aü˙˙˙?ô^ţţř]/ŕä{€�@˙˙˙˙�ţ˙˙˙�˙˙˙˙´ŕ˙č]Ě˙˙˙?˙˙˙Ý tźŁ� 8€���Ťî:� �8p4����tt ˙˙˙Ę˙˙˙0ôí?ß 0ô?ÁŻřçű…? ° ×ë ]ź˙˙˙ň˙˙˙}Ă__Żô…»Ľď×čú|‚˝q0ţü‚ľ+ŕS�˙˙˙ ţ˙˙˙aĐ·K}>@PČóń|{<0°Çăú‚Á_PÇ…“€˙˙t˙˙˙˙ň»üÉ�ůŔ‡ß/_ä‹‹€üÎ� ˙Đ˙˙˙˙ůň'˙DäË/âÁ Čů "c�˙t…˙˙˙˙‘Í˙€|/_ŕ äË/ _D{�˙t˙˙˙˙€öÎ˙whŔ@ĂĐO�¸˙®€˙˙˙˙ /ů˙@ż /8ŘűýxĽť//,ůz@ß_±ô*�|=.ĐĹL˙˙˙đ˙˙˙ŕŢs��€8€ŕáđ�� �ŢŔ���p� ��ŕ AWPË˙˙˙˙°ü˙˙óÔgˇÁ�ÜwđŔ|ţîۧĎ{íőĐ_>âűoź?úÁëGŽ�˙˙Đ˙˙˙˙ńß@>Hţů|ů_˙ďż��ŕ˙˙˙ŕ˙˙˙ŔĚd�¶m˙`�€˙˙����źÓg�“çú<gý<źý¬źÓ|ţůg<ź×ó�íŔţ�î�� ��Ú��hp�p� ��  h¸Ł á���€áh¸Ł €��m۶®m۶۶m›¶Ý¶m۶mŰmŰtm´m۶����€n�������p����Ŕ ����€F@���€hěF����8�4�4�Ŕ��Ŕn�������€�ŕ��������Ŕ��€7��������Ž@���Ŕ�������MÓv ¶mş6۶m۶۶mÓµišm۶m¶mŰ¶Ş ^ßEŐŁđú˘ŞŞUUU=(5ŞŢ©ęťjvQŁŞWŞSJuŞÂŞQZő(­RťV©NŐ)őS@S@V*V*)ő«·„ľę-ˇŻą €˙  t˙˙˙˙-ü˙ţ—/]�ř"_ü�Ŕúď=ů_D_ľtĐż|ń�€/"�đß{AW¸x˙˙˙˙źü˙˙đ¤Ëů â<t‹|€˙˙˙�˙˙˙˙:�ü˙˙ +\˙˙˙˙lţ˙đż" "ů" ‹Čä@ä˙]á˙˙˙˙ďżó˙_�äË ňżďä|éE�@@�ńât…%˙˙˙˙›ó˙˙ő‹B‹p˝ĂţüŔ˙ Ü˙űOĹNí¸O˙űŇżţúéţúëŻú믿믿ţ�@”úAPETAGEXĐ���������� ���������������bpm�6�������Part�4�������disc�4�������discc�5�������trackc�3�������date�2001�������year�2001�������Title�full�������disctotal�5�������Track�02/03�������discnumber�4�������totaldiscs�5�������tracktotal�3�������compilation�1�������totaltracks�3 �������Album�the album �������Genre�the genre �������label�the label �������Artist�the artist �������lyrics�the lyrics �������publisher�the label �������Comment�the comments �������Composer�the composer �������grouping�the grouping$�������musicbrainz_albumid�9e873859-8aa4-4790-b985-5a953e8ef628$�������musicbrainz_trackid�8b882575-08a5-4452-a7a7-cbb8a1531f9e$�������musicbrainz_artistid�7cf0ea9d-86b9-4dad-ba9e-2355a64899ea$�������musicbrainz_releasetrackid�c29f3a57-b439-46fd-a2e2-93776b1371e0APETAGEXĐ����������€������������������������������������������������������������beetbox-beets-01f1faf/test/rsrc/full.ogg������������������������������������������������������������0000664�0000000�0000000�00000023700�14723254774�0020341�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������OggS���������ţ–ç=����Ý[vorbis����D¬��€»��€»��€»��¸OggS����������ţ–ç=���¸…Ţ.˙˙˙˙˙˙Ž˙˙˙˙˙˙˙˙˙˙˙˙˙2vorbis���Xiph.Org libVorbis I 20050304������ALBUM=the album���ARTIST=the artist���BPM=6���COMMENT=the comments ���COMPILATION=1���COMPOSER=the composer ���DATE=2001���DISC=4���DISCC=5 ���DISCNUMBER=4 ���DISCTOTAL=5���GENRE=the genre���GROUPING=the grouping���LYRICS=the lyrics ���TITLE=full ���TRACKNUMBER=2 ���TRACKTOTAL=3 ���YEAR=20018���musicbrainz_trackid=8b882575-08a5-4452-a7a7-cbb8a1531f9e9���musicbrainz_artistid=7cf0ea9d-86b9-4dad-ba9e-2355a64899ea8���musicbrainz_albumid=9e873859-8aa4-4790-b985-5a953e8ef628���label=the label���publisher=the label?���musicbrainz_releasetrackid=c29f3a57-b439-46fd-a2e2-93776b1371e0������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������vorbisBCV���cT)F™RŇJ‰s”1F™b’J‰Ą„BHťsS©9לk¬ąµ „SP)™RŽRic)™RKI%t:'ťc[IÁÖk‹A¶„ šRL)Ä”RŠBSŚ)Ĺ”RJB%t:ćSŽJ(A¸śs«µ––c‹©t’Jç$dLBH)…’JĄSNBH5–ÖR)sRRjAč „B¶ „ ‚ĐU���Ŕ@˛ �P��ЎŠ„†¬�2�� (Žâ(Ž#9’cI˛ �����ŔpI‘ɱ$KŇ,KÓDQU}Ő6UUöu]×u]×u 4d���@H§™Ą  d Y� ���F(ÂBCV����b(9&´ć|sŽf9h*Ĺćtp"ŐćIn*ććśsÎ9'›sĆ8çśsŠrf1h&´ćśsf)h&´ćśsžÄćAkŞ´ćśsĆ9§qFçśsš´ćAj6Öćśs´¦9j.Ĺćśs"ĺćIm.ŐćśsÎ9çśsÎ9çśsާspN8çśs˘öćZnBçśs>§{sB8çśsÎ9çśsÎ9çśs‚ĐU����A6†q§ HźŁEiȤÝŁĂ$h r ©GŁŁ‘Rę ”TĆI)ť 4d����!„RH!…RH!…R!†bČ)§ś‚ *©¤˘Š2Ę,łĚ2Ë,łĚ2ë°łÎ:ě0ÄC ­´KMµŐXc­ąçśkŇZi­µÖJ)Ą”RJ) Y�€��dAF!…R!¦śrĘ)¨ BCV�€�����<ÉsDGtDGtDGtDGtDÇs<G”DI”DI´LËÔLOUŐ•][ÖeÝömavÝ÷uß÷uă×…aY–eY–eY–eY–eY–eY‚ĐU����€B!…RH!ĄcĚ1ç “PB 4d��� ���ŔQĹq$Gr$É’,I“4Kł<ÍÓ<MôDQMÓTEWtEÝ´EŮ”M×tMŮtUYµ]Y¶mŮÖm_–mß÷}ß÷}ß÷}ß÷}ß÷u Y�H��čHޤHФHŽă8’$ˇ!«�����(ŠŁ8ŽăH’$I–¤IžĺY˘fj¦gzިˇ!«��@�������(šâ)¦â)˘â9˘#J˘eZ˘¦j®(›˛ëş®ëş®ëş®ëş®ëş®ëş®ëş®ëş®ëş®ëş®ëş®ëş.˛ ���БɑI‘I‘ÉBCV�2���p Çɱ,KÓ<ÍÓ<MôDOôLO]ŃBCV�€�������0$ĂR,Gs4I”TKµTMµTKUOUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU5MÓ4M 4d%���ŔbŤÁĺ !%%ĺŢ“ž1&!µ^!‘’Ţ1ž2˘ rŢBă Y�D��Ć ÇsČ9G©“9ç¨t”祎Rg)ĹbÍ(•ŘR¬ŤsŽRG­Ł”b,-v”RŤ©Ć����,„BCV�Q��„1H)¤bŚ9§śCŚ)çs†1ćsŽ9ç tR*çśtNJÄsŽ9§śsR:'•sNJ'ˇ��€��€� ˇĐ@ś�€A’<Oň4Q”4OESt]Q4]×ň<ŐôLSU=ŃTUSUmŮTUY–<Ď4=ÓTUĎ4UŐTUY6UU–EUŐmÓuuŰtUÝ–mŰ÷][vQUmÝT]Ű7U×ö]Ůö}YÖucň<UőLÓu=ÓteŐum[u]]÷LS–MוeÓumŰ•e]weŮ÷5Ót]ÓUeŮt]ŮveW·]Yö}Óu…ß•e_WeYv]÷…[וĺt]ÝWeW7VYö}[×…áÖua™<OU=Ót]Ď4]Wu]_W]×Ö5Ó”eÓumŮT]YveŮ÷]WÖuĎ4eŮt]Ű6]W–]Yö}W–uÝt]_WeYřUWöuYוáÖmá7]×÷UYö…W–uáÖuaąu]>Uő}Sv…áteß×…ßYn]8–Ńu}a•máXeY9~áX–Ý÷•et]_XmŮVY†_řťĺö}ăxu]nÝçĚşď Çď¤űĘÓŐmc™}ÝYf_wŽá:żđă©ŞŻ›®+ §, żíëĆłűľ˛Ś®ëűŞ, ż*۱ëľóüľ°,ŁěúÂj˰ڶ1Üľn,żpËkëĘ1ëľQ¶u|_x Ăótu]yf]ÇöutăG8~Ę��€�€�Ę@ˇ!+€8�Ź$‰˘dY˘(Y–(Š¦čş˘hş®¤i¦©ižiZšgš¦iŞ˛)š®,išiZžfšš§™¦hš®kš¦¬Š¦)˦jʲiš˛ěş˛m»®lۢiʲiš˛lš¦,»˛«Ű®ě꺤Y¦©yžijžgš¦jʲiš®«yžjzžhŞž(ŞŞjŞŞ­ŞŞ,[žgššč©¦'ŠŞjަ­šŞ*˦ŞÚ˛i޶lŞŞm»Şěú˛mëşiŞ˛mަ-›ŞjŰ®ěę˛,Űş/išijžgššç™¦iš˛lšŞ+[ž§šž(ŞŞć‰¦jŞŞ,›¦ŞĘ–癪'ŠŞę‰žkšŞ*˦jÚŞiš¶lŞŞ-›¦*Ë®műľëʲnŞŞl›Şjë¦jʲlËľďʪ)˦ŞÚ˛iŞ˛-۲ď˲¬ű˘iʲiŞ˛mŞŞ.˲młlűşhš˛mަ-›Ş*۲-űş,ŰşďĘ®o«Ş¬ë˛-űşîú®pëş0Ľ˛lűެúş+Űşoë2Űö}DÓ”eS5mŰTUYveŮöeŰö}Ń4m[UU[6MŐ¶eYö}Y¶ma4MŮ6UUÖMŐ´mY–ma¶eáveŮ·e[öuוu_×}ă×eÝ溲í˲­űŞ«ú¶îűÂpë®đ ��p��0ˇ ˛���ŚaŚ1ŤRÎ9ˇQĘ9ç dÎA!•Ě9!”’9ˇ””2ç ”’Rˇ””Z !””Rk��8��Ř )±8@ˇ!+€T��ăX–癢jÚ˛cIž'ŠŞ©Ş¶íH–牢iŞŞm[ž'Ц©Ş®ëëšç‰˘iŞŞëęşhš¦©Ş®ëşş.š˘©ŞŞëş˛®›¦ŞŞ®+»˛ěë¦ŞŞŞëĘ®,űÂŞş®+˲më°ޮëʲl۶oÜş®ëľďű‘­ëş.üÂ1 G�ŕ �@6¬ŽpR4XhČJ� �€0!B!„RJ!Ą”��0ŕ��`B(4dE�'��C)¤”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RH)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ©¤”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)•RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”R �Šp�z0ˇ ˛�H��ŚQJ)Ćś1ćcĐI()bĚ9Ć”’Rĺ„Ri-·Ę9!¤ÔRm™sRZ‹1ć3礤[Í9‡RR‹±ćškVk®5çZZ«5לsÍą´k®9לsË1לsÎ9çsÎ9çśsÎ�ŕ48�€ذ:ÂIŃX`ˇ!+€T��ĄsÎ9čRŚ9ç„"…sÎ9!TŚ9çtB¨sĚ9!„9ç„B!s:č „B„Bˇ”ÎA!„J(!„B!„:!„B!„B!„RJ!„B ˇ”P��`�@€ «#śŤ˛���€– R΄AŽAŹ AĘQ3 BL9Ń™bNj3S9ťtjAŮ^2 ��€ � Ŕ (řB1��AĚ …U°Ŕ  ćŔD„D� H»¸€.\ĐĹ]BB‚X@ 88á†'Ţđ„ś STę ����� �ŕ�ŕ �""š«°¸ŔČĐŘŕčđ������ř��8>€ć*,.02468:<��������€€€�����@���€€OggS��@–������ţ–ç=���ý‚ĺ'02310276;:?CCBl…‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹¬Ř%ô� B8FkF#ëśzĘýýžĺJI`ź76�BU5Ŕ: š†îK.Ţ[/IŔ´�€Ž€ ~ĘýýžĺJĽçŰJŮ�@!TŐ€ €z wP{@Ű‚š_,ß5đ�č@ �4�>Şý˙üĺ žíkĄ°�@!TUč�40ŃMtô™ŕÉĆ+ÚŮÁ…WÇŃ�ŕĐ>Şý˙üšäâvy¨��…PUˇô…|G×OӱЂ††ŤĘpB�Ŕ&€>Şý˙üšäîô4Ş��…PU'�Đ@$¸O}Ç%ĺMŇ;†:€ �žä>Şý˙üšäŇ´đ&”��ŞĘ* @tđ¨�´ŻůçaďĄ)t (�4 �šý˙Ore1éM0�BU � 1>ŢY(ĄÚsYŔ&-RJgčˇŐMkňŘ�Ŕ‚Všý˙OrĄ2ŘĺIŘ��UUĹ@EO5š‚nE~IôĆÓ´JĘqŘ)d˘U)°�$ @šý˙OrĄ28ĺMč��ŞĘj� ĚY¬"ćeç…d×Ôdţ‘ňcw˘ ±“¦‘�€d �:šý˙OrĄ`°Ű›¸�TU:�Ě]ą¤ż\Ďôł\ĎŇ{QF<Q©š5¬ĐŔ`MX�Ţyýök”‹2íMQ#�€B¨¨˘Đ@§ŰŢçµNUďáM őNůf 3 óéęĐÝlC–ž >H4¬T05�Ţyýök”‹2¸ŰŰXaE��…P + &EŇć^ ô#©”¶&Ú!ir}Ď””Ωaku¨“)lY ‚& H�Ţyýök”‹2íMP�*jT�¦Ţ1{Ă1^_Y•˘~vçt×Zź}ą.󭡣Ş9“5\1á)č�ŕÁ_#�Ţyý˙ţĺR Ny“�B1ę,�r(¶Řňł4ptmp sR›¦ŁL/g9‚čŢ?®Í0—M)�°”�,Z�ýÎuą‘Ţ8� 4´X‰ h©u Ŕ÷ßŇ‹ö~>SŁČü†ý$=cr¦Čß7˛üĽöŽĄĎĂŚóů0Ę«®bĄ*Żß·ţQť˙Šá¨Y4®#öO¤Ĺ± Š«=ˇ P{údX]ÎÔýÎ59 ŁĽ �„f)6đÖ”łA`fś“€˝^ťÄ!<©¬i0/dĄ·?/Ôhł=é«m¤ň¶Ţ­™žŹŤwŰŐZ:Ý–˙‰^®¦×Ö‘&uVwű’CĹ_Ĺjđ-mŚ����������������������������ýÎu9 %˝ ��Đ"++IŔ@âĘAGŐŤrý5]ó�o”X€Ż/săÎŹ\ú çHÇ‘ŮqoĹóČĐ3ú.+Ľđ¬qÖ\Eť¬SF’ż1R-ŕ"ç ÜI¦ĎlÂjÂÉĚěć���������������������������������ýÎwŮ4-=� ĤĽĚdŔ˘ĐI�ŕ,1ć8‘qü]âj­_č÷⥧Թéô''ďsçőu|‰ěwűćąĹÇ덳Î勝/óˇ<:Ť0ŃŰĺ$ˇÍ˝'—3Śí®Źý ��������������������������������ýÎu9q­< B�ˇˇËÂÉgPŕ€~57kg§Pżd´]ťŮŰüąśÎoż’_ÚąJs)ęă5÷Ňä¤Ĺp–ô•őÚc\^;ÇĄQĺtFôb[D®áě §­“������������������������������������ýÎ5ąVŢ�h’ʦˇ�°@vvż´ÄNľd ¦˛˛Ki*Żu67[`&· 'nV‡§Ż‹“ËČcşt“źq’C>-VţFźÄĽVŮ ]ęďtGNĘy49ŞéaśŮMă����������������������������������������ýÎ5ąäŤ�ˇXd‰‘��\%`8Ź'\ŕÁÁű5ôŹ7‰M?/'x‘,ׯ×}YżŽ˝<S«oÍYI¤Ű0î­< V_R†]ŠíÍžJ];揅śŢí·BłHűń·•8NŔŮ˝Dä�����������������������������������ýÎ5ą8‘Ţ�B“„eŔ_™h$�áž''Đ„Y8˘ŘEŃ´Ç˝^‹ďç«Ü.pNë°Ä-’CaĂ+83|©űŐźŤ-Éśť¶ĎUxY¶]FVS”ijúP[éFÂR/P:Ű»r��������������������������������������ýÎ7ąVľ�BłE R€�Ŕz+ó´¶źëž•ĘĘőŠa߉xR=¬±7ćL‰Ű &‡Ü‡nʬőúÖVŢk\ńÖďá šŰŻŕ´şBĄ:ť1Í“‘±‚¦bDţö1Í ‘Ş®���������������������������������ýÎuąž8� ”Ôe…1ŔŇ,�0™ŽŽG­;H`5ŰrŹbR;VsĎÜlËŚjąPĎFlôn3¦°íúuPéˇ)`,V*ýxuC=ľ™rN:Ś…ý¬=Oä}w�ôöúEad•”cb�������������������������������ýÎ~7Ů8Yľ„�ÄËJ84ÜcPČąëlbĐźĄBâŔ(ŰŘ,·âŠĺ$ł¦ÂĐ›. üdĂĆy/”cův€~ý—:lϬ™Ńk8Ĺuoć”HȆ¨$Ĺ ö˙óŞÁžą˝Ď�����������������������������ýÎuąáiH��Z„®0rea�X¸¶(cp˛KÓëćtŹ#)s:.ĆhëřL¬^ł)Z Km“IJyś’c#Qďln2t6]ň~’]¦NĘ „*đ<´o1X¨ŔźÇŤGSz��������������������������������������ýÎuąŢ��B“¬<•@ ľÁq] ŘÔÜ‹ć$"93Ë’zlć3Ĺ—ŕ”S©Űď“á˙ąz)ĺLżĄě. ›&ukbgÄ÷Ę_:`…HňcĽď0D64úb‘˙ďŕ 9ű!r�������������������������������ýÎuą(‘ž„*�B“˘Ś«Š:�>nmcfFU)¸¸đ“‡0ŮëŢľŐŃřÚťť·•ęP[©+T˛űëĽý2š\ÍüÓSś ÂG.WąčX©;·ĺUFŔhkôo†FŘ/÷ŕÂu cĘ:����������������������������ýÎ7Ů–Ţ8�€š• ›0Řd7•\´×vÎ}ií˝E«MÝ M3‹Š"äđşĎ~�µ˝bł¶#Ah©'ňR‚ŃÖ&y,]~ŹMG“-9¶#8…ţůäëĽ#s/y����������������������������������ýÎ7Ů8Qľ1�•Ö%8�€iup;š¤_I5…ŔÖ˙?»ŘÄ&ňҵäduˇn±{˝ď…é!‰ź»:•¬Ď…úzĹU.25žBÉ–+Ęčq0ďîQ®*/CUë&^ܱé-��������������������������������ýÎ59q#} �„&ĺ68ZŔ€ôĐ»t3ü´WÜ™ohm™äP»-¨Y?Ç{ž°ęŽLXř8&MöŘ f%ŞO©›÷NÉ”‰ÍÍŻ®b˘ě˝›ěÎČPwˇëWI;–���������������������������������ýÎwŮ(–ž„*�hRdÁPL�ŹKşá?7śŹI—»BťŹÁ¦Ń|_O&±arúäşvQŕ$ąk×°d­Áĺ˛l­Lc/["°R#lcú,čVŔÝťŠ…«äŕHďbz¬Ű{8T�������������������������������������ýÎ7Ů8QŢ1�ÉJQ3=H�Đi—Ť ‚©ćĘě•ä¸óM6żÝRmöµ|fę «ö’ô<xQ[‚ Ł§Ý˝űjTż03Ѹů-ťao8*Ę.B¶ŘrI…Z™Řyůۙ�����������������������������ýÎ~5ąQľ1�ˇu‰8{,�U¦»k,ż,¦żW}IŤŠZGv›Ü ý±gěÉVöCA’ô—”4#ŤâwLqu;Ž‚óÄ~BH Iţ҉fDaďŚne›������������������������������������ýÎwŮ8–žHb -ŞˇŔě�@cŰweł¬fô“'ŹI»#ł:«/Ősâ§ LNao—Ĺ’üłęÎhrŻ‹K5«ŚámŃ {Ř–ÔnČ[oČcőQ˝‚€ď šî*§ťÚč˘ŢŢ���������������������������������ýÎ7ŮâŤ�ˇ˘`đ&@ ď~Ë˝r8zôľóG­řHä·5ż^yÚxńé\™JfŇŹ}ŔmťŁvŐľËôLa!9'ěbŰ>ŃZ*Ěü(NbÎŃŮ&ç˛����������������������������������������ýÎuą–ž�@-jIÁ�…&&č�h‹o\ś‰[˘�ĆüJx2ÝĎÚ ^Cí7^:«YGcF¦ňO}žÝşž‡žŹ™‰°±<É~ľ¬2Pâü"SÔމíőŞŔö™!Gć&ŰóS’‰ťÎI…¨��������������������������ýÎuą8QŢ(�BĚR¬H¦Đ�‡YB,’˛-oĹx3CT§XČčňßŃ| éČÓůĆű<Z>Ńł^^Ş„^‰)5=‚%G˝pďb€{fy](€>(Ö÷:;ubjŮ:kGG:�����������������������������OggS�D¬������ţ–ç=���‡ż˛¬‹‹‹‹‹‹ýÎ59 %˝ ‚��”. :�>Źţč]µ¤?÷TăĚxL€çsÔžM›PĎXšÁ„—+q!^#§ĂaâčŃ|S«ă.Ź„™ăµ•«Q®%(Îsnĺ¶©lő]”N-{~����������������������������������������ýÎwŮ8™ž0�B“b…qÄń–�X9vń)l}¬ĹĽŹb‘Id=!ű…t,W˘7Ĺć˙9±¸őń­Ű˙â® Rńçf©ÄŔoé<ďËÓ—·§ľUÚe±”׫üpő0Úé[(wÉÎ"…��������������������������ýÎuą8–Ţ„2�h–˘"hrđČ)=|éP›Ű"ýr:Iň"qŤö°Ě蹨šŻŕŽľRß"ŕ€"«•uŢ˝‰ ţąÚ‡żš.řAsń©Ďnµďn7…Ł'ňčeŽ~ ���������������������������������ýÎuąŢ0�B Í c@2 �°8čä>h»ď_6zyu—YĆü|$Ô•»ąŽëö¬ĘŔĹ…÷ńSěŮ n'E˛0!¸¶˛ö>~v3üayč™zź›9óMÔk ¦1×5„éW7ń#ŢežČi����������������������������ýÎwŮ8‘ž1�h–bŢFh‚�¦zmśňp4<Żĺ˛é*é´ ´ÚäÂśÉ"N¨„Şń|oâSđjl’ĚôřśxŽMqIß–Š'S´Î—WřKw®˛:&U0TĚn;Ť‘bbž ĆPČe�������������������������������������ýÎݞ 8��P‹<Üŕ0P8ťÓą­j� �x�z}]Ü�����������������������������������������������������������������������������������������������������������������������������������������������������������beetbox-beets-01f1faf/test/rsrc/full.opus�����������������������������������������������������������0000664�0000000�0000000�00000020235�14723254774�0020553�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������OggS���������'·o����IkéOpusHeaddD¬�����OggS����������'·o���Ń}¬˙˙˙˙˙˙˝OpusTags���libopus 1.1-beta���%���ENCODER=opusenc from opus-tools 0.1.7 ���TITLE=full ���TRACKNUMBER=2���ARTIST=the artist ���DISCNUMBER=4���ALBUM=the album ���TRACKTOTAL=3 ���DISCTOTAL=5 ���COMPILATION=1 ���DATE=2001���GENRE=the genre���COMPOSER=the composer���DESCRIPTION=the comments ���TOTALTRACKS=3���DISC=4���DISCC=5 ���YEAR=2001���BPM=6���lyrics=the lyrics���grouping=the grouping8���musicbrainz_trackid=8b882575-08a5-4452-a7a7-cbb8a1531f9e9���musicbrainz_artistid=7cf0ea9d-86b9-4dad-ba9e-2355a64899ea8���musicbrainz_albumid=9e873859-8aa4-4790-b985-5a953e8ef628���label=the label���publisher=the label?���musicbrainz_releasetrackid=c29f3a57-b439-46fd-a2e2-93776b1371e0����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������OggS��€»������'·o���%Q‹32Ú€}y{zvzz}}~}wzx|{‚uu{{z{|{syz|€qv}„}t†}}{řű» é]F˙ů_2prtĚĆhć­NĘŕšgÔ—Ô)Ľ˛X$ĂŢ}”dł,.tśUë!vq|�‡% ФŤŽřŘGmŇĽID^?Q3A› ±iŔ•ó`Łżdx;ĺ3nĺOć{0ŮĆk˝XQĘŞŤhég8rľ¦ô·ś¬§5ż“©˘,˘˝•@›˝ŽwžčŐí^†ý%Ay÷×xá$ Cšéßä<®lĐ/ *„ ďf' aX;‡]Ÿň¨ôţĂ…kŰ…łó\ÖÜ[ěݡ$0®ßݲt} =2řaĺ‚›Ë2`7 ŹŇÓÁöâ�É|„ä°d±áä(Ď4ŕR°©Tł¤ …'ˇâ Ů Ác.•†ęz.’Ú2óŠ,¬ŻĹf…9LômŘP*•î ‡ŇΡXÔĽgąÎĄ†’#—ö—«  =ą+˘í–—Ëo’Ň '>/řaäL’–8›N­u@€•ˇBÚô]…čH>pEĆYK©Ě&—g‹‚Č<l:GľŃ´çřśĺk×ř<ő”Ůby˙Ń'š‹ą© 'ŕňjCmľ´ŰĐ`3ü•Ü ńT8m^RĎ6jżĆŮ…öáIçślÓ¸ňZć>xÚ'řaŕ+¤ôšŞ˝P§0ľĎţ›l2nNÖE±d‘ÁŞËLŠ5ź”.ŇúšÂ‘Ç <´˘Ŕ “ É}`n´ńDc˝býşA{•ňď@ę9`FSô<uť‘ L”Č4a°›f¶~É˙€§‰C$ú4<5 ĺpć0yçŘcřaŕ<O_=´bb^ĹĎe×´JIÁĹŁÇçđ×ÝŢŮŤ6îę H–4żÔ ÂŔ™(f˝d&™ĂáxĺMéżw»0č FˇBŚśĐłÄaą„*Gť“ńP·§°k}r3 XŁšň«’l,:NňŞ Ę…Äv§´— ­§şs=0äřaäF“šp¨FyŠËO…ܦŦSÖí5ËŇę‚1Í/ďĎŔ3Ţ5ßąYą‹áę* (=h4„–ĆÓ\ć_Ů÷ÚKI†a…jLWë T…•¨"€<2Â…ęŔEYçÉ@Á$Áďý'€ �qżŚÖmŁ< o#.ę Ořaŕ$*kŤÉ×t:í#‰µ˛"#pÖ˛|%DńtKMÓ:'™*˝y;Iýł°€I{®[$˙Ś[źneäBÜáFr"9 %˘ťŚ>© ÄÖHgDJřůף·Ł]燿–«<Ň& 6°?ç:ó1‚ő±MG¬“ĐŻřaäLÉ˙sŻ!jĺN,ĽÉH_{H¨šsť±Š3.!0Ć\}7ü_j—Ş·"h E¶pÇ-Ęďc¬˘ÓYÇŤ˝fĚčÂýť\†Ű3 Ú"ęUkˇ»»ş%×̰ćm}Ť¸ľÁ†e×:4鑜}Ë„%V¨źdZ f Fřaŕ+Ą#)}Čěľ×`°Ď<‘=óüYó ł–ŞĄĄ;µüŇ‘ŽŽm†řom˙6ôs ]FÍźdÄoSxç‚lĄ.šĚ»Ż«T¤…‚qŤwäŽk߲O׊ś_ŻX8±<u•/d7JÎ8/)”ŃĺľËŻ'đ•mdřaŕ%f–只ŁuÇűé€7”Ą\uřĺQ*n:„‰sřڤkŔ…‹»”ŤghŤŔ^—A{Ůî_ú%IăŠđbČmŃ.Ô%?&Ůń9išË†ž>_I¦×­ŚăÁ/„§80\rs’U †`Fś*˛Ç�µŠ#ě ß“Uá‰uk¦bSřaŕ$ü ő|“wÓČ6V2$ �¨ëg<yx0ŘMš-yę>Âę»ŰKB5·’¬_h )ýőěňFł™WĘĄUš ]VE‰fą5Ďĺ°Ł*6s\˛·vâŰÜ ‹E“ Č6úĎ˝ŹŁqpâ �†Ž'ŕď ‡Yřaä[J�Ŕť5úď Ĺ(zŽź-/§©ě®)ăD•ᣙo\bŠ—2zŃ]7ĎŐgĄšC6rżľ'#"×VŃ fŢe‘ĺë×ÝuXb>Ńß>. ŁşźÚ› Kq?ř° FćĺHx8áĘTpáţ*ßźGý~Áˇ˛6‡`Í`y…řaŕ%zőŁA>‹˘Îâ$ŮІҨąŚn+¶˝Ĺ5<őz6¶G`“Ň–¤xĐérhEˇ'đE:µĘsy™­—ÂÜ秲ăű<QÚ¦©ÚůÍ]tpút„C %ĎyZ©'Ž˘ĺŇH”ÝzÄyărÄ÷㇆Aé7,¤žXł¤VXřaŕ<![d“Vfţw5ŮKĽűL…¦ő9dË•§JF1[9vSz?‹ČŚčâĆ‘1’ÄŘćş©"/}ma;Úťô¨kĽľ‡}ĺ †Ř­Pṡ‘§0w˙NóXd)˛kiÍąĺ ]šűiÖ&2qöĺôíŐć®hhřaä`,av]ź¨S yÔÄ2[CÇqW¦tv ¤ßv€ŚhĂ}%xᯆFď ‘âß®K®›ĽÇq§&}ÁÉ3Ć=äO ßÝĺbŢüödÁżeî˛č1& ]ńb`_ď€|A7'×DŐ@J”’·:<°řlţ Lřaŕ$?,îęáH{OĆ?ĺŁ%¸Í/ş0 CÎÍ/\Ťě‹D1P;ňűIw›h{ĘäażPkI–*EŃš<°±ŘĆŘ'‰=íNˇÇ>M©űts[¤6GŮyËŕŠD͢c{ú‰´Ç~·I7XEŚţdĘOśo Á¶˙ęôń"IřaŕCÔ–�ĘnŚ(>·Č4vaŕ·Gś¶ Ż(<GÄRĂdŹĺŞźö7k"˛Öµ1ł(G€y¸JčĎŰ|3ěă.~LÉĽv˛ĐMm@Kg¨°‚°÷kÔP¤ď*ÄyâĆC#–g–+3Ź·0?ş0ÇĆ˙Jřaän6ÝövÖż|±ôćŮ• _ ýĽ'^IU7ájař ĹNľ›âCGűĽxH0 -¦Ę®V…sG˛©±ţj;ęśź|Ű—OŇ6d'[d—ĽÄ_őö+ř ° ÖXŁ„&őŹ ް>5|ó˙1cË€?Ť®Hăööřaŕ„;łD§ŹKCB QÂŢcFKŰ<^ýú|j»3@01—žŁüyšôqJ·t ľs‚Îą”Be͆ODŇ(núň2GÂÓ3~ \ÔÝ9±MęŃČű›°ëłÉşKw=»x¤T$ѨŤŃ[Eo@G|ŮIAŻdüĹźřaŕ<!ĂÉ\VPTěY^Đ}űĽP˘Y ~E+ŮL’ČĽ”‰ÝrCv‡L¨ĂŕS1J‚Ě4„Ý{1`4Č}•ĂU÷=ÁŚŇqŐi™’FÚăÝň+ţ¤>ÔÇíč$â´Üăy©]uˇ;˙ßĘđ ’\ŞßçČ!ęIt@ćŘ+ ÷NĆ"řaŕ„1ŢçNŁÎ‚eŤ%[¦L>Ś�Ż~F ‡nŤ]:nEţ ăc~qs’ÝÄeÓĎ­CßçÎŘçO8·Yăl*~úÝÉ4׋đ[< GîWOďϲŘc®¶«“lé5ş‡XđŻ7"fř–ľFĄl J}’sřaŕ:ř#5ÍM7Ý‚§8:5Y żňNď«w)@X˝¶ŠŃűĆ7JŕłË@őçYŽÜÍLŰ˙zhcX AĆz©™Ś©W6ߊ ô>Ţ5:÷?W ţäÝLż/‡ŔâpuGžŚ˝ŽăşěNËS¬)Xréřaŕ+§_˛Bh+ä9€:ř1ńÜ~M¬Ý\IŹ·ÍŢÓ–iŞ<óÎĐÇŇ(ťkµ ·Uuzc´_íĄői…üPîI|.ÎŚĽKé8@ś#v¦[;m=ÝŤŠP—ĎŮ5«€9UţžÜÔ©o–ݎţ&Ů‹îšŘPD®(`Iřaŕ…Ź6Ř1ši\Eě(,ŮŠłŞ•‡čE] RąŔŰ)FV%ę÷_űŚ‚,2ť+�LŞď;U­A$?µ75b‘ÓÜ_łÔ‰+L¬”í?{yoÖ°Pś· &łGëé|jC•á‡ď@$Üy$.ŔłĎ3ě”q řaŕ!kś¶‹ËpŢýăTďĹjô‡UGwCqâ†aVöĆÍKź<đ}„P'T7G‚ż¤Ä(/ĂT}‹EľD ÄkśźMôş•ĺÝEĎëy-će%ŰFŽý1ŃŔÔ;+\ĎyŔă'ö¦|µuČĂřÖ«?ĘUJ ˛m1QGÓřaäyđä¶ęŤ™»š*şQK—í–›gr¤ŇÖÉ˝ň&“y‰Ť[˙d3yľë5ÓÜĹrËôzf§BBeGý î`ńV-…Q»J÷âßÝ([=´ ŻÔ¶ĽËaż™Z)ýžn·c˝ ˛eelç#ß3ř7ŰřaäY1ŐŮJ˘xô>RÖ3Ţ&Xď%ayŔć`ź÷Ȩ°ăi9xäĎXu’„‰;ËŞ[c_Q|ÇTÔ—ŚÎozeuS=n1hú¤’đž˛g„+Kô^ŐÓe_'^9uĆxĚ´ŐI„±1ľn�ĺ6ăň‹H: “Łbřaŕ$ŕÎ1Ú~ŇíĂüZďzŹ$Ü}Zw ŚČ5Ř[F5™ćEH3H†ŢÝ·xťÚkžÍákŠ×<§TUĄ‘Ř“¨ú¬V=Ď˙t߸µő˝+ FtÂsŔ…=ł©˝1lˇ˝ÎŻmůYŢ1w[ÚĹ«çXÇhJꡟAřaŕ9řbÜYXÝđäŐ€Pj\őůA#12j‹ě—Eä€Vđűľ»řjöŁ@ d˛|/öŻTâöAďĹ=9ĄÍÉ…Î0ÉĂ˙ăěĘqŁĘ9ćM>wP‘€­Űý¦Uémϲí—ůŠ~’G ¦„Š%[>źiZ oâřaŕ$*kŤ°Púň©Kç‚°ş|óřdn:ăZµ ä;7QR}e«á řaaŽ‹®mň?lÂI(q¬čźk6ěUËďďöílžĆÂÚYuOtônľ6ů6űaÜFÜLî¶3.Lla…K Şi szĄSőňF⬩‘[ĺřaŕ„6×ęҡ€7śËĐk¶Ö6ţ ěð–ÂGňÖDů*‡4lĽźµ›ˇ%OŔLoŻ#9™c[oHŹďąS˛x©Ţ'Ż#‡¦SUîu¨@%‘aŕTŮÜáiMů·k;iůÔ©µfŻyťÄ1z #„řaĺ´`áŁrwqEČM’™cCDPçłžţV¤´ŽřŃÓ`\ůÔ‰2†ň5•C§RSńÜíßÝĚ Źmf›÷ś%uť6g( ˝+4pŢΗpůoQ%Čđ;XŔžÇbÄ› VI×¶_ńĄ˙ÂTź§OěU€Ď'ĹÔŻ„&řaĺVąÉśq ‚)˛‡EÁy8E§±ĽDnŰ pPÓőˇŇf_!ćÇŰ+‰vÖôsžÔŐ%Da€'gbiĂY>ÉSE˙“ťHFjf«˙á±%Đ0á˙ç“:„NŮ> SÝ$.7S’·äM)â‰/DPî°'řaäN>=)Ô"µâ�…GˇÓ±&pĂדá%=ŤĂ”č@J߲4i)V÷¶[ÇŔÉV¤OhixŻ´ł<\"ą•e€}Âá*L¨ëoĺ­Ĺ·U,˙ŞOú/Ó)C0}”"_ býň§Vc@9Xě·([+d—_řaŕ,™~‰“î:@HĎ”xŢCż°ŢÄĺNPVĎc„5iL¦îŞ9ôąuOŕŞ,™#IO®ChôÓu+ }®=Cü7\®+ő*ëňŚěöé„^“Ć„űÖęĽÂľ–n9{Ëuť.Cűm×g1féźĐ‰aʬ{iž"I`‡rS Ařaŕ+Ą#dĆ·Ä‚¶ąä]qaV˙8×rňĹ™É>%ŰŤÇu’¬ŢTĂMv$łľ^đ˝(Or' ]˙¶MĄAĄ7Ps«N˛ÇŘ[‘Tí‡"âU¤ŔĚéč•ʢYąőľÓÁ¦B?bî°TeTlťö ΀e¤k0sT?HŕŻßôřaŕ+Ż˙Up)„@ęĚoÜÇ>i+ĄUn{(BkOO$H<ďßA»y™ / SîŢ 1L“§tč1ľö™qúŇ]l÷=§iŞÂ™mh0€!”áßĺJĚ€Ŕüę<«q° ZŘ·r ŽŠc`vĘYp¨Łřaŕ~Ócż^ÇsBtB 8ô»Ń¦ÝůŽ×J ¦j%Xg}pŰ i 4ęRNŮľ/}Q‘hj@¬Ŕ¶- Á�Ăzä;_A bsĽ´ąH™Đ„Liµ=źŕCÎň*‡Wݶ‹ę‚Nl¬€á+/í^†Îů<:qf\«UЇΛ?ÚřaŕsWÚNͻҿŠ}ÁYě’)hŕd P0H©F¨5rű}§Ń”Í—fd1Zo¬ő~©ÚČ{µ�%qLĺ×É1| jĂ üs®ĄLÚśH¨ödé§ń×­ľ€µ~φÜl§'‡‘»~ ćĚ‹čě±ńăV4o1qHřaŕ§Ęt0ľl҇óŇţFĘ„1žž‘şĘ_ÁYŤńÔíaři'ź,V0âĂżŽąÉTkűôb‘€ve/źuÝx"Ôâ,b1ĐfŁ=Űźżv›‹U˝ŕ™fá}†;ĘĎۉ2>u{c?,…Ľĵ,©Î¦QëwŰYŐ&ţń†żmĄşyřaäx» IUíG>ńżL‚ődGNĚĂîŔ$”%¤ /ŽŘŐM@ه»ô•t?�lsŞěÖ›ŮÎ&×"aÄogĚ3G[ŕ€˝˛çŻW%a—PăřÎz¬áľ’F}E­‹CŠŢUeâo{iä–׆IJFĽâpŁ»—¶+±/ ˇ%n˘řaŕ€ f¬i<Čp ,9ňe(°Qćîh9ÝÎ|# ‚‡Ş.U‰«u ¦¦)šťĎ…]ąfý237p–±M\¬wE]ŹěŇ ·± Ő¨ĚÓV*¶MyăYcyŠŔ_yĂ``„w}´á:b<4a˝Ë6?Ůĺ‘„‘DG–ĽŁ}öä÷5q<ýřaĺŠß~šâ0±réG)YĺdP›yyÁ+e…Á7k“#'q8 ŇĄńěó™lÍćLh .~_9|±ŹŔ‡d»7—ŚH¦ ä¤n&×/üŢj’F÷Ě=­b®ŽÓń†Á»fŚ_<ű™QÝV€řúż,~YóăBřaäyDĚôsH‹5żqG Ń µP î�$ö­ĂĆŻłb÷ňlćä¬Đ�Í…! Ło†ÄëWĂň+č%őâ~ÔÄóMVÇę„dŐŇŠľ}�ŕ\"âŠQSŐ!Ü>[}+D˝ý,dËé+"©`ÄOŽź“E–Ĺgřaŕ,{tţ\ŰEpŇŇ€Ë>Sň,›ěCk{ Ľŕ}…}¨ô©wŮ_¬35µ˛ç͵ŃNčđ÷©Płtţ;8eű#eNX‚Çn]­©ťŰ6÷)ű”ć:úF z2č!ŽŹ@kGĺŰ’�źR ©jfTŻ\ĽVWóť"Ů‹DŹP†×ëb…Cřaŕ…‰ż{á‹§…L±3řrń±·%C‰uÂŚu…tA̰ڕŽHćţăopź×pŐčŚ˙„<ł«ŕFfŰŔÁvÉaÓŇŕÝĂřł7Ŕ1Űpß ;5.ÜfY˛kńxľ^rÂS—Żżń%G‘©iĺ…]‡Ş4Đ™ Xůřaŕ……ić#ťáˇI Óí"TŔ ;Lů\^S µRÝC" lú†VŞˇĚMdŐ@C­˙[Ó5­Y5rľ#T¦Ö%^°lílÓ’R0SŇz<äOұc<ői÷o’pw3\řßuöEn8 n+D €Śůžé qůňřaäFÇąöˇč­imŞîŠoĽ~–y¬­(p|Čđ‹gˇźî*tÄ’Iű0ď0ę˝XŃ€YXÖßä°8súoÔ<AÔ<®˝KěĎĂi  Ń{,ćř&\:*Ŕ9x·)W×ýďá`Ť»„›…‰Lş´éNČŔ+ wřaŕ+Ĺ<ĚV´á‘€˘™ďJłź3 zí5ť’a|. D8őbÍ4!ě**_ť]ô÷7î˘ĆĎŤ=r0:§ Č Ą Ó —“&ŐÔűśVm@GĘ—±K¶H§yŮŘĎ6Geµ±“‘Šă˛Ř±',‰˝_x0{ËďlhŮ^j”śřaŕ…6qĐvĹü|L>ÄĘŹK»tXL˘îKă(şđ?Ĺé…/81"Ű1˝‘ŚŰ5G]ŃŇçl=7ÍŃžŘr=ÂN_×ăX5Ü®VĹŔŞś*ĽR^ßÝŢ7ą·`TĐÍçç»ýůě“'ŽyQżXŔqů M ÔŻHPN¤@OggS�äĽ������'·o���J¤@0¤ř}úˇ>Ú¤ŞîpxZ„=BF>ł-&˙ëAĚz"ś:>łÖ'óŞKóqß\Fî:!N r€h «}ń'M¤yz5é›ĆG4®Ëłnś‚QhgŠQˇsŽ’ÁT*†Ą´›NČďŢťł¦ˇX)v¬)ĘŽU,QN ¶´ř©¬}ŽH_Ş«¬2ě2ô©ě,'\IĽe·˝Áfk–ły5ůX�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������beetbox-beets-01f1faf/test/rsrc/full.wma������������������������������������������������������������0000664�0000000�0000000�00000064403�14723254774�0020356�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������0&˛uŽfϦŮ�Ş�bÎlĹ���������ˇÜ«ŚG©ĎŽä�Ŕ Seh�����������������������ÄQ�������€>Őޱť�������Đt����ĐĘ›����� ���������€ ��€ ���ô�µż_.©ĎŽă�Ŕ Seb�������ŇÓ«ş©ĎŽć�Ŕ Se�4���ęËřĹŻ[wH„gŞŚDúLĘ���������”#D”ŃIˇANEpT���������3&˛uŽfϦŮ�Ş�bÎlB������� ��������f�u�l�l���t�h�e� �a�r�t�i�s�t���@¤ĐŇăŇ—đ� É^¨P–������$��W�M�/�A�l�b�u�m�T�i�t�l�e������t�h�e� �a�l�b�u�m��� �T�I�T�L�E����� �f�u�l�l���6�W�M�/�C�o�n�t�e�n�t�G�r�o�u�p�D�e�s�c�r�i�p�t�i�o�n������t�h�e� �g�r�o�u�p�i�n�g����B�P�M������6����W�M�/�L�y�r�i�c�s������t�h�e� �l�y�r�i�c�s��� �L�A�B�E�L������t�h�e� �l�a�b�e�l����T�O�T�A�L�T�R�A�C�K�S������3����W�M�/�Y�e�a�r����� �2�0�0�1��� �D�A�T�E����� �2�0�0�1����W�M�/�C�o�m�p�o�s�e�r������t�h�e� �c�o�m�p�o�s�e�r���(�M�U�S�I�C�B�R�A�I�N�Z�_�A�L�B�U�M�I�D�����J�9�e�8�7�3�8�5�9�-�8�a�a�4�-�4�7�9�0�-�b�9�8�5�-�5�a�9�5�3�e�8�e�f�6�2�8����W�M�/�P�a�r�t�O�f�S�e�t������4���*�M�u�s�i�c�B�r�a�i�n�z�/�T�r�a�c�k� �I�d�����J�8�b�8�8�2�5�7�5�-�0�8�a�5�-�4�4�5�2�-�a�7�a�7�-�c�b�b�8�a�1�5�3�1�f�9�e����W�M�/�T�r�a�c�k�N�u�m�b�e�r������2���,�M�u�s�i�c�B�r�a�i�n�z�/�A�r�t�i�s�t� �I�d�����J�7�c�f�0�e�a�9�d�-�8�6�b�9�-�4�d�a�d�-�b�a�9�e�-�2�3�5�5�a�6�4�8�9�9�e�a����W�M�/�C�o�m�m�e�n�t�s������t�h�e� �c�o�m�m�e�n�t�s����T�R�A�C�K�T�O�T�A�L������3���*�M�u�s�i�c�B�r�a�i�n�z�/�A�l�b�u�m� �I�d�����J�9�e�8�7�3�8�5�9�-�8�a�a�4�-�4�7�9�0�-�b�9�8�5�-�5�a�9�5�3�e�8�e�f�6�2�8����G�R�O�U�P�I�N�G������t�h�e� �g�r�o�u�p�i�n�g��� �D�I�S�C�C������5���*�M�U�S�I�C�B�R�A�I�N�Z�_�A�R�T�I�S�T�I�D�����J�7�c�f�0�e�a�9�d�-�8�6�b�9�-�4�d�a�d�-�b�a�9�e�-�2�3�5�5�a�6�4�8�9�9�e�a����L�Y�R�I�C�S������t�h�e� �l�y�r�i�c�s���"�W�M�/�I�s�C�o�m�p�i�l�a�t�i�o�n���������T�o�t�a�l�D�i�s�c�s������5��� �Y�E�A�R����� �2�0�0�1����W�M�/�P�u�b�l�i�s�h�e�r������t�h�e� �l�a�b�e�l����D�E�S�C�R�I�P�T�I�O�N������t�h�e� �c�o�m�m�e�n�t�s����D�I�S�C�T�O�T�A�L������5����C�O�M�P�I�L�A�T�I�O�N������1���(�M�U�S�I�C�B�R�A�I�N�Z�_�T�R�A�C�K�I�D�����J�8�b�8�8�2�5�7�5�-�0�8�a�5�-�4�4�5�2�-�a�7�a�7�-�c�b�b�8�a�1�5�3�1�f�9�e����T�o�t�a�l�T�r�a�c�k�s������3����W�M�/�G�e�n�r�e������t�h�e� �g�e�n�r�e���(�W�M�/�E�n�c�o�d�i�n�g�S�e�t�t�i�n�g�s������L�a�v�f�5�4�.�2�9�.�1�0�4���$�W�M�/�B�e�a�t�s�P�e�r�M�i�n�u�t�e������6���:�M�u�s�i�c�B�r�a�i�n�z�/�R�e�l�e�a�s�e� �T�r�a�c�k� �I�d�����J�c�2�9�f�3�a�5�7�-�b�4�3�9�-�4�6�f�d�-�a�2�e�2�-�9�3�7�7�6�b�1�3�7�1�e�0���6�M�U�S�I�C�B�R�A�I�N�Z�_�R�E�L�E�A�S�E�T�R�A�C�K�I�D�����J�c�2�9�f�3�a�5�7�-�b�4�3�9�-�4�6�f�d�-�a�2�e�2�-�9�3�7�7�6�b�1�3�7�1�e�0���‘Ü··©ĎŽć�Ŕ Ser�������@žiřM[Ϩý�€_\D+PÍĂżŹaĎ‹˛�Ş�´â �������������������a�D¬��€>��ç� ����������çç��@Rц1Đ٤� ÉHöd�������ARц1Đ٤� ÉHö�����W�i�n�d�o�w�s� �M�e�d�i�a� �A�u�d�i�o� �V�8������atÔßĘ E¤şš«Ë–Şč/���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������h�e� �l�a�b�e�l����D�E�S�C�R�I�P�T�I�O�N������t�h�e� �c�o�m�m�e�n�t�s����D�I�S�C�T�O�T�A�L������5����C�O�M�P�I�L�A�T�I�O�N������1���(�M�U�S�I�C�B�R�A�I�N�Z�_�T�R�A�C�K�I�D�����J�8�b�8�8�2�5�7�5�-�0�8�a�5�-�4�4�5�2�-�a�7�a�7�-�c�b�b�8�a�1�5�3�1�f�9�e����T�o�t�a�l�T�r�a�c�k�s������3����W�M�/�G�e�n�r�e������t�h�e� �g�e�n�r�e���(�W�M�/�E�n�c�o�d�i�n�g�S�e�t�t�i�n�g�s������L�a�v�f�5�4�.�2�9�.�1�0�4���$�W�M�/�B�e�a�t�s�P�e�r�M�i�n�u�t�e������6���‘Ü··©ĎŽć�Ŕ Ser�������@žiřM[Ϩý�€_\D+PÍĂżŹaĎ‹˛�Ş�´â �������������������a�D¬��€>��ç� ����������çç��@Rц1Đ٤� ÉHöd�������ARц1Đ٤� ÉHö�����W�i�n�d�o�w�s� �M�e�d�i�a� �A�u�d�i�o� �V�8������ah�e� �l�a�b�e�l����D�E�S�C�R�I�P�T�I�O�N������t�h�e� �c�o�m�m�e�n�t�s����D�I�S�C�T�O�T�A�L������5����C�O�M�P�I�L�A�T�I�O�N������1���(�M�U�S�I�C�B�R�A�I�N�Z�_�T�R�A�C�K�I�D�����J�8�b�8�8�2�5�7�5�-�0�8�a�5�-�4�4�5�2�-�a�7�a�7�-�c�b�b�8�a�1�5�3�1�f�9�e����T�o�t�a�l�T�r�a�c�k�s������3����W�M�/�G�e�n�r�e������t�h�e� �g�e�n�r�e���(�W�M�/�E�n�c�o�d�i�n�g�S�e�t�t�i�n�g�s������L�a�v�f�5�4�.�2�9�.�1�0�4���‘Ü··©ĎŽć�Ŕ Ser�������@žiřM[Ϩý�€_\D+PÍĂżŹaĎ‹˛�Ş�´â �������������������a�D¬��€>��ç� ����������çç��@Rц1Đ٤� ÉHöd�������ARц1Đ٤� ÉHö�����W�i�n�d�o�w�s� �M�e�d�i�a� �A�u�d�i�o� �V�8������a���t�h�e� �c�o�m�m�e�n�t�s����D�I�S�C�T�O�T�A�L������5����C�O�M�P�I�L�A�T�I�O�N������1���(�M�U�S�I�C�B�R�A�I�N�Z�_�T�R�A�C�K�I�D�����J�8�b�8�8�2�5�7�5�-�0�8�a�5�-�4�4�5�2�-�a�7�a�7�-�c�b�b�8�a�1�5�3�1�f�9�e����T�o�t�a�l�T�r�a�c�k�s������3����W�M�/�G�e�n�r�e������t�h�e� �g�e�n�r�e���(�W�M�/�E�n�c�o�d�i�n�g�S�e�t�t�i�n�g�s������L�a�v�f�5�4�.�2�9�.�1�0�4���‘Ü··©ĎŽć�Ŕ Ser�������@žiřM[Ϩý�€_\D+PÍĂżŹaĎ‹˛�Ş�´â �������������������a�D¬��€>��ç� ����������çç��@Rц1Đ٤� ÉHöd�������ARц1Đ٤� ÉHö�����W�i�n�d�o�w�s� �M�e�d�i�a� �A�u�d�i�o� �V�8������a���t�h�e� �c�o�m�m�e�n�t�s����D�I�S�C�T�O�T�A�L������5����C�O�M�P�I�L�A�T�I�O�N������1���(�M�U�S�I�C�B�R�A�I�N�Z�_�T�R�A�C�K�I�D�����J�8�b�8�8�2�5�7�5�-�0�8�a�5�-�4�4�5�2�-�a�7�a�7�-�c�b�b�8�a�1�5�3�1�f�9�e����W�M�/�G�e�n�r�e������t�h�e� �g�e�n�r�e���(�W�M�/�E�n�c�o�d�i�n�g�S�e�t�t�i�n�g�s������L�a�v�f�5�4�.�2�9�.�1�0�4���‘Ü··©ĎŽć�Ŕ Ser�������@žiřM[Ϩý�€_\D+PÍĂżŹaĎ‹˛�Ş�´â �������������������a�D¬��€>��ç� ����������çç��@Rц1Đ٤� ÉHöd�������ARц1Đ٤� ÉHö�����W�i�n�d�o�w�s� �M�e�d�i�a� �A�u�d�i�o� �V�8������a5����C�O�M�P�I�L�A�T�I�O�N������1���(�M�U�S�I�C�B�R�A�I�N�Z�_�T�R�A�C�K�I�D�����J�8�b�8�8�2�5�7�5�-�0�8�a�5�-�4�4�5�2�-�a�7�a�7�-�c�b�b�8�a�1�5�3�1�f�9�e����W�M�/�G�e�n�r�e������t�h�e� �g�e�n�r�e���(�W�M�/�E�n�c�o�d�i�n�g�S�e�t�t�i�n�g�s������L�a�v�f�5�4�.�2�9�.�1�0�4���‘Ü··©ĎŽć�Ŕ Ser�������@žiřM[Ϩý�€_\D+PÍĂżŹaĎ‹˛�Ş�´â �������������������a�D¬��€>��ç� ����������çç��@Rц1Đ٤� ÉHöd�������ARц1Đ٤� ÉHö�����W�i�n�d�o�w�s� �M�e�d�i�a� �A�u�d�i�o� �V�8������a�1���(�M�U�S�I�C�B�R�A�I�N�Z�_�T�R�A�C�K�I�D�����J�8�b�8�8�2�5�7�5�-�0�8�a�5�-�4�4�5�2�-�a�7�a�7�-�c�b�b�8�a�1�5�3�1�f�9�e����W�M�/�G�e�n�r�e������t�h�e� �g�e�n�r�e���(�W�M�/�E�n�c�o�d�i�n�g�S�e�t�t�i�n�g�s������L�a�v�f�5�4�.�2�9�.�1�0�4���‘Ü··©ĎŽć�Ŕ Ser�������@žiřM[Ϩý�€_\D+PÍĂżŹaĎ‹˛�Ş�´â �������������������a�D¬��€>��ç� ����������çç��@Rц1Đ٤� ÉHöd�������ARц1Đ٤� ÉHö�����W�i�n�d�o�w�s� �M�e�d�i�a� �A�u�d�i�o� �V�8������aT�R�A�C�K�I�D�����J�8�b�8�8�2�5�7�5�-�0�8�a�5�-�4�4�5�2�-�a�7�a�7�-�c�b�b�8�a�1�5�3�1�f�9�e����W�M�/�G�e�n�r�e������t�h�e� �g�e�n�r�e���(�W�M�/�E�n�c�o�d�i�n�g�S�e�t�t�i�n�g�s������L�a�v�f�5�4�.�2�9�.�1�0�4���‘Ü··©ĎŽć�Ŕ Ser�������@žiřM[Ϩý�€_\D+PÍĂżŹaĎ‹˛�Ş�´â �������������������a�D¬��€>��ç� ����������çç��@Rц1Đ٤� ÉHöd�������ARц1Đ٤� ÉHö�����W�i�n�d�o�w�s� �M�e�d�i�a� �A�u�d�i�o� �V�8������a����W�i�n�d�o�w�s� �M�e�d�i�a� �A�u�d�i�o� �V�8������a6&˛uŽfϦŮ�Ş�bÎl2K�����������������������������‚�� ]“����‹�„����ç�� ��ç“˙@�� ÓĂĎ6Ó‚}mŕ ;€¦Jŕ$´ć“$ťÜ$›ě ?�'qU1Ů1%¦4©€` -I$Ŕ�IPťc Ô I¨R„UI10U5R’I‰¸IjR` Ů$Ä@«JTBTÓI�Ą1,E4” ¦�”Ň’ŇŇ€I:˘„bfĄ1&C�›°�` łQR@H@1Vš`𠆀H “�:ÓP@¨ţĄKĚI^o'»@*@�7C˛@ •ÂL Ŕ$¨Ä %©--¦ši!T€H””żHEPůjH@&B4ŠJJÁóä"„0Ň•…ý Sƶ°|µALU2”’k?ßí/¨/ßżĄ,B(âýqôŠ1BŇĐ~¶¶š-ÜKlřż!+t„H˘„„ĺ8Ť H¨‰ˇh-Űߥ)B Űô-P_”PVéCäQný"—év0{eýIBŇŇŞ¸V‚i~·ĆüPč6ňµA \@ŃĹA KTżÚŰëpăĄk‰ż, ńřRѤ ­żvŮFPµB(âó^®oă§ŹÍů»yZŔUÁ”P„ă(Ŕ\KYFP…»}ż÷o¤ÓM–Ę?_Ą Rýmřâ·> ·× p?,ŁŠÝúđ´ŕ*ŕ}Ćý–­ËKx)~üţ°óyKďŇoéăýұ~ěůĽ§(đďΚĆó\yďű§ó¬sXß•?´­-˙µ´[–¨}BŢSJŇ8řlj9ďůĺ>Á·÷úýqş_ͧ‹őEľ±ř˙'<!\üë‡÷ŕ|dHZýeNÝű±nýq§ń‹ucgµce ŇŐŚ˙Â9ďHý×R˙ŽśöʸÁ6óTŕŞÜúXř%[ü­ĎéĘ0p~ýqŰíŮě\C¸‡ăEżÍń§÷X˙˛iŁV7čNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN����ç��J ��ç–˙@���üYż�pánI›+çâ«$@ ,ťDĂaa $�N3Śë�2�B’�R¨ddLˇ!# MÉŞ$±jmD BED U5 $ B{AI„›  „™e‰0[ˇ/Ę ‚ ’ţPR‰Â ÂP±jI� ) !%'e .@„‚*Ä„şÁ(0T€–€h' H¬„Čl’P�ş5&CHA$Ť];ĺş +Č2g`H‚@*$j‚¦˘�™ ¤ΔI ŁJi.¤„†‚„_aĄ4Ĺ(U•PŔˇ%$E™@/а@)„R€ú•ިRţ„¤ˇn)·%!ř4PŠiH”?¤˘·@(|¶V’_”(Đ•‚Őßo $ÓI¤!ľXyčŠhˇ…şYHJךĘCĺ‚]¸|ůóđ¶ůcĹů!iňe]! JC ńq[ĐíÜx ‚ú‡ĎŹ›}Äž.?ÓńGšˇn€•Ľˇő˝mö?ŻÍ÷ĺnă[ăü“E’×çůq'ʉinÜűÓH¦•·ř+Zˇi(ă·ˇkóü˙I·ţ©Zü¨Zó_˘š_¬8–éü¨ýżvčÁ]uŚŚĄmn•ŻŰĺşŕ޸ĄÄTR´íÖ­é·Eü–‘”í‚Dâ·ţÍżhýĺ+BÝXőŽ·E8[źäKĆů)üß~FŠhČ“ňtżä“ć˙4ŹÝąÄJOź­V2ŐcˇŇô`$[ë=żYě·ć«Ţ˙P‡˙›ĄßţoíŹĎ–ŚöüíĂňČŘ nßűđ�,«ľ/çř† ź‘\2˙Ç?:ŕĘOçűý-¦śöókoň—ő ţ,JÓďĐ~µ\?›ěˇkŹ[ť˝cq[ňśÇĹGpńŕ“ô˙ô·gçHˇj±–čˇŰçżóÔţ–‹ĄÖĘŘŔUŚNNNNNNNNNNNNNNNNNNNNNNNNN����ç��x ��ç–˙@��o�ćt,Ď�,el˝ŤP0uÉÍŠXq3±2Ľ1 j , ™Đ Č@ ‰™i^Ŕj†PhAˇ%€H!°@�Hé‚D0QT–—Z P %jD €d°‘$$ RQ%BĹ‚uĄ(Ii’S2˘ZA0%†II$))2 (HW”ĹBAH1YĆH ĹD&RJĎ@†¨N@b„ ćć±%S-P é­“ÎK;Ť˛DH-;cJ†4CBćČo I É«,`-S,Y%(��ĆĄ)¨)BVÉ4%2 `Va(”ÔŠ¦—١YÔ Rý1@AL�‚)[9”Ąý.ÚŃV„n(~ü¬KäľZĄ)ý‡ô¶‚„>~ú—č·ŃÄ·ŮH˘š_%úiÝM.Ę]źŮK°´V˛ž+sô~_§`;t㎟ߛü°c[źOšă[t-·AĄ ·?üż|T>ˇűúBŇÓęr•Ąˇ”­ńńń-żý¦ŢýŮ·[–Çý>ót~ż'A·Đ„%lÓE(?ş0çÔń­şi )·ŰéĄňßîŢ·C÷ÉnŔ@­ĺ4qşR#)O˝k(ZvĎż:< żĘhŔT"±ź-­­yĽĄ6úr‘Ĺ@[Á*ŤXČů­~ë«3Xöü§=˙tţ¸ż\nÇÇ‚WÜi⦊Ć=©(YťXůşÇ[[¨Ś˙)ýůş?+{ !öt˝/żKhó\vőłÇO„Q•+yĘ-ŹŔUŹ‚[ucĺYň[Đ0ňřqŕ?5€˙tŕ”ĄÄ\úŔN‚‡Á<A? í­üI4?viZ/¨ăEp>§`0ţ¸2š2•Ą†{Qŕx6š>ŔT—ô%ů¤ş[ţđü_y»{7öáć폷ńe)Ę_y â × şŘ즚ŕâşĆ|NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN����ç��§ ��ç–˙@���đ%,V,XU2Ľť˘ d†´µ°DôZb`°D€�« ’ZŇÄ’XŞpÍR/¸I… &`c †˛X¬ŔH1…$�Ś)1´ĚJI,ˇ•�2"MK˘ A2˘B`’*!&©JADŐ€ë$Ši–„¤‰ E&®‘(hŘ+D¤„Ą!¤Á$‰``:–h�&FÄꤸl¶�¶ ¬ÜôF”aŘ…E„–ą‚Cv[0¤á@™Ť00Š&¬“‡)EděEBť�MCQ 3P ľ4R EB( ±JKôżBPPB0č@„„[‘M/ĹĄ‰ Ű¨J€°¤¬Rüˇ`ËTţęOT©QcMĽ°–ż-ZZĄhŇý nĘ-ôń:\ka<i| 4śRš_ŹÝ<_—íb<kOÖ–ź§ňZŁó)â 6V’|ŃŁňˇő˝?®%˛‚ŹŕŇC˛ůňŇÜ­2Ş-ŇFQ YJÓ˙Đ·Űßq&„—Ü_Ş˙ŠźÖPVŹäţÝ”y¤ĐúÝćß:˛M¸'÷€Çî”ůĄŞE>úüÖÓůSÇĹćź~ż×OóÔ~Íp; yHZĘ ˙̡q!ŇƸ8风ßůÓG®/É ĄŻÉn‡ď˙kHóHŔ_µĽýőpşZ…§ÂÝ”şSÄźĎóăüߥm÷xÂiĘ_;t8µn®§ňO8‹ęĆ}”ń~¸Đit˛ŢSźż[¬`˙`޸pD†¸„sĺBŢP_× P”Ö=Ž·BŐc'(ăŹŐcy®'ŐÁ€˙očˇ˙p›cóĺÁ^vkLŁţ_“÷KWQĆ·‚WŘ ­Űź×�ÁV}F §=đ_×ŢSBŐ4ľđ‚,Ą(Ŕ~oóÁ*ÓúǬlů-ÜvÇľqNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN���������������������������������������������������������������������������������������������������������������������������������������������������‚�� ]“ą���Ś�„����ç��Ő ��ç–˙@���“ąĆ_­mšV˙ b†­°&�6˘ H7#@&RI($L™&ú»’@(ÓŁ" h&gQ‡0c "`éě “†2QyJB%,Dš€&`–ŠČ¦S„ 2Ě@0’i jH”Ă*J 1 (‚i¤”  $˘©Ę ÂJa0BJI%Ą­$X €H–„ëD§ X©łB «-#@Á!¨JoV ]G@ĐcĽ«-TËNÉéŞÁł ±Ą†®ŞI1Q‘ąBJ… LÁ’…€Ŕ$„îši¦Ą@“JÄ- �.ËôŠQB@~„HDBŐ%4ST(@JIJء…Ąˇň�XżDS+eóä!/Č~•şBĂŠ”#ö´r¦„QNâ}Jj—ÁŰż·­ŰźŇ‡Ď‘@vxŠ-Łö4>~mĹŰ-SÄům%J-ëU_qţü)4&»)ZVô‡n´·H~·ůŽ?ŇVŠÚ?'ă(O@}ú®Č»gĎŇx·ůŰ­ô¬Vhúâ yíúâÍţ5żÉˇa€é·ţNÚś!mú+źÝ(óKĎ’HĘCëzVČB?YHâC˙4űŹôűЏO›ĄmbŹË•˝Ňďíß´Ž5¤ľOéÄ>SůŰ˙*P¶ĎÉőްĎ|€©ókvî%ľ/αčˇ8Í­Sű§\täKůţ‹ĄóÝëq|·B7K­WOçĹÄyľ#ű}ůůş_Ł=Ę“üĺ˙›ZĄo(·Óo¬zśCś÷·ţřřÖ¸ü#ž˙ľ$ŰÖ–đéóúÓOšĘyĽrŠĆ[Ęx‡šűĘi·×QÇű§Í[¸˙!űüż^t¶{~ř––‡šĎl‚Kupe<o­Ř)JÚŃů¸‰Mc˙>µů­QNP·ů¦ŞŢ%ů˘źÎ¸ Ą€NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN����ç�� ��ç–˙@���üđ-TŔ×ß˝GĂ©=ő�¶4C $š DΠ&I:MOü�`áÄ´€6 ‚FČuaĚTAc áA@a@-2E@i0–,P I$¤ÔŔh“:I‰-/ŤB(, áá´¤ĄcRL Љ(H©JMDÉi@š‚�ŃIQ¬6  Š†KL’™‰‚PL„ÂJRH4Ë@YP 5€" $�†ŔÄ iJH`�$°fb`liĚC`)ŰĽŔçżťŽ¶ënć6€¬Ë ¤†5@Lµ hĂÉ$‚€Ä¦ŠĄ`+:H„¬QE dÁŠQA¦…€E-|ů4�ýúh B‡ô‹ú(BA@Ú)<tĄ/–ťąi~ě?Gé@vV蜎°˘—Đ’‡lú«÷Ďß&„sú mĆŐ2µBŇVßĐ·EĽ¦‚•˛š-ÓúĄ Iâ/‡íoň||Őą[ŃG1ůM¸RŠRVĂęÓo~ţßME4”%oŠ•ľ0i|´”RµÄ¶-Ô-‡Č·#ţ˙IFă<HvP¶üŃĆVß'ÓTQ€ź­…ľ'ČŁ‰6úüQűˇ(ŔK_·Öţ+ć(~š+…ő˝tľ˘Ś~–Ăő†Péd?|?/?*nŰň®AZ¬e¤ůżŇ:ŰËî.$śö§Üg˛xť›vS”RţŢ·GZM6î?× ‹gŹÍÓXĹőcľˇ˙ĺ”ńńĺtř‘ć––«8%ü˛śoBqeżÎÜ˙× |źÍŇůJÝż‹Ť4-QĹ\+UĂCä~UŔ°üŃ€łŰóŔtŰë5Ŕź4˙öÝ-HăX`“ůĽˇóţ:Ć[ýżđŽ~ţŚ˙ňýÖ>{ˇkÍW 8–ň‹cÜCeµżÝEx o)Á1”»/¸«ő€©t˙ź%câG€€¸żóv˙5ćżo©KęÇ(Ę^NNNNNNNNNNNNNNNNNNNNNNNNNNNN����ç��2 ��ç–˙@���b—•aEDŘ%­iBLA2�d 5Ôf¬�vî‰ ”Ő Kd™ @ :„€&*’’!¤!bÔ"f@‘.%©˘¬‰¨ŔZ JR a,’CP†! %3U RDI%×Q�‚ŞÂ$HA@“0™(X�VPÁ 0¦¬¦¬,Ś"€*‚°‰@0RK¨ОІÔL†„TH‰U% ± (Ř ‚ĂľLŠ˘ĂUkI ͬnTbe‚.ÔČÂTs!¦éDư€ ¶Z 1`‚  Ja$¬°Ů k%Ôś±~ý�™5|¤Ąm4;jΗéM)$iH ”>R"˘Â“Y ŇE @[¦€Ä)( %ý55–ÖĘ2J¨·Qý 4!(?·Đ‡ô¦ŢŃ&*>âvÜkOź,)BR’µBi ˇi`·JRůú©OŢčEDŇ(đŠ?K||PýúpńXüD`>'ůN}oăý?Oäč+T-¤ź ·&Ţűe?Ľ˘ßúă‡ĹG¸ńRřľâ§óţ OpřUSoĘ-ő(+\h}O〟ż¬rrŽ7AFP¶˙">„e]ľ[âü‘á,¦ŹÉĄinś§)â¤V7[ź~¸éE[ř©âGQo|CĄřť’VÇża÷Q€żiGëöâ˙ä]>Uľ%·ëeoŔ@T�ë…o)[[|íčýĺ'Â%·>óVÇţÎ _`’¸JŰţ+{®ÁĎtńţíÔ„‹š@óx6’˙ňZ·ĄmOďÍ­%kÂ.źźŕ‘k‰ńZJ|×ć+öSoý­ľŁ÷ćśDqö·ćň‹c­Ç÷”ĺ Ăźy˛·űĘ2šŕŰć©®˙’ŁÂ+VúŕĘ<HJŰ˙ĎőnŁÍŰ«óvVđmáN—[NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN����ç��a ��ç–˙@���µđ" \jŽŽ)™ťFćËZX đZ`Âť±X ËÜ.&©‚j·bÝH–ť€"6˘ˇBHH @J D2 T A$ ŃH$ÓHŞ`ś"NČÚLaU5v "¨’^ŞĂP ”’Ö$еACâ0 BIŞ`Ô(’ZŮ0BA�D�k%ĂH~JBX„%$€AXšpę ”0!)ł$dhë "T)dh–Ů�93¦•'d\KzÓ@lCTd“-3vpć @%„$E@‚Ě"$–Ő�LD˛€ű äBR”ˇiJJ*Đ) ŃT† ˛)%AABBš(M a ¶ž:e$aR)BP_?"«ňZ’€ěŃIH ń'ŚŐJV’ýń„żˇ!SkkBŠżI@ ¬úvŮ+e¤˘›wô[¨â_µŞdţäĐSů-ĘŢ‚x­ŕ~­áú×mÉB_1™Kę8ż@RŠPšľiűĺżŘĘâ Ň€ż* ĐZZKţ3ů#ąB_ FÖ˙ÚÚ×ćűőE9HůËi‡ďż*xÖżO«„>¸Ö˛•şŕÇú[/żYO|šióTŃOčÓ”S””ńŐ„ç·éin•żĘÝ”qۇínÝáŇrůűĘiˇ6ôŹ×ę±ß‡mlv{qńńĺ/ß[ió\kYBÖů?4ĺ±ŇµÄůi˙äůBp°IÇů?~µžČĎn5 śĄ6ęĆĘşü펥öQ€©/řźŰčŔ^iŰĎę‡ůRZ`%ŞĆĎ|‚[uż‹=|#žß»~R˙>J©ý[ň‡ř”-q×óUڵoĎ5”¦ÝžÎ—·`•–Ę+śš”[–­Ďßľ¬u§mć˙ÜNꍮÚŐÄ;çáÓđ}‚QoüÍ»Í�NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN���������������������������������������������������������������������������������������������������������������������������������������������������‚�� ]“s��‹�„ ����ç��Ź ��ç–˙@��Ä�ň˛AP{ő¶ges@ ˛ZŐ5¶Á�ÉJ ĆŘĂƄČ'´„•ź H5 Ť„&*Nżä‚‚’¤T`b J †BRM ’„ŔD/HIŞ*%t. "ЏA2iT"DH Ş™€„’‘†[Q%• HŞ-RL€ @hD ¦pB6@Ŕ"óe KPI ş,DÍB`D@… =ŢÜŰŚAUĂ•dʆb eÄ@D˛HiŇBI’)nä,$JDˇ¦—ÉX€ @BL�ł4‡Č%“)&’”KúJ%lÓĆHĄôˇ!mىvÜa4ŃI/‘ űđřĄnˇ˘ŞVĐŠÁ}ÇHCőĄ´-ˇú8ź…¤P‡ÍĄő’ú‚’·Ć„ĐŚŚQEÉK°?°ů ·Ük°ú ń:´-­§Ź)X>Z[âĘKyO…­QÇKűzŰçŔR•ĄŹäµEĐ8ź?h·ř“úĘ2—éâA·ľ+t¸‚ĘVźţO˙TÖ„mߢž;z×îŘăXŐŠRţ¸E->E»Ľ±—Ďé··ľ·›}»óŁÂ˙B„P˙Í;~%ż7\=¸©Łř_í÷íkň[›ësဩ #=©ĘÚެĎÂűu4ľ·e."Pr…»}4­­3Ůýpĺ’›wâ/˙+{ünýq-ç˝)•ŻŘüż\|x%ü–ÖĽÓçĺ/«ŹÍľ—ÇÍţÖ‹Ąµ”e9C§Ţ­?°µnóuÔ۰?´~¸ëLĺ/¸śC`.7ĎýkôégÔWŢ{~­ůGäšáŔb±řÝ/\JĚí˝ýpţíÔ­ńV[EOËÍ×Mpe6ď[źćźçÉnĘłÝk=ň•ŞRâ"?_·ř ÍÇ{->tűńe`/7ű¬{pNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN ����ç��ľ ��ç•˙@��O3  -đ9˛ÄÉ×X᫣ZŤ¶Aˇ’Ö):�Ő’T««Ŕ%A€$Şd„!±LA’‰ŠĚD IJĐaIJŔ¤™lĄ)% BHLŐ ˘Q%0&Ş%0‘I šHX¬RŠQ‡ I4˘RDĐCe IiŠPł‘&Š„S5$„–�š‚˛ŠP”‚* e! �A;©Ů”š‡DTMPIh"PfÓ2 ‘†XVy“¶@� Íć$Ŕ‘`4s0W–X[$á†HbgD“˝$UlŔ„*$€‰«R’¶DBQBhA4”?&Ś© „¦¬aż/šQAD´¤”š"„ˇű°ţ„-x\‚R*ĐěłIXRí¤RA|š_ r‡Ä qˇjßJGJh‡Ôż¦Š©(·daGHČâmÄŐBBŐ/¤¬¶âMąřZ}VÜ·ĹBhâÍ~IZ)§(ă˘ßM ˇňM [®ĎŤ­ľ ®Aş˙’Ý8ö(ZŁŠ—Ä[˙TëN€“űýżóX ÝnüŁóş?/ÝąPiM?´Đ) vĺ®*ë¸B/Ý˝óühX§őG愾 Iü‚h·×Ďwţz/7ĹűđşÇ ­ŰżvŕźÉ÷…q[ß[ë‚ŢůŔ˙7ů„qľ(âŁöů´Śü[¸đ ż 0Th·~GÍ%˙ZĘ-ţi¬ů&‡˙§Ţl>[Ŕo˙<ý87żË͢—öęVĽ×€€Ř?ýřGőů"śZeŇř$Ąýąý¸Ö7íőpq>q ”g±ă·`•?¬öýWç”çĘú¸2—˙´Đł8·€łŰ=ßŕ7őŹo·ÓĹĹ€­čEců¬§ň®汣ŹÍş}¸đqú ¶ü…¸ ¶ęÇĂćźV™q Xßž® ÷ZŔh‘'äř¸‰jÚĹţ 2•¤á NNNNNNNNNNN ����ç��ě ��ç–˙@���őő1ŕ E.üĆÍĐwa(,± ­ÇB utL(fcDÁ`. Ť�‚%ÉÚI¨€-”ˇ H$„H�’ŘI’S&$…RŕI,™)¦ZR¬6˘ÄŠ`BÔ”„‚)Am)HF¨(C&MH u(”ĚĘT$‚tH )A((�Q5S'üɆÂAQ�ĄI�”3q†`$QD a·S57$Ŕł-Đ%¦ŘÜFŘ,ŽË&o­2ĽhĚho`…„2I4Ča«’ČIĄ a楇ŠS‘蔬IŞěŐJj!4Š · $-ĄôBÚ Ő Jjżă(ŽFO•ŠPŠV…/¸‹çÉ·„„P_Ë'ȡij‡îÇţ.$:)âG[H(Z·-:RÔÓÇBÁő˝l[ĐůnßA§ŠÝJŇx–ę%(+@|˙)+’]šhZKě%/¸że?şZ v˙ČPµnăĘV+šÂÜé|Ą—őŚh§ń…ş€[¸čX iĄő‘-U¤~­ţ$ń~Tż~µűŻŐc?ŔiĄöS”PţßCáNâýůĽ2źŕ­;`>ýľđ‡R-Î"Ű©tsďĐů&ܶ˙)Z[3”żŔX%ýq­ĺ/©MüŁöÓůO„Vč9E4ĺ/ßů¤g·›®ůRšá«ůŇ˙(¬l÷|°[.‚•·ŮFUŇůŠ)ŇČĎj_×ęÜ·ÇűŔhđµ«vPµćßWQůşíoŤýuĂáX ő»ŚÓ‘.P¶éd~·-׸ůŻ6•«vPét~uŽâ'č[“ĹĹů-Ąmúp.—KěöOęßMĽxA÷ě')E6ňâ/š)Ę?#năvË_«r?\N–}€­Ëx*Ę?UÂř× Ýpş^±Ý-H}XŢn´ÇćéI”çÉžç(˘„eĂćźţ`NNNNNNNNNNNNNNNNNNNNNN ����ç����ç–˙@���ç�%ř,HżA~”’Ĺđ!˝ťf%°ÖťĹíz ŔXd€ $¸uŐ€PČ- ’P ’ ™©1 ’J ˇ!5!µ %‰L€I Ś3 ŞDCúĄ¨–ja†“�˘Ş‚ś$ `©…T’$–¤„ �´ ™0 ‰ ‡Y(L ™aP2Á-;*j@ É–U2PvÖT@Ř€€Č&bÉhh €I Ř�´Á*†ZŔş[- ˛ˇ�Ä•6I6É��]XD‚Ђ٬ň’YC¤¬… �S PxŠPP MRH¦TÚ•¤ ˇhJĤPěľ ˇl-?B—ô»)ÂŮ„I¦ŠŹ˙oË ËúM (ˇ&š2…¤>-%+eb”ń?ă§Šßú˘“JĂ(ĹÇ/ß­/Ú]—Ë|r)ˇ FRý%`éz™Yž4')˘š-çŤ"ŠxżIâˇúÇĹnŔAńZZAl>}C÷čn[ý-Łô‚´°?§á÷Rů˙.7ő_ĺ?’+Íe  żĘ8‚ßhóUŤÄř>ÇGě>Ŕ_—ę±–gV’·GçűŔTŁ`'ß–SO·ţĎđS沅Ľöý?ýe9AŁň|!Çćę­äIn¬d»wëC‰§ôýňŢPµ@Jźĺ˙n!Ý,µ”-ľăüÖg_ńŇ_  ”[Ń\cÍq`*-öŕ2ŠmŹÁR8đK揚[ăü˙'ůí€ßÖ3©óO¨~´|"·ů­Q”q¬g_~O‘ŕT¤V6 (BŢ{ y·?¦ŘŕJڤ[˙+{Ą_Ďß-V7ęÜśĄ6˙ËźˇĆ˙>Jk+O˙/ÎÝÄýóĄĽŔÝĹ€Ý/ů× ˝ilŕ,Ćé~,řF‹xüë(§)§Ź)AŁ´ţ––ĽÝce.—ZÁ"�NNNNNNNNNNNNNNNNNNNNNNNNN���������������������������������������������������������������������������������������������������������������������������������������������������‚�� ]“-��‹�„ ����ç��I��ç–˙@��_�<bË7öT•ú>×I°Ć44˘{`ŮRAL—TĨÉ€RĆK@*L)H-`‚ Lfgb*T 1 DÄ�)0™„IEC%0"0¶üeé@v%ţv+"”TŔ$Ś9J*T‰dDĚJBh%auÉ�5)(5!"ę l"©’P„[´ ˇ%ÖM!¤Í I(©8A•Ih5jBN  ™č‰Ń ć[T4oa€ďLlŃŔ“ŰNÎş0fÁ‰$3¶‰)‘TĹ‚H¨v4B*0ŤDJ�)(Ja"­C@(`)!4‚iJA|˛‡ôĂó oŔ‚ŠtÔ,_ŃJčˇI|„RâůŰSCäŇA)|ů)˘”şRí˙B_ŇJ·n[Ąin(Ąn—Öţ:V“”>|ú©6íSúL­»)EDÓů V–ř±đ»z4?|µGĺ”Ón«s÷Ď©[GJ€RÇć˙€ţ±‘NR] _ŞĆ·~ř€âJßęŽ4­>|‹u (·­#)¬oÍ÷çEo–đV¶2Š*üŐ4ş ý:RĐRř$~źŰ’ţ¸˙^k‹‰÷qÍľZvüKKO˙Tľ[ˇ.ŕ|h·~Ń~‰âqö„QX߯ßçnُ‚âýţUŹ”q­,żÍˇmúŢSNSůQ”W (Ę?*ǡo=ßş2ź`‘nßNZă[Ďgë\HJßš}”V2ж_-Űß­­"Ź ˘´Ĺľ›v Ź–ż+rŐ˙F±íĂ=ň•§Ţmý!5Ă€ň‡ůC±mąÄ:?o¸“Ç€˛—ôţN‚JÇýţßÍÄUŻÍ––˙čüÂ×…-ŕ;cÂĆ)â§őŤGď)·ŰłÚŞŰúmÁ4x �+vúÓ—çć˙.*”ľ?żĺV=EJŕ|ű‹ŽŘďÉbšŕý~¨ăt°NNNNNNNNNNNNNNNNNNNNNNNNNNNNNN����ç��w��ç•˙@���5řYŕ J•ţ{lŻż˘Í*'ţXľ 4Ý�µ2€ ¦I €Ölť(‚ŘCś8 A’tMY$Rś3(IK“N’ɨÂA: ,L�LAiÔ(&BAĂ2€Đ0PU( “LŐaI@$B+2„’š€Ş %™(š*U$Ĺ&I5R€DЍ%�Ňě‘%!¨B‰‚P!'ĄR„ŠŁÄ % 8S!�AA�AfXPµF†FťPŇ"Ű:Ëš¤9ʡ—¶&H*–b ¨iB@HBH ÂH@4”¬ŕUv@j0@@ %‰h„PýúIĄňÚ(ĄŘ«I¤…´Š$Ë÷áÓDЍ�GÂŞÝ?[&•ľ7d»4%nŢŠ*Ň”˘ Ďí`…‹ţ*_¤Đ¶8Ňýihq­ˇinÝú lZĄ˙đ´­->©ĹEş˘(éă¦HJxÖčâ[ĺŞSJń$Ł�ú„q->·ÓOď÷o}ćźäKK÷˙™E/ŠŇmëO˙>4SMřéJ×ěeHNĄú]ÝF –Š?5µ«sëz_Ńoói âBş c­›}půşPŹÓńúĄĐ+Íe#)üźŚ˘Ü´¶üľţ<FŽ.>:ý McŰÖĐűŠ”[˙iFRµGďőXůíOäź7žé Ć—á˙š[ă ăZBmâÝű·»o5žä?ń'őůţożOňŠp?•6ńlzkR‘ćĎ們Y[Â\DüËęŕ¬t&±źńeĺżËľűÍçµ.–AĘk÷›tłü"ţŢ˙ôx°é÷›ýůĽ÷/‘Ĺůŕ.?×ĺ€˙\IĘ-î—[˘Ü˙ŹŕđŽPúšĆ}Io>L¦žô×�Ęű®H×ÉÁ/šŔUĂ\5Ťů`<ŁxBŚ”~˙vÇ‚éoÎÝ€­Ř%tąŔN!˙_µŻËÂÎ{eíÇZak=‹ěHN����ç��¦��ç–˙@���bđ#ŐŘňsgˇuÍ"HÚ›]˛ËĂ&;b&#`ť™¦‰,n€h$Ő«±I"%2CĚ Âpŕ5; ¤™I&jÁhh@I-5Ą0Ą „H�J" ©‡EM”ĘH�ČKeŐ)(4Ő‰TH"”dVDK ¤B@NA¦fU€�d¤‚RN’dP–Ą«B`Č„¤ÄI %’Ě&\’K"Ú°7 T¨lžî…XK™¸HřcR%Qˇ…›�É1 !&bZL@YŔ¤“2Ň�HJ*! ™šP±Á¤¤˘“ XŇPL>J""ˇ „±4ĐE¤Rš( „>�4Ő4¦‚ůlńĐ’°©Ä”Rú i Kô? ‘ BÓőş2KńRšh[âĐJVç(ľ¸ź~ÎPP„~Ëę}oĄŮâD”ż(ŁŠÜěRšÎx±|ě%Đic\ XŠGR·FDĄđ[ęÇvÔŰËô[ĘRě-ńţ’ţßBQGíij_~O…ş)Ďk}?°ů9M5QÇoˇŘ~‡é¬z´¶´ýńĹGä< mĎĐ·Ĺot¤óŃńQáúMâvĽ(ŰßţżIZZHĘ0(}‚»~®Ń ć©Ę,Xëv÷ëyJGéiţ•Ş?<hĘ_ĺ6ě÷âăŔyFăü’kk~$;/¸°çů`ŮúĎ{wąŰ~UŽ˙ő\[\�Ö5ąţ -ô,Ę Â| _—Ζý-§(Zü–˙ŽÝ‘- ¸ź~bŻ?;u¸řEóđú‹aÂŇÖS€żKKK3–÷HÚ;}přCň[˘±¸ß罺ܵ斖ř°·>Łňýŕ4>¬t?Ćż*x°KűOďő”:~ĎwŢoµÂ·eĄĄ«çKţĎ…­ř+_ŔăZ®ČNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN����ç��Ô��ç–˙@���ďđ*|¨ßWŻ!˘e‚BŞ( +ÚlI¶D5"ÎŔ2AVTŃH10dACDÁKpŠhĂŞ[$NŃ �Ú€I*ÔBI"Ä Ą¨‚F©Ş  DĚ  [ (ˇa ˇPH/é�‚$Č2PA‰I(�A‚@! „§¬[Q`�L‚° @¨·PDU ťÁI Ń”…I $A2�CZLČ2‘ �� I€2bR©Z4nľÎúu†††BŞŮ`›•l)"[«Ł¸Â@’&!bL8`Ą¦PH1NŘR”Ř:Š�Kö @Ą Th%úLŐE, JP]‡eZ|č<E)} S„SE6đš&˘VĘV¤Ń@[ˇő&‚·oăJŇÝ'Ťú_~«7ďÁ~šíö÷Ŕ>ˇ­ľvô"Źŕq>?› TZăvôš8¨4~°î‡ô Š/Öź˘źŰ·|“\/ĘÚŰę Ó÷ëX*(JÚ”ń!úKoŤ‰ BÝĆíé4ĄúŐ?Ą¸Ŕ\Aň\?ŻČ Ň”[‘A·Űß­ĺOR+… Tˇâ§÷”бý~T[ż_§ütůşEGô­Bm˙¤ľ}n·~x [|ž$V5şŠ–Ľ#ĹžĎݶ -Ö÷ÖęQ\ Ŕ´·ć­éZZ˘ßKţ5żĘßnĘż'ŐŽě­e)[üĐţźŐąŘoŠÝGć|ŢPµ\ۨĘ?Đ´ýnÝžéýqĐ´µůŕ”Qá׼>üňšŕŔ^jŹĎ"9jÜúž:áĘ~–“”ůŁžŘ ?—ĺůĺ6˙ÚÝż­$'ôŠÇ·­[żhĄk=˙4W„<Ó«TŁŽ‡ďłäâ[˘ź×ë=ë¸UÝąőżÍÖ1ý8‡Áµ$Zq _ľ® zاͭҊkŢýĐV’ýn±«„Q”yş_× ĎpűÍţ°Lŕ'Ňú…ż~te/ÝŠŕÁ¶¸2€NNNNNNNNNNNNNNN���������������������������������������������������������������������������������������������������������������������������������������������������‚�� ]“ç��‹�„����ç����ç–˙@���tÇ€€ +WŐ7÷ Žř;!Ą„@č$I¬Ó*ĆÎÚ–€Â„bál.dD”�P"K�a$Ў!¤Č0X„Č€X„ I" RQ)&DÓ �J ‚V “`RŃÂ(EILŇVx1TÔAL A& €°BeŘŮbDPł„¬@T¤¦$ŔŮd´B!"˘V0:5Du¸"J$CH’@fˇ›$LôÖImELG`ă™,h,föĽI!®»€„�f‚„j2‚ B Ă’P@ŞtŚ*•Z)4&Zů&¬PV)JjP‡ácBÚ’ ”>&‡Č ˘ŞÔ—d-ˇů¤Đ•ŠęŠ)ă~VńŰÓĆ�ĄůăĄđ BP?r‡ô5JŘă~„SQl|•µŞVđT˙#;4-”¨@ '‹ŚCę´Ąő!ŰŇů&šhă„~T}~ÓoÁRĆš8Ň‹zݸ¦€_,S”[ź~Ö˛š8¸íÖ쥨ŁÍżK÷ö˙ÍĐ(·ŕ'ď˙+ćëň)óO©Zt·›4­%#öů AĐV<hâ+tR_~ÝŤi(·?~ž%ľ'΀„ŁľNQgíuÂůkÍV9I® ÷â˘ßůżýż ţPŠçúâ[·~Ş~UŹ€¸˙oč·Ö7żôú¸_ľÂâü˛„Ń”!iÄ7ćégčŔkn"żă·‚‡n·”ľ@NRhú}K÷°>oyĄ¤ţgŽßXÉâ6즜ö§"T5ůů´qŰđKJ­?·‡ßżĚż[üÂ+Ť$Ł~k_ť)ýqe ˘Řrú‹tŕ/ŰĄĹđąoo‹őżĎňtýnÁXŐĂžţh×p>ótţht»üŁś^n˘0šB+¸ť?řFśˇ(ĄňßšĄ.–Źáŕ8¸xčMc­y·ÉOš@NNNNNNNNNNNNNNNNNNNNNNNNNNNNNN����ç��1��ç–˙@���đ*˙—ÚŢĹŃłň.ÔcŐŤ Ăgm Ęł53($”˛2A!˛0Á«!—6–ȆŔEI  Ë SwT$,D¤T€K�ÔŠ¤0 ¶€)‚”ˇ´@!˛ hM@‰¨Ó„ Âp’u �Š’ "“%5@ťHHË‚BP†msI€Ů� )HAĐĂu˛RÂ@:%AJp H%¦"MÚd¶a¬ŮDÉ`$Ŕąa[ˇF64\á­ąŤcPeI$’Đť-iI� „Ą(¨„:BA€±BBP/Â#dĐ„Ňţh ŃCĺJhĄ»*,@|—A)ăM)BÔ"”ş�M.ÉYë\KA# ń4%ŮĘúVŤCO…—ĆŢ•[E<KtqŔ¦˘(·żqCęě§Á_¤?~…¤qR% óâvéBRč!ő6çȨýkőJ2:VéBß4ŰżT-»e¤Đ·o@vÖňr…żĚľYÉ[Ęx“ÇůŰßM?~·FS”~tţ–¸’źŇEšÝ¤Âۨ¦‡ăÍŠá·§ő€˙oáKűpâ·qş[‰l---HŔoĘ×ć([Ł=íĎí•®?ŇÚĹn®PţŢŠ+ňĘ?vçůK÷üN!†kt~i– ň€éä­ÓXĹ/–ë/üÝp`'JDeţë÷Ţ·ţx(Č—ňFRVčt¶ S@ZŁÍ~Ž{[ň®––ź8‚EpŰ’í­î–·ń-ÓnOš®ŢkYCďĐt«c°ŇŰî5§˙şŕ·?ăt»çÉü«ÓöRâ- ©·›up[Ťą%6ăE±˙żĘ¸ ŇE_ťcŰđl¬oÍöQXÜg­­ŕ:ĆÁ*ßéiň<!‚JĆ.ĆQ\6ă‚\ţŹĎ(üżUÁűqoýyşá¬jśT xđW”y¤eIX:[ŚNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN����ç��_��ç–˙@���Bđ)«$1ß©jýh°a‰5ueHj˘€B**¤ ÁHidT“1 ĺŃ`h&H…R�5A3-C ’Ň”fPDIJpIT„I‚Âe$ˇ$2 ”°Re•b[4ś"(“PIBEí 2 NČ%( 2Jd”Bb¬A`�Ŕ@H�ĘDČB–ĄIĐ()Tf’& 1)`$©Vv&C0LkA†@ Ů‘V“•`ą‰ ô7*Ş!1 €Đ¬3pI‚I¨  ę¬µ3Q€ĐM)j$Ą4’jP@ )Lš””şL€­ż„I¤‚µHH¨Š’‡Ëˇúb«˛_!cB)Ąm/ŔHXČ}?´Ň)~@I|’Š*ÜKh¬‚dSD¦š˝m…˛J ?¤&łă"mk‹ňMŕ%¤dJh@ˇŮźď#ĺFDy·q-P—ČXĎéî%®>+đ( [–ß"޵EąĐq[Đű(¤Ű˛”şš xÜ•¤Đ_Úi}űZĎ|?ń%«zÚ?hMŻÝ N–~ýiőcŰĘŢ{RýjÝůű¦š“n®?ń?5Ś+±mŘ cćé·-żŔ®$Ňżt`‘ů.HZĘV¨ýľý-!."e%kB ďËŤ/ňźŐ<Oßy¬«żÉúx–“ /íÖę8ü/‹)üđßäů%ţ Ąź>ŁÂ+n‚JÇâ<F¸)4ţ^iî/6µ”ţ_ź›â}nýeOëŤB�Gpgµż)ü˙7Ö˙ÉŇÜT~Xţtţ˛ś€«ÝĹXřőĹůĺ"ßćż~o‰˙éńŔT~vÇ-e%oÜ˙ö…Ľ‰_~uŽéžř&Sů×iž,¦ź+řĘ?,_ś%óĄ"Űä-ÓXĎ˙yď”ËÂ?‘qÍ`<űüëNNNNNNNNNNNNNNNNNNNNNNNNNNNNN����ç��Ž��ç–˙@�� ¶ ź�—ŘÉÝi,“$ÎŻ#M)&Hh3ZkIŃ-:2�&Ai„\�u™hB±I„1$ڍ PPS¦%€$Ât*¦YŠd¤B‚Ń-!! Č’�MPI™$B€Š0i ! ”‘)‚!(–€�„‚P’%€€e«Џl‚„0K0REA– ! ¤U€S2¤ĚČae!Š0 €2aÖB&FČ_[W,l‘ôó•ëń5˝ČbäEF„í,C C(A¨�ŞŔgE Ą­&˘Â$   ˘Vh/‹­+eÖ„ĄiRi(„BhJBP¶ÓDĐĄúR-É Ňţ„$?)/ )ˇmmŘII|‚”Ą>¨)}nŞú·qń‡ët% ŁŤň„‚;n7ÔĺÉ Ć•«pM ĺ°(C°ěˇ4&…ş <`R·\CJméK· Ň‡ř+·%ů·?|¶EKv “ĹĹRßůŹěźŐ OĘŮ@Ę)"šr…Ż7Jh|µMľ„۸lj¸ăń-P•ĄĄ´ľ·ĺ!ý!h`*ŕý¤ŠršŕGî…ľ%®1úĘ2‘ű|—ŢjŚh}oZ⦗Ͽvëy}ćx°ęś g·P•µ‡żňóYFXţIYśG€€xҶ¶·G÷ŮMżŤ˙ä ?iĘ0ćK§ţ$[©q”¦‡kúM±ĂŤ/ř¨·ŕ>5™Űr-ŐŽŹßä‹zÝľ‡ôń—ĺm (ŔX6 Ö7cŕ0ţŘ÷Ëo–©ó_ŻÎ—Kń­żĘ2ś˘ß€ť,·‚ZőĆŠŕ·çµ([F{-ěĄŘ­1Eż=¸‹¸$tµ»ůňmô˙>é{zE9­źÓ˙­çşŰĄ’ţ¸ršát˙\"ÜxßľüÝ,@â}luľÝ”qţéFQ‚L§šá[ZHNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN���������������������������������������������������������������������������������������������������������������������������������������������������‚��]‚ ��/�‚����ç��Ľ��ç–˙@�� ®q~�&<ńrňX$é|N¶t 2ćîňł˛Y �ÁÂ- şfC«�¶IE@A &L$"©  &`j”Š‚ŞJ ()J�&¤CHJ*%W 2P’d B¬Ua P(!%"ŞID%�!ÔA0SQ2LH!-~Ij¤DT‚$0t€€Â „ŕ±*‚,HĐÁ!Öšˇ°Ęş©-“ČŮBv2u˛DÁ ­Ş�ĐĐ –ÁÖ¤‰Ťjf*Fľ ęR€ ™|ĘJHK* š„$Ę[Q(X„€™Z(€)% 4Š©AL� Ňü‘8TÓV�~•‰ˇ@I \O€)…·š•M)JSJhĄ|Iˇl%˙˛K÷Ĺ)ÓÇE%nÍ©%ő%&ß–)+UiK˛’x’±·ÓK˙Úßő®4¦ŢOé+o„­HĄ+gńńCďŇüżÓ”ĄöSÄ´ý4ÓĆE4AZKôĺ?—Qo·%+iâ6ôŰÖÔ·ĺ$ŇC˙7€íëa>mkŹňă[¤ĹÇú¦Šxß`.?Úk…m+x (}”->Ęîšů­ţ_ˇú|‘*׾śV=+’ŢĽč/˛•ĄŻŮĄ_żĎ‰ĐV˛•şá[·�'tŃ”?·ŕ<§ÍţN–t¶Qů[üŐ¸ľý!jP)G›@ćíÜ·ÖúĆĘ|_’ŰĄ˙_µŞhŽ*ż·eĽß6ăć–ü#ÇĆţŢ—ÜyM;86-SůţKtSCôˇú_Űź8†Á-›Ą“oć­Î”µŔü¦¸?Vőľ>3O瀏›ýşYOş—Ôż|’Ś«Š_¬kůçüž r„xA<oĐšk…Ňün–śĄmfpş^‹}cţĽÖQůń?ŔN‘¨Ďt`Úr”qg·ćţ±¸˛šm˙“Ž"ńVńŃnâ}ů[ÓnˇŇďüßš5ŚNNNNNNNNNNNNNNNN����ç��ë��ç–˙@���ŕˇÇËŔ@¦ëľ »Ţö5|Tc@kC L †™bflČ %F- ÓZ¬1$‰-H0ŇJĎŰ0@A‘ ’ĆA,”3,ś4!)c*C*D ”�B Z�×RT@¨FV"e�’ ”˘™(©$ KFĐj¶©RH  �BX AQ"¬ ‹8‚H�„˘`L)Bp@‚* ‚ŠAfŚÁ“3«aCŰ!’Ă-•I#¸-]tĚ kWôYbč)9v±¤ËA(Ă`•  ( �˘jÁÔV BE# .¤jQ)BB J´,)A/ß&˘hZš (~D>v_·H¤-,iŞ  R…ˇ —Á/ÍeOˇúŔ¦š ô…ŞiKňM4ŰŤ(Ş´ŃA AâKńB-ďĄń|$#ö¶€V¨©üĘŠj?hăýÓJRVݲŃK˛ě% k@P´¶ű÷ň´ŠR—A+i·­ŃžÉĄqźĐ<u)ăGŔKżŰ÷ÎŰö”ń“ůR—ţć’µć-ďż#MGČ?—›âýŕ/ÉőEĽˇaÇHˇöüĂűrÝ®?Ý(˘šąB)Ę2—eő¸šöä[ÂxčK4ľZĄý[kóýqqRQ‚µˇůŁ(óX !mo)ˇj‡é|´-ď­ř ž,ŁŹÍ>Ę)ŔVü÷}…” ĺ(~•·ô~c‹=íȢś‘Xč'ŠšĆýŠ_>¬jÓ+O°ńžé 8 Š˙(§Íe/Ęň” QCäҵZf‡öűwŰî,ű)}űŁ‹őúEőŻ5\4ŁŠŘî/ óyGçBOço·~Yďž˙ŻÖĘ<ŢS”ţ_“ĽuŤXö˙Őc'‰."šĆş0ÖĘK¦¸-é/łä§=żYJ8‘X˙Ş+†ßXÔ8†˘±ř’ůmÄ7NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN�����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������beetbox-beets-01f1faf/test/rsrc/full.wv�������������������������������������������������������������0000664�0000000�0000000�00000032540�14723254774�0020223�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������wvpk 2����D¬������D¬��Ľâ¶é!RIFF¬X�WAVEfmt �����D¬��X���dataX�BWWGVH�CĎßďë �����–��e����ŠŮ�Ŕt@Ą¬ ‘ɦfŢ1/°¤% �k^‹™«\ÁĄ”Eăž7h… <–°ÍeŔD*b=p™bE‘ÎÁ¨¸‰±I(€šxŔ‰Ŕ(N%ŞTŁ´°TéćÂé6™r¬‰ťS(Đ`xtÔMI)gŮxL¨Up M ”ľˇhŚ`pH /Bš×`0â@ 7K6ePÉ=J8(p˛Íü†Bd h �sr`ÁMybáhÎn2÷T ŁŹđ6Á:ŞűrÇ`…ŠĆŽ’Ń�%"39>JL9Ć� uě)<ÚL;=&B8GAářkŮFhF”›;n@+A¬P`ĂH›AŠFÖ$FlŠfľ÷Ŕ} f©S›áDś4/°Q¬RŔ+žÓŇ"(â°¤ Ť� WéÄÜ÷´),˛0±µĐMP„Ţ·$Ë„ÇÄÁqŽŇŹFŕd-Ŕ×?©µfsh"¤ aXjŞŚ`˛<˝†Ă•°9Śy1xŽÁ‡�ęrV�śu°<Ő.�™î¤˘Ü>˘q±Ä-8np#á8ŚÜąâC#yT†1=Ć ťÔÚ@ČĹNňc$ŵ’Álrc U˘«‰ $¸O–JŞFQ ó&łQd™Ž4/ z @j�Q g(l ĆÖ43ůJ~ñfĐŚ1¦Š%€¤ĂhfTÔ�ŕôN¸ŕc“8HŕLaD R+‡9QT~„şDĂŚ2:Q›IP�vjŽńÁë atÚXťÖ!�HŞFÜ °�†ĂË)Ě15ŐqK P |–MáŘI=ŤŠÁJ`Ő±ęíĂ+�D™™X­ĐC=2™7TŽJ3)�‹cđáDÍ,É"¤ÇńQ'#z¸PúDü—‰6ř RYÎ;ĐŽB¨¤�4�Ž']'�T ¬Ŕ‚D3@…ÖśX J|pĺS ‰ šŤ‹Db2Ćń|! xgľpČ�ćš˝=��˘^jc'ɸeÜ`ä&Ŕ©Đ DP+ÔČŕĆŽńXČĂś% ™jŞY–ôjX†ěj‘�lˇ:dŮ’Ű[µ>4& Ŕá>Ä ĉŇNĐřáhŚĺĄ€,d N˘łq<^@b…>Ögľ�ÁÓŠQĎ:X-č`“FřˇAŘ\Ť¸~˘µfs µI”Ĺ’Ĺ óNŠăĘń˘'v‚nBŕ„žąĂřP.”ăA9؇2Ęic"Y čŚ4FXžŞa!ŤŚP§"řř�cb˘ă(đ’ľA6fČůÍČĎ@q*.¨`Ż[kj®Ň� ł€‘c¨YĘń+ěx„j‚áX‰ľB$ěâ\R+ČD× R‡€fŃD6ŘTł,é"l,ŔRÂ’éŽW€PxS="=i t 7©ĺT3A—0`d„ÜĂÄQ0$Ž2îH;-îÁb5Ń`´ ČGN4ŹÁÓCO†P–°¬ě†M! ¤ YpJ…”ŚD  m�€ŘĽ,x ´1.Â0 MĆČĂĘ•DÔś€´Ńz")6÷Á± P,®hôŔFŐ\�Ňťš–t?Š‹F¦‘6˛ŽxAkF—XäjM P#Ţĺ¨X`dI4Ś=ř(Ł 9» 0óĺ·Xš�€—tě`¶h AŔĚŮŘ#G€¤ÖO†Ö 4šĂó% ÁNvş¨%„N ZD3”ˇ-97 .!í˘ÎäÄ÷iA”‘ŚÔ X‰ŕ;#QŔ&܄쓭:LtnT‹ ŕhé¤mP¬„IÁ(ÍK Đ‚¤Ĺ-Z¶GO‚d‚7ŚP@gŹY=ž (‘ą‚p%l!HLJ|ÂʤfédÎĂA8>�@k0'-?c¬Í‘ Ž‹, f ÎĄ“ „dłĂ’jŘ)2q ˇ‘…ej0§ČhAPë©®=a–Ś2`©…Pň\wçńP�0UĘĽˇP!ŕgĘňŔ± €§¶X~śµ¨ąäl8şH,fłü:‹¬ŕL;ĆÔą$vÂCHŞ@x?V  Sí0ű�#"ł:X3ĽŤ ó’ŕVźŕĆCEÁŔ4:bqL$`<¦¦„?L5/•ieââ8{�`hňbXs¤á`�€©…+CŔ”T€Ú2żĆR(™ş¦šj”Ç*™x„yXňŽ­ #Mb‹p8‘‚E5Ŕcş¨ă@#+QRčHH>ş@áŇ@4ĽŁޤ“ĹŮQŹ%óRH®°ôpSť*-¦ń—h ŘŔHä Ď5Ę�6Ř9; LT>•$FŽ5hYdaŔ”â2CyěÇGÉ0‘A®A>A#•�©őÁ:`@=c“.Tx™1 ;dX%‘ >âĆFEćN€–˛@H‰ČĚťÁ™ŘÁ­Á �`ćĘÜŕ@ňX% łĂX€‹ňŚ©b €Xę ŮÜ™š AfS(�ë1ú…‘ŮŚ  Ł™€gfň…YDËx aP¤J4@"h„Bą ó2Ă/ Č‰ŠŚáćĘ| ­ ŽÓČ Ç†m*ń46Í—ŞÇžđ�Í ĺx‘ˇ Ô´ŤGp#áx/Ôbă[€ 4¸@@ -ć$°TF9n€‰g˛Ö xXöꌍˇÂšąZł Ël a#©*�čhęT¨yAŮ#ŠqÁÓYO°ŽpHdJÓNbÁZ‚zşńŚiʧe“Ę«ÇĐĄ2Ö™„Ł`Ň×|Ň•%‚I°‰™'(‡ F†*’ĘTó�Ö¬Žň�H+ZÁA” &Ŕ K^ n( I†ťŚkŚ`čĐ8°ÜŔČ#‰t2ÇÂ%ăůaŢitŹąA’AAU¨fýĆe"„±8Ôä˛NT�˛ ‹FŃc=•Ę��ŰpJCʰ˘‘Âř .A<Ć&&ŽĆĚŔŁY:ÇM‘‘€‰Ŕžő1yfK,(Ź�$ŁĘ á<ŕŐŔ kʔ߄‰Zk$AÍ’?AĚK@AÇŃ3%f6” HSٵ)ÇkŔ °ä g;Dµ{”‡ÄÁĺĐ8Ň@$p ÉŃ$ Ł�VÜMŔ<Ő-" S‰; G4M¤g=AsMzMćEe.ˇť 0zbeÔi¬;óöá¡dî ĐD °Ĺĺ´L¸ŕ0F X9• ýRŁÂw±‡¨â—) �Ů1& ŘyŘWr”ešmŚ6łMÔ,¤çľČ´ΡW‰Ĺ˛§˛rŔLd“zęĄă˛y1B6o' Ę‹—ŽłQ–Ŕ*KĐ"04EÄ´1qů¸ˇŰAš@őA›í0¦î©†ě®K`%(·5'‡›ž˘\ÓĺPgý›±&´™’€R@3 N•Âu4Fářř¨xA<zrÉ’Ą¬|0KX¤2Ń„d:%2Ď ˘ ;Ö†T›ĐĘ´M�s‰™PÔSŔ,OZd´$‚q˘=GYE¨Ëtű Ę)EcÂ�„¦> ‘kFđ(;šă×đÄKJÔ�Ŕ4ŁŽ“GŇ8 DHŚÓ ccͰ ±2ÁÂúĄÁ�bś…=];p™PÓepj@Ź RâČóH)Lö Éu¦«sÉĽÄhB6°0fŚI)VćŢ2HÉL’`Źp| Ł%3„JÁXT€ňŐá×x´ŁÄĽ Ú”3É Ů@ôĂ"GȤ j�„6M ˇ(^kFá1yfgŕ´L´d:*ă%¨Ý€=XÄňÖMAćƇó …l¨Š*E ™JěŘŔ@qD¦»á†#PZ#ĘĆDGXś�i €/�Í7šKÉ–ŹtAŃ bŤÚS@t\P9�n ѡ°�k1V(Tr18ʰŤDŽt”%�€‘i2E&¬Çü†(ś†s±Ď°Ę­€>â–ŕ…‘4ąF� “DÍ/ŢűP ´Çä±â̵#EX2yĚ•Dd”%3L řOÍŃx… {Äp Xçř‹PAj¶ćč"Ô©‚ŽKN4uŞ @X6Ĺ'Rp ”T„é Z€XŠŕÜ  K`‡ŠF&­D†!ĽCI‰ ÖŚGt„F6ˇŔuŞĂ9Ŕ@2›„FرĹ@b¸ĆdA&¸Nu”$c1ŮS„,W Ş�sâŐ�ˇŤr7@˛°ÂGĂp4ř€<€‰šĚM5E`\Xd€ŘŁ$ĺ1¸ĂD­5’ pĽ,Oěá:�6âˇ'†‡”*/Š"Ě3ó€Pˇ^´%Če"‹­h"uC‰i�Í ež’ÖF=ĆʸÄH¤ă!„KČ_˛uH$ÍĄÔZ 3ř0HřqZś6Š”Iü  \'V rePNĄ�†fă%$R‚ń-s‡pʱž8„»`ÎĐ=�˘©0_4˘h¤cýETŘ‹ĚAŚą´ÚP™_Ŕ€š/1ŁÁ- D5Ą–Ž€™2/ˇŮĐR@ç!ŕG'ÄxâbÍ<Ë%Č1–Á8l8pzp€ Ě*ŃłŚ1q‘PŽ• Ĺ 9ÖˇA�¤Ę•ą‚D(PĽľ A H˘W„ă"Š%�"GÄىxi‰ÇhűdN•őăŻ?T@łld A›Ęšĺ\‘a 0i4ѱ.B>Ëbî3sx8ĂIż 8\ “wŔ;Š !ĺF «‰—:xSSJjńŐÜO…”!Č2îPy0„WăŃX $Ó@†Ż7Rjčź;Ŕ|á#e®˘T¬˛ú@‰j*/€‰ŤaşaáD ��l˛ÇşĐ8ZĆÚ5SYx„x4ŚRI—ÉlÁâ"kFhÍ×"ŮDC©R{4‘ěC±€‰Rd�xf=R"ĆTŇ f Ź2†ăCmp›daa#ĄŁ‹ŁqL6ŕ�ĺ‹5z”AR 09Á­�Ŕs‚} dŁ©dbAĚWćŇăín˘¬–ŚĘ3±bGÂ1/tAŤq4�ŽhŠä;#ş§rŃk.9ć.(€´ĐpĐXŘbëá7Ő¸łk¬9šńŃ:‚ŽX|r¸tġÂü@¬`ÁˇK€Ń®q–„ �„+�¦{Â%đrŚC–%Çî\r e‚aF˛Ă(K' K ‹=–D#§B÷č-Ĺ“’𢂦Ŕ˝˘ĘB.ĘqˇHd9äŚ.ßČŽĘ…0pfa˛!‡{XTt0ˇk!BJînäW6Ą• W�€ŮHÔNâ °IAËT‚F�&ˡÂ4ó‹ň©¤©B´Ó T¬�ě¤6]iq#l ˛„›–HO„@˘Ń‡QFËcwđ¨2—��ĚŮř’H@-Ń:Šđ a#B‹ě¤Rľ#@Őŕ�É«C…Ögâbâhbä,ć¤`“aaCą*b \B«(n@S a2Rh&—FĚ’C2eŢ€°P¨đx*”%ŹýcXd`F“…– ŕoRzͤOt(GcPŰ7HDČžĐčý„ĽOLÖńŇĘŽb(D k�Iâ ŹD…—Sb2ÉŔFdÎC/RćH<BÁD8`Ś›ęĂ5@§ĹG`€ďi™jŞăvëŚKL>Ô– Ăr°Á|Á’n06"¸“ ”ă5 Qţ%PęaŘSO� ”aYb‰ÍŐ!yžn„,\jĄĹädť«K°!AQJÓ37c-T)'–ÄS(Ś …ds¦L]łÄq4Ę€÷1t45˛z€ŠÎ|ŕşp´�Á5/ UBcŚ•ÁB;@雀U$…»ŕŇĚ/!TéŽü Sđ—â!Tâf̦{pťtN@Gb˛eÂFaއ€ÇY‚ö0™şN=©z°2Ę`٧0�Iń $Łéř’H…Ł%žQĂ_ |+ŕPÖ�tM5ĺ¸Aäč7âă—"e(ŕP"ć¦těΕą fh�[€c-Ź0+›)P,ăTÎV8\:2,U�`Ź}@˘™¦ćNĽ ‘Jad"TÓžtC Ě×€€UëO\€€3‘‡Đ‰2ó°´ĆĹ'8Ó:Š ôaŁ!‡%ż, —^(±t<ĂPy8‡$•$° (©Ć�ĐPXRsę�Ř‘[D^Ą‚1%FT6ŇFf¨.IA‹ąa`F<×\j´Ě[#žŤ&ć“<}0‚ b˘�E˛Ŕ( ±žő°f)OuÄGž*¶îó¬ań ˘c. (ż(µä ń‘ąY&h‘‡R 3_=Çí§đ).>Ú@«hĐ›/qŽ…9ĹČŕ´‚úĆk��w`¦ŹqX`Á’á>ŚO y�@T$šI‰�#…qfád(čˇxťqɉ€LÄ€‘–L´6هĄ€� S{„‚Ćš°Bć©ÁĽ8ÎŃŚ)`)0RĚ­AŇN5K5µCÍ5Ő©Ň© €™jČśŻ%‚:őAÖ:âB8$Â%\’8„J*IM‘&Ч%CĽĄí¨žĽ ühscQáň#\B)ę4Z|Iňť–` Đé† ­č`¨9’ËiPé0•bL$Á)RŮpBL‰ş<ĚĂHá2zÄc‘€M&3úŕuĐ… u¬1:.<Đ‚Đt zQ—ĺCj˛>Ŕ7Yzć$� ‘a€‰¤¨B;Ç�„8ŕě!™hŞ©)Ë ČĆ7ÄŠ% Kf?Č\ĂĹęAŮÂϵnÁÔ,7X �Č\MŮ =o“Ąµş ŮIK��€•ÁÚzEĄq ł9H…ŠEŕHˇć P^”ΦȠ™©ž�šh,¬’ĂŔžú Ů–ń0ÚHyęb˘R!5 ďq ďŮhâ‚4�ĐaĹ\9AŔŇV€MY‡C9¨H–Ř0nŠ!0YČ z°´4ČI3*ŹV , b¬8ţ…DĐ#ˇ”RH' ¦4µŕ†�<9r<Ć©ÁM!‡ß&IX8"�"3Ť1S 7W'‡d˛OGĐ ŻÂgÇﵪ10ÖÄQˇËô‰b„ÂN5Ąc•‰ę¨ ŽnÂ"&逥”ŽIeć-Ń5_¦Ń“Ď%jşgŞ<ŽYÂdl@gáç”IOkÁB±gĚPÁ aQvÚ�ÍTr $ÁËKplFcĚ2¸4Ň™bs±˝0ѨŇY “g @R©‘Ţ”ě#JsŞ&f7=\Řč¤o¦S{ E¦@@sňśTaadP”;.×°Ĺ`  ‘!ăŃ@‰ghE�%"6š6@šG‚p@ö`–‘82y E•Ěś'Ö–ˇ” Hf¶a â‘BcSdŢ#Ĺ'[Ş%%nP'€hĽcşX Ő˝20–4Ŕ¨F#IŔQ¬$š z(ŞŘĹn&p¸.PĂK aŃĂ],ƢAs6˙(r2Zh§Â t*ĚĄ™^Ă`Fž|)Ą%1¸U™Ŕ=4 ;8dx¬ˇÜ�Z3‹EX<|RaáEŃ�ŤRŽ/IŮeIá°ůĺ«a,Ą”Ía(‰×HÚŮPĆ=Ô­łv8�B¢ÖTŽO˛ ‰Ŕ\(/…/BĆfEp,1đČ W±Č JŐlˇ^ŠďŽBL% AŁĄ00üqŔÎŐp)�€N}3‡sCŤkÂĘ@‚ ĽĆ°µ ¸�60`q‰‡˛E=ô|€ X&ě°Ň‘Š[Eř2…›ÓÁ#ĂFuĚIu$.Ą^,]<5šK  Qbăťđ—uxM$Ý~Ť)ĎFĂLŃ&ů�…•Důi_�ib�0Ó; –0¨Sti(ÚŮ€J\‹®˛”É™ľuŔ˘‘)…ť#A¤PHdbÔ´{ 6��lÂK¦łă3RI…;G Xc¤E ľK*§d°n¦čȨ7T*ă‚Ć%¬ĹkŚ>' �ĺĹF¦ŁöA @˘ÁÚ(ůI· ěŠŕyGůeD‰ ©ŽFIž®€Ľ7ČoI\ƉͽŠŮ( Î;4Đ+BńEX0yŕFsĹ1RPaéPjşYplžh,7‡ćč$Ä#¶ĺSÂďP…| {\u†¶Pä6Ą‰ÖPa40óµă (ËB$:˝9ĽÎÁI4‰Pĺ°4„yahâ€ô`ZŔÔúđ2ŇÁ¨âQÜř˝Ć¸˛‘Š (dOQ…tÍ•DeC>ťéH*5Ú0ž´Ć P±ŐB;@S8l^–Ă „Ł€péęx˛„ÖJ.±Đ"¸�ÇGQŠÍ›"&gdŇĽ›In0ÚE‘čEĂ—Â/lÂ#,0âĄaZě‡A ™9��ť Xó¬Ii;ô—�Ç` Äŕ�Đ… a^ŕŐ „ć»1Îč3Ó@-Ť�2ŮQfy‘u(gĐ02lˇͰ�|f´Ě,M¦i4®8;ŽH4ŠĂ`&ĆÔń¤DÜÉĺ €X·`"ĽÁÍ©§`ě4¤�謷"ę‡/» ˘čń= ‚Ž8ŕ� ·m S+3k")KŘseJŹW�ĺŃ1A�Yd&Ť :©v JÄł*If)§:@ň„K5‰‚rśeŮ Zě!&J“SÍ'`6Ö+Ŕ „‚�°6ŘtŹůŠBďÓ÷ ń\¨™`ĚZe‰tZŽG/M*B�•«Ă=¸ŠŽJ‚Ä`ÔŔRH š‰Ć¸‰9†rő@Ááö„üXa‘ŽyĚ<˘’f~ß Ďh;A‰­V µh¨-źˇ<¨Aęđ…##Hś(Ö03€§Ó’ZFŚý -Hiwč˛óQö‰nZC»cÍSŠ,AŚí�ś7ľ ŕ˘č N!ň¸~ÔŠăh(s€Ä L˛1ŽŃ‘8sÝi±pVĺ…@X2¤17/ž—Qˤ;eĄCp ¸”%40Ś6”uAšÁ2gyäĹÄě1ű, p ĚŃ'6đ6côŚ/#€ZźdŠň’,® DŁp$ɲ$"TЬ ô–Í%HŃ'©M]b Ç âůăDňA�4`1×(âT24H9 ń‘Ă:Q13(/’‚wú P3žcAp‡" ók™ě ÉÁJnĆ%Ř`\r´WµÖCZá’he¨ákR  Ä‹3Óbľp>ĆHÖô-é�źĐ‚7¸2}IĘ=M™i\D˛uX’ZŕpÝ®ˇN•±# ´Óś�(·€Kµp$4űAF0‘Ł«ög„ Ľ8®‚Ńa[:*äF‘5V±Âw"dF$Rj€Ą„ŤČ¤p#ńób/<Ű3BÄR`Âŕ€Ë&CÂÓ+ÄŞĽ4bĆęBˇY q®3—ŚĐ2ci@‚MŃiç´ÍDgšĘšÔ~HS!)7—€E8őARJ‡ŃÄh€r¬:Ħč”#>ÇšB�µW�&}č0tŕŽ‹>Ĺ ,—�–ČłűO€%莴€c�(eS�$(@­ŽFŤO˛hě �%tD41ëuġI‹ŔÎ0¦Fć%,@D&4fĘTæš!ŢC€¨†$âlîĚ´��`Ž5%RˇbTs}ću ŞP8űAő‰qL�nžJŤ(‡$’yłŮ@C§;n�’jłt±·€ĆŔť M4@ Δ›�ô<Ý …*ĂqY ­h×Ŕ\jÂhxm= Ľ˘'ÎjôÇČ u±÷ŕěŇ É`8 ڏtl�yŢqp�ŔĽäXH1ó �‡ŐŃpG T–Ť…©9Áx©Ř$€(ŔT”Ă $ČFÇ”ăkD-Â2Ń· P»s5Q‚Ó×DsGB4x óâŔxpU8 Pľ�˝<SÍË/]Ź ‘0K+óZ�F íI�R4ć—‚IFn69ŞÁ@L­9;ăa `䨆ć8P¸3磋o š!±U�ˇą>ÜCa¦ši‡çÍ!0�6Úđ@ů +‰ĂĐ�Ă@˘Ĺ ř0?ě× ”T#:'·BXÁ58KOëőô&lŽQ+`�ެ¨‰ FŕX ‰“ˇ%ęîÁűÉÔV`‰TÉě*Rľ‘¬aŽľ(R$Ë@6¬Ŕ!5ÖćÎ\9NŹk� �pŞ` ¸Č6|F�MyZ4Ë©Y˛đ ‘©QrĽ§‰W3{¨Ąµ&ŔŚ @ö膰M…ădv�ŘÔĚ;©bÝ óÔZ��L±lN+á6Ŕ •hŁ9@—-ó±őŁŰ+�ťě·‚D¶Ęh€Ü4™<:ćj�śˇ5 Ł\I\}T "€Ére.hŚĐh°`3.f#ţY&ĹÎŹ¤DáTkHL@ Ň0 hNm’˘¸@`gj2P!=XăLCŔrgĆŔ|»d’H =ÄĘ0Đ©2«ˇŘ€– Äcş'uŇa4 �@ EŁĚAÄšĐ=˝3ŕ4.Ö‡;ăŠ� O#20J´§íÇE„&hŽC÷‰;ŃńşĐ-!n ��Ý#z^u˝ P^jfŹV€DsHD„ l4(4Ő©&ÄG<PsuŞXĺ5�€c|(ŔT3m†4VąÇp<'˛ćČ@´§0 L˘ć Ńzé(.1Ś,$ŠFjćcŁČl�‡ĆÉ�%2¨I5�&&ŇM0—Ť(ŇX &°7qrĆ�Ťj( Çő˛&›.5ۇ!r6jfTŔ€¨r‡«@ićEÖä©ĂŠe0đ�ó˘ę‡yJAFŮ<q8&HMăFĺ3LŐąA˘ rC‘AQϵ F6Śr¬b^ í@Ql"°Ňa 8Ľ™kH>ĉ¨5ÎŽ=- &� < § A㬄EgXĚ‚EĂÇQÂ<#-…3Hlc0­O¦™;jŤ0/Bi´ĚKzHJŚ&ăh26`'ăđZ™FHÄ3W�<(Őce¨Í@ĘĐg^�‰†GÜ<"Üġ%ŢZQꔇE ;hdŕ™G,!×ŘŚ1· Č9OmŽď„ýHDÔcExΡ]�¬4ŕŔŁ!źŮ$€”jŞL ,­çY0AfM$…Š ¬§Äă4�Z—$ ŞÄĄa#aŔNuŠ (˘‡ÜѦi:Ôy”Śî\ú´r„/E60®‹$4â‹ŔăÔ>Ľ>ŁD‚ŕF`z6%ćů R¤Ů�ŤGs|¨Â]0h”u(”—Z�.Íř0“dƤ3R9t¸ &´"y ‰ZH&• ¦®ŁńĐá‘5§šJE=¸‚YÂKˇ`^tt?%yŕ°8đ]Y4Č9î «ç†ś>ȱaNđ~$űa¬h ”l”Ś —€ D†°čYEpÜ�n"âDA6=X®€Ř9^š™ŽÁ±�v*›˛8Ů † í©€t'5Wg¶�8Ĺú‰šGĂ\ű˘‚(%öÁŐB­oĚa<ŚdŤ‘�ó† /‹Ş,5W†â‚€B/Žč•=úТ 9ÖŚ¬±ľ@Qš>Tâ óaP�ś•° i1Ň, ÉËsŠ„Éş (X84ĆCiÚcpň‚•¨$<�&…¦+ˇ\"@±.iŃ8Â5‘č5W§a2 ˘[BŁń0Đ�Ë­Sr ,Oh‘9@•h ',A@â‰pN‘Łý´'YMö ś(ˇ,±^Äăđ]Đ(@M…—ŘĹ(@Zť €ëXi™Báx„‡e*W‚”ˇš«3žÝOs"YČ Oľhž‚E!e8…PĆĆOY0Ńi±`6I@Ľ#ň5�3dĐ]SʡR.:“žě?b•fG¸RÚą0W¬Ě•pCˇD8˘Ô„¤ćJ¸aĐTGsLĐ‚8Ş×\c凬k”hB"),ŇĐ0Dřkb( ©[>ĽB¨¦}đµÝ€ ™ÇĂ–óÂq+đ „h€4ČótK ÁE8\B•DKQx‚š(#€6e "ĄKćcpćÔi –6ô”<o9Ë0=żÔ0Q8‘H†˛qXmŕŕk‚^cĐký„)ŮČeę ¸9`Ôii$@�hĘ7ňś“`=X^ ć­dF8.˛¸ „ ć’TNF*–yM4xQ0Jo"S3Ő\ĹńB-Ŕ0Ť‘bfxĂ÷:h%"łp!Aj}h‰8ŢSM^Ký!ö”Z¬`m:úË©Đ\㼬˛EB`G› _}� &6@lž>$¬8žÂĂöÇA†Ę 2 SĹŢOÚ†Jt=D%ڇiMމ�pśF?„ŤS#%‚B_~˘‰&Ů’` ď °Ăʉ˛©tŘP´CöPo€ŔO` ZóĹg‚dB,HТ{†�Ęs÷ńŞ8�Gbá(đşRD*kxŽŽyq°�ćă+€¨5ČÔšc2U†\‘Ł1,ňRdŁ,ěDaĹć aëP±‚ˇUŚ^Á3d‰KŃÔ¸�KJ(¨BäY'ö•8TŘج™»rŔÁ*G@‰F  M‡*ĽÇŔĚ•ˇđ•ąŔq<0z�ň Ńa�¨l4ć ©˛č:�şÄ©2)<mF`4Őŕ7ä¸XăŃ3jBx:J¦®YČŚ`¦}™4W@x\,FA††§s‡ăë°°Ń2®…”!ŤxFFHŔM11PäŹXG;Hil€E:ŻRŁć>82NMIžŠ8i°đsC0*�µ`šó`©2nBŃř5 °ô‚f(c¨pĘŐ„śđ"ĺŇ´�e†×8 {ęŚ[„ĹDĐ”r†Iä¬1j6@ęlˇ…[†gÂ:ÁĆŠPłĽ0WG#‚h<Ś â©!qú‘!PgéŁcČ*¤äŘş¦ć´Č0ëHG H$OŚ7ôd%‰Ä#2Ą±!´Ř‚­ h᪠—„ŕ5&Ě|6p ŕŔH5—�‚‘’ (Nâ€$ü‚<X^&8‹dc�Ůc–Đa3ŐAě „"{Ŕ‡‘ht„Ćr H*$ÖÁ2Âŕë@%{P*„ŔÍ•)ěTłŃx€řÔ˛1”‰”cćYM+ëPp2<7X;ôÓK"‚„&‚pô‰j.1pĐ9ŕ!{hWC-h¦Ç0 kÝ|Ň#:JŤʢz–Ga äÄN8źułz´T bŕp• =Ú! ˛A…›pŮů\KÁ(o5Z Ô–A,E4¨‰ ŔÜ`~"0gźĚ¦ŽŐ�‰d–ĹŮ™ ¬e9QÔˇ!yJyŽR wČR…§đ¬‡KO©ŮÖÄ«ä LѸ|*hyđŚ/‘Rzh83˘Ń‡ ś xĆ=€,8$’<–>pěÜ ‚č*FBĂĂh%¦u°L80�™LŽ,«PěąRä67�CDć #{´}ś\­fŚUlM`=`A™â0C‘řĐĘARK#Ť‰p ň €ěhޏ5đ‰tjńM‡*Ř�śy1¤YĆ?@‘Ź’§X0ŐĎ‘�¬¸)M–€�\§($‘SM�…q‚YE1µ ş�§Ś!Ł#-S­E,˝2„Łi¦Áh˘PqEBR) ň˛h â‚Ę%0<xĘh`yPćşôĐĄ¦CBŁĚŃŔr)¬ Ĺ’ieµ ‡(N˘ą‚â ¨đ‚ˇx2L ™R 9ăŰŔ{Łe^XŚ:ÔC\0b�·Ew tIŕ"đß8,8Pćc(mČŔ\9>‰7U‚˛©<śÇ°§‘C2¤(ĄPłń4*\:ş`é—eRAš�řo ’jĆ/¤˛µ . ÖĚ=¸X—M€<Č�šŐHX Á±:–K eĆkÂd€ â5ĹGcl� —�ćĐL tŔ†Jô:ŕ�YCŃ.¤ă  &iTŁ1(z˘Śť«C¨ńžę HËÄ­´$ %€ś7„đkc—Äj #îT™ x�‰“ÝDpLŚ Ą@Ďtbäl^ /0Ç@Őç!h𨺣‹RÎi“čp Ľ0ư*ŔÄúđ22€ł… ¨iM!¨Q Żf•°Ć‘ޞHčâH�Y�ŔăÉCe˘1 6!™>5E0a“L �ŔiA,`ą ¨’ł>0t 7W¦%č€l€*9…*aÁä{í2l�Ě™qöB ĽČ¸Ń,<"Ăre\!°Ű/ŹpQŘPÄVpŘ’Ě!N)Vćę”dTŹ.“.AěÁKÂŚŁ€dÁq±ĄÁIę8ă ¦ĐTOëđN§~j‰#Ś&ôŘťNc’Ę$RC–űˇăť@IÉA Ňă7&5WFĉă’a<!GŁ €xÔeů¨#ЦH€4–ZÔAK S%š+˘Ă× iŽÁ1• HVÂ$m�2Ht)>ŘHcóÄé§ęĂ !Ć+Pś›J>P,AĎ„¦.U˘J"14ˇq3R¬§”R1J‘ ˇ9np …9 D l¤Ś<ÂČO>ś’qVâťŃ… ‡+Á-'´<Đ€v3DH Ňt <q¶–@S ¸Bë‰`¨™šy3„q3U­…Čô|g”>h˛^€f�OëjćµRšy,)UöQ?âgŤ)Ŕ©ŁDP\L!Á28Q pe `Nęq€&ŤDc,b¨ÂˇÔѡTQkčŔR ó"(H8¨2d:bą�b°,‹d h< \Re¦˘Z şiÖľÄhq'°Ś)i^U°Ř�Żš—"ź� ć‡öQ ŕÄî\ [‡YN%Ša [NXc–U f”S%Ô3Ş©ĹC×ńě8 h‡¦šÖ¦jx Ŕ&T‘BĹÎc:\B>eX$‰őˇkOax”�ŞXÁ‘a`¸ŞÂ R”c}†˝¦©m(ł Q@4g‘ř�Ř(a=˘‡…ŤŚăP î°ÔT覚)ó¦ če€lŚ%Oě$¤čÁĚÎAçctÖ@TcQA<=úN)I~*żN:ś�Š#(­2lP‹Śá ’›'´ÇX€LÇi G ·†Ä Y¸ Ż1µ3™@Ą_$źJŔŘČŕóh¦ZG@&�Bd ąš8=S"b€`Ř8;˘ÉĚ,ŐÔ5Ő¤ĂaeVG'Äš,th`Ű ApaájŔŽUN…Ť=zÇ5Ę7�¸Ą=Ą¸“�8ÔCC‰Ťh٦8DnS"Ú<ž(d}¸”©˝Ôh?€`!š řa/„Đä{'I‚ń)0°Ł}ĆŤ$¬gö…÷XĐ!VPŔ“DžW?Eaţf°�ĂÄ&vK,LěN…zP"·!2RńŤ<Ç‹8 †`J±Ę%Mĺ™Ő ňĽL`qޞLµ ˛B@gHĘ3d%ë>p˘l„/C Y^ ĄJB9pL$ĚeAƤÇ$± ™Ĺ",zÉ‘�M u$‰ Ďťů ‚ßxL6đ�‚ŽĆ ňBj®Br2D­”ÔA�ťúŃŠŇXJĚ‹*F#/XpDhiĄ™�Ż6©ŕŤąt„Č nZ4C<@J€C‡”ąš¸>Ĺ�*TJ×±>ŘÜ��dGsŘV@¶™Ö�ě 4Ec 6ű=Z A–Ř óÄ5ĺćMAT`”ZŔSĺ Ž 6źPó ý :w‹C!Ź’Ů×lG^@Ęń~z$4Ľ^ÁЧtj2e"OˇkÂÇÍAĘn�DÚM(…SX^ ĆÂ, -Ć ‰ Zk� ř«cű�;†e $‡GëT.č桨  Sh ľŠ‡†Áq‚FO€ÂHâ,/€hĂ3o ¶ ň:ŕK#@ki ¤ťbŹ $3ťČŔÚ�™óŇ��L5ő¤Dü<5Çţ k„'KNĆé ™ 3öŕd†ŐTł”±Šśr–¸ĂÁJšĚŽqÚa (%«Ńˇ€-–ĆKň0™˙APETAGEXĐ���������� ���������������bpm�6�������disc�4�������part�4�������discc�5�������trackc�3�������date�2001�������year�2001�������title�full�������disctotal�5�������track�02/03�������discnumber�4�������totaldiscs�5�������tracktotal�3�������compilation�1�������totaltracks�3 �������album�the album �������genre�the genre �������label�the label �������artist�the artist �������lyrics�the lyrics �������publisher�the label �������comment�the comments �������composer�the composer �������grouping�the grouping$�������musicbrainz_albumid�9e873859-8aa4-4790-b985-5a953e8ef628$�������musicbrainz_trackid�8b882575-08a5-4452-a7a7-cbb8a1531f9e$�������musicbrainz_artistid�7cf0ea9d-86b9-4dad-ba9e-2355a64899ea$�������musicbrainz_releasetrackid�c29f3a57-b439-46fd-a2e2-93776b1371e0APETAGEXĐ����������€������������������������������������������������������������������������������������������������������������������������������������������������������������������������beetbox-beets-01f1faf/test/rsrc/image-2x3.jpg�������������������������������������������������������0000664�0000000�0000000�00000001164�14723254774�0021077�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������˙Ř˙ŕ�JFIF��H�H��˙Ű�C�˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙Ű�C˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙Ŕ����˙Ä����������� ˙Ä�µ���}�!1AQa"q2‘ˇ#B±ÁRŃđ$3br‚ %&'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz„…†‡‰Š’“”•–—™š˘Ł¤Ą¦§¨©Ş˛ł´µ¶·¸ąşÂĂÄĹĆÇČÉĘŇÓÔŐÖ×ŘŮÚáâăäĺćçčéęńňóôőö÷řůú˙Ä�������� ˙Ä�µ��w�!1AQaq"2B‘ˇ±Á #3RđbrŃ $4á%ń&'()*56789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz‚„…†‡‰Š’“”•–—™š˘Ł¤Ą¦§¨©Ş˛ł´µ¶·¸ąşÂĂÄĹĆÇČÉĘŇÓÔŐÖ×ŘŮÚâăäĺćçčéęňóôőö÷řůú˙Ú� ��?�’€?˙Ů������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������beetbox-beets-01f1faf/test/rsrc/image-2x3.png�������������������������������������������������������0000664�0000000�0000000�00000000233�14723254774�0021077�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������‰PNG  ��� IHDR���������6IÖ��� pHYs�� �� �šś���tIMEŢ  J™���tEXtComment�Created with GIMPW���IDAT×cü˙˙?‚�9Ń…'����IEND®B`‚���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������beetbox-beets-01f1faf/test/rsrc/image-2x3.tiff������������������������������������������������������0000664�0000000�0000000�00000000316�14723254774�0021245�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������II*� ���˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙ ��������������������Ć������������������������������������������¶�������ľ����������(�����������H������H����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������beetbox-beets-01f1faf/test/rsrc/image-jpeg.mp3������������������������������������������������������0000664�0000000�0000000�00000024324�14723254774�0021332�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������ID3����STRCK�����0/0�TPOS�����0/0�TDRC�����0000�APIC����image/jpeg��˙Ř˙ŕ�JFIF��H�H��˙Ű�C�˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙Ű�C˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙Ŕ����˙Ä����������� ˙Ä�µ���}�!1AQa"q2‘ˇ#B±ÁRŃđ$3br‚ %&'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz„…†‡‰Š’“”•–—™š˘Ł¤Ą¦§¨©Ş˛ł´µ¶·¸ąşÂĂÄĹĆÇČÉĘŇÓÔŐÖ×ŘŮÚáâăäĺćçčéęńňóôőö÷řůú˙Ä�������� ˙Ä�µ��w�!1AQaq"2B‘ˇ±Á #3RđbrŃ $4á%ń&'()*56789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz‚„…†‡‰Š’“”•–—™š˘Ł¤Ą¦§¨©Ş˛ł´µ¶·¸ąşÂĂÄĹĆÇČÉĘŇÓÔŐÖ×ŘŮÚâăäĺćçčéęňóôőö÷řůú˙Ú� ��?�’€?˙ŮTBPM�����0�TCMP�����0�TDOR�����0000�UFID�����http://musicbrainz.org�USLT�����XXX��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������˙űPÄ�����������������Info������(��!w�  &&,,,33999@@FFFLLSSSYY```fflllssyyy€€†††ŚŚ“““™™   ¦¦¬¬¬łłąąąŔŔĆĆĆĚĚÓÓÓŮŮŕŕŕććěěěóóůůů˙˙���9LAME3.97 Ą����-ţ��@$|B��@��!w_»ő^�������������������������������˙űPÄ�1Şü@TŘ5"L�‹‰€TÇ˙˙˙˙˙BhNs‡y„}@�3žÇ;çBú2żúźď!?úţ}Cˇ„'B‰�¨ŔbĘ€,=°�˛ 7›âýcE`M.¦2 :şJwť f}w§[?˙˙˙˙®Ť‘ŇŤW:®Ş…F Ś�ř·BŇ5ĄĘ纡Ö^ł­*+R:kB·ßżŰ®ź˙ß˙ĺ™üľ[&8—Ub0'QX`€hˇe˙˙ź˙Ů2yŐYä˙űRÄ!)± @} 86`€łˇµŕv"6‡¬F” łäÄÓ”šŁźĂ=ŘŠ˙Ă˙íçŢvó?VaVÓŻSä¤ËŚIůóläe÷M[#@~¸¨H'ŠÝzŁU0¤f˙˙˙úZc{˛™Î¤d îî‰tÖÇi1ާ+Ó>b—WnŻ›ľ·˙˙˙ü˙újÝkv}I*shěCÚ #9§™„�Ä! �×u»kŃ{µjk¦¨™É+Q@·ăÚ·]šŮ+¶Žşm˙öéF˙żnďâçĂWőČóŻ˙űRÄ;­µ @×Ŕ÷¶a N#ś4ěśHšW®JU?¦^gÍç=żĄv„y*g^M)\ŚčŃZż˝W$4>ÚdLN[Íűĺú>ĄS3őę*Ööß G_ĘxV#łB)ë´eT¶H Ś–šN@Ńk%©§GI,™eU]¦zQ.ű«,¶-Ż¬ŞŁ"Ůőó·˙ő¤~ů}ßJWňť}ŢÖTE¤>é ČMŠB#–¤D™?Č˙ď–\dáO=!›źbtph˝'r†PŚ·¨X.vlČK˙űRÄW ­µ =¶!'ˇ˝…Ö.™|ź˙÷şůănµXNkíÝbß aWIvȇ˘'HT'@ifłł"rŮ•µz:ö˛íŁ<ęŕŠZZVlĄVC¦žĚ´dOŻ÷G+>F{)M¨¦łk-«ˇ‘§!M.hńqő•Ň'ĆfŚz0@ú˙˙úďťşYďmV¶ígÔÖwµT†‘ x…!G!'GtŐ ĚgůÖŹ2éK.ŞüÖµ«mŢ“şŠkśR1U¦cë’ë&ČĘ ÄhÓF…˙űRÄn‰Y± = ¶ Ä'ˇ’�#¤´UdVißrŚ —N^’1·|ÓÜQęĺZ=ŢÎëO7Z¶ĆŇŤ}ůăűł«×ĆwáńCĽ[fB˘Ż> ’†R*Š(Ł ?ÎŚŽ˙k;L¨Źf*nSˇSyĚsfiÝT…*2ŮŠ&®ă;+Č•ď˙¦Ťµ żî‚YSőcY4FÚ™s­Î(M 4q°Č¬4EXuŕ(ĐfJ8 vhă dznE"p zł3¬Ó˘z˙ţr|í…˙űRÄ„ Áµ@ 7Á#¶`Č' ÷oň°×«¬Übß—w.Lű7eŠ’@K”Ą)um.W§[MF˘‹vR6"î‘mÚvbÖf±QYŞGuJdłýľş~ü2[q޵öźŹšŠÜfĘBýIŚ“Ů+q Ń áY�¤űľčŕl;4·pĚ2!äęŁfŚKËCŽ…I¤«»Y%>®#ŢľŰřţ?ŢŻ‰Źy—ł,¸bˇAY‡ž Ť,‘+Ť…” %*Ś?˙˙˙Ë˙˙˙˙éíçX‘©˙űRÄ– ł =�ý¶aH�› Ň+ڧş…-zÎv»c±¸F”ř1ŢV”Ôß?Břü'‘ßČ‹űßţoë$–R[ ĹdtŹŻ# 4± KY$íq:4s˛#M’ŔĘŔŐ= „gědLt2•f;^«écµ]Lô[;ŁŐS–Őß˙úßD÷ľ˝ú~"‚i]1N^ w/jÉO)Ş MFW+MČť6_+iĽoĚŠ¬ó%Čěéô™Á® )Ë $'¬ž\W'5ł’źź6?'˙ţpüĎ&Ý™Ţăq˙űRİÉŻ =9¶ Č�ŁXôˇŮě”<ö =}aÔwĄ“ꫜL»©ä�¤NýÝgfˇV X‚Š÷uQNkŘJ$&¨gČ©s©ĐŞčČ„\ôôµ?M?Ý˙’ŰXßéCe+0Ylö¦Ńi4,b`ř=@DW˙˙˙-oU¦—IT;fĘ·j‘ ÇŐ’:ětŃ… ąčs5KÝ[$Ú?Ó#D §Źřţ?ţxç“ëĚô¬-5ń d_e°ŮSä9Ç$, ˙űRÄÄJÍł Ť= 6!'ˇŘaţ˙?éö”`ÂI‰‚”]ĎKi»eóf;éi#R ¶/;:FSś}LÄ×s ÓÔĎW¶óŁ–=h¸c ¤4\jŚ" Eî A 8. `&ˇ r đäŕ‡ĺŹĄ?ôő˙ńň‡¬ż-ŽBĽ}ĺřĺVAT÷QŢ>™Z­v_˙WS›QW3µPÍÇ ›;ŮóŮN~U~í˙ëĺxJ§nŃ,ą!h,č–Ôš ŇdjŤ-i˛ żDH\”˙űRÄŐ ˇŻ ť5ᡌËA€ J ŠĆ@�ß˙˙˙EV"ُĚkĚeR‘÷w)ЬÇtzŻ.ćE!ęK©nčĆvząW[şűţúµu—ö¶ëĆ7”…NťJ(ô"5±›Öó@ÁaL=#ŽĹ @˙üżü R‡˙˙˙䉕ŕt˝wiŇ«UGQlwE˛‰ě¬#GđPŕ‘°şâNÜ#›±Î_ůSĚžž‘ś¬?Wý˘Ź“c‚ĂNBĄ%©ëÎn_XčaĺŃ0˛Áej±˙űRÄč 5µ`‡¶ŕ!¸I¶Çď˙˙˙˙iňĚ—31[šĎ-ľy?ď ś””󤊮nK6‘â–ŞÔŐ{±†yŐN~;sůU/µľöĽ˝ÍŻŠ¸»kˇśĐMl0>Př`ůŃP 8XLĆT,aLq€˙˙˙ţĄY˙˙u­>yń˛ďr‡•vá†Ďů¸JK×u|â•1ÚEf®´0’ rl즽ţŐĄéö6\on-dfY”š·®ßi ŐěŃ*˘'m9Ĺ!D##ŕ98ů˙űRÄč eł� -ÉA¶ˇ &ř˙˙˙Ż˙˙˙ŢüŹ"1µ“Y2 ĘiR+E˝‹'Ç  N�ŕÍOJä(aŘ™dÚ%‡y©µţű3;%fr®g,«źXPąüKÔ1¬´ĽŐîS&!§“I…ó—–—ްĐ˙˙˙üç˙˙˙b]Í�aI¤ÝŃÂŕšW„=Ë»¨K=\i“ĹńlÁIus?W;źńÉeűĺß8g—UYńCcÂIęË®)M–VX`‰L3b3EŘ„$TV˙űRÄěLeµ�@ŤžÁw¶ �¦¨Ś<Â�/˙˙˙˙˙˙ţwËMr:jdZ‘—uĘ@śÍĺW‚ÉÉWŠĂ‰G’=A ‚Á.ŤyXžÎ<˛,·ÎçîWQ`®f[:ş+gdr´’—2vŐ02+#`PH"(ř•Đ˙îM©4D6±Ď‹2•ŐJůݲn¬0^ŚgZ"‘ClV¤±ÔŞŇîEy~źóﱾ˛ü ‰ú…Ča5TŠ…W¦djj¨a#ÉÉTDL@Đl”\ҶĀ˙˙˙ëđ˙˙űRÄęKqł@Ť= ‚¶ łŮó‘vľY™©¶q‰'ţj»%#»+Ńž„l�2Ĺ$hś^´ Ń‘PIÄĺ/é^S4ŕ?Ě›Ë3ѱ1WO\+uîŚý”† —¤:!H¸€N /Š„sµ?@�ů>˙˙˙˙˙˙ź;Ô)*_ŞąjdJŚĺ$Ç™Ya´ LŚę Ę „Ď8Ů«cľďąç}» Q©űÝđm™ˇQ†çXËďTQŮŘ7(ĎB˘ŁÄäm2• ˙ůK˙˙˙űRÄé ™±@Ť= q6 H¦ůú˙˙˙çlAŻű÷l|őżTęmš(ţ™ýę-r đm±Ôe¶ďȧ9Y™ĺ¦â›ţţcwßo¬÷OĎ̆awV­“¨)“dK‹ž°ŕđĆĺ“&Î<D„4-x4&š—Qâ�>x˙˙˙ů”ޞ˙˙ëáÚľË ŤśŹú]ą˛ńľę­\)„›q„]»LBĺÂU—RĐó†eNŢ~ţ~W™ĎßžľR»±;'––ZĆ×Ŕ\µx©,ť·ŮKÂßM·Ž˙˙űRÄë }µ@ ;A”6ŕłŘ˙˙ţ¦/—˙˙Ç9sIBéˇ EËu;#XŠĘV˘‚…ąÎ—D8TuGtF[Š‘.Ôďň+»0–őÍęl˛ŃÇQęAŽj$¦?~ę•(@MNV'ž -Ś–©�6p˙˙˙ďúú˙˙‘ś™P\Т/?ôéB%ŻZÖ*Jt˘a.fćýÁšÖÔ’p‰µ-s˙.ß˙ý9żôżî?K=Ť¶ÓK^gϬÁ•Ö3‹ :--€Üý:ö„› Ű`@˙űRÄě %µ` 7Á’6 3ŘŃ� Ë×˙úµ6Úň˛ä4Ź"Ş(‡)ťčŠ˘ęCŐ°ą$3ÝXî-,t9XĆ»4çeSU˙ú©Őf¨š?Y“jM¸ń đß%\ȸm ‰`dşND ČVĹꀽW˙˙őÝď5’©î›k3·SH ~śëÍX);“ZÔźQ‘{ů­- JIxeŢçů·Wýüůó+Ö{ż°ňőLäo&ŹłI1ŠźÔÄI˘ČěD`5ĂŔh6 •A1@•˛˙űRÄëK™µŔŤť…6 HłŘĘ$DëŞ~Ç˙˙‹<÷cb4Ĺs\\ĎB·ĐérZĺ^ď™n†ĺMÄÚSĽ™RgĚşěĎÎßz˙ü÷×z¬uWľŘÂĄµJB‰VsŠÁjá`jĄ ˇ‰ČFŠś˝��.ábD °ňŕ0»/˙ő)ť_ Í ĽhŢecöÔ»éäcE3łÔŃdŮďhĂ(·h÷ÚÎňî>Ż˙ů‰^?˙˙żűţ»íüuEŚeĆ ©ÔQ …””Â:   Đá˙űRÄę yµŔŤžÁj6`Ś&ů�˙˙ţ_š@;vż˙©­D üC0đu"ť,gĽC„¦ #6†ţś|Ż“Ĺ5„ůnÍŇ7ŹąĎÎźŢgržnßůĽ´4ö7őŰ]ĺ¬+Ţ‚ćaCÔČO]:HOX×0�>heŐ,Ź­úěÝŐ(˝Tšn‰5Şâ4u˝é–ýźľZ<öŐëłűă°gZ٤ăńsźą.îMózffvźóťütďbíł[Ő6TzäjY:˙ Dâ*qŐ8†˙űRÄí =µ@ 5Ak6a(�˘at$;^‚Z,–HÚş­ ť˙˙˙÷H(©¸Dzś†3EĐ®e§3¦;&QÂä9ou%Ĺn C”‚d=+_˙˙ţÝčT»X™%8á< bHĂG‹ ‚01C‚ ��Q�˙˙ţť53{˙ú˘°ÚsÉ–Ą~ß|…™AŃ4͏˱Sá:Ě#rm%di?2łČďô˝´üżçc_űt×™¶Ne [|óLL,ť%é8&Y7™lő:1ąŁ±ě˙űRÄě‚ ±µ @A}¶ ¨ł ‚?˙˙ţͨVÝÁż˙řźln4 Ť‘ň8őTĹű,EYŻ «ÉN57C“5TlÉ‚n{ Ó€¬Ą=gu˙źéČęYňĽeP–•‚~ŮU (i–q &®pH@)H„©ŠpB�˙˙˙ý×%—7˙µü)ýM>©-;c®k9r/©Ä…ä‡ áb]C‚†AÉëŠ$0äLYpŰő˙˙ś§oŞ˝dV’{8Ştú‘Q 1#moHt´YerRDL(˙űRÄě ‘±�Ŕ “=¶ˇŚ�–  ĹÓ�]˙° 4zbŁČfËţ|¤0 Ą  ‚�ŞC•¤>ÁH›IVľ{¦×°ĺ2l“)M1 R3˙˙˙T·eٱŽčŠŠ ĘU,1ŃŽ–$Á Ő��`Ň µ¬Ő\u"˙ôż™桅?«m8ëT),ůK_űľ^UQó3ő­H†©ň”>‰ň2%~#ŻŹřůţ~:ŽŰt(s8ע¤¨†« ń2F ݱ`ôĐwkąĆÉ(€˙űRÄěKőł@Ťťw6`ڧi˙˙ţi3m EM˙˙ĽČ_nLDWcIĹ3“Ú ¨G!)\ĘyűE:ŽÇpÖĂ&CmMZŮ7‡¤˙–ţ|ş™˙¨ Čě&ÚŮžL™‰*(ˇ"bLŐ$"]!*©•��Q�˙˙˙ S¬e˙ůFg/’ł4&g§őŃ Ě蔿J1¬ TĂ1ëK;„nĺ\Äšc(0ŁN´,ňŰsüçsź)<wß­´˘ľT‹ ­FÂEr"č´t,BLŹRëx˙űRÄë Uµa =?6ˇô�Šh ĽH(¨�˙˙ô_ĆDđ ż˙ňôŃEśúhZ«“/ĹĽ\ÓđIţLYěĘęŞĆÚ>%“‰ S*Dsźü‡óüóuťűb¤ĹF䉜¤ŕüĆ\Â(ćVZa”BŠ ,`vŤ˛JŠ•��� ´@Ň·˙îŮgs{˙ů•©<Ć&Nr=îyÎĆľĽvě8ż™ć’ö«XŰ;G们Şç˙Ó·íëĘ®ŁQJěŠĘ ÎěcÄ1ĹP&4˙űRÄď }±@ ‰}¶ ڧi85a«d?˙˙ňm?Ŕ2ÓZźýĘfyÝĂá•QźĎ)%[+lg®VŁ#‘ˇ^¬ŕ*Ýş©ť×ęěŢ÷űŰż/!rČT®}÷'.Ť˘Uć’¦Ňç˘mŔ›Q\V`}f€��Q�˙˙ţN×Lůż˙şń–¤^CžęŢÎwÓ `ÖVq%KĂ yݢC=Zó$Ô‹:Ęt˘Ňňú^\ŮçĹŞ*é‹ É,m' ś2F ¤š¤˙űRÄë‚ uł@Ť>É‚¶ ¨§hL›ŔR)\Úú�áš�˙˙ýSŮÎÇ&‹7˙Mś"ŚŢ‡–kŠ: ”a!Á<QâşČc˘&hÉ« ±%ô÷˙ťßáąµýdń¨±%úĘ\b–ö‰R)d `ş3ńer­Ź���Ýô@‘C±úKă<UÝ›·HÉ9©°Şr“n …·˘ŢžŐR=*Úh…¬Š˝ÖGÝ˙ő×MKr˝*/W9Ěć«A Ă5Ę 9B#ë�ĺq��˙˙˙űRÄç Q±   łÉs5ŕĚ'ˇ˙îŠk•‘áRĺO˙˙aĽ˛y|o0R×ű’Â:ĆX~ó›%:iëÂaQ ŐpgÇŹíKřV$Ď+•ËţË0züĽ13Z3–X„Ăhą×âNÁű§ć’,CÇÜRY˛$RXęVR5���^3D˙˙˙·b[` ‹=ź˙ňâ˝4Vů¤EÉJ`ÉNą‚3ÍóÚbńtŞš246ĺe'WKzśőĎóç˙˙É+îý˙vókoR…ěS4”NZŇ<?˙űRÄé }µ@Ť=d¶!4 §Ůâ’dŐRu�ŁoüH$/D‘&|Č•®˙/8m¬f3/<ĽâÁţnjiĐgy]ĎÜ‘×{A;CŢUý¨íŞ–‡ě_ź˙çů˙ó?żś%"=Ě] $S`BBm(€ˇr hŐ���rKcI˙˙ęĆ82JKśĄş†U#«ľR}?ďÎ3ĎNă-/´ćÚÇS¸#˘Ł’0;s5ĄIĺÎ$jâ?b}Üż˙˙˙ţÍčW•Qhc”¬„şfşF` ’Č˙˙đ˙űRÄč‚ A±  ±Áź6`h!łi ź“»RkYA 4˙˙üľ?IßNo-öI-ůżâvú!ĚČŰĐHĄPA C=ŐSx”^¤=Dmô˙˙˙˙ô­÷Duu%§yčTIÇq` �ކ€�}|˙w˙¦á`0µ`˙ţéŽ[ŹSE¶ĺőĎřßdßó/ţľŤęXŤ v͆†h|„sv—¶Mž�ˇéˇŹÝďăĹeK?÷ßö{˙?6YŁ ^†«TËŠlˇđĺIšËő“QX#BJÄ!™ńËPô;>%-Đ˙űRÄé‚ IŻ ˇ ;IX6a´�Ťč› ˙˙˙±ĚĹp†@َ,Ěß˙ś%čw*LSł3ÁËěÝ209TÄžs)ʦEtşąT΋S©čVf{nĎFű˙˙˙µ:ŽŢB۶'¨bZÔ(–HË €™í“Ě”¨ ‹jľu��[ki�ţî—˙ŽŚŁe aöżůezŕ’<Ző2UÎE3-P˘p˘˛;™Ó1•Tňˇ*­IĘĂv:5ŐŐ¦–Wb˙˙˙˙KF˝mK^áĆŘ#�.•��˙˙ý˙űRÄëJń±ˇ UŃ?µâtŠ»ť<Čć(˛äb+‘=í5‰—١j˙ýŤjB5Śt{·ŤJĽb"ěđŇĹ $/7“Ű­Ę_˙‘fÜëćěËťť‰•›{ľ{9ď|Ú8>0 ë `…e���KkI?‹˛žrŘĂłçmŚ«Üó±)’Ň2ţÎÔĆ<ĄŘĎ•s9‰ş!YÔyĚbM™† **:Ě×mŃU[˙˙˙˙Ý™¨ö»:µdaö•ŹC (ąŠ9»î �?˙Ő˙űRÄőNµ�ŕ™ť¶ ô!&řŁ':¸—8µc…3¶ôéďsAĐ«őŞ'ßűŻ4÷{2ÓKcŐ>Č…X™‹eÄ43B3Ýn…ůź—ĺ˙oJ§ąSś™Śc$‰4šFĚWU®ˇ"M3ĄĂž*j(Cp ˘���rÝ« ˙˙Řd<ͱ¦ŚD8L».˙ Y“€¬=Ť{ą?]T.sž]…u¦m©9ÚťĘÝ *»ć°›ß_üżďçńő^_Ýő:­Vá[4®a÷’» Đ/*B„Ô ˙űRÄę m§  ­Ů”¶ ´!łˇŁËcDś§Ç󑔡Đ@ß*´żâ˛źK˙4#őp”[ˇkUI„G$7)ˇgÖÚ$ÍlÖˡÎł?˙˙űw§g»3vA„©ÂÜŞ@3ˇîF$Ę���rďł@˙˙ٲODd9ÂJ7"5ĎüĄţ×WBMsé[Ae[íCPĐ )ÓËË´ĎrSR6*O3z›1ůÄËŽ^ÇY˙˙óż;źg<„÷!MŐ]Ďfžá¦QCęi$zR˛Ó`˙űRÄë‚ éł  ±Ń“6 ¨!§ŮJá€6µ�˙˙˙˝ĐŠ1Ź~„¦˙ů&GIĐäćZ Î+×·ŇzZ®U$·s…)iw*•·78YäQÖ‘U«ń!žó˙ţţrGÜsn[Ň„çßä‚^eÚiśĚU‚ŻÉĐ.K*��߬E˙úô*ĺ“3Yħ˙ě’"§íöš)śłČŚó¶  ŞAjĄhÜP Ł ę*9`˘ j˙źźç˙ňżý·{łç­2eÎ@ÎÇNh˘˙űRÄë‚ ± Ť>ÉN6ačŠz´†Őł‚‰‘$ ·mcŚ�ttäŐ3ŢŢ^ó?żţw§G/>}Š«źĂ6őÚWMH(€çB1#Ş‹F‰Ć#ÖÎŽŁ˘Ŕ6˙ű?L‚V@���‚ďóH€äľnR…•2»d‹·gŹš÷‹[zľŕšÝ=Ć)ú,Ä˝>el×î‹h›ę©šŻźůëľú˙ţ8žőiżAšIĚÂ�łąĆ F<É$ń p†â„(żr4I˙ţ˙űRÄď -ݎ 7ɶ`ô!§h( L ”ă·t?Ă“ţűčşV¶áČs5B¶U)8Ť&°őH?pÄ  Ú۫ uŇ/5amë>ĺů˙˙˙˙ţß;3¶únôY#šKC˛ţ-RzĄşĚŔ±i˝��ľHŃ$ eąîÖ8fBŢ-vtď˙yŃfś…Dµł… óë=B“ĐszI›ba’Ş\‰T’“]–_??ď˙˙˙·÷;™{~uÚ˛cÓIUP%€ŚC+‡0ç8W-Ś€˙˙Ę-˙űRÄç‚ ]± ˇ Ý ˇă´�ŤŇ{™ Ó¤rěF‡,ČŇĎ'=AĽC—'émĺP™ Ş›ĘĂ[T¶łŰ‡&”ŹcŰňn•5-~Égů_˙‡3úiZĽ­÷ŚbŠŕÖ4ÉĹ g¬tÉH,Ś2†¦!ipM4 ‘ 4ĚŚŔ×—˙äÖË™ÉHČŐ–ˇ‘“YŤZËPÉ•”'#Y&˛Ë!”ą¬ś˛öJGüż˙ö‘¬ż#“,˛Ô5k,ŽFłĎ˛ć_ůË–( NŽFˇ¬‚šŠf\˙űRÄń Ył  Á‡¶!hšúrnŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞLAME3.97ŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞ˙űRÄě‚ ő± @oٶ`h¦řŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞ˙űRÄěË]°Ú €t��4€��ŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������beetbox-beets-01f1faf/test/rsrc/image.ape�����������������������������������������������������������0000664�0000000�0000000�00000033707�14723254774�0020462�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������MAC –��4�������������Ü3����������Ů0' 0éoĹ|Íţqôčî�����D¬�������D¬��P���('¸�������č+6ąD€»…›gzżă˙nč±`ČéŻCÔC-]áXUÚ˝ŰÎý–Ěą€ËRôa‹% –ýŇń0ó©'lOéŘ[&ď(ďďHő)ś"i%6aMJO1FĹ@…D8Ůqlpů¤ŚE ]“ą$ę ĺýwëvy0äĎ\ţv7®Ś±I®Ć ̇m7Ą.uMŇÉč$Ĺý #­×x|‰Sźúgb;<·|:}|{H?k¬ň"ÁÓ{äěćwß áÝéݡ"vútŽ`OM".�˙Ä+.Ë÷"q=f“; ĺF&8e*’<ű´íH4ŠČiçŢ-w»´7rý:¤ŰśJ \ó!¶Ü$W'8[4“ě«„ËAvěMXFĘUÄÍťHU®qta!;{;Şëą ĘÁ­ŕG^ô�ad¶Gę™gŚľPĄ­)«Ś·Č úîŘ™ÜzĐšSŽřg_ęQ¤Ü=ĎS Şů¶¸âčMż‡nć”– ­u%ă÷ú˘ÓÁ8˝ Ě úx«=j]m6“aú@Ť“I›L]ö~çęn0.*§ăĎćcžânQjŠ0‚ôęí‰6j®ń1ÚC%g§4ß\-GhŮ3Ý•[čŕH"’—ó@ů>Ż3°+©ŮÎ˙äÜG˝ů§W3r<b…ŘC3D:n•8ZŃ ž}ě؜׎BĹ‚Ţč&±č-ňěđäą:kň_+y‰ĐyAZ(>ÚKn>śŇÚ+{<zţĎÔ„ĽuϸlD +cbŰÄc„>ç˛ňô·Š¬´ &…ń6uż_zęmeS)K+2ňkň± éŘ8ôÝ‘cĽ3L®ÝÚ퍎Şv˛WܸÚßčŃ‘¶Yŕş“@§1"kâr}őqIĆţu ¸ ˇ©[#ŘČôđ¦‘w“f•—C?s·”b Ś[XAĹĘ Ă=‰‹\ ú„yäüťvJeą•ŮׂfÁ$0˛¤yŁž}:ŽÎ Ř­ô[čr‰zŘ>Q†Áx „“(® ~ 9Ĺ5QţKť¸ÓÓČÓ&)±¶‘9jÜaĄ«`ľę‹A8¤Ě×– ”©żŐ"“ŕ†;bR3¸ “D©ťW vćÇ)ăĎłS›ÎŁŽŹÚĘ̲A}ĎĐꂇTÖ‹ľDcXHwüWE>Čŕ‘'•:ЕȜÄqqoie4>#Ź÷ -&}ě#}FŹí5pĹ㏱ăł*"•(taŕˇÂŕĹ“"Ń)®BÜ -·,AEexjĄźňÚ†›űu®�RŹH×n_¶n¶(ö¶Îâg'v9HĂ^%Ü!?ľ"XÎ@µŰÚ6×›R÷…­‰ŻĚÎyÔĆá=ŔĎ‚öŻ–8/Ö3VőMć»8ż±˙u!đ™rP@ÄÔŻ ›“řqSzá~ĺjwń¤xTčv¨TÚ.'˙î†>ĐäŇš?Î`XşůĘT÷:™—ýĚ Aă?”1˛\@đd±Fâ|ɶĆD˝‹°6ő Xőś“4„,đÁMÝRż#÷°ŚĄ&ěeőľ­E„›źväĘýS˛š+vWČăsJ˙Ëžýxń˘Ěśă{ö[·>¦pQ… t˙łňŽ)®Ě ¤Ç`ămnX±^xőÚ-Ç®“©=®ćŤ´|ťt˘Ŕr…βţ‰ą!ɰvČ ^Tż ßN‘k1˝9Ą#śŢiÔ[@ÝöSÉ‘’č&ŔĎvš¶¦_Ö.ŻoţUśťć÷ŮŐt÷xLÚ˝ęř‰H/ ‚Ďm…ŇfčżĂÉ‹.y Ć8Ô÷KüQE¶p:•7'§Nnľ„ě 8lhËŻîo=›}Żľ•9P}Ví{sÔč˙ŽŤďnsŔXiV<Yô‘ľąÜÂú.ă«Ëö­4·ˇpDf&¶ ÷^°"–IXâíEwŔ°<Éýň&U¸Búħ:üşť°{“S™«´ UqXÓ_ZËŃő„)ćmţ}85äżÖÉ:gë}‹Ŕ^ňĽw5»PGH/űíúy‰<Ş~‡~WŐź=%Jg(ýj2i™ÁRf™hq5Ô[á:'b‰:ćhzhgM °€ÇŹĄ‰ŢÚŢĺŇ´+fä1‰óo „śĺ€ÔĄÓý§/ýčQţ´ů4QRĄăᣎ_ęS0>Ó”íEI€íH=ŇősşK—Î{ÍÁ»ŘŇcyO€g›ˇPű‘–ő‰żÉĹţµ\3yy8#;çtş2‡˙ę[mš ©í‡˙‰y0ÚäQv{Dź×®ú3@ä ă­h˝lXÂqKž!®!őĐ�Öąú§çÚ‡źîŔř–%5Pú]×|ąő´††#i2‰öíÍý Eäůě›ęL˙î­ 7XĄ˘)E»[Ýá[Z$ѾƺŖ˝żîKšW¤ĄŇĂśíeš�Y#ńŮ,‰íŤŢ„ě.eŔŇ<Š[čÍ؆,ŤI#ŚôŇ·5´BÚ ˘6Ěĺ)ĄŮůMP™12]ž Bäă'‘i"’-,ąXQ3Á™€Ř_@řĺ©ĆY5ÎŚDdťm P Uëe*zď =ý…,ňc‡-żgCó˝6a‘˘1ucô°,a$őÖ×zܤ ćV‚ňŔRĺ “”$}ôÇĹ~ď¸9EZç1°H.1.–j¸˘îżź{—N]aL·.7 ¦Ż€DQ‰l¨°Ůń‘±89Ĺgg­vDmę^ĹĽäžďđDUV‰.’‹ „OX“EV˝ńúĹŻĹż>qázŻ™Q}(Ż#¸MoNŞţ‚,–-ç~bűc–97ţŰ W_ýÚ 'sžNYO j]feČu´Ę6óÝ3¬ĄNâ‘+1g¸`äjÄ;Vn6ý˛pf_OBdkŕžpěuŐqž\HM…kŹűÇbµY2}¶PĎe'ĽŇqCGˇ[Jě7‰P~Oëę ’k¬™‘Ő3“/—A ]%†°h@ o<V¤Š/lÜŻDÍĚrä;Ś’B®ÓĽó€Če9óŮťÇN‡‘ëČP—díş«ö-GLjc¶íDNäçs­_Íî\\Č^˙Oöť.˝]ň´ ńd*jxÜť_Ĺ{?ň żÍ:4~ ëCŃfÖ$ u—V€T5•Ě4—|@‘#F2ç�xZ:Ú“¶)ŮäZLU˛ŠYńxź9­˝ĎÇ*¶^w23p(×kz0íť2NYT„Íö‡<1_íŢĂ{FTă© ëz–ú€Ěżs­PźtłÉŇú1 –Ű# ˝6#´»”› 0 ł2ýJáPX"<7Ę·ĆOáJ—čwCâj˙h*Ą}áVBřńďXŁŁ˙ŘÝűÂţ9ňסڬż‚ŕ.ę' ;h|Ĺnůf˝.ËťŢW|ú{­¬žžn™żýÓÁET˘”(Ań!¬ ŮfĺnŠnŔźW¦éî—ŕ·ń¸B÷rfŻÁ”O}7S|ąsľ[(7üe@aóÉý+¦§"Ü´¨Ŕ±3ć­3˘=DÄG|ÚŁSá\űËËŐ»GޤjfĆsé‚śŚˇcOýů5«Ö4ŔčAľ^Ď·Ţă�če>zWÇlýŇ0—uąrî[˙KŸ}–ún˛gţĚ@)E˘ …/ď׿•A ă썚¦›źÝéFz ‹ěӓ͸B‰@[Ë %Ű×á!Ę~nÂ8o¦®H#7±=ŞđG źöżqÍĐóО`Ka˙„íHĂPXŮóú{ ŚŻŢz“đ|\+Čç|+Nn{W´*ă›Ňý2†usá1\$«J÷‰µ®E3Ő˙°Ű (ÓÁň‰ĄQl µÓâű¦\ő€V 6°ě¶†RĐ-]ú–SŃM¬´«}.ŇZ+í)BĎw{”ű]ňŹ×ĽBôŽ Ł[Ú~š–’‹©<nľV,ë}#ŃL;ś5¸˝ťĘ�tf[ČVŞÔÔ8¨_TŃ7<ŘÓŢ»……ótu-ŽŔ'DB[XŔŻĹ?›Î:<Űn^}ĽłPČÄ —O·=Ř“y˛T-óĎ_uXXö‡JhËy ź´;ŤS’ t•ŠRňż×›ŽSŕ‘ܲ¸Ďf é,MĚ ÎnŇ­šß.‚ĆŮ>–uÖ<e ˇĆŚcUŐű äöŻÂáçł:ß á~*Ň:A1ž«¦1L–•PוÉ€‘¸Đ_¦âw¤MkyŇ‘>ęk‹§*ë[“Réů˝ĽČKń^˙Wń.ýŹyx^čSĄ`ąÇXń Tť9 Á‰}­ ,f%cň¶z¤Cdđ°‰/oűXfÍÁgî/ňŽćÖLb ăząIŃ9z,çik߸m2Ă�Hßč:ÚČ™U3±6ę›ĐÔVŕ-­!S)­Tµţňt —Ăߊq‚üăÎ4zM’î=03mcí(f4E×Ů9¨Ji#çŘ$Ö€˛‡.Ľ¶˝î—fąOŰša—żÇFaöćoËFVą68qc•ň:u[PŻ_«eŇžfŕëf›ŰÖ�»$;RľíźÄ=§Ś‘!MŹĆ—íS±(ŮbéLIŹçoWŮ>ňź“î铳ŇÓ¶—S{Iî=C.…ŠŰÍ'ĂÎ?^“Hut†q§ą›Yn—¦´®1o2˛ňz.˝ :LŤ?ŃÁmt‘¦×x) Lěĺ\číö󬍩K•aż´¬p\!)ó §÷ŻP#ű…?#™đĆ<wIµÉő«Ż�gŰEqŠó>ÎęX58ć»ů޵¸*l¦&#ŕp(müL‡3–B±AóéV$®ň÷\Ö^›G4–Řď”yłŢ°"Q’©s¬Żaň—R%VŞwPRő±űoĆuŇÍ‘?şËèđÔř| ŃjĹĆ ßFZ© Uíçíw¦łĘ�ĺÝLeňt—ŢˇŁ±ß\®]ťnfČ yř*e6úŚ.ü¦”PŤčZ Öâ‚ ş!ýi…ď«{ď¤Ý†S¶hÚëęŘgeyäÎy&úń —Ę6#ýF(;`;S÷íţX·ŕ‡«¶~Nţ>^„Ćjß˝#䬭VĎ%ކ„Řd8tLíĄÚF:änś_ îőČxrr«ĺô± „ŕŽVˇ˙—;ś!ŃĆüHŮĄ©˝IÄAş_\d™ęŢYšštIËľ§ăzŇ!¦%,Ľ"{–( –ĆiđĹĹŻÓďI«»öŤe˛˘mnřqĄŚÉ…włËX!qőüm­~"ś.Ă5Ě08ˇ8’ëA&›k÷y»pĚÝg¸ţäˇ!O úĆ5Ň/#Fv˛Ńľ»AßaI$¶öůIM$éV]ĆŽÎtőş ą\€Ť^ö*­B§X/e`{Ćh ÎLóęĄ5ÎV4híE@ňEÁhŚŤ2˛H1C|ggÍĘłĎß® :O­ —Bż ¸9’Ś0˛ďXwTl}Š$4¨éť]¸ô‘“l“ÎôFÝެ˝0ĆeSbŽ+ż’í‰<ŃL¸J‘ő^‹!hŤňďˇSiűA Źfa‚B$?é×ZAt¸# – =š üżÎGú—çTűÔűőUľ‘‘+FčX §L7˙»-áăx»µżN`ŻÁ„6$cöÓ ÁÂBÚő:—ţN’mkCŘ=ŽăF/}8âÖă^ÚDđAsą´®#x_™•C«KT]ÖƲÔľ˝mřa×á}üo† Vú´âä‚€ +Z*łN÷·Gť°.™ IĘ›ęáS \†Ä?ΤRý}-JzăˇÖbÍ­węµŘµ„ …Čť™_’ä7ĄĆóĄsRáÁĹtÍ­n—’ëT;×ńŚl CŽS÷Q’ëĄŐ>Tˇă‹#A陡Ż:^<k»äŢ‘m˘Ę>ŻhőQ”ŠĺÚĆĐĚU fB\˙śm\ŐMrYó^ů`—A‡ô–śEëQďZ‹’Ź˙ĚŞ™ľĐ8#”Ú:V:ŢgJo�’C‚ß0~×}”ćtž4$ĄşóÔň°Ái“@b8epÚµÁµíĆE+b÷y–ßb´čłň„«.ÓĐîv0ů›™H9ű('Ť%™ÎqÓÁ‰‡N«)†{ś‰b˛§?›,-nţ x˝k Î?UT…áʰ4vl”ĄP{%ŞÝ‹vÄuݦBnđCżjI÷4CLJ§ŹěłXk?fCŻĺjpőßżh×q”«ě50RHU*‡ŇŘĚ-m‹:Á|ă5´;Q•›µß&Ľůě¶PérçŐnŽŚL‘Uçň×5÷©yéšI,OYˇ*„áůŽ“)XLĐN5 8đ‡L¨Vá6«łW 9Q R#+ކ K˘ěýäXÜg.náá2Šá»í2KŇŘ—ďTőÂŢ�ËÇÝ„ĄF0XC‡,S_ç-ĎÔ,ŢÇńäÄÜŕ–¸‚ ö3"¤m˛wr•‰kBÇR‰žÎręK¦Éĺ.yú°V:±Ü;â *ť 0čk˝—VĽ!ľ�“ŽBçýę ÔŠ¬±ěż´Šú祽ň´D…¶ďbhtÄ·+ǵ!dĆŻ˘t'UNa¤…‘řĂČĆłR�V»3J†™˘ńqôňď(ÖôŹ<xm%cw†8Ňž gVlz/ZŔφ“é{ꇕ�őGď@îvŤ•“pذU×ML-wmŐ<ő[ü ‰ŽęiAĺ8\0"k87Ęž??—…Ź Zî"¶B'ěŤry/ ڬG’ń)Ę•Hśjľd/¦EŢLŃĆ˙4×8éG©Vä^LÁĘr1˙›¤äP =ŁżgŚ(F݇VĹŮY´ü4Ĺd•SĐL×ZÄČ˙@,Ú†qń­Ű÷sUwOéB©ŘŤ‹đáińŽ$Bżü(Ü»/ŢśSŔŃůdńëŽ3{Y¨¸¶ŰAňĄ ŮS'¦ęçX*=eB3;näŚ1 îr§ír™‚ĘG۶šăÍ®TóÄÍ <ĽĚžá—‰šyłsWűCáÜ=ŞÝĂIjĐyg!=°Š¤mŻ\2Łö}bD!bëŻ}ÂWśš˘ĺĐÓÝ=XŘj´:×…*hýřA81 #$ÄcŘWfš'çF]iJo·âY…˙d çmdĂß B~!=9- •ÔµţŢ yMMfŞżlŤp‹ŮŢuÁßá°ć•ą6ą•]«Ůřk˛¨6´%{둞: µ·Ĺłë₍ďŤű`çśş!TĚşp=ygxć(q–®ČZnĽ@ŠEľűö˝aśeŻc6ho†UčŹĹ[óuř"Đď¤}†aĚäť“ĐÉ>:óŽ)ß»^]Q¬#őy«‰Câ«9ĺÜĹÎŢŃĹ…˙GąDZjśłí&⥎Aŕ=䲽um©q>©jJF¨’!Ů<uő´«®™ [·cPFéwä=CăĐV‹ä–ďšĐ§ !9<Ç?Ѷ7K_ĐÎÇM+ž3ËžŻëŮĄ¶Ővvţó{şŕFßC´zÜź đ8<Č ćŹ;©hhöŢI$bÍŽ5ëF»‡]SFôxĺő©QÉł˝"v1ßs©žfźxI9Ă{hoŘw"~•Ż”6Ď"ů1ˇlâ>ÍŔZ »{Ę^Š©óf!— o:Kç\tÚJ8™X@ —úÎj Ü4«ş;07:MóŃ!©çń3Vď‚€dܱ MÝppËTýµ]÷?lťŹórÔÔV/b „Ö†68¨ş.'“[±$<בiﮂ¶ćŽż4 Í®§đú€1ÜŹń.†úôÂ~pç”d>?ŘŐľćw‰8­±PąLçbđ\8Q·Ě˛mďťf\ e+‰ő˝Í® ů?ĘěŞd)dś“ÉóŽŤĄµ„Ź…®W™�®´ŃIr$YB8v@Ř 06ŹŠf%¸.ý·Ę+1_ĘŁ€Ěî^Ѥŕ <•şîp[GépĎŚwĚ ‚ěŮ´EâÔq6ŰŞńË´×›ý_-/Ľ‰kç �qNúăKŽńLDu÷ďá’ c_DM!ĚâçwsV·¨ą -đUÓ÷ăŘźAŻŠ´Ľq®g@Ukwíěp~SE9˝tݲdu�ÍŹt˙}*Á&ďYeŹzÂîŰSŮńEóÝâ=|çÁ Ʀ ;ďĐ<_ŹoâLÜŤňÔ|’'Y÷űČá=‡2¶y˘nď˛Ě}VśúRětOćĽ ţ•A0gÎ:KÉ{‡ěµÂ]č­I‰}/ŕŔjóŇŹah•_t ®I$5‘uĚ&.µŇq¤2ÍÎ^żw…dMMOč1-)zŕuńĂX<Ű]ÖIâ ŽČ" Ý,/g® €ă|őŻ>éźu¦¶ËŔ9ĂV˙ŹűžÉŇ/ň5k€ě-!“[Äř&ţńŚ FÖ Ö{ëńq±ŘőwhŽĄ ćéyő3˛]—ćäéŰŰP\5NDůěŮžožcAČSéâVm>[ţâ}kE:Ž"üşÍTĆ ?Wź î'aď3QíjŠuŘx�gÇúnrj–čMQyµäçźĎťhřV´W†–&Ţ˙ŰEŮ)ó–Č«<ŞýLÓm˙ŵJEöíÄDâ|řdćCjąžßA§+ä׉Ôw,Ý rcĆýMÜ­«M“ĎפŤkFKgÓÇ üˇÇZcxé ěĘ)lQYŽ*RuątO’2˘Ľ^˝ňµW@ýäŢ‚˝ÄĐ`ţÜÂTťŻšÜ† fCpK:©Ţl)Ü9Řc––Mž¸¦ß®&)n,•ě‹Nń%ů»ß4Ťç{Ä?3ťĐ^@>čČJ„.ňE| 0ˇ×\Ť�ě§=e÷7üŘsÉZ$ 5Ţ«w? \."<$Ó)´ťßpäRwčĚß6Î;ně\+@°ĂiÍő”ë–RxĂŁ7Öî.V€~傦Bmk4ń@áé‚%?-f$1ż˛ÍFbńçšă•˛ßPďeCS r:ýJ(üqŔf‹µ9âç�ä¶L iáŘŐÁNS(†î)»%w–yűyđS(Ô$("­Ť¤-­ŁH2Á‹˛f>šËŮVďŕ$xb<A±—öáđî Kd|v^E“7”°ŻSöZ©[đ¶v×+ô{©î–°ŔyĂÄŐ“®ÍÎäYqXĹč†é|e´’vbÚ’źCˇ8YGŮÄ]‘O˘ĽĐ×ߤğ˝˘¸Ä {P„ĹBúč§V)&Šą Ž;U03µ}˛v;XäR…eď–[OIĚz†ŕČŽłŕŽß®Ú?†ľÖ2 č¸ä A<›4eôcM ­Ę}rţrg„^ ÜŢďtËŠÝ”á]ă“úÉ�€"!Ľ?$5’â !©A^ý©I·[ŁŘîvOy(™ßÖ»řĆO–�#„ÜüM@)ţFő}XöJ`7kŃć4eg´R÷Ć!o5"ĐćQ¸‚ĂŢĘ·Ďŕ—ö.řÝG(Ĺz§{âBh‚úGD¶řJ¤öOm`3olśkË6âß ^ófxvž€Ů×ÖúëÇřidŔ h ·A×űR0×)Ó\HXCH/UĂu};›8ĆńĂcăľ?ě˛t_/úäŻöňřĹ�[Ńô•‰ąö’Žö ŤNËYĆďxĄźÜĐ Vݸ̲]Ţ…y -ÍGČqAĚÉAĹ·źvxÚčLÖŃަš0~˘­/żż­Ź żë~9pľúlŁ�iˇ(¨as¦Ç" ‚ä\Ý_ Ц1]/JšźŞÓˇˇq÷@Ýl3lŐ ý„rămâńK [ńCĆ7íĎbI”ˇ^ďń‰N`ďwhg‚ü1Šg˙ˇ?Xýéý‘GŤ[Ľ¦¸5F-dLŻ%d^OGŰe÷Ď ‘ —ź‡Ž‰ą:DĹ˝«ß9ěŻäE/xźňüNŰ­5 p_·'PSî_cÔŘ ş�%9kŤ LGrôcŞI[ŠîLýH(e¶i—ŮOOĄ]âţ€l|żă:őęŞJ:đ«š_‹Ď~ŞŘ)9F§Î߉ÁŐ+ą”[ěj‰ü‹‘€%€. w6Ú5<çY'đ8^ĺABb8´¶ÚŁv`¬P§`5i§ťú#+Le€\6fO`÷™đŻ<ĚF"_™äÝőµţ®ßśSőjxYý&–v,¸¦MväŻ Ę+­[DZ‚üšKE ĚP›É¬ÂĂ™)˛ô_‹v:Ľq¨(<áa'L`R÷÷Í ~=CĆ;É`/Éúp{…@ĆZßńâëjÉrĹ8ŐÎîT—‰ąťŻl+ü`Ő‰^ֲۡŃĚŻm{sk¦`oC0ŃčĂČßîç<Íű#éě$VcCuAé%µSú÷“\,çJőű9 L”čź6 I4‰¦¶�´%ŇCwa4ËŤ ÷l3�b5¤ÔźŃ« T%ä8Ĺę!ř(©Í+‘#yűDźj†±1{`?1E^–?fé÷÷î->‹žsĆhĂ%uÎIíňm+'đNÝçdëź?żpőË—8}ÖÖ�ĘŕpÝbŤčî§‘™ß謏ŕĚŠPÄ«Ż(KL.Íg ĽkHĽa+Śxé‚3�ü1›+éOéçĚ…ar±7& ëÁvődVóůOÉËę6!ď3ŢB4˙ř,F|sÄ·ˇž¦ëżőw@*B`Đ_]ćq>ů/ èĂńť˝ř×Ô"ęÎÇNiK&”…=.˙hľŔ-JÓěćÎ;RvÓő¨„Ŕ;YDŻźđÂpâšôT>JmŃŘ„§3ażŐS ¬J+`Ő=*ăŚ1o05ÖYĽň1¨kŻ2eîÉŇÚĂ:ĹŽň5IrÝ Źĺ¬WÚ1Ë1(Ł X=~É`ä5ř;B,d—´5ŠB KZOů>üS.ŹTÝ Š é%*¸ŚĹşţíoOú4yJÔ8"B‘¶`G YSôâ#ź‰ U¨I• 1ú÷nůäîy)~Nj7Ź÷•ZÜgą\|˘˘EĐ <Z*îđ_™„î‚˙BŇ‹KÖĎ€‰zĂäWpťŔšŤ‡›şy2cʧŕZ×xŰgeźţ”ŽRŽß8§f†÷·Źţ¤@ţ–ÎăHš@NK„V¦şĆQ@ű¶Ń„Ľđ÷iÚ§gߤ!×÷<*ˇó"¤!—XżŮÎó¤pás2x…IFuisn›Ű˝t'YÚ7ŚŁ +`gý 0kN.ˇť„á|&VńWE˝žĘĆTTŔ8]U5ßH°›Ő¬ Řf~dŻI<Ů,“;¬ygÇgE0¸÷ŽĹ<¸0YöiMßHÖw(ÄTý•ŕÍŮ`î-|Ő@V‰8é˝°=%šU¸#joę3ćîKţÖhÉhÚŞ 3¤•/ä‹ë)Źi=d?xcǶŔcéăFx ?Ćr2•°$Hx=žă퍍Ńz;Ů9Bw8A”šbsÝĄÔf÷U^ťÎ�gß+c˘z [\“A«¬é6µĄ–®“Ů‘›?§ÓĆéŻ03® źoý©F[ `6!ǵ˙)ÇŐMT&čoţ˝Ť]6ˇËŐň‘DSdÎ:ÔÓěĹŕ°±Rş@Â6 ŕłiŢŚ)Öá ŇQ$^† —?ęPź1ľ.(Uł˘-Üž\m`1ó•ýKµÉ�–ĂíÍ*o¤‰ď-đ_c¸ó R—I,ŠĽ%żüÖÚąÚ›ő4V‘/•ĎÖďkIp›»PQˇZS“ìÉď¸-c·Üż7÷k9u#FŮHc&­.2/N&¦ş‚h¬Tîž §ă¶mÓ¬Ŕ <^ÂRá'M±.Ď2ú<Ě'ŢćČg2PM—]ś@ČăY z_\®vVŠČ5ĹuÂĘ÷ Xhŕ\Ô J4,ł^\ NcĄ(ľjHŚ–Ół¦:k¸ŤĚ`ďň„hÝÚßvű8A=…ÁmBŠ©/@dű}8çëI®«»¨Äçľ~ú†úĺź´§D°™îćŇ6•A4Čł-—° %¨ĐFy‡eä—´pµnłD*DŰyřŕ.ďš3*^˙©„żNâ2!Č©/gÍü:RCá#h,ň“6eç‡S㨻©]d¦^5ß`đĹŻw4V˛źÓEGKź¬:ʉ|ó$Kć€~ÜŮÉżI±Y§ćđuíż1µ±ÔÁƨµşĽ'\—«ňŽ6];ĺ˙FÎEÚęÓ{Ă‘”[ęMĹ**Lţš_0P'¸¸$üëâ„_ŐżxŚ»©Żé’dF˘4zÎÓAć3t\â `é\Jňy–Ě™p"ś^ĽÚr^8ýÓmFöH!ÜËŽ1;GŮ®Łµëx2dť őASČ—†xFöAÚÄ*,ߑÆ™× =»0ZT!¦Í!ä—|¶ľMú‚šŐj9vJÁˇM©üšŽĽ"űI7ły iŻé™\Ň.¦7ŚřU*°ĚuĐ/((BńmYY̰ÇÜČ%@ąąŔ/ôAČĚ\nľÍAĘ/×3<“`¶v2+‰„ľ˘zÇb<­nr¤ŮÜ'âÚę?#…OgMČÍ ;¨ŽEdĺÝÜ ËnqÓQ»–1±Kú{_ŰÄ„ők˛¬ř łˇë%Ǣ˙ErúXˇ[/Ľ8bvdô^őĹ*{/sŁţ:ô´0ÄśĘD ŇÇK–l’ĽŢ.~ŹÜ^©#,Ć  ×ŕ2ŁźW`¸HhŃ”¦Ę´Šö{úÎł/ĺ˙ĚŞ˝Ć©"e›(ů iłÝř•˝$.Č>«˙«0‚ŽínSîĹŤŔĽ“UúP �Rf=˛ÎŇòUEą!ż_’šďŰ?~ ĺŽ+ćΰÔ[Ł!W;ţ#„µ‘ú%‚tqšeŠ*ÚĹö»ĘŽLđ ű‹ňY9WTd Ę+żvsÓ~Vň!@śş’šÝ ;éWÉďlťŃÚUéf€ć6€ŇGľŤ´y ˛ü–Ą%rŽŠLb@çĺ-ŹŹ,ßÚy ÁĄÝ MxR 伝o^‹ÉüdçN¬źôf¤ĎóÄĺmŕ% Ű1÷[ňKÇ;‹.žúBşĹ,ý˘á�:ţŁţ^§ÁĚ&µ> ę/!†3|Ö«SŢ—yTßF?řË0E22˘>¤jQu?ěžx€©’űî~$[Ó«™8ôŻů{Üw!?I‰’a ×)!ö@ŕ°˝Č@¶ jTz\žů+ţUĽ•řĐęĂŞĆü߼–T˝Ś‘ŁşBŚ+.ú€Ý%Ű'±Zú oMO™ś8b(şÔßg.ŮŘE·ťácGZŇËęźB—ćľWx‚»5őÔ0¤'śç##�9]!@&ĚóŹÝ˘FýL†FŠĐ*ß•Âa¤6ď`­Ł 1\j¶Ś©űܦ#ÂôĘÁ«ĚŔ5~µZ ś¬;s–íąŘźŻo9­5 :!Ď$˝—ˇ`môŽJşŇ¨ŃC+śţH;q !LO‚ńŞá’bŤ5­O~ 6D„o§őÉňŃkCZĺą@ȶD›©t†xĹř Űőż4‡ď¬Ž<śŚU§9ë—žćgů˛ź•ë–zŰ"rUŻ BśŚ‡,ż|Ľ?;Éůńń0sę@aŠ€Ëł�ŤéYňó:‰’@5¦1ńq±0ĆYDăc:âPoBę.Ő­˛WZ˛ÍDL÷·ŢÜ ĹěZ­°ezž> \_Ľ7tm–îÓë9&˘DütÉđ—Ü•hI >kć…F€‡ţŇLöŃqWPBX`á#¦´«ÚćAßůţ#ÎÓXnł$u1ŤY ŔuĂ|ţE)čx†Hżő.®-ż\tż<7¶dĆ®(ěTüľ%ž(Í‚%Çę–¨«¸™Ç¨-’7ď±=Ş�aß’Ö±ü@†?Ŕ‘ČTˇ}ý‰öó·p ÷§ŕp(¶ŤRW‚»\źT@˛ ž˝îâ˛;)BhôR9ďţ÷ďß®ťă«´ßě?}_™‰lż &°Y–čę˝odŠ‘ô*ďđĘżdD«´âí€ŤĚ łűŞ]›őëha >ôyTMp´2‹¸e q¬±Ť8/ßźjČ ÚǫɿŔĎ kł Y® ľ m’čş].Ę}űß`ć4Ę1Sy;€ŕ,ÉŻt �r!Mő1Á:°/i)¸XŁŽźçĚa‘Xö–ř 4đRš(Ś”ŽŐŐxşäIfQ3sĺ/ŚŔ†Ńöž5ŇŐĂ',ö÷,˝Ç˛Şu[rwÝţ ÖĎn i á‰Tç†Ó9XĎs§łŠč \·Ř =şśôî1 "¸coäĹ/ęˇă……¶“ éžk?'-ÍHWŞk …ukČ1 0f›-ÝŹĐ”¬5˛gńéăŞřÖc» q;ZŔî5\ÂÓY5*÷[ŕŘx–ŔńŻß R»Y b˘úÄ_(%…×L™ëa h˛§5(·yÓn>ť‘›éDŠ-¤Á»/HňŇ{uäef/TCIÇ·R+FlÁ±řÄ» ÷UăÚ8ďúfBűa¦Ţ<š’</T¤őGćşlŤ>9 ˇŚUvnɆŚ`g Kł1–ŠNťDß·¬ ÇL§? :ÚYS3oÜ 2b~ôg ó(š/¨y'ć”´¸ç@I%ťJšZĐö3 lülŠY±=± ^!uaŻĽeAöč(†ź34ţܵ˙ĎSşaţ^ŔrŠŃrmgG`+n]8Ôīż‹ů<Aú%Wg{Ć6|AŔ6’t„Ř đRšGÝą.Uěî±SW0fľ‰pjCń ş]ljĹę¶ĂzóyxQR!äST´úȬxN˝łŰ>¨_Ă`ń řHpn]áĽ.2Děň{µ¨đDáÁí¶|’>– ÇÓö~­mWĚÚxIť#Żg—zžČ;‡*Ő~¶ďĹ ¦ëŢziť9§�6@ű<ű˛ÂÔĆşI&o /Ţ…tŻÔ,=?«v•s»ÜĄŇÎ @™R)e~3ŰŃëŤűcęhiI—·Ëť¸Ą9«ľ9Ü|F{˙C2V1ŤŤ°÷ŢSYęôše@µ 9_, í ń9%¶óŁůÂúĂbź…#ć2ä`O\úČf+e^ěϦŹę«_~úĆaŐgŰÖ;¸i † ď‘:§3mmŘÁÚţ„ ź{w©!dP-ĆćSęgŁ0şÂÁ[Ü{efOŠ –s@¶ôĎŃ_!”um˛hńM}ő!Źĺ‹ë+Ň&‚6YH C‹DµŐ{_ĹĹЧ0T7m`)C/4 Ő/a‡ •uâ©}XŰăcF ‰›Çš“SéşÄGÔĂ€bý�ťzđóąEísÚ‚XĄçs Ú˙Ů‘ž˙:°÷dţ%ÎN—fÉóá ôVÍó ç掶¤CXĎIýóę +{A:3ťÎ×\4ń&�0]-k•Č�M$8ŁÄúâţ…éă§Ťjë]at ¸˙±/ć‰h& Ě(ŢŚj MÎśűĘ+«ó˝�ăĎąˇÇÉ8,'ÂC˛Y0‚ůnů­eCÄ«®"ˇĹ«B¨ű1řęĺŚůN>R.pń1ü§)L­©Ó…WRW‡ŰZű°Ĺl˙ç„ŁŞ–ÝŚµé¤ &Ě‘ÔÜ+UáĚ&Ú«u´ýÝÖ~XŞĎ"cÁ"l@u¸Ń0ľůňČý×Ó'Ř ‚S˘XŠŔăZ¨\%téÔrŠ‹n¤Ą5Ěç*¨\QzáĆ=@%â|żřŃű.TÂO"µŘ¸dGúE"sXYŕ!ľäx­}M§ÖÎAdLąÖ\•«üó�Í˝lö·¦˛ńeQMYĽ’ăĆÓŃęěÎâĐň^§źşŠÚđ­)č›ňę)—*ńćö} q ç‡ 0‹ăe¨ÇpwvšÓ ćĄIÂŻĽŘW‡ô,2_FüZlŤŕ:Ô%,,ťÜ!ş9ľ…yÓ”)[Ź~CZD,ÚëM“ß|NŔů_¨ĂJ[‘1hëäÔnĄĹ-Ş^÷rsĚ«§ymLBÁ÷Py‡…<Z÷Ă6Ó¦CAĹ–%G”ꍕJtćľ'(©{ŮŁő ÷›¶úŘ2d±Ţ§-MÉ9ŢšëÇd˛VôxÁYů‡­­ ÷8 Ô˝.Ä-ë:ŇÔ‡hľ„¦ ľĺ+xE/Ř<ŠG n{VŇłYzVÝ>˙k#”ŃQ{óxĹôgü[ ŘH¬­aĽ ůݤw˘wGŐźů^;Ś<ó?ó¨<ˇÝ!ĆbNmÖđ<^Ë´™É ýPÖËťŇ6»y^‹ůˇĐ†¤ż•;Ń„@Š˘¤vf+]ű˛2{ćˇÜVFŞÚó¨e¦&™k1ŐVîj6Ľ_Ł™f˝×L2bJŻSĚŮmýť«3QIÜ]ą»ÜÖ�ć_źŁ‡đ8řź<u›x|~é.2„»”ŹoůóHęsM˘üť4Śx]Św7u…%Vą`‰ERyX¨őĆH˙…zďZ^ÎßSXĂźşďŁĎ›8[Žžw´ć.zPł+íĹď_G}ĽKŤű±.M·É´'(˘*˙…l‹1)číÁ®±Ř+B.çeÚupxW±đČ·m,´ŇŃü<ńľŘVôŚ 2•s ‰ŽěŤFŚŇ6úŔ+®#KWzGĆ?“wèPh<!츬bŢŰBŢ,jđp=zŇŢ _~&§~Éá—ďG2` !ű‡©|/i›íÉ”/R\JľßĹ'¬.qíW’ŻAmşą¶éJ9msұÂX~‡¶%AéŮĘžoWUfďč¨ßČÇ` 8G“ˇTűŢ|´¬­5ő\7vôUB»gĐrtŔ ±1WZcć�Vźž,~Ş˛#+0®÷^_Ş„Ĺ2QU.‚Ł‚/ß–;ŮWȬԝ¦@7 şÔ°”X;îŐňŽ×qĽĂÖµTa_Â'ČnPń®f˝&ň´ríőQÓLHf=¤Ŕ'ÚRő‡ŮŞŻäLó?;ť}dn9¶6`°é¦ŹëmŰőm piŮ’ď`™ăď@Âűď;¦­ŤŚnrÙč–~á $€_¨jÍ3Z őíÜŕŃŘ­Č8ŇüˇŽŁ×’ ‘°ber[ááM˝kuć}üžĆćö%L˛\‰!IHČY¤î,Ů'ąywŻČ'\ô:�?$ÉໆZSU ÍÉÜ@:ŇgŕŰeھƮţA%a-d˙qÓĆX?gçË»ăČąŠ ń� iBâ˛Ć=é¦Ĺ‹"Ą*˙cdőSb3ç´p¦Ř#ČçYýÂ/ă†ÄôZâ+<˘?s1Ň®Iďs5ÜÚe˙4Rx˛ŕÉŻ{ŁüRß‚FÔ¶OŽüý7!R$_Šô…×ôŁ gľť˝üĘ[ŞLOŮ7Ę0±}5OËĂ4Ę ę{Ć:PÁŽ$¸^­ S ’Ńó{‚ݤ=vĂUŐ‡pPůµě~')Ęŕ#—¬C;}4âŐş5 N?z(X°¸:Pčá4u ÁÇ $?KŘŢhCŚluI†Ő=·< 7ŢR”á॑‰‡”.đŞě7L­—í‰MłúÜuŻ-7s@Gqż˘ďŇ`4ŕ,ä|__S5‘@ę1áül]���_����APETAGEXĐ��{�������� ��������§������Cover Art (front)�album cover�‰PNG  ��� IHDR���������6IÖ��� pHYs�� �� �šś���tIMEŢ  J™���tEXtComment�Created with GIMPW���IDAT×cü˙˙?‚�9Ń…'����IEND®B`‚�����Cover Art (artist)�the artist�˙Ř˙ŕ�JFIF��H�H��˙Ű�C�˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙Ű�C˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙Ŕ����˙Ä����������� ˙Ä�µ���}�!1AQa"q2‘ˇ#B±ÁRŃđ$3br‚ %&'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz„…†‡‰Š’“”•–—™š˘Ł¤Ą¦§¨©Ş˛ł´µ¶·¸ąşÂĂÄĹĆÇČÉĘŇÓÔŐÖ×ŘŮÚáâăäĺćçčéęńňóôőö÷řůú˙Ä�������� ˙Ä�µ��w�!1AQaq"2B‘ˇ±Á #3RđbrŃ $4á%ń&'()*56789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz‚„…†‡‰Š’“”•–—™š˘Ł¤Ą¦§¨©Ş˛ł´µ¶·¸ąşÂĂÄĹĆÇČÉĘŇÓÔŐÖ×ŘŮÚâăäĺćçčéęňóôőö÷řůú˙Ú� ��?�’€?˙ŮAPETAGEXĐ��{��������€�����������������������������������������������������������������beetbox-beets-01f1faf/test/rsrc/image.flac����������������������������������������������������������0000664�0000000�0000000�00000052602�14723254774�0020615�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������fLaC���"����÷ Ä@đ��¬D!îÄvoWe×l°÷��Ď������ image/png��� album cover����������������›‰PNG  ��� IHDR���������6IÖ��� pHYs�� �� �šś���tIMEŢ  J™���tEXtComment�Created with GIMPW���IDAT×cü˙˙?‚�9Ń…'����IEND®B`‚�¨������ image/jpeg��� the artist���������������t˙Ř˙ŕ�JFIF��H�H��˙Ű�C�˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙Ű�C˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙Ŕ����˙Ä����������� ˙Ä�µ���}�!1AQa"q2‘ˇ#B±ÁRŃđ$3br‚ %&'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz„…†‡‰Š’“”•–—™š˘Ł¤Ą¦§¨©Ş˛ł´µ¶·¸ąşÂĂÄĹĆÇČÉĘŇÓÔŐÖ×ŘŮÚáâăäĺćçčéęńňóôőö÷řůú˙Ä�������� ˙Ä�µ��w�!1AQaq"2B‘ˇ±Á #3RđbrŃ $4á%ń&'()*56789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz‚„…†‡‰Š’“”•–—™š˘Ł¤Ą¦§¨©Ş˛ł´µ¶·¸ąşÂĂÄĹĆÇČÉĘŇÓÔŐÖ×ŘŮÚâăäĺćçčéęňóôőö÷řůú˙Ú� ��?�’€?˙Ů�Ă�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������˙řÉ�•@��µÇ!̡'†hPćhRs%… $ˇaäĚ̔Ô9C9)™<ˇC™”ĚĚĂ“%8Y”ź)…% aś(r’‡49CL9C%RY’Ě<ž… dÓ ”)™™śÎd¦M 礧2…&yůgĐÉĺgĘaá94ť% Lł9ś(Xp¤¤ˇĂB…3™e% B‡ D™ˇ= ˇ@ł ) !ý Ě’s(NN’…2P¤ˇĺe&faʤĐĺ3$C'3™ g$ł 3”%8RS%™"Bś,’” IC„By— °aN”„Lä‘ ”Ę”ˇ”’yC“2i…9˙˙¤ˇa))™B‡ f,0©@¦„ˇIÉţ†Re ˇ(XSť …†Ng:OCž†sˇC™C<3Ě4ÉL(hĚ9ĺ”)“"))(Rp¦Jg3™™S39ňĘI’‡?)“)9C3'(e äžPĘI,)(S L”)'&p¦r†śˇĚáae ”2P)2HD%B†”†Je2yB„P‘‡‡(~y@¤ˇ4)“L<™2“¦K(y(”2“‡3ýPç)3™™”)<ĘaCRˇg)“)3ćS0Ą äź”ÉŇ2d@°ĺ$晡C…†s,ÎC“™ XzBś)™’a@ҦĚĘĘI伡I’‡%0ćRe'ĘPÉB’PĚĂI”'&RP”2yNe2 ‘ 9ILÂ$2™)"% LˇIš”ţXRe&P¦p(L¤ţ†r…Pć…$ň–,2’P”2’g JÉ43šˇCL)™Ěó”3' Đĺ$C„ÉMRS%330%3 pˇI”2“†IBr’a˙ůL’™2“ 3”É)'śˇĚ§= ’x|)C9'če$Δ dćsŇ$"I…9ô” 0§2̦g4) )(J¦g p¦fs)†ś¤ˇ…™¤LĚ“ÂPçĐ)’„@ĐäBr D…„ˇᡒ¤¤ô<'BP,(g(aaNĚź)ĘC930¤“ĂĚĚĚ)(RrPĺ0¦L¤ô9C””9śˇÂ!™HD fJ ç ÉB r“L‘$čg= 4Í ”9˙<ˇÉ™™Ę<Ě™L%”’JL9ü¤ó9™C™L硜ˇIB!(D9™ä§ L¤ŇS%2RRPСáI…†i4ĚĚĘ” fr’r„§9HD93ç@Í Ę3ĐÎPĚÉ™™™ĚĚĚ‘r™% 8dˇĎBPÉĘLó9)C9”3’S32†JdLˇĘ¤¤ˇLáˇĚ")Ě,™Ič””90áÉL¦O”(g e&Rt3” D99IˇIL4)(y‡(g?@§ L™I”ź)ś)ÎIB’‡))(r†PáC””9Ë sL2’O)%(O<ćJJJJůĚĚ,™I$C!L9B„ʦMçB…‡(e$”ˇ2Â…8y”“çĺź˙‘L¤™™%0ç”)'IáNL)3ĺ I™’r‡™ ÂśĘ)“L””ádĚ”š9ť'IL”””9II)3?ýś(g…2R†fL˙ô2“!(D ™C32Jaś™L,ĘJrfH„ȇ“<)™…&hfPáC…8r!9ĘJrPJ”ÉčffdˇIĺ9ˇB§Đfx˙řÉ’@�µÇh!)œ“4)2™™śˇI™™ÂśśˇĚćfHˇ™L3”3źC”ĚĘĺ&s9CśˇIB“ĘĘŕS‡2D2RRP¤Ęý đĘL¤Ňz:†e Ě™ç˙ˇÂ$Í B…Đ"LÄ2S„I„Iśĺ&|ĘLĺ&PˇĚśç(fdša¦M™ĘL¤Ę”(JLĚ)(S'2PÓ g!d–JfP¦ffLĺ$§ fL¤ĎBP¤¤¦fK3̡BP¦aIĚ”)2†hg4(hD Lˇśô' S% r\3„@°Ďĺ&PäĘ"žILĺ p§8D9™ĘLĘśÎffaIB’…% NPžaL”)(y(S“)0˛aIÂ’…' J ™ň…&P9@ćr„§ fL¦¦M… LáČN(RfP¤Ď(RP¤˙‘& Îd"rfs9B“™“ňśô(Jr™(Pť Ě” Jˇś D”)™”(s'3ś¤§‡3Đ™L)3ĚĘYśÎPćRe2JJ’Pĺ ć™4Ă”)8P¤ÎfJĺž’™""ÎS’’IIBp§33%8\9žff4<By˙ŇS2„ˇa ¤óĺ'(R¤ţ†Nr‡…&s̤ź2…&y9“3%hOĘO”ˇ<Đ)Ă)2™™™śˇI‘ ć ”9IC3LÉ”¤"LádÂ$Â$”) Re'ô)2g?C33'ÎS'C9ô JyL“3&’‡(y(RPĺ39Ni‡“L̡ś™BS%’śĎ™ćfd¦JdĐ礡IB’™)’…f’†…&b“4(s(RPóPçIBÂÎPˇ’!†™’™48XD3™"9…2hRf‡I@p§2D™Iň™2™:N’‡ püd¦P¤ÉI–Éś)9ÎhH@áO(D Éĺ&Rr’ˇIśÉIIBÂ…3 páIšJdÄ& ™ÂĚ”‘ ”Ě,’™(sô3IŇtšPš(S'333% LˇC™ˇB„Ą ˇś)(dˇBs!$°¤ YšÉL“„@Đ"0áĺ332S%(g3<ÎPćP¤’™)”4)(L¤˙C”2ˇžIÉB“4”‘Ă(sC9@¤Ąś™’‡(y4)3ICC”ť CCĐĘL§ rfH† ™IˇI”)'(”4)(S…9śˇÂ!“xf†sĐ”3™aB™2† L) JÉI–ź¦Éž“čdůB“™'̧4)8r…2Sś¤Č“É)™4Ç3ˇĘ9“)4(XP‰3ĘdĘNRr†RNfe&S'(Rd¤Ęd Rfe0ĺ źĘfffJ)™<ç<ĘIĎ9C’‡9Cśä@ĐĚÎK( ç2Xg%&S dđs… LáĘJs8Y%30§$Ľ‘2t)(D% aI””)(PŇg)‡’r†RLäÂÉ…™ĘLćfsĘ™$C aš™,úĚá8P8S&™)’’‡49C”3”)%’2e%D Ndó9)Br… I™)’’ś? rÉ””ɦÂ…2e'ˇ(”"§2xg !”Ě̤Îsś"Lĺ3 L™™Ę g†Nd¦H…Sčdˇ“C„C“% ”9¦BP¤ XP,ÂĘśÎH…&é……9™C'ň™'ϡ“@ą˙řÉ›@�µĆŔ!%'dé’…' áNfRLˇĚ")%2S JĚ,™“9šdˇd‘&@áI’!9śĚĚ39é)…2ri‡(hd8RP"J” …!’… ĺf!ś™BS333™ fś°°NpdĄĚ)˛‡2…!JI”''IC”32r™?)™“ˇ= Éśˇđ§(y)(XáB“<ĘI”93‡(g='ˇ’‡=…'% LˇLÎ|§2ĚćfaĐÉĘ”3“2†JdˇI…)3B‡†“&BĚ)ś(SJ……$ĘC¤(D…9Ęg‡ ˇI™I”’”2D(Jt3Ă”39é)= ˙Ičr… Ęg p¤Ď30L¤Ą ćRNPÉB’fe2e&PÎO”ÉC3””Ě̔ɡIĂášJs”Ă™ĚĘĘÉL)(P§…… JÉśĚĺe ä‘$Ęd”™CáNĚĚ”2~RP“3<ˇIáBĂ)3™™™™™™)(sĘa<”)“)))(sŇĐć‡<ˇˇ’!™“Ry„I%32RhRND2~D4=0¦NaILĘB$"LćRež“B™™ś)"3ô&S&RNds>e$ĘIC„I?(RaLĂɦÉĚ̡IžS…9Č“4Ě)Éš Nr!(RyB’xJDPĘ99”9ůe‡=0ĘLĺ…%™3™ĚĘ™ˇśˇÂ!(S&RćaJIáś(S'0§' 3ÉL”)̧ dó‘ žPćg0‰2PĐĘL°łBˇ’PÉ”šg>i'Bee I…8RP¤ČNd‘ śáNLćIL(Rg ćS% ”&JLICˇBPJhPäĚĘB”“ś¤ĎźĐĺ%%% ¤ĘJaĘB„ó–r$™He$ÉB!†™?"ÉĘ~s”3™LçĐ))ú(Iˇ Rg(g0‰3Í’ž†sˇČBĚ4“śáśü¦ffL¤śˇÉLšOIô9CĂ2|%PÎ ”Ă)%2PJ ”(pˇC9333š…0ÎhsCť śĺ Ę,ü.)=!ĺ ¦NRg” &rPÎIfg0Ó'aš…“3ćRfe B!3™)Pá@˛J8PÉ |ôĂ(PáN†PÎJB…&PáÎae%32’~dBag2’|¦s@‰0ů™’S33' ” ”&D'0˛M ”(P§9CśˇĚç3Âś)")’!2!ÉĐ(RPô9čdˇÂ!†g(Re'ü¦JB!HS)“)’L”)8XDe0ó ™”)2“ţS'”ź)2“”ÉůB“2“úfs9™C93% ™ÉĐÂĂ)2†rPˇL”?)2… ć'ĘLˇĚĺ'ˇĘsĘdňs†JIL”Ě)™2!†“3) RLňaILšaIɲR‡&sśćH… LĚĚ”ÉLĚ̡śĚĐ)(D39%'¤"‘ śź”2S%% 礡I”ÉL”3’™™?(S0Đ Rgź”Ě”2Pˇ)üˇÉ”„@Ś€D…„ĘM0”Ă33&“¤ˇĘ™'ĘJ”™ç)™’†ÉÂś(2e„ʡĎĘNP¤Ę)’g%„ĘJśˇ2!“™’†IÉČ„ĘY&’t&S$ł,¤é†…'% IC% ¦™”8“˙řÉś@˙˙µĆP!9BR‡™“ÎPđĺ”$ň…'3% ”šdp§&†NfaI”ĂĚág2’eB„ˇ’†O”)™ĚСĘ”ˇś"9’…&i)Ď"(fg?C'ĚĚ™9śü°@¤Ě̡ś™C8D2pˇÎfs9śĎ$Iś°ů”…2†s rP<“9))"xL¤ĚˇĚ.Iˇ‰RR†r„ô śôĚĘ’” JH|¤ůgÓ0¤¦e áLĘI4ź”Îç–ÉL,"8r™)’™ś)3IĄ ¦Â!™…“3„@°ĺ$ĘL"ÉB’źB„ŇIˇBR‡‰™“Bae2S’…2O …(M ”š™aNfe…% r“)%$¦)(ú¤)(d”™ó"y2†…'ˇ’†Lˇ“\žRe'”(pˇfS „ˇ¤ĚčfP)™(hd¤äC‡ˇĘfRdBe ”ÉňÂ’‡)4(g0d§)<Â!„I<3’…&yB“śĐ¤Ę“ÉčfPç%2RĚňĘáBÉ™”źĐÂĂý&…&tť0ňtÉNLˇĚ¦r‡RS aä:ϡ’… Ěś3C9C9$C…3$C”„™Ěó dÍ'L<”)2™śĺ$¦IBćfrD)ffg% !JCL9”3% LÄP¦NM0ňPˇIśÉfLˇĚç9C™™C9śÉĂäĚĚ”ĚĚ”) "™™’”š™¦g(sň’P¤,Ę™43źC'™Îg3™I”™L„Lź)™:ž, ™IĄś3C')’s$@¤ˇI)(S%2XD$Cś"B”9śˇśśĎ9C 9ž~YˇCS… Đ3§$ąś)“™™śÎPÎgĺ2J“)(r„ˇC“2e%ś2™†…%™")śˇĚĚÉB’g fg2’rS2áaśĺrD)3‘„B„PĘ™C”„Cś2““4ÉLĚĚćyĘÉOĐááC38R†rdˇ’…'8D9ĚćS!°"dčr’…&2e%PĘB‡“™2 P¤ÂžS…9ĘNPСə)Rf’…&Rr!C”(Rri…2s2RPˇĚÓRtšaäˇI(p RfP"9Ě‘ ”)(RO% 2D9™ĚĚĚ̡L™@ç)śË3”3“™á,(hRC (žJP”ˇ=ÉäÓ ĺS3śĺ će2hPç (dˇ’“"r!3ÎRg(L¤ô̦B”“˙)(ICý’’‡…&|ˇC“™ĘÂä/”™C“(s …|ĐÉü°¤äB| (Iň…&r†)™9™™)?”ĚÎ}“‡3”<<’…!BÉ”3Ây™”9”ÎdI'”™dˇ¤Ą Ę9BrP„BO’ F,ý ”ĂĚ9’Ę“2rS3%%%% IBĄfP,:R†B%…JI”3%‡3(g0ł3™")’™:JfP"\ˇIź2’fs9™‡(̙ʔ"IćD) ™>P¤(RrRĄ!BÂ’…$”(JPçˇIBĚĚĚ)9™™™™)(r“Ňçô'ü¦ađô%Ô̔32P)3Ďź(y…2pˇĂś)I<9”9™™™)™Âś(S3™™™)’S&D&D3„I”ź)9I”ČDÄÇ˙řɉ@�µĆč!339śĘađ§ dóÎfaĘ!s LĐÉB‡ N“L<”)8g?Ň…ÎP¦L¦JP”ˇ4(JBa&P¦Lä@Í °¦fM ç˙ˇÍ L¤ˇ”38Pć…&RS‡g=De É”„I”3…0¦NJJHD)9‡’Y@¤ˇ™ĺ2Pˇáäś˙)(L¤”“ĺ9ŇzM0ĺ Ě,Ϥ(X&xD™”4 J)9’™’„@ááe'I”” JÉĺ2|¤ˇ(p¦NP¤Â!Â’’…P¤ĘM Jfd¤äC'30§!a”Ă™”8D8D)(Dśˇ)™†R NĎ)>čr„ˇ’L™@ÎLˇÉćs0¦aIB‡“L¤ˇ™™‡’áBhD $đˇI”Ďśˇ™)™’ P¤ÂźË 9ÎRg33338S3'333$C'%'IŇP¤đä@ňdC338D4(Y3”% “2D(JJBP˛…&fffpł p¦e J“2Pä@ĺ%(OPĚĚ“ÉJ™)’™Ę̤”™IĘd‘…3 fg3™ĚʡśĚÉL””ÉLɦ“ĺ2t °”ČDÉ32S&’†„C™ň™9L(D% ™I9„C%'(rPÎe&S'ˇBRs:ś)’Xe&r’e!¦JaL™IL–g9C„C'2S dä¦yůL<…2hfp‰3IŇt(dĄ čÉĚ”'C &RM% s)2™(”<ˇÎ¤ź 4ÉL̡š<ÉĐĘLL¤¤¦g dĘ™“IB“†p¦N‡"8e Ě™B‡3˙čp‰(ćJB‰“™™’!Â!IśˇĚůLĚĺ!9ćrS%2S9’’†xs¤ô32|¦NPćRz9Ędé;&ɦ¤ˇLĚĎ’’ĚĘdčr†fL¤ĘfS3™ůNĚĚĚĚçˇÎD’RhP”ň™:Ă”93™Â™9™ĘNS$Đ“Be$ˇH_üó9CĚć’…&)“ňśé(Re0§$ D”)ĚĘÉ”332S<ň…dˇÉ™4źčäź)“ţ’…&RzJˇC… ‘R…PĚÉ”™Ę(SPÉI„”))RP¤Ą ä”ÉLćRIILÂ…!Oˇ= ý&”'S$ňS”™Ę™(S0¤Â‡ (s,ˇĘ2fr’†fsˇIš™Â!Îd¦M0ňPˇĚФ"!JIpĘNfs<ˇaš,"ʡ“ćRe‡(Nd¦fJd§ 3™C%% B‡ =%RP¦g„ˇäĚÉ”‘ ćOš?ô(JĂ„@°ĘM0ËHD  R š™ĎI‘ Đ)HR’Rf“C„I…)$ˇĎIˇN¤™I”” Naˇ4(9Bžˇ43™žyC@¤Í äd§ĐˇĚ˙)“ y'3”(s39™…2s Lˇ”™IB…!BÎYô2r‡3”3“3%9ź””2zÎI’d¦g˙B’S!IůL9śĚĘ̤”,>`A“)%8r!)ô2„Ę|ůBŔrddĄ$ĺ B– J™’™™ćyĘ y‡(dĐÉBLćK32P¤ç)”%čf‡)’™))(RLçĐÉBe&†y™śĚÂe&)2’’†‡))(sŇzaňî’˙řÉŽ@˙˙µĆČ!ĺ” 0Ą B’IBĘĐ" XAPˇ2y…™L””33 Re BśÉL)’”)8PáLĚĚÎdađ§2Pˇ’”% Có”3™Îd¤¤ˇÍ'I¤–P<ĂB†…32‡(PÎĺ&S3<9 0ćrS%0ˇIB™…… ź”Ě” NdĐ)d"LΔ)3Cś¤ f†r„ˇIIBÉśáá‘ 43…2JL¦OC”š2S”ĚĘdćfg8Y’!™…2Pg„ĐÉB“‡‡%gţ~˙”ČDĚ)Ě"pŢ Y…Č\’Ę… Đ)(“ĂÉ40Đp¤ˇIBś…™™Ă”2džáe ůˇ&„…ňi(Rp¤ˇaNH†fNaćg”9…(p…™ĚćyůˇBxPćg2…!9Ěó$C%(‘’‡&pˇI™Ęś4ˇ”ÉÉ32P°Js3%2i…2hs>RPĎ0¤ĘJžg(y2Â!<ôš2†pĄ$B‡2fJfP¤ĚĚ”,)"&fsCB“Ě‘0řP˛N„ˇIša)†Re3˙ˇ…Sž… Đô”šdˇĘ’‡y(RIr8S39ÎRr…ä̇É&PÉáË JfPç†áLš¤"ĂB’śĎ™”&DD8e&’†…% H)I”Ě㇠p¦g ‡PćfPÉĂ”9IL”ÉIB“)’…&zB„¦OÍ!))(Y2„Č„Ęfr‡(L‰2’’ Rg ¤Č„ó2“’M ”<ť2PˇĚ–P,2‡ fPÎsĐÎPÎe™JË…8RPˇĚ¤P§(faĚ,™CS… 3™™(s)…(JfRfK0§„„ł%)‡Â™2†s<ˇÉĚćJ9”ĚÉL“†pR¤ü¦N‡"(r‡‡)Y0dŇIfrS3<ĺ&y™…&S32†gNICźČˇBPĘaňg2†rPĎP)’”<”)”™LÜͅ&fPç rap’Čr‡)(hP”ĺ2PÉĺLĚĚ"L‘ @‚ś¦L¤Ë J™(™(g&e s%% ĘađÍ ĎC”ž‡(S33'% J,8RP))ž†sBf¦ĘL°¦OžP¤”)39CC”„B“9C„C™LžS8P‰ ç(g&PćRJa̤Í™”“™S2P°¤ł330dćO…8hR9C)&p°") PÎĚÎRe3ť2S2“"…&g 3Ęp§'(g0LĘ… C$ˇC…9C”2Pç)9śĚĚĚĚĘĘaĎ)™ĘHPáNdI„IC)2’’!ÉB’gLÉś3’śôÂ’…3' (J3CB‡&ffe$đĘB……”<3… Č)čd%% ”$dR) d¦HHYśĘaÎP¦aC33…8P°Ë f!Jdś“Ă9ůC8D9ÎdL"d”Τ",,"ÎP¤ D™IC’!BSś¤ˇ=™“% ”ĚÎr“ś¤"Lç3ĚüĐÉCš™C””ĚĚĚĚÂÂ’!(S…9™Ěĺ ”’IžP¤Ą đΤ%0”) RSúĐááC”2“(S$ˇH|3IB™’™"Í ćD’|¤že'ý0řhD!0‰ s™™)(Re39”ĂL§aBÂĘ’áˇNĚĚćp¤ĘN™6‡C˙řɇ@�µĆŘ! (e&J2„"d¤¤ˇL)Đ,Ę)XRR~RNdNaĎţ‡9Éś¤že$ˇIĚ”) 2RD JćRO3” dĚč™Cś˛‡™)“IĐÉóç”) PĚÎL¤’Rs 0ł JI„C%9O&™)48YCÉB‡'’„HP˛fH‡9Ió PŇO@ł4(rO”ś¤Â&O P“LůI”(RPô&0¦g$ĄJ…)%2hd RdˇI”$¤’!2 s‡y‡xffN†J†hNRP4ĺźÓ…933&Re!äËɤô9C”9é†hdˇ…’~S'9C8D%8DĂ™32RRD NLˇ”™¦J B$)ĘaN‡4ÉrD) ÉB†… Jś(s4ÉfáNsĚł)HPˇCB‡(sB‡'(Pĺ d§(Sd”áfP¤)HS3%3%%P¦frJd¤ˇIB’…“9C)3˙I)BJdĄ0Ą ”Ěĺ ĚůLžPćÉĚĚĘaNLÉţ…&S39""ÎffPůB™™2 s4'BJaIš"(dŇzJś”¤(fPćP°ˇaB“=” ̡̜Ę% NRO2‡Â™™?@§ 9”9é(PСI”<šd¦r…“)(RS3”)3”32rS&’™,ÉCIÓ ¤™ĘˇI”ćy”™I˙ůC™śĺ¦K‡ÉIL”ĚÎPСä,¤ y™:)“)S&e$ůȆJ|ˇa˙úaL“2z8s)&Pá̡ś)™’!’™)™™Be'"I432 FáL™Iç"̡śˇĘd§ $R"Lź)…ČS3%™”8\ˇIL¦™)…% 8Y(pRNIL%RbĎśÎd°ˇˇ˙”” NdˇC“8e2Pť!BÉś)9CÉů)<2ÎS'ô(g&sŇtšˇáśčRp§'ĘĚç(ry…’S3ź?(Y3”ÉJ“y’!žC% ‡<¦f™Ďţ’†„Bg%2Y’™†…%39’!“™)(důLĚź”ĚÎPć‡4)0p Yť áB!3'3śá¤ţN™™S<ť ”ÉĐšˇäĐĺ%2„ćJfp§ fd¦J@°ˇNtĚĘL¤”ČD™Â™śü¤Ě””)(S“(psúˇ”%3"ňs(dˇLÉđŇ„ĐÉL”ĚÂ!HS˙ţY”ĚÉBP,™ĎĐÍ0°ÉB™@ł'33 dČĘdĄ!BÂ’!(D%&IdĘd§0°ˇĘJf™ˇĎB|Lˇ3%… °¤Ë%9ÎR ĘĚ)™žS'ĘpĄ$çü¦fLLĐ,(prfO 3ś¤Ęa̤”É(Rs)8S“9†… OCž“"a”…48S2J"™4Ěĺ')“”2s3ĚĚ8S” (ĘfffLĘI”D8s%&…&S$Bd@¤ˇIL”É)(S9šĄ$ĺ&RLÎPÎd‰0˛ff|Ęa™ś"™3(™Éé4<„HeS2e%2S&’… LΓ¤ˇLÂÂ……39IBP, s'ň™%!ćaˇ’aLĚÂÂ!ÉĚ””Ěχ fg pˇIĚ)Éže&R~hD(Lˇś))“LĚ,Â!ˇšy2k»˙řÉ€@˙ţµĆh aśśĚÉBˇHPˇĘˇ™™śôž…9IĎ ćg332R†re ”ˇÉ"Hy™ĘOčg?ˇśň“(PĐ"LÎfaL™L2Ŕh&rÂ’„˙ţe!¤-”'"9L”ĂĚĚ)‡3ˇ™Â’„ĘfO”“ÉNäůó9ś§)™™…8S3%2tĂ)“Ě生)‡e&39C3%É9”3’Y‡)(hRP¦NaBˇI(s)śˇC”(|4ÉIˇ)˙čaf} 礗'IICšaň†hg™đł3P”(y9™”’…&JY„L2“9fSfe ”(J™ĎIčdčN†r™ś”ÉIB!'%™2†PˇÉÂ…$¦M’áBPçL”Ę…0¤ˇO'BP"B„C% )‰2…PˇˇC”ť0"J”3źB‡3C9”ˇ<ˇCś(y™Ę9<šaĎůćs3(g2‡9)ĂÂś)™śé= @¤će$ćD2P¦IChD% JJ2“ ™™(P”"% ”’™)™™Â!™™(RO&’… dňg9C% N9'Ęs‘&i’™)’™)(XS0°ˇL”"̦O˙ţ†eaL’‡%&He$¦ffJ%&r“ç(s9CĚÎPÎS$@J,’PŕA B™’†Oʦdä@¤ˇB“"BśĘL*"B™„C<…(S!|¤˙čPÎL̡ś”2S)“úç)Ă”Ây„BP „ˇB“ÉL‘„PňS™™"3ˇHPСä嚡‚™áLžI2„ˇaI@°ô2’„Bg ĚĚ(Ráae á™HYBS0Ň„)“IC@) dô™fRĚ”2R†fdđ˛ä’!„˛hP˛äĚÎe2†Jr“Ę™)% B dáfe%™ĂĐ™I„C…(sĘÂś"ś’äP”Â…‡3”9™ś”)2™)™’!Â$"&LÉůL“”’P)(PćffJf¤(D†R¤ž<ĂB†y…s„šdł8R„Ę“B‡2…¨‘Ě”¤žP¤ĘO9Ŕ°ˇÎ†sC”„C… Ę44,,"2“ˇI„ĘJfsĺ3 J2S39ÂÉ”9śÎg(Rs”,Ď(rs„C’^Sź”̡ aN!@°ç(O…)33™”8Pđť ç)0¤äˇI”<”ĚĂźčr’’…&hRrS&†fg(hr„ˇ™C)ł$C2“ ̦”&†xe!Ě)’ś"L˙ȆJPÎC”””(s4ž’‡"a¦JfM0”2P)ÂIO”ĂOC”)9…3$¤™“32S&!“Âś„I„C38Y”"(r’…“9ÎdL¤é‡’™"ĘC:B‡&dśÉá<“Ă‘…”2PÉC'Ę™1 ”ĚĚ)(Rs332S’!“ĂĚ)2……8S%3(s?)™Í Na¤é4(ry)’Ě<ÉLÎsś,™™™…&Đć…&S0¤ˇCI…“9™Â“y(RRX B!(D%(rĘ% Lˇ)Î&%Ě“ä‰Ŕ‰(R|¤ˇ0‰2e!Đ"BśĚĚĚšN†só8P§ p°JH„ˇaNÉ”ćPáśÎPó'ţ†p¦OĘJ(PáB“,Îe$üüĺ$ˇIžPˇI…3™”8D32rS$C0ęŕ˙řÉ­@�µĆŕ!™”’S NaLÉ”””2P¤ú愞ffJffRˇ“™)’” °“C9¦M% L¤ˇĘô8D4(y:”3D9)(… däˇIĚ”ç,"†PĂáčffd”™ĚćĚ̤Â!Â!ÉB“?ˇśĘL¤ś¤(D¤ňyI@¤"%2ä,ˇĎň“4r††S <§2”‡C9IBs>‡‡&PÍIÓ L™ĚĚÉC@JaNˇś<¦OĘI"%†NJJfd¦aśĘdô J„I2„Đ)Ă™ĚĚĚ̡IĚź?”Ě"ÉB„ÎI RPĐĎ 2D2S$C%2D&D'2†rs9…9?”Ěź)…8PĚÉžg9ţ‡(frP‰ áLĚšL‚HĄ&S2„ˇ”(i’™śĘ™C™ĺ L̤’&INJRBĘáĎC”)2“Đ̡ÉBR˛’…'0§ s“?ţe$.Oň™:%<°¤ˇ= ó@ˇ"gĘdůNt8D‡¤)Ę8r’Ą%% Ě)ś(S&SR B$2!“™”9‡3LÎd¦B$ÂĘfpˇˇB™)śˇˇ9“9žP§ 38P,ˇLĚśĂL”ĚÎPćg(s™Ě˙ĐÉC9B…% Jaʇ †™†RIL)“™…8S…3’DÉň™3”% JJáB$2„óúJfp§&r‡% B”’S‡ĺ9Ó%$@ĐJPÎ L”šLpˇĘJ™C2e39IB™)™’™4ĂÉLĘĘ2f} ”3(s4ž“čdˇ’„ĘO)™™śý ć… O0ňNg”)0P”Ď„Lś™™™™P¤Ď!32Ri(Rs% !Iś3ž„ĐÉC””)2†Ra&RRD 2S'C 3B’‡"ˇ9@s(p‚!N¤ˇL„@°§”ɔÙ̡śś¦g†rNIĺ0Í …'(Pĺ ó”%™")™śĺ&s„@°¤ţ†Re&…!C”ť$dˇĘś)™™ś¦ećH’aá‘&s™”9ůLśË ćffJpˇI™ˇś‘ žP˛e2S9@˛…33ť2Pĺ ˇBD9L))’™‡“B‡3B“):)<§†rs"„B™É)“B‡2S…9™™)RgC”)“ś"(D%&PĺĚ™gŇP¤šś"…&†J(p¤ˇ2$…Ă90¦(S I¦f!JND HCśĘaćdĐÉC”)“)=0¦Iće$ó”śJ”3(p RgúJÎaL™IBÂ…’R†fJś)&dĘ9’‡3śˇćr‡(g Ę™“L<”ĚÉL”‘ ”„BP‰'>IB‡’|ˇ¤ˇa˙С–IĘS!Lť‘„D2R†p¦aI”ĂɦI<”,(sȆJ}’…†Y2’JP2D)2™4)(S' ˇś=Ě"Í3™Ě‘“™Â D…„Ę“"(dˇśé‡ÉФ2Â…&e Iá9(p‰ “„CY@˛e9Idä¦J”3ź9I@#0˙‘LĚĚĚĚś¤ô 3čg(Rf“"aäćs”™C”P,94fNRP)3(O3ĘĘĘ”2R†JaäŇ„Ą% Ňzd¦Pĺ™ÉL–fs9)™™BdBNz"= ˇÂ!'(Pžt(XpJ@‚ż˙řÉ Ş@��µĆ g™Âś(Y%„BP‰% LĘ“(r’…2J™””9C”9Ičg(RP¤áI@ł?ä@ćNg)'ś)’hd¤ţ†gM!) RP¤äBe'L‘É4𙎙@¤ˇBzˇL9šaLśĚˇ’‡†…2†pé%33„BP°ˇI™B’”)™4ž“¦ˇC™¤ˇaO9C… Nd§™™™”<>dćffJ‡ô3’™™äˇLÉNaĐ™9††M!šäHICɦH†Jd¦fg$B“:ň™“ćRg)2“9By)CÉ)’YĐ D™™IÓ†x !NC…$C9śĎ= IčJ%(r‡(e&P¦Ng9I”ÉÎs–9™śˇC3dˇĚú2’…%’…… dć™IIINáfK33339B’‡2XrÂ…2rd@ˇˇĂ””ÉL”š4(S2s s3339’!śĎ(Re ”9ILĚĚ)3ü§:9ť =ÉĘśź>e$ó)3”ś˛„ˇC38P°§ĺ8XD $ĘNPС̤ˇC9ž… É”źň™?СĚú"s&Pĺ ÎáNÉ”) ’t3…”8D2RfS%32D2S9Ę™I)’„ĘB!C)3’Pće$ˇIOB„ćL¦r’!ÉC“9™y4Îg30¦L¤ˇÎy… ”)śÎg3) hĚ4ĂĚ”"C"I”“)&g3”)(RP9™Îá¦HĚô3Ă”42PśĘaĘ&JIĘś,"ĚáC…C–çůʆ’fJĘaIś¤Ędź2’IÉý ffa†LćH†p‡<ĘI"9B™ś"„Be ĺ IB’…$C'(dĄB!·(sB‡“"(Pćň“9Ěó™ĚÎPćPäÎffM0ňS$B“ĎIé= ™C’‡3”’„@ĺ 8yIš33&@Éažç)3”™B“%2t3”9ňe%! D9Ęg‡' J32†Jd¦Jps')'BdC'2e&$Î)’™D8YůĘIň™I<”Ě”ČDS2”ÂY„BPˇäô…9,"…2e ¦ˇBS…%3(JfdP”"(g&†L¦Idˇf'Bú%?čfP”“2†…g(g&d¤ˇĘÉäŮ…9ś¤ô dĚĘIˇ†” RaCś§=30dˇLÂÂ’“L>JRˇáĘĺB”ł3”)śÂ$Â!ś(fRe339…2e$@¤¤ÂÂ…'% J™ˇ™Â!HR‡:aNe LüˇI…3 ˇš” :4 P™@ćS8SśPćpňP¤ĘJśÉI2RP¤ćfxĐЧ'(PÍ ó”8D’†aI8P¤ĘJ˙B~faÍ !BS”Ěĺ% ')ŔŚ’YĘpˇ¦S0¤Ě¤Ęfe áůLÎhS9™”&D…)…!Jd˙)’‡‡@ł))“L)ÉLÎt(rfJ%śĚΡ’…%% L¤ˇI”ÉÉáNPáćr“”9™RaáC9”Ěť áI”)†Xf“‘ ś¦fs˙ĐňP)ˇCś(J|ů”8RD%(y<¦dç30ŇP˛adšaL%Ă<źC'ĺ%Â…†C%))™)"™ÎPćKDYCD8P§B@­ý˙řy CÔ@��µÇ(©i*”´ĄĄ-*ZUd«(´Ą-IKDĘJYeDÂŇĘRË*YJ”¨µ˘©KQT˘ŇÉ•)ZXš,µ%,©(ą)QjJZ”©IjRL¨šRŇŞT˛Ą)K(­%rWJV*X©bĘ‹R–T˘ä˘ĺ)R‹JVT¬´Ą©J–RŇKQj‹R•µ*KRZÉU%©-EI‹E­*¬©U’T¤š-,©JŞ\˛UbĄ‹KJZ-eKZJÔ––¤©¤‰ŞER‹‹I”–´L´ZĘRĄ©–Tµ,–RRÔ’Ň•-*˛Ň–•YJ‰”Ą%“%JK%T©eIeJRĄ%–”\´˛©,´˛ÔĄ%©.-,¸˛Ô•”´«J\YZ¤ŃeJެĄJŞURŐ*,¤˛•,´¨µ%I2’ÔĄ)--IjR’Ô–YQKR#)-U•*©2•Ee¤ÉeĄ+RZ¨µIJ˘eJK*Y*˛ŇU%eJŞŞŞ¬¨š,–+,©)J‹TZĄ)e%”¬¨šYRËRT¤´«,¬µ*&I‰”¨ĄÉZ˘eeKRU*É+)JZJ˘Ę–TŞŃk*R´˛µ)R˘ĺ*ZX¨šQ5"ęT˛Ę”¤ÉdŃKJYQ4ĄR©eJT˛¤ĄĄ-RZŇU(¸­JRL¤©EKK*’­,©ieRZ’ÔĄ)R•-%R•*Yqbhµ”´•IZ’Ő)RĄ–”Ą©)2’–)&II’ɢʕ&QYKR”©U•,ĄJ«)j*Yh˛ĄV•¨µ%IeDĘRRĄJŞUTµ)QiUJ¬’Ô”Ą¤“IjJL¤µĄĄĄ•+)Q2”¨Ş˘É•Y*Ąe)iEÉe˘©JRĄ)J‰•**R–’ŞUU–\¤©eIJQieĘRŇR”˘Ô\YrTL©JRÔTĄ”¨´˛˛ŇĄ(™(µ\Z”ĄKR©+K-%K%”–”™iIeIIeJʢ•)j•V˘–”µJ-J˛U”©UK.)T˛ĄUU”–T\˛´µEÄĹĄR•,ŞJҬĄJ´«J´¬´±kE,Ą%-)UK-*IU)U*LZYUKJ”©R•JTĄ)&J–*TT«QK)--QrĘ–R’–RĄ,¸ĄÉU,©JZJ©eĹ‹R¤\˘âbŃ2˘eE©EĄ*˛’Ę•ZYRҬĄ”¬«,˛T”Ą)R‹’Z”R¨ĄRZZZ”©e))UeK*Š.J-JK&’”Ą¬Z˘ĄĄ*T\L©KR‹”Z˘eEe%KJ––-QKIU+KTŞË*RËK,©JZ’Z’«)*TĄJZKRVJLZQ4YyUĄVYIjRŃe©)j,´ĄJR–’–”\”Ą)IjKR”Ą*RĄ)JR”¤µ(µEdÉdÉT˘Ő%ZJ˘–’Ą–”µ%©--Dµ%©e*Qr•”¬ĄK)JŠ–”Ą”––\&T’«*-IR”ĄJU*YhĄ¨€q¦������������������������������������������������������������������������������������������������������������������������������beetbox-beets-01f1faf/test/rsrc/image.m4a�����������������������������������������������������������0000664�0000000�0000000�00000013346�14723254774�0020373�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000��������������������������������������������������������������������������������������������������������������������������������������������������������������������������� ftypM4A ����M4A mp42isom������ Šmoov���lmvhd����ÄLé_ÄLď���¬D��¸���������������������������������������������@��������������������������������trak���\tkhd���ÄLé_ÄLď����������¸����������������������������������������������@�������������mdia��� mdhd����ÄLé_ÄLď���¬D��¸�UÄ�����"hdlr��������soun����������������Óminf���smhd�����������$dinf���dref���������� url �����—stbl���gstsd����������Wmp4a���������������������¬D�����3esds����€€€"���€€€@����x��ú�€€€€€€���stts����������.������(stsc����������������������������Ěstsz�����������.���������2���2������"���"���'���-���+���1���/���+���&���0���'���'���&���%���)���(���-���,���*���)���.���)���(���*���%���)���-���4���&���6���.���*���,���$���,���3���/���'���&��� ������stco���������Ć��é���� •udta�� Ťmeta�������"hdlr��������mdirappl���������°��^ilst��7covr���«data�������‰PNG  ��� IHDR���������6IÖ��� pHYs�� �� �šś���tIMEŢ  J™���tEXtComment�Created with GIMPW���IDAT×cü˙˙?‚�9Ń…'����IEND®B`‚��„data��� ����˙Ř˙ŕ�JFIF��H�H��˙Ű�C�˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙Ű�C˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙Ŕ����˙Ä����������� ˙Ä�µ���}�!1AQa"q2‘ˇ#B±ÁRŃđ$3br‚ %&'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz„…†‡‰Š’“”•–—™š˘Ł¤Ą¦§¨©Ş˛ł´µ¶·¸ąşÂĂÄĹĆÇČÉĘŇÓÔŐÖ×ŘŮÚáâăäĺćçčéęńňóôőö÷řůú˙Ä�������� ˙Ä�µ��w�!1AQaq"2B‘ˇ±Á #3RđbrŃ $4á%ń&'()*56789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz‚„…†‡‰Š’“”•–—™š˘Ł¤Ą¦§¨©Ş˛ł´µ¶·¸ąşÂĂÄĹĆÇČÉĘŇÓÔŐÖ×ŘŮÚâăäĺćçčéęňóôőö÷řůú˙Ú� ��?�’€?˙Ů���©cmt���data�������comment��free�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������free��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������(mdat�Đ�ě\«11;Ä<Ŕ�ř¤4„ `Ifŕe—^ŕďŹP0#Ü·›ťÁbÄ»VOŘŮ(*ľJ€q8�ň„q�©-@>Ťč[-3ţ!‹rtgĽ<bžl¶‹ĺÔ‘ô]đNLŹÉ “€�řÂPD€TĹm8|P¶Hü>Ľ(Îą¶q]bĄ¸�úÄPEŮ€€ ްDăŮ‘>JUl3ËA$Ö)i)Ŕ�úÄA#€+:3ď`Xa¶™ÉůQ;«Ŕ±čŕ�ö˘" „Î Ž×� óf{q wZ“Ä 3zżf#)NŮq'�ř˘" „kËr˝ŮÖŁ gʧý'”ę )y*¦Úö»,°Îµxˇ�ú˘" „r †BXVFěě7nhϦNž|z%ôe0�Ue®«Ś ś�ö‚B„_`9ĺo˘đJ­Dz0Ż)ňřáŰ1F8‘ú·ĺÉ>7t·#‹Uŕ�ř¤4#ŚXŰőŢ™ľáŕŇ`¸Ž‚Ţ×xT†aG~fäHv<ý¶ÁŔ�úÄ`?� &×63Đ”l:äGŕë’ 莵š„ÇÓëĺN‚ŕ�ú˘R#A�ש9Ď<łÔiÇ%ÖřꦗµŠÍÎę®yfžÎ�ô‚T„ `®!I}uhnV$?‹+äˇ(Z«„ÁˇÜtaĄVi±+±l‰8�úÄ3#PŤ�0Ľń•T`’3ąčžÓ#?}ĄŐőÚ»ěÔ‹€�ú¤Q#Hl`µÁ˝ĄËK§)Q)E‚ńő>O˛ÂôŻSÔ¦�úĆ"#P ŰdpËźżŰ2é­~sŰČÓďŤ'Pîě=Í&ü!�úÄPG�<Ž0NÝÍEř™_ő'1…:ő‹ĺ\�ř˘ ,l qĽÔş<¬>čđÍ&ďĂž¦J3}•]dQ€€8�ú˘`F  8I+–¶:aá–;0ĄÇ>m>;ßMľíŰŹź× Ą/g�ř¤p>€�ŢbSci›”¤L z:~HŤ5M3©'°A+č&„f ‡Ö(�p�ö‚0!dŔ˘’Ż:%˘C°9Błî+]ţ3Z†ąčcJr†Â¶Ź�ö¤`E€a,]µ)\BiwČ,yç@ĂşD¸ůťü'ħĄ¨Ü�ř˘ ĐŔ€0qóŕ=bžmBĹAwůH°×! šDSŞ 8�ö‚5 !`Â4†§ĺ^ńíŞ^:$9µĎ…gs`M%…ĘZZtŞ^UŁY(o€�ú˘"¤09ś’#NŃ·#̬zÚW›;t A-1dyß”3¤^ ŕ�ú˘" "ě�FâwěĽú„,µ¸rŠţ¬js%”źŇísŮfžep=Ľ�ř¤`G €@ŔăŇ5?ŕĽ`•ŘÉÍM˛ČöĆýYB^×;C€�ř˘`BČ ĆăiRř—‡iéŽ60 9üľ<zäŽ_ężĺŔ�ö¤`E€%MĚŰFZÇ…‹÷x&)íS%–Ć5r꽡lÇq<�ř˘(E,?€"<0Ąŕ~WŢ€/‹Ďµţîĺ˙€y2tvĹ^O)! Ŕ8�ř¤p„p °ŕéËtCf€Ş<ŽÜYnÍPaüRísµٶ†1śžiĆ, ¸�ř˘`Gążzz>ĺÓvë…(Ü˙˙GYJ´…=˘oçlÇW�ř¤3 BĐ3ÜQhLÖĆ$/ę‡b<÷~łµ8ëűŰΚSŤ­n*jőXeĹ×7’#ŕ�ô‚"(„@Ř Ad¨*Â`óŮ'á¬Á®co·>Vűça &µ% �ř¤`F‚ ˘±äßxă}¸<ëĘüfP[2ÚqXä.Š'Iµp�ú˘" „¤*ÓŘšÖ'ęŠÇÉR™z[oÝÓ¬ía‚„ľ”XÇS2p'Ŕ�řĆ2! éZđŢx˛µľůěú$©żVn´%j(óVŔ�ö¤`F‚€öŽťF†@›c}}đCÂ2>Ű<ĆçO5Ł!ąQŢ/grDŹđ�ú¤aBŕô‡2‰´S(^®ZŹdđ{¦P¤ź~‰Ůۦ‘6 �Sşś�ö˘ „,`Ľ-1wNŢ�AŔÍ”XPKhˇwKŰ#0R¬Y±Xť-Ŕ�ř˘`E "ĹImq¤>w˘Č1s5{!ÁXş[Ż/ćŰ?ÓGŹĄx�ř˘2"%€Xä  aÔž®ŽćĎű©ĆĎçż÷şrŻLaŔě\ŞRi"?pě\¬ „ô@Ŕ\������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������beetbox-beets-01f1faf/test/rsrc/image.mp3�����������������������������������������������������������0000664�0000000�0000000�00000024324�14723254774�0020407�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������ID3����SAPIC��3���image/png�album cover�‰PNG  ��� IHDR���������6IÖ��� pHYs�� �� �šś���tIMEŢ  J™���tEXtComment�Created with GIMPW���IDAT×cü˙˙?‚�9Ń…'����IEND®B`‚APIC�� ���image/jpeg�the artist�˙Ř˙ŕ�JFIF��H�H��˙Ű�C�˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙Ű�C˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙Ŕ����˙Ä����������� ˙Ä�µ���}�!1AQa"q2‘ˇ#B±ÁRŃđ$3br‚ %&'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz„…†‡‰Š’“”•–—™š˘Ł¤Ą¦§¨©Ş˛ł´µ¶·¸ąşÂĂÄĹĆÇČÉĘŇÓÔŐÖ×ŘŮÚáâăäĺćçčéęńňóôőö÷řůú˙Ä�������� ˙Ä�µ��w�!1AQaq"2B‘ˇ±Á #3RđbrŃ $4á%ń&'()*56789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz‚„…†‡‰Š’“”•–—™š˘Ł¤Ą¦§¨©Ş˛ł´µ¶·¸ąşÂĂÄĹĆÇČÉĘŇÓÔŐÖ×ŘŮÚâăäĺćçčéęňóôőö÷řůú˙Ú� ��?�’€?˙Ů����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������˙űPÄ�����������������Info������(��!w�  &&,,,33999@@FFFLLSSSYY```fflllssyyy€€†††ŚŚ“““™™   ¦¦¬¬¬łłąąąŔŔĆĆĆĚĚÓÓÓŮŮŕŕŕććěěěóóůůů˙˙���9LAME3.97 Ą����-ţ��@$|B��@��!w_»ő^�������������������������������˙űPÄ�1Şü@TŘ5"L�‹‰€TÇ˙˙˙˙˙BhNs‡y„}@�3žÇ;çBú2żúźď!?úţ}Cˇ„'B‰�¨ŔbĘ€,=°�˛ 7›âýcE`M.¦2 :şJwť f}w§[?˙˙˙˙®Ť‘ŇŤW:®Ş…F Ś�ř·BŇ5ĄĘ纡Ö^ł­*+R:kB·ßżŰ®ź˙ß˙ĺ™üľ[&8—Ub0'QX`€hˇe˙˙ź˙Ů2yŐYä˙űRÄ!)± @} 86`€łˇµŕv"6‡¬F” łäÄÓ”šŁźĂ=ŘŠ˙Ă˙íçŢvó?VaVÓŻSä¤ËŚIůóläe÷M[#@~¸¨H'ŠÝzŁU0¤f˙˙˙úZc{˛™Î¤d îî‰tÖÇi1ާ+Ó>b—WnŻ›ľ·˙˙˙ü˙újÝkv}I*shěCÚ #9§™„�Ä! �×u»kŃ{µjk¦¨™É+Q@·ăÚ·]šŮ+¶Žşm˙öéF˙żnďâçĂWőČóŻ˙űRÄ;­µ @×Ŕ÷¶a N#ś4ěśHšW®JU?¦^gÍç=żĄv„y*g^M)\ŚčŃZż˝W$4>ÚdLN[Íűĺú>ĄS3őę*Ööß G_ĘxV#łB)ë´eT¶H Ś–šN@Ńk%©§GI,™eU]¦zQ.ű«,¶-Ż¬ŞŁ"Ůőó·˙ő¤~ů}ßJWňť}ŢÖTE¤>é ČMŠB#–¤D™?Č˙ď–\dáO=!›źbtph˝'r†PŚ·¨X.vlČK˙űRÄW ­µ =¶!'ˇ˝…Ö.™|ź˙÷şůănµXNkíÝbß aWIvȇ˘'HT'@ifłł"rŮ•µz:ö˛íŁ<ęŕŠZZVlĄVC¦žĚ´dOŻ÷G+>F{)M¨¦łk-«ˇ‘§!M.hńqő•Ň'ĆfŚz0@ú˙˙úďťşYďmV¶ígÔÖwµT†‘ x…!G!'GtŐ ĚgůÖŹ2éK.ŞüÖµ«mŢ“şŠkśR1U¦cë’ë&ČĘ ÄhÓF…˙űRÄn‰Y± = ¶ Ä'ˇ’�#¤´UdVißrŚ —N^’1·|ÓÜQęĺZ=ŢÎëO7Z¶ĆŇŤ}ůăűł«×ĆwáńCĽ[fB˘Ż> ’†R*Š(Ł ?ÎŚŽ˙k;L¨Źf*nSˇSyĚsfiÝT…*2ŮŠ&®ă;+Č•ď˙¦Ťµ żî‚YSőcY4FÚ™s­Î(M 4q°Č¬4EXuŕ(ĐfJ8 vhă dznE"p zł3¬Ó˘z˙ţr|í…˙űRÄ„ Áµ@ 7Á#¶`Č' ÷oň°×«¬Übß—w.Lű7eŠ’@K”Ą)um.W§[MF˘‹vR6"î‘mÚvbÖf±QYŞGuJdłýľş~ü2[q޵öźŹšŠÜfĘBýIŚ“Ů+q Ń áY�¤űľčŕl;4·pĚ2!äęŁfŚKËCŽ…I¤«»Y%>®#ŢľŰřţ?ŢŻ‰Źy—ł,¸bˇAY‡ž Ť,‘+Ť…” %*Ś?˙˙˙Ë˙˙˙˙éíçX‘©˙űRÄ– ł =�ý¶aH�› Ň+ڧş…-zÎv»c±¸F”ř1ŢV”Ôß?Břü'‘ßČ‹űßţoë$–R[ ĹdtŹŻ# 4± KY$íq:4s˛#M’ŔĘŔŐ= „gědLt2•f;^«écµ]Lô[;ŁŐS–Őß˙úßD÷ľ˝ú~"‚i]1N^ w/jÉO)Ş MFW+MČť6_+iĽoĚŠ¬ó%Čěéô™Á® )Ë $'¬ž\W'5ł’źź6?'˙ţpüĎ&Ý™Ţăq˙űRİÉŻ =9¶ Č�ŁXôˇŮě”<ö =}aÔwĄ“ꫜL»©ä�¤NýÝgfˇV X‚Š÷uQNkŘJ$&¨gČ©s©ĐŞčČ„\ôôµ?M?Ý˙’ŰXßéCe+0Ylö¦Ńi4,b`ř=@DW˙˙˙-oU¦—IT;fĘ·j‘ ÇŐ’:ětŃ… ąčs5KÝ[$Ú?Ó#D §Źřţ?ţxç“ëĚô¬-5ń d_e°ŮSä9Ç$, ˙űRÄÄJÍł Ť= 6!'ˇŘaţ˙?éö”`ÂI‰‚”]ĎKi»eóf;éi#R ¶/;:FSś}LÄ×s ÓÔĎW¶óŁ–=h¸c ¤4\jŚ" Eî A 8. `&ˇ r đäŕ‡ĺŹĄ?ôő˙ńň‡¬ż-ŽBĽ}ĺřĺVAT÷QŢ>™Z­v_˙WS›QW3µPÍÇ ›;ŮóŮN~U~í˙ëĺxJ§nŃ,ą!h,č–Ôš ŇdjŤ-i˛ żDH\”˙űRÄŐ ˇŻ ť5ᡌËA€ J ŠĆ@�ß˙˙˙EV"ُĚkĚeR‘÷w)ЬÇtzŻ.ćE!ęK©nčĆvząW[şűţúµu—ö¶ëĆ7”…NťJ(ô"5±›Öó@ÁaL=#ŽĹ @˙üżü R‡˙˙˙䉕ŕt˝wiŇ«UGQlwE˛‰ě¬#GđPŕ‘°şâNÜ#›±Î_ůSĚžž‘ś¬?Wý˘Ź“c‚ĂNBĄ%©ëÎn_XčaĺŃ0˛Áej±˙űRÄč 5µ`‡¶ŕ!¸I¶Çď˙˙˙˙iňĚ—31[šĎ-ľy?ď ś””󤊮nK6‘â–ŞÔŐ{±†yŐN~;sůU/µľöĽ˝ÍŻŠ¸»kˇśĐMl0>Př`ůŃP 8XLĆT,aLq€˙˙˙ţĄY˙˙u­>yń˛ďr‡•vá†Ďů¸JK×u|â•1ÚEf®´0’ rl즽ţŐĄéö6\on-dfY”š·®ßi ŐěŃ*˘'m9Ĺ!D##ŕ98ů˙űRÄč eł� -ÉA¶ˇ &ř˙˙˙Ż˙˙˙ŢüŹ"1µ“Y2 ĘiR+E˝‹'Ç  N�ŕÍOJä(aŘ™dÚ%‡y©µţű3;%fr®g,«źXPąüKÔ1¬´ĽŐîS&!§“I…ó—–—ްĐ˙˙˙üç˙˙˙b]Í�aI¤ÝŃÂŕšW„=Ë»¨K=\i“ĹńlÁIus?W;źńÉeűĺß8g—UYńCcÂIęË®)M–VX`‰L3b3EŘ„$TV˙űRÄěLeµ�@ŤžÁw¶ �¦¨Ś<Â�/˙˙˙˙˙˙ţwËMr:jdZ‘—uĘ@śÍĺW‚ÉÉWŠĂ‰G’=A ‚Á.ŤyXžÎ<˛,·ÎçîWQ`®f[:ş+gdr´’—2vŐ02+#`PH"(ř•Đ˙îM©4D6±Ď‹2•ŐJůݲn¬0^ŚgZ"‘ClV¤±ÔŞŇîEy~źóﱾ˛ü ‰ú…Ča5TŠ…W¦djj¨a#ÉÉTDL@Đl”\ҶĀ˙˙˙ëđ˙˙űRÄęKqł@Ť= ‚¶ łŮó‘vľY™©¶q‰'ţj»%#»+Ńž„l�2Ĺ$hś^´ Ń‘PIÄĺ/é^S4ŕ?Ě›Ë3ѱ1WO\+uîŚý”† —¤:!H¸€N /Š„sµ?@�ů>˙˙˙˙˙˙ź;Ô)*_ŞąjdJŚĺ$Ç™Ya´ LŚę Ę „Ď8Ů«cľďąç}» Q©űÝđm™ˇQ†çXËďTQŮŘ7(ĎB˘ŁÄäm2• ˙ůK˙˙˙űRÄé ™±@Ť= q6 H¦ůú˙˙˙çlAŻű÷l|őżTęmš(ţ™ýę-r đm±Ôe¶ďȧ9Y™ĺ¦â›ţţcwßo¬÷OĎ̆awV­“¨)“dK‹ž°ŕđĆĺ“&Î<D„4-x4&š—Qâ�>x˙˙˙ů”ޞ˙˙ëáÚľË ŤśŹú]ą˛ńľę­\)„›q„]»LBĺÂU—RĐó†eNŢ~ţ~W™ĎßžľR»±;'––ZĆ×Ŕ\µx©,ť·ŮKÂßM·Ž˙˙űRÄë }µ@ ;A”6ŕłŘ˙˙ţ¦/—˙˙Ç9sIBéˇ EËu;#XŠĘV˘‚…ąÎ—D8TuGtF[Š‘.Ôďň+»0–őÍęl˛ŃÇQęAŽj$¦?~ę•(@MNV'ž -Ś–©�6p˙˙˙ďúú˙˙‘ś™P\Т/?ôéB%ŻZÖ*Jt˘a.fćýÁšÖÔ’p‰µ-s˙.ß˙ý9żôżî?K=Ť¶ÓK^gϬÁ•Ö3‹ :--€Üý:ö„› Ű`@˙űRÄě %µ` 7Á’6 3ŘŃ� Ë×˙úµ6Úň˛ä4Ź"Ş(‡)ťčŠ˘ęCŐ°ą$3ÝXî-,t9XĆ»4çeSU˙ú©Őf¨š?Y“jM¸ń đß%\ȸm ‰`dşND ČVĹꀽW˙˙őÝď5’©î›k3·SH ~śëÍX);“ZÔźQ‘{ů­- JIxeŢçů·Wýüůó+Ö{ż°ňőLäo&ŹłI1ŠźÔÄI˘ČěD`5ĂŔh6 •A1@•˛˙űRÄëK™µŔŤť…6 HłŘĘ$DëŞ~Ç˙˙‹<÷cb4Ĺs\\ĎB·ĐérZĺ^ď™n†ĺMÄÚSĽ™RgĚşěĎÎßz˙ü÷×z¬uWľŘÂĄµJB‰VsŠÁjá`jĄ ˇ‰ČFŠś˝��.ábD °ňŕ0»/˙ő)ť_ Í ĽhŢecöÔ»éäcE3łÔŃdŮďhĂ(·h÷ÚÎňî>Ż˙ů‰^?˙˙żűţ»íüuEŚeĆ ©ÔQ …””Â:   Đá˙űRÄę yµŔŤžÁj6`Ś&ů�˙˙ţ_š@;vż˙©­D üC0đu"ť,gĽC„¦ #6†ţś|Ż“Ĺ5„ůnÍŇ7ŹąĎÎźŢgržnßůĽ´4ö7őŰ]ĺ¬+Ţ‚ćaCÔČO]:HOX×0�>heŐ,Ź­úěÝŐ(˝Tšn‰5Şâ4u˝é–ýźľZ<öŐëłűă°gZ٤ăńsźą.îMózffvźóťütďbíł[Ő6TzäjY:˙ Dâ*qŐ8†˙űRÄí =µ@ 5Ak6a(�˘at$;^‚Z,–HÚş­ ť˙˙˙÷H(©¸Dzś†3EĐ®e§3¦;&QÂä9ou%Ĺn C”‚d=+_˙˙ţÝčT»X™%8á< bHĂG‹ ‚01C‚ ��Q�˙˙ţť53{˙ú˘°ÚsÉ–Ą~ß|…™AŃ4͏˱Sá:Ě#rm%di?2łČďô˝´üżçc_űt×™¶Ne [|óLL,ť%é8&Y7™lő:1ąŁ±ě˙űRÄě‚ ±µ @A}¶ ¨ł ‚?˙˙ţͨVÝÁż˙řźln4 Ť‘ň8őTĹű,EYŻ «ÉN57C“5TlÉ‚n{ Ó€¬Ą=gu˙źéČęYňĽeP–•‚~ŮU (i–q &®pH@)H„©ŠpB�˙˙˙ý×%—7˙µü)ýM>©-;c®k9r/©Ä…ä‡ áb]C‚†AÉëŠ$0äLYpŰő˙˙ś§oŞ˝dV’{8Ştú‘Q 1#moHt´YerRDL(˙űRÄě ‘±�Ŕ “=¶ˇŚ�–  ĹÓ�]˙° 4zbŁČfËţ|¤0 Ą  ‚�ŞC•¤>ÁH›IVľ{¦×°ĺ2l“)M1 R3˙˙˙T·eٱŽčŠŠ ĘU,1ŃŽ–$Á Ő��`Ň µ¬Ő\u"˙ôż™桅?«m8ëT),ůK_űľ^UQó3ő­H†©ň”>‰ň2%~#ŻŹřůţ~:ŽŰt(s8ע¤¨†« ń2F ݱ`ôĐwkąĆÉ(€˙űRÄěKőł@Ťťw6`ڧi˙˙ţi3m EM˙˙ĽČ_nLDWcIĹ3“Ú ¨G!)\ĘyűE:ŽÇpÖĂ&CmMZŮ7‡¤˙–ţ|ş™˙¨ Čě&ÚŮžL™‰*(ˇ"bLŐ$"]!*©•��Q�˙˙˙ S¬e˙ůFg/’ł4&g§őŃ Ě蔿J1¬ TĂ1ëK;„nĺ\Äšc(0ŁN´,ňŰsüçsź)<wß­´˘ľT‹ ­FÂEr"č´t,BLŹRëx˙űRÄë Uµa =?6ˇô�Šh ĽH(¨�˙˙ô_ĆDđ ż˙ňôŃEśúhZ«“/ĹĽ\ÓđIţLYěĘęŞĆÚ>%“‰ S*Dsźü‡óüóuťűb¤ĹF䉜¤ŕüĆ\Â(ćVZa”BŠ ,`vŤ˛JŠ•��� ´@Ň·˙îŮgs{˙ů•©<Ć&Nr=îyÎĆľĽvě8ż™ć’ö«XŰ;G们Şç˙Ó·íëĘ®ŁQJěŠĘ ÎěcÄ1ĹP&4˙űRÄď }±@ ‰}¶ ڧi85a«d?˙˙ňm?Ŕ2ÓZźýĘfyÝĂá•QźĎ)%[+lg®VŁ#‘ˇ^¬ŕ*Ýş©ť×ęěŢ÷űŰż/!rČT®}÷'.Ť˘Uć’¦Ňç˘mŔ›Q\V`}f€��Q�˙˙ţN×Lůż˙şń–¤^CžęŢÎwÓ `ÖVq%KĂ yݢC=Zó$Ô‹:Ęt˘Ňňú^\ŮçĹŞ*é‹ É,m' ś2F ¤š¤˙űRÄë‚ uł@Ť>É‚¶ ¨§hL›ŔR)\Úú�áš�˙˙ýSŮÎÇ&‹7˙Mś"ŚŢ‡–kŠ: ”a!Á<QâşČc˘&hÉ« ±%ô÷˙ťßáąµýdń¨±%úĘ\b–ö‰R)d `ş3ńer­Ź���Ýô@‘C±úKă<UÝ›·HÉ9©°Şr“n …·˘ŢžŐR=*Úh…¬Š˝ÖGÝ˙ő×MKr˝*/W9Ěć«A Ă5Ę 9B#ë�ĺq��˙˙˙űRÄç Q±   łÉs5ŕĚ'ˇ˙îŠk•‘áRĺO˙˙aĽ˛y|o0R×ű’Â:ĆX~ó›%:iëÂaQ ŐpgÇŹíKřV$Ď+•ËţË0züĽ13Z3–X„Ăhą×âNÁű§ć’,CÇÜRY˛$RXęVR5���^3D˙˙˙·b[` ‹=ź˙ňâ˝4Vů¤EÉJ`ÉNą‚3ÍóÚbńtŞš246ĺe'WKzśőĎóç˙˙É+îý˙vókoR…ěS4”NZŇ<?˙űRÄé }µ@Ť=d¶!4 §Ůâ’dŐRu�ŁoüH$/D‘&|Č•®˙/8m¬f3/<ĽâÁţnjiĐgy]ĎÜ‘×{A;CŢUý¨íŞ–‡ě_ź˙çů˙ó?żś%"=Ě] $S`BBm(€ˇr hŐ���rKcI˙˙ęĆ82JKśĄş†U#«ľR}?ďÎ3ĎNă-/´ćÚÇS¸#˘Ł’0;s5ĄIĺÎ$jâ?b}Üż˙˙˙ţÍčW•Qhc”¬„şfşF` ’Č˙˙đ˙űRÄč‚ A±  ±Áź6`h!łi ź“»RkYA 4˙˙üľ?IßNo-öI-ůżâvú!ĚČŰĐHĄPA C=ŐSx”^¤=Dmô˙˙˙˙ô­÷Duu%§yčTIÇq` �ކ€�}|˙w˙¦á`0µ`˙ţéŽ[ŹSE¶ĺőĎřßdßó/ţľŤęXŤ v͆†h|„sv—¶Mž�ˇéˇŹÝďăĹeK?÷ßö{˙?6YŁ ^†«TËŠlˇđĺIšËő“QX#BJÄ!™ńËPô;>%-Đ˙űRÄé‚ IŻ ˇ ;IX6a´�Ťč› ˙˙˙±ĚĹp†@َ,Ěß˙ś%čw*LSł3ÁËěÝ209TÄžs)ʦEtşąT΋S©čVf{nĎFű˙˙˙µ:ŽŢB۶'¨bZÔ(–HË €™í“Ě”¨ ‹jľu��[ki�ţî—˙ŽŚŁe aöżůezŕ’<Ző2UÎE3-P˘p˘˛;™Ó1•Tňˇ*­IĘĂv:5ŐŐ¦–Wb˙˙˙˙KF˝mK^áĆŘ#�.•��˙˙ý˙űRÄëJń±ˇ UŃ?µâtŠ»ť<Čć(˛äb+‘=í5‰—١j˙ýŤjB5Śt{·ŤJĽb"ěđŇĹ $/7“Ű­Ę_˙‘fÜëćěËťť‰•›{ľ{9ď|Ú8>0 ë `…e���KkI?‹˛žrŘĂłçmŚ«Üó±)’Ň2ţÎÔĆ<ĄŘĎ•s9‰ş!YÔyĚbM™† **:Ě×mŃU[˙˙˙˙Ý™¨ö»:µdaö•ŹC (ąŠ9»î �?˙Ő˙űRÄőNµ�ŕ™ť¶ ô!&řŁ':¸—8µc…3¶ôéďsAĐ«őŞ'ßűŻ4÷{2ÓKcŐ>Č…X™‹eÄ43B3Ýn…ůź—ĺ˙oJ§ąSś™Śc$‰4šFĚWU®ˇ"M3ĄĂž*j(Cp ˘���rÝ« ˙˙Řd<ͱ¦ŚD8L».˙ Y“€¬=Ť{ą?]T.sž]…u¦m©9ÚťĘÝ *»ć°›ß_üżďçńő^_Ýő:­Vá[4®a÷’» Đ/*B„Ô ˙űRÄę m§  ­Ů”¶ ´!łˇŁËcDś§Ç󑔡Đ@ß*´żâ˛źK˙4#őp”[ˇkUI„G$7)ˇgÖÚ$ÍlÖˡÎł?˙˙űw§g»3vA„©ÂÜŞ@3ˇîF$Ę���rďł@˙˙ٲODd9ÂJ7"5ĎüĄţ×WBMsé[Ae[íCPĐ )ÓËË´ĎrSR6*O3z›1ůÄËŽ^ÇY˙˙óż;źg<„÷!MŐ]Ďfžá¦QCęi$zR˛Ó`˙űRÄë‚ éł  ±Ń“6 ¨!§ŮJá€6µ�˙˙˙˝ĐŠ1Ź~„¦˙ů&GIĐäćZ Î+×·ŇzZ®U$·s…)iw*•·78YäQÖ‘U«ń!žó˙ţţrGÜsn[Ň„çßä‚^eÚiśĚU‚ŻÉĐ.K*��߬E˙úô*ĺ“3Yħ˙ě’"§íöš)śłČŚó¶  ŞAjĄhÜP Ł ę*9`˘ j˙źźç˙ňżý·{łç­2eÎ@ÎÇNh˘˙űRÄë‚ ± Ť>ÉN6ačŠz´†Őł‚‰‘$ ·mcŚ�ttäŐ3ŢŢ^ó?żţw§G/>}Š«źĂ6őÚWMH(€çB1#Ş‹F‰Ć#ÖÎŽŁ˘Ŕ6˙ű?L‚V@���‚ďóH€äľnR…•2»d‹·gŹš÷‹[zľŕšÝ=Ć)ú,Ä˝>el×î‹h›ę©šŻźůëľú˙ţ8žőiżAšIĚÂ�łąĆ F<É$ń p†â„(żr4I˙ţ˙űRÄď -ݎ 7ɶ`ô!§h( L ”ă·t?Ă“ţűčşV¶áČs5B¶U)8Ť&°őH?pÄ  Ú۫ uŇ/5amë>ĺů˙˙˙˙ţß;3¶únôY#šKC˛ţ-RzĄşĚŔ±i˝��ľHŃ$ eąîÖ8fBŢ-vtď˙yŃfś…Dµł… óë=B“ĐszI›ba’Ş\‰T’“]–_??ď˙˙˙·÷;™{~uÚ˛cÓIUP%€ŚC+‡0ç8W-Ś€˙˙Ę-˙űRÄç‚ ]± ˇ Ý ˇă´�ŤŇ{™ Ó¤rěF‡,ČŇĎ'=AĽC—'émĺP™ Ş›ĘĂ[T¶łŰ‡&”ŹcŰňn•5-~Égů_˙‡3úiZĽ­÷ŚbŠŕÖ4ÉĹ g¬tÉH,Ś2†¦!ipM4 ‘ 4ĚŚŔ×—˙äÖË™ÉHČŐ–ˇ‘“YŤZËPÉ•”'#Y&˛Ë!”ą¬ś˛öJGüż˙ö‘¬ż#“,˛Ô5k,ŽFłĎ˛ć_ůË–( NŽFˇ¬‚šŠf\˙űRÄń Ył  Á‡¶!hšúrnŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞLAME3.97ŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞ˙űRÄě‚ ő± @oٶ`h¦řŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞ˙űRÄěË]°Ú €t��4€��ŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������beetbox-beets-01f1faf/test/rsrc/image.ogg�����������������������������������������������������������0000664�0000000�0000000�00000023072�14723254774�0020463�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������OggS���������ţ–ç=����Ý[vorbis����D¬��€»��€»��€»��¸OggS����������ţ–ç=���”ô€˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙2vorbis���Xiph.Org libVorbis I 20050304���+��METADATA_BLOCK_PICTURE=AAAAAwAAAAlpbWFnZS9wbmcAAAALYWxidW0gY292ZXIAAAACAAAAAwAAACAAAAAAAAAAm4lQTkcNChoKAAAADUlIRFIAAAACAAAAAwgCAAAANohJ1gAAAAlwSFlzAAALEwAACxMBAJqcGAAAAAd0SU1FB94CCg4MEEoBFpkAAAAZdEVYdENvbW1lbnQAQ3JlYXRlZCB3aXRoIEdJTVBXgQ4XAAAAFUlEQVQI12P8//8/AwMDEwMDA4ICADkbAwPRhX8nAAAAAElFTkSuQmCCŁ��METADATA_BLOCK_PICTURE=AAAACAAAAAppbWFnZS9qcGVnAAAACnRoZSBhcnRpc3QAAAACAAAAAwAAACAAAAAAAAACdP/Y/+AAEEpGSUYAAQEBAEgASAAA/9sAQwD//////////////////////////////////////////////////////////////////////////////////////9sAQwH//////////////////////////////////////////////////////////////////////////////////////8AAEQgAAwACAwERAAIRAQMRAf/EAB8AAAEFAQEBAQEBAAAAAAAAAAABAgMEBQYHCAkKC//EALUQAAIBAwMCBAMFBQQEAAABfQECAwAEEQUSITFBBhNRYQcicRQygZGhCCNCscEVUtHwJDNicoIJChYXGBkaJSYnKCkqNDU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6g4SFhoeIiYqSk5SVlpeYmZqio6Slpqeoqaqys7S1tre4ubrCw8TFxsfIycrS09TV1tfY2drh4uPk5ebn6Onq8fLz9PX29/j5+v/EAB8BAAMBAQEBAQEBAQEAAAAAAAABAgMEBQYHCAkKC//EALURAAIBAgQEAwQHBQQEAAECdwABAgMRBAUhMQYSQVEHYXETIjKBCBRCkaGxwQkjM1LwFWJy0QoWJDThJfEXGBkaJicoKSo1Njc4OTpDREVGR0hJSlNUVVZXWFlaY2RlZmdoaWpzdHV2d3h5eoKDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uLj5OXm5+jp6vLz9PX29/j5+v/aAAwDAQACEQMRAD8AkoA//9k=vorbisBCV���cT)F™RŇJ‰s”1F™b’J‰Ą„BHťsS©9לk¬ąµ „SP)™RŽRic)™RKI%t:'ťc[IÁÖk‹A¶„ šRL)Ä”RŠBSŚ)Ĺ”RJB%t:ćSŽJ(A¸śs«µ––c‹©t’Jç$dLBH)…’JĄSNBH5–ÖR)sRRjAč „B¶ „ ‚ĐU���Ŕ@˛ �P��ЎŠ„†¬�2�� (Žâ(Ž#9’cI˛ �����ŔpI‘ɱ$KŇ,KÓDQU}Ő6UUöu]×u]×u 4d���@H§™Ą  d Y� ���F(ÂBCV����b(9&´ć|sŽf9h*Ĺćtp"ŐćIn*ććśsÎ9'›sĆ8çśsŠrf1h&´ćśsf)h&´ćśsžÄćAkŞ´ćśsĆ9§qFçśsš´ćAj6Öćśs´¦9j.Ĺćśs"ĺćIm.ŐćśsÎ9çśsÎ9çśsާspN8çśs˘öćZnBçśs>§{sB8çśsÎ9çśsÎ9çśs‚ĐU����A6†q§ HźŁEiȤÝŁĂ$h r ©GŁŁ‘Rę ”TĆI)ť 4d����!„RH!…RH!…R!†bČ)§ś‚ *©¤˘Š2Ę,łĚ2Ë,łĚ2ë°łÎ:ě0ÄC ­´KMµŐXc­ąçśkŇZi­µÖJ)Ą”RJ) Y�€��dAF!…R!¦śrĘ)¨ BCV�€�����<ÉsDGtDGtDGtDGtDÇs<G”DI”DI´LËÔLOUŐ•][ÖeÝömavÝ÷uß÷uă×…aY–eY–eY–eY–eY–eY‚ĐU����€B!…RH!ĄcĚ1ç “PB 4d��� ���ŔQĹq$Gr$É’,I“4Kł<ÍÓ<MôDQMÓTEWtEÝ´EŮ”M×tMŮtUYµ]Y¶mŮÖm_–mß÷}ß÷}ß÷}ß÷}ß÷u Y�H��čHޤHФHŽă8’$ˇ!«�����(ŠŁ8ŽăH’$I–¤IžĺY˘fj¦gzިˇ!«��@�������(šâ)¦â)˘â9˘#J˘eZ˘¦j®(›˛ëş®ëş®ëş®ëş®ëş®ëş®ëş®ëş®ëş®ëş®ëş®ëş.˛ ���БɑI‘I‘ÉBCV�2���p Çɱ,KÓ<ÍÓ<MôDOôLO]ŃBCV�€�������0$ĂR,Gs4I”TKµTMµTKUOUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU5MÓ4M 4d%���ŔbŤÁĺ !%%ĺŢ“ž1&!µ^!‘’Ţ1ž2˘ rŢBă Y�D��Ć ÇsČ9G©“9ç¨t”祎Rg)ĹbÍ(•ŘR¬ŤsŽRG­Ł”b,-v”RŤ©Ć����,„BCV�Q��„1H)¤bŚ9§śCŚ)çs†1ćsŽ9ç tR*çśtNJÄsŽ9§śsR:'•sNJ'ˇ��€��€� ˇĐ@ś�€A’<Oň4Q”4OESt]Q4]×ň<ŐôLSU=ŃTUSUmŮTUY–<Ď4=ÓTUĎ4UŐTUY6UU–EUŐmÓuuŰtUÝ–mŰ÷][vQUmÝT]Ű7U×ö]Ůö}YÖucň<UőLÓu=ÓteŐum[u]]÷LS–MוeÓumŰ•e]weŮ÷5Ót]ÓUeŮt]ŮveW·]Yö}Óu…ß•e_WeYv]÷…[וĺt]ÝWeW7VYö}[×…áÖua™<OU=Ót]Ď4]Wu]_W]×Ö5Ó”eÓumŮT]YveŮ÷]WÖuĎ4eŮt]Ű6]W–]Yö}W–uÝt]_WeYřUWöuYוáÖmá7]×÷UYö…W–uáÖuaąu]>Uő}Sv…áteß×…ßYn]8–Ńu}a•máXeY9~áX–Ý÷•et]_XmŮVY†_řťĺö}ăxu]nÝçĚşď Çď¤űĘÓŐmc™}ÝYf_wŽá:żđă©ŞŻ›®+ §, żíëĆłűľ˛Ś®ëűŞ, ż*۱ëľóüľ°,ŁěúÂj˰ڶ1Üľn,żpËkëĘ1ëľQ¶u|_x Ăótu]yf]ÇöutăG8~Ę��€�€�Ę@ˇ!+€8�Ź$‰˘dY˘(Y–(Š¦čş˘hş®¤i¦©ižiZšgš¦iŞ˛)š®,išiZžfšš§™¦hš®kš¦¬Š¦)˦jʲiš˛ěş˛m»®lۢiʲiš˛lš¦,»˛«Ű®ě꺤Y¦©yžijžgš¦jʲiš®«yžjzžhŞž(ŞŞjŞŞ­ŞŞ,[žgššč©¦'ŠŞjަ­šŞ*˦ŞÚ˛i޶lŞŞm»Şěú˛mëşiŞ˛mަ-›ŞjŰ®ěę˛,Űş/išijžgššç™¦iš˛lšŞ+[ž§šž(ŞŞć‰¦jŞŞ,›¦ŞĘ–癪'ŠŞę‰žkšŞ*˦jÚŞiš¶lŞŞ-›¦*Ë®műľëʲnŞŞl›Şjë¦jʲlËľďʪ)˦ŞÚ˛iŞ˛-۲ď˲¬ű˘iʲiŞ˛mŞŞ.˲młlűşhš˛mަ-›Ş*۲-űş,ŰşďĘ®o«Ş¬ë˛-űşîú®pëş0Ľ˛lűެúş+Űşoë2Űö}DÓ”eS5mŰTUYveŮöeŰö}Ń4m[UU[6MŐ¶eYö}Y¶ma4MŮ6UUÖMŐ´mY–ma¶eáveŮ·e[öuוu_×}ă×eÝ溲í˲­űŞ«ú¶îűÂpë®đ ��p��0ˇ ˛���ŚaŚ1ŤRÎ9ˇQĘ9ç dÎA!•Ě9!”’9ˇ””2ç ”’Rˇ””Z !””Rk��8��Ř )±8@ˇ!+€T��ăX–癢jÚ˛cIž'ŠŞ©Ş¶íH–牢iŞŞm[ž'Ц©Ş®ëëšç‰˘iŞŞëęşhš¦©Ş®ëşş.š˘©ŞŞëş˛®›¦ŞŞ®+»˛ěë¦ŞŞŞëĘ®,űÂŞş®+˲më°ޮëʲl۶oÜş®ëľďű‘­ëş.üÂ1 G�ŕ �@6¬ŽpR4XhČJ� �€0!B!„RJ!Ą”��0ŕ��`B(4dE�'��C)¤”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RH)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ©¤”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)•RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”R �Šp�z0ˇ ˛�H��ŚQJ)Ćś1ćcĐI()bĚ9Ć”’Rĺ„Ri-·Ę9!¤ÔRm™sRZ‹1ć3礤[Í9‡RR‹±ćškVk®5çZZ«5לsÍą´k®9לsË1לsÎ9çsÎ9çśsÎ�ŕ48�€ذ:ÂIŃX`ˇ!+€T��ĄsÎ9čRŚ9ç„"…sÎ9!TŚ9çtB¨sĚ9!„9ç„B!s:č „B„Bˇ”ÎA!„J(!„B!„:!„B!„B!„RJ!„B ˇ”P��`�@€ «#śŤ˛���€– R΄AŽAŹ AĘQ3 BL9Ń™bNj3S9ťtjAŮ^2 ��€ � Ŕ (řB1��AĚ …U°Ŕ  ćŔD„D� H»¸€.\ĐĹ]BB‚X@ 88á†'Ţđ„ś STę ����� �ŕ�ŕ �""š«°¸ŔČĐŘŕčđ������ř��8>€ć*,.02468:<��������€€€�����@���€€OggS��@–������ţ–ç=���ý‚ĺ'02310276;:?CCBl…‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹¬Ř%ô� B8FkF#ëśzĘýýžĺJI`ź76�BU5Ŕ: š†îK.Ţ[/IŔ´�€Ž€ ~ĘýýžĺJĽçŰJŮ�@!TŐ€ €z wP{@Ű‚š_,ß5đ�č@ �4�>Şý˙üĺ žíkĄ°�@!TUč�40ŃMtô™ŕÉĆ+ÚŮÁ…WÇŃ�ŕĐ>Şý˙üšäâvy¨��…PUˇô…|G×OӱЂ††ŤĘpB�Ŕ&€>Şý˙üšäîô4Ş��…PU'�Đ@$¸O}Ç%ĺMŇ;†:€ �žä>Şý˙üšäŇ´đ&”��ŞĘ* @tđ¨�´ŻůçaďĄ)t (�4 �šý˙Ore1éM0�BU � 1>ŢY(ĄÚsYŔ&-RJgčˇŐMkňŘ�Ŕ‚Všý˙OrĄ2ŘĺIŘ��UUĹ@EO5š‚nE~IôĆÓ´JĘqŘ)d˘U)°�$ @šý˙OrĄ28ĺMč��ŞĘj� ĚY¬"ćeç…d×Ôdţ‘ňcw˘ ±“¦‘�€d �:šý˙OrĄ`°Ű›¸�TU:�Ě]ą¤ż\Ďôł\ĎŇ{QF<Q©š5¬ĐŔ`MX�Ţyýök”‹2íMQ#�€B¨¨˘Đ@§ŰŢçµNUďáM őNůf 3 óéęĐÝlC–ž >H4¬T05�Ţyýök”‹2¸ŰŰXaE��…P + &EŇć^ ô#©”¶&Ú!ir}Ď””Ωaku¨“)lY ‚& H�Ţyýök”‹2íMP�*jT�¦Ţ1{Ă1^_Y•˘~vçt×Zź}ą.󭡣Ş9“5\1á)č�ŕÁ_#�Ţyý˙ţĺR Ny“�B1ę,�r(¶Řňł4ptmp sR›¦ŁL/g9‚čŢ?®Í0—M)�°”�,Z�ýÎuą‘Ţ8� 4´X‰ h©u Ŕ÷ßŇ‹ö~>SŁČü†ý$=cr¦Čß7˛üĽöŽĄĎĂŚóů0Ę«®bĄ*Żß·ţQť˙Šá¨Y4®#öO¤Ĺ± Š«=ˇ P{údX]ÎÔýÎ59 ŁĽ �„f)6đÖ”łA`fś“€˝^ťÄ!<©¬i0/dĄ·?/Ôhł=é«m¤ň¶Ţ­™žŹŤwŰŐZ:Ý–˙‰^®¦×Ö‘&uVwű’CĹ_Ĺjđ-mŚ����������������������������ýÎu9 %˝ ��Đ"++IŔ@âĘAGŐŤrý5]ó�o”X€Ż/săÎŹ\ú çHÇ‘ŮqoĹóČĐ3ú.+Ľđ¬qÖ\Eť¬SF’ż1R-ŕ"ç ÜI¦ĎlÂjÂÉĚěć���������������������������������ýÎwŮ4-=� ĤĽĚdŔ˘ĐI�ŕ,1ć8‘qü]âj­_č÷⥧Թéô''ďsçőu|‰ěwűćąĹÇ덳Î勝/óˇ<:Ť0ŃŰĺ$ˇÍ˝'—3Śí®Źý ��������������������������������ýÎu9q­< B�ˇˇËÂÉgPŕ€~57kg§Pżd´]ťŮŰüąśÎoż’_ÚąJs)ęă5÷Ňä¤Ĺp–ô•őÚc\^;ÇĄQĺtFôb[D®áě §­“������������������������������������ýÎ5ąVŢ�h’ʦˇ�°@vvż´ÄNľd ¦˛˛Ki*Żu67[`&· 'nV‡§Ż‹“ËČcşt“źq’C>-VţFźÄĽVŮ ]ęďtGNĘy49ŞéaśŮMă����������������������������������������ýÎ5ąäŤ�ˇXd‰‘��\%`8Ź'\ŕÁÁű5ôŹ7‰M?/'x‘,ׯ×}YżŽ˝<S«oÍYI¤Ű0î­< V_R†]ŠíÍžJ];揅śŢí·BłHűń·•8NŔŮ˝Dä�����������������������������������ýÎ5ą8‘Ţ�B“„eŔ_™h$�áž''Đ„Y8˘ŘEŃ´Ç˝^‹ďç«Ü.pNë°Ä-’CaĂ+83|©űŐźŤ-Éśť¶ĎUxY¶]FVS”ijúP[éFÂR/P:Ű»r��������������������������������������ýÎ7ąVľ�BłE R€�Ŕz+ó´¶źëž•ĘĘőŠa߉xR=¬±7ćL‰Ű &‡Ü‡nʬőúÖVŢk\ńÖďá šŰŻŕ´şBĄ:ť1Í“‘±‚¦bDţö1Í ‘Ş®���������������������������������ýÎuąž8� ”Ôe…1ŔŇ,�0™ŽŽG­;H`5ŰrŹbR;VsĎÜlËŚjąPĎFlôn3¦°íúuPéˇ)`,V*ýxuC=ľ™rN:Ś…ý¬=Oä}w�ôöúEad•”cb�������������������������������ýÎ~7Ů8Yľ„�ÄËJ84ÜcPČąëlbĐźĄBâŔ(ŰŘ,·âŠĺ$ł¦ÂĐ›. üdĂĆy/”cův€~ý—:lϬ™Ńk8Ĺuoć”HȆ¨$Ĺ ö˙óŞÁžą˝Ď�����������������������������ýÎuąáiH��Z„®0rea�X¸¶(cp˛KÓëćtŹ#)s:.ĆhëřL¬^ł)Z Km“IJyś’c#Qďln2t6]ň~’]¦NĘ „*đ<´o1X¨ŔźÇŤGSz��������������������������������������ýÎuąŢ��B“¬<•@ ľÁq] ŘÔÜ‹ć$"93Ë’zlć3Ĺ—ŕ”S©Űď“á˙ąz)ĺLżĄě. ›&ukbgÄ÷Ę_:`…HňcĽď0D64úb‘˙ďŕ 9ű!r�������������������������������ýÎuą(‘ž„*�B“˘Ś«Š:�>nmcfFU)¸¸đ“‡0ŮëŢľŐŃřÚťť·•ęP[©+T˛űëĽý2š\ÍüÓSś ÂG.WąčX©;·ĺUFŔhkôo†FŘ/÷ŕÂu cĘ:����������������������������ýÎ7Ů–Ţ8�€š• ›0Řd7•\´×vÎ}ií˝E«MÝ M3‹Š"äđşĎ~�µ˝bł¶#Ah©'ňR‚ŃÖ&y,]~ŹMG“-9¶#8…ţůäëĽ#s/y����������������������������������ýÎ7Ů8Qľ1�•Ö%8�€iup;š¤_I5…ŔÖ˙?»ŘÄ&ňҵäduˇn±{˝ď…é!‰ź»:•¬Ď…úzĹU.25žBÉ–+Ęčq0ďîQ®*/CUë&^ܱé-��������������������������������ýÎ59q#} �„&ĺ68ZŔ€ôĐ»t3ü´WÜ™ohm™äP»-¨Y?Ç{ž°ęŽLXř8&MöŘ f%ŞO©›÷NÉ”‰ÍÍŻ®b˘ě˝›ěÎČPwˇëWI;–���������������������������������ýÎwŮ(–ž„*�hRdÁPL�ŹKşá?7śŹI—»BťŹÁ¦Ń|_O&±arúäşvQŕ$ąk×°d­Áĺ˛l­Lc/["°R#lcú,čVŔÝťŠ…«äŕHďbz¬Ű{8T�������������������������������������ýÎ7Ů8QŢ1�ÉJQ3=H�Đi—Ť ‚©ćĘě•ä¸óM6żÝRmöµ|fę «ö’ô<xQ[‚ Ł§Ý˝űjTż03Ѹů-ťao8*Ę.B¶ŘrI…Z™Řyůۙ�����������������������������ýÎ~5ąQľ1�ˇu‰8{,�U¦»k,ż,¦żW}IŤŠZGv›Ü ý±gěÉVöCA’ô—”4#ŤâwLqu;Ž‚óÄ~BH Iţ҉fDaďŚne›������������������������������������ýÎwŮ8–žHb -ŞˇŔě�@cŰweł¬fô“'ŹI»#ł:«/Ősâ§ LNao—Ĺ’üłęÎhrŻ‹K5«ŚámŃ {Ř–ÔnČ[oČcőQ˝‚€ď šî*§ťÚč˘ŢŢ���������������������������������ýÎ7ŮâŤ�ˇ˘`đ&@ ď~Ë˝r8zôľóG­řHä·5ż^yÚxńé\™JfŇŹ}ŔmťŁvŐľËôLa!9'ěbŰ>ŃZ*Ěü(NbÎŃŮ&ç˛����������������������������������������ýÎuą–ž�@-jIÁ�…&&č�h‹o\ś‰[˘�ĆüJx2ÝĎÚ ^Cí7^:«YGcF¦ňO}žÝşž‡žŹ™‰°±<É~ľ¬2Pâü"SÔމíőŞŔö™!Gć&ŰóS’‰ťÎI…¨��������������������������ýÎuą8QŢ(�BĚR¬H¦Đ�‡YB,’˛-oĹx3CT§XČčňßŃ| éČÓůĆű<Z>Ńł^^Ş„^‰)5=‚%G˝pďb€{fy](€>(Ö÷:;ubjŮ:kGG:�����������������������������OggS�D¬������ţ–ç=���‡ż˛¬‹‹‹‹‹‹ýÎ59 %˝ ‚��”. :�>Źţč]µ¤?÷TăĚxL€çsÔžM›PĎXšÁ„—+q!^#§ĂaâčŃ|S«ă.Ź„™ăµ•«Q®%(Îsnĺ¶©lő]”N-{~����������������������������������������ýÎwŮ8™ž0�B“b…qÄń–�X9vń)l}¬ĹĽŹb‘Id=!ű…t,W˘7Ĺć˙9±¸őń­Ű˙â® Rńçf©ÄŔoé<ďËÓ—·§ľUÚe±”׫üpő0Úé[(wÉÎ"…��������������������������ýÎuą8–Ţ„2�h–˘"hrđČ)=|éP›Ű"ýr:Iň"qŤö°Ě蹨šŻŕŽľRß"ŕ€"«•uŢ˝‰ ţąÚ‡żš.řAsń©Ďnµďn7…Ł'ňčeŽ~ ���������������������������������ýÎuąŢ0�B Í c@2 �°8čä>h»ď_6zyu—YĆü|$Ô•»ąŽëö¬ĘŔĹ…÷ńSěŮ n'E˛0!¸¶˛ö>~v3üayč™zź›9óMÔk ¦1×5„éW7ń#ŢežČi����������������������������ýÎwŮ8‘ž1�h–bŢFh‚�¦zmśňp4<Żĺ˛é*é´ ´ÚäÂśÉ"N¨„Şń|oâSđjl’ĚôřśxŽMqIß–Š'S´Î—WřKw®˛:&U0TĚn;Ť‘bbž ĆPČe�������������������������������������ýÎݞ 8��P‹<Üŕ0P8ťÓą­j� �x�z}]Ü�����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������beetbox-beets-01f1faf/test/rsrc/image.wma�����������������������������������������������������������0000664�0000000�0000000�00000057761�14723254774�0020507�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������0&˛uŽfϦŮ�Ş�bÎlł���������ˇÜ«ŚG©ĎŽä�Ŕ Seh�����������������������ÄQ�������€>Őޱť�������Đt����ĐĘ›����� ���������€ ��€ ���ô�µż_.©ĎŽă�Ŕ SeP������ŇÓ«ş©ĎŽć�Ŕ Se�"��ęËřĹŻ[wH„gŞŚDúLĘ���������”#D”ŃIˇANEpT�������������Ě���W�M�/�P�i�c�t�u�r�e���›���i�m�a�g�e�/�p�n�g���a�l�b�u�m� �c�o�v�e�r���‰PNG  ��� IHDR���������6IÖ��� pHYs�� �� �šś���tIMEŢ  J™���tEXtComment�Created with GIMPW���IDAT×cü˙˙?‚�9Ń…'����IEND®B`‚3&˛uŽfϦŮ�Ş�bÎl,����������������������@¤ĐŇăŇ—đ� É^¨PŰ��������W�M�/�P�i�c�t�u�r�e����Ąt��i�m�a�g�e�/�j�p�e�g���t�h�e� �a�r�t�i�s�t���˙Ř˙ŕ�JFIF��H�H��˙Ű�C�˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙Ű�C˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙Ŕ����˙Ä����������� ˙Ä�µ���}�!1AQa"q2‘ˇ#B±ÁRŃđ$3br‚ %&'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz„…†‡‰Š’“”•–—™š˘Ł¤Ą¦§¨©Ş˛ł´µ¶·¸ąşÂĂÄĹĆÇČÉĘŇÓÔŐÖ×ŘŮÚáâăäĺćçčéęńňóôőö÷řůú˙Ä�������� ˙Ä�µ��w�!1AQaq"2B‘ˇ±Á #3RđbrŃ $4á%ń&'()*56789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz‚„…†‡‰Š’“”•–—™š˘Ł¤Ą¦§¨©Ş˛ł´µ¶·¸ąşÂĂÄĹĆÇČÉĘŇÓÔŐÖ×ŘŮÚâăäĺćçčéęňóôőö÷řůú˙Ú� ��?�’€?˙ّܷ·©ĎŽć�Ŕ Ser�������@žiřM[Ϩý�€_\D+PÍĂżŹaĎ‹˛�Ş�´â �������������������a�D¬��€>��ç� ����������çç��@Rц1Đ٤� ÉHöd�������ARц1Đ٤� ÉHö�����W�i�n�d�o�w�s� �M�e�d�i�a� �A�u�d�i�o� �V�8������ah�e� �l�a�b�e�l����D�E�S�C�R�I�P�T�I�O�N������t�h�e� �c�o�m�m�e�n�t�s����D�I�S�C�T�O�T�A�L������5����C�O�M�P�I�L�A�T�I�O�N������1���(�M�U�S�I�C�B�R�A�I�N�Z�_�T�R�A�C�K�I�D�����J�8�b�8�8�2�5�7�5�-�0�8�a�5�-�4�4�5�2�-�a�7�a�7�-�c�b�b�8�a�1�5�3�1�f�9�e����T�o�t�a�l�T�r�a�c�k�s������3����W�M�/�G�e�n�r�e������t�h�e� �g�e�n�r�e���(�W�M�/�E�n�c�o�d�i�n�g�S�e�t�t�i�n�g�s������L�a�v�f�5�4�.�2�9�.�1�0�4���$�W�M�/�B�e�a�t�s�P�e�r�M�i�n�u�t�e������6���‘Ü··©ĎŽć�Ŕ Ser�������@žiřM[Ϩý�€_\D+PÍĂżŹaĎ‹˛�Ş�´â �������������������a�D¬��€>��ç� ����������çç��@Rц1Đ٤� ÉHöd�������ARц1Đ٤� ÉHö�����W�i�n�d�o�w�s� �M�e�d�i�a� �A�u�d�i�o� �V�8������ah�e� �l�a�b�e�l����D�E�S�C�R�I�P�T�I�O�N������t�h�e� �c�o�m�m�e�n�t�s����D�I�S�C�T�O�T�A�L������5����C�O�M�P�I�L�A�T�I�O�N������1���(�M�U�S�I�C�B�R�A�I�N�Z�_�T�R�A�C�K�I�D�����J�8�b�8�8�2�5�7�5�-�0�8�a�5�-�4�4�5�2�-�a�7�a�7�-�c�b�b�8�a�1�5�3�1�f�9�e����T�o�t�a�l�T�r�a�c�k�s������3����W�M�/�G�e�n�r�e������t�h�e� �g�e�n�r�e���(�W�M�/�E�n�c�o�d�i�n�g�S�e�t�t�i�n�g�s������L�a�v�f�5�4�.�2�9�.�1�0�4���‘Ü··©ĎŽć�Ŕ Ser�������@žiřM[Ϩý�€_\D+PÍĂżŹaĎ‹˛�Ş�´â �������������������a�D¬��€>��ç� ����������çç��@Rц1Đ٤� ÉHöd�������ARц1Đ٤� ÉHö�����W�i�n�d�o�w�s� �M�e�d�i�a� �A�u�d�i�o� �V�8������a���t�h�e� �c�o�m�m�e�n�t�s����D�I�S�C�T�O�T�A�L������5����C�O�M�P�I�L�A�T�I�O�N������1���(�M�U�S�I�C�B�R�A�I�N�Z�_�T�R�A�C�K�I�D�����J�8�b�8�8�2�5�7�5�-�0�8�a�5�-�4�4�5�2�-�a�7�a�7�-�c�b�b�8�a�1�5�3�1�f�9�e����T�o�t�a�l�T�r�a�c�k�s������3����W�M�/�G�e�n�r�e������t�h�e� �g�e�n�r�e���(�W�M�/�E�n�c�o�d�i�n�g�S�e�t�t�i�n�g�s������L�a�v�f�5�4�.�2�9�.�1�0�4���‘Ü··©ĎŽć�Ŕ Ser�������@žiřM[Ϩý�€_\D+PÍĂżŹaĎ‹˛�Ş�´â �������������������a�D¬��€>��ç� ����������çç��@Rц1Đ٤� ÉHöd�������ARц1Đ٤� ÉHö�����W�i�n�d�o�w�s� �M�e�d�i�a� �A�u�d�i�o� �V�8������a���t�h�e� �c�o�m�m�e�n�t�s����D�I�S�C�T�O�T�A�L������5����C�O�M�P�I�L�A�T�I�O�N������1���(�M�U�S�I�C�B�R�A�I�N�Z�_�T�R�A�C�K�I�D�����J�8�b�8�8�2�5�7�5�-�0�8�a�5�-�4�4�5�2�-�a�7�a�7�-�c�b�b�8�a�1�5�3�1�f�9�e����W�M�/�G�e�n�r�e������t�h�e� �g�e�n�r�e���(�W�M�/�E�n�c�o�d�i�n�g�S�e�t�t�i�n�g�s������L�a�v�f�5�4�.�2�9�.�1�0�4���‘Ü··©ĎŽć�Ŕ Ser�������@žiřM[Ϩý�€_\D+PÍĂżŹaĎ‹˛�Ş�´â �������������������a�D¬��€>��ç� ����������çç��@Rц1Đ٤� ÉHöd�������ARц1Đ٤� ÉHö�����W�i�n�d�o�w�s� �M�e�d�i�a� �A�u�d�i�o� �V�8������a5����C�O�M�P�I�L�A�T�I�O�N������1���(�M�U�S�I�C�B�R�A�I�N�Z�_�T�R�A�C�K�I�D�����J�8�b�8�8�2�5�7�5�-�0�8�a�5�-�4�4�5�2�-�a�7�a�7�-�c�b�b�8�a�1�5�3�1�f�9�e����W�M�/�G�e�n�r�e������t�h�e� �g�e�n�r�e���(�W�M�/�E�n�c�o�d�i�n�g�S�e�t�t�i�n�g�s������L�a�v�f�5�4�.�2�9�.�1�0�4���‘Ü··©ĎŽć�Ŕ Ser�������@žiřM[Ϩý�€_\D+PÍĂżŹaĎ‹˛�Ş�´â �������������������a�D¬��€>��ç� ����������çç��@Rц1Đ٤� ÉHöd�������ARц1Đ٤� ÉHö�����W�i�n�d�o�w�s� �M�e�d�i�a� �A�u�d�i�o� �V�8������a�1���(�M�U�S�I�C�B�R�A�I�N�Z�_�T�R�A�C�K�I�D�����J�8�b�8�8�2�5�7�5�-�0�8�a�5�-�4�4�5�2�-�a�7�a�7�-�c�b�b�8�a�1�5�3�1�f�9�e����W�M�/�G�e�n�r�e������t�h�e� �g�e�n�r�e���(�W�M�/�E�n�c�o�d�i�n�g�S�e�t�t�i�n�g�s������L�a�v�f�5�4�.�2�9�.�1�0�4���‘Ü··©ĎŽć�Ŕ Ser�������@žiřM[Ϩý�€_\D+PÍĂżŹaĎ‹˛�Ş�´â �������������������a�D¬��€>��ç� ����������çç��@Rц1Đ٤� ÉHöd�������ARц1Đ٤� ÉHö�����W�i�n�d�o�w�s� �M�e�d�i�a� �A�u�d�i�o� �V�8������aT�R�A�C�K�I�D�����J�8�b�8�8�2�5�7�5�-�0�8�a�5�-�4�4�5�2�-�a�7�a�7�-�c�b�b�8�a�1�5�3�1�f�9�e����W�M�/�G�e�n�r�e������t�h�e� �g�e�n�r�e���(�W�M�/�E�n�c�o�d�i�n�g�S�e�t�t�i�n�g�s������L�a�v�f�5�4�.�2�9�.�1�0�4���‘Ü··©ĎŽć�Ŕ Ser�������@žiřM[Ϩý�€_\D+PÍĂżŹaĎ‹˛�Ş�´â �������������������a�D¬��€>��ç� ����������çç��@Rц1Đ٤� ÉHöd�������ARц1Đ٤� ÉHö�����W�i�n�d�o�w�s� �M�e�d�i�a� �A�u�d�i�o� �V�8������a����W�i�n�d�o�w�s� �M�e�d�i�a� �A�u�d�i�o� �V�8������a6&˛uŽfϦŮ�Ş�bÎl2K�����������������������������‚�� ]“����‹�„����ç�� ��ç“˙@�� ÓĂĎ6Ó‚}mŕ ;€¦Jŕ$´ć“$ťÜ$›ě ?�'qU1Ů1%¦4©€` -I$Ŕ�IPťc Ô I¨R„UI10U5R’I‰¸IjR` Ů$Ä@«JTBTÓI�Ą1,E4” ¦�”Ň’ŇŇ€I:˘„bfĄ1&C�›°�` łQR@H@1Vš`𠆀H “�:ÓP@¨ţĄKĚI^o'»@*@�7C˛@ •ÂL Ŕ$¨Ä %©--¦ši!T€H””żHEPůjH@&B4ŠJJÁóä"„0Ň•…ý Sƶ°|µALU2”’k?ßí/¨/ßżĄ,B(âýqôŠ1BŇĐ~¶¶š-ÜKlřż!+t„H˘„„ĺ8Ť H¨‰ˇh-Űߥ)B Űô-P_”PVéCäQný"—év0{eýIBŇŇŞ¸V‚i~·ĆüPč6ňµA \@ŃĹA KTżÚŰëpăĄk‰ż, ńřRѤ ­żvŮFPµB(âó^®oă§ŹÍů»yZŔUÁ”P„ă(Ŕ\KYFP…»}ż÷o¤ÓM–Ę?_Ą Rýmřâ·> ·× p?,ŁŠÝúđ´ŕ*ŕ}Ćý–­ËKx)~üţ°óyKďŇoéăýұ~ěůĽ§(đďΚĆó\yďű§ó¬sXß•?´­-˙µ´[–¨}BŢSJŇ8řlj9ďůĺ>Á·÷úýqş_ͧ‹őEľ±ř˙'<!\üë‡÷ŕ|dHZýeNÝű±nýq§ń‹ucgµce ŇŐŚ˙Â9ďHý×R˙ŽśöʸÁ6óTŕŞÜúXř%[ü­ĎéĘ0p~ýqŰíŮě\C¸‡ăEżÍń§÷X˙˛iŁV7čNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN����ç��J ��ç–˙@���üYż�pánI›+çâ«$@ ,ťDĂaa $�N3Śë�2�B’�R¨ddLˇ!# MÉŞ$±jmD BED U5 $ B{AI„›  „™e‰0[ˇ/Ę ‚ ’ţPR‰Â ÂP±jI� ) !%'e .@„‚*Ä„şÁ(0T€–€h' H¬„Čl’P�ş5&CHA$Ť];ĺş +Č2g`H‚@*$j‚¦˘�™ ¤ΔI ŁJi.¤„†‚„_aĄ4Ĺ(U•PŔˇ%$E™@/а@)„R€ú•ިRţ„¤ˇn)·%!ř4PŠiH”?¤˘·@(|¶V’_”(Đ•‚Őßo $ÓI¤!ľXyčŠhˇ…şYHJךĘCĺ‚]¸|ůóđ¶ůcĹů!iňe]! JC ńq[ĐíÜx ‚ú‡ĎŹ›}Äž.?ÓńGšˇn€•Ľˇő˝mö?ŻÍ÷ĺnă[ăü“E’×çůq'ʉinÜűÓH¦•·ř+Zˇi(ă·ˇkóü˙I·ţ©Zü¨Zó_˘š_¬8–éü¨ýżvčÁ]uŚŚĄmn•ŻŰĺşŕ޸ĄÄTR´íÖ­é·Eü–‘”í‚Dâ·ţÍżhýĺ+BÝXőŽ·E8[źäKĆů)üß~FŠhČ“ňtżä“ć˙4ŹÝąÄJOź­V2ŐcˇŇô`$[ë=żYě·ć«Ţ˙P‡˙›ĄßţoíŹĎ–ŚöüíĂňČŘ nßűđ�,«ľ/çř† ź‘\2˙Ç?:ŕĘOçűý-¦śöókoň—ő ţ,JÓďĐ~µ\?›ěˇkŹ[ť˝cq[ňśÇĹGpńŕ“ô˙ô·gçHˇj±–čˇŰçżóÔţ–‹ĄÖĘŘŔUŚNNNNNNNNNNNNNNNNNNNNNNNNN����ç��x ��ç–˙@��o�ćt,Ď�,el˝ŤP0uÉÍŠXq3±2Ľ1 j , ™Đ Č@ ‰™i^Ŕj†PhAˇ%€H!°@�Hé‚D0QT–—Z P %jD €d°‘$$ RQ%BĹ‚uĄ(Ii’S2˘ZA0%†II$))2 (HW”ĹBAH1YĆH ĹD&RJĎ@†¨N@b„ ćć±%S-P é­“ÎK;Ť˛DH-;cJ†4CBćČo I É«,`-S,Y%(��ĆĄ)¨)BVÉ4%2 `Va(”ÔŠ¦—١YÔ Rý1@AL�‚)[9”Ąý.ÚŃV„n(~ü¬KäľZĄ)ý‡ô¶‚„>~ú—č·ŃÄ·ŮH˘š_%úiÝM.Ę]źŮK°´V˛ž+sô~_§`;t㎟ߛü°c[źOšă[t-·AĄ ·?üż|T>ˇűúBŇÓęr•Ąˇ”­ńńń-żý¦ŢýŮ·[–Çý>ót~ż'A·Đ„%lÓE(?ş0çÔń­şi )·ŰéĄňßîŢ·C÷ÉnŔ@­ĺ4qşR#)O˝k(ZvĎż:< żĘhŔT"±ź-­­yĽĄ6úr‘Ĺ@[Á*ŤXČů­~ë«3Xöü§=˙tţ¸ż\nÇÇ‚WÜi⦊Ć=©(YťXůşÇ[[¨Ś˙)ýůş?+{ !öt˝/żKhó\vőłÇO„Q•+yĘ-ŹŔUŹ‚[ucĺYň[Đ0ňřqŕ?5€˙tŕ”ĄÄ\úŔN‚‡Á<A? í­üI4?viZ/¨ăEp>§`0ţ¸2š2•Ą†{Qŕx6š>ŔT—ô%ů¤ş[ţđü_y»{7öáć폷ńe)Ę_y â × şŘ즚ŕâşĆ|NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN����ç��§ ��ç–˙@���đ%,V,XU2Ľť˘ d†´µ°DôZb`°D€�« ’ZŇÄ’XŞpÍR/¸I… &`c †˛X¬ŔH1…$�Ś)1´ĚJI,ˇ•�2"MK˘ A2˘B`’*!&©JADŐ€ë$Ši–„¤‰ E&®‘(hŘ+D¤„Ą!¤Á$‰``:–h�&FÄꤸl¶�¶ ¬ÜôF”aŘ…E„–ą‚Cv[0¤á@™Ť00Š&¬“‡)EděEBť�MCQ 3P ľ4R EB( ±JKôżBPPB0č@„„[‘M/ĹĄ‰ Ű¨J€°¤¬Rüˇ`ËTţęOT©QcMĽ°–ż-ZZĄhŇý nĘ-ôń:\ka<i| 4śRš_ŹÝ<_—íb<kOÖ–ź§ňZŁó)â 6V’|ŃŁňˇő˝?®%˛‚ŹŕŇC˛ůňŇÜ­2Ş-ŇFQ YJÓ˙Đ·Űßq&„—Ü_Ş˙ŠźÖPVŹäţÝ”y¤ĐúÝćß:˛M¸'÷€Çî”ůĄŞE>úüÖÓůSÇĹćź~ż×OóÔ~Íp; yHZĘ ˙̡q!ŇƸ8风ßůÓG®/É ĄŻÉn‡ď˙kHóHŔ_µĽýőpşZ…§ÂÝ”şSÄźĎóăüߥm÷xÂiĘ_;t8µn®§ňO8‹ęĆ}”ń~¸Đit˛ŢSźż[¬`˙`޸pD†¸„sĺBŢP_× P”Ö=Ž·BŐc'(ăŹŐcy®'ŐÁ€˙očˇ˙p›cóĺÁ^vkLŁţ_“÷KWQĆ·‚WŘ ­Űź×�ÁV}F §=đ_×ŢSBŐ4ľđ‚,Ą(Ŕ~oóÁ*ÓúǬlů-ÜvÇľqNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN���������������������������������������������������������������������������������������������������������������������������������������������������‚�� ]“ą���Ś�„����ç��Ő ��ç–˙@���“ąĆ_­mšV˙ b†­°&�6˘ H7#@&RI($L™&ú»’@(ÓŁ" h&gQ‡0c "`éě “†2QyJB%,Dš€&`–ŠČ¦S„ 2Ě@0’i jH”Ă*J 1 (‚i¤”  $˘©Ę ÂJa0BJI%Ą­$X €H–„ëD§ X©łB «-#@Á!¨JoV ]G@ĐcĽ«-TËNÉéŞÁł ±Ą†®ŞI1Q‘ąBJ… LÁ’…€Ŕ$„îši¦Ą@“JÄ- �.ËôŠQB@~„HDBŐ%4ST(@JIJء…Ąˇň�XżDS+eóä!/Č~•şBĂŠ”#ö´r¦„QNâ}Jj—ÁŰż·­ŰźŇ‡Ď‘@vxŠ-Łö4>~mĹŰ-SÄům%J-ëU_qţü)4&»)ZVô‡n´·H~·ůŽ?ŇVŠÚ?'ă(O@}ú®Č»gĎŇx·ůŰ­ô¬Vhúâ yíúâÍţ5żÉˇa€é·ţNÚś!mú+źÝ(óKĎ’HĘCëzVČB?YHâC˙4űŹôűЏO›ĄmbŹË•˝Ňďíß´Ž5¤ľOéÄ>SůŰ˙*P¶ĎÉőްĎ|€©ókvî%ľ/αčˇ8Í­Sű§\täKůţ‹ĄóÝëq|·B7K­WOçĹÄyľ#ű}ůůş_Ł=Ę“üĺ˙›ZĄo(·Óo¬zśCś÷·ţřřÖ¸ü#ž˙ľ$ŰÖ–đéóúÓOšĘyĽrŠĆ[Ęx‡šűĘi·×QÇű§Í[¸˙!űüż^t¶{~ř––‡šĎl‚Kupe<o­Ř)JÚŃů¸‰Mc˙>µů­QNP·ů¦ŞŢ%ů˘źÎ¸ Ą€NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN����ç�� ��ç–˙@���üđ-TŔ×ß˝GĂ©=ő�¶4C $š DΠ&I:MOü�`áÄ´€6 ‚FČuaĚTAc áA@a@-2E@i0–,P I$¤ÔŔh“:I‰-/ŤB(, áá´¤ĄcRL Љ(H©JMDÉi@š‚�ŃIQ¬6  Š†KL’™‰‚PL„ÂJRH4Ë@YP 5€" $�†ŔÄ iJH`�$°fb`liĚC`)ŰĽŔçżťŽ¶ënć6€¬Ë ¤†5@Lµ hĂÉ$‚€Ä¦ŠĄ`+:H„¬QE dÁŠQA¦…€E-|ů4�ýúh B‡ô‹ú(BA@Ú)<tĄ/–ťąi~ě?Gé@vV蜎°˘—Đ’‡lú«÷Ďß&„sú mĆŐ2µBŇVßĐ·EĽ¦‚•˛š-ÓúĄ Iâ/‡íoň||Őą[ŃG1ůM¸RŠRVĂęÓo~ţßME4”%oŠ•ľ0i|´”RµÄ¶-Ô-‡Č·#ţ˙IFă<HvP¶üŃĆVß'ÓTQ€ź­…ľ'ČŁ‰6úüQűˇ(ŔK_·Öţ+ć(~š+…ő˝tľ˘Ś~–Ăő†Péd?|?/?*nŰň®AZ¬e¤ůżŇ:ŰËî.$śö§Üg˛xť›vS”RţŢ·GZM6î?× ‹gŹÍÓXĹőcľˇ˙ĺ”ńńĺtř‘ć––«8%ü˛śoBqeżÎÜ˙× |źÍŇůJÝż‹Ť4-QĹ\+UĂCä~UŔ°üŃ€łŰóŔtŰë5Ŕź4˙öÝ-HăX`“ůĽˇóţ:Ć[ýżđŽ~ţŚ˙ňýÖ>{ˇkÍW 8–ň‹cÜCeµżÝEx o)Á1”»/¸«ő€©t˙ź%câG€€¸żóv˙5ćżo©KęÇ(Ę^NNNNNNNNNNNNNNNNNNNNNNNNNNNN����ç��2 ��ç–˙@���b—•aEDŘ%­iBLA2�d 5Ôf¬�vî‰ ”Ő Kd™ @ :„€&*’’!¤!bÔ"f@‘.%©˘¬‰¨ŔZ JR a,’CP†! %3U RDI%×Q�‚ŞÂ$HA@“0™(X�VPÁ 0¦¬¦¬,Ś"€*‚°‰@0RK¨ОІÔL†„TH‰U% ± (Ř ‚ĂľLŠ˘ĂUkI ͬnTbe‚.ÔČÂTs!¦éDư€ ¶Z 1`‚  Ja$¬°Ů k%Ôś±~ý�™5|¤Ąm4;jΗéM)$iH ”>R"˘Â“Y ŇE @[¦€Ä)( %ý55–ÖĘ2J¨·Qý 4!(?·Đ‡ô¦ŢŃ&*>âvÜkOź,)BR’µBi ˇi`·JRůú©OŢčEDŇ(đŠ?K||PýúpńXüD`>'ůN}oăý?Oäč+T-¤ź ·&Ţűe?Ľ˘ßúă‡ĹG¸ńRřľâ§óţ OpřUSoĘ-ő(+\h}O〟ż¬rrŽ7AFP¶˙">„e]ľ[âü‘á,¦ŹÉĄinś§)â¤V7[ź~¸éE[ř©âGQo|CĄřť’VÇża÷Q€żiGëöâ˙ä]>Uľ%·ëeoŔ@T�ë…o)[[|íčýĺ'Â%·>óVÇţÎ _`’¸JŰţ+{®ÁĎtńţíÔ„‹š@óx6’˙ňZ·ĄmOďÍ­%kÂ.źźŕ‘k‰ńZJ|×ć+öSoý­ľŁ÷ćśDqö·ćň‹c­Ç÷”ĺ Ăźy˛·űĘ2šŕŰć©®˙’ŁÂ+VúŕĘ<HJŰ˙ĎőnŁÍŰ«óvVđmáN—[NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN����ç��a ��ç–˙@���µđ" \jŽŽ)™ťFćËZX đZ`Âť±X ËÜ.&©‚j·bÝH–ť€"6˘ˇBHH @J D2 T A$ ŃH$ÓHŞ`ś"NČÚLaU5v "¨’^ŞĂP ”’Ö$еACâ0 BIŞ`Ô(’ZŮ0BA�D�k%ĂH~JBX„%$€AXšpę ”0!)ł$dhë "T)dh–Ů�93¦•'d\KzÓ@lCTd“-3vpć @%„$E@‚Ě"$–Ő�LD˛€ű äBR”ˇiJJ*Đ) ŃT† ˛)%AABBš(M a ¶ž:e$aR)BP_?"«ňZ’€ěŃIH ń'ŚŐJV’ýń„żˇ!SkkBŠżI@ ¬úvŮ+e¤˘›wô[¨â_µŞdţäĐSů-ĘŢ‚x­ŕ~­áú×mÉB_1™Kę8ż@RŠPšľiűĺżŘĘâ Ň€ż* ĐZZKţ3ů#ąB_ FÖ˙ÚÚ×ćűőE9HůËi‡ďż*xÖżO«„>¸Ö˛•şŕÇú[/żYO|šióTŃOčÓ”S””ńŐ„ç·éin•żĘÝ”qۇínÝáŇrůűĘiˇ6ôŹ×ę±ß‡mlv{qńńĺ/ß[ió\kYBÖů?4ĺ±ŇµÄůi˙äůBp°IÇů?~µžČĎn5 śĄ6ęĆĘşü펥öQ€©/řźŰčŔ^iŰĎę‡ůRZ`%ŞĆĎ|‚[uż‹=|#žß»~R˙>J©ý[ň‡ř”-q×óUڵoĎ5”¦ÝžÎ—·`•–Ę+śš”[–­Ďßľ¬u§mć˙ÜNꍮÚŐÄ;çáÓđ}‚QoüÍ»Í�NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN���������������������������������������������������������������������������������������������������������������������������������������������������‚�� ]“s��‹�„ ����ç��Ź ��ç–˙@��Ä�ň˛AP{ő¶ges@ ˛ZŐ5¶Á�ÉJ ĆŘĂƄČ'´„•ź H5 Ť„&*Nżä‚‚’¤T`b J †BRM ’„ŔD/HIŞ*%t. "ЏA2iT"DH Ş™€„’‘†[Q%• HŞ-RL€ @hD ¦pB6@Ŕ"óe KPI ş,DÍB`D@… =ŢÜŰŚAUĂ•dʆb eÄ@D˛HiŇBI’)nä,$JDˇ¦—ÉX€ @BL�ł4‡Č%“)&’”KúJ%lÓĆHĄôˇ!mىvÜa4ŃI/‘ űđřĄnˇ˘ŞVĐŠÁ}ÇHCőĄ´-ˇú8ź…¤P‡ÍĄő’ú‚’·Ć„ĐŚŚQEÉK°?°ů ·Ük°ú ń:´-­§Ź)X>Z[âĘKyO…­QÇKűzŰçŔR•ĄŹäµEĐ8ź?h·ř“úĘ2—éâA·ľ+t¸‚ĘVźţO˙TÖ„mߢž;z×îŘăXŐŠRţ¸E->E»Ľ±—Ďé··ľ·›}»óŁÂ˙B„P˙Í;~%ż7\=¸©Łř_í÷íkň[›ësဩ #=©ĘÚެĎÂűu4ľ·e."Pr…»}4­­3Ůýpĺ’›wâ/˙+{ünýq-ç˝)•ŻŘüż\|x%ü–ÖĽÓçĺ/«ŹÍľ—ÇÍţÖ‹Ąµ”e9C§Ţ­?°µnóuÔ۰?´~¸ëLĺ/¸śC`.7ĎýkôégÔWŢ{~­ůGäšáŔb±řÝ/\JĚí˝ýpţíÔ­ńV[EOËÍ×Mpe6ď[źćźçÉnĘłÝk=ň•ŞRâ"?_·ř ÍÇ{->tűńe`/7ű¬{pNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN ����ç��ľ ��ç•˙@��O3  -đ9˛ÄÉ×X᫣ZŤ¶Aˇ’Ö):�Ő’T««Ŕ%A€$Şd„!±LA’‰ŠĚD IJĐaIJŔ¤™lĄ)% BHLŐ ˘Q%0&Ş%0‘I šHX¬RŠQ‡ I4˘RDĐCe IiŠPł‘&Š„S5$„–�š‚˛ŠP”‚* e! �A;©Ů”š‡DTMPIh"PfÓ2 ‘†XVy“¶@� Íć$Ŕ‘`4s0W–X[$á†HbgD“˝$UlŔ„*$€‰«R’¶DBQBhA4”?&Ś© „¦¬aż/šQAD´¤”š"„ˇű°ţ„-x\‚R*ĐěłIXRí¤RA|š_ r‡Ä qˇjßJGJh‡Ôż¦Š©(·daGHČâmÄŐBBŐ/¤¬¶âMąřZ}VÜ·ĹBhâÍ~IZ)§(ă˘ßM ˇňM [®ĎŤ­ľ ®Aş˙’Ý8ö(ZŁŠ—Ä[˙TëN€“űýżóX ÝnüŁóş?/ÝąPiM?´Đ) vĺ®*ë¸B/Ý˝óühX§őG愾 Iü‚h·×Ďwţz/7ĹűđşÇ ­ŰżvŕźÉ÷…q[ß[ë‚ŢůŔ˙7ů„qľ(âŁöů´Śü[¸đ ż 0Th·~GÍ%˙ZĘ-ţi¬ů&‡˙§Ţl>[Ŕo˙<ý87żË͢—öęVĽ×€€Ř?ýřGőů"śZeŇř$Ąýąý¸Ö7íőpq>q ”g±ă·`•?¬öýWç”çĘú¸2—˙´Đł8·€łŰ=ßŕ7őŹo·ÓĹĹ€­čEců¬§ň®汣ŹÍş}¸đqú ¶ü…¸ ¶ęÇĂćźV™q Xßž® ÷ZŔh‘'äř¸‰jÚĹţ 2•¤á NNNNNNNNNNN ����ç��ě ��ç–˙@���őő1ŕ E.üĆÍĐwa(,± ­ÇB utL(fcDÁ`. Ť�‚%ÉÚI¨€-”ˇ H$„H�’ŘI’S&$…RŕI,™)¦ZR¬6˘ÄŠ`BÔ”„‚)Am)HF¨(C&MH u(”ĚĘT$‚tH )A((�Q5S'üɆÂAQ�ĄI�”3q†`$QD a·S57$Ŕł-Đ%¦ŘÜFŘ,ŽË&o­2ĽhĚho`…„2I4Ča«’ČIĄ a楇ŠS‘蔬IŞěŐJj!4Š · $-ĄôBÚ Ő Jjżă(ŽFO•ŠPŠV…/¸‹çÉ·„„P_Ë'ȡij‡îÇţ.$:)âG[H(Z·-:RÔÓÇBÁő˝l[ĐůnßA§ŠÝJŇx–ę%(+@|˙)+’]šhZKě%/¸że?şZ v˙ČPµnăĘV+šÂÜé|Ą—őŚh§ń…ş€[¸čX iĄő‘-U¤~­ţ$ń~Tż~µűŻŐc?ŔiĄöS”PţßCáNâýůĽ2źŕ­;`>ýľđ‡R-Î"Ű©tsďĐů&ܶ˙)Z[3”żŔX%ýq­ĺ/©MüŁöÓůO„Vč9E4ĺ/ßů¤g·›®ůRšá«ůŇ˙(¬l÷|°[.‚•·ŮFUŇůŠ)ŇČĎj_×ęÜ·ÇűŔhđµ«vPµćßWQůşíoŤýuĂáX ő»ŚÓ‘.P¶éd~·-׸ůŻ6•«vPét~uŽâ'č[“ĹĹů-Ąmúp.—KěöOęßMĽxA÷ě')E6ňâ/š)Ę?#năvË_«r?\N–}€­Ëx*Ę?UÂř× Ýpş^±Ý-H}XŢn´ÇćéI”çÉžç(˘„eĂćźţ`NNNNNNNNNNNNNNNNNNNNNN ����ç����ç–˙@���ç�%ř,HżA~”’Ĺđ!˝ťf%°ÖťĹíz ŔXd€ $¸uŐ€PČ- ’P ’ ™©1 ’J ˇ!5!µ %‰L€I Ś3 ŞDCúĄ¨–ja†“�˘Ş‚ś$ `©…T’$–¤„ �´ ™0 ‰ ‡Y(L ™aP2Á-;*j@ É–U2PvÖT@Ř€€Č&bÉhh €I Ř�´Á*†ZŔş[- ˛ˇ�Ä•6I6É��]XD‚Ђ٬ň’YC¤¬… �S PxŠPP MRH¦TÚ•¤ ˇhJĤPěľ ˇl-?B—ô»)ÂŮ„I¦ŠŹ˙oË ËúM (ˇ&š2…¤>-%+eb”ń?ă§Šßú˘“JĂ(ĹÇ/ß­/Ú]—Ë|r)ˇ FRý%`éz™Yž4')˘š-çŤ"ŠxżIâˇúÇĹnŔAńZZAl>}C÷čn[ý-Łô‚´°?§á÷Rů˙.7ő_ĺ?’+Íe  żĘ8‚ßhóUŤÄř>ÇGě>Ŕ_—ę±–gV’·GçűŔTŁ`'ß–SO·ţĎđS沅Ľöý?ýe9AŁň|!Çćę­äIn¬d»wëC‰§ôýňŢPµ@Jźĺ˙n!Ý,µ”-ľăüÖg_ńŇ_  ”[Ń\cÍq`*-öŕ2ŠmŹÁR8đK揚[ăü˙'ůí€ßÖ3©óO¨~´|"·ů­Q”q¬g_~O‘ŕT¤V6 (BŢ{ y·?¦ŘŕJڤ[˙+{Ą_Ďß-V7ęÜśĄ6˙ËźˇĆ˙>Jk+O˙/ÎÝÄýóĄĽŔÝĹ€Ý/ů× ˝ilŕ,Ćé~,řF‹xüë(§)§Ź)AŁ´ţ––ĽÝce.—ZÁ"�NNNNNNNNNNNNNNNNNNNNNNNNN���������������������������������������������������������������������������������������������������������������������������������������������������‚�� ]“-��‹�„ ����ç��I��ç–˙@��_�<bË7öT•ú>×I°Ć44˘{`ŮRAL—TĨÉ€RĆK@*L)H-`‚ Lfgb*T 1 DÄ�)0™„IEC%0"0¶üeé@v%ţv+"”TŔ$Ś9J*T‰dDĚJBh%auÉ�5)(5!"ę l"©’P„[´ ˇ%ÖM!¤Í I(©8A•Ih5jBN  ™č‰Ń ć[T4oa€ďLlŃŔ“ŰNÎş0fÁ‰$3¶‰)‘TĹ‚H¨v4B*0ŤDJ�)(Ja"­C@(`)!4‚iJA|˛‡ôĂó oŔ‚ŠtÔ,_ŃJčˇI|„RâůŰSCäŇA)|ů)˘”şRí˙B_ŇJ·n[Ąin(Ąn—Öţ:V“”>|ú©6íSúL­»)EDÓů V–ř±đ»z4?|µGĺ”Ón«s÷Ď©[GJ€RÇć˙€ţ±‘NR] _ŞĆ·~ř€âJßęŽ4­>|‹u (·­#)¬oÍ÷çEo–đV¶2Š*üŐ4ş ý:RĐRř$~źŰ’ţ¸˙^k‹‰÷qÍľZvüKKO˙Tľ[ˇ.ŕ|h·~Ń~‰âqö„QX߯ßçnُ‚âýţUŹ”q­,żÍˇmúŢSNSůQ”W (Ę?*ǡo=ßş2ź`‘nßNZă[Ďgë\HJßš}”V2ж_-Űß­­"Ź ˘´Ĺľ›v Ź–ż+rŐ˙F±íĂ=ň•§Ţmý!5Ă€ň‡ůC±mąÄ:?o¸“Ç€˛—ôţN‚JÇýţßÍÄUŻÍ––˙čüÂ×…-ŕ;cÂĆ)â§őŤGď)·ŰłÚŞŰúmÁ4x �+vúÓ—çć˙.*”ľ?żĺV=EJŕ|ű‹ŽŘďÉbšŕý~¨ăt°NNNNNNNNNNNNNNNNNNNNNNNNNNNNNN����ç��w��ç•˙@���5řYŕ J•ţ{lŻż˘Í*'ţXľ 4Ý�µ2€ ¦I €Ölť(‚ŘCś8 A’tMY$Rś3(IK“N’ɨÂA: ,L�LAiÔ(&BAĂ2€Đ0PU( “LŐaI@$B+2„’š€Ş %™(š*U$Ĺ&I5R€DЍ%�Ňě‘%!¨B‰‚P!'ĄR„ŠŁÄ % 8S!�AA�AfXPµF†FťPŇ"Ű:Ëš¤9ʡ—¶&H*–b ¨iB@HBH ÂH@4”¬ŕUv@j0@@ %‰h„PýúIĄňÚ(ĄŘ«I¤…´Š$Ë÷áÓDЍ�GÂŞÝ?[&•ľ7d»4%nŢŠ*Ň”˘ Ďí`…‹ţ*_¤Đ¶8Ňýihq­ˇinÝú lZĄ˙đ´­->©ĹEş˘(éă¦HJxÖčâ[ĺŞSJń$Ł�ú„q->·ÓOď÷o}ćźäKK÷˙™E/ŠŇmëO˙>4SMřéJ×ěeHNĄú]ÝF –Š?5µ«sëz_Ńoói âBş c­›}půşPŹÓńúĄĐ+Íe#)üźŚ˘Ü´¶üľţ<FŽ.>:ý McŰÖĐűŠ”[˙iFRµGďőXůíOäź7žé Ć—á˙š[ă ăZBmâÝű·»o5žä?ń'őůţożOňŠp?•6ńlzkR‘ćĎ們Y[Â\DüËęŕ¬t&±źńeĺżËľűÍçµ.–AĘk÷›tłü"ţŢ˙ôx°é÷›ýůĽ÷/‘Ĺůŕ.?×ĺ€˙\IĘ-î—[˘Ü˙ŹŕđŽPúšĆ}Io>L¦žô×�Ęű®H×ÉÁ/šŔUĂ\5Ťů`<ŁxBŚ”~˙vÇ‚éoÎÝ€­Ř%tąŔN!˙_µŻËÂÎ{eíÇZak=‹ěHN����ç��¦��ç–˙@���bđ#ŐŘňsgˇuÍ"HÚ›]˛ËĂ&;b&#`ť™¦‰,n€h$Ő«±I"%2CĚ Âpŕ5; ¤™I&jÁhh@I-5Ą0Ą „H�J" ©‡EM”ĘH�ČKeŐ)(4Ő‰TH"”dVDK ¤B@NA¦fU€�d¤‚RN’dP–Ą«B`Č„¤ÄI %’Ě&\’K"Ú°7 T¨lžî…XK™¸HřcR%Qˇ…›�É1 !&bZL@YŔ¤“2Ň�HJ*! ™šP±Á¤¤˘“ XŇPL>J""ˇ „±4ĐE¤Rš( „>�4Ő4¦‚ůlńĐ’°©Ä”Rú i Kô? ‘ BÓőş2KńRšh[âĐJVç(ľ¸ź~ÎPP„~Ëę}oĄŮâD”ż(ŁŠÜěRšÎx±|ě%Đic\ XŠGR·FDĄđ[ęÇvÔŰËô[ĘRě-ńţ’ţßBQGíij_~O…ş)Ďk}?°ů9M5QÇoˇŘ~‡é¬z´¶´ýńĹGä< mĎĐ·Ĺot¤óŃńQáúMâvĽ(ŰßţżIZZHĘ0(}‚»~®Ń ć©Ę,Xëv÷ëyJGéiţ•Ş?<hĘ_ĺ6ě÷âăŔyFăü’kk~$;/¸°çů`ŮúĎ{wąŰ~UŽ˙ő\[\�Ö5ąţ -ô,Ę Â| _—Ζý-§(Zü–˙ŽÝ‘- ¸ź~bŻ?;u¸řEóđú‹aÂŇÖS€żKKK3–÷HÚ;}přCň[˘±¸ß罺ܵ斖ř°·>Łňýŕ4>¬t?Ćż*x°KűOďő”:~ĎwŢoµÂ·eĄĄ«çKţĎ…­ř+_ŔăZ®ČNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN����ç��Ô��ç–˙@���ďđ*|¨ßWŻ!˘e‚BŞ( +ÚlI¶D5"ÎŔ2AVTŃH10dACDÁKpŠhĂŞ[$NŃ �Ú€I*ÔBI"Ä Ą¨‚F©Ş  DĚ  [ (ˇa ˇPH/é�‚$Č2PA‰I(�A‚@! „§¬[Q`�L‚° @¨·PDU ťÁI Ń”…I $A2�CZLČ2‘ �� I€2bR©Z4nľÎúu†††BŞŮ`›•l)"[«Ł¸Â@’&!bL8`Ą¦PH1NŘR”Ř:Š�Kö @Ą Th%úLŐE, JP]‡eZ|č<E)} S„SE6đš&˘VĘV¤Ń@[ˇő&‚·oăJŇÝ'Ťú_~«7ďÁ~šíö÷Ŕ>ˇ­ľvô"Źŕq>?› TZăvôš8¨4~°î‡ô Š/Öź˘źŰ·|“\/ĘÚŰę Ó÷ëX*(JÚ”ń!úKoŤ‰ BÝĆíé4ĄúŐ?Ą¸Ŕ\Aň\?ŻČ Ň”[‘A·Űß­ĺOR+… Tˇâ§÷”бý~T[ż_§ütůşEGô­Bm˙¤ľ}n·~x [|ž$V5şŠ–Ľ#ĹžĎݶ -Ö÷ÖęQ\ Ŕ´·ć­éZZ˘ßKţ5żĘßnĘż'ŐŽě­e)[üĐţźŐąŘoŠÝGć|ŢPµ\ۨĘ?Đ´ýnÝžéýqĐ´µůŕ”Qá׼>üňšŕŔ^jŹĎ"9jÜúž:áĘ~–“”ůŁžŘ ?—ĺůĺ6˙ÚÝż­$'ôŠÇ·­[żhĄk=˙4W„<Ó«TŁŽ‡ďłäâ[˘ź×ë=ë¸UÝąőżÍÖ1ý8‡Áµ$Zq _ľ® zاͭҊkŢýĐV’ýn±«„Q”yş_× ĎpűÍţ°Lŕ'Ňú…ż~te/ÝŠŕÁ¶¸2€NNNNNNNNNNNNNNN���������������������������������������������������������������������������������������������������������������������������������������������������‚�� ]“ç��‹�„����ç����ç–˙@���tÇ€€ +WŐ7÷ Žř;!Ą„@č$I¬Ó*ĆÎÚ–€Â„bál.dD”�P"K�a$Ў!¤Č0X„Č€X„ I" RQ)&DÓ �J ‚V “`RŃÂ(EILŇVx1TÔAL A& €°BeŘŮbDPł„¬@T¤¦$ŔŮd´B!"˘V0:5Du¸"J$CH’@fˇ›$LôÖImELG`ă™,h,föĽI!®»€„�f‚„j2‚ B Ă’P@ŞtŚ*•Z)4&Zů&¬PV)JjP‡ácBÚ’ ”>&‡Č ˘ŞÔ—d-ˇů¤Đ•ŠęŠ)ă~VńŰÓĆ�ĄůăĄđ BP?r‡ô5JŘă~„SQl|•µŞVđT˙#;4-”¨@ '‹ŚCę´Ąő!ŰŇů&šhă„~T}~ÓoÁRĆš8Ň‹zݸ¦€_,S”[ź~Ö˛š8¸íÖ쥨ŁÍżK÷ö˙ÍĐ(·ŕ'ď˙+ćëň)óO©Zt·›4­%#öů AĐV<hâ+tR_~ÝŤi(·?~ž%ľ'΀„ŁľNQgíuÂůkÍV9I® ÷â˘ßůżýż ţPŠçúâ[·~Ş~UŹ€¸˙oč·Ö7żôú¸_ľÂâü˛„Ń”!iÄ7ćégčŔkn"żă·‚‡n·”ľ@NRhú}K÷°>oyĄ¤ţgŽßXÉâ6즜ö§"T5ůů´qŰđKJ­?·‡ßżĚż[üÂ+Ť$Ł~k_ť)ýqe ˘Řrú‹tŕ/ŰĄĹđąoo‹őżĎňtýnÁXŐĂžţh×p>ótţht»üŁś^n˘0šB+¸ť?řFśˇ(ĄňßšĄ.–Źáŕ8¸xčMc­y·ÉOš@NNNNNNNNNNNNNNNNNNNNNNNNNNNNNN����ç��1��ç–˙@���đ*˙—ÚŢĹŃłň.ÔcŐŤ Ăgm Ęł53($”˛2A!˛0Á«!—6–ȆŔEI  Ë SwT$,D¤T€K�ÔŠ¤0 ¶€)‚”ˇ´@!˛ hM@‰¨Ó„ Âp’u �Š’ "“%5@ťHHË‚BP†msI€Ů� )HAĐĂu˛RÂ@:%AJp H%¦"MÚd¶a¬ŮDÉ`$Ŕąa[ˇF64\á­ąŤcPeI$’Đť-iI� „Ą(¨„:BA€±BBP/Â#dĐ„Ňţh ŃCĺJhĄ»*,@|—A)ăM)BÔ"”ş�M.ÉYë\KA# ń4%ŮĘúVŤCO…—ĆŢ•[E<KtqŔ¦˘(·żqCęě§Á_¤?~…¤qR% óâvéBRč!ő6çȨýkőJ2:VéBß4ŰżT-»e¤Đ·o@vÖňr…żĚľYÉ[Ęx“ÇůŰßM?~·FS”~tţ–¸’źŇEšÝ¤Âۨ¦‡ăÍŠá·§ő€˙oáKűpâ·qş[‰l---HŔoĘ×ć([Ł=íĎí•®?ŇÚĹn®PţŢŠ+ňĘ?vçůK÷üN!†kt~i– ň€éä­ÓXĹ/–ë/üÝp`'JDeţë÷Ţ·ţx(Č—ňFRVčt¶ S@ZŁÍ~Ž{[ň®––ź8‚EpŰ’í­î–·ń-ÓnOš®ŢkYCďĐt«c°ŇŰî5§˙şŕ·?ăt»çÉü«ÓöRâ- ©·›up[Ťą%6ăE±˙żĘ¸ ŇE_ťcŰđl¬oÍöQXÜg­­ŕ:ĆÁ*ßéiň<!‚JĆ.ĆQ\6ă‚\ţŹĎ(üżUÁűqoýyşá¬jśT xđW”y¤eIX:[ŚNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN����ç��_��ç–˙@���Bđ)«$1ß©jýh°a‰5ueHj˘€B**¤ ÁHidT“1 ĺŃ`h&H…R�5A3-C ’Ň”fPDIJpIT„I‚Âe$ˇ$2 ”°Re•b[4ś"(“PIBEí 2 NČ%( 2Jd”Bb¬A`�Ŕ@H�ĘDČB–ĄIĐ()Tf’& 1)`$©Vv&C0LkA†@ Ů‘V“•`ą‰ ô7*Ş!1 €Đ¬3pI‚I¨  ę¬µ3Q€ĐM)j$Ą4’jP@ )Lš””şL€­ż„I¤‚µHH¨Š’‡Ëˇúb«˛_!cB)Ąm/ŔHXČ}?´Ň)~@I|’Š*ÜKh¬‚dSD¦š˝m…˛J ?¤&łă"mk‹ňMŕ%¤dJh@ˇŮźď#ĺFDy·q-P—ČXĎéî%®>+đ( [–ß"޵EąĐq[Đű(¤Ű˛”şš xÜ•¤Đ_Úi}űZĎ|?ń%«zÚ?hMŻÝ N–~ýiőcŰĘŢ{RýjÝůű¦š“n®?ń?5Ś+±mŘ cćé·-żŔ®$Ňżt`‘ů.HZĘV¨ýľý-!."e%kB ďËŤ/ňźŐ<Oßy¬«żÉúx–“ /íÖę8ü/‹)üđßäů%ţ Ąź>ŁÂ+n‚JÇâ<F¸)4ţ^iî/6µ”ţ_ź›â}nýeOëŤB�Gpgµż)ü˙7Ö˙ÉŇÜT~Xţtţ˛ś€«ÝĹXřőĹůĺ"ßćż~o‰˙éńŔT~vÇ-e%oÜ˙ö…Ľ‰_~uŽéžř&Sů×iž,¦ź+řĘ?,_ś%óĄ"Űä-ÓXĎ˙yď”ËÂ?‘qÍ`<űüëNNNNNNNNNNNNNNNNNNNNNNNNNNNNN����ç��Ž��ç–˙@�� ¶ ź�—ŘÉÝi,“$ÎŻ#M)&Hh3ZkIŃ-:2�&Ai„\�u™hB±I„1$ڍ PPS¦%€$Ât*¦YŠd¤B‚Ń-!! Č’�MPI™$B€Š0i ! ”‘)‚!(–€�„‚P’%€€e«Џl‚„0K0REA– ! ¤U€S2¤ĚČae!Š0 €2aÖB&FČ_[W,l‘ôó•ëń5˝ČbäEF„í,C C(A¨�ŞŔgE Ą­&˘Â$   ˘Vh/‹­+eÖ„ĄiRi(„BhJBP¶ÓDĐĄúR-É Ňţ„$?)/ )ˇmmŘII|‚”Ą>¨)}nŞú·qń‡ët% ŁŤň„‚;n7ÔĺÉ Ć•«pM ĺ°(C°ěˇ4&…ş <`R·\CJméK· Ň‡ř+·%ů·?|¶EKv “ĹĹRßůŹěźŐ OĘŮ@Ę)"šr…Ż7Jh|µMľ„۸lj¸ăń-P•ĄĄ´ľ·ĺ!ý!h`*ŕý¤ŠršŕGî…ľ%®1úĘ2‘ű|—ŢjŚh}oZ⦗Ͽvëy}ćx°ęś g·P•µ‡żňóYFXţIYśG€€xҶ¶·G÷ŮMżŤ˙ä ?iĘ0ćK§ţ$[©q”¦‡kúM±ĂŤ/ř¨·ŕ>5™Űr-ŐŽŹßä‹zÝľ‡ôń—ĺm (ŔX6 Ö7cŕ0ţŘ÷Ëo–©ó_ŻÎ—Kń­żĘ2ś˘ß€ť,·‚ZőĆŠŕ·çµ([F{-ěĄŘ­1Eż=¸‹¸$tµ»ůňmô˙>é{zE9­źÓ˙­çşŰĄ’ţ¸ršát˙\"ÜxßľüÝ,@â}luľÝ”qţéFQ‚L§šá[ZHNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN���������������������������������������������������������������������������������������������������������������������������������������������������‚��]‚ ��/�‚����ç��Ľ��ç–˙@�� ®q~�&<ńrňX$é|N¶t 2ćîňł˛Y �ÁÂ- şfC«�¶IE@A &L$"©  &`j”Š‚ŞJ ()J�&¤CHJ*%W 2P’d B¬Ua P(!%"ŞID%�!ÔA0SQ2LH!-~Ij¤DT‚$0t€€Â „ŕ±*‚,HĐÁ!Öšˇ°Ęş©-“ČŮBv2u˛DÁ ­Ş�ĐĐ –ÁÖ¤‰Ťjf*Fľ ęR€ ™|ĘJHK* š„$Ę[Q(X„€™Z(€)% 4Š©AL� Ňü‘8TÓV�~•‰ˇ@I \O€)…·š•M)JSJhĄ|Iˇl%˙˛K÷Ĺ)ÓÇE%nÍ©%ő%&ß–)+UiK˛’x’±·ÓK˙Úßő®4¦ŢOé+o„­HĄ+gńńCďŇüżÓ”ĄöSÄ´ý4ÓĆE4AZKôĺ?—Qo·%+iâ6ôŰÖÔ·ĺ$ŇC˙7€íëa>mkŹňă[¤ĹÇú¦Šxß`.?Úk…m+x (}”->Ęîšů­ţ_ˇú|‘*׾śV=+’ŢĽč/˛•ĄŻŮĄ_żĎ‰ĐV˛•şá[·�'tŃ”?·ŕ<§ÍţN–t¶Qů[üŐ¸ľý!jP)G›@ćíÜ·ÖúĆĘ|_’ŰĄ˙_µŞhŽ*ż·eĽß6ăć–ü#ÇĆţŢ—ÜyM;86-SůţKtSCôˇú_Űź8†Á-›Ą“oć­Î”µŔü¦¸?Vőľ>3O瀏›ýşYOş—Ôż|’Ś«Š_¬kůçüž r„xA<oĐšk…Ňün–śĄmfpş^‹}cţĽÖQůń?ŔN‘¨Ďt`Úr”qg·ćţ±¸˛šm˙“Ž"ńVńŃnâ}ů[ÓnˇŇďüßš5ŚNNNNNNNNNNNNNNNN����ç��ë��ç–˙@���ŕˇÇËŔ@¦ëľ »Ţö5|Tc@kC L †™bflČ %F- ÓZ¬1$‰-H0ŇJĎŰ0@A‘ ’ĆA,”3,ś4!)c*C*D ”�B Z�×RT@¨FV"e�’ ”˘™(©$ KFĐj¶©RH  �BX AQ"¬ ‹8‚H�„˘`L)Bp@‚* ‚ŠAfŚÁ“3«aCŰ!’Ă-•I#¸-]tĚ kWôYbč)9v±¤ËA(Ă`•  ( �˘jÁÔV BE# .¤jQ)BB J´,)A/ß&˘hZš (~D>v_·H¤-,iŞ  R…ˇ —Á/ÍeOˇúŔ¦š ô…ŞiKňM4ŰŤ(Ş´ŃA AâKńB-ďĄń|$#ö¶€V¨©üĘŠj?hăýÓJRVݲŃK˛ě% k@P´¶ű÷ň´ŠR—A+i·­ŃžÉĄqźĐ<u)ăGŔKżŰ÷ÎŰö”ń“ůR—ţć’µć-ďż#MGČ?—›âýŕ/ÉőEĽˇaÇHˇöüĂűrÝ®?Ý(˘šąB)Ę2—eő¸šöä[ÂxčK4ľZĄý[kóýqqRQ‚µˇůŁ(óX !mo)ˇj‡é|´-ď­ř ž,ŁŹÍ>Ę)ŔVü÷}…” ĺ(~•·ô~c‹=íȢś‘Xč'ŠšĆýŠ_>¬jÓ+O°ńžé 8 Š˙(§Íe/Ęň” QCäҵZf‡öűwŰî,ű)}űŁ‹őúEőŻ5\4ŁŠŘî/ óyGçBOço·~Yďž˙ŻÖĘ<ŢS”ţ_“ĽuŤXö˙Őc'‰."šĆş0ÖĘK¦¸-é/łä§=żYJ8‘X˙Ş+†ßXÔ8†˘±ř’ůmÄ7NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������beetbox-beets-01f1faf/test/rsrc/image_unknown_type.mp3����������������������������������������������0000664�0000000�0000000�00000024324�14723254774�0023227�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������ID3����SAPIC��3��image/png�˙album cover�‰PNG  ��� IHDR���������6IÖ��� pHYs�� �� �šś���tIMEŢ  J™���tEXtComment�Created with GIMPW���IDAT×cü˙˙?‚�9Ń…'����IEND®B`‚������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������˙űPÄ�����������������Info������(��!w�  &&,,,33999@@FFFLLSSSYY```fflllssyyy€€†††ŚŚ“““™™   ¦¦¬¬¬łłąąąŔŔĆĆĆĚĚÓÓÓŮŮŕŕŕććěěěóóůůů˙˙���9LAME3.97 Ą����-ţ��@$|B��@��!w_»ő^�������������������������������˙űPÄ�1Şü@TŘ5"L�‹‰€TÇ˙˙˙˙˙BhNs‡y„}@�3žÇ;çBú2żúźď!?úţ}Cˇ„'B‰�¨ŔbĘ€,=°�˛ 7›âýcE`M.¦2 :şJwť f}w§[?˙˙˙˙®Ť‘ŇŤW:®Ş…F Ś�ř·BŇ5ĄĘ纡Ö^ł­*+R:kB·ßżŰ®ź˙ß˙ĺ™üľ[&8—Ub0'QX`€hˇe˙˙ź˙Ů2yŐYä˙űRÄ!)± @} 86`€łˇµŕv"6‡¬F” łäÄÓ”šŁźĂ=ŘŠ˙Ă˙íçŢvó?VaVÓŻSä¤ËŚIůóläe÷M[#@~¸¨H'ŠÝzŁU0¤f˙˙˙úZc{˛™Î¤d îî‰tÖÇi1ާ+Ó>b—WnŻ›ľ·˙˙˙ü˙újÝkv}I*shěCÚ #9§™„�Ä! �×u»kŃ{µjk¦¨™É+Q@·ăÚ·]šŮ+¶Žşm˙öéF˙żnďâçĂWőČóŻ˙űRÄ;­µ @×Ŕ÷¶a N#ś4ěśHšW®JU?¦^gÍç=żĄv„y*g^M)\ŚčŃZż˝W$4>ÚdLN[Íűĺú>ĄS3őę*Ööß G_ĘxV#łB)ë´eT¶H Ś–šN@Ńk%©§GI,™eU]¦zQ.ű«,¶-Ż¬ŞŁ"Ůőó·˙ő¤~ů}ßJWňť}ŢÖTE¤>é ČMŠB#–¤D™?Č˙ď–\dáO=!›źbtph˝'r†PŚ·¨X.vlČK˙űRÄW ­µ =¶!'ˇ˝…Ö.™|ź˙÷şůănµXNkíÝbß aWIvȇ˘'HT'@ifłł"rŮ•µz:ö˛íŁ<ęŕŠZZVlĄVC¦žĚ´dOŻ÷G+>F{)M¨¦łk-«ˇ‘§!M.hńqő•Ň'ĆfŚz0@ú˙˙úďťşYďmV¶ígÔÖwµT†‘ x…!G!'GtŐ ĚgůÖŹ2éK.ŞüÖµ«mŢ“şŠkśR1U¦cë’ë&ČĘ ÄhÓF…˙űRÄn‰Y± = ¶ Ä'ˇ’�#¤´UdVißrŚ —N^’1·|ÓÜQęĺZ=ŢÎëO7Z¶ĆŇŤ}ůăűł«×ĆwáńCĽ[fB˘Ż> ’†R*Š(Ł ?ÎŚŽ˙k;L¨Źf*nSˇSyĚsfiÝT…*2ŮŠ&®ă;+Č•ď˙¦Ťµ żî‚YSőcY4FÚ™s­Î(M 4q°Č¬4EXuŕ(ĐfJ8 vhă dznE"p zł3¬Ó˘z˙ţr|í…˙űRÄ„ Áµ@ 7Á#¶`Č' ÷oň°×«¬Übß—w.Lű7eŠ’@K”Ą)um.W§[MF˘‹vR6"î‘mÚvbÖf±QYŞGuJdłýľş~ü2[q޵öźŹšŠÜfĘBýIŚ“Ů+q Ń áY�¤űľčŕl;4·pĚ2!äęŁfŚKËCŽ…I¤«»Y%>®#ŢľŰřţ?ŢŻ‰Źy—ł,¸bˇAY‡ž Ť,‘+Ť…” %*Ś?˙˙˙Ë˙˙˙˙éíçX‘©˙űRÄ– ł =�ý¶aH�› Ň+ڧş…-zÎv»c±¸F”ř1ŢV”Ôß?Břü'‘ßČ‹űßţoë$–R[ ĹdtŹŻ# 4± KY$íq:4s˛#M’ŔĘŔŐ= „gědLt2•f;^«écµ]Lô[;ŁŐS–Őß˙úßD÷ľ˝ú~"‚i]1N^ w/jÉO)Ş MFW+MČť6_+iĽoĚŠ¬ó%Čěéô™Á® )Ë $'¬ž\W'5ł’źź6?'˙ţpüĎ&Ý™Ţăq˙űRİÉŻ =9¶ Č�ŁXôˇŮě”<ö =}aÔwĄ“ꫜL»©ä�¤NýÝgfˇV X‚Š÷uQNkŘJ$&¨gČ©s©ĐŞčČ„\ôôµ?M?Ý˙’ŰXßéCe+0Ylö¦Ńi4,b`ř=@DW˙˙˙-oU¦—IT;fĘ·j‘ ÇŐ’:ětŃ… ąčs5KÝ[$Ú?Ó#D §Źřţ?ţxç“ëĚô¬-5ń d_e°ŮSä9Ç$, ˙űRÄÄJÍł Ť= 6!'ˇŘaţ˙?éö”`ÂI‰‚”]ĎKi»eóf;éi#R ¶/;:FSś}LÄ×s ÓÔĎW¶óŁ–=h¸c ¤4\jŚ" Eî A 8. `&ˇ r đäŕ‡ĺŹĄ?ôő˙ńň‡¬ż-ŽBĽ}ĺřĺVAT÷QŢ>™Z­v_˙WS›QW3µPÍÇ ›;ŮóŮN~U~í˙ëĺxJ§nŃ,ą!h,č–Ôš ŇdjŤ-i˛ żDH\”˙űRÄŐ ˇŻ ť5ᡌËA€ J ŠĆ@�ß˙˙˙EV"ُĚkĚeR‘÷w)ЬÇtzŻ.ćE!ęK©nčĆvząW[şűţúµu—ö¶ëĆ7”…NťJ(ô"5±›Öó@ÁaL=#ŽĹ @˙üżü R‡˙˙˙䉕ŕt˝wiŇ«UGQlwE˛‰ě¬#GđPŕ‘°şâNÜ#›±Î_ůSĚžž‘ś¬?Wý˘Ź“c‚ĂNBĄ%©ëÎn_XčaĺŃ0˛Áej±˙űRÄč 5µ`‡¶ŕ!¸I¶Çď˙˙˙˙iňĚ—31[šĎ-ľy?ď ś””󤊮nK6‘â–ŞÔŐ{±†yŐN~;sůU/µľöĽ˝ÍŻŠ¸»kˇśĐMl0>Př`ůŃP 8XLĆT,aLq€˙˙˙ţĄY˙˙u­>yń˛ďr‡•vá†Ďů¸JK×u|â•1ÚEf®´0’ rl즽ţŐĄéö6\on-dfY”š·®ßi ŐěŃ*˘'m9Ĺ!D##ŕ98ů˙űRÄč eł� -ÉA¶ˇ &ř˙˙˙Ż˙˙˙ŢüŹ"1µ“Y2 ĘiR+E˝‹'Ç  N�ŕÍOJä(aŘ™dÚ%‡y©µţű3;%fr®g,«źXPąüKÔ1¬´ĽŐîS&!§“I…ó—–—ްĐ˙˙˙üç˙˙˙b]Í�aI¤ÝŃÂŕšW„=Ë»¨K=\i“ĹńlÁIus?W;źńÉeűĺß8g—UYńCcÂIęË®)M–VX`‰L3b3EŘ„$TV˙űRÄěLeµ�@ŤžÁw¶ �¦¨Ś<Â�/˙˙˙˙˙˙ţwËMr:jdZ‘—uĘ@śÍĺW‚ÉÉWŠĂ‰G’=A ‚Á.ŤyXžÎ<˛,·ÎçîWQ`®f[:ş+gdr´’—2vŐ02+#`PH"(ř•Đ˙îM©4D6±Ď‹2•ŐJůݲn¬0^ŚgZ"‘ClV¤±ÔŞŇîEy~źóﱾ˛ü ‰ú…Ča5TŠ…W¦djj¨a#ÉÉTDL@Đl”\ҶĀ˙˙˙ëđ˙˙űRÄęKqł@Ť= ‚¶ łŮó‘vľY™©¶q‰'ţj»%#»+Ńž„l�2Ĺ$hś^´ Ń‘PIÄĺ/é^S4ŕ?Ě›Ë3ѱ1WO\+uîŚý”† —¤:!H¸€N /Š„sµ?@�ů>˙˙˙˙˙˙ź;Ô)*_ŞąjdJŚĺ$Ç™Ya´ LŚę Ę „Ď8Ů«cľďąç}» Q©űÝđm™ˇQ†çXËďTQŮŘ7(ĎB˘ŁÄäm2• ˙ůK˙˙˙űRÄé ™±@Ť= q6 H¦ůú˙˙˙çlAŻű÷l|őżTęmš(ţ™ýę-r đm±Ôe¶ďȧ9Y™ĺ¦â›ţţcwßo¬÷OĎ̆awV­“¨)“dK‹ž°ŕđĆĺ“&Î<D„4-x4&š—Qâ�>x˙˙˙ů”ޞ˙˙ëáÚľË ŤśŹú]ą˛ńľę­\)„›q„]»LBĺÂU—RĐó†eNŢ~ţ~W™ĎßžľR»±;'––ZĆ×Ŕ\µx©,ť·ŮKÂßM·Ž˙˙űRÄë }µ@ ;A”6ŕłŘ˙˙ţ¦/—˙˙Ç9sIBéˇ EËu;#XŠĘV˘‚…ąÎ—D8TuGtF[Š‘.Ôďň+»0–őÍęl˛ŃÇQęAŽj$¦?~ę•(@MNV'ž -Ś–©�6p˙˙˙ďúú˙˙‘ś™P\Т/?ôéB%ŻZÖ*Jt˘a.fćýÁšÖÔ’p‰µ-s˙.ß˙ý9żôżî?K=Ť¶ÓK^gϬÁ•Ö3‹ :--€Üý:ö„› Ű`@˙űRÄě %µ` 7Á’6 3ŘŃ� Ë×˙úµ6Úň˛ä4Ź"Ş(‡)ťčŠ˘ęCŐ°ą$3ÝXî-,t9XĆ»4çeSU˙ú©Őf¨š?Y“jM¸ń đß%\ȸm ‰`dşND ČVĹꀽW˙˙őÝď5’©î›k3·SH ~śëÍX);“ZÔźQ‘{ů­- JIxeŢçů·Wýüůó+Ö{ż°ňőLäo&ŹłI1ŠźÔÄI˘ČěD`5ĂŔh6 •A1@•˛˙űRÄëK™µŔŤť…6 HłŘĘ$DëŞ~Ç˙˙‹<÷cb4Ĺs\\ĎB·ĐérZĺ^ď™n†ĺMÄÚSĽ™RgĚşěĎÎßz˙ü÷×z¬uWľŘÂĄµJB‰VsŠÁjá`jĄ ˇ‰ČFŠś˝��.ábD °ňŕ0»/˙ő)ť_ Í ĽhŢecöÔ»éäcE3łÔŃdŮďhĂ(·h÷ÚÎňî>Ż˙ů‰^?˙˙żűţ»íüuEŚeĆ ©ÔQ …””Â:   Đá˙űRÄę yµŔŤžÁj6`Ś&ů�˙˙ţ_š@;vż˙©­D üC0đu"ť,gĽC„¦ #6†ţś|Ż“Ĺ5„ůnÍŇ7ŹąĎÎźŢgržnßůĽ´4ö7őŰ]ĺ¬+Ţ‚ćaCÔČO]:HOX×0�>heŐ,Ź­úěÝŐ(˝Tšn‰5Şâ4u˝é–ýźľZ<öŐëłűă°gZ٤ăńsźą.îMózffvźóťütďbíł[Ő6TzäjY:˙ Dâ*qŐ8†˙űRÄí =µ@ 5Ak6a(�˘at$;^‚Z,–HÚş­ ť˙˙˙÷H(©¸Dzś†3EĐ®e§3¦;&QÂä9ou%Ĺn C”‚d=+_˙˙ţÝčT»X™%8á< bHĂG‹ ‚01C‚ ��Q�˙˙ţť53{˙ú˘°ÚsÉ–Ą~ß|…™AŃ4͏˱Sá:Ě#rm%di?2łČďô˝´üżçc_űt×™¶Ne [|óLL,ť%é8&Y7™lő:1ąŁ±ě˙űRÄě‚ ±µ @A}¶ ¨ł ‚?˙˙ţͨVÝÁż˙řźln4 Ť‘ň8őTĹű,EYŻ «ÉN57C“5TlÉ‚n{ Ó€¬Ą=gu˙źéČęYňĽeP–•‚~ŮU (i–q &®pH@)H„©ŠpB�˙˙˙ý×%—7˙µü)ýM>©-;c®k9r/©Ä…ä‡ áb]C‚†AÉëŠ$0äLYpŰő˙˙ś§oŞ˝dV’{8Ştú‘Q 1#moHt´YerRDL(˙űRÄě ‘±�Ŕ “=¶ˇŚ�–  ĹÓ�]˙° 4zbŁČfËţ|¤0 Ą  ‚�ŞC•¤>ÁH›IVľ{¦×°ĺ2l“)M1 R3˙˙˙T·eٱŽčŠŠ ĘU,1ŃŽ–$Á Ő��`Ň µ¬Ő\u"˙ôż™桅?«m8ëT),ůK_űľ^UQó3ő­H†©ň”>‰ň2%~#ŻŹřůţ~:ŽŰt(s8ע¤¨†« ń2F ݱ`ôĐwkąĆÉ(€˙űRÄěKőł@Ťťw6`ڧi˙˙ţi3m EM˙˙ĽČ_nLDWcIĹ3“Ú ¨G!)\ĘyűE:ŽÇpÖĂ&CmMZŮ7‡¤˙–ţ|ş™˙¨ Čě&ÚŮžL™‰*(ˇ"bLŐ$"]!*©•��Q�˙˙˙ S¬e˙ůFg/’ł4&g§őŃ Ě蔿J1¬ TĂ1ëK;„nĺ\Äšc(0ŁN´,ňŰsüçsź)<wß­´˘ľT‹ ­FÂEr"č´t,BLŹRëx˙űRÄë Uµa =?6ˇô�Šh ĽH(¨�˙˙ô_ĆDđ ż˙ňôŃEśúhZ«“/ĹĽ\ÓđIţLYěĘęŞĆÚ>%“‰ S*Dsźü‡óüóuťűb¤ĹF䉜¤ŕüĆ\Â(ćVZa”BŠ ,`vŤ˛JŠ•��� ´@Ň·˙îŮgs{˙ů•©<Ć&Nr=îyÎĆľĽvě8ż™ć’ö«XŰ;G们Şç˙Ó·íëĘ®ŁQJěŠĘ ÎěcÄ1ĹP&4˙űRÄď }±@ ‰}¶ ڧi85a«d?˙˙ňm?Ŕ2ÓZźýĘfyÝĂá•QźĎ)%[+lg®VŁ#‘ˇ^¬ŕ*Ýş©ť×ęěŢ÷űŰż/!rČT®}÷'.Ť˘Uć’¦Ňç˘mŔ›Q\V`}f€��Q�˙˙ţN×Lůż˙şń–¤^CžęŢÎwÓ `ÖVq%KĂ yݢC=Zó$Ô‹:Ęt˘Ňňú^\ŮçĹŞ*é‹ É,m' ś2F ¤š¤˙űRÄë‚ uł@Ť>É‚¶ ¨§hL›ŔR)\Úú�áš�˙˙ýSŮÎÇ&‹7˙Mś"ŚŢ‡–kŠ: ”a!Á<QâşČc˘&hÉ« ±%ô÷˙ťßáąµýdń¨±%úĘ\b–ö‰R)d `ş3ńer­Ź���Ýô@‘C±úKă<UÝ›·HÉ9©°Şr“n …·˘ŢžŐR=*Úh…¬Š˝ÖGÝ˙ő×MKr˝*/W9Ěć«A Ă5Ę 9B#ë�ĺq��˙˙˙űRÄç Q±   łÉs5ŕĚ'ˇ˙îŠk•‘áRĺO˙˙aĽ˛y|o0R×ű’Â:ĆX~ó›%:iëÂaQ ŐpgÇŹíKřV$Ď+•ËţË0züĽ13Z3–X„Ăhą×âNÁű§ć’,CÇÜRY˛$RXęVR5���^3D˙˙˙·b[` ‹=ź˙ňâ˝4Vů¤EÉJ`ÉNą‚3ÍóÚbńtŞš246ĺe'WKzśőĎóç˙˙É+îý˙vókoR…ěS4”NZŇ<?˙űRÄé }µ@Ť=d¶!4 §Ůâ’dŐRu�ŁoüH$/D‘&|Č•®˙/8m¬f3/<ĽâÁţnjiĐgy]ĎÜ‘×{A;CŢUý¨íŞ–‡ě_ź˙çů˙ó?żś%"=Ě] $S`BBm(€ˇr hŐ���rKcI˙˙ęĆ82JKśĄş†U#«ľR}?ďÎ3ĎNă-/´ćÚÇS¸#˘Ł’0;s5ĄIĺÎ$jâ?b}Üż˙˙˙ţÍčW•Qhc”¬„şfşF` ’Č˙˙đ˙űRÄč‚ A±  ±Áź6`h!łi ź“»RkYA 4˙˙üľ?IßNo-öI-ůżâvú!ĚČŰĐHĄPA C=ŐSx”^¤=Dmô˙˙˙˙ô­÷Duu%§yčTIÇq` �ކ€�}|˙w˙¦á`0µ`˙ţéŽ[ŹSE¶ĺőĎřßdßó/ţľŤęXŤ v͆†h|„sv—¶Mž�ˇéˇŹÝďăĹeK?÷ßö{˙?6YŁ ^†«TËŠlˇđĺIšËő“QX#BJÄ!™ńËPô;>%-Đ˙űRÄé‚ IŻ ˇ ;IX6a´�Ťč› ˙˙˙±ĚĹp†@َ,Ěß˙ś%čw*LSł3ÁËěÝ209TÄžs)ʦEtşąT΋S©čVf{nĎFű˙˙˙µ:ŽŢB۶'¨bZÔ(–HË €™í“Ě”¨ ‹jľu��[ki�ţî—˙ŽŚŁe aöżůezŕ’<Ző2UÎE3-P˘p˘˛;™Ó1•Tňˇ*­IĘĂv:5ŐŐ¦–Wb˙˙˙˙KF˝mK^áĆŘ#�.•��˙˙ý˙űRÄëJń±ˇ UŃ?µâtŠ»ť<Čć(˛äb+‘=í5‰—١j˙ýŤjB5Śt{·ŤJĽb"ěđŇĹ $/7“Ű­Ę_˙‘fÜëćěËťť‰•›{ľ{9ď|Ú8>0 ë `…e���KkI?‹˛žrŘĂłçmŚ«Üó±)’Ň2ţÎÔĆ<ĄŘĎ•s9‰ş!YÔyĚbM™† **:Ě×mŃU[˙˙˙˙Ý™¨ö»:µdaö•ŹC (ąŠ9»î �?˙Ő˙űRÄőNµ�ŕ™ť¶ ô!&řŁ':¸—8µc…3¶ôéďsAĐ«őŞ'ßűŻ4÷{2ÓKcŐ>Č…X™‹eÄ43B3Ýn…ůź—ĺ˙oJ§ąSś™Śc$‰4šFĚWU®ˇ"M3ĄĂž*j(Cp ˘���rÝ« ˙˙Řd<ͱ¦ŚD8L».˙ Y“€¬=Ť{ą?]T.sž]…u¦m©9ÚťĘÝ *»ć°›ß_üżďçńő^_Ýő:­Vá[4®a÷’» Đ/*B„Ô ˙űRÄę m§  ­Ů”¶ ´!łˇŁËcDś§Ç󑔡Đ@ß*´żâ˛źK˙4#őp”[ˇkUI„G$7)ˇgÖÚ$ÍlÖˡÎł?˙˙űw§g»3vA„©ÂÜŞ@3ˇîF$Ę���rďł@˙˙ٲODd9ÂJ7"5ĎüĄţ×WBMsé[Ae[íCPĐ )ÓËË´ĎrSR6*O3z›1ůÄËŽ^ÇY˙˙óż;źg<„÷!MŐ]Ďfžá¦QCęi$zR˛Ó`˙űRÄë‚ éł  ±Ń“6 ¨!§ŮJá€6µ�˙˙˙˝ĐŠ1Ź~„¦˙ů&GIĐäćZ Î+×·ŇzZ®U$·s…)iw*•·78YäQÖ‘U«ń!žó˙ţţrGÜsn[Ň„çßä‚^eÚiśĚU‚ŻÉĐ.K*��߬E˙úô*ĺ“3Yħ˙ě’"§íöš)śłČŚó¶  ŞAjĄhÜP Ł ę*9`˘ j˙źźç˙ňżý·{łç­2eÎ@ÎÇNh˘˙űRÄë‚ ± Ť>ÉN6ačŠz´†Őł‚‰‘$ ·mcŚ�ttäŐ3ŢŢ^ó?żţw§G/>}Š«źĂ6őÚWMH(€çB1#Ş‹F‰Ć#ÖÎŽŁ˘Ŕ6˙ű?L‚V@���‚ďóH€äľnR…•2»d‹·gŹš÷‹[zľŕšÝ=Ć)ú,Ä˝>el×î‹h›ę©šŻźůëľú˙ţ8žőiżAšIĚÂ�łąĆ F<É$ń p†â„(żr4I˙ţ˙űRÄď -ݎ 7ɶ`ô!§h( L ”ă·t?Ă“ţűčşV¶áČs5B¶U)8Ť&°őH?pÄ  Ú۫ uŇ/5amë>ĺů˙˙˙˙ţß;3¶únôY#šKC˛ţ-RzĄşĚŔ±i˝��ľHŃ$ eąîÖ8fBŢ-vtď˙yŃfś…Dµł… óë=B“ĐszI›ba’Ş\‰T’“]–_??ď˙˙˙·÷;™{~uÚ˛cÓIUP%€ŚC+‡0ç8W-Ś€˙˙Ę-˙űRÄç‚ ]± ˇ Ý ˇă´�ŤŇ{™ Ó¤rěF‡,ČŇĎ'=AĽC—'émĺP™ Ş›ĘĂ[T¶łŰ‡&”ŹcŰňn•5-~Égů_˙‡3úiZĽ­÷ŚbŠŕÖ4ÉĹ g¬tÉH,Ś2†¦!ipM4 ‘ 4ĚŚŔ×—˙äÖË™ÉHČŐ–ˇ‘“YŤZËPÉ•”'#Y&˛Ë!”ą¬ś˛öJGüż˙ö‘¬ż#“,˛Ô5k,ŽFłĎ˛ć_ůË–( NŽFˇ¬‚šŠf\˙űRÄń Ył  Á‡¶!hšúrnŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞLAME3.97ŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞ˙űRÄě‚ ő± @oٶ`h¦řŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞ˙űRÄěË]°Ú €t��4€��ŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������beetbox-beets-01f1faf/test/rsrc/itunes_library_unix.xml���������������������������������������������0000664�0000000�0000000�00000015414�14723254774�0023524�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <plist version="1.0"> <dict> <key>Major Version</key><integer>1</integer> <key>Minor Version</key><integer>1</integer> <key>Date</key><date>2015-05-08T14:36:28Z</date> <key>Application Version</key><string>12.1.2.27</string> <key>Features</key><integer>5</integer> <key>Show Content Ratings</key><true/> <key>Music Folder</key><string>file:////Music/</string> <key>Library Persistent ID</key><string>1ABA8417E4946A32</string> <key>Tracks</key> <dict> <key>634</key> <dict> <key>Track ID</key><integer>634</integer> <key>Name</key><string>Tessellate</string> <key>Artist</key><string>alt-J</string> <key>Album Artist</key><string>alt-J</string> <key>Album</key><string>An Awesome Wave</string> <key>Genre</key><string>Alternative</string> <key>Kind</key><string>MPEG audio file</string> <key>Size</key><integer>5525212</integer> <key>Total Time</key><integer>182674</integer> <key>Disc Number</key><integer>1</integer> <key>Disc Count</key><integer>1</integer> <key>Track Number</key><integer>3</integer> <key>Track Count</key><integer>13</integer> <key>Year</key><integer>2012</integer> <key>Date Modified</key><date>2015-02-02T15:23:08Z</date> <key>Date Added</key><date>2014-04-24T09:28:38Z</date> <key>Bit Rate</key><integer>238</integer> <key>Sample Rate</key><integer>44100</integer> <key>Play Count</key><integer>0</integer> <key>Play Date</key><integer>3513593824</integer> <key>Skip Count</key><integer>3</integer> <key>Skip Date</key><date>2015-02-05T15:41:04Z</date> <key>Rating</key><integer>80</integer> <key>Album Rating</key><integer>80</integer> <key>Album Rating Computed</key><true/> <key>Artwork Count</key><integer>1</integer> <key>Sort Album</key><string>Awesome Wave</string> <key>Sort Artist</key><string>alt-J</string> <key>Persistent ID</key><string>20E89D1580C31363</string> <key>Track Type</key><string>File</string> <key>Location</key><string>file:///Music/Alt-J/An%20Awesome%20Wave/03%20Tessellate.mp3</string> <key>File Folder Count</key><integer>4</integer> <key>Library Folder Count</key><integer>2</integer> </dict> <key>636</key> <dict> <key>Track ID</key><integer>636</integer> <key>Name</key><string>Breezeblocks</string> <key>Artist</key><string>alt-J</string> <key>Album Artist</key><string>alt-J</string> <key>Album</key><string>An Awesome Wave</string> <key>Genre</key><string>Alternative</string> <key>Kind</key><string>MPEG audio file</string> <key>Size</key><integer>6827195</integer> <key>Total Time</key><integer>227082</integer> <key>Disc Number</key><integer>1</integer> <key>Disc Count</key><integer>1</integer> <key>Track Number</key><integer>4</integer> <key>Track Count</key><integer>13</integer> <key>Year</key><integer>2012</integer> <key>Date Modified</key><date>2015-02-02T15:23:08Z</date> <key>Date Added</key><date>2014-04-24T09:28:38Z</date> <key>Bit Rate</key><integer>237</integer> <key>Sample Rate</key><integer>44100</integer> <key>Play Count</key><integer>31</integer> <key>Play Date</key><integer>3513594051</integer> <key>Play Date UTC</key><date>2015-05-04T12:20:51Z</date> <key>Skip Count</key><integer>0</integer> <key>Rating</key><integer>100</integer> <key>Album Rating</key><integer>80</integer> <key>Album Rating Computed</key><true/> <key>Artwork Count</key><integer>1</integer> <key>Sort Album</key><string>Awesome Wave</string> <key>Sort Artist</key><string>alt-J</string> <key>Persistent ID</key><string>D7017B127B983D38</string> <key>Track Type</key><string>File</string> <key>Location</key><string>file://localhost/Music/Alt-J/An%20Awesome%20Wave/04%20Breezeblocks.mp3</string> <key>File Folder Count</key><integer>4</integer> <key>Library Folder Count</key><integer>2</integer> </dict> <key>638</key> <dict> <key>Track ID</key><integer>638</integer> <key>Name</key><string>❦ (Ripe & Ruin)</string> <key>Artist</key><string>alt-J</string> <key>Album Artist</key><string>alt-J</string> <key>Album</key><string>An Awesome Wave</string> <key>Kind</key><string>MPEG audio file</string> <key>Size</key><integer>2173293</integer> <key>Total Time</key><integer>72097</integer> <key>Disc Number</key><integer>1</integer> <key>Disc Count</key><integer>1</integer> <key>Track Number</key><integer>2</integer> <key>Track Count</key><integer>13</integer> <key>Year</key><integer>2012</integer> <key>Date Modified</key><date>2015-05-09T17:04:53Z</date> <key>Date Added</key><date>2015-02-02T15:28:39Z</date> <key>Bit Rate</key><integer>233</integer> <key>Sample Rate</key><integer>44100</integer> <key>Play Count</key><integer>8</integer> <key>Play Date</key><integer>3514109973</integer> <key>Play Date UTC</key><date>2015-05-10T11:39:33Z</date> <key>Skip Count</key><integer>1</integer> <key>Skip Date</key><date>2015-02-02T15:29:10Z</date> <key>Album Rating</key><integer>80</integer> <key>Album Rating Computed</key><true/> <key>Artwork Count</key><integer>1</integer> <key>Sort Album</key><string>Awesome Wave</string> <key>Sort Artist</key><string>alt-J</string> <key>Persistent ID</key><string>183699FA0554D0E6</string> <key>Track Type</key><string>File</string> <key>Location</key><string>file:///Music/Alt-J/An%20Awesome%20Wave/02%20%E2%9D%A6%20(Ripe%20&%20Ruin).mp3</string> <key>File Folder Count</key><integer>4</integer> <key>Library Folder Count</key><integer>2</integer> </dict> </dict> <key>Playlists</key> <array> <dict> <key>Name</key><string>Library</string> <key>Master</key><true/> <key>Playlist ID</key><integer>11480</integer> <key>Playlist Persistent ID</key><string>CD6FF684E7A6A166</string> <key>Visible</key><false/> <key>All Items</key><true/> <key>Playlist Items</key> <array> <dict> <key>Track ID</key><integer>634</integer> </dict> <dict> <key>Track ID</key><integer>636</integer> </dict> <dict> <key>Track ID</key><integer>638</integer> </dict> </array> </dict> <dict> <key>Name</key><string>Music</string> <key>Playlist ID</key><integer>16906</integer> <key>Playlist Persistent ID</key><string>4FB2E64E0971DD45</string> <key>Distinguished Kind</key><integer>4</integer> <key>Music</key><true/> <key>All Items</key><true/> <key>Playlist Items</key> <array> <dict> <key>Track ID</key><integer>634</integer> </dict> <dict> <key>Track ID</key><integer>636</integer> </dict> <dict> <key>Track ID</key><integer>638</integer> </dict> </array> </dict> </array> </dict> </plist> ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������beetbox-beets-01f1faf/test/rsrc/itunes_library_windows.xml������������������������������������������0000664�0000000�0000000�00000016067�14723254774�0024240�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <plist version="1.0"> <dict> <key>Major Version</key><integer>1</integer> <key>Minor Version</key><integer>1</integer> <key>Date</key><date>2015-05-11T15:27:14Z</date> <key>Application Version</key><string>12.1.2.27</string> <key>Features</key><integer>5</integer> <key>Show Content Ratings</key><true/> <key>Music Folder</key><string>file://localhost/C:/Documents%20and%20Settings/Owner/My%20Documents/My%20Music/iTunes/iTunes%20Media/</string> <key>Library Persistent ID</key><string>B4C9F3EE26EFAF78</string> <key>Tracks</key> <dict> <key>180</key> <dict> <key>Track ID</key><integer>180</integer> <key>Name</key><string>Tessellate</string> <key>Artist</key><string>alt-J</string> <key>Album Artist</key><string>alt-J</string> <key>Album</key><string>An Awesome Wave</string> <key>Genre</key><string>Alternative</string> <key>Kind</key><string>MPEG audio file</string> <key>Size</key><integer>5525212</integer> <key>Total Time</key><integer>182674</integer> <key>Disc Number</key><integer>1</integer> <key>Disc Count</key><integer>1</integer> <key>Track Number</key><integer>3</integer> <key>Track Count</key><integer>13</integer> <key>Year</key><integer>2012</integer> <key>Date Modified</key><date>2015-02-02T15:23:08Z</date> <key>Date Added</key><date>2014-04-24T09:28:38Z</date> <key>Bit Rate</key><integer>238</integer> <key>Sample Rate</key><integer>44100</integer> <key>Play Count</key><integer>0</integer> <key>Play Date</key><integer>3513593824</integer> <key>Skip Count</key><integer>3</integer> <key>Skip Date</key><date>2015-02-05T15:41:04Z</date> <key>Rating</key><integer>80</integer> <key>Album Rating</key><integer>80</integer> <key>Album Rating Computed</key><true/> <key>Artwork Count</key><integer>1</integer> <key>Sort Album</key><string>Awesome Wave</string> <key>Sort Artist</key><string>alt-J</string> <key>Persistent ID</key><string>20E89D1580C31363</string> <key>Track Type</key><string>File</string> <key>Location</key><string>file://localhost/G:/Music/Alt-J/An%20Awesome%20Wave/03%20Tessellate.mp3</string> <key>File Folder Count</key><integer>-1</integer> <key>Library Folder Count</key><integer>-1</integer> </dict> <key>183</key> <dict> <key>Track ID</key><integer>183</integer> <key>Name</key><string>Breezeblocks</string> <key>Artist</key><string>alt-J</string> <key>Album Artist</key><string>alt-J</string> <key>Album</key><string>An Awesome Wave</string> <key>Genre</key><string>Alternative</string> <key>Kind</key><string>MPEG audio file</string> <key>Size</key><integer>6827195</integer> <key>Total Time</key><integer>227082</integer> <key>Disc Number</key><integer>1</integer> <key>Disc Count</key><integer>1</integer> <key>Track Number</key><integer>4</integer> <key>Track Count</key><integer>13</integer> <key>Year</key><integer>2012</integer> <key>Date Modified</key><date>2015-02-02T15:23:08Z</date> <key>Date Added</key><date>2014-04-24T09:28:38Z</date> <key>Bit Rate</key><integer>237</integer> <key>Sample Rate</key><integer>44100</integer> <key>Play Count</key><integer>31</integer> <key>Play Date</key><integer>3513594051</integer> <key>Play Date UTC</key><date>2015-05-04T12:20:51Z</date> <key>Skip Count</key><integer>0</integer> <key>Rating</key><integer>100</integer> <key>Album Rating</key><integer>80</integer> <key>Album Rating Computed</key><true/> <key>Artwork Count</key><integer>1</integer> <key>Sort Album</key><string>Awesome Wave</string> <key>Sort Artist</key><string>alt-J</string> <key>Persistent ID</key><string>D7017B127B983D38</string> <key>Track Type</key><string>File</string> <key>Location</key><string>file://localhost/G:/Music/Alt-J/An%20Awesome%20Wave/04%20Breezeblocks.mp3</string> <key>File Folder Count</key><integer>-1</integer> <key>Library Folder Count</key><integer>-1</integer> </dict> <key>638</key> <dict> <key>Track ID</key><integer>638</integer> <key>Name</key><string>❦ (Ripe & Ruin)</string> <key>Artist</key><string>alt-J</string> <key>Album Artist</key><string>alt-J</string> <key>Album</key><string>An Awesome Wave</string> <key>Kind</key><string>MPEG audio file</string> <key>Size</key><integer>2173293</integer> <key>Total Time</key><integer>72097</integer> <key>Disc Number</key><integer>1</integer> <key>Disc Count</key><integer>1</integer> <key>Track Number</key><integer>2</integer> <key>Track Count</key><integer>13</integer> <key>Year</key><integer>2012</integer> <key>Date Modified</key><date>2015-05-09T17:04:53Z</date> <key>Date Added</key><date>2015-02-02T15:28:39Z</date> <key>Bit Rate</key><integer>233</integer> <key>Sample Rate</key><integer>44100</integer> <key>Play Count</key><integer>8</integer> <key>Play Date</key><integer>3514109973</integer> <key>Play Date UTC</key><date>2015-05-10T11:39:33Z</date> <key>Skip Count</key><integer>1</integer> <key>Skip Date</key><date>2015-02-02T15:29:10Z</date> <key>Album Rating</key><integer>80</integer> <key>Album Rating Computed</key><true/> <key>Artwork Count</key><integer>1</integer> <key>Sort Album</key><string>Awesome Wave</string> <key>Sort Artist</key><string>alt-J</string> <key>Persistent ID</key><string>183699FA0554D0E6</string> <key>Track Type</key><string>File</string> <key>Location</key><string>file://localhost/G:/Experiments/Alt-J/An%20Awesome%20Wave/02%20%E2%9D%A6%20(Ripe%20&%20Ruin).mp3</string> <key>File Folder Count</key><integer>4</integer> <key>Library Folder Count</key><integer>2</integer> </dict> </dict> <key>Playlists</key> <array> <dict> <key>Name</key><string>Bibliotheek</string> <key>Master</key><true/> <key>Playlist ID</key><integer>72</integer> <key>Playlist Persistent ID</key><string>728AA5B1D00ED23B</string> <key>Visible</key><false/> <key>All Items</key><true/> <key>Playlist Items</key> <array> <dict> <key>Track ID</key><integer>180</integer> </dict> <dict> <key>Track ID</key><integer>183</integer> </dict> <dict> <key>Track ID</key><integer>638</integer> </dict> </array> </dict> <dict> <key>Name</key><string>Muziek</string> <key>Playlist ID</key><integer>103</integer> <key>Playlist Persistent ID</key><string>8120A002B0486AD7</string> <key>Distinguished Kind</key><integer>4</integer> <key>Music</key><true/> <key>All Items</key><true/> <key>Playlist Items</key> <array> <dict> <key>Track ID</key><integer>180</integer> </dict> <dict> <key>Track ID</key><integer>183</integer> </dict> <dict> <key>Track ID</key><integer>638</integer> </dict> </array> </dict> </array> </dict> </plist> �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������beetbox-beets-01f1faf/test/rsrc/lyrics/�������������������������������������������������������������0000775�0000000�0000000�00000000000�14723254774�0020204�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������beetbox-beets-01f1faf/test/rsrc/lyrics/examplecom/��������������������������������������������������0000775�0000000�0000000�00000000000�14723254774�0022336�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������beetbox-beets-01f1faf/test/rsrc/lyrics/examplecom/beetssong.txt�������������������������������������0000664�0000000�0000000�00000070343�14723254774�0025077�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <html> <head> <title>John Doe - beets song Lyrics

beets song Lyrics



John Doe beets song lyrics
Lyrics search for Artist - Song:

Back to the: Music Lyrics > John Doe lyrics > beets song lyrics

John Doe
beets song lyrics

Ringtones left icon Send "beets song" Ringtone to your Cell Ringtones right icon

Beets is the media library management system for obsessive music geeks.
The purpose of beets is to get your music collection right once and for all. It catalogs your collection, automatically improving its metadata as it goes. It then provides a bouquet of tools for manipulating and accessing your music.
Here's an example of beets' brainy tag corrector doing its thing: Because beets is designed as a library, it can do almost anything you can imagine for your music collection. Via plugins, beets becomes a panacea
Ringtones left icon Send "beets song" Ringtone to your Cell Ringtones right icon

Share beets song lyrics

  RATE THIS SONG!

Add to Favorites Lyrics Email to a Friend John Doe - beets song Lyrics
Rating:

Use the following form to post your meaning of this song, rate it, or submit comments about this song.


0
Name:
Comment:
Type maps backwards (spam prevention):


There are no comments for this song yet.
ToneFuse Music

beetbox-beets-01f1faf/test/rsrc/lyrics/geniuscom/000077500000000000000000000000001472325477400221755ustar00rootroot00000000000000beetbox-beets-01f1faf/test/rsrc/lyrics/geniuscom/2pacalleyezonmelyrics.txt000066400000000000000000020146151472325477400272670ustar00rootroot00000000000000 2Pac – All Eyez On Me Lyrics | Genius Lyrics

All Eyez On Me Lyrics

[Intro: 2Pac]
Big Syke, 'Nook, Hank, Bogart, Big Sur (yeah)
Y'all know how this shit go (you know)
All eyes on me
Motherfuckin' OG
Roll up in the club and shit, is that right?
All eyes on me
All eyes on me
But you know what?

[Verse 1: 2Pac]
I bet you got it twisted, you don't know who to trust
So many player-hatin' niggas tryna sound like us

Say they ready for the funk, but I don't think they knowin'
Straight to the depths of Hell is where those cowards goin'
Well, are you still down? Nigga, holla when you see me

And let these devils be sorry for the day they finally freed me
I got a caravan of niggas every time we ride
Hittin' motherfuckers up when we pass by
Until I die, live the life of a boss player 'cause even when I'm high
Fuck with me and get crossed later, the futures in my eyes
'cause all I want is cash and things
A five-double-0 Benz, flauntin' flashy rings, uhh
Bitches pursue me like a dream
Been known to disappear before your eyes just like a dope fiend
It seems, my main thing was to be major paid
The game sharper than a motherfuckin' razor blade
Say money bring bitches and bitches bring lies
One nigga's gettin' jealous and motherfuckers died
Depend on me like the first and fifteenth
They might hold me for a second, but these punks won't get me

We got foe niggas and low riders in ski masks
Screamin', "Thug Life!" every time they pass, all eyes on me
[Chorus: 2Pac]
Live the life of a thug nigga until the day I die
Live the life of a boss player (All eyes on me) 'cause even gettin' high
All eyes on me
Live the life of a thug nigga until the day I die
Live the life of a boss player 'cause even gettin' high

[Interlude: Big Syke]
Hey, to my nigga Pac

[Verse 2: Big Syke]
So much trouble in the world, nigga
Can't nobody feel your pain
The world's changin' every day, time's movin' fast
My girl said I need a raise, how long will she last?
I'm caught between my woman and my pistol and my chips
Triple beam, got some smokers on, whistle as I dip
I'm lost in the land with no plan, livin' life flawless
Crime boss, contraband, let me toss this
Mediocres got a lot of nerve, let my bucket swerve
I'm takin' off from the curb
The nervousness neglect make me pack a TEC
Devoted to servin' this Moët and pay checks
Like Akai satellite, nigga, I'm forever ballin'
It ain't right: parasites, triggers, and fleas crawlin'
Sucker, duck and get busted, no emotion
My devotion is handlin' my business, nigga, keep on coastin'
Where you goin', I been there, came back as lonely, homie
Steady flowin' against the grain, niggas still don't know me
It's about the money in this rap shit, this crap shit
It ain't funny, niggas don't even know how to act, shit
What can I do? What can I say? Is there another way?
Blunts and gin all day, 24 parlay
My little homie G, can't you see I'm buster-free?
Niggas can't stand me; all eyes on me
[Chorus: 2Pac]
Live the life of a thug nigga until the day I die
Live the life of a boss player 'cause even gettin' high
All eyes on me
All eyes on me
Live the life of a thug nigga until the day I die
Live the life of a boss player 'cause even gettin' high
All eyes on me

[Verse 3: 2Pac]
The feds is watchin', niggas plottin' to get me
Will I survive? Will I die? Come on, let's picture the possibility
Givin' me charges, lawyers makin' a grip
I told the judge I was raised wrong and that's why I blaze shit
Was hyper as a kid, cold as a teenager
On my mobile, callin' big shots on the scene major
Packin' hundreds in my drawers, fuck the law
Bitches, I fuck with a passion, I'm livin' rough and raw
Catchin' cases at a fast rate, ballin' in the fast lane
Hustle 'til the mornin', never stopped until the cash came
Live my life as a thug nigga until the day I die
Live my life as a boss player, 'cause even gettin' high
These niggas got me tossin' shit
I put the top down, now it's time to floss my shit
Keep your head up, nigga, make these motherfuckers suffer
Up in the Benz, burnin' rubber
The money is mandatory, the hoes is for the stress
This criminal lifestyle, equipped with the bulletproof vest
Make sure your eyes is on the meal ticket
Get your money, motherfucker, let's get rich and we'll kick it
All eyes on me
[Chorus: 2Pac]
Live the life as a thug nigga until the day I die
Live the life as a boss player 'cause even gettin' high
All eyes on me
All eyes on me
Live the life of a thug nigga until the day I die
Live the life of a boss player 'cause even gettin' high
All eyes on me

[Outro: 2Pac]
Pay attention, my niggas! See how that shit go?
Nigga walk up in this motherfucker and it be like, "Bing!"
Cops, bitches, every-motherfuckin'-body

(Live my life as a thug nigga until the day I die)
(Live my life as a boss playa, 'cause even gettin' high)
I got bustas, hoes, and police watchin' a nigga, you know?
(I live my life as a thug nigga until the day I die)
(Livin' life as a boss playa, 'cause even gettin' high)
He he he, it's like what they think
I'm walkin' around with some ki's in my pocket or somethin'
They think I'm goin' back to jail, they really on that dope
(Live my life as a thug nigga until the day I die)
(Live my life as a boss playa)
I know y'all watchin', I know y'all got me in the scopes
(Live my life as a thug nigga until the day I die)
(Live my life as a boss playa, 'cause even gettin' high)
I know y'all know this is Thug Life, baby!
Y'all got me under surveillance, huh?
All eyes on me, but I'm knowin'

How to Format Lyrics:

  • Type out all lyrics, even repeating song parts like the chorus
  • Lyrics should be broken down into individual lines
  • Use section headers above different song parts like [Verse], [Chorus], etc.
  • Use italics (<i>lyric</i>) and bold (<b>lyric</b>) to distinguish between different vocalists in the same song part
  • If you don’t understand a lyric, use [?]

To learn more, check out our transcription guide or visit our transcribers forum

About

Genius Annotation

The title track off of 2Pac’s album All Eyez on Me samples Linda Clifford’s “Never Gonna Stop” (also used for Nas' “Street Dreams,” which was released a few months later in July 1996). Producer Johnny J recalls connecting with 2Pac for this track:

That was the very first track I laid when we got together at Death Row. When he just got out of jail, just got released, two days later he’s like, “â€J’, get to the studio, I’m with Death Row now.” I assumed it was a joke, somebody perpetrating Tupac. I’m like “Hell no – â€Pac is locked up!” He’s like “J, I’m out” I walk in, 15 minutes into the session, the first beat I put in the drum machine is “All Eyez On Me.” I wasn’t going to show him the track, honestly. I was like, “This track? Nah, it’s not finished. It’s incomplete.” My wife says, “Hey, it’s a dope beat!” So I just pop it in, titles just come right off his fuckin’ head.

This classic gives us an idea of what the media was doing with Pac’s life. At this moment, all eyes in the music world were on him due to the intrigue around his release from prison, signing with the notorious Death Row Records, as well as the 2Pac/Death Row/West Coast vs. Biggie/Bad Boy/East Coast beef.

Q&A

Find answers to frequently asked questions about the song and explore its deeper meaning

What did 2Pac say about "All Eyez On Me"?
Genius Answer

Kendrick on the Impact of Tupacs All Eyez on Me and California Love Video Shoot

“I was 8 yrs old when I first saw you. I couldn’t describe how I felt at that moment. So many emotions. Full of excitement. Full of joy and eagerness. 20 yrs later I understand exactly what that feeling was. INSPIRED,” Lamar wrote (via Pitchfork). “The people that you touched on that small intersection changed lives forever. I told myself I wanted to be a voice for man one day. Whoever knew I was speaking out loud for u to listen. Thank you, K.L.”

As Lamar told Rolling Stone, he was eight years old when he sat atop his father’s shoulder and witnessed Tupac and Dr. Dre film the video for “California Love” at the Compton Swap Meet. “I want to say they were in a white Bentley,” Lamar said. “These motorcycle cops trying to conduct traffic but one almost scraped the car, and Pac stood up on the passenger seat, like, â€Yo, what the fuck!’ Yelling at the police, just like on his motherfucking songs. He gave us what we wanted.” Lamar would later shoot a scene at that same swap meet for his “King Kunta” music video, a nod to 2Pac.

Credits
Featuring
Producer
Phonographic Copyright â„—
Mixing Engineer
Assistant Engineer
Mastering Engineer
Recorded At
Can-Am Studios (Tarzana, CA)
Release Date
February 13, 1996
View All Eyez On Me samples
Tags
Comments
beetbox-beets-01f1faf/test/rsrc/lyrics/geniuscom/Ttngchinchillalyrics.txt000066400000000000000000005652461472325477400271410ustar00rootroot00000000000000 TTNG – Chinchilla Lyrics | Genius Lyrics

🚧  The new song page is now the default experience! We need your help to continue improving contributor features.  🚧

So far we've lost focus
Let's just concentrate on words that could mean everything

On nights like this
We drink ourselves dry
And make promises
Without intention

So fortunate that this was brought up
The last time. As I recall
I can’t hold up your every expectation

On nights like this
We drink ourselves dry
And make promises
Without intention

My God, is this what we’ve become?
Living parodies of love and loss
Can we really be all that lost?

So fortunate that this was brought up
The last time. As I recall
I can’t hold up your every expectation

One moment to another I am restless
Seems making love forever can often risk your heart
And I cannot remember when I was this messed up
In service of another I am beautiful
How to Format Lyrics:
  • Type out all lyrics, even if it’s a chorus that’s repeated throughout the song
  • The Section Header button breaks up song sections. Highlight the text then click the link
  • Use Bold and Italics only to distinguish between different singers in the same verse.
    • E.g. “Verse 1: Kanye West, Jay-Z, Both”
  • Capitalize each line
  • To move an annotation to different lyrics in the song, use the [...] menu to switch to referent editing mode

About

This song bio is unreviewed
Genius Annotation

This song is about those relationships with a lot of fights and reconciliations. The singer and his couple are aruging/reconciliating, telling themselves everything is going to be better and things will change for good, specially when they get drunk, just to fight and reconciliate over and over again.

Ask us a question about this song
No questions asked yet
Credits
Written By
Stuart Smith
Release Date
October 13, 2008
Tags
Comments
Add a comment
Get the conversation started
Be the first to comment
beetbox-beets-01f1faf/test/rsrc/lyrics/geniuscom/Wutangclancreamlyrics.txt000066400000000000000000031407551472325477400273160ustar00rootroot00000000000000 Wu-Tang Clan – C.R.E.A.M. Lyrics | Genius Lyrics
{{:: 'cloud_flare_always_on_short_message' | i18n }}
Check @genius for updates. We'll have things fixed soon.
Https%3a%2f%2fimages

C.R.E.A.M.

Wu-Tang Clan

About “C.R.E.A.M.”

Arguably one of the most iconic songs in hip-hop, the underlying idea of “C.R.E.A.M.” is found in its title—cash rules everything. The timeless piano riffs and background vocals come from a chopped up sample of The Charmels†1967 record, “As Long As I’ve Got You,” that make up the entire track.

Although it was released as an official single in 1994, “C.R.E.A.M.” was first recorded in 1991, around the same time as RZA’s assault case, and featured himself and Ghostface Killah. The track went through several revisions and was later re-recorded by Raekwon and Inspectah Deck in 1993—an early title of the song was “Lifestyles of the Mega-Rich.”

In 2017, RZA explained to Power 106 how the final version of the track came together:

Once we got to the studio, I decided that this track had to be on the Wu-Tang album. I reminded Rae and Deck of their verses—their verses were long. […] Method Man, the master of hooks at the time, came in with this hook right here: â€cash rules everything around me, cream, get the money.’ Once he added that element, I knew it was going to be a smash.

Since its release, the song and chorus have been referenced countless times by several artists. It has also been featured in movies such as Eminem’s 8 Mile and the N.W.A biopic, Straight Outta Compton.

  • What has RZA said about the song?

  • What has Raekwon said about the song?

    â€C.R.E.A.M.’ did a lot for my career personally. It gave me an opportunity to revisit the times where that cream meant that much to us. So, yeah, when I think of this record it just automatically puts me back into â€87/’88 where we were standing in front of the building. It’s cold outside. We didn’t care. We’re out there, all black on trying to make dollars. Just trying to make some money and trying to eat. Survive.

    This song, I remember writing to the beat a long time ago before we actually came out. That beat is old. That was probably like a â€89 beat. RZA had it that long because he had a bunch of breaks. He had all kind of things and he was making beats back then, but we was just picking and that beat happened to always sit around and I would be like, â€I want that beat, so don’t give that beat to nobody.’ And he kept his word and let me have it.

    Meth came up with the hook but our dude named Raider Ruckus, this was like Meth’s homeboy back then, like they was real close, he came up with the phrase â€cash rules everything around me.’ So when he showed Meth what it was and was like, â€Cash rules everything around me,’ Meth was like, â€Word, you right!’ And turned it into a movie, and I came in later that day and heard it and co-signed it.

    via Complex

  • What has U-God said about the song?

    “C.R.E.A.M.” is a true song. Everything Inspectah Deck and Raekwon said is 100 percent true. Not one line in that entire song is a lie, or even a slight exaggeration. Deck did sell base, and he did go to jail at the age of fifteen. Rae was sticking up white boys on ball courts, rocking the same damn ’Lo sweater. And of “course, Meth on the hook was like butter on the popcorn. Meth knew the hard times, too, being out there smoking woolies and pumping crack, etc. That raspy shit he was kicking just echoed in everyone’s head long after the song was done playing.

    The realism on “C.R.E.A.M.” is what resonates with so many people all over the world. People everywhere know that sentiment of being slaves to the dollar. Cash is king, and we are its lowly subjects. That’s pretty much the case in every nation around the world, the desperation to put your life and your freedom on the line to make a couple dollars. Whether you’re working, stripping, hustling, or slinging, whether you’re a business owner or homeless, cash rules everything around us.

    Source: Raw:My Journey into Wu-Tang

  • What songs were sampled on the beat for “C.R.E.A.M.?”

    The vocals and background sample that can be heard on the song’s intro were taken taken from The Charmels’ 1967 song “As Long as I’ve Got You”:

    The classic keys sample that can be heard throughout the beat was also taken from the previously mentioned song:

  • What has Method Man said about the song?

    Meth told Complex,

    â€C.R.E.A.M.’ was the one that really put us on the map if you wanna be technical. I wasn’t there when they recorded â€C.R.E.A.M.’ I came in after the fact. RZA was like, â€Put a hook on this song’ and I put a hook on it. That’s how it always went. I liked doing hooks.

    The hook for that was done by my man Raider Ruckus. We used to work at the Statue of Liberty and when we were coming home we used to come up with all these made-up words that were acronyms.

    We had words like â€BIBWAM’ which meant, â€Bitches Is Busted Without A Man’ and all this other crazy shit. Raider Ruckus was so ill with the way he put the words together. We would call money â€cream’ so he took each letter and made a word out of it and killed it the way he did it.

    Something like that had never been done before as far as a hook or even a way of speaking. This is just showing and proving that we paid attention in class when we was kids. You can’t do shit like that unless you got a brain in your fucking head! You got to have some level of intelligence to do something like that.

    The best acronym for a word that I heard was â€P.R.O.J.E.C.T.S.’ by Killah Priest. He said â€People Relying On Just Enough Cash To Survive.’ And he’s the one that came up with â€Basic Instructions Before Leaving Earth,’ the acronym for B.I.B.L.E. This ain’t no fluke shit man.

    There’s a reason you got millions upon millions of fucking kids running around with Wu-Tang tattoos. You don’t just put something on your body permanently unless it’s official. At that time, when you’re coming out brand new and representing where you come from, everybody from that area wants you to win because they win. That’s what it was like for us.

    We were the only dudes from Staten Island doing it so everybody from Staten Island wanted us to win. Not just dudes from Staten Island, but dudes from Brooklyn too because they had peoples in the group too. Then it was just grimy niggas who loved to see real shit, saying, â€We riding with them Wu-Tang niggas. Fuck all that shiny suit shit!’ That ain’t no take on Puff, a lot of niggas was wearing suits and shit man, but that ain’t us.

"C.R.E.A.M." Track Info

beetbox-beets-01f1faf/test/rsrc/lyrics/geniuscom/sample.txt000066400000000000000000000242071472325477400242240ustar00rootroot00000000000000 SAMPLE – SONG Lyrics | g-example Lyrics
#

SONG

SAMPLE

SONG Lyrics

!!!! MISSING LYRICS HERE !!!
More on g-example
beetbox-beets-01f1faf/test/rsrc/lyrics/tekstowopl/000077500000000000000000000000001472325477400224175ustar00rootroot00000000000000beetbox-beets-01f1faf/test/rsrc/lyrics/tekstowopl/piosenka24kgoldncityofangels1.txt000077500000000000000000002744431472325477400310500ustar00rootroot00000000000000 24kGoldn - City Of Angels - tekst i tłumaczenie piosenki na Tekstowo.pl
2 170 744 tekstĂłw, 20 217 poszukiwanych i 501 oczekujÄ…cych

24kGoldn - City Of Angels

Tekst dodał(a): asdfghjklmnop Edytuj tekst
Tłumaczenie dodał(a): tapcapslock Edytuj tłumaczenie
Teledysk dodał(a): olcia_197 Edytuj teledysk

Tekst piosenki:

[Chorus]
I sold my soul to the devil for designer
They said, "Go to hell," but I told 'em I don’t wanna
If you know me well, then you know that I ain't goin'
’Cause I don't wanna, I don't wanna
I don't wanna die young
The city of angels where I have my fun
Don't wanna die young
When I'm gone, remember all I've done-one

[Verse]
We've had our fun-un
But now I’m done-one
’Cause you crazy (Yeah), I can't take it (No)
Just wanted to see you naked
Heard time like money, can’t waste it
What's the price of fame? 'Cause I can taste it
So I'm chasin’ (Yeah), and I'm facin'
A little Hennessy, it might be good for me

[Chorus]
I sold my soul to the devil for designer
They said, "Go to hell," but I told 'em I don't wanna
If you know me well, then you know that I ain't goin'
'Cause I don't wanna, I don't wanna
I don't wanna die young
The city of angels where I have my fun
Don't wanna die young
When I'm gone, remember all I've done-one
Dodaj interpretację do tego tekstu »

 

Historia edycji tekstu

Tłumaczenie:

Pokaż tłumaczenie
[ChĂłr]
Sprzedałem duszę diabłu za projektanta
Powiedzieli „Idź do piekła”, ale powiedziałem im, że nie chcę
Jeśli dobrze mnie znasz, to wiesz, że nie idę
Bo nie chcÄ™, nie chcÄ™
Nie chcę umrzeć młodo
Miasto aniołów, w którym dobrze się bawię
Nie chcę umrzeć młodo
Kiedy odejdę, pamiętaj wszystko, co zrobiłem

[Werset]
Mieliśmy naszą zabawę
Ale teraz skończyłem
Bo jesteś szalony (Tak), nie mogę tego znieść (Nie)
Chciałem tylko zobaczyć cię nago
Słyszałem czas jak pieniądze, nie można go marnować
Jaka jest cena sławy? Bo mogę to posmakować
Więc chasin '(Yeah) i patrzę
Trochę Hennessy, może być dla mnie dobre

[ChĂłr]
Sprzedałem duszę diabłu za projektanta
Powiedzieli „Idź do piekła”, ale powiedziałem im, że nie chcę
Jeśli dobrze mnie znasz, to wiesz, że nie idę
Bo nie chcÄ™, nie chcÄ™
Nie chcę umrzeć młodo
Miasto aniołów, w którym dobrze się bawię
Nie chcę umrzeć młodo
Kiedy odejdę, pamiętaj wszystko, co zrobiłem

 

Historia edycji tłumaczenia
Rok wydania:

2019

Edytuj metrykÄ™
Płyty:

Dropped Outta College

Komentarze (0):

2 170 744 tekstĂłw, 20 217 poszukiwanych i 501 oczekujÄ…cych

Największy serwis z tekstami piosenek w Polsce. Każdy może znaleźć u nas teksty piosenek, teledyski oraz tłumaczenia swoich ulubionych utworów.
Zachęcamy wszystkich użytkowników do dodawania nowych tekstów, tłumaczeń i teledysków!


Reklama | Kontakt | FAQ Polityka prywatności
beetbox-beets-01f1faf/test/rsrc/lyrics/tekstowopl/piosenkabaileybiggerblackeyedsusan.txt000077500000000000000000002712761472325477400322770ustar00rootroot00000000000000 Bailey Bigger - Black Eyed Susan - tekst i tłumaczenie piosenki na Tekstowo.pl
2 170 745 tekstĂłw, 20 217 poszukiwanych i 502 oczekujÄ…cych

Bailey Bigger - Black Eyed Susan

Tekst dodał(a): Adelle Edytuj tekst
Tłumaczenie dodał(a): brak Dodaj tłumaczenie
Teledysk dodał(a): Adelle Edytuj teledysk

Tekst piosenki:

Black eyed Susan
Sun shines in your veins
If the clouds are moving
Never hear her complain
Yeah, black eyed Susan
Just waiting on a drop of rain

Black eyed Susan
Stands true and tall
If the storms are brewing
She ain't worried at all
Yeah, black eyed Susan
Just waiting on a drop to fall

Everyone calls her the sunflower
And no one knows her name
She's a little girl on the side of the road
Waiting on a drop of rain
Rain
Rain

Black eyed Susan
Ain't it just a shame?
'Cause now you're losing
All your petals in a vase
In a vase
She got picked and I never really knew her name
Everyone calls her the sunflower
And no one knows her name
She's a little girl on the side of the road
Waiting on a drop of rain
Rain
Rain
Yeah, she got picked and I never really knew her name
Dodaj interpretację do tego tekstu »

 

Historia edycji tekstu

Tłumaczenie:

Niestety nikt nie dodał jeszcze tłumaczenia tego utworu.

Dodaj tłumaczenie lub wyślij prośbę o tłumaczenie

 

Autor:

(brak)

Edytuj metrykÄ™

Komentarze (0):

2 170 745 tekstĂłw, 20 217 poszukiwanych i 502 oczekujÄ…cych

Największy serwis z tekstami piosenek w Polsce. Każdy może znaleźć u nas teksty piosenek, teledyski oraz tłumaczenia swoich ulubionych utworów.
Zachęcamy wszystkich użytkowników do dodawania nowych tekstów, tłumaczeń i teledysków!


Reklama | Kontakt | FAQ Polityka prywatności
piosenkabeethovenbeethovenpianosonata17tempestthe3rdmovement.txt000077500000000000000000002576251472325477400374320ustar00rootroot00000000000000beetbox-beets-01f1faf/test/rsrc/lyrics/tekstowopl Beethoven - Beethoven Piano Sonata 17 Tempest The 3rd Movement - na Tekstowo.pl
2 170 744 tekstĂłw, 20 217 poszukiwanych i 502 oczekujÄ…cych

Beethoven - Beethoven Piano Sonata 17 Tempest The 3rd Movement

Utwór dodał(a): anmar09
Utwór instrumentalny Ten utwór ma słowa? Dodaj tekst
Teledysk dodał(a): anmar09 Edytuj teledysk
Ścieżka dźwiękowa:

PokojĂłwka

Komentarze (0):

2 170 744 tekstĂłw, 20 217 poszukiwanych i 502 oczekujÄ…cych

Największy serwis z tekstami piosenek w Polsce. Każdy może znaleźć u nas teksty piosenek, teledyski oraz tłumaczenia swoich ulubionych utworów.
Zachęcamy wszystkich użytkowników do dodawania nowych tekstów, tłumaczeń i teledysków!


Reklama | Kontakt | FAQ Polityka prywatności
beetbox-beets-01f1faf/test/rsrc/lyricstext.yaml000066400000000000000000000074651472325477400220110ustar00rootroot00000000000000# Song used by LyricsGooglePluginMachineryTest Beets_song: | beets is the media library management system for obsessive music geeks the purpose of beets is to get your music collection right once and for all it catalogs your collection automatically improving its metadata as it goes it then provides a bouquet of tools for manipulating and accessing your music here's an example of beets' brainy tag corrector doing its because beets is designed as a library it can do almost anything you can imagine for your music collection via plugins beets becomes a panacea missing_texts: | Lyricsmania staff is working hard for you to add $TITLE lyrics as soon as they'll be released by $ARTIST, check back soon! In case you have the lyrics to $TITLE and want to send them to us, fill out the following form. # Songs lyrics used to test the different sources present in the google custom search engine. # Text is randomized for copyright infringement reason. Amsterdam: | coup corps coeur invitent mains comme trop morue le hantent mais la dames joli revenir aux mangent croquer pleine plantent rire de sortent pleins fortune d'amsterdam bruit ruisselants large poissons braguette leur putains blanches jusque pissent dans soleils dansent et port bien vertu nez sur chaleur femmes rotant dorment marins boivent bu les que d'un qui je une cou hambourg plus ils dents ou tournent or berges d'ailleurs tout ciel haubans ce son lueurs en lune ont mouchent leurs long frottant jusqu'en vous regard montrent langueurs chantent tordent pleure donnent drames mornes des panse pour un sent encore referment nappes au meurent geste quand puis alors frites grosses batave expire naissent reboivent oriflammes grave riant a enfin rance fier y bouffer s'entendre se mieux Lady_Madonna: | feed his money tuesday manage didn't head feet see arrives at in madonna rest morning children wonder how make thursday your to sunday music papers come tie you has was is listen suitcase ends friday run that needed breast they child baby mending on lady learned a nun like did wednesday bed think without afternoon night meet the playing lying Jazz_n_blues: | all shoes money through follow blow til father to his hit jazz kiss now cool bar cause 50 night heading i'll says yeah cash forgot blues out what for ways away fingers waiting got ever bold screen sixty throw wait on about last compton days o pick love wall had within jeans jd next miss standing from it's two long fight extravagant tell today more buy shopping that didn't what's but russian up can parkway balance my and gone am it as at in check if bags when cross machine take you drinks coke june wrong coming fancy's i n' impatient so the main's spend that's Hey_it_s_ok: | and forget be when please it against fighting mama cause ! again what said things papa hey to much lovers way wet was too do drink and i who forgive hey fourteen please know not wanted had myself ok friends bed times looked swear act found the my mean Black_magic_woman: | blind heart sticks just don't into back alone see need yes your out devil make that to black got you might me woman turning spell stop baby with 'round a on stone messin' magic i of tricks up leave turn bad so pick she's my can't u_n_eye: | let see cool bed for sometimes are place told in yeah or ride open hide blame knee your my borders perfect i of laying lies they love the night all out saying fast things said that on face hit hell no low not bullets bullet fly time maybe over is roof a it know now airplane where and tonight brakes just waste we go an to you was going eye start need insane cross gotta mood life with hurts too whoa me fight little every oh would thousand but high lay space do down private beetbox-beets-01f1faf/test/rsrc/min.flac000066400000000000000000000526021472325477400203160ustar00rootroot00000000000000fLaC"÷ Ä@đ¬D!îÄvoWe×l°÷5 reference libFLAC 1.2.1 20070917 TITLE=minó˙řÉ•@µÇ!̡'†hPćhRs%… $ˇaäĚ̔Ô9C9)™<ˇC™”ĚĚĂ“%8Y”ź)…% aś(r’‡49CL9C%RY’Ě<ž… dÓ ”)™™śÎd¦M 礧2…&yůgĐÉĺgĘaá94ť% Lł9ś(Xp¤¤ˇĂB…3™e% B‡ D™ˇ= ˇ@ł ) !ý Ě’s(NN’…2P¤ˇĺe&faʤĐĺ3$C'3™ g$ł 3”%8RS%™"Bś,’” IC„By— °aN”„Lä‘ ”Ę”ˇ”’yC“2i…9˙˙¤ˇa))™B‡ f,0©@¦„ˇIÉţ†Re ˇ(XSť …†Ng:OCž†sˇC™C<3Ě4ÉL(hĚ9ĺ”)“"))(Rp¦Jg3™™S39ňĘI’‡?)“)9C3'(e äžPĘI,)(S L”)'&p¦r†śˇĚáae ”2P)2HD%B†”†Je2yB„P‘‡‡(~y@¤ˇ4)“L<™2“¦K(y(”2“‡3ýPç)3™™”)<ĘaCRˇg)“)3ćS0Ą äź”ÉŇ2d@°ĺ$晡C…†s,ÎC“™ XzBś)™’a@ҦĚĘĘI伡I’‡%0ćRe'ĘPÉB’PĚĂI”'&RP”2yNe2 ‘ 9ILÂ$2™)"% LˇIš”ţXRe&P¦p(L¤ţ†r…Pć…$ň–,2’P”2’g JÉ43šˇCL)™Ěó”3' Đĺ$C„ÉMRS%330%3 pˇI”2“†IBr’a˙ůL’™2“ 3”É)'śˇĚ§= ’x|)C9'če$Δ dćsŇ$"I…9ô” 0§2̦g4) )(J¦g p¦fs)†ś¤ˇ…™¤LĚ“ÂPçĐ)’„@ĐäBr D…„ˇᡒ¤¤ô<'BP,(g(aaNĚź)ĘC930¤“ĂĚĚĚ)(RrPĺ0¦L¤ô9C””9śˇÂ!™HD fJ ç ÉB r“L‘$čg= 4Í ”9˙<ˇÉ™™Ę<Ě™L%”’JL9ü¤ó9™C™L硜ˇIB!(D9™ä§ L¤ŇS%2RRPСáI…†i4ĚĚĘ” fr’r„§9HD93ç@Í Ę3ĐÎPĚÉ™™™ĚĚĚ‘r™% 8dˇĎBPÉĘLó9)C9”3’S32†JdLˇĘ¤¤ˇLáˇĚ")Ě,™Ič””90áÉL¦O”(g e&Rt3” D99IˇIL4)(y‡(g?@§ L™I”ź)ś)ÎIB’‡))(r†PáC””9Ë sL2’O)%(O<ćJJJJůĚĚ,™I$C!L9B„ʦMçB…‡(e$”ˇ2Â…8y”“çĺź˙‘L¤™™%0ç”)'IáNL)3ĺ I™’r‡™ ÂśĘ)“L””ádĚ”š9ť'IL”””9II)3?ýś(g…2R†fL˙ô2“!(D ™C32Jaś™L,ĘJrfH„ȇ“<)™…&hfPáC…8r!9ĘJrPJ”ÉčffdˇIĺ9ˇB§Đfx˙řÉ’@µÇh!)œ“4)2™™śˇI™™ÂśśˇĚćfHˇ™L3”3źC”ĚĘĺ&s9CśˇIB“ĘĘŕS‡2D2RRP¤Ęý đĘL¤Ňz:†e Ě™ç˙ˇÂ$Í B…Đ"LÄ2S„I„Iśĺ&|ĘLĺ&PˇĚśç(fdša¦M™ĘL¤Ę”(JLĚ)(S'2PÓ g!d–JfP¦ffLĺ$§ fL¤ĎBP¤¤¦fK3̡BP¦aIĚ”)2†hg4(hD Lˇśô' S% r\3„@°Ďĺ&PäĘ"žILĺ p§8D9™ĘLĘśÎffaIB’…% NPžaL”)(y(S“)0˛aIÂ’…' J ™ň…&P9@ćr„§ fL¦¦M… LáČN(RfP¤Ď(RP¤˙‘& Îd"rfs9B“™“ňśô(Jr™(Pť Ě” Jˇś D”)™”(s'3ś¤§‡3Đ™L)3ĚĘYśÎPćRe2JJ’Pĺ ć™4Ă”)8P¤ÎfJĺž’™""ÎS’’IIBp§33%8\9žff4e$ĘIC„I?(RaLĂɦÉĚ̡IžS…9Č“4Ě)Éš Nr!(RyB’xJDPĘ99”9ůe‡=0ĘLĺ…%™3™ĚĘ™ˇśˇÂ!(S&RćaJIáś(S'0§' 3ÉL”)̧ dó‘ žPćg0‰2PĐĘL°łBˇ’PÉ”šg>i'Bee I…8RP¤ČNd‘ śáNLćIL(Rg ćS% ”&JLICˇBPJhPäĚĘB”“ś¤ĎźĐĺ%%% ¤ĘJaĘB„ó–r$™He$ÉB!†™?"ÉĘ~s”3™LçĐ))ú(Iˇ Rg(g0‰3Í’ž†sˇČBĚ4“śáśü¦ffL¤śˇÉLšOIô9CĂ2|%PÎ ”Ă)%2PJ ”(pˇC9333š…0ÎhsCť śĺ Ę,ü.)=!ĺ ¦NRg” &rPÎIfg0Ó'aš…“3ćRfe B!3™)Pá@˛J8PÉ |ôĂ(PáN†PÎJB…&PáÎae%32’~dBag2’|¦s@‰0ů™’S33' ” ”&D'0˛M ”(P§9CśˇĚç3Âś)")’!2!ÉĐ(RPô9čdˇÂ!†g(Re'ü¦JB!HS)“)’L”)8XDe0ó ™”)2“ţS'”ź)2“”ÉůB“2“úfs9™C93% ™ÉĐÂĂ)2†rPˇL”?)2… ć'ĘLˇĚĺ'ˇĘsĘdňs†JIL”Ě)™2!†“3) RLňaILšaIɲR‡&sśćH… LĚĚ”ÉLĚ̡śĚĐ)(D39%'¤"‘ śź”2S%% 礡I”ÉL”3’™™?(S0Đ Rgź”Ě”2Pˇ)üˇÉ”„@Ś€D…„ĘM0”Ă33&“¤ˇĘ™'ĘJ”™ç)™’†ÉÂś(2e„ʡĎĘNP¤Ę)’g%„ĘJśˇ2!“™’†IÉČ„ĘY&’t&S$ł,¤é†…'% IC% ¦™”8“˙řÉś@˙˙µĆP!9BR‡™“ÎPđĺ”$ň…'3% ”šdp§&†NfaI”ĂĚág2’eB„ˇ’†O”)™ĚСĘ”ˇś"9’…&i)Ď"(fg?C'ĚĚ™9śü°@¤Ě̡ś™C8D2pˇÎfs9śĎ$Iś°ů”…2†s rP<“9))"xL¤ĚˇĚ.Iˇ‰RR†r„ô śôĚĘ’” JH|¤ůgÓ0¤¦e áLĘI4ź”Îç–ÉL,"8r™)’™ś)3IĄ ¦Â!™…“3„@°ĺ$ĘL"ÉB’źB„ŇIˇBR‡‰™“Bae2S’…2O …(M ”š™aNfe…% r“)%$¦)(ú¤)(d”™ó"y2†…'ˇ’†Lˇ“\žRe'”(pˇfS „ˇ¤ĚčfP)™(hd¤äC‡ˇĘfRdBe ”ÉňÂ’‡)4(g0d§)<Â!„I<3’…&yB“śĐ¤Ę“ÉčfPç%2RĚňĘáBÉ™”źĐÂĂý&…&tť0ňtÉNLˇĚ¦r‡RS aä:ϡ’… Ěś3C9C9$C…3$C”„™Ěó dÍ'L<”)2™śĺ$¦IBćfrD)ffg% !JCL9”3% LÄP¦NM0ňPˇIśÉfLˇĚç9C™™C9śÉĂäĚĚ”ĚĚ”) "™™’”š™¦g(sň’P¤,Ę™43źC'™Îg3™I”™L„Lź)™:ž, ™IĄś3C')’s$@¤ˇI)(S%2XD$Cś"B”9śˇśśĎ9C 9ž~YˇCS… Đ3§$ąś)“™™śÎPÎgĺ2J“)(r„ˇC“2e%ś2™†…%™")śˇĚĚÉB’g fg2’rS2áaśĺrD)3‘„B„PĘ™C”„Cś2““4ÉLĚĚćyĘÉOĐááC38R†rdˇ’…'8D9ĚćS!°"dčr’…&2e%PĘB‡“™2 P¤ÂžS…9ĘNPСə)Rf’…&Rr!C”(Rri…2s2RPˇĚÓRtšaäˇI(p RfP"9Ě‘ ”)(RO% 2D9™ĚĚĚ̡L™@ç)śË3”3“™á,(hRC (žJP”ˇ=ÉäÓ ĺS3śĺ će2hPç (dˇ’“"r!3ÎRg(L¤ô̦B”“˙)(ICý’’‡…&|ˇC“™ĘÂä/”™C“(s …|ĐÉü°¤äB| (Iň…&r†)™9™™)?”ĚÎ}“‡3”<<’…!BÉ”3Ây™”9”ÎdI'”™dˇ¤Ą Ę9BrP„BO’ F,ý ”ĂĚ9’Ę“2rS3%%%% IBĄfP,:R†B%…JI”3%‡3(g0ł3™")’™:JfP"\ˇIź2’fs9™‡(̙ʔ"IćD) ™>P¤(RrRĄ!BÂ’…$”(JPçˇIBĚĚĚ)9™™™™)(r“Ňçô'ü¦ađô%Ô̔32P)3Ďź(y…2pˇĂś)I<9”9™™™)™Âś(S3™™™)’S&D&D3„I”ź)9I”ČDÄÇ˙řɉ@µĆč!339śĘađ§ dóÎfaĘ!s LĐÉB‡ N“L<”)8g?Ň…ÎP¦L¦JP”ˇ4(JBa&P¦Lä@Í °¦fM ç˙ˇÍ L¤ˇ”38Pć…&RS‡g=De É”„I”3…0¦NJJHD)9‡’Y@¤ˇ™ĺ2Pˇáäś˙)(L¤”“ĺ9ŇzM0ĺ Ě,Ϥ(X&xD™”4 J)9’™’„@ááe'I”” JÉĺ2|¤ˇ(p¦NP¤Â!Â’’…P¤ĘM Jfd¤äC'30§!a”Ă™”8D8D)(Dśˇ)™†R NĎ)>čr„ˇ’L™@ÎLˇÉćs0¦aIB‡“L¤ˇ™™‡’áBhD $đˇI”Ďśˇ™)™’ P¤ÂźË 9ÎRg33338S3'333$C'%'IŇP¤đä@ňdC338D4(Y3”% “2D(JJBP˛…&fffpł p¦e J“2Pä@ĺ%(OPĚĚ“ÉJ™)’™Ę̤”™IĘd‘…3 fg3™ĚʡśĚÉL””ÉLɦ“ĺ2t °”ČDÉ32S&’†„C™ň™9L(D% ™I9„C%'(rPÎe&S'ˇBRs:ś)’Xe&r’e!¦JaL™IL–g9C„C'2S dä¦yůL<…2hfp‰3IŇt(dĄ čÉĚ”'C &RM% s)2™(”<ˇÎ¤ź 4ÉL̡š<ÉĐĘLL¤¤¦g dĘ™“IB“†p¦N‡"8e Ě™B‡3˙čp‰(ćJB‰“™™’!Â!IśˇĚůLĚĺ!9ćrS%2S9’’†xs¤ô32|¦NPćRz9Ędé;&ɦ¤ˇLĚĎ’’ĚĘdčr†fL¤ĘfS3™ůNĚĚĚĚçˇÎD’RhP”ň™:Ă”93™Â™9™ĘNS$Đ“Be$ˇH_üó9CĚć’…&)“ňśé(Re0§$ D”)ĚĘÉ”332S<ň…dˇÉ™4źčäź)“ţ’…&RzJˇC… ‘R…PĚÉ”™Ę(SPÉI„”))RP¤Ą ä”ÉLćRIILÂ…!Oˇ= ý&”'S$ňS”™Ę™(S0¤Â‡ (s,ˇĘ2fr’†fsˇIš™Â!Îd¦M0ňPˇĚФ"!JIpĘNfs<ˇaš,"ʡ“ćRe‡(Nd¦fJd§ 3™C%% B‡ =%RP¦g„ˇäĚÉ”‘ ćOš?ô(JĂ„@°ĘM0ËHD  R š™ĎI‘ Đ)HR’Rf“C„I…)$ˇĎIˇN¤™I”” Naˇ4(9Bžˇ43™žyC@¤Í äd§ĐˇĚ˙)“ y'3”(s39™…2s Lˇ”™IB…!BÎYô2r‡3”3“3%9ź””2zÎI’d¦g˙B’S!IůL9śĚĘ̤”,>`A“)%8r!)ô2„Ę|ůBŔrddĄ$ĺ B– J™’™™ćyĘ y‡(dĐÉBLćK32P¤ç)”%čf‡)’™))(RLçĐÉBe&†y™śĚÂe&)2’’†‡))(sŇzaňî’˙řÉŽ@˙˙µĆČ!ĺ” 0Ą B’IBĘĐ" XAPˇ2y…™L””33 Re BśÉL)’”)8PáLĚĚÎdađ§2Pˇ’”% Có”3™Îd¤¤ˇÍ'I¤–P<ĂB†…32‡(PÎĺ&S3<9 0ćrS%0ˇIB™…… ź”Ě” NdĐ)d"LΔ)3Cś¤ f†r„ˇIIBÉśáá‘ 43…2JL¦OC”š2S”ĚĘdćfg8Y’!™…2Pg„ĐÉB“‡‡%gţ~˙”ČDĚ)Ě"pŢ Y…Č\’Ę… Đ)(“ĂÉ40Đp¤ˇIBś…™™Ă”2džáe ůˇ&„…ňi(Rp¤ˇaNH†fNaćg”9…(p…™ĚćyůˇBxPćg2…!9Ěó$C%(‘’‡&pˇI™Ęś4ˇ”ÉÉ32P°Js3%2i…2hs>RPĎ0¤ĘJžg(y2Â!<ôš2†pĄ$B‡2fJfP¤ĚĚ”,)"&fsCB“Ě‘0řP˛N„ˇIša)†Re3˙ˇ…Sž… Đô”šdˇĘ’‡y(RIr8S39ÎRr…ä̇É&PÉáË JfPç†áLš¤"ĂB’śĎ™”&DD8e&’†…% H)I”Ě㇠p¦g ‡PćfPÉĂ”9IL”ÉIB“)’…&zB„¦OÍ!))(Y2„Č„Ęfr‡(L‰2’’ Rg ¤Č„ó2“’M ”<ť2PˇĚ–P,2‡ fPÎsĐÎPÎe™JË…8RPˇĚ¤P§(faĚ,™CS… 3™™(s)…(JfRfK0§„„ł%)‡Â™2†s<ˇÉĚćJ9”ĚÉL“†pR¤ü¦N‡"(r‡‡)Y0dŇIfrS3<ĺ&y™…&S32†gNICźČˇBPĘaňg2†rPĎP)’”<”)”™LÜͅ&fPç rap’Čr‡)(hP”ĺ2PÉĺLĚĚ"L‘ @‚ś¦L¤Ë J™(™(g&e s%% ĘađÍ ĎC”ž‡(S33'% J,8RP))ž†sBf¦ĘL°¦OžP¤”)39CC”„B“9C„C™LžS8P‰ ç(g&PćRJa̤Í™”“™S2P°¤ł330dćO…8hR9C)&p°") PÎĚÎRe3ť2S2“"…&g 3Ęp§'(g0LĘ… C$ˇC…9C”2Pç)9śĚĚĚĚĘĘaĎ)™ĘHPáNdI„IC)2’’!ÉB’gLÉś3’śôÂ’…3' (J3CB‡&ffe$đĘB……”<3… Č)čd%% ”$dR) d¦HHYśĘaÎP¦aC33…8P°Ë f!Jdś“Ă9ůC8D9ÎdL"d”Τ",,"ÎP¤ D™IC’!BSś¤ˇ=™“% ”ĚÎr“ś¤"Lç3ĚüĐÉCš™C””ĚĚĚĚÂÂ’!(S…9™Ěĺ ”’IžP¤Ą đΤ%0”) RSúĐááC”2“(S$ˇH|3IB™’™"Í ćD’|¤že'ý0řhD!0‰ s™™)(Re39”ĂL§aBÂĘ’áˇNĚĚćp¤ĘN™6‡C˙řɇ@µĆŘ! (e&J2„"d¤¤ˇL)Đ,Ę)XRR~RNdNaĎţ‡9Éś¤že$ˇIĚ”) 2RD JćRO3” dĚč™Cś˛‡™)“IĐÉóç”) PĚÎL¤’Rs 0ł JI„C%9O&™)48YCÉB‡'’„HP˛fH‡9Ió PŇO@ł4(rO”ś¤Â&O P“LůI”(RPô&0¦g$ĄJ…)%2hd RdˇI”$¤’!2 s‡y‡xffN†J†hNRP4ĺźÓ…933&Re!äËɤô9C”9é†hdˇ…’~S'9C8D%8DĂ™32RRD NLˇ”™¦J B$)ĘaN‡4ÉrD) ÉB†… Jś(s4ÉfáNsĚł)HPˇCB‡(sB‡'(Pĺ d§(Sd”áfP¤)HS3%3%%P¦frJd¤ˇIB’…“9C)3˙I)BJdĄ0Ą ”Ěĺ ĚůLžPćÉĚĚĘaNLÉţ…&S39""ÎffPůB™™2 s4'BJaIš"(dŇzJś”¤(fPćP°ˇaB“=” ̡̜Ę% NRO2‡Â™™?@§ 9”9é(PСI”<šd¦r…“)(RS3”)3”32rS&’™,ÉCIÓ ¤™ĘˇI”ćy”™I˙ůC™śĺ¦K‡ÉIL”ĚÎPСä,¤ y™:)“)S&e$ůȆJ|ˇa˙úaL“2z8s)&Pá̡ś)™’!’™)™™Be'"I432 FáL™Iç"̡śˇĘd§ $R"Lź)…ČS3%™”8\ˇIL¦™)…% 8Y(pRNIL%RbĎśÎd°ˇˇ˙”” NdˇC“8e2Pť!BÉś)9CÉů)<2ÎS'ô(g&sŇtšˇáśčRp§'ĘĚç(ry…’S3ź?(Y3”ÉJ“y’!žC% ‡<¦f™Ďţ’†„Bg%2Y’™†…%39’!“™)(důLĚź”ĚÎPć‡4)0p Yť áB!3'3śá¤ţN™™S<ť ”ÉĐšˇäĐĺ%2„ćJfp§ fd¦J@°ˇNtĚĘL¤”ČD™Â™śü¤Ě””)(S“(psúˇ”%3"ňs(dˇLÉđŇ„ĐÉL”ĚÂ!HS˙ţY”ĚÉBP,™ĎĐÍ0°ÉB™@ł'33 dČĘdĄ!BÂ’!(D%&IdĘd§0°ˇĘJf™ˇĎB|Lˇ3%… °¤Ë%9ÎR ĘĚ)™žS'ĘpĄ$çü¦fLLĐ,(prfO 3ś¤Ęa̤”É(Rs)8S“9†… OCž“"a”…48S2J"™4Ěĺ')“”2s3ĚĚ8S” (ĘfffLĘI”D8s%&…&S$Bd@¤ˇIL”É)(S9šĄ$ĺ&RLÎPÎd‰0˛ff|Ęa™ś"™3(™Éé4<„HeS2e%2S&’… LΓ¤ˇLÂÂ……39IBP, s'ň™%!ćaˇ’aLĚÂÂ!ÉĚ””Ěχ fg pˇIĚ)Éže&R~hD(Lˇś))“LĚ,Â!ˇšy2k»˙řÉ€@˙ţµĆh aśśĚÉBˇHPˇĘˇ™™śôž…9IĎ ćg332R†re ”ˇÉ"Hy™ĘOčg?ˇśň“(PĐ"LÎfaL™L2Ŕh&rÂ’„˙ţe!¤-”'"9L”ĂĚĚ)‡3ˇ™Â’„ĘfO”“ÉNäůó9ś§)™™…8S3%2tĂ)“Ě生)‡e&39C3%É9”3’Y‡)(hRP¦NaBˇI(s)śˇC”(|4ÉIˇ)˙čaf} 礗'IICšaň†hg™đł3P”(y9™”’…&JY„L2“9fSfe ”(J™ĎIčdčN†r™ś”ÉIB!'%™2†PˇÉÂ…$¦M’áBPçL”Ę…0¤ˇO'BP"B„C% )‰2…PˇˇC”ť0"J”3źB‡3C9”ˇ<ˇCś(y™Ę9<šaĎůćs3(g2‡9)ĂÂś)™śé= @¤će$ćD2P¦IChD% JJ2“ ™™(P”"% ”’™)™™Â!™™(RO&’… dňg9C% N9'Ęs‘&i’™)’™)(XS0°ˇL”"̦O˙ţ†eaL’‡%&He$¦ffJ%&r“ç(s9CĚÎPÎS$@J,’PŕA B™’†Oʦdä@¤ˇB“"BśĘL*"B™„C<…(S!|¤˙čPÎL̡ś”2S)“úç)Ă”Ây„BP „ˇB“ÉL‘„PňS™™"3ˇHPСä嚡‚™áLžI2„ˇaI@°ô2’„Bg ĚĚ(Ráae á™HYBS0Ň„)“IC@) dô™fRĚ”2R†fdđ˛ä’!„˛hP˛äĚÎe2†Jr“Ę™)% B dáfe%™ĂĐ™I„C…(sĘÂś"ś’äP”Â…‡3”9™ś”)2™)™’!Â$"&LÉůL“”’P)(PćffJf¤(D†R¤ž<ĂB†y…s„šdł8R„Ę“B‡2…¨‘Ě”¤žP¤ĘO9Ŕ°ˇÎ†sC”„C… Ę44,,"2“ˇI„ĘJfsĺ3 J2S39ÂÉ”9śÎg(Rs”,Ď(rs„C’^Sź”̡ aN!@°ç(O…)33™”8Pđť ç)0¤äˇI”<”ĚĂźčr’’…&hRrS&†fg(hr„ˇ™C)ł$C2“ ̦”&†xe!Ě)’ś"L˙ȆJPÎC”””(s4ž’‡"a¦JfM0”2P)ÂIO”ĂOC”)9…3$¤™“32S&!“Âś„I„C38Y”"(r’…“9ÎdL¤é‡’™"ĘC:B‡&dśÉá<“Ă‘…”2PÉC'Ę™1 ”ĚĚ)(Rs332S’!“ĂĚ)2……8S%3(s?)™Í Na¤é4(ry)’Ě<ÉLÎsś,™™™…&Đć…&S0¤ˇCI…“9™Â“y(RRX B!(D%(rĘ% Lˇ)Î&%Ě“ä‰Ŕ‰(R|¤ˇ0‰2e!Đ"BśĚĚĚšN†só8P§ p°JH„ˇaNÉ”ćPáśÎPó'ţ†p¦OĘJ(PáB“,Îe$üüĺ$ˇIžPˇI…3™”8D32rS$C0ęŕ˙řÉ­@µĆŕ!™”’S NaLÉ”””2P¤ú愞ffJffRˇ“™)’” °“C9¦M% L¤ˇĘô8D4(y:”3D9)(… däˇIĚ”ç,"†PĂáčffd”™ĚćĚ̤Â!Â!ÉB“?ˇśĘL¤ś¤(D¤ňyI@¤"%2ä,ˇĎň“4r††S <§2”‡C9IBs>‡‡&PÍIÓ L™ĚĚÉC@JaNˇś<¦OĘI"%†NJJfd¦aśĘdô J„I2„Đ)Ă™ĚĚĚ̡IĚź?”Ě"ÉB„ÎI RPĐĎ 2D2S$C%2D&D'2†rs9…9?”Ěź)…8PĚÉžg9ţ‡(frP‰ áLĚšL‚HĄ&S2„ˇ”(i’™śĘ™C™ĺ L̤’&INJRBĘáĎC”)2“Đ̡ÉBR˛’…'0§ s“?ţe$.Oň™:%<°¤ˇ= ó@ˇ"gĘdůNt8D‡¤)Ę8r’Ą%% Ě)ś(S&SR B$2!“™”9‡3LÎd¦B$ÂĘfpˇˇB™)śˇˇ9“9žP§ 38P,ˇLĚśĂL”ĚÎPćg(s™Ě˙ĐÉC9B…% Jaʇ †™†RIL)“™…8S…3’DÉň™3”% JJáB$2„óúJfp§&r‡% B”’S‡ĺ9Ó%$@ĐJPÎ L”šLpˇĘJ™C2e39IB™)™’™4ĂÉLĘĘ2f} ”3(s4ž“čdˇ’„ĘO)™™śý ć… O0ňNg”)0P”Ď„Lś™™™™P¤Ď!32Ri(Rs% !Iś3ž„ĐÉC””)2†Ra&RRD 2S'C 3B’‡"ˇ9@s(p‚!N¤ˇL„@°§”ɔÙ̡śś¦g†rNIĺ0Í …'(Pĺ ó”%™")™śĺ&s„@°¤ţ†Re&…!C”ť$dˇĘś)™™ś¦ećH’aá‘&s™”9ůLśË ćffJpˇI™ˇś‘ žP˛e2S9@˛…33ť2Pĺ ˇBD9L))’™‡“B‡3B“):)<§†rs"„B™É)“B‡2S…9™™)RgC”)“ś"(D%&PĺĚ™gŇP¤šś"…&†J(p¤ˇ2$…Ă90¦(S I¦f!JND HCśĘaćdĐÉC”)“)=0¦Iće$ó”śJ”3(p RgúJÎaL™IBÂ…’R†fJś)&dĘ9’‡3śˇćr‡(g Ę™“L<”ĚÉL”‘ ”„BP‰'>IB‡’|ˇ¤ˇa˙С–IĘS!Lť‘„D2R†p¦aI”ĂɦI<”,(sȆJ}’…†Y2’JP2D)2™4)(S' ˇś=Ě"Í3™Ě‘“™Â D…„Ę“"(dˇśé‡ÉФ2Â…&e Iá9(p‰ “„CY@˛e9Idä¦J”3ź9I@#0˙‘LĚĚĚĚś¤ô 3čg(Rf“"aäćs”™C”P,94fNRP)3(O3ĘĘĘ”2R†JaäŇ„Ą% Ňzd¦Pĺ™ÉL–fs9)™™BdBNz"= ˇÂ!'(Pžt(XpJ@‚ż˙řÉ Ş@µĆ g™Âś(Y%„BP‰% LĘ“(r’…2J™””9C”9Ičg(RP¤áI@ł?ä@ćNg)'ś)’hd¤ţ†gM!) RP¤äBe'L‘É4𙎙@¤ˇBzˇL9šaLśĚˇ’‡†…2†pé%33„BP°ˇI™B’”)™4ž“¦ˇC™¤ˇaO9C… Nd§™™™”<>dćffJ‡ô3’™™äˇLÉNaĐ™9††M!šäHICɦH†Jd¦fg$B“:ň™“ćRg)2“9By)CÉ)’YĐ D™™IÓ†x !NC…$C9śĎ= IčJ%(r‡(e&P¦Ng9I”ÉÎs–9™śˇC3dˇĚú2’…%’…… dć™IIINáfK33339B’‡2XrÂ…2rd@ˇˇĂ””ÉL”š4(S2s s3339’!śĎ(Re ”9ILĚĚ)3ü§:9ť =ÉĘśź>e$ó)3”ś˛„ˇC38P°§ĺ8XD $ĘNPС̤ˇC9ž… É”źň™?СĚú"s&Pĺ ÎáNÉ”) ’t3…”8D2RfS%32D2S9Ę™I)’„ĘB!C)3’Pće$ˇIOB„ćL¦r’!ÉC“9™y4Îg30¦L¤ˇÎy… ”)śÎg3) hĚ4ĂĚ”"C"I”“)&g3”)(RP9™Îá¦HĚô3Ă”42PśĘaĘ&JIĘś,"ĚáC…C–çůʆ’fJĘaIś¤Ędź2’IÉý ffa†LćH†p‡<ĘI"9B™ś"„Be ĺ IB’…$C'(dĄB!·(sB‡“"(Pćň“9Ěó™ĚÎPćPäÎffM0ňS$B“ĎIé= ™C’‡3”’„@ĺ 8yIš33&@Éažç)3”™B“%2t3”9ňe%! D9Ęg‡' J32†Jd¦Jps')'BdC'2e&$Î)’™D8YůĘIň™I<”Ě”ČDS2”ÂY„BPˇäô…9,"…2e ¦ˇBS…%3(JfdP”"(g&†L¦Idˇf'Bú%?čfP”“2†…g(g&d¤ˇĘÉäŮ…9ś¤ô dĚĘIˇ†” RaCś§=30dˇLÂÂ’“L>JRˇáĘĺB”ł3”)śÂ$Â!ś(fRe339…2e$@¤¤ÂÂ…'% J™ˇ™Â!HR‡:aNe LüˇI…3 ˇš” :4 P™@ćS8SśPćpňP¤ĘJśÉI2RP¤ćfxĐЧ'(PÍ ó”8D’†aI8P¤ĘJ˙B~faÍ !BS”Ěĺ% ')ŔŚ’YĘpˇ¦S0¤Ě¤Ęfe áůLÎhS9™”&D…)…!Jd˙)’‡‡@ł))“L)ÉLÎt(rfJ%śĚΡ’…%% L¤ˇI”ÉÉáNPáćr“”9™RaáC9”Ěť áI”)†Xf“‘ ś¦fs˙ĐňP)ˇCś(J|ů”8RD%(y<¦dç30ŇP˛adšaL%Ă<źC'ĺ%Â…†C%))™)"™ÎPćKDYCD8P§B@­ý˙řy CÔ@µÇ(©i*”´ĄĄ-*ZUd«(´Ą-IKDĘJYeDÂŇĘRË*YJ”¨µ˘©KQT˘ŇÉ•)ZXš,µ%,©(ą)QjJZ”©IjRL¨šRŇŞT˛Ą)K(­%rWJV*X©bĘ‹R–T˘ä˘ĺ)R‹JVT¬´Ą©J–RŇKQj‹R•µ*KRZÉU%©-EI‹E­*¬©U’T¤š-,©JŞ\˛UbĄ‹KJZ-eKZJÔ––¤©¤‰ŞER‹‹I”–´L´ZĘRĄ©–Tµ,–RRÔ’Ň•-*˛Ň–•YJ‰”Ą%“%JK%T©eIeJRĄ%–”\´˛©,´˛ÔĄ%©.-,¸˛Ô•”´«J\YZ¤ŃeJެĄJŞURŐ*,¤˛•,´¨µ%I2’ÔĄ)--IjR’Ô–YQKR#)-U•*©2•Ee¤ÉeĄ+RZ¨µIJ˘eJK*Y*˛ŇU%eJŞŞŞ¬¨š,–+,©)J‹TZĄ)e%”¬¨šYRËRT¤´«,¬µ*&I‰”¨ĄÉZ˘eeKRU*É+)JZJ˘Ę–TŞŃk*R´˛µ)R˘ĺ*ZX¨šQ5"ęT˛Ę”¤ÉdŃKJYQ4ĄR©eJT˛¤ĄĄ-RZŇU(¸­JRL¤©EKK*’­,©ieRZ’ÔĄ)R•-%R•*Yqbhµ”´•IZ’Ő)RĄ–”Ą©)2’–)&II’ɢʕ&QYKR”©U•,ĄJ«)j*Yh˛ĄV•¨µ%IeDĘRRĄJŞUTµ)QiUJ¬’Ô”Ą¤“IjJL¤µĄĄĄ•+)Q2”¨Ş˘É•Y*Ąe)iEÉe˘©JRĄ)J‰•**R–’ŞUU–\¤©eIJQieĘRŇR”˘Ô\YrTL©JRÔTĄ”¨´˛˛ŇĄ(™(µ\Z”ĄKR©+K-%K%”–”™iIeIIeJʢ•)j•V˘–”µJ-J˛U”©UK.)T˛ĄUU”–T\˛´µEÄĹĄR•,ŞJҬĄJ´«J´¬´±kE,Ą%-)UK-*IU)U*LZYUKJ”©R•JTĄ)&J–*TT«QK)--QrĘ–R’–RĄ,¸ĄÉU,©JZJ©eĹ‹R¤\˘âbŃ2˘eE©EĄ*˛’Ę•ZYRҬĄ”¬«,˛T”Ą)R‹’Z”R¨ĄRZZZ”©e))UeK*Š.J-JK&’”Ą¬Z˘ĄĄ*T\L©KR‹”Z˘eEe%KJ––-QKIU+KTŞË*RËK,©JZ’Z’«)*TĄJZKRVJLZQ4YyUĄVYIjRŃe©)j,´ĄJR–’–”\”Ą)IjKR”Ą*RĄ)JR”¤µ(µEdÉdÉT˘Ő%ZJ˘–’Ą–”µ%©--Dµ%©e*Qr•”¬ĄK)JŠ–”Ą”––\&T’«*-IR”ĄJU*YhĄ¨€q¦beetbox-beets-01f1faf/test/rsrc/min.m4a000066400000000000000000000133461472325477400200740ustar00rootroot00000000000000 ftypM4A M4A mp42isom ŠmoovlmvhdÄLé_ÄLď¬D¸@trak\tkhdÄLé_ÄLď¸@mdia mdhdÄLé_ÄLď¬D¸UÄ"hdlrsounÓminfsmhd$dinfdref url —stblgstsdWmp4a¬D3esds€€€"€€€@xú€€€€€€stts.(stscĚstsz.22""'-+1/+&0''&%)(-,*).)(*%)-4&6.*,$,3/'& stcoĆé •udta Ťmeta"hdlrmdirappl°ilst©namdatamincpildatapgapdatatmpodata6©too.dataiTunes v7.6.2, QuickTime 7.4.5Ľ----meancom.apple.iTunesnameiTunSMPB„data 00000000 00000840 0000037C 000000000000AC44 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000˘----meancom.apple.iTunesnameiTunNORMjdata 00000000 00000000 00000000 00000000 00000228 00000000 00000008 00000000 00000187 00000000\freefree(mdatĐě\«11;Ä<Ŕř¤4„ `Ifŕe—^ŕďŹP0#Ü·›ťÁbÄ»VOŘŮ(*ľJ€q8ň„q©-@>Ťč[-3ţ!‹rtgĽĽ(Îą¶q]bĄ¸úÄPEŮ€€ ްDăŮ‘>JUl3ËA$Ö)i)ŔúÄA#€+:3ď`Xa¶™ÉůQ;«Ŕ±čŕö˘" „Î Ž× óf{q wZ“Ä 3zżf#)NŮq'ř˘" „kËr˝ŮÖŁ gʧý'”ę )y*¦Úö»,°Îµxˇú˘" „r †BXVFěě7nhϦNž|z%ôe0Ue®«Ś śö‚B„_`9ĺo˘đJ­Dz0Ż)ňřáŰ1F8‘ú·ĺÉ>7t·#‹Uŕř¤4#ŚXŰőŢ™ľáŕŇ`¸Ž‚Ţ×xT†aG~fäHv<ý¶ÁŔúÄ`? &×63Đ”l:äGŕë’ 莵š„ÇÓëĺN‚ŕú˘R#Aש9Ď<łÔiÇ%ÖřꦗµŠÍÎę®yfžÎô‚T„ `®!I}uhnV$?‹+äˇ(Z«„ÁˇÜtaĄVi±+±l‰8úÄ3#PŤ0Ľń•T`’3ąčžÓ#?}ĄŐőÚ»ěÔ‹€ú¤Q#Hl`µÁ˝ĄËK§)Q)E‚ńő>O˛ÂôŻSÔ¦úĆ"#P ŰdpËźżŰ2é­~sŰČÓďŤ'Pîě=Í&ü!úÄPG<Ž0NÝÍEř™_ő'1…:ő‹ĺ\ř˘ ,l qĽÔş<¬>čđÍ&ďĂž¦J3}•]dQ€€8ú˘`F  8I+–¶:aá–;0ĄÇ>m>;ßMľíŰŹź× Ą/gř¤p>€ŢbSci›”¤L z:~HŤ5M3©'°A+č&„f ‡Ö(pö‚0!dŔ˘’Ż:%˘C°9Błî+]ţ3Z†ąčcJr†Â¶Źö¤`E€a,]µ)\BiwČ,yç@ĂşD¸ůťü'ħĄ¨Üř˘ ĐŔ€0qóŕ=bžmBĹAwůH°×! šDSŞ 8ö‚5 !`Â4†§ĺ^ńíŞ^:$9µĎ…gs`M%…ĘZZtŞ^UŁY(o€ú˘"¤09ś’#NŃ·#̬zÚW›;t A-1dyß”3¤^ ŕú˘" "ěFâwěĽú„,µ¸rŠţ¬js%”źŇísŮfžep=Ľř¤`G €@ŔăŇ5?ŕĽ`•ŘÉÍM˛ČöĆýYB^×;C€ř˘`BČ ĆăiRř—‡iéŽ60 9üľĺÓvë…(Ü˙˙GYJ´…=˘oçlÇWř¤3 BĐ3ÜQhLÖĆ$/ę‡b<÷~łµ8ëűŰΚSŤ­n*jőXeĹ×7’#ŕô‚"(„@Ř Ad¨*Â`óŮ'á¬Á®co·>Vűça &µ% ř¤`F‚ ˘±äßxă}¸<ëĘüfP[2ÚqXä.Š'Iµpú˘" „¤*ÓŘšÖ'ęŠÇÉR™z[oÝÓ¬ía‚„ľ”XÇS2p'ŔřĆ2! éZđŢx˛µľůěú$©żVn´%j(óVŔö¤`F‚€öŽťF†@›c}}đCÂ2>Ű<ĆçO5Ł!ąQŢ/grDŹđú¤aBŕô‡2‰´S(^®ZŹdđ{¦P¤ź~‰Ůۦ‘6 Sşśö˘ „,`Ľ-1wNŢAŔÍ”XPKhˇwKŰ#0R¬Y±Xť-Ŕř˘`E "ĹImq¤>w˘Č1s5{!ÁXş[Ż/ćŰ?ÓGŹĄxř˘2"%€Xä  aÔž®ŽćĎű©ĆĎçż÷şrŻLaŔě\ŞRi"?pě\¬ „ô@Ŕ\beetbox-beets-01f1faf/test/rsrc/min.mp3000066400000000000000000000310241472325477400201030ustar00rootroot00000000000000ID34COMengiTunPGAP0TENiTunes v7.6.2COMhengiTunNORM 80000000 00000000 00000000 00000000 00000224 00000000 00000008 00000000 000003AC 00000000COM‚engiTunSMPB 00000000 00000210 00000A2C 000000000000AC44 00000000 000021AC 00000000 00000000 00000000 00000000 00000000 00000000TT2min˙ű`Ŕ F=íÉ‘A#ô‰ą˙˙˙˙F0Ăß˙C ˙_ůď˙˙˙˙óĎŁ)çÚa”2ůŠz<‚žyő=xů=O `ĐĘÇŤ  ĄŕĆyé>`řÜá ĐŔXč" z)ăBś"Ŕ$)‚ A„ ˙˙˙˙˙ř˙×?˙ű˙˙˙˙ţľÝvbŁ×Bť¬¦ ł4Ŕă°˛+¸µÂ¤ÍrąÁÔR°Ä%;ťîRŁQ[˙˙˙ďÖÜî˙łľÚ7UW6dTUT!ub8´Çř çčx?ń.Ě€Ź˙ÚÚ˘ł<ŚaeźDY/˙˙˙˝˙PśĽ·$D¬(躡ďPŚi§˙ű`Ŕ€Š9ŕ… éâH#4•¸‹RŇAbÉuĎ&Ž ü:=ÄÝL´2!Ô¶#9îŽÔ#"5ż˙˙˙ú­˙fE$ěȉ,Ş1HËez˛1’Ş!ĨR ‰Ě§)˘((â˘,Ęc°š)ÄÜX˘¬,qE„T9N@řt˙˙Ňţ÷˙?˙ű`Ŕ ň= mÁ¸Č#tŤ¸ż˙˙óËäÜŻđÉ÷INˇćiŢ˙{ŢžYg§S—ÜëdE/'˙˙˙˙˙˙˙çţ§Ě¸g Wëď&%ÍirÄDuń6.ÝítÜ‘mtP&ę,Ž«˘’Duhâb¨D›ŔN±˙˙˙˙˙ý`Z˙ý{˙˙˙˙˙çĚímŢóĄ/ĂžjľGH„<&ńÎCĄ˙ü„žl÷˙˙˙˙˙˙˙ýţ˙çJÎÉPŤ1•ĄI’ňÔä»ň9Ł3łZHFĘ;ŽÔr/viĂ1  Ü‰…Ł2‡ ěÁ˙˙˙˙˙Öő˙ţy˙?˙˙ůz\¨Ű+dDÎŞŹ1ĆckTIw˙űbŔ ć% „MɬH#t‰¸™¨…¶R˛¶Í´ö˙˙˙˙dÝꓳĐ÷1ęr© ĘĎłîŇ»łlÖ• lΆmC‘Uę÷c,ě$(tČ4BK˙˙˙˙˙ëĺý˛ëZS˙˙˙˙üąQRĹ­ÝŚ‡>łĚl®®č§1ڍËf:V¨m&ľĺDGB;?˙˙˙˙˙ů­RĚé+9‡}•+Ďł+±ÍUĐčĘbPSú;1Ś%Ü; Š…R•Ěwt”€Î9B˙˙˙˙ţ¶˙ë˙•˙˙˙˙äěúRwó5ťlŞ„s>ʸő#ĚäşuFlëĐęɕћ˙˙˙Ú˙ł«ÔŞFJČŞyŚEus–˙ű`Ŕ" ? „­Á¬Ć#t•ąa2ft*3άVwVl̸â•ęc@@Ç‚o2ÇĺQÇ;> ˘Ł€?˙˙ős˙˙˙˙˙ü»µ®ĺ:=¨îďv:şş•VB/Îlęčö-ĘZ$Ű™5O˙˙˙ţÍ'*MDŁť'» Č.–zą›S«”ĚęW!„Ça溡Z!3#NǞ#!˘âcH5VA¸ň¨`†`±óőůţż˙˙˙˙®•g;RWTťŮUŐ˝ÔÝQeŃŃTÄľÎB%O˙˙˙őůŃgęŐ9F F‘ťT‡j9"Î(cJb#!ÇxŐ3ł ŞE˙ű`Ŕ, Ú= „­ÉŻČ#4‰¸vcśqL8áaŕĘPq˘Ń2$48˙˙˙˙ţÖŻu˙ş˙˙˙˙˙?Ł‘Ý”„5›Rl›ogGĐŚŞöi7şŐŽ·ěKw˙˙˙˙˙ÚŇ"1Ý_™Ü§8¤T„Dgťä;YśFä%PěĆ \ČäRŠF €‚äS3: ÁČQBŞŕ•Ü(˙˙˙˙ţÍ—˙/üˇßźő˙˙˙o‘śíbnYŘz(‰‘ëu×J:ä·IdŁďZĐÔŮ˙˙˙˙˙ţźJ'»J Ff®ď©Ne+ Č 8ä*&*†q2 0Ě AŠAD…0’ŤQ!2Ş”ĄA§aQE˙ű`Ŕ6€ ¶A „­Á¬EŁtŤą0˙˙˙ý°/ó׿üż˙˙˙˙ň]sąśĐô… 仪Dţ9FzÓ¦[7‘tĄ+Mą2äł˙˙˙˙˙˙˙é˙9ćÓ*FE‘D‰»ë2ł×r™9“Ň·I­|ĽUčĘă±Áz°spÂ+† JŰŘ8˙˙˙˙ţŤ©üĽňgRfŇĎĺ˙˙ýţ•$íc1üý§ş!Yz:d÷#ę×*ş%Pú?{Ż˙˙˙˙˙čɦ·5\ÎRŁľě‚ŠTv{Ä$A™ RkŃi†!ٱĹĘÖ(|¨rpđé˘î<ÂÁŔ+)h0Ŕ5"üż˙˙˙˙˙dî˙űbŔBVA „­ÁÄÇŁ4•ąŹF±:ĽŰ>yę­)Ýě¬ĺşQň˛+7cfT˙˙˙ëŮ[óÉWŁ•hrż˙˙˙˙ŹiO‰Ť&׊aҶ§ö’ŃşkL“ÔW0÷?ÜpµpÝ˙˙˙˙˙˙˙1˙×ýúÚuz÷¤sň8ë›~9Ťç^;Ž’…ěar:¦hZ—Ro|†AwÚÖěXňo®Ü**>@Ö0˙˙˙˙˙ëËWuűJŤFŃYěîĺňYŮrć\îjí»*"±Hß˙˙˙˙~î‰m¨—g*:…+"ofi•ŢÂ#EŹ ¨˛)JçWACę,$aqĘÇ)„€p1Ś"Pč‘ĹH‡(ѡ`8˙űbŔo€5 … É˝H"Ě•¸3čň˙Ż˙˙˙Ö×ÝzčĽě×»Čr/Ȣ Ę®”dŁ3Ń—yN¤q˙˙˙÷ä{"Zĺu#ĚeR ¸ńŽÄsî®e-ESi‘pŐ( ›0“„bŽ3‡Ŕăs†yNEr”ÓŠ¨€ ŚŔ˙˙˙˙˙íyď˙éß˙˙˙ýz.Ą1,o5ä+9·Ežuvf±Îőąv}ČČ–N˙˙˙˙Dű}lłµ+3UTŠCoĽĄ>%čOyS!ťXڇ‘Ći;ť\ę8#)Ĺ:‰cĹ•Ęt(P‘Ŕ?˙˙˙Ň˙ů˙ó˙˙˙˙ů´š÷f˙ű`Ŕx 9 ­Ů¤GŁt‰ąb$„vşftgdű©kU-™śčƤĆVäfŰ˙˙˙˙ěźęR>Ó­ŐK5(i5; Îń“•ŃŠwiÇ*ą¤"âŠ*.*1â¦c´x´çRH@“#•bŔ˙˙˙ö@ż˙-»<ŽÄjµ=]ÖÓTޤ®Ć96Kä<îäsnłĽó—˙˙˙ýv"Ńő{Ń]LëgRťg©ŞF5ŽFuőZÇQ3ŹQÓ ‡Ç°›”a‡¨Ô µĚ.tP‰GQCĺ<ĂŠ˙˙˙˙Öţ]˙ţ«Ż˙˙˙ţŢy‘žr3ÚŰg§ź¤m4U++!d“Ö¶_J´Í{˙ű`Ŕ nA „­ÁŞGŁ4ą˙˙˙˙˙˙˙˙öů7ČqyiHsC0¤Er_?* –gĐNP‰îF¸v#UCŞ9ŽB!* jčŤA‡1Y(0˙˙˙˙ý`_óňţ_˙˙˙˙üßíŃžďzJ_ąK&´išôE3Šeu!&>ţF˙˙˙˙éĚýŃ˙ÜW˙˙˙˙˙˙˙˙?íńó÷+kLÖĂůŽ*Řëk‘s*—gJŹ4`žąEĆÇviF"‡$z©†9‚XőÂaË”9pčP·XT]Ç˙˙˙˙˙c`qîy˙ű`Ŕ˛€ .A „MÁ­GŁtŤą‰#±’ż%ľ§˙ţY¸tűîú㙬ꙉyĄ†ýwŠőÚ©¦÷ŢŇĄ©S»éşć˙˙˙˙˙˙˙ţ˙ż˙űżŽąškŻ«YO›­˛<6<’±…&TÉë˘Ć!wB±E2 ¸öłJ‡4\V…K1`°’ކ6Ëq1Š@˙˙˙˙ý±Í#WĺżĚ«˙˙˙ý:*\ČŞuo™ŹťH(έb+JB{1¨¦î·±•jj˙˙˙ţť7Ą®·tT2+9#ÝÖ<–şśČ©*­ ayŚQ1§3•Ú&=ĄB:``ë‘‚˘§Š ‰Zpđ[JQ˙ű`ŔŔŠA … ÂČ"´ˇ¸ €k?ýţ˝˙˙˙˙˙ţŐdR•¨ÉżWDJ2oR›±ÎÓęUd©Šf3¦Óo5ź˙˙˙˙ňKk­Ę„y™bęěęt3–S’Č5‹Ž «Ź,† 1 áňŽrťJâŠ0˘„#(@Xęč$,⊏8Š4xD*…„˙˙˙˙˙’Ëäű×˙˙˙˙űνey5v}úş*±K÷U2‘ÖgR™–tK"µoE˙˙˙˙˙éîíLĘŚE:2+Ů’}Hî8†fc ‹ł)†ĹĚîěBaÇds¸“!…„XPrxńĘ,Š($0<*5 †4lü˙ű`Ŕł€˛A „­ÁŰH"ô•¸Ö|˙˙˙˙˙˙ôM‘Ű[V۲NI”¬ÚÖçbk3›ß˘ŞŐ‘Z~ż˙˙˙őˇj‡»µ¶g*•Ř”ssB«Ąr”¬QGމ ”„)ĺ10‚ädĆw<)]Ě($aE8pj‡ĐhŔłŔ˘¦‰€˙˙˙˙ţŤßü˙F—=ĆÚţż˙ůĺ®›±÷őĚk*"§f5§˘*YŐC^bşę§»LÜŐ˛˙˙˙˙˙ëî‰wcަUÎ<Ę;0Ô=QÖ«žŽ¤Ź:łóż˙˙˙Ďéą&q…+T©µ'ŮTU•ŚgeR á‡B32)—ýž·D|ď˙˙˙˙Ó]«MÉş+YŚ[Ö¦0Ň)ĘbŽĄŽ5EQ„ÄEa!\hAÂ!ذ łBĂîg Ŕ(ńá‚ÂC &P 8|DV&Q˙˙˙˙˙ű`Ŕ´€ÂA „­Á×ÇŁ4•ą˙éä^¤–ú,‹˙ź˙˙žŇ÷f»‘×d[Ňş˝ VąLës ws)P§z^¬t252]ko˙˙˙ď§Ł÷]»ÍĘS2•gsƙٞDkaśÔ9Ç#Â"%F âDL*Ł˘ (B" ‡XÂŽAěbĽ>˙˙˙˙˙±…ĺţY//˙˙˙üć©J$î”Y?RşŃôVT}ŢŠrQKjßZmu}v˙˙˙ţ«ëzttz%¦TS•1ä#ĚĆ˝j=‘ŇŞ”ŞQ!0c ÓH,˘ĺ ŁÄÄĂŠ,ˇŃw‡Č 8ç1Đĺ†0¸ĐŰ ĂjĐö˙űbŔ¶€A ­ŃŮČ"ô•¸}s—˙ţż˙˙Ä@÷:XČ®M*¨Ccą§r”ü­#çDQz­Şý§µ)˙˙˙˙ýţ–ŃNbç›-LQl„0׳ )ťÜk ;Â,5ÜŁĹ‹;"Š 8ЦA¨$ÁŃqĆ\M…EAB@Áç( *˙˙˙ţŤ˙-ă4Ń—pţR˙˙˙ł˘îVlěU·K&öu+<Ď™‡tLěěv3ˇŠÔłI{×˙˙˙˙úűű•YŃ Ą*«“ęçŞîR†Đ<Ž=ÇÔLU…aĹqáL (s”ń¨§D4P€Č‚Ä !€8˙˙˙˙˙ű`Ŕ·€’A „­ÁńČ"´•¸˙ěŚ w"šçĎ’–«˙˙ţM˙ÇýwóóŻ÷Üu]fFĚŐH÷SR‰őô±×KŻ×˙˙˙˙˙˙˙˙˙Ďw§qóßLň°»OĄO3r˛1GÔ­mÍ»±ĺ;=šSĘ8yŁ2lĎ#•0ŃÄĂHpň ˘ĄŹ% « €˙˙˙˙˙˛źĎ_ĺËz˙˙˙ţţŠŚ÷Z2>ô±• ŚĚ]ŞC\ťČ„VGGuyŐ(Ęr˘J´˙˙˙˙ű[BUÝźaj«öŁ[‹Ő™HŠčăLR«yڇś¬C%Üq]Qbě->A!Yâ†`Ł1\Xâ@`Ň y?˙ű`Ŕ·6A „­ÁâČ"ôˇ¸—#˙˙˙˙Ę­ä&ĘŞËťŐčęĘ}V¤1ÖłFnną:ű[Ż˙˙˙ëmjĚësˇŇŐF!ŠbŠę®Ć)†‹‚*‘LŚc°jˇ¬*‰˘ď0pĄ>$av!śXč4DHÁÁ5‡ĹĨU(p@1@˙˙˙ţ­üŚ˙ĄÔËďë˙˙˙7ęěµR!ź#ٵŠč¨~ܦf5ćfb1ŢŽÝS"Ű˙˙˙ţßčÍišŁP®vJÖ®d1qčq”2ÚČ0:Ĺ+‘T6aÂbEl?2‡DD…‡‡\M"B Ł˘† ”Ác4\6Ă ˙ű`Ŕµ€.A „­ÁçČ"´•¸´ýą~ö˙˙˙ţT«Ű"Ł—ÜÇž—ŃsŮŃhę®DÎmYd˝îdSnŰ˙˙˙˙éíľ­:Ş3š§š•c%®5HQU<§!ĹEHcDH@8â(‡E`=‡(ąĂ§qR¸˛:$&<(ją‹ĆЏ  hŘ6­óćż˙˙˙˙#í%)sMŞŘí1ćrÚě·IUkwG‘rîGtTe÷Ń—˙˙˙˙ŢŐęŰ++Jζ%ƱŢČÄĚ<‹;‘XÇ8q‚:‹ Aa6qe- @ĘAA¦`‘Da"ÄĆÄ„@đ™”:?˙˙˙˙ý$˙ű`Ŕ·€A „­ÁďČ"´•¸/ţN_¬™ôë˙˙ä ÷Gő>ľJĄÓ:˛ť—G4¸±Q(„tC\î{+©•®gO˙˙˙˙쮕˝Ôł!O<®ë!¨Žd,ŠćR1>‚%f0°¬@ę>qâˇô (¨ĺś4ÖQ!0ř€şX‚eQC€˙˙˙˙˙Ňäţgě?˙˙˙˙˙-‘ś¦ŽI"*ĚDŁ™•̧Y§In–jH~­’Vkg»T®Š¬‹˙˙˙˙•úŃĚŽäRËŐ®¬–K¦í[ÝF"Ψpá®ĺs ”<îáC‡A r$ň 0×R‹ŽĚW€˙˙˙˙Ű˙˙űbŔµ^A ­ÁéÇâô•¸˝˙˙ß›˙˙üż->ůŞó/ß&@şŇzšşË)˘Ô-^źđĽŽ˝Ďmű˙˙˙˙˙˙˙˙ĺ‘ßće ·îV›E‚6ŤťŢő$4Cą»Ľ­dPŔŽŠ$B¬ŽAĹŁE "Ł,†8SW©9 € €`5‘ç˙Ëćkúë˙˙ň˙ZšçTCŁú^5Ýő‘ś¨bT©‘Üz-®–™=¤˝ź˙˙˙ýiCž”čËB)©au”‚o,ŃČ8‡c‡ ""@”AĐDĄ*<:*‡ °x:@ůDEEH8:.څЇ†˙˙˙˙¶üżůëď˙˙˙˙©˙˙–ŽÍ­îGH˘’°»Nޱ™ÄW%˛zŐóŘřDo“eŢ˙˙˙˙˙˙˙ţ_ü?)ąleyX†żç)ÎśzRĚź"]Ë•L´'dxČĐtS 3j±Ű é ŕ˙˙˙˙˙Ŕ~ds˙ű`Ŕ˝€>A … ÂH"´•¸ć˝ ˙#˙˙˙˲µĐŐc0‰&V‰ZâBĹrĚdpëFEyFm Ą*ł¬Îľë×˙˙˙˙˙żżmYĘŠVĘĆ}LęĆ+>j=‚¨,ĺ-*UcĹB˛=ť QSÎPčŃÎ"ĘYH  ˙˙˙˙˙˙˙˙˙˙˙˙Ż˙˙˙˙˙Ż˙ý~Ć(`JEŞ*!ŮŮĘbŞ”Vs ` #ł±Š©˙ł”ÁB(˙ű`Ŕł€ n? „mÁŃF˘ô•ą˙űbŔ»€&@ŢMŔ[Ŕ˙ű`Ŕ˙€ŢŔbeetbox-beets-01f1faf/test/rsrc/oldape.ape000066400000000000000000000332031472325477400206330ustar00rootroot00000000000000MAC P4Ü3Ů0' 0éoĹ|ÍţqôčîD¬D¬P('¸č+6ąD€»…›gzżă˙nč±`ČéŻCÔC-]áXUÚ˝ŰÎý–Ěą€ËRôa‹% –ýŇń0ó©'lOéŘ[&ď(ďďHő)ś"i%6aMJO1FĹ@…D8Ůqlpů¤ŚE ]“ą$ę ĺýwëvy0äĎ\ţv7®Ś±I®Ć ̇m7Ą.uMŇÉč$Ĺý #­×x|‰Sźúgb;<·|:}|{H?k¬ň"ÁÓ{äěćwß áÝéݡ"vútŽ`OM".˙Ä+.Ë÷"q=f“; ĺF&8e*’<ű´íH4ŠČiçŢ-w»´7rý:¤ŰśJ \ó!¶Ü$W'8[4“ě«„ËAvěMXFĘUÄÍťHU®qta!;{;Şëą ĘÁ­ŕG^ôad¶Gę™gŚľPĄ­)«Ś·Č úîŘ™ÜzĐšSŽřg_ęQ¤Ü=ĎS Şů¶¸âčMż‡nć”– ­u%ă÷ú˘ÓÁ8˝ Ě úx«=j]m6“aú@Ť“I›L]ö~çęn0.*§ăĎćcžânQjŠ0‚ôęí‰6j®ń1ÚC%g§4ß\-GhŮ3Ý•[čŕH"’—ó@ů>Ż3°+©ŮÎ˙äÜG˝ů§W3rÚKn>śŇÚ+{ç˛ňô·Š¬´ &…ń6uż_zęmeS)K+2ňkň± éŘ8ôÝ‘cĽ3L®ÝÚ퍎Şv˛WܸÚßčŃ‘¶Yŕş“@§1"kâr}őqIĆţu ¸ ˇ©[#ŘČôđ¦‘w“f•—C?s·”b Ś[XAĹĘ Ă=‰‹\ ú„yäüťvJeą•ŮׂfÁ$0˛¤yŁž}:ŽÎ Ř­ô[čr‰zŘ>Q†Áx „“(® ~ 9Ĺ5QţKť¸ÓÓČÓ&)±¶‘9jÜaĄ«`ľę‹A8¤Ě×– ”©żŐ"“ŕ†;bR3¸ “D©ťW vćÇ)ăĎłS›ÎŁŽŹÚĘ̲A}ĎĐꂇTÖ‹ľDcXHwüWE>Čŕ‘'•:ЕȜÄqqoie4>#Ź÷ -&}ě#}FŹí5pĹ㏱ăł*"•(taŕˇÂŕĹ“"Ń)®BÜ -·,AEexjĄźňÚ†›űu®RŹH×n_¶n¶(ö¶Îâg'v9HĂ^%Ü!?ľ"XÎ@µŰÚ6×›R÷…­‰ŻĚÎyÔĆá=ŔĎ‚öŻ–8/Ö3VőMć»8ż±˙u!đ™rP@ÄÔŻ ›“řqSzá~ĺjwń¤xTčv¨TÚ.'˙î†>ĐäŇš?Î`XşůĘT÷:™—ýĚ Aă?”1˛\@đd±Fâ|ɶĆD˝‹°6ő Xőś“4„,đÁMÝRż#÷°ŚĄ&ěeőľ­E„›źväĘýS˛š+vWČăsJ˙Ëžýxń˘Ěśă{ö[·>¦pQ… t˙łňŽ)®Ě ¤Ç`ămnX±^xőÚ-Ç®“©=®ćŤ´|ťt˘Ŕr…βţ‰ą!ɰvČ ^Tż ßN‘k1˝9Ą#śŢiÔ[@ÝöSÉ‘’č&ŔĎvš¶¦_Ö.ŻoţUśťć÷ŮŐt÷xLÚ˝ęř‰H/ ‚Ďm…ŇfčżĂÉ‹.y Ć8Ô÷KüQE¶p:•7'§Nnľ„ě 8lhËŻîo=›}Żľ•9P}Ví{sÔč˙ŽŤďnsŔXiVÓ”íEI€íH=ŇősşK—Î{ÍÁ»ŘŇcyO€g›ˇPű‘–ő‰żÉĹţµ\3yy8#;çtş2‡˙ę[mš ©í‡˙‰y0ÚäQv{Dź×®ú3@ä ă­h˝lXÂqKž!®!őĐÖąú§çÚ‡źîŔř–%5Pú]×|ąő´††#i2‰öíÍý Eäůě›ęL˙î­ 7XĄ˘)E»[Ýá[Z$ѾƺŖ˝żîKšW¤ĄŇĂśíešY#ńŮ,‰íŤŢ„ě.eŔŇ<Š[čÍ؆,ŤI#ŚôŇ·5´BÚ ˘6Ěĺ)ĄŮůMP™12]ž Bäă'‘i"’-,ąXQ3Á™€Ř_@řĺ©ĆY5ÎŚDdťm P Uëe*zď =ý…,ňc‡-żgCó˝6a‘˘1ucô°,a$őÖ×zܤ ćV‚ňŔRĺ “”$}ôÇĹ~ď¸9EZç1°H.1.–j¸˘îżź{—N]aL·.7 ¦Ż€DQ‰l¨°Ůń‘±89Ĺgg­vDmę^ĹĽäžďđDUV‰.’‹ „OX“EV˝ńúĹŻĹż>qázŻ™Q}(Ż#¸MoNŞţ‚,–-ç~bűc–97ţŰ W_ýÚ 'sžNYO j]feČu´Ę6óÝ3¬ĄNâ‘+1g¸`äjÄ;Vn6ý˛pf_OBdkŕžpěuŐqž\HM…kŹűÇbµY2}¶PĎe'ĽŇqCGˇ[Jě7‰P~Oëę ’k¬™‘Ő3“/—A ]%†°h@ ozWÇlýŇ0—uąrî[˙KŸ}–ún˛gţĚ@)E˘ …/ď׿•A ă썚¦›źÝéFz ‹ěӓ͸B‰@[Ë %Ű×á!Ę~nÂ8o¦®H#7±=ŞđG źöżqÍĐóО`Ka˙„íHĂPXŮóú{ ŚŻŢz“đ|\+Čç|+Nn{W´*ă›Ňý2†usá1\$«J÷‰µ®E3Ő˙°Ű (ÓÁň‰ĄQl µÓâű¦\ő€V 6°ě¶†RĐ-]ú–SŃM¬´«}.ŇZ+í)BĎw{”ű]ňŹ×ĽBôŽ Ł[Ú~š–’‹©–uÖęk‹§*ë[“Réů˝ĽČKń^˙Wń.ýŹyx^čSĄ`ąÇXń Tť9 Á‰}­ ,f%cň¶z¤Cdđ°‰/oűXfÍÁgî/ňŽćÖLb ăząIŃ9z,çik߸m2ĂHßč:ÚČ™U3±6ę›ĐÔVŕ-­!S)­Tµţňt —Ăߊq‚üăÎ4zM’î=03mcí(f4E×Ů9¨Ji#çŘ$Ö€˛‡.Ľ¶˝î—fąOŰša—żÇFaöćoËFVą68qc•ň:u[PŻ_«eŇžfŕëf›ŰÖ»$;RľíźÄ=§Ś‘!MŹĆ—íS±(ŮbéLIŹçoWŮ>ňź“î铳ŇÓ¶—S{Iî=C.…ŠŰÍ'ĂÎ?^“Hut†q§ą›Yn—¦´®1o2˛ňz.˝ :LŤ?ŃÁmt‘¦×x) Lěĺ\číö󬍩K•aż´¬p\!)ó §÷ŻP#ű…?#™đĆÎęX58ć»ů޵¸*l¦&#ŕp(müL‡3–B±AóéV$®ň÷\Ö^›G4–Řď”yłŢ°"Q’©s¬Żaň—R%VŞwPRő±űoĆuŇÍ‘?şËèđÔř| ŃjĹĆ ßFZ© Uíçíw¦łĘĺÝLeňt—ŢˇŁ±ß\®]ťnfČ yř*e6úŚ.ü¦”PŤčZ Öâ‚ ş!ýi…ď«{ď¤Ý†S¶hÚëęŘgeyäÎy&úń —Ę6#ýF(;`;S÷íţX·ŕ‡«¶~Nţ>^„Ćjß˝#䬭VĎ%ކ„Řd8tLíĄÚF:änś_ îőČxrr«ĺô± „ŕŽVˇ˙—;ś!ŃĆüHŮĄ©˝IÄAş_\d™ęŢYšštIËľ§ăzŇ!¦%,Ľ"{–( –ĆiđĹĹŻÓďI«»öŤe˛˘mnřqĄŚÉ…włËX!qőüm­~"ś.Ă5Ě08ˇ8’ëA&›k÷y»pĚÝg¸ţäˇ!O úĆ5Ň/#Fv˛Ńľ»AßaI$¶öůIM$éV]ĆŽÎtőş ą\€Ť^ö*­B§X/e`{Ćh ÎLóęĄ5ÎV4híE@ňEÁhŚŤ2˛H1C|ggÍĘłĎß® :O­ —Bż ¸9’Ś0˛ďXwTl}Š$4¨éť]¸ô‘“l“ÎôFÝެ˝0ĆeSbŽ+ż’í‰<ŃL¸J‘ő^‹!hŤňďˇSiűA Źfa‚B$?é×ZAt¸# – =š üżÎGú—çTűÔűőUľ‘‘+FčX §L7˙»-áăx»µżN`ŻÁ„6$cöÓ ÁÂBÚő:—ţN’mkCŘ=ŽăF/}8âÖă^ÚDđAsą´®#x_™•C«KT]ÖƲÔľ˝mřa×á}üo† Vú´âä‚€ +Z*łN÷·Gť°.™ IĘ›ęáS \†Ä?ΤRý}-JzăˇÖbÍ­węµŘµ„ …Čť™_’ä7ĄĆóĄsRáÁĹtÍ­n—’ëT;×ńŚl CŽS÷Q’ëĄŐ>Tˇă‹#A陡Ż:^ŻhőQ”ŠĺÚĆĐĚU fB\˙śm\ŐMrYó^ů`—A‡ô–śEëQďZ‹’Ź˙ĚŞ™ľĐ8#”Ú:V:ŢgJo’C‚ß0~×}”ćtž4$ĄşóÔň°Ái“@b8epÚµÁµíĆE+b÷y–ßb´čłň„«.ÓĐîv0ů›™H9ű('Ť%™ÎqÓÁ‰‡N«)†{ś‰b˛§?›,-nţ x˝k Î?UT…áʰ4vl”ĄP{%ŞÝ‹vÄuݦBnđCżjI÷4CLJ§ŹěłXk?fCŻĺjpőßżh×q”«ě50RHU*‡ŇŘĚ-m‹:Á|ă5´;Q•›µß&Ľůě¶PérçŐnŽŚL‘Uçň×5÷©yéšI,OYˇ*„áůŽ“)XLĐN5 8đ‡L¨Vá6«łW 9Q R#+ކ K˘ěýäXÜg.náá2Šá»í2KŇŘ—ďTőÂŢËÇÝ„ĄF0XC‡,S_ç-ĎÔ,ŢÇńäÄÜŕ–¸‚ ö3"¤m˛wr•‰kBÇR‰žÎręK¦Éĺ.yú°V:±Ü;â *ť 0čk˝—VĽ!ľ“ŽBçýę ÔŠ¬±ěż´Šú祽ň´D…¶ďbhtÄ·+ǵ!dĆŻ˘t'UNa¤…‘řĂČĆłRV»3J†™˘ńqôňď(ÖôŹ<xm%cw†8Ňž gVlz/ZŔφ“é{ꇕőGď@îvŤ•“pذU×ML-wmŐ<ő[ü ‰ŽęiAĺ8\0"k87Ęž??—…Ź Zî"¶B'ěŤry/ ڬG’ń)Ę•Hśjľd/¦EŢLŃĆ˙4×8éG©Vä^LÁĘr1˙›¤äP =ŁżgŚ(F݇VĹŮY´ü4Ĺd•SĐL×ZÄČ˙@,Ú†qń­Ű÷sUwOéB©ŘŤ‹đáińŽ$Bżü(Ü»/ŢśSŔŃůdńëŽ3{Y¨¸¶ŰAňĄ ŮS'¦ęçX*=eB3;näŚ1 îr§ír™‚ĘG۶šăÍ®TóÄÍ <ĽĚžá—‰šyłsWűCáÜ=ŞÝĂIjĐyg!=°Š¤mŻ\2Łö}bD!bëŻ}ÂWśš˘ĺĐÓÝ=XŘj´:×…*hýřA81 #$ÄcŘWfš'çF]iJo·âY…˙d çmdĂß B~!=9- •ÔµţŢ yMMfŞżlŤp‹ŮŢuÁßá°ć•ą6ą•]«Ůřk˛¨6´%{둞: µ·Ĺłë₍ďŤű`çśş!TĚşp=ygxć(q–®ČZnĽ@ŠEľűö˝aśeŻc6ho†UčŹĹ[óuř"Đď¤}†aĚäť“ĐÉ>:óŽ)ß»^]Q¬#őy«‰Câ«9ĺÜĹÎŢŃĹ…˙GąDZjśłí&⥎Aŕ=䲽um©q>©jJF¨’!ŮÍŔZ »{Ę^Š©óf!— o:Kç\tÚJ8™X@ —úÎj Ü4«ş;07:MóŃ!©çń3Vď‚€dܱ MÝppËTýµ]÷?lťŹórÔÔV/b „Ö†68¨ş.'“[±$<בiﮂ¶ćŽż4 Í®§đú€1ÜŹń.†úôÂ~pç”d>?ŘŐľćw‰8­±PąLçbđ\8Q·Ě˛mďťf\ e+‰ő˝Í® ů?ĘěŞd)dś“ÉóŽŤĄµ„Ź…®W™®´ŃIr$YB8v@Ř 06ŹŠf%¸.ý·Ę+1_ĘŁ€Ěî^Ѥŕ <•şîp[GépĎŚwĚ ‚ěŮ´EâÔq6ŰŞńË´×›ý_-/Ľ‰kç qNúăKŽńLDu÷ďá’ c_DM!ĚâçwsV·¨ą -đUÓ÷ăŘźAŻŠ´Ľq®g@Ukwíěp~SE9˝tݲduÍŹt˙}*Á&ďYeŹzÂîŰSŮńEóÝâ=|çÁ Ʀ ;ďĐ<_ŹoâLÜŤňÔ|’'Y÷űČá=‡2¶y˘nď˛Ě}VśúRětOćĽ ţ•A0gÎ:KÉ{‡ěµÂ]č­I‰}/ŕŔjóŇŹah•_t ®I$5‘uĚ&.µŇq¤2ÍÎ^żw…dMMOč1-)zŕuńĂX<Ű]ÖIâ ŽČ" Ý,/g® €ă|őŻ>éźu¦¶ËŔ9ĂV˙ŹűžÉŇ/ň5k€ě-!“[Äř&ţńŚ FÖ Ö{ëńq±ŘőwhŽĄ ćéyő3˛]—ćäéŰŰP\5NDůěŮžožcAČSéâVm>[ţâ}kE:Ž"üşÍTĆ ?Wź î'aď3QíjŠuŘxgÇúnrj–čMQyµäçźĎťhřV´W†–&Ţ˙ŰEŮ)ó–Č«<ŞýLÓm˙ŵJEöíÄDâ|řdćCjąžßA§+ä׉Ôw,Ý rcĆýMÜ­«M“ĎפŤkFKgÓÇ üˇÇZcxé ěĘ)lQYŽ*RuątO’2˘Ľ^˝ňµW@ýäŢ‚˝ÄĐ`ţÜÂTťŻšÜ† fCpK:©Ţl)Ü9Řc––Mž¸¦ß®&)n,•ě‹Nń%ů»ß4Ťç{Ä?3ťĐ^@>čČJ„.ňE| 0ˇ×\Ťě§=e÷7üŘsÉZ$ 5Ţ«w? \."<$Ó)´ťßpäRwčĚß6Î;ně\+@°ĂiÍő”ë–RxĂŁ7Öî.V€~傦Bmk4ń@áé‚%?-f$1ż˛ÍFbńçšă•˛ßPďeCS r:ýJ(üqŔf‹µ9âçä¶L iáŘŐÁNS(†î)»%w–yűyđS(Ô$("­Ť¤-­ŁH2Á‹˛f>šËŮVďŕ$xb‹žsĆhĂ%uÎIíňm+'đNÝçdëź?żpőË—8}ÖÖĘŕpÝbŤčî§‘™ß謏ŕĚŠPÄ«Ż(KL.Íg ĽkHĽa+Śxé‚3ü1›+éOéçĚ…ar±7& ëÁvődVóůOÉËę6!ď3ŢB4˙ř,F|sÄ·ˇž¦ëżőw@*B`Đ_]ćq>ů/ èĂńť˝ř×Ô"ęÎÇNiK&”…=.˙hľŔ-JÓěćÎ;RvÓő¨„Ŕ;YDŻźđÂpâšôT>JmŃŘ„§3ażŐS ¬J+`Ő=*ăŚ1o05ÖYĽň1¨kŻ2eîÉŇÚĂ:ĹŽň5IrÝ Źĺ¬WÚ1Ë1(Ł X=~É`ä5ř;B,d—´5ŠB KZOů>üS.ŹTÝ Š é%*¸ŚĹşţíoOú4yJÔ8"B‘¶`G YSôâ#ź‰ U¨I• 1ú÷nůäîy)~Nj7Ź÷•ZÜgą\|˘˘EĐ «˙«0‚ŽínSîĹŤŔĽ“UúP Rf=˛ÎŇòUEą!ż_’šďŰ?~ ĺŽ+ćΰÔ[Ł!W;ţ#„µ‘ú%‚tqšeŠ*ÚĹö»ĘŽLđ ű‹ňY9WTd Ę+żvsÓ~Vň!@śş’šÝ ;éWÉďlťŃÚUéf€ć6€ŇGľŤ´y ˛ü–Ą%rŽŠLb@çĺ-ŹŹ,ßÚy ÁĄÝ MxR 伝o^‹ÉüdçN¬źôf¤ĎóÄĺmŕ% Ű1÷[ňKÇ;‹.žúBşĹ,ý˘á:ţŁţ^§ÁĚ&µ> ę/!†3|Ö«SŢ—yTßF?řË0E22˘>¤jQu?ěžx€©’űî~$[Ó«™8ôŻů{Üw!?I‰’a ×)!ö@ŕ°˝Č@¶ jTz\žů+ţUĽ•řĐęĂŞĆü߼–T˝Ś‘ŁşBŚ+.ú€Ý%Ű'±Zú oMO™ś8b(şÔßg.ŮŘE·ťácGZŇËęźB—ćľWx‚»5őÔ0¤'śç##9]!@&ĚóŹÝ˘FýL†FŠĐ*ß•Âa¤6ď`­Ł 1\j¶Ś©űܦ#ÂôĘÁ«ĚŔ5~µZ ś¬;s–íąŘźŻo9­5 :!Ď$˝—ˇ`môŽJşŇ¨ŃC+śţH;q !LO‚ńŞá’bŤ5­O~ 6D„o§őÉňŃkCZĺą@ȶD›©t†xĹř Űőż4‡ď¬Ž<śŚU§9ë—žćgů˛ź•ë–zŰ"rUŻ BśŚ‡,ż|Ľ?;Éůńń0sę@aŠ€ËłŤéYňó:‰’@5¦1ńq±0ĆYDăc:âPoBę.Ő­˛WZ˛ÍDL÷·ŢÜ ĹěZ­°ezž> \_Ľ7tm–îÓë9&˘DütÉđ—Ü•hI >kć…F€‡ţŇLöŃqWPBX`á#¦´«ÚćAßůţ#ÎÓXnł$u1ŤY ŔuĂ|ţE)čx†Hżő.®-ż\tż<7¶dĆ®(ěTüľ%ž(Í‚%Çę–¨«¸™Ç¨-’7ď±=Şaß’Ö±ü@†?Ŕ‘ČTˇ}ý‰öó·p ÷§ŕp(¶ŤRW‚»\źT@˛ ž˝îâ˛;)BhôR9ďţ÷ďß®ťă«´ßě?}_™‰lż &°Y–čę˝odŠ‘ô*ďđĘżdD«´âí€ŤĚ łűŞ]›őëha >ôyTMp´2‹¸e q¬±Ť8/ßźjČ ÚǫɿŔĎ kł Y® ľ m’čş].Ę}űß`ć4Ę1Sy;€ŕ,ÉŻt r!Mő1Á:°/i)¸XŁŽźçĚa‘Xö–ř 4đRš(Ś”ŽŐŐxşäIfQ3sĺ/ŚŔ†Ńöž5ŇŐĂ',ö÷,˝Ç˛Şu[rwÝţ ÖĎn i á‰Tç†Ó9XĎs§łŠč \·Ř =şśôî1 "¸coäĹ/ęˇă……¶“ éžk?'-ÍHWŞk …ukČ1 0f›-ÝŹĐ”¬5˛gńéăŞřÖc» q;ZŔî5\ÂÓY5*÷[ŕŘx–ŔńŻß R»Y b˘úÄ_(%…×L™ëa h˛§5(·yÓn>ť‘›éDŠ-¤Á»/HňŇ{uäef/TCIÇ·R+FlÁ±řÄ» ÷UăÚ8ďúfBűa¦Ţ<š’9 ˇŚUvnɆŚ`g Kł1–ŠNťDß·¬ ÇL§? :ÚYS3oÜ 2b~ôg ó(š/¨y'ć”´¸ç@I%ťJšZĐö3 lülŠY±=± ^!uaŻĽeAöč(†ź34ţܵ˙ĎSşaţ^ŔrŠŃrmgG`+n]8Ôīż‹ů¨_Ă`ń řHpn]áĽ.2Děň{µ¨đDáÁí¶|’>– ÇÓö~­mWĚÚxIť#Żg—zžČ;‡*Ő~¶ďĹ ¦ëŢziť9§6@ű<ű˛ÂÔĆşI&o /Ţ…tŻÔ,=?«v•s»ÜĄŇÎ @™R)e~3ŰŃëŤűcęhiI—·Ëť¸Ą9«ľ9Ü|F{˙C2V1ŤŤ°÷ŢSYęôše@µ 9_, í ń9%¶óŁůÂúĂbź…#ć2ä`O\úČf+e^ěϦŹę«_~úĆaŐgŰÖ;¸i † ď‘:§3mmŘÁÚţ„ ź{w©!dP-ĆćSęgŁ0şÂÁ[Ü{efOŠ –s@¶ôĎŃ_!”um˛hńM}ő!Źĺ‹ë+Ň&‚6YH C‹DµŐ{_ĹĹЧ0T7m`)C/4 Ő/a‡ •uâ©}XŰăcF ‰›Çš“SéşÄGÔĂ€býťzđóąEísÚ‚XĄçs Ú˙Ů‘ž˙:°÷dţ%ÎN—fÉóá ôVÍó ç掶¤CXĎIýóę +{A:3ťÎ×\4ń&0]-k•ČM$8ŁÄúâţ…éă§Ťjë]at ¸˙±/ć‰h& Ě(ŢŚj MÎśűĘ+«ó˝ăĎąˇÇÉ8,'ÂC˛Y0‚ůnů­eCÄ«®"ˇĹ«B¨ű1řęĺŚůN>R.pń1ü§)L­©Ó…WRW‡ŰZű°Ĺl˙ç„ŁŞ–ÝŚµé¤ &Ě‘ÔÜ+UáĚ&Ú«u´ýÝÖ~XŞĎ"cÁ"l@u¸Ń0ľůňČý×Ó'Ř ‚S˘XŠŔăZ¨\%téÔrŠ‹n¤Ą5Ěç*¨\QzáĆ=@%â|żřŃű.TÂO"µŘ¸dGúE"sXYŕ!ľäx­}M§ÖÎAdLąÖ\•«üóÍ˝lö·¦˛ńeQMYĽ’ăĆÓŃęěÎâĐň^§źşŠÚđ­)č›ňę)—*ńćö} q ç‡ 0‹ăe¨ÇpwvšÓ ćĄIÂŻĽŘW‡ô,2_FüZlŤŕ:Ô%,,ťÜ!ş9ľ…yÓ”)[Ź~CZD,ÚëM“ß|NŔů_¨ĂJ[‘1hëäÔnĄĹ-Ş^÷rsĚ«§ymLBÁ÷Py‡…˙k#”ŃQ{óxĹôgü[ ŘH¬­aĽ ůݤw˘wGŐźů^;Ś<ó?ó¨<ˇÝ!ĆbNmÖđ<^Ë´™É ýPÖËťŇ6»y^‹ůˇĐ†¤ż•;Ń„@Š˘¤vf+]ű˛2{ćˇÜVFŞÚó¨e¦&™k1ŐVîj6Ľ_Ł™f˝×L2bJŻSĚŮmýť«3QIÜ]ą»ÜÖć_źŁ‡đ8řźe$ĘIC„I?(RaLĂɦÉĚ̡IžS…9Č“4Ě)Éš Nr!(RyB’xJDPĘ99”9ůe‡=0ĘLĺ…%™3™ĚĘ™ˇśˇÂ!(S&RćaJIáś(S'0§' 3ÉL”)̧ dó‘ žPćg0‰2PĐĘL°łBˇ’PÉ”šg>i'Bee I…8RP¤ČNd‘ śáNLćIL(Rg ćS% ”&JLICˇBPJhPäĚĘB”“ś¤ĎźĐĺ%%% ¤ĘJaĘB„ó–r$™He$ÉB!†™?"ÉĘ~s”3™LçĐ))ú(Iˇ Rg(g0‰3Í’ž†sˇČBĚ4“śáśü¦ffL¤śˇÉLšOIô9CĂ2|%PÎ ”Ă)%2PJ ”(pˇC9333š…0ÎhsCť śĺ Ę,ü.)=!ĺ ¦NRg” &rPÎIfg0Ó'aš…“3ćRfe B!3™)Pá@˛J8PÉ |ôĂ(PáN†PÎJB…&PáÎae%32’~dBag2’|¦s@‰0ů™’S33' ” ”&D'0˛M ”(P§9CśˇĚç3Âś)")’!2!ÉĐ(RPô9čdˇÂ!†g(Re'ü¦JB!HS)“)’L”)8XDe0ó ™”)2“ţS'”ź)2“”ÉůB“2“úfs9™C93% ™ÉĐÂĂ)2†rPˇL”?)2… ć'ĘLˇĚĺ'ˇĘsĘdňs†JIL”Ě)™2!†“3) RLňaILšaIɲR‡&sśćH… LĚĚ”ÉLĚ̡śĚĐ)(D39%'¤"‘ śź”2S%% 礡I”ÉL”3’™™?(S0Đ Rgź”Ě”2Pˇ)üˇÉ”„@Ś€D…„ĘM0”Ă33&“¤ˇĘ™'ĘJ”™ç)™’†ÉÂś(2e„ʡĎĘNP¤Ę)’g%„ĘJśˇ2!“™’†IÉČ„ĘY&’t&S$ł,¤é†…'% IC% ¦™”8“˙řÉś@˙˙µĆP!9BR‡™“ÎPđĺ”$ň…'3% ”šdp§&†NfaI”ĂĚág2’eB„ˇ’†O”)™ĚСĘ”ˇś"9’…&i)Ď"(fg?C'ĚĚ™9śü°@¤Ě̡ś™C8D2pˇÎfs9śĎ$Iś°ů”…2†s rP<“9))"xL¤ĚˇĚ.Iˇ‰RR†r„ô śôĚĘ’” JH|¤ůgÓ0¤¦e áLĘI4ź”Îç–ÉL,"8r™)’™ś)3IĄ ¦Â!™…“3„@°ĺ$ĘL"ÉB’źB„ŇIˇBR‡‰™“Bae2S’…2O …(M ”š™aNfe…% r“)%$¦)(ú¤)(d”™ó"y2†…'ˇ’†Lˇ“\žRe'”(pˇfS „ˇ¤ĚčfP)™(hd¤äC‡ˇĘfRdBe ”ÉňÂ’‡)4(g0d§)<Â!„I<3’…&yB“śĐ¤Ę“ÉčfPç%2RĚňĘáBÉ™”źĐÂĂý&…&tť0ňtÉNLˇĚ¦r‡RS aä:ϡ’… Ěś3C9C9$C…3$C”„™Ěó dÍ'L<”)2™śĺ$¦IBćfrD)ffg% !JCL9”3% LÄP¦NM0ňPˇIśÉfLˇĚç9C™™C9śÉĂäĚĚ”ĚĚ”) "™™’”š™¦g(sň’P¤,Ę™43źC'™Îg3™I”™L„Lź)™:ž, ™IĄś3C')’s$@¤ˇI)(S%2XD$Cś"B”9śˇśśĎ9C 9ž~YˇCS… Đ3§$ąś)“™™śÎPÎgĺ2J“)(r„ˇC“2e%ś2™†…%™")śˇĚĚÉB’g fg2’rS2áaśĺrD)3‘„B„PĘ™C”„Cś2““4ÉLĚĚćyĘÉOĐááC38R†rdˇ’…'8D9ĚćS!°"dčr’…&2e%PĘB‡“™2 P¤ÂžS…9ĘNPСə)Rf’…&Rr!C”(Rri…2s2RPˇĚÓRtšaäˇI(p RfP"9Ě‘ ”)(RO% 2D9™ĚĚĚ̡L™@ç)śË3”3“™á,(hRC (žJP”ˇ=ÉäÓ ĺS3śĺ će2hPç (dˇ’“"r!3ÎRg(L¤ô̦B”“˙)(ICý’’‡…&|ˇC“™ĘÂä/”™C“(s …|ĐÉü°¤äB| (Iň…&r†)™9™™)?”ĚÎ}“‡3”<<’…!BÉ”3Ây™”9”ÎdI'”™dˇ¤Ą Ę9BrP„BO’ F,ý ”ĂĚ9’Ę“2rS3%%%% IBĄfP,:R†B%…JI”3%‡3(g0ł3™")’™:JfP"\ˇIź2’fs9™‡(̙ʔ"IćD) ™>P¤(RrRĄ!BÂ’…$”(JPçˇIBĚĚĚ)9™™™™)(r“Ňçô'ü¦ađô%Ô̔32P)3Ďź(y…2pˇĂś)I<9”9™™™)™Âś(S3™™™)’S&D&D3„I”ź)9I”ČDÄÇ˙řɉ@µĆč!339śĘađ§ dóÎfaĘ!s LĐÉB‡ N“L<”)8g?Ň…ÎP¦L¦JP”ˇ4(JBa&P¦Lä@Í °¦fM ç˙ˇÍ L¤ˇ”38Pć…&RS‡g=De É”„I”3…0¦NJJHD)9‡’Y@¤ˇ™ĺ2Pˇáäś˙)(L¤”“ĺ9ŇzM0ĺ Ě,Ϥ(X&xD™”4 J)9’™’„@ááe'I”” JÉĺ2|¤ˇ(p¦NP¤Â!Â’’…P¤ĘM Jfd¤äC'30§!a”Ă™”8D8D)(Dśˇ)™†R NĎ)>čr„ˇ’L™@ÎLˇÉćs0¦aIB‡“L¤ˇ™™‡’áBhD $đˇI”Ďśˇ™)™’ P¤ÂźË 9ÎRg33338S3'333$C'%'IŇP¤đä@ňdC338D4(Y3”% “2D(JJBP˛…&fffpł p¦e J“2Pä@ĺ%(OPĚĚ“ÉJ™)’™Ę̤”™IĘd‘…3 fg3™ĚʡśĚÉL””ÉLɦ“ĺ2t °”ČDÉ32S&’†„C™ň™9L(D% ™I9„C%'(rPÎe&S'ˇBRs:ś)’Xe&r’e!¦JaL™IL–g9C„C'2S dä¦yůL<…2hfp‰3IŇt(dĄ čÉĚ”'C &RM% s)2™(”<ˇÎ¤ź 4ÉL̡š<ÉĐĘLL¤¤¦g dĘ™“IB“†p¦N‡"8e Ě™B‡3˙čp‰(ćJB‰“™™’!Â!IśˇĚůLĚĺ!9ćrS%2S9’’†xs¤ô32|¦NPćRz9Ędé;&ɦ¤ˇLĚĎ’’ĚĘdčr†fL¤ĘfS3™ůNĚĚĚĚçˇÎD’RhP”ň™:Ă”93™Â™9™ĘNS$Đ“Be$ˇH_üó9CĚć’…&)“ňśé(Re0§$ D”)ĚĘÉ”332S<ň…dˇÉ™4źčäź)“ţ’…&RzJˇC… ‘R…PĚÉ”™Ę(SPÉI„”))RP¤Ą ä”ÉLćRIILÂ…!Oˇ= ý&”'S$ňS”™Ę™(S0¤Â‡ (s,ˇĘ2fr’†fsˇIš™Â!Îd¦M0ňPˇĚФ"!JIpĘNfs<ˇaš,"ʡ“ćRe‡(Nd¦fJd§ 3™C%% B‡ =%RP¦g„ˇäĚÉ”‘ ćOš?ô(JĂ„@°ĘM0ËHD  R š™ĎI‘ Đ)HR’Rf“C„I…)$ˇĎIˇN¤™I”” Naˇ4(9Bžˇ43™žyC@¤Í äd§ĐˇĚ˙)“ y'3”(s39™…2s Lˇ”™IB…!BÎYô2r‡3”3“3%9ź””2zÎI’d¦g˙B’S!IůL9śĚĘ̤”,>`A“)%8r!)ô2„Ę|ůBŔrddĄ$ĺ B– J™’™™ćyĘ y‡(dĐÉBLćK32P¤ç)”%čf‡)’™))(RLçĐÉBe&†y™śĚÂe&)2’’†‡))(sŇzaňî’˙řÉŽ@˙˙µĆČ!ĺ” 0Ą B’IBĘĐ" XAPˇ2y…™L””33 Re BśÉL)’”)8PáLĚĚÎdađ§2Pˇ’”% Có”3™Îd¤¤ˇÍ'I¤–P<ĂB†…32‡(PÎĺ&S3<9 0ćrS%0ˇIB™…… ź”Ě” NdĐ)d"LΔ)3Cś¤ f†r„ˇIIBÉśáá‘ 43…2JL¦OC”š2S”ĚĘdćfg8Y’!™…2Pg„ĐÉB“‡‡%gţ~˙”ČDĚ)Ě"pŢ Y…Č\’Ę… Đ)(“ĂÉ40Đp¤ˇIBś…™™Ă”2džáe ůˇ&„…ňi(Rp¤ˇaNH†fNaćg”9…(p…™ĚćyůˇBxPćg2…!9Ěó$C%(‘’‡&pˇI™Ęś4ˇ”ÉÉ32P°Js3%2i…2hs>RPĎ0¤ĘJžg(y2Â!<ôš2†pĄ$B‡2fJfP¤ĚĚ”,)"&fsCB“Ě‘0řP˛N„ˇIša)†Re3˙ˇ…Sž… Đô”šdˇĘ’‡y(RIr8S39ÎRr…ä̇É&PÉáË JfPç†áLš¤"ĂB’śĎ™”&DD8e&’†…% H)I”Ě㇠p¦g ‡PćfPÉĂ”9IL”ÉIB“)’…&zB„¦OÍ!))(Y2„Č„Ęfr‡(L‰2’’ Rg ¤Č„ó2“’M ”<ť2PˇĚ–P,2‡ fPÎsĐÎPÎe™JË…8RPˇĚ¤P§(faĚ,™CS… 3™™(s)…(JfRfK0§„„ł%)‡Â™2†s<ˇÉĚćJ9”ĚÉL“†pR¤ü¦N‡"(r‡‡)Y0dŇIfrS3<ĺ&y™…&S32†gNICźČˇBPĘaňg2†rPĎP)’”<”)”™LÜͅ&fPç rap’Čr‡)(hP”ĺ2PÉĺLĚĚ"L‘ @‚ś¦L¤Ë J™(™(g&e s%% ĘađÍ ĎC”ž‡(S33'% J,8RP))ž†sBf¦ĘL°¦OžP¤”)39CC”„B“9C„C™LžS8P‰ ç(g&PćRJa̤Í™”“™S2P°¤ł330dćO…8hR9C)&p°") PÎĚÎRe3ť2S2“"…&g 3Ęp§'(g0LĘ… C$ˇC…9C”2Pç)9śĚĚĚĚĘĘaĎ)™ĘHPáNdI„IC)2’’!ÉB’gLÉś3’śôÂ’…3' (J3CB‡&ffe$đĘB……”<3… Č)čd%% ”$dR) d¦HHYśĘaÎP¦aC33…8P°Ë f!Jdś“Ă9ůC8D9ÎdL"d”Τ",,"ÎP¤ D™IC’!BSś¤ˇ=™“% ”ĚÎr“ś¤"Lç3ĚüĐÉCš™C””ĚĚĚĚÂÂ’!(S…9™Ěĺ ”’IžP¤Ą đΤ%0”) RSúĐááC”2“(S$ˇH|3IB™’™"Í ćD’|¤že'ý0řhD!0‰ s™™)(Re39”ĂL§aBÂĘ’áˇNĚĚćp¤ĘN™6‡C˙řɇ@µĆŘ! (e&J2„"d¤¤ˇL)Đ,Ę)XRR~RNdNaĎţ‡9Éś¤že$ˇIĚ”) 2RD JćRO3” dĚč™Cś˛‡™)“IĐÉóç”) PĚÎL¤’Rs 0ł JI„C%9O&™)48YCÉB‡'’„HP˛fH‡9Ió PŇO@ł4(rO”ś¤Â&O P“LůI”(RPô&0¦g$ĄJ…)%2hd RdˇI”$¤’!2 s‡y‡xffN†J†hNRP4ĺźÓ…933&Re!äËɤô9C”9é†hdˇ…’~S'9C8D%8DĂ™32RRD NLˇ”™¦J B$)ĘaN‡4ÉrD) ÉB†… Jś(s4ÉfáNsĚł)HPˇCB‡(sB‡'(Pĺ d§(Sd”áfP¤)HS3%3%%P¦frJd¤ˇIB’…“9C)3˙I)BJdĄ0Ą ”Ěĺ ĚůLžPćÉĚĚĘaNLÉţ…&S39""ÎffPůB™™2 s4'BJaIš"(dŇzJś”¤(fPćP°ˇaB“=” ̡̜Ę% NRO2‡Â™™?@§ 9”9é(PСI”<šd¦r…“)(RS3”)3”32rS&’™,ÉCIÓ ¤™ĘˇI”ćy”™I˙ůC™śĺ¦K‡ÉIL”ĚÎPСä,¤ y™:)“)S&e$ůȆJ|ˇa˙úaL“2z8s)&Pá̡ś)™’!’™)™™Be'"I432 FáL™Iç"̡śˇĘd§ $R"Lź)…ČS3%™”8\ˇIL¦™)…% 8Y(pRNIL%RbĎśÎd°ˇˇ˙”” NdˇC“8e2Pť!BÉś)9CÉů)<2ÎS'ô(g&sŇtšˇáśčRp§'ĘĚç(ry…’S3ź?(Y3”ÉJ“y’!žC% ‡<¦f™Ďţ’†„Bg%2Y’™†…%39’!“™)(důLĚź”ĚÎPć‡4)0p Yť áB!3'3śá¤ţN™™S<ť ”ÉĐšˇäĐĺ%2„ćJfp§ fd¦J@°ˇNtĚĘL¤”ČD™Â™śü¤Ě””)(S“(psúˇ”%3"ňs(dˇLÉđŇ„ĐÉL”ĚÂ!HS˙ţY”ĚÉBP,™ĎĐÍ0°ÉB™@ł'33 dČĘdĄ!BÂ’!(D%&IdĘd§0°ˇĘJf™ˇĎB|Lˇ3%… °¤Ë%9ÎR ĘĚ)™žS'ĘpĄ$çü¦fLLĐ,(prfO 3ś¤Ęa̤”É(Rs)8S“9†… OCž“"a”…48S2J"™4Ěĺ')“”2s3ĚĚ8S” (ĘfffLĘI”D8s%&…&S$Bd@¤ˇIL”É)(S9šĄ$ĺ&RLÎPÎd‰0˛ff|Ęa™ś"™3(™Éé4<„HeS2e%2S&’… LΓ¤ˇLÂÂ……39IBP, s'ň™%!ćaˇ’aLĚÂÂ!ÉĚ””Ěχ fg pˇIĚ)Éže&R~hD(Lˇś))“LĚ,Â!ˇšy2k»˙řÉ€@˙ţµĆh aśśĚÉBˇHPˇĘˇ™™śôž…9IĎ ćg332R†re ”ˇÉ"Hy™ĘOčg?ˇśň“(PĐ"LÎfaL™L2Ŕh&rÂ’„˙ţe!¤-”'"9L”ĂĚĚ)‡3ˇ™Â’„ĘfO”“ÉNäůó9ś§)™™…8S3%2tĂ)“Ě生)‡e&39C3%É9”3’Y‡)(hRP¦NaBˇI(s)śˇC”(|4ÉIˇ)˙čaf} 礗'IICšaň†hg™đł3P”(y9™”’…&JY„L2“9fSfe ”(J™ĎIčdčN†r™ś”ÉIB!'%™2†PˇÉÂ…$¦M’áBPçL”Ę…0¤ˇO'BP"B„C% )‰2…PˇˇC”ť0"J”3źB‡3C9”ˇ<ˇCś(y™Ę9<šaĎůćs3(g2‡9)ĂÂś)™śé= @¤će$ćD2P¦IChD% JJ2“ ™™(P”"% ”’™)™™Â!™™(RO&’… dňg9C% N9'Ęs‘&i’™)’™)(XS0°ˇL”"̦O˙ţ†eaL’‡%&He$¦ffJ%&r“ç(s9CĚÎPÎS$@J,’PŕA B™’†Oʦdä@¤ˇB“"BśĘL*"B™„C<…(S!|¤˙čPÎL̡ś”2S)“úç)Ă”Ây„BP „ˇB“ÉL‘„PňS™™"3ˇHPСä嚡‚™áLžI2„ˇaI@°ô2’„Bg ĚĚ(Ráae á™HYBS0Ň„)“IC@) dô™fRĚ”2R†fdđ˛ä’!„˛hP˛äĚÎe2†Jr“Ę™)% B dáfe%™ĂĐ™I„C…(sĘÂś"ś’äP”Â…‡3”9™ś”)2™)™’!Â$"&LÉůL“”’P)(PćffJf¤(D†R¤ž<ĂB†y…s„šdł8R„Ę“B‡2…¨‘Ě”¤žP¤ĘO9Ŕ°ˇÎ†sC”„C… Ę44,,"2“ˇI„ĘJfsĺ3 J2S39ÂÉ”9śÎg(Rs”,Ď(rs„C’^Sź”̡ aN!@°ç(O…)33™”8Pđť ç)0¤äˇI”<”ĚĂźčr’’…&hRrS&†fg(hr„ˇ™C)ł$C2“ ̦”&†xe!Ě)’ś"L˙ȆJPÎC”””(s4ž’‡"a¦JfM0”2P)ÂIO”ĂOC”)9…3$¤™“32S&!“Âś„I„C38Y”"(r’…“9ÎdL¤é‡’™"ĘC:B‡&dśÉá<“Ă‘…”2PÉC'Ę™1 ”ĚĚ)(Rs332S’!“ĂĚ)2……8S%3(s?)™Í Na¤é4(ry)’Ě<ÉLÎsś,™™™…&Đć…&S0¤ˇCI…“9™Â“y(RRX B!(D%(rĘ% Lˇ)Î&%Ě“ä‰Ŕ‰(R|¤ˇ0‰2e!Đ"BśĚĚĚšN†só8P§ p°JH„ˇaNÉ”ćPáśÎPó'ţ†p¦OĘJ(PáB“,Îe$üüĺ$ˇIžPˇI…3™”8D32rS$C0ęŕ˙řÉ­@µĆŕ!™”’S NaLÉ”””2P¤ú愞ffJffRˇ“™)’” °“C9¦M% L¤ˇĘô8D4(y:”3D9)(… däˇIĚ”ç,"†PĂáčffd”™ĚćĚ̤Â!Â!ÉB“?ˇśĘL¤ś¤(D¤ňyI@¤"%2ä,ˇĎň“4r††S <§2”‡C9IBs>‡‡&PÍIÓ L™ĚĚÉC@JaNˇś<¦OĘI"%†NJJfd¦aśĘdô J„I2„Đ)Ă™ĚĚĚ̡IĚź?”Ě"ÉB„ÎI RPĐĎ 2D2S$C%2D&D'2†rs9…9?”Ěź)…8PĚÉžg9ţ‡(frP‰ áLĚšL‚HĄ&S2„ˇ”(i’™śĘ™C™ĺ L̤’&INJRBĘáĎC”)2“Đ̡ÉBR˛’…'0§ s“?ţe$.Oň™:%<°¤ˇ= ó@ˇ"gĘdůNt8D‡¤)Ę8r’Ą%% Ě)ś(S&SR B$2!“™”9‡3LÎd¦B$ÂĘfpˇˇB™)śˇˇ9“9žP§ 38P,ˇLĚśĂL”ĚÎPćg(s™Ě˙ĐÉC9B…% Jaʇ †™†RIL)“™…8S…3’DÉň™3”% JJáB$2„óúJfp§&r‡% B”’S‡ĺ9Ó%$@ĐJPÎ L”šLpˇĘJ™C2e39IB™)™’™4ĂÉLĘĘ2f} ”3(s4ž“čdˇ’„ĘO)™™śý ć… O0ňNg”)0P”Ď„Lś™™™™P¤Ď!32Ri(Rs% !Iś3ž„ĐÉC””)2†Ra&RRD 2S'C 3B’‡"ˇ9@s(p‚!N¤ˇL„@°§”ɔÙ̡śś¦g†rNIĺ0Í …'(Pĺ ó”%™")™śĺ&s„@°¤ţ†Re&…!C”ť$dˇĘś)™™ś¦ećH’aá‘&s™”9ůLśË ćffJpˇI™ˇś‘ žP˛e2S9@˛…33ť2Pĺ ˇBD9L))’™‡“B‡3B“):)<§†rs"„B™É)“B‡2S…9™™)RgC”)“ś"(D%&PĺĚ™gŇP¤šś"…&†J(p¤ˇ2$…Ă90¦(S I¦f!JND HCśĘaćdĐÉC”)“)=0¦Iće$ó”śJ”3(p RgúJÎaL™IBÂ…’R†fJś)&dĘ9’‡3śˇćr‡(g Ę™“L<”ĚÉL”‘ ”„BP‰'>IB‡’|ˇ¤ˇa˙С–IĘS!Lť‘„D2R†p¦aI”ĂɦI<”,(sȆJ}’…†Y2’JP2D)2™4)(S' ˇś=Ě"Í3™Ě‘“™Â D…„Ę“"(dˇśé‡ÉФ2Â…&e Iá9(p‰ “„CY@˛e9Idä¦J”3ź9I@#0˙‘LĚĚĚĚś¤ô 3čg(Rf“"aäćs”™C”P,94fNRP)3(O3ĘĘĘ”2R†JaäŇ„Ą% Ňzd¦Pĺ™ÉL–fs9)™™BdBNz"= ˇÂ!'(Pžt(XpJ@‚ż˙řÉ Ş@µĆ g™Âś(Y%„BP‰% LĘ“(r’…2J™””9C”9Ičg(RP¤áI@ł?ä@ćNg)'ś)’hd¤ţ†gM!) RP¤äBe'L‘É4𙎙@¤ˇBzˇL9šaLśĚˇ’‡†…2†pé%33„BP°ˇI™B’”)™4ž“¦ˇC™¤ˇaO9C… Nd§™™™”<>dćffJ‡ô3’™™äˇLÉNaĐ™9††M!šäHICɦH†Jd¦fg$B“:ň™“ćRg)2“9By)CÉ)’YĐ D™™IÓ†x !NC…$C9śĎ= IčJ%(r‡(e&P¦Ng9I”ÉÎs–9™śˇC3dˇĚú2’…%’…… dć™IIINáfK33339B’‡2XrÂ…2rd@ˇˇĂ””ÉL”š4(S2s s3339’!śĎ(Re ”9ILĚĚ)3ü§:9ť =ÉĘśź>e$ó)3”ś˛„ˇC38P°§ĺ8XD $ĘNPС̤ˇC9ž… É”źň™?СĚú"s&Pĺ ÎáNÉ”) ’t3…”8D2RfS%32D2S9Ę™I)’„ĘB!C)3’Pće$ˇIOB„ćL¦r’!ÉC“9™y4Îg30¦L¤ˇÎy… ”)śÎg3) hĚ4ĂĚ”"C"I”“)&g3”)(RP9™Îá¦HĚô3Ă”42PśĘaĘ&JIĘś,"ĚáC…C–çůʆ’fJĘaIś¤Ędź2’IÉý ffa†LćH†p‡<ĘI"9B™ś"„Be ĺ IB’…$C'(dĄB!·(sB‡“"(Pćň“9Ěó™ĚÎPćPäÎffM0ňS$B“ĎIé= ™C’‡3”’„@ĺ 8yIš33&@Éažç)3”™B“%2t3”9ňe%! D9Ęg‡' J32†Jd¦Jps')'BdC'2e&$Î)’™D8YůĘIň™I<”Ě”ČDS2”ÂY„BPˇäô…9,"…2e ¦ˇBS…%3(JfdP”"(g&†L¦Idˇf'Bú%?čfP”“2†…g(g&d¤ˇĘÉäŮ…9ś¤ô dĚĘIˇ†” RaCś§=30dˇLÂÂ’“L>JRˇáĘĺB”ł3”)śÂ$Â!ś(fRe339…2e$@¤¤ÂÂ…'% J™ˇ™Â!HR‡:aNe LüˇI…3 ˇš” :4 P™@ćS8SśPćpňP¤ĘJśÉI2RP¤ćfxĐЧ'(PÍ ó”8D’†aI8P¤ĘJ˙B~faÍ !BS”Ěĺ% ')ŔŚ’YĘpˇ¦S0¤Ě¤Ęfe áůLÎhS9™”&D…)…!Jd˙)’‡‡@ł))“L)ÉLÎt(rfJ%śĚΡ’…%% L¤ˇI”ÉÉáNPáćr“”9™RaáC9”Ěť áI”)†Xf“‘ ś¦fs˙ĐňP)ˇCś(J|ů”8RD%(y<¦dç30ŇP˛adšaL%Ă<źC'ĺ%Â…†C%))™)"™ÎPćKDYCD8P§B@­ý˙řy CÔ@µÇ(©i*”´ĄĄ-*ZUd«(´Ą-IKDĘJYeDÂŇĘRË*YJ”¨µ˘©KQT˘ŇÉ•)ZXš,µ%,©(ą)QjJZ”©IjRL¨šRŇŞT˛Ą)K(­%rWJV*X©bĘ‹R–T˘ä˘ĺ)R‹JVT¬´Ą©J–RŇKQj‹R•µ*KRZÉU%©-EI‹E­*¬©U’T¤š-,©JŞ\˛UbĄ‹KJZ-eKZJÔ––¤©¤‰ŞER‹‹I”–´L´ZĘRĄ©–Tµ,–RRÔ’Ň•-*˛Ň–•YJ‰”Ą%“%JK%T©eIeJRĄ%–”\´˛©,´˛ÔĄ%©.-,¸˛Ô•”´«J\YZ¤ŃeJެĄJŞURŐ*,¤˛•,´¨µ%I2’ÔĄ)--IjR’Ô–YQKR#)-U•*©2•Ee¤ÉeĄ+RZ¨µIJ˘eJK*Y*˛ŇU%eJŞŞŞ¬¨š,–+,©)J‹TZĄ)e%”¬¨šYRËRT¤´«,¬µ*&I‰”¨ĄÉZ˘eeKRU*É+)JZJ˘Ę–TŞŃk*R´˛µ)R˘ĺ*ZX¨šQ5"ęT˛Ę”¤ÉdŃKJYQ4ĄR©eJT˛¤ĄĄ-RZŇU(¸­JRL¤©EKK*’­,©ieRZ’ÔĄ)R•-%R•*Yqbhµ”´•IZ’Ő)RĄ–”Ą©)2’–)&II’ɢʕ&QYKR”©U•,ĄJ«)j*Yh˛ĄV•¨µ%IeDĘRRĄJŞUTµ)QiUJ¬’Ô”Ą¤“IjJL¤µĄĄĄ•+)Q2”¨Ş˘É•Y*Ąe)iEÉe˘©JRĄ)J‰•**R–’ŞUU–\¤©eIJQieĘRŇR”˘Ô\YrTL©JRÔTĄ”¨´˛˛ŇĄ(™(µ\Z”ĄKR©+K-%K%”–”™iIeIIeJʢ•)j•V˘–”µJ-J˛U”©UK.)T˛ĄUU”–T\˛´µEÄĹĄR•,ŞJҬĄJ´«J´¬´±kE,Ą%-)UK-*IU)U*LZYUKJ”©R•JTĄ)&J–*TT«QK)--QrĘ–R’–RĄ,¸ĄÉU,©JZJ©eĹ‹R¤\˘âbŃ2˘eE©EĄ*˛’Ę•ZYRҬĄ”¬«,˛T”Ą)R‹’Z”R¨ĄRZZZ”©e))UeK*Š.J-JK&’”Ą¬Z˘ĄĄ*T\L©KR‹”Z˘eEe%KJ––-QKIU+KTŞË*RËK,©JZ’Z’«)*TĄJZKRVJLZQ4YyUĄVYIjRŃe©)j,´ĄJR–’–”\”Ą)IjKR”Ą*RĄ)JR”¤µ(µEdÉdÉT˘Ő%ZJ˘–’Ą–”µ%©--Dµ%©e*Qr•”¬ĄK)JŠ–”Ą”––\&T’«*-IR”ĄJU*YhĄ¨€q¦beetbox-beets-01f1faf/test/rsrc/partial.m4a000066400000000000000000000133461472325477400207450ustar00rootroot00000000000000 ftypM4A M4A mp42isom ŠmoovlmvhdÄLé_ÄLď ¬D¸@trak\tkhdÄLé_ÄLď ¸@mdia mdhdÄLé_ÄLď ¬D¸UÄ"hdlrsounÓminfsmhd$dinfdref url —stblgstsdWmp4a¬D3esds€€€"€€€@xú€€€€€€stts.(stscĚstsz.22""'-+1/+&0''&%)(-,*).)(*%)-4&6.*,$,3/'& stcoĆé •udta Ťmeta"hdlrmdirappl°ilst©namdatapartialcpildatapgapdatatmpodata6©too.dataiTunes v7.6.2, QuickTime 7.4.5Ľ----meancom.apple.iTunesnameiTunSMPB„data 00000000 00000840 0000037C 000000000000AC44 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000˘----meancom.apple.iTunesnameiTunNORMjdata 00000000 00000000 00000000 00000000 00000228 00000000 00000008 00000000 00000187 00000000"©ARTdatathe artist!©albdatathe album trkndatadiskdata×freefree(mdatĐě\«11;Ä<Ŕř¤4„ `Ifŕe—^ŕďŹP0#Ü·›ťÁbÄ»VOŘŮ(*ľJ€q8ň„q©-@>Ťč[-3ţ!‹rtgĽĽ(Îą¶q]bĄ¸úÄPEŮ€€ ްDăŮ‘>JUl3ËA$Ö)i)ŔúÄA#€+:3ď`Xa¶™ÉůQ;«Ŕ±čŕö˘" „Î Ž× óf{q wZ“Ä 3zżf#)NŮq'ř˘" „kËr˝ŮÖŁ gʧý'”ę )y*¦Úö»,°Îµxˇú˘" „r †BXVFěě7nhϦNž|z%ôe0Ue®«Ś śö‚B„_`9ĺo˘đJ­Dz0Ż)ňřáŰ1F8‘ú·ĺÉ>7t·#‹Uŕř¤4#ŚXŰőŢ™ľáŕŇ`¸Ž‚Ţ×xT†aG~fäHv<ý¶ÁŔúÄ`? &×63Đ”l:äGŕë’ 莵š„ÇÓëĺN‚ŕú˘R#Aש9Ď<łÔiÇ%ÖřꦗµŠÍÎę®yfžÎô‚T„ `®!I}uhnV$?‹+äˇ(Z«„ÁˇÜtaĄVi±+±l‰8úÄ3#PŤ0Ľń•T`’3ąčžÓ#?}ĄŐőÚ»ěÔ‹€ú¤Q#Hl`µÁ˝ĄËK§)Q)E‚ńő>O˛ÂôŻSÔ¦úĆ"#P ŰdpËźżŰ2é­~sŰČÓďŤ'Pîě=Í&ü!úÄPG<Ž0NÝÍEř™_ő'1…:ő‹ĺ\ř˘ ,l qĽÔş<¬>čđÍ&ďĂž¦J3}•]dQ€€8ú˘`F  8I+–¶:aá–;0ĄÇ>m>;ßMľíŰŹź× Ą/gř¤p>€ŢbSci›”¤L z:~HŤ5M3©'°A+č&„f ‡Ö(pö‚0!dŔ˘’Ż:%˘C°9Błî+]ţ3Z†ąčcJr†Â¶Źö¤`E€a,]µ)\BiwČ,yç@ĂşD¸ůťü'ħĄ¨Üř˘ ĐŔ€0qóŕ=bžmBĹAwůH°×! šDSŞ 8ö‚5 !`Â4†§ĺ^ńíŞ^:$9µĎ…gs`M%…ĘZZtŞ^UŁY(o€ú˘"¤09ś’#NŃ·#̬zÚW›;t A-1dyß”3¤^ ŕú˘" "ěFâwěĽú„,µ¸rŠţ¬js%”źŇísŮfžep=Ľř¤`G €@ŔăŇ5?ŕĽ`•ŘÉÍM˛ČöĆýYB^×;C€ř˘`BČ ĆăiRř—‡iéŽ60 9üľĺÓvë…(Ü˙˙GYJ´…=˘oçlÇWř¤3 BĐ3ÜQhLÖĆ$/ę‡b<÷~łµ8ëűŰΚSŤ­n*jőXeĹ×7’#ŕô‚"(„@Ř Ad¨*Â`óŮ'á¬Á®co·>Vűça &µ% ř¤`F‚ ˘±äßxă}¸<ëĘüfP[2ÚqXä.Š'Iµpú˘" „¤*ÓŘšÖ'ęŠÇÉR™z[oÝÓ¬ía‚„ľ”XÇS2p'ŔřĆ2! éZđŢx˛µľůěú$©żVn´%j(óVŔö¤`F‚€öŽťF†@›c}}đCÂ2>Ű<ĆçO5Ł!ąQŢ/grDŹđú¤aBŕô‡2‰´S(^®ZŹdđ{¦P¤ź~‰Ůۦ‘6 Sşśö˘ „,`Ľ-1wNŢAŔÍ”XPKhˇwKŰ#0R¬Y±Xť-Ŕř˘`E "ĹImq¤>w˘Č1s5{!ÁXş[Ż/ćŰ?ÓGŹĄxř˘2"%€Xä  aÔž®ŽćĎű©ĆĎçż÷şrŻLaŔě\ŞRi"?pě\¬ „ô@Ŕ\beetbox-beets-01f1faf/test/rsrc/partial.mp3000066400000000000000000000310241472325477400207540ustar00rootroot00000000000000ID34COMengiTunPGAP0TENiTunes v7.6.2COMhengiTunNORM 80000000 00000000 00000000 00000000 00000224 00000000 00000008 00000000 000003AC 00000000COM‚engiTunSMPB 00000000 00000210 00000A2C 000000000000AC44 00000000 000021AC 00000000 00000000 00000000 00000000 00000000 00000000TP1 the artistTAL the albumTRK2TPA4TT2 partial˙ű`Ŕ F=íÉ‘A#ô‰ą˙˙˙˙F0Ăß˙C ˙_ůď˙˙˙˙óĎŁ)çÚa”2ůŠz<‚žyő=xů=O `ĐĘÇŤ  ĄŕĆyé>`řÜá ĐŔXč" z)ăBś"Ŕ$)‚ A„ ˙˙˙˙˙ř˙×?˙ű˙˙˙˙ţľÝvbŁ×Bť¬¦ ł4Ŕă°˛+¸µÂ¤ÍrąÁÔR°Ä%;ťîRŁQ[˙˙˙ďÖÜî˙łľÚ7UW6dTUT!ub8´Çř çčx?ń.Ě€Ź˙ÚÚ˘ł<ŚaeźDY/˙˙˙˝˙PśĽ·$D¬(躡ďPŚi§˙ű`Ŕ€Š9ŕ… éâH#4•¸‹RŇAbÉuĎ&Ž ü:=ÄÝL´2!Ô¶#9îŽÔ#"5ż˙˙˙ú­˙fE$ěȉ,Ş1HËez˛1’Ş!ĨR ‰Ě§)˘((â˘,Ęc°š)ÄÜX˘¬,qE„T9N@řt˙˙Ňţ÷˙?˙ű`Ŕ ň= mÁ¸Č#tŤ¸ż˙˙óËäÜŻđÉ÷INˇćiŢ˙{ŢžYg§S—ÜëdE/'˙˙˙˙˙˙˙çţ§Ě¸g Wëď&%ÍirÄDuń6.ÝítÜ‘mtP&ę,Ž«˘’Duhâb¨D›ŔN±˙˙˙˙˙ý`Z˙ý{˙˙˙˙˙çĚímŢóĄ/ĂžjľGH„<&ńÎCĄ˙ü„žl÷˙˙˙˙˙˙˙ýţ˙çJÎÉPŤ1•ĄI’ňÔä»ň9Ł3łZHFĘ;ŽÔr/viĂ1  Ü‰…Ł2‡ ěÁ˙˙˙˙˙Öő˙ţy˙?˙˙ůz\¨Ű+dDÎŞŹ1ĆckTIw˙űbŔ ć% „MɬH#t‰¸™¨…¶R˛¶Í´ö˙˙˙˙dÝꓳĐ÷1ęr© ĘĎłîŇ»łlÖ• lΆmC‘Uę÷c,ě$(tČ4BK˙˙˙˙˙ëĺý˛ëZS˙˙˙˙üąQRĹ­ÝŚ‡>łĚl®®č§1ڍËf:V¨m&ľĺDGB;?˙˙˙˙˙ů­RĚé+9‡}•+Ďł+±ÍUĐčĘbPSú;1Ś%Ü; Š…R•Ěwt”€Î9B˙˙˙˙ţ¶˙ë˙•˙˙˙˙äěúRwó5ťlŞ„s>ʸő#ĚäşuFlëĐęɕћ˙˙˙Ú˙ł«ÔŞFJČŞyŚEus–˙ű`Ŕ" ? „­Á¬Ć#t•ąa2ft*3άVwVl̸â•ęc@@Ç‚o2ÇĺQÇ;> ˘Ł€?˙˙ős˙˙˙˙˙ü»µ®ĺ:=¨îďv:şş•VB/Îlęčö-ĘZ$Ű™5O˙˙˙ţÍ'*MDŁť'» Č.–zą›S«”ĚęW!„Ça溡Z!3#NǞ#!˘âcH5VA¸ň¨`†`±óőůţż˙˙˙˙®•g;RWTťŮUŐ˝ÔÝQeŃŃTÄľÎB%O˙˙˙őůŃgęŐ9F F‘ťT‡j9"Î(cJb#!ÇxŐ3ł ŞE˙ű`Ŕ, Ú= „­ÉŻČ#4‰¸vcśqL8áaŕĘPq˘Ń2$48˙˙˙˙ţÖŻu˙ş˙˙˙˙˙?Ł‘Ý”„5›Rl›ogGĐŚŞöi7şŐŽ·ěKw˙˙˙˙˙ÚŇ"1Ý_™Ü§8¤T„Dgťä;YśFä%PěĆ \ČäRŠF €‚äS3: ÁČQBŞŕ•Ü(˙˙˙˙ţÍ—˙/üˇßźő˙˙˙o‘śíbnYŘz(‰‘ëu×J:ä·IdŁďZĐÔŮ˙˙˙˙˙ţźJ'»J Ff®ď©Ne+ Č 8ä*&*†q2 0Ě AŠAD…0’ŤQ!2Ş”ĄA§aQE˙ű`Ŕ6€ ¶A „­Á¬EŁtŤą0˙˙˙ý°/ó׿üż˙˙˙˙ň]sąśĐô… 仪Dţ9FzÓ¦[7‘tĄ+Mą2äł˙˙˙˙˙˙˙é˙9ćÓ*FE‘D‰»ë2ł×r™9“Ň·I­|ĽUčĘă±Áz°spÂ+† JŰŘ8˙˙˙˙ţŤ©üĽňgRfŇĎĺ˙˙ýţ•$íc1üý§ş!Yz:d÷#ę×*ş%Pú?{Ż˙˙˙˙˙čɦ·5\ÎRŁľě‚ŠTv{Ä$A™ RkŃi†!ٱĹĘÖ(|¨rpđé˘î<ÂÁŔ+)h0Ŕ5"üż˙˙˙˙˙dî˙űbŔBVA „­ÁÄÇŁ4•ąŹF±:ĽŰ>yę­)Ýě¬ĺşQň˛+7cfT˙˙˙ëŮ[óÉWŁ•hrż˙˙˙˙ŹiO‰Ť&׊aҶ§ö’ŃşkL“ÔW0÷?ÜpµpÝ˙˙˙˙˙˙˙1˙×ýúÚuz÷¤sň8ë›~9Ťç^;Ž’…ěar:¦hZ—Ro|†AwÚÖěXňo®Ü**>@Ö0˙˙˙˙˙ëËWuűJŤFŃYěîĺňYŮrć\îjí»*"±Hß˙˙˙˙~î‰m¨—g*:…+"ofi•ŢÂ#EŹ ¨˛)JçWACę,$aqĘÇ)„€p1Ś"Pč‘ĹH‡(ѡ`8˙űbŔo€5 … É˝H"Ě•¸3čň˙Ż˙˙˙Ö×ÝzčĽě×»Čr/Ȣ Ę®”dŁ3Ń—yN¤q˙˙˙÷ä{"Zĺu#ĚeR ¸ńŽÄsî®e-ESi‘pŐ( ›0“„bŽ3‡Ŕăs†yNEr”ÓŠ¨€ ŚŔ˙˙˙˙˙íyď˙éß˙˙˙ýz.Ą1,o5ä+9·Ežuvf±Îőąv}ČČ–N˙˙˙˙Dű}lłµ+3UTŠCoĽĄ>%čOyS!ťXڇ‘Ći;ť\ę8#)Ĺ:‰cĹ•Ęt(P‘Ŕ?˙˙˙Ň˙ů˙ó˙˙˙˙ů´š÷f˙ű`Ŕx 9 ­Ů¤GŁt‰ąb$„vşftgdű©kU-™śčƤĆVäfŰ˙˙˙˙ěźęR>Ó­ŐK5(i5; Îń“•ŃŠwiÇ*ą¤"âŠ*.*1â¦c´x´çRH@“#•bŔ˙˙˙ö@ż˙-»<ŽÄjµ=]ÖÓTޤ®Ć96Kä<îäsnłĽó—˙˙˙ýv"Ńő{Ń]LëgRťg©ŞF5ŽFuőZÇQ3ŹQÓ ‡Ç°›”a‡¨Ô µĚ.tP‰GQCĺ<ĂŠ˙˙˙˙Öţ]˙ţ«Ż˙˙˙ţŢy‘žr3ÚŰg§ź¤m4U++!d“Ö¶_J´Í{˙ű`Ŕ nA „­ÁŞGŁ4ą˙˙˙˙˙˙˙˙öů7ČqyiHsC0¤Er_?* –gĐNP‰îF¸v#UCŞ9ŽB!* jčŤA‡1Y(0˙˙˙˙ý`_óňţ_˙˙˙˙üßíŃžďzJ_ąK&´išôE3Šeu!&>ţF˙˙˙˙éĚýŃ˙ÜW˙˙˙˙˙˙˙˙?íńó÷+kLÖĂůŽ*Řëk‘s*—gJŹ4`žąEĆÇviF"‡$z©†9‚XőÂaË”9pčP·XT]Ç˙˙˙˙˙c`qîy˙ű`Ŕ˛€ .A „MÁ­GŁtŤą‰#±’ż%ľ§˙ţY¸tűîú㙬ꙉyĄ†ýwŠőÚ©¦÷ŢŇĄ©S»éşć˙˙˙˙˙˙˙ţ˙ż˙űżŽąškŻ«YO›­˛<6<’±…&TÉë˘Ć!wB±E2 ¸öłJ‡4\V…K1`°’ކ6Ëq1Š@˙˙˙˙ý±Í#WĺżĚ«˙˙˙ý:*\ČŞuo™ŹťH(έb+JB{1¨¦î·±•jj˙˙˙ţť7Ą®·tT2+9#ÝÖ<–şśČ©*­ ayŚQ1§3•Ú&=ĄB:``ë‘‚˘§Š ‰Zpđ[JQ˙ű`ŔŔŠA … ÂČ"´ˇ¸ €k?ýţ˝˙˙˙˙˙ţŐdR•¨ÉżWDJ2oR›±ÎÓęUd©Šf3¦Óo5ź˙˙˙˙ňKk­Ę„y™bęěęt3–S’Č5‹Ž «Ź,† 1 áňŽrťJâŠ0˘„#(@Xęč$,⊏8Š4xD*…„˙˙˙˙˙’Ëäű×˙˙˙˙űνey5v}úş*±K÷U2‘ÖgR™–tK"µoE˙˙˙˙˙éîíLĘŚE:2+Ů’}Hî8†fc ‹ł)†ĹĚîěBaÇds¸“!…„XPrxńĘ,Š($0<*5 †4lü˙ű`Ŕł€˛A „­ÁŰH"ô•¸Ö|˙˙˙˙˙˙ôM‘Ű[V۲NI”¬ÚÖçbk3›ß˘ŞŐ‘Z~ż˙˙˙őˇj‡»µ¶g*•Ř”ssB«Ąr”¬QGމ ”„)ĺ10‚ädĆw<)]Ě($aE8pj‡ĐhŔłŔ˘¦‰€˙˙˙˙ţŤßü˙F—=ĆÚţż˙ůĺ®›±÷őĚk*"§f5§˘*YŐC^bşę§»LÜŐ˛˙˙˙˙˙ëî‰wcަUÎ<Ę;0Ô=QÖ«žŽ¤Ź:łóż˙˙˙Ďéą&q…+T©µ'ŮTU•ŚgeR á‡B32)—ýž·D|ď˙˙˙˙Ó]«MÉş+YŚ[Ö¦0Ň)ĘbŽĄŽ5EQ„ÄEa!\hAÂ!ذ łBĂîg Ŕ(ńá‚ÂC &P 8|DV&Q˙˙˙˙˙ű`Ŕ´€ÂA „­Á×ÇŁ4•ą˙éä^¤–ú,‹˙ź˙˙žŇ÷f»‘×d[Ňş˝ VąLës ws)P§z^¬t252]ko˙˙˙ď§Ł÷]»ÍĘS2•gsƙٞDkaśÔ9Ç#Â"%F âDL*Ł˘ (B" ‡XÂŽAěbĽ>˙˙˙˙˙±…ĺţY//˙˙˙üć©J$î”Y?RşŃôVT}ŢŠrQKjßZmu}v˙˙˙ţ«ëzttz%¦TS•1ä#ĚĆ˝j=‘ŇŞ”ŞQ!0c ÓH,˘ĺ ŁÄÄĂŠ,ˇŃw‡Č 8ç1Đĺ†0¸ĐŰ ĂjĐö˙űbŔ¶€A ­ŃŮČ"ô•¸}s—˙ţż˙˙Ä@÷:XČ®M*¨Ccą§r”ü­#çDQz­Şý§µ)˙˙˙˙ýţ–ŃNbç›-LQl„0׳ )ťÜk ;Â,5ÜŁĹ‹;"Š 8ЦA¨$ÁŃqĆ\M…EAB@Áç( *˙˙˙ţŤ˙-ă4Ń—pţR˙˙˙ł˘îVlěU·K&öu+<Ď™‡tLěěv3ˇŠÔłI{×˙˙˙˙úűű•YŃ Ą*«“ęçŞîR†Đ<Ž=ÇÔLU…aĹqáL (s”ń¨§D4P€Č‚Ä !€8˙˙˙˙˙ű`Ŕ·€’A „­ÁńČ"´•¸˙ěŚ w"šçĎ’–«˙˙ţM˙ÇýwóóŻ÷Üu]fFĚŐH÷SR‰őô±×KŻ×˙˙˙˙˙˙˙˙˙Ďw§qóßLň°»OĄO3r˛1GÔ­mÍ»±ĺ;=šSĘ8yŁ2lĎ#•0ŃÄĂHpň ˘ĄŹ% « €˙˙˙˙˙˛źĎ_ĺËz˙˙˙ţţŠŚ÷Z2>ô±• ŚĚ]ŞC\ťČ„VGGuyŐ(Ęr˘J´˙˙˙˙ű[BUÝźaj«öŁ[‹Ő™HŠčăLR«yڇś¬C%Üq]Qbě->A!Yâ†`Ł1\Xâ@`Ň y?˙ű`Ŕ·6A „­ÁâČ"ôˇ¸—#˙˙˙˙Ę­ä&ĘŞËťŐčęĘ}V¤1ÖłFnną:ű[Ż˙˙˙ëmjĚësˇŇŐF!ŠbŠę®Ć)†‹‚*‘LŚc°jˇ¬*‰˘ď0pĄ>$av!śXč4DHÁÁ5‡ĹĨU(p@1@˙˙˙ţ­üŚ˙ĄÔËďë˙˙˙7ęěµR!ź#ٵŠč¨~ܦf5ćfb1ŢŽÝS"Ű˙˙˙ţßčÍišŁP®vJÖ®d1qčq”2ÚČ0:Ĺ+‘T6aÂbEl?2‡DD…‡‡\M"B Ł˘† ”Ác4\6Ă ˙ű`Ŕµ€.A „­ÁçČ"´•¸´ýą~ö˙˙˙ţT«Ű"Ł—ÜÇž—ŃsŮŃhę®DÎmYd˝îdSnŰ˙˙˙˙éíľ­:Ş3š§š•c%®5HQU<§!ĹEHcDH@8â(‡E`=‡(ąĂ§qR¸˛:$&<(ją‹ĆЏ  hŘ6­óćż˙˙˙˙#í%)sMŞŘí1ćrÚě·IUkwG‘rîGtTe÷Ń—˙˙˙˙ŢŐęŰ++Jζ%ƱŢČÄĚ<‹;‘XÇ8q‚:‹ Aa6qe- @ĘAA¦`‘Da"ÄĆÄ„@đ™”:?˙˙˙˙ý$˙ű`Ŕ·€A „­ÁďČ"´•¸/ţN_¬™ôë˙˙ä ÷Gő>ľJĄÓ:˛ť—G4¸±Q(„tC\î{+©•®gO˙˙˙˙쮕˝Ôł!O<®ë!¨Žd,ŠćR1>‚%f0°¬@ę>qâˇô (¨ĺś4ÖQ!0ř€şX‚eQC€˙˙˙˙˙Ňäţgě?˙˙˙˙˙-‘ś¦ŽI"*ĚDŁ™•̧Y§In–jH~­’Vkg»T®Š¬‹˙˙˙˙•úŃĚŽäRËŐ®¬–K¦í[ÝF"Ψpá®ĺs ”<îáC‡A r$ň 0×R‹ŽĚW€˙˙˙˙Ű˙˙űbŔµ^A ­ÁéÇâô•¸˝˙˙ß›˙˙üż->ůŞó/ß&@şŇzšşË)˘Ô-^źđĽŽ˝Ďmű˙˙˙˙˙˙˙˙ĺ‘ßće ·îV›E‚6ŤťŢő$4Cą»Ľ­dPŔŽŠ$B¬ŽAĹŁE "Ł,†8SW©9 € €`5‘ç˙Ëćkúë˙˙ň˙ZšçTCŁú^5Ýő‘ś¨bT©‘Üz-®–™=¤˝ź˙˙˙ýiCž”čËB)©au”‚o,ŃČ8‡c‡ ""@”AĐDĄ*<:*‡ °x:@ůDEEH8:.څЇ†˙˙˙˙¶üżůëď˙˙˙˙©˙˙–ŽÍ­îGH˘’°»Nޱ™ÄW%˛zŐóŘřDo“eŢ˙˙˙˙˙˙˙ţ_ü?)ąleyX†żç)ÎśzRĚź"]Ë•L´'dxČĐtS 3j±Ű é ŕ˙˙˙˙˙Ŕ~ds˙ű`Ŕ˝€>A … ÂH"´•¸ć˝ ˙#˙˙˙˲µĐŐc0‰&V‰ZâBĹrĚdpëFEyFm Ą*ł¬Îľë×˙˙˙˙˙żżmYĘŠVĘĆ}LęĆ+>j=‚¨,ĺ-*UcĹB˛=ť QSÎPčŃÎ"ĘYH  ˙˙˙˙˙˙˙˙˙˙˙˙Ż˙˙˙˙˙Ż˙ý~Ć(`JEŞ*!ŮŮĘbŞ”Vs ` #ł±Š©˙ł”ÁB(˙ű`Ŕł€ n? „mÁŃF˘ô•ą˙űbŔ»€&@ŢMŔ[Ŕ˙ű`Ŕ˙€ŢŔbeetbox-beets-01f1faf/test/rsrc/playlist.m3u000066400000000000000000000001121472325477400211600ustar00rootroot00000000000000#EXTM3U /This/is/a/path/to_a_file.mp3 /This/is/another/path/to_a_file.mp3 beetbox-beets-01f1faf/test/rsrc/playlist.m3u8000066400000000000000000000001141472325477400212520ustar00rootroot00000000000000#EXTM3U /This/is/ĂĄ/path/to_a_file.mp3 /This/is/another/path/tö_a_file.mp3 beetbox-beets-01f1faf/test/rsrc/playlist_non_ext.m3u000066400000000000000000000001021472325477400227110ustar00rootroot00000000000000/This/is/a/path/to_a_file.mp3 /This/is/another/path/to_a_file.mp3 beetbox-beets-01f1faf/test/rsrc/playlist_windows.m3u8000066400000000000000000000001261472325477400230270ustar00rootroot00000000000000#EXTM3U x:\This\is\ĂĄ\path\to_a_file.mp3 x:\This\is\another\path\tö_a_file.mp3 beetbox-beets-01f1faf/test/rsrc/pure.wma000066400000000000000000000562001472325477400203630ustar00rootroot000000000000000&˛uŽfϦ٪bÎlBˇÜ«ŚG©ĎŽäŔ SehÄQ€>ŐޱťĐtĐĘ› € € ôµż_.©ĎŽăŔ SebŇÓ«ş©ĎŽćŔ Se4ęËřĹŻ[wH„gŞŚDúLĘ”#D”ŃIˇANEpT3&˛uŽfϦ٪bÎljthe titlethe artistthe comments@¤ĐŇăŇ—đ É^¨P‘Ü··©ĎŽćŔ Ser@žiřM[Ϩý€_\D+PÍĂżŹaĎ‹˛Ş´â aD¬€>ç çç@Rц1Đ٤ ÉHödARц1Đ٤ ÉHöWindows Media Audio V8ahe labelDESCRIPTIONthe commentsDISCTOTAL5COMPILATION1(MUSICBRAINZ_TRACKIDJ8b882575-08a5-4452-a7a7-cbb8a1531f9eTotalTracks3WM/Genrethe genre(WM/EncodingSettingsLavf54.29.104$WM/BeatsPerMinute6‘Ü··©ĎŽćŔ Ser@žiřM[Ϩý€_\D+PÍĂżŹaĎ‹˛Ş´â aD¬€>ç çç@Rц1Đ٤ ÉHödARц1Đ٤ ÉHöWindows Media Audio V8ahe labelDESCRIPTIONthe commentsDISCTOTAL5COMPILATION1(MUSICBRAINZ_TRACKIDJ8b882575-08a5-4452-a7a7-cbb8a1531f9eTotalTracks3WM/Genrethe genre(WM/EncodingSettingsLavf54.29.104‘Ü··©ĎŽćŔ Ser@žiřM[Ϩý€_\D+PÍĂżŹaĎ‹˛Ş´â aD¬€>ç çç@Rц1Đ٤ ÉHödARц1Đ٤ ÉHöWindows Media Audio V8athe commentsDISCTOTAL5COMPILATION1(MUSICBRAINZ_TRACKIDJ8b882575-08a5-4452-a7a7-cbb8a1531f9eTotalTracks3WM/Genrethe genre(WM/EncodingSettingsLavf54.29.104‘Ü··©ĎŽćŔ Ser@žiřM[Ϩý€_\D+PÍĂżŹaĎ‹˛Ş´â aD¬€>ç çç@Rц1Đ٤ ÉHödARц1Đ٤ ÉHöWindows Media Audio V8athe commentsDISCTOTAL5COMPILATION1(MUSICBRAINZ_TRACKIDJ8b882575-08a5-4452-a7a7-cbb8a1531f9eWM/Genrethe genre(WM/EncodingSettingsLavf54.29.104‘Ü··©ĎŽćŔ Ser@žiřM[Ϩý€_\D+PÍĂżŹaĎ‹˛Ş´â aD¬€>ç çç@Rц1Đ٤ ÉHödARц1Đ٤ ÉHöWindows Media Audio V8a5COMPILATION1(MUSICBRAINZ_TRACKIDJ8b882575-08a5-4452-a7a7-cbb8a1531f9eWM/Genrethe genre(WM/EncodingSettingsLavf54.29.104‘Ü··©ĎŽćŔ Ser@žiřM[Ϩý€_\D+PÍĂżŹaĎ‹˛Ş´â aD¬€>ç çç@Rц1Đ٤ ÉHödARц1Đ٤ ÉHöWindows Media Audio V8a1(MUSICBRAINZ_TRACKIDJ8b882575-08a5-4452-a7a7-cbb8a1531f9eWM/Genrethe genre(WM/EncodingSettingsLavf54.29.104‘Ü··©ĎŽćŔ Ser@žiřM[Ϩý€_\D+PÍĂżŹaĎ‹˛Ş´â aD¬€>ç çç@Rц1Đ٤ ÉHödARц1Đ٤ ÉHöWindows Media Audio V8aTRACKIDJ8b882575-08a5-4452-a7a7-cbb8a1531f9eWM/Genrethe genre(WM/EncodingSettingsLavf54.29.104‘Ü··©ĎŽćŔ Ser@žiřM[Ϩý€_\D+PÍĂżŹaĎ‹˛Ş´â aD¬€>ç çç@Rц1Đ٤ ÉHödARц1Đ٤ ÉHöWindows Media Audio V8aWindows Media Audio V8a6&˛uŽfϦ٪bÎl2K‚ ]“‹„ç ç“˙@ ÓĂĎ6Ó‚}mŕ ;€¦Jŕ$´ć“$ťÜ$›ě ?'qU1Ů1%¦4©€` -I$ŔIPťc Ô I¨R„UI10U5R’I‰¸IjR` Ů$Ä@«JTBTÓIĄ1,E4” ¦”Ň’ŇŇ€I:˘„bfĄ1&C›°` łQR@H@1Vš`𠆀H “:ÓP@¨ţĄKĚI^o'»@*@7C˛@ •ÂL Ŕ$¨Ä %©--¦ši!T€H””żHEPůjH@&B4ŠJJÁóä"„0Ň•…ý Sƶ°|µALU2”’k?ßí/¨/ßżĄ,B(âýqôŠ1BŇĐ~¶¶š-ÜKlřż!+t„H˘„„ĺ8Ť H¨‰ˇh-Űߥ)B Űô-P_”PVéCäQný"—év0{eýIBŇŇŞ¸V‚i~·ĆüPč6ňµA \@ŃĹA KTżÚŰëpăĄk‰ż, ńřRѤ ­żvŮFPµB(âó^®oă§ŹÍů»yZŔUÁ”P„ă(Ŕ\KYFP…»}ż÷o¤ÓM–Ę?_Ą Rýmřâ·> ·× p?,ŁŠÝúđ´ŕ*ŕ}Ćý–­ËKx)~üţ°óyKďŇoéăýұ~ěůĽ§(đďΚĆó\yďű§ó¬sXß•?´­-˙µ´[–¨}BŢSJŇ8řlj9ďůĺ>Á·÷úýqş_ͧ‹őEľ±ř˙'~ú—č·ŃÄ·ŮH˘š_%úiÝM.Ę]źŮK°´V˛ž+sô~_§`;t㎟ߛü°c[źOšă[t-·AĄ ·?üż|T>ˇűúBŇÓęr•Ąˇ”­ńńń-żý¦ŢýŮ·[–Çý>ót~ż'A·Đ„%lÓE(?ş0çÔń­şi )·ŰéĄňßîŢ·C÷ÉnŔ@­ĺ4qşR#)O˝k(ZvĎż:< żĘhŔT"±ź-­­yĽĄ6úr‘Ĺ@[Á*ŤXČů­~ë«3Xöü§=˙tţ¸ż\nÇÇ‚WÜi⦊Ć=©(YťXůşÇ[[¨Ś˙)ýůş?+{ !öt˝/żKhó\vőłÇO„Q•+yĘ-ŹŔUŹ‚[ucĺYň[Đ0ňřqŕ?5€˙tŕ”ĄÄ\úŔN‚‡Á§`0ţ¸2š2•Ą†{Qŕx6š>ŔT—ô%ů¤ş[ţđü_y»{7öáć폷ńe)Ę_y â × şŘ즚ŕâşĆ|NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNç§ ç–˙@đ%,V,XU2Ľť˘ d†´µ°DôZb`°D€« ’ZŇÄ’XŞpÍR/¸I… &`c †˛X¬ŔH1…$Ś)1´ĚJI,ˇ•2"MK˘ A2˘B`’*!&©JADŐ€ë$Ši–„¤‰ E&®‘(hŘ+D¤„Ą!¤Á$‰``:–h&FÄꤸl¶¶ ¬ÜôF”aŘ…E„–ą‚Cv[0¤á@™Ť00Š&¬“‡)EděEBťMCQ 3P ľ4R EB( ±JKôżBPPB0č@„„[‘M/ĹĄ‰ Ű¨J€°¤¬Rüˇ`ËTţęOT©QcMĽ°–ż-ZZĄhŇý nĘ-ôń:\kaúüÖÓůSÇĹćź~ż×OóÔ~Íp; yHZĘ ˙̡q!ŇƸ8风ßůÓG®/É ĄŻÉn‡ď˙kHóHŔ_µĽýőpşZ…§ÂÝ”şSÄźĎóăüߥm÷xÂiĘ_;t8µn®§ňO8‹ęĆ}”ń~¸Đit˛ŢSźż[¬`˙`޸pD†¸„sĺBŢP_× P”Ö=Ž·BŐc'(ăŹŐcy®'ŐÁ€˙očˇ˙p›cóĺÁ^vkLŁţ_“÷KWQĆ·‚WŘ ­Űź×ÁV}F §=đ_×ŢSBŐ4ľđ‚,Ą(Ŕ~oóÁ*ÓúǬlů-ÜvÇľqNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN‚ ]“ąŚ„çŐ ç–˙@“ąĆ_­mšV˙ b†­°&6˘ H7#@&RI($L™&ú»’@(ÓŁ" h&gQ‡0c "`éě “†2QyJB%,Dš€&`–ŠČ¦S„ 2Ě@0’i jH”Ă*J 1 (‚i¤”  $˘©Ę ÂJa0BJI%Ą­$X €H–„ëD§ X©łB «-#@Á!¨JoV ]G@ĐcĽ«-TËNÉéŞÁł ±Ą†®ŞI1Q‘ąBJ… LÁ’…€Ŕ$„îši¦Ą@“JÄ- .ËôŠQB@~„HDBŐ%4ST(@JIJء…ĄˇňXżDS+eóä!/Č~•şBĂŠ”#ö´r¦„QNâ}Jj—ÁŰż·­ŰźŇ‡Ď‘@vxŠ-Łö4>~mĹŰ-SÄům%J-ëU_qţü)4&»)ZVô‡n´·H~·ůŽ?ŇVŠÚ?'ă(O@}ú®Č»gĎŇx·ůŰ­ô¬Vhúâ yíúâÍţ5żÉˇa€é·ţNÚś!mú+źÝ(óKĎ’HĘCëzVČB?YHâC˙4űŹôűЏO›ĄmbŹË•˝Ňďíß´Ž5¤ľOéÄ>SůŰ˙*P¶ĎÉőްĎ|€©ókvî%ľ/αčˇ8Í­Sű§\täKůţ‹ĄóÝëq|·B7K­WOçĹÄyľ#ű}ůůş_Ł=Ę“üĺ˙›ZĄo(·Óo¬zśCś÷·ţřřÖ¸ü#ž˙ľ$ŰÖ–đéóúÓOšĘyĽrŠĆ[Ęx‡šűĘi·×QÇű§Í[¸˙!űüż^t¶{~ř––‡šĎl‚Kupeµů­QNP·ů¦ŞŢ%ů˘źÎ¸ Ą€NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNç ç–˙@üđ-TŔ×ß˝GĂ©=ő¶4C $š DΠ&I:MOü`áÄ´€6 ‚FČuaĚTAc áA@a@-2E@i0–,P I$¤ÔŔh“:I‰-/ŤB(, áá´¤ĄcRL Љ(H©JMDÉi@š‚ŃIQ¬6  Š†KL’™‰‚PL„ÂJRH4Ë@YP 5€" $†ŔÄ iJH`$°fb`liĚC`)ŰĽŔçżťŽ¶ënć6€¬Ë ¤†5@Lµ hĂÉ$‚€Ä¦ŠĄ`+:H„¬QE dÁŠQA¦…€E-|ů4ýúh B‡ô‹ú(BA@Ú){ˇkÍW 8–ň‹cÜCeµżÝEx o)Á1”»/¸«ő€©t˙ź%câG€€¸żóv˙5ćżo©KęÇ(Ę^NNNNNNNNNNNNNNNNNNNNNNNNNNNNç2 ç–˙@b—•aEDŘ%­iBLA2d 5Ôf¬vî‰ ”Ő Kd™ @ :„€&*’’!¤!bÔ"f@‘.%©˘¬‰¨ŔZ JR a,’CP†! %3U RDI%×Q‚ŞÂ$HA@“0™(XVPÁ 0¦¬¦¬,Ś"€*‚°‰@0RK¨ОІÔL†„TH‰U% ± (Ř ‚ĂľLŠ˘ĂUkI ͬnTbe‚.ÔČÂTs!¦éDư€ ¶Z 1`‚  Ja$¬°Ů k%Ôś±~ý™5|¤Ąm4;jΗéM)$iH ”>R"˘Â“Y ŇE @[¦€Ä)( %ý55–ÖĘ2J¨·Qý 4!(?·Đ‡ô¦ŢŃ&*>âvÜkOź,)BR’µBi ˇi`·JRůú©OŢčEDŇ(đŠ?K||PýúpńXüD`>'ůN}oăý?Oäč+T-¤ź ·&Ţűe?Ľ˘ßúă‡ĹG¸ńRřľâ§óţ OpřUSoĘ-ő(+\h}O〟ż¬rrŽ7AFP¶˙">„e]ľ[âü‘á,¦ŹÉĄinś§)â¤V7[ź~¸éE[ř©âGQo|CĄřť’VÇża÷Q€żiGëöâ˙ä]>Uľ%·ëeoŔ@Të…o)[[|íčýĺ'Â%·>óVÇţÎ _`’¸JŰţ+{®ÁĎtńţíÔ„‹š@óx6’˙ňZ·ĄmOďÍ­%kÂ.źźŕ‘k‰ńZJ|×ć+öSoý­ľŁ÷ćśDqö·ćň‹c­Ç÷”ĺ Ăźy˛·űĘ2šŕŰć©®˙’ŁÂ+Vúŕʸֲ•şŕÇú[/żYO|šióTŃOčÓ”S””ńŐ„ç·éin•żĘÝ”qۇínÝáŇrůűĘiˇ6ôŹ×ę±ß‡mlv{qńńĺ/ß[ió\kYBÖů?4ĺ±ŇµÄůi˙äůBp°IÇů?~µžČĎn5 śĄ6ęĆĘşü펥öQ€©/řźŰčŔ^iŰĎę‡ůRZ`%ŞĆĎ|‚[uż‹=|#žß»~R˙>J©ý[ň‡ř”-q×óUڵoĎ5”¦ÝžÎ—·`•–Ę+śš”[–­Ďßľ¬u§mć˙ÜNꍮÚŐÄ;çáÓđ}‚QoüÍ»ÍNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN‚ ]“s‹„ çŹ ç–˙@Äň˛AP{ő¶ges@ ˛ZŐ5¶ÁÉJ ĆŘĂƄČ'´„•ź H5 Ť„&*Nżä‚‚’¤T`b J †BRM ’„ŔD/HIŞ*%t. "ЏA2iT"DH Ş™€„’‘†[Q%• HŞ-RL€ @hD ¦pB6@Ŕ"óe KPI ş,DÍB`D@… =ŢÜŰŚAUĂ•dʆb eÄ@D˛HiŇBI’)nä,$JDˇ¦—ÉX€ @BLł4‡Č%“)&’”KúJ%lÓĆHĄôˇ!mىvÜa4ŃI/‘ űđřĄnˇ˘ŞVĐŠÁ}ÇHCőĄ´-ˇú8ź…¤P‡ÍĄő’ú‚’·Ć„ĐŚŚQEÉK°?°ů ·Ük°ú ń:´-­§Ź)X>Z[âĘKyO…­QÇKűzŰçŔR•ĄŹäµEĐ8ź?h·ř“úĘ2—éâA·ľ+t¸‚ĘVźţO˙TÖ„mߢž;z×îŘăXŐŠRţ¸E->E»Ľ±—Ďé··ľ·›}»óŁÂ˙B„P˙Í;~%ż7\=¸©Łř_í÷íkň[›ësဩ #=©ĘÚެĎÂűu4ľ·e."Pr…»}4­­3Ůýpĺ’›wâ/˙+{ünýq-ç˝)•ŻŘüż\|x%ü–ÖĽÓçĺ/«ŹÍľ—ÇÍţÖ‹Ąµ”e9C§Ţ­?°µnóuÔ۰?´~¸ëLĺ/¸śC`.7ĎýkôégÔWŢ{~­ůGäšáŔb±řÝ/\JĚí˝ýpţíÔ­ńV[EOËÍ×Mpe6ď[źćźçÉnĘłÝk=ň•ŞRâ"?_·ř ÍÇ{->tűńe`/7ű¬{pNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN çľ ç•˙@O3  -đ9˛ÄÉ×X᫣ZŤ¶Aˇ’Ö):Ő’T««Ŕ%A€$Şd„!±LA’‰ŠĚD IJĐaIJŔ¤™lĄ)% BHLŐ ˘Q%0&Ş%0‘I šHX¬RŠQ‡ I4˘RDĐCe IiŠPł‘&Š„S5$„–š‚˛ŠP”‚* e! A;©Ů”š‡DTMPIh"PfÓ2 ‘†XVy“¶@ Íć$Ŕ‘`4s0W–X[$á†HbgD“˝$UlŔ„*$€‰«R’¶DBQBhA4”?&Ś© „¦¬aż/šQAD´¤”š"„ˇű°ţ„-x\‚R*ĐěłIXRí¤RA|š_ r‡Ä qˇjßJGJh‡Ôż¦Š©(·daGHČâmÄŐBBŐ/¤¬¶âMąřZ}VÜ·ĹBhâÍ~IZ)§(ă˘ßM ˇňM [®ĎŤ­ľ ®Aş˙’Ý8ö(ZŁŠ—Ä[˙TëN€“űýżóX ÝnüŁóş?/ÝąPiM?´Đ) vĺ®*ë¸B/Ý˝óühX§őG愾 Iü‚h·×Ďwţz/7ĹűđşÇ ­ŰżvŕźÉ÷…q[ß[ë‚ŢůŔ˙7ů„qľ(âŁöů´Śü[¸đ ż 0Th·~GÍ%˙ZĘ-ţi¬ů&‡˙§Ţl>[Ŕo˙<ý87żË͢—öęVĽ×€€Ř?ýřGőů"śZeŇř$Ąýąý¸Ö7íőpq>q ”g±ă·`•?¬öýWç”çĘú¸2—˙´Đł8·€łŰ=ßŕ7őŹo·ÓĹĹ€­čEců¬§ň®汣ŹÍş}¸đqú ¶ü…¸ ¶ęÇĂćźV™q Xßž® ÷ZŔh‘'äř¸‰jÚĹţ 2•¤á NNNNNNNNNNN çě ç–˙@őő1ŕ E.üĆÍĐwa(,± ­ÇB utL(fcDÁ`. Ť‚%ÉÚI¨€-”ˇ H$„H’ŘI’S&$…RŕI,™)¦ZR¬6˘ÄŠ`BÔ”„‚)Am)HF¨(C&MH u(”ĚĘT$‚tH )A((Q5S'üɆÂAQĄI”3q†`$QD a·S57$Ŕł-Đ%¦ŘÜFŘ,ŽË&o­2ĽhĚho`…„2I4Ča«’ČIĄ a楇ŠS‘蔬IŞěŐJj!4Š · $-ĄôBÚ Ő Jjżă(ŽFO•ŠPŠV…/¸‹çÉ·„„P_Ë'ȡij‡îÇţ.$:)âG[H(Z·-:RÔÓÇBÁő˝l[ĐůnßA§ŠÝJŇx–ę%(+@|˙)+’]šhZKě%/¸że?şZ v˙ČPµnăĘV+šÂÜé|Ą—őŚh§ń…ş€[¸čX iĄő‘-U¤~­ţ$ń~Tż~µűŻŐc?ŔiĄöS”PţßCáNâýůĽ2źŕ­;`>ýľđ‡R-Î"Ű©tsďĐů&ܶ˙)Z[3”żŔX%ýq­ĺ/©MüŁöÓůO„Vč9E4ĺ/ßů¤g·›®ůRšá«ůŇ˙(¬l÷|°[.‚•·ŮFUŇůŠ)ŇČĎj_×ęÜ·ÇűŔhđµ«vPµćßWQůşíoŤýuĂáX ő»ŚÓ‘.P¶éd~·-׸ůŻ6•«vPét~uŽâ'č[“ĹĹů-Ąmúp.—KěöOęßMĽxA÷ě')E6ňâ/š)Ę?#năvË_«r?\N–}€­Ëx*Ę?UÂř× Ýpş^±Ý-H}XŢn´ÇćéI”çÉžç(˘„eĂćźţ`NNNNNNNNNNNNNNNNNNNNNN çç–˙@ç%ř,HżA~”’Ĺđ!˝ťf%°ÖťĹíz ŔXd€ $¸uŐ€PČ- ’P ’ ™©1 ’J ˇ!5!µ %‰L€I Ś3 ŞDCúĄ¨–ja†“˘Ş‚ś$ `©…T’$–¤„ ´ ™0 ‰ ‡Y(L ™aP2Á-;*j@ É–U2PvÖT@Ř€€Č&bÉhh €I Ř´Á*†ZŔş[- ˛ˇÄ•6I6É]XD‚Ђ٬ň’YC¤¬… S PxŠPP MRH¦TÚ•¤ ˇhJĤPěľ ˇl-?B—ô»)ÂŮ„I¦ŠŹ˙oË ËúM (ˇ&š2…¤>-%+eb”ń?ă§Šßú˘“JĂ(ĹÇ/ß­/Ú]—Ë|r)ˇ FRý%`éz™Yž4')˘š-çŤ"ŠxżIâˇúÇĹnŔAńZZAl>}C÷čn[ý-Łô‚´°?§á÷Rů˙.7ő_ĺ?’+Íe  żĘ8‚ßhóUŤÄř>ÇGě>Ŕ_—ę±–gV’·GçűŔTŁ`'ß–SO·ţĎđS沅Ľöý?ýe9AŁň|!Çćę­äIn¬d»wëC‰§ôýňŢPµ@Jźĺ˙n!Ý,µ”-ľăüÖg_ńŇ_  ”[Ń\cÍq`*-öŕ2ŠmŹÁR8đK揚[ăü˙'ůí€ßÖ3©óO¨~´|"·ů­Q”q¬g_~O‘ŕT¤V6 (BŢ{ y·?¦ŘŕJڤ[˙+{Ą_Ďß-V7ęÜśĄ6˙ËźˇĆ˙>Jk+O˙/ÎÝÄýóĄĽŔÝĹ€Ý/ů× ˝ilŕ,Ćé~,řF‹xüë(§)§Ź)AŁ´ţ––ĽÝce.—ZÁ"NNNNNNNNNNNNNNNNNNNNNNNNN‚ ]“-‹„ çIç–˙@_×I°Ć44˘{`ŮRAL—TĨÉ€RĆK@*L)H-`‚ Lfgb*T 1 DÄ)0™„IEC%0"0¶üeé@v%ţv+"”TŔ$Ś9J*T‰dDĚJBh%auÉ5)(5!"ę l"©’P„[´ ˇ%ÖM!¤Í I(©8A•Ih5jBN  ™č‰Ń ć[T4oa€ďLlŃŔ“ŰNÎş0fÁ‰$3¶‰)‘TĹ‚H¨v4B*0ŤDJ)(Ja"­C@(`)!4‚iJA|˛‡ôĂó oŔ‚ŠtÔ,_ŃJčˇI|„RâůŰSCäŇA)|ů)˘”şRí˙B_ŇJ·n[Ąin(Ąn—Öţ:V“”>|ú©6íSúL­»)EDÓů V–ř±đ»z4?|µGĺ”Ón«s÷Ď©[GJ€RÇć˙€ţ±‘NR] _ŞĆ·~ř€âJßęŽ4­>|‹u (·­#)¬oÍ÷çEo–đV¶2Š*üŐ4ş ý:RĐRř$~źŰ’ţ¸˙^k‹‰÷qÍľZvüKKO˙Tľ[ˇ.ŕ|h·~Ń~‰âqö„QX߯ßçnُ‚âýţUŹ”q­,żÍˇmúŢSNSůQ”W (Ę?*ǡo=ßş2ź`‘nßNZă[Ďgë\HJßš}”V2ж_-Űß­­"Ź ˘´Ĺľ›v Ź–ż+rŐ˙F±íĂ=ň•§Ţmý!5Ă€ň‡ůC±mąÄ:?o¸“Ç€˛—ôţN‚JÇýţßÍÄUŻÍ––˙čüÂ×…-ŕ;cÂĆ)â§őŤGď)·ŰłÚŞŰúmÁ4x +vúÓ—çć˙.*”ľ?żĺV=EJŕ|ű‹ŽŘďÉbšŕý~¨ăt°NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNçwç•˙@5řYŕ J•ţ{lŻż˘Í*'ţXľ 4ݵ2€ ¦I €Ölť(‚ŘCś8 A’tMY$Rś3(IK“N’ɨÂA: ,LLAiÔ(&BAĂ2€Đ0PU( “LŐaI@$B+2„’š€Ş %™(š*U$Ĺ&I5R€DЍ%Ňě‘%!¨B‰‚P!'ĄR„ŠŁÄ % 8S!AAAfXPµF†FťPŇ"Ű:Ëš¤9ʡ—¶&H*–b ¨iB@HBH ÂH@4”¬ŕUv@j0@@ %‰h„PýúIĄňÚ(ĄŘ«I¤…´Š$Ë÷áÓDЍGÂŞÝ?[&•ľ7d»4%nŢŠ*Ň”˘ Ďí`…‹ţ*_¤Đ¶8Ňýihq­ˇinÝú lZĄ˙đ´­->©ĹEş˘(éă¦HJxÖčâ[ĺŞSJń$Łú„q->·ÓOď÷o}ćźäKK÷˙™E/ŠŇmëO˙>4SMřéJ×ěeHNĄú]ÝF –Š?5µ«sëz_Ńoói âBş c­›}půşPŹÓńúĄĐ+Íe#)üźŚ˘Ü´¶üľţ:ý McŰÖĐűŠ”[˙iFRµGďőXůíOäź7žé Ć—á˙š[ă ăZBmâÝű·»o5žä?ń'őůţożOňŠp?•6ńlzkR‘ćĎ們Y[Â\DüËęŕ¬t&±źńeĺżËľűÍçµ.–AĘk÷›tłü"ţŢ˙ôx°é÷›ýůĽ÷/‘Ĺůŕ.?×ĺ€˙\IĘ-î—[˘Ü˙ŹŕđŽPúšĆ}Io>L¦žô×Ęű®H×ÉÁ/šŔUĂ\5Ťů`<ŁxBŚ”~˙vÇ‚éoÎÝ€­Ř%tąŔN!˙_µŻËÂÎ{eíÇZak=‹ěHNç¦ç–˙@bđ#ŐŘňsgˇuÍ"HÚ›]˛ËĂ&;b&#`ť™¦‰,n€h$Ő«±I"%2CĚ Âpŕ5; ¤™I&jÁhh@I-5Ą0Ą „HJ" ©‡EM”ĘHČKeŐ)(4Ő‰TH"”dVDK ¤B@NA¦fU€d¤‚RN’dP–Ą«B`Č„¤ÄI %’Ě&\’K"Ú°7 T¨lžî…XK™¸HřcR%Qˇ…›É1 !&bZL@YŔ¤“2ŇHJ*! ™šP±Á¤¤˘“ XŇPL>J""ˇ „±4ĐE¤Rš( „>4Ő4¦‚ůlńĐ’°©Ä”Rú i Kô? ‘ BÓőş2KńRšh[âĐJVç(ľ¸ź~ÎPP„~Ëę}oĄŮâD”ż(ŁŠÜěRšÎx±|ě%Đic\ XŠGR·FDĄđ[ęÇvÔŰËô[ĘRě-ńţ’ţßBQGíij_~O…ş)Ďk}?°ů9M5QÇoˇŘ~‡é¬z´¶´ýńĹGä< mĎĐ·Ĺot¤óŃńQáúMâvĽ(ŰßţżIZZHĘ0(}‚»~®Ń ć©Ę,Xëv÷ëyJGéiţ•Ş?<hĘ_ĺ6ě÷âăŔyFăü’kk~$;/¸°çů`ŮúĎ{wąŰ~UŽ˙ő\[\Ö5ąţ -ô,Ę Â| _—Ζý-§(Zü–˙ŽÝ‘- ¸ź~bŻ?;u¸řEóđú‹aÂŇÖS€żKKK3–÷HÚ;}přCň[˘±¸ß罺ܵ斖ř°·>Łňýŕ4>¬t?Ćż*x°KűOďő”:~ĎwŢoµÂ·eĄĄ«çKţĎ…­ř+_ŔăZ®ČNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNçÔç–˙@ďđ*|¨ßWŻ!˘e‚BŞ( +ÚlI¶D5"ÎŔ2AVTŃH10dACDÁKpŠhĂŞ[$NŃ Ú€I*ÔBI"Ä Ą¨‚F©Ş  DĚ  [ (ˇa ˇPH/é‚$Č2PA‰I(A‚@! „§¬[Q`L‚° @¨·PDU ťÁI Ń”…I $A2CZLČ2‘ I€2bR©Z4nľÎúu†††BŞŮ`›•l)"[«Ł¸Â@’&!bL8`Ą¦PH1NŘR”Ř:ŠKö @Ą Th%úLŐE, JP]‡eZ|衭ľvô"Źŕq>?› TZăvôš8¨4~°î‡ô Š/Öź˘źŰ·|“\/ĘÚŰę Ó÷ëX*(JÚ”ń!úKoŤ‰ BÝĆíé4ĄúŐ?Ą¸Ŕ\Aň\?ŻČ Ň”[‘A·Űß­ĺOR+… Tˇâ§÷”бý~T[ż_§ütůşEGô­Bm˙¤ľ}n·~x [|ž$V5şŠ–Ľ#ĹžĎݶ -Ö÷ÖęQ\ Ŕ´·ć­éZZ˘ßKţ5żĘßnĘż'ŐŽě­e)[üĐţźŐąŘoŠÝGć|ŢPµ\ۨĘ?Đ´ýnÝžéýqĐ´µůŕ”Qá׼>üňšŕŔ^jŹĎ"9jÜúž:áĘ~–“”ůŁžŘ ?—ĺůĺ6˙ÚÝż­$'ôŠÇ·­[żhĄk=˙4W„<Ó«TŁŽ‡ďłäâ[˘ź×ë=ë¸UÝąőżÍÖ1ý8‡Áµ$Zq _ľ® zاͭҊkŢýĐV’ýn±«„Q”yş_× ĎpűÍţ°Lŕ'Ňú…ż~te/ÝŠŕÁ¶¸2€NNNNNNNNNNNNNNN‚ ]“ç‹„çç–˙@tÇ€€ +WŐ7÷ Žř;!Ą„@č$I¬Ó*ĆÎÚ–€Â„bál.dD”P"Ka$Ў!¤Č0X„Č€X„ I" RQ)&DÓ J ‚V “`RŃÂ(EILŇVx1TÔAL A& €°BeŘŮbDPł„¬@T¤¦$ŔŮd´B!"˘V0:5Du¸"J$CH’@fˇ›$LôÖImELG`ă™,h,föĽI!®»€„f‚„j2‚ B Ă’P@ŞtŚ*•Z)4&Zů&¬PV)JjP‡ácBÚ’ ”>&‡Č ˘ŞÔ—d-ˇů¤Đ•ŠęŠ)ă~VńŰÓĆĄůăĄđ BP?r‡ô5JŘă~„SQl|•µŞVđT˙#;4-”¨@ '‹ŚCę´Ąő!ŰŇů&šhă„~T}~ÓoÁRĆš8Ň‹zݸ¦€_,S”[ź~Ö˛š8¸íÖ쥨ŁÍżK÷ö˙ÍĐ(·ŕ'ď˙+ćëň)óO©Zt·›4­%#öů AĐVoyĄ¤ţgŽßXÉâ6즜ö§"T5ůů´qŰđKJ­?·‡ßżĚż[üÂ+Ť$Ł~k_ť)ýqe ˘Řrú‹tŕ/ŰĄĹđąoo‹őżĎňtýnÁXŐĂžţh×p>ótţht»üŁś^n˘0šB+¸ť?řFśˇ(ĄňßšĄ.–Źáŕ8¸xčMc­y·ÉOš@NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNç1ç–˙@đ*˙—ÚŢĹŃłň.ÔcŐŤ Ăgm Ęł53($”˛2A!˛0Á«!—6–ȆŔEI  Ë SwT$,D¤T€KÔŠ¤0 ¶€)‚”ˇ´@!˛ hM@‰¨Ó„ Âp’u Š’ "“%5@ťHHË‚BP†msI€Ů )HAĐĂu˛RÂ@:%AJp H%¦"MÚd¶a¬ŮDÉ`$Ŕąa[ˇF64\á­ąŤcPeI$’Đť-iI „Ą(¨„:BA€±BBP/Â#dĐ„Ňţh ŃCĺJhĄ»*,@|—A)ăM)BÔ"”şM.ÉYë\KA# ń4%ŮĘúVŤCO…—ĆŢ•[E+đ( [–ß"޵EąĐq[Đű(¤Ű˛”şš xÜ•¤Đ_Úi}űZĎ|?ń%«zÚ?hMŻÝ N–~ýiőcŰĘŢ{RýjÝůű¦š“n®?ń?5Ś+±mŘ cćé·-żŔ®$Ňżt`‘ů.HZĘV¨ýľý-!."e%kB ďËŤ/ňźŐŁÂ+n‚JÇâ¨)}nŞú·qń‡ët% ŁŤň„‚;n7ÔĺÉ Ć•«pM ĺ°(C°ěˇ4&…ş <`R·\CJméK· Ň‡ř+·%ů·?|¶EKv “ĹĹRßůŹěźŐ OĘŮ@Ę)"šr…Ż7Jh|µMľ„۸lj¸ăń-P•ĄĄ´ľ·ĺ!ý!h`*ŕý¤ŠršŕGî…ľ%®1úĘ2‘ű|—ŢjŚh}oZ⦗Ͽvëy}ćx°ęś g·P•µ‡żňóYFXţIYśG€€xҶ¶·G÷ŮMżŤ˙ä ?iĘ0ćK§ţ$[©q”¦‡kúM±ĂŤ/ř¨·ŕ>5™Űr-ŐŽŹßä‹zÝľ‡ôń—ĺm (ŔX6 Ö7cŕ0ţŘ÷Ëo–©ó_ŻÎ—Kń­żĘ2ś˘ß€ť,·‚ZőĆŠŕ·çµ([F{-ěĄŘ­1Eż=¸‹¸$tµ»ůňmô˙>é{zE9­źÓ˙­çşŰĄ’ţ¸ršát˙\"ÜxßľüÝ,@â}luľÝ”qţéFQ‚L§šá[ZHNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN‚]‚ /‚çĽç–˙@ ®q~&<ńrňX$é|N¶t 2ćîňł˛Y ÁÂ- şfC«¶IE@A &L$"©  &`j”Š‚ŞJ ()J&¤CHJ*%W 2P’d B¬Ua P(!%"ŞID%!ÔA0SQ2LH!-~Ij¤DT‚$0t€€Â „ŕ±*‚,HĐÁ!Öšˇ°Ęş©-“ČŮBv2u˛DÁ ­ŞĐĐ –ÁÖ¤‰Ťjf*Fľ ęR€ ™|ĘJHK* š„$Ę[Q(X„€™Z(€)% 4Š©AL Ňü‘8TÓV~•‰ˇ@I \O€)…·š•M)JSJhĄ|Iˇl%˙˛K÷Ĺ)ÓÇE%nÍ©%ő%&ß–)+UiK˛’x’±·ÓK˙Úßő®4¦ŢOé+o„­HĄ+gńńCďŇüżÓ”ĄöSÄ´ý4ÓĆE4AZKôĺ?—Qo·%+iâ6ôŰÖÔ·ĺ$ŇC˙7€íëa>mkŹňă[¤ĹÇú¦Šxß`.?Úk…m+x (}”->Ęîšů­ţ_ˇú|‘*׾śV=+’ŢĽč/˛•ĄŻŮĄ_żĎ‰ĐV˛•şá[·'tŃ”?·ŕ<§ÍţN–t¶Qů[üŐ¸ľý!jP)G›@ćíÜ·ÖúĆĘ|_’ŰĄ˙_µŞhŽ*ż·eĽß6ăć–ü#ÇĆţŢ—ÜyM;86-SůţKtSCôˇú_Űź8†Á-›Ą“oć­Î”µŔü¦¸?Vőľ>3O瀏›ýşYOş—Ôż|’Ś«Š_¬kůçüž r„xAv_·H¤-,iŞ  R…ˇ —Á/ÍeOˇúŔ¦š ô…ŞiKňM4ŰŤ(Ş´ŃA AâKńB-ďĄń|$#ö¶€V¨©üĘŠj?hăýÓJRVݲŃK˛ě% k@P´¶ű÷ň´ŠR—A+i·­ŃžÉĄqźĐĘ)ŔVü÷}…” ĺ(~•·ô~c‹=íȢś‘Xč'ŠšĆýŠ_>¬jÓ+O°ńžé 8 Š˙(§Íe/Ęň” QCäҵZf‡öűwŰî,ű)}űŁ‹őúEőŻ5\4ŁŠŘî/ óyGçBOço·~Yďž˙ŻÖĘ<ŢS”ţ_“ĽuŤXö˙Őc'‰."šĆş0ÖĘK¦¸-é/łä§=żYJ8‘X˙Ş+†ßXÔ8†˘±ř’ůmÄ7NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNbeetbox-beets-01f1faf/test/rsrc/soundcheck-nonascii.m4a000066400000000000000000000133461472325477400232400ustar00rootroot00000000000000 ftypM4A M4A mp42isom ŠmoovlmvhdÄLé_ÄLď¬D¸@trak\tkhdÄLé_ÄLď¸@mdia mdhdÄLé_ÄLď¬D¸UÄ"hdlrsounÓminfsmhd$dinfdref url —stblgstsdWmp4a¬D3esds€€€"€€€@xú€€€€€€stts.(stscĚstsz.22""'-+1/+&0''&%)(-,*).)(*%)-4&6.*,$,3/'& stcoĆé •udta Ťmeta"hdlrmdirappl°‡ilst©namdatafull"©ARTdatathe artist$©wrtdatathe composer!©albdatathe album!©gendatathe genre trkndatadiskdata©daydata2001cpildatapgapdatatmpodata6©too.dataiTunes v7.6.2, QuickTime 7.4.5N----meancom.apple.iTunesnameLabeldatathe labelR----meancom.apple.iTunesnamepublisherdatathe labely----meancom.apple.iTunes!nameMusicBrainz Artist Id4data7cf0ea9d-86b9-4dad-ba9e-2355a64899eax----meancom.apple.iTunes nameMusicBrainz Track Id4data8b882575-08a5-4452-a7a7-cbb8a1531f9ex----meancom.apple.iTunes nameMusicBrainz Album Id4data9e873859-8aa4-4790-b985-5a953e8ef628¨----meancom.apple.iTunesnameiTunNORMpdata 000009DD 00000ACD 00004337 0000460C 00029224 000220C3 00007FFF 00007FFF 0001FC7B 0001E5E5ďż˝Ľ----meancom.apple.iTunesnameiTunSMPB„data 00000000 00000840 0000037C 000000000000AC44 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000"©lyrdatathe lyrics$©cmtdatathe comments$©grpdatathe grouping(aART datathe album artistŘfreefree(mdatĐě\«11;Ä<Ŕř¤4„ `Ifŕe—^ŕďŹP0#Ü·›ťÁbÄ»VOŘŮ(*ľJ€q8ň„q©-@>Ťč[-3ţ!‹rtgĽĽ(Îą¶q]bĄ¸úÄPEŮ€€ ްDăŮ‘>JUl3ËA$Ö)i)ŔúÄA#€+:3ď`Xa¶™ÉůQ;«Ŕ±čŕö˘" „Î Ž× óf{q wZ“Ä 3zżf#)NŮq'ř˘" „kËr˝ŮÖŁ gʧý'”ę )y*¦Úö»,°Îµxˇú˘" „r †BXVFěě7nhϦNž|z%ôe0Ue®«Ś śö‚B„_`9ĺo˘đJ­Dz0Ż)ňřáŰ1F8‘ú·ĺÉ>7t·#‹Uŕř¤4#ŚXŰőŢ™ľáŕŇ`¸Ž‚Ţ×xT†aG~fäHv<ý¶ÁŔúÄ`? &×63Đ”l:äGŕë’ 莵š„ÇÓëĺN‚ŕú˘R#Aש9Ď<łÔiÇ%ÖřꦗµŠÍÎę®yfžÎô‚T„ `®!I}uhnV$?‹+äˇ(Z«„ÁˇÜtaĄVi±+±l‰8úÄ3#PŤ0Ľń•T`’3ąčžÓ#?}ĄŐőÚ»ěÔ‹€ú¤Q#Hl`µÁ˝ĄËK§)Q)E‚ńő>O˛ÂôŻSÔ¦úĆ"#P ŰdpËźżŰ2é­~sŰČÓďŤ'Pîě=Í&ü!úÄPG<Ž0NÝÍEř™_ő'1…:ő‹ĺ\ř˘ ,l qĽÔş<¬>čđÍ&ďĂž¦J3}•]dQ€€8ú˘`F  8I+–¶:aá–;0ĄÇ>m>;ßMľíŰŹź× Ą/gř¤p>€ŢbSci›”¤L z:~HŤ5M3©'°A+č&„f ‡Ö(pö‚0!dŔ˘’Ż:%˘C°9Błî+]ţ3Z†ąčcJr†Â¶Źö¤`E€a,]µ)\BiwČ,yç@ĂşD¸ůťü'ħĄ¨Üř˘ ĐŔ€0qóŕ=bžmBĹAwůH°×! šDSŞ 8ö‚5 !`Â4†§ĺ^ńíŞ^:$9µĎ…gs`M%…ĘZZtŞ^UŁY(o€ú˘"¤09ś’#NŃ·#̬zÚW›;t A-1dyß”3¤^ ŕú˘" "ěFâwěĽú„,µ¸rŠţ¬js%”źŇísŮfžep=Ľř¤`G €@ŔăŇ5?ŕĽ`•ŘÉÍM˛ČöĆýYB^×;C€ř˘`BČ ĆăiRř—‡iéŽ60 9üľĺÓvë…(Ü˙˙GYJ´…=˘oçlÇWř¤3 BĐ3ÜQhLÖĆ$/ę‡b<÷~łµ8ëűŰΚSŤ­n*jőXeĹ×7’#ŕô‚"(„@Ř Ad¨*Â`óŮ'á¬Á®co·>Vűça &µ% ř¤`F‚ ˘±äßxă}¸<ëĘüfP[2ÚqXä.Š'Iµpú˘" „¤*ÓŘšÖ'ęŠÇÉR™z[oÝÓ¬ía‚„ľ”XÇS2p'ŔřĆ2! éZđŢx˛µľůěú$©żVn´%j(óVŔö¤`F‚€öŽťF†@›c}}đCÂ2>Ű<ĆçO5Ł!ąQŢ/grDŹđú¤aBŕô‡2‰´S(^®ZŹdđ{¦P¤ź~‰Ůۦ‘6 Sşśö˘ „,`Ľ-1wNŢAŔÍ”XPKhˇwKŰ#0R¬Y±Xť-Ŕř˘`E "ĹImq¤>w˘Č1s5{!ÁXş[Ż/ćŰ?ÓGŹĄxř˘2"%€Xä  aÔž®ŽćĎű©ĆĎçż÷şrŻLaŔě\ŞRi"?pě\¬ „ô@Ŕ\beetbox-beets-01f1faf/test/rsrc/space_time.mp3000066400000000000000000000310241472325477400214310ustar00rootroot00000000000000ID34TIT2fullTPE1 the artistTRCK2/3TALB the albumTPOS4/5TDRC2009-09-04T14:20TCON the genreCOMMhengiTunNORM 80000000 00000000 00000000 00000000 00000224 00000000 00000008 00000000 000003AC 00000000TCMP1TENCiTunes v7.6.2COMMengthe commentsTBPM6TIT1the groupingCOMMengiTunPGAP0COMMengiTunSMPB 00000000 00000210 00000A2C 000000000000AC44 00000000 000021AC 00000000 00000000 00000000 00000000 00000000 00000000USLTengthe lyricsTCOMthe composerTPE2the album artist˙ű`Ŕ F=íÉ‘A#ô‰ą˙˙˙˙F0Ăß˙C ˙_ůď˙˙˙˙óĎŁ)çÚa”2ůŠz<‚žyő=xů=O `ĐĘÇŤ  ĄŕĆyé>`řÜá ĐŔXč" z)ăBś"Ŕ$)‚ A„ ˙˙˙˙˙ř˙×?˙ű˙˙˙˙ţľÝvbŁ×Bť¬¦ ł4Ŕă°˛+¸µÂ¤ÍrąÁÔR°Ä%;ťîRŁQ[˙˙˙ďÖÜî˙łľÚ7UW6dTUT!ub8´Çř çčx?ń.Ě€Ź˙ÚÚ˘ł<ŚaeźDY/˙˙˙˝˙PśĽ·$D¬(躡ďPŚi§˙ű`Ŕ€Š9ŕ… éâH#4•¸‹RŇAbÉuĎ&Ž ü:=ÄÝL´2!Ô¶#9îŽÔ#"5ż˙˙˙ú­˙fE$ěȉ,Ş1HËez˛1’Ş!ĨR ‰Ě§)˘((â˘,Ęc°š)ÄÜX˘¬,qE„T9N@řt˙˙Ňţ÷˙?˙ű`Ŕ ň= mÁ¸Č#tŤ¸ż˙˙óËäÜŻđÉ÷INˇćiŢ˙{ŢžYg§S—ÜëdE/'˙˙˙˙˙˙˙çţ§Ě¸g Wëď&%ÍirÄDuń6.ÝítÜ‘mtP&ę,Ž«˘’Duhâb¨D›ŔN±˙˙˙˙˙ý`Z˙ý{˙˙˙˙˙çĚímŢóĄ/ĂžjľGH„<&ńÎCĄ˙ü„žl÷˙˙˙˙˙˙˙ýţ˙çJÎÉPŤ1•ĄI’ňÔä»ň9Ł3łZHFĘ;ŽÔr/viĂ1  Ü‰…Ł2‡ ěÁ˙˙˙˙˙Öő˙ţy˙?˙˙ůz\¨Ű+dDÎŞŹ1ĆckTIw˙űbŔ ć% „MɬH#t‰¸™¨…¶R˛¶Í´ö˙˙˙˙dÝꓳĐ÷1ęr© ĘĎłîŇ»łlÖ• lΆmC‘Uę÷c,ě$(tČ4BK˙˙˙˙˙ëĺý˛ëZS˙˙˙˙üąQRĹ­ÝŚ‡>łĚl®®č§1ڍËf:V¨m&ľĺDGB;?˙˙˙˙˙ů­RĚé+9‡}•+Ďł+±ÍUĐčĘbPSú;1Ś%Ü; Š…R•Ěwt”€Î9B˙˙˙˙ţ¶˙ë˙•˙˙˙˙äěúRwó5ťlŞ„s>ʸő#ĚäşuFlëĐęɕћ˙˙˙Ú˙ł«ÔŞFJČŞyŚEus–˙ű`Ŕ" ? „­Á¬Ć#t•ąa2ft*3άVwVl̸â•ęc@@Ç‚o2ÇĺQÇ;> ˘Ł€?˙˙ős˙˙˙˙˙ü»µ®ĺ:=¨îďv:şş•VB/Îlęčö-ĘZ$Ű™5O˙˙˙ţÍ'*MDŁť'» Č.–zą›S«”ĚęW!„Ça溡Z!3#NǞ#!˘âcH5VA¸ň¨`†`±óőůţż˙˙˙˙®•g;RWTťŮUŐ˝ÔÝQeŃŃTÄľÎB%O˙˙˙őůŃgęŐ9F F‘ťT‡j9"Î(cJb#!ÇxŐ3ł ŞE˙ű`Ŕ, Ú= „­ÉŻČ#4‰¸vcśqL8áaŕĘPq˘Ń2$48˙˙˙˙ţÖŻu˙ş˙˙˙˙˙?Ł‘Ý”„5›Rl›ogGĐŚŞöi7şŐŽ·ěKw˙˙˙˙˙ÚŇ"1Ý_™Ü§8¤T„Dgťä;YśFä%PěĆ \ČäRŠF €‚äS3: ÁČQBŞŕ•Ü(˙˙˙˙ţÍ—˙/üˇßźő˙˙˙o‘śíbnYŘz(‰‘ëu×J:ä·IdŁďZĐÔŮ˙˙˙˙˙ţźJ'»J Ff®ď©Ne+ Č 8ä*&*†q2 0Ě AŠAD…0’ŤQ!2Ş”ĄA§aQE˙ű`Ŕ6€ ¶A „­Á¬EŁtŤą0˙˙˙ý°/ó׿üż˙˙˙˙ň]sąśĐô… 仪Dţ9FzÓ¦[7‘tĄ+Mą2äł˙˙˙˙˙˙˙é˙9ćÓ*FE‘D‰»ë2ł×r™9“Ň·I­|ĽUčĘă±Áz°spÂ+† JŰŘ8˙˙˙˙ţŤ©üĽňgRfŇĎĺ˙˙ýţ•$íc1üý§ş!Yz:d÷#ę×*ş%Pú?{Ż˙˙˙˙˙čɦ·5\ÎRŁľě‚ŠTv{Ä$A™ RkŃi†!ٱĹĘÖ(|¨rpđé˘î<ÂÁŔ+)h0Ŕ5"üż˙˙˙˙˙dî˙űbŔBVA „­ÁÄÇŁ4•ąŹF±:ĽŰ>yę­)Ýě¬ĺşQň˛+7cfT˙˙˙ëŮ[óÉWŁ•hrż˙˙˙˙ŹiO‰Ť&׊aҶ§ö’ŃşkL“ÔW0÷?ÜpµpÝ˙˙˙˙˙˙˙1˙×ýúÚuz÷¤sň8ë›~9Ťç^;Ž’…ěar:¦hZ—Ro|†AwÚÖěXňo®Ü**>@Ö0˙˙˙˙˙ëËWuűJŤFŃYěîĺňYŮrć\îjí»*"±Hß˙˙˙˙~î‰m¨—g*:…+"ofi•ŢÂ#EŹ ¨˛)JçWACę,$aqĘÇ)„€p1Ś"Pč‘ĹH‡(ѡ`8˙űbŔo€5 … É˝H"Ě•¸3čň˙Ż˙˙˙Ö×ÝzčĽě×»Čr/Ȣ Ę®”dŁ3Ń—yN¤q˙˙˙÷ä{"Zĺu#ĚeR ¸ńŽÄsî®e-ESi‘pŐ( ›0“„bŽ3‡Ŕăs†yNEr”ÓŠ¨€ ŚŔ˙˙˙˙˙íyď˙éß˙˙˙ýz.Ą1,o5ä+9·Ežuvf±Îőąv}ČČ–N˙˙˙˙Dű}lłµ+3UTŠCoĽĄ>%čOyS!ťXڇ‘Ći;ť\ę8#)Ĺ:‰cĹ•Ęt(P‘Ŕ?˙˙˙Ň˙ů˙ó˙˙˙˙ů´š÷f˙ű`Ŕx 9 ­Ů¤GŁt‰ąb$„vşftgdű©kU-™śčƤĆVäfŰ˙˙˙˙ěźęR>Ó­ŐK5(i5; Îń“•ŃŠwiÇ*ą¤"âŠ*.*1â¦c´x´çRH@“#•bŔ˙˙˙ö@ż˙-»<ŽÄjµ=]ÖÓTޤ®Ć96Kä<îäsnłĽó—˙˙˙ýv"Ńő{Ń]LëgRťg©ŞF5ŽFuőZÇQ3ŹQÓ ‡Ç°›”a‡¨Ô µĚ.tP‰GQCĺ<ĂŠ˙˙˙˙Öţ]˙ţ«Ż˙˙˙ţŢy‘žr3ÚŰg§ź¤m4U++!d“Ö¶_J´Í{˙ű`Ŕ nA „­ÁŞGŁ4ą˙˙˙˙˙˙˙˙öů7ČqyiHsC0¤Er_?* –gĐNP‰îF¸v#UCŞ9ŽB!* jčŤA‡1Y(0˙˙˙˙ý`_óňţ_˙˙˙˙üßíŃžďzJ_ąK&´išôE3Šeu!&>ţF˙˙˙˙éĚýŃ˙ÜW˙˙˙˙˙˙˙˙?íńó÷+kLÖĂůŽ*Řëk‘s*—gJŹ4`žąEĆÇviF"‡$z©†9‚XőÂaË”9pčP·XT]Ç˙˙˙˙˙c`qîy˙ű`Ŕ˛€ .A „MÁ­GŁtŤą‰#±’ż%ľ§˙ţY¸tűîú㙬ꙉyĄ†ýwŠőÚ©¦÷ŢŇĄ©S»éşć˙˙˙˙˙˙˙ţ˙ż˙űżŽąškŻ«YO›­˛<6<’±…&TÉë˘Ć!wB±E2 ¸öłJ‡4\V…K1`°’ކ6Ëq1Š@˙˙˙˙ý±Í#WĺżĚ«˙˙˙ý:*\ČŞuo™ŹťH(έb+JB{1¨¦î·±•jj˙˙˙ţť7Ą®·tT2+9#ÝÖ<–şśČ©*­ ayŚQ1§3•Ú&=ĄB:``ë‘‚˘§Š ‰Zpđ[JQ˙ű`ŔŔŠA … ÂČ"´ˇ¸ €k?ýţ˝˙˙˙˙˙ţŐdR•¨ÉżWDJ2oR›±ÎÓęUd©Šf3¦Óo5ź˙˙˙˙ňKk­Ę„y™bęěęt3–S’Č5‹Ž «Ź,† 1 áňŽrťJâŠ0˘„#(@Xęč$,⊏8Š4xD*…„˙˙˙˙˙’Ëäű×˙˙˙˙űνey5v}úş*±K÷U2‘ÖgR™–tK"µoE˙˙˙˙˙éîíLĘŚE:2+Ů’}Hî8†fc ‹ł)†ĹĚîěBaÇds¸“!…„XPrxńĘ,Š($0<*5 †4lü˙ű`Ŕł€˛A „­ÁŰH"ô•¸Ö|˙˙˙˙˙˙ôM‘Ű[V۲NI”¬ÚÖçbk3›ß˘ŞŐ‘Z~ż˙˙˙őˇj‡»µ¶g*•Ř”ssB«Ąr”¬QGމ ”„)ĺ10‚ädĆw<)]Ě($aE8pj‡ĐhŔłŔ˘¦‰€˙˙˙˙ţŤßü˙F—=ĆÚţż˙ůĺ®›±÷őĚk*"§f5§˘*YŐC^bşę§»LÜŐ˛˙˙˙˙˙ëî‰wcަUÎ<Ę;0Ô=QÖ«žŽ¤Ź:łóż˙˙˙Ďéą&q…+T©µ'ŮTU•ŚgeR á‡B32)—ýž·D|ď˙˙˙˙Ó]«MÉş+YŚ[Ö¦0Ň)ĘbŽĄŽ5EQ„ÄEa!\hAÂ!ذ łBĂîg Ŕ(ńá‚ÂC &P 8|DV&Q˙˙˙˙˙ű`Ŕ´€ÂA „­Á×ÇŁ4•ą˙éä^¤–ú,‹˙ź˙˙žŇ÷f»‘×d[Ňş˝ VąLës ws)P§z^¬t252]ko˙˙˙ď§Ł÷]»ÍĘS2•gsƙٞDkaśÔ9Ç#Â"%F âDL*Ł˘ (B" ‡XÂŽAěbĽ>˙˙˙˙˙±…ĺţY//˙˙˙üć©J$î”Y?RşŃôVT}ŢŠrQKjßZmu}v˙˙˙ţ«ëzttz%¦TS•1ä#ĚĆ˝j=‘ŇŞ”ŞQ!0c ÓH,˘ĺ ŁÄÄĂŠ,ˇŃw‡Č 8ç1Đĺ†0¸ĐŰ ĂjĐö˙űbŔ¶€A ­ŃŮČ"ô•¸}s—˙ţż˙˙Ä@÷:XČ®M*¨Ccą§r”ü­#çDQz­Şý§µ)˙˙˙˙ýţ–ŃNbç›-LQl„0׳ )ťÜk ;Â,5ÜŁĹ‹;"Š 8ЦA¨$ÁŃqĆ\M…EAB@Áç( *˙˙˙ţŤ˙-ă4Ń—pţR˙˙˙ł˘îVlěU·K&öu+<Ď™‡tLěěv3ˇŠÔłI{×˙˙˙˙úűű•YŃ Ą*«“ęçŞîR†Đ<Ž=ÇÔLU…aĹqáL (s”ń¨§D4P€Č‚Ä !€8˙˙˙˙˙ű`Ŕ·€’A „­ÁńČ"´•¸˙ěŚ w"šçĎ’–«˙˙ţM˙ÇýwóóŻ÷Üu]fFĚŐH÷SR‰őô±×KŻ×˙˙˙˙˙˙˙˙˙Ďw§qóßLň°»OĄO3r˛1GÔ­mÍ»±ĺ;=šSĘ8yŁ2lĎ#•0ŃÄĂHpň ˘ĄŹ% « €˙˙˙˙˙˛źĎ_ĺËz˙˙˙ţţŠŚ÷Z2>ô±• ŚĚ]ŞC\ťČ„VGGuyŐ(Ęr˘J´˙˙˙˙ű[BUÝźaj«öŁ[‹Ő™HŠčăLR«yڇś¬C%Üq]Qbě->A!Yâ†`Ł1\Xâ@`Ň y?˙ű`Ŕ·6A „­ÁâČ"ôˇ¸—#˙˙˙˙Ę­ä&ĘŞËťŐčęĘ}V¤1ÖłFnną:ű[Ż˙˙˙ëmjĚësˇŇŐF!ŠbŠę®Ć)†‹‚*‘LŚc°jˇ¬*‰˘ď0pĄ>$av!śXč4DHÁÁ5‡ĹĨU(p@1@˙˙˙ţ­üŚ˙ĄÔËďë˙˙˙7ęěµR!ź#ٵŠč¨~ܦf5ćfb1ŢŽÝS"Ű˙˙˙ţßčÍišŁP®vJÖ®d1qčq”2ÚČ0:Ĺ+‘T6aÂbEl?2‡DD…‡‡\M"B Ł˘† ”Ác4\6Ă ˙ű`Ŕµ€.A „­ÁçČ"´•¸´ýą~ö˙˙˙ţT«Ű"Ł—ÜÇž—ŃsŮŃhę®DÎmYd˝îdSnŰ˙˙˙˙éíľ­:Ş3š§š•c%®5HQU<§!ĹEHcDH@8â(‡E`=‡(ąĂ§qR¸˛:$&<(ją‹ĆЏ  hŘ6­óćż˙˙˙˙#í%)sMŞŘí1ćrÚě·IUkwG‘rîGtTe÷Ń—˙˙˙˙ŢŐęŰ++Jζ%ƱŢČÄĚ<‹;‘XÇ8q‚:‹ Aa6qe- @ĘAA¦`‘Da"ÄĆÄ„@đ™”:?˙˙˙˙ý$˙ű`Ŕ·€A „­ÁďČ"´•¸/ţN_¬™ôë˙˙ä ÷Gő>ľJĄÓ:˛ť—G4¸±Q(„tC\î{+©•®gO˙˙˙˙쮕˝Ôł!O<®ë!¨Žd,ŠćR1>‚%f0°¬@ę>qâˇô (¨ĺś4ÖQ!0ř€şX‚eQC€˙˙˙˙˙Ňäţgě?˙˙˙˙˙-‘ś¦ŽI"*ĚDŁ™•̧Y§In–jH~­’Vkg»T®Š¬‹˙˙˙˙•úŃĚŽäRËŐ®¬–K¦í[ÝF"Ψpá®ĺs ”<îáC‡A r$ň 0×R‹ŽĚW€˙˙˙˙Ű˙˙űbŔµ^A ­ÁéÇâô•¸˝˙˙ß›˙˙üż->ůŞó/ß&@şŇzšşË)˘Ô-^źđĽŽ˝Ďmű˙˙˙˙˙˙˙˙ĺ‘ßće ·îV›E‚6ŤťŢő$4Cą»Ľ­dPŔŽŠ$B¬ŽAĹŁE "Ł,†8SW©9 € €`5‘ç˙Ëćkúë˙˙ň˙ZšçTCŁú^5Ýő‘ś¨bT©‘Üz-®–™=¤˝ź˙˙˙ýiCž”čËB)©au”‚o,ŃČ8‡c‡ ""@”AĐDĄ*<:*‡ °x:@ůDEEH8:.څЇ†˙˙˙˙¶üżůëď˙˙˙˙©˙˙–ŽÍ­îGH˘’°»Nޱ™ÄW%˛zŐóŘřDo“eŢ˙˙˙˙˙˙˙ţ_ü?)ąleyX†żç)ÎśzRĚź"]Ë•L´'dxČĐtS 3j±Ű é ŕ˙˙˙˙˙Ŕ~ds˙ű`Ŕ˝€>A … ÂH"´•¸ć˝ ˙#˙˙˙˲µĐŐc0‰&V‰ZâBĹrĚdpëFEyFm Ą*ł¬Îľë×˙˙˙˙˙żżmYĘŠVĘĆ}LęĆ+>j=‚¨,ĺ-*UcĹB˛=ť QSÎPčŃÎ"ĘYH  ˙˙˙˙˙˙˙˙˙˙˙˙Ż˙˙˙˙˙Ż˙ý~Ć(`JEŞ*!ŮŮĘbŞ”Vs ` #ł±Š©˙ł”ÁB(˙ű`Ŕł€ n? „mÁŃF˘ô•ą˙űbŔ»€&@ŢMŔ[Ŕ˙ű`Ŕ˙€ŢŔbeetbox-beets-01f1faf/test/rsrc/spotify/000077500000000000000000000000001472325477400203745ustar00rootroot00000000000000beetbox-beets-01f1faf/test/rsrc/spotify/album_info.json000066400000000000000000000651061472325477400234120ustar00rootroot00000000000000{ "album_type": "compilation", "artists": [ { "external_urls": { "spotify": "https://open.spotify.com/artist/0LyfQWJT6nXafLPZqxe9Of" }, "href": "https://api.spotify.com/v1/artists/0LyfQWJT6nXafLPZqxe9Of", "id": "0LyfQWJT6nXafLPZqxe9Of", "name": "Various Artists", "type": "artist", "uri": "spotify:artist:0LyfQWJT6nXafLPZqxe9Of" } ], "available_markets": [], "copyrights": [ { "text": "2013 Back Lot Music", "type": "C" }, { "text": "2013 Back Lot Music", "type": "P" } ], "external_ids": { "upc": "857970002363" }, "external_urls": { "spotify": "https://open.spotify.com/album/5l3zEmMrOhOzG8d8s83GOL" }, "genres": [], "href": "https://api.spotify.com/v1/albums/5l3zEmMrOhOzG8d8s83GOL", "id": "5l3zEmMrOhOzG8d8s83GOL", "images": [ { "height": 640, "url": "https://i.scdn.co/image/ab67616d0000b27399140a62d43aec760f6172a2", "width": 640 }, { "height": 300, "url": "https://i.scdn.co/image/ab67616d00001e0299140a62d43aec760f6172a2", "width": 300 }, { "height": 64, "url": "https://i.scdn.co/image/ab67616d0000485199140a62d43aec760f6172a2", "width": 64 } ], "label": "Back Lot Music", "name": "Despicable Me 2 (Original Motion Picture Soundtrack)", "popularity": 0, "release_date": "2013-06-18", "release_date_precision": "day", "total_tracks": 24, "tracks": { "href": "https://api.spotify.com/v1/albums/5l3zEmMrOhOzG8d8s83GOL/tracks?offset=0&limit=50", "items": [ { "artists": [ { "external_urls": { "spotify": "https://open.spotify.com/artist/5nLYd9ST4Cnwy6NHaCxbj8" }, "href": "https://api.spotify.com/v1/artists/5nLYd9ST4Cnwy6NHaCxbj8", "id": "5nLYd9ST4Cnwy6NHaCxbj8", "name": "CeeLo Green", "type": "artist", "uri": "spotify:artist:5nLYd9ST4Cnwy6NHaCxbj8" } ], "available_markets": [], "disc_number": 1, "duration_ms": 221805, "explicit": false, "external_urls": { "spotify": "https://open.spotify.com/track/3EiEbQAR44icEkz3rsMI0N" }, "href": "https://api.spotify.com/v1/tracks/3EiEbQAR44icEkz3rsMI0N", "id": "3EiEbQAR44icEkz3rsMI0N", "is_local": false, "name": "Scream", "preview_url": null, "track_number": 1, "type": "track", "uri": "spotify:track:3EiEbQAR44icEkz3rsMI0N" }, { "artists": [ { "external_urls": { "spotify": "https://open.spotify.com/artist/3NVrWkcHOtmPbMSvgHmijZ" }, "href": "https://api.spotify.com/v1/artists/3NVrWkcHOtmPbMSvgHmijZ", "id": "3NVrWkcHOtmPbMSvgHmijZ", "name": "The Minions", "type": "artist", "uri": "spotify:artist:3NVrWkcHOtmPbMSvgHmijZ" } ], "available_markets": [], "disc_number": 1, "duration_ms": 39065, "explicit": false, "external_urls": { "spotify": "https://open.spotify.com/track/1G4Z91vvEGTYd2ZgOD0MuN" }, "href": "https://api.spotify.com/v1/tracks/1G4Z91vvEGTYd2ZgOD0MuN", "id": "1G4Z91vvEGTYd2ZgOD0MuN", "is_local": false, "name": "Another Irish Drinking Song", "preview_url": null, "track_number": 2, "type": "track", "uri": "spotify:track:1G4Z91vvEGTYd2ZgOD0MuN" }, { "artists": [ { "external_urls": { "spotify": "https://open.spotify.com/artist/2RdwBSPQiwcmiDo9kixcl8" }, "href": "https://api.spotify.com/v1/artists/2RdwBSPQiwcmiDo9kixcl8", "id": "2RdwBSPQiwcmiDo9kixcl8", "name": "Pharrell Williams", "type": "artist", "uri": "spotify:artist:2RdwBSPQiwcmiDo9kixcl8" } ], "available_markets": [], "disc_number": 1, "duration_ms": 176078, "explicit": false, "external_urls": { "spotify": "https://open.spotify.com/track/7DKqhn3Aa0NT9N9GAcagda" }, "href": "https://api.spotify.com/v1/tracks/7DKqhn3Aa0NT9N9GAcagda", "id": "7DKqhn3Aa0NT9N9GAcagda", "is_local": false, "name": "Just a Cloud Away", "preview_url": null, "track_number": 3, "type": "track", "uri": "spotify:track:7DKqhn3Aa0NT9N9GAcagda" }, { "artists": [ { "external_urls": { "spotify": "https://open.spotify.com/artist/2RdwBSPQiwcmiDo9kixcl8" }, "href": "https://api.spotify.com/v1/artists/2RdwBSPQiwcmiDo9kixcl8", "id": "2RdwBSPQiwcmiDo9kixcl8", "name": "Pharrell Williams", "type": "artist", "uri": "spotify:artist:2RdwBSPQiwcmiDo9kixcl8" } ], "available_markets": [], "disc_number": 1, "duration_ms": 233305, "explicit": false, "external_urls": { "spotify": "https://open.spotify.com/track/6NPVjNh8Jhru9xOmyQigds" }, "href": "https://api.spotify.com/v1/tracks/6NPVjNh8Jhru9xOmyQigds", "id": "6NPVjNh8Jhru9xOmyQigds", "is_local": false, "name": "Happy", "preview_url": null, "track_number": 4, "type": "track", "uri": "spotify:track:6NPVjNh8Jhru9xOmyQigds" }, { "artists": [ { "external_urls": { "spotify": "https://open.spotify.com/artist/3NVrWkcHOtmPbMSvgHmijZ" }, "href": "https://api.spotify.com/v1/artists/3NVrWkcHOtmPbMSvgHmijZ", "id": "3NVrWkcHOtmPbMSvgHmijZ", "name": "The Minions", "type": "artist", "uri": "spotify:artist:3NVrWkcHOtmPbMSvgHmijZ" } ], "available_markets": [], "disc_number": 1, "duration_ms": 98211, "explicit": false, "external_urls": { "spotify": "https://open.spotify.com/track/5HSqCeDCn2EEGR5ORwaHA0" }, "href": "https://api.spotify.com/v1/tracks/5HSqCeDCn2EEGR5ORwaHA0", "id": "5HSqCeDCn2EEGR5ORwaHA0", "is_local": false, "name": "I Swear", "preview_url": null, "track_number": 5, "type": "track", "uri": "spotify:track:5HSqCeDCn2EEGR5ORwaHA0" }, { "artists": [ { "external_urls": { "spotify": "https://open.spotify.com/artist/3NVrWkcHOtmPbMSvgHmijZ" }, "href": "https://api.spotify.com/v1/artists/3NVrWkcHOtmPbMSvgHmijZ", "id": "3NVrWkcHOtmPbMSvgHmijZ", "name": "The Minions", "type": "artist", "uri": "spotify:artist:3NVrWkcHOtmPbMSvgHmijZ" } ], "available_markets": [], "disc_number": 1, "duration_ms": 175291, "explicit": false, "external_urls": { "spotify": "https://open.spotify.com/track/2Ls4QknWvBoGSeAlNKw0Xj" }, "href": "https://api.spotify.com/v1/tracks/2Ls4QknWvBoGSeAlNKw0Xj", "id": "2Ls4QknWvBoGSeAlNKw0Xj", "is_local": false, "name": "Y.M.C.A.", "preview_url": null, "track_number": 6, "type": "track", "uri": "spotify:track:2Ls4QknWvBoGSeAlNKw0Xj" }, { "artists": [ { "external_urls": { "spotify": "https://open.spotify.com/artist/2RdwBSPQiwcmiDo9kixcl8" }, "href": "https://api.spotify.com/v1/artists/2RdwBSPQiwcmiDo9kixcl8", "id": "2RdwBSPQiwcmiDo9kixcl8", "name": "Pharrell Williams", "type": "artist", "uri": "spotify:artist:2RdwBSPQiwcmiDo9kixcl8" } ], "available_markets": [], "disc_number": 1, "duration_ms": 206105, "explicit": false, "external_urls": { "spotify": "https://open.spotify.com/track/1XkUmKLbm1tzVtrkdj2Ou8" }, "href": "https://api.spotify.com/v1/tracks/1XkUmKLbm1tzVtrkdj2Ou8", "id": "1XkUmKLbm1tzVtrkdj2Ou8", "is_local": false, "name": "Fun, Fun, Fun", "preview_url": null, "track_number": 7, "type": "track", "uri": "spotify:track:1XkUmKLbm1tzVtrkdj2Ou8" }, { "artists": [ { "external_urls": { "spotify": "https://open.spotify.com/artist/2RdwBSPQiwcmiDo9kixcl8" }, "href": "https://api.spotify.com/v1/artists/2RdwBSPQiwcmiDo9kixcl8", "id": "2RdwBSPQiwcmiDo9kixcl8", "name": "Pharrell Williams", "type": "artist", "uri": "spotify:artist:2RdwBSPQiwcmiDo9kixcl8" } ], "available_markets": [], "disc_number": 1, "duration_ms": 254705, "explicit": false, "external_urls": { "spotify": "https://open.spotify.com/track/42lHGtAZd6xVLC789afLWt" }, "href": "https://api.spotify.com/v1/tracks/42lHGtAZd6xVLC789afLWt", "id": "42lHGtAZd6xVLC789afLWt", "is_local": false, "name": "Despicable Me", "preview_url": null, "track_number": 8, "type": "track", "uri": "spotify:track:42lHGtAZd6xVLC789afLWt" }, { "artists": [ { "external_urls": { "spotify": "https://open.spotify.com/artist/2RaHCHhZWBXn460JpMaicz" }, "href": "https://api.spotify.com/v1/artists/2RaHCHhZWBXn460JpMaicz", "id": "2RaHCHhZWBXn460JpMaicz", "name": "Heitor Pereira", "type": "artist", "uri": "spotify:artist:2RaHCHhZWBXn460JpMaicz" } ], "available_markets": [], "disc_number": 1, "duration_ms": 126825, "explicit": false, "external_urls": { "spotify": "https://open.spotify.com/track/7uAC260NViRKyYW4st4vri" }, "href": "https://api.spotify.com/v1/tracks/7uAC260NViRKyYW4st4vri", "id": "7uAC260NViRKyYW4st4vri", "is_local": false, "name": "PX-41 Labs", "preview_url": null, "track_number": 9, "type": "track", "uri": "spotify:track:7uAC260NViRKyYW4st4vri" }, { "artists": [ { "external_urls": { "spotify": "https://open.spotify.com/artist/2RaHCHhZWBXn460JpMaicz" }, "href": "https://api.spotify.com/v1/artists/2RaHCHhZWBXn460JpMaicz", "id": "2RaHCHhZWBXn460JpMaicz", "name": "Heitor Pereira", "type": "artist", "uri": "spotify:artist:2RaHCHhZWBXn460JpMaicz" } ], "available_markets": [], "disc_number": 1, "duration_ms": 87118, "explicit": false, "external_urls": { "spotify": "https://open.spotify.com/track/6YLmc6yT7OGiNwbShHuEN2" }, "href": "https://api.spotify.com/v1/tracks/6YLmc6yT7OGiNwbShHuEN2", "id": "6YLmc6yT7OGiNwbShHuEN2", "is_local": false, "name": "The Fairy Party", "preview_url": null, "track_number": 10, "type": "track", "uri": "spotify:track:6YLmc6yT7OGiNwbShHuEN2" }, { "artists": [ { "external_urls": { "spotify": "https://open.spotify.com/artist/2RaHCHhZWBXn460JpMaicz" }, "href": "https://api.spotify.com/v1/artists/2RaHCHhZWBXn460JpMaicz", "id": "2RaHCHhZWBXn460JpMaicz", "name": "Heitor Pereira", "type": "artist", "uri": "spotify:artist:2RaHCHhZWBXn460JpMaicz" } ], "available_markets": [], "disc_number": 1, "duration_ms": 339478, "explicit": false, "external_urls": { "spotify": "https://open.spotify.com/track/5lwsXhSXKFoxoGOFLZdQX6" }, "href": "https://api.spotify.com/v1/tracks/5lwsXhSXKFoxoGOFLZdQX6", "id": "5lwsXhSXKFoxoGOFLZdQX6", "is_local": false, "name": "Lucy And The AVL", "preview_url": null, "track_number": 11, "type": "track", "uri": "spotify:track:5lwsXhSXKFoxoGOFLZdQX6" }, { "artists": [ { "external_urls": { "spotify": "https://open.spotify.com/artist/2RaHCHhZWBXn460JpMaicz" }, "href": "https://api.spotify.com/v1/artists/2RaHCHhZWBXn460JpMaicz", "id": "2RaHCHhZWBXn460JpMaicz", "name": "Heitor Pereira", "type": "artist", "uri": "spotify:artist:2RaHCHhZWBXn460JpMaicz" } ], "available_markets": [], "disc_number": 1, "duration_ms": 87478, "explicit": false, "external_urls": { "spotify": "https://open.spotify.com/track/2FlWtPuBMGo0a0X7LGETyk" }, "href": "https://api.spotify.com/v1/tracks/2FlWtPuBMGo0a0X7LGETyk", "id": "2FlWtPuBMGo0a0X7LGETyk", "is_local": false, "name": "Goodbye Nefario", "preview_url": null, "track_number": 12, "type": "track", "uri": "spotify:track:2FlWtPuBMGo0a0X7LGETyk" }, { "artists": [ { "external_urls": { "spotify": "https://open.spotify.com/artist/2RaHCHhZWBXn460JpMaicz" }, "href": "https://api.spotify.com/v1/artists/2RaHCHhZWBXn460JpMaicz", "id": "2RaHCHhZWBXn460JpMaicz", "name": "Heitor Pereira", "type": "artist", "uri": "spotify:artist:2RaHCHhZWBXn460JpMaicz" } ], "available_markets": [], "disc_number": 1, "duration_ms": 86998, "explicit": false, "external_urls": { "spotify": "https://open.spotify.com/track/3YnhGNADeUaoBTjB1uGUjh" }, "href": "https://api.spotify.com/v1/tracks/3YnhGNADeUaoBTjB1uGUjh", "id": "3YnhGNADeUaoBTjB1uGUjh", "is_local": false, "name": "Time for Bed", "preview_url": null, "track_number": 13, "type": "track", "uri": "spotify:track:3YnhGNADeUaoBTjB1uGUjh" }, { "artists": [ { "external_urls": { "spotify": "https://open.spotify.com/artist/2RaHCHhZWBXn460JpMaicz" }, "href": "https://api.spotify.com/v1/artists/2RaHCHhZWBXn460JpMaicz", "id": "2RaHCHhZWBXn460JpMaicz", "name": "Heitor Pereira", "type": "artist", "uri": "spotify:artist:2RaHCHhZWBXn460JpMaicz" } ], "available_markets": [], "disc_number": 1, "duration_ms": 180265, "explicit": false, "external_urls": { "spotify": "https://open.spotify.com/track/6npUKThV4XI20VLW5ryr5O" }, "href": "https://api.spotify.com/v1/tracks/6npUKThV4XI20VLW5ryr5O", "id": "6npUKThV4XI20VLW5ryr5O", "is_local": false, "name": "Break-In", "preview_url": null, "track_number": 14, "type": "track", "uri": "spotify:track:6npUKThV4XI20VLW5ryr5O" }, { "artists": [ { "external_urls": { "spotify": "https://open.spotify.com/artist/2RaHCHhZWBXn460JpMaicz" }, "href": "https://api.spotify.com/v1/artists/2RaHCHhZWBXn460JpMaicz", "id": "2RaHCHhZWBXn460JpMaicz", "name": "Heitor Pereira", "type": "artist", "uri": "spotify:artist:2RaHCHhZWBXn460JpMaicz" } ], "available_markets": [], "disc_number": 1, "duration_ms": 95011, "explicit": false, "external_urls": { "spotify": "https://open.spotify.com/track/1qyFlqVfbgyiM7tQ2Jy9vC" }, "href": "https://api.spotify.com/v1/tracks/1qyFlqVfbgyiM7tQ2Jy9vC", "id": "1qyFlqVfbgyiM7tQ2Jy9vC", "is_local": false, "name": "Stalking Floyd Eaglesan", "preview_url": null, "track_number": 15, "type": "track", "uri": "spotify:track:1qyFlqVfbgyiM7tQ2Jy9vC" }, { "artists": [ { "external_urls": { "spotify": "https://open.spotify.com/artist/2RaHCHhZWBXn460JpMaicz" }, "href": "https://api.spotify.com/v1/artists/2RaHCHhZWBXn460JpMaicz", "id": "2RaHCHhZWBXn460JpMaicz", "name": "Heitor Pereira", "type": "artist", "uri": "spotify:artist:2RaHCHhZWBXn460JpMaicz" } ], "available_markets": [], "disc_number": 1, "duration_ms": 189771, "explicit": false, "external_urls": { "spotify": "https://open.spotify.com/track/4DRQctGiqjJkbFa7iTK4pb" }, "href": "https://api.spotify.com/v1/tracks/4DRQctGiqjJkbFa7iTK4pb", "id": "4DRQctGiqjJkbFa7iTK4pb", "is_local": false, "name": "Moving to Australia", "preview_url": null, "track_number": 16, "type": "track", "uri": "spotify:track:4DRQctGiqjJkbFa7iTK4pb" }, { "artists": [ { "external_urls": { "spotify": "https://open.spotify.com/artist/2RaHCHhZWBXn460JpMaicz" }, "href": "https://api.spotify.com/v1/artists/2RaHCHhZWBXn460JpMaicz", "id": "2RaHCHhZWBXn460JpMaicz", "name": "Heitor Pereira", "type": "artist", "uri": "spotify:artist:2RaHCHhZWBXn460JpMaicz" } ], "available_markets": [], "disc_number": 1, "duration_ms": 85878, "explicit": false, "external_urls": { "spotify": "https://open.spotify.com/track/1TSjM9GY2oN6RO6aYGN25n" }, "href": "https://api.spotify.com/v1/tracks/1TSjM9GY2oN6RO6aYGN25n", "id": "1TSjM9GY2oN6RO6aYGN25n", "is_local": false, "name": "Going to Save the World", "preview_url": null, "track_number": 17, "type": "track", "uri": "spotify:track:1TSjM9GY2oN6RO6aYGN25n" }, { "artists": [ { "external_urls": { "spotify": "https://open.spotify.com/artist/2RaHCHhZWBXn460JpMaicz" }, "href": "https://api.spotify.com/v1/artists/2RaHCHhZWBXn460JpMaicz", "id": "2RaHCHhZWBXn460JpMaicz", "name": "Heitor Pereira", "type": "artist", "uri": "spotify:artist:2RaHCHhZWBXn460JpMaicz" } ], "available_markets": [], "disc_number": 1, "duration_ms": 87158, "explicit": false, "external_urls": { "spotify": "https://open.spotify.com/track/3AEMuoglM1myQ8ouIyh8LG" }, "href": "https://api.spotify.com/v1/tracks/3AEMuoglM1myQ8ouIyh8LG", "id": "3AEMuoglM1myQ8ouIyh8LG", "is_local": false, "name": "El Macho", "preview_url": null, "track_number": 18, "type": "track", "uri": "spotify:track:3AEMuoglM1myQ8ouIyh8LG" }, { "artists": [ { "external_urls": { "spotify": "https://open.spotify.com/artist/2RaHCHhZWBXn460JpMaicz" }, "href": "https://api.spotify.com/v1/artists/2RaHCHhZWBXn460JpMaicz", "id": "2RaHCHhZWBXn460JpMaicz", "name": "Heitor Pereira", "type": "artist", "uri": "spotify:artist:2RaHCHhZWBXn460JpMaicz" } ], "available_markets": [], "disc_number": 1, "duration_ms": 47438, "explicit": false, "external_urls": { "spotify": "https://open.spotify.com/track/2d7fEVYdZnjlya3MPEma21" }, "href": "https://api.spotify.com/v1/tracks/2d7fEVYdZnjlya3MPEma21", "id": "2d7fEVYdZnjlya3MPEma21", "is_local": false, "name": "Jillian", "preview_url": null, "track_number": 19, "type": "track", "uri": "spotify:track:2d7fEVYdZnjlya3MPEma21" }, { "artists": [ { "external_urls": { "spotify": "https://open.spotify.com/artist/2RaHCHhZWBXn460JpMaicz" }, "href": "https://api.spotify.com/v1/artists/2RaHCHhZWBXn460JpMaicz", "id": "2RaHCHhZWBXn460JpMaicz", "name": "Heitor Pereira", "type": "artist", "uri": "spotify:artist:2RaHCHhZWBXn460JpMaicz" } ], "available_markets": [], "disc_number": 1, "duration_ms": 89398, "explicit": false, "external_urls": { "spotify": "https://open.spotify.com/track/7h8WnOo4Fh6NvfTUnR7nOa" }, "href": "https://api.spotify.com/v1/tracks/7h8WnOo4Fh6NvfTUnR7nOa", "id": "7h8WnOo4Fh6NvfTUnR7nOa", "is_local": false, "name": "Take Her Home", "preview_url": null, "track_number": 20, "type": "track", "uri": "spotify:track:7h8WnOo4Fh6NvfTUnR7nOa" }, { "artists": [ { "external_urls": { "spotify": "https://open.spotify.com/artist/2RaHCHhZWBXn460JpMaicz" }, "href": "https://api.spotify.com/v1/artists/2RaHCHhZWBXn460JpMaicz", "id": "2RaHCHhZWBXn460JpMaicz", "name": "Heitor Pereira", "type": "artist", "uri": "spotify:artist:2RaHCHhZWBXn460JpMaicz" } ], "available_markets": [], "disc_number": 1, "duration_ms": 212691, "explicit": false, "external_urls": { "spotify": "https://open.spotify.com/track/25A9ZlegjJ0z2fI1PgTqy2" }, "href": "https://api.spotify.com/v1/tracks/25A9ZlegjJ0z2fI1PgTqy2", "id": "25A9ZlegjJ0z2fI1PgTqy2", "is_local": false, "name": "El Macho's Lair", "preview_url": null, "track_number": 21, "type": "track", "uri": "spotify:track:25A9ZlegjJ0z2fI1PgTqy2" }, { "artists": [ { "external_urls": { "spotify": "https://open.spotify.com/artist/2RaHCHhZWBXn460JpMaicz" }, "href": "https://api.spotify.com/v1/artists/2RaHCHhZWBXn460JpMaicz", "id": "2RaHCHhZWBXn460JpMaicz", "name": "Heitor Pereira", "type": "artist", "uri": "spotify:artist:2RaHCHhZWBXn460JpMaicz" } ], "available_markets": [], "disc_number": 1, "duration_ms": 117745, "explicit": false, "external_urls": { "spotify": "https://open.spotify.com/track/48GwOCuPhWKDktq3efmfRg" }, "href": "https://api.spotify.com/v1/tracks/48GwOCuPhWKDktq3efmfRg", "id": "48GwOCuPhWKDktq3efmfRg", "is_local": false, "name": "Home Invasion", "preview_url": null, "track_number": 22, "type": "track", "uri": "spotify:track:48GwOCuPhWKDktq3efmfRg" }, { "artists": [ { "external_urls": { "spotify": "https://open.spotify.com/artist/2RaHCHhZWBXn460JpMaicz" }, "href": "https://api.spotify.com/v1/artists/2RaHCHhZWBXn460JpMaicz", "id": "2RaHCHhZWBXn460JpMaicz", "name": "Heitor Pereira", "type": "artist", "uri": "spotify:artist:2RaHCHhZWBXn460JpMaicz" } ], "available_markets": [], "disc_number": 1, "duration_ms": 443251, "explicit": false, "external_urls": { "spotify": "https://open.spotify.com/track/6dZkl2egcKVm8rO9W7pPWa" }, "href": "https://api.spotify.com/v1/tracks/6dZkl2egcKVm8rO9W7pPWa", "id": "6dZkl2egcKVm8rO9W7pPWa", "is_local": false, "name": "The Big Battle", "preview_url": null, "track_number": 23, "type": "track", "uri": "spotify:track:6dZkl2egcKVm8rO9W7pPWa" }, { "artists": [ { "external_urls": { "spotify": "https://open.spotify.com/artist/3NVrWkcHOtmPbMSvgHmijZ" }, "href": "https://api.spotify.com/v1/artists/3NVrWkcHOtmPbMSvgHmijZ", "id": "3NVrWkcHOtmPbMSvgHmijZ", "name": "The Minions", "type": "artist", "uri": "spotify:artist:3NVrWkcHOtmPbMSvgHmijZ" } ], "available_markets": [], "disc_number": 1, "duration_ms": 13886, "explicit": false, "external_urls": { "spotify": "https://open.spotify.com/track/2L0OyiAepqAbKvUZfWovOJ" }, "href": "https://api.spotify.com/v1/tracks/2L0OyiAepqAbKvUZfWovOJ", "id": "2L0OyiAepqAbKvUZfWovOJ", "is_local": false, "name": "Ba Do Bleep", "preview_url": null, "track_number": 24, "type": "track", "uri": "spotify:track:2L0OyiAepqAbKvUZfWovOJ" } ], "limit": 50, "next": null, "offset": 0, "previous": null, "total": 24 }, "type": "album", "uri": "spotify:album:5l3zEmMrOhOzG8d8s83GOL" }beetbox-beets-01f1faf/test/rsrc/spotify/missing_request.json000066400000000000000000000004501472325477400245070ustar00rootroot00000000000000{ "tracks" : { "href" : "https://api.spotify.com/v1/search?query=duifhjslkef+album%3Alkajsdflakjsd+artist%3A&offset=0&limit=20&type=track", "items" : [ ], "limit" : 20, "next" : null, "offset" : 0, "previous" : null, "total" : 0 } }beetbox-beets-01f1faf/test/rsrc/spotify/track_info.json000066400000000000000000000045421472325477400234130ustar00rootroot00000000000000{ "album": { "album_type": "compilation", "artists": [ { "external_urls": { "spotify": "https://open.spotify.com/artist/0LyfQWJT6nXafLPZqxe9Of" }, "href": "https://api.spotify.com/v1/artists/0LyfQWJT6nXafLPZqxe9Of", "id": "0LyfQWJT6nXafLPZqxe9Of", "name": "Various Artists", "type": "artist", "uri": "spotify:artist:0LyfQWJT6nXafLPZqxe9Of" } ], "available_markets": [], "external_urls": { "spotify": "https://open.spotify.com/album/5l3zEmMrOhOzG8d8s83GOL" }, "href": "https://api.spotify.com/v1/albums/5l3zEmMrOhOzG8d8s83GOL", "id": "5l3zEmMrOhOzG8d8s83GOL", "images": [ { "height": 640, "url": "https://i.scdn.co/image/ab67616d0000b27399140a62d43aec760f6172a2", "width": 640 }, { "height": 300, "url": "https://i.scdn.co/image/ab67616d00001e0299140a62d43aec760f6172a2", "width": 300 }, { "height": 64, "url": "https://i.scdn.co/image/ab67616d0000485199140a62d43aec760f6172a2", "width": 64 } ], "name": "Despicable Me 2 (Original Motion Picture Soundtrack)", "release_date": "2013-06-18", "release_date_precision": "day", "total_tracks": 24, "type": "album", "uri": "spotify:album:5l3zEmMrOhOzG8d8s83GOL" }, "artists": [ { "external_urls": { "spotify": "https://open.spotify.com/artist/2RdwBSPQiwcmiDo9kixcl8" }, "href": "https://api.spotify.com/v1/artists/2RdwBSPQiwcmiDo9kixcl8", "id": "2RdwBSPQiwcmiDo9kixcl8", "name": "Pharrell Williams", "type": "artist", "uri": "spotify:artist:2RdwBSPQiwcmiDo9kixcl8" } ], "available_markets": [], "disc_number": 1, "duration_ms": 233305, "explicit": false, "external_ids": { "isrc": "USQ4E1300686" }, "external_urls": { "spotify": "https://open.spotify.com/track/6NPVjNh8Jhru9xOmyQigds" }, "href": "https://api.spotify.com/v1/tracks/6NPVjNh8Jhru9xOmyQigds", "id": "6NPVjNh8Jhru9xOmyQigds", "is_local": false, "name": "Happy", "popularity": 1, "preview_url": null, "track_number": 4, "type": "track", "uri": "spotify:track:6NPVjNh8Jhru9xOmyQigds" }beetbox-beets-01f1faf/test/rsrc/spotify/track_request.json000066400000000000000000000100471472325477400241450ustar00rootroot00000000000000{ "tracks":{ "href":"https://api.spotify.com/v1/search?query=Happy+album%3ADespicable+Me+2+artist%3APharrell+Williams&offset=0&limit=20&type=track", "items":[ { "album":{ "album_type":"compilation", "available_markets":[ "AD", "AR", "AT", "AU", "BE", "BG", "BO", "BR", "CA", "CH", "CL", "CO", "CR", "CY", "CZ", "DE", "DK", "DO", "EC", "EE", "ES", "FI", "FR", "GB", "GR", "GT", "HK", "HN", "HU", "IE", "IS", "IT", "LI", "LT", "LU", "LV", "MC", "MT", "MX", "MY", "NI", "NL", "NO", "NZ", "PA", "PE", "PH", "PL", "PT", "PY", "RO", "SE", "SG", "SI", "SK", "SV", "TR", "TW", "US", "UY" ], "external_urls":{ "spotify":"https://open.spotify.com/album/5l3zEmMrOhOzG8d8s83GOL" }, "href":"https://api.spotify.com/v1/albums/5l3zEmMrOhOzG8d8s83GOL", "id":"5l3zEmMrOhOzG8d8s83GOL", "images":[ { "height":640, "width":640, "url":"https://i.scdn.co/image/cb7905340c132365bbaee3f17498f062858382e8" }, { "height":300, "width":300, "url":"https://i.scdn.co/image/af369120f0b20099d6784ab31c88256113f10ffb" }, { "height":64, "width":64, "url":"https://i.scdn.co/image/9dad385ddf2e7db0bef20cec1fcbdb08689d9ae8" } ], "name":"Despicable Me 2 (Original Motion Picture Soundtrack)", "type":"album", "uri":"spotify:album:5l3zEmMrOhOzG8d8s83GOL" }, "artists":[ { "external_urls":{ "spotify":"https://open.spotify.com/artist/2RdwBSPQiwcmiDo9kixcl8" }, "href":"https://api.spotify.com/v1/artists/2RdwBSPQiwcmiDo9kixcl8", "id":"2RdwBSPQiwcmiDo9kixcl8", "name":"Pharrell Williams", "type":"artist", "uri":"spotify:artist:2RdwBSPQiwcmiDo9kixcl8" } ], "available_markets":[ "AD", "AR", "AT", "AU", "BE", "BG", "BO", "BR", "CA", "CH", "CL", "CO", "CR", "CY", "CZ", "DE", "DK", "DO", "EC", "EE", "ES", "FI", "FR", "GB", "GR", "GT", "HK", "HN", "HU", "IE", "IS", "IT", "LI", "LT", "LU", "LV", "MC", "MT", "MX", "MY", "NI", "NL", "NO", "NZ", "PA", "PE", "PH", "PL", "PT", "PY", "RO", "SE", "SG", "SI", "SK", "SV", "TR", "TW", "US", "UY" ], "disc_number":1, "duration_ms":233305, "explicit":false, "external_ids":{ "isrc":"USQ4E1300686" }, "external_urls":{ "spotify":"https://open.spotify.com/track/6NPVjNh8Jhru9xOmyQigds" }, "href":"https://api.spotify.com/v1/tracks/6NPVjNh8Jhru9xOmyQigds", "id":"6NPVjNh8Jhru9xOmyQigds", "name":"Happy", "popularity":89, "preview_url":"https://p.scdn.co/mp3-preview/6b00000be293e6b25f61c33e206a0c522b5cbc87", "track_number":4, "type":"track", "uri":"spotify:track:6NPVjNh8Jhru9xOmyQigds" } ], "limit":20, "next":null, "offset":0, "previous":null, "total":1 } } beetbox-beets-01f1faf/test/rsrc/t_time.m4a000066400000000000000000000133461472325477400205720ustar00rootroot00000000000000 ftypM4A M4A mp42isom ŠmoovlmvhdÄLé_ÄLď¬D¸@trak\tkhdÄLé_ÄLď¸@mdia mdhdÄLé_ÄLď¬D¸UÄ"hdlrsounÓminfsmhd$dinfdref url —stblgstsdWmp4a¬D3esds€€€"€€€@xú€€€€€€stts.(stscĚstsz.22""'-+1/+&0''&%)(-,*).)(*%)-4&6.*,$,3/'& stcoĆé •udta Ťmeta"hdlrmdirappl°ilst©namdatafull"©ARTdatathe artist$©wrtdatathe composer!©albdatathe album!©gendatathe genre trkndatadiskdata,©day$data1987-03-31T07:00:00Zcpildatapgapdatatmpodata6©too.dataiTunes v7.6.2, QuickTime 7.4.5˘----meancom.apple.iTunesnameiTunNORMjdata 00000000 00000000 00000000 00000000 00000228 00000000 00000008 00000000 00000187 00000000Ľ----meancom.apple.iTunesnameiTunSMPB„data 00000000 00000840 0000037C 000000000000AC44 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000"©lyrdatathe lyrics(aART datathe album artist$©cmtdatathe comments$©grpdatathe grouping×freefree(mdatĐě\«11;Ä<Ŕř¤4„ `Ifŕe—^ŕďŹP0#Ü·›ťÁbÄ»VOŘŮ(*ľJ€q8ň„q©-@>Ťč[-3ţ!‹rtgĽĽ(Îą¶q]bĄ¸úÄPEŮ€€ ްDăŮ‘>JUl3ËA$Ö)i)ŔúÄA#€+:3ď`Xa¶™ÉůQ;«Ŕ±čŕö˘" „Î Ž× óf{q wZ“Ä 3zżf#)NŮq'ř˘" „kËr˝ŮÖŁ gʧý'”ę )y*¦Úö»,°Îµxˇú˘" „r †BXVFěě7nhϦNž|z%ôe0Ue®«Ś śö‚B„_`9ĺo˘đJ­Dz0Ż)ňřáŰ1F8‘ú·ĺÉ>7t·#‹Uŕř¤4#ŚXŰőŢ™ľáŕŇ`¸Ž‚Ţ×xT†aG~fäHv<ý¶ÁŔúÄ`? &×63Đ”l:äGŕë’ 莵š„ÇÓëĺN‚ŕú˘R#Aש9Ď<łÔiÇ%ÖřꦗµŠÍÎę®yfžÎô‚T„ `®!I}uhnV$?‹+äˇ(Z«„ÁˇÜtaĄVi±+±l‰8úÄ3#PŤ0Ľń•T`’3ąčžÓ#?}ĄŐőÚ»ěÔ‹€ú¤Q#Hl`µÁ˝ĄËK§)Q)E‚ńő>O˛ÂôŻSÔ¦úĆ"#P ŰdpËźżŰ2é­~sŰČÓďŤ'Pîě=Í&ü!úÄPG<Ž0NÝÍEř™_ő'1…:ő‹ĺ\ř˘ ,l qĽÔş<¬>čđÍ&ďĂž¦J3}•]dQ€€8ú˘`F  8I+–¶:aá–;0ĄÇ>m>;ßMľíŰŹź× Ą/gř¤p>€ŢbSci›”¤L z:~HŤ5M3©'°A+č&„f ‡Ö(pö‚0!dŔ˘’Ż:%˘C°9Błî+]ţ3Z†ąčcJr†Â¶Źö¤`E€a,]µ)\BiwČ,yç@ĂşD¸ůťü'ħĄ¨Üř˘ ĐŔ€0qóŕ=bžmBĹAwůH°×! šDSŞ 8ö‚5 !`Â4†§ĺ^ńíŞ^:$9µĎ…gs`M%…ĘZZtŞ^UŁY(o€ú˘"¤09ś’#NŃ·#̬zÚW›;t A-1dyß”3¤^ ŕú˘" "ěFâwěĽú„,µ¸rŠţ¬js%”źŇísŮfžep=Ľř¤`G €@ŔăŇ5?ŕĽ`•ŘÉÍM˛ČöĆýYB^×;C€ř˘`BČ ĆăiRř—‡iéŽ60 9üľĺÓvë…(Ü˙˙GYJ´…=˘oçlÇWř¤3 BĐ3ÜQhLÖĆ$/ę‡b<÷~łµ8ëűŰΚSŤ­n*jőXeĹ×7’#ŕô‚"(„@Ř Ad¨*Â`óŮ'á¬Á®co·>Vűça &µ% ř¤`F‚ ˘±äßxă}¸<ëĘüfP[2ÚqXä.Š'Iµpú˘" „¤*ÓŘšÖ'ęŠÇÉR™z[oÝÓ¬ía‚„ľ”XÇS2p'ŔřĆ2! éZđŢx˛µľůěú$©żVn´%j(óVŔö¤`F‚€öŽťF†@›c}}đCÂ2>Ű<ĆçO5Ł!ąQŢ/grDŹđú¤aBŕô‡2‰´S(^®ZŹdđ{¦P¤ź~‰Ůۦ‘6 Sşśö˘ „,`Ľ-1wNŢAŔÍ”XPKhˇwKŰ#0R¬Y±Xť-Ŕř˘`E "ĹImq¤>w˘Č1s5{!ÁXş[Ż/ćŰ?ÓGŹĄxř˘2"%€Xä  aÔž®ŽćĎű©ĆĎçż÷şrŻLaŔě\ŞRi"?pě\¬ „ô@Ŕ\beetbox-beets-01f1faf/test/rsrc/test_completion.sh000066400000000000000000000056621472325477400224540ustar00rootroot00000000000000# Function stub compopt() { return 0; } initcli() { COMP_WORDS=( "beet" "$@" ) let COMP_CWORD=${#COMP_WORDS[@]}-1 COMP_LINE="${COMP_WORDS[@]}" let COMP_POINT=${#COMP_LINE} _beet } completes() { for word in "$@"; do [[ " ${COMPREPLY[@]} " == *[[:space:]]$word[[:space:]]* ]] || return 1 done } COMMANDS='fields import list update remove stats version modify move write help' HELP_OPTS='-h --help' test_commands() { initcli '' && completes $COMMANDS && initcli -v '' && completes $COMMANDS && initcli -l help '' && completes $COMMANDS && initcli -d list '' && completes $COMMANDS && initcli -h '' && completes $COMMANDS && true } test_command_aliases() { initcli ls && completes list && initcli l && ! completes ls && initcli im && completes import && true } test_global_opts() { initcli - && completes \ -l --library \ -d --directory \ -h --help \ -c --config \ -v --verbose && true } test_global_file_opts() { # FIXME somehow file completion only works when the completion # function is called by the shell completion utilities. So we can't # test it here initcli --library '' && completes $(compgen -d) && initcli -l '' && completes $(compgen -d) && initcli --config '' && completes $(compgen -d) && initcli -c '' && completes $(compgen -d) && true } test_global_dir_opts() { initcli --directory '' && completes $(compgen -d) && initcli -d '' && completes $(compgen -d) && true } test_fields_command() { initcli fields - && completes -h --help && initcli fields '' && completes $(compgen -d) && true } test_import_files() { initcli import '' && completes $(compgen -d) && initcli import --copy -P '' && completes $(compgen -d) && initcli import --log '' && completes $(compgen -d) && true } test_import_options() { initcli imp - completes \ -h --help \ -c --copy -C --nocopy \ -w --write -W --nowrite \ -a --autotag -A --noautotag \ -p --resume -P --noresume \ -l --log --flat } test_list_options() { initcli list - completes \ -h --help \ -a --album \ -p --path } test_list_query() { initcli list 'x' && [[ -z "${COMPREPLY[@]}" ]] && initcli list 'art' && completes \ 'artist:' \ 'artpath:' && initcli list 'artits:x' && [[ -z "${COMPREPLY[@]}" ]] && true } test_help_command() { initcli help '' && completes $COMMANDS && true } test_plugin_command() { initcli te && completes test && initcli test - && completes -o --option && true } run_tests() { local tests=$(set | \ grep --extended-regexp --only-matching '^test_[a-zA-Z_]* \(\) $' |\ grep --extended-regexp --only-matching '[a-zA-Z_]*' ) local fail=0 if [[ -n $@ ]]; then tests="$@" fi for t in $tests; do $t || { fail=1 && echo "$t failed" >&2; } done return $fail } run_tests "$@" && echo "completion tests passed" beetbox-beets-01f1faf/test/rsrc/unicode’d.mp3000066400000000000000000002230411472325477400221070ustar00rootroot00000000000000ID3xATIT2fullTPE1 the artistTRCK2/3TALB the albumTPOS4/5TDRC2001TCON the genreTBPM6TCMP1TDOR0000TIPL arrangerTPUB the labelTCOMthe composerTIT1the groupingTENCiTunes v7.6.2COMMengiTunPGAP0USLTengthe lyricsCOMMengthe commentsTPE2the album artistTXXXR128_ALBUM_GAIN0TXXXR128_TRACK_GAIN0TXXXREPLAYGAIN_TRACK_GAIN0.00 dBTXXX REPLAYGAIN_TRACK_PEAK0.000244TXXX;MusicBrainz Album Id9e873859-8aa4-4790-b985-5a953e8ef628UFID;http://musicbrainz.org8b882575-08a5-4452-a7a7-cbb8a1531f9eTXXX<MusicBrainz Artist Id7cf0ea9d-86b9-4dad-ba9e-2355a64899eaCOMMhengiTunNORM 000003E8 000003E8 000009C4 000009C4 00000000 00000000 00000007 00000007 00000000 00000000COMMengiTunSMPB 00000000 00000210 00000A2C 000000000000AC44 00000000 000021AC 00000000 00000000 00000000 00000000 00000000 00000000TXXXh<MusicBrainz Album Type['[', "'", '[', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", '[', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", ',', "'", ',', ' ', "'", ' ', "'", ',', ' ', "'", '"', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", '"', "'", ',', ' ', "'", ',', "'", ',', ' ', "'", ' ', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", '[', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", ',', "'", ',', ' ', "'", ' ', "'", ',', ' ', "'", '"', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", '"', "'", ',', ' ', "'", ',', "'", ',', ' ', "'", ' ', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", ',', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", ',', "'", ',', ' ', "'", ' ', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", ' ', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", ',', "'", ',', ' ', "'", ' ', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", '"', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", ',', "'", ',', ' ', "'", ' ', "'", ',', ' ', "'", '"', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", '"', "'", ',', ' ', "'", ',', "'", ',', ' ', "'", ' ', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", '"', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", ',', "'", ',', ' ', "'", ' ', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", ',', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", ',', "'", ',', ' ', "'", ' ', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", ' ', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", ',', "'", ',', ' ', "'", ' ', "'", ',', ' ', "'", '"', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", '"', "'", ',', ' ', "'", ',', "'", ',', ' ', "'", ' ', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", '[', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", ',', "'", ',', ' ', "'", ' ', "'", ',', ' ', "'", '"', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", '"', "'", ',', ' ', "'", ',', "'", ',', ' ', "'", ' ', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", ',', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", ',', "'", ',', ' ', "'", ' ', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", ' ', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", ',', "'", ',', ' ', "'", ' ', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", '"', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", ',', "'", ',', ' ', "'", ' ', "'", ',', ' ', "'", '"', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", '"', "'", ',', ' ', "'", ',', "'", ',', ' ', "'", ' ', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", '"', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", ',', "'", ',', ' ', "'", ' ', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", ',', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", ',', "'", ',', ' ', "'", ' ', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", ' ', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", ',', "'", ',', ' ', "'", ' ', "'", ',', ' ', "'", '"', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", '"', "'", ',', ' ', "'", ',', "'", ',', ' ', "'", ' ', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", ',', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", ',', "'", ',', ' ', "'", ' ', "'", ',', ' ', "'", '"', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", '"', "'", ',', ' ', "'", ',', "'", ',', ' ', "'", ' ', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", ',', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", ',', "'", ',', ' ', "'", ' ', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", ' ', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", ',', "'", ',', ' ', "'", ' ', "'", ',', ' ', "'", '"', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", '"', "'", ',', ' ', "'", ',', "'", ',', ' ', "'", ' ', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", ' ', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", ',', "'", ',', ' ', "'", ' ', "'", ',', ' ', "'", '"', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", '"', "'", ',', ' ', "'", ',', "'", ',', ' ', "'", ' ', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", ',', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", ',', "'", ',', ' ', "'", ' ', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", ' ', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", ',', "'", ',', ' ', "'", ' ', "'", ',', ' ', "'", '"', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", '"', "'", ',', ' ', "'", ',', "'", ',', ' ', "'", ' ', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", '"', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", ',', "'", ',', ' ', "'", ' ', "'", ',', ' ', "'", '"', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", '"', "'", ',', ' ', "'", ',', "'", ',', ' ', "'", ' ', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", ',', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", ',', "'", ',', ' ', "'", ' ', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", ' ', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", ',', "'", ',', ' ', "'", ' ', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", '"', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", ',', "'", ',', ' ', "'", ' ', "'", ',', ' ', "'", '"', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", '"', "'", ',', ' ', "'", ',', "'", ',', ' ', "'", ' ', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", '"', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", ',', "'", ',', ' ', "'", ' ', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", ',', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", ',', "'", ',', ' ', "'", ' ', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", ' ', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", ',', "'", ',', ' ', "'", ' ', "'", ',', ' ', "'", '"', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", '"', "'", ',', ' ', "'", ',', "'", ',', ' ', "'", ' ', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", '"', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", ',', "'", ',', ' ', "'", ' ', "'", ',', ' ', "'", '"', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", '"', "'", ',', ' ', "'", ',', "'", ',', ' ', "'", ' ', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", ',', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", ',', "'", ',', ' ', "'", ' ', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", ' ', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", ',', "'", ',', ' ', "'", ' ', "'", ',', ' ', "'", '"', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", '"', "'", ',', ' ', "'", ',', "'", ',', ' ', "'", ' ', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", ',', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", ',', "'", ',', ' ', "'", ' ', "'", ',', ' ', "'", '"', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", '"', "'", ',', ' ', "'", ',', "'", ',', ' ', "'", ' ', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", ',', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", ',', "'", ',', ' ', "'", ' ', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", ' ', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", ',', "'", ',', ' ', "'", ' ', "'", ',', ' ', "'", '"', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", '"', "'", ',', ' ', "'", ',', "'", ',', ' ', "'", ' ', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", ' ', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", ',', "'", ',', ' ', "'", ' ', "'", ',', ' ', "'", '"', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", '"', "'", ',', ' ', "'", ',', "'", ',', ' ', "'", ' ', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", ',', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", ',', "'", ',', ' ', "'", ' ', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", ' ', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", ',', "'", ',', ' ', "'", ' ', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", '"', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", ',', "'", ',', ' ', "'", ' ', "'", ',', ' ', "'", '"', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", '"', "'", ',', ' ', "'", ',', "'", ',', ' ', "'", ' ', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", '"', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", ',', "'", ',', ' ', "'", ' ', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", ',', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", ',', "'", ',', ' ', "'", ' ', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", ' ', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", ',', "'", ',', ' ', "'", ' ', "'", ',', ' ', "'", '"', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", '"', "'", ',', ' ', "'", ',', "'", ',', ' ', "'", ' ', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", '[', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", ',', "'", ',', ' ', "'", ' ', "'", ',', ' ', "'", '"', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", '"', "'", ',', ' ', "'", ',', "'", ',', ' ', "'", ' ', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", ',', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", ',', "'", ',', ' ', "'", ' ', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", ' ', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", ',', "'", ',', ' ', "'", ' ', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", '"', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", ',', "'", ',', ' ', "'", ' ', "'", ',', ' ', "'", '"', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", '"', "'", ',', ' ', "'", ',', "'", ',', ' ', "'", ' ', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", '"', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", ',', "'", ',', ' ', "'", ' ', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", ',', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", ',', "'", ',', ' ', "'", ' ', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", ' ', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", ',', "'", ',', ' ', "'", ' ', "'", ',', ' ', "'", '"', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", '"', "'", ',', ' ', "'", ',', "'", ',', ' ', "'", ' ', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", ',', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", ',', "'", ',', ' ', "'", ' ', "'", ',', ' ', "'", '"', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", '"', "'", ',', ' ', "'", ',', "'", ',', ' ', "'", ' ', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", ',', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", ',', "'", ',', ' ', "'", ' ', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", ' ', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", ',', "'", ',', ' ', "'", ' ', "'", ',', ' ', "'", '"', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", '"', "'", ',', ' ', "'", ',', "'", ',', ' ', "'", ' ', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", ' ', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", ',', "'", ',', ' ', "'", ' ', "'", ',', ' ', "'", '"', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", '"', "'", ',', ' ', "'", ',', "'", ',', ' ', "'", ' ', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", ',', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", ',', "'", ',', ' ', "'", ' ', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", ' ', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", ',', "'", ',', ' ', "'", ' ', "'", ',', ' ', "'", '"', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", '"', "'", ',', ' ', "'", ',', "'", ',', ' ', "'", ' ', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", '"', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", ',', "'", ',', ' ', "'", ' ', "'", ',', ' ', "'", '"', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", '"', "'", ',', ' ', "'", ',', "'", ',', ' ', "'", ' ', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", ',', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", ',', "'", ',', ' ', "'", ' ', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", ' ', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", ',', "'", ',', ' ', "'", ' ', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", '"', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", ',', "'", ',', ' ', "'", ' ', "'", ',', ' ', "'", '"', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", '"', "'", ',', ' ', "'", ',', "'", ',', ' ', "'", ' ', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", '"', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", ',', "'", ',', ' ', "'", ' ', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", ',', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", ',', "'", ',', ' ', "'", ' ', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", ' ', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", ',', "'", ',', ' ', "'", ' ', "'", ',', ' ', "'", '"', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", '"', "'", ',', ' ', "'", ',', "'", ',', ' ', "'", ' ', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", '"', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", ',', "'", ',', ' ', "'", ' ', "'", ',', ' ', "'", '"', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", '"', "'", ',', ' ', "'", ',', "'", ',', ' ', "'", ' ', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", ',', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", ',', "'", ',', ' ', "'", ' ', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", ' ', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", ',', "'", ',', ' ', "'", ' ', "'", ',', ' ', "'", '"', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", '"', "'", ',', ' ', "'", ',', "'", ',', ' ', "'", ' ', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", ',', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", ',', "'", ',', ' ', "'", ' ', "'", ',', ' ', "'", '"', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", '"', "'", ',', ' ', "'", ',', "'", ',', ' ', "'", ' ', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", ',', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", ',', "'", ',', ' ', "'", ' ', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", ' ', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", ',', "'", ',', ' ', "'", ' ', "'", ',', ' ', "'", '"', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", '"', "'", ',', ' ', "'", ',', "'", ',', ' ', "'", ' ', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", ' ', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", ',', "'", ',', ' ', "'", ' ', "'", ',', ' ', "'", '"', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", '"', "'", ',', ' ', "'", ',', "'", ',', ' ', "'", ' ', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", ',', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", ',', "'", ',', ' ', "'", ' ', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", ' ', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", ',', "'", ',', ' ', "'", ' ', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", '"', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", ',', "'", ',', ' ', "'", ' ', "'", ',', ' ', "'", '"', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", '"', "'", ',', ' ', "'", ',', "'", ',', ' ', "'", ' ', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", '"', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", ',', "'", ',', ' ', "'", ' ', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", ',', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", ',', "'", ',', ' ', "'", ' ', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", ' ', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", ',', "'", ',', ' ', "'", ' ', "'", ',', ' ', "'", '"', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", '"', "'", ',', ' ', "'", ',', "'", ',', ' ', "'", ' ', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", ',', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", ',', "'", ',', ' ', "'", ' ', "'", ',', ' ', "'", '"', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", '"', "'", ',', ' ', "'", ',', "'", ',', ' ', "'", ' ', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", ',', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", ',', "'", ',', ' ', "'", ' ', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", ' ', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", ',', "'", ',', ' ', "'", ' ', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", '"', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", ',', "'", ',', ' ', "'", ' ', "'", ',', ' ', "'", '"', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", '"', "'", ',', ' ', "'", ',', "'", ',', ' ', "'", ' ', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", '"', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", ',', "'", ',', ' ', "'", ' ', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", ',', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", ',', "'", ',', ' ', "'", ' ', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", ' ', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", ',', "'", ',', ' ', "'", ' ', "'", ',', ' ', "'", '"', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", '"', "'", ',', ' ', "'", ',', "'", ',', ' ', "'", ' ', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", ',', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", ',', "'", ',', ' ', "'", ' ', "'", ',', ' ', "'", '"', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", '"', "'", ',', ' ', "'", ',', "'", ',', ' ', "'", ' ', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", ',', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", ',', "'", ',', ' ', "'", ' ', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", ' ', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", ',', "'", ',', ' ', "'", ' ', "'", ',', ' ', "'", '"', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", '"', "'", ',', ' ', "'", ',', "'", ',', ' ', "'", ' ', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", ' ', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", ',', "'", ',', ' ', "'", ' ', "'", ',', ' ', "'", '"', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", '"', "'", ',', ' ', "'", ',', "'", ',', ' ', "'", ' ', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", ',', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", ',', "'", ',', ' ', "'", ' ', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", ' ', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", ',', "'", ',', ' ', "'", ' ', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", '"', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", ',', "'", ',', ' ', "'", ' ', "'", ',', ' ', "'", '"', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", '"', "'", ',', ' ', "'", ',', "'", ',', ' ', "'", ' ', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", '"', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", ',', "'", ',', ' ', "'", ' ', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", ',', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", ',', "'", ',', ' ', "'", ' ', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", ' ', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", ',', "'", ',', ' ', "'", ' ', "'", ',', ' ', "'", '"', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", '"', "'", ',', ' ', "'", ',', "'", ',', ' ', "'", ' ', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", ' ', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", ',', "'", ',', ' ', "'", ' ', "'", ',', ' ', "'", '"', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", '"', "'", ',', ' ', "'", ',', "'", ',', ' ', "'", ' ', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", ',', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", ',', "'", ',', ' ', "'", ' ', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", ' ', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", ',', "'", ',', ' ', "'", ' ', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", '"', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", ',', "'", ',', ' ', "'", ' ', "'", ',', ' ', "'", '"', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", '"', "'", ',', ' ', "'", ',', "'", ',', ' ', "'", ' ', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", '"', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", ',', "'", ',', ' ', "'", ' ', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", ',', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", ',', "'", ',', ' ', "'", ' ', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", ' ', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", ',', "'", ',', ' ', "'", ' ', "'", ',', ' ', "'", '"', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", '"', "'", ',', ' ', "'", ',', "'", ',', ' ', "'", ' ', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", ',', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", ',', "'", ',', ' ', "'", ' ', "'", ',', ' ', "'", '"', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", '"', "'", ',', ' ', "'", ',', "'", ',', ' ', "'", ' ', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", ',', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", ',', "'", ',', ' ', "'", ' ', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", ' ', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", ',', "'", ',', ' ', "'", ' ', "'", ',', ' ', "'", '"', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", '"', "'", ',', ' ', "'", ',', "'", ',', ' ', "'", ' ', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", ' ', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", ',', "'", ',', ' ', "'", ' ', "'", ',', ' ', "'", '"', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", '"', "'", ',', ' ', "'", ',', "'", ',', ' ', "'", ' ', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", ',', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", ',', "'", ',', ' ', "'", ' ', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", ' ', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", ',', "'", ',', ' ', "'", ' ', "'", ',', ' ', "'", '"', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", '"', "'", ',', ' ', "'", ',', "'", ',', ' ', "'", ' ', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", '"', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", ',', "'", ',', ' ', "'", ' ', "'", ',', ' ', "'", '"', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", '"', "'", ',', ' ', "'", ',', "'", ',', ' ', "'", ' ', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", ',', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", ',', "'", ',', ' ', "'", ' ', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", ' ', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", ',', "'", ',', ' ', "'", ' ', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", '"', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", ',', "'", ',', ' ', "'", ' ', "'", ',', ' ', "'", '"', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", '"', "'", ',', ' ', "'", ',', "'", ',', ' ', "'", ' ', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", '"', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", ',', "'", ',', ' ', "'", ' ', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", ',', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", ',', "'", ',', ' ', "'", ' ', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", ' ', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", ',', "'", ',', ' ', "'", ' ', "'", ',', ' ', "'", '"', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", '"', "'", ',', ' ', "'", ',', "'", ',', ' ', "'", ' ', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", '"', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", ',', "'", ',', ' ', "'", ' ', "'", ',', ' ', "'", '"', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", '"', "'", ',', ' ', "'", ',', "'", ',', ' ', "'", ' ', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", ',', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", ',', "'", ',', ' ', "'", ' ', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", ' ', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", ',', "'", ',', ' ', "'", ' ', "'", ',', ' ', "'", '"', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", '"', "'", ',', ' ', "'", ',', "'", ',', ' ', "'", ' ', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", ',', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", ',', "'", ',', ' ', "'", ' ', "'", ',', ' ', "'", '"', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", '"', "'", ',', ' ', "'", ',', "'", ',', ' ', "'", ' ', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", ',', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", ',', "'", ',', ' ', "'", ' ', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", ' ', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", ',', "'", ',', ' ', "'", ' ', "'", ',', ' ', "'", '"', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", '"', "'", ',', ' ', "'", ',', "'", ',', ' ', "'", ' ', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", ' ', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", ',', "'", ',', ' ', "'", ' ', "'", ',', ' ', "'", '"', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", '"', "'", ',', ' ', "'", ',', "'", ',', ' ', "'", ' ', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", ',', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", ',', "'", ',', ' ', "'", ' ', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", ' ', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", ',', "'", ',', ' ', "'", ' ', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", '"', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", ',', "'", ',', ' ', "'", ' ', "'", ',', ' ', "'", '"', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", '"', "'", ',', ' ', "'", ',', "'", ',', ' ', "'", ' ', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", '"', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", ',', "'", ',', ' ', "'", ' ', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", ',', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", ',', "'", ',', ' ', "'", ' ', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", ' ', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", ',', "'", ',', ' ', "'", ' ', "'", ',', ' ', "'", '"', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", '"', "'", ',', ' ', "'", ',', "'", ',', ' ', "'", ' ', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", ']', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", ',', "'", ',', ' ', "'", ' ', "'", ',', ' ', "'", '"', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", '"', "'", ',', ' ', "'", ',', "'", ',', ' ', "'", ' ', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", ',', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", ',', "'", ',', ' ', "'", ' ', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", ' ', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", ',', "'", ',', ' ', "'", ' ', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", '"', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", ',', "'", ',', ' ', "'", ' ', "'", ',', ' ', "'", '"', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", '"', "'", ',', ' ', "'", ',', "'", ',', ' ', "'", ' ', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", '"', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", ',', "'", ',', ' ', "'", ' ', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", ',', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", ',', "'", ',', ' ', "'", ' ', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", ' ', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", ',', "'", ',', ' ', "'", ' ', "'", ',', ' ', "'", '"', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", '"', "'", ',', ' ', "'", ',', "'", ',', ' ', "'", ' ', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", ',', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", ',', "'", ',', ' ', "'", ' ', "'", ',', ' ', "'", '"', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", '"', "'", ',', ' ', "'", ',', "'", ',', ' ', "'", ' ', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", ',', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", ',', "'", ',', ' ', "'", ' ', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", ' ', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", ',', "'", ',', ' ', "'", ' ', "'", ',', ' ', "'", '"', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", '"', "'", ',', ' ', "'", ',', "'", ',', ' ', "'", ' ', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", ' ', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", ',', "'", ',', ' ', "'", ' ', "'", ',', ' ', "'", '"', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", '"', "'", ',', ' ', "'", ',', "'", ',', ' ', "'", ' ', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", ',', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", ',', "'", ',', ' ', "'", ' ', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", ' ', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", ',', "'", ',', ' ', "'", ' ', "'", ',', ' ', "'", '"', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", '"', "'", ',', ' ', "'", ',', "'", ',', ' ', "'", ' ', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", '"', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", ',', "'", ',', ' ', "'", ' ', "'", ',', ' ', "'", '"', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", '"', "'", ',', ' ', "'", ',', "'", ',', ' ', "'", ' ', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", ',', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", ',', "'", ',', ' ', "'", ' ', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", ' ', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", ',', "'", ',', ' ', "'", ' ', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", '"', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", ',', "'", ',', ' ', "'", ' ', "'", ',', ' ', "'", '"', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", '"', "'", ',', ' ', "'", ',', "'", ',', ' ', "'", ' ', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", '"', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", ',', "'", ',', ' ', "'", ' ', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", ',', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", ',', "'", ',', ' ', "'", ' ', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", ' ', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", ',', "'", ',', ' ', "'", ' ', "'", ',', ' ', "'", '"', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", '"', "'", ',', ' ', "'", ',', "'", ',', ' ', "'", ' ', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", '"', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", ',', "'", ',', ' ', "'", ' ', "'", ',', ' ', "'", '"', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", '"', "'", ',', ' ', "'", ',', "'", ',', ' ', "'", ' ', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", ',', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", ',', "'", ',', ' ', "'", ' ', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", ' ', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", ',', "'", ',', ' ', "'", ' ', "'", ',', ' ', "'", '"', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", '"', "'", ',', ' ', "'", ',', "'", ',', ' ', "'", ' ', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", ',', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", ',', "'", ',', ' ', "'", ' ', "'", ',', ' ', "'", '"', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", '"', "'", ',', ' ', "'", ',', "'", ',', ' ', "'", ' ', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", ',', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", ',', "'", ',', ' ', "'", ' ', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", ' ', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", ',', "'", ',', ' ', "'", ' ', "'", ',', ' ', "'", '"', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", '"', "'", ',', ' ', "'", ',', "'", ',', ' ', "'", ' ', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", ' ', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", ',', "'", ',', ' ', "'", ' ', "'", ',', ' ', "'", '"', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", '"', "'", ',', ' ', "'", ',', "'", ',', ' ', "'", ' ', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", ',', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", ',', "'", ',', ' ', "'", ' ', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", ' ', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", ',', "'", ',', ' ', "'", ' ', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", '"', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", ',', "'", ',', ' ', "'", ' ', "'", ',', ' ', "'", '"', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", '"', "'", ',', ' ', "'", ',', "'", ',', ' ', "'", ' ', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", '"', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", ',', "'", ',', ' ', "'", ' ', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", ',', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", ',', "'", ',', ' ', "'", ' ', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", ' ', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", ',', "'", ',', ' ', "'", ' ', "'", ',', ' ', "'", '"', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", '"', "'", ',', ' ', "'", ',', "'", ',', ' ', "'", ' ', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", ']', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", ',', "'", ',', ' ', "'", ' ', "'", ',', ' ', "'", '"', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", '"', "'", ',', ' ', "'", ',', "'", ',', ' ', "'", ' ', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", ',', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", ',', "'", ',', ' ', "'", ' ', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", ' ', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", ',', "'", ',', ' ', "'", ' ', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", '"', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", ',', "'", ',', ' ', "'", ' ', "'", ',', ' ', "'", '"', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", '"', "'", ',', ' ', "'", ',', "'", ',', ' ', "'", ' ', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", '"', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", ',', "'", ',', ' ', "'", ' ', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", ',', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", ',', "'", ',', ' ', "'", ' ', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", ' ', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", ',', "'", ',', ' ', "'", ' ', "'", ',', ' ', "'", '"', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", '"', "'", ',', ' ', "'", ',', "'", ',', ' ', "'", ' ', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", ']', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", ',', "'", ',', ' ', "'", ' ', "'", ',', ' ', "'", '"', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", '"', "'", ',', ' ', "'", ',', "'", ',', ' ', "'", ' ', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", ']', "'", ',', ' ', '"', "'", '"', ',', ' ', "'", ']', "'", ']']˙ű`Ŕ F=íÉ‘A#ô‰ą˙˙˙˙F0Ăß˙C ˙_ůď˙˙˙˙óĎŁ)çÚa”2ůŠz<‚žyő=xů=O `ĐĘÇŤ  ĄŕĆyé>`řÜá ĐŔXč" z)ăBś"Ŕ$)‚ A„ ˙˙˙˙˙ř˙×?˙ű˙˙˙˙ţľÝvbŁ×Bť¬¦ ł4Ŕă°˛+¸µÂ¤ÍrąÁÔR°Ä%;ťîRŁQ[˙˙˙ďÖÜî˙łľÚ7UW6dTUT!ub8´Çř çčx?ń.Ě€Ź˙ÚÚ˘ł<ŚaeźDY/˙˙˙˝˙PśĽ·$D¬(躡ďPŚi§˙ű`Ŕ€Š9ŕ… éâH#4•¸‹RŇAbÉuĎ&Ž ü:=ÄÝL´2!Ô¶#9îŽÔ#"5ż˙˙˙ú­˙fE$ěȉ,Ş1HËez˛1’Ş!ĨR ‰Ě§)˘((â˘,Ęc°š)ÄÜX˘¬,qE„T9N@řt˙˙Ňţ÷˙?˙ű`Ŕ ň= mÁ¸Č#tŤ¸ż˙˙óËäÜŻđÉ÷INˇćiŢ˙{ŢžYg§S—ÜëdE/'˙˙˙˙˙˙˙çţ§Ě¸g Wëď&%ÍirÄDuń6.ÝítÜ‘mtP&ę,Ž«˘’Duhâb¨D›ŔN±˙˙˙˙˙ý`Z˙ý{˙˙˙˙˙çĚímŢóĄ/ĂžjľGH„<&ńÎCĄ˙ü„žl÷˙˙˙˙˙˙˙ýţ˙çJÎÉPŤ1•ĄI’ňÔä»ň9Ł3łZHFĘ;ŽÔr/viĂ1  Ü‰…Ł2‡ ěÁ˙˙˙˙˙Öő˙ţy˙?˙˙ůz\¨Ű+dDÎŞŹ1ĆckTIw˙űbŔ ć% „MɬH#t‰¸™¨…¶R˛¶Í´ö˙˙˙˙dÝꓳĐ÷1ęr© ĘĎłîŇ»łlÖ• lΆmC‘Uę÷c,ě$(tČ4BK˙˙˙˙˙ëĺý˛ëZS˙˙˙˙üąQRĹ­ÝŚ‡>łĚl®®č§1ڍËf:V¨m&ľĺDGB;?˙˙˙˙˙ů­RĚé+9‡}•+Ďł+±ÍUĐčĘbPSú;1Ś%Ü; Š…R•Ěwt”€Î9B˙˙˙˙ţ¶˙ë˙•˙˙˙˙äěúRwó5ťlŞ„s>ʸő#ĚäşuFlëĐęɕћ˙˙˙Ú˙ł«ÔŞFJČŞyŚEus–˙ű`Ŕ" ? „­Á¬Ć#t•ąa2ft*3άVwVl̸â•ęc@@Ç‚o2ÇĺQÇ;> ˘Ł€?˙˙ős˙˙˙˙˙ü»µ®ĺ:=¨îďv:şş•VB/Îlęčö-ĘZ$Ű™5O˙˙˙ţÍ'*MDŁť'» Č.–zą›S«”ĚęW!„Ça溡Z!3#NǞ#!˘âcH5VA¸ň¨`†`±óőůţż˙˙˙˙®•g;RWTťŮUŐ˝ÔÝQeŃŃTÄľÎB%O˙˙˙őůŃgęŐ9F F‘ťT‡j9"Î(cJb#!ÇxŐ3ł ŞE˙ű`Ŕ, Ú= „­ÉŻČ#4‰¸vcśqL8áaŕĘPq˘Ń2$48˙˙˙˙ţÖŻu˙ş˙˙˙˙˙?Ł‘Ý”„5›Rl›ogGĐŚŞöi7şŐŽ·ěKw˙˙˙˙˙ÚŇ"1Ý_™Ü§8¤T„Dgťä;YśFä%PěĆ \ČäRŠF €‚äS3: ÁČQBŞŕ•Ü(˙˙˙˙ţÍ—˙/üˇßźő˙˙˙o‘śíbnYŘz(‰‘ëu×J:ä·IdŁďZĐÔŮ˙˙˙˙˙ţźJ'»J Ff®ď©Ne+ Č 8ä*&*†q2 0Ě AŠAD…0’ŤQ!2Ş”ĄA§aQE˙ű`Ŕ6€ ¶A „­Á¬EŁtŤą0˙˙˙ý°/ó׿üż˙˙˙˙ň]sąśĐô… 仪Dţ9FzÓ¦[7‘tĄ+Mą2äł˙˙˙˙˙˙˙é˙9ćÓ*FE‘D‰»ë2ł×r™9“Ň·I­|ĽUčĘă±Áz°spÂ+† JŰŘ8˙˙˙˙ţŤ©üĽňgRfŇĎĺ˙˙ýţ•$íc1üý§ş!Yz:d÷#ę×*ş%Pú?{Ż˙˙˙˙˙čɦ·5\ÎRŁľě‚ŠTv{Ä$A™ RkŃi†!ٱĹĘÖ(|¨rpđé˘î<ÂÁŔ+)h0Ŕ5"üż˙˙˙˙˙dî˙űbŔBVA „­ÁÄÇŁ4•ąŹF±:ĽŰ>yę­)Ýě¬ĺşQň˛+7cfT˙˙˙ëŮ[óÉWŁ•hrż˙˙˙˙ŹiO‰Ť&׊aҶ§ö’ŃşkL“ÔW0÷?ÜpµpÝ˙˙˙˙˙˙˙1˙×ýúÚuz÷¤sň8ë›~9Ťç^;Ž’…ěar:¦hZ—Ro|†AwÚÖěXňo®Ü**>@Ö0˙˙˙˙˙ëËWuűJŤFŃYěîĺňYŮrć\îjí»*"±Hß˙˙˙˙~î‰m¨—g*:…+"ofi•ŢÂ#EŹ ¨˛)JçWACę,$aqĘÇ)„€p1Ś"Pč‘ĹH‡(ѡ`8˙űbŔo€5 … É˝H"Ě•¸3čň˙Ż˙˙˙Ö×ÝzčĽě×»Čr/Ȣ Ę®”dŁ3Ń—yN¤q˙˙˙÷ä{"Zĺu#ĚeR ¸ńŽÄsî®e-ESi‘pŐ( ›0“„bŽ3‡Ŕăs†yNEr”ÓŠ¨€ ŚŔ˙˙˙˙˙íyď˙éß˙˙˙ýz.Ą1,o5ä+9·Ežuvf±Îőąv}ČČ–N˙˙˙˙Dű}lłµ+3UTŠCoĽĄ>%čOyS!ťXڇ‘Ći;ť\ę8#)Ĺ:‰cĹ•Ęt(P‘Ŕ?˙˙˙Ň˙ů˙ó˙˙˙˙ů´š÷f˙ű`Ŕx 9 ­Ů¤GŁt‰ąb$„vşftgdű©kU-™śčƤĆVäfŰ˙˙˙˙ěźęR>Ó­ŐK5(i5; Îń“•ŃŠwiÇ*ą¤"âŠ*.*1â¦c´x´çRH@“#•bŔ˙˙˙ö@ż˙-»<ŽÄjµ=]ÖÓTޤ®Ć96Kä<îäsnłĽó—˙˙˙ýv"Ńő{Ń]LëgRťg©ŞF5ŽFuőZÇQ3ŹQÓ ‡Ç°›”a‡¨Ô µĚ.tP‰GQCĺ<ĂŠ˙˙˙˙Öţ]˙ţ«Ż˙˙˙ţŢy‘žr3ÚŰg§ź¤m4U++!d“Ö¶_J´Í{˙ű`Ŕ nA „­ÁŞGŁ4ą˙˙˙˙˙˙˙˙öů7ČqyiHsC0¤Er_?* –gĐNP‰îF¸v#UCŞ9ŽB!* jčŤA‡1Y(0˙˙˙˙ý`_óňţ_˙˙˙˙üßíŃžďzJ_ąK&´išôE3Šeu!&>ţF˙˙˙˙éĚýŃ˙ÜW˙˙˙˙˙˙˙˙?íńó÷+kLÖĂůŽ*Řëk‘s*—gJŹ4`žąEĆÇviF"‡$z©†9‚XőÂaË”9pčP·XT]Ç˙˙˙˙˙c`qîy˙ű`Ŕ˛€ .A „MÁ­GŁtŤą‰#±’ż%ľ§˙ţY¸tűîú㙬ꙉyĄ†ýwŠőÚ©¦÷ŢŇĄ©S»éşć˙˙˙˙˙˙˙ţ˙ż˙űżŽąškŻ«YO›­˛<6<’±…&TÉë˘Ć!wB±E2 ¸öłJ‡4\V…K1`°’ކ6Ëq1Š@˙˙˙˙ý±Í#WĺżĚ«˙˙˙ý:*\ČŞuo™ŹťH(έb+JB{1¨¦î·±•jj˙˙˙ţť7Ą®·tT2+9#ÝÖ<–şśČ©*­ ayŚQ1§3•Ú&=ĄB:``ë‘‚˘§Š ‰Zpđ[JQ˙ű`ŔŔŠA … ÂČ"´ˇ¸ €k?ýţ˝˙˙˙˙˙ţŐdR•¨ÉżWDJ2oR›±ÎÓęUd©Šf3¦Óo5ź˙˙˙˙ňKk­Ę„y™bęěęt3–S’Č5‹Ž «Ź,† 1 áňŽrťJâŠ0˘„#(@Xęč$,⊏8Š4xD*…„˙˙˙˙˙’Ëäű×˙˙˙˙űνey5v}úş*±K÷U2‘ÖgR™–tK"µoE˙˙˙˙˙éîíLĘŚE:2+Ů’}Hî8†fc ‹ł)†ĹĚîěBaÇds¸“!…„XPrxńĘ,Š($0<*5 †4lü˙ű`Ŕł€˛A „­ÁŰH"ô•¸Ö|˙˙˙˙˙˙ôM‘Ű[V۲NI”¬ÚÖçbk3›ß˘ŞŐ‘Z~ż˙˙˙őˇj‡»µ¶g*•Ř”ssB«Ąr”¬QGމ ”„)ĺ10‚ädĆw<)]Ě($aE8pj‡ĐhŔłŔ˘¦‰€˙˙˙˙ţŤßü˙F—=ĆÚţż˙ůĺ®›±÷őĚk*"§f5§˘*YŐC^bşę§»LÜŐ˛˙˙˙˙˙ëî‰wcަUÎ<Ę;0Ô=QÖ«žŽ¤Ź:łóż˙˙˙Ďéą&q…+T©µ'ŮTU•ŚgeR á‡B32)—ýž·D|ď˙˙˙˙Ó]«MÉş+YŚ[Ö¦0Ň)ĘbŽĄŽ5EQ„ÄEa!\hAÂ!ذ łBĂîg Ŕ(ńá‚ÂC &P 8|DV&Q˙˙˙˙˙ű`Ŕ´€ÂA „­Á×ÇŁ4•ą˙éä^¤–ú,‹˙ź˙˙žŇ÷f»‘×d[Ňş˝ VąLës ws)P§z^¬t252]ko˙˙˙ď§Ł÷]»ÍĘS2•gsƙٞDkaśÔ9Ç#Â"%F âDL*Ł˘ (B" ‡XÂŽAěbĽ>˙˙˙˙˙±…ĺţY//˙˙˙üć©J$î”Y?RşŃôVT}ŢŠrQKjßZmu}v˙˙˙ţ«ëzttz%¦TS•1ä#ĚĆ˝j=‘ŇŞ”ŞQ!0c ÓH,˘ĺ ŁÄÄĂŠ,ˇŃw‡Č 8ç1Đĺ†0¸ĐŰ ĂjĐö˙űbŔ¶€A ­ŃŮČ"ô•¸}s—˙ţż˙˙Ä@÷:XČ®M*¨Ccą§r”ü­#çDQz­Şý§µ)˙˙˙˙ýţ–ŃNbç›-LQl„0׳ )ťÜk ;Â,5ÜŁĹ‹;"Š 8ЦA¨$ÁŃqĆ\M…EAB@Áç( *˙˙˙ţŤ˙-ă4Ń—pţR˙˙˙ł˘îVlěU·K&öu+<Ď™‡tLěěv3ˇŠÔłI{×˙˙˙˙úűű•YŃ Ą*«“ęçŞîR†Đ<Ž=ÇÔLU…aĹqáL (s”ń¨§D4P€Č‚Ä !€8˙˙˙˙˙ű`Ŕ·€’A „­ÁńČ"´•¸˙ěŚ w"šçĎ’–«˙˙ţM˙ÇýwóóŻ÷Üu]fFĚŐH÷SR‰őô±×KŻ×˙˙˙˙˙˙˙˙˙Ďw§qóßLň°»OĄO3r˛1GÔ­mÍ»±ĺ;=šSĘ8yŁ2lĎ#•0ŃÄĂHpň ˘ĄŹ% « €˙˙˙˙˙˛źĎ_ĺËz˙˙˙ţţŠŚ÷Z2>ô±• ŚĚ]ŞC\ťČ„VGGuyŐ(Ęr˘J´˙˙˙˙ű[BUÝźaj«öŁ[‹Ő™HŠčăLR«yڇś¬C%Üq]Qbě->A!Yâ†`Ł1\Xâ@`Ň y?˙ű`Ŕ·6A „­ÁâČ"ôˇ¸—#˙˙˙˙Ę­ä&ĘŞËťŐčęĘ}V¤1ÖłFnną:ű[Ż˙˙˙ëmjĚësˇŇŐF!ŠbŠę®Ć)†‹‚*‘LŚc°jˇ¬*‰˘ď0pĄ>$av!śXč4DHÁÁ5‡ĹĨU(p@1@˙˙˙ţ­üŚ˙ĄÔËďë˙˙˙7ęěµR!ź#ٵŠč¨~ܦf5ćfb1ŢŽÝS"Ű˙˙˙ţßčÍišŁP®vJÖ®d1qčq”2ÚČ0:Ĺ+‘T6aÂbEl?2‡DD…‡‡\M"B Ł˘† ”Ác4\6Ă ˙ű`Ŕµ€.A „­ÁçČ"´•¸´ýą~ö˙˙˙ţT«Ű"Ł—ÜÇž—ŃsŮŃhę®DÎmYd˝îdSnŰ˙˙˙˙éíľ­:Ş3š§š•c%®5HQU<§!ĹEHcDH@8â(‡E`=‡(ąĂ§qR¸˛:$&<(ją‹ĆЏ  hŘ6­óćż˙˙˙˙#í%)sMŞŘí1ćrÚě·IUkwG‘rîGtTe÷Ń—˙˙˙˙ŢŐęŰ++Jζ%ƱŢČÄĚ<‹;‘XÇ8q‚:‹ Aa6qe- @ĘAA¦`‘Da"ÄĆÄ„@đ™”:?˙˙˙˙ý$˙ű`Ŕ·€A „­ÁďČ"´•¸/ţN_¬™ôë˙˙ä ÷Gő>ľJĄÓ:˛ť—G4¸±Q(„tC\î{+©•®gO˙˙˙˙쮕˝Ôł!O<®ë!¨Žd,ŠćR1>‚%f0°¬@ę>qâˇô (¨ĺś4ÖQ!0ř€şX‚eQC€˙˙˙˙˙Ňäţgě?˙˙˙˙˙-‘ś¦ŽI"*ĚDŁ™•̧Y§In–jH~­’Vkg»T®Š¬‹˙˙˙˙•úŃĚŽäRËŐ®¬–K¦í[ÝF"Ψpá®ĺs ”<îáC‡A r$ň 0×R‹ŽĚW€˙˙˙˙Ű˙˙űbŔµ^A ­ÁéÇâô•¸˝˙˙ß›˙˙üż->ůŞó/ß&@şŇzšşË)˘Ô-^źđĽŽ˝Ďmű˙˙˙˙˙˙˙˙ĺ‘ßće ·îV›E‚6ŤťŢő$4Cą»Ľ­dPŔŽŠ$B¬ŽAĹŁE "Ł,†8SW©9 € €`5‘ç˙Ëćkúë˙˙ň˙ZšçTCŁú^5Ýő‘ś¨bT©‘Üz-®–™=¤˝ź˙˙˙ýiCž”čËB)©au”‚o,ŃČ8‡c‡ ""@”AĐDĄ*<:*‡ °x:@ůDEEH8:.څЇ†˙˙˙˙¶üżůëď˙˙˙˙©˙˙–ŽÍ­îGH˘’°»Nޱ™ÄW%˛zŐóŘřDo“eŢ˙˙˙˙˙˙˙ţ_ü?)ąleyX†żç)ÎśzRĚź"]Ë•L´'dxČĐtS 3j±Ű é ŕ˙˙˙˙˙Ŕ~ds˙ű`Ŕ˝€>A … ÂH"´•¸ć˝ ˙#˙˙˙˲µĐŐc0‰&V‰ZâBĹrĚdpëFEyFm Ą*ł¬Îľë×˙˙˙˙˙żżmYĘŠVĘĆ}LęĆ+>j=‚¨,ĺ-*UcĹB˛=ť QSÎPčŃÎ"ĘYH  ˙˙˙˙˙˙˙˙˙˙˙˙Ż˙˙˙˙˙Ż˙ý~Ć(`JEŞ*!ŮŮĘbŞ”Vs ` #ł±Š©˙ł”ÁB(˙ű`Ŕł€ n? „mÁŃF˘ô•ą˙űbŔ»€&@ŢMŔ[Ŕ˙ű`Ŕ˙€ŢŔbeetbox-beets-01f1faf/test/rsrc/unparseable.aiff000066400000000000000000002543201472325477400220350ustar00rootroot00000000000000FORMXČAIFFCOMM¬D@¬DSSNDX˙˙˙ţ˙ţ˙ţ˙˙˙ţ˙ţ˙ý˙ü˙ţ˙˙˙ý˙ţ˙˙˙˙˙˙˙ý˙ű˙ý˙ţ˙ü˙ý˙ţ˙ý˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙ţ˙˙˙ţ˙˙˙ţ˙ţ˙ţ˙ţ˙ţ˙ţ˙˙˙ţ˙ü˙ű˙ý˙˙˙ţ˙ţ˙ţ˙ţ˙ţ˙ţ˙ý˙˙˙˙˙˙˙˙˙ý˙˙˙ţ˙˙˙ţ˙ţ˙ü˙ý˙ţ˙ţ˙ý˙ý˙˙˙ţ˙˙˙˙˙ţ˙˙˙ţ˙˙˙ţ˙˙˙ţ˙˙˙ţ˙˙˙˙˙ţ˙˙˙˙˙˙˙˙˙˙˙ţ˙˙˙˙˙ý˙ý˙˙˙˙˙˙˙ý˙ű˙ü˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙ţ˙ţ˙˙˙˙˙˙˙˙˙ţ˙ü˙ý˙˙˙˙˙ţ˙˙˙ţ˙˙˙ţ˙ý˙˙˙˙˙ţ˙ţ˙ý˙ţ˙ţ˙˙˙˙˙˙˙˙˙ý˙ţ˙˙˙˙˙˙˙˙˙˙˙ý˙ű˙ţ˙˙˙ý˙ţ˙˙˙ü˙ţ˙ţ˙ý˙ü˙˙˙ý˙ý˙ý˙˙˙ţ˙˙˙ţ˙˙˙˙˙ý˙˙˙˙˙ţ˙ţ˙ü˙ý˙ţ˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙ý˙ú˙ü˙˙˙˙˙ý˙ű˙ý˙ý˙˙˙˙˙ţ˙ţ˙ţ˙˙˙ţ˙ţ˙ý˙˙˙ţ˙ţ˙˙˙˙˙˙˙ý˙ý˙ţ˙ý˙ţ˙ţ˙˙˙ţ˙ý˙˙˙ţ˙˙˙˙˙˙˙˙˙ţ˙˙˙ţ˙˙˙˙˙ţ˙˙˙ţ˙˙˙˙˙˙˙ţ˙ý˙ţ˙˙˙˙˙˙˙˙˙˙˙˙˙ý˙ý˙˙˙˙˙˙˙ý˙˙˙˙˙ţ˙ţ˙˙˙ý˙ţ˙˙˙˙˙˙˙ţ˙˙˙˙˙ý˙˙˙˙˙ţ˙ű˙ú˙ţ˙˙˙˙˙ţ˙ý˙ý˙ţ˙ţ˙˙˙ţ˙ý˙ţ˙ű˙ý˙˙˙˙˙ţ˙ţ˙ý˙˙˙ý˙ý˙ý˙˙˙ţ˙ý˙ţ˙˙˙ţ˙ü˙ý˙˙˙ţ˙ý˙ţ˙˙˙ţ˙ý˙˙˙˙˙˙˙˙˙˙˙ý˙ý˙˙˙˙˙˙˙ý˙ţ˙ű˙ü˙˙˙˙˙ţ˙ü˙ű˙ţ˙˙˙˙˙ý˙ű˙ţ˙ţ˙ţ˙ű˙ű˙˙˙ţ˙ü˙ý˙ţ˙˙˙˙˙ţ˙ý˙ţ˙˙˙ţ˙ý˙ý˙ţ˙ţ˙ţ˙˙˙ý˙ű˙ý˙ý˙ü˙ý˙ţ˙ţ˙ţ˙˙˙˙˙ţ˙˙˙ţ˙ţ˙˙˙ý˙ü˙ţ˙˙˙ţ˙ţ˙˙˙ţ˙ţ˙˙˙˙˙ţ˙˙˙˙˙˙˙ý˙ü˙ţ˙ţ˙ţ˙ý˙˙˙ţ˙˙˙ý˙ý˙ţ˙ţ˙ţ˙˙˙˙˙˙˙ý˙ţ˙˙˙ý˙ý˙ţ˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙ý˙ý˙ý˙ý˙ý˙˙˙ţ˙˙˙˙˙˙˙ü˙ý˙˙˙ţ˙ý˙˙˙ţ˙ţ˙˙˙ţ˙˙˙ţ˙ý˙ý˙ý˙ţ˙˙˙ţ˙ý˙ü˙ű˙ű˙ý˙ţ˙˙˙ţ˙ý˙ü˙ý˙ţ˙ý˙ţ˙ý˙ý˙ű˙ü˙ţ˙ý˙ű˙ü˙ü˙ţ˙ţ˙ţ˙ţ˙ţ˙ţ˙˙˙ţ˙˙˙ţ˙ý˙˙˙˙˙ü˙ý˙˙˙ţ˙ţ˙˙˙ţ˙˙˙˙˙ü˙ý˙˙˙˙˙ü˙ţ˙˙˙˙˙˙˙ý˙ţ˙ţ˙ý˙˙˙ý˙ý˙ü˙ý˙ţ˙ţ˙˙˙ţ˙ü˙ü˙ţ˙ţ˙˙˙ü˙ű˙ü˙ű˙ý˙˙˙˙˙ţ˙ű˙ü˙ţ˙ü˙˙˙˙˙˙˙˙˙ţ˙˙˙˙˙ý˙ý˙ţ˙˙˙˙˙˙˙˙˙ţ˙ý˙ţ˙˙˙˙˙ü˙˙˙˙˙˙˙ţ˙ý˙ţ˙ý˙ü˙ţ˙˙˙ţ˙˙˙ţ˙ţ˙˙˙˙˙˙˙˙˙˙˙˙˙ţ˙ü˙ý˙ţ˙ţ˙ţ˙ü˙ű˙ý˙˙˙ţ˙˙˙ü˙ý˙˙˙ţ˙ţ˙˙˙ţ˙˙˙˙˙˙˙ţ˙ţ˙ţ˙ü˙ű˙ű˙ü˙ţ˙˙˙ţ˙ţ˙˙˙˙˙ý˙ý˙ţ˙ţ˙ű˙ţ˙˙˙ţ˙˙˙˙˙˙˙ý˙ţ˙˙˙ý˙ü˙˙˙˙˙ü˙˙˙˙˙ţ˙ţ˙ţ˙˙˙˙˙ü˙ű˙ý˙ý˙˙˙˙˙˙˙ü˙˙˙˙˙ţ˙ţ˙ţ˙ţ˙ţ˙ţ˙˙˙˙˙ü˙˙˙˙˙ý˙˙˙ţ˙ţ˙ý˙˙˙˙˙ţ˙ý˙˙˙ţ˙˙˙˙˙˙˙ţ˙ü˙ý˙˙˙ţ˙ý˙˙˙ţ˙ţ˙ý˙˙˙ţ˙˙˙ţ˙˙˙ţ˙ý˙ü˙ý˙ţ˙˙˙ţ˙ý˙ý˙ý˙ü˙˙˙ý˙ü˙ü˙ý˙ţ˙˙˙˙˙˙˙˙˙ţ˙˙˙ţ˙ţ˙ţ˙ţ˙ţ˙ţ˙ţ˙ţ˙ţ˙˙˙˙˙˙˙˙˙ü˙ţ˙˙˙˙˙˙˙ţ˙ü˙ţ˙ý˙ű˙ü˙ţ˙ý˙ţ˙˙˙ý˙ţ˙˙˙ţ˙ţ˙˙˙ţ˙˙˙ţ˙ü˙ü˙ü˙˙˙ţ˙ü˙˙˙˙˙˙˙ţ˙˙˙˙˙ţ˙ţ˙˙˙ý˙ü˙ţ˙ţ˙˙˙ý˙ţ˙ţ˙˙˙ţ˙ţ˙ü˙˙˙ţ˙˙˙ţ˙ű˙ú˙ý˙˙˙ţ˙˙˙˙˙˙˙ý˙ţ˙ý˙ý˙ţ˙ţ˙ţ˙ţ˙˙˙ţ˙ţ˙ý˙ü˙˙˙ý˙ý˙˙˙˙˙˙˙˙˙˙˙˙˙ţ˙ý˙ý˙ţ˙˙˙ţ˙ţ˙ţ˙˙˙ţ˙ý˙ý˙ü˙ý˙˙˙ţ˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙ý˙ý˙˙˙ý˙ţ˙˙˙ţ˙ý˙ţ˙ý˙ü˙ţ˙˙˙˙˙˙˙ţ˙˙˙˙˙ü˙˙˙ţ˙ţ˙˙˙ţ˙ţ˙˙˙ţ˙˙˙ý˙˙˙˙˙ţ˙ţ˙˙˙ţ˙˙˙˙˙˙˙ţ˙ý˙˙˙ţ˙ţ˙˙˙ţ˙˙˙ţ˙ţ˙ý˙ţ˙ý˙ţ˙ţ˙ý˙ü˙ü˙ţ˙˙˙˙˙ţ˙ý˙ţ˙ţ˙ţ˙ţ˙˙˙˙˙ü˙ü˙ý˙ţ˙ţ˙ţ˙˙˙ý˙ů˙ű˙ý˙ţ˙ţ˙ţ˙˙˙˙˙ü˙ţ˙ţ˙ţ˙ţ˙ţ˙ţ˙ţ˙ţ˙˙˙ý˙˙˙˙˙ţ˙ţ˙˙˙˙˙˙˙˙˙ţ˙˙˙˙˙˙˙˙˙ý˙ý˙˙˙˙˙˙˙˙˙˙˙˙˙ţ˙ţ˙ý˙ţ˙˙˙˙˙ţ˙ü˙ţ˙˙˙ţ˙ű˙ý˙ý˙ý˙ý˙ţ˙ţ˙˙˙˙˙˙˙ţ˙˙˙ü˙ü˙ý˙ý˙ű˙˙˙ý˙ý˙ţ˙ý˙˙˙˙˙ű˙ý˙ý˙ý˙ý˙ü˙˙˙˙˙ţ˙˙˙˙˙ţ˙˙˙˙˙˙˙ţ˙˙˙˙˙˙˙˙˙˙˙ü˙˙˙˙˙ţ˙ţ˙˙˙ü˙ţ˙ţ˙ţ˙ţ˙ţ˙ţ˙ţ˙˙˙ţ˙ţ˙˙˙ţ˙ý˙ü˙ü˙ţ˙ţ˙ţ˙ţ˙ü˙ý˙ţ˙˙˙ţ˙˙˙˙˙˙˙˙˙˙˙˙˙ý˙ţ˙ý˙ţ˙˙˙˙˙ţ˙˙˙˙˙ý˙ý˙ţ˙˙˙˙˙ţ˙˙˙ţ˙˙˙˙˙ţ˙ţ˙ţ˙˙˙ţ˙ý˙ţ˙ý˙ý˙ü˙ţ˙˙˙ü˙ü˙ü˙ý˙ý˙ü˙ü˙ţ˙˙˙ý˙ű˙ű˙ü˙ţ˙ü˙ţ˙˙˙˙˙ţ˙˙˙˙˙˙˙ţ˙ţ˙˙˙ţ˙ý˙˙˙˙˙ţ˙˙˙˙˙ţ˙˙˙ţ˙ý˙˙˙˙˙˙˙˙˙˙˙˙˙ţ˙ý˙ţ˙ý˙ţ˙ţ˙ţ˙˙˙ţ˙ţ˙ţ˙ţ˙ú˙ű˙ü˙˙˙˙˙ţ˙ţ˙˙˙˙˙˙˙˙˙˙˙ţ˙ý˙˙˙ţ˙ţ˙ţ˙˙˙ţ˙ţ˙˙˙ý˙ţ˙ü˙ü˙ţ˙ý˙˙˙˙˙˙˙˙˙˙˙ý˙ţ˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙ţ˙ý˙ü˙ý˙ű˙ü˙˙˙˙˙ţ˙ţ˙˙˙˙˙˙˙˙˙˙˙˙˙ţ˙ţ˙˙˙ţ˙ű˙ţ˙ý˙˙˙˙˙˙˙ţ˙˙˙˙˙ţ˙ţ˙˙˙˙˙ţ˙ţ˙ý˙˙˙˙˙˙˙˙˙˙˙ý˙ý˙˙˙˙˙ţ˙ý˙ţ˙˙˙ţ˙ţ˙˙˙ţ˙ţ˙ţ˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙ţ˙ý˙˙˙ţ˙ţ˙˙˙ţ˙ü˙ü˙ţ˙˙˙ţ˙˙˙˙˙ţ˙˙˙˙˙˙˙ţ˙˙˙ţ˙˙˙˙˙˙˙˙˙˙˙ţ˙ý˙ý˙˙˙ţ˙ý˙ý˙˙˙˙˙˙˙˙˙˙˙˙˙ţ˙ý˙ţ˙˙˙ţ˙ţ˙˙˙ţ˙˙˙ţ˙˙˙˙˙˙˙ý˙ý˙ý˙ţ˙ü˙ű˙ý˙ţ˙ý˙˙˙˙˙˙˙ý˙˙˙˙˙ţ˙˙˙˙˙ü˙ý˙ţ˙ý˙˙˙˙˙ţ˙ý˙ü˙ý˙ţ˙ţ˙˙˙ţ˙ü˙ű˙ű˙ţ˙˙˙ţ˙˙˙ţ˙˙˙˙˙ţ˙ţ˙˙˙ü˙˙˙ý˙ü˙ţ˙ţ˙˙˙˙˙ý˙ţ˙˙˙ţ˙ý˙ţ˙ţ˙˙˙˙˙ţ˙˙˙ţ˙ţ˙˙˙ü˙ü˙˙˙ţ˙ţ˙˙˙ţ˙˙˙ţ˙˙˙ţ˙ţ˙ţ˙ţ˙ţ˙˙˙ü˙ý˙ţ˙˙˙˙˙ţ˙˙˙˙˙˙˙ţ˙˙˙ţ˙ý˙ü˙˙˙˙˙ţ˙˙˙˙˙ţ˙ţ˙ţ˙ţ˙˙˙˙˙ţ˙ý˙ţ˙˙˙ţ˙˙˙ţ˙ý˙ý˙ţ˙˙˙˙˙˙˙˙˙˙˙˙˙ţ˙˙˙˙˙˙˙˙˙ý˙˙˙˙˙ţ˙˙˙ţ˙ý˙˙˙˙˙˙˙ţ˙˙˙ý˙ý˙˙˙˙˙˙˙˙˙˙˙ţ˙ţ˙ţ˙˙˙ý˙ű˙ţ˙˙˙ţ˙ý˙ü˙ý˙ţ˙ţ˙ţ˙˙˙ý˙ű˙ű˙ţ˙˙˙˙˙ţ˙˙˙˙˙˙˙ţ˙ý˙ü˙ţ˙ţ˙ý˙ţ˙ţ˙ţ˙ţ˙˙˙ţ˙ţ˙ý˙˙˙ţ˙ü˙ý˙ý˙ţ˙ű˙ü˙ý˙˙˙˙˙ý˙˙˙˙˙ý˙ţ˙ţ˙ü˙ý˙ý˙ý˙ý˙ţ˙ţ˙˙˙ţ˙˙˙˙˙ý˙ű˙ţ˙˙˙˙˙ţ˙ý˙ţ˙˙˙˙˙˙˙˙˙˙˙˙˙ý˙˙˙˙˙˙˙˙˙˙˙ý˙ü˙ţ˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙ţ˙˙˙˙˙ţ˙ű˙ü˙ţ˙˙˙ü˙ü˙˙˙˙˙ţ˙ţ˙˙˙ý˙ý˙ţ˙ţ˙ý˙ý˙ý˙ţ˙˙˙ţ˙ý˙ţ˙ü˙ü˙ţ˙ű˙ţ˙ý˙ţ˙˙˙ţ˙ţ˙ţ˙ţ˙˙˙ţ˙ţ˙ý˙ý˙˙˙˙˙ţ˙ţ˙ü˙ţ˙˙˙˙˙ţ˙ţ˙ţ˙ý˙˙˙˙˙˙˙ţ˙ţ˙ţ˙ý˙ţ˙˙˙˙˙ý˙ý˙˙˙˙˙˙˙ý˙ţ˙˙˙ţ˙ţ˙ý˙ü˙˙˙˙˙ţ˙ţ˙ţ˙˙˙ý˙ý˙ţ˙ţ˙˙˙˙˙˙˙˙˙ü˙ü˙˙˙˙˙˙˙˙˙ţ˙ţ˙ţ˙ţ˙ţ˙˙˙˙˙ý˙ţ˙˙˙ţ˙ţ˙˙˙ý˙ű˙ý˙˙˙ţ˙ý˙ý˙˙˙˙˙˙˙ý˙ţ˙˙˙ý˙ţ˙˙˙˙˙˙˙˙˙˙˙˙˙ţ˙ü˙ý˙˙˙˙˙˙˙˙˙ý˙ý˙˙˙˙˙ţ˙ý˙˙˙˙˙˙˙˙˙˙˙ţ˙ü˙ţ˙ţ˙˙˙˙˙ţ˙ţ˙˙˙ţ˙ţ˙˙˙ţ˙˙˙˙˙˙˙˙˙ţ˙ý˙˙˙ţ˙˙˙˙˙˙˙˙˙ţ˙ý˙˙˙ţ˙ü˙ý˙ţ˙˙˙ţ˙ý˙˙˙ţ˙ý˙ý˙ţ˙ű˙ű˙ţ˙˙˙˙˙ţ˙ý˙ţ˙˙˙˙˙ý˙ţ˙˙˙ţ˙˙˙ţ˙ţ˙˙˙ţ˙ý˙ţ˙˙˙˙˙˙˙ţ˙˙˙˙˙˙˙˙˙ţ˙ţ˙ý˙ü˙ü˙ü˙ý˙˙˙˙˙˙˙ţ˙ý˙ţ˙˙˙ý˙˙˙˙˙ý˙ý˙ü˙ý˙ţ˙ţ˙ý˙ţ˙˙˙ü˙ý˙ţ˙˙˙˙˙˙˙ţ˙˙˙˙˙˙˙ţ˙˙˙˙˙ţ˙˙˙ţ˙ü˙ű˙ý˙˙˙˙˙˙˙˙˙ţ˙ý˙ý˙ý˙ţ˙ţ˙˙˙ţ˙˙˙˙˙ţ˙ţ˙˙˙˙˙ţ˙˙˙ţ˙˙˙ţ˙ý˙˙˙ţ˙ü˙ü˙˙˙˙˙˙˙˙˙ý˙ţ˙ý˙ý˙ţ˙˙˙˙˙˙˙ţ˙˙˙ý˙ý˙ý˙˙˙ý˙˙˙˙˙˙˙˙˙˙˙ţ˙ü˙ü˙ü˙ü˙ü˙ü˙ý˙˙˙ţ˙˙˙ţ˙ţ˙ţ˙ţ˙ţ˙˙˙ţ˙˙˙˙˙˙˙˙˙˙˙ý˙ţ˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙ţ˙ü˙ü˙˙˙˙˙˙˙ý˙ţ˙˙˙˙˙˙˙ý˙ţ˙˙˙ţ˙ţ˙ţ˙ţ˙˙˙ý˙ü˙ţ˙ű˙ü˙ţ˙ţ˙ţ˙˙˙˙˙˙˙ü˙ü˙ý˙ţ˙ţ˙ţ˙˙˙ţ˙ţ˙ý˙ú˙ý˙˙˙ţ˙˙˙ţ˙ý˙˙˙˙˙ţ˙ţ˙ţ˙ý˙ý˙ý˙ţ˙˙˙ţ˙ţ˙ţ˙˙˙ý˙ý˙˙˙˙˙˙˙ü˙ű˙ý˙˙˙˙˙ţ˙˙˙˙˙ţ˙ý˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙ý˙ţ˙ý˙ţ˙ý˙ţ˙ý˙ţ˙˙˙ţ˙ţ˙˙˙˙˙ý˙ý˙ţ˙ý˙ţ˙ţ˙˙˙˙˙ý˙ý˙˙˙˙˙ţ˙˙˙ý˙˙˙˙˙ý˙ţ˙˙˙ý˙ţ˙ý˙ţ˙˙˙ý˙ţ˙ţ˙ü˙ü˙ţ˙ţ˙ü˙ý˙˙˙˙˙ý˙˙˙˙˙ý˙˙˙˙˙ţ˙ţ˙˙˙ý˙ý˙ý˙ţ˙˙˙˙˙ţ˙˙˙˙˙˙˙ý˙ű˙ü˙ţ˙˙˙ţ˙ţ˙ű˙ű˙ţ˙ţ˙ü˙ý˙˙˙ţ˙ü˙ý˙˙˙ţ˙ü˙ý˙ţ˙˙˙ý˙ţ˙ý˙ü˙ü˙ü˙˙˙ý˙ü˙ţ˙˙˙ý˙ý˙˙˙˙˙˙˙˙˙˙˙˙˙ţ˙ü˙ý˙ţ˙ţ˙ţ˙˙˙ţ˙ţ˙˙˙ý˙ý˙ţ˙ţ˙˙˙˙˙ý˙˙˙˙˙˙˙ţ˙ţ˙ţ˙˙˙˙˙ü˙˙˙˙˙ý˙ţ˙ţ˙˙˙ţ˙ţ˙˙˙ű˙ü˙˙˙˙˙˙˙˙˙ţ˙ü˙ű˙ý˙˙˙˙˙˙˙˙˙˙˙ý˙ű˙ý˙ţ˙ţ˙ý˙ü˙ţ˙˙˙˙˙ţ˙ţ˙ü˙ý˙˙˙˙˙˙˙ţ˙ű˙ý˙˙˙˙˙˙˙˙˙˙˙ţ˙ý˙˙˙ţ˙˙˙ţ˙ţ˙˙˙ţ˙˙˙˙˙ý˙ţ˙˙˙ţ˙ý˙˙˙˙˙ţ˙ţ˙˙˙˙˙˙˙˙˙ţ˙˙˙ý˙ţ˙˙˙ý˙ű˙ü˙ţ˙˙˙˙˙˙˙˙˙ţ˙ţ˙˙˙˙˙˙˙ţ˙ý˙ţ˙ý˙˙˙˙˙ţ˙ţ˙ý˙ţ˙ţ˙˙˙˙˙ü˙ý˙ţ˙ü˙ü˙ű˙ţ˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙ý˙ţ˙ý˙˙˙˙˙˙˙˙˙˙˙˙˙ý˙ü˙ţ˙ţ˙˙˙˙˙˙˙˙˙˙˙˙˙ţ˙ţ˙ý˙ţ˙ţ˙ü˙˙˙ţ˙˙˙˙˙ţ˙˙˙ţ˙ü˙ţ˙ţ˙˙˙˙˙ţ˙˙˙˙˙ţ˙ý˙ţ˙ü˙ý˙˙˙˙˙˙˙ţ˙˙˙˙˙˙˙ţ˙ü˙ý˙˙˙ý˙ű˙ý˙˙˙˙˙˙˙˙˙˙˙ţ˙˙˙˙˙ţ˙˙˙ţ˙ý˙ý˙ţ˙˙˙ü˙ü˙˙˙ţ˙˙˙ţ˙ţ˙ţ˙ţ˙ţ˙ţ˙ţ˙ţ˙˙˙ý˙ţ˙ţ˙ý˙ý˙˙˙ü˙ü˙ţ˙ţ˙˙˙˙˙ţ˙ţ˙ţ˙ý˙˙˙˙˙˙˙ţ˙ţ˙ţ˙ţ˙˙˙˙˙ý˙˙˙˙˙ü˙ý˙ţ˙˙˙ţ˙ü˙ţ˙ţ˙˙˙ţ˙˙˙˙˙ý˙ţ˙˙˙˙˙ţ˙ţ˙˙˙ţ˙ţ˙ţ˙ţ˙ţ˙˙˙˙˙ţ˙ţ˙˙˙˙˙ţ˙ţ˙˙˙ü˙ý˙ţ˙ţ˙˙˙˙˙˙˙˙˙˙˙˙˙ţ˙˙˙ţ˙˙˙˙˙ý˙ţ˙ţ˙˙˙˙˙˙˙˙˙ţ˙ţ˙˙˙˙˙ţ˙˙˙˙˙˙˙˙˙˙˙ţ˙ý˙ý˙˙˙˙˙ţ˙ţ˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙ţ˙˙˙˙˙ý˙ţ˙ý˙ţ˙˙˙˙˙˙˙˙˙ý˙ü˙ţ˙˙˙˙˙ţ˙ý˙ţ˙ţ˙ţ˙ý˙ţ˙˙˙ý˙ü˙ţ˙˙˙ý˙ţ˙ţ˙˙˙˙˙ţ˙˙˙ţ˙˙˙˙˙ý˙˙˙˙˙˙˙ţ˙ü˙ű˙ü˙˙˙ţ˙˙˙˙˙˙˙˙˙˙˙ţ˙ţ˙ý˙ü˙ü˙ţ˙ý˙ű˙ú˙ü˙˙˙˙˙˙˙˙˙ý˙ţ˙˙˙ţ˙ý˙ú˙ű˙ţ˙ý˙˙˙˙˙˙˙ţ˙˙˙ţ˙˙˙ţ˙ţ˙˙˙ţ˙ţ˙˙˙˙˙˙˙˙˙ţ˙ţ˙ţ˙˙˙ý˙ü˙ý˙ţ˙ü˙ý˙˙˙ţ˙˙˙ţ˙ţ˙ţ˙˙˙˙˙ý˙ţ˙˙˙˙˙˙˙˙˙˙˙ú˙ý˙˙˙ý˙ţ˙˙˙˙˙ţ˙ű˙ů˙ý˙ţ˙ţ˙˙˙ţ˙˙˙ţ˙˙˙ţ˙˙˙˙˙ý˙ţ˙˙˙˙˙˙˙˙˙˙˙˙˙ý˙˙˙ý˙ý˙ý˙ű˙ű˙ţ˙˙˙ý˙ţ˙˙˙˙˙˙˙ţ˙ţ˙ý˙ü˙ý˙˙˙ý˙˙˙˙˙ţ˙ţ˙˙˙˙˙˙˙ý˙˙˙˙˙˙˙˙˙ţ˙˙˙ţ˙ţ˙˙˙˙˙˙˙ţ˙ţ˙ţ˙ý˙ţ˙ý˙ý˙ţ˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙ý˙ţ˙˙˙ý˙ü˙ţ˙˙˙ý˙ű˙ű˙ţ˙˙˙˙˙ţ˙ţ˙ţ˙˙˙˙˙ţ˙˙˙˙˙ý˙ý˙ţ˙ţ˙ţ˙ţ˙˙˙ţ˙ţ˙˙˙˙˙ü˙ţ˙˙˙ţ˙ţ˙ţ˙ţ˙˙˙˙˙ü˙ú˙˙˙ţ˙ű˙ű˙ú˙ý˙ţ˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙ţ˙ý˙ý˙˙˙˙˙˙˙˙˙˙˙ţ˙ý˙˙˙ţ˙ü˙ý˙ţ˙˙˙˙˙˙˙ţ˙ý˙ţ˙˙˙˙˙˙˙ţ˙˙˙˙˙ţ˙˙˙ţ˙ý˙˙˙˙˙ţ˙ţ˙˙˙ţ˙ý˙ţ˙˙˙˙˙ý˙ţ˙˙˙˙˙ţ˙ű˙ţ˙˙˙ţ˙˙˙ţ˙ţ˙˙˙ü˙ý˙˙˙ü˙ü˙ţ˙ţ˙ţ˙˙˙˙˙ţ˙˙˙ţ˙˙˙˙˙ý˙ţ˙˙˙˙˙ţ˙ţ˙˙˙˙˙ý˙ü˙ý˙ü˙˙˙ţ˙ý˙ţ˙˙˙˙˙ý˙ý˙˙˙˙˙˙˙ţ˙ý˙ü˙ý˙˙˙ý˙ţ˙˙˙˙˙ý˙ţ˙˙˙ý˙ţ˙ţ˙˙˙ü˙˙˙˙˙˙˙ţ˙ý˙ţ˙ý˙ý˙˙˙˙˙˙˙˙˙ţ˙ý˙ţ˙ý˙ţ˙˙˙˙˙ţ˙˙˙ţ˙ţ˙ţ˙ţ˙˙˙˙˙˙˙ţ˙ţ˙ţ˙ţ˙ü˙ü˙ý˙˙˙˙˙ţ˙ý˙ţ˙ţ˙˙˙ý˙ű˙ü˙˙˙ý˙ţ˙ý˙˙˙˙˙˙˙ţ˙ţ˙˙˙˙˙˙˙ü˙ý˙ţ˙˙˙˙˙ţ˙˙˙ţ˙ü˙ý˙ţ˙ţ˙˙˙˙˙˙˙ţ˙ţ˙˙˙ý˙ţ˙˙˙ţ˙˙˙ý˙ý˙ţ˙˙˙˙˙ţ˙˙˙ý˙ţ˙ý˙˙˙˙˙˙˙˙˙˙˙˙˙ţ˙˙˙ý˙ü˙ţ˙˙˙ţ˙˙˙ý˙ü˙ţ˙˙˙ý˙ţ˙˙˙˙˙ý˙ű˙ţ˙ü˙ý˙˙˙ţ˙ţ˙˙˙ţ˙˙˙ţ˙˙˙ţ˙ý˙ü˙ý˙ý˙˙˙˙˙˙˙ţ˙˙˙ţ˙ţ˙˙˙ţ˙ü˙ý˙˙˙ţ˙˙˙ţ˙ţ˙˙˙˙˙ü˙ý˙˙˙˙˙ţ˙ţ˙ý˙˙˙˙˙ţ˙˙˙ţ˙ţ˙ţ˙ţ˙ţ˙ţ˙ţ˙˙˙˙˙ţ˙ţ˙ţ˙ţ˙ţ˙˙˙˙˙˙˙˙˙˙˙ţ˙˙˙˙˙˙˙˙˙ţ˙ý˙˙˙ţ˙ý˙˙˙ţ˙ý˙˙˙˙˙ý˙ű˙˙˙˙˙˙˙˙˙˙˙ţ˙ű˙˙˙˙˙˙˙˙˙˙˙ţ˙ţ˙ţ˙˙˙ţ˙ţ˙ü˙ü˙ý˙ţ˙ý˙ü˙ţ˙˙˙ý˙ű˙ü˙ţ˙˙˙ţ˙˙˙˙˙˙˙˙˙˙˙ý˙˙˙˙˙˙˙˙˙˙˙ý˙ý˙˙˙˙˙ţ˙ţ˙˙˙˙˙ý˙ţ˙ü˙ü˙˙˙˙˙ý˙ţ˙˙˙˙˙ý˙ţ˙ý˙ü˙˙˙˙˙˙˙ţ˙ţ˙ý˙ţ˙˙˙˙˙ţ˙ý˙ü˙ý˙ý˙ý˙˙˙˙˙ý˙˙˙˙˙ý˙˙˙˙˙ţ˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙ţ˙ü˙ý˙˙˙ţ˙ý˙ý˙˙˙ţ˙ţ˙ü˙ţ˙ţ˙˙˙ý˙ý˙ý˙ý˙ţ˙ü˙˙˙˙˙ý˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙ţ˙˙˙ţ˙˙˙ţ˙˙˙ţ˙ţ˙˙˙ý˙ţ˙ý˙˙˙ý˙ý˙ţ˙˙˙˙˙ţ˙˙˙ţ˙ţ˙ű˙ü˙ý˙ý˙ţ˙˙˙˙˙˙˙˙˙ţ˙ţ˙˙˙ţ˙ü˙ü˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙ţ˙˙˙ý˙ý˙˙˙ý˙ý˙ţ˙˙˙˙˙˙˙˙˙˙˙ţ˙ţ˙ţ˙ü˙ü˙ý˙˙˙˙˙˙˙˙˙˙˙ü˙ý˙ý˙ü˙ţ˙˙˙˙˙ţ˙ţ˙ý˙ţ˙ý˙ý˙˙˙ü˙ţ˙˙˙˙˙˙˙˙˙˙˙ţ˙˙˙˙˙˙˙˙˙˙˙ü˙ý˙˙˙ţ˙ţ˙ţ˙ü˙ý˙˙˙˙˙˙˙˙˙˙˙ţ˙ý˙˙˙˙˙ý˙ý˙ţ˙ý˙ţ˙˙˙˙˙˙˙˙˙ţ˙ü˙ű˙ý˙˙˙ţ˙˙˙˙˙ţ˙˙˙˙˙ţ˙ý˙ü˙ţ˙ţ˙˙˙˙˙ţ˙˙˙˙˙ţ˙˙˙˙˙ţ˙ý˙ý˙ţ˙˙˙˙˙ü˙˙˙˙˙ü˙ţ˙ţ˙˙˙ý˙ţ˙˙˙˙˙˙˙ţ˙ü˙ý˙˙˙˙˙ý˙ű˙ţ˙˙˙ý˙ý˙˙˙˙˙ţ˙˙˙ţ˙ţ˙˙˙˙˙˙˙˙˙ţ˙˙˙˙˙ţ˙˙˙˙˙˙˙˙˙˙˙˙˙ý˙ţ˙˙˙ý˙ü˙ţ˙˙˙ý˙ý˙˙˙˙˙ţ˙˙˙˙˙ţ˙ý˙˙˙ü˙ű˙ü˙˙˙˙˙˙˙˙˙˙˙ý˙ţ˙ý˙˙˙˙˙˙˙ý˙˙˙˙˙˙˙ţ˙ü˙ţ˙˙˙˙˙˙˙ý˙˙˙ţ˙˙˙ű˙ů˙ü˙˙˙˙˙ţ˙ţ˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙ţ˙ţ˙ţ˙˙˙˙˙˙˙ý˙ý˙ţ˙˙˙˙˙˙˙ţ˙˙˙ţ˙ţ˙˙˙ţ˙ü˙ý˙ţ˙ü˙ý˙ü˙ý˙ţ˙˙˙˙˙ü˙˙˙˙˙ü˙˙˙˙˙ű˙ú˙ű˙˙˙˙˙ţ˙ţ˙˙˙ţ˙ý˙ü˙ţ˙ţ˙ý˙ý˙˙˙˙˙ţ˙ţ˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙ý˙ý˙ţ˙ţ˙ý˙ý˙ţ˙ţ˙ý˙ü˙ý˙˙˙ý˙ţ˙ţ˙ü˙ü˙ţ˙ţ˙ý˙˙˙ţ˙ü˙ţ˙ţ˙˙˙˙˙ţ˙ţ˙˙˙˙˙˙˙ţ˙˙˙ţ˙ţ˙ţ˙˙˙ţ˙ţ˙˙˙ţ˙ţ˙ţ˙ţ˙˙˙˙˙˙˙˙˙ţ˙ţ˙˙˙˙˙ţ˙ý˙ţ˙ţ˙ţ˙˙˙ţ˙˙˙˙˙˙˙ý˙ţ˙ţ˙ţ˙ü˙ý˙˙˙˙˙ţ˙˙˙˙˙ţ˙ý˙˙˙˙˙˙˙ţ˙˙˙ţ˙ţ˙˙˙˙˙ţ˙˙˙˙˙ţ˙˙˙ý˙ţ˙˙˙˙˙ý˙ţ˙˙˙ý˙ý˙ý˙ý˙˙˙ţ˙ţ˙ţ˙˙˙˙˙˙˙˙˙ţ˙˙˙ţ˙ţ˙ý˙ţ˙˙˙ţ˙ţ˙ţ˙ý˙ý˙˙˙˙˙˙˙ţ˙ý˙ţ˙˙˙˙˙ý˙ţ˙ţ˙ý˙ţ˙˙˙ý˙˙˙ţ˙˙˙ý˙ü˙˙˙˙˙ű˙ű˙ý˙˙˙˙˙ý˙ý˙ý˙˙˙ý˙ý˙ü˙ţ˙ý˙ű˙ý˙˙˙ý˙ů˙ú˙ü˙˙˙ţ˙ţ˙˙˙ü˙ţ˙˙˙˙˙ţ˙ţ˙ţ˙ý˙ü˙˙˙ţ˙ü˙ý˙˙˙ţ˙˙˙ţ˙˙˙ţ˙ý˙ü˙ý˙ý˙ý˙˙˙ţ˙ţ˙˙˙ţ˙˙˙ţ˙ţ˙ţ˙˙˙˙˙˙˙˙˙˙˙˙˙ţ˙˙˙ý˙ü˙ţ˙ţ˙ţ˙ţ˙ţ˙ţ˙˙˙˙˙˙˙˙˙˙˙ü˙ü˙ţ˙˙˙ţ˙ý˙ý˙˙˙ţ˙˙˙˙˙ţ˙ý˙ţ˙ý˙ţ˙ű˙ü˙ţ˙ţ˙ţ˙˙˙ţ˙˙˙ţ˙ý˙ý˙˙˙˙˙ţ˙ţ˙˙˙˙˙ţ˙ţ˙ţ˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙ý˙ţ˙˙˙ţ˙˙˙ţ˙ţ˙˙˙˙˙˙˙ţ˙ţ˙ý˙˙˙˙˙˙˙˙˙ţ˙ý˙˙˙ţ˙ý˙˙˙˙˙˙˙ý˙ţ˙ý˙ű˙ű˙˙˙˙˙ý˙˙˙ý˙ţ˙ţ˙ţ˙˙˙˙˙˙˙˙˙˙˙ţ˙ý˙˙˙ţ˙ý˙ý˙˙˙ţ˙˙˙˙˙ţ˙ţ˙˙˙ţ˙˙˙ţ˙ý˙ţ˙ţ˙ţ˙ţ˙ţ˙ţ˙ţ˙ţ˙˙˙˙˙ţ˙˙˙ţ˙˙˙˙˙ú˙ű˙ý˙˙˙˙˙ţ˙ü˙ý˙ţ˙ţ˙ü˙˙˙ţ˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙ţ˙ţ˙ţ˙ţ˙˙˙˙˙ý˙ý˙ţ˙˙˙˙˙˙˙˙˙ţ˙˙˙ţ˙ţ˙ţ˙˙˙ţ˙ţ˙ý˙˙˙ţ˙ý˙ý˙ý˙ţ˙˙˙˙˙ý˙ţ˙˙˙˙˙ü˙ý˙ţ˙˙˙˙˙˙˙˙˙˙˙ý˙ţ˙ţ˙ţ˙ü˙˙˙˙˙ţ˙˙˙ý˙ü˙˙˙˙˙ý˙ý˙ţ˙˙˙˙˙ý˙ţ˙˙˙˙˙ţ˙ţ˙ţ˙˙˙˙˙˙˙ţ˙˙˙ţ˙˙˙˙˙ý˙ý˙ý˙ý˙ý˙ý˙˙˙ţ˙ü˙ţ˙˙˙˙˙ţ˙ţ˙˙˙ţ˙˙˙˙˙˙˙ţ˙ý˙˙˙˙˙˙˙ţ˙˙˙˙˙ţ˙˙˙ţ˙ý˙ţ˙˙˙˙˙ţ˙ü˙ý˙˙˙˙˙˙˙ţ˙˙˙˙˙˙˙ý˙ý˙ţ˙˙˙˙˙˙˙˙˙ţ˙ü˙ý˙˙˙ţ˙ţ˙ţ˙ţ˙ý˙ţ˙˙˙˙˙˙˙ý˙ý˙˙˙˙˙˙˙˙˙ü˙ü˙˙˙˙˙ý˙ý˙ţ˙˙˙ţ˙ü˙ý˙ý˙ţ˙ţ˙ý˙ű˙ý˙˙˙ţ˙ü˙ű˙ý˙ý˙˙˙ţ˙ý˙˙˙ţ˙˙˙ü˙˙˙˙˙ţ˙˙˙˙˙ţ˙ý˙ţ˙ţ˙ţ˙˙˙˙˙ü˙ü˙ý˙˙˙˙˙˙˙˙˙˙˙ţ˙ü˙ý˙˙˙˙˙ţ˙ţ˙˙˙ţ˙ý˙ý˙ţ˙ţ˙˙˙ţ˙˙˙˙˙˙˙ý˙˙˙ý˙ü˙ý˙ý˙ţ˙ý˙ý˙˙˙ý˙ý˙ţ˙˙˙˙˙ý˙ý˙ý˙ý˙˙˙˙˙˙˙˙˙˙˙˙˙ý˙ý˙˙˙ţ˙ţ˙ţ˙ţ˙˙˙˙˙ý˙ý˙˙˙ţ˙ý˙˙˙˙˙˙˙˙˙ü˙ý˙˙˙ţ˙ý˙˙˙ţ˙˙˙ţ˙ţ˙˙˙ý˙˙˙˙˙ţ˙˙˙ţ˙ý˙ţ˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙ţ˙ý˙˙˙ţ˙ü˙ű˙ţ˙˙˙ý˙ý˙˙˙˙˙ý˙ţ˙ţ˙ű˙ű˙ý˙˙˙ţ˙ý˙ţ˙˙˙˙˙ý˙ý˙˙˙˙˙˙˙˙˙ý˙˙˙˙˙˙˙ý˙ű˙ü˙ţ˙˙˙ţ˙ţ˙ţ˙ţ˙˙˙˙˙ţ˙˙˙˙˙ű˙ý˙ű˙ü˙ţ˙ý˙ţ˙ţ˙ű˙ü˙˙˙ý˙ý˙ý˙ý˙˙˙ü˙˙˙ţ˙ý˙˙˙˙˙˙˙˙˙ţ˙˙˙ţ˙˙˙˙˙˙˙ţ˙˙˙˙˙˙˙˙˙ţ˙˙˙˙˙˙˙˙˙ţ˙ý˙ţ˙˙˙˙˙˙˙ţ˙˙˙˙˙˙˙ý˙ý˙ţ˙ţ˙ý˙ţ˙ţ˙ý˙˙˙ţ˙˙˙˙˙˙˙ü˙ű˙ú˙ý˙˙˙˙˙ý˙ü˙ü˙ţ˙ţ˙˙˙ü˙ţ˙˙˙˙˙˙˙ţ˙ý˙ý˙˙˙ţ˙ţ˙ţ˙˙˙ü˙ý˙˙˙ţ˙˙˙˙˙˙˙˙˙˙˙˙˙ţ˙ţ˙ý˙˙˙˙˙ţ˙ý˙ý˙˙˙ü˙ý˙˙˙˙˙˙˙ü˙ý˙˙˙ţ˙ţ˙˙˙ţ˙ţ˙ű˙ů˙ű˙ţ˙ţ˙˙˙˙˙˙˙˙˙ü˙ţ˙˙˙ý˙˙˙˙˙ţ˙ţ˙ţ˙ţ˙ţ˙ţ˙˙˙ţ˙˙˙˙˙ţ˙ý˙˙˙ţ˙ţ˙˙˙˙˙˙˙˙˙ţ˙˙˙ţ˙˙˙ţ˙ţ˙˙˙ý˙ů˙ú˙ţ˙˙˙ţ˙ý˙ý˙ţ˙ţ˙˙˙˙˙ţ˙ý˙ţ˙ý˙ý˙ţ˙ü˙ü˙ţ˙ţ˙˙˙ý˙ü˙˙˙˙˙ý˙ü˙ü˙ţ˙ţ˙ţ˙ţ˙ţ˙ţ˙ţ˙˙˙ý˙˙˙˙˙˙˙ţ˙ţ˙˙˙ţ˙˙˙ţ˙˙˙ţ˙˙˙˙˙ţ˙ý˙ý˙ü˙ű˙ţ˙˙˙ý˙ţ˙ţ˙˙˙˙˙ý˙ü˙ţ˙ţ˙ý˙ý˙ţ˙˙˙ţ˙˙˙˙˙ţ˙ű˙ý˙ý˙ý˙˙˙ţ˙ý˙ý˙˙˙˙˙˙˙˙˙˙˙˙˙ţ˙˙˙˙˙ţ˙ţ˙ü˙˙˙˙˙ü˙ý˙˙˙˙˙˙˙ţ˙˙˙˙˙˙˙ţ˙ţ˙ü˙ü˙ţ˙˙˙˙˙˙˙˙˙˙˙ý˙ü˙˙˙˙˙ü˙ţ˙˙˙˙˙ý˙ü˙˙˙˙˙ţ˙˙˙˙˙ý˙ţ˙˙˙˙˙ţ˙ý˙ű˙ú˙ý˙˙˙ţ˙ý˙ü˙ü˙ţ˙ý˙˙˙˙˙ý˙ţ˙˙˙ţ˙ý˙ü˙ü˙ţ˙ý˙ţ˙˙˙˙˙˙˙˙˙˙˙ţ˙˙˙˙˙˙˙ý˙ý˙ţ˙˙˙˙˙˙˙ţ˙˙˙˙˙ţ˙ţ˙ţ˙˙˙ű˙ü˙˙˙˙˙˙˙˙˙˙˙˙˙ý˙ý˙ý˙ý˙ý˙ý˙ţ˙˙˙ţ˙˙˙˙˙ü˙ű˙ý˙˙˙ţ˙ţ˙ţ˙ţ˙˙˙˙˙ý˙˙˙ţ˙˙˙˙˙ţ˙˙˙˙˙ý˙ú˙ü˙˙˙˙˙˙˙ţ˙ý˙˙˙ý˙ű˙˙˙ţ˙ý˙˙˙˙˙˙˙˙˙˙˙ý˙ý˙˙˙ţ˙ý˙ţ˙˙˙ţ˙ţ˙ţ˙ý˙˙˙˙˙˙˙ü˙ú˙ű˙˙˙˙˙ţ˙˙˙ţ˙˙˙˙˙ţ˙ý˙ü˙˙˙˙˙ţ˙ţ˙ţ˙˙˙ý˙ţ˙˙˙ţ˙ţ˙˙˙˙˙ý˙ű˙ý˙ţ˙ţ˙˙˙ţ˙ţ˙˙˙˙˙ü˙ü˙ý˙ţ˙˙˙ý˙ţ˙ü˙ţ˙˙˙˙˙ţ˙˙˙˙˙ý˙ű˙ü˙ţ˙˙˙ý˙ţ˙˙˙ţ˙ţ˙˙˙ţ˙˙˙ţ˙˙˙ţ˙˙˙ţ˙ţ˙˙˙ţ˙ü˙ý˙˙˙ţ˙˙˙˙˙ţ˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙ţ˙ý˙ý˙˙˙˙˙˙˙˙˙ý˙ű˙ü˙ţ˙˙˙˙˙ţ˙ü˙˙˙ţ˙˙˙ţ˙ý˙ý˙˙˙ţ˙ü˙ú˙ú˙ű˙˙˙ţ˙˙˙ţ˙ü˙ű˙ű˙ý˙ţ˙ý˙ţ˙˙˙ţ˙˙˙ţ˙ý˙˙˙˙˙ţ˙˙˙˙˙ţ˙˙˙˙˙˙˙ţ˙ţ˙˙˙ţ˙ţ˙ý˙˙˙ü˙ű˙ü˙˙˙˙˙˙˙˙˙˙˙˙˙ý˙ý˙ý˙ţ˙ţ˙ý˙ü˙˙˙˙˙˙˙˙˙ţ˙˙˙ţ˙˙˙˙˙˙˙ţ˙ú˙ű˙ţ˙˙˙˙˙˙˙˙˙ý˙ţ˙˙˙˙˙˙˙ţ˙ý˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙ţ˙ü˙ü˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙ţ˙ý˙ý˙˙˙˙˙˙˙˙˙ü˙ý˙ý˙ü˙ü˙ü˙ţ˙˙˙˙˙˙˙ý˙ü˙˙˙ţ˙ý˙ţ˙˙˙ţ˙ý˙ţ˙˙˙˙˙˙˙ţ˙ţ˙˙˙˙˙ý˙ţ˙˙˙˙˙˙˙˙˙˙˙ţ˙ţ˙ţ˙ţ˙ţ˙ţ˙˙˙ţ˙ü˙ü˙ü˙ű˙˙˙˙˙ü˙ý˙˙˙˙˙ţ˙ţ˙ţ˙˙˙˙˙ţ˙˙˙˙˙ý˙ý˙ţ˙ý˙˙˙˙˙ţ˙ţ˙ú˙ţ˙˙˙ú˙ü˙ţ˙ţ˙˙˙˙˙˙˙˙˙˙˙˙˙ţ˙ţ˙ţ˙ţ˙ţ˙˙˙˙˙˙˙˙˙ţ˙˙˙ţ˙ţ˙ţ˙ţ˙˙˙˙˙ţ˙˙˙˙˙ţ˙ţ˙ý˙ţ˙ţ˙ý˙˙˙ţ˙ţ˙ţ˙ţ˙˙˙˙˙ţ˙ü˙ü˙ý˙ţ˙ü˙ü˙˙˙˙˙ű˙ű˙ü˙ţ˙ü˙ü˙ţ˙ţ˙˙˙ţ˙ţ˙˙˙ţ˙ü˙ý˙˙˙˙˙˙˙ţ˙ţ˙˙˙˙˙˙˙˙˙ü˙ü˙ţ˙˙˙ţ˙ý˙ţ˙˙˙ţ˙ţ˙ţ˙ý˙ţ˙˙˙˙˙˙˙˙˙ţ˙ü˙ý˙ţ˙˙˙ý˙ý˙˙˙˙˙ţ˙ü˙ţ˙˙˙˙˙˙˙˙˙˙˙ţ˙ţ˙˙˙ţ˙ţ˙˙˙˙˙˙˙ţ˙ý˙ţ˙˙˙˙˙ý˙ţ˙ý˙ü˙ý˙˙˙ţ˙ţ˙ü˙ý˙˙˙ţ˙ý˙˙˙˙˙ţ˙ţ˙ţ˙ý˙ţ˙ţ˙ţ˙˙˙˙˙ţ˙ý˙ţ˙ý˙˙˙˙˙ý˙˙˙˙˙˙˙˙˙ţ˙ü˙ý˙ţ˙ţ˙ţ˙˙˙˙˙ţ˙˙˙˙˙˙˙˙˙ţ˙ý˙˙˙˙˙ţ˙˙˙˙˙ţ˙˙˙˙˙˙˙˙˙ý˙ţ˙˙˙˙˙ţ˙˙˙˙˙˙˙ý˙˙˙˙˙ţ˙˙˙˙˙ţ˙ü˙˙˙˙˙˙˙ţ˙˙˙˙˙ţ˙˙˙ţ˙ü˙ü˙ü˙ü˙ţ˙˙˙ý˙ü˙ţ˙ý˙ţ˙ţ˙ý˙ý˙ţ˙ý˙ţ˙˙˙˙˙˙˙ý˙ţ˙˙˙˙˙˙˙˙˙ý˙ý˙˙˙˙˙ţ˙ţ˙ţ˙ţ˙˙˙ţ˙˙˙˙˙˙˙˙˙˙˙ý˙ý˙ţ˙˙˙˙˙˙˙˙˙ţ˙ý˙ú˙ţ˙˙˙ü˙ý˙˙˙˙˙˙˙ţ˙ý˙ý˙˙˙˙˙ţ˙ý˙ý˙ţ˙ý˙ţ˙˙˙ý˙ü˙ţ˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙ţ˙˙˙˙˙ţ˙˙˙˙˙ţ˙˙˙˙˙ţ˙ý˙ý˙˙˙ý˙ý˙˙˙ţ˙ţ˙ü˙ý˙ý˙ű˙ü˙ţ˙˙˙˙˙˙˙ţ˙ţ˙˙˙˙˙ţ˙˙˙˙˙ţ˙˙˙˙˙ţ˙ý˙˙˙ý˙ţ˙˙˙˙˙˙˙ţ˙ţ˙ţ˙ţ˙ü˙ý˙˙˙ţ˙ţ˙˙˙˙˙˙˙ü˙˙˙˙˙ţ˙ţ˙ţ˙˙˙˙˙˙˙˙˙ţ˙ý˙˙˙˙˙ţ˙ý˙˙˙ţ˙˙˙ţ˙ý˙˙˙ţ˙˙˙ţ˙˙˙˙˙ţ˙˙˙˙˙ý˙ţ˙˙˙˙˙ţ˙ý˙ţ˙ý˙ý˙˙˙˙˙˙˙ţ˙ý˙ü˙ý˙ý˙˙˙ţ˙ţ˙ţ˙ţ˙ţ˙ţ˙˙˙˙˙˙˙˙˙ý˙ţ˙ü˙ţ˙˙˙ţ˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙ý˙ţ˙˙˙˙˙ţ˙ý˙ü˙ü˙ű˙ý˙ţ˙ý˙ý˙ý˙ű˙ý˙ý˙ý˙ý˙ţ˙ű˙ţ˙˙˙ţ˙˙˙˙˙ţ˙˙˙ü˙ű˙ý˙˙˙ţ˙ü˙ý˙˙˙ý˙ý˙ý˙ţ˙˙˙˙˙ý˙ý˙˙˙ţ˙˙˙ý˙ý˙ý˙ý˙ţ˙ţ˙ţ˙ý˙ţ˙˙˙˙˙˙˙˙˙˙˙ý˙ý˙ţ˙ţ˙˙˙ý˙ű˙ý˙˙˙˙˙˙˙ý˙ý˙˙˙˙˙ý˙ţ˙˙˙ý˙ý˙˙˙˙˙˙˙ţ˙ű˙ů˙ú˙ţ˙˙˙ţ˙˙˙ţ˙˙˙ţ˙˙˙ţ˙ţ˙ü˙ú˙ű˙˙˙˙˙˙˙˙˙ţ˙˙˙˙˙ţ˙˙˙ţ˙˙˙˙˙˙˙ý˙ý˙ý˙ţ˙˙˙ţ˙ţ˙ý˙ţ˙˙˙˙˙ţ˙˙˙˙˙ţ˙˙˙ţ˙ý˙ţ˙˙˙˙˙ý˙ţ˙˙˙ţ˙˙˙ţ˙˙˙ţ˙ü˙ű˙ţ˙ý˙˙˙ţ˙ţ˙ý˙ý˙ý˙ý˙ű˙ü˙ţ˙˙˙ţ˙ţ˙ý˙ú˙ú˙ú˙ű˙˙˙˙˙ţ˙˙˙˙˙ý˙ü˙˙˙˙˙ţ˙˙˙˙˙˙˙ţ˙˙˙ý˙ý˙˙˙˙˙˙˙ţ˙ţ˙˙˙ţ˙ţ˙˙˙˙˙ţ˙˙˙˙˙ý˙ü˙ü˙ţ˙ý˙ü˙ý˙ţ˙˙˙˙˙ü˙ű˙ţ˙ü˙ý˙ý˙ţ˙ý˙ý˙˙˙ţ˙ü˙ý˙˙˙ţ˙ü˙ý˙ţ˙ţ˙˙˙ý˙ţ˙˙˙ý˙ţ˙ţ˙˙˙˙˙ţ˙ţ˙ý˙ü˙ţ˙˙˙˙˙˙˙˙˙ţ˙ü˙ţ˙ţ˙˙˙ü˙ý˙˙˙ý˙˙˙˙˙˙˙˙˙ţ˙˙˙˙˙ţ˙ü˙˙˙˙˙˙˙ý˙ţ˙˙˙˙˙˙˙ţ˙˙˙˙˙˙˙˙˙˙˙ý˙˙˙˙˙ţ˙ý˙˙˙ţ˙ţ˙ţ˙˙˙ţ˙˙˙˙˙ý˙ţ˙˙˙ý˙ý˙ü˙ý˙˙˙˙˙ţ˙ţ˙˙˙ţ˙˙˙˙˙ý˙ü˙ü˙ü˙ý˙ţ˙ý˙ý˙˙˙˙˙ţ˙ý˙ý˙˙˙ţ˙ý˙˙˙˙˙˙˙ţ˙ý˙˙˙˙˙ű˙ű˙ű˙ű˙ý˙ţ˙ţ˙ţ˙˙˙˙˙ý˙ý˙ţ˙˙˙˙˙˙˙ţ˙˙˙˙˙ý˙ţ˙˙˙˙˙˙˙˙˙˙˙ţ˙˙˙ţ˙ţ˙˙˙ţ˙ü˙ţ˙˙˙˙˙˙˙˙˙˙˙ţ˙ü˙ţ˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙ý˙˙˙ý˙ü˙ţ˙ţ˙ţ˙ý˙˙˙ţ˙ý˙ý˙ţ˙ý˙ţ˙ţ˙ü˙ţ˙ý˙ý˙ţ˙˙˙˙˙˙˙ţ˙˙˙˙˙ý˙ű˙ü˙ţ˙˙˙˙˙˙˙˙˙ý˙˙˙˙˙ţ˙ý˙˙˙˙˙˙˙˙˙ţ˙ý˙˙˙˙˙˙˙ţ˙ý˙ý˙˙˙˙˙˙˙ţ˙ý˙ü˙ý˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙ý˙˙˙˙˙ţ˙ý˙ü˙ü˙ü˙ü˙ü˙ü˙ý˙˙˙˙˙ý˙ţ˙˙˙˙˙˙˙˙˙˙˙ţ˙ţ˙˙˙ţ˙ţ˙˙˙ţ˙ü˙ţ˙˙˙ţ˙ý˙ý˙˙˙˙˙˙˙ţ˙ţ˙˙˙ţ˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙ţ˙˙˙ţ˙˙˙ţ˙ţ˙ţ˙ţ˙˙˙ţ˙ţ˙ţ˙˙˙˙˙ţ˙ţ˙ţ˙˙˙ü˙ű˙˙˙˙˙ý˙ţ˙˙˙˙˙ţ˙ű˙ű˙ţ˙˙˙ţ˙˙˙ţ˙ţ˙˙˙˙˙˙˙ţ˙ű˙ü˙˙˙ţ˙ű˙ü˙˙˙˙˙˙˙˙˙˙˙ü˙ü˙˙˙˙˙ü˙ţ˙ţ˙ý˙˙˙˙˙ţ˙ý˙ý˙˙˙˙˙˙˙˙˙˙˙ţ˙ý˙ţ˙ý˙˙˙˙˙˙˙ţ˙˙˙ý˙ü˙ţ˙ţ˙ţ˙ţ˙ü˙˙˙˙˙˙˙ý˙ý˙˙˙ý˙ý˙˙˙˙˙˙˙˙˙ý˙ý˙ţ˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙ţ˙ý˙ý˙˙˙˙˙ţ˙ţ˙˙˙ţ˙ý˙ý˙ţ˙˙˙˙˙ţ˙˙˙˙˙˙˙ý˙ü˙ţ˙ţ˙ţ˙˙˙ţ˙˙˙˙˙ü˙ü˙˙˙ţ˙ţ˙ţ˙ý˙ţ˙˙˙˙˙ý˙˙˙ţ˙ý˙ţ˙˙˙˙˙ţ˙ý˙˙˙˙˙˙˙˙˙˙˙ý˙ţ˙ţ˙ţ˙ţ˙ţ˙ţ˙ţ˙ţ˙ţ˙˙˙˙˙˙˙˙˙˙˙ţ˙ú˙ů˙ü˙ţ˙ý˙ý˙˙˙ţ˙ý˙˙˙ţ˙ü˙ý˙˙˙ý˙ú˙ý˙ý˙ű˙ű˙˙˙ţ˙ý˙ű˙ü˙ţ˙ţ˙˙˙ý˙ü˙ţ˙˙˙ţ˙ý˙ţ˙ý˙ţ˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙ţ˙ü˙ü˙ý˙˙˙ţ˙ý˙ü˙˙˙˙˙ţ˙ţ˙ţ˙ţ˙ţ˙ý˙ü˙ü˙ţ˙ţ˙˙˙˙˙ţ˙˙˙˙˙ý˙ţ˙˙˙˙˙ü˙ű˙ý˙ţ˙ţ˙ţ˙ţ˙ţ˙ţ˙ü˙ţ˙ţ˙˙˙˙˙ţ˙˙˙˙˙ý˙ţ˙ý˙ý˙ţ˙˙˙˙˙ţ˙˙˙ţ˙ý˙˙˙˙˙ţ˙ý˙ţ˙ý˙ý˙ţ˙ţ˙ű˙ů˙ú˙ý˙ţ˙ţ˙ţ˙ţ˙ţ˙˙˙ţ˙ý˙ţ˙ý˙˙˙˙˙˙˙˙˙˙˙˙˙ý˙ţ˙˙˙˙˙ý˙ţ˙˙˙ý˙ű˙ú˙ü˙˙˙ý˙ţ˙˙˙˙˙˙˙ý˙ý˙ţ˙˙˙˙˙˙˙˙˙˙˙ý˙ü˙ţ˙˙˙˙˙ţ˙˙˙˙˙˙˙˙˙ţ˙˙˙˙˙ý˙ţ˙ý˙ü˙˙˙˙˙ü˙ý˙˙˙ţ˙ú˙ů˙ţ˙˙˙˙˙˙˙ţ˙ý˙ţ˙ý˙ţ˙ý˙ţ˙ţ˙˙˙˙˙ţ˙˙˙ţ˙ý˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙ţ˙ü˙ü˙ý˙˙˙˙˙ý˙˙˙ţ˙ţ˙ţ˙ţ˙˙˙ţ˙ý˙ý˙˙˙˙˙˙˙˙˙ü˙ů˙ü˙˙˙ý˙˙˙˙˙ţ˙ý˙˙˙ü˙ý˙ţ˙ţ˙ý˙ý˙˙˙˙˙˙˙ü˙ţ˙ţ˙˙˙˙˙˙˙ţ˙ţ˙˙˙ţ˙˙˙˙˙˙˙˙˙˙˙˙˙ţ˙ţ˙˙˙˙˙˙˙ţ˙ţ˙˙˙˙˙ý˙ü˙ţ˙˙˙ý˙ü˙˙˙˙˙ţ˙˙˙˙˙ţ˙˙˙˙˙ý˙ý˙ü˙ţ˙˙˙ý˙ý˙˙˙˙˙ţ˙˙˙˙˙ţ˙ţ˙˙˙˙˙˙˙˙˙ţ˙ţ˙ý˙ţ˙˙˙ţ˙˙˙˙˙ţ˙˙˙˙˙ú˙ű˙˙˙˙˙ü˙ý˙ţ˙ý˙ţ˙ü˙ý˙˙˙ţ˙ü˙ű˙ý˙ý˙˙˙˙˙ţ˙ý˙˙˙ý˙ţ˙˙˙ţ˙˙˙ţ˙ý˙˙˙˙˙ţ˙ý˙˙˙ý˙ţ˙ý˙˙˙˙˙˙˙ţ˙˙˙ţ˙ţ˙˙˙ý˙ý˙ţ˙˙˙ý˙˙˙ý˙ţ˙˙˙˙˙ţ˙ü˙ý˙˙˙ţ˙ý˙ü˙ţ˙ý˙ý˙ý˙ţ˙˙˙˙˙˙˙˙˙˙˙˙˙ţ˙ţ˙ţ˙ţ˙ţ˙˙˙ü˙ý˙ţ˙ý˙˙˙˙˙ţ˙ý˙ţ˙ý˙ţ˙˙˙ţ˙ü˙ţ˙ý˙˙˙˙˙ţ˙ý˙ý˙ţ˙ţ˙˙˙˙˙˙˙˙˙ţ˙ý˙˙˙ţ˙ţ˙ţ˙ý˙ţ˙˙˙ţ˙˙˙ţ˙ţ˙ű˙ý˙˙˙˙˙ţ˙ý˙ý˙ü˙ű˙ţ˙˙˙ţ˙ţ˙˙˙˙˙ü˙ű˙˙˙ţ˙ţ˙ţ˙˙˙ţ˙ú˙ţ˙ţ˙˙˙˙˙ţ˙ţ˙ý˙ţ˙ţ˙ý˙˙˙˙˙ţ˙ű˙ý˙ý˙ý˙ţ˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙ý˙ţ˙ý˙ű˙ý˙˙˙ü˙ţ˙ţ˙˙˙ü˙˙˙˙˙ţ˙ü˙ű˙ú˙ü˙˙˙ü˙ú˙ţ˙ţ˙˙˙˙˙ţ˙ý˙ý˙ţ˙ü˙ű˙ý˙˙˙ţ˙ý˙ý˙˙˙ý˙˙˙˙˙˙˙˙˙ţ˙ý˙ţ˙˙˙˙˙˙˙˙˙˙˙ţ˙˙˙˙˙˙˙ţ˙ú˙ů˙ý˙˙˙ţ˙ü˙ý˙˙˙˙˙ţ˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙ţ˙˙˙ü˙ü˙ü˙ý˙˙˙ţ˙ü˙ý˙ý˙ţ˙ţ˙˙˙˙˙ţ˙ü˙ý˙ý˙˙˙˙˙ţ˙ţ˙ţ˙˙˙ţ˙ü˙ţ˙ý˙ţ˙˙˙˙˙ţ˙˙˙ţ˙˙˙ý˙˙˙˙˙ý˙ţ˙˙˙˙˙ý˙ý˙˙˙ţ˙˙˙ý˙ţ˙ý˙˙˙˙˙˙˙˙˙˙˙ý˙ţ˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙ţ˙˙˙˙˙ţ˙ţ˙˙˙˙˙ţ˙ţ˙ţ˙ý˙ţ˙˙˙˙˙˙˙˙˙ü˙˙˙ţ˙ţ˙ţ˙˙˙˙˙ţ˙ţ˙ţ˙˙˙˙˙ü˙ý˙˙˙˙˙˙˙ţ˙ý˙ţ˙ţ˙ü˙ü˙ý˙ţ˙ü˙ű˙ţ˙˙˙˙˙ý˙ü˙ý˙ý˙ţ˙ý˙˙˙ţ˙ý˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙ý˙ý˙˙˙ý˙ý˙ý˙ý˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙ý˙ý˙ţ˙ţ˙ţ˙ţ˙ý˙ý˙ţ˙˙˙ţ˙˙˙ţ˙˙˙ţ˙˙˙ţ˙ţ˙ţ˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙ţ˙ţ˙ý˙ţ˙ţ˙ţ˙ţ˙ţ˙˙˙˙˙˙˙˙˙˙˙˙˙ţ˙˙˙ţ˙˙˙ţ˙˙˙ý˙ý˙ţ˙˙˙˙˙ý˙ţ˙˙˙ţ˙˙˙˙˙ý˙ü˙˙˙˙˙˙˙˙˙˙˙ţ˙˙˙˙˙ý˙ţ˙˙˙ţ˙ţ˙˙˙ţ˙˙˙ý˙ţ˙ţ˙˙˙ý˙ý˙ţ˙˙˙˙˙˙˙ţ˙ţ˙ţ˙ţ˙ţ˙ü˙ü˙ý˙ý˙ţ˙ţ˙˙˙ţ˙ü˙ü˙ý˙˙˙ý˙ű˙ű˙˙˙ţ˙ţ˙ţ˙ţ˙ţ˙˙˙˙˙ţ˙ţ˙˙˙ţ˙˙˙ý˙ţ˙˙˙˙˙ţ˙ý˙ü˙ü˙ü˙˙˙˙˙ţ˙˙˙ý˙ţ˙˙˙˙˙˙˙ţ˙˙˙˙˙ţ˙ţ˙˙˙ţ˙˙˙ţ˙ţ˙ü˙ţ˙˙˙ţ˙ţ˙˙˙ţ˙ţ˙˙˙ţ˙ý˙ý˙˙˙˙˙ý˙ý˙ţ˙˙˙˙˙˙˙ţ˙ű˙ý˙ý˙ţ˙˙˙˙˙ţ˙˙˙ţ˙˙˙ü˙ý˙ý˙ý˙˙˙ţ˙ţ˙˙˙˙˙ý˙ü˙ý˙ü˙ý˙ţ˙ţ˙ţ˙˙˙˙˙˙˙ţ˙˙˙ţ˙˙˙˙˙ţ˙ţ˙˙˙˙˙˙˙˙˙ţ˙˙˙ţ˙ţ˙ţ˙ü˙ţ˙˙˙ţ˙ü˙ţ˙˙˙˙˙ü˙ý˙˙˙ţ˙ý˙ý˙ţ˙ý˙ű˙ü˙ý˙ý˙ţ˙˙˙˙˙˙˙ü˙ú˙ý˙ţ˙ý˙ţ˙˙˙˙˙ţ˙˙˙˙˙ü˙ý˙ţ˙ţ˙˙˙ţ˙ţ˙˙˙ţ˙˙˙˙˙˙˙˙˙ţ˙ţ˙ţ˙˙˙ţ˙ü˙ý˙ţ˙ý˙˙˙˙˙˙˙ý˙ü˙ţ˙ý˙ţ˙˙˙˙˙ý˙ţ˙˙˙˙˙˙˙˙˙ý˙ü˙ý˙ţ˙ţ˙ţ˙ţ˙ü˙ü˙ţ˙ü˙˙˙ţ˙ţ˙ý˙˙˙˙˙ţ˙ý˙ţ˙ý˙ţ˙ü˙ý˙ý˙ţ˙˙˙ţ˙ţ˙˙˙˙˙˙˙ţ˙ý˙˙˙˙˙ü˙ý˙ţ˙ý˙˙˙˙˙ţ˙˙˙˙˙˙˙ţ˙˙˙˙˙˙˙ţ˙˙˙˙˙ü˙ü˙ý˙˙˙˙˙ý˙ý˙˙˙˙˙ţ˙ý˙˙˙˙˙˙˙˙˙˙˙˙˙ý˙ý˙ý˙ű˙ü˙˙˙ţ˙˙˙ţ˙ţ˙˙˙˙˙ţ˙ţ˙ţ˙ţ˙ţ˙ý˙ţ˙˙˙ý˙ý˙˙˙ý˙ý˙ü˙ţ˙ű˙ý˙ţ˙˙˙ý˙ű˙ü˙ţ˙˙˙˙˙˙˙ţ˙ý˙ý˙ţ˙˙˙˙˙˙˙ţ˙ű˙ů˙ú˙ý˙ţ˙ţ˙ý˙ţ˙ý˙˙˙˙˙ţ˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙ý˙ý˙ý˙ý˙ţ˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙ţ˙ý˙ţ˙˙˙˙˙˙˙˙˙˙˙ý˙ţ˙˙˙ţ˙ţ˙ü˙ý˙ţ˙ţ˙ý˙ý˙ý˙ý˙ý˙ý˙ţ˙ý˙ţ˙ü˙ţ˙ţ˙ţ˙˙˙˙˙˙˙ý˙ü˙ü˙ý˙˙˙ţ˙ü˙ü˙˙˙ţ˙ú˙ú˙ţ˙ţ˙ü˙ý˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙ý˙˙˙ý˙ý˙˙˙˙˙˙˙ţ˙ü˙ý˙ţ˙˙˙˙˙˙˙˙˙ţ˙ţ˙ü˙ţ˙˙˙ţ˙ţ˙˙˙ţ˙˙˙ţ˙ţ˙ţ˙ţ˙˙˙˙˙˙˙ţ˙ţ˙˙˙˙˙˙˙˙˙˙˙˙˙ţ˙ý˙˙˙˙˙ţ˙˙˙ţ˙˙˙ţ˙˙˙ţ˙ţ˙˙˙ý˙ű˙ţ˙˙˙˙˙ý˙ý˙ý˙ý˙ý˙˙˙˙˙˙˙˙˙˙˙˙˙ţ˙ý˙ý˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙ţ˙˙˙ţ˙ţ˙˙˙ţ˙ţ˙˙˙˙˙ý˙ü˙˙˙ţ˙ţ˙ţ˙ţ˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙ţ˙ü˙ű˙ý˙˙˙ţ˙ţ˙˙˙ţ˙ţ˙ü˙ů˙ů˙ý˙ţ˙˙˙ţ˙˙˙˙˙˙˙ü˙ű˙ü˙˙˙˙˙ţ˙ý˙˙˙ţ˙ý˙ý˙˙˙ţ˙˙˙ţ˙ţ˙˙˙˙˙ý˙ý˙˙˙ý˙ü˙ţ˙˙˙ţ˙ţ˙ţ˙ţ˙ţ˙˙˙ţ˙˙˙ţ˙˙˙˙˙ţ˙˙˙˙˙ţ˙˙˙˙˙˙˙˙˙ý˙ý˙ţ˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙ţ˙ý˙ţ˙ý˙ţ˙˙˙˙˙˙˙˙˙ý˙ý˙ţ˙˙˙˙˙˙˙˙˙˙˙ţ˙ţ˙ý˙ţ˙˙˙˙˙˙˙ţ˙ţ˙ţ˙ý˙ţ˙˙˙˙˙˙˙ý˙ü˙ý˙˙˙˙˙˙˙ü˙ü˙ý˙ţ˙ţ˙˙˙˙˙˙˙˙˙ţ˙ü˙ţ˙ţ˙ý˙ţ˙ţ˙˙˙ý˙˙˙˙˙˙˙ţ˙˙˙ţ˙ţ˙˙˙˙˙ü˙ý˙˙˙ţ˙ý˙˙˙ý˙ü˙ý˙ý˙ţ˙ţ˙˙˙˙˙˙˙˙˙˙˙˙˙ţ˙ý˙ý˙ţ˙ý˙ţ˙ţ˙˙˙˙˙ţ˙ý˙ţ˙ţ˙ţ˙˙˙˙˙˙˙˙˙˙˙˙˙ý˙ý˙ţ˙˙˙ý˙ţ˙ţ˙ţ˙ţ˙˙˙ý˙ţ˙˙˙ţ˙ţ˙ţ˙ţ˙˙˙˙˙ţ˙ţ˙ű˙ű˙ţ˙˙˙ţ˙ţ˙ţ˙˙˙ţ˙ü˙ű˙ý˙˙˙˙˙ţ˙ý˙ţ˙ü˙ü˙ţ˙˙˙˙˙˙˙ü˙˙˙ý˙ý˙ţ˙˙˙˙˙˙˙˙˙˙˙ţ˙ý˙ţ˙ü˙ý˙˙˙ţ˙ý˙˙˙˙˙ţ˙ţ˙ţ˙ţ˙ţ˙˙˙ţ˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙ţ˙ţ˙˙˙ţ˙˙˙˙˙ţ˙ţ˙ţ˙˙˙ţ˙ţ˙˙˙ţ˙ý˙˙˙ţ˙˙˙˙˙ý˙˙˙˙˙ţ˙ţ˙˙˙˙˙˙˙˙˙˙˙˙˙ţ˙˙˙˙˙ý˙ý˙ý˙ý˙ý˙ý˙ţ˙ý˙ţ˙ţ˙˙˙˙˙ý˙˙˙˙˙ţ˙ţ˙˙˙˙˙ţ˙˙˙ţ˙ţ˙ţ˙˙˙ţ˙˙˙ţ˙ţ˙ţ˙ý˙ý˙ý˙ý˙˙˙˙˙ţ˙ţ˙ţ˙ţ˙ţ˙ţ˙ţ˙ţ˙ţ˙ţ˙ţ˙ţ˙˙˙ţ˙ý˙ţ˙˙˙˙˙˙˙ý˙ý˙ţ˙ţ˙˙˙˙˙ý˙ţ˙˙˙ţ˙ý˙˙˙˙˙˙˙˙˙˙˙ý˙ý˙ţ˙˙˙˙˙˙˙˙˙ţ˙˙˙ţ˙ţ˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙ţ˙˙˙ţ˙ü˙ţ˙˙˙˙˙˙˙ţ˙ý˙ţ˙ţ˙ţ˙˙˙˙˙ý˙ţ˙ţ˙ţ˙˙˙ţ˙ţ˙˙˙˙˙ý˙ţ˙˙˙˙˙ý˙ý˙ý˙ţ˙ű˙ü˙˙˙˙˙˙˙˙˙ţ˙ý˙ý˙˙˙˙˙ţ˙ý˙ý˙ý˙ý˙ý˙ţ˙˙˙˙˙˙˙ţ˙ü˙ű˙ý˙˙˙ţ˙ý˙˙˙˙˙˙˙˙˙˙˙ü˙ü˙ţ˙˙˙ţ˙ţ˙˙˙˙˙˙˙ý˙ţ˙˙˙˙˙˙˙ţ˙˙˙ý˙ü˙˙˙˙˙ţ˙ý˙ü˙ý˙ţ˙˙˙ý˙˙˙ţ˙˙˙ţ˙ţ˙ţ˙˙˙ţ˙˙˙˙˙ţ˙ý˙˙˙ý˙ý˙ü˙ţ˙ü˙ú˙ü˙ţ˙ý˙ţ˙ü˙ü˙˙˙˙˙˙˙˙˙ý˙ţ˙ý˙ţ˙ţ˙ý˙ý˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙ţ˙˙˙ţ˙˙˙˙˙˙˙˙˙ý˙ű˙ý˙˙˙˙˙˙˙˙˙˙˙˙˙ţ˙ţ˙˙˙˙˙ý˙ý˙ţ˙ţ˙˙˙˙˙˙˙ţ˙˙˙˙˙ţ˙ý˙ý˙ý˙ţ˙ý˙ý˙ý˙ű˙ü˙˙˙˙˙ţ˙ţ˙˙˙ţ˙ţ˙˙˙ţ˙˙˙ý˙ü˙ý˙˙˙˙˙ţ˙˙˙˙˙ţ˙ţ˙ţ˙ţ˙˙˙ý˙ţ˙˙˙ţ˙ţ˙ţ˙ţ˙ý˙ü˙ţ˙˙˙˙˙˙˙˙˙˙˙˙˙ţ˙˙˙˙˙ý˙ü˙ţ˙˙˙ý˙˙˙ţ˙ű˙ý˙˙˙ţ˙˙˙ţ˙ý˙ţ˙˙˙˙˙˙˙˙˙˙˙˙˙ţ˙ü˙˙˙˙˙˙˙˙˙ţ˙ý˙ü˙ţ˙˙˙ţ˙˙˙ţ˙˙˙˙˙ţ˙˙˙ţ˙˙˙ţ˙ţ˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙ý˙˙˙˙˙˙˙ţ˙ý˙ţ˙˙˙ý˙˙˙˙˙ţ˙ţ˙˙˙˙˙ţ˙˙˙˙˙ý˙ý˙ý˙ý˙ţ˙ţ˙ü˙ű˙ý˙ý˙ý˙ý˙ţ˙˙˙ţ˙ţ˙˙˙˙˙ý˙˙˙˙˙˙˙˙˙ţ˙˙˙˙˙ý˙ý˙ţ˙ţ˙ý˙ţ˙ű˙ţ˙˙˙˙˙˙˙ţ˙ý˙˙˙ý˙ú˙ţ˙˙˙ü˙ţ˙˙˙ţ˙˙˙˙˙ţ˙ţ˙ţ˙ţ˙˙˙ý˙ü˙˙˙˙˙ü˙˙˙˙˙˙˙˙˙ü˙ű˙ý˙˙˙˙˙˙˙ý˙ţ˙˙˙˙˙ţ˙ý˙ţ˙ţ˙ý˙ţ˙˙˙˙˙ţ˙˙˙˙˙ý˙ý˙˙˙ţ˙˙˙˙˙ţ˙˙˙ţ˙˙˙˙˙ü˙ý˙ţ˙ţ˙ý˙ý˙ű˙˙˙˙˙ü˙˙˙˙˙ţ˙˙˙ý˙˙˙ţ˙ű˙ü˙ý˙˙˙ţ˙ţ˙ţ˙ý˙ý˙˙˙˙˙˙˙˙˙ţ˙ý˙ý˙ţ˙˙˙ţ˙ţ˙ý˙ü˙ý˙˙˙ý˙ü˙ţ˙ţ˙ţ˙˙˙˙˙ý˙ţ˙˙˙˙˙˙˙˙˙ţ˙ü˙ű˙ý˙˙˙˙˙˙˙˙˙˙˙˙˙ý˙ý˙ţ˙˙˙˙˙ý˙ţ˙˙˙ţ˙ý˙ţ˙˙˙˙˙ý˙ű˙ý˙ţ˙ţ˙˙˙ţ˙˙˙ţ˙ý˙˙˙˙˙ţ˙ý˙ý˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙ţ˙˙˙˙˙ţ˙˙˙˙˙ţ˙ţ˙˙˙ü˙ý˙˙˙ţ˙ţ˙ý˙ţ˙˙˙ţ˙˙˙ţ˙ţ˙ű˙ý˙˙˙˙˙ý˙ţ˙ý˙ý˙˙˙˙˙ţ˙ţ˙ý˙˙˙˙˙˙˙˙˙˙˙ţ˙ý˙ý˙˙˙ţ˙˙˙ţ˙ü˙˙˙˙˙ü˙ý˙ű˙ý˙ý˙ü˙ý˙ü˙ü˙˙˙ý˙ý˙˙˙ţ˙ý˙ý˙˙˙˙˙˙˙˙˙˙˙ţ˙ü˙ţ˙ý˙˙˙˙˙ţ˙ý˙ţ˙ţ˙˙˙˙˙ţ˙ü˙ţ˙ţ˙ý˙ý˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙ţ˙ý˙ţ˙ý˙˙˙˙˙ý˙ţ˙ţ˙ý˙˙˙˙˙ţ˙˙˙˙˙ţ˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙ý˙ţ˙˙˙˙˙ţ˙ţ˙ý˙ý˙ý˙˙˙˙˙ţ˙ţ˙ţ˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙ý˙ţ˙ţ˙ţ˙˙˙ţ˙˙˙˙˙˙˙ţ˙˙˙˙˙ţ˙ţ˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙ý˙ý˙ü˙ű˙ü˙˙˙˙˙ý˙ţ˙˙˙˙˙˙˙ţ˙ţ˙˙˙˙˙˙˙ţ˙˙˙˙˙˙˙ţ˙˙˙˙˙ţ˙˙˙ü˙ţ˙ţ˙ý˙ý˙ţ˙˙˙˙˙ü˙ý˙ţ˙˙˙ţ˙˙˙ű˙ű˙ţ˙ü˙ű˙˙˙˙˙˙˙ţ˙ţ˙ý˙ţ˙ţ˙ü˙ţ˙˙˙˙˙˙˙˙˙ţ˙ţ˙ţ˙ţ˙ü˙˙˙ţ˙˙˙ţ˙ü˙ţ˙˙˙˙˙˙˙˙˙ý˙ý˙ţ˙ţ˙ü˙ý˙ţ˙ü˙ű˙ţ˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙ţ˙ý˙ü˙ý˙˙˙˙˙˙˙˙˙ý˙ü˙ý˙˙˙˙˙˙˙ý˙˙˙˙˙ţ˙ý˙ý˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙ţ˙˙˙˙˙˙˙ţ˙˙˙ý˙ţ˙ţ˙ý˙ţ˙ý˙ţ˙ţ˙ţ˙ţ˙ý˙ü˙˙˙ţ˙ü˙ű˙ű˙ţ˙˙˙ţ˙˙˙ţ˙ţ˙ţ˙ţ˙ţ˙˙˙ţ˙ţ˙ú˙ü˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙ý˙ű˙ü˙˙˙˙˙˙˙ý˙˙˙ţ˙ţ˙˙˙ţ˙ţ˙ţ˙˙˙˙˙ü˙ý˙ţ˙ţ˙˙˙˙˙ü˙ü˙ţ˙˙˙ţ˙ţ˙˙˙˙˙˙˙ý˙ţ˙ý˙ţ˙˙˙ţ˙ţ˙˙˙˙˙ý˙ý˙˙˙˙˙ţ˙ţ˙ý˙ţ˙˙˙ý˙˙˙˙˙˙˙˙˙ü˙ű˙ţ˙ţ˙ţ˙ţ˙ţ˙ţ˙ţ˙ţ˙˙˙ţ˙˙˙ţ˙ţ˙˙˙˙˙ţ˙˙˙˙˙ý˙ü˙ý˙ţ˙ţ˙ţ˙ţ˙ý˙ý˙ţ˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙ý˙˙˙ţ˙ý˙ý˙ý˙ű˙ű˙ü˙ţ˙ý˙ý˙ţ˙ţ˙ţ˙ý˙ţ˙ý˙ţ˙˙˙ţ˙ý˙ü˙ţ˙ý˙ü˙ţ˙˙˙˙˙ţ˙ţ˙ţ˙ţ˙ţ˙ü˙ü˙ţ˙ý˙ý˙˙˙˙˙˙˙ü˙ţ˙ţ˙˙˙ţ˙˙˙ţ˙˙˙˙˙˙˙ý˙ü˙ü˙ţ˙˙˙˙˙ü˙ý˙ý˙ţ˙ý˙ţ˙˙˙˙˙˙˙ţ˙ý˙ý˙ü˙˙˙˙˙˙˙ý˙ţ˙ý˙ý˙˙˙ý˙˙˙˙˙˙˙˙˙ţ˙˙˙ţ˙ý˙ú˙ü˙ý˙ţ˙ţ˙ţ˙ţ˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙ý˙˙˙ţ˙˙˙˙˙˙˙ý˙ţ˙˙˙ý˙ţ˙ý˙˙˙˙˙ţ˙ý˙ý˙ý˙ţ˙˙˙˙˙ţ˙˙˙˙˙ý˙˙˙˙˙ţ˙ý˙˙˙ý˙ü˙ű˙ţ˙˙˙ű˙ţ˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙ý˙ý˙ţ˙ţ˙ý˙˙˙˙˙˙˙˙˙ţ˙ü˙ţ˙˙˙ý˙˙˙˙˙˙˙˙˙ţ˙ý˙ţ˙ţ˙ű˙ţ˙ţ˙ü˙ţ˙ý˙ü˙ţ˙ţ˙ţ˙ţ˙ü˙ý˙˙˙˙˙ţ˙ţ˙˙˙ţ˙ţ˙ý˙ü˙ü˙ý˙ţ˙˙˙˙˙˙˙˙˙˙˙ý˙ţ˙ţ˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙ý˙ý˙ţ˙ţ˙ý˙˙˙˙˙ţ˙˙˙˙˙ţ˙ţ˙˙˙˙˙ý˙ţ˙˙˙˙˙ý˙ţ˙˙˙˙˙˙˙ţ˙ý˙ţ˙˙˙ţ˙ý˙ý˙ţ˙˙˙˙˙ý˙ü˙ű˙ú˙ú˙ý˙˙˙ţ˙˙˙ţ˙ü˙ű˙ý˙˙˙˙˙˙˙˙˙˙˙˙˙ţ˙ü˙ý˙˙˙˙˙˙˙˙˙ý˙˙˙ţ˙ü˙ţ˙ţ˙ţ˙ţ˙ţ˙˙˙ţ˙ý˙ţ˙˙˙˙˙ţ˙ţ˙ý˙ţ˙ţ˙ü˙ţ˙˙˙˙˙˙˙˙˙ý˙ţ˙˙˙˙˙˙˙˙˙˙˙ţ˙ý˙ţ˙˙˙˙˙˙˙˙˙ţ˙ü˙ţ˙ţ˙ţ˙˙˙ý˙ü˙ü˙˙˙˙˙˙˙˙˙˙˙ţ˙˙˙˙˙˙˙˙˙ţ˙˙˙˙˙ţ˙˙˙˙˙ý˙˙˙˙˙ţ˙˙˙ý˙ý˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙ţ˙˙˙ţ˙ü˙ţ˙ý˙˙˙˙˙˙˙˙˙ý˙ü˙ý˙ţ˙˙˙˙˙ţ˙ý˙ţ˙˙˙ţ˙˙˙ý˙ţ˙˙˙˙˙ţ˙ý˙˙˙ü˙ű˙ţ˙ý˙˙˙˙˙˙˙ţ˙˙˙ý˙ű˙ű˙ú˙ţ˙˙˙˙˙˙˙˙˙˙˙˙˙ţ˙ţ˙ţ˙˙˙ü˙ű˙ţ˙˙˙˙˙˙˙ţ˙ý˙ü˙ý˙ţ˙ý˙ţ˙˙˙ţ˙ţ˙˙˙ţ˙ţ˙ţ˙ţ˙˙˙ţ˙ţ˙ţ˙ţ˙ţ˙ţ˙ţ˙ţ˙ţ˙ý˙ü˙˙˙ţ˙ý˙˙˙ý˙˙˙˙˙ţ˙ý˙ţ˙˙˙ţ˙˙˙ţ˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙ý˙ţ˙˙˙˙˙˙˙ý˙ţ˙˙˙ţ˙ý˙ű˙ű˙ţ˙˙˙˙˙˙˙ű˙ű˙ţ˙ý˙ţ˙ý˙˙˙˙˙ţ˙ţ˙ţ˙ţ˙˙˙˙˙˙˙˙˙ţ˙ý˙˙˙˙˙˙˙ý˙ţ˙˙˙˙˙ţ˙ű˙ü˙ţ˙˙˙ţ˙ý˙ţ˙ţ˙ţ˙˙˙˙˙˙˙ţ˙˙˙ü˙ý˙ţ˙ţ˙ţ˙˙˙ţ˙˙˙˙˙˙˙˙˙ý˙ü˙ţ˙ţ˙˙˙ü˙˙˙ţ˙ý˙˙˙˙˙˙˙ţ˙ý˙ý˙˙˙˙˙˙˙ţ˙ţ˙˙˙ţ˙ţ˙ţ˙ţ˙ý˙ü˙ţ˙˙˙ý˙ý˙˙˙˙˙ý˙ü˙ţ˙˙˙ţ˙ű˙ý˙ţ˙ţ˙˙˙ţ˙ţ˙ţ˙ý˙˙˙ý˙˙˙˙˙ţ˙ý˙˙˙ý˙ý˙˙˙ý˙˙˙˙˙˙˙˙˙˙˙˙˙ţ˙ţ˙ţ˙ü˙ű˙ű˙ţ˙ţ˙˙˙˙˙˙˙˙˙ý˙ý˙ţ˙ţ˙˙˙˙˙˙˙ţ˙ţ˙ý˙ţ˙ţ˙ţ˙˙˙ţ˙ý˙ý˙˙˙˙˙ţ˙ý˙˙˙˙˙˙˙˙˙˙˙˙˙ý˙ü˙ţ˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙ý˙ţ˙˙˙ţ˙ţ˙ý˙ű˙ý˙˙˙ţ˙ţ˙˙˙˙˙ţ˙ţ˙ü˙ţ˙ţ˙˙˙ţ˙˙˙ţ˙˙˙˙˙˙˙ý˙˙˙˙˙ü˙ű˙ü˙ü˙ý˙ţ˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙ţ˙˙˙˙˙˙˙˙˙˙˙ý˙ţ˙˙˙˙˙˙˙˙˙˙˙ţ˙ý˙ý˙ý˙ţ˙ű˙ű˙ţ˙˙˙˙˙ţ˙ý˙ű˙˙˙˙˙˙˙˙˙ţ˙ý˙ý˙ü˙ý˙˙˙˙˙˙˙˙˙ţ˙ý˙ý˙˙˙ţ˙ý˙ý˙ţ˙ý˙˙˙˙˙ü˙ü˙ý˙˙˙ţ˙˙˙˙˙ý˙˙˙ţ˙˙˙ţ˙ţ˙ţ˙ý˙˙˙˙˙˙˙˙˙ü˙ú˙ű˙ý˙˙˙ţ˙ý˙˙˙ý˙ý˙˙˙˙˙ý˙ţ˙ţ˙ý˙˙˙ţ˙˙˙ţ˙˙˙˙˙ţ˙ý˙ý˙ţ˙ţ˙˙˙˙˙ţ˙˙˙ţ˙ý˙˙˙˙˙˙˙˙˙˙˙˙˙ü˙ý˙˙˙ý˙ý˙ű˙ú˙ü˙ţ˙ţ˙˙˙ţ˙˙˙ţ˙ţ˙˙˙˙˙ţ˙ţ˙ţ˙˙˙˙˙ü˙ý˙˙˙ţ˙ý˙ý˙˙˙˙˙˙˙ţ˙ü˙ů˙ű˙ţ˙˙˙˙˙˙˙˙˙ţ˙ý˙ü˙ţ˙˙˙˙˙ţ˙˙˙ţ˙ţ˙ý˙ý˙ţ˙˙˙ţ˙˙˙˙˙ţ˙˙˙ý˙ý˙ý˙ý˙ý˙ý˙ţ˙˙˙˙˙ý˙ţ˙ý˙ű˙ţ˙˙˙ý˙ţ˙ü˙ý˙˙˙˙˙ţ˙ţ˙˙˙ý˙ú˙ü˙˙˙ţ˙ţ˙ý˙˙˙ţ˙˙˙ţ˙ţ˙ţ˙˙˙˙˙ţ˙ü˙ý˙ü˙ú˙ű˙ý˙˙˙ţ˙ý˙˙˙ţ˙ţ˙ü˙ý˙˙˙ţ˙ý˙ţ˙ý˙˙˙ţ˙ţ˙˙˙ţ˙ţ˙ţ˙ţ˙ű˙ý˙˙˙˙˙ţ˙ý˙ý˙˙˙˙˙ţ˙ý˙˙˙ţ˙˙˙ţ˙ţ˙ý˙˙˙ţ˙˙˙˙˙˙˙ý˙˙˙˙˙ţ˙˙˙˙˙˙˙˙˙˙˙ý˙ý˙ý˙ţ˙ý˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙ü˙ű˙ü˙ý˙ý˙ţ˙˙˙ý˙ý˙˙˙ý˙˙˙˙˙ţ˙˙˙ţ˙ý˙ţ˙ţ˙ţ˙ţ˙ü˙ű˙˙˙ţ˙˙˙ţ˙ý˙ý˙˙˙˙˙˙˙˙˙˙˙ţ˙˙˙ţ˙ţ˙ţ˙˙˙˙˙˙˙˙˙˙˙ţ˙˙˙ý˙ý˙ţ˙˙˙˙˙˙˙ý˙˙˙˙˙ţ˙ţ˙ţ˙ţ˙˙˙ţ˙ý˙ý˙˙˙ü˙ü˙ţ˙˙˙ü˙˙˙˙˙˙˙˙˙ţ˙˙˙ţ˙ü˙ű˙ţ˙˙˙ţ˙ý˙˙˙˙˙ţ˙˙˙˙˙ţ˙ţ˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙ţ˙ý˙˙˙ţ˙ţ˙ý˙˙˙ţ˙ţ˙ţ˙ţ˙˙˙ţ˙ý˙˙˙˙˙ţ˙ţ˙ţ˙ý˙ţ˙ý˙ţ˙ţ˙ý˙ţ˙˙˙˙˙ţ˙ý˙˙˙ţ˙ü˙ý˙ý˙ý˙ţ˙ű˙ű˙ű˙ü˙ţ˙˙˙ţ˙ţ˙ţ˙ţ˙˙˙˙˙ý˙ţ˙ý˙ý˙ý˙ţ˙˙˙ý˙ü˙˙˙˙˙˙˙˙˙ý˙ü˙ü˙˙˙ý˙ü˙ţ˙ţ˙ţ˙ţ˙ţ˙ţ˙ţ˙ý˙ţ˙ţ˙˙˙ţ˙ý˙˙˙˙˙ý˙˙˙ţ˙ý˙ţ˙ý˙ý˙˙˙ý˙ţ˙ţ˙ý˙˙˙ţ˙˙˙ţ˙ű˙ű˙ţ˙ý˙ý˙˙˙˙˙ý˙ţ˙˙˙ý˙ü˙ý˙ý˙˙˙˙˙ý˙˙˙˙˙˙˙˙˙˙˙ţ˙ű˙ý˙˙˙˙˙˙˙˙˙ţ˙ţ˙ý˙˙˙ţ˙ü˙ü˙ú˙ü˙˙˙˙˙ţ˙ţ˙ţ˙ţ˙ţ˙ţ˙ţ˙ţ˙ü˙˙˙ţ˙ţ˙ţ˙ţ˙ű˙ý˙˙˙˙˙ţ˙ű˙ü˙ý˙ţ˙ţ˙˙˙ţ˙ţ˙ţ˙ý˙ý˙ţ˙ý˙˙˙˙˙˙˙ţ˙ţ˙ý˙ý˙ţ˙ý˙ý˙ý˙˙˙ţ˙ţ˙ý˙˙˙ý˙ţ˙ý˙˙˙˙˙ţ˙ü˙ý˙ü˙ý˙˙˙ü˙ů˙ú˙ţ˙˙˙ţ˙ü˙ü˙˙˙˙˙ţ˙˙˙˙˙˙˙˙˙ü˙ü˙ü˙ý˙˙˙ţ˙ý˙ţ˙˙˙˙˙˙˙˙˙˙˙ţ˙˙˙˙˙ţ˙ţ˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙ţ˙˙˙˙˙ţ˙˙˙ý˙ţ˙˙˙ţ˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙ý˙˙˙ţ˙ţ˙ý˙ý˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙ţ˙ü˙ú˙ű˙ţ˙˙˙˙˙ü˙ý˙˙˙ţ˙ü˙ý˙˙˙ţ˙ţ˙˙˙ţ˙ţ˙ţ˙ý˙˙˙ţ˙ţ˙ţ˙ý˙˙˙˙˙ü˙ý˙˙˙ţ˙˙˙ţ˙ý˙ü˙˙˙˙˙ţ˙˙˙ţ˙ý˙ţ˙˙˙ý˙ý˙ţ˙ý˙ű˙ý˙ţ˙˙˙ţ˙ţ˙˙˙ý˙ý˙ý˙ţ˙˙˙ţ˙ý˙˙˙˙˙˙˙ţ˙ţ˙ý˙ţ˙˙˙ţ˙˙˙˙˙˙˙ţ˙˙˙˙˙˙˙ţ˙˙˙ţ˙ü˙ý˙˙˙˙˙ü˙ţ˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙ý˙ţ˙˙˙˙˙˙˙˙˙˙˙ţ˙ţ˙ý˙ţ˙ţ˙ý˙ú˙ý˙ţ˙ţ˙˙˙˙˙˙˙˙˙ű˙ú˙ý˙ý˙ű˙ű˙ţ˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙ý˙ţ˙˙˙˙˙˙˙˙˙˙˙ţ˙ţ˙ý˙ü˙ý˙˙˙ţ˙ű˙ű˙ý˙ü˙ý˙˙˙˙˙˙˙˙˙ý˙ý˙ű˙ý˙ű˙ţ˙˙˙ü˙ţ˙˙˙ý˙ű˙ű˙ý˙ý˙˙˙˙˙ţ˙˙˙˙˙˙˙ţ˙ţ˙˙˙˙˙ţ˙˙˙ţ˙ý˙ü˙ý˙˙˙ţ˙˙˙˙˙˙˙ţ˙˙˙ţ˙ü˙ý˙ţ˙ţ˙˙˙˙˙ý˙˙˙˙˙˙˙˙˙ţ˙ű˙ü˙˙˙˙˙˙˙ţ˙˙˙˙˙˙˙˙˙ţ˙ű˙ű˙ü˙ţ˙˙˙ü˙˙˙ţ˙ü˙ţ˙˙˙ţ˙ţ˙˙˙ţ˙˙˙˙˙˙˙˙˙˙˙˙˙ţ˙˙˙˙˙˙˙ţ˙ü˙ţ˙ţ˙ý˙ý˙ý˙ý˙˙˙˙˙ţ˙˙˙˙˙˙˙ý˙ţ˙˙˙˙˙ý˙˙˙ý˙˙˙ý˙ţ˙ţ˙ý˙ţ˙ý˙ü˙ý˙ý˙ţ˙ţ˙˙˙˙˙˙˙˙˙˙˙ţ˙ţ˙˙˙ţ˙˙˙˙˙ý˙ü˙ţ˙ţ˙˙˙˙˙ü˙ţ˙ý˙˙˙˙˙˙˙ţ˙ý˙˙˙ţ˙˙˙˙˙˙˙ü˙ű˙ţ˙˙˙˙˙ü˙ţ˙˙˙ý˙ü˙ţ˙˙˙ţ˙˙˙ţ˙˙˙ü˙ü˙ý˙ţ˙ţ˙ý˙ý˙ý˙˙˙ţ˙˙˙ţ˙ü˙ú˙ű˙˙˙˙˙˙˙˙˙˙˙ü˙ý˙ý˙ű˙ű˙ű˙ű˙ý˙ţ˙ţ˙ý˙˙˙ţ˙ü˙ţ˙ţ˙˙˙ţ˙ţ˙˙˙˙˙ţ˙˙˙˙˙ü˙ý˙˙˙ţ˙ü˙ý˙˙˙˙˙˙˙ţ˙˙˙ţ˙ţ˙ţ˙ü˙ţ˙˙˙ţ˙˙˙˙˙ţ˙˙˙˙˙ţ˙˙˙˙˙ţ˙˙˙ţ˙ţ˙ţ˙ţ˙˙˙˙˙ţ˙˙˙˙˙ü˙˙˙˙˙˙˙˙˙ţ˙˙˙˙˙ý˙ţ˙˙˙˙˙ţ˙˙˙ý˙ý˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙ý˙ţ˙˙˙ý˙ü˙ţ˙ţ˙ý˙˙˙˙˙˙˙ţ˙˙˙ţ˙˙˙ţ˙˙˙˙˙ţ˙ţ˙ţ˙˙˙˙˙ţ˙ü˙ţ˙˙˙ţ˙ţ˙˙˙˙˙ţ˙ý˙ý˙ţ˙ţ˙˙˙ü˙ü˙ű˙ý˙˙˙˙˙˙˙˙˙˙˙˙˙ţ˙ű˙ü˙ý˙ţ˙˙˙ţ˙ý˙ţ˙˙˙˙˙˙˙ý˙ţ˙ţ˙ü˙ţ˙˙˙ţ˙ţ˙ü˙ü˙˙˙˙˙ţ˙˙˙˙˙˙˙ţ˙ü˙ţ˙ý˙ý˙ţ˙ý˙ü˙˙˙ţ˙˙˙ý˙ţ˙ý˙ţ˙˙˙ý˙ü˙ţ˙˙˙˙˙˙˙˙˙ý˙ý˙˙˙˙˙˙˙˙˙˙˙ţ˙ţ˙ý˙˙˙˙˙ý˙ű˙ú˙ţ˙ţ˙˙˙ţ˙˙˙ý˙ü˙ţ˙ţ˙˙˙ü˙ţ˙ţ˙˙˙ý˙ý˙ţ˙˙˙ţ˙ü˙ü˙ű˙ý˙˙˙˙˙˙˙ý˙˙˙ţ˙ü˙ţ˙ţ˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙ţ˙˙˙ý˙ý˙ý˙ý˙ý˙˙˙˙˙˙˙˙˙˙˙ý˙ţ˙ý˙ţ˙ý˙˙˙˙˙˙˙ţ˙ý˙ü˙ý˙˙˙˙˙ţ˙ý˙ý˙ü˙ü˙ý˙˙˙ţ˙˙˙˙˙ý˙ü˙ű˙ü˙˙˙˙˙˙˙ţ˙ý˙ű˙ů˙ű˙ý˙˙˙ţ˙ţ˙˙˙ţ˙ý˙˙˙ý˙ý˙ţ˙˙˙˙˙˙˙˙˙˙˙ţ˙ţ˙ü˙ü˙ţ˙ţ˙ţ˙˙˙˙˙ý˙ţ˙˙˙˙˙˙˙˙˙˙˙ţ˙ý˙ţ˙ý˙˙˙˙˙˙˙˙˙˙˙˙˙ţ˙ý˙ţ˙˙˙ý˙ý˙ý˙ý˙ţ˙ţ˙ü˙ţ˙˙˙˙˙˙˙˙˙ţ˙ţ˙ü˙ý˙˙˙˙˙ţ˙ý˙ü˙˙˙˙˙ţ˙˙˙˙˙˙˙˙˙ţ˙ţ˙ţ˙ţ˙ý˙˙˙˙˙ţ˙ţ˙ý˙ü˙ţ˙˙˙ý˙˙˙ý˙˙˙ý˙ţ˙˙˙ţ˙ţ˙˙˙˙˙ý˙ý˙ý˙ţ˙˙˙˙˙˙˙ţ˙ý˙˙˙˙˙ü˙ý˙ţ˙˙˙ţ˙˙˙˙˙˙˙ţ˙ý˙ţ˙ţ˙ţ˙ţ˙ţ˙˙˙˙˙ü˙ü˙˙˙˙˙ý˙ü˙ţ˙˙˙ţ˙˙˙˙˙ý˙ü˙˙˙ý˙ţ˙˙˙ţ˙˙˙˙˙˙˙˙˙˙˙˙˙ţ˙ü˙ţ˙ţ˙ý˙ţ˙ý˙ţ˙˙˙˙˙˙˙ţ˙ü˙ý˙˙˙ţ˙ý˙ý˙ý˙ţ˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙ţ˙˙˙˙˙ţ˙ý˙ţ˙ţ˙ţ˙˙˙ţ˙˙˙ý˙ţ˙˙˙˙˙˙˙˙˙˙˙ţ˙˙˙ţ˙˙˙ţ˙ý˙˙˙˙˙˙˙˙˙ţ˙ý˙˙˙˙˙˙˙˙˙ţ˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙ţ˙ý˙ţ˙ý˙ţ˙˙˙ţ˙˙˙˙˙ý˙ţ˙ţ˙ţ˙˙˙˙˙˙˙ţ˙ţ˙˙˙ţ˙ţ˙ţ˙ü˙ü˙ý˙˙˙ţ˙˙˙˙˙˙˙ý˙ü˙ý˙ţ˙ü˙˙˙˙˙˙˙ý˙ý˙ţ˙ţ˙ţ˙˙˙˙˙ţ˙˙˙ý˙ü˙˙˙˙˙ţ˙˙˙˙˙ţ˙ý˙ţ˙ý˙ý˙ü˙ű˙ű˙ţ˙˙˙˙˙˙˙ţ˙ý˙ţ˙˙˙˙˙˙˙˙˙˙˙˙˙ţ˙ţ˙˙˙ý˙ţ˙˙˙˙˙ţ˙ţ˙ţ˙ý˙˙˙˙˙ű˙ţ˙˙˙˙˙ý˙ý˙ţ˙˙˙ţ˙ý˙˙˙ý˙ü˙ü˙ţ˙˙˙ţ˙ţ˙˙˙ţ˙˙˙˙˙˙˙˙˙ý˙ý˙˙˙ţ˙ü˙ţ˙ü˙ý˙˙˙˙˙ţ˙ţ˙ü˙ý˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙ţ˙ý˙˙˙ţ˙ü˙ý˙ţ˙ţ˙ý˙ţ˙ý˙ý˙˙˙˙˙˙˙˙˙ý˙ţ˙ţ˙˙˙ý˙˙˙ţ˙ý˙ţ˙ü˙ý˙ý˙˙˙˙˙˙˙˙˙˙˙ţ˙˙˙ţ˙ý˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙ý˙ü˙ţ˙˙˙˙˙ü˙ý˙ţ˙ţ˙˙˙˙˙ý˙ü˙ţ˙ţ˙ţ˙˙˙ţ˙ţ˙˙˙˙˙˙˙˙˙ţ˙ü˙ţ˙ţ˙ţ˙ţ˙˙˙ţ˙ý˙ţ˙˙˙ý˙ú˙ű˙ü˙˙˙ţ˙ţ˙ţ˙˙˙˙˙ý˙ü˙ü˙ţ˙ţ˙ţ˙˙˙˙˙˙˙ţ˙˙˙ţ˙˙˙ţ˙ţ˙˙˙ý˙ţ˙˙˙ý˙ý˙˙˙˙˙ţ˙ý˙˙˙˙˙˙˙˙˙ţ˙ý˙˙˙˙˙˙˙ý˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙ţ˙ü˙ţ˙ţ˙ţ˙˙˙˙˙˙˙ţ˙ý˙ý˙ţ˙˙˙˙˙ţ˙˙˙˙˙ţ˙ý˙ý˙ţ˙ţ˙ţ˙ý˙ţ˙ý˙ţ˙ý˙˙˙ţ˙ţ˙ţ˙ţ˙ü˙ý˙˙˙˙˙˙˙˙˙ţ˙ü˙ü˙ţ˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙ţ˙ý˙ţ˙˙˙˙˙˙˙˙˙ţ˙ý˙ţ˙˙˙˙˙˙˙˙˙ţ˙˙˙ţ˙ţ˙ţ˙ţ˙˙˙ţ˙ţ˙ţ˙ţ˙ţ˙˙˙ţ˙˙˙ţ˙˙˙ý˙ţ˙ý˙ü˙˙˙˙˙ü˙ţ˙˙˙ţ˙ţ˙ţ˙ű˙ű˙ý˙˙˙˙˙˙˙˙˙˙˙˙˙ü˙ţ˙ţ˙˙˙˙˙ü˙ü˙˙˙ý˙ý˙˙˙˙˙˙˙˙˙ý˙ý˙ý˙ţ˙˙˙˙˙˙˙ţ˙ţ˙˙˙˙˙˙˙˙˙ţ˙˙˙ţ˙˙˙˙˙ţ˙˙˙˙˙˙˙ţ˙ţ˙˙˙˙˙˙˙˙˙˙˙˙˙ý˙ý˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙ý˙ý˙ú˙ý˙ţ˙ţ˙˙˙˙˙˙˙˙˙ţ˙˙˙ţ˙˙˙ţ˙ţ˙ţ˙˙˙˙˙ý˙ý˙ý˙ý˙˙˙ţ˙˙˙˙˙˙˙˙˙ý˙ţ˙ý˙ý˙˙˙˙˙ţ˙ý˙ţ˙ý˙˙˙˙˙ţ˙˙˙ţ˙ý˙ý˙˙˙ý˙ü˙ý˙˙˙˙˙˙˙ţ˙ý˙ţ˙ý˙ý˙ý˙ţ˙˙˙ţ˙ţ˙ţ˙ţ˙ý˙ţ˙˙˙˙˙˙˙˙˙˙˙ü˙ú˙ü˙˙˙ţ˙ţ˙ţ˙ţ˙ţ˙˙˙˙˙˙˙˙˙ü˙ţ˙ţ˙ţ˙˙˙ţ˙ţ˙ţ˙˙˙˙˙ý˙ţ˙ű˙ű˙ü˙˙˙˙˙ţ˙ű˙ý˙˙˙˙˙˙˙˙˙˙˙˙˙ý˙ý˙ţ˙ţ˙ü˙ţ˙˙˙˙˙ţ˙˙˙ý˙˙˙ţ˙ţ˙ü˙ü˙˙˙˙˙ý˙˙˙ţ˙ý˙ţ˙˙˙˙˙˙˙˙˙˙˙ý˙˙˙ţ˙˙˙˙˙˙˙˙˙ý˙ţ˙˙˙ţ˙˙˙˙˙ý˙ý˙˙˙˙˙˙˙˙˙˙˙ý˙ű˙ţ˙˙˙ý˙ţ˙˙˙˙˙˙˙˙˙˙˙ţ˙ţ˙ý˙ý˙ü˙ű˙ű˙ý˙˙˙˙˙˙˙˙˙˙˙ţ˙ţ˙˙˙ţ˙ţ˙ţ˙ý˙ţ˙ţ˙ţ˙ţ˙ţ˙ţ˙˙˙ý˙ý˙˙˙˙˙ü˙˙˙˙˙ţ˙ý˙ü˙ţ˙ţ˙ţ˙˙˙ý˙˙˙˙˙˙˙ţ˙˙˙ţ˙ţ˙ý˙˙˙ţ˙˙˙ţ˙ţ˙˙˙ţ˙ü˙ý˙ţ˙ţ˙ý˙ţ˙˙˙ý˙ý˙˙˙˙˙˙˙˙˙ţ˙ý˙ý˙˙˙ţ˙˙˙ý˙ý˙ü˙ű˙ý˙˙˙ţ˙ţ˙ţ˙ţ˙˙˙ţ˙ý˙ţ˙ý˙ý˙˙˙˙˙˙˙˙˙˙˙˙˙ţ˙˙˙ţ˙ţ˙˙˙ţ˙ý˙ţ˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙ţ˙ţ˙ţ˙˙˙ü˙ţ˙ý˙ţ˙˙˙ţ˙ţ˙ţ˙ţ˙ţ˙ţ˙ţ˙˙˙˙˙ţ˙˙˙˙˙ţ˙˙˙ü˙ű˙ţ˙˙˙˙˙˙˙˙˙ý˙ţ˙˙˙˙˙ţ˙ţ˙ţ˙ű˙ü˙˙˙˙˙˙˙˙˙˙˙ţ˙ű˙ü˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙ţ˙˙˙˙˙ţ˙˙˙ţ˙˙˙˙˙˙˙˙˙ţ˙ţ˙ý˙ý˙˙˙ţ˙˙˙ý˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙ţ˙ţ˙ý˙ý˙ý˙ý˙ţ˙ű˙ý˙˙˙˙˙˙˙ţ˙ű˙ü˙ţ˙˙˙˙˙˙˙ţ˙˙˙˙˙ý˙˙˙ü˙ţ˙˙˙˙˙ý˙ý˙ý˙ţ˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙ý˙ű˙ü˙ţ˙˙˙˙˙˙˙ý˙ý˙ý˙ű˙ţ˙ý˙˙˙˙˙˙˙˙˙ţ˙˙˙˙˙˙˙˙˙ü˙ý˙ű˙ý˙˙˙˙˙ţ˙ţ˙˙˙ţ˙ý˙ţ˙ţ˙ý˙˙˙˙˙ü˙ú˙ű˙˙˙ţ˙ű˙ţ˙˙˙˙˙˙˙˙˙ý˙ţ˙ý˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙ü˙ű˙ţ˙˙˙˙˙˙˙˙˙ý˙ý˙˙˙ý˙ţ˙˙˙˙˙ý˙ý˙˙˙˙˙˙˙˙˙ţ˙˙˙ţ˙˙˙˙˙˙˙ű˙ú˙˙˙˙˙ý˙ţ˙˙˙ţ˙ý˙ý˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙ý˙˙˙ü˙ű˙˙˙˙˙˙˙ý˙ý˙ţ˙˙˙ţ˙ţ˙ý˙ý˙˙˙˙˙˙˙˙˙˙˙˙˙ý˙˙˙˙˙ý˙˙˙˙˙ý˙ü˙ţ˙ţ˙˙˙ţ˙ţ˙ţ˙ţ˙ţ˙ţ˙˙˙˙˙ý˙ü˙˙˙˙˙ţ˙˙˙ţ˙ý˙ý˙ţ˙ý˙ý˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙ţ˙ý˙ý˙˙˙˙˙ţ˙ý˙ţ˙ţ˙ý˙˙˙ţ˙˙˙ţ˙˙˙˙˙˙˙˙˙ţ˙˙˙˙˙ţ˙ţ˙˙˙ţ˙ţ˙ţ˙˙˙ţ˙ü˙ű˙ţ˙˙˙˙˙˙˙˙˙˙˙˙˙ý˙ý˙ţ˙˙˙˙˙ţ˙ý˙ý˙ü˙ý˙ţ˙ţ˙ţ˙ţ˙˙˙˙˙˙˙˙˙ţ˙ţ˙ü˙ţ˙˙˙ý˙˙˙˙˙˙˙ý˙ý˙˙˙ţ˙ţ˙˙˙˙˙˙˙ţ˙˙˙˙˙˙˙˙˙ý˙ý˙ü˙ţ˙˙˙ţ˙ţ˙ţ˙ţ˙˙˙˙˙˙˙ţ˙ţ˙ţ˙˙˙ţ˙˙˙˙˙˙˙˙˙ý˙ý˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙ţ˙ţ˙˙˙˙˙˙˙˙˙˙˙˙˙ý˙ý˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙ţ˙ý˙ý˙ţ˙˙˙˙˙˙˙˙˙ţ˙˙˙ţ˙ţ˙ý˙ţ˙ţ˙ý˙ü˙ý˙ţ˙ţ˙ţ˙ţ˙˙˙ţ˙ţ˙ţ˙˙˙˙˙ý˙ţ˙˙˙˙˙˙˙ţ˙ý˙ý˙˙˙˙˙˙˙ţ˙˙˙˙˙ţ˙ý˙ţ˙ý˙ý˙ţ˙˙˙˙˙ý˙ű˙ý˙ţ˙˙˙ţ˙ý˙˙˙ţ˙ţ˙ţ˙˙˙ý˙ü˙ţ˙ý˙ü˙ý˙˙˙˙˙˙˙ţ˙ü˙ü˙˙˙˙˙ţ˙ü˙ţ˙˙˙ţ˙ţ˙˙˙ţ˙˙˙˙˙˙˙˙˙ţ˙˙˙˙˙ţ˙˙˙ţ˙ý˙ý˙˙˙˙˙˙˙˙˙ý˙˙˙˙˙˙˙ţ˙˙˙˙˙˙˙ý˙ü˙ý˙ţ˙˙˙˙˙˙˙˙˙ţ˙˙˙ţ˙ü˙ý˙˙˙ţ˙ý˙ý˙ý˙ű˙ý˙ţ˙ţ˙ý˙ü˙ý˙˙˙ţ˙ţ˙ţ˙ý˙˙˙˙˙˙˙ţ˙ü˙ú˙ű˙˙˙˙˙˙˙˙˙ţ˙ţ˙ţ˙ţ˙˙˙˙˙ţ˙˙˙˙˙˙˙˙˙ţ˙ţ˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙ü˙ü˙ü˙ţ˙ý˙ú˙ú˙ű˙ý˙˙˙ţ˙ü˙ű˙ý˙ý˙˙˙ý˙ţ˙ý˙ý˙ţ˙ţ˙ű˙ü˙ý˙ü˙˙˙˙˙˙˙˙˙ţ˙˙˙˙˙˙˙˙˙ţ˙ű˙ţ˙ţ˙˙˙˙˙ý˙ţ˙ü˙ý˙ý˙ţ˙˙˙˙˙˙˙˙˙ű˙ý˙ý˙ü˙ý˙ţ˙ý˙ţ˙˙˙ý˙ţ˙˙˙ý˙ü˙ţ˙ý˙ü˙˙˙ţ˙˙˙˙˙˙˙ű˙ü˙˙˙˙˙ţ˙ţ˙˙˙ý˙ü˙ţ˙˙˙ţ˙ţ˙ţ˙ý˙ý˙ţ˙˙˙ý˙ü˙ţ˙˙˙ý˙ü˙ý˙ý˙ý˙ý˙˙˙˙˙ü˙ü˙ţ˙ţ˙˙˙˙˙˙˙ţ˙˙˙˙˙˙˙˙˙˙˙˙˙ţ˙ý˙ý˙˙˙ţ˙ý˙˙˙ý˙ú˙ú˙ý˙ţ˙ţ˙ţ˙ţ˙ţ˙ţ˙ţ˙ţ˙˙˙˙˙˙˙˙˙ü˙ý˙ţ˙ţ˙ţ˙ý˙ţ˙ţ˙ý˙ü˙ý˙ţ˙ţ˙˙˙ţ˙ý˙˙˙ţ˙ü˙ü˙˙˙˙˙˙˙ý˙ü˙ű˙ü˙˙˙˙˙ţ˙ţ˙ţ˙ţ˙ţ˙˙˙˙˙ý˙ý˙ý˙ű˙ý˙ü˙ű˙ü˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙ü˙ű˙ţ˙˙˙˙˙˙˙˙˙ý˙ţ˙ţ˙˙˙˙˙˙˙ý˙ű˙ű˙ü˙ý˙˙˙˙˙˙˙˙˙ţ˙ý˙ţ˙ý˙ţ˙˙˙ý˙ü˙ţ˙˙˙ţ˙ţ˙ý˙ţ˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙ý˙˙˙˙˙˙˙ý˙˙˙ý˙ý˙ţ˙ü˙ű˙ý˙˙˙ţ˙ţ˙˙˙˙˙ţ˙ţ˙ý˙ü˙ţ˙ţ˙ţ˙˙˙ţ˙ý˙˙˙ţ˙˙˙ű˙ý˙˙˙˙˙ţ˙ţ˙ţ˙ţ˙ţ˙ţ˙˙˙˙˙ţ˙˙˙ţ˙˙˙˙˙ţ˙˙˙˙˙ţ˙ý˙˙˙˙˙ţ˙ý˙ţ˙ţ˙ţ˙ţ˙˙˙ţ˙ţ˙˙˙˙˙ţ˙ú˙ú˙ţ˙˙˙ý˙˙˙˙˙˙˙ý˙ü˙ý˙ý˙ţ˙ţ˙ţ˙˙˙˙˙ţ˙˙˙ţ˙ü˙˙˙ţ˙ţ˙ţ˙˙˙ý˙ú˙ű˙˙˙˙˙ý˙ţ˙ţ˙˙˙˙˙˙˙ţ˙˙˙ý˙˙˙ţ˙˙˙˙˙˙˙ü˙ű˙ű˙ü˙ţ˙ţ˙ü˙ü˙ţ˙ţ˙˙˙˙˙ý˙ü˙ü˙ţ˙ţ˙ţ˙ţ˙ý˙ü˙ü˙ţ˙ý˙ü˙ý˙ţ˙ţ˙˙˙˙˙ý˙ý˙˙˙ţ˙ţ˙˙˙ţ˙ţ˙ţ˙˙˙˙˙ü˙ü˙ü˙ü˙˙˙ý˙ţ˙˙˙˙˙˙˙ţ˙ű˙ű˙˙˙ţ˙˙˙˙˙˙˙˙˙˙˙˙˙ţ˙ü˙ţ˙˙˙ý˙˙˙˙˙ţ˙ý˙ü˙ý˙ţ˙˙˙ţ˙ű˙ű˙˙˙˙˙˙˙ţ˙ý˙ý˙ţ˙ţ˙ţ˙˙˙ý˙ý˙ţ˙˙˙ţ˙ý˙ţ˙ţ˙ý˙ý˙ţ˙ţ˙˙˙˙˙ü˙˙˙˙˙ţ˙ý˙ţ˙ü˙ü˙ţ˙˙˙ţ˙ţ˙ţ˙ţ˙ţ˙ü˙ú˙ü˙˙˙ü˙ý˙˙˙ţ˙ý˙ý˙ţ˙ţ˙˙˙ţ˙˙˙˙˙˙˙˙˙˙˙ţ˙ţ˙˙˙˙˙˙˙˙˙˙˙ţ˙ý˙˙˙˙˙˙˙˙˙ţ˙ţ˙ý˙ű˙ý˙˙˙ţ˙ý˙ţ˙ţ˙ý˙ý˙ý˙ý˙ţ˙˙˙ý˙˙˙˙˙ý˙ţ˙˙˙ţ˙ý˙ý˙˙˙˙˙ý˙ű˙ý˙ű˙ű˙ý˙ý˙ý˙ý˙ý˙˙˙ţ˙ţ˙ý˙ü˙ý˙ţ˙˙˙ţ˙ü˙ý˙˙˙ţ˙˙˙ţ˙ü˙ţ˙˙˙ű˙ý˙˙˙˙˙ý˙˙˙˙˙ţ˙˙˙˙˙ü˙ü˙ü˙˙˙˙˙˙˙ţ˙˙˙ü˙ű˙ţ˙ţ˙ý˙˙˙ţ˙ý˙˙˙ý˙ý˙ţ˙ý˙ý˙ý˙˙˙˙˙ţ˙ü˙ü˙˙˙ý˙ű˙ţ˙˙˙˙˙˙˙ţ˙ý˙˙˙˙˙˙˙˙˙˙˙ý˙ţ˙˙˙˙˙˙˙˙˙˙˙˙˙ţ˙ý˙ţ˙ţ˙ű˙ü˙ţ˙ţ˙ţ˙˙˙ţ˙ţ˙ţ˙ü˙ý˙ţ˙ý˙ű˙ü˙ţ˙˙˙˙˙ü˙ý˙ţ˙ý˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙ţ˙˙˙ţ˙ű˙ý˙˙˙˙˙ţ˙ý˙ţ˙ţ˙˙˙ý˙ü˙ý˙ý˙ü˙˙˙ţ˙˙˙ţ˙˙˙˙˙ý˙ü˙ţ˙˙˙˙˙ţ˙ţ˙ţ˙ţ˙˙˙˙˙˙˙˙˙ý˙ü˙ü˙ţ˙ý˙ţ˙ţ˙ý˙ü˙ţ˙˙˙ý˙ý˙ű˙ţ˙ţ˙ü˙ý˙ý˙ţ˙ţ˙˙˙˙˙ţ˙ü˙ý˙˙˙˙˙ţ˙ü˙ţ˙˙˙ü˙˙˙˙˙ţ˙˙˙ţ˙ţ˙˙˙ţ˙˙˙ý˙ü˙˙˙˙˙ý˙ü˙ţ˙˙˙ý˙ü˙˙˙ý˙˙˙ü˙ű˙ţ˙˙˙˙˙ý˙ţ˙ţ˙ţ˙˙˙˙˙˙˙˙˙˙˙˙˙ţ˙ţ˙ţ˙ü˙ý˙˙˙˙˙ý˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙ý˙˙˙˙˙ţ˙ý˙˙˙˙˙˙˙˙˙˙˙˙˙ţ˙ü˙ý˙ţ˙ţ˙˙˙ý˙˙˙˙˙ţ˙ý˙˙˙˙˙˙˙ü˙ű˙ý˙ţ˙ţ˙ţ˙ţ˙ţ˙ţ˙ý˙ü˙˙˙ţ˙ű˙ţ˙ţ˙ţ˙ý˙˙˙˙˙ü˙˙˙˙˙ţ˙ţ˙˙˙ý˙ü˙ý˙˙˙˙˙˙˙˙˙˙˙ţ˙ý˙˙˙ţ˙˙˙˙˙˙˙ţ˙ţ˙ţ˙˙˙ý˙ţ˙˙˙˙˙ý˙ţ˙˙˙ţ˙˙˙˙˙˙˙˙˙˙˙ţ˙˙˙ţ˙˙˙˙˙˙˙ý˙ţ˙˙˙ý˙ţ˙˙˙˙˙˙˙˙˙ţ˙ţ˙ţ˙ţ˙ţ˙˙˙ţ˙ţ˙˙˙˙˙˙˙ţ˙ţ˙ý˙ü˙ű˙ű˙ý˙˙˙˙˙ţ˙ý˙ý˙ü˙ý˙˙˙˙˙ţ˙ţ˙ţ˙ţ˙ţ˙ţ˙ý˙ý˙ţ˙ý˙ü˙ţ˙˙˙˙˙ý˙ý˙ţ˙˙˙ü˙ý˙˙˙ý˙ü˙ý˙˙˙ţ˙ţ˙˙˙ţ˙˙˙˙˙˙˙ý˙ü˙ţ˙˙˙˙˙˙˙ţ˙ţ˙˙˙ţ˙ű˙ţ˙˙˙˙˙ţ˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙ţ˙˙˙˙˙ü˙ü˙˙˙˙˙ű˙ü˙ý˙ţ˙ţ˙ý˙˙˙ţ˙˙˙ý˙ţ˙˙˙˙˙ý˙ý˙ý˙ü˙ý˙ţ˙ý˙ţ˙ţ˙ţ˙˙˙˙˙˙˙˙˙˙˙˙˙ţ˙ý˙ţ˙˙˙ý˙ý˙˙˙ü˙ý˙ţ˙ý˙ý˙ý˙ý˙˙˙ý˙ý˙ý˙ü˙˙˙˙˙ţ˙˙˙˙˙ý˙˙˙˙˙˙˙˙˙˙˙˙˙ţ˙ţ˙˙˙˙˙˙˙ţ˙˙˙˙˙˙˙ţ˙ý˙˙˙ţ˙ű˙ů˙ý˙˙˙˙˙˙˙˙˙ý˙ü˙ţ˙ţ˙ţ˙˙˙˙˙˙˙ţ˙ý˙ţ˙ü˙ý˙ţ˙ţ˙˙˙ţ˙ţ˙ü˙˙˙˙˙ţ˙ţ˙˙˙˙˙ţ˙˙˙˙˙ţ˙˙˙˙˙ţ˙˙˙ţ˙ý˙ý˙˙˙˙˙ţ˙ý˙ţ˙˙˙˙˙˙˙˙˙ý˙ţ˙ý˙ţ˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙ţ˙ý˙ţ˙˙˙ţ˙˙˙˙˙ý˙ţ˙˙˙ý˙˙˙˙˙ý˙ý˙ţ˙˙˙˙˙ţ˙ý˙˙˙˙˙ţ˙ţ˙ţ˙ţ˙˙˙ý˙ţ˙˙˙˙˙ţ˙˙˙˙˙ý˙ü˙ţ˙ţ˙˙˙˙˙˙˙˙˙˙˙ţ˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙ý˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙ý˙ţ˙˙˙ţ˙˙˙ý˙ţ˙˙˙˙˙˙˙ý˙ţ˙ý˙ü˙˙˙˙˙ý˙˙˙˙˙˙˙˙˙˙˙˙˙ý˙ţ˙˙˙˙˙ţ˙˙˙˙˙ý˙ţ˙ţ˙ţ˙ü˙ţ˙ţ˙ü˙ü˙ţ˙ţ˙ţ˙ý˙˙˙˙˙ý˙˙˙˙˙ü˙ý˙ţ˙˙˙ţ˙ţ˙˙˙˙˙ü˙ý˙ţ˙˙˙˙˙ý˙ţ˙ţ˙˙˙˙˙ţ˙ţ˙ü˙ý˙ţ˙˙˙ţ˙˙˙˙˙˙˙ü˙ý˙˙˙˙˙ţ˙ţ˙ý˙ü˙ţ˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙ţ˙ţ˙˙˙˙˙ý˙ű˙ű˙ţ˙˙˙ý˙ţ˙ţ˙ţ˙ţ˙ý˙˙˙˙˙ý˙ý˙ţ˙˙˙˙˙˙˙ţ˙ý˙ţ˙ţ˙˙˙˙˙˙˙ţ˙ý˙ţ˙ţ˙˙˙˙˙ţ˙ţ˙ţ˙ţ˙˙˙ţ˙˙˙˙˙ţ˙˙˙˙˙ý˙ü˙˙˙˙˙ý˙ţ˙˙˙˙˙˙˙˙˙ţ˙˙˙˙˙˙˙˙˙ţ˙˙˙ţ˙ü˙ý˙˙˙ţ˙ü˙ú˙ű˙ţ˙˙˙˙˙˙˙˙˙ţ˙ý˙ţ˙ý˙ý˙ý˙ţ˙˙˙˙˙˙˙ţ˙ý˙ý˙ý˙˙˙˙˙ţ˙˙˙˙˙˙˙ý˙ţ˙˙˙˙˙ý˙ý˙ý˙ţ˙˙˙˙˙˙˙ý˙ü˙ű˙ü˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙ţ˙˙˙ţ˙ý˙ţ˙˙˙˙˙˙˙˙˙˙˙ţ˙ü˙ű˙ţ˙˙˙˙˙ţ˙ţ˙˙˙˙˙˙˙˙˙ý˙ţ˙˙˙˙˙ţ˙˙˙ţ˙˙˙ţ˙ý˙ü˙˙˙ţ˙˙˙˙˙ţ˙ţ˙ý˙ü˙ü˙ţ˙ý˙ü˙ţ˙ý˙ţ˙ű˙ţ˙ý˙ý˙ţ˙˙˙˙˙ý˙ţ˙˙˙˙˙˙˙˙˙˙˙ţ˙ý˙ú˙ý˙˙˙˙˙ţ˙ý˙ţ˙˙˙˙˙ţ˙ţ˙ţ˙ţ˙ţ˙˙˙ţ˙˙˙˙˙˙˙˙˙˙˙ý˙ý˙ý˙ý˙ý˙ü˙ý˙ţ˙ý˙ţ˙ý˙˙˙ţ˙ü˙ý˙˙˙˙˙ţ˙ţ˙ţ˙˙˙ţ˙˙˙˙˙ţ˙˙˙ü˙ý˙ű˙ü˙ý˙˙˙˙˙ţ˙˙˙˙˙ü˙ţ˙˙˙ü˙ý˙ţ˙˙˙ţ˙˙˙ţ˙ţ˙˙˙˙˙˙˙ý˙˙˙ţ˙ý˙˙˙ţ˙ţ˙ý˙ţ˙ţ˙ţ˙ý˙˙˙˙˙ţ˙ţ˙˙˙˙˙ý˙ý˙ü˙ţ˙˙˙ţ˙ý˙ü˙ţ˙ţ˙ý˙ü˙ü˙˙˙˙˙˙˙ý˙ţ˙ű˙ű˙ü˙ţ˙˙˙˙˙˙˙˙˙˙˙˙˙ţ˙ű˙ű˙ţ˙ţ˙ţ˙ţ˙ý˙ü˙ü˙˙˙ţ˙ţ˙˙˙ţ˙˙˙ţ˙ü˙ü˙ţ˙ý˙ţ˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙ţ˙ţ˙ţ˙ţ˙˙˙˙˙ü˙ý˙ţ˙ţ˙˙˙˙˙ţ˙ţ˙ţ˙ű˙ű˙ţ˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙ţ˙ü˙ý˙ţ˙ţ˙˙˙˙˙ţ˙ţ˙ţ˙ű˙ú˙ü˙ţ˙ý˙ű˙ű˙ý˙ţ˙˙˙˙˙˙˙ü˙ý˙˙˙˙˙ţ˙ţ˙˙˙ţ˙˙˙ţ˙ý˙ţ˙˙˙ţ˙ý˙ţ˙˙˙ţ˙ţ˙ţ˙ţ˙˙˙˙˙ţ˙ý˙ý˙˙˙˙˙˙˙˙˙˙˙ý˙ý˙˙˙˙˙ţ˙ţ˙ţ˙˙˙ţ˙ţ˙ü˙ü˙˙˙˙˙ţ˙ţ˙ţ˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙ţ˙˙˙ţ˙ţ˙ţ˙ţ˙ű˙ý˙ţ˙ţ˙ţ˙ţ˙ţ˙ţ˙˙˙˙˙ü˙ü˙ý˙ţ˙ý˙ţ˙˙˙˙˙˙˙ţ˙ţ˙ü˙ü˙˙˙ţ˙˙˙˙˙˙˙˙˙˙˙˙˙ţ˙ý˙˙˙˙˙ţ˙ý˙˙˙˙˙ţ˙ü˙ý˙˙˙˙˙ţ˙˙˙ţ˙ţ˙˙˙ţ˙ţ˙ţ˙ý˙ţ˙ü˙ţ˙ţ˙ţ˙˙˙ţ˙˙˙ţ˙˙˙ý˙ţ˙˙˙˙˙ţ˙ţ˙ý˙ü˙ţ˙ý˙ý˙˙˙ţ˙˙˙˙˙ý˙ţ˙˙˙˙˙ý˙ý˙˙˙˙˙˙˙ü˙ý˙˙˙˙˙ţ˙˙˙ý˙ţ˙˙˙ţ˙ţ˙˙˙˙˙ü˙ý˙ţ˙ţ˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙ü˙ü˙˙˙ţ˙ţ˙˙˙ţ˙ţ˙˙˙ţ˙ý˙˙˙˙˙ţ˙ţ˙˙˙ü˙ü˙˙˙ţ˙˙˙˙˙ý˙˙˙ţ˙˙˙˙˙˙˙ý˙˙˙˙˙ý˙ý˙ý˙˙˙ý˙ý˙ţ˙˙˙˙˙ţ˙˙˙ý˙ý˙ý˙˙˙ý˙ü˙ý˙ţ˙ţ˙ţ˙ý˙ü˙ţ˙˙˙ý˙ü˙ţ˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙ý˙ţ˙ţ˙˙˙ţ˙˙˙˙˙˙˙˙˙ý˙ü˙˙˙ý˙ý˙ţ˙˙˙˙˙ţ˙ý˙ý˙ű˙ý˙˙˙˙˙˙˙˙˙˙˙ţ˙ü˙ü˙˙˙˙˙ý˙ü˙ü˙ü˙˙˙˙˙ü˙ţ˙ý˙ý˙ý˙ý˙ţ˙˙˙˙˙ţ˙˙˙˙˙˙˙˙˙˙˙˙˙ţ˙ý˙˙˙˙˙˙˙ţ˙ý˙˙˙˙˙˙˙˙˙˙˙˙˙ţ˙˙˙˙˙ţ˙ţ˙˙˙ý˙ű˙ü˙˙˙˙˙˙˙ţ˙˙˙˙˙˙˙ţ˙ţ˙˙˙ţ˙ü˙˙˙˙˙˙˙˙˙ţ˙ý˙ţ˙˙˙˙˙˙˙ý˙ý˙ţ˙˙˙˙˙˙˙ý˙ý˙˙˙˙˙ý˙ý˙ý˙˙˙ţ˙ţ˙˙˙ţ˙ţ˙˙˙˙˙ţ˙ţ˙ý˙ţ˙˙˙ý˙ý˙˙˙ü˙ţ˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙ý˙ý˙˙˙˙˙˙˙ţ˙ý˙˙˙ţ˙ü˙ţ˙ţ˙˙˙ţ˙ţ˙ü˙ü˙˙˙ţ˙ţ˙˙˙˙˙˙˙ţ˙ü˙ţ˙ţ˙ţ˙ý˙ý˙ţ˙ý˙ţ˙ţ˙˙˙˙˙˙˙ý˙ü˙˙˙˙˙ţ˙˙˙˙˙ý˙ţ˙˙˙ţ˙ţ˙˙˙ţ˙ţ˙ţ˙ţ˙˙˙˙˙ý˙ý˙ţ˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙ţ˙˙˙˙˙˙˙˙˙ţ˙ý˙˙˙ţ˙ü˙ű˙ü˙ţ˙˙˙ţ˙˙˙˙˙ţ˙ţ˙ý˙ü˙ţ˙˙˙˙˙˙˙ţ˙˙˙˙˙ţ˙˙˙˙˙˙˙ţ˙˙˙ţ˙ţ˙ţ˙ţ˙ţ˙ţ˙ţ˙˙˙ţ˙ţ˙˙˙ý˙ü˙ţ˙ü˙ü˙ű˙ý˙ý˙ţ˙ţ˙ý˙ţ˙˙˙˙˙˙˙˙˙ţ˙ü˙ý˙ţ˙ü˙ü˙˙˙˙˙ü˙ü˙ý˙ţ˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙ý˙ű˙ü˙ţ˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙ý˙ţ˙˙˙˙˙˙˙ý˙ý˙ű˙ü˙ţ˙˙˙ţ˙ţ˙˙˙˙˙˙˙ţ˙˙˙˙˙ţ˙˙˙˙˙ý˙ţ˙˙˙ţ˙ý˙ţ˙ý˙ý˙ţ˙˙˙˙˙ţ˙ű˙ý˙˙˙ţ˙ţ˙˙˙˙˙ţ˙ü˙ţ˙˙˙ý˙ţ˙ţ˙ţ˙˙˙˙˙ü˙ú˙ü˙ý˙ü˙ţ˙˙˙ţ˙ü˙ů˙ű˙ţ˙˙˙ţ˙˙˙˙˙ý˙ý˙˙˙ţ˙ű˙ţ˙˙˙ţ˙ţ˙˙˙ţ˙ţ˙ý˙˙˙˙˙˙˙ţ˙ý˙ý˙˙˙˙˙ţ˙ţ˙ţ˙ţ˙ý˙ü˙ü˙ţ˙˙˙˙˙ü˙ý˙ţ˙˙˙ű˙ţ˙˙˙ý˙ý˙ţ˙˙˙ţ˙ü˙ű˙ý˙˙˙˙˙˙˙ţ˙ţ˙˙˙ţ˙ý˙˙˙˙˙˙˙˙˙˙˙˙˙ţ˙˙˙˙˙˙˙ü˙ů˙ü˙˙˙˙˙˙˙ţ˙ý˙ţ˙˙˙˙˙ý˙˙˙˙˙ţ˙˙˙˙˙ý˙ü˙ý˙˙˙ţ˙ý˙ý˙ý˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙ţ˙ţ˙ţ˙ý˙ü˙˙˙˙˙˙˙ţ˙˙˙˙˙ü˙ű˙ţ˙ţ˙˙˙˙˙˙˙˙˙˙˙ţ˙ý˙˙˙ţ˙ţ˙˙˙˙˙ţ˙ţ˙ý˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙ý˙ű˙ü˙ţ˙˙˙ţ˙ţ˙ý˙ý˙˙˙ţ˙ü˙ţ˙ţ˙ý˙ý˙ţ˙˙˙˙˙ţ˙ű˙ý˙ţ˙ţ˙ţ˙ţ˙ţ˙˙˙˙˙˙˙ţ˙˙˙˙˙˙˙ý˙ý˙ţ˙˙˙˙˙ý˙ţ˙˙˙˙˙˙˙˙˙ţ˙˙˙˙˙ý˙ü˙ţ˙˙˙ý˙ţ˙˙˙˙˙˙˙ţ˙˙˙ţ˙ţ˙ý˙ü˙ü˙ţ˙ţ˙ý˙ű˙ý˙ý˙ý˙ý˙˙˙˙˙˙˙˙˙ý˙ü˙ţ˙˙˙˙˙ý˙ý˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙ý˙ţ˙˙˙ţ˙˙˙ý˙ý˙ţ˙˙˙˙˙ü˙ű˙ţ˙ü˙ü˙˙˙˙˙ţ˙ý˙˙˙˙˙ý˙ţ˙ţ˙˙˙ţ˙˙˙˙˙ý˙ţ˙ţ˙ý˙ţ˙˙˙ü˙ü˙˙˙˙˙ţ˙˙˙˙˙ţ˙˙˙˙˙ţ˙ţ˙˙˙˙˙˙˙ý˙ţ˙ü˙ý˙ü˙ü˙ţ˙ţ˙ü˙ü˙ű˙ü˙˙˙˙˙ý˙ű˙ţ˙˙˙˙˙˙˙ţ˙˙˙˙˙˙˙ý˙ţ˙˙˙ţ˙˙˙ý˙ú˙ű˙ţ˙ý˙˙˙˙˙ţ˙ţ˙ý˙ţ˙ţ˙ţ˙˙˙˙˙ţ˙˙˙˙˙ţ˙˙˙ý˙ý˙ţ˙˙˙˙˙ţ˙˙˙˙˙ý˙ý˙ţ˙ţ˙˙˙˙˙ý˙˙˙ţ˙˙˙˙˙˙˙ţ˙˙˙ý˙ý˙ţ˙˙˙˙˙ţ˙ţ˙ý˙ü˙˙˙˙˙ü˙ý˙˙˙˙˙˙˙ý˙ý˙ý˙ţ˙ý˙˙˙ý˙ý˙ţ˙˙˙˙˙ý˙˙˙ý˙˙˙˙˙ţ˙ý˙ý˙ý˙ţ˙ţ˙ý˙ţ˙˙˙ý˙ţ˙ţ˙ţ˙˙˙ţ˙ü˙ű˙ý˙˙˙ţ˙ý˙˙˙˙˙˙˙ţ˙ý˙ţ˙ţ˙ţ˙˙˙ţ˙˙˙ţ˙ţ˙ţ˙ü˙˙˙˙˙ţ˙ý˙ý˙˙˙˙˙˙˙˙˙˙˙ţ˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙ţ˙ý˙ţ˙ţ˙˙˙ý˙ý˙ý˙ţ˙ţ˙˙˙ü˙ý˙ţ˙ý˙ţ˙˙˙˙˙ţ˙ý˙ţ˙ü˙˙˙˙˙ý˙ü˙ű˙ý˙ţ˙˙˙ţ˙ţ˙ý˙ý˙˙˙˙˙˙˙˙˙ţ˙ţ˙˙˙ţ˙ű˙ű˙ţ˙ü˙ý˙ý˙ţ˙ţ˙ý˙ý˙˙˙˙˙˙˙ý˙ţ˙˙˙˙˙ţ˙˙˙˙˙˙˙ţ˙ý˙ţ˙ţ˙˙˙˙˙ý˙˙˙ţ˙ý˙ü˙ý˙˙˙ü˙ý˙ţ˙ý˙ţ˙˙˙ţ˙˙˙ţ˙˙˙˙˙ţ˙ü˙ý˙ý˙ţ˙˙˙˙˙˙˙ý˙ţ˙ý˙ý˙ţ˙˙˙ţ˙ý˙ü˙ü˙˙˙˙˙ü˙ţ˙ý˙ü˙ţ˙˙˙˙˙˙˙˙˙˙˙˙˙ţ˙ű˙ü˙ý˙ý˙ý˙ţ˙ú˙ü˙ţ˙ţ˙˙˙ţ˙ţ˙˙˙ý˙ý˙˙˙˙˙ţ˙˙˙˙˙ţ˙ţ˙˙˙˙˙ţ˙˙˙˙˙ý˙˙˙ţ˙ţ˙ü˙ý˙˙˙ţ˙ü˙ý˙ý˙ţ˙ţ˙˙˙˙˙ţ˙ý˙˙˙˙˙ü˙ţ˙ţ˙ý˙ü˙ţ˙ţ˙˙˙ü˙ţ˙ţ˙ý˙ü˙˙˙˙˙ţ˙˙˙ţ˙ý˙ţ˙˙˙˙˙˙˙˙˙ý˙ţ˙ţ˙ţ˙˙˙ţ˙˙˙˙˙ú˙ü˙˙˙ý˙ü˙ţ˙ţ˙ţ˙ţ˙ţ˙ţ˙ţ˙ţ˙ţ˙ţ˙˙˙ţ˙˙˙˙˙ü˙ţ˙˙˙ţ˙ţ˙˙˙ý˙ţ˙˙˙ý˙ý˙ü˙ý˙ţ˙˙˙˙˙˙˙˙˙ţ˙˙˙ţ˙ý˙˙˙˙˙ý˙˙˙˙˙ü˙˙˙ý˙ű˙ţ˙ű˙ý˙˙˙˙˙ţ˙˙˙˙˙ü˙ý˙˙˙ţ˙ţ˙˙˙˙˙˙˙ţ˙ý˙˙˙ţ˙ý˙˙˙ţ˙ý˙˙˙ţ˙ý˙ţ˙ţ˙ý˙ţ˙˙˙ţ˙˙˙˙˙ţ˙ţ˙ţ˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙ţ˙ý˙˙˙˙˙ý˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙ţ˙ţ˙ţ˙ţ˙ţ˙˙˙˙˙ý˙ü˙˙˙˙˙ţ˙ý˙ü˙˙˙ţ˙ţ˙ţ˙˙˙ý˙ý˙ţ˙˙˙ý˙ű˙ţ˙˙˙ü˙ü˙˙˙˙˙˙˙˙˙˙˙ý˙ý˙˙˙ý˙˙˙˙˙ü˙ü˙ţ˙˙˙ţ˙ü˙ý˙˙˙ţ˙ý˙ý˙ţ˙˙˙ţ˙˙˙˙˙ý˙ý˙ţ˙ý˙ű˙ü˙ţ˙˙˙˙˙ţ˙˙˙ţ˙˙˙ţ˙˙˙˙˙ţ˙ţ˙˙˙ý˙ţ˙ý˙ţ˙˙˙ý˙ţ˙ü˙ţ˙ý˙ű˙ý˙˙˙˙˙˙˙˙˙ţ˙˙˙˙˙˙˙ý˙ý˙ţ˙ţ˙ţ˙˙˙˙˙˙˙ţ˙ţ˙ţ˙˙˙ü˙ü˙ý˙ý˙˙˙ü˙ý˙ý˙ű˙ý˙˙˙ý˙ý˙˙˙˙˙˙˙˙˙ţ˙ü˙ý˙ţ˙ţ˙˙˙ý˙ţ˙˙˙ţ˙ţ˙ý˙ú˙ű˙˙˙˙˙˙˙˙˙ţ˙˙˙ý˙ý˙ü˙ţ˙˙˙ţ˙˙˙˙˙ý˙ý˙ţ˙˙˙˙˙˙˙ţ˙ý˙ţ˙ţ˙ý˙ü˙˙˙˙˙ţ˙ý˙ý˙˙˙ţ˙ţ˙ţ˙ţ˙ţ˙ý˙ý˙ţ˙ţ˙ü˙ý˙˙˙ţ˙ü˙ý˙˙˙ý˙ý˙˙˙˙˙˙˙˙˙˙˙˙˙ţ˙˙˙ý˙ü˙ţ˙˙˙˙˙ý˙˙˙˙˙˙˙ţ˙ý˙˙˙ý˙ţ˙˙˙˙˙˙˙˙˙ţ˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙ţ˙ý˙ţ˙ý˙ţ˙ý˙ü˙ý˙ţ˙ţ˙ţ˙ţ˙ţ˙ţ˙˙˙˙˙ü˙˙˙˙˙ţ˙ţ˙ţ˙ţ˙ţ˙ţ˙ţ˙˙˙˙˙ý˙ý˙ý˙ţ˙˙˙ý˙˙˙ţ˙ţ˙ţ˙ţ˙ü˙ţ˙˙˙˙˙ţ˙˙˙˙˙ű˙ú˙ý˙˙˙ţ˙ý˙˙˙˙˙˙˙ţ˙˙˙˙˙˙˙ţ˙ý˙ţ˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙ţ˙ü˙ý˙˙˙˙˙˙˙ţ˙ţ˙ţ˙˙˙˙˙ţ˙˙˙˙˙ţ˙˙˙ţ˙˙˙˙˙ţ˙ţ˙˙˙˙˙ţ˙ý˙ţ˙˙˙ţ˙ţ˙˙˙ţ˙˙˙ý˙ů˙ú˙ü˙ţ˙˙˙˙˙˙˙ţ˙˙˙ţ˙ţ˙˙˙ü˙˙˙˙˙ü˙ţ˙ü˙ü˙˙˙ţ˙ý˙˙˙ţ˙ý˙ý˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙ý˙ý˙˙˙ţ˙˙˙ţ˙˙˙ţ˙ý˙ţ˙˙˙ý˙ý˙ţ˙˙˙˙˙˙˙˙˙ţ˙˙˙ţ˙ţ˙˙˙˙˙˙˙˙˙ţ˙ţ˙ý˙˙˙˙˙˙˙˙˙ţ˙ü˙ű˙ű˙ű˙ú˙ü˙ţ˙ý˙ü˙ţ˙˙˙ý˙ü˙ű˙ü˙˙˙˙˙˙˙ý˙˙˙ý˙ý˙ţ˙˙˙˙˙ý˙ţ˙˙˙˙˙˙˙˙˙˙˙ý˙ý˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙ţ˙ü˙ý˙˙˙˙˙˙˙ţ˙˙˙ý˙ű˙ű˙ü˙ý˙ţ˙ţ˙ű˙ú˙ţ˙˙˙ţ˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙ý˙ü˙˙˙ý˙ý˙ý˙ţ˙˙˙˙˙ý˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙ţ˙ý˙ý˙˙˙˙˙ţ˙ü˙ý˙˙˙˙˙ţ˙˙˙˙˙ţ˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙ý˙ţ˙ţ˙ţ˙˙˙ý˙ü˙ţ˙˙˙ý˙ü˙˙˙˙˙ý˙ţ˙˙˙ţ˙ü˙ý˙ţ˙˙˙˙˙˙˙ţ˙˙˙˙˙˙˙ţ˙˙˙˙˙ţ˙˙˙ţ˙˙˙˙˙˙˙ý˙ü˙ü˙ü˙ü˙˙˙˙˙ý˙˙˙˙˙ţ˙ţ˙ţ˙ý˙ű˙ţ˙˙˙ţ˙˙˙ţ˙ý˙ţ˙ý˙˙˙ţ˙˙˙˙˙ý˙ü˙ü˙ţ˙˙˙ý˙ý˙ţ˙˙˙ţ˙˙˙ţ˙ţ˙˙˙˙˙ý˙ü˙ţ˙˙˙˙˙ý˙ü˙ű˙ý˙˙˙˙˙ţ˙ý˙˙˙˙˙ţ˙ţ˙ţ˙ţ˙ţ˙ţ˙ţ˙ţ˙ţ˙˙˙ţ˙ü˙ţ˙ţ˙ü˙ü˙˙˙˙˙ţ˙ţ˙ţ˙˙˙ţ˙ý˙ü˙ý˙˙˙ü˙ţ˙˙˙˙˙˙˙˙˙˙˙˙˙ţ˙ý˙ţ˙˙˙˙˙˙˙ţ˙ý˙˙˙˙˙ţ˙˙˙ţ˙ţ˙˙˙˙˙˙˙ţ˙ţ˙ü˙ű˙ú˙ű˙ý˙ţ˙˙˙ţ˙ý˙˙˙˙˙ţ˙˙˙ţ˙ü˙ý˙˙˙ý˙ý˙ţ˙ţ˙˙˙ţ˙ţ˙ţ˙˙˙ţ˙ý˙˙˙ţ˙ý˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙ţ˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙ý˙ý˙ţ˙˙˙˙˙ţ˙˙˙˙˙ţ˙˙˙˙˙ý˙ű˙ů˙ú˙ţ˙˙˙˙˙˙˙˙˙ý˙˙˙˙˙˙˙˙˙ţ˙˙˙˙˙˙˙ţ˙˙˙˙˙ţ˙˙˙ţ˙˙˙˙˙˙˙ü˙ţ˙ţ˙˙˙˙˙ţ˙˙˙˙˙ţ˙˙˙˙˙˙˙˙˙˙˙˙˙ţ˙ü˙ý˙ţ˙˙˙˙˙ý˙˙˙˙˙˙˙˙˙ý˙ý˙˙˙˙˙ţ˙ü˙ţ˙˙˙ţ˙ţ˙˙˙ţ˙˙˙ţ˙ţ˙˙˙˙˙˙˙˙˙ţ˙ţ˙ý˙˙˙˙˙ü˙ý˙˙˙ţ˙˙˙˙˙ţ˙ţ˙˙˙ý˙ţ˙ý˙˙˙˙˙˙˙ý˙ü˙˙˙˙˙ý˙˙˙ţ˙ţ˙ţ˙˙˙ţ˙ü˙ţ˙ţ˙˙˙˙˙˙˙˙˙ţ˙ţ˙ţ˙ü˙ý˙˙˙ţ˙˙˙ţ˙ţ˙˙˙˙˙˙˙ţ˙˙˙˙˙ü˙ý˙ţ˙˙˙˙˙ý˙ý˙ý˙ý˙ý˙ý˙ü˙˙˙˙˙ţ˙˙˙˙˙˙˙ţ˙ý˙ý˙ţ˙ý˙˙˙˙˙ţ˙˙˙ţ˙ü˙ű˙ý˙ţ˙ţ˙ţ˙ţ˙ý˙ý˙ý˙ý˙˙˙˙˙ţ˙ţ˙˙˙ý˙ţ˙ý˙ű˙ü˙˙˙ţ˙ý˙ţ˙˙˙ţ˙ý˙ţ˙ţ˙ü˙ţ˙ý˙˙˙˙˙ţ˙˙˙ţ˙˙˙ý˙ü˙ý˙ţ˙ţ˙ü˙ţ˙ý˙ü˙ţ˙ţ˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙ţ˙ý˙˙˙ţ˙ý˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙ţ˙˙˙˙˙˙˙ţ˙ü˙ý˙ţ˙˙˙˙˙˙˙ţ˙˙˙ţ˙ý˙˙˙ţ˙ý˙˙˙˙˙ţ˙˙˙ţ˙ű˙ů˙ű˙˙˙˙˙ü˙ü˙˙˙ţ˙˙˙˙˙˙˙ü˙ü˙ü˙ü˙ü˙ý˙ý˙ű˙ü˙ţ˙ý˙ý˙ţ˙ý˙˙˙˙˙ţ˙˙˙˙˙ţ˙˙˙ţ˙ü˙ý˙˙˙ţ˙ţ˙˙˙˙˙ţ˙ţ˙˙˙ţ˙ţ˙ţ˙ý˙ü˙ý˙ü˙ű˙ţ˙ý˙˙˙ţ˙ţ˙ţ˙ý˙ü˙ţ˙ţ˙ţ˙˙˙˙˙ý˙˙˙˙˙˙˙ţ˙ý˙ţ˙˙˙˙˙˙˙˙˙˙˙ü˙ű˙ý˙˙˙˙˙ţ˙˙˙˙˙˙˙˙˙ý˙ü˙ý˙ţ˙ţ˙˙˙ţ˙˙˙ü˙ý˙ý˙ý˙˙˙ý˙ý˙ţ˙˙˙˙˙˙˙ü˙ű˙ţ˙˙˙˙˙ţ˙˙˙˙˙ţ˙ţ˙ý˙ţ˙ţ˙˙˙˙˙˙˙˙˙˙˙˙˙ý˙ţ˙ţ˙ţ˙ţ˙˙˙ţ˙ţ˙ţ˙˙˙ü˙ü˙˙˙ţ˙˙˙˙˙ţ˙ţ˙ţ˙ý˙ý˙ţ˙ţ˙˙˙ţ˙ý˙ü˙ü˙ű˙ý˙ţ˙ţ˙˙˙ý˙ţ˙˙˙˙˙ţ˙ţ˙˙˙˙˙˙˙ţ˙ü˙ü˙ţ˙ţ˙˙˙ţ˙ţ˙˙˙˙˙ţ˙˙˙ţ˙ţ˙ý˙ţ˙˙˙ţ˙ţ˙ü˙ţ˙ţ˙˙˙˙˙ý˙˙˙˙˙ü˙ý˙˙˙ţ˙ţ˙ý˙ý˙ü˙ý˙˙˙ţ˙ý˙ţ˙ý˙˙˙˙˙ţ˙ý˙ý˙˙˙˙˙˙˙˙˙˙˙ţ˙˙˙ý˙ý˙ţ˙˙˙˙˙˙˙ý˙ý˙˙˙˙˙ţ˙˙˙˙˙ü˙ü˙ý˙˙˙˙˙ţ˙˙˙˙˙˙˙ţ˙ý˙˙˙ü˙ű˙ü˙˙˙˙˙ţ˙ţ˙ţ˙˙˙˙˙˙˙˙˙ü˙ű˙ü˙˙˙˙˙˙˙ý˙˙˙˙˙˙˙˙˙ţ˙˙˙˙˙ţ˙˙˙ţ˙ý˙ţ˙ý˙˙˙˙˙ţ˙˙˙ţ˙ü˙˙˙ţ˙˙˙ý˙˙˙˙˙˙˙ţ˙ü˙ü˙ü˙ţ˙ý˙ţ˙ý˙ü˙ü˙˙˙˙˙ţ˙ţ˙ý˙ţ˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙ý˙ý˙ţ˙˙˙ţ˙ű˙ü˙˙˙˙˙ý˙˙˙ý˙ţ˙˙˙˙˙ý˙ţ˙ý˙ţ˙˙˙ţ˙ţ˙ţ˙ý˙ű˙ý˙˙˙ţ˙˙˙˙˙ý˙ý˙ţ˙ý˙˙˙ţ˙ü˙ý˙˙˙ţ˙ü˙ý˙˙˙˙˙˙˙ý˙ü˙ţ˙ţ˙ţ˙ţ˙ţ˙ý˙ú˙ý˙˙˙˙˙ţ˙˙˙˙˙ü˙ü˙ţ˙ý˙ý˙˙˙˙˙ý˙˙˙ţ˙ţ˙ţ˙ţ˙ţ˙ý˙ű˙ú˙ü˙ţ˙˙˙ţ˙˙˙˙˙ý˙ţ˙˙˙˙˙ý˙ţ˙˙˙˙˙˙˙ţ˙ü˙ý˙ţ˙ü˙ý˙ţ˙ţ˙ţ˙ţ˙ţ˙˙˙ü˙ü˙˙˙ý˙ý˙˙˙˙˙ţ˙ü˙ű˙ű˙˙˙˙˙˙˙ţ˙ý˙ţ˙˙˙ţ˙ý˙ý˙˙˙ţ˙ü˙˙˙˙˙ý˙ţ˙˙˙˙˙ţ˙˙˙˙˙˙˙˙˙˙˙ţ˙˙˙ý˙ý˙ý˙ý˙ţ˙ţ˙ţ˙ţ˙˙˙˙˙ý˙˙˙˙˙ţ˙ţ˙ü˙ţ˙˙˙ţ˙ţ˙ţ˙ţ˙ý˙ţ˙ţ˙ţ˙ü˙ý˙˙˙ţ˙˙˙ţ˙ţ˙ţ˙ţ˙ý˙ý˙ţ˙˙˙ţ˙ţ˙ţ˙ţ˙˙˙ý˙ý˙ý˙˙˙˙˙˙˙ţ˙ţ˙ţ˙˙˙ţ˙ţ˙ü˙ţ˙ţ˙ţ˙˙˙ţ˙ţ˙˙˙˙˙ţ˙ý˙ü˙˙˙˙˙ü˙ţ˙˙˙˙˙˙˙˙˙˙˙ý˙ý˙˙˙ţ˙ţ˙ţ˙ţ˙˙˙ý˙ü˙ţ˙ţ˙ţ˙˙˙˙˙ü˙ý˙˙˙˙˙˙˙˙˙˙˙˙˙ý˙ü˙ű˙ü˙˙˙˙˙˙˙ý˙ţ˙˙˙˙˙ý˙ţ˙˙˙˙˙ţ˙ý˙ţ˙˙˙ý˙ý˙˙˙ţ˙ý˙ţ˙ý˙˙˙˙˙ţ˙˙˙ţ˙ý˙˙˙˙˙˙˙˙˙ý˙ţ˙ý˙ü˙˙˙˙˙ţ˙ţ˙ţ˙ţ˙ţ˙˙˙˙˙ý˙ü˙ţ˙˙˙ý˙ţ˙˙˙ý˙ý˙ü˙ű˙ţ˙˙˙ý˙ý˙˙˙ţ˙ţ˙ţ˙ţ˙ţ˙˙˙ţ˙˙˙˙˙ü˙ú˙ý˙˙˙ý˙ü˙ţ˙˙˙ý˙ý˙˙˙˙˙ţ˙ý˙˙˙ţ˙ţ˙ţ˙ý˙ţ˙˙˙ţ˙ţ˙ţ˙˙˙˙˙˙˙˙˙˙˙ţ˙˙˙ý˙˙˙˙˙ý˙˙˙˙˙˙˙ý˙ý˙ý˙˙˙ţ˙ţ˙˙˙˙˙ţ˙ý˙ţ˙˙˙ţ˙˙˙˙˙ý˙ţ˙ü˙ú˙ý˙ý˙ü˙ý˙˙˙˙˙˙˙˙˙˙˙ţ˙ü˙ý˙ý˙ý˙˙˙ţ˙ý˙˙˙˙˙˙˙ü˙ý˙˙˙ţ˙ý˙˙˙ü˙ű˙ţ˙ý˙ý˙˙˙˙˙˙˙ţ˙˙˙˙˙˙˙ţ˙˙˙ţ˙˙˙ţ˙˙˙ţ˙˙˙˙˙˙˙˙˙ţ˙ü˙ý˙˙˙ţ˙ţ˙˙˙ţ˙ţ˙ý˙ţ˙˙˙ý˙ü˙˙˙ţ˙ú˙ű˙˙˙˙˙ţ˙ü˙ű˙ý˙˙˙˙˙˙˙˙˙ý˙ü˙ţ˙˙˙˙˙ţ˙ţ˙ý˙ţ˙ţ˙ü˙ý˙ý˙ü˙ţ˙˙˙˙˙ű˙ý˙ţ˙ţ˙˙˙ţ˙ý˙˙˙˙˙ý˙˙˙˙˙˙˙ý˙ý˙ţ˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙ţ˙ý˙ţ˙˙˙ţ˙˙˙˙˙ţ˙ý˙˙˙˙˙˙˙ţ˙ý˙ü˙˙˙˙˙˙˙˙˙˙˙˙˙ý˙˙˙˙˙ý˙˙˙˙˙ü˙ű˙ţ˙˙˙˙˙ţ˙ý˙˙˙ý˙˙˙˙˙ý˙˙˙˙˙˙˙˙˙ý˙ţ˙˙˙ţ˙˙˙ý˙ţ˙˙˙˙˙˙˙˙˙ţ˙ý˙ü˙ý˙ţ˙˙˙ý˙ý˙˙˙˙˙˙˙˙˙ţ˙ý˙ý˙ü˙ţ˙˙˙˙˙˙˙˙˙ţ˙ţ˙˙˙˙˙˙˙˙˙˙˙ţ˙ţ˙ű˙ý˙˙˙˙˙ţ˙˙˙˙˙˙˙˙˙ţ˙ú˙ü˙˙˙ţ˙ý˙˙˙˙˙ţ˙˙˙˙˙˙˙ý˙˙˙˙˙ü˙ý˙˙˙ţ˙˙˙ţ˙ý˙˙˙˙˙ü˙˙˙˙˙ý˙ţ˙ţ˙˙˙˙˙˙˙˙˙ţ˙˙˙ý˙ý˙˙˙ţ˙˙˙˙˙ý˙˙˙ţ˙ý˙ţ˙ţ˙ţ˙ü˙ţ˙ţ˙ý˙ý˙ý˙˙˙ý˙ý˙˙˙ý˙ţ˙ţ˙˙˙˙˙ţ˙˙˙˙˙˙˙ţ˙˙˙ţ˙ţ˙˙˙ţ˙˙˙˙˙ţ˙˙˙˙˙ý˙ţ˙˙˙˙˙ţ˙ţ˙ý˙ü˙ü˙ü˙ý˙ý˙ý˙ţ˙ţ˙˙˙˙˙˙˙˙˙ţ˙ý˙˙˙ţ˙ý˙˙˙˙˙˙˙˙˙˙˙ý˙ţ˙ţ˙˙˙˙˙ţ˙˙˙˙˙ý˙ţ˙ý˙ý˙ý˙ý˙ţ˙˙˙˙˙ý˙ţ˙ý˙˙˙ý˙ý˙ţ˙˙˙ý˙ţ˙˙˙˙˙ţ˙ţ˙ý˙ý˙˙˙˙˙ţ˙˙˙ţ˙ţ˙˙˙ţ˙ţ˙ý˙ü˙˙˙˙˙ţ˙˙˙˙˙ţ˙ţ˙˙˙ý˙ţ˙˙˙ý˙ý˙ţ˙˙˙ţ˙ý˙ţ˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙ý˙ü˙˙˙ţ˙ţ˙ý˙ü˙ý˙˙˙ţ˙ý˙ü˙ü˙ý˙ü˙˙˙ţ˙ţ˙ý˙ţ˙˙˙˙˙˙˙˙˙ý˙ý˙˙˙˙˙˙˙ü˙ű˙ý˙˙˙ţ˙ý˙˙˙˙˙˙˙˙˙ý˙ţ˙ţ˙˙˙˙˙ţ˙˙˙ţ˙ý˙˙˙˙˙˙˙˙˙ý˙ţ˙ý˙˙˙˙˙˙˙˙˙ý˙ý˙˙˙˙˙˙˙˙˙ţ˙ý˙ţ˙ţ˙ţ˙ţ˙ý˙ţ˙ţ˙˙˙ý˙ý˙ţ˙˙˙˙˙˙˙˙˙ý˙ü˙˙˙˙˙˙˙ü˙ü˙ü˙ű˙ű˙ţ˙˙˙˙˙˙˙ü˙ű˙˙˙ţ˙ţ˙˙˙˙˙˙˙˙˙˙˙˙˙ţ˙ţ˙ţ˙ý˙ţ˙ţ˙˙˙ý˙ü˙ţ˙ţ˙˙˙ţ˙ý˙ü˙ţ˙˙˙˙˙ţ˙˙˙˙˙˙˙˙˙ý˙˙˙˙˙ţ˙˙˙˙˙ý˙ţ˙˙˙ü˙ü˙ţ˙˙˙ţ˙ü˙ű˙ý˙˙˙ý˙ý˙ý˙˙˙ţ˙˙˙ţ˙ý˙ţ˙˙˙˙˙˙˙ţ˙ý˙˙˙˙˙ţ˙ý˙˙˙˙˙ţ˙˙˙ţ˙ţ˙ţ˙ý˙ţ˙ţ˙˙˙˙˙ý˙˙˙˙˙˙˙˙˙ţ˙ý˙ţ˙˙˙˙˙ţ˙˙˙˙˙ţ˙˙˙˙˙˙˙ţ˙˙˙˙˙ţ˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙ţ˙˙˙˙˙˙˙˙˙˙˙ţ˙ü˙˙˙˙˙ű˙ý˙˙˙ţ˙ý˙ţ˙ţ˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙ţ˙ü˙ý˙˙˙ţ˙ţ˙˙˙ý˙ü˙ý˙˙˙˙˙ý˙ţ˙˙˙˙˙˙˙ý˙ý˙ý˙˙˙˙˙ţ˙ý˙ü˙ű˙˙˙ţ˙ţ˙ţ˙ţ˙ţ˙˙˙ţ˙˙˙ţ˙ţ˙˙˙ţ˙ý˙ý˙ü˙ţ˙ý˙˙˙˙˙ţ˙˙˙˙˙˙˙ý˙ý˙ţ˙˙˙˙˙ţ˙ý˙ü˙ü˙ý˙˙˙˙˙˙˙˙˙ţ˙ţ˙ţ˙ţ˙ţ˙˙˙˙˙˙˙˙˙ţ˙ţ˙˙˙˙˙ţ˙˙˙ţ˙˙˙ţ˙ţ˙˙˙ý˙ü˙ý˙˙˙˙˙˙˙˙˙ţ˙˙˙ţ˙ţ˙ý˙ü˙ü˙ü˙˙˙ţ˙ü˙ý˙ţ˙ü˙ů˙ř˙ű˙˙˙˙˙˙˙˙˙˙˙ţ˙ţ˙ý˙ţ˙ţ˙ţ˙ü˙ţ˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙ü˙ű˙ý˙ţ˙ý˙˙˙˙˙ţ˙˙˙˙˙˙˙˙˙ý˙ű˙ý˙˙˙˙˙ţ˙ý˙˙˙˙˙˙˙˙˙ý˙ţ˙ý˙˙˙˙˙ţ˙˙˙˙˙˙˙˙˙˙˙ý˙ý˙ű˙ţ˙ý˙˙˙˙˙ü˙ű˙ý˙˙˙˙˙ţ˙ţ˙ý˙˙˙ţ˙ý˙ý˙ţ˙ű˙ü˙˙˙ý˙ţ˙˙˙˙˙˙˙ü˙ü˙˙˙˙˙˙˙ý˙ű˙ţ˙˙˙˙˙˙˙˙˙ţ˙ţ˙ţ˙˙˙˙˙˙˙ý˙ţ˙ţ˙˙˙˙˙˙˙ţ˙˙˙˙˙ý˙ű˙ü˙ţ˙ý˙ü˙ţ˙ţ˙˙˙˙˙ţ˙˙˙ţ˙˙˙ţ˙ţ˙ţ˙ţ˙˙˙˙˙ţ˙ý˙ţ˙˙˙˙˙ţ˙˙˙˙˙ý˙˙˙ţ˙ü˙˙˙˙˙˙˙˙˙˙˙˙˙ţ˙ţ˙ţ˙ü˙˙˙˙˙ţ˙ý˙˙˙˙˙ţ˙˙˙˙˙ţ˙ţ˙˙˙ţ˙˙˙˙˙ý˙ţ˙ű˙ü˙˙˙˙˙ţ˙ţ˙ţ˙˙˙ţ˙˙˙˙˙˙˙ţ˙ţ˙˙˙˙˙˙˙˙˙ý˙ţ˙ü˙ű˙ý˙˙˙˙˙ţ˙ţ˙˙˙ţ˙˙˙˙˙ţ˙ü˙ý˙ţ˙˙˙˙˙˙˙˙˙˙˙ý˙ú˙ű˙˙˙ţ˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙ţ˙ţ˙˙˙ţ˙ü˙ţ˙˙˙ţ˙˙˙˙˙ţ˙ý˙ü˙ý˙ţ˙ţ˙ţ˙˙˙ţ˙˙˙ţ˙ţ˙˙˙ţ˙ý˙ý˙˙˙ţ˙ü˙˙˙ţ˙˙˙ţ˙ý˙˙˙˙˙ţ˙˙˙˙˙ţ˙ü˙˙˙˙˙ü˙ü˙ü˙ţ˙ü˙ţ˙ţ˙ý˙ú˙ü˙˙˙ü˙ű˙ţ˙˙˙˙˙ý˙ţ˙˙˙ţ˙˙˙˙˙ţ˙˙˙˙˙ţ˙˙˙˙˙ţ˙ý˙ü˙ü˙˙˙ý˙ű˙ű˙ü˙ý˙˙˙˙˙˙˙˙˙˙˙ý˙˙˙˙˙˙˙˙˙ţ˙ţ˙˙˙ţ˙ţ˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙ţ˙ý˙ý˙ţ˙˙˙ţ˙˙˙ţ˙˙˙ţ˙ü˙ţ˙ţ˙ü˙ü˙ü˙ý˙ý˙ţ˙ţ˙ţ˙ţ˙ţ˙ý˙˙˙ü˙ü˙˙˙ţ˙˙˙˙˙˙˙ý˙ţ˙ý˙˙˙ţ˙ţ˙ţ˙ü˙ţ˙ţ˙ţ˙ý˙ü˙˙˙˙˙ţ˙˙˙ţ˙˙˙ţ˙˙˙˙˙ü˙ü˙˙˙ţ˙˙˙˙˙˙˙˙˙˙˙ü˙˙˙˙˙ţ˙˙˙ý˙ü˙ţ˙˙˙ý˙ţ˙ý˙ü˙ţ˙ţ˙ţ˙˙˙ţ˙ţ˙˙˙˙˙ţ˙˙˙˙˙˙˙˙˙˙˙ţ˙˙˙ţ˙ţ˙˙˙˙˙˙˙ý˙ú˙ű˙ţ˙˙˙ţ˙ü˙˙˙˙˙ý˙ü˙ý˙˙˙ţ˙˙˙ý˙ý˙ţ˙˙˙˙˙ý˙ű˙ü˙ţ˙˙˙˙˙˙˙˙˙ţ˙˙˙˙˙ţ˙˙˙ý˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙ý˙ý˙˙˙ü˙ü˙ţ˙ţ˙˙˙˙˙ý˙ý˙˙˙˙˙ý˙˙˙ý˙ţ˙ţ˙˙˙ţ˙ý˙ţ˙ţ˙˙˙ý˙ţ˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙ţ˙ý˙ý˙˙˙ý˙˙˙˙˙ý˙ţ˙˙˙ţ˙ţ˙˙˙ţ˙ţ˙ţ˙˙˙ţ˙˙˙˙˙ţ˙ý˙ţ˙˙˙ý˙ý˙ü˙ţ˙˙˙˙˙ü˙ţ˙˙˙ý˙ý˙ţ˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙ţ˙ü˙ţ˙˙˙˙ID3 ID3beetbox-beets-01f1faf/test/rsrc/unparseable.alac.m4a000066400000000000000000000153441472325477400225110ustar00rootroot00000000000000 ftypM4A M4A mp42isom ćmoovlmvhdÍĎáÖÍĎáÖ¬D¬D@îtrak\tkhdÍĎáÖÍĎáÖ¬D@Šmdia mdhdÍĎáÖÍĎáÖ¬D¬DUÄ"hdlrsoun@minfsmhd$dinfdref url stblXstsdHalac¬D$alac( ˙¬UF¬D stts  D(stsc@stsz #·9ßÂçɬI9Rstco´’ „udta |meta"hdlrmdirappl+ilst#©daydataOct 3, 1995 #freeňfree ěmdat řÇ˙„˙€ňĆÝ0Ŕ-ÇqŤŢÇ]ż(ť+¦?ŕُ˙€+F?ŕø8Â;ť˝řŚa×®óîňŚ8ŔăŃ„D ;Â; íî@tŚ#ş0ë×y€úFÜíµĎ Ç]ŕ7xývďGvď ŰĘ'Pu”Ľć:ďŔGpúĆÜy»~€đĂŻ]ŕ}ăq‡q‡^ŕ7o@Ý˝ŔGp˙{Ś8R0„v»~€üĚuŢţĂ®đüľ0Žŕţp˙€¤ŕţ]Gq{€đŢ˙ů{˙đ2˙€ňĆ×ŢĽĄ˙]Ś8~q„wđ.Á×oŃĘ˙IŚ#»w€ţGp˙˝Ś8Ŕ"ă»Ŕđ Á×xţ©Gp¤aĂţp˙€rŽŕţAp˙€*ŽŕţýGvďzď˙dŚ#¸HÇűĆzíúÜ˝Ŕ uŢ˝,:ďŔăđnqú6\ Hčç=ć9Ď@?´bč €˙ť€¤aŔG;Ć.€˙€0Ž€”aŔÇ+{€Ś]nđ»^€ĆüHs°`s°}Ł@~C­Űk¨óŚ uŘôŁ@Ŕ&Ç[·Ŕ0Ł ŕç`ÎŔ˙ZŚ/řlbčř bëw€ sglÉ@ţ'şÝĽ€ýG@věĽbĺ=v>ń…˙· xnŢ@˙A˙GŚ]ÎŔç Ěsž€~ĂťŻ\RqŃĄúĆü€s°yFüŚs°Ŕ$‡;üäs°ŔAă ţ<9Ř?AÖíł©T…€Ý€Ś]nŢ@™úľňű‡;Ö0ÎĆě˙€9Fü1t€Ň0ÎŔéąO@ţc­Ď@^vTFhR5•Oř LÉÜÉ˙('ü4Ą»řR›KeußăÉĘ ˙UżŹř|ćR´R¨.˛ŘŠ„! DRJ…BˇR•)˝É!ę‘×'&J(‚ZÄ ‰@¤,¨T3”¤¸™DV*đ×ů3’AĨŠS'l‘*SŔŐ…["(+‚9¨BPP%1"!A›ţNw’#)‰"I R™.P!ÁS"*”r I˛±(BSŔ ‡Ŕ $ÇüňęD«…)şŔ JSŔ S]r#żŕr'·ü¶xäBSŔďřőţ ţ ýr!CżŕÓcţĐT;“#Ŕ¦Oř ĚŇoř}ű’M˙á‘Ô&wđŇŠ?ŕ'üxĺv§üľ˙‚‘pbeetbox-beets-01f1faf/test/rsrc/unparseable.ape000066400000000000000000000322041472325477400216700ustar00rootroot00000000000000MAC –4Ü3Ů0' 0éoĹ|ÍţqôčîD¬D¬P('¸č+6ąD€»…›gzżă˙nč±`ČéŻCÔC-]áXUÚ˝ŰÎý–Ěą€ËRôa‹% –ýŇń0ó©'lOéŘ[&ď(ďďHő)ś"i%6aMJO1FĹ@…D8Ůqlpů¤ŚE ]“ą$ę ĺýwëvy0äĎ\ţv7®Ś±I®Ć ̇m7Ą.uMŇÉč$Ĺý #­×x|‰Sźúgb;<·|:}|{H?k¬ň"ÁÓ{äěćwß áÝéݡ"vútŽ`OM".˙Ä+.Ë÷"q=f“; ĺF&8e*’<ű´íH4ŠČiçŢ-w»´7rý:¤ŰśJ \ó!¶Ü$W'8[4“ě«„ËAvěMXFĘUÄÍťHU®qta!;{;Şëą ĘÁ­ŕG^ôad¶Gę™gŚľPĄ­)«Ś·Č úîŘ™ÜzĐšSŽřg_ęQ¤Ü=ĎS Şů¶¸âčMż‡nć”– ­u%ă÷ú˘ÓÁ8˝ Ě úx«=j]m6“aú@Ť“I›L]ö~çęn0.*§ăĎćcžânQjŠ0‚ôęí‰6j®ń1ÚC%g§4ß\-GhŮ3Ý•[čŕH"’—ó@ů>Ż3°+©ŮÎ˙äÜG˝ů§W3rÚKn>śŇÚ+{ç˛ňô·Š¬´ &…ń6uż_zęmeS)K+2ňkň± éŘ8ôÝ‘cĽ3L®ÝÚ퍎Şv˛WܸÚßčŃ‘¶Yŕş“@§1"kâr}őqIĆţu ¸ ˇ©[#ŘČôđ¦‘w“f•—C?s·”b Ś[XAĹĘ Ă=‰‹\ ú„yäüťvJeą•ŮׂfÁ$0˛¤yŁž}:ŽÎ Ř­ô[čr‰zŘ>Q†Áx „“(® ~ 9Ĺ5QţKť¸ÓÓČÓ&)±¶‘9jÜaĄ«`ľę‹A8¤Ě×– ”©żŐ"“ŕ†;bR3¸ “D©ťW vćÇ)ăĎłS›ÎŁŽŹÚĘ̲A}ĎĐꂇTÖ‹ľDcXHwüWE>Čŕ‘'•:ЕȜÄqqoie4>#Ź÷ -&}ě#}FŹí5pĹ㏱ăł*"•(taŕˇÂŕĹ“"Ń)®BÜ -·,AEexjĄźňÚ†›űu®RŹH×n_¶n¶(ö¶Îâg'v9HĂ^%Ü!?ľ"XÎ@µŰÚ6×›R÷…­‰ŻĚÎyÔĆá=ŔĎ‚öŻ–8/Ö3VőMć»8ż±˙u!đ™rP@ÄÔŻ ›“řqSzá~ĺjwń¤xTčv¨TÚ.'˙î†>ĐäŇš?Î`XşůĘT÷:™—ýĚ Aă?”1˛\@đd±Fâ|ɶĆD˝‹°6ő Xőś“4„,đÁMÝRż#÷°ŚĄ&ěeőľ­E„›źväĘýS˛š+vWČăsJ˙Ëžýxń˘Ěśă{ö[·>¦pQ… t˙łňŽ)®Ě ¤Ç`ămnX±^xőÚ-Ç®“©=®ćŤ´|ťt˘Ŕr…βţ‰ą!ɰvČ ^Tż ßN‘k1˝9Ą#śŢiÔ[@ÝöSÉ‘’č&ŔĎvš¶¦_Ö.ŻoţUśťć÷ŮŐt÷xLÚ˝ęř‰H/ ‚Ďm…ŇfčżĂÉ‹.y Ć8Ô÷KüQE¶p:•7'§Nnľ„ě 8lhËŻîo=›}Żľ•9P}Ví{sÔč˙ŽŤďnsŔXiVÓ”íEI€íH=ŇősşK—Î{ÍÁ»ŘŇcyO€g›ˇPű‘–ő‰żÉĹţµ\3yy8#;çtş2‡˙ę[mš ©í‡˙‰y0ÚäQv{Dź×®ú3@ä ă­h˝lXÂqKž!®!őĐÖąú§çÚ‡źîŔř–%5Pú]×|ąő´††#i2‰öíÍý Eäůě›ęL˙î­ 7XĄ˘)E»[Ýá[Z$ѾƺŖ˝żîKšW¤ĄŇĂśíešY#ńŮ,‰íŤŢ„ě.eŔŇ<Š[čÍ؆,ŤI#ŚôŇ·5´BÚ ˘6Ěĺ)ĄŮůMP™12]ž Bäă'‘i"’-,ąXQ3Á™€Ř_@řĺ©ĆY5ÎŚDdťm P Uëe*zď =ý…,ňc‡-żgCó˝6a‘˘1ucô°,a$őÖ×zܤ ćV‚ňŔRĺ “”$}ôÇĹ~ď¸9EZç1°H.1.–j¸˘îżź{—N]aL·.7 ¦Ż€DQ‰l¨°Ůń‘±89Ĺgg­vDmę^ĹĽäžďđDUV‰.’‹ „OX“EV˝ńúĹŻĹż>qázŻ™Q}(Ż#¸MoNŞţ‚,–-ç~bűc–97ţŰ W_ýÚ 'sžNYO j]feČu´Ę6óÝ3¬ĄNâ‘+1g¸`äjÄ;Vn6ý˛pf_OBdkŕžpěuŐqž\HM…kŹűÇbµY2}¶PĎe'ĽŇqCGˇ[Jě7‰P~Oëę ’k¬™‘Ő3“/—A ]%†°h@ ozWÇlýŇ0—uąrî[˙KŸ}–ún˛gţĚ@)E˘ …/ď׿•A ă썚¦›źÝéFz ‹ěӓ͸B‰@[Ë %Ű×á!Ę~nÂ8o¦®H#7±=ŞđG źöżqÍĐóО`Ka˙„íHĂPXŮóú{ ŚŻŢz“đ|\+Čç|+Nn{W´*ă›Ňý2†usá1\$«J÷‰µ®E3Ő˙°Ű (ÓÁň‰ĄQl µÓâű¦\ő€V 6°ě¶†RĐ-]ú–SŃM¬´«}.ŇZ+í)BĎw{”ű]ňŹ×ĽBôŽ Ł[Ú~š–’‹©–uÖęk‹§*ë[“Réů˝ĽČKń^˙Wń.ýŹyx^čSĄ`ąÇXń Tť9 Á‰}­ ,f%cň¶z¤Cdđ°‰/oűXfÍÁgî/ňŽćÖLb ăząIŃ9z,çik߸m2ĂHßč:ÚČ™U3±6ę›ĐÔVŕ-­!S)­Tµţňt —Ăߊq‚üăÎ4zM’î=03mcí(f4E×Ů9¨Ji#çŘ$Ö€˛‡.Ľ¶˝î—fąOŰša—żÇFaöćoËFVą68qc•ň:u[PŻ_«eŇžfŕëf›ŰÖ»$;RľíźÄ=§Ś‘!MŹĆ—íS±(ŮbéLIŹçoWŮ>ňź“î铳ŇÓ¶—S{Iî=C.…ŠŰÍ'ĂÎ?^“Hut†q§ą›Yn—¦´®1o2˛ňz.˝ :LŤ?ŃÁmt‘¦×x) Lěĺ\číö󬍩K•aż´¬p\!)ó §÷ŻP#ű…?#™đĆÎęX58ć»ů޵¸*l¦&#ŕp(müL‡3–B±AóéV$®ň÷\Ö^›G4–Řď”yłŢ°"Q’©s¬Żaň—R%VŞwPRő±űoĆuŇÍ‘?şËèđÔř| ŃjĹĆ ßFZ© Uíçíw¦łĘĺÝLeňt—ŢˇŁ±ß\®]ťnfČ yř*e6úŚ.ü¦”PŤčZ Öâ‚ ş!ýi…ď«{ď¤Ý†S¶hÚëęŘgeyäÎy&úń —Ę6#ýF(;`;S÷íţX·ŕ‡«¶~Nţ>^„Ćjß˝#䬭VĎ%ކ„Řd8tLíĄÚF:änś_ îőČxrr«ĺô± „ŕŽVˇ˙—;ś!ŃĆüHŮĄ©˝IÄAş_\d™ęŢYšštIËľ§ăzŇ!¦%,Ľ"{–( –ĆiđĹĹŻÓďI«»öŤe˛˘mnřqĄŚÉ…włËX!qőüm­~"ś.Ă5Ě08ˇ8’ëA&›k÷y»pĚÝg¸ţäˇ!O úĆ5Ň/#Fv˛Ńľ»AßaI$¶öůIM$éV]ĆŽÎtőş ą\€Ť^ö*­B§X/e`{Ćh ÎLóęĄ5ÎV4híE@ňEÁhŚŤ2˛H1C|ggÍĘłĎß® :O­ —Bż ¸9’Ś0˛ďXwTl}Š$4¨éť]¸ô‘“l“ÎôFÝެ˝0ĆeSbŽ+ż’í‰<ŃL¸J‘ő^‹!hŤňďˇSiűA Źfa‚B$?é×ZAt¸# – =š üżÎGú—çTűÔűőUľ‘‘+FčX §L7˙»-áăx»µżN`ŻÁ„6$cöÓ ÁÂBÚő:—ţN’mkCŘ=ŽăF/}8âÖă^ÚDđAsą´®#x_™•C«KT]ÖƲÔľ˝mřa×á}üo† Vú´âä‚€ +Z*łN÷·Gť°.™ IĘ›ęáS \†Ä?ΤRý}-JzăˇÖbÍ­węµŘµ„ …Čť™_’ä7ĄĆóĄsRáÁĹtÍ­n—’ëT;×ńŚl CŽS÷Q’ëĄŐ>Tˇă‹#A陡Ż:^ŻhőQ”ŠĺÚĆĐĚU fB\˙śm\ŐMrYó^ů`—A‡ô–śEëQďZ‹’Ź˙ĚŞ™ľĐ8#”Ú:V:ŢgJo’C‚ß0~×}”ćtž4$ĄşóÔň°Ái“@b8epÚµÁµíĆE+b÷y–ßb´čłň„«.ÓĐîv0ů›™H9ű('Ť%™ÎqÓÁ‰‡N«)†{ś‰b˛§?›,-nţ x˝k Î?UT…áʰ4vl”ĄP{%ŞÝ‹vÄuݦBnđCżjI÷4CLJ§ŹěłXk?fCŻĺjpőßżh×q”«ě50RHU*‡ŇŘĚ-m‹:Á|ă5´;Q•›µß&Ľůě¶PérçŐnŽŚL‘Uçň×5÷©yéšI,OYˇ*„áůŽ“)XLĐN5 8đ‡L¨Vá6«łW 9Q R#+ކ K˘ěýäXÜg.náá2Šá»í2KŇŘ—ďTőÂŢËÇÝ„ĄF0XC‡,S_ç-ĎÔ,ŢÇńäÄÜŕ–¸‚ ö3"¤m˛wr•‰kBÇR‰žÎręK¦Éĺ.yú°V:±Ü;â *ť 0čk˝—VĽ!ľ“ŽBçýę ÔŠ¬±ěż´Šú祽ň´D…¶ďbhtÄ·+ǵ!dĆŻ˘t'UNa¤…‘řĂČĆłRV»3J†™˘ńqôňď(ÖôŹ<xm%cw†8Ňž gVlz/ZŔφ“é{ꇕőGď@îvŤ•“pذU×ML-wmŐ<ő[ü ‰ŽęiAĺ8\0"k87Ęž??—…Ź Zî"¶B'ěŤry/ ڬG’ń)Ę•Hśjľd/¦EŢLŃĆ˙4×8éG©Vä^LÁĘr1˙›¤äP =ŁżgŚ(F݇VĹŮY´ü4Ĺd•SĐL×ZÄČ˙@,Ú†qń­Ű÷sUwOéB©ŘŤ‹đáińŽ$Bżü(Ü»/ŢśSŔŃůdńëŽ3{Y¨¸¶ŰAňĄ ŮS'¦ęçX*=eB3;näŚ1 îr§ír™‚ĘG۶šăÍ®TóÄÍ <ĽĚžá—‰šyłsWűCáÜ=ŞÝĂIjĐyg!=°Š¤mŻ\2Łö}bD!bëŻ}ÂWśš˘ĺĐÓÝ=XŘj´:×…*hýřA81 #$ÄcŘWfš'çF]iJo·âY…˙d çmdĂß B~!=9- •ÔµţŢ yMMfŞżlŤp‹ŮŢuÁßá°ć•ą6ą•]«Ůřk˛¨6´%{둞: µ·Ĺłë₍ďŤű`çśş!TĚşp=ygxć(q–®ČZnĽ@ŠEľűö˝aśeŻc6ho†UčŹĹ[óuř"Đď¤}†aĚäť“ĐÉ>:óŽ)ß»^]Q¬#őy«‰Câ«9ĺÜĹÎŢŃĹ…˙GąDZjśłí&⥎Aŕ=䲽um©q>©jJF¨’!ŮÍŔZ »{Ę^Š©óf!— o:Kç\tÚJ8™X@ —úÎj Ü4«ş;07:MóŃ!©çń3Vď‚€dܱ MÝppËTýµ]÷?lťŹórÔÔV/b „Ö†68¨ş.'“[±$<בiﮂ¶ćŽż4 Í®§đú€1ÜŹń.†úôÂ~pç”d>?ŘŐľćw‰8­±PąLçbđ\8Q·Ě˛mďťf\ e+‰ő˝Í® ů?ĘěŞd)dś“ÉóŽŤĄµ„Ź…®W™®´ŃIr$YB8v@Ř 06ŹŠf%¸.ý·Ę+1_ĘŁ€Ěî^Ѥŕ <•şîp[GépĎŚwĚ ‚ěŮ´EâÔq6ŰŞńË´×›ý_-/Ľ‰kç qNúăKŽńLDu÷ďá’ c_DM!ĚâçwsV·¨ą -đUÓ÷ăŘźAŻŠ´Ľq®g@Ukwíěp~SE9˝tݲduÍŹt˙}*Á&ďYeŹzÂîŰSŮńEóÝâ=|çÁ Ʀ ;ďĐ<_ŹoâLÜŤňÔ|’'Y÷űČá=‡2¶y˘nď˛Ě}VśúRětOćĽ ţ•A0gÎ:KÉ{‡ěµÂ]č­I‰}/ŕŔjóŇŹah•_t ®I$5‘uĚ&.µŇq¤2ÍÎ^żw…dMMOč1-)zŕuńĂX<Ű]ÖIâ ŽČ" Ý,/g® €ă|őŻ>éźu¦¶ËŔ9ĂV˙ŹűžÉŇ/ň5k€ě-!“[Äř&ţńŚ FÖ Ö{ëńq±ŘőwhŽĄ ćéyő3˛]—ćäéŰŰP\5NDůěŮžožcAČSéâVm>[ţâ}kE:Ž"üşÍTĆ ?Wź î'aď3QíjŠuŘxgÇúnrj–čMQyµäçźĎťhřV´W†–&Ţ˙ŰEŮ)ó–Č«<ŞýLÓm˙ŵJEöíÄDâ|řdćCjąžßA§+ä׉Ôw,Ý rcĆýMÜ­«M“ĎפŤkFKgÓÇ üˇÇZcxé ěĘ)lQYŽ*RuątO’2˘Ľ^˝ňµW@ýäŢ‚˝ÄĐ`ţÜÂTťŻšÜ† fCpK:©Ţl)Ü9Řc––Mž¸¦ß®&)n,•ě‹Nń%ů»ß4Ťç{Ä?3ťĐ^@>čČJ„.ňE| 0ˇ×\Ťě§=e÷7üŘsÉZ$ 5Ţ«w? \."<$Ó)´ťßpäRwčĚß6Î;ně\+@°ĂiÍő”ë–RxĂŁ7Öî.V€~傦Bmk4ń@áé‚%?-f$1ż˛ÍFbńçšă•˛ßPďeCS r:ýJ(üqŔf‹µ9âçä¶L iáŘŐÁNS(†î)»%w–yűyđS(Ô$("­Ť¤-­ŁH2Á‹˛f>šËŮVďŕ$xb‹žsĆhĂ%uÎIíňm+'đNÝçdëź?żpőË—8}ÖÖĘŕpÝbŤčî§‘™ß謏ŕĚŠPÄ«Ż(KL.Íg ĽkHĽa+Śxé‚3ü1›+éOéçĚ…ar±7& ëÁvődVóůOÉËę6!ď3ŢB4˙ř,F|sÄ·ˇž¦ëżőw@*B`Đ_]ćq>ů/ èĂńť˝ř×Ô"ęÎÇNiK&”…=.˙hľŔ-JÓěćÎ;RvÓő¨„Ŕ;YDŻźđÂpâšôT>JmŃŘ„§3ażŐS ¬J+`Ő=*ăŚ1o05ÖYĽň1¨kŻ2eîÉŇÚĂ:ĹŽň5IrÝ Źĺ¬WÚ1Ë1(Ł X=~É`ä5ř;B,d—´5ŠB KZOů>üS.ŹTÝ Š é%*¸ŚĹşţíoOú4yJÔ8"B‘¶`G YSôâ#ź‰ U¨I• 1ú÷nůäîy)~Nj7Ź÷•ZÜgą\|˘˘EĐ «˙«0‚ŽínSîĹŤŔĽ“UúP Rf=˛ÎŇòUEą!ż_’šďŰ?~ ĺŽ+ćΰÔ[Ł!W;ţ#„µ‘ú%‚tqšeŠ*ÚĹö»ĘŽLđ ű‹ňY9WTd Ę+żvsÓ~Vň!@śş’šÝ ;éWÉďlťŃÚUéf€ć6€ŇGľŤ´y ˛ü–Ą%rŽŠLb@çĺ-ŹŹ,ßÚy ÁĄÝ MxR 伝o^‹ÉüdçN¬źôf¤ĎóÄĺmŕ% Ű1÷[ňKÇ;‹.žúBşĹ,ý˘á:ţŁţ^§ÁĚ&µ> ę/!†3|Ö«SŢ—yTßF?řË0E22˘>¤jQu?ěžx€©’űî~$[Ó«™8ôŻů{Üw!?I‰’a ×)!ö@ŕ°˝Č@¶ jTz\žů+ţUĽ•řĐęĂŞĆü߼–T˝Ś‘ŁşBŚ+.ú€Ý%Ű'±Zú oMO™ś8b(şÔßg.ŮŘE·ťácGZŇËęźB—ćľWx‚»5őÔ0¤'śç##9]!@&ĚóŹÝ˘FýL†FŠĐ*ß•Âa¤6ď`­Ł 1\j¶Ś©űܦ#ÂôĘÁ«ĚŔ5~µZ ś¬;s–íąŘźŻo9­5 :!Ď$˝—ˇ`môŽJşŇ¨ŃC+śţH;q !LO‚ńŞá’bŤ5­O~ 6D„o§őÉňŃkCZĺą@ȶD›©t†xĹř Űőż4‡ď¬Ž<śŚU§9ë—žćgů˛ź•ë–zŰ"rUŻ BśŚ‡,ż|Ľ?;Éůńń0sę@aŠ€ËłŤéYňó:‰’@5¦1ńq±0ĆYDăc:âPoBę.Ő­˛WZ˛ÍDL÷·ŢÜ ĹěZ­°ezž> \_Ľ7tm–îÓë9&˘DütÉđ—Ü•hI >kć…F€‡ţŇLöŃqWPBX`á#¦´«ÚćAßůţ#ÎÓXnł$u1ŤY ŔuĂ|ţE)čx†Hżő.®-ż\tż<7¶dĆ®(ěTüľ%ž(Í‚%Çę–¨«¸™Ç¨-’7ď±=Şaß’Ö±ü@†?Ŕ‘ČTˇ}ý‰öó·p ÷§ŕp(¶ŤRW‚»\źT@˛ ž˝îâ˛;)BhôR9ďţ÷ďß®ťă«´ßě?}_™‰lż &°Y–čę˝odŠ‘ô*ďđĘżdD«´âí€ŤĚ łűŞ]›őëha >ôyTMp´2‹¸e q¬±Ť8/ßźjČ ÚǫɿŔĎ kł Y® ľ m’čş].Ę}űß`ć4Ę1Sy;€ŕ,ÉŻt r!Mő1Á:°/i)¸XŁŽźçĚa‘Xö–ř 4đRš(Ś”ŽŐŐxşäIfQ3sĺ/ŚŔ†Ńöž5ŇŐĂ',ö÷,˝Ç˛Şu[rwÝţ ÖĎn i á‰Tç†Ó9XĎs§łŠč \·Ř =şśôî1 "¸coäĹ/ęˇă……¶“ éžk?'-ÍHWŞk …ukČ1 0f›-ÝŹĐ”¬5˛gńéăŞřÖc» q;ZŔî5\ÂÓY5*÷[ŕŘx–ŔńŻß R»Y b˘úÄ_(%…×L™ëa h˛§5(·yÓn>ť‘›éDŠ-¤Á»/HňŇ{uäef/TCIÇ·R+FlÁ±řÄ» ÷UăÚ8ďúfBűa¦Ţ<š’9 ˇŚUvnɆŚ`g Kł1–ŠNťDß·¬ ÇL§? :ÚYS3oÜ 2b~ôg ó(š/¨y'ć”´¸ç@I%ťJšZĐö3 lülŠY±=± ^!uaŻĽeAöč(†ź34ţܵ˙ĎSşaţ^ŔrŠŃrmgG`+n]8Ôīż‹ů¨_Ă`ń řHpn]áĽ.2Děň{µ¨đDáÁí¶|’>– ÇÓö~­mWĚÚxIť#Żg—zžČ;‡*Ő~¶ďĹ ¦ëŢziť9§6@ű<ű˛ÂÔĆşI&o /Ţ…tŻÔ,=?«v•s»ÜĄŇÎ @™R)e~3ŰŃëŤűcęhiI—·Ëť¸Ą9«ľ9Ü|F{˙C2V1ŤŤ°÷ŢSYęôše@µ 9_, í ń9%¶óŁůÂúĂbź…#ć2ä`O\úČf+e^ěϦŹę«_~úĆaŐgŰÖ;¸i † ď‘:§3mmŘÁÚţ„ ź{w©!dP-ĆćSęgŁ0şÂÁ[Ü{efOŠ –s@¶ôĎŃ_!”um˛hńM}ő!Źĺ‹ë+Ň&‚6YH C‹DµŐ{_ĹĹЧ0T7m`)C/4 Ő/a‡ •uâ©}XŰăcF ‰›Çš“SéşÄGÔĂ€býťzđóąEísÚ‚XĄçs Ú˙Ů‘ž˙:°÷dţ%ÎN—fÉóá ôVÍó ç掶¤CXĎIýóę +{A:3ťÎ×\4ń&0]-k•ČM$8ŁÄúâţ…éă§Ťjë]at ¸˙±/ć‰h& Ě(ŢŚj MÎśűĘ+«ó˝ăĎąˇÇÉ8,'ÂC˛Y0‚ůnů­eCÄ«®"ˇĹ«B¨ű1řęĺŚůN>R.pń1ü§)L­©Ó…WRW‡ŰZű°Ĺl˙ç„ŁŞ–ÝŚµé¤ &Ě‘ÔÜ+UáĚ&Ú«u´ýÝÖ~XŞĎ"cÁ"l@u¸Ń0ľůňČý×Ó'Ř ‚S˘XŠŔăZ¨\%téÔrŠ‹n¤Ą5Ěç*¨\QzáĆ=@%â|żřŃű.TÂO"µŘ¸dGúE"sXYŕ!ľäx­}M§ÖÎAdLąÖ\•«üóÍ˝lö·¦˛ńeQMYĽ’ăĆÓŃęěÎâĐň^§źşŠÚđ­)č›ňę)—*ńćö} q ç‡ 0‹ăe¨ÇpwvšÓ ćĄIÂŻĽŘW‡ô,2_FüZlŤŕ:Ô%,,ťÜ!ş9ľ…yÓ”)[Ź~CZD,ÚëM“ß|NŔů_¨ĂJ[‘1hëäÔnĄĹ-Ş^÷rsĚ«§ymLBÁ÷Py‡…˙k#”ŃQ{óxĹôgü[ ŘH¬­aĽ ůݤw˘wGŐźů^;Ś<ó?ó¨<ˇÝ!ĆbNmÖđ<^Ë´™É ýPÖËťŇ6»y^‹ůˇĐ†¤ż•;Ń„@Š˘¤vf+]ű˛2{ćˇÜVFŞÚó¨e¦&™k1ŐVîj6Ľ_Ł™f˝×L2bJŻSĚŮmýť«3QIÜ]ą»ÜÖć_źŁ‡đ8řźe$ĘIC„I?(RaLĂɦÉĚ̡IžS…9Č“4Ě)Éš Nr!(RyB’xJDPĘ99”9ůe‡=0ĘLĺ…%™3™ĚĘ™ˇśˇÂ!(S&RćaJIáś(S'0§' 3ÉL”)̧ dó‘ žPćg0‰2PĐĘL°łBˇ’PÉ”šg>i'Bee I…8RP¤ČNd‘ śáNLćIL(Rg ćS% ”&JLICˇBPJhPäĚĘB”“ś¤ĎźĐĺ%%% ¤ĘJaĘB„ó–r$™He$ÉB!†™?"ÉĘ~s”3™LçĐ))ú(Iˇ Rg(g0‰3Í’ž†sˇČBĚ4“śáśü¦ffL¤śˇÉLšOIô9CĂ2|%PÎ ”Ă)%2PJ ”(pˇC9333š…0ÎhsCť śĺ Ę,ü.)=!ĺ ¦NRg” &rPÎIfg0Ó'aš…“3ćRfe B!3™)Pá@˛J8PÉ |ôĂ(PáN†PÎJB…&PáÎae%32’~dBag2’|¦s@‰0ů™’S33' ” ”&D'0˛M ”(P§9CśˇĚç3Âś)")’!2!ÉĐ(RPô9čdˇÂ!†g(Re'ü¦JB!HS)“)’L”)8XDe0ó ™”)2“ţS'”ź)2“”ÉůB“2“úfs9™C93% ™ÉĐÂĂ)2†rPˇL”?)2… ć'ĘLˇĚĺ'ˇĘsĘdňs†JIL”Ě)™2!†“3) RLňaILšaIɲR‡&sśćH… LĚĚ”ÉLĚ̡śĚĐ)(D39%'¤"‘ śź”2S%% 礡I”ÉL”3’™™?(S0Đ Rgź”Ě”2Pˇ)üˇÉ”„@Ś€D…„ĘM0”Ă33&“¤ˇĘ™'ĘJ”™ç)™’†ÉÂś(2e„ʡĎĘNP¤Ę)’g%„ĘJśˇ2!“™’†IÉČ„ĘY&’t&S$ł,¤é†…'% IC% ¦™”8“˙řÉś@˙˙µĆP!9BR‡™“ÎPđĺ”$ň…'3% ”šdp§&†NfaI”ĂĚág2’eB„ˇ’†O”)™ĚСĘ”ˇś"9’…&i)Ď"(fg?C'ĚĚ™9śü°@¤Ě̡ś™C8D2pˇÎfs9śĎ$Iś°ů”…2†s rP<“9))"xL¤ĚˇĚ.Iˇ‰RR†r„ô śôĚĘ’” JH|¤ůgÓ0¤¦e áLĘI4ź”Îç–ÉL,"8r™)’™ś)3IĄ ¦Â!™…“3„@°ĺ$ĘL"ÉB’źB„ŇIˇBR‡‰™“Bae2S’…2O …(M ”š™aNfe…% r“)%$¦)(ú¤)(d”™ó"y2†…'ˇ’†Lˇ“\žRe'”(pˇfS „ˇ¤ĚčfP)™(hd¤äC‡ˇĘfRdBe ”ÉňÂ’‡)4(g0d§)<Â!„I<3’…&yB“śĐ¤Ę“ÉčfPç%2RĚňĘáBÉ™”źĐÂĂý&…&tť0ňtÉNLˇĚ¦r‡RS aä:ϡ’… Ěś3C9C9$C…3$C”„™Ěó dÍ'L<”)2™śĺ$¦IBćfrD)ffg% !JCL9”3% LÄP¦NM0ňPˇIśÉfLˇĚç9C™™C9śÉĂäĚĚ”ĚĚ”) "™™’”š™¦g(sň’P¤,Ę™43źC'™Îg3™I”™L„Lź)™:ž, ™IĄś3C')’s$@¤ˇI)(S%2XD$Cś"B”9śˇśśĎ9C 9ž~YˇCS… Đ3§$ąś)“™™śÎPÎgĺ2J“)(r„ˇC“2e%ś2™†…%™")śˇĚĚÉB’g fg2’rS2áaśĺrD)3‘„B„PĘ™C”„Cś2““4ÉLĚĚćyĘÉOĐááC38R†rdˇ’…'8D9ĚćS!°"dčr’…&2e%PĘB‡“™2 P¤ÂžS…9ĘNPСə)Rf’…&Rr!C”(Rri…2s2RPˇĚÓRtšaäˇI(p RfP"9Ě‘ ”)(RO% 2D9™ĚĚĚ̡L™@ç)śË3”3“™á,(hRC (žJP”ˇ=ÉäÓ ĺS3śĺ će2hPç (dˇ’“"r!3ÎRg(L¤ô̦B”“˙)(ICý’’‡…&|ˇC“™ĘÂä/”™C“(s …|ĐÉü°¤äB| (Iň…&r†)™9™™)?”ĚÎ}“‡3”<<’…!BÉ”3Ây™”9”ÎdI'”™dˇ¤Ą Ę9BrP„BO’ F,ý ”ĂĚ9’Ę“2rS3%%%% IBĄfP,:R†B%…JI”3%‡3(g0ł3™")’™:JfP"\ˇIź2’fs9™‡(̙ʔ"IćD) ™>P¤(RrRĄ!BÂ’…$”(JPçˇIBĚĚĚ)9™™™™)(r“Ňçô'ü¦ađô%Ô̔32P)3Ďź(y…2pˇĂś)I<9”9™™™)™Âś(S3™™™)’S&D&D3„I”ź)9I”ČDÄÇ˙řɉ@µĆč!339śĘađ§ dóÎfaĘ!s LĐÉB‡ N“L<”)8g?Ň…ÎP¦L¦JP”ˇ4(JBa&P¦Lä@Í °¦fM ç˙ˇÍ L¤ˇ”38Pć…&RS‡g=De É”„I”3…0¦NJJHD)9‡’Y@¤ˇ™ĺ2Pˇáäś˙)(L¤”“ĺ9ŇzM0ĺ Ě,Ϥ(X&xD™”4 J)9’™’„@ááe'I”” JÉĺ2|¤ˇ(p¦NP¤Â!Â’’…P¤ĘM Jfd¤äC'30§!a”Ă™”8D8D)(Dśˇ)™†R NĎ)>čr„ˇ’L™@ÎLˇÉćs0¦aIB‡“L¤ˇ™™‡’áBhD $đˇI”Ďśˇ™)™’ P¤ÂźË 9ÎRg33338S3'333$C'%'IŇP¤đä@ňdC338D4(Y3”% “2D(JJBP˛…&fffpł p¦e J“2Pä@ĺ%(OPĚĚ“ÉJ™)’™Ę̤”™IĘd‘…3 fg3™ĚʡśĚÉL””ÉLɦ“ĺ2t °”ČDÉ32S&’†„C™ň™9L(D% ™I9„C%'(rPÎe&S'ˇBRs:ś)’Xe&r’e!¦JaL™IL–g9C„C'2S dä¦yůL<…2hfp‰3IŇt(dĄ čÉĚ”'C &RM% s)2™(”<ˇÎ¤ź 4ÉL̡š<ÉĐĘLL¤¤¦g dĘ™“IB“†p¦N‡"8e Ě™B‡3˙čp‰(ćJB‰“™™’!Â!IśˇĚůLĚĺ!9ćrS%2S9’’†xs¤ô32|¦NPćRz9Ędé;&ɦ¤ˇLĚĎ’’ĚĘdčr†fL¤ĘfS3™ůNĚĚĚĚçˇÎD’RhP”ň™:Ă”93™Â™9™ĘNS$Đ“Be$ˇH_üó9CĚć’…&)“ňśé(Re0§$ D”)ĚĘÉ”332S<ň…dˇÉ™4źčäź)“ţ’…&RzJˇC… ‘R…PĚÉ”™Ę(SPÉI„”))RP¤Ą ä”ÉLćRIILÂ…!Oˇ= ý&”'S$ňS”™Ę™(S0¤Â‡ (s,ˇĘ2fr’†fsˇIš™Â!Îd¦M0ňPˇĚФ"!JIpĘNfs<ˇaš,"ʡ“ćRe‡(Nd¦fJd§ 3™C%% B‡ =%RP¦g„ˇäĚÉ”‘ ćOš?ô(JĂ„@°ĘM0ËHD  R š™ĎI‘ Đ)HR’Rf“C„I…)$ˇĎIˇN¤™I”” Naˇ4(9Bžˇ43™žyC@¤Í äd§ĐˇĚ˙)“ y'3”(s39™…2s Lˇ”™IB…!BÎYô2r‡3”3“3%9ź””2zÎI’d¦g˙B’S!IůL9śĚĘ̤”,>`A“)%8r!)ô2„Ę|ůBŔrddĄ$ĺ B– J™’™™ćyĘ y‡(dĐÉBLćK32P¤ç)”%čf‡)’™))(RLçĐÉBe&†y™śĚÂe&)2’’†‡))(sŇzaňî’˙řÉŽ@˙˙µĆČ!ĺ” 0Ą B’IBĘĐ" XAPˇ2y…™L””33 Re BśÉL)’”)8PáLĚĚÎdađ§2Pˇ’”% Có”3™Îd¤¤ˇÍ'I¤–P<ĂB†…32‡(PÎĺ&S3<9 0ćrS%0ˇIB™…… ź”Ě” NdĐ)d"LΔ)3Cś¤ f†r„ˇIIBÉśáá‘ 43…2JL¦OC”š2S”ĚĘdćfg8Y’!™…2Pg„ĐÉB“‡‡%gţ~˙”ČDĚ)Ě"pŢ Y…Č\’Ę… Đ)(“ĂÉ40Đp¤ˇIBś…™™Ă”2džáe ůˇ&„…ňi(Rp¤ˇaNH†fNaćg”9…(p…™ĚćyůˇBxPćg2…!9Ěó$C%(‘’‡&pˇI™Ęś4ˇ”ÉÉ32P°Js3%2i…2hs>RPĎ0¤ĘJžg(y2Â!<ôš2†pĄ$B‡2fJfP¤ĚĚ”,)"&fsCB“Ě‘0řP˛N„ˇIša)†Re3˙ˇ…Sž… Đô”šdˇĘ’‡y(RIr8S39ÎRr…ä̇É&PÉáË JfPç†áLš¤"ĂB’śĎ™”&DD8e&’†…% H)I”Ě㇠p¦g ‡PćfPÉĂ”9IL”ÉIB“)’…&zB„¦OÍ!))(Y2„Č„Ęfr‡(L‰2’’ Rg ¤Č„ó2“’M ”<ť2PˇĚ–P,2‡ fPÎsĐÎPÎe™JË…8RPˇĚ¤P§(faĚ,™CS… 3™™(s)…(JfRfK0§„„ł%)‡Â™2†s<ˇÉĚćJ9”ĚÉL“†pR¤ü¦N‡"(r‡‡)Y0dŇIfrS3<ĺ&y™…&S32†gNICźČˇBPĘaňg2†rPĎP)’”<”)”™LÜͅ&fPç rap’Čr‡)(hP”ĺ2PÉĺLĚĚ"L‘ @‚ś¦L¤Ë J™(™(g&e s%% ĘađÍ ĎC”ž‡(S33'% J,8RP))ž†sBf¦ĘL°¦OžP¤”)39CC”„B“9C„C™LžS8P‰ ç(g&PćRJa̤Í™”“™S2P°¤ł330dćO…8hR9C)&p°") PÎĚÎRe3ť2S2“"…&g 3Ęp§'(g0LĘ… C$ˇC…9C”2Pç)9śĚĚĚĚĘĘaĎ)™ĘHPáNdI„IC)2’’!ÉB’gLÉś3’śôÂ’…3' (J3CB‡&ffe$đĘB……”<3… Č)čd%% ”$dR) d¦HHYśĘaÎP¦aC33…8P°Ë f!Jdś“Ă9ůC8D9ÎdL"d”Τ",,"ÎP¤ D™IC’!BSś¤ˇ=™“% ”ĚÎr“ś¤"Lç3ĚüĐÉCš™C””ĚĚĚĚÂÂ’!(S…9™Ěĺ ”’IžP¤Ą đΤ%0”) RSúĐááC”2“(S$ˇH|3IB™’™"Í ćD’|¤že'ý0řhD!0‰ s™™)(Re39”ĂL§aBÂĘ’áˇNĚĚćp¤ĘN™6‡C˙řɇ@µĆŘ! (e&J2„"d¤¤ˇL)Đ,Ę)XRR~RNdNaĎţ‡9Éś¤že$ˇIĚ”) 2RD JćRO3” dĚč™Cś˛‡™)“IĐÉóç”) PĚÎL¤’Rs 0ł JI„C%9O&™)48YCÉB‡'’„HP˛fH‡9Ió PŇO@ł4(rO”ś¤Â&O P“LůI”(RPô&0¦g$ĄJ…)%2hd RdˇI”$¤’!2 s‡y‡xffN†J†hNRP4ĺźÓ…933&Re!äËɤô9C”9é†hdˇ…’~S'9C8D%8DĂ™32RRD NLˇ”™¦J B$)ĘaN‡4ÉrD) ÉB†… Jś(s4ÉfáNsĚł)HPˇCB‡(sB‡'(Pĺ d§(Sd”áfP¤)HS3%3%%P¦frJd¤ˇIB’…“9C)3˙I)BJdĄ0Ą ”Ěĺ ĚůLžPćÉĚĚĘaNLÉţ…&S39""ÎffPůB™™2 s4'BJaIš"(dŇzJś”¤(fPćP°ˇaB“=” ̡̜Ę% NRO2‡Â™™?@§ 9”9é(PСI”<šd¦r…“)(RS3”)3”32rS&’™,ÉCIÓ ¤™ĘˇI”ćy”™I˙ůC™śĺ¦K‡ÉIL”ĚÎPСä,¤ y™:)“)S&e$ůȆJ|ˇa˙úaL“2z8s)&Pá̡ś)™’!’™)™™Be'"I432 FáL™Iç"̡śˇĘd§ $R"Lź)…ČS3%™”8\ˇIL¦™)…% 8Y(pRNIL%RbĎśÎd°ˇˇ˙”” NdˇC“8e2Pť!BÉś)9CÉů)<2ÎS'ô(g&sŇtšˇáśčRp§'ĘĚç(ry…’S3ź?(Y3”ÉJ“y’!žC% ‡<¦f™Ďţ’†„Bg%2Y’™†…%39’!“™)(důLĚź”ĚÎPć‡4)0p Yť áB!3'3śá¤ţN™™S<ť ”ÉĐšˇäĐĺ%2„ćJfp§ fd¦J@°ˇNtĚĘL¤”ČD™Â™śü¤Ě””)(S“(psúˇ”%3"ňs(dˇLÉđŇ„ĐÉL”ĚÂ!HS˙ţY”ĚÉBP,™ĎĐÍ0°ÉB™@ł'33 dČĘdĄ!BÂ’!(D%&IdĘd§0°ˇĘJf™ˇĎB|Lˇ3%… °¤Ë%9ÎR ĘĚ)™žS'ĘpĄ$çü¦fLLĐ,(prfO 3ś¤Ęa̤”É(Rs)8S“9†… OCž“"a”…48S2J"™4Ěĺ')“”2s3ĚĚ8S” (ĘfffLĘI”D8s%&…&S$Bd@¤ˇIL”É)(S9šĄ$ĺ&RLÎPÎd‰0˛ff|Ęa™ś"™3(™Éé4<„HeS2e%2S&’… LΓ¤ˇLÂÂ……39IBP, s'ň™%!ćaˇ’aLĚÂÂ!ÉĚ””Ěχ fg pˇIĚ)Éže&R~hD(Lˇś))“LĚ,Â!ˇšy2k»˙řÉ€@˙ţµĆh aśśĚÉBˇHPˇĘˇ™™śôž…9IĎ ćg332R†re ”ˇÉ"Hy™ĘOčg?ˇśň“(PĐ"LÎfaL™L2Ŕh&rÂ’„˙ţe!¤-”'"9L”ĂĚĚ)‡3ˇ™Â’„ĘfO”“ÉNäůó9ś§)™™…8S3%2tĂ)“Ě生)‡e&39C3%É9”3’Y‡)(hRP¦NaBˇI(s)śˇC”(|4ÉIˇ)˙čaf} 礗'IICšaň†hg™đł3P”(y9™”’…&JY„L2“9fSfe ”(J™ĎIčdčN†r™ś”ÉIB!'%™2†PˇÉÂ…$¦M’áBPçL”Ę…0¤ˇO'BP"B„C% )‰2…PˇˇC”ť0"J”3źB‡3C9”ˇ<ˇCś(y™Ę9<šaĎůćs3(g2‡9)ĂÂś)™śé= @¤će$ćD2P¦IChD% JJ2“ ™™(P”"% ”’™)™™Â!™™(RO&’… dňg9C% N9'Ęs‘&i’™)’™)(XS0°ˇL”"̦O˙ţ†eaL’‡%&He$¦ffJ%&r“ç(s9CĚÎPÎS$@J,’PŕA B™’†Oʦdä@¤ˇB“"BśĘL*"B™„C<…(S!|¤˙čPÎL̡ś”2S)“úç)Ă”Ây„BP „ˇB“ÉL‘„PňS™™"3ˇHPСä嚡‚™áLžI2„ˇaI@°ô2’„Bg ĚĚ(Ráae á™HYBS0Ň„)“IC@) dô™fRĚ”2R†fdđ˛ä’!„˛hP˛äĚÎe2†Jr“Ę™)% B dáfe%™ĂĐ™I„C…(sĘÂś"ś’äP”Â…‡3”9™ś”)2™)™’!Â$"&LÉůL“”’P)(PćffJf¤(D†R¤ž<ĂB†y…s„šdł8R„Ę“B‡2…¨‘Ě”¤žP¤ĘO9Ŕ°ˇÎ†sC”„C… Ę44,,"2“ˇI„ĘJfsĺ3 J2S39ÂÉ”9śÎg(Rs”,Ď(rs„C’^Sź”̡ aN!@°ç(O…)33™”8Pđť ç)0¤äˇI”<”ĚĂźčr’’…&hRrS&†fg(hr„ˇ™C)ł$C2“ ̦”&†xe!Ě)’ś"L˙ȆJPÎC”””(s4ž’‡"a¦JfM0”2P)ÂIO”ĂOC”)9…3$¤™“32S&!“Âś„I„C38Y”"(r’…“9ÎdL¤é‡’™"ĘC:B‡&dśÉá<“Ă‘…”2PÉC'Ę™1 ”ĚĚ)(Rs332S’!“ĂĚ)2……8S%3(s?)™Í Na¤é4(ry)’Ě<ÉLÎsś,™™™…&Đć…&S0¤ˇCI…“9™Â“y(RRX B!(D%(rĘ% Lˇ)Î&%Ě“ä‰Ŕ‰(R|¤ˇ0‰2e!Đ"BśĚĚĚšN†só8P§ p°JH„ˇaNÉ”ćPáśÎPó'ţ†p¦OĘJ(PáB“,Îe$üüĺ$ˇIžPˇI…3™”8D32rS$C0ęŕ˙řÉ­@µĆŕ!™”’S NaLÉ”””2P¤ú愞ffJffRˇ“™)’” °“C9¦M% L¤ˇĘô8D4(y:”3D9)(… däˇIĚ”ç,"†PĂáčffd”™ĚćĚ̤Â!Â!ÉB“?ˇśĘL¤ś¤(D¤ňyI@¤"%2ä,ˇĎň“4r††S <§2”‡C9IBs>‡‡&PÍIÓ L™ĚĚÉC@JaNˇś<¦OĘI"%†NJJfd¦aśĘdô J„I2„Đ)Ă™ĚĚĚ̡IĚź?”Ě"ÉB„ÎI RPĐĎ 2D2S$C%2D&D'2†rs9…9?”Ěź)…8PĚÉžg9ţ‡(frP‰ áLĚšL‚HĄ&S2„ˇ”(i’™śĘ™C™ĺ L̤’&INJRBĘáĎC”)2“Đ̡ÉBR˛’…'0§ s“?ţe$.Oň™:%<°¤ˇ= ó@ˇ"gĘdůNt8D‡¤)Ę8r’Ą%% Ě)ś(S&SR B$2!“™”9‡3LÎd¦B$ÂĘfpˇˇB™)śˇˇ9“9žP§ 38P,ˇLĚśĂL”ĚÎPćg(s™Ě˙ĐÉC9B…% Jaʇ †™†RIL)“™…8S…3’DÉň™3”% JJáB$2„óúJfp§&r‡% B”’S‡ĺ9Ó%$@ĐJPÎ L”šLpˇĘJ™C2e39IB™)™’™4ĂÉLĘĘ2f} ”3(s4ž“čdˇ’„ĘO)™™śý ć… O0ňNg”)0P”Ď„Lś™™™™P¤Ď!32Ri(Rs% !Iś3ž„ĐÉC””)2†Ra&RRD 2S'C 3B’‡"ˇ9@s(p‚!N¤ˇL„@°§”ɔÙ̡śś¦g†rNIĺ0Í …'(Pĺ ó”%™")™śĺ&s„@°¤ţ†Re&…!C”ť$dˇĘś)™™ś¦ećH’aá‘&s™”9ůLśË ćffJpˇI™ˇś‘ žP˛e2S9@˛…33ť2Pĺ ˇBD9L))’™‡“B‡3B“):)<§†rs"„B™É)“B‡2S…9™™)RgC”)“ś"(D%&PĺĚ™gŇP¤šś"…&†J(p¤ˇ2$…Ă90¦(S I¦f!JND HCśĘaćdĐÉC”)“)=0¦Iće$ó”śJ”3(p RgúJÎaL™IBÂ…’R†fJś)&dĘ9’‡3śˇćr‡(g Ę™“L<”ĚÉL”‘ ”„BP‰'>IB‡’|ˇ¤ˇa˙С–IĘS!Lť‘„D2R†p¦aI”ĂɦI<”,(sȆJ}’…†Y2’JP2D)2™4)(S' ˇś=Ě"Í3™Ě‘“™Â D…„Ę“"(dˇśé‡ÉФ2Â…&e Iá9(p‰ “„CY@˛e9Idä¦J”3ź9I@#0˙‘LĚĚĚĚś¤ô 3čg(Rf“"aäćs”™C”P,94fNRP)3(O3ĘĘĘ”2R†JaäŇ„Ą% Ňzd¦Pĺ™ÉL–fs9)™™BdBNz"= ˇÂ!'(Pžt(XpJ@‚ż˙řÉ Ş@µĆ g™Âś(Y%„BP‰% LĘ“(r’…2J™””9C”9Ičg(RP¤áI@ł?ä@ćNg)'ś)’hd¤ţ†gM!) RP¤äBe'L‘É4𙎙@¤ˇBzˇL9šaLśĚˇ’‡†…2†pé%33„BP°ˇI™B’”)™4ž“¦ˇC™¤ˇaO9C… Nd§™™™”<>dćffJ‡ô3’™™äˇLÉNaĐ™9††M!šäHICɦH†Jd¦fg$B“:ň™“ćRg)2“9By)CÉ)’YĐ D™™IÓ†x !NC…$C9śĎ= IčJ%(r‡(e&P¦Ng9I”ÉÎs–9™śˇC3dˇĚú2’…%’…… dć™IIINáfK33339B’‡2XrÂ…2rd@ˇˇĂ””ÉL”š4(S2s s3339’!śĎ(Re ”9ILĚĚ)3ü§:9ť =ÉĘśź>e$ó)3”ś˛„ˇC38P°§ĺ8XD $ĘNPС̤ˇC9ž… É”źň™?СĚú"s&Pĺ ÎáNÉ”) ’t3…”8D2RfS%32D2S9Ę™I)’„ĘB!C)3’Pće$ˇIOB„ćL¦r’!ÉC“9™y4Îg30¦L¤ˇÎy… ”)śÎg3) hĚ4ĂĚ”"C"I”“)&g3”)(RP9™Îá¦HĚô3Ă”42PśĘaĘ&JIĘś,"ĚáC…C–çůʆ’fJĘaIś¤Ędź2’IÉý ffa†LćH†p‡<ĘI"9B™ś"„Be ĺ IB’…$C'(dĄB!·(sB‡“"(Pćň“9Ěó™ĚÎPćPäÎffM0ňS$B“ĎIé= ™C’‡3”’„@ĺ 8yIš33&@Éažç)3”™B“%2t3”9ňe%! D9Ęg‡' J32†Jd¦Jps')'BdC'2e&$Î)’™D8YůĘIň™I<”Ě”ČDS2”ÂY„BPˇäô…9,"…2e ¦ˇBS…%3(JfdP”"(g&†L¦Idˇf'Bú%?čfP”“2†…g(g&d¤ˇĘÉäŮ…9ś¤ô dĚĘIˇ†” RaCś§=30dˇLÂÂ’“L>JRˇáĘĺB”ł3”)śÂ$Â!ś(fRe339…2e$@¤¤ÂÂ…'% J™ˇ™Â!HR‡:aNe LüˇI…3 ˇš” :4 P™@ćS8SśPćpňP¤ĘJśÉI2RP¤ćfxĐЧ'(PÍ ó”8D’†aI8P¤ĘJ˙B~faÍ !BS”Ěĺ% ')ŔŚ’YĘpˇ¦S0¤Ě¤Ęfe áůLÎhS9™”&D…)…!Jd˙)’‡‡@ł))“L)ÉLÎt(rfJ%śĚΡ’…%% L¤ˇI”ÉÉáNPáćr“”9™RaáC9”Ěť áI”)†Xf“‘ ś¦fs˙ĐňP)ˇCś(J|ů”8RD%(y<¦dç30ŇP˛adšaL%Ă<źC'ĺ%Â…†C%))™)"™ÎPćKDYCD8P§B@­ý˙řy CÔ@µÇ(©i*”´ĄĄ-*ZUd«(´Ą-IKDĘJYeDÂŇĘRË*YJ”¨µ˘©KQT˘ŇÉ•)ZXš,µ%,©(ą)QjJZ”©IjRL¨šRŇŞT˛Ą)K(­%rWJV*X©bĘ‹R–T˘ä˘ĺ)R‹JVT¬´Ą©J–RŇKQj‹R•µ*KRZÉU%©-EI‹E­*¬©U’T¤š-,©JŞ\˛UbĄ‹KJZ-eKZJÔ––¤©¤‰ŞER‹‹I”–´L´ZĘRĄ©–Tµ,–RRÔ’Ň•-*˛Ň–•YJ‰”Ą%“%JK%T©eIeJRĄ%–”\´˛©,´˛ÔĄ%©.-,¸˛Ô•”´«J\YZ¤ŃeJެĄJŞURŐ*,¤˛•,´¨µ%I2’ÔĄ)--IjR’Ô–YQKR#)-U•*©2•Ee¤ÉeĄ+RZ¨µIJ˘eJK*Y*˛ŇU%eJŞŞŞ¬¨š,–+,©)J‹TZĄ)e%”¬¨šYRËRT¤´«,¬µ*&I‰”¨ĄÉZ˘eeKRU*É+)JZJ˘Ę–TŞŃk*R´˛µ)R˘ĺ*ZX¨šQ5"ęT˛Ę”¤ÉdŃKJYQ4ĄR©eJT˛¤ĄĄ-RZŇU(¸­JRL¤©EKK*’­,©ieRZ’ÔĄ)R•-%R•*Yqbhµ”´•IZ’Ő)RĄ–”Ą©)2’–)&II’ɢʕ&QYKR”©U•,ĄJ«)j*Yh˛ĄV•¨µ%IeDĘRRĄJŞUTµ)QiUJ¬’Ô”Ą¤“IjJL¤µĄĄĄ•+)Q2”¨Ş˘É•Y*Ąe)iEÉe˘©JRĄ)J‰•**R–’ŞUU–\¤©eIJQieĘRŇR”˘Ô\YrTL©JRÔTĄ”¨´˛˛ŇĄ(™(µ\Z”ĄKR©+K-%K%”–”™iIeIIeJʢ•)j•V˘–”µJ-J˛U”©UK.)T˛ĄUU”–T\˛´µEÄĹĄR•,ŞJҬĄJ´«J´¬´±kE,Ą%-)UK-*IU)U*LZYUKJ”©R•JTĄ)&J–*TT«QK)--QrĘ–R’–RĄ,¸ĄÉU,©JZJ©eĹ‹R¤\˘âbŃ2˘eE©EĄ*˛’Ę•ZYRҬĄ”¬«,˛T”Ą)R‹’Z”R¨ĄRZZZ”©e))UeK*Š.J-JK&’”Ą¬Z˘ĄĄ*T\L©KR‹”Z˘eEe%KJ––-QKIU+KTŞË*RËK,©JZ’Z’«)*TĄJZKRVJLZQ4YyUĄVYIjRŃe©)j,´ĄJR–’–”\”Ą)IjKR”Ą*RĄ)JR”¤µ(µEdÉdÉT˘Ő%ZJ˘–’Ą–”µ%©--Dµ%©e*Qr•”¬ĄK)JŠ–”Ą”––\&T’«*-IR”ĄJU*YhĄ¨€q¦beetbox-beets-01f1faf/test/rsrc/unparseable.m4a000066400000000000000000000133461472325477400216120ustar00rootroot00000000000000 ftypM4A M4A mp42isom ŠmoovlmvhdÄLé_ÄLď¬D¸@trak\tkhdÄLé_ÄLď¸@mdia mdhdÄLé_ÄLď¬D¸UÄ"hdlrsounÓminfsmhd$dinfdref url —stblgstsdWmp4a¬D3esds€€€"€€€@xú€€€€€€stts.(stscĚstsz.22""'-+1/+&0''&%)(-,*).)(*%)-4&6.*,$,3/'& stcoĆé •udta Ťmeta"hdlrmdirappl°+ilst#©daydataOct 3, 1995 4freefree(mdatĐě\«11;Ä<Ŕř¤4„ `Ifŕe—^ŕďŹP0#Ü·›ťÁbÄ»VOŘŮ(*ľJ€q8ň„q©-@>Ťč[-3ţ!‹rtgĽĽ(Îą¶q]bĄ¸úÄPEŮ€€ ްDăŮ‘>JUl3ËA$Ö)i)ŔúÄA#€+:3ď`Xa¶™ÉůQ;«Ŕ±čŕö˘" „Î Ž× óf{q wZ“Ä 3zżf#)NŮq'ř˘" „kËr˝ŮÖŁ gʧý'”ę )y*¦Úö»,°Îµxˇú˘" „r †BXVFěě7nhϦNž|z%ôe0Ue®«Ś śö‚B„_`9ĺo˘đJ­Dz0Ż)ňřáŰ1F8‘ú·ĺÉ>7t·#‹Uŕř¤4#ŚXŰőŢ™ľáŕŇ`¸Ž‚Ţ×xT†aG~fäHv<ý¶ÁŔúÄ`? &×63Đ”l:äGŕë’ 莵š„ÇÓëĺN‚ŕú˘R#Aש9Ď<łÔiÇ%ÖřꦗµŠÍÎę®yfžÎô‚T„ `®!I}uhnV$?‹+äˇ(Z«„ÁˇÜtaĄVi±+±l‰8úÄ3#PŤ0Ľń•T`’3ąčžÓ#?}ĄŐőÚ»ěÔ‹€ú¤Q#Hl`µÁ˝ĄËK§)Q)E‚ńő>O˛ÂôŻSÔ¦úĆ"#P ŰdpËźżŰ2é­~sŰČÓďŤ'Pîě=Í&ü!úÄPG<Ž0NÝÍEř™_ő'1…:ő‹ĺ\ř˘ ,l qĽÔş<¬>čđÍ&ďĂž¦J3}•]dQ€€8ú˘`F  8I+–¶:aá–;0ĄÇ>m>;ßMľíŰŹź× Ą/gř¤p>€ŢbSci›”¤L z:~HŤ5M3©'°A+č&„f ‡Ö(pö‚0!dŔ˘’Ż:%˘C°9Błî+]ţ3Z†ąčcJr†Â¶Źö¤`E€a,]µ)\BiwČ,yç@ĂşD¸ůťü'ħĄ¨Üř˘ ĐŔ€0qóŕ=bžmBĹAwůH°×! šDSŞ 8ö‚5 !`Â4†§ĺ^ńíŞ^:$9µĎ…gs`M%…ĘZZtŞ^UŁY(o€ú˘"¤09ś’#NŃ·#̬zÚW›;t A-1dyß”3¤^ ŕú˘" "ěFâwěĽú„,µ¸rŠţ¬js%”źŇísŮfžep=Ľř¤`G €@ŔăŇ5?ŕĽ`•ŘÉÍM˛ČöĆýYB^×;C€ř˘`BČ ĆăiRř—‡iéŽ60 9üľĺÓvë…(Ü˙˙GYJ´…=˘oçlÇWř¤3 BĐ3ÜQhLÖĆ$/ę‡b<÷~łµ8ëűŰΚSŤ­n*jőXeĹ×7’#ŕô‚"(„@Ř Ad¨*Â`óŮ'á¬Á®co·>Vűça &µ% ř¤`F‚ ˘±äßxă}¸<ëĘüfP[2ÚqXä.Š'Iµpú˘" „¤*ÓŘšÖ'ęŠÇÉR™z[oÝÓ¬ía‚„ľ”XÇS2p'ŔřĆ2! éZđŢx˛µľůěú$©żVn´%j(óVŔö¤`F‚€öŽťF†@›c}}đCÂ2>Ű<ĆçO5Ł!ąQŢ/grDŹđú¤aBŕô‡2‰´S(^®ZŹdđ{¦P¤ź~‰Ůۦ‘6 Sşśö˘ „,`Ľ-1wNŢAŔÍ”XPKhˇwKŰ#0R¬Y±Xť-Ŕř˘`E "ĹImq¤>w˘Č1s5{!ÁXş[Ż/ćŰ?ÓGŹĄxř˘2"%€Xä  aÔž®ŽćĎű©ĆĎçż÷şrŻLaŔě\ŞRi"?pě\¬ „ô@Ŕ\beetbox-beets-01f1faf/test/rsrc/unparseable.mp3000066400000000000000000000226011472325477400216220ustar00rootroot00000000000000ID3TDRC Oct 3, 1995˙űPÄInfo(!w  &&,,,33999@@FFFLLSSSYY```fflllssyyy€€†††ŚŚ“““™™   ¦¦¬¬¬łłąąąŔŔĆĆĆĚĚÓÓÓŮŮŕŕŕććěěěóóůůů˙˙9LAME3.97 Ą-ţ@$|B@!w_»ő^˙űPÄ1Şü@TŘ5"L‹‰€TÇ˙˙˙˙˙BhNs‡y„}@3žÇ;çBú2żúźď!?úţ}Cˇ„'B‰¨ŔbĘ€,=°˛ 7›âýcE`M.¦2 :şJwť f}w§[?˙˙˙˙®Ť‘ŇŤW:®Ş…F Śř·BŇ5ĄĘ纡Ö^ł­*+R:kB·ßżŰ®ź˙ß˙ĺ™üľ[&8—Ub0'QX`€hˇe˙˙ź˙Ů2yŐYä˙űRÄ!)± @} 86`€łˇµŕv"6‡¬F” łäÄÓ”šŁźĂ=ŘŠ˙Ă˙íçŢvó?VaVÓŻSä¤ËŚIůóläe÷M[#@~¸¨H'ŠÝzŁU0¤f˙˙˙úZc{˛™Î¤d îî‰tÖÇi1ާ+Ó>b—WnŻ›ľ·˙˙˙ü˙újÝkv}I*shěCÚ #9§™„Ä! ×u»kŃ{µjk¦¨™É+Q@·ăÚ·]šŮ+¶Žşm˙öéF˙żnďâçĂWőČóŻ˙űRÄ;­µ @×Ŕ÷¶a N#ś4ěśHšW®JU?¦^gÍç=żĄv„y*g^M)\ŚčŃZż˝W$4>ÚdLN[Íűĺú>ĄS3őę*Ööß G_ĘxV#łB)ë´eT¶H Ś–šN@Ńk%©§GI,™eU]¦zQ.ű«,¶-Ż¬ŞŁ"Ůőó·˙ő¤~ů}ßJWňť}ŢÖTE¤>é ČMŠB#–¤D™?Č˙ď–\dáO=!›źbtph˝'r†PŚ·¨X.vlČK˙űRÄW ­µ =¶!'ˇ˝…Ö.™|ź˙÷şůănµXNkíÝbß aWIvȇ˘'HT'@ifłł"rŮ•µz:ö˛íŁ<ęŕŠZZVlĄVC¦žĚ´dOŻ÷G+>F{)M¨¦łk-«ˇ‘§!M.hńqő•Ň'ĆfŚz0@ú˙˙úďťşYďmV¶ígÔÖwµT†‘ x…!G!'GtŐ ĚgůÖŹ2éK.ŞüÖµ«mŢ“şŠkśR1U¦cë’ë&ČĘ ÄhÓF…˙űRÄn‰Y± = ¶ Ä'ˇ’#¤´UdVißrŚ —N^’1·|ÓÜQęĺZ=ŢÎëO7Z¶ĆŇŤ}ůăűł«×ĆwáńCĽ[fB˘Ż> ’†R*Š(Ł ?ÎŚŽ˙k;L¨Źf*nSˇSyĚsfiÝT…*2ŮŠ&®ă;+Č•ď˙¦Ťµ żî‚YSőcY4FÚ™s­Î(M 4q°Č¬4EXuŕ(ĐfJ8 vhă dznE"p zł3¬Ó˘z˙ţr|í…˙űRÄ„ Áµ@ 7Á#¶`Č' ÷oň°×«¬Übß—w.Lű7eŠ’@K”Ą)um.W§[MF˘‹vR6"î‘mÚvbÖf±QYŞGuJdłýľş~ü2[q޵öźŹšŠÜfĘBýIŚ“Ů+q Ń áY¤űľčŕl;4·pĚ2!äęŁfŚKËCŽ…I¤«»Y%>®#ŢľŰřţ?ŢŻ‰Źy—ł,¸bˇAY‡ž Ť,‘+Ť…” %*Ś?˙˙˙Ë˙˙˙˙éíçX‘©˙űRÄ– ł =ý¶aH› Ň+ڧş…-zÎv»c±¸F”ř1ŢV”Ôß?Břü'‘ßČ‹űßţoë$–R[ ĹdtŹŻ# 4± KY$íq:4s˛#M’ŔĘŔŐ= „gědLt2•f;^«écµ]Lô[;ŁŐS–Őß˙úßD÷ľ˝ú~"‚i]1N^ w/jÉO)Ş MFW+MČť6_+iĽoĚŠ¬ó%Čěéô™Á® )Ë $'¬ž\W'5ł’źź6?'˙ţpüĎ&Ý™Ţăq˙űRİÉŻ =9¶ ČŁXôˇŮě”<ö =}aÔwĄ“ꫜL»©ä¤NýÝgfˇV X‚Š÷uQNkŘJ$&¨gČ©s©ĐŞčČ„\ôôµ?M?Ý˙’ŰXßéCe+0Ylö¦Ńi4,b`ř=@DW˙˙˙-oU¦—IT;fĘ·j‘ ÇŐ’:ětŃ… ąčs5KÝ[$Ú?Ó#D §Źřţ?ţxç“ëĚô¬-5ń d_e°ŮSä9Ç$, ˙űRÄÄJÍł Ť= 6!'ˇŘaţ˙?éö”`ÂI‰‚”]ĎKi»eóf;éi#R ¶/;:FSś}LÄ×s ÓÔĎW¶óŁ–=h¸c ¤4\jŚ" Eî A 8. `&ˇ r đäŕ‡ĺŹĄ?ôő˙ńň‡¬ż-ŽBĽ}ĺřĺVAT÷QŢ>™Z­v_˙WS›QW3µPÍÇ ›;ŮóŮN~U~í˙ëĺxJ§nŃ,ą!h,č–Ôš ŇdjŤ-i˛ żDH\”˙űRÄŐ ˇŻ ť5ᡌËA€ J ŠĆ@ß˙˙˙EV"ُĚkĚeR‘÷w)ЬÇtzŻ.ćE!ęK©nčĆvząW[şűţúµu—ö¶ëĆ7”…NťJ(ô"5±›Öó@ÁaL=#ŽĹ @˙üżü R‡˙˙˙䉕ŕt˝wiŇ«UGQlwE˛‰ě¬#GđPŕ‘°şâNÜ#›±Î_ůSĚžž‘ś¬?Wý˘Ź“c‚ĂNBĄ%©ëÎn_XčaĺŃ0˛Áej±˙űRÄč 5µ`‡¶ŕ!¸I¶Çď˙˙˙˙iňĚ—31[šĎ-ľy?ď ś””󤊮nK6‘â–ŞÔŐ{±†yŐN~;sůU/µľöĽ˝ÍŻŠ¸»kˇśĐMl0>Př`ůŃP 8XLĆT,aLq€˙˙˙ţĄY˙˙u­>yń˛ďr‡•vá†Ďů¸JK×u|â•1ÚEf®´0’ rl즽ţŐĄéö6\on-dfY”š·®ßi ŐěŃ*˘'m9Ĺ!D##ŕ98ů˙űRÄč eł -ÉA¶ˇ &ř˙˙˙Ż˙˙˙ŢüŹ"1µ“Y2 ĘiR+E˝‹'Ç  NŕÍOJä(aŘ™dÚ%‡y©µţű3;%fr®g,«źXPąüKÔ1¬´ĽŐîS&!§“I…ó—–—ްĐ˙˙˙üç˙˙˙b]ÍaI¤ÝŃÂŕšW„=Ë»¨K=\i“ĹńlÁIus?W;źńÉeűĺß8g—UYńCcÂIęË®)M–VX`‰L3b3EŘ„$TV˙űRÄěLeµ@ŤžÁw¶ ¦¨Ś<Â/˙˙˙˙˙˙ţwËMr:jdZ‘—uĘ@śÍĺW‚ÉÉWŠĂ‰G’=A ‚Á.ŤyXžÎ<˛,·ÎçîWQ`®f[:ş+gdr´’—2vŐ02+#`PH"(ř•Đ˙îM©4D6±Ď‹2•ŐJůݲn¬0^ŚgZ"‘ClV¤±ÔŞŇîEy~źóﱾ˛ü ‰ú…Ča5TŠ…W¦djj¨a#ÉÉTDL@Đl”\ҶĀ˙˙˙ëđ˙˙űRÄęKqł@Ť= ‚¶ łŮó‘vľY™©¶q‰'ţj»%#»+Ńž„l2Ĺ$hś^´ Ń‘PIÄĺ/é^S4ŕ?Ě›Ë3ѱ1WO\+uîŚý”† —¤:!H¸€N /Š„sµ?@ů>˙˙˙˙˙˙ź;Ô)*_ŞąjdJŚĺ$Ç™Ya´ LŚę Ę „Ď8Ů«cľďąç}» Q©űÝđm™ˇQ†çXËďTQŮŘ7(ĎB˘ŁÄäm2• ˙ůK˙˙˙űRÄé ™±@Ť= q6 H¦ůú˙˙˙çlAŻű÷l|őżTęmš(ţ™ýę-r đm±Ôe¶ďȧ9Y™ĺ¦â›ţţcwßo¬÷OĎ̆awV­“¨)“dK‹ž°ŕđĆĺ“&Îx˙˙˙ů”ޞ˙˙ëáÚľË ŤśŹú]ą˛ńľę­\)„›q„]»LBĺÂU—RĐó†eNŢ~ţ~W™ĎßžľR»±;'––ZĆ×Ŕ\µx©,ť·ŮKÂßM·Ž˙˙űRÄë }µ@ ;A”6ŕłŘ˙˙ţ¦/—˙˙Ç9sIBéˇ EËu;#XŠĘV˘‚…ąÎ—D8TuGtF[Š‘.Ôďň+»0–őÍęl˛ŃÇQęAŽj$¦?~ę•(@MNV'ž -Ś–©6p˙˙˙ďúú˙˙‘ś™P\Т/?ôéB%ŻZÖ*Jt˘a.fćýÁšÖÔ’p‰µ-s˙.ß˙ý9żôżî?K=Ť¶ÓK^gϬÁ•Ö3‹ :--€Üý:ö„› Ű`@˙űRÄě %µ` 7Á’6 3ŘŃ Ë×˙úµ6Úň˛ä4Ź"Ş(‡)ťčŠ˘ęCŐ°ą$3ÝXî-,t9XĆ»4çeSU˙ú©Őf¨š?Y“jM¸ń đß%\ȸm ‰`dşND ČVĹꀽW˙˙őÝď5’©î›k3·SH ~śëÍX);“ZÔźQ‘{ů­- JIxeŢçů·Wýüůó+Ö{ż°ňőLäo&ŹłI1ŠźÔÄI˘ČěD`5ĂŔh6 •A1@•˛˙űRÄëK™µŔŤť…6 HłŘĘ$DëŞ~Ç˙˙‹<÷cb4Ĺs\\ĎB·ĐérZĺ^ď™n†ĺMÄÚSĽ™RgĚşěĎÎßz˙ü÷×z¬uWľŘÂĄµJB‰VsŠÁjá`jĄ ˇ‰ČFŠś˝.ábD °ňŕ0»/˙ő)ť_ Í ĽhŢecöÔ»éäcE3łÔŃdŮďhĂ(·h÷ÚÎňî>Ż˙ů‰^?˙˙żűţ»íüuEŚeĆ ©ÔQ …””Â:   Đá˙űRÄę yµŔŤžÁj6`Ś&ů˙˙ţ_š@;vż˙©­D üC0đu"ť,gĽC„¦ #6†ţś|Ż“Ĺ5„ůnÍŇ7ŹąĎÎźŢgržnßůĽ´4ö7őŰ]ĺ¬+Ţ‚ćaCÔČO]:HOX×0>heŐ,Ź­úěÝŐ(˝Tšn‰5Şâ4u˝é–ýźľZ<öŐëłűă°gZ٤ăńsźą.îMózffvźóťütďbíł[Ő6TzäjY:˙ Dâ*qŐ8†˙űRÄí =µ@ 5Ak6a(˘at$;^‚Z,–HÚş­ ť˙˙˙÷H(©¸Dzś†3EĐ®e§3¦;&QÂä9ou%Ĺn C”‚d=+_˙˙ţÝčT»X™%8á< bHĂG‹ ‚01C‚ Q˙˙ţť53{˙ú˘°ÚsÉ–Ą~ß|…™AŃ4͏˱Sá:Ě#rm%di?2łČďô˝´üżçc_űt×™¶Ne [|óLL,ť%é8&Y7™lő:1ąŁ±ě˙űRÄě‚ ±µ @A}¶ ¨ł ‚?˙˙ţͨVÝÁż˙řźln4 Ť‘ň8őTĹű,EYŻ «ÉN57C“5TlÉ‚n{ Ó€¬Ą=gu˙źéČęYňĽeP–•‚~ŮU (i–q &®pH@)H„©ŠpB˙˙˙ý×%—7˙µü)ýM>©-;c®k9r/©Ä…ä‡ áb]C‚†AÉëŠ$0äLYpŰő˙˙ś§oŞ˝dV’{8Ştú‘Q 1#moHt´YerRDL(˙űRÄě ‘±Ŕ “=¶ˇŚ–  ĹÓ]˙° 4zbŁČfËţ|¤0 Ą  ‚ŞC•¤>ÁH›IVľ{¦×°ĺ2l“)M1 R3˙˙˙T·eٱŽčŠŠ ĘU,1ŃŽ–$Á Ő`Ň µ¬Ő\u"˙ôż™桅?«m8ëT),ůK_űľ^UQó3ő­H†©ň”>‰ň2%~#ŻŹřůţ~:ŽŰt(s8ע¤¨†« ń2F ݱ`ôĐwkąĆÉ(€˙űRÄěKőł@Ťťw6`ڧi˙˙ţi3m EM˙˙ĽČ_nLDWcIĹ3“Ú ¨G!)\ĘyűE:ŽÇpÖĂ&CmMZŮ7‡¤˙–ţ|ş™˙¨ Čě&ÚŮžL™‰*(ˇ"bLŐ$"]!*©•Q˙˙˙ S¬e˙ůFg/’ł4&g§őŃ Ě蔿J1¬ TĂ1ëK;„nĺ\Äšc(0ŁN´,ňŰsüçsź)%“‰ S*Dsźü‡óüóuťűb¤ĹF䉜¤ŕüĆ\Â(ćVZa”BŠ ,`vŤ˛JŠ• ´@Ň·˙îŮgs{˙ů•©<Ć&Nr=îyÎĆľĽvě8ż™ć’ö«XŰ;G们Şç˙Ó·íëĘ®ŁQJěŠĘ ÎěcÄ1ĹP&4˙űRÄď }±@ ‰}¶ ڧi85a«d?˙˙ňm?Ŕ2ÓZźýĘfyÝĂá•QźĎ)%[+lg®VŁ#‘ˇ^¬ŕ*Ýş©ť×ęěŢ÷űŰż/!rČT®}÷'.Ť˘Uć’¦Ňç˘mŔ›Q\V`}f€Q˙˙ţN×Lůż˙şń–¤^CžęŢÎwÓ `ÖVq%KĂ yݢC=Zó$Ô‹:Ęt˘Ňňú^\ŮçĹŞ*é‹ É,m' ś2F ¤š¤˙űRÄë‚ uł@Ť>É‚¶ ¨§hL›ŔR)\Úúáš˙˙ýSŮÎÇ&‹7˙Mś"ŚŢ‡–kŠ: ”a!Á%-Đ˙űRÄé‚ IŻ ˇ ;IX6a´Ťč› ˙˙˙±ĚĹp†@َ,Ěß˙ś%čw*LSł3ÁËěÝ209TÄžs)ʦEtşąT΋S©čVf{nĎFű˙˙˙µ:ŽŢB۶'¨bZÔ(–HË €™í“Ě”¨ ‹jľu[kiţî—˙ŽŚŁe aöżůezŕ’<Ző2UÎE3-P˘p˘˛;™Ó1•Tňˇ*­IĘĂv:5ŐŐ¦–Wb˙˙˙˙KF˝mK^áĆŘ#.•˙˙ý˙űRÄëJń±ˇ UŃ?µâtŠ»ť<Čć(˛äb+‘=í5‰—١j˙ýŤjB5Śt{·ŤJĽb"ěđŇĹ $/7“Ű­Ę_˙‘fÜëćěËťť‰•›{ľ{9ď|Ú8>0 ë `…eKkI?‹˛žrŘĂłçmŚ«Üó±)’Ň2ţÎÔĆ<ĄŘĎ•s9‰ş!YÔyĚbM™† **:Ě×mŃU[˙˙˙˙Ý™¨ö»:µdaö•ŹC (ąŠ9»î ?˙Ő˙űRÄőNµŕ™ť¶ ô!&řŁ':¸—8µc…3¶ôéďsAĐ«őŞ'ßűŻ4÷{2ÓKcŐ>Č…X™‹eÄ43B3Ýn…ůź—ĺ˙oJ§ąSś™Śc$‰4šFĚWU®ˇ"M3ĄĂž*j(Cp ˘rÝ« ˙˙Řd<ͱ¦ŚD8L».˙ Y“€¬=Ť{ą?]T.sž]…u¦m©9ÚťĘÝ *»ć°›ß_üżďçńő^_Ýő:­Vá[4®a÷’» Đ/*B„Ô ˙űRÄę m§  ­Ů”¶ ´!łˇŁËcDś§Ç󑔡Đ@ß*´żâ˛źK˙4#őp”[ˇkUI„G$7)ˇgÖÚ$ÍlÖˡÎł?˙˙űw§g»3vA„©ÂÜŞ@3ˇîF$Ęrďł@˙˙ٲODd9ÂJ7"5ĎüĄţ×WBMsé[Ae[íCPĐ )ÓËË´ĎrSR6*O3z›1ůÄËŽ^ÇY˙˙óż;źg<„÷!MŐ]Ďfžá¦QCęi$zR˛Ó`˙űRÄë‚ éł  ±Ń“6 ¨!§ŮJá€6µ˙˙˙˝ĐŠ1Ź~„¦˙ů&GIĐäćZ Î+×·ŇzZ®U$·s…)iw*•·78YäQÖ‘U«ń!žó˙ţţrGÜsn[Ň„çßä‚^eÚiśĚU‚ŻÉĐ.K*߬E˙úô*ĺ“3Yħ˙ě’"§íöš)śłČŚó¶  ŞAjĄhÜP Ł ę*9`˘ j˙źźç˙ňżý·{łç­2eÎ@ÎÇNh˘˙űRÄë‚ ± Ť>ÉN6ačŠz´†Őł‚‰‘$ ·mcŚttäŐ3ŢŢ^ó?żţw§G/>}Š«źĂ6őÚWMH(€çB1#Ş‹F‰Ć#ÖÎŽŁ˘Ŕ6˙ű?L‚V@‚ďóH€äľnR…•2»d‹·gŹš÷‹[zľŕšÝ=Ć)ú,Ä˝>el×î‹h›ę©šŻźůëľú˙ţ8žőiżAšIĚÂłąĆ F<É$ń p†â„(żr4I˙ţ˙űRÄď -ݎ 7ɶ`ô!§h( L ”ă·t?Ă“ţűčşV¶áČs5B¶U)8Ť&°őH?pÄ  Ú۫ uŇ/5amë>ĺů˙˙˙˙ţß;3¶únôY#šKC˛ţ-RzĄşĚŔ±i˝ľHŃ$ eąîÖ8fBŢ-vtď˙yŃfś…Dµł… óë=B“ĐszI›ba’Ş\‰T’“]–_??ď˙˙˙·÷;™{~uÚ˛cÓIUP%€ŚC+‡0ç8W-Ś€˙˙Ę-˙űRÄç‚ ]± ˇ Ý ˇă´ŤŇ{™ Ó¤rěF‡,ČŇĎ'=AĽC—'émĺP™ Ş›ĘĂ[T¶łŰ‡&”ŹcŰňn•5-~Égů_˙‡3úiZĽ­÷ŚbŠŕÖ4ÉĹ g¬tÉH,Ś2†¦!ipM4 ‘ 4ĚŚŔ×—˙äÖË™ÉHČŐ–ˇ‘“YŤZËPÉ•”'#Y&˛Ë!”ą¬ś˛öJGüż˙ö‘¬ż#“,˛Ô5k,ŽFłĎ˛ć_ůË–( NŽFˇ¬‚šŠf\˙űRÄń Ył  Á‡¶!hšúrnŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞLAME3.97ŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞ˙űRÄě‚ ő± @oٶ`h¦řŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞ˙űRÄěË]°Ú €t4€ŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞbeetbox-beets-01f1faf/test/rsrc/unparseable.mpc000066400000000000000000000043541472325477400217070ustar00rootroot00000000000000MP+' \@”7–sĚ˙ţ ˙ŇĹDfŃçü˙˙Ü3Ď?óĎ<óĎ>÷Ě>÷üó˙üsĎüóĎ?EDDţńđ>@Čń_éŇá @ Ń @;8şo´č€€Ţ ‘˙Eľ|‘_Dä¤Č—DDDDů""_|ľ""ED""""‘""Čŕ‹P 8şŃh ˝ŃŤ¶m۶ӵ]×K۶m^UŐ6Ş^ő÷jĺ/ú{ŻŞ‹Uݶm»ň—mkšg{¦KŰ[Ú,H’łmŃĄçç&źŰli۶łsmŮ"Ň”(ŤŞş­lu~ĺ‚˝˙ÝgUÝÓ·ő—U÷U±o Úó5„4ĆiŚß@NĄjT=ť’¦SŇ©N˙˙Đ˙˙˙˙ůD&Ľ|/]ŕ˝|_ţ˙8yč˙˙AW˙˙˙˙üú˙DŔ˙üCx/ţ‹źŕ˙]˙˙˙˙@bň˙çyŠÄáď1řëýÁ!ôóé€?A_OŘ˙˙?˙˙˙˙>Ŕ˙˙ş˙˙˙˙ýv˝ä`oôôyCŔŹWě€Ážpř öú‚@źŔ§˙˙AW˙˙˙˙Đ—üűů‚˝Ŕ /觇~żžĎč úův>ßôż˙˙˙đ˙˙˙®pń˙˙˙ů˙˙˙đ2."âĺß?€/ż‹˙˙˙˙ř˙˙AW¸u˙˙˙˙ü˙˙ř EDä˙D@ŕwđ˙˙@˙˙˙˙ç˙˙?č ˙˙˙˙tů“˙ ňż€ń "|ůŕEी|˙şÂ˙˙˙˙Č—ă˙ €˙ä€|ľn˙Đ˙˙˙˙â?,˙" |ů ‘˙;€ŔÜŔ€mSş(›˙ ţ˙9öźťçěÓgőڵőĽžł|ÎźßKź÷ń‹ŔDDÄ"m›vů´k۶ۦmŰ´Űvm۶mŰmۦ]¶kŰ6Đŕ 8ŔxŔĐ hĐh ĐŁoŔ¸ŔápиŁpoŔ"Ŕá" """""‘/""H€DDä‹PDD€7€íĐ đ@čntŁކŔčÝŤp´Ý@ĂŃ׸m۶k¦mŰuŰ.]۶]»nŰ®kŰéÚ6m˙Űv˙ż˙˙˙ż˙ß˙ż˙˙ß˙˙Şq*ůN%+ŞcEU5ŤJŞ ’ŞÂX=WŁýĐsŢ!§Đçr ýW8y˙˙˙Aü˙˙˙?ô^ţţř]/ŕä{€@˙˙˙˙ţ˙˙˙˙˙˙˙´ŕ˙č]Ě˙˙˙?˙˙˙Ý tźŁ 8€Ťî: 8p4tt ˙˙˙Ę˙˙˙0ôí?ß 0ô?ÁŻřçű…? ° ×ë ]ź˙˙˙ň˙˙˙}Ă__Żô…»Ľď×čú|‚˝q0ţü‚ľ+ŕS˙˙˙ ţ˙˙˙aĐ·K}>@PČóń|{<0°Çăú‚Á_PÇ…“€˙˙t˙˙˙˙ň»üÉůŔ‡ß/_ä‹‹€üΠ˙Đ˙˙˙˙ůň'˙DäË/âÁ Čů "c˙t…˙˙˙˙‘Í˙€|/_ŕ äË/ _D{˙t˙˙˙˙€öÎ˙whŔ@ĂĐO¸˙®€˙˙˙˙ /ů˙@ż /8ŘűýxĽť//,ůz@ß_±ô*|=.ĐĹL˙˙˙đ˙˙˙ŕŢs€8€ŕáđ ŢŔp ŕ AWPË˙˙˙˙°ü˙˙óÔgˇÁÜwđŔ|ţîۧĎ{íőĐ_>âűoź?úÁëGŽ˙˙Đ˙˙˙˙ńß@>Hţů|ů_˙ďżŕ˙˙˙ŕ˙˙˙ŔĚd¶m˙`€˙˙źÓg“çú§{sB8çśsÎ9çśsÎ9çśs‚ĐUA6†q§ HźŁEiȤÝŁĂ$h r ©GŁŁ‘Rę ”TĆI)ť 4d!„RH!…RH!…R!†bČ)§ś‚ *©¤˘Š2Ę,łĚ2Ë,łĚ2ë°łÎ:ě0ÄC ­´KMµŐXc­ąçśkŇZi­µÖJ)Ą”RJ) Y€dAF!…R!¦śrĘ)¨ BCV€<ÉsDGtDGtDGtDGtDÇsUő}Sv…áteß×…ßYn]8–Ńu}a•máXeY9~áX–Ý÷•et]_XmŮVY†_řťĺö}ăxu]nÝçĚşď Çď¤űĘÓŐmc™}ÝYf_wŽá:żđă©ŞŻ›®+ §, żíëĆłűľ˛Ś®ëűŞ, ż*۱ëľóüľ°,ŁěúÂj˰ڶ1Üľn,żpËkëĘ1ëľQ¶u|_x Ăótu]yf]ÇöutăG8~Ę€€Ę@ˇ!+€8Ź$‰˘dY˘(Y–(Š¦čş˘hş®¤i¦©ižiZšgš¦iŞ˛)š®,išiZžfšš§™¦hš®kš¦¬Š¦)˦jʲiš˛ěş˛m»®lۢiʲiš˛lš¦,»˛«Ű®ě꺤Y¦©yžijžgš¦jʲiš®«yžjzžhŞž(ŞŞjŞŞ­ŞŞ,[žgššč©¦'ŠŞjަ­šŞ*˦ŞÚ˛i޶lŞŞm»Şěú˛mëşiŞ˛mަ-›ŞjŰ®ěę˛,Űş/išijžgššç™¦iš˛lšŞ+[ž§šž(ŞŞć‰¦jŞŞ,›¦ŞĘ–癪'ŠŞę‰žkšŞ*˦jÚŞiš¶lŞŞ-›¦*Ë®műľëʲnŞŞl›Şjë¦jʲlËľďʪ)˦ŞÚ˛iŞ˛-۲ď˲¬ű˘iʲiŞ˛mŞŞ.˲młlűşhš˛mަ-›Ş*۲-űş,ŰşďĘ®o«Ş¬ë˛-űşîú®pëş0Ľ˛lűެúş+Űşoë2Űö}DÓ”eS5mŰTUYveŮöeŰö}Ń4m[UU[6MŐ¶eYö}Y¶ma4MŮ6UUÖMŐ´mY–ma¶eáveŮ·e[öuוu_×}ă×eÝ溲í˲­űŞ«ú¶îűÂpë®đ p0ˇ ˛ŚaŚ1ŤRÎ9ˇQĘ9ç dÎA!•Ě9!”’9ˇ””2ç ”’Rˇ””Z !””Rk8Ř )±8@ˇ!+€TăX–癢jÚ˛cIž'ŠŞ©Ş¶íH–牢iŞŞm[ž'Ц©Ş®ëëšç‰˘iŞŞëęşhš¦©Ş®ëşş.š˘©ŞŞëş˛®›¦ŞŞ®+»˛ěë¦ŞŞŞëĘ®,űÂŞş®+˲më°ޮëʲl۶oÜş®ëľďű‘­ëş.üÂ1 Gŕ @6¬ŽpR4XhČJ €0!B!„RJ!Ą”0ŕ`B(4dE'C)¤”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RH)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ©¤”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)•RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”R Špz0ˇ ˛HŚQJ)Ćś1ćcĐI()bĚ9Ć”’Rĺ„Ri-·Ę9!¤ÔRm™sRZ‹1ć3礤[Í9‡RR‹±ćškVk®5çZZ«5לsÍą´k®9לsË1לsÎ9çsÎ9çśsÎŕ48€ذ:ÂIŃX`ˇ!+€TĄsÎ9čRŚ9ç„"…sÎ9!TŚ9çtB¨sĚ9!„9ç„B!s:č „B„Bˇ”ÎA!„J(!„B!„:!„B!„B!„RJ!„B ˇ”P`@€ «#śŤ˛€– R΄AŽAŹ AĘQ3 BL9Ń™bNj3S9ťtjAŮ^2 € Ŕ (řB1AĚ …U°Ŕ  ćŔD„D H»¸€.\ĐĹ]BB‚X@ 88á†'Ţđ„ś STę ŕŕ ""š«°¸ŔČĐŘŕčđř8>€ć*,.02468:<€€€@€€OggS@–ţ–ç=ý‚ĺ'02310276;:?CCBl…‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹¬Ř%ô B8FkF#ëśzĘýýžĺJI`ź76BU5Ŕ: š†îK.Ţ[/IŔ´€Ž€ ~ĘýýžĺJĽçŰJŮ@!TŐ€ €z wP{@Ű‚š_,ß5đč@ 4>Şý˙üĺ žíkĄ°@!TUč40ŃMtô™ŕÉĆ+ÚŮÁ…WÇŃŕĐ>Şý˙üšäâvy¨…PUˇô…|G×OӱЂ††ŤĘpBŔ&€>Şý˙üšäîô4Ş…PU'Đ@$¸O}Ç%ĺMŇ;†:€ žä>Şý˙üšäŇ´đ&”ŞĘ* @t𨴯ůçaďĄ)t (4 šý˙Ore1éM0BU 1>ŢY(ĄÚsYŔ&-RJgčˇŐMkňŘŔ‚Všý˙OrĄ2ŘĺIŘUUĹ@EO5š‚nE~IôĆÓ´JĘqŘ)d˘U)°$ @šý˙OrĄ28ĺMčŞĘj ĚY¬"ćeç…d×Ôdţ‘ňcw˘ ±“¦‘€d :šý˙OrĄ`°Ű›¸TU:Ě]ą¤ż\Ďôł\ĎŇ{QFH4¬T05Ţyýök”‹2¸ŰŰXaE…P + &EŇć^ ô#©”¶&Ú!ir}Ď””Ωaku¨“)lY ‚& HŢyýök”‹2íMP*jT¦Ţ1{Ă1^_Y•˘~vçt×Zź}ą.󭡣Ş9“5\1á)čŕÁ_#Ţyý˙ţĺR Ny“B1ę,r(¶Řňł4ptmp sR›¦ŁL/g9‚čŢ?®Í0—M)°”,ZýÎuą‘Ţ8 4´X‰ h©u Ŕ÷ßŇ‹ö~>SŁČü†ý$=cr¦Čß7˛üĽöŽĄĎĂŚóů0Ę«®bĄ*Żß·ţQť˙Šá¨Y4®#öO¤Ĺ± Š«=ˇ P{údX]ÎÔýÎ59 ŁĽ „f)6đÖ”łA`fś“€˝^ťÄ!<©¬i0/dĄ·?/Ôhł=é«m¤ň¶Ţ­™žŹŤwŰŐZ:Ý–˙‰^®¦×Ö‘&uVwű’CĹ_Ĺjđ-mŚýÎu9 %˝ Đ"++IŔ@âĘAGŐŤrý5]óo”X€Ż/săÎŹ\ú çHÇ‘ŮqoĹóČĐ3ú.+Ľđ¬qÖ\Eť¬SF’ż1R-ŕ"ç ÜI¦ĎlÂjÂÉĚěćýÎwŮ4-= ĤĽĚdŔ˘ĐIŕ,1ć8‘qü]âj­_č÷⥧Թéô''ďsçőu|‰ěwűćąĹÇ덳Î勝/óˇ<:Ť0ŃŰĺ$ˇÍ˝'—3Śí®Źý ýÎu9q­< BˇˇËÂÉgPŕ€~57kg§Pżd´]ťŮŰüąśÎoż’_ÚąJs)ęă5÷Ňä¤Ĺp–ô•őÚc\^;ÇĄQĺtFôb[D®áě §­“ýÎ5ąVŢh’ʦˇ°@vvż´ÄNľd ¦˛˛Ki*Żu67[`&· 'nV‡§Ż‹“ËČcşt“źq’C>-VţFźÄĽVŮ ]ęďtGNĘy49ŞéaśŮMăýÎ5ąäŤˇXd‰‘\%`8Ź'\ŕÁÁű5ôŹ7‰M?/'x‘,ׯ×}YżŽ˝nmcfFU)¸¸đ“‡0ŮëŢľŐŃřÚťť·•ęP[©+T˛űëĽý2š\ÍüÓSś ÂG.WąčX©;·ĺUFŔhkôo†FŘ/÷ŕÂu cĘ:ýÎ7Ů–Ţ8€š• ›0Řd7•\´×vÎ}ií˝E«MÝ M3‹Š"äđşĎ~µ˝bł¶#Ah©'ňR‚ŃÖ&y,]~ŹMG“-9¶#8…ţůäëĽ#s/yýÎ7Ů8Qľ1•Ö%8€iup;š¤_I5…ŔÖ˙?»ŘÄ&ňҵäduˇn±{˝ď…é!‰ź»:•¬Ď…úzĹU.25žBÉ–+Ęčq0ďîQ®*/CUë&^ܱé-ýÎ59q#} „&ĺ68ZŔ€ôĐ»t3ü´WÜ™ohm™äP»-¨Y?Ç{ž°ęŽLXř8&MöŘ f%ŞO©›÷NÉ”‰ÍÍŻ®b˘ě˝›ěÎČPwˇëWI;–ýÎwŮ(–ž„*hRdÁPLŹKşá?7śŹI—»BťŹÁ¦Ń|_O&±arúäşvQŕ$ąk×°d­Áĺ˛l­Lc/["°R#lcú,čVŔÝťŠ…«äŕHďbz¬Ű{8TýÎ7Ů8QŢ1ÉJQ3=HĐi—Ť ‚©ćĘě•ä¸óM6żÝRmöµ|fę «ö’ôŃZ*Ěü(NbÎŃŮ&ç˛ýÎuą–ž@-jIÁ…&&čh‹o\ś‰[˘ĆüJx2ÝĎÚ ^Cí7^:«YGcF¦ňO}žÝşž‡žŹ™‰°±<É~ľ¬2Pâü"SÔމíőŞŔö™!Gć&ŰóS’‰ťÎI…¨ýÎuą8QŢ(BĚR¬H¦Đ‡YB,’˛-oĹx3CT§XČčňßŃ| éČÓůĆűŃł^^Ş„^‰)5=‚%G˝pďb€{fy](€>(Ö÷:;ubjŮ:kGG:OggSD¬ţ–ç=‡ż˛¬‹‹‹‹‹‹ýÎ59 %˝ ‚”. :>Źţč]µ¤?÷TăĚxL€çsÔžM›PĎXšÁ„—+q!^#§ĂaâčŃ|S«ă.Ź„™ăµ•«Q®%(Îsnĺ¶©lő]”N-{~ýÎwŮ8™ž0B“b…qÄń–X9vń)l}¬ĹĽŹb‘Id=!ű…t,W˘7Ĺć˙9±¸őń­Ű˙â® Rńçf©ÄŔoé<ďËÓ—·§ľUÚe±”׫üpő0Úé[(wÉÎ"…ýÎuą8–Ţ„2h–˘"hrđČ)=|éP›Ű"ýr:Iň"qŤö°Ě蹨šŻŕŽľRß"ŕ€"«•uŢ˝‰ ţąÚ‡żš.řAsń©Ďnµďn7…Ł'ňčeŽ~ ýÎuąŢ0B Í c@2 °8čä>h»ď_6zyu—YĆü|$Ô•»ąŽëö¬ĘŔĹ…÷ńSěŮ n'E˛0!¸¶˛ö>~v3üayč™zź›9óMÔk ¦1×5„éW7ń#ŢežČiýÎwŮ8‘ž1h–bŢFh‚¦zmśňp4<Żĺ˛é*é´ ´ÚäÂśÉ"N¨„Şń|oâSđjl’ĚôřśxŽMqIß–Š'S´Î—WřKw®˛:&U0TĚn;Ť‘bbž ĆPČeýÎݞ 8P‹<Üŕ0P8ťÓą­j xz}]Übeetbox-beets-01f1faf/test/rsrc/unparseable.opus000066400000000000000000000201071472325477400221100ustar00rootroot00000000000000OggSteż/ä\ OpusHeaddD¬OggSteż/ó®YOpusTags libopus 1.1&ENCODER=opusenc from opus-tools 0.1.2 DATE=Oct 3, 1995OggS€»też/dYoá3˙@š™›——‘™–ť•™”“––—“—–—–™•š›š™—’”™™–“–š—“”—•——•–řřř™9Ü>ř-ůčE˝BSu53ś¬ů€$/bĘ0•Ó”:ę'X*[|äŕR‚űc»} :Ł$ÖzŘúŐě§sD“÷INŔrF›wĺˇóß.!^x*iVńş}´üŢđ×]ë{ŹŻŕpT ĚŽŘ2ĂâÝ*©Ŕ`ţKůŃkd1¤ś%ÝŁˇč\ý“źčŠË|±‹]­(hÎŇ®Źä¨’)4ľ‰±«ŔFž•[ =Lj„ąnĺü{Ě>žor3±@Žlc¬ý7 ěVU«§ĐÔ qx[»Žăt]{ĹąbęС‘1ăDt>í¨çרoŤÖB«ľKĘź’qÓAm·Ď]ή¬·1%6ŔÎ ao»¤iâĆĄe¦¤,]”Ř“ďt߉Bs…F¸mFŚyó;&řaß:¶WrĚŰÖČr‡0–ľ i1U·…ž_-@X)/C’®ÎŤ{.jÝŰ*ç§‘>‰LÖ`/±Ţ!8®ů‡=­—}‰ĹµމćřD®ř#ňÂVmR Z›Řŕ;ÁÄ Ąě{ŽT>¸vSBű ›Ľ<ű‡­c‹Î>+ëđáŤh¸}«¶´LU$#rą!Q¦\řaŐ™ăÓŹŘe¦¨X,ŢR.bn˛o<“Nďđî\«ăq JŞ–űš|®Ł2ÔÝpĽQűahG —L”oEĎ5€qžŽ¸¬')„Ó˝ąĹ˝”Q<5¸"ŞöŽ‚t”nÁ;5úç8óüËfű 3â)ň˝‘…´±Ă\ł))…ňŞąAŕ’⪫ţi>|5|/9ÇŢBfłĎ1Ž-7/5,ă…řaŐ¬w訿ńÔžüJŕrU•íŇĎ’=Ż7eŽÜ#ţ$zÔž ď¦Ň…7 Ą´^6r•âĘoUâZË ›®X*ÖŘęe4^č>ňG®íŰßä^ŕ|¨é1ˇMěŮu'r]o1HŻŔrü%¸­´ 0ć,3VeŞŞ—xD®?·1­K+,IqŤ 3YĎw,kżr ŤeLřaŐ±›ŔŕvťŤâÄB Ýś[Őűüaő†Pcž»,DY°ˇ©µKuć?‡öŰ^kůé˙Ďý0<ŹőňFWŇă¤~•ăşx›vř†¦Ä#Ž˝ěq䞦×Ę´Á ţvŢĹ ţů§Gúî+bGŮ©VeöŕIT`ěÜ)‡őäL¦đř3ŐÍĽÁÉŻ3¦Ë÷ČjŚřaŐÔÁsc0fxŹ‹ë{Šžâ##˝ňY˙é i„[•~€ é¤×Pz Ŕżň6 _ýĘţ4 řaŇŤÚ§ÁçĚXZ1§gşW·b°úĚÚł‚]­Ť*ß~ŇSä„$“Ă­śfĎŃ+3ju[#v›ňÝ$XHe\n§§ÁjŁ’»*ß5zźş™c— Ą[Éęo®„eësż‘ágɇžÂÝöą ÷ů€˛| ľ›|–ĺ»-¨O# b±k0úó¤ţ)BřaŐ­]xúë|°ňdˇŰʑы,–0ä]ZŐšIë­"”1"¬0jť;Zj#ŰRť-—٧ż7Ę·ăYC˝â¶ş+Í/8ďůęíŰ/őŕćţmíOžůGĎZňĺ^Zfe ˘5,k¸tÍ…—`\€@ĚňăŚQűgşJěfËž™o=âp,ް FŹ“HĽNVŽ”ůL<ĂřaŐ°żë+…'µ˛cÇěůcI]ż}(ń»ŞŤq6#ŤghóJđwÖMQjˇq)H6$HćzyXW«8‰9vý˘¸]_YóoDóő§ţm6PŢć<}WÎ\Oµđ%îh‡:ă¨.¨ťÜ"®6Ńs ŃKeG»îvd@9‚Ӹ󟕞ŐÝ/­řJ|2˙T=/řaŐ¬Öé÷‡†8©z #<öśCݸEČé|Ń&ökČĎáT{SXîwŐˇ•´ałäőTÚ-ÚEy S/ó^:9‚@ÜůŃĄăM Ů©±ÓŮ•¬°EBFŇ—<°ôx>–2°źť|ó'íĄv„ýWg*uaôR–76AřaŢí۬!ÂŔŤ‹­łôżł7­*Šs 0of Ł´ÄH!Ţvćwꑊţp•ĆyE#U Lô%í.µG.#žť µIŽ4I(j g^ލţf"~÷Ăoć +µsW(š—4<ń~íŢĺ4ŃfÄviĆ:§¶LΕZΞ'üÔpĆĺ.ŤśL1Ý-54UNü] ÎřaŢĚĐ[J2ÉŽ]ďF·ń@Ü1r\¨ýÓe(lţ¨<¸i t[ćąŘë˙ölg5ÁiF`éq­IÁíĚyP±71ŕa-Ýď>B\6™&vÎUő;ú HŽgŇm‹Zrç#±XFÄĂ»í (qËń\˙dřaŇf/1@÷qŽ#'•zíűú.Bޞ%C€‰yG¬ňKŻöRfQk*Ą˙1ͬősËb°6űOĂ8á) TP¨pNřeď0!óĆ1îŘ™˝‚î٧LédĐNâ,Š<19d«Đ"ĹŃlďÔÜó•&aÇËęo1»±Ş vJd ĘÁ˘ęž|o,/bĽ bl%\;Ď“vĆřaŐŮ)[" áX‡GIměpßŮ?…!ŢŃűđÉBçˇű2<·PůŹ•Ď?¨$9îNß[śţř°ß¶ą¤ęmĐńsľ <=ýú š˛=U‰—o»‚zĆľäe äž[ż¨dÉ‚ÝXšŽp †f á Wđ [^fSăú®LxŮ?ŹAâŃ÷6ů,ë`ŐÄĄ{l?@ů#fřaß(ůí÷÷cďťźv°TŐf^Şě“Y.ŮXĂFĺŰ‘o«ÚÍn„Y)o-üżT˙¤6AÎ&c@ŽôĎ*@ń+Dˇ-úD}N  ¤<–…)ţϤżz˛ŰÍî–±‘ŤłhpŹ—˘˝.¬7’ąAš7S‰<źq=·=zŇ˙aSsZÁ,…:ď’Đ[Q ¬‘â ëřaß&ľłń4DP{uömÄ€N—ůÜéŔĆ}śzIXĽ¬Ľi«ô‡s ˘&éŰqŘeXŻ >!B¬Q—A‹1ú”GäaÉs¦(a?ÚţUş_Ě 8d~?űˇdZÝ5Đć?Ó@mâopÔťDnB`t·Úž;ú yűÎéaŚ?2X"Ą§ő™M;ŹlȰ,Ć™kĺířaŐÔťŇě”ÁÁ›08n"Oj ĹEő¶'ضz=ŽÁr®ŻÝzĆ.HtŇ0Âu/÷}ëŽ<{ŇMú đioň†é@€“S«öčx8‚+÷ŔˇŤuĂ𾊚NJ1ő'¨Jäîb; `Ű´<—G$&Ë5ˇ,ţ*ńíëŻ3ó˙űÖ„ěÖł3Ëç"ᛟv×ÔÇ\ řaŐ¬ťď±PžŞş*›oqŢ6Q [3!ľ˙F@)P{°˛áű€Ď‘gÇtţ™ŇsY…rÉ(/?B„ y rŢxËm_ôŢÁč«`–/ďăšZQ?´ e ú?FggýňčęćAxß-G&ݦcIkČŤçNá7;ť”&)ŕFj0'6óP´Ô;*řaŐ›Y@´«Š•ć ž ă†c t¦Ą¬/Ó/ĘVŕfLŞ•ĹqkIÓy‚ęBđŤ+Đ?¨„z§¸Cż®έ(ÚżäýĹ»S˘¸‰$Ájz{ĺAŕ€ŽŇŇmĎw?Ě_v÷;üůA©ÚÜwĂť’ď'CMşĚt…u đÇć·áśAÁm‰tĺűJw®¤%`­řaŐ«GĄÄáA0ŕ^.aʶE*čŚ)@çďţ”ĂXbů3ËXrDđ–)–-6«B\!ž¸Ř¤‘‹Ď1Bݡ€qŻRmj>Ý0’M„‹âí…{6‚QSńVչè Şëm¤§,ŢúŰLőď±Ă[Âţ ˇ‘8}•¦ąąî€Ž *·whŤ8;)ßŰśf ÝNˇxÓ†2JűřaŐ¬žŘé”W"דą0"J­®>ňX‹iäă‰U&A3J¶ź?7Ł ¸S4âv"Ł9Ů›úŽŞ×âńáNn=Í™pŰóÖ;pUŹ| cpŽZĺ !+ď“‘¦ąšx,V%Ţ.:żĆĹČ L,ŕz1ŕç·ĚٻѰďČaOS«ĆĆŤCîě¦Č¬r4Ľŕm„ŘĽřaŐ›b`®éđŕÝ÷'ł#p' H{™¸Ć^ŮďĂŰq±ßw2ząýţř\ŃzÇ&ꓺB"LŞkĘó*8ËM1P’çņ›h<Éî»ŕđA‹hMM<‘ŠMű†ŞĂUÄ•¶=¸a-y#ŻhףSÓ šłSç<üŞď4xţŮ´“ËXP '"ĄđÖjÚ›ăVĹUóŽR–”˙µžřaÓ,{Qwšs¶µq˛ţcšAşÔ!<ŔE{mݤëý0•OęĐ_QAY§ß' ŹoěÔDßJDvęÍČÚ„÷đ7mĐ`fźÍî'ŃúŕB‰AçĄR:N°e>7ŤŐ!Ö÷-ŹčźŤŕM™Śc†Z1ĹĎ÷Ń?TŘf˘DPčJ3 f›‡áEx *—śÝ|͵ĄŞ©.RżářaŢĚ0§Ü \}$—ë-§d’‡Ž«ŮÝ€őWů‡CTNI´Ň3Śşł\Ž˝ř2pg&˘WvÔ-¨Ĺ4ىí÷2ăe?ňŠĺ­MG®×vn‹ę™u›śH˙¦á˘żi0Ą%Çnj¶‹Ł–®ńźrü6ŕU‡& Z¬ť’QCRĚŁŐh L=µZŕřuŔă=TÜ˝±pŚĽ4óčřaŐ›ö˙Ş­•–ұ3,öD©¨:¬Â€]\ÓËü˛6ßk Ę‹?GoÁ»ďCiôÄőh’‹› Őłk «Ł–ë™-´¸CSÄŤąˇOťěç‹·¬Äµ´(@ •±„6O׾l6K.ňĐíŚKťAĎ|ÚżQ[gďpöUаułŞĹy ¦±8eďí2˝ň ÷˝´řaÖt\ntţŇQ•ĘşÇU Ă`Ž#î,mÂ+ic¶ôÚGŕ>úAă é#…Ý€F÷ňőś:lžÇâç٤͵‘O3UCÁ[z÷ŇÝY§.Bß# 1oĺ ĺ»>áÇŻŁ1>e˛…{x=G Â*bťżşŐT×ͧÓ|˝|¦%ę?Ô3Ǭ6Eiç/“ďŞbTá›ËAhwТ/dÜřaŢňűčr^|ůxh¶´%ťăŕ'•ŘGJC» _Ö6ˇÚK5(Ą”]Řnˇ§Ű-OrŠd 7ŹMµ•vrĽUüŽMܸ!5„M;ÓĹŹwřVY‘7±ű˝äÜ.öÎť)’´Öúxz‡“zQśţ:ŔßŕŮň´„şâć(:áúşjŠÉ%×# Ńwś3”n44·.}A-hěřaŢěg×îÜgµR=ŘţĽgŁĎľš9dť t©´/-ŘVx*ޡŤ+ă{ŽíűŰĂ·l›(R2›öMĬéÔOę@Ćeł‹î¤Ů Ćą’řc˙<É.e#čäOP8ČáJĹň=Ř­ľJ€µÝńřßU8ż [^îl»¬9¦îíDKľ\ËÁD#‡řaÖtq0Ô‡†gĺŕśúĚKŘdąĚ.Čďý›^źŕýR6H Ĺ"*ŞŢ!ŹÍ—ŠăŞvOŻîQçN™YßÇ@„ăÚÂqĺ ßXŇ(ďÔřD®b& üz>Ú¦[™S;9•L‹tNöÔ•‡'#ŤeĄHۆ]“öś\eL°.ůţÚň@h˙T¶Ů›DGAźčOeÝ[­Bá$:ŘnË^áě$$?§Oâ›ÝÚ&*čôŃYěřaÖqę¨*űń:ľÝôýťRą†ę¨Ř&ý)˙0)ó1L¤ďąşúe -Ü`Ýś›ý«ľv"ZÄn,9tŁüŻHp°˛ÖC Žá%¸ß¨/F¨öÄKŠ+Îą˝2}ď(,#B¬çŚú×i4Řű]ĹxçYż˛Ó ±Ë˝—lQ-éBŇ aÖe8‚9ŢxMÝýY˛ŕ.b˝LC\@euřaÓYŔ®vĂۤtě|¦ ĺŢďar·°×M ž{Ŕq†$¨2ŻŇC4T’]şOµŕřw†Ľ mÎĎL—D3WöŘŮS¦@9uăݶV®l5a—PăŐG™ aEH^Ż"kňpHnşńw×°÷©™5GćÇF®e…ąeÄÔŐôÄŤXaŐź  L¶[‘q„­řaŐŞşh8Óü:ˇÄQ}6‘(aćf4¬\Á;Š_YĽ­´đţOT±H˙•,îĐ\mÚś+WWť]ŮpW=Ă™.‡ĄÓí¶°ä6żK§ŠĘ»5~ůŞ8śd”ô7OŤ,Ëú],Í3ťzršąOSóžPÔÁźyiÚCÁ˘WŤÝĹŹ[#m~a!X[×UÁ‰zň0řaŐ¬É&ö®NEoJ5ťă_¬ä.‚m{(%™Ëű, ,YÄŕóŞEôŚ3÷Iő3M(ßpLŘr&}t®ôżçNë5Rł›ĹŔÓ‘ýĆĺŚ`f .Iźs®ý6°©öt±Ľş €ívé'3‹¬)˛Ď%Nł°z›páRo\› ´çŹžvň÷íĎęÉlĚ/Ś7 CřaÖt Zx’Q‡D‚W9I¦sĹ-÷Ĺ0ďčiirźŘ^:jűÎ?ĹPT-ô7ŐN p˙ť›XYÉs€ŞÚúÔuo—żOAŠa"ÉB--g©đ¸ÍšnÜŞ‘*śä8Ó–ÔŃjRtL-/jyÜWŽQĆ• u‚~….ţĚ!`hĽ>BĐ<éŕđ)#,~ł)řaÓLuŰ*Du=fÍQÍpĺůay±GB鯸BݍEéůr/Ł (Ł­%č<™űÜܴؓ˛‚ŤáΙ<´üçP şg· ďďHaob–iŞU/ÁÁ,§ËÖţ c&ç‹ ¸Ąęz¦â•÷űZĐšÜTÚéĄo‰řßöźęśAőÝyQĺ*+öVýó&ŞŃÂřaŐŞĐĚLŮíÄLĚĄlëNoŚg"ü©é\=ZJńňŠ ĎôąÔ¦ ßiÍ+vÜb/:túŮš ~^kçY¨D/5ÍlT·˝č$k}ŐqĽż1—˛n”ł’ňĹ QŰ_঺Ű*Ź"µúNoĆŤÇcjNÁşg4fDĆß7Ń»­Ęt“ç×bH˘Ďüt0Ž ŠTSřaŐ¬ťuPúÄä.ŢĐ#m‡¶ěŘ;†ĺžy,J•"ĉ)ěá¶ipÝą8ݧ"®ý1…¨Wü€QcCŤS_t7Éř°ý÷‚./ą?]Čž÷ɰ«%Ż /±¶n†…9ŹHă% Ľç‡Y—99u63[Ó¸‚ü\hř‹'Ž—ÇMfžt˝Q›ŮÁXMsR˝ŃA/ĎřaŐŞŮĽăZ Ö†˙I!vŠu›dH®ĹÇ<˝ ‘ź{o¨ Ä<@ĺ<@ěj†C%¨TáäDs[kâ­oĂńÝĎŕtł˙÷ěÖpŞÁ~“?lÎ1Ne$Ä}úE‚Âth9ü˙•ŐEÖt@VNU}qŽ&._+#ň°s˙Z´Čé‘>Zäűky°ľFwÝź–…Ž`…OęZřaŐ°jß ˝Ee m›ţ¤żň0Ú^;Ł(87ů—Ć ×µ”k˘ühP(N©ň–@_#äÔśŔśmű§¬–(¬?T‹eá“ÁË2Żl Đ’¤n"-–lĎđźYXě_ü›Ž¬Ľ-o^ť|4 BĎęŹL“W=Tz +HŹ„ĺ.E­^fkŐޱťĐtĐĘ› € € ôµż_.©ĎŽăŔ SebŇÓ«ş©ĎŽćŔ Se4ęËřĹŻ[wH„gŞŚDúLĘ”#D”ŃIˇANEpT3&˛uŽfϦ٪bÎl,@¤ĐŇăŇ—đ É^¨PHWM/YearOct 3, 1995‘Ü··©ĎŽćŔ Ser@žiřM[Ϩý€_\D+PÍĂżŹaĎ‹˛Ş´â aD¬€>ç çç@Rц1Đ٤ ÉHödARц1Đ٤ ÉHöWindows Media Audio V8ahe labelDESCRIPTIONthe commentsDISCTOTAL5COMPILATION1(MUSICBRAINZ_TRACKIDJ8b882575-08a5-4452-a7a7-cbb8a1531f9eTotalTracks3WM/Genrethe genre(WM/EncodingSettingsLavf54.29.104$WM/BeatsPerMinute6‘Ü··©ĎŽćŔ Ser@žiřM[Ϩý€_\D+PÍĂżŹaĎ‹˛Ş´â aD¬€>ç çç@Rц1Đ٤ ÉHödARц1Đ٤ ÉHöWindows Media Audio V8ahe labelDESCRIPTIONthe commentsDISCTOTAL5COMPILATION1(MUSICBRAINZ_TRACKIDJ8b882575-08a5-4452-a7a7-cbb8a1531f9eTotalTracks3WM/Genrethe genre(WM/EncodingSettingsLavf54.29.104‘Ü··©ĎŽćŔ Ser@žiřM[Ϩý€_\D+PÍĂżŹaĎ‹˛Ş´â aD¬€>ç çç@Rц1Đ٤ ÉHödARц1Đ٤ ÉHöWindows Media Audio V8athe commentsDISCTOTAL5COMPILATION1(MUSICBRAINZ_TRACKIDJ8b882575-08a5-4452-a7a7-cbb8a1531f9eTotalTracks3WM/Genrethe genre(WM/EncodingSettingsLavf54.29.104‘Ü··©ĎŽćŔ Ser@žiřM[Ϩý€_\D+PÍĂżŹaĎ‹˛Ş´â aD¬€>ç çç@Rц1Đ٤ ÉHödARц1Đ٤ ÉHöWindows Media Audio V8athe commentsDISCTOTAL5COMPILATION1(MUSICBRAINZ_TRACKIDJ8b882575-08a5-4452-a7a7-cbb8a1531f9eWM/Genrethe genre(WM/EncodingSettingsLavf54.29.104‘Ü··©ĎŽćŔ Ser@žiřM[Ϩý€_\D+PÍĂżŹaĎ‹˛Ş´â aD¬€>ç çç@Rц1Đ٤ ÉHödARц1Đ٤ ÉHöWindows Media Audio V8a5COMPILATION1(MUSICBRAINZ_TRACKIDJ8b882575-08a5-4452-a7a7-cbb8a1531f9eWM/Genrethe genre(WM/EncodingSettingsLavf54.29.104‘Ü··©ĎŽćŔ Ser@žiřM[Ϩý€_\D+PÍĂżŹaĎ‹˛Ş´â aD¬€>ç çç@Rц1Đ٤ ÉHödARц1Đ٤ ÉHöWindows Media Audio V8a1(MUSICBRAINZ_TRACKIDJ8b882575-08a5-4452-a7a7-cbb8a1531f9eWM/Genrethe genre(WM/EncodingSettingsLavf54.29.104‘Ü··©ĎŽćŔ Ser@žiřM[Ϩý€_\D+PÍĂżŹaĎ‹˛Ş´â aD¬€>ç çç@Rц1Đ٤ ÉHödARц1Đ٤ ÉHöWindows Media Audio V8aTRACKIDJ8b882575-08a5-4452-a7a7-cbb8a1531f9eWM/Genrethe genre(WM/EncodingSettingsLavf54.29.104‘Ü··©ĎŽćŔ Ser@žiřM[Ϩý€_\D+PÍĂżŹaĎ‹˛Ş´â aD¬€>ç çç@Rц1Đ٤ ÉHödARц1Đ٤ ÉHöWindows Media Audio V8aWindows Media Audio V8a6&˛uŽfϦ٪bÎl2K‚ ]“‹„ç ç“˙@ ÓĂĎ6Ó‚}mŕ ;€¦Jŕ$´ć“$ťÜ$›ě ?'qU1Ů1%¦4©€` -I$ŔIPťc Ô I¨R„UI10U5R’I‰¸IjR` Ů$Ä@«JTBTÓIĄ1,E4” ¦”Ň’ŇŇ€I:˘„bfĄ1&C›°` łQR@H@1Vš`𠆀H “:ÓP@¨ţĄKĚI^o'»@*@7C˛@ •ÂL Ŕ$¨Ä %©--¦ši!T€H””żHEPůjH@&B4ŠJJÁóä"„0Ň•…ý Sƶ°|µALU2”’k?ßí/¨/ßżĄ,B(âýqôŠ1BŇĐ~¶¶š-ÜKlřż!+t„H˘„„ĺ8Ť H¨‰ˇh-Űߥ)B Űô-P_”PVéCäQný"—év0{eýIBŇŇŞ¸V‚i~·ĆüPč6ňµA \@ŃĹA KTżÚŰëpăĄk‰ż, ńřRѤ ­żvŮFPµB(âó^®oă§ŹÍů»yZŔUÁ”P„ă(Ŕ\KYFP…»}ż÷o¤ÓM–Ę?_Ą Rýmřâ·> ·× p?,ŁŠÝúđ´ŕ*ŕ}Ćý–­ËKx)~üţ°óyKďŇoéăýұ~ěůĽ§(đďΚĆó\yďű§ó¬sXß•?´­-˙µ´[–¨}BŢSJŇ8řlj9ďůĺ>Á·÷úýqş_ͧ‹őEľ±ř˙'~ú—č·ŃÄ·ŮH˘š_%úiÝM.Ę]źŮK°´V˛ž+sô~_§`;t㎟ߛü°c[źOšă[t-·AĄ ·?üż|T>ˇűúBŇÓęr•Ąˇ”­ńńń-żý¦ŢýŮ·[–Çý>ót~ż'A·Đ„%lÓE(?ş0çÔń­şi )·ŰéĄňßîŢ·C÷ÉnŔ@­ĺ4qşR#)O˝k(ZvĎż:< żĘhŔT"±ź-­­yĽĄ6úr‘Ĺ@[Á*ŤXČů­~ë«3Xöü§=˙tţ¸ż\nÇÇ‚WÜi⦊Ć=©(YťXůşÇ[[¨Ś˙)ýůş?+{ !öt˝/żKhó\vőłÇO„Q•+yĘ-ŹŔUŹ‚[ucĺYň[Đ0ňřqŕ?5€˙tŕ”ĄÄ\úŔN‚‡Á§`0ţ¸2š2•Ą†{Qŕx6š>ŔT—ô%ů¤ş[ţđü_y»{7öáć폷ńe)Ę_y â × şŘ즚ŕâşĆ|NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNç§ ç–˙@đ%,V,XU2Ľť˘ d†´µ°DôZb`°D€« ’ZŇÄ’XŞpÍR/¸I… &`c †˛X¬ŔH1…$Ś)1´ĚJI,ˇ•2"MK˘ A2˘B`’*!&©JADŐ€ë$Ši–„¤‰ E&®‘(hŘ+D¤„Ą!¤Á$‰``:–h&FÄꤸl¶¶ ¬ÜôF”aŘ…E„–ą‚Cv[0¤á@™Ť00Š&¬“‡)EděEBťMCQ 3P ľ4R EB( ±JKôżBPPB0č@„„[‘M/ĹĄ‰ Ű¨J€°¤¬Rüˇ`ËTţęOT©QcMĽ°–ż-ZZĄhŇý nĘ-ôń:\kaúüÖÓůSÇĹćź~ż×OóÔ~Íp; yHZĘ ˙̡q!ŇƸ8风ßůÓG®/É ĄŻÉn‡ď˙kHóHŔ_µĽýőpşZ…§ÂÝ”şSÄźĎóăüߥm÷xÂiĘ_;t8µn®§ňO8‹ęĆ}”ń~¸Đit˛ŢSźż[¬`˙`޸pD†¸„sĺBŢP_× P”Ö=Ž·BŐc'(ăŹŐcy®'ŐÁ€˙očˇ˙p›cóĺÁ^vkLŁţ_“÷KWQĆ·‚WŘ ­Űź×ÁV}F §=đ_×ŢSBŐ4ľđ‚,Ą(Ŕ~oóÁ*ÓúǬlů-ÜvÇľqNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN‚ ]“ąŚ„çŐ ç–˙@“ąĆ_­mšV˙ b†­°&6˘ H7#@&RI($L™&ú»’@(ÓŁ" h&gQ‡0c "`éě “†2QyJB%,Dš€&`–ŠČ¦S„ 2Ě@0’i jH”Ă*J 1 (‚i¤”  $˘©Ę ÂJa0BJI%Ą­$X €H–„ëD§ X©łB «-#@Á!¨JoV ]G@ĐcĽ«-TËNÉéŞÁł ±Ą†®ŞI1Q‘ąBJ… LÁ’…€Ŕ$„îši¦Ą@“JÄ- .ËôŠQB@~„HDBŐ%4ST(@JIJء…ĄˇňXżDS+eóä!/Č~•şBĂŠ”#ö´r¦„QNâ}Jj—ÁŰż·­ŰźŇ‡Ď‘@vxŠ-Łö4>~mĹŰ-SÄům%J-ëU_qţü)4&»)ZVô‡n´·H~·ůŽ?ŇVŠÚ?'ă(O@}ú®Č»gĎŇx·ůŰ­ô¬Vhúâ yíúâÍţ5żÉˇa€é·ţNÚś!mú+źÝ(óKĎ’HĘCëzVČB?YHâC˙4űŹôűЏO›ĄmbŹË•˝Ňďíß´Ž5¤ľOéÄ>SůŰ˙*P¶ĎÉőްĎ|€©ókvî%ľ/αčˇ8Í­Sű§\täKůţ‹ĄóÝëq|·B7K­WOçĹÄyľ#ű}ůůş_Ł=Ę“üĺ˙›ZĄo(·Óo¬zśCś÷·ţřřÖ¸ü#ž˙ľ$ŰÖ–đéóúÓOšĘyĽrŠĆ[Ęx‡šűĘi·×QÇű§Í[¸˙!űüż^t¶{~ř––‡šĎl‚Kupeµů­QNP·ů¦ŞŢ%ů˘źÎ¸ Ą€NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNç ç–˙@üđ-TŔ×ß˝GĂ©=ő¶4C $š DΠ&I:MOü`áÄ´€6 ‚FČuaĚTAc áA@a@-2E@i0–,P I$¤ÔŔh“:I‰-/ŤB(, áá´¤ĄcRL Љ(H©JMDÉi@š‚ŃIQ¬6  Š†KL’™‰‚PL„ÂJRH4Ë@YP 5€" $†ŔÄ iJH`$°fb`liĚC`)ŰĽŔçżťŽ¶ënć6€¬Ë ¤†5@Lµ hĂÉ$‚€Ä¦ŠĄ`+:H„¬QE dÁŠQA¦…€E-|ů4ýúh B‡ô‹ú(BA@Ú){ˇkÍW 8–ň‹cÜCeµżÝEx o)Á1”»/¸«ő€©t˙ź%câG€€¸żóv˙5ćżo©KęÇ(Ę^NNNNNNNNNNNNNNNNNNNNNNNNNNNNç2 ç–˙@b—•aEDŘ%­iBLA2d 5Ôf¬vî‰ ”Ő Kd™ @ :„€&*’’!¤!bÔ"f@‘.%©˘¬‰¨ŔZ JR a,’CP†! %3U RDI%×Q‚ŞÂ$HA@“0™(XVPÁ 0¦¬¦¬,Ś"€*‚°‰@0RK¨ОІÔL†„TH‰U% ± (Ř ‚ĂľLŠ˘ĂUkI ͬnTbe‚.ÔČÂTs!¦éDư€ ¶Z 1`‚  Ja$¬°Ů k%Ôś±~ý™5|¤Ąm4;jΗéM)$iH ”>R"˘Â“Y ŇE @[¦€Ä)( %ý55–ÖĘ2J¨·Qý 4!(?·Đ‡ô¦ŢŃ&*>âvÜkOź,)BR’µBi ˇi`·JRůú©OŢčEDŇ(đŠ?K||PýúpńXüD`>'ůN}oăý?Oäč+T-¤ź ·&Ţűe?Ľ˘ßúă‡ĹG¸ńRřľâ§óţ OpřUSoĘ-ő(+\h}O〟ż¬rrŽ7AFP¶˙">„e]ľ[âü‘á,¦ŹÉĄinś§)â¤V7[ź~¸éE[ř©âGQo|CĄřť’VÇża÷Q€żiGëöâ˙ä]>Uľ%·ëeoŔ@Të…o)[[|íčýĺ'Â%·>óVÇţÎ _`’¸JŰţ+{®ÁĎtńţíÔ„‹š@óx6’˙ňZ·ĄmOďÍ­%kÂ.źźŕ‘k‰ńZJ|×ć+öSoý­ľŁ÷ćśDqö·ćň‹c­Ç÷”ĺ Ăźy˛·űĘ2šŕŰć©®˙’ŁÂ+Vúŕʸֲ•şŕÇú[/żYO|šióTŃOčÓ”S””ńŐ„ç·éin•żĘÝ”qۇínÝáŇrůűĘiˇ6ôŹ×ę±ß‡mlv{qńńĺ/ß[ió\kYBÖů?4ĺ±ŇµÄůi˙äůBp°IÇů?~µžČĎn5 śĄ6ęĆĘşü펥öQ€©/řźŰčŔ^iŰĎę‡ůRZ`%ŞĆĎ|‚[uż‹=|#žß»~R˙>J©ý[ň‡ř”-q×óUڵoĎ5”¦ÝžÎ—·`•–Ę+śš”[–­Ďßľ¬u§mć˙ÜNꍮÚŐÄ;çáÓđ}‚QoüÍ»ÍNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN‚ ]“s‹„ çŹ ç–˙@Äň˛AP{ő¶ges@ ˛ZŐ5¶ÁÉJ ĆŘĂƄČ'´„•ź H5 Ť„&*Nżä‚‚’¤T`b J †BRM ’„ŔD/HIŞ*%t. "ЏA2iT"DH Ş™€„’‘†[Q%• HŞ-RL€ @hD ¦pB6@Ŕ"óe KPI ş,DÍB`D@… =ŢÜŰŚAUĂ•dʆb eÄ@D˛HiŇBI’)nä,$JDˇ¦—ÉX€ @BLł4‡Č%“)&’”KúJ%lÓĆHĄôˇ!mىvÜa4ŃI/‘ űđřĄnˇ˘ŞVĐŠÁ}ÇHCőĄ´-ˇú8ź…¤P‡ÍĄő’ú‚’·Ć„ĐŚŚQEÉK°?°ů ·Ük°ú ń:´-­§Ź)X>Z[âĘKyO…­QÇKűzŰçŔR•ĄŹäµEĐ8ź?h·ř“úĘ2—éâA·ľ+t¸‚ĘVźţO˙TÖ„mߢž;z×îŘăXŐŠRţ¸E->E»Ľ±—Ďé··ľ·›}»óŁÂ˙B„P˙Í;~%ż7\=¸©Łř_í÷íkň[›ësဩ #=©ĘÚެĎÂűu4ľ·e."Pr…»}4­­3Ůýpĺ’›wâ/˙+{ünýq-ç˝)•ŻŘüż\|x%ü–ÖĽÓçĺ/«ŹÍľ—ÇÍţÖ‹Ąµ”e9C§Ţ­?°µnóuÔ۰?´~¸ëLĺ/¸śC`.7ĎýkôégÔWŢ{~­ůGäšáŔb±řÝ/\JĚí˝ýpţíÔ­ńV[EOËÍ×Mpe6ď[źćźçÉnĘłÝk=ň•ŞRâ"?_·ř ÍÇ{->tűńe`/7ű¬{pNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN çľ ç•˙@O3  -đ9˛ÄÉ×X᫣ZŤ¶Aˇ’Ö):Ő’T««Ŕ%A€$Şd„!±LA’‰ŠĚD IJĐaIJŔ¤™lĄ)% BHLŐ ˘Q%0&Ş%0‘I šHX¬RŠQ‡ I4˘RDĐCe IiŠPł‘&Š„S5$„–š‚˛ŠP”‚* e! A;©Ů”š‡DTMPIh"PfÓ2 ‘†XVy“¶@ Íć$Ŕ‘`4s0W–X[$á†HbgD“˝$UlŔ„*$€‰«R’¶DBQBhA4”?&Ś© „¦¬aż/šQAD´¤”š"„ˇű°ţ„-x\‚R*ĐěłIXRí¤RA|š_ r‡Ä qˇjßJGJh‡Ôż¦Š©(·daGHČâmÄŐBBŐ/¤¬¶âMąřZ}VÜ·ĹBhâÍ~IZ)§(ă˘ßM ˇňM [®ĎŤ­ľ ®Aş˙’Ý8ö(ZŁŠ—Ä[˙TëN€“űýżóX ÝnüŁóş?/ÝąPiM?´Đ) vĺ®*ë¸B/Ý˝óühX§őG愾 Iü‚h·×Ďwţz/7ĹűđşÇ ­ŰżvŕźÉ÷…q[ß[ë‚ŢůŔ˙7ů„qľ(âŁöů´Śü[¸đ ż 0Th·~GÍ%˙ZĘ-ţi¬ů&‡˙§Ţl>[Ŕo˙<ý87żË͢—öęVĽ×€€Ř?ýřGőů"śZeŇř$Ąýąý¸Ö7íőpq>q ”g±ă·`•?¬öýWç”çĘú¸2—˙´Đł8·€łŰ=ßŕ7őŹo·ÓĹĹ€­čEců¬§ň®汣ŹÍş}¸đqú ¶ü…¸ ¶ęÇĂćźV™q Xßž® ÷ZŔh‘'äř¸‰jÚĹţ 2•¤á NNNNNNNNNNN çě ç–˙@őő1ŕ E.üĆÍĐwa(,± ­ÇB utL(fcDÁ`. Ť‚%ÉÚI¨€-”ˇ H$„H’ŘI’S&$…RŕI,™)¦ZR¬6˘ÄŠ`BÔ”„‚)Am)HF¨(C&MH u(”ĚĘT$‚tH )A((Q5S'üɆÂAQĄI”3q†`$QD a·S57$Ŕł-Đ%¦ŘÜFŘ,ŽË&o­2ĽhĚho`…„2I4Ča«’ČIĄ a楇ŠS‘蔬IŞěŐJj!4Š · $-ĄôBÚ Ő Jjżă(ŽFO•ŠPŠV…/¸‹çÉ·„„P_Ë'ȡij‡îÇţ.$:)âG[H(Z·-:RÔÓÇBÁő˝l[ĐůnßA§ŠÝJŇx–ę%(+@|˙)+’]šhZKě%/¸że?şZ v˙ČPµnăĘV+šÂÜé|Ą—őŚh§ń…ş€[¸čX iĄő‘-U¤~­ţ$ń~Tż~µűŻŐc?ŔiĄöS”PţßCáNâýůĽ2źŕ­;`>ýľđ‡R-Î"Ű©tsďĐů&ܶ˙)Z[3”żŔX%ýq­ĺ/©MüŁöÓůO„Vč9E4ĺ/ßů¤g·›®ůRšá«ůŇ˙(¬l÷|°[.‚•·ŮFUŇůŠ)ŇČĎj_×ęÜ·ÇűŔhđµ«vPµćßWQůşíoŤýuĂáX ő»ŚÓ‘.P¶éd~·-׸ůŻ6•«vPét~uŽâ'č[“ĹĹů-Ąmúp.—KěöOęßMĽxA÷ě')E6ňâ/š)Ę?#năvË_«r?\N–}€­Ëx*Ę?UÂř× Ýpş^±Ý-H}XŢn´ÇćéI”çÉžç(˘„eĂćźţ`NNNNNNNNNNNNNNNNNNNNNN çç–˙@ç%ř,HżA~”’Ĺđ!˝ťf%°ÖťĹíz ŔXd€ $¸uŐ€PČ- ’P ’ ™©1 ’J ˇ!5!µ %‰L€I Ś3 ŞDCúĄ¨–ja†“˘Ş‚ś$ `©…T’$–¤„ ´ ™0 ‰ ‡Y(L ™aP2Á-;*j@ É–U2PvÖT@Ř€€Č&bÉhh €I Ř´Á*†ZŔş[- ˛ˇÄ•6I6É]XD‚Ђ٬ň’YC¤¬… S PxŠPP MRH¦TÚ•¤ ˇhJĤPěľ ˇl-?B—ô»)ÂŮ„I¦ŠŹ˙oË ËúM (ˇ&š2…¤>-%+eb”ń?ă§Šßú˘“JĂ(ĹÇ/ß­/Ú]—Ë|r)ˇ FRý%`éz™Yž4')˘š-çŤ"ŠxżIâˇúÇĹnŔAńZZAl>}C÷čn[ý-Łô‚´°?§á÷Rů˙.7ő_ĺ?’+Íe  żĘ8‚ßhóUŤÄř>ÇGě>Ŕ_—ę±–gV’·GçűŔTŁ`'ß–SO·ţĎđS沅Ľöý?ýe9AŁň|!Çćę­äIn¬d»wëC‰§ôýňŢPµ@Jźĺ˙n!Ý,µ”-ľăüÖg_ńŇ_  ”[Ń\cÍq`*-öŕ2ŠmŹÁR8đK揚[ăü˙'ůí€ßÖ3©óO¨~´|"·ů­Q”q¬g_~O‘ŕT¤V6 (BŢ{ y·?¦ŘŕJڤ[˙+{Ą_Ďß-V7ęÜśĄ6˙ËźˇĆ˙>Jk+O˙/ÎÝÄýóĄĽŔÝĹ€Ý/ů× ˝ilŕ,Ćé~,řF‹xüë(§)§Ź)AŁ´ţ––ĽÝce.—ZÁ"NNNNNNNNNNNNNNNNNNNNNNNNN‚ ]“-‹„ çIç–˙@_×I°Ć44˘{`ŮRAL—TĨÉ€RĆK@*L)H-`‚ Lfgb*T 1 DÄ)0™„IEC%0"0¶üeé@v%ţv+"”TŔ$Ś9J*T‰dDĚJBh%auÉ5)(5!"ę l"©’P„[´ ˇ%ÖM!¤Í I(©8A•Ih5jBN  ™č‰Ń ć[T4oa€ďLlŃŔ“ŰNÎş0fÁ‰$3¶‰)‘TĹ‚H¨v4B*0ŤDJ)(Ja"­C@(`)!4‚iJA|˛‡ôĂó oŔ‚ŠtÔ,_ŃJčˇI|„RâůŰSCäŇA)|ů)˘”şRí˙B_ŇJ·n[Ąin(Ąn—Öţ:V“”>|ú©6íSúL­»)EDÓů V–ř±đ»z4?|µGĺ”Ón«s÷Ď©[GJ€RÇć˙€ţ±‘NR] _ŞĆ·~ř€âJßęŽ4­>|‹u (·­#)¬oÍ÷çEo–đV¶2Š*üŐ4ş ý:RĐRř$~źŰ’ţ¸˙^k‹‰÷qÍľZvüKKO˙Tľ[ˇ.ŕ|h·~Ń~‰âqö„QX߯ßçnُ‚âýţUŹ”q­,żÍˇmúŢSNSůQ”W (Ę?*ǡo=ßş2ź`‘nßNZă[Ďgë\HJßš}”V2ж_-Űß­­"Ź ˘´Ĺľ›v Ź–ż+rŐ˙F±íĂ=ň•§Ţmý!5Ă€ň‡ůC±mąÄ:?o¸“Ç€˛—ôţN‚JÇýţßÍÄUŻÍ––˙čüÂ×…-ŕ;cÂĆ)â§őŤGď)·ŰłÚŞŰúmÁ4x +vúÓ—çć˙.*”ľ?żĺV=EJŕ|ű‹ŽŘďÉbšŕý~¨ăt°NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNçwç•˙@5řYŕ J•ţ{lŻż˘Í*'ţXľ 4ݵ2€ ¦I €Ölť(‚ŘCś8 A’tMY$Rś3(IK“N’ɨÂA: ,LLAiÔ(&BAĂ2€Đ0PU( “LŐaI@$B+2„’š€Ş %™(š*U$Ĺ&I5R€DЍ%Ňě‘%!¨B‰‚P!'ĄR„ŠŁÄ % 8S!AAAfXPµF†FťPŇ"Ű:Ëš¤9ʡ—¶&H*–b ¨iB@HBH ÂH@4”¬ŕUv@j0@@ %‰h„PýúIĄňÚ(ĄŘ«I¤…´Š$Ë÷áÓDЍGÂŞÝ?[&•ľ7d»4%nŢŠ*Ň”˘ Ďí`…‹ţ*_¤Đ¶8Ňýihq­ˇinÝú lZĄ˙đ´­->©ĹEş˘(éă¦HJxÖčâ[ĺŞSJń$Łú„q->·ÓOď÷o}ćźäKK÷˙™E/ŠŇmëO˙>4SMřéJ×ěeHNĄú]ÝF –Š?5µ«sëz_Ńoói âBş c­›}půşPŹÓńúĄĐ+Íe#)üźŚ˘Ü´¶üľţ:ý McŰÖĐűŠ”[˙iFRµGďőXůíOäź7žé Ć—á˙š[ă ăZBmâÝű·»o5žä?ń'őůţożOňŠp?•6ńlzkR‘ćĎ們Y[Â\DüËęŕ¬t&±źńeĺżËľűÍçµ.–AĘk÷›tłü"ţŢ˙ôx°é÷›ýůĽ÷/‘Ĺůŕ.?×ĺ€˙\IĘ-î—[˘Ü˙ŹŕđŽPúšĆ}Io>L¦žô×Ęű®H×ÉÁ/šŔUĂ\5Ťů`<ŁxBŚ”~˙vÇ‚éoÎÝ€­Ř%tąŔN!˙_µŻËÂÎ{eíÇZak=‹ěHNç¦ç–˙@bđ#ŐŘňsgˇuÍ"HÚ›]˛ËĂ&;b&#`ť™¦‰,n€h$Ő«±I"%2CĚ Âpŕ5; ¤™I&jÁhh@I-5Ą0Ą „HJ" ©‡EM”ĘHČKeŐ)(4Ő‰TH"”dVDK ¤B@NA¦fU€d¤‚RN’dP–Ą«B`Č„¤ÄI %’Ě&\’K"Ú°7 T¨lžî…XK™¸HřcR%Qˇ…›É1 !&bZL@YŔ¤“2ŇHJ*! ™šP±Á¤¤˘“ XŇPL>J""ˇ „±4ĐE¤Rš( „>4Ő4¦‚ůlńĐ’°©Ä”Rú i Kô? ‘ BÓőş2KńRšh[âĐJVç(ľ¸ź~ÎPP„~Ëę}oĄŮâD”ż(ŁŠÜěRšÎx±|ě%Đic\ XŠGR·FDĄđ[ęÇvÔŰËô[ĘRě-ńţ’ţßBQGíij_~O…ş)Ďk}?°ů9M5QÇoˇŘ~‡é¬z´¶´ýńĹGä< mĎĐ·Ĺot¤óŃńQáúMâvĽ(ŰßţżIZZHĘ0(}‚»~®Ń ć©Ę,Xëv÷ëyJGéiţ•Ş?<hĘ_ĺ6ě÷âăŔyFăü’kk~$;/¸°çů`ŮúĎ{wąŰ~UŽ˙ő\[\Ö5ąţ -ô,Ę Â| _—Ζý-§(Zü–˙ŽÝ‘- ¸ź~bŻ?;u¸řEóđú‹aÂŇÖS€żKKK3–÷HÚ;}přCň[˘±¸ß罺ܵ斖ř°·>Łňýŕ4>¬t?Ćż*x°KűOďő”:~ĎwŢoµÂ·eĄĄ«çKţĎ…­ř+_ŔăZ®ČNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNçÔç–˙@ďđ*|¨ßWŻ!˘e‚BŞ( +ÚlI¶D5"ÎŔ2AVTŃH10dACDÁKpŠhĂŞ[$NŃ Ú€I*ÔBI"Ä Ą¨‚F©Ş  DĚ  [ (ˇa ˇPH/é‚$Č2PA‰I(A‚@! „§¬[Q`L‚° @¨·PDU ťÁI Ń”…I $A2CZLČ2‘ I€2bR©Z4nľÎúu†††BŞŮ`›•l)"[«Ł¸Â@’&!bL8`Ą¦PH1NŘR”Ř:ŠKö @Ą Th%úLŐE, JP]‡eZ|衭ľvô"Źŕq>?› TZăvôš8¨4~°î‡ô Š/Öź˘źŰ·|“\/ĘÚŰę Ó÷ëX*(JÚ”ń!úKoŤ‰ BÝĆíé4ĄúŐ?Ą¸Ŕ\Aň\?ŻČ Ň”[‘A·Űß­ĺOR+… Tˇâ§÷”бý~T[ż_§ütůşEGô­Bm˙¤ľ}n·~x [|ž$V5şŠ–Ľ#ĹžĎݶ -Ö÷ÖęQ\ Ŕ´·ć­éZZ˘ßKţ5żĘßnĘż'ŐŽě­e)[üĐţźŐąŘoŠÝGć|ŢPµ\ۨĘ?Đ´ýnÝžéýqĐ´µůŕ”Qá׼>üňšŕŔ^jŹĎ"9jÜúž:áĘ~–“”ůŁžŘ ?—ĺůĺ6˙ÚÝż­$'ôŠÇ·­[żhĄk=˙4W„<Ó«TŁŽ‡ďłäâ[˘ź×ë=ë¸UÝąőżÍÖ1ý8‡Áµ$Zq _ľ® zاͭҊkŢýĐV’ýn±«„Q”yş_× ĎpűÍţ°Lŕ'Ňú…ż~te/ÝŠŕÁ¶¸2€NNNNNNNNNNNNNNN‚ ]“ç‹„çç–˙@tÇ€€ +WŐ7÷ Žř;!Ą„@č$I¬Ó*ĆÎÚ–€Â„bál.dD”P"Ka$Ў!¤Č0X„Č€X„ I" RQ)&DÓ J ‚V “`RŃÂ(EILŇVx1TÔAL A& €°BeŘŮbDPł„¬@T¤¦$ŔŮd´B!"˘V0:5Du¸"J$CH’@fˇ›$LôÖImELG`ă™,h,föĽI!®»€„f‚„j2‚ B Ă’P@ŞtŚ*•Z)4&Zů&¬PV)JjP‡ácBÚ’ ”>&‡Č ˘ŞÔ—d-ˇů¤Đ•ŠęŠ)ă~VńŰÓĆĄůăĄđ BP?r‡ô5JŘă~„SQl|•µŞVđT˙#;4-”¨@ '‹ŚCę´Ąő!ŰŇů&šhă„~T}~ÓoÁRĆš8Ň‹zݸ¦€_,S”[ź~Ö˛š8¸íÖ쥨ŁÍżK÷ö˙ÍĐ(·ŕ'ď˙+ćëň)óO©Zt·›4­%#öů AĐVoyĄ¤ţgŽßXÉâ6즜ö§"T5ůů´qŰđKJ­?·‡ßżĚż[üÂ+Ť$Ł~k_ť)ýqe ˘Řrú‹tŕ/ŰĄĹđąoo‹őżĎňtýnÁXŐĂžţh×p>ótţht»üŁś^n˘0šB+¸ť?řFśˇ(ĄňßšĄ.–Źáŕ8¸xčMc­y·ÉOš@NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNç1ç–˙@đ*˙—ÚŢĹŃłň.ÔcŐŤ Ăgm Ęł53($”˛2A!˛0Á«!—6–ȆŔEI  Ë SwT$,D¤T€KÔŠ¤0 ¶€)‚”ˇ´@!˛ hM@‰¨Ó„ Âp’u Š’ "“%5@ťHHË‚BP†msI€Ů )HAĐĂu˛RÂ@:%AJp H%¦"MÚd¶a¬ŮDÉ`$Ŕąa[ˇF64\á­ąŤcPeI$’Đť-iI „Ą(¨„:BA€±BBP/Â#dĐ„Ňţh ŃCĺJhĄ»*,@|—A)ăM)BÔ"”şM.ÉYë\KA# ń4%ŮĘúVŤCO…—ĆŢ•[E+đ( [–ß"޵EąĐq[Đű(¤Ű˛”şš xÜ•¤Đ_Úi}űZĎ|?ń%«zÚ?hMŻÝ N–~ýiőcŰĘŢ{RýjÝůű¦š“n®?ń?5Ś+±mŘ cćé·-żŔ®$Ňżt`‘ů.HZĘV¨ýľý-!."e%kB ďËŤ/ňźŐŁÂ+n‚JÇâ¨)}nŞú·qń‡ët% ŁŤň„‚;n7ÔĺÉ Ć•«pM ĺ°(C°ěˇ4&…ş <`R·\CJméK· Ň‡ř+·%ů·?|¶EKv “ĹĹRßůŹěźŐ OĘŮ@Ę)"šr…Ż7Jh|µMľ„۸lj¸ăń-P•ĄĄ´ľ·ĺ!ý!h`*ŕý¤ŠršŕGî…ľ%®1úĘ2‘ű|—ŢjŚh}oZ⦗Ͽvëy}ćx°ęś g·P•µ‡żňóYFXţIYśG€€xҶ¶·G÷ŮMżŤ˙ä ?iĘ0ćK§ţ$[©q”¦‡kúM±ĂŤ/ř¨·ŕ>5™Űr-ŐŽŹßä‹zÝľ‡ôń—ĺm (ŔX6 Ö7cŕ0ţŘ÷Ëo–©ó_ŻÎ—Kń­żĘ2ś˘ß€ť,·‚ZőĆŠŕ·çµ([F{-ěĄŘ­1Eż=¸‹¸$tµ»ůňmô˙>é{zE9­źÓ˙­çşŰĄ’ţ¸ršát˙\"ÜxßľüÝ,@â}luľÝ”qţéFQ‚L§šá[ZHNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN‚]‚ /‚çĽç–˙@ ®q~&<ńrňX$é|N¶t 2ćîňł˛Y ÁÂ- şfC«¶IE@A &L$"©  &`j”Š‚ŞJ ()J&¤CHJ*%W 2P’d B¬Ua P(!%"ŞID%!ÔA0SQ2LH!-~Ij¤DT‚$0t€€Â „ŕ±*‚,HĐÁ!Öšˇ°Ęş©-“ČŮBv2u˛DÁ ­ŞĐĐ –ÁÖ¤‰Ťjf*Fľ ęR€ ™|ĘJHK* š„$Ę[Q(X„€™Z(€)% 4Š©AL Ňü‘8TÓV~•‰ˇ@I \O€)…·š•M)JSJhĄ|Iˇl%˙˛K÷Ĺ)ÓÇE%nÍ©%ő%&ß–)+UiK˛’x’±·ÓK˙Úßő®4¦ŢOé+o„­HĄ+gńńCďŇüżÓ”ĄöSÄ´ý4ÓĆE4AZKôĺ?—Qo·%+iâ6ôŰÖÔ·ĺ$ŇC˙7€íëa>mkŹňă[¤ĹÇú¦Šxß`.?Úk…m+x (}”->Ęîšů­ţ_ˇú|‘*׾śV=+’ŢĽč/˛•ĄŻŮĄ_żĎ‰ĐV˛•şá[·'tŃ”?·ŕ<§ÍţN–t¶Qů[üŐ¸ľý!jP)G›@ćíÜ·ÖúĆĘ|_’ŰĄ˙_µŞhŽ*ż·eĽß6ăć–ü#ÇĆţŢ—ÜyM;86-SůţKtSCôˇú_Űź8†Á-›Ą“oć­Î”µŔü¦¸?Vőľ>3O瀏›ýşYOş—Ôż|’Ś«Š_¬kůçüž r„xAv_·H¤-,iŞ  R…ˇ —Á/ÍeOˇúŔ¦š ô…ŞiKňM4ŰŤ(Ş´ŃA AâKńB-ďĄń|$#ö¶€V¨©üĘŠj?hăýÓJRVݲŃK˛ě% k@P´¶ű÷ň´ŠR—A+i·­ŃžÉĄqźĐĘ)ŔVü÷}…” ĺ(~•·ô~c‹=íȢś‘Xč'ŠšĆýŠ_>¬jÓ+O°ńžé 8 Š˙(§Íe/Ęň” QCäҵZf‡öűwŰî,ű)}űŁ‹őúEőŻ5\4ŁŠŘî/ óyGçBOço·~Yďž˙ŻÖĘ<ŢS”ţ_“ĽuŤXö˙Őc'‰."šĆş0ÖĘK¦¸-é/łä§=żYJ8‘X˙Ş+†ßXÔ8†˘±ř’ůmÄ7NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNbeetbox-beets-01f1faf/test/rsrc/unparseable.wv000066400000000000000000000312001472325477400215520ustar00rootroot00000000000000wvpk 2D¬D¬Ľâ¶é!RIFF¬XWAVEfmt D¬XdataXBWWGVHCĎßďë –eŠŮŔt@Ą¬ ‘ɦfŢ1/°¤% k^‹™«\ÁĄ”Eăž7h… <–°ÍeŔD*b=p™bE‘ÎÁ¨¸‰±I(€šxŔ‰Ŕ(N%ŞTŁ´°TéćÂé6™r¬‰ťS(Đ`xtÔMI)gŮxL¨Up M ”ľˇhŚ`pH /Bš×`0â@ 7K6ePÉ=J8(p˛Íü†Bd h sr`ÁMybáhÎn2÷T ŁŹđ6Á:ŞűrÇ`…ŠĆŽ’Ń%"39>JL9Ć uě)<ÚL;=&B8GAářkŮFhF”›;n@+A¬P`ĂH›AŠFÖ$FlŠfľ÷Ŕ} f©S›áDś4/°Q¬RŔ+žÓŇ"(â°¤ Ť WéÄÜ÷´),˛0±µĐMP„Ţ·$Ë„ÇÄÁqŽŇŹFŕd-Ŕ×?©µfsh"¤ aXjŞŚ`˛<˝†Ă•°9Śy1xŽÁ‡ęrVśu°<Ő.™î¤˘Ü>˘q±Ä-8np#á8ŚÜąâC#yT†1=Ć ťÔÚ@ČĹNňc$ŵ’Álrc U˘«‰ $¸O–JŞFQ ó&łQd™Ž4/ z @jQ g(l ĆÖ43ůJ~ñfĐŚ1¦Š%€¤ĂhfTÔŕôN¸ŕc“8HŕLaD R+‡9QT~„şDĂŚ2:Q›IPvjŽńÁë atÚXťÖ!HŞFÜ °†ĂË)Ě15ŐqK P |–MáŘI=ŤŠÁJ`Ő±ęíĂ+D™™X­ĐC=2™7TŽJ3)‹cđáDÍ,É"¤ÇńQ'#z¸PúDü—‰6ř RYÎ;ĐŽB¨¤4Ž']'T ¬Ŕ‚D3@…ÖśX J|pĺS ‰ šŤ‹Db2Ćń|! xgľpČćš˝=˘^jc'ɸeÜ`ä&Ŕ©Đ DP+ÔČŕĆŽńXČĂś% ™jŞY–ôjX†ěj‘lˇ:dŮ’Ű[µ>4& Ŕá>Ä ĉŇNĐřáhŚĺĄ€,d N˘łq<^@b…>ÖgľÁÓŠQĎ:X-č`“FřˇAŘ\Ť¸~˘µfs µI”Ĺ’Ĺ óNŠăĘń˘'v‚nBŕ„žąĂřP.”ăA9؇2Ęic"Y čŚ4FXžŞa!ŤŚP§"řřcb˘ă(đ’ľA6fČůÍČĎ@q*.¨`Ż[kj®Ň ł€‘c¨YĘń+ěx„j‚áX‰ľB$ěâ\R+ČD× R‡€fŃD6ŘTł,é"l,ŔRÂ’éŽW€PxS="=i t 7©ĺT3A—0`d„ÜĂÄQ0$Ž2îH;-îÁb5Ń`´ ČGN4ŹÁÓCO†P–°¬ě†M! ¤ YpJ…”ŚD  m€ŘĽ,x ´1.Â0 MĆČĂĘ•DÔś€´Ńz")6÷Á± P,®hôŔFŐ\Ňťš–t?Š‹F¦‘6˛ŽxAkF—XäjM P#Ţĺ¨X`dI4Ś=ř(Ł 9» 0óĺ·Xš€—tě`¶h AŔĚŮŘ#G€¤ÖO†Ö 4šĂó% ÁNvş¨%„N ZD3”ˇ-97 .!í˘ÎäÄ÷iA”‘ŚÔ X‰ŕ;#QŔ&܄쓭:LtnT‹ ŕhé¤mP¬„IÁ(ÍK Đ‚¤Ĺ-Z¶GO‚d‚7ŚP@gŹY=ž (‘ą‚p%l!HLJ|ÂʤfédÎĂA8>@k0'-?c¬Í‘ Ž‹, f ÎĄ“ „dłĂ’jŘ)2q ˇ‘…ej0§ČhAPë©®=a–Ś2`©…Pň\wçńP0UĘĽˇP!ŕgĘňŔ± €§¶X~śµ¨ąäl8şH,fłü:‹¬ŕL;ĆÔą$vÂCHŞ@x?V  Sí0ű#"ł:X3ĽŤ ó’ŕVźŕĆCEÁŔ4:bqL$`<¦¦„?L5/•ieââ8{`hňbXs¤á`€©…+CŔ”T€Ú2żĆR(™ş¦šj”Ç*™x„yXňŽ­ #Mb‹p8‘‚E5Ŕcş¨ă@#+QRčHH>ş@áŇ@4ĽŁޤ“ĹŮQŹ%óRH®°ôpSť*-¦ń—h ŘŔHä Ď5Ę6Ř9; LT>•$FŽ5hYdaŔ”â2CyěÇGÉ0‘A®A>A#•©őÁ:`@=c“.Tx™1 ;dX%‘ >âĆFEćN€–˛@H‰ČĚťÁ™ŘÁ­Á `ćĘÜŕ@ňX% łĂX€‹ňŚ©b €Xę ŮÜ™š AfS(ë1ú…‘ŮŚ  Ł™€gfň…YDËx aP¤J4@"h„Bą ó2Ă/ Č‰ŠŚáćĘ| ­ ŽÓČ Ç†m*ń46Í—ŞÇžđÍ ĺx‘ˇ Ô´ŤGp#áx/Ôbă[€ 4¸@@ -ć$°TF9n€‰g˛Ö xXöꌍˇÂšąZł Ël a#©*čhęT¨yAŮ#ŠqÁÓYO°ŽpHdJÓNbÁZ‚zşńŚiʧe“Ę«ÇĐĄ2Ö™„Ł`Ň×|Ň•%‚I°‰™'(‡ F†*’ĘTóÖ¬ŽňH+ZÁA” &Ŕ K^ n( I†ťŚkŚ`čĐ8°ÜŔČ#‰t2ÇÂ%ăůaŢitŹąA’AAU¨fýĆe"„±8Ôä˛NT˛ ‹FŃc=•ĘŰpJCʰ˘‘Âř .A<Ć&&ŽĆĚŔŁY:ÇM‘‘€‰Ŕžő1yfK,(Ź$ŁĘ á<ŕŐŔ kʔ߄‰Zk$AÍ’?AĚK@AÇŃ3%f6” HSٵ)ÇkŔ °ä g;Dµ{”‡ÄÁĺĐ8Ň@$p ÉŃ$ ŁVÜMŔ<Ő-" S‰; G4M¤g=AsMzMćEe.ˇť 0zbeÔi¬;óöá¡dî ĐD °Ĺĺ´L¸ŕ0F X9• ýRŁÂw±‡¨â—) Ů1& ŘyŘWr”ešmŚ6łMÔ,¤çľČ´ΡW‰Ĺ˛§˛rŔLd“zęĄă˛y1B6o' Ę‹—ŽłQ–Ŕ*KĐ"04EÄ´1qů¸ˇŰAš@őA›í0¦î©†ě®K`%(·5'‡›ž˘\ÓĺPgý›±&´™’€R@3 N•Âu4Fářř¨xA ‘kFđ(;šă×đÄKJÔŔ4ŁŽ“GŇ8 DHŚÓ ccͰ ±2ÁÂúĄÁbś…=];p™PÓepj@Ź RâČóH)Lö Éu¦«sÉĽÄhB6°0fŚI)VćŢ2HÉL’`Źp| Ł%3„JÁXT€ňŐá×x´ŁÄĽ Ú”3É Ů@ôĂ"GȤ j„6M ˇ(^kFá1yfgŕ´L´d:*ă%¨Ý€=XÄňÖMAćƇó …l¨Š*E ™JěŘŔ@qD¦»á†#PZ#ĘĆDGXśi €/Í7šKÉ–ŹtAŃ bŤÚS@t\P9n ѡ°k1V(Tr18ʰŤDŽt”%€‘i2E&¬Çü†(ś†s±Ď°Ę­€>â–ŕ…‘4ąF “DÍ/ŢűP ´Çä±â̵#EX2yĚ•Dd”%3L řOÍŃx… {Äp Xçř‹PAj¶ćč"Ô©‚ŽKN4uŞ @X6Ĺ'Rp ”T„é Z€XŠŕÜ  K`‡ŠF&­D†!ĽCI‰ ÖŚGt„F6ˇŔuŞĂ9Ŕ@2›„FرĹ@b¸ĆdA&¸Nu”$c1ŮS„,W ŞsâŐˇŤr7@˛°ÂGĂp4ř€<€‰šĚM5E`\Xd€ŘŁ$ĺ1¸ĂD­5’ pĽ,Oěá:6âˇ'†‡”*/Š"Ě3ó€Pˇ^´%Če"‹­h"uC‰iÍ ež’ÖF=ĆʸÄH¤ă!„KČ_˛uH$ÍĄÔZ 3ř0HřqZś6Š”Iü  \'V rePNĄ†fă%$R‚ń-s‡pʱž8„»`ÎĐ=˘©0_4˘h¤cýETŘ‹ĚAŚą´ÚP™_Ŕ€š/1ŁÁ- D5Ą–Ž€™2/ˇŮĐR@ç!ŕG'ÄxâbÍ<Ë%Č1–Á8l8pzp€ Ě*ŃłŚ1q‘PŽ• Ĺ 9ÖˇA¤Ę•ą‚D(PĽľ A H˘W„ă"Š%"GÄىxi‰ÇhűdN•őăŻ?T@łld A›Ęšĺ\‘a 0i4ѱ.B>Ëbî3sx8ĂIż 8\ “wŔ;Š !ĺF «‰—:xSSJjńŐÜO…”!Č2îPy0„WăŃX $Ó@†Ż7Rjčź;Ŕ|á#e®˘T¬˛ú@‰j*/€‰ŤaşaáD l˛ÇşĐ8ZĆÚ5SYx„x4ŚRI—ÉlÁâ"kFhÍ×"ŮDC©R{4‘ěC±€‰Rdxf=R"ĆTŇ f Ź2†ăCmp›daa#ĄŁ‹ŁqL6ŕĺ‹5z”AR 09Á­Ŕs‚} dŁ©dbAĚWćŇăín˘¬–ŚĘ3±bGÂ1/tAŤq4ŽhŠä;#ş§rŃk.9ć.(€´ĐpĐXŘbëá7Ő¸łk¬9šńŃ:‚ŽX|r¸tġÂü@¬`ÁˇK€Ń®q–„ „+¦{Â%đrŚC–%Çî\r e‚aF˛Ă(K' K ‹=–D#§B÷č-Ĺ“’𢂦Ŕ˝˘ĘB.ĘqˇHd9äŚ.ßČŽĘ…0pfa˛!‡{XTt0ˇk!BJînäW6Ą• W€ŮHÔNâ °IAËT‚F&ˡÂ4ó‹ň©¤©B´Ó T¬ě¤6]iq#l ˛„›–HO„@˘Ń‡QFËcwđ¨2—ĚŮř’H@-Ń:Šđ a#B‹ě¤Rľ#@ŐŕÉ«C…Ögâbâhbä,ć¤`“aaCą*b \B«(n@S a2Rh&—FĚ’C2eŢ€°P¨đx*”%ŹýcXd`F“…– ŕoRzͤOt(GcPŰ7HDČžĐčý„ĽOLÖńŇĘŽb(D kIâ ŹD…—Sb2ÉŔFdÎC/RćH<BÁD8`Ś›ęĂ5@§ĹG`€ďi™jŞăvëŚKL>Ô– Ăr°Á|Á’n06"¸“ ”ă5 Qţ%PęaŘSO ”aYb‰ÍŐ!yžn„,\jĄĹädť«K°!AQJÓ37c-T)'–ÄS(Ś …ds¦L]łÄq4Ę€÷1t45˛z€ŠÎ|ŕşp´Á5/ UBcŚ•ÁB;@雀U$…»ŕŇĚ/!TéŽü Sđ—â!Tâf̦{pťtN@Gb˛eÂFaއ€ÇY‚ö0™şN=©z°2Ę`٧0Iń $Łéř’H…Ł%žQĂ_ |+ŕPÖtM5ĺ¸Aäč7âă—"e(ŕP"ć¦těΕą fh[€c-Ź0+›)P,ăTÎV8\:2,U`Ź}@˘™¦ćNĽ ‘Jad"TÓžtC Ě×€€UëO\€€3‘‡Đ‰2ó°´ĆĹ'8Ó:Š ôaŁ!‡%ż, —^(±t<ĂPy8‡$•$° (©ĆĐPXRsęŘ‘[D^Ą‚1%FT6ŇFf¨.IA‹ąa`F<×\j´Ě[#žŤ&ć“<}0‚ b˘E˛Ŕ( ±žő°f)OuÄGž*¶îó¬ań ˘c. (ż(µä ń‘ąY&h‘‡R 3_=Çí§đ).>Ú@«hĐ›/qŽ…9ĹČŕ´‚úĆkw`¦ŹqX`Á’á>ŚO y@T$šI‰#…qfád(čˇxťqɉ€LÄ€‘–L´6هĄ€ S{„‚Ćš°Bć©ÁĽ8ÎŃŚ)`)0RĚ­AŇN5K5µCÍ5Ő©Ň© €™jČśŻ%‚:őAÖ:âB8$Â%\’8„J*IM‘&Ч%CĽĄí¨žĽ ühscQáň#\B)ę4Z|Iňť–` Đé† ­č`¨9’ËiPé0•bL$Á)RŮpBL‰ş<ĚĂHá2zÄc‘€M&3úŕuĐ… u¬1:.<Đ‚Đt zQ—ĺCj˛>Ŕ7Yzć$ ‘a€‰¤¨B;Ç„8ŕě!™hŞ©)Ë ČĆ7ÄŠ% Kf?Č\ĂĹęAŮÂϵnÁÔ,7X Č\MŮ =o“Ąµş ŮIK€•ÁÚzEĄq ł9H…ŠEŕHˇć P^”ΦȠ™©žšh,¬’ĂŔžú Ů–ń0ÚHyęb˘R!5 ďq ďŮhâ‚4ĐaĹ\9AŔŇV€MY‡C9¨H–Ř0nŠ!0YČ z°´4ČI3*ŹV , b¬8ţ…DĐ#ˇ”RH' ¦4µŕ†<9r<Ć©ÁM!‡ß&IX8""3Ť1S 7W'‡d˛OGĐ ŻÂgÇﵪ10ÖÄQˇËô‰b„ÂN5Ąc•‰ę¨ ŽnÂ"&逥”ŽIeć-Ń5_¦Ń“Ď%jşgŞ<ŽYÂdl@gáç”IOkÁB±gĚPÁ aQvÚÍTr $ÁËKplFcĚ2¸4Ň™bs±˝0ѨŇY “g @R©‘Ţ”ě#JsŞ&f7=\Řč¤o¦S{ E¦@@sňśTaadP”;.×°Ĺ`  ‘!ăŃ@‰ghE%"6š6@šG‚p@ö`–‘82y E•Ěś'Ö–ˇ” Hf¶a â‘BcSdŢ#Ĺ'[Ş%%nP'€hĽcşX Ő˝20–4Ŕ¨F#IŔQ¬$š z(ŞŘĹn&p¸.PĂK aŃĂ],ƢAs6˙(r2Zh§Â t*ĚĄ™^Ă`Fž|)Ą%1¸U™Ŕ=4 ;8dx¬ˇÜZ3‹EX<|RaáEŃŤRŽ/IŮeIá°ůĺ«a,Ą”Ía(‰×HÚŮPĆ=Ô­łv8B¢ÖTŽO˛ ‰Ŕ\(/…/BĆfEp,1đČ W±Č JŐlˇ^ŠďŽBL% AŁĄ00üqŔÎŐp)€N}3‡sCŤkÂĘ@‚ ĽĆ°µ ¸60`q‰‡˛E=ô|€ X&ě°Ň‘Š[Eř2…›ÓÁ#ĂFuĚIu$.Ą^,]<5šK  Qbăťđ—uxM$Ý~Ť)ĎFĂLŃ&ů…•Důi_ib0Ó; –0¨Sti(ÚŮ€J\‹®˛”É™ľuŔ˘‘)…ť#A¤PHdbÔ´{ 6lÂK¦łă3RI…;G Xc¤E ľK*§d°n¦čȨ7T*ă‚Ć%¬ĹkŚ>' ĺĹF¦ŁöA @˘ÁÚ(ůI· ěŠŕyGůeD‰ ©ŽFIž®€Ľ7ČoI\ƉͽŠŮ( Î;4Đ+BńEX0yŕFsĹ1RPaéPjşYplžh,7‡ćč$Ä#¶ĺSÂďP…| {\u†¶Pä6Ą‰ÖPa40óµă (ËB$:˝9ĽÎÁI4‰Pĺ°4„yahâ€ô`ZŔÔúđ2ŇÁ¨âQÜř˝Ć¸˛‘Š (dOQ…tÍ•DeC>ťéH*5Ú0ž´Ć P±ŐB;@S8l^–Ă „Ł€péęx˛„ÖJ.±Đ"¸ÇGQŠÍ›"&gdŇĽ›In0ÚE‘čEĂ—Â/lÂ#,0âĄaZě‡A ™9ť Xó¬Ii;ô—Ç` ÄŕĐ… a^ŕŐ „ć»1Îč3Ó@-Ť2ŮQfy‘u(gĐ02lˇͰ|f´Ě,M¦i4®8;ŽH4ŠĂ`&ĆÔń¤DÜÉĺ €X·`"ĽÁÍ©§`ě4¤č¬·"ę‡/» ˘čń= ‚Ž8ŕ ·m S+3k")KŘseJŹWĺŃ1AYd&Ť :©v JÄł*If)§:@ň„K5‰‚rśeŮ Zě!&J“SÍ'`6Ö+Ŕ „‚°6ŘtŹůŠBďÓ÷ ń\¨™`ĚZe‰tZŽG/M*B•«Ă=¸ŠŽJ‚Ä`ÔŔRH š‰Ć¸‰9†rő@Ááö„üXa‘ŽyĚ<˘’f~ß Ďh;A‰­V µh¨-źˇ<¨Aęđ…##Hś(Ö03€§Ó’ZFŚý -Hiwč˛óQö‰nZC»cÍSŠ,AŚíś7ľ ŕ˘č N!ň¸~ÔŠăh(s€Ä L˛1ŽŃ‘8sÝi±pVĺ…@X2¤17/ž—Qˤ;eĄCp ¸”%40Ś6”uAšÁ2gyäĹÄě1ű, p ĚŃ'6đ6côŚ/#€ZźdŠň’,® DŁp$ɲ$"TЬ ô–Í%HŃ'©M]b Ç âůăDňA4`1×(âT24H9 ń‘Ă:Q13(/’‚wú P3žcAp‡" ók™ě ÉÁJnĆ%Ř`\r´WµÖCZá’he¨ákR  Ä‹3Óbľp>ĆHÖô-éźĐ‚7¸2}IĘ=M™i\D˛uX’ZŕpÝ®ˇN•±# ´Óś(·€Kµp$4űAF0‘Ł«ög„ Ľ8®‚Ńa[:*äF‘5V±Âw"dF$Rj€Ą„ŤČ¤p#ńób/<Ű3BÄR`Âŕ€Ë&CÂÓ+ÄŞĽ4bĆęBˇY q®3—ŚĐ2ci@‚MŃiç´ÍDgšĘšÔ~HS!)7—€E8őARJ‡ŃÄh€r¬:Ħč”#>ÇšBµW&}č0tŕŽ‹>Ĺ ,—–ČłűO€%莴€c(eS$(@­ŽFŤO˛hě %tD41ëuġI‹ŔÎ0¦Fć%,@D&4fĘTæš!ŢC€¨†$âlîĚ´`Ž5%RˇbTs}ću ŞP8űAő‰qLnžJŤ(‡$’yłŮ@C§;n’jłt±·€ĆŔť M4@ Δ›ô<Ý …*ĂqY ­h×Ŕ\jÂhxm= Ľ˘'ÎjôÇČ u±÷ŕěŇ É`8 ڏtlyŢqpŔĽäXH1ó ‡ŐŃpG T–Ť…©9Áx©Ř$€(ŔT”Ă $ČFÇ”ăkD-Â2Ń· P»s5Q‚Ó×DsGB4x óâŔxpU8 Pľ˝ÜCa¦ši‡çÍ!06Úđ@ů +‰ĂĐĂ@˘Ĺ ř0?ě× ”T#:'·BXÁ58KOëőô&lŽQ+`ެ¨‰ FŕX ‰“ˇ%ęîÁűÉÔV`‰TÉě*Rľ‘¬aŽľ(R$Ë@6¬Ŕ!5ÖćÎ\9NŹk pŞ` ¸Č6|FMyZ4Ë©Y˛đ ‘©QrĽ§‰W3{¨Ąµ&ŔŚ @ö膰M…ădvŘÔĚ;©bÝ óÔZL±lN+á6Ŕ •hŁ9@—-ó±őŁŰ+ťě·‚D¶Ęh€Ü4™<:ćjśˇ5 Ł\I\}T "€Ére.hŚĐh°`3.f#ţY&ĹÎŹ¤DáTkHL@ Ň0 hNm’˘¸@`gj2P!=XăLCŔrgĆŔ|»d’H =ÄĘ0Đ©2«ˇŘ€– Äcş'uŇa4 @ EŁĚAÄšĐ=˝3ŕ4.Ö‡;㊠O#20J´§íÇE„&hŽC÷‰;ŃńşĐ-!n Ý#z^u˝ P^jfŹV€DsHD„ l4(4Ő©&ÄGĉ¨5ÎŽ=- & < § A㬄EgXĚ‚EĂÇQÂ<#-…3Hlc0­O¦™;jŤ0/Bi´ĚKzHJŚ&ăh26`'ăđZ™FHÄ3W<(Őce¨Í@ĘĐg^‰†GÜ<"Üġ%ŢZQꔇE ;hdŕ™G,!×ŘŚ1· Č9OmŽď„ýHDÔcExΡ]¬4ŕŔŁ!źŮ$€”jŞL ,­çY0AfM$…Š ¬§Äă4Z—$ ŞÄĄa#aŔNuŠ (˘‡ÜѦi:Ôy”Śî\ú´r„/E60®‹$4â‹ŔăÔ>Ľ>ŁD‚ŕF`z6%ćů R¤ŮŤGs|¨Â]0h”u(”—Z.Íř0“dƤ3R9t¸ &´"y ‰ZH&• ¦®ŁńĐá‘5§šJE=¸‚YÂKˇ`^tt?%yŕ°8đ]Y4Č9î «ç†ś>ȱaNđ~$űa¬h ”l”Ś —€ D†°čYEpÜn"âDA6=X®€Ř9^š™ŽÁ±v*›˛8Ů † í©€t'5Wg¶8Ĺú‰šGĂ\ű˘‚(%öÁŐB­oĚa<ŚdŤ‘ó† /‹Ş,5W†â‚€B/Žč•=úТ 9ÖŚ¬±ľ@Qš>Tâ óaPś•° i1Ň, ÉËsŠ„Éş (X84ĆCiÚcpň‚•¨$<&…¦+ˇ\"@±.iŃ8Â5‘č5W§a2 ˘[BŁń0ĐË­Sr ,Oh‘9@•h ',A@â‰pN‘Łý´'YMö ś(ˇ,±^Äăđ]Đ(@M…—ŘĹ(@Zť €ëXi™Báx„‡e*W‚”ˇš«3žÝOs"YČ Oľhž‚E!e8…PĆĆOY0Ńi±`6I@Ľ#ň53dĐ]SʡR.:“žě?b•fG¸RÚą0W¬Ě•pCˇD8˘Ô„¤ćJ¸aĐTGsLĐ‚8Ş×\c凬k”hB"),ŇĐ0Dřkb( ©[>ĽB¨¦}đµÝ€ ™ÇĂ–óÂq+đ „h€4ČótK ÁE8\B•DKQx‚š(#€6e "ĄKćcpćÔi –6ô”$¬8žÂĂöÇA†Ę 2 SĹŢOÚ†Jt=D%ڇiMމpśF?„ŤS#%‚B_~˘‰&Ů’` ď °Ăʉ˛©tŘP´CöPo€ŔO` ZóĹg‚dB,HТ{†Ęs÷ńŞ8Gbá(đşRD*kxŽŽyq°ćă+€¨5ČÔšc2U†\‘Ł1,ňRdŁ,ěDaĹć aëP±‚ˇUŚ^Á3d‰KŃÔ¸KJ(¨BäY'ö•8TŘج™»rŔÁ*G@‰F  M‡*ĽÇŔĚ•ˇđ•ąŔq<0zň Ńa¨l4ć ©˛č:şÄ©2)82NMIžŠ8i°đsC0*µ`šó`©2nBŃř5 °ô‚f(c¨pĘŐ„śđ"ĺŇ´e†×8 {ęŚ[„ĹDĐ”r†Iä¬1j6@ęlˇ…[†gÂ:ÁĆŠPłĽ0WG#‚h<Ś â©!qú‘!PgéŁcČ*¤äŘş¦ć´Č0ëHG H$OŚ7ôd%‰Ä#2Ą±!´Ř‚­ h᪠—„ŕ5&Ě|6p ŕŔH5—‚‘’ (Nâ€$ü‚pěÜ ‚č*FBĂĂh%¦u°L80™LŽ,«PěąRä67CDć #{´}ś\­fŚUlM`=`A™â0C‘řĐĘARK#Ť‰p ň €ěhޏ5đ‰tjńM‡*Řśy1¤YĆ?@‘Ź’§X0ŐĎ‘¬¸)M–€\§($‘SM…q‚YE1µ ş§Ś!Ł#-S­E,˝2„Łi¦Áh˘PqEBR) ň˛h â‚Ę%0‰7U‚˛©<śÇ°§‘C2¤(ĄPłń4*\:ş`é—eRAšřo ’jĆ/¤˛µ . ÖĚ=¸X—M€<ČšŐHX Á±:–K eĆkÂd€ â5ĹGcl —ćĐL tŔ†Jô:ŕYCŃ.¤ă  &iTŁ1(z˘Śť«C¨ńžę HËÄ­´$ %€ś7„đkc—Äj #îT™ x‰“ÝDpLŚ Ą@Ďtbäl^ /0Ç@Őç!h𨺣‹RÎi“čp Ľ0ư*ŔÄúđ22€ł… ¨iM!¨Q Żf•°Ć‘ޞHčâHYŔăÉCe˘1 6!™>5E0a“L ŔiA,`ą ¨’ł>0t 7W¦%č€l€*9…*aÁä{í2lĚ™qöB ĽČ¸Ń,<"Ăre\!°Ű/ŹpQŘPÄVpŘ’Ě!N)Vćę”dTŹ.“.AěÁKÂŚŁ€dÁq±ĄÁIę8ă ¦ĐTOëđN§~j‰#Ś&ôŘťNc’Ę$RC–űˇăť@IÉA Ňă7&5WFĉă’aŘHcóÄé§ęĂ !Ć+Pś›J>P,AĎ„¦.U˘J"14ˇq3R¬§”R1J‘ ˇ9np …9 D l¤Ś<ÂČO>ś’qVâťŃ… ‡+Á-'´<Đ€v3DH Ňt h˛^€fOëjćµRšy,)UöQ?âgŤ)Ŕ©ŁDP\L!Á28Q pe `Nęq€&ŤDc,b¨ÂˇÔѡTQkčŔR ó"(H8¨2d:bąb°,‹d h< \Re¦˘Z şiÖľÄhq'°Ś)i^U°ŘŻš—"ź ć‡öQ ŕÄî\ [‡YN%Ša [NXc–U f”S%Ô3Ş©ĹC×ńě8 h‡¦šÖ¦jx Ŕ&T‘BĹÎc:\B>eX$‰őˇkOax”ŞXÁ‘a`¸ŞÂ R”c}†˝¦©m(ł Q@4g‘řŘ(a=˘‡…ŤŚăP î°ÔT覚)ó¦ če€lŚ%Oě$¤čÁĚÎAçctÖ@TcQA<=úN)I~*żN:śŠ#(­2lP‹Śá ’›'´ÇX€LÇi G ·†Ä Y¸ Ż1µ3™@Ą_$źJŔŘČŕóh¦ZG@&Bd ąš8=S"b€`Ř8;˘ÉĚ,ŐÔ5Ő¤ĂaeVG'Äš,th`Ű ApaájŔŽUN…Ť=zÇ5Ę7¸Ą=Ą¸“8ÔCC‰Ťh٦8DnS"Ú<ž(d}¸”©˝Ôh?€`!š řa/„Đä{'I‚ń)0°Ł}ĆŤ$¬gö…÷XĐ!VPŔ“DžW?Eaţf°ĂÄ&vK,LěN…zP"·!2RńŤ<Ç‹8 †`J±Ę%Mĺ™Ő ňĽL`qޞLµ ˛B@gHĘ3d%ë>p˘l„/C Y^ ĄJB9pL$ĚeAƤÇ$± ™Ĺ",zÉ‘M u$‰ Ďťů ‚ßxL6đ‚ŽĆ ňBj®Br2D­”ÔAťúŃŠŇXJĚ‹*F#/XpDhiĄ™Ż6©ŕŤąt„Č nZ4C<@J€C‡”ąš¸>Ĺ*TJ×±>ŘÜdGsŘV@¶™Öě 4Ec 6ű=Z A–Ř óÄ5ĺćMAT`”ZŔSĺ Ž 6źPó ý :w‹C!Ź’Ů×lG^@Ęń~z$4Ľ^ÁЧtj2e"OˇkÂÇÍAĘnDÚM(…SX^ ĆÂ, -Ć ‰ Zk ř«cű;†e $‡GëT.č桨  Sh ľŠ‡†Áq‚FO€ÂHâ,/€hĂ3o ¶ ň:ŕK#@ki ¤ťbŹ $3ťČŔÚ™óŇL5ő¤Dü<5Çţ k„'KNĆé ™ 3öŕd†ŐTł”±Šśr–¸ĂÁJšĚŽqÚa (%«Ńˇ€-–ĆKň0™˙APETAGEXĐ8  DATEOct 3, 1995APETAGEXĐ8€beetbox-beets-01f1faf/test/rsrc/whitenoise.flac000066400000000000000000010631141472325477400217120ustar00rootroot00000000000000fLaC" 0 ¸pw`©fqÉL0AÜŃ:Ĺ„D reference libFLAC 1.4.3 20230623Comment=Processed by SoX˙řĘ |×V#áč9ÉK@ť{Y|ĽŮżŰ ~ś–«ęÚ–´cż˙±ćT/„ź™<Ĺ’WŮi–r鼝Žn=E2 ÚPôlm!Ę@ÎŰěpa ŞG’Şl#WßkoŮ„şěwl§ć–ŐÉFĹŕŮ5R®Ń“!F3pAëŔRcćĂÂ+ŔăG$ Ďjf}„X1ץ_3~K©üT”řb‹ ŇOeaĹ0|háZ!q4ü+–—ůżj™BÖ‚E[N‹šŢĐmDÉüÔ^çe{w3ź8{é7âťç5}Qv™‹ŕˇ)Ó¶Çfdŕ_e„üŰ~ Ŕś<’ô0ízH‹ ipŘ·/HEŹ*üeĽrÇň™#cѦBOĎúÄßŃ:¦źÓćc\uţŻîýC™ĘŹĄ$«ë@áúíw8±+îÎcÎiGfĂ•¶™˘‘ę,éČ ĂV˙h•R3ĚŢ4¦‡}bŘ˝ţBŞjîM‡j&Ęĺ5çä ôÇvÖ™\Dd/ L Ä1ČŹB‡*ř f—Ęąřňn ”¬™p "Ć&ĄÝĽOi~ťĹ·fzCumÁ5Đ•ĄAžçŠőnˇ†ň ?ÝOÉŮŽéóÇ­k]č­ÄuČÉę-QŘŽ »:á`hYŞŰngeĽ®ˇë`Ś÷Ű…5»™śćěęĽ(sH@zťµ0âůHŞrŰ•%`c9»éȇťÎ§tµ 4ł~Ŕ7J7ţˇ?)łĂf&(ŁQ‘çůŁeáŢK†Đ¬Â{RnÚ‹(±RŹĽrŢÇ ď+ĺŃŚ¬JîźźôŠG¸dI O9f&ÜéiĎ×ňw¶ÓŘŇ-e‚0öđ‰ßš±Jy»ă/€“6Ţ®Ľ¬19÷”UĆ4C©1ú^š_EL*iÍ”·hÁ†ľ¨Ĺc¶Ń‹—˝.Ă©@ËŔŇ٠ܱqĄ“ßÁ¨÷tŞ+N¶?q“‹a`Zg±ĂeÍŹY~$˙;s…a»ąm:ĎPˇY ľžŚ°NčMqňçř+éćęWJ‹­ŁĎ|OlTŞŁN‹I¨ĐI-¦űž` QŰÄ%|Tß;>D,`Ŕ˝ ŞĐ<ÎÂźÇäD6´,1÷/iřżr$ODľ+ż·Zľ?ĆŁ÷ťŹ?´dđőUç Îüˇ$üzâ Űĺ^ÝžŢ+{Ë,SßĺgMâ¨w|q#ŕ:‹Ů.ř&B°y›ë‹¨ßT{|âĎF)“ZŃ nJÂWÓÔ€żú« (Dlča´đAzľ:®A+oľ¨ĚzŔFQaěîŘ›C— L%ÉÉŐÁu_L«É ¦ĘK űnňâŻÜI3¨GąkF.‘yd4Ú«ŕ S·ŻĘĎ$ć丏PÚU¶ąýN yŻé<ôçńřhŐů2yĂ\łoHÚő%H ĐpSÄ1tfgńŹT „;7±ÁŠ)¬ś©Ô„´ĐĽďÇĹ89s2Ě<ĆM~s´2]Ź핬ĽH.vz0·FŢfgˇił}Yäş “ux“­DWşn®ĐŁę˙”6Ź#nVčŇń•VnlhĆŚXzÔkŠ‹‹\(<†r]y}~íąg97n“Äv-bíO2ż › nůmŇ«ň˘QlžRśE´ĹÚŻM<¸qć5~„›>ŢďşDt}]–.bŮrĽ;Č9rZ”¬ź[`~c;Z‡eeÄ(受ÓĎwŠ‘®oĚŠ:_‰ŰdOYöĹăPŻĎň¤"ě~‡¸Ţk+—ĎFŹÎC"Äč¶˝Xʏ7Ĺř+cî}ŚÜCŮĂ…ż=ÂŮ2üÄą^$ AY żžË»Ť,Wě Yő/ł2źóUpŰđÖv‚侌h]Ę®GÖ–Č{ÉÉ–ŘźĄŰîqÍ" _‹çz|}ď%‚<ßJC´€ÂŞň Ě«–¨`oţźG†|ťHug°~eťµţ˛ĚWzbą(÷e¦ŠF®UÎA(!ąĘ‡·4Ż•6,Ł •ţźĎ´š˝Í¶˙>9€ şâSÖů}x‡¤Sť(FĹĽKôݲχ'ą„ţ÷Ŕ1Ă…f–ĘOuüyÎş@ĽŢlś¸.©xxŹcŹ}«S»„őĄ Ł‹R =x&Ú‰F}p‰č›Üń«Ĺ[h[Óą’FČpH4Ɔ$…i{YÄaţIy’ÍhúnŻ.łł»IâF–ôÎ1ç" ĽçŤ¬‹% –u(xgZ\§+Ŕ‹Ilúϝޚ@8 ®0‹ďVlě ú!ĚFť‘ †–0%ŕ5»Nkj˛ß¶q˘—\™ąw E­—Ůl Jiµ""#@S: çŨ-/Ą›V4„dč˝ÚĎG›×ú¬›ŮÎP 5 ž^ â8.ŚžýëSbëâPŽÓáx¸˝4Śfü<汇çĂś)@Ę‚ăˇFśÚ [Ę6“ÓfµÚFEY,…đWrĎ ¨¤ß7Ů·”ŰÉ“č"šĚ gĘsi BaŞaťłj]ĎÜTĎn·Ä>fnę§Í×džÔČô`W.Ú=‹Çű„P>x‰Đpǰ)Ô®KŁ!˝CéQ¸•ÎÁţ 0Ëš¬Ŕ%©o8_Ş)ĎŘýŮŠ{‹ů“Ą$›„‚„­ž5&&#6Iżń,aăňĽGűý—±Í"¬6íCťI侪cŐéŞĐÍ»$ňH{nĐüĎçĺÉزYSNO,“„9M đ;0Sóî–ß3ş7q`¤ď€šwS8 ÓX T]Ç_-ą|҇~=^Ţ޲ůj˝é‚pĘß‘ťôsEBăĄyb j¤V‘ŰĹo´cvč† Ź˘ĐÉ3ň ­€rŁE]áUÂżů(!ü±ôV@;Dć"ž¶ >H‚ÄZĺ÷S•ž~5ľ9Vř–ežţŠąá@&šéŤůł5¶„ŚŘżNíĆX¤Ä‚L}Á97tϵ§—\÷kŕ§ďÖ´ÝΠÍWĆóŇ(gĆý[$é‹?·ˇ1ŽEĺśżeCgß×ęÍČéě¬_ţůľ{ÍÁ¬ś_ѸS$#%ż˘Ń0ĆőČ/4,ů_AďÇŮď9/ă<" ö^ůc!Ă ™3Í4Ř›‘»ĺşją2R«:0ĺgťsż˛ße3âJ#¤TGŐĐĹ7Ő>¤ŤËĆŤgĐPeó®ŐWn­l†[BeDIfűZ­/ŽÜµ˘@^?ŹN?ˇY”YbťyW¨ÁÄcű÷g´Z¤}ŚĚ·~7(‘˝Ôę¶Qś0˙ …eµçx·©Č·—eŞžÁí˝Ď"†Ł‡Ň…–ĹÖŢ‹Ž ŢK®´)Ö< će©ĺ¶‰¬zô÷%u ‚Ţ–,É„®b‚eĎŠř0iÜUĎf<µĂÖľ¨|N_$”t™íđ:,řdý´ß•©ŕ–î˙̶˝äş^B9Őn\ÔťˇĐm #;Cr»˘Et}'Ěut™™Pą0E*]¤ąüÓözŰWľ‡É¨áč$%…ĐÇ®á˙â:đŐ¨ţoN*ō땜3jVÂ1Ž©9»Â^Ď|ç —łunŕTó2b…ŕžďu}lâ_˝téÄÖe+ŚrŻÜUŤHŮł#q€Ĺ«gŁňUXŘéôm|b ľ±Ž]mĆ5äG¬EK=żWrëµ]|ˇ'•§=Ŕl™ÎĘ}ąIP§ŹDÁN_śIú`ëPÁĚO8#{E‘«…˘cQpҵf¦’Úşó–ѺGmoň ˛qäôîlËŮH·ÉżŇ€yIĽ4 ¦cfČŐłŢu‘‹]›¸ýčb™Äµ7łHŘĎŔ>u[ś˙•ÎS%˙1©ń· lr/5?ýeu+nO ľ!qĽĄď÷UÁ–zĂ-];oíýó rB}¨ ^#É2Ě™,±EŁěKXúé±%OĽ9ÜZb¦ZuđWz>xůÇir”Â1¦ –#4òĘ ĄlÍ*iË&!ďÇ<+¶ŤóŮ4v±µVŕ¬; §ä—†ÂöłĎíW٢HŇ–‡ÎkŇu…śŃçűĘ3ű«bčJTÜÄT’)Ë>Z"÷N5ŕYÍşŤ˝Ić&śÔź^U°hčéi}S~懧RŔË® 96oĹÖ!qL´é}ťü»â·me¬*Ú+äš|%1m–Üx_jŚ7swˇŽj|óŽ<+Ę˝?oëv»Ăöo4;­8íąJXô'罵=Őˇ:|ç«JEô´ŹĽ‰ć$ź|ľ ­+ú$· Z:&_ĚqŇ|©Ć îP=YwD™’w–ő€D®u¦_„Ë+ߍAőZźM$ěú4´„í~ÜZ„ĘĆą/UµŃzvŐ •â#-”ťB‹DzőžjŐŁ%g|©]!áÝ‚MtýJmZˇ<-ń:´Ńł ©Îö^uŐŔCî ^«>4Čnć[“ TËsŢPß‚Rď*ЉăârzŚ‹ńlĆĽ+>Uš$µd ’irĽřkBú”űkâl^‹¨Ą B]čp  Dřz+=Ü…8Ö‡Óąr 5ˇăQ`” ÄůC {ěL¤‚gŐ;‘Ď*/&hŔŞďHžňóýÎá-ÖíůEű@xm/”zňb¨ńE!${:í%~®˘V}&Sß7uˇu ć¦÷Ťµ×ßľ~ÂNÁxq Zˇ¨Á{Óíz‡)NşbîŹHÎa°@ä>Ě%h ż.ýčŇ]MňµŻ)Ö.<%‡sĆZŞOâóeťçˇ3ßôě‰]ŠĎf\çŮöŮŚ­jß )°ŇI O×áB©AvĎ ďn]xĄ˙lëÓYĄhć¨ZH‚pđÔ îě” d ®Fą 4Řál7ĺ-ŃŇ´\’łě1@‡]gúLşA˛z _:퀋7…eúTKe€ ™ăôĂ\°NŘbÇőżŮ]ţŇŐö3XŠ)¦‡~ň{Úőëz`J,ÂŇíĐĘ7? €y’eĚ+Ęă–-łŤ«o‹!*eË<$ţ¶®â´IósC‡¤¸’ĺ{_q¨÷kŚD8ôťěÎ…~@r‚Ţú î&{/"Ş…ž{#v3 »/A)§F ěX^ FT5Ű󞢪^Ş+ż‚…lşz~´}×Ě`‹˛@M`H=î_7§’S)‰Ś\Ź3$źżUä<[¬R ťyĺóÚ_wÎŻŃŁźäXëTŰ«Kç¸ô!óZM7iń±´Ű‹i.(€1p"Ĺž•˙÷ń™]¦çk2#eç4lŘVĂĘr’ăw~µJşf!ĂŐöyř*o„0V4€ H8 Đ ›˘`.ŻąŔď#:ŇΫhcât §ô5ö2¶¶hzNň5‚:€ť°fz'‘t ¸."KÝ›żţCű—Ňü‚ł /÷bčĂč1öüŇď'(ivŢËĆX-9ëž•y‘ ę|;n—rVŃ夆]4Đ®Użě5ăŢť{ßAőĚ)‡PxFtŚ7CĹúxżůÖrľX‚ űOúŔýLbg|O3­Ĺaóü绺µg»*CËúoš,ˇjÔŕÚđSŇqjĹĘPç>f°‘ HŘBČaĹŹHQ-äÝUÉ+fő€$9ČŤŘR´Ö¶ł ľżvŰąÇdÓčŃďó!ôŚ aň}1Çë ŘgТĹ3J‹?§Á8¨cí—G7hĂ?ëSqdŐ C~u*oß4ő…â.ĎđÚ”ä yŕîTËŻĘýŃřMwpÂe·„vÍ{ďSČ÷!ńÍĂUŃ6AĐĺK©Ů„˛i|ţFcĐaVţ@˝]dŢ×Ńq¨ `4Á®¶6 …'˛çtÚ˘˛}mü 3éEĄ§&ĄËţc–b\÷YOK?RßY-afQýĐ•’ˇ4%Č®!.‡ď †Ůś(Zťüa0˙Ą!B J:—Ň» ¦ĆuHi×O…šŤ†çĽßr%ŤĎźż ˘gvů]‘‹”ňŐ…(2”úťí đ´džç·ĐŚZU ‹ĹG3}›•a™+Â[ËŐşé–.läKâ¸U” )Űç=ń6rŇ[ÉMLUk sţçP§:—w1[ÇË"“MĽ†?qů ťî&ß±ţlZśŔŢWťĽpEfçőßÂDEŢ/r ö×(]0‘Ź´Ô3(žv8¨F9AtaĄ0Ĺf‰y,%ă—škĂJiCnˇtśżQ®§Wę1K ş2Üu»s:-ŰŐŚĺ•őü6äµÜÉϲ»©SF–zAQg_“űˇĚr=Á‰ÓwiÔúţÖ »a„ŮMBg°¶»6ŔFt™†yńă*´ćhZ!UsG«i‘ej›cMąď3zÖä<çiľ¬5hV˝ĂémŮâ;&ÔRÍç%V‹fßÝCł®>¶´jró ę4ďŃXt´-bˇĺ*âÉt‡_e4s“˘Ź2Z.[6©:,÷TÝZŚK¨äYócŘTÉjąţ(ęçÓ(˘1¸˝a˘cŻDÝO°%(?–Oˇ™«Fµ¸Ă[JéŚŔĚčK‰ŁŢÉÉŢƉ<-”ˇă üKg0…=ÍČp,«ŐčŃ.Öć÷ĘŻĄ°´ pQţčÖŘÔibć‚®; ź4BIčŔ‡’;ĺŔśőRGüD”ÔöGW 91-(ţěýŚ“Ś˝zýŤěľś›m–87ĽŢ!ßv`‰ńĐŮĹŰTi~Đp<~[禷ˇµŁs:(Çč!K91Ż×˘çě*3şˇ|«Č !Őŕ2Z¤ćÄ)˙CśłĆŇSŔţbđXśÇCH{•p¨CvÚ*Šţú<»„ýĐjŮHE˝ü&|ű¤ßˇ”sZ’…#{ęškŞŚ}②TŹďř|óqM?21˘šŹ2AŃx“ź)đ/ëćjT§H4˝ f<2Lł¬„áŕŐ›`hÔŔüä¤ Mč|ÓçJŤlv8X–IÇßş UÓ+•˘R}ťžĄ÷]˘čŞŐňQD’íŹQ©˙+čś±HG-¨ú=jxŢü'äĐ >ŃLľđ- -ň(ú0y „î,6AR^Ž)Źä3dŤ!ÓŻ…Ůóç z†ČbqZuů"ŕáŤÚ«Bŕr0\'5x)ŰQňź®ÁÔâ„)űXÁj÷–ôd±vu†>ÓDAş[ąěąţgýR©€xĽ=eŔ1ŘsÁ1Đ( 5 ž’"ĘÉ5qŕ@§TtxË)ˇ@ܱŤů°q?l´U3#jRXHvLž<ć¤E“#=ď!Ź›Č÷DôőyąO˘Ęâ@ŽBfZ4#;¨ůÔ§麌H‘Ćo‰?śľćĚYó3bˇÖ&;ĹM#’f­3o˘«+¸¶m5Öě«ďCĂV:1Ł·S§-&-Ó]ťžg©áŃrÔIÎ Ä‚¨řRśCHw.÷Łł!ÁvXΚŇÝHžŇg:©ă+®e×Y–ß––ُš_Ňhął•É§‹ËE´qµiá•ͨ#…¬ó‘2Ô5‰>FČÓŤmcń6ţYĄöź™;ŰP÷2·žOR›vô뇻éă°"RD-üděÁĐʧ…EčŞMŇČž–'ĚŹL1Ą‹Fä(/lädFŁár{’¨pĽ‘C\­‹ˇLó˘o+˙ůÄŰl&Ń`%—„Ăŕ†¬±6vwż9%›0©Ą3¦?ôú·’d`FG 䡻¤ŃŘ’ŹŽSň~QФL:žN|Łu¶ AĹQ"]ÂO˘ ÇK}ŰkVŁŔşl2|}ŰÚwkZ‹_1ë”%‹u%´> Yn!ĎŤg®(ŰK“Ů–s‡yg`d®Ťcߪ¤śĄćVËŧ–ëÔ{ŚŮL˙x1vě8Qc.ˇi/UÔ“0čů±Ě·u˝Đ,‚Úä ’†î¦Ŕ7öÇ…$č•ÜEď5T”pśLrŻĺş·WŻ §ćüe•ĆpőH»`Đ«řNÍÉîëLŚrýŃëŮÚńće¨ÉA°äTY—a™Đ.{Ľĺ&ô[| }(‰ô˛iu$%[ę–yM·KçTu"ŤŮ‹˘ßηŐăřž Ęy„ÂnI(Ă!VvÎ"o8ĐÔbđ¦dФŤNL*Á)µJ˙Ľ¨3§ÂëÓ«>”UŚiŃžX†jĂVCK÷~ç¤ÔRy9öN¬Ş­Őh?qŘI25—[çĘňŰ­[JśĆćnń±t»úŮ/W Ő:ˇ(»â;ě W˙ ˛wŘnîw|?š%ë‚<(QI‚/uΠ7úŤ„¬ü±7n·±áŘyúy ŘŤ§8qlâîy ű+±ł´eÜ<üłwdŞ.MŃž[Ů\dí­Ň† —ĎEئi=‘äD6÷ůEé(“©˛Ö?K,˝ţ;óˇ?Tv›‹OA>TŐß[šţy@ô.ât.UśűV´id4bí…ŔÁ­8'(#ďęĐWťÇŚg~Šl^:ř”;A?âSŃ®*^"†Ř˙dȉ6]µáµĆ şâ:&“ÇĆŞbŁ şE·ŃWqĘz˘)¤7KÖô=i\ăÂNL±YPšŤů:$Ž©pŰ›WY9ĘݡW#ťaŁ ¨wxfţ:J¸ô€:eĹş^ľ í"Ćkü_‘·÷*n= ×đ*`ąö€f Ö vĄ·hěUTĆą¤Atß B‹Ë0‚áÓśôGÁ’V/µK-ô0ÁűZűÉ‚śµăcŔľ,ňJH·ł©ôÓř#>ę\Ă$»qÍś&ťůÖťÁëd'‡H_‚©ĆXsź‰v답KÎcÚż ”ÂyĐóč#|ô® B]u ŞĄ:đw¶jqVe\ý-ILÔHâ˝HqŻ\O˝6,)ĹAöÔ@ť·N…“ŘÜťkx¶Đ=_Ź´Wu¨óöęßaĘţÁăÇƲŚ%ŻU­0GŐ, ŚŹÂ‚íY©>Yآ ÷‚łQßÉ/•öržÄz ÔÝ«vHŽQ†ó9řeHş&±ż«MŮąü…}ζâcTĚdČƱ֣Ĺc+Ś;wŽČÄđ\ČQâë"ż§‰J T¶úŘ[°íXW;Ďéʱ%ĽŁ"Š+ĹÝa–üĚ hŔŁH âq§;  ŐI Żú‡ŻOľ˘šunZÔ—äŃ•ě ×ÚśŇë ä"Që–k9˘ŽŁĹéÁÁ&^r(–ńřŘ·ˇ±Dy&˛z±Ó˛úđ?ŕÉx–űŐý'žň0w›F“ W©L–v‡= Ć“ţĂL÷tZ‡ÔilŔßiÚ3M·É;ę('‚á^ŞŐŮ=d5€ń¨Đ{’đ€š03L°…vĂB… EŽoČ‚(j?Re´0äőŮ̉,eĺpĆ8} ·4řŮŹţlë’őťç1‘ dá ’˛™ť5Ó{S>¶¶÷KRË*Ž ¨HKUDVPëx-s5ó&Ża÷Éhß Fůcéőł„ú&YĎń12ő 9…%ĺ±»ńo*ăé|ä@XmsťP’˘° ňĽp'¶ĘÉľIbw›cCŠ–ţ%1űŤĹămEÁxúbÄ”»¶żW=ş˙ĎD˙’Ľ,Ú›Č!Q…ąÁˇr„F”…˛I);(Ýó‘żNe–ĽU˙ô­űýr%:ż2~śxWĘ`&áĄ?CŞ”0ŚŘ#zĺ¸x#üŽ«Â±«ź+kÇu¤N‘ Ił˛µŢfTĐ‘ŤhŞnČVnË€~݆·q"çěo:7zµťăŽÇbdZ¸đ´»„Ű ăĂĽ0ŮhŮłÔh«hQLYĽeÍF—‹§“]âĹLLŕcąd%-{–ů­Ű–É7dALŇĐ5‰CjI[Ď}'hßkE Çhľ?… 3Äu/äÜYaµ#Ńm3ĆČGzĐWĺ9}Mą€Ł)ć—ŢíÜK-ËÓň¤Ý®mík™ÄďÉ0ęɩۀe6I!ŠÎŻLř,ęŠÖ0Hf 8:ŽD>Ę0>O"?ł{…ŃTˇS ŢéI¦ˇTi@/‚qÁúôB»őŚC<&Uj9ŁĂvmÓ¦ç"pIţ5Ţ=·c"5s°Öx«Íě)îďďG˙•Cgö+8ę­H<ţ÷ gęďP' Ú&‚™‘$Ť+‡ŮŇƬÜ95_Ż~]ÂeSS=«ůôŕčőŚÜQÔ€“ă0íń¬mäh7WÓqnë4OŻ‘ľżdć–ô”“Â®Ń ć‘ë>‘8|~ýÔ„ÉýXÜ^Rţ.>”Ĺ vW˙LR–«®ŔU™ă$oÎĚK ¤P«şčG\©s¸Ů¨!x.ĽYŚý´ŢKâ§±´EŃĘFJAµ–&¦\x Ştč$ă!CYR\•?•°YÁĚăÁ+ě’”LĂö¤~vß…@ᑨkAÖ ,>8P¨(NxŠÂŞ˘îĽ‹™”¶ŕ o$›š?źäź6»Dü…í™2EÁÔż~‹użŁ[îÍÍhE (Đ"řîy<‚ýţŰ•ă"®)ŐŤQ[˛uą O'7m%„ž rSŐ>ĘýŞl•b”‚[zšĂne÷ Ď›đN‡ÔR¨Śe—Đ07[›V/Ţ|yÔBz!ř8‚×Acüńs‡/‹‹.y™anďşćb J1Ě/ŻĆ8Ť:2wíě/j{ÇęźđFBF!g—»…ťĎ cŃ˝Fç{Ń´óCއo^Ŭ©â{Ąt^F‘oGĐGÚűÄąşSšźťp;Łg}olź“»—Á]¸_}D¬µß0=âwś˘—meMŕUßŕ[˘ ú0'á űH”YŇS¸ňM«ŽTŮ'BUÚÁ¤\žÜÄ×1nś¦ËÜG9$09 Ü›N4T«`ŞĐ˝’Ú”Íq8ť:GĐ)__•–{Ŕ®^­V?†c°«íËi˙©L>/x•:Z*G‰~˙3t”'!Ůŕí2˙ćC„ ‘űŰÜp¬Ë„ńB©WôţÁÉf/đúŰ®t冻ˇyޤ|P…žjOI ™Ź“VÁ8ť­äÖé8tě±…íoxr©*—Ë`Lę4šÂBbĄďMxúušAŕÔ¦lŻ Ť*#˛cÖĄO˘:—)w‰#zU¶uźâÖŔSţ2¦ăđîšÝťŞüŕäńN󤡜7›kÍ8DŹÔ¸†-mXÁw§ZBz;2Oű4Öé?!z Ář„!˝ô]Mľő7gýřkĆëěY¨PíBe¦+Uţ÷ü™&îÍâsC­˙L÷öćů'ĺÝtiIHYÜ-22x–|„˝®2/«˙…Ĺ1ĺxŤŰO˙´Đ…傺yűą(FpQ…cĆÝ&lŞh¦Ĺ·ÜĄöv–B¤jŰ‚Kś%ÂçZMV÷Ń5¸—IüťůU¤Ž°f‘"kÇtUíC—óńD}q–…Ľ­°vĹĉRč=ź% łÝcm» P_(šPĘ©˘Č{EçôëItj: žśĺD0`7eW(ť6AašŚĐWÍő^˛µí#ouôO…Kć^zPŤ'zö Jʵü|Öą.…*{ů\#ާŞţš/ń¨”ż¬x˙—…óN¶°žąxüI ç bŽÂń­â=č—H°É 0+H˘ÍÖë¨y1ó?lk[/˘#Ú}Z÷÷VÖMÍaxk°ÓQCÚwVŹmpXÁ<–Ş#WÝUDkľ*łÜ5rĐsr»?řЎ´u*$™Ôj…Ód‡ÂĆ\"ţ|(÷GŐô‹ Ű8Ŕ“ŇhVuzŮ÷źÔ*zk«G›šl7›©ť>nÇ@Ś´_fbÇ©dĽ;*J[đalÍR ›zĚ•_^^Ś^RE‹€ ‘M)X…J˝VTj¬ŽPJjcE¸˙ŐQى¬ň¸o5_f~Ń»ş#·KK’1+Ťj5Q‚O9”ś†/Ä…ů(ÓĘśőďěĆę\ů±§E–8Śâuťđpä§áŰ…`2)Ý×?7Ř·YUpÁc ]*·ďÁ‘«0Ď”ép‚ A6ŃËüz‡íŕUň §]‰ ®lOůŁĐ‰Öł`3‚˛`AZřަÍwÂYy‰¨p?–}Ó‹=ąňĐhÄŁ7Čph9V!ŔŇe¸u{]ą2Ŕ|ËÁpĐšEżoí¶vd¦Ę(ëîý]z@5Čv$JěŤć9źč”^šzKľ ÄüŰ˝)ĚÎë˙˘ţţŔWW–o!„§D?Ǹ´‚µˇć#(ŇŁŢŢşy Ť!tĺß–­‡Ľ[ &p‚ż^l$8^´wŹě=ä(·ąźÄÄΕË31.éÇRŠÎ{‰µŰnĨ>JŃK‰IóĎJztőű00í ĺĄp>ç—2÷PÉÉ\řř^ţőŹĐ 6ÓďŢ|§„ű2î.J”G V×đňцç07Em]ęčÉ—OŐ–›@ű†×JŚ„(xm?ŻŽu1Mg´wËd$¦.˙b…‚6Ę~}ł 2_2ŮĘNA ůŘŹw+đ7˘ĽXyăýť;1eĚÚÔŽ];V Gßµş±LąÔ*Ř©8i"NĽ˛ôî+‘• eČúhw˝!éż÷Ż'™ÎKrÇ @ŢbÇ ­=Ńŕا>u-} 1-Ȇç ď…ëóĄf[â ŮY,iV°f/.Ęm ©Ť˘î(&oX8Eźć-j%f„o‹J3ÂĎD`RŻÉĹĆ6eMôŞŹ5®í‚Ô­+íHëľÍ 1z#ĐśÉܶĘúׯöŻř ¬MĺĎ„/ÝDž3¸c—ĎăâtI*â\Ł”Řń¦PŤ„î‹”ďň YÖŹE*żk"LŃ?9ŹŢĄ\cń]+Ĺ±ŚŁşŐĘúw¨=[ÝĄh©Ł ‹†NŮśN\R/Ó%šb4OÖńŔ?\j­ŽZ‘˛G™­)řL¬% ?”ŠŁć1(ađ~Rý€K‡ű ’¬ }‚‰ĐóWD|ýÔŚ”‘Ô·EŁ9bÎfv Bş‹ ^Ҷ$ŕDKřE˝ÝŐ‰ňÉŤž°b—lšÇâĹš'yskÚľ&ÉÁ†S¨ř,G*ęvYx^őQ/sÜś3ú<~¤Ęüq$éç“‹ŕŻc6r·ŽŰ(‡ąę8ŤĺY˛ M¤ś)ĘtďjÎovĎ}¨˘éčOŮčŘ{DëéźŮć$xŚíşö”Ĺ_˛n7’]aZŰ­ç7Ąő(•PÇĹ›A$sS×âśŕP[òě¨8ČFIX J=D*ŚaóĄoGh“Óń§)Ł>Äb>¤¶ĐŃ3/Z€0Y°ťäíĹY¦ŕg+–gŐUŢö‚Ýihi®ş ß3wŁĘőÖŐM'^Á‘LeŮěČK"©ţz’TemÖ)d>éO-‚NUą|šÁ)hZEĄ,řĹ5O.§ë]m7^^Ď p?:c[ßf†‡Ěč\„`ڇu #ŃŽÜÔ-¸öf«Ox0çqÉňµČz$UPJŘ,ł„ey[ŁaôFnĎÔ1diýGŕwřÉJQţ:dÇ^ű®y_Ú›g4rV 7tŔ]^ń]ěĺŐYx—ťp2"ÁáŽĎú/ńy˛Xţ’¸0»x~¤´ľzÎáĺ¬ü•b!łđ$‡t† qť»Š]˙ŐÂu¬ţú¬ýöCĄ'tÖ­u9MÚv2h—łĂZ@~­îČň'g.„|}řŰ.ŮR ăó "¶˘żý‚6ě°·Z§ť«taą±[§»ĐůŢ"(# QnuÔ—»]Í^^‚‘N}Ř‚±ě”ů$P#ië9Ü˝¦ÎNy–ChžB3‹ŻpÉĄ)±¸&ˇÔ]Ś] hm˙ŢqÉÖi•ĽśAą_űqŻĐToA"#‹ńýd.I ™Yä(§y=ú¤ď fˇIĹÝlŻăőI(ľü–'Ă4)¨4{8äú˛´ş*ĂżľJźT%Ë|˙ýčTŮŹ—ްd|H§Řćć, /‡5Ně=iGZ؟ϽIW9–Ę _«r†|búş˙řĘ {,Ł3{2K/ÚyËĂ’Ě—>«”\Č űxőé0„)QŹÉޤl\ŻŇoiŤ(#Â:ą’炜şm1Ě%€pB+$ßäŽ~ŃUg<—T ˙b:ˇ˛÷Ň^‹é›‡ĽŠł7Ś$^›Ôę‹ů–b`Ch,Ń€‚R‘Ţ|wą¶í"Đs2ó4¨ú§éÔ˘‘čB ťu°D…ŁóŃ,?€Đ –›Eµ'AñnžđŻ{`“ ™2:©ŔfpŞŃ/Şo…=řŘŚÖ|7ţ؇ˇ¸Đh«x­¬U Dt Fßlp·Hx(ĆHş3&€ő„U·f0ńDlN„Ě Ţ„BçńŤű‡ÜJ¤Ś[•O°Â۶ Ú\®ÇâÁĂ©ÓfvzŽ*Ź^cqµôšđáI^Łk­7 z’äNVŹËÔsůŤm/~VĚ+C¬ˇáF%CáÓĦ†ťz¶`i)İíĐďßęql-•Şé¤Ľ˝8˙­"č›7"¨YÂs|8;KhZćß±S›ćťLś>“2ŘWGÖ&›ü‰!^ŰĚ5Hmâʵ#Ň›–łF% đSŻ·ý@DMŢŠ](ۦTiŠc•BUˇűĺĐőž=¬3ý»ˇŹ—ßĐł) JÝ ˘TÉëţOěvýeO˝ŇŚćűëţńNôGéĽZ Śâă řŇ`ÜFĎ·{íhMř‰™ „~LnŢu ‚ĂłxbçQrĐ‚­•nfÁĎ–`Ca|鸰řŇáž-mJHdÂ5ú˘Qď®ŕgqu(Ő'~őú9=¬çőMŮ2®>?ýKR†‚ Ów­Ťń|3łđáW,éKún«ÄóĹ]»ĐD] Eĺ#g–_dĄ„  R^UT$•®[smőŕ&¦4ID˝śňýÍúˇ§])ȤT$ ľŔ§ľ×)…ň”˘Ź™MÖből{¬Ö˙Î9‰łŤ y&Ř6˘QF´áŰľůÎÔ“Î+řső‚ł÷î_6WChenNö~{rWÖĺ[hC6©š”¦5;Óqép ö-HđŁÓ߯pmĆÚgĽÁ•ܨ)°č‰,1Ź^ö\€Kk ÎÖ“m¤Iu sOUęĄôÁ9ÜU9ń6%L@$ßřW ¦ŁW??Ş|ޞą ĺµz“&g`!öŰ·_Žlýa92ý8Pó 7A«±·“ŻŠ™Q9č*Ž‚şYĹX9ňŕ˝.ŃůUy˙”Ďběs^mwĘHÔ°Ú”şŔÎ7z?üëŻŰë5*ŠcëuH rX!Ôm? r„RúnAĄ#ď3]Üy§ŽîŰG·ŃIq|‹™0&±_půkőő¶ô–˛÷RáĄ3|Ŕ.Şu.S|ÔG qmľSÎ8"^ŻÂˇoXc[;c®\ľÂŰߢďŃ™š!E‰ü™6ë¶ĆĘú‘Ť9qŔ‚ić#Ď=Ç~•šłŰëŢú‰Ăoł{#>÷ţ ŐA+mdŘ룺“ä ~Éi=‘Ü­XYĆŘńVÍöU!VP0Ĺ›—ţ{‡Ďßn!¶¶íw›tśßQQĂňˇ^ţu5Dş·«‹ÔA1`’oµĚŁ‹Z˝q_ň<„‘ŕőšYŰĎ›H,¸´”˘kGŠ#ö’=(Ü >P:ˢżeŽ;ćß8]Č™ă# Ô o6‚T/Q¤,đNęľ0źé­ 1ĚŇŰ´ÍŻD.«‹†zÁK)]ß’ |äwi˛`–l‚-©*,Ü}h0âűLT<ńi´Hş/ŻľËń‡ßµ˘D«‹î2{‹¸±oúiőwĂÍÁô•ĽŁ8oĘ%ÍUV‹Â*ÄIţni§Q2/ţ´Ź,›#8Ľ0q¬"Ŕ” &ËţFlUĎ&.Po‘ ˇ6ţ!ľ}'Ż–á8Ń›ÇKî®4ďĆ׸ëĂŚ8"™Ž×CĂ nBŐPăÉľBęh÷o\G6š`‹őäT_°ŁćóVMyđ‰˛ şow°«I uŹ“Ĺ®˙éF[Λ@Ů0ÁŚI¸Ş\źIŮTÓĄB5óS =Ľ¬¤ÓDč %”Ő8CčëÍ_ťŽż…ĺ. WäĺBĚ‘˙‹ÁÉ‹.[ż±źBšÎ#ôNŐŠř ' ‘»— y5“ ŤĺÇkQ3pXXĄ´_L+´ĂŘxb^Ľ'á¨L)!$ D9kMźčş˲ ˛0‚Lib±ý¨żŚ®őÍ'HÚŢâ«2Ô‘†Ë“C ='­±ľl`lżř¶‰Ď’…©oÁT0ąsáÄi8 Űä[ŘŁÝüÍ&)Ú†CeeCWUëݦŞn°ÜĽ‹ŰŢţ¦6ń $óe_®ľŇ¶˛$EŻő׊ŔÚĽĚxŁ“ăéŇ>Áš›\î\řa+ŘJó¦1ËRĚwkîSĆť.mEUÄćܶKp׸ěRöX3˝Ď8ËaIx!hÓęż$Ôű…‘%a75Ä‘aťŇ0ÎjgĎN2ň'OÉŻ€ nÁÄ­Cd•MxÚ®c-őI—şLUVS†k`V•5nšJ‘—˛ŰüĚ<÷žľůÖm ]aMCŰŤÇEŽŢł­ť!ăXÍ5Ýv˝­ë RL*x†iALĹj+ĽŁ\×?–z 8sŔéĐ2±OKôbPéřĹB}Nɲd´„-7#MĚűKţ#S%ßK‹’ˇYÔŁh<Ęqo·}żů뎟Ş!MŃĚłö´7r––ó˛˝M7›3ĺ\âW¬ ;‰÷ó†ěmźpŞ?ţůhöÎq±'šŁVŤźˇáÝú]Ź;­xÍiz4b‚NÓRîCĄËŤ˘ÝŐ~ĐŢń”fÝČíŇŞŻŠÁąĆs €ŕŢ5›Âzđ‰°ąťÍ«Öą;µŐíÝŠ 1íßEPů0QŢ‘LY˘Ŕýžńĺ…-V ŕ°“¦79ŕö?ĆsşŃďɰrčm)€®Zô˝|;ů^Úů9rô3ŇÖ\žećřŞŔµX čw éÜ5çxßjŃB«%›[őS®‹ŮI-˝®va´ó-®x#Xňr|D#'ŤČ@¦[ĽŁÍ“×+!#OŘŁUmđßS#ą+ścĹ,H#ĄŐëŃF*Ţo«®9c— ßgÍç"ŞĆhmăŽë¤T.¸!×\ÇŰ6$"1ńYńÓ3ş«€mľQ%Ć=G޶±‹úo§§vd}śD„vh;é lhŠő×׼7™ăë@©@8ţ#Ţół ¦îH€‡źő›sżĄˇůDâoÖ+Özťi™(”>›ŔŰY *´x¬aťp˝š‘öA­¬—;™’=b%ÂH‹OĘŐĽä´””Q*.ezó÷K‚ux‹•ˇÍKbułc(ÂcąDÄďS]"K>Äéco}+(«źP#>śë¶Z–va¤|í7żV`p¸P8ľ_=1Ş ĎÉQ®řŰxUó»·b2ł÷ĺť ŕÇWó һƙáa°ú´ÁÍÝKô–*5°ß1ÍVüOÁťćiňíźZđ÷$ŰŰ©y¬‘=xtđE¸«ýÉÔŚEť"›}ĺ]Ŕ”+Mč?^AdčkÉë06tŕS¶Gř\'pŽúEáůŽ#Iś_Ĺ:f÷]%ŽĽO7±ÍëÚ.›ĘĽ•żx®!ŇqÝ|0Ş#‹ťĎËÝ_DW]@>éQvČD“ú„”óÚL őŠ /'Ť©Zśői54® rˇpbŃ2†+ůŁxÁÂßqcÝeźFaçl Ý{<ËŚIÖś• Ńçúˇ~›ńĂ„9Ĺ%î‘ÚŽ€cŕf5ń7&!Ůśń=€xţvɢx‘™g8‰[ĺ•9ÖÝŚ(Úl?6˝¶ô©}|çćÎ ťÂ¬'mI§p°˘8DĂĽŮŮhMżVŕmź_Łu^} 䣊X p9_ KQ;ÝĽ˛I6đŐÓ×6ň ˙ѦţŢälZ®iô&ćăh[Á պΪpP_-L›ř÷n`Ř–ĆŻfCë%÷ń•gŤCR(ÉüN¨Ç×XŹ#c٤tBJC‡të÷ŹÎÎufű˛Wŕý`ţĚ {ÇşŞD˘Ę\­lťP˝Ů>(ň6ÂRv¸ĽcóV^[/ť{n3T‘ ĽźőЧjÄj&(ů¬ť7“ÖAq>NvN—aˇ$ůîö3pî’XeYÖĽŻpyĂńýČ;“&(F1öĺ“`ŚĆŻőLÎfdŐ…p!<Ú˛S@f Aű«łP1¦á6Ţ™´‹ÁŢ8mcĽ” wąśFŞŕC_±*äÄJC˘Pž5™öoíA*-8ŕ7Ť†U·oâZýf1H'Ë•ü›±\Í›yŚzÇŕ–rl­n˝–íŞ3 *ČbuĂ}z§G—úľWD˘±ĺúL»Ě(zb˘›‹‰´˙<&‹3ťH‡C]Ó·JxM+±Ü@Lt—:#ýDś2ź×MµŐq Hü¶5ŕĄ*‰Čđ­"úş×cS˝+@éŇéď‘)BÍÄÔ9=ĚLĹv’˙ÂÓv”oI™j…¶Żŕ1˝Ü»Dł"ÎBj®Ľ–Řŕżm!¦Tćĺ<˛f†#b!Ú ˛V–ĄÂ>÷PU­Yě«ÎD»´ý‹jŤGHZˇĹě9č,®Ęćiş‰ĆďĹ1„ŰĂ~–F+ţÉŁdËĂž4ďŻGżN ?`}›:JáÜVś5Ž_»)µ˛çR„ť–p«7{ ŽdlÍrÄŔ®ôе}«-9˘™ő6GH›;‰ęUćj—Â(_±˝Ô0/“¦ľüjlR×#f39lB–?ˇE2(~?$Bf˘ŢŐý“Ž}µřK±f~V ěÚ¦­‘řiK”ő"ä}HzţÁŤkŁć‰ŮÇlRĐ?…ęŘ“‚‡ŕ<'dpŤ„ŘĹËťń ŠqJßiąÂ e âźĹ“2Át×yuÂ0©Eé`ż4lW—^â¬ĂńĐ?¨-@t—Ľ*TÖLҬź;+u˙ p˘„Ę­#»ß¶D,÷ý†ćAÜśRzë1ŰtĹaZuźL4(ÓA›dśrf­ß"ź¤Křôćřčý’d‚¤h6{F ´Ń¬Věh‹­išëU-<=‡ž„ĚC®}$Gm5Ę.DŃkăWĐJ¬ŢPbŁAĐಠv%EiŃSCžź˝×íż©2ÜůvFŮ\Ź&˝ĺ4ö KĘÝ3t/ĘĽSś°aäVÚ¶mŢěş!®s|0)ó-a0,ţ{Đbv€Dô®ÜRč\ġCF;4w4K©‚N&Ć 0ÔčŻíK1”Qq`a.cšĆj÷ţeł!Şí5č†Ä‹Íşž^fnú]#‚>kmňMĹą[Sî6J+ťa›„ŽÄĽí«6'ůś‘Nľu9ŤăŢ*ÚÓ.žř=ô^+ĺábŚ•cĂmIÇ`¦Og(âż^Ř;âčTQ_K¨x¶Ř±Mí©vă„69‚D>‚ąŽ—çsňذ%p@ü‰™µ­R°"ůŁ»e•˛xu7€ŘGŇÔĆaóÍ­pYĎ˙'vm“ab©r~ýí€PBíĄ}ˇ!j’cîţ^‚ńĆŠ(Ý­Ŕi·»:ą2Y/éf(«7|ŐČdµ/6± ¨& o5_cmVj~_Oá€3żC‹_EÔžh¦-›6Żýl±ˇ˘n‹_ó.e6.›şÓĂŻ\ßLve1qµP§ť„SÖ]Ň’y °G&Ĺ-+BĎLŐľí˝ń…OzŤ\Ű{çU6— o/Č|`‹öî p”7_ LZ›/îíµˇrP‘P4ĂfóŽ`ów«oŠç děŚ6WÂ?L.ŚPý®xÔĘńËĘf &űĂ;–ęG, ’ąIj5ŕ»ů‹˙]®JdOí˛…uŃ-ř§üm¸BşÜ°É­Hüô(›“*‘Îť2/ŔäŰöŇMţ» ­ĄÂ ŔđL [qľŇŢÁ€™ š«.öU ŰYőDŹî_ő 2s Đ]pőřb6~MÖŠä«őÁ»śő'wŮ+Î7ř^ŐpćńŚmK%s®Bu@žÝŠ,U+Ąn"´× ŻzŞŁíH˝Ú猭ë‹ÎXE.+—¬ó‘çÓµ•C9¦ŮŰč•ęK‚>Ń9!29®ööN ¦ŞÖť-Ír·KtĄr:J:Ę9*˝çṉ́ĺÓNŔ‡žź˝Ř÷bŻ€»ź×wfâÔ 6 šŘmřĂđ#ňâôĆ­!˘[yű±ŽÚÜkĄO´é´á¬Ă[kCÇB6ŘB¨˙č3®W‚©ŮO(ذđôçBŢ*Pź;ôăA&ĺSŹĄú9Ä|1·ß$0d ś´G?ßÝżťH˘ĺ¤®šÁdĄ”F)Şřltv9h%CtŃš5˛¶ęTë÷Şv·NýŢs &ČQMIś.°€ůN/Ş>–:‡O6;DöĹAŔŹLŠÄůcxV)[˙ ł»ükŇŚÂżĎÉN/9ŕr·ÉÍ‹!ř u1Š1‘»Wľ Qwáł 1˘Ďă­±Cěk^§1ů‹Ôů6™%ĹB ç@D¨Ş)›ł7ě=ď:&]šŠOÜë·7’#±·\;˝Şy;wĆT(­m%F€Ŕ×îKrŕµ:%0ÉT1łÍsůpY{ë6ł Tł>ľ·ŇH`Ř€ěLÁ0Ż Y«(G]Ěhß!¤źÓ8ń˝·ö€GÎĐě˙şHŮ×ß9­p;čńź°Ě·agü{…fłI@Âc3ŮÖŞpřĽ®y=ĐĐęŕI ›G›łNUÖOSĂĂĚLÉšţJ›Rx¦=Ł™>ŠPÚˇý“0Ą,č<ňŚŰMĆE:ç§JŮ-źüřt`š»ú)zUŻú÷=űÚţß_"ă7Ie$·M´QŻť Q–'.öŃń¨ľ>6b® ^ˇî”;íłßzI^âÜs%ßŃĚ ‹ő+j^[Ń•› ŘBŠ+Ţű°=‘Tňm0Ć[®ęCĎ/á| îťąüßB@:Ç*˝Ťv·ţ@'0Ćw™črż’ÄŁńIß­ŕŚ-ĹŽ «śxFcˇ5¶Đc›^q‹ÂŢcb©÷Epcł?88a+]ťQ” [vÚßÜC2 ˇÍU–}Áw­¨yÖaśźšż>ÔŞ ÇgÓ9Ď~8m˛Cđ|üčĐ!„Ý®IY ] ˘µÓěix†eŇüHzy®üźÉXÁ™ĎĄ[Ž`ÁŢ ńüNwúďˇ"Ľá3íE#䛚B iž@ÇtV:ÎX2BöţYąëجÍóȤóÎ’˝©v´¸^ŕFU˘X:IX"et­Î¶›ĄlÁÎ’•łg?ŁYŢĹÖJ5ÁUşą %a-Ź>Ą’ś:Ă… ÉnűgŻVĘŠ6ΔäQ S7űfAďŔÝţF°ÄĘRC€ÎCÄ[Î"_ĺ–Kě"¦q%\ajDŢĐBAȡ84ĘÚ}Ě[ĽĹ[űčňnáşZ_R‘#;xkŞ„¦ôéÉw¨LŘYkAý»đ={~Ń‚8%ĘÎĹ+˙NÔ‰>ĂŹţy·?ŁŇäfÜ÷0ÄÉÇ t0\>߬ţě\fN$vLścăRn([ŇcH|h|Uőł÷ŮyMޤó(Ç;ëč#'› ĺ¦j†­pŔţŰfŹ;*…=đt}čě‡B*TDÁÇaęc×îUť»ĽL5JÖ±‡k¨_®HĺËÚd2ëŚ0k)g2‰1x’z¨č0¤`WqđţvGu%]«-FŤtĎĂůećt”ú´(wđrö4šÇßťŐłsÔ>Zˇ}”Ü1˛4t&,Hĺ’¤L€6„zőÔ^čY’ójE·˝PÇa¸7Nwîmc2ĺÔyC§'#˝R‚”¦JŤó,·PăĐÉš\QĎzyřSľÁWjX˛{Év™C,ť#“*+¦ÓWb”A­9ËŘ:˙¦.žˇŚń$Éň3×ü`ŹqĆ?~ǢGî®Á$uůX’f!­śŔ0‡đYC‘ęš{·¬ÇŘ”n"™ ©qg ď»`h&A= \ ë{“ÇÄoA ú‚ڎĴaű\kh*a{”ń >bh–•ítg.`ŤÓSTY擉B-U˙ëşH®ëŽ-˝ß‹ňň,W‚TđYbˇ›Oő¦7Ş^¦Nň´ÂD5 ,ąďFsCŻ˝7‡Qá­w_řţ5&‹/É ŰÝ|JAzŇÓŃÓ¤”%:>ŽVXő.u†‚•ň8Đ„#¸I©Cv5 ÖčHrEŐ«G§Â¬‹`s'3ŰO'ŤĹ×ó îxVßĚEÝö^4WŢ?n)¦Ąö‰Fş±~đňT3SňÇŞ zčŕĘX ć=0ţö–ťďő%_:÷ĺî°„gű[°ÁWX¦áJ5]‡;¨ľ×Čg#Łśç´ńI¤Ăł#›Úg4‘’±ŚGě9ČsGŤéÚŹŞ "ăbÚ |…SĘ*Á^#6U5Ą«zYc×”O’»W(ZÚ¬Đ;˝Z˝Şź)$/@ MÚ2źţÎîÓŚú“ĆŤ&;dăő2Ť}q€äˇł}g®IźŕŞE?lů:Nn[ĹĆ$c¬ lJÇ”_M03…-I•rY÷nú,Ś1-QóIŃý4äs}ŞŁšTpžĽ ýĎ(Á)?  Z‚±µY4ÎFöZÔ¨řĄę’ÝŁˇfč'ÍĘ5mžŘ6hm<ý»Ş`‰Ľµ =%yr˛Ř5”@A«/iĹrhí BnA-|řN!-–˛ő˝Ů‘LŽ1Ęx@‚+¨–L€)›aCLl^íoÔ–6fzzšgýó|  Ak /ÁżšgŇľöWĐNYıů©4 ´W|Ă )Zúáµĺä{ůç»#,‘”˙µQ^Ň­÷˛§L[§ý†Aňtvy‰]{ßUĎQÇ]Çäô;÷±hŔµ–SČKŽ>śÄLÓkNDŠŁŽxĽŢgď>¶LWGÍ:ň7“üŇ.U0h ]…z#i%ŘŔ΀ĐÓ­BCűűU|oŇ C•ŃŘť8:;´âq䀒kÂqY÷¤uŔS‰Ą.„>Č{ďŤĎ%ăţs÷~ĎŮĆuqĘSLD‘ż$=˘0{šc;Ó&xýmĺLw k Š™uşžË¬PußCÄ÷•i„˙†P,8ă?ŕŹZ†™˝.« ·0Ť¨^K®ĺórF‘\äňÜŢ řXgŕĎźŹĎI ü«˝ĺ!ćÉfŃĘ "§˙AŔm|¶ŔŠ´fĹ”@^”řőŘľA\¨ÄUĆŞlS¸c)GĂć_{řąÜhkµ6 §ľ#b“ fAśŐ“Š5®ź˝0/Đ VëhżNśÜDÖ…/»Su4+··Â´NŻĆ’=Č|}ľsz k+ FZÜRÜäŰ:Ó@¬8ęoµa%ÝéŰN#á˙d˛’+Ěô•˙qÖ2o˙žö‘‰y'ARC9Qlv‘;sś‡÷MbžęŁýDăŔ2ňŢ)Úp-<÷s.Ý  Ç`’ŐwµŻÖ~•łkÁé>ľ<#wV¬ âÜŔ‡ýŮ'—Í÷%ĘšdĘ&Â]łć'„…›É¬ëëéŮŢŹ:) X?¤Q lęW'Ú:ŘšÎ|ń e•Áąn¶Ą|A’oĄ0NĐc˝)ş*r­†Š (“_MHä[Ő,vŠ2ľ+Čž»ă/˙ČN(zdLᶦˇAĹĎb)Ű€ë¶úméŹ9Ŕ‘˙;´n ±Éˇ°` }ÁÖÉŕnßţţÚ”¬Öiߪŀö¤ÁX2Č#ů¤ y; ŰđašdI=$Ţh .ż¬Ő.ăăÍŽ‡ĽÍCĐű,&Lrůóx°ž6u› Ś Ť“ř—­-CGWžpŃĚ…çÎs›m¶•÷^ŻU2ę5ľ‘uć3ʶd,Ůixµ23l~S.l,o%%$' Đz;b±tÜH˝ôvu§ŮÂş!†‘ô<¦C-šą<)ÚJ‹˛Š` ě§ô3{ô,gk%„.Xz…•ľSÉ ľh÷Ô='·†u@*ň¤]'H–ÄŤ}¤ÇĆđĚ4pM9°¤śXč<Ňe@«ó|fĹ ¬0óa1•”αBĆÉf„Łeď“|Jcv´•ŇŔęĄŕuy<Řʨ€ĹdFßaT|5/ݨ`'‡OfGcLćrŇ;ŹHhď绂4¦±‰IŰňd‰ń€>Źyý±×Y.8ď˙ˇwȢž‚UĺüŰcKLIŁ\ˇ?f3VNż7Ďź/pM]ŕ‚ĐöPa7“Śš€^|î8 Ľ]PĐšV˝ţ>A6×q®JĹ.ĘŤěo|wĘ Ř·ń-}ĘcIô'ťţ”TAŻ7s†¨óít)Á%ěcpÍËjó@ÝćŰÚt$hŁ4t]S‰$źĂŇÝ.…w®˘Ů•«Îţ@iG V0yă8mÝMžČŃĽNËzI !pDăšjŚX:H`rĆ®IµŠÖ˘.?—­Ü:łMłü‹2Ďą^ÍuÉä[;k÷NéűĂwŇwá+ééĐ"JÓd›ŕSţë|jTJŃČigj;‘ż’&@ç`ľ V19J¶`• A¶Ô*"ĄťDŃů~`yö{Ä/A”E H©»†D°ŽóE Ř’týň˙ŐśťŚ…8 Ż=-d4ţ¸:ŐB4wŇ”¶RŠÝý‡Ş­óňG‡­3cЇ}ĂűU-NVąŘź´pß‚ČXěáĘ'/ťűYÔ|”$$ž=ń’—8ÄăĘ»C_Řr¦B±>šwž1$§-ł…Ć2m+@ľ»ÉĎ8wE’Wd¶Ą8%“~ŚŃ˝v×bň75}›t‡¸IJőĂíó}˛k<‚ÍŤ,ŤßKżi«ŐŢŃő«@jD‹O5Ó‰"­‰Ś’”GÖAhíŠ{-%6©«,Váâö˘(küKëG¦÷B'ĺFagî#ć$D÷É‹ş¶n1núOV”ĺOôió×”ŘMrţ: ęÖRä˝ß•G»áÇ.đm+ĂCq=şłŕ5ęôş6•tĘw";%`„|Đ:<¦.ţŤ”ĚGÚ3ř;łĆAćęug޶ĺ±jESŃr#]…TĆTŘÎîđ© ¦n‰Ü9E y&ąŢ•éĚO3Wú*úyÔ 2ĄŔ§pO(˙OŔI–칆9ëH?™˛252y€‹2Ť,ܧżušĎĽëP÷¸¦ZŃ7>üµÝ™ę Ü+E¨‡Ů&Ü<„ÄáV~ÇÇ~K<ŘmĚ9ëT4Tgś3}ŹMLYío‚bŰC/ĎŞú«H7ݰź úŰ/dbŐ›ŕ´o »źAŃ ŘŃËŚ©ZD^ŽÜn+]CŁĐBi°ď*ý9gĹĆlŠi ?#·§v)™2DŠ0˙*4Ńŕ8‹ŘÝO<ún~CbŃmżšF Fôë^­?’ŠĘŘFnuŢ—°M­Vżsß»Ť ń(„…‰Yâ!…FŘZjŠG$Ů{{kNíĆbŹřÖ†~öI\ę±öXúlýKř+EüµĐ]ŤNŠ,¸.oŘ •+ăľ´ůSŞÚßW!ŕ')^…€ű何ôË˙łsů„‚YÁ…ćmvč‹t²,Őš°ůBň);°łë‰“l––µVvCcĘVE’5Öů˙ď@ «Ř>¦±C\â-·T4#LëU1Ą—n¨ÇŹJěÇßş@b0ĐxhŽ úź…#yéjŇ".ä5ˇčęŚ'~aOĺőÚ/¸íz^i\a˝dEá†aĄ·ßłfp7„ęâ¬ö°ŃŇWlYś¤Ďţ‰"%°É38”X‘*µŕ xE˘ńKőńęĘÖŽCW®šX(˙©nÜcÎUÔ†jôŢéxAbţĘÉJóŹXQšmö,`¸ Âî¦%ĘnŹáç HąŐE;a…™›(ł“ÜTa–·»¶×¶DiőđG8KsĘmJL˝Ý^Qőš·g#][Ą[©“-<‘îËľŢTĽô§‹ŠĹfS'*š™>0p"´A YHăĎÔQŚ»„.ĐŘ9[ć7ĹbôŔ“]đ‰†¶“F¸Ëx‡ż˛^Ą“Ĺ'/x™?Ła·Xý»‡ľf†—ŕNgß§Â\Żwz¦”^äú·$™Ţ?Ňë}ë–%Ű\„H¸ŕĆŹşžđçpĐ—‘´Žö·źŞ3zMΔÄN˘/ogYŮ’z-če{ćE¤ˇ$óÍE‰Ägnpőyu$žK<¬,ĄŚHśxch|xî5ů23 Ilţ6 e!őňIjyxąÚńv#†‹•éĂĹ#Zp¬2óűËýű¤á¶°wgJ˝·<[eéزÓŘŐkކő‡…lÂŢ©Y༺™Âm?â=ĆĂ:µLň˝ż7^{( ľĄér}Ţ%vţe?ĄÂ×x”˛zc¬O•9á–°cnx¬ž¦°~Pźd–€|ăČYŽúę°ŻZsdŕŘ‹šŃ†?zíG©]îJµ˝ź$-ńdCřň3TPrľ[™Olp¨Čgह>aţSé¶imüłŁ]|-NŇ5*"Bsµ*}[˘źeÝJ`KM´…v3j“Űţč ,=¦dgyęď9`”“‘©sű„ «“WUřb•;„R1˛¨ řů«÷/N;[–q>bog '{ŮÓ|g‘[Lk˝dđúf>ćÇŃî ˇzöv,h&eśŕ.H€Fć”ü  ŞşŤÇV݇˝r·ˇ<¦'r|ÔvÓI{D?ŽâŮ•÷7ÖîX Ep˛<ÇüŻé?šŚ¬ęVŠ źôKŤ€Â`kk#ŃŰ(IsËĄ‡°zs×ŰąúäµĆęrřśox8Vľë^Ş, †˝” [·`x„]”˘G°' ó*e~UlP|o»ÇöńRI_íÍ÷y¦Ĺ%–®_Ů^«ŻJDLŁrj–źAĺ/„˘Ő–äÉž‘–îĐhŤ* fh¶sö©«yůHŔ¨ř3łˇ˛+ÁĆ˙«©Jää´ôQÔ)ň€÷ S©&ĺŻňÇIňl%=`ěżďôŻ*4Buľwĺ Ú÷2.Q1Ş´aÁű Mň€ ß-ĄŇ0ćÜq4Ąn੪žBAÉo÷‘Ďţ_•:R¬wuI7ĽŹ˝€H ý/Xtó7Š,ßkţâVĹöx$°ŤĽ—RŢhIžav$Ŕa/U\zbÓŔnx!ç×bßűŰÔđüctĂ,˘â3™Ä—C) v4ŘrŽ­ ČůöZĺ¨ ÝFîýÉCĐ«”đ݆™L.¶Đ¶mË…»QŹÖ€kËňŁ$Ő»(˘‰w¶ÎI[ż=&Íŕ^ÄMťűz<Š€˛çđuó"Š'éß>·™‰,¦śČTJ66Ľ1%Ć 5m÷%ýó ŞgÂľ盚¨Ąĺ8=Ő•Bógwx%Lžkí)ÄĂôŃňtŽNŤŢÓż.ŔMr+Řc-‡°^ŔÄ'‹Ś.ŠIBx$§&‚çâĂ(`Ű>›.ČŘR^ ¨ÓrňŁŔÓ >*{_ ­]ˇIĺ<řň¶‚ýw)Ru¬„IŔł$ćMnz•ĺ—3n$Č'¤_ĚŞä‰M®Ă¬˙7Ĺ óë—ŚFö`m©ŚŕůI/ĚţJ?櫱š§®ŃtÓĎ*×8ˇ“fKÄq÷„î$ő¤µůüDG7—¤»p$îµíµobĆ÷¶ę©š Ě9ůčj ¶x÷żŔž8L|—*đŤľ-™JŰ®ÚZ*ĽI˛­d˙řĘ r…đCH{ŮpĎj’q¨ÜČ˝{‡…K‹äúöžTăµnôm4żČ9AО@ś “¸’¸=Aő\YÎŔîQRłß3`oňŚBG+#·2î×C "§ú¦TŚůÇJt÷đĺ(<ÔD}ňeąWo6ą`N•DUaAŕ·Ä삊˝V$°ńřÓŕÓ  ?óôVŞŘó“„,ˇ–tĽ€×ŠG4A“nĎšĄĆ¬‡7ů± łSíjÚô˝ňČ–Ó ćMFfy}.”ŞđrŃ"ÝO2×–…™,|&Ú^mP:®% Ö#o0Q Iźc)XćÓh"ý=Ć'-˝řźnRŠB—'§Łë˙P‡s'”ŠvucÓŁÁ L=AŻhśc§(fŔřőëÉ€‘á®8’µ9fD@ď÷Ćw"Űš¬›çŁWâK#•ĚÝĄµ¸sá¸ĐŞIAiX[ÂŻžĚśű§â›»›Y @0"ęÉ:(6xQń”kMÁÝ ŰqTŮÍf¨Ź¦éW#;QŽ×ń2#â6é[Ůř ߌ$éĹ!‰őůÍD¶ý_DäŇ5jqr-.´4[ČĐĎĘZ.Ď7Ď+ë˙nżúŮZ’:GĄ\ż­h7.=nc]J­§¶–~eý –=l†ÇáÝA)vž ßęěNÁsŮÉ(†Z CßÔűÄ8Äçž~{ź=őÖL†Ëśc‰QHFâ.zÇ‘8¨éÇKŐ‹,ŃŮjĺ(?>Ş/Y7¸)ržŞą Ź ÷©đtI`MÝyĐXC©5Ř:ĐŞ™żŔqu˛[N^ţQPă4Ç3•@ăÔ–U49˙Ţmtđm ¤×ý!&C峦ˇną«‹FΗf`qcýqĹDIŢŕ­¬mşÓßÜô«¨ű2OăĐ•0ďaDÔGŰőř+ 2Tp]ۨý« ż¸27D—,^ŹřR´Í¦zt%ŇíftČŔé\źýş?(äM&ô~Ą^ĹP!L'ŻpjĘáŠU…;'Ô×*ŃŁÝŠ>_—płVĚÍ7yšf™ ˝ŚTÇcîr¬ĎÓÁýŃśä˙ĄQ…–żR›â#ťß†R";ŃŽ‚V›¤M húęňÇIŠĐŁŹŚ^Çţ“ĐqŰeađů›ŠŽ&ř‘?Üč_Q®*ň- «x‚+W暣]NO­Źtź#áŕíń$ŢŕăFĚ@uśiČ›áv.ě>27jCc+jh’šnź¨&ę śS:kçÇfÇ6\Púš@ëd×Qin&ó—đ/4mvňťiŔ™V‘‰*őd™«hěĄcŇŞŹÝ'–Ôě*@¦BťQ8-ĆÇŻó©%Ň~ăŁNQËŻstzÓŕk\_aäkÍC \7 f¶3žT{ţŻ)Ü5EŠŰŽĎ®ťQŻĎ~ÂŻSgĐ3tłT?öőÎz@ФHWŃ ÍQň÷«ö`팏6-eĺôč»,ćS|FÖ%Őę™h]c†Í›ßbä9rĎ‹RQn: ”LŕÂD>fĽë®ţ…úď0íř ’tHÇM^Z»ZO‹ťD‚Îť’ŠqÜ2#bÁ5e•{őľÉU¨ôćľpŃâ/›7ˇCą(jr÷›´Â5ÇŰ-ç/–ěumĄI1‡Eť¸DC¦QÝ--ĂÓPc¦cˇ|/Z±Ő“÷Ż… µř‰ŕň¶Ďj—áđĘ­íë_$[3>[Łtµ™ß~ĺ>óŰĺů¬8Ç_ăcž;wŚŁěłÜ~†aŔż›tę–OݰÖÚ.„ľ«śĐŔ ěH3ú”ËK ˝TĆU=cđ_n@!§‹«|¨ł ňCÁbĂĽŻš1Z µžC°jźUĂ |^˙PďB}ąŰ媣Ía °j\˝Â‰ŻC‘2ŕ˝.řH,QĹHŃÓ5l±÷o´Ď÷k:¬·<Ëć«9ľ[©Ô#N¤N±0Ď=7ň˛ů»ůtq˘×ř,¶K]”v(˛4üv¨˛{˘[Í@ü 3dűÚýU™ts¨őűPFůôZ;P‚˛Ú¤ďbó·DvVK$3† ´IěNj|ß+Ę\—€Ü´ ä} Ĺő! ףů_© kwéeô‡&”ě\wĽ÷Ý1 ű_'˝ö©Ş—»‘ŠŃCŇsӛÎăV¶×ŤńnQ@)ďkđÓ6ÖâH^Śv.¦ $-Ž4y"Ş<#(<°D)Ô˛|\ĚŠîĆÁD=@éh;L–g“x·őüQ!ţËZÚâ6n$gC]{ń/:áJ; Űľ˙Ũ›°rť*ÝńüxŐ«sĄ9ďJĄY­XT[F)“Č/˙c@×ö8fO 5—XŘŞĽ@-Äçh‹ę2yyRážĘ˛ýŇS]n0’‡ąĂň˝`!äîĹQ)©‰‡wÂ$es$pZ&›ÂRKŇ1nÍń.B`¤Łş˛bQmGé˘Qµ`Ť†nDÖ1mç&ďzŠçôu–ť’Ú]E¶’Ižk$J:3ä·ş4˛¤f^>Ţ溢»Ň0l°XÚ҆˘XhU¦|­€µ¦‡}˛8ňăf©3fëßDËߨZQŞl…u„şg\Ąí5*Ś‹h©čőžĚÝZq€ŠsĆűÁđÄígŐÔ(„ŢÔpzź_máç…"OVŮć]ŕó–`‰ÂÔŞlŘěBWx)±~AYk Rާ»Ť˛PH­<ÜŇmŰÓCR#çA đ Čt$Ľ­7Ŕ,ů=qbS“ćśő_![˝WáčcúÚĽ|Y† éK+XóčY ¨ßn¨î'«‚ś,7Ł–őZacÂě<0źŇ!”_`îlµjďŇăžrOť}‹(jé­¬śNŰđi¦ĽÝtă†űîM{×NýаoÎ`‡=/ŕÂŽ˝@ľ,_H‚çé€Ô Äf>lîCW›T^ÔŚÖ—±6†@ÄTS›VŹg焵`Łďő…&y„¦„¶6Ňţ§'ŔGkkžČ†ć)ÇÉI¨×Ö®ÎMÄ·%ó;+N5·¤\9¸ŕîËŃ8k)űţQ ä©ŃH@€-`Î6ÁÔg•5(ٵĂoź&¬Ý¦ůţýZ3Vľ#‡ANŠŔE@Űü,ŰLyS†í_ 9MśŘĚâĘże$¡śĂôšŻ‚ř€Ęęe ¸EčDő×p”Ú‚ü:6qH)n -#i Ó€¦w©č.Ń?۲ѦţZťŹń7ÍŽKŽ“:ĄŽk>8?ťÇĺ°¬”L©u"‡¬ätPů¦­8¸SĄ !#’Ĺu¸QŠ}MśÂ9ަ&® ŞxłK.‚]­ä°™}…ŇA®ÇßÇ äémôédpݰĆ)nŽĄµ"BăĹđ"¦o+÷™̉×ɦÖGĆ YŹŕ·ŕççĐĐŞnbLßłÉ ë˝!č÷«ŢéÝWçF˝ZéËŮ~“:©Ź´;$aId=âÓ[ąx8_Đ4¦jÄřó&éQ•WÉ{’üąß䌅ËËÚoŔ<°_źÜO–ő´;=Żş-}ó1Ť¸î`foČŚiljě3Ë µeóŚŘ’řWąç…6Ią|ťĆˇ6‘O^gŁg˝ĎµE¬E4Ců¨c˘´ę_z Ő¶‰/ÁďȨÎúWőEÖ8vŰE¸†—7¦č@rŤçŰĂ‘®7\µ‹Řń !°ńůÁjHýlY±t,dć]IANůaŹn YůşÚ ‡čż#FË>I„Đ@?ť-,†c8qŮęM5XXÍ'fG%A)ě: śLx°jqĺXńwÍ7Ăzß[şLHÄIUŤŇrŁĂ¬Ra¦™ÍÂńŠ5é\ů*˛JçŽ9Ŕ[łsŹX3Úľ*XTĚęłH2U Áń˙ć'Ë‘´”aݦ†ŞBś¦M*Eţ=ú=kˇ´r|ËćZ‘ŤŞDza9«:c€” -Í%ĘĹ .şŁć Ůń»Úżg>Ś#AÔőX­-z ®eGNŘzr2őťă‚ŤŢ´»+ZéÂÂuX‡řýU!üÉOŢŤĐ3Ý`Šr|7ČUbýÉąö‡Tă ~s$ÓrţŤ\Ąúއ3HĚ"˙Đ<ŽŐm©Hś°‹´¬¤±›KćĐ?Ţđ!9EA·ÓĄ-.xv‹Š"5/Jý!řĹOýîŐ*^'—’ĽK ř%7ý§vŠ‰Â W‡ Ŕß/ăĹ <4"k(őţáĽzN"`?I©’ÝGîb—̸F ĆŞ›fTŤđ˝”Ă6ŘÁHD’—ĹîŽ ?wNٶť¤ô·őÔWÍ7˘¤h–Ň`©ä^†Âw-"ť´îîÔ‘áA÷WšÂM[·I Zµ|ÖŻ6X5ţ„&Ć©oApo¸vľÄmâ§tÔńś “€Ďş„ĎČ縎$Ľ=©ĎPďWc…TY@]e3 üEěďŹ!ý¬!Gp+ĎŚŠ7„«ŁŮň˙ŕđD‡8Řáăú,É^ý žĆEs8ňĽH{˛˘ŚtÓ~ LŰŞf8G8ň%řčjĄ¦“NńjSÔDÉßţ}Z©šsĎa9L§ŃY $ ŢĄq§0SĎ…>€9 ­ă]Öîë癿z+ąźô–C`ź·Rµ'dť¸cšŻnďKâ»Z±Ü꩜®<ú±‹Ěމx•Sö®Ź? śŞ†)MĂk[>ˇ˘ňťÔOěîĉʅ_ó’hI–ŚŐd,k=-”¤Řčáţ˝˘’©‘&0tçĆĺśC¬Đ>ô­—d\đIéę­†t~Čsô`Fúß=Bu,ɸ÷#z˙ٱ9±|TťĄ­ľZP«±˛í¤Ú^Š×o‰sçQ*^®GHÄurEŰR‚rš=ĚÎ6?·ťöĐŢl¨;áxŃž°Fu(x߆›3AŮçúy"a5 -RÓ5z sźQwÂÁµâ€’* Ĺ)1‡:÷¨[(‡5řżk ¦˛·{ţÚaĺÁ¬?JʤjQÖ¤ ©łYô@XÚGIYÖ|=–%sôxË–Ç ÜČaz–¶ň×4GîŮf=ňy 䞆˙2ÚĽ nÂvÝýA!ë6Şr@âHÔÎßůc«ö2Ź7Gân qÔ?»řM5 môŮ@vĺŰE´’ţ …@}¬NP´ß*Ęăéšö¤ýÉ6Žâ‡Ú|| uqĄ·BôBwÍEďDŠ.zShŕ¬é€\9ł­á=řŁîc'™-¬W ~ — H=¨J OƦAčű¨Ş-ýŇw űčgT?6}ę˙†ł˙ŔýeoO*—Ăż—^=i(‚lÂŢz¦¤q…ăź(ŁÄ©ć|s6ÔÍ&‚B˛ÝĐ÷ôÜÝ ̄›{XYsŹvÍNK3Zű."­ŮşđGđ‹ţOW쪛›÷<hU&˝OBLnRŰ{€ťŘ¬”D©*™ŞÎa–ÎÂ)Ą4>)VĆÍč#= ,2niŮ9ŐNŮM+üŁŘw]:-ˇĄ ni%^©5…#Ř‚ ÇŕDJ@ŤŢňÚ“ťyó&Ç‘á$„"ş)* G+奟,›· żg„{m˝¸ĄĄ§Wz IM<ť6ëţ}±7^un•Ô-§}mĺ×­¬*ę#)0—zyvdůx}Ęšx‚ŕhöÂ]‡„‹đ§ĺ gťq†Ű(ŹUÁGKÄ©›č»ăĎAłPőyÂH¨źÓfuazřPJ2g)ä·Ycäĺ#ń«ËýđjXz3dEĂ7‘­ţň†ÖdY]AűdŘĂ€ńi“řá¬,“—;čfäx;˙Â.éŇM'_/` 9«$8Â…;ězŕ ţscu‚cźÖĘö”Tx@5ŕ[,B­Ž®ďĎă“q–Q?Mľ‰»$G¤X×Ö(ĽµĘ™ őłay„Ăú8&$ÉW:×ńźů´¤ŢövaÜÉÓ~ /‘ţ‘˙CdÎîľÖUµF·vçѰś V !H)¬ë8%–{!ľďW4_Ô™ž“‚˝AťT€±"äĹ^f@H'ń¦OÇlČ6źĆUA™ÎĽ¶ö¸ą \xŃĹD…ż×÷"ďďđCą}߇ŕ]˙ö'.ÉtßÄ5ü+39Ý+†‰;pńÇÎv8903-đ’őĺĺđ4]ç(/_{µZĂĐNĘ—ëÁŘľ©(8őűŻ[’ÍŃ?3ŕű m|Żűc4ź§WeǦiĐ|J­Á$Hm•ˇß¸aYÜ—8OZ€…RÖ{Mşndž1¨7>Ćb·Ńĺ%úhÉŕäb=ćdvd˙d¨ä÷şPĎř—’¨HÄŹ2#Bór >ň‹?7>Ř3ÔźFŃR[¨lÂĚK5·XWb*Q{AÓŕ!„HŁ#*Ń«©Čý‘WČűE,ľ0}1_›ˇŐ§$p ââüň7¶}őń/KłW_lHžá77¸A‰†ÂžÚ{Íx2˛‡$(ZůEf€'ş{mĺ¶"-]}ő&»Ěë7ťÄ„höY|ŮšY۔޻&µ˘C ’/[]~ň0QDľ®®‚nţиN߲(uřÔý ŕ‚Źˇ0KÎř{jUřu´SłnaŃ? {v4Ů‘u‰úśĎČĎ’Ć‘uŻ‘Y±ĚI»çs?]QŽR ţi!LŤNşpűĹrą¶ŕ•:ąeŐ÷űĚő¬P*‡ Ä)¶śćأ͆ÁŚp«ßŰK–ÝÓmµ;Ě,-őĹ,Î)M6]ÇNDÔ˝µńŮ1ö1J§Ë)Š}ŰČęTÚ걎ßaß‹śç¶Î96•`ł;_<÷?ţ^Íú Ťź--a«7˛ś‡! jŐ:\6źBó×Ď;ţfRU«bµ‘ÁĐĺ®]ôL"đwRôî4ĘEOwÔÉżjçÄĺÁ˘aR9ˇBÜéJž°ZTĘb Ŕ!3KOCB3äŚüăQTĺžŔóćÜ‚}`<Ő+çŃ— Čwô ;ű'L±ˇ ·%UW$Óučťrs ť ©¸4É@ł˛“vr ¶śJú¶§:ôoü&mş•˝ÇqşŃvá&>ülÉü!O% ü•hĽ”¬ŐďRwŚLˇ˘ř;µ1ÓśI–Ä-§ó 7-A:XkKé¸ŘÚ?ĽsÇ ˇ±áňĺüľ"&"„ĄÂňu"ndż(/ŚdH_DrŃŰŰÍ˙F\A~Ó˝Hmspż^<7ë“3N°.ÍĐÂHD¤dđ¤SŽÖS~íÄĽ ‹RoÉÁ-¤y‡“—›8Ű`źh“™Tl «K÷8’••Çď%IcÜ‘jxáÉć$¬ęx(¦ eS0CbpѰT'‚áew1űşżŮGŕť˝‰ RÝÝ5Qî&a˘H$ żŮÉBăU¦Vţüőܑ앧áśÇ®«‡ÔSĂMvr&鉾ÉÁE'áł±9 ŻIý–´mß·ZbęŞo. „{jŰž\ôÉľEŚ}č ËŹ÷B<Č"]•“ő)Á$Ú‹ąmÓ>2§ÍߥČĚżYŃ7ßGŚĆgb/ÚŐăÄđ~G°&–#ÉËG]v¦ s ś ć~Ŕ>”Pl„t… ĘŠ3ŇžE.-ÎÉÁ÷ű\řŠfü Ř‹7 tĐÁů_˝µéĄ@jY=;ĚŤ”ëú. pJĺ*u®¤ĺb2śĚ{Yk»™rχűö×VčÍvôźQč u—čÍŞô+_lÖe9 čÚTáďěk)j— ş¦ázü‰i«őro!a4¸SuDš¶?\ ą8VXš¶®Ë±p‚rI-)~c!¦úg ćňą‘¬ţŰéśĎŃ.ÁßAdŘž¨ÓiÖQ›¦ŽU>Ň>»ŔĹË(˘„ ňu·O˙A@~ڎ{°K¨Wöş˝«U2Ĺ+xLÓ™ežŻĆ}7Ś [É™2ŰË9ŰÓlĄÜS¤Čm91űŤ›%ŇŽZţ¦Š”˝üćřJWÝÂd0¶S&qĄ›CĘ҇»ALŔŰöC<^~ŢT—–™'˛7ő^ v9l:ő±žđ' R_ŔĽL‡S7ˇFüŤMMá*"ƛγ3÷–¬~Ă*’š[ţaŁîlaú ôÚśJŇú&Q˛„haÖkvâ‘UĺbÚ„LĆÚyćZ.B¸Ńfň˝÷WŽš‘ßD.0k[ZݦS ŤěcTo҆-·‚Dd¸6“ŮĆ—ÁˇŠÂ®ŔĂň=ł-čXÁAZŢTZńĽ@ÄĘfÉ?,ąVĐÇJuŽľ¤ö÷ŤĚ¸ Ň¦vĆĹWĄ‹@(ćK)á¶h-YBk»‰s¬“BL˛žU°†’QaŽW ;Á­^áj*śSÚPóAŢň­ăšrŞFŔqu§• " '¤?˘Ŕçř¦¤ä)‹ň‡jßĎz#DłđpëĚCJOÁ[~ĆJQV– |q…ÜÎ]´ô‘Á6ď4—U†ÖĘSŔˇúŕ^{ÖUĂ3cůüŠšőî\´×U~{%a>Hç nóáĘfőnDM‘M›3¸Ą€ŻĎ¨Ś˘R_Ślya3-ý2S5Šé–>äËĺt[x•†¸ńJÝ7•ś|&hŞ˝ ;ËzçYZmĄ§ün‚ÄäąŃS»¸MĹ‚šMŰz ®|ő.›ÓńBBÔ)HN+;¬š”ĐŞţřŞ·\äij5`Ü⯳äC 6\)*{;C´*HVŇÚŔĘ©ęϵeOPޤiy¸G„‘ ą@y)ă ×*… w qą ̤)ţdą¨áëţÁ<·ľw'jŃ4oĆĹeš˝#Rç»_őÚč“őŢřČ'ßPe棝,lěě<Ą¦”.g8šăýĄßë¨Ü ż˙Ç‘Ô$`פôŠ€GŁ*ţcŠřéVÖ3]{¨Ş sg"ŤTTF%fbOfä—ĹčlH \brRĐżź)&ă#ŕŮŃ'>…HÂO…`3KJ@Şí†ÁRűUó_Y2}˙ę}űJ ?HuhđîšMT°»H-Ďc~—zś˙﫣Żń„6ŚS `F ż‘GîşŮř=EöË[Ą|JcTx-_ÇQź%”gPŻ®<] }géÖ|é›ĺ»ďşăĘ˙Š›”kGa¤Ď '[=ńw4źÜŰ˙\»&VđL248Ľ´ĺqé˙¶Iđô1–…Í€\Óóčg®X7’î_ř]Ľ‹kĽ™Ď€(˙EČŮcµ˘ĎV1±ˇŁ‡{™8Ľ›woʨKpxŞ7±˙eS¬ÜţŃpÄáQ°ć&v¸!šc2}Gó ŃčÚőRŐ~ť-ĆŹ„jĂ´ÍÔ${nRă•yµÓlVJm"l "Ô·¤ź3ě5ŁŚ˝í!@Ł™[ŔMřÉ ˙űn´ÖÜřm ÜŐ‘ídôüŤG+˘śŰÁ‡'mś^^ŔŇpú5ىĐu™y6ĂUx&.‡f‹F›~ …Łi8ăw!k‹—ŤŤ éMW›‘çŔ?˛¶ŤÎ€‹‡6Ź˝¶ _,zĂÇVíÝ*Ťöl%‰‰`JO„ ş´I¨.®^ëůĐzB¸¤¨¦!®A˝X˙)ôF…ľ‰BS" á1řâţń¬>ÉĆ)[./˘cóŤ~ÂĎăjµÎ~7/}čw«W|%mnżśĆNΞű‘?ŹŇLkŢF3™-ö,ýc ȧg4TµTş´}Ş%r^ş(úS®ŃIą>‚ŽXW0"¶•üąî&;W?|!q ßé ‘ŃŚ·ˇÄ'¤lR09S?űo‘™‘ńp#üOI*Łčđ Í¢ü€kź}ÄŤ{îÉ@g‡ű—t°ÔŇI,„Ă7’a3"KĚ!ŐäfV÷zv¨îaĂ­#Š|“4`§î ŘčŮhŮü—° ¸>ǸFéˇ0 °KIb/¶v̸ęŐútžä…Vő]“™žh )}Ë ¦ČĐZĚlű™wZáĺźo5Â^Źť¦Ž(5łD™pŐ±˘0 Kžé_t|ş&}0Gs]lÝţł,Ř/ Î¶Ŕ‡`TZŔńo ,‹đsmPüŠŘ)”ÁnňÜ&BťýµIGŹŞeH ĺEĄ_”ă–ÜűZzíúŤćěň%_&—Ch\9âťÜ^µÉʏŻző/ű¸(Č€ď$ú<´Ż˛ş¤ňžöŢ|µĺ|Ś3Î3P&ž:ĚR…og¨©µ„ȵ‰ęr´lëë8d7סvRč·cüť\řilôp“ßd*ťš™˝źŤÍ)h>ôĎ·)XŚŇ÷Ţ {\ŢĚ3⢊$gľ]«‚Ą^nľWńqâćé$ńMIâľ­uMVŤ›0 đÖfßkžťhC›ÉŁÚü>?˙ZÄ›¨ëŤi 3d,· ÜX!«d Í~ô1iµBRm„ůˇő]®Sôş1ěPÇě«]źł.ţóóömoóJ`břaę’ľ™d¤lŽ·fą‡dgÍĐńfý3 íaNËq…Je\±ŁŻ4bY{)HđP-ţÔ› •ţőŮ>L﮼ >űž-q]Đ5Î\KÄexoRĎČó5ůf˛Ń(ŻcŮ00!űÉŔLPcS!«µwÓxbs¶!ůú×ĺÎ×)&JРȰńěĄěîśş<±ő.kŕUNyęŘ5ѤŃD^JľÔüUŰ®brą‚­»í•I×CYôUłNµăJ:Ň=$ńÚčŃ—EŇĂÍŇ×­ş$đŕďŇřŁ\|C¬É(^„Ôp€<ů7°ónâ+dÄuT¶L¨Šb|ĽÇC…?Toé°vçFAW(a:¸A˙Yý˝¨čý˝¦^¨É¶‹ËDČqťâ ŔÇ&DőŚňĹĂř/'Mۜͫšˇýĺ¶ę— üŢđÓť\X]4eOűX%ĽrłŹ|”ÔŐZ„Šě™~SY”Ý‘+~&c ýa˘JáĆ!ž|0UăĂĺűJL;-‹ś6÷”ń®KX´4ÁQ™„®iűÔB8‰ɵu1űą(ěl_Ę|?%b ÍSHe.îrş @ąLÓÝ ^Go^ĺ:É?hRO˘=‹…Ł•¨ b«˝45äź8WCČŮĘVVl:zvxµ¨!ŐĂzK•)=%I·~eŘďCµ™ü¬áâŐęuv~ŽĄťŃQźĺDJOý˙‘JKŢů…źŞű¦Z$U§7Ů!‚éÝ}"®e‡^<‚ÜýĐ·*:iőíKˇýnEČ„ >ŚnĎ]ů´:űXY{0v^k/Z@ |¶p=#Ž˙Žu¨ćF\ń,ĄX…wŹĺ˛ŢׇTB©‘i.Đä:j—r0Ŕ=¤ÉµÄôÄ[%(\'ŐŰŹEѰ¦đ&Ť*“]\ćˇÓ‘,)nÉę šâA†ŞNtľŐ Đ;€˘śżpTg…ČşĽ©‡P`máíGYDl©Yľ0Žťˇćś+Z –‚Foł?b¨g€ćľAâ§;¦ć6¨xť ęçŞJ/Ř5źňN´šů°ît‡8ŚAŻ;'oW„HÓÎŽR,…‡ĺęJy>[-Ąű=ČŚňN­ĂšÚ˙c§¬ĺČĺPF, ÉrĐj{M¦±l8Ǧ/·‹yâĺ5^C \OŐ÷{†˙[DËB6¤ęš`RlĹ Näˇ#‡ŮV<Ąń¦%W]óŢ•8~ĺżČ; dQĽé¶şN,'N˝7„"·űÉ%=ý·ß"×ZÁ`ŔWĹWĺ[±¬ÄoC e"kÄ*ű4qę»hu±¶ľ()ůŞxĘ|?˘ąhąÍ«ď-hđŐ™/··€(~S˛Č{ {FŘ+âÉ_*tŮ^ú”rD#Ů'@!ňMh‚¨EďÁ:_…?`ScS"ŕjŇ‘KżbŢŻ»WA˛…«ČčÚL5Ęčč˙üęÜN!ĺm~Ć/”p*ŽÇ·ÜÔI1»Šg¨uřóŹŹÍ‡±ÇçUŕ łÔ%6@kHR€CűÚ4[-ź/ŠQ7ç §Nď«oĽ˘ěü âů~w.A<Ú_ZăSľjĆNkňAłš=yTđ0ä.ŮęHň¨MŁjĹú•®ąQÍą@˛ ĹjN‡#ľši$Q»8•Ô}żÎ^lFÇ`ţŃ,NÖxčë|w[^‘ËŘYäÔl `i˛ďo~;~ۦhl+čH]ßp® śîë—mX=čĆ„Už˘Qѧ“ÔE˙Ďôę7ˇá—aů/€¶Â-59Bâ2™ŘŃ>›°†ztńÉ[—Jč]– ńßěşň!Z HČgŠ\Łĺ45d$…ÚĘş>ę/řÁ¦‡Ľ9ý?39í@ű‡ÄwM>7ń@F´ MĹtŞ^ĄčŇŔ¨R94E¬z|×°yQhL_ŰPb«N1) ]{Éö BVF]†ř85ţÍ%†lĆ"ßR!äórÜŐeQŤ&Oy˝@®Ż1+N—»ô¤SĚÔ~ Y™^@:Ă˝$é<`j 4Ď*—ú¦n÷„…Žn¤‚~â*S­aôŃ.Béř }đ|xĂ)pŹíŽúĄ"ś¸vűŻŞÍ_$m[رĹĹKŕ’Î?;¨w,…ő„›Ěî^Á-FçtĂ”,9ćřwÇűŤô˝ß:€>p†5a·‹‹®˛úŘNg„ZɰÝĚw+ß“ŘĂčČ;D žşk§Űo2–ÁťLu)Úw±}řô©`·/ř×Ň8ŮÄ'† OąŽ¬Ü+ţ˛-×{@äźô_]ŠR©Żä-tĘ…« U‡Çq‹"~^‚ę%Áo& $łç]Ť– €ţOĄ|śyęÁDĐ‘€0î´4"+łăÔŘW¶î¨ńż'‚m C}…«űzĐíţTIŐŽŹĐîřáo“Ҥ«Í) „?䜥Ŕč3ÄŔ>´˝3wĹÖ!uŻĂîݧ–†j'YŻź÷»QpéoŚLđl?Ę˝Ą%!X ć‹xÄ Ý¬Ą­ŢôE¨‰R˝ĆĂ<ŢÖb„ÓÇ9 ínPé)âů"!‡€¤¤Mĺőľ—5Óü°3ąĎÝ6ÜĂći‹ö,hÇŞAâ& .l ÂPÂ:™2ČôÂóö^ą/ôpůŇî4-ŮWTbÚ•(˛dĺÓwǸď`€Çá ?ńW/ó˙ĹÜ:ćÚz¦{Ň^&( ż}Ęě§$Ż9tđ–tW ?čRK/PI~i•č˝ů°„ékełSř.„HrüUŻĂĄ”ç}·92»«Ń&;˙#j­QŰůĆ~SckĆ»6ÎÔ,·)#aNýĂ´†*<˘fߡFŽŹ™„Ăčf®ď|ĎMS™qžŁ˙PŐţÔ´ nů̬l2Ă"ęÄ៦b0ŁłŞ¨YvŢÂŁ÷©škkĚ™7–ĎŇĹyv’q0nŰz;ůR¨7q·Ôń¶ńÍa’§§µĆ" ű Öy;}f—ÁÖyßr5"‹pĐŹ,űíż˝%›ń9Ńŕ§[;ŚbČ +)ď—·ł±¸XlgŘ>ĹR"Cmś›-#wřłüđ<ë +^äŚn|ýµY*Ň3?Ź*ŰE“VŮnÄss}õdfţ+Ćw#)Ü-‡XD>čO¶ŹüńĂo=ýľ°U%ąłÝÉNć\ďŹÇđÁ‰}Ů_y¦¤eHř*5ÜÔUNFÔ*Ő3fŔ„EÔ:ccr^§,?’‰F#îľÉ›€y$Ž­×±oKřa—†©äOŤűîí]Cćú,zŻ€ťęr«Y“޸¨’C‚˙řĘ uă=SIĹ©:núć lcľěnXk›ą{SçĆZÜěŚ,g(@r6Ďf«Wísş)I ¤ź ”ZŽx —aD‡®vń˘¨x§….v5čy‚iX3ňf^Ě5őj$Ă,’ ›šBZSľÎ–ĺ©Č  ݂龧Ó\|^·2ňy ˇ<Ťž ÓjωáüŹB ’ĺ¬7â\ŇX}` …˝č‡µVĘż#j@š)v55(zKç•ôĆŇä§úšÖ}˙íDkŔ)Fm˘X®Ý őÉoU¤LB6¬mVŹ'Ž©‰ÝMÚ´î´|T™‚×KČfćUŠă8j3h…í<# NĐUř;ëpÎîŞ&›Z»÷Üb{Ş]×@´#ÉFXúó‰^S˘M®n8xô6$űßDaQČĎą‰\QD–őĹkŢMn řłvéĹ'%zkőfĹđ&±Ĺ9ĐĐ‘ ‹lřiB+1S÷I÷Ę„¨)ńĆŔt g˝E˝C½Eű8$-îąä€žyîȧ“raÂQOKłčă9*z ߏ°ůŹyŘęŮýÝ-\Íu~ką¶‚á¨˙Ţđęn»OK Ú§Ť×9ő^.ěO…¤ q*$•¬Jx€@’–gš1 Ĺ} ÓR™!ťŤĹئqYŘ.q49lvĐ|B™[ü“,ől"ĚS¦|ĎÉN[YP+é¤_ŇTX©ţiÍ!˘żř43[;güHďúţ­Ź@ăşpÉ(oąPWҧzŮa‘˘ DÄG§]§u >í9‹Ô‹†ZްS(pC‹*ž¶'aŞY̢ĂzÇ2ĐH*źź ¦*ýx3˝lďćŐPöX‘uöđp­»s ťDďBČÁ~ źďšŢq®dOŤ}ë2ŻhĄ;)fJ]žóůpRÔŻě+u-‘„cÝDÚů"1»čXnŰÁWrçäâǬÉ^ekŐe„qj˝ Xuą9š,g+Ďt~üCÔZoÓáĐG“·ź±ŔçČň­śTŚ eŞ!@4™nă/ѨŁy÷‰v˘6)—M({Ł~ś%¸|Ć?Ql§ěąÁUŚUČsOĺjĘ=3ĐßU%˙Ëë.ŇS««âQ׊ćđÓGᆉ\ľWʱzmźav¬ĘVËq¸GođOo/ł/»•!]Ŕ‘x4_‹9+3îž\îźś±pĹ•pľŽAë˲ôpĐčWą ţń–˝ř9Îşď<ĺb Ą/BL9ŽacĺÍ·Ăńö‘j>_ ?%´mg8ľqZ˙‹˙Ü7čŽí©î‚Y˙D—Nčұ*—»3¦ %Mĺ’ľ:&°ŕ âxWg2ľQxôp€a›ÓóVeۦŘD…€d츬ť.őÓ–ăA*ě¶PÉ®ł%öĎ@ŹÓ…Ěůě?ž€®ájÍ’oҲô#”\±ykĂu˝Ć;9^MÚ\˘fźűŢ^†‚X·F `ť0mü"›„ĺS|9ď‹D"őť±)ŐÝo8—ĺĚßBĘt™S&©bHU Ó8©0śWŇ€°F_L4×ţÉf ńĽë¸"ŮQnn¦mćˇ*K-µů»ŁvL«‡.´8ô…‹N]ÓFS7e&Tá”L/-we~b‚¨IfWŞů‹@â?Ô'3OŔbń˘XřČűČK4MwG¸W{RĎă&°?ŃĄ©Sé(NM^6Ś´4´ĺ]ď I•JuŰpđʉŚ0|·o{ôt}D\#_ÉŃ3cďäg SEş]2·•|0çúślF#B+—ŃąŽmĎYŹ]‰Ň¶ľP·Ş0`q1ëçV3N»ňÄŞb˘Ň|oé—ÔEKçź·x!ĹŃň±ţÍóő¸NŔeöťNüP!ęąŮ-– Ý^ ±ýŕiĎVŮVß śőrDâeŔř5äš©=t“íH0p!#ŞAj;…)P ¦Ě 5\?úĺ?ÍF=¶;qô‚ü§ŐÂ!Ł|¶ď;$ő±J"ľktěXŰÜŮkŢâĂ™žWŔíí`ę0.Kĺ-ZÓNŞÁÉím\˘Ü 1iěö™×áÚĹŠŽ~CQńöŐl8Ż1·ÁŮQ ä_Q˝O3]ęm#蟦„äÚ R ď¤fŇÖqŐěTőťŇ€sLYëń…˛ňů¤BNźPű׬ś·H‘&\˝.č@tąl—yÔ#Úˇç§]Ně†ŔTŐ!łóä±úöL”"©p&ľ oßQÝěínś·"pw9n'?ž?·yýĎ×0(š¶Ž-ł˛˝Ő¬±o~wŽ,Aë˛P}€đ{ł@â†ďĽťĺź@b˛0ĎŞf8A@Ó} G»«&\'ŘäšK–Ý[ükŤ°rŞ~xŚl›o—OňZ9:Q¬ř‡iČč‘DM'­Ć9M€‚ţćßý[k5L.6ŽHËúRÝ`¤Ű>ú”BăGSDK°ˇ˝ž)±âŐmŹŁď—ŞżŃjŃE l+Ë1űąÎg{yŢn'p«Lł‘Ăp˙’®Ăż·čă‰üSňěiXw}ÝCZťwAT2ö,‘śÁC»Ë=–ĺÚ Ăp ĂGĘnDĘĚ"=˙’ AćÚ„Á/Ä&dXĚP}ŇÁşGš<-áŃëŐş4ZFHŤw:ÄßÚg±ÖVĆ›[HpUä°äB˛®Ů©«”ćxCµą%«Šl`"¦ÄŚCÓúwł#ih(b˝á9¬;ćĹ`@Ľ6Š1v|(˝FĎČ3Äşkjř5Ô®ýHÝoK•¤ÚÍ»F9B`\ţ©cLî«đ.ˇ˛lÜŃłśÖ´Sň­*¸?ű]¦JlĎ|0Ř«żCmnW”D•ČíTĹ/żá¦5 _ý܆lcŠüŹŮ'»ÁHÔţđ'¨úV´=k,7¸HÂw¦k®_\N‚†cýů­d@QfŚ#Nóů5 PŐË zÎůHµšÜ”[öyWQ1 "WÝ÷ŚdH3%`±'X{u!lďÖÚćS…·Ż `C‹’®®D©ölGĆžîáYĂ\ Íä©T: š0´-. ×vI˘ ź|W¤©Ö@24¦KW‹‹NPR°źWpÍ©ËHÓ©6¨”Ăj¶É!T黯ö¶á–ŐŤP•çOU})JÜ ě.ďüëŤ\×ŔjÇ‚ä–Ń€ ¶+Ú8ÝŤ¤č;0=•1©d2 ©µçíţgćQ“]zě«|µhȶŢÂ-o)áSâ*¦ ť|`(P f-b#×óS7 ·ŤĆ ĽȾ[Ľü×ô~řăâ0áńMçşłEhĚoďéK–xĚz©íîř·'y ¸8® w×Ҭâ¨sŹéÜťÓâLŚĹîeM‘t:ݧ@|‰š;YżsPtÁŔ!qÚî‰âYýM0]ěާč6‹ŞV“0—Ę8®vß3K˛äpI»˙Č1Rúo5/ÎăyžDX âÔČ—=źĄçňµ­Ú]ć-•ŻIŃáP¤Ôu¨T7Ôµ)@°Q–˙m<öş6Άţ+˘ôű±űq”e;8†{€żfřđśU$ëYĺSSşĽ†K®´A%Äň~*=.Š Ŕ‘×5t±Z'Ů˙Ĺä^ź2č×#äÁđ>VEŚ"ď™§íÎşzę~^ĂŔÎ<#™Pô^+^‰ëď’ä4¶}ŃÓ‰ŤÂéÉ1šFE´%hěóŞÖČ—ěz ĐTĚ@ô†‘f3ŇťÝJ§?4iţú }%S0ËŁaë{Ă}«BëŤYv„ -"Y‡Ôůžď+cSć5k°Ťâ¤­Cg_$$üĆg5ĽK« ^O=ާŁ,†°\Ţ×Ŕ/& Ů罬`JyŻá>ňé]ˇń{éíŁ*bů Ť5ýn»WĘ\śďú'ăí7ruż˙¬Ý~”*…¤ŕ«Ă3AŮsŕë¸Đ[‰ńSlµ Ď.vĽ¸V® ŞŁďÇu%¶‚đ4úňZfŘŤŤ{Í#8śî‰«đômgŐ–PTřtěNř9oߊ‰7nˇ488c9qČ tnëˇvĐŻzŽB­^b&ç'"a|.>J•¤#›ćČ˙ř|D¶ě±“gű­Ë »jŰ­Ś=]»ÓHţ‰b~hŘo“\µ{XĘí7uÉ\ő.ŞÝ« Ž FyLŽ;W‚–áđĆnLř7}ţDĄi–O S¶b3‘z<ä[?ü™ň‘ĹÉ9öa§ďd#©ćžztÉ+Ŕ´`LúĄ$ĂݵÜÓ=)Z/ďU»0¤9Áa«ŞÔŘĎł„(T4ĺBŮşWJ¦Ű–ĘŞ±jëm81¨U0:5`ŞQؙӭHV§Dyťi&5µ®Ń†<"™¬)€7K’qz§9€Ü ™J/J$/Óçťž<čđ<>örřѱěŤíťŰ·»źÎĺ+A2A+L·gVVşS’× ěĄ7×Ń{˝ë;¸Z“käřż(ŐUŁ,ŘťBUÖĘUŞUÂÜ÷…Ď^ÍH Bď‰uĹIř3e("]ăe˘ř0˛syŹ>J ňnĎâK]ÍČdKŤmŠž 6Y/Ţ—ÜÂĘĘŠR™7D§ŕ{, %âîElrs®M ÚĎädŠ(ý-Źy!˙¦AÖX™N \ď˛m@µ$Ž9ž×ËBDÂĚciýĺ1^ŔĽ9úžQĽ2ţÖ»vXJ.ćJHЇ8Ĺ˝Zaµ›»¶»’ŁiN Ő|ÍşŰčŕÝ[ľő-‘7ÓTb0łŃÎŮĐÖ‘PĘK3gEÁűó#.†žŠ un_r˙/nUžh.âWěţńYť€ýž+\ę_BťţřŤ÷«Ć—đŁŮ"N”Ţ7ť sžJf­ŇE‡ĹÎçBPň|‘Ą¸ÖyŮ9§HÇ9Ô­ÜYľł^!$‘d˘ŞÇ|ęüŮ$«ŻĘż›ŇÜ“DÔPPk™0ľTĂ<÷–Kéjc1_`ÜŐ·hńóĆ/9źő?7›•ť’'¤tMLl˙›˙ÍD¦üž|FĆ™÷×{¶î‚9L@ŔÚIp˛PŹĽôWMř/ŕňCľ™N‘g˛ç¦DâĹ›¦T)¨vřŽ<žÉéőŁę^6# ?dŻ0›ÇŽsĘjYńµ÷= iÇiđýz5ő}€9‘–Ë» řŠsşs÷ůżIH—/¶ćqdŻ;ç…}Ó-9j®W%_gĎ·Űs.ťbúË~q;AűÂčwÉ#á0Öţp{ň•ĆV”7­Îs2ńŢäNüă7˘Tđ Ő¸=˘ölgóyô&Ť“÷µP±Żă3ńĎ‚˛@d–0ŇÓKőŤ·r§€Ć,ôÇÚ‰<ţ9‚Ž ŠĂ"OŠ3 ’ “Ú ’ó+Éj Ö!$­zžŤmk;Ol„ĺľ/·Áč¶Ę<˝s ˧eąF9Şë2ĆđŚĘŇŻ &É?¤öݎmVk0yĄT [łkz‘Jť¶‘đÔ6` 8uµđé‡A!<Ä÷đ¶§äőŢÁ`żŤßˇ0|Ĺ)SÓ0‹äńżĎ¨ďsäχF’vąmĚvB!˝OBPÔ÷!6§ĽűÝ,n†oLüĐC± STÇ´G,ŞVäeÁŹą›ż[ÝI4 ×~¦'ţ\_nż~WÄÄCöűuTĘĽ¨G9K Aż •ľŽžé"ůĹ˝Du‚´npÁ:÷řG. HmŞ6í^Çq»ó±hq3x5Ü ÁjaďqĎňhKqĹ}čůDÄs{× ×JQç66 Şňé}Đ/ŻăËŞ¸,nA©R*¬‰âôťěŐ~Ł\í˘·8ńg¶ŁáĆ‚Ę%`p…gŤ‰ ČŮ×3śŹ3Ű4Y(_,ďgňŘV>3 śf´ĎĽ»)SŻi ®Ö9\ăŢÇM33]V‘h2ď7oźOł…”üç” ,Ęäś–‹şýűńA‚Ýl›f*QżŁ9óD¦Â%†F}ĐAůAÄ3><‰Ďrđ =ä™m¦mŐőkFńÉËj—Ëá+p‡ö#Ývá,!C‡ÎŃ],EfuŰÚ Ő YTá© ¬Ş(+«nŞn€žX_±±3oÝUę–ÔBç†TŔŔ~6Ť:ž08ľâ›ő›ßÎÝ@—ťΕťGşëGʰ„ “CňK!¸YŚăúŞDď§ŕK‹Ř{ŠbM©Ń§Y2gC€şâ7rđDí1˘ˇ©ľ—öĆw 6ud”BKsű»ĹŮXqËS,?&{őŽWťµQžäű÷Ł—Ç®ě@ íMşďZžH€ĆŽĹźĽäúXÁV†;…÷ e«ŢŹä(˘ŘR«ÁÂÜ1—AĄ)B7IUze; ËiGš¬PŽőž¸ťłî}‰HĽ‚ÎmĽjś~ńŠy†Ż6d§nÖŤ&‹fľtaKa°LuCŁu(Üî—ÚĚ-–abäëă7ńwńVsőal†ígÚň}Ş3cŰýꑼľĂŐÉ”ÖIôl{łĄv<8C?Ë’Ńä ô˝wÄ•l=ř×—MqËž‹C®r*?´ôw´…Ä9ŻL-Í~ěCÂ8ŤC…:3Ă×Jęŕ§›‚´š×Ň ÄcźÉńmEp}ĆGŤYJđ™06ܨ^ÓB9ßiÄY–ŚxŽ€|ĂČâJřśV÷B ŕ~\ë˛oÔŚY»ł„-}śh˘¦şUiíoĂ۱NÁĐzN!ćrńIŇdÉEP|ôóEŻÁ/¸yXŽ˘a٦lmZeNÓťŘő:ŔŢn˙‹Ľ3TÝACŞXתtpă4¤@'áőŽk’ŚŔŢăóhŔmµVĽW-Ü*oZQÖcŻđiŁ1‘h›wdˇ62—áűŮF!áUÝń’gôäb­Z{@GÔsa«CwjčtźíĚń)h*śíů°-ďâ˙±‹Y´ÖídŇí›öuČŚ'Ĺfž{©«0ÂŇ˝’á•dBde*‚«ąÝ(O..”źNÄĹű`Â`?źçRŁ$Ö)Áôzl†}•“‹Ž«OŢ.9řcv·~NĚCŚ'ą2ô3cĚ QČń+Żł}ŮQÄä]G ^:ŰŰ>(”mÇń˙ÜůŁr0śŞľJT«—w›Ąy›…eßĚĂ7Z\…OĹw$-«Ôşś±^VZBĚ.‡š­‘UŞ‘´EěMł¶•i‘  `ŹŻ°7ůČ żőÎÝÝ /É@‚ş ő·×s”mlĺ©éPxéNL—…°§®8^©^đ .Ećé]÷[{:‹ćv,Úf*i¤–@ "Uň:ÖI¸ř1Iý×z~ĺţ7ź„}]„Ř|î3Ďą“¬KÜGŘ~(bś=yą]f΂z…ý—ŹFŚéšy˘‚á˘E öl|'1đŮ ó“&ć=qyé,°ú*öń5 ˝Pó]±)µ´Z"6ż¸ü<# y.gH¨á,!{O›]›`H Ę-ă–Ýŕ0‚Px'Ł˘N_üAĂGdHú«–Ů.čjĽă»9đMUM Ľ*ů¸á<Ť¦JÖčÝ»±hY \ţš–´§žĺ;ľŇ˙¨í°łi«Í€⿬6:-¤ôgűüô€%Ó?. ¤:ÂŕÎÓ<ŞŻYĂěŐU™¨Š—Ô÷­h 8OÝ éż9‹Ř|›ąa{JĄPŐÄÁJO€Beő/µf¶<ôâhä‰vfÉĎ3ô´řů¨b‘˝żÜÔ'żěxÚ–řŽ’ó/Ý@1T[™Ř´őG7ZŻlÂl@Í›Ô<}­ŕܡ5Ýĺ_,:ř?öŢbV Ů÷-ô^u;ŐźÓ)i´–ĺköôŢU™š„~sD2DuଫçŽĎę'” bécéÖdIűB’đ7ż6B*äˉ© !$¶A ęÜlXJĺďóżÂfR˛Ę˛±໩m‹ ·FŁ;ę*“ěpŠ [çˇ2TrÎŃEŃUeÄ•n„čŢq¸<\ź»'ânÜ´8¨j’g•jüŕŢ8­łŚ1H…®âcý!‘ňÄĆ{Y*ç~Kś >SÖI™fD2xHż˝ˇ6JC'W@Cí‰Ç ß5˘ 0ďS¦ćÇ4Ź ¶Ą‰U @5aBY3%˝wG˘Ľfěŕ´öÎŞ¸¸7€!˛čßÇúŞżĽŞÜ­©iÉźŕ"ęÉíČŇÉDą×’Ŕ2ž—\•^0K›¸®©[dţőC“=˘y)Ľvá–ă/Ręş-ß=}n“~ú›…´ë,ą Ň,nľ9¦h«†D źVżĎ•/¤~|·1iëeO ďj×[¶±pÖ„úd™¸ČŠbsŁ·Fe'DW“ď8†Íi(đ&´`Ă95aâ0yĎJĄ™Ě—;Łâ­µDŢ#Ë‹@w–Ţâ°Ăë.ţźźÝľ61‘čć}0wŽqŻĎ g¨ ž–Ůç=›ÎŠ]`ř±•ú¡Ńŕáú‘§Ő˛@ĆŁçhá ‹'Ce6H @)o˙¨čGCŕÜÎ7*ąĚŁďä^ ¬¬ş_TÝ•}:űË]ş´Ţ—”·ř“Ťi·‡úO-â1t»‡77©¶µöú›ÚP`‹WňÜţ˘+ý”L™üŻĘŰ&•öOn’őF< Ę Ľîqš$‰ä®vMł¤í=”>I]Çq†ŰŁeeĆQţ;e{‹¸k/Hr±ńµ@DĄZeöD vN&V€R% jsVŇö\őŽ4G0pŞ7ş,űűŽ- Îľ-ĽrQ˙¬Ć}<¦TĆGŕżFĆ1)JtŃř$’­pSä‡gŮY/U·(}ˇéř›Ş;Q>ž5­á8M¦O/Zç7Žýi‡MľPܰÖôFNńTĄĂÜ’öAě±-,ĽÂťĽG Q´cő’C褉2$±n1í^«»ŇŚM’“ń .ÄŘúޤX”G®LÖsŃ a 1Vß­Eb~šżÜxx/˛şxČUO3/‚Ű+đz6…IK`CĚŔÍľS“őgč L“šüÜÔŰ·˙Y® é/$R)¤ č$>Ś„f@™1ůŁ0Ą1çb˙ęŚ_1gŰ}lí{%ó¬µ$"°_Ł}ŇqNKµßTęŻĆqçvőúż;\¦ď„ÉĆ=Ž #Ý`y»>ćIŇôâŇD0_%CÎA2ŇG‘mŐ.­ř*í yŰżn)Ü&5×»ľŽ§“ŠďŐ—3aĐH XĹ’ż®«B×>sŔŕÜn?sYŔ®íŇ]ęyCP;]'j±`gŇGCRkü©:-~lyÂţ˙÷ôăŕˇ×»˘x”đ:¸ŹáŢŞP'”9ë^ßŔÔŠŃoPeĹ7ÉóT&ÚśtĘk sU™ śýŠ[%8m4śqĚONÎĆÇ]!G5Oa k•ŞĐË" ďľbh7`˝µUŕkË6÷˘ĺ\ůţ\&Čáňä&JKŠs¬ÇÉ4#ăfMĹŠüAĘV“›Č?i_3‡:=1pł"+Ő5”ÝŐbp:© ¨Ş§Sý$ă%8sç ĚÁâ×keĎ{¦ĐOÖŘűi#mnEyÎźSř >=ťĄ7]16âĆ*ŇćŐľ_HÜUń%»•udÜé¸]«b?1ÎrÚÜŘ8›ëď^ÍřšÇíËëxe:úă™üş7Ú1_ĎމÜŢ8nü}Áohź ŹŇłl µ7ŰŽhI–}ó‘dňxČqK{2oĽj–éSľˇł–óĘ‘ĺśúϚ·w”$¸FµçŢeug'Ď—Ś«Š‹´•ŢŃB\D™óe)ä }ľ>l'7ľ˛E˛Űő.5N‡R:Ł–if |Í}·YI0¬ŢP]@µe Ů›xä(Ú[Óą_ä‘Vh´ü˘5›ü!â’Ĺqp?I†™íŞX%2~ágَu@żŞ}4ËtdÚ¬˙źa™HQôěÎĹé;eŚĎIË‹ű„ŤŤRÚŁ@°pŁŰ¤/ÔU;Q#ÚŔ˘Püâ€Éµa/]­źWzł`ۆ]2ąş‰FÔŐ‹Y˝ Řcé„?_˘d5»&ję}©Ż‚×9 Ű­’%ćúf§_F‰'~:ţĂ×äÝeF¦Í(ÍďxÖ{gYY™z¶B§V«%˛AŰč&€ÎNÜ•CĄÝ×/ĹćU^ĆmŐii+ĎwĚnb°^;źa\J†X+‡,ŢÔţ6Ú®ąśZř›UCŁŽÁ5ĺÁYTJ3-ÝĹ]¤,ąŤMo`k -ůŞ<—pÄßFó°ůşý2>6Ę»ź@]F†LVßeśQKëµ˝MŠ6ĔْylÁź}ĚPőČ[ŐËřČýËŰČ?%¬A+nź *iůćIÁ•O=óމÎĐΑŁTÓÄćÄoí>l…ž][PŇîŞâăzOI@ W•ˇóá’€E 3č5ˇF[qS†0µ1ş¬˛áßŕĚjŢÖJöh(Űşµ@îľ>eIńAEC’đ¶±ús™ď4daµĐčC8Úź7PeĘ„2uŻ˘=˛°ĄĎ˘O-†„ VźŃZTaÓ&-´˙ł*·Ą§Ô÷“ ‘gçÇÓ5}đŠ›˘‚ ÉÍ}ŮěTŁËÇŚÂ(G ·=ěüôŕśĹHÎ\4#2Ć_XÖ&jÇe.îE\IŃö±AŇQN ĚKśĄźÉŔ‘¸®[đşX?ö2×›QĹ苬~(´Ť[żĂ§7 é]€ L]ą”cŚĐ"¦;­V„-­~ßau^?KM?d<áÔ© ů)A) ´vČ­­=ôžV-‘ĚŤW&×µł Ł‹lL‹×o«+ǻ㨨I Dq«i‘ó>›ĎV(:Qvď Ö¨ ŕU#÷˙íĺÁZż“v54Ý’±6z!Á m㫌ݥŰÜĂŠÝHÚ»›8Ó—WXÚić¤_۬±N=Ţ4ü¤gźíŰŕć~ńŠvż†¬:…BŮe69Ąqłôž o?dü©Ť}ăŚeźqÇ>µDmîÓL/ä]š*&-˙‘a‘'o ďŮ­¶;Ź%ř„iÓĆ9ŚČ3 ”Ňą«ˇÁÓ-’ÄČ˙:€ËÜlŞ˝DňŘś®q«>mj † ;™ĘO §k‡Ć,ş?äŁÜ¨÷ÇgQ7ťÝ®xĂěĽmő€¶µî´-Ön¶µ¤Űă˘ă€ăUřÉmç0Đ<š´Z„YSuxŘ7éČ«Ćěań|@?B:ď,Çéc“| '~Ä1p×Zв)ť4ĺś šÜ,ĽWÔ'•[ÚŇ—ź9ňęźš¦ň.ŚŘĘŻýVč¨Ĺňž]ŘqCx»3擡m?1Ä…÷ą‡Ń渹±í®ŠvÁu5¬jä·ąŁ;fÚ(V&ďâć@ۉk•Ă{—›ă #đç’x™•ž0ZűWĹ{š‰l}SËÚ|‚Ěý=âŞHýÎSb©_růZ:†3AĐ‹¬=má-8!ýzĺÜ Řç 9ÝĺöżÝKzŔpü"Ň ˛I–C…ňŻQú1 Ăć–éĎw؟ñŤĺ¸éD:<‘Ť& ŐkÔ8c":đř oî,źî}˘&ýK (Q ęĆBx‚é;z.íâY$ ú8jľÉ:Á­‡S_Ůâ1ŰńI.+PÇŚR»Ś×Ćő°c±c´v‘ĽŹX€µÍŠj/ř}ä ­ëNĽ7–9űzb˛1ĘÜZ¶M~±»Á3*|©sTRşµi^™ÁĂ3&*5ޤ‘ô@'şzĆ8ökom z˝eŢ}áś^ŚŐőVW §‚ą­ý(×Ue ÔO8#ß9+ם˘AvŢšk8:ކKb—Ë…5Íóś9vŘţKŚC˶žüü°˝Říy/*ęľ„Rľo'—T0ÚÖÜ˙Óő+2˝Tn?ăďĎT‚zj21x–í…‡ŁKĹä˙’ű©!; ÁđΩĉkeé·r†°ëw<1˙ÄĆ™Íű¬Â]&ü''×L g[Ú‘zę¦@rząBË€.Ü!"ťË~`$őݧ¬űařć­xÍ8Ęö3•ŐoŚ ‚IěďÜYˇFé^ŻÝŘ4YĘË’číV’Č!±Äř‹%–Ď&ß©â<Ë4cl‚A[5 8¶B L°ż/¦ÂŘ„F'/žŮ/Y%Đ'ŮhřZ59Şă_żgč÷Ł~4ľT>33îÍđcŐĎÓyQ’2*9$żŚ$đHáëO!ÝX-¨ĺ‰›Ž@űžŻPĄ|n¨¸ąUÁžâlÉKGŮűLh…ż 꽑nŐÜÚS‘¤|9łS„Ë@“„(DŠC€«×1iŁ-EJaśw'±ă&\ţ;/YŇGÄ— Ëdçíý{·9 ",…nz䎇Čě44„µÇe çF©ĘÔ2sy»»Ś;Î?Űfäq4H)R-v‘8¬q}eב‡´Ü¨ć’çIRő›t˝-úµ¦vݶ)·÷¸,ß&!NŻ_zľK˛›Şßâ”XŻ k=˙ZrCë0{}”R &ˇc8[ w­ VőUµń p©ęé2±2I+¸]j4pś(úżď¸WZô\C_gĆęĎE –*Lm‹YŮö®I.ůPĚ·ę€ “·¨ÄńłOŻHuŠŕx„¸LK÷Ťä€ňµűŠv¤wů”>"Ě,}S…e7«+IO˙Óă/­łhhPz¨ű«şFý=i׼ĚL­¤_çąú± Ě!„V ®2ńÁFź»=“‘©>>v©žro˘˘ŕ[Řlż6ËŚh@9¦?jAxz{'!cäQ©Ç˙řĘ `DŠbx¨‹†Ľ™Ôý‡»OM˝Âsë=ĂźĄőR?÷“şäŕÂyŮÖOÓy^©ˇZ˛íłÝ…|}ľk`ą48T,ś€a ,$-Km&ŇĂBc8@ÁĎŕ—î,†çţl6›? tĆMżŞYx«°ű‡ÚĆ'”u˙ZödČ5Şd.IJŇbsRő_Ś!”(\\˛@UąKĆ(Ą“Mq‰Ô"±›ÉkÖ„9_‘©ł91FšńÚŮŁ­mĉ-yę(ÜݝЮт?XHµ4<m© ;đküËłŢ0Ë{°1¸ö÷XÍ)’ěYŽáPqw4˛™?<=-$Ä‹,ŮGXEÉwÉŃś,cl;:„Š€n!Đw—'ĺ<|…KđG$@I%>-żĺ’§,Ă…&á›C “ôçK1ŕ6]ô js’ŹgŢD4Ľ±»ÓK4˙účS›0…gÚjéNĄ 6ĺcĂ#ŹĘÜśÁ{8S÷ýśŢ?ĽÖ.—DčÎ<-ë†9!·˝“lË|o(8iśBWČöפ§ mtšů7ë4ßČ^ŁĄ“ďČ6(ŢÚ×Âůmťw@DÂɆr”‘t)(uŃ‚fżjązÔ÷Á1ŠW’]‹~ Ž÷`%ÂdQ$C¬ĂéĄU"ů›o˘&ÂÜK2;đg#¦ fÖ˘Ű(‘µ ٲýźÜcÁÇ">¬ąbŤ­îVaM"ŃTśíĐ:’ô =…ktЦ$f Vľů4R,!F3j©n č˙”]Ž98Dr‡wne…ĺ|µ7¨Ŕ5ý1ëxIĄ¸ŽňŹ~őŁâ'©ţY¦öý;ĐHu)›5‹±©‡ßs\yĺÇn4>©ú«fŕÇu! ik&A41žŕÚ„“aÝř]Ź-9„ĆuŐ_›T ©2ĺQXŕ˝Ůlt`Ť)fŘgü5 ĂČ]şă*µ{úEţ8Ő›ŮbąÉě›á[ľ{â OW·»4¬GuSÚö´ů'MPŮUj™RV65‘Šm‹v†}—‘č'noYŻĘ§÷‡©q©/!ŃeCĂŹ‰˝¨€Ęú ´)+íJťeÜ~˘ ,µ‘ CÎí­z_® IEę¨í§Č˛ďěź`ýú×%ň_üfçýáݶď"qęřsŐ×›Ł">)Ľ¤Ýçŕ®9ľ©Š°žáx«i썦Ýíă~'G0Q_ăą[éó^4Űě—ÔĽżŻ×—h¬qĎ|ұáhHBźk•DĚ$Š,2Ţ~ĘćŠů›'Ić§>•)ĺń©Żz2¸tÉîm%qpI–ɵĐŰ ą”b#CjÓĎCîPq7ÎÔ<~R´Mú9^ Ů0wŚw ˝:öa€ĆÓßTG@6ˇËNĐmű#’vgľäŹŢ`łdŤŹ‘ŘUË>˝š î \ĐDd_{¨eˇś¬ąŮj^HKbËGĆ(Á˙Rˡ8<Ň%Ľ5ęÝîÍ|Ľ´¸•qŤŃźč?±‰›—<-ß0ÇĹ›-ßLĽ†WŠŞÇ˛ś©5ĂšěuˇÚQC9dŇŃ?¨ť»đôLI"— 5Ał'żýďĘnĘ/}—;Ż‚Â$,Č‚U‰aÖÁçň ‡F¶ŞD <‡P-`đ†ÚěNČç?DÔˇţ]OoŘę5sűłře#»Â=Ň%«/ý“%ŽfÄQfM¤¸|+˙31ÉâÇwÜŚX÷®Ŕ/Đ·®yKµ(MqH§Q‰ÓĽă;ľ(¬ňüÓýEZO„EY8SÝĐ„ŞćřńJ8j–Bm†”¤9ËĆ»™â#Îr€Đî1éâ ëÄäňáhGň¨€.ň&Éę÷)ť‡[šŘ“eŽ˝ľqyó’×ô>[Ž ¬mÚ ¬9ĽZC®ĂkóţľĐlˇ[ďŢ‹˛đzýůÂŚö´—śůď S»/ŞZĄ„Xčo×€SeÁęcd|v~)q“$+Ągqa ńk,zp=*»­{ěnUY’â fÇá©BĹŕHŞÍk=@as'/–ážÎ EűáE-ĺ˝Á×h%®†Č3ŃĽĹ\őÝ‘ ľňéPQ›´˙ŽďăÍ&áđ¦÷Íx–ň7ŤŔLš§”gĘ)-Ľň[J›Îę­ťÚ%łě§ą~ěp˛…}ťZ'ŕŁÇčŃͬĄű •Ó˝©·ĐPW‘ŽŤŻU‹;¶ ‘“€ďĘ»|ŹëŢ ˘ýë±IME‡(çß7ú;ţÖľI‡[3Ż—™J:R$tŃS¶ ń$GŤţGIżč×ýËĂç'O ‘˙@ăČä ŰJ(/ůVŤóë*P°~űF†7@D#“7Óqc8÷О>ÜĄń*k׏ĻP­ÍýČ7%§Z«Ç»Á-ťzó,ĂŘ"˛˛šaéČucŐá ë`ką…ü,őË:VţYą»V§9<Řć7÷1VŠ~&M˘,Ő&ĺmŠc§#T nN«îB©bá`ÉȱČ÷m2¶Š»–Ł}|ŹČ%Ö(×W 5-‡Ý㪉×Áś«/n@ÍĹۇ Ć;}P(qÖ“ě|(i_™°'ŹÚÓšŁ§úÝR¨-fÉ[ŢÄłńhd(\ˇ˝ÖÚ&ÎÉ’Cí_¦x3ßH8aŃâŚĎ)˝ËÚPh&"ó÷…‰ío"ŇÚPd˘y­Ţ·xĚ˝¨GĐ#â$©»D|‹R華X ű2đŻĚN{ßDx‹ŕŠDrgj¦ô-áy|»uHę)#4lżÎ_™%żĺĽŢíěZeošĐ° Ć÷Śîĺ .”KšŮ}ŮžßZć5(í’®¶˙+@¨ŞC0őv˘ZÎĘßQX,ÜNôµÂľŽWšd’Wo«?Áďk1!ŹÔ(żĘW5·÷‹§ĂZťbŕP´Nô¬•Ć}’1©LÔÎé+Ďž”ţŠ‚´üź‹ t-t®e›SŚĚ´uáî&. ÉýWP´îĚ}XQ¶+4T~ÜíZŔ_WćM†±Ťô«ˇIŐň#Đ˝žČHĺÍĺť…:Jl 72ţk¦ĹÇ$Č{2˛Ýá\  ßIf‚Ť-űĂÂ˙42\4ÚoÉÜ"l.š´3Ö’´míµ-ä;ś0®o]>”Vëč‡ 2ądńá-©oďµ˝ó–WůÉwËjBXşµÓLIr6ĎĎІľwÄŤvް„gâUíş€¬şŔRiűU¦ľĎóá˘K»#eúÉ`šĂYŽ˝ĺl—GűÁŞÉ3˙áď›3oeaÇÍUÄô ČD™ŹŰÓĄÚĘŕę4ÉŽť–OÜÝśh6ĚĚŞ,*K®·Á-Y˝‚á%LóOťŞ–@?©™fč­Ăw8– ,T¤»¦†,ÔŘk?iE%Amáô5xóíb€ý‡ť€ë:*äGyCy"ŻŔŔĺĹÍSĐcSĄĚőŮÇg®ýŹ Ď ż‰tŔeW5chż´|U8A˝Î »ý4Caź$kôŻQ_é ëúN‰{ŇilÇG$*üóﱯBňŔ:ĄW_ch^ÜÄB󸪴&U䪌%T‡˛řŹ7>„oáÝ~Zĸ…Ăs:8>O­ágŕ c˙Ý~7¶­'ü~źi|ŹM0žŔˇ”ČXŰâOúé.Ă’ú8rQ‰ôľCŹ]ä…Ë%Śůl~f¶wÇ€“őťÉ?¤ÎA^dńq•P {YX’cܸC©Uz WÔđ»6HgŢ—9PHHs' raíÜš9ĄBw2¨řóôßh©ÇäŔAbéäŐ 1Ę˝ćžă'šĽÉâë'E±¨» _-ŇÚ°ľR˛ú-Ëb$¨6j%jéPq'ŔŮy+1ŕ& /âů| ]Yö"ë•+#]«çż6K.öĐŹP—\˛·Ŕ;ڦÖŃŃo@sßb˛ž6ÂŔhíU{˝•u® oĚ%˘Yíş68>/ gěÔ©|Ę^zE—ý0,‹–ąa!_ÓO{¬Ń˛őG%§úťó7î ˙ą_#ů5ş˛DhŠV q&»zśŘ€úß|<Ü\6˘…:"fMĆČé´Ăľ•Ĺd:[ ť%×IźFI­ĄUďK|ĂÓßTŐ::qyJ–üdk9·IBúZ˛°ď[ v:)1řďőrŹÝřvěNÚrxą2~řźÂä6H¸ö“1â™mÔ{&;¸mn(]ý ˛} ÂĄyâśoâ)±’öKÉxŢ[ćřE™”Š Ë˙ŠŕĄčI"@l#ŕé¬^ľ=Ëj¸k13¤>ÉňgîóE ˇ,«:f8Ţͱ˝nŤ ŃEyĄžŰ×G*ÂźÜR) Ç(Ž~[›5ě1Çzc…-ćĚnĂśf¸ůç4É‹;± ¤ź˘˙ş§Ý˘†Ű=[%űŚcö*Ók‡;ńÚëťá ŹâŞ0>ËÇ<0q&‡‰ń“WˇWúf ˙ąłŰţ •ŽSŁ dĹZÍt’gbőĺ¨ţ _čŁĂĽă4_ÎËd÷2ÁF˛( .ă,r_ĂdÝqĘŁąBŘMÓZ@2“¤ÖľÄ8j It‰k"+Sö1ÔLŇL[z.ďĚ)ŁŞ^ž±aC§Ă¶t2.¨‰\„r14V•ň0r8$˙Ţ<9ÄpY–f?˝`ŁI\–~‚ü†BRŤ°%źâîÇv®ĺg‹J´Ł,ňó<x)¤śčĽ´ ¦7ŁÄĘ›$Ź,§0 óů˘ä1=hkŃô•eaţaÇ'ĆqgŽň<Úř@w¤`A“[3˛×#műl‚ĺWC´GFGŹëvŮľ8'žJ(ÍSIţÎǧ‘]Sć(Ë Z\^gŞşX­eÝ%ü0^1ŢŽ¤gŮ”ą‚é4˝•%ˇ‚lÉÂü›ďJú® nřK‹‡ řw˝ČvÂGy1ő_Dď›\„Ť„¤O&TP(nµćâ:·˙čśaĹŠŤ>˝Qß9ň| ë$ŽTúĽŽrĄ±íŁŽ3¤Ľă<ČŹeÎŹń›ßšE÷:lÜ ®°˛§”ÔW „ţ–pýć75yh{íŃĆŃßţŢ_7x64ż¨čqoPČŻől€‡ŘćCŕ¸©Ż‡áAd˙ÁYčBtXK©_|złdÍ_‹eRš"<„^öÁ.gÚP“¸ÖŃŞ‘7Ô Ż<ćXľłŞŠ>Čfűć("§g™úKĘ†ŰžÇ Ý1ȡVt¨°Ú–ę/ě<ÜĄ`íŁÔĚúş«C|ăżţ§ę­Á@ś­Ú|{ä)’˘»eöŇN-ţ­Ľî®H…˛Fî*Ü/€´P]ămM% E‡ź[·T#ťÓŠ4r5›…ŔzśŰ7ć6š˝SčëE3ąJ•ĺ·řu‚h\çćq´“@OqAƶeÍ3×AŮW„ô…l›_ËŻ(F‹ăĂfVM[ř m á…YKÓk¨6¨}i°9^lM°qm ´VĂď ŘôŞű‘C{ŮÁG<ď&bMz5tk(`·Ń×ٲř5^ç‡XY¨\6ąb\Ćá—ú:`“Śn[!,n=Ěi/Ëę=´,g ÚZÖżŐwrqúÄ·+=ź“á1BČŃŞ”>ÇNgľÇ'÷ć÷MőŔ^ł)ßńĆa ‰r“jâaćiZJů°ëÍ4aü^S´Cä®`żú•ömŻ“ş˘ÚgÜ8OBY»¨Đ誦AztQ'C:¨˛Ĺ<ŢDډ&<›ńĽÎvČ™ÎŐ/&Ó°şnÝ*"Ěęxî!ŚÔ”Ş07ćĽhĹŚů\í@Úc»€‹zą5:hlRnfc©´lLŐ˘rňděÎb ę‚Űž ¨Ţć<Häĺ(ż#Í%ţxN‰9Ńů/#sŔž!’Ţ÷Ţő¦Â!íâË~â‹„;bĎ×?\“(|Ńđżpme’|ç´·~hvß‹÷vm¦Wm ’h^ÓDŕŐĹńvtÖž`\2rËb—WĹ˙'¬ »:5ˇUfż+Ł9÷!{뻲ĘĎg8Ňi[ď¶8t­Śu·e^,ůeşČ›ë’1ěoŘłáŐÂ9ŰëŔźw/]®ůRyAݬą ePSWݰ­d¤ąĽkŇôCo|1~<䬤ˇbŤpĹšŃÚEŹű.*ĆÚ’]ş>F}Ż „ú V?I9.úEóęÂ9[ŇŐm‘|ĺ\ zĺ¶ćÄR0©~Źč#ŐZßČ>w 4łÁ´!+Üí·ôfŮŁ)–.¤Ű÷ fx/K—–+eĚćĘČÖzÔ/CWť€_”óŠjŃĆZe¤X:v‰6"ô”·úv_e1,Ůůâ÷č…}Ş–MRşQ”ăč)‹/Á}óI#$ÍŚĄO[Ě…Ędk|Ż}ŐV ÎyŇQႾcPă*Ń·Ö˙?ń…řz¦âű”ş`lr¶‚źá÷‘—jLŕ^ąÜ”ďˇďzŔB{ÄäGâ+:ý¬Ż‡ńMżů VŹ!ăĽ3Ě×Ó5Đő%Ţ4Ä‹°µ<4—ŰÝwŁ!ţ”ĐŠ\€ó­Zţ}Š˙2x±'¸f˛éAh*1Ǩ h‰ĂŽÜ+iYŠÁ;{Óm“ó°cźďňtžgęwg/u«ڦ«>n‚d)"°#Žčqu±Şjr?ÁM=€/çö-ÓŰÇȸE |ÚőŚŢF»č!×*Ěö˘« ™ÍHPd»Ěí•ŐSęĺE5tάÎ-zű~Ă0YMŇáó©6 Łű¤Î¬ń ÔX'ŢŮ{oˇÎj0ąçO~ßĺ™bĘ‚RKň˘:ŘÍđ¸Ą_í{Čé I›üľŁOŻ<x´ŐK†Jݵä0I\ɰŽyŕŘ~Š$ď@BîzQţö7µ”ÖŮŠg$ŕś,!©Ćó+]Ľ$ś,ť[«o¦H>[ ž Pׄ'3ńtff諺rňd†Zę·J,rh@Ěpĺ0†O§KR0ŚrÔĆ;Sí7~R´6|Ç4¦« ]őIt@s÷Đ­*čgĂ`dž¬@ĺIvźSâ'Hh0¶3ĂxsÁŰĹĺu‚i­„±&g© ˝’Žuq5g44_ɱ`%&űy5A^#\8;=íÔÓ°ö'ŚJç§!ÇoňYuŠ“üR•‹ą+kZpçtŐ•ÜŰĽm,\-4ďű›7;ѤCöÚŁÓ"óŁĆf‹Ż2í 6*ky˙őe×äűé‰j2ľ»W€4łîEć+ďÎj[W°c 3;ĺSŰ«ý=)ă;ŞF#9I`¤˘ťąűŐ#—úR?ćs>ńkübŰTŚ˘«U˙n˛Ż2Ô1‰f1hSôŃ|ޤbiü?,"NÄq˛äˇ˙N<Üî€\y›źűĺâ±oŁÔQÉBą>¸žeŘ{˝<ŇÚÖű84…ŮOpŰŁÉť-4© ?Łv+Ô˝äöŇ!żcQcń =ÉŹµŃ1‘¦ 4ď)ĘĂ=Ń«˘÷Âó wăئĄÁ鍴(;”Aęr$"K‰ş!hą±Ň§súkcu­ŽvKDV¦'^Nh$.&Ő ć7x÷Fú7ćŞ*Ń-g–f ¨Đ~ÓjZęĎŞ&ěeśt%9ľ»GĄj—}K8×ď(jĆU<Ŕ‰®p¤ĂíĽ©A—˛P®p Ž ňţoŐ%†'Če›Ó„š:ŃćŃľ%¤B=,ô’x“ «~xˇK @߸Ö%`c#-sĚőLÂ(K(\Ţśéˇ÷Éau¶üÂř> nŻć} „ŽI†Đ7B;¶Ö ¶ć3KIţŻ(ŕŠ#•ĺąśýŐ{Í|'ŇŚOävrB8Ô>ŃĽ)¬l–˝yri}=Ęú.â±óиçW> őç˛I/ŰQŢ, uéÇfĄ-°H… u˙»;¶N›V( ݆IĽEľ*8(W|@Ç=íşŐýW@Ěĺ›Î‘H§řŹÇÍ]8(QĺÄă˛yůÎ/UÝN¬˘9Ô,‡E0“Úĺ6Ą·˝IÎ0 ĎTś’KĄĐXčXxök–EńĽ„N÷×87a?Ł{ﳇËŔą±Ü®!á./CˇY†^†[oNŚwÚËr‡Î-—MĽ/ŃS*Ą/ކ9§OIÄÝ;lL¬‡zÎ5 ®Ó°”$í“8»“†A}N†ţD®…j°n;ŢŤůtáßńŽz6§äg­-ÓDť×˘ZřóŮ„6ĺH$"«Íăm¤uc•lC śG[iŢp7řKOöÔT+“>úŤy”K™‚ë(@ČÝ/§®"'YRě†Îwp9Ä…ŤžHěRřę‰űŇӾ屸XÉ’JĆüź~şP}žÎ\^ŞWŁĽĚ-‚Ť?A–}Ŕ˝Őr^j@ľVsyŤgăJĽxráx~ ĐthRěÇ1ýcÁľM—ňeŁAÝĹ0Äďî lĆi˙¸†íQwĹ ĹŽX-Ů9Еܺž îžý))´ŽńĹ2¸¦kɰ mtíZZ0˘ŚŁBµÉműłTůîp9i!ńŁ3[DâczJy¶Xc±O÷5Ő#J ˙Z%rÚb0„úXé#Ü$G‘2«~ÜřWW{ ΄СćmłĽtç|ÇşSvލ,uMa„EŚěbżť˝‘5ę”]^ÄÇ÷¦+:/çâNĆxç^•©<ň]e©""» îGë6ôp9÷^'‰uĽŕŻs˝r˝ŻýJoš°Ĺò%ľ‘i©ô3«%ŁąŃžů‘t±UęĹrOl×ę®ZçÍ…n’6`‚sabLŰES'® —˝2wřá˝TV9CbÎ!٦â a'¦ż[ö•˛ Z”3·“˝Ő"ęÖçĆĚ\íĆ)S’Üŕ]Žó­š­‘n®ˇŰérꬹX( t+Ň)´'Ş¶Ţ†„wđĘhA÷v»vŰwd–üUöčbô'¶.*Ĺמ 7 CąŇL 5ő(nSîĹßľż9Fż÷vęO=40|ţ–’?L_Ĺůö-Đř ‰ŔB Ģů1#|ë¸×ŰÄ oŞkKX qźčeŘ@Ű=¬ŔÄp'ü6řš)ş7ďß O#¬Ř™‘†k·×o­”çIË|›ŻE¬çĚ„{ůýéăôą˛ŃÓłż Zí8rÖŕ–ÔjÖđÂKĹŤTHňu{b’đFQ[÷J˛€üM0ój•Ë>%Ž2x@÷Ó`q¨íwlő­gÜŮĽ<§çĄâű˘×)ÉrSďŘŕŁŇ¨ŕŤXľř´®A„UK?Ç6(üŽŰ?—cĆ'^%ęÇ,F˝ů` bEňĽčŢř2Ęׄyş¬¶Kş=óö¬¨ŰDrvčlč^­;cË婦ą ęťŔ|›/¬gKLü,ş žřđĚ˝?…% L~ěhŠ/ŢF„WôźÎ}$Şŕ-…Ă?¶Řä! 15¸uĺ—¦‡,üÜcCŠů.r}üŞřwÂ~Ŕx'ÜŇĆE“A‚4‘đA_Ăţ[m°ńâŤÖ(cĚü'Ýţ$­ŽFÄ ×ŕ\(÷*č°Q]ŕ˘_řęőŃ~X0‚(‡YÂĘđJ…2š\<¦dQŹśHÎJvíśF+YÍiîĎŹń.q[ĎĚY íyó•O›ćňŕ DĘŃB~®« ů+ݍŰyAý~ŕ¨"‰bCěĄJşY sUD ł_°˘_:˝ţż÷śăĄ˙GhĂČ.k ąĺśŮŐŠÜ&ĽŁ)ĆžBAąí"J(“Vmëić¨++>"+—ŻkîP’çZ•ß®ô¶ŕ6łh˘éäĺú?•Í,é%„ÖZkÇ1„KÉĂꙬ1şž‰”Ϧ:$\ůK(ç hŃmĽj’»AŰôó…uŠP™Č-‰żwŠá€5ňBqţoŕŹć4^'@I6k ű©8çr5©*ŕµé0 ç rúnn˛]ÍŘ—®Ě2V)'Ńô…Kz*ŰŘ ^ÂŽv‡ŰĚ»ĆÉŤĚÜřh‘ŇéSÎśŽd"·ä¤gĚć©ŞŁ¸·ÂĽ×ľZ©óMńźý pÂĘźŠWčjT¤ěă ąť¤ŢŚ/¦wDe5z¨kw@y‹Ě@YgččĘŞYßü¤Ő/ tŐN)ĄçÍZ“Ě©¸4aóN7ŕŘ<3RŃŕF0˘ÍíO^p7ćąËm]ĺJś îšHßZĚHĺd›ťT,wl¦_ńo'nh6­6ěé3ŠâD„fwžb+TľmŇ%¦%‡ŻJtrŤt…Áĺ“ŮáläE‰¦^ś˛Ď˘ îŃő{úkË´@˙ľC` űcą­ l ­‘ďĄ7ř ŽĄř% Ă9Ůʶsr>˘áŇ#ʉŐ^ť d$¬âV-yµ.#.X$ Ď`oţĄ&Ť´öyc¬ĺ‹]2OS[[<Ë÷MŽ:24}ö|ćö?ăc,Ëý#A›X ¦©Ű®Hyżş«˙ך.ę‡?hůněJŁ&ă% h.[YÄ=‹Ç‹ĐÓvĽŇgĘó—±\ĽŽp-VŢ÷.Îyµ€¦î–YéfĚđKžńö!ˇ„2ś8“¨ýĆĎÉĽţÍZ䡽ľ¬ F"šgxË«:űţo DÝĆÖe9˙;‡ 0¶ ‰ž±oX×,ľ9éäĺ@±ęűĐÇőÓö>Ë—¤C=öo×ÇXUÇ[Ó)m%ăćš­Ťh h°¨/Ć+ŇLąüÄ„±(jx(€„]aßĆd—ď.¸s]›ˇź w@á2ÎOĽ.'÷©6t?F=ěN9)Ű1»ł"ËąŚK`˛óEíšx™ë†řMÉ|Ą Í«ôćđשá§0căŠ9Ł öÄ8sďŔú‚đµ$6™-b§%łC#dö.7y—^şO ďIŻĘAäţÉ 0[ „g†i Ť“Ŕ,ŚVbZą•˙•0zH¤°˙#ŽdůÂHo…Á8š<Ź"ďç(&¬ŠÍxŃáźL­°†Ez˘4ś•UÇXs'ru ‘LǤPŮőíB\`Ş=ëtćpű‹ G~ŃĎPD‚ŠC9ó{ĤçŇś'Jă95†śźYaDŁ~Ž=T•·}ŠŘĄ{Ĺžűą‡˛ÜÖŚuwIŇP›1ů|&ÂĄ(A˙ţŻĐöcžëÎ^˙n˘µş˙Đ>EFoş1ľŚôyĆ  7ě¬B˘TiBđOdËĘƶRĐą«ÉÂł‹Ă’T:áŐě[6aĺ9~‡mPti‘ęs8×k×e‰Usdíú2\'§ ĽV#=©4ď#Ş#b†P˙6áÂďôđn˘šÄxÝďÁ 8ďđ5¦űG9‚Łh3I]ÁVŽŘÉS ĆR_ZEÁ…Ś˝}ÂNÔĎßćl,Fyý2JŤPeU˙ ©čręĎŻ ěćŚűůçžýUęÎ5Ăě˝üŽĚůlŤ^@^ď«ŕŻÁcâ<çOQq=ŮÝ×i†S}:0–záĹMÖ$5JżchWŐ!ĺtÂĺuęŁTdř$ĎŘô ś4ţÇí ß™ľfĚa_Ł"(¬I…ÍNý»Ąđ¬­&·˝oäÂŽ3Ă|UĎ~ŠťÓzb]JˇĄ˛Ő±˘UŻ˙řĘ g©×sčYIą®Ł ë 9ëUžM=ë;ܲ*‚)ŧ—Ďp»ăĽđOŇ©¶˛c3żů®+Âp±¬ť@ îą°‰“EŽ«$)Q7”|[ ZźŢ˘ăwóBYŇü*~şűżÍwvadŰđRDý4˘ÜĚ©oIŔ íŃöŢ·\3QT˙şâ4ě:ő›t'?ŁBđvŞă—5l˝ÄŰUôÉŻď.ń¨B§|»°I ±zS¶Ąoężę:X}±̦ńwŚLÇb ‘iąĚŐ•IĄ„ŮۆŢî°Öe¸¤V s§6Ča•Ďp=ç¬~O bĘżÇ& ÇDÂÜ;–UŠ\LĚ| Ź€Ă·#JĆhGĚç"üŮé °g&şťĎ)˝±? Ůžu‰€Ş¬‹`1CŃ;Xgk K9y*ns ·†GÁl7e±ˇČ@出YĹ© hđ.§G>N)š…nűs)Ůgýa1Sý0(3cUľg™©ĽÄ|Śx8šđňńÂ'ŞRŤyň[ćÚ"˙—ś?_źt=‘bÜťR%)uŞ"ÓDcŰܸD†řě0Żkđ†÷QEé˘Ę· â©Ö˘%*•›í;]#4$KW‘řtçáśî”TőýŠ>żâöd,čsŹ K=ş–Sµđ_sđ:–÷1'‡ËüĎ Ěű?HG(śÂ‡×°÷Ĺí©i{ÉŠ5ĹŇń®Aü´ä™żĽa¸°q({NHëŕ¦ëŐ€Ż˘čů˙o;mc}ŠŃl¤&ďŔŤâż0ŐÇ_ÄŻ¶ň~h—5íô}čę5vrş~A’cŚÁI¤·}®0żźs°;"ür)‡ţŻ©|3@“W¶|üälťoŃ%č±§$L¤˘ŕąŕqô• EŐ‡Ŕ›ôa5ćżŢ„‹±2ĆU:ôť&ä“\IžCš?ţSiߣŠâmżŚáGČÉ5Éw>ěnjnó±0ą®/v…?‡h" J9q˙Ó’Ţ9¶‚Ăś¤.u—ÄF©Ě/ŽmÎşg—qßš!•ót 9n‡n‚^˝öYL±Ú˙`Úxí9žłŘBô {ă!ścm¸.Ŕ-ÚLÉÂŢŮ´÷î:?hyábÂúŁdĐ5}Wm@x@YłÍ+=şÉl˛ňÂec']>pyůDĄPYâAÓÓ“¬JtŞžÄ:ş]‘†ˇ°–s,”b—Ŕ•Ű•=Cö#aUk,Bµ(ÝŞ»Ľyž§đO,[čë@=¶Ŕ Ţ5őă4Ëźť˛xŐ|“ś ź™x“ŰŻŁřą"â“Ď« 6g ň~Éč  ¤$š]˝5xÝ©ŮaK2Ŕݱ•¦ä‹ÄŹŤp€"ŘĄ¶`­ĹP)ôŰ‘„H¨ÁâąězĎnD;»w© ăÖv¬mťNDůEYDźqFTŚ/E2őŘ,oľ ˛óSRŢő9şëšor™‹Ě— ń-´©ę¶ňÂč|üiv®čĎ Ćm–ś}jyL=Ĺčž®pŻĎâ2ŰW üBP±FŢJ‘>pŔŠ ôü]ŠŕF_>ÎISűąđ‰ÎY$Ĺ=f]4„î"ZQ[6ßO˛–ŮťëŘ‘W©ˇ"- Î{*ĺRŻň~‡×^’(6ęĂź—Ę&-ÇŤŃ0Ęúcůy·;-CI‰ćÔŃť?±š†ÚbZNĆ€ÖăłÖř4Ţ)”ť[‚Q´“˛ŤüqˇäÇ}„ASL$É?*)j ÓřVQőÎwÄp`Â%gŘ ˝"?ŇZśmŮň–ĄĂőĆ´‚t$ äTąfoŔ~î«z™V‡™á~,ď˘ÖÍć«F˝ŽÝť¤Z„-'¬Ľ–Y>I¶Č˘â·˛0ő™”TXň ĺ’"« iÉŁo<¬Eňy‡­slř°ž«&*6s\˙ BßdÉXŽšlË..M”ĺżőCÂŞL%'Óˇ[%Y«ťt¬NXÁ¦ősÜky—âśž–ť!·Ŕi{‹J>÷«ÍvßĎRGjí`Ł˛Âµ†˘ďůč¦ŰîShw,ÜKr›4Ç}•^`Hz«HľčţŹ®­řqS$2méJÓ”Ö&´Đ©°@eúš`E}´;I™iWŕřz„†ę,™"ćZLR=zµD)Ůćź“KZ°XÇ{îýľ\ó™6đE—ĘúR—§ ¦ÓÉŽÚ; ›Av8 )jĆőWŤ:ćKŘ’¦,¸a­“´Ónc†•…·ş+‹‘Qů7·çţĽßRŔ_‰vЍîÂ˙ëP<őSdł”`ËTsđܦ÷uů2ý3 WŠIç ®_ě·ŰŐűll§1׌—•?ĄĆíÁö˘ŐxÍÚĽ ŃL“^ í nµ|K8ý„î9vAßL¨Ť ůPĐ…ŰÖóż¸aę; ôÚ¶ +%Śwé¸xš€xĹŞÁ?㫇ěľŘj#ÖŘ"yľo*H‹ń;SSߢ\8¸Ź@ß»rňE*Äđ“wĐ^ ~ź=%Ý’ÂÔŚqŃbfá§°s˛ ‡÷«zW'’Fg+\Î&akˇ üô`CAH·2×ŕŤeĺsÄSšćŃnŃě ÉzěĹ"•ßz55¦9ŔäCýA˙ĆÚş9Hx®ßÝuPĎĄ‡¸©c/Ý)ÔĚt†ŕŹyś.$Ţ ¦ţşˇg.S0§Ń7\Ýřż@R\ďĘF}<í`PĆ'YŰcŇ0ľžř{fęg{-ËĘ,ż, ńo‘+”Zb |Ş1,ÔEşÎTČ–# «ÄµśÂÜ?ąĚy‹ ý˛ĆmHŐ’ýRD꺎jĎűµµ ˝‘ҸI\ŃK—±NňL9‹§r c;˘ÝcżŐ.ËŻ?$[ËuчőjZŮQřaE2ńë;Ś’Ů{RÚ„dîŢJ\ő·L¸#=†ruj°S0÷b˙xJý˙O)Ä ) ůi}™ }§zß^Pó˘ jŻ‚ú«o&î´dýNŐ„3ř ěŞ^Ői~ţˇaýÍ%×ń[čc=źZŽE?E妥7çíŕ-ŮŃ„ť_=ŹÍ««A†ńâwx•î` %%Sŕą.ôţÄPŻľçĹkżăěÍEĹ/ëţĂYYu´ Ď‹Üű˛!9š–d^Î9Si«î(ŻÝnŽŚ3»:ݤšSłŇîApő{NmflŮű‡‰|Â!•¸WwIńŻßp!šo\Ň7Ë<é‹’˙9¦YÚ¬¸F¸Đ:‚ľá9Y7ŞÝ=ţl˝đÁ‹˝wć,i|ćŇO8îgAŘ 4)Š˝l>ăĆÍç'‘Äh’8ăĹ!®đÇrUf8͇/^÷@”«2Ë8ĘíÜŠ¨źů×KĄa˛ß@¦q왳.µ©vqýí(˝uiń—Ą7"ŕ(Ě’iŠľy°×ĺą”FX˝=ßD µ °‰¬Ä2ĎěÄ€ Q»l ‹Ćbű:şÔ}‰˘h"ßE€h Q•şÜĆGí!OňKăőć9ř<8KŃŇ:ÉäíT»Vô‘¤–Ő¨}ʍHč•*}fÍĂ…˛„˙®Đ#G­‚ śo—ą|бő(QUů* ѶB0©zC’µßĺ*ŐľéîROŰ÷†lĎ!%V°Mw OŃD[[o4ú’Y fo;)…Ľb™Ľ¦4’0Ă"3?‚熂¶Üh4üRîZU’)aVXą tą u4ŃÂn˛lĂoť-ě˝Ő§ Q~.R!‚n±Ę`8^6ó?óĆ@-HüĆ ‰Ë¬vúĂáťkž®ę¸cR`Ş–«3aúŇĘ“ů\Š:ˇ¶Ä΀-ż \ĆŢÝ©µŽĚGé|#úÄ–vŢěi4?Ţ%śď̢ãV¬µćô§•3›“Ý(Á† NÜXł<÷Ľ‘ńuÖé­;R°1y´Ä„’_ů8SĐÇ2ŚöelMAőG»÷ŕźYÄĚwT'?8Útqbę$ Aľ26ÜĆX0şQĂÝK¦Ô Ę0˛®°/ĘäĎíŘ…cü÷űb\ňňxűa†űMKü^â/íđ×­¶CB˘Čд˲ö~.=—ĺMĆaĹÎŇ/†>8±1cSĎśGé˙ů÷ĐůHEogĺg -ĽX×=Epď„Kv`Bäur×­äŃľýĂŹţ©séŰĘrsęĺ*¸vqů¬Lđá$›Ë±3ěvé>úŇďtś ŁŚ»śr9¨čO,’Páť‘¤ąG‡ŘŃmWb=ëţI_1«˙@라Ö(üKŘTo¸Z´j6ýÖ°ĐďźĚ'šg=o̬-›˝łuń`7V™&złÎ:V3•°Ë' V{cuś0€óGĚĹîŰ€ oţ fYď{@©‡>ÖŇŠŞąä ´ żyśĚc‹ČAYĹĎmK<ĚőWďowZ3B×Č)ąŃÇcrR$?‘6ś1>Ç*í—Ž«…1şĂI¬˙˙ävpZß\HgZꡣ•&'™ěŁĎ±¶5reün°­’˘>ÓŤz_#Ď=)»4 ĽĆw~z[É1ßjŠĎ豈čP]”Ą®¬8’ŮşťI-˛˘yŁěÚ:̱…^3Ö¦ŢVŻ\ndśČX8ćŢR(-k‹óO°tr´i7§Cŕšź’ Eő‹Z·|7›ŰEóúd«ÇW_l’n{o䬲© (aa›mµ|úp_î–â.V•&¸&L{jFKđ“u¬Xç”ĚĽ[vľŁPý'ó_źidwTľ.â)ž®4Źň÷aöc N›>ëęP·~Üg•€­î8ŔşĄ._ `ądŮż•#µ }‰°˝á™˘Ë+Ş(J)'~]%[żŇHű¨+A޲ď+vëËCťÔ¸aä—$x ]÷n#‡-›,Ą˛ś+ťáKOcňɸtÍ•3çWʰ“ëM=Ţ[žwěŚGÂWá·¶Ş|eO,W~L‚Őćş8€¶‹`Z/=˘A¶bŕYcÇŻűX$ˇ¨c6ÁµË¬[†IĆÇśŚë3•řϧ%ŢËIť]y;făovłőQqËľÓ† ‘µ€pç@©”„Ę–ëŽÜ 6ř˙Lsř:ż= p?Íšs3·š1NIĺ@™oĽäq&•¶>rçćěÂ?ÉŁĎžaNź¦ ü—;Ň6\5JuŰć3y2ĂáŕŐŃ2®Ĺ¨ŚŘěťi/>ŰąěxłçťČp #=á©˝ĽŤÍÜ5 ’ýĽ#*+&ĐůFGÚ ­âţś°úwyC•áPä çňމł¸¶ô69I®‚?Ě—Ć6±c®O˛:dúÖJýýűĐ~1ßýăćľ4Ęv‡ySŽF–3&ă}©±–ë­źŕ—” רř±˙Śc«¸pˇ6JE°”¦ˇŐć`ŘźMŢdŚ=+X:Z$¨ă<şŠ!ş–ĺ:Ţ 2mˇcÖđżąÜ ťy«,ö(j*ÝĐ$ȡ8zjţ÷úVzNX |mŃ< Â0ŃĂ«57So‹!ôçJřĘŁ<~`m\YPN‰ÖcŽüѢAbunťg‘ň&2ĺŘš—€{O4¬ř/:˝~¸Śéî^÷ŔÉ`¨Ž˘3\ß—ýßk˝îzxŮü´ČU˘Ç ·®.,†ö¤±˛AŐCA÷eJ™ňXiĎ+ ¬Ľ±rä;ĂĽÂSl7ó¨ůF¬8¨żk”?Q(±üúr]`¬tÚj]"Ćö” ekÉü‰lŤŮĐŔNe¤˛QŽ]yqB/›ňţ(–ô¬1}e&·›Ť±ŃščŢ|_€2°„„´ŕ’˘äµYŔeo3 4š(ä‹Ďżß_EL†[DĄĘ,Őš¬µ ±Ş>ÖKqŁęö®oč8íhWeS?˛F,›S˝oeÔpÍËçťAv˘¬Ť}Ť`˙ńűÎĆɇç“p}¬“=vJ»t\>7…Ĺżż;ít&OQcXÖ8¤’ď{čC¤*tŇĘŃmúĚżO‡7 ­°?l1XŰ 4VŇů='ś§ó÷€¸ĽOa<Š•°.îVĆ}ů=)OHŕ1˝Ú´Ď•©_“5 DUó‰F·5$FH×} 3ŹOş=nëä0ę|L*|Ę´zt­‰żţßT28Ű€DjGšŃó˙7Łł}Í`#ÓqĄ\BŤAă*LV"gnÍÇ^g]QÉýźfŁ˙ťź|.GŃ,‰óşyć"vŃd„ çĂQĽ†«$EÁ\n7LY±ÉRThuďň}Ń TQ×Vž«śÚ5AŮš^úN=HäPA(ĺ;kô€ĄTk®9¶Ň=€ÜÝq§‡¶™8â—˙őľăíôĚĎěÎ@)Űź/ŮŚův9Ë@eÝž4hXšŁ‰-ęF‹°§ÄQÓyŽž'ŰÓ~ŕ¶˙#[%:r°[Ď˝!ßíVĺ¸8¨ˇ Ě٬ R€ čG0ľĺÝó H1S(–ĄbK1î- I‘¨ă™őod©‡!¦¬^éöďÍ| őpŮŘ ›ů(Š1Q·@™ŕÁâ!`Q…–{VYM\śs‚Ţ(©@¬ŻHSţ›ţ‚ü}_BOFí^3řx2§Ěý&ëôÓ ;h•|yeb î8ńŰĺď:ŻŐc…b™0L+‡fZůüȡľY~ŐWÜGżńE Ý˝<ł(—/2u4 $;“ h§Vü†üŰ LäŔÁ…Č(bú×B€bX­°4:ˇůúAףáo× “; dŮSë(!bKRFŃ´[[§b«şéćaŽ ­Ť—Ç«(űďRu*«je'Üm+ŽH~°S]y?¸ĎăA÷ĄlqWoz mÁ´–םÝXţh+â4¦ßź· ^Łt@’+d›Żň.Ľ†óÄąąNqäĺýh@A¬d÷ŕ24ŞWfŻ»©>8±Ď3|Ćßť´ű5,ćF`€@|&ÍQS10ďÜÓŚś»ÎX”Ć {·‡éÄÁM\ÝÍĚrhičŽA`·`ç aă#W±Ht>Hqµçë*Í­ç{Ě8q?´÷&:©ź]jš¶&U˛ż ŰcŢݵ)łŠżšĄŤÔĘVY•ëňti^] AyăZľvK$4š°žáŇpl…6ZŹÝ÷P·sŁl$ćôş“Ęh“Ů揑Heă÷gVXyĘÇ„FćZ»L 8n¬|öĽEú¶.4‚>Ícůj˙­—)ÄN UÂ?)ŚÝÖ1˝2—^°‚Ď|żLŰ˝6†.µŘŇ•Üb&˙ăm1gŇfń» ›I-­ËbKč7I?HźůľgĎ€}IC¬»ÄÔL¶|Ě"ůôi/śÚŹkéůŐlő}ÜŔÖ0‘-ďX¨=Í”í$&kZÉ{>•Kú(+±·ý8dË6l¬řOŁbG‹’Ł“ŹÚćď ę2ÓŽ€Eů„·ámüPD¤u´âţ桑{‚¶gM>eÉf¨Í\—ŞIôŠíâ6ç~äĆzČ=–”ż4&Ćľ2—ÓĹ‚bŠŤh¶â…ç߲ŤtÁ ç®4Ëßo´›&÷ôÍ_Ä3?m[üM ÝG’ît± cç¨kÉ^.ńAX2–ĎsĹĄNTȢc é*J,9pNEŐă$/ËvG—˙z¦‰.ÚN§đ đŰĐ{ŹĺLt>{žg‹rQƵÝQT[ę1)ÄČcżąE†ŠD˧ŃŘ ËÁ],q\łk#ÍČósD Ű"•µN­'“'čČ?v:$ĺDQşvUl҉šhßÄT5{ŞÂ›đ釗d*Y`]˘rçJża-š:uň?ęĺ{›6ĐÎľşQ$Ť-óeá@qţŢH^…•:wQô×ĐţžĆź¤ăˬÄ`2›ŚzÂÄJŐ%ë8WO:ęoqť ‘ůNVwÁtŔę˝ 8ă‚S•©îßÉ`ü˝P/niÝě]˹Ù ™‘݇­Kě«ä,EůřáĽg%u˝oq¬őű¤{É‹—Vó:^Ő˛3t*&€E`¨şĹłćpKl^ź›`_ľeaŔDňT[ü ’ꀝxč‹I(Ú‰ąŤ×ź"€‰¦Y4Ú,ó3sţY5€âIpÝhÚ~ęţ3±ľMŇŽýčuéąÚôľ]Ľ“Ýj;{:őÜ.»Q,űÇ'Y¬HŐż‘‰ËîµÚç=ćçýra¶ŔNĘÂŘěËs ř$W/7ăCr•Ö«¬\’Ň]ěWŚĺW-…l©„7ĹW>µűy.š%D„}‡$‰zÂwÓi•n暨BĹ0žbr}=ĽŔUšĹŠA&•{"ÔĐŐÇwy–k#ęĄŔó»Eí™ žµĆÉmNmŔfmmÍ #ÉĚ7;Ëş·±(§Bb„’ÎdžMâ^ŚÚ|Ăzo‰·ąř&4üŕ‘M}¬ę,•¸€Ĺ©‡k/¬DđO3˙nCµyvZ'uţĹCÉ)Ńu6qĘ!Źú¸t ôló¦„Ë•Ő ?ĂČk~€«ńš?šŘP ë–ľSŃ–Íý‹Űf3IŃÔŹ3K&Ľ§«xF>Îú"Lţ„ë§`Łçĺ@âĂíä;XĎnůCu!–BµŢĚR^Ďk˙žŽÂ{Ďď,lž ¬OŻ+µD»8żó“aĚÝ I9Ś“gű§=»§Đ!ţ˛sárÂň®ç,`ß·¸~A4%űÔ!¬b5€žĽaĘPőłžłS5ÓŐ=ĆÍëLů“ń,ie¶:»"\k(ő)çŁĎ˛Sř2ÜťĐÓÍuL‰ýĘ•yű+¸–G` TďĹP“(ßk×{÷¦ěoĹ<đý6x[1²Ve–X@<µ)Š—´9˙VV„Ö}ĹJx›uŔKh*Fµí}1V}oGZîo#|7¨»‘`˙}şŐpkFÚ…ęöšzáß”zžx€Ă„)ÚXĚÓ©'Ź|A?Ŕşň$ \Ő@4±ĘĚŻ ô6z]¸Ë’>W‘zŃákÜ6DŢv S@ˇ#-b1}3má÷łé`Xˬ8f™ď)ľQa«÷B~›Ć|R· I 0Ëô=Ď&×?meë¤06UްóáÄĎ‘ eŕ*ßÁoxh~ i`6«Y—đwzď‘ÚÁL12kÔ‡ŠŻaĹ»I3!ąäL”ŞÁjFá±TĘŕ!Ó®u2çĺĘ«k0*úFĎ?Šäiy…ŚUćĺ5ÚëŕW@đ° Uíđ ;Q?ŔÔVB{#’QH,ZhŘóĚZń7•n]>Đ>@/ÂuĆ<F‹îSă즀 ÷–E6w-Uż)_©F!÷V˘ˇËÓlćżc|ĽgŽK>‘銖ű±~Ń‹ď 3®`– ěá÷h%Ľţ4Ł ÖgLšH˘đ•Űp勉 Ú2¨íµ!De Ť°^pŞ‹×˝ţż"ddÁăĘ|ť{tc˘6Â{R1ăíĹŘÔfE>‡©Ţ°–•Ě@sň¬¬BŢBĹű˛»’Kb5î#S+ÁşG™"J `cîB1Ę6qnđťî— ôiÔ<Vá© W%J_z®ŽűĘĚ˸ÁT)•[B“Ý®‰ Ü‹,$7Ľ Âlí™§Ç.Ňt™ŃíŞ€ÁtţpŽ–o—HB˙  VŐFś^Uöi_ĺůá9h˝g†¸7íáŐ+ălíú)ďż…D/ŇCě2ŻnÝ×ÔĹ77ď­ß¤2ť>“dzŁtdÔ™ˡMÇĎA3ą‡—âsb]ŻBAAżv>l»®ůĎšôW!Â6,˛†gă>Ýn ”Ąe|ţ|'TŇ…¤x7i ㇟Uiĺ2PńÓQqGrl…ĹÉ_­€÷Řśťű§ĄxX¦ŇČÔ÷akdÍĚz’µˇ·2Nü˝tĆ҉Ő<-u«Y;¬?ŽÂX©ÍfŰĚ ±˙¨]íR52ëę…>tt57ÉÉDĘ…ŔAé!_ń- r»LvxlÓÍFśuS«,M+w76Ąg"€ˇůcH~Zcn_ Ň^űăöáqM5Ź{$P©ÜÓC+'Śč„zÂ<'„ýí‚Ő<†! ô/µ°ă¨}!Ô€*9śů ŘBÜ·}ŹzľlÄcďz¬ĽnÍ.Ô|‡5§8  ü+| ¦™ľ%dH…6š˛Ĺ^ˇ”ôq.=fŕ3ŠÉË Rß……jgdžŐoŽAO:ęxMś} ŢŹcŹÖZdĹGôLŮw^RçôrŠFÁHs̬ç"ŮÂü>]˘LůcŻ=Ä 3«;OK8T÷§µ^rz´Y˝?Mx™‡®•IˇvľHN©1ŰZ†BWžý>ŃŽA¦Ç®=î×’ŰŤëg–˛ŻĚšŘXÚÂ[ś.Éc2!Ž@†9\©śädđUĽéJ«ôa8c¦˝:l›«ü7Ŕž:¶ڏŻl˙ă<źÉťţvĘ5Cn™•\†úŐŐo]|T8¨AH&–řgyžB[:±RŐßO±:´ âÁ' ĹÂĄ¶ëCË äňĚŰn•{ARj\Č6¬Őşx–R­Ł‘;…CZŞW/7ůëś×B[_ćŤnťĽ”mšát8ů*“”qŰM®@Ď?=M/\pe­GsÂŕµ!ß±ˇąąTŕ†Zř0áH,ËŁ[ˇÂ X.+˙-‡oŞ%Áţ7¨_i Ý=ÜŞ°ť÷Ăü“*Ńć6ć:×7šUwf¶b·} ;çx;.ĐAW|¸ţÖ2^ł5źŔ áâĽuŰÇ@)2®ŚÜuoĆ |5‡f¸÷?>xŠ(őÁÚî6cřÍm.žK {ZĂy',vů?z‘ OŤh …?!ˇź‚÷˘5P¬N÷0t×wą´`§Šc3yĆ5‘§ W Λ[®Ňh|/.đÜą4ŞÚĘĂě“Ń{[ŕ9~ÓbB Jb D-TţĂZĘmY¸¨ášCęôË0C’ő'e÷ŽÇĂSY UH돩pG®Ţő÷±BWeţOŘĄ§†qQ·lcÔM:PŮj¬0Ä+«Öčśm2÷J+Ď«Ľßçľ^ý>HćŞôâľ÷ęUs`DQP[ú5`aoÉťšKRцţ)EŹ&‘ĺxÚ28ůÉN­úo5sŢd„ë‹_'WHgĐšĄÜšźQg ¸.ü9ąÄLKM>çíĄŮ,5 NŞÄ=ąĘR1±ÇEO(A­bJĄ°A…2–y=ń!·r ýčšŕÚę‡Ué¶^53Y«N%éţüG˘AŰe1˝}yĄy0J2$v’ŞiƤĺWŁĽV%¶očô?ňÔ$^˘¦fŐpŹŐ$Ľ­0´ß›ľĹ¤EÍ» OOEXÎG`đîŰł]ńqŢŮ@Sâ‡ýťťK1ôŘÝT“ĂnL|‚­s.R˛őřŮ ý¬©×č‹n IŠ}_=>‘w)ł_ŮAOý§ť/äH˛ u©‚uÝjâźś†1ĄńŠ*׍$¶3Ąsµ…K)ćţlÓ€¤9*KMź“ŃąŇ;&ię@8—Ę_9ó·|ČYĐpÍň·|8{ę" ŽĎ›×ҢdŃL3a §Î˛ŚŚ«ĄĘ“§›/‹J„-7>ą¨ÝÜ[mÇ6‡2ŃÔ!đHÉMÍ{XáŚSń±'Č|;Ą±ˇ˝·âˇ»u:Í»ŐIúW7‚FĘ ďTI~*ďě ŕÉ%4uŃŔžą ŮycUM¨pÇ"LWéˇކ:™äő5Ü)ľYÝBčx 4˙×1ž]_ÓÔż"ř ą{ćÓ#żďă¨4ŮÇľiҬgX®ĺÉG]1zúźŢN7 Ş)×ë5ź Âëoá—Ńłéoŕ‚mpU'×Ŕ^Ö­Ľ¨ ©MW^\Pu5;;ą2tOň6ĂCůP˙ @·›4ji 7V:“T#% BŃx¬ÄŽ ˘±˙O—ď)ôt:Yü%Ć7ę|’ű#ô,j;źY2ŔJĄšĺ­=&t§… âKޡň ĹjŇRXŘďtŐ¶ćHQ~^ú'wIXü;‡PŘof@‰ôĘ{ű«„Ń—&ś·‰ńń|×R!¨Kˇ6Űd3oŮ3»BŇ‘Y†°‰ş zOŹýDŹ´E‚lFsÁ×Rif‰s}ěÍ Ă&Şšš®ë8AęŇ]¤ §B(7ö”Şn%ÝŹ®ŤŃÖk¸C`żîŮYg‰wB,$B[~ńđ˙řĘ n$…ŁoMŞ{[Ҹ»?îÝMb‹˙ˇ:­”!)_¬€4•Ń+yűň!IľüŇ'Ż}YśsEmT‹ô  ˘Ík†–s'­‚ qbš:Y‡ËÇV´…E%'Ř2Mîř©çŃTˇö÷G6ҡ¸ö ÎÎ3čĘ[d’r8 äJu43 |ž}áWĐk8DDȉÂ1 ŃG0 zYgŃI46€Ç;ÚcúŹ NY!\ Ç9©×Ô9ŕő™IćźĺË”*VĎ@Đ-Ť[]Cˇ(Ćëuĺ~î3|„Aťe{nX¨6 •/ĘëóŮ™6Ýfă_U’ßţš kWnń%źyzbŃRĘ“:ź–”BSO)Ú™K,{ÜŚđ¶"z´hŤˇÜbľÉćżĐz9CS±PŕóµwŘ}µ.BJ4žůR@ÝëéşvË˝ţĺ9’Í%=ă3xŁnfgWiŹ·LůŚÜyěŞUwŔ´¶@żÜ{_kýĂçĹż'CY®gŞLÖ#2ö âY@ĹŰą2h3ćJl¨ÖWä@čdżÝ”‡ŘFÔ“ÚĐ=ërĆ8”Äóó$ĄD ´÷Ĺ[v¦q cr°:CŻ@L¤9š9,˛XĘ»Őß[ŔMč# ÷î˙˛‹ëµAżWX‹J VXس㼫˝GBm¸R\™@€TÚţäőĹ÷źŹTŤHÖw·ý!Ł“{}5§p&ʱG¨Č­ ˘ă˝ AšÁtşQŁŇ2$…?a.5˙Evč·ęÝE ăMńęÄ=Ý¿ُ”bÎR+Y›¶A hCéM±WĂy`˙ľŞędçž»LSv­ĄłöT7ąÇô+3ď„á°Ú%ĚN&h Ş6ž÷uÔĂÔÉ6Bzw âť-·$4 *Ywň‘ŽÔŁş]ĘÔ”_˛?Id¤ ĹŘŰďĽNDžčC„9É’F—ű'pŹ™JĽĘŁ~ Čy&`Ď=Ŕh>t87ßŢő˘Ë[*gŇž+#…ÇÁ[Tž±¶Ü™¤˘Ťľ¤}¬Ş¨fĄćDM˛*XŹŃxôw~Ćx‚¤ú[É­!1^Č+ăŮZ 1Ő{ĺúlőO? ś˘ęŕ2^$›Ńűܷ\ç ű¶Î™ľZpßŔ |vÄ2Ľ«ďK/S±Ku  Đ>f–ü‘“†ÁAcôč).Z9™Ď)ŚFÉ*^ N=üs${©ĺů±·yo·&›ý•@ŃĽ Ç­GR./í?bP:đňš3tEíś!¨”•~ŹÎB'†ŠS?őčąCnH®ł~Ů,,€ÂüŢ–1*„ďM} ŕňƱЄ,XG™Kń·ŰľhG}CáŚ8ÝŰóöOnânÝŇÚ¸č«w‹$ţl3ŞĂ=ŇęnŽ–i©gGâqJ ßI÷KRĐŇ´Z|Ť=iĘéę…É{?jŮ9ĂĄ_ó“vŽÓ˘‘îşu5s#Z#[^Y6ßyń­ůý­éŃ•ŕf6z%Dçŕ‰ëâZîŕźmбGva Ë®©g˘LšŽtč6ďJz]Ú€ŕ•Ő&?1%óť–šء;-Ť\ĺcI‡&ŃíĹşH łnQ¦–h)Mść1¨Iëşř˘AŕUcLZrExČgbŹÂVLČÂHTssĆĄ˘tĺŚ#Ű7|áÔ i)±7_^®NŘé'íÖ¸­ăĹquÜúľ„Ľw‘ź?C)]ëxR•ŁôgáöżĽŹýY-«Úď^O}ŠČß^] QŐnŞŔž×S7/Ţ,6 źůţH@Č~».䦣Eiřu.~,ĎTí­é »=¤@×¶P+4O}÷ˇ˙ÚÉ­Ö©Gńöżnşâë(ĺÚDÍ:5‹LKýxZaâ|žś JaJAę(=čśeM×?˝ýÝĎq*ÔöBjđ¤–ř¬KĎ·?őę‚ŮżůÂę` }pK˛öŞ2Ď– B mŤZ–ÉGzGŠ0°ż87»Ý{‰{-ëoŇýÄâégůŤĂŻńČ?}á83B‚δÁ÷Ýßléťß˙hw÷s-çEÝâ=J€–ćsđ;=c•–%ł]v:ÔJ:4zˇ§ó /WŠ÷3¤–ýt •óG´Čz,9ŞżËl‘AqŤŔJµIltĽ˛™5tFSnF@Jbč·Ý$4őOÔ3ô·I3+‹[Ů|gěňéÎćű=·#´N<g2#áo¶­źÄiÝU«M!uô[m4ż~9ć-6m衹 ë1BhbĆ6uň  Ôb§ž}V)čĎŚonҡ•[ÖĹ‘ű· ¶#(d’žO otĐŠżÇřńShŰŁµÝÇE´a¸LOł].Z¸äŐríśáÔÔ+b>˛NŞ"yY`Űk×Tęľmú§•drę.[•ßUYľëöť‹FpFř<´ý'$uľp¸éwM¶µĄŞĂÓ=glEQŕ3 BĄ‚G2ă]kˇ¤Učí‘çŃ  ČĆ5‘ńÍ(EşÄÉ3e†ÝşËßöŰžŢ[ř@‘zv;YšŔúbŞă“žÎÁ&nźűŚ«…ąl♕ӻł0ŔĐPŚ,ľ¨öx­ú¦!Wsüm¦řç„ýě9̑ҹĹ!áÄfż-ý«Ěyx]z;ZőY OO°A~˛xţ$0Ý–§Ĺěhźš§|ŕP !aŞ&ěśNÄÖ“©Ät÷éÄ#,†ś¸x!ńrř™î’ö űdÚ8|\›zÉqś‚Ă'K·0Çéµ ěíłḻLčÓ‡±5އaů­1Ł˝ü´Yô0)Čh™ö¤( zéh”ç_gôÂÜ˝úă _™ĆIŁ=|‡]ŤŔBd-ź6¬ż:Şď…#ßž˛+IQ†Ę_ďŢĂ@ŞíĆěŽj›N&cćpEÇmŚUŹËąđ& I8oF#č`_´üţnµ-Ă´‘HCk§ˇ­ Í–pŮťŃÝŽńń”€1;”ďŘZ¨őŇÄŰâ Yp. dfÖZçŮv>NZ˙p…«čt :iú‰ŕvĎWŤ[/TóŽU’ťkÔšv“ä'čřK.=‚xŞ<żTGiµ#?ö‡÷dŔ®÷Uć3OňŮ­J‰vÖËö^<ß ™›y>vó™VWN¬ţ˛Z‚úĹy¶ťˇŕT°E-"2yć5v 2çdĹľÜÓđÚ@Ň7µQ)ÍZů1„z°Ą^AGĂ_€z1¨.d®ľ{nĂa{”]¸¶iUmQLTđĺípÝŃ’wÚ}Î’Óůiĺ{€}ůÓÝíěđ"ŠÔr¶Í?í9OäHíu“Ů›ŕHG k´gBÉż Üň|ę%îuMˇ¬E˝ŠŚ°đžzýQjaĽţ}ąáÔLD)Ťł]ůš°·đđřů®\¸ô„Ľ[Ů{0ZŻž\đ ‚@CěWŃ™ŠćČ ýf™řöl‘&^–ĎbaęBDlÂó3+u''§p^…_xźŹ ‡ˇC)($ľ~Śča{ýpGΨĄ˙9Ęš¸)(žőçć™`N3X˝‘wÜ—ő™Ň‡Ăă„Ĺç8 crŻ9đ ü6Öĺš{iŕZŘ-ĽŇ´Íý˛ř§SŢT Ô´8, Ť )˝ĄńĚŁZ¤ů›ńvÔ¸Ľ·Ńnž´ö¨‰ő<ˇ…¶„„$Ź˘3ó)Í#LÎÉfżźĘéä——őÁSĚëVĎ>¶|ćéŘDőşw—2VI·Í7ŕ“ew¶ËŐęŕ‰Ů1ŤLŞ‚Ź[ŢFČBłË¤ąóŠp9ż®eM9Á•ý(ˇ7†—źĎăŕČ®=ÝP‡ĆĘZ˛ü'Ť™ô™j–ĄŮKî›,tIÜ;đőŰBŽć)ś ĂşöIúŁâ ťÍąťWÜuěxîćz'É[jźśëó–7ĎFS›BmĎi^’ô*1$••ÚĐú\”j ‘Ôáďé ĹeÜf'ŚäŠ…üˇ:óőż<9˛­'?ÍRąšy˝ynKÔÔ PEUC‘`ň™ă ŕ?cRúÓ‚˙ qÍ%\Ć-^ąŚu¦g¦ŽÎ˛}«5~Â@ôć~k·ĹU{q´yľÇŢł=ë˘=ÇRTOÉĚ«Y?ÄQ—˛˝f R<=pB5…Ŕ#đuľňň÷ęÎ~%:5}r ÍSŮ©?ÜÎÚ§éđî(ě® ńa &čiŮąz |÷Ŕ'mV•QrËqużµB§f>~™·=«źjÉěŘă@‹/ećKčĆďS»2Îb@˙ť#fL‹Sc#?6u‚ü˙˛ŹS• ¶™O;ľŇßÜ.Őź ł$MQsÚâ Îâ\ýxevM~Ů®Q«…4NchŽÖvÝ7÷Ťľ—śâ Ő¤7éŹĚWhŰ·÷ęŢ š.©CŃ8îÄÚ Z´í‰n›ôŕ¦ďkBńxCţÜ\#€•ů6˝»Kč’­2‰†Ó}CËâ˙ç˛dĽé â/pCĹ1<€`oT°=ö±/ú™<äŻV)kDtÉđĎëH,1]Ž/MÓx28!nPlm@=[Ž_čřł§ŚÉkî†â2[´čśK¬D«˘¦ńbxt8ćgĺłďŽ ™”ůěĺű ˝w,Úqůˇ6ńçýµ™u;_3Ľĺyp‚×Ć+ky #P$.@…&Qľąw/ß&—Ö“pŕĄőŮŢ4Z¦ŽU§U@éŠ"~đ°ő*ţWîeüńŸ ŔD­d“x´Î–Űa±ĘĆUqôĄ~ëćâC˘(Sď˝ĹYŇXÔÖgŁX}ß ĺţ!›SăŁĘżďVp¶Űjv‚ˇe~Áסëś~N‰_3íꬾĎ4Ŕi;¦z:É Cfb&Şń§”ޱpţţşë -]ţťÚâŻwłSęi1Hî trJµ}Óp.˛ý|XJ„uB÷Gg˙č#۸ŽÓđY©h„DZQŃ9dŃb]0IT•ŮWI,Âŕ‡Tő—‘Ň{nä”ÇŠéş­ÇfS7 ¶«-´ňNM^9gɱ-VÚ< >6‘G`‰™˛b€JŔč|š±–}kú˛Í cŽÂ*{,Öďk;ă.WČ|Âö`˘zĆş®rX"`ɸţ^>ĽDavBu3ϲú'âälI<Á ŮŁc~V óĎW‡G;I—Ŕ …8=+KúXÝi€˙ąŁîŇ<ĹA´˝ kŽs—óĂÚŮOäcKvŰ˙ƬxĽuıě=[•EEm†yçĘ„D“™Ąí;śNęB× ]·7e–˘F¤műiHx×ĎT}¤iŕ”1ŹrG«Pémo,·(Şčö…*ăaĆÉJ®ÝŢjte˙±}_DÂm±Á~ ć=bS©ŁŢŇçZĐ? ŘwÎqfáF‡Lďą·˙Ö*Ű‰Ššqś¶;z‹­WťdbČú…1TŽk,ŞPŰjđQYĂ•Oýń«ŚŽF“ĺöŔě`âń„ÓfŘ]ˇ•Y6tČ F𪠄-}FQÝö@ćácŹ;PťękďÔŻ{đNĺ6ş©ě{źü‹ĹüąÂ ¸ cý€Őy22čÝ-T3ŃßBÄWĂiˇ‚ćÂ&)Ú%fţ°P†a­Óé奿pçn¤J‹ň}*`)Ľ/ĺž‘ÇÖ5çGY&ě7cś×_x¶ůo¸eKÚů“ °IBÍÇ…0!ś%ŃNÝÔĹ@8•¤_‹6dÚŠűCl€uÍ4‘Đ Nč줥űFdl|§˝)÷«h%Ű0Wš=zŞâ˘s±Ńă#í9ţ2X©6řžŘ;ŞůDұ1D\ÓV»ë‰ÁÓűP(Ík¸”^y3‚_ź{Š f[Ăawé!{cęŻ$ł6޶žŁßPš łţ•K]ÁPe.0ÝtIIlÄFĎ“8„ľ˛A~>KPHgc×ăVŤNĘéücPź 5Z­ˇ»RĹńř:Md¦Őz¤Úß‘ Ä˝Ú6¤’°ľ§ëѡ?«Đ‡/|T_r<5Ü?šI ß»+dTCsţ»ćŠ/‘ ÉĆżY_ÁŰ Ý VńFąÖµ{gĄ4ć)ÜÁ\OŰ߸ó9WűŔn÷]»›đ4şUŢ´7µź/v©ůNÇXŢ>n*mm)ZG6Źsbtk·z—Ë–ţxń›|šuQ«~ú0 ™5ý$25])íÁíVhć™»yš¨DŽő˙+›€€ŇiÄŽ<Ż–Ň¬SĄţ<ľ­ŘËďĘétćřČ–î WżMĽ@svĚ­·ŮYŇÓŇ?D´Zš—’Ä荝®p4\ĐGčŕJh"™:ł$!ŕw}jřĐ źGCC?™r%rŹűÓ×hJLrz_3`~›1€E0H”ߊEéC( ĹŮ÷D.˘'oČ]”żÉ 1/‡ęµc Ţë¸ő<7ÚTB˙;m˙öMq¤«Ü+ŮŻPÁ"ˇ¤3Éöu_1ď#ô~Ú¬Ąa"’”ůLQÉZQÝJ ^ą-’Ç{ š.O¦‡!gm?Qó\ö¬»ČT75˝¤ ^‚E´ÄŁšČ%ŰÍs9ëÉGLĄá6öM˛É›şÔ2%%„ţőîiŤ=ÚÁŽQŞgiRHßJ(Ďpbo { űE¬ŞFżŽřô˛ôö:„»eL8˛W¦ă;íŘ&şÓ<µkŢI8ÍćłÓ‘*Óŕ°ybŕŹ‚t°7­ör|y…¤cÁ: ěŢĺČś ­´ö'%çs  ÎAęÖűČńşŽ·Ń 25íä~uŔŹ6mhA0=Ä6Ň7çľµG ÷€”‚űÉČĎňÝҡeU2'[âxęŐ˘|~†7…V€řş L2LžÜm´ýěś!Ý4GLłF{ivßČ·Ą¦>ß7WĎŔ• ŃSż%D÷3Ak0ő}^Ř G W†Ż$†ţSyş'n§€±’˝4‚vă‰eĺ™éK Čgťrm8*Ś$Âř#j—%Śž3bÁo|+r'ąâ\Ę­WáĆ:ŃńHĺZ®e´ee”Ż(|Ú\”×\ŹßénĺtĄ ó}rZ÷HKŢýŠ&BBn37OOŃ\»o­!\üŁúF ¶Ř>lŽ˘I|q§Q×p.^ŘŢ%űKâQ-° É!1÷ş´dĆÚÍPé }8 ÓĽĎ}sŢŚoOˇŰ:vžâ lúę)AO…<Šŕn/¨lɲ”ńş4]@JČ ˘°‰.'n~c‚’4kůxB;łť/^ cůý¤‰ąť2)1>$ţw×é]¤\‰ű:_Uť¸-Ú±Ćţ¨xĐrˇüFŔBą¬Čv\·7Ö>‹łś©´%+eľQ%*AH҇ q›«quj”5!%Čín©ŃţĘą FÖřâáŽčFY’żÝ/« ×ţígÝBCfmŤ8IDîp0şÂe † >‹˛trýµ€«pG·%{˘Ź?öhŢ­¦őŰR [1qśŚSI+ÚhĎNRŮhPËç2\…°"ôď $¬÷¦?_ĺVéŕšťŤ-“ÁlÔ>ňu€& %rĐoŃň1.ŔnčQß±wżšłj\± ćřľ˛G˘)Âáú¬+Ľ];±6Ć·Ľő]ˇŁ;3ťKŘ·bŐ”âo¶ô±UvžSxN íĹ ó˙ĽvĘOčń'YánÄűl!Üč¤Â| ޵Ąţ‡53ű—⤳¬éVX LV+CańÉ0Ş´UżřJĂÝt¬đ±X:ť•ŹăN˛ «‘V: ${ŢŢ óU.1Tr–üŢ蔦ÄÜ/ÎŚh!řł"Ołű7Ę4ěťLkšyüâPΠ^ž3xŠ,©fóĶöB””Ô ¸r—Ţ&)Ĺ‚Ć˙¦X—6SÚáJ„2"«T(P?C ,wJŻ3‡ld‘ćP&ňv[ î 3ňłfWžK_…#™ ĂšżqšG¸€ä¦3ĹżS –bgŠô1ţ^Á6_R@"<ŕݶÁlĚEŃ&Ţ5!ézmvŻ«•A#ËÓçb󡕫6TĆġcb[b"{ AĂö%ž żŞŢe”k 2DlłP#±WQěƆu`µk;JŹ‘h‹˙â_+7şţ䉎Ëđ+Şr¦ą›˛ô;° ”ľőŹłŤľXŠř}O„UKB§ĆĘ_¶…4Ş2Ă˙ĐÔě.¬K–¬~ ľáđr TÔ>t+Ľđ‚đ-iĚVky—Ů1L7%#›}hŞí×ÄcďYÄ$~^uĘăk0„»űjA=PÚ6S).±› ŹSUŔůťNÄŔLÚątJ´MĹ“ÁLţK"¨hN6¤ w!fŠ=Źd÷ÜI¸¶ý]ńŮCÂ’qQźűW»Ä5ěéąű& ±lt,9ć2w¦±Ó#ĎU çÜĹKi«>óě‡LeÉ`‡âfĆ>„ő91<Ć's6Ňč¶řPIbµś“4qeô΢Óô%Úr(°‡JG˛ÖvT™©iËÝEz©[~‘oŘCÍ]¦Is&˘GÍ ęqČ@Ţ’Ż·•Ő3Pźrü*[‹x˘‡cµˇ0$ŢŤ‡§ü˘<ă·× ?(văÍĂĂżą”K,†ÜžŃ"ôU¶­…ţ›˝–ŠR)[Đ S'=e‡đý&{uĄOŘĘ*ßú)>Áű6j#˛0]¤;ŰІC×J˘¬yĂ‚L›|U¶ťŕJ ^VŘ›'¦űRÂ0‰$uUR\¨ôń)\¬őqs¦ÄÇSťď-_żÜî@«Ň|Uź4üˇ”üigżť:†‹[Ł»ŔÇRq=đâ{ŘT Űz ä&‹ĂZ`‚oVŽńUś“Q+=–б[‰ÇA»NŇ8¶:o`orÚfPđ L†Šˇ°ntéÄzŘĐ=beşß1#‘^ącn 1‹&Ĭ]ŘEýđ÷FŽr¸ŰänŻ,úătžDîŤă.`ź¤<ŽŚ~f¬jöµ¤wĘ;¸H‡˘ŻŔ0áćH|ŇžúĘuťGąÎĹŽ,g!.¦ÁDÚÉuŧAx~ň`¬ME†ęÍ›%Žß*҆~«GÍŤ`ĚŇĎVŔJz1şz‹•§TkĽ„¬pBÉ;2ŹkVů®›čŮľnâúžYFĐ‚š…\ ĺ2aAó>›I†~’ȶšb—""´éěĄĺ=o źŁ[x3Ę®ţÉŞ˙˝µ7Äş( Ő—±!flR˙żÝŽM;Îg‹†É‡ŚzŮ~ClĎLަ¨5ĘőĹ;ôóşeĆĹĂ%ĐSTXŢ;ß:îA‡ř˙+ĎĐop%q|nÝ'ÔžĘ˙pęVmd ţţ6ŤË]ĽQ˙Śt…Ą`F‚y¬‚ĘZ˘ #¬?(‰äŤ-ZBć»Ô ˇ·Ő‰řKMÜśňöű1Ô–ňů»{S˝řIĹG`ËÚ•˙¢tqŢ@K—ŻŞŃÂ9°@mŹjăbłK,á‰e«^˘óŇE#ţ +Ç4ß´ŁOűkîPZ>ŮŇrGUŐ‹Iㆶ–o ¶Ł.<řOUşuK%Vc÷1¨÷×­KJ:t†'°pö‹Ľsp­× âNßţßâ EŽ9žÓ ١vIťbĽîđŕ<”Ř- [(Hřé·ŮŔNŠuŻ2#ž` E5Ág&(ó8n°näđ›iqéޱ赚~]oÁĽ„©ł ęŹl Ţŕk÷Ú8 ÚDiô®%JkI;/2ŃH9Ş<áąąnŚ{/­ÝŤ >x†{–¤¤‰&ë2bÉ7w˙Ö´éĚH¶ÄĂöOŁcí<´\őYˇĺČ ÍĹr×¬Ś˘… pb|DańHĺ k|‹űZW  j-ńËŹp§ŽwJ•†i޵{9ÚţGõͣS´Í“ÓĄ(Fźń“ÖMP;áŐ-Ł&¶†eí-ŘťFś—«Ţôjřb5í;ôźhWp/ú |  ńĹĺ Rĺn6 áőĺ ĚŕĄtŮľf zĹĐé çPŕŐôľ±<+ED!xĽy[Ó¶ű]ዣ.±Ř—8)C“nÔÍjňrŮvMFÂNárB™¤čŁuď—y”'ľL?\Ös§ĐA覄ÔHĺőŻ*@1¶.'św1m÷"®ůĐćÔŤ_ăŰFöŕƦíNü7¨“Űx˝…sťš÷¤G/ăÍm;sµřŠŮ™ÖĄ3üľ,B=ć‚řmÄÁ%5 Ř@4™ú±Đ‘xęŇMôÉX3™ţ­go3ÜҢ-Ρ¦ţţ¤îâÎÜçů.t¦Q!!=R-ëC l‹>šńąXt+qŃ06i–K1ŤË?ŠPŰĐe>6ß}WŔCÚÜM–ß廝`u&`Š.^=“t ă^9â>*AŁspČ®<Ş_ŚśćRçtbŕĺ·kˇçbŹĐÁú °f.ŘSđ $ĺ÷s.7ľů†ď6›!ÄŽTHITřĺG”X—Č‹ZŁ? •Ϣѧ­U-PfŐżD 4¤@Ź:ʉŮDą¶e#ÄĽ!×:f$Í ďĂ%•9L!O&ř˝Á ďYÄJ˛R&í|đµw\­ĺq¤,©´˝ŤŽí˘űG Zw®6§µ“T‚aˇď ň%=MzU<*ý'*čő•Ë9˘˙#Ź1˛x5V\ČžLBîŤD:Wk4Ů÷=8R)äm<2nwŕÜ}śđËŹŠ eÚňE™’ĽÎ]=ÄËPŮôę™ ĐĐWf‘“>H ŠÍÔÉkĽ˝VŚŐ#˛Ü;ĘM|mF 7ëĎ1ŹŔ÷tş¸Ů’Aî)îF#lćÄ_~7´‚ú!SR»\ć fF­ 륳¸˛MĺŃM¶MT•ŻN^zÍÇ é#¨°aHîŞË.ŻÔ_·JbÁýR×$¬č—n„:$–pz„®·^ôëAM~ŐeőYľ–0ŕŃŮxěŔ‰¶f·9Nĺň`_1;ßY©ŠĐˇ]8ňĐ=şžßýĂć—Ď–Djb)˛©cc$B(”QIÄé ÷]M©yg‹D˛­Öú”+á'<Ěcyč:‰¶;‹X´Ť·Ż§p»ĎLÄ|±´W/ßÍßXč±Pś–1ĺI·b$AL@“?‘Ń yS=6yďĂŘ}Ŕ 4ę­&aR!*^Höň­Xku8Íëá˛ktíÖ߲ŻÓ>ˇ©§[gýňçíp]Ö&ÚólÝ€Ň(aQę´ŮO¸2‹oV ť9‹´p´ËŢ~{ńu—UpîÍ-ukÖNĆÜ/ŠŇBś¤]<–Ě,GíDŔÚŮf?9ěä¶% Ňm-Ľ WfŞ4nć—śxń™nk‘Ę–x&`¤^ߜĺRl]ăłĆĂZślí‚%â*gü‘_4J2 â)ů |ĺĺieĂČ4ôôŐůŁ&l˙Oő{ý‘1öfylaýEÄĂHd:”dA»=řy&Q˲PqZ˙<úŹńëŻÖÁI?Nb:jß/±Ůý\4RńxzgB꽜łúB N/Dó0]ođűŔ‚u“Öş_´†”"*j˝«ž˛ÁłĺđHľäaX®§µČĎ#ÝyĹ=5Zn·’W§Ş$.ĐÔţ"úźV—ĹŁ†®¶NYäZO(®*ëÎF6$Č8˝TZUÔj’^÷ŹŠë”ËFď^ká7ŰĎZTń0ţťŐ CŞZ7€vű‡ĂS„@6šĐmĘŁEP¸v]Ęçdë2Tâ¸7L{ľě#n8D­®šťŽ$NŞˇŽÝĽ2_}Ú–>•Iµ$¬*č@é—Ć`JKŞÍŐĹË‘&|ÎmČěÔWV;©ăXă«Ő ×˙ísзۉX]ŽŢB(Ŕ4·Ň9ŇpzGšÓ%ŇJxO©š•Żż|*›ćúŻlam‡…r%îgt-:Y× qDˇ±źCëĐâwŢ^Ŕ&Ń?ş+*ůă"´28ß“÷ž§u`Ç‚]ćŮ2x} ®ĹÄx´čˇ›‰Ć( X`P=+í.`(݉™-Ęí¨śµĹN¶fvČ.ĹDĎůř®ČpĐWĹ˝y‹'s88€ ŮĂŻ$Ťp„1oç†OvŤFUęé–(Úsč.ěy´s…hN»2÷ą—†Ť<ä‘‹ŁŹoDM ^ Á ‚‡˛‡a¶8OtĹ0 ž%Ç®IO ’jÇ$Š×ł¦ěĄ;fŇý¶ď㎜.4َ Ňę`¦źô~€ĽĆ5)kł©Ţď<ך­$kÄ9¬ăA­˝MB“C^ÓBµŕ)Âś˝úK¸/°„ľÎĹNóżůaEďfžZ(¬ŢŇĄ”ŮŇŕg{Ţlů5çűÚ§zžű̆• ‚=lŃ9ž»<˛÷4ßtzěR°TĽ WéĘ ü;•†§É¶ Ň iÂuýXě){ż|Ţ^«ć 2r+ܨZj)· \.ç4Ňů÷śA[d6ŮĆ"ÜůTĽëÔk>Ŕä$ŐDYňňť“LémPţÁĹŇL{AüřwĚ#yžë,e˛LfŻĂY©Ă(rÓŐ\’>LáŽńĽMY7F„ bцwpŽüô[kŠKÄŁ—¸ŔčěgE9JÉ\ūѺŁ2-áĉ[ÁÎL8.ű¶Żđ&ä-”ÇOÇHËÉ$.˙tĎ˙ NŻ1ý·żruďöÁ—&ďÄnGŞŐňg¬~ďâ[űâSÄťC[&/ĽŹ€k(äFzŇ%çw¸§‰-›ěČő†IźÉ@ĐŽ'ôB Sőě¦÷ŕW—"C9ČŁÇÁăâ5ţU×MZ<ţbđúZI€żí§5«©ĺť5¶ ň"?‚D K&\ ŮI64®6oŘD °‚t.ŁŢ|ǡěďhÖz%` X<Ŕ/u_ľ‹Mĺ©CĎ?c~Đ!¤Ů»(MfĆGun@g¬çóňy1”t´Ů®<)Š_ťý—© â(µ¤đ”«pŔ“ZÄW2<ćÖ&ˇy_ÎCáK^ŇđW;תÓůÓÄľňĚYłžüFJQyi€ b9Q˙CĆ„ă3páÉnĎŘhĺpî‚Ů^‹ŢvŤŹ±Ž^k­×a üB†cĆEőć9CoßŁé­ µÇ\ă|ţŮh@ A~Öţńş‚¶ŚipIöşwŰ’˙wŢÍřĆ ödŘ Ă’Ň §Ç4I\8;˝{ą>aC˝ŻĆ(KŽbdy;+1ý č@¦Z¦›aYĚäC;i›ę÷ßkřČM­4Ě~ż9bW«ÜŻÖc4ôs=/íKÄČž®óĚő5 Čü˛_иţ†ˇď ´Îľ­zö5>Źż·ĆRŔ™JÖ~ĚŐŹ±¦Ďä‘ĺÓ\ ßđťUú×Ń MŔś<„Ďu;˝b˝C@ď_2ťÚ…uöö•‡„şqt3/Äl*ÔŁžç‹ď€0ó…ÄÓ˙Ś´H5׹ţ|íJ[ě”8ÇÂĆU–Ź‹‰.o™śi&:„0đŇ_)Řűš€&ë˘ŇőLBĹ˙řĘ i€q“VěéČí;Żő¬Ô,Ś7‹?nđŮۦJc1dľşďĽD„ímF„IŁwä‚™ÓÍâ­M¸Ç,KVPEGdűÄÎş0 «Uz!NBŹă҉˛%4J(Tś2Z—˘L2š5hµäWÁH(…ŕ»qŰץqň í0–ąÔźÂZ”s´tŇGö$ăŚu‡˝„€çŚłă±NŰűo ”s 䑝‰bzăZđuiyń±Ćf‡Šő ×Âe/ě‡,ëçzS°ýČŮTj5ń¤Äy&6ŻĐ]ôXú&±żJUâŠ5ňÖ9Lî4-|,ľő‰F=bäZ•’* ěĹ&/ L"śŮęÔngöÁre'€ł˙~Ú ×ogjŃ{”yŤ?ŞI®KŇ€2(qţby'ج^gYç ,W˙#Ëá)°č®)†®üÚ#xI;‡č=…I§FWH«˝ťÍJtť6É—yvÄG9$ÄÜť,? ş­ü‘ŃSGë-,€Ě|H­· ~·>sţsŕ\ŹBó~r!XČBŚűűA &zÜP]Ź™sH#O„ęŰ»‡©É_łBµ`ÚđĚU&ˇ÷Ô““Ęű™?9ôôuŞ'™‚1KŐž/±5^ť9ó ˙~ßtŰ„sQË'ű=‰lźmi?˛’µ§SÉ–‚íÉ/=iZĘäőDN‹îŐ>Ą_¸ŘOťÇ1lŤçďĆ÷Ău(ĺÁżh~Ş2_ŕ ˇ«NC±LáÎŹ‚ŽP!:ţKNŐOS#UźŇŢëUEčřź›2Ţâá W}ľPM1†`BÇÉ>ÎŕWÓ+zwÚůś€˝O–ĹŁzÇăCD8V˙¸ €Ůżµ`Vu XşlÚŐËîý©$ »RbÚu4Ľś6…ĘřiĹČăkŃę3 _ńäĐ»‚4Ň­ĽhUč4 Ś’/ša”[BŘ©ŰN¶¦9îŔ+Y{•sM7XĎÉc¨úN"•7ܡÉw.AvÚCO7—\»“žčdŁ;†Py3¨GQúŤéixÓ6w?űPlá:>SˇxĆ ĘČ3ĽóÖŘO¬´Ň®]<ň÷*5exĄ5^‡VńđŹ—z·ýŇ%W¬×óżűl6µ7Źh Űiá€őZF†Ů Éä;)­m"1Á«ŢŞc`ÖEç|N –˛‘/±óK”y úIą’đ—9ąđAw×ô ˝ľµwDů(›Á<>qă*Ş[÷ĺ Lëž‚í‰4KXçjUÂŐwěľ·­ďMň=ŇŞÚ榟HęźŮşÍţÂŔŔQ^oJ…y%ĺŮń%VąÁÔ‰yXYÁ«”éöŤqŽyd\_bkĘź˙`ŠÉ?Ťăb•a8ă›ZŰ< J:,ÂÎOwúĐhţMů‰—_YNVń3â;hĂť×dŕA“y+ĹĘéůT“ěZŹŢ:ˡ0DdXŕsgiQJI']6ǵű81 }űšŢţż/ů~<ĹL`–TFܰ÷oŁ9e˝4­îg{öĂŠ2Ű3)@Üf}kÚ Ń=Gs!?“"9Ţőű_“j ,'S€Uú.ýž~ľ«j±Ä§Éţ`•šz8 kOĆiµQ„S.®)H±÷ű»Á2·”ş‚ĄÍ§aŮÚ:oh·?ĹÔ\Ô÷Éo%vSÁŤb ă€fůĎĽí‘p,´ÉRžw•w厽2««©s¤Ţ–Ýv ™°ď“ëR5ä‘ĘžĺW"< pś¤•Ďp#¬ýy¶\Ű +Í]hU‡Ýlżëoˇ5űhňśzŕŃ€W @j>3ŢąňŕGhŻ´c°L‡°ó^\Žň“{DůK‹0F5ýĐ ű.{-آ˝oráy˝uŁ›TřP5Ń©n—J[€OŔʧu˙ÂöVĄű?ă]FĂ-”ĐęÖE™Ď®Řy(—ŐjLsŻ”_Ťç&2zFÜ ­Ľ!?ť—Ţ˙и5Z6ˇW•3¶ k„™z¤ĺíěĽc5zľ"ˇ©-5ěöc!î eĘ€ď°Ĺ¶, )Ţ0ő!›%/ĐJWc{‹Ôř6ö°µ4GžŕHć ¦cÇ‹Xää‡Ć,Ý˝M<}qÄ´Ľ0_ĚÁV6¤Ëi粯:Ś×N”8h´_ť’+!˘«đKýšé‘­ÜY4B_N/ĹŚ ‰>śćJ)ôü˘bb5v®4 ®8jˇEnĂŇ_?â­·ěS óYÁĹ9ü˸ݢIxn'”$Ś”¸ďąľű×Á×z‰`ľęVËŞ Úžgr‹tsábB&řŇŽ)–‡†Tżq‘6†f†z>jč±mh˙„žyUĚŚ­ôs.‰\µ ÝS>ÖɤOĽ¤…»˝Ç \?Ó nŤŁ:#¶´KŻ&†Š;r^ĽI :‡G†«äkmŽřŠv~ť 0ËŻŹ…wëĄs N,´»sT`2ż˘c@»cŘ| ŠvJö2TĐß2«BŘ”Ť3îŽV§5~<ô5+ůŹMâÖÂë˛ńŢ©mżH/ŤŰ…4…ÔĂ®r%8}iZ-óÁ°÷R $©ŹiÎNĆk *u)Î;Bů %÷­˝5ŚNçTo`YČőŇ}{äKńź˙şe,TŹMwŘ^+˙|L-Kp_U%šSĆE¬-ĚYVť8Ké™,vÜŘił &ĹĘÄ•6Tx5öż.»ĆJü¸‘ÉWsŐBU•XŞ5ŕ(ń#Ű’ eäĘT>ć,xHÝ :ł‰O,yńzÚ8ó€ÇbHQzWö–“gśůý}d€2¬€ě«áąÇS"r,Ő “¤fóQč𲭍+đé4ifŐ©˙¶ű ©oYäÚ®űCb_yëgżŞ<ęĘo˛tŘě-Îť3ôµEńŻÄŔÎ-z+k«Ľ…20ŁLôó~Ç‚îü® ߏÓň™ë@ަy˘€°¸ľRkúě}бb3ŠG%›ů–Í$ę+Ě·G°CŹ1Śď{Ân8€ą]«{ѰüŤ›\Áčâ­tŔL}%¨ć›ÎŤOÁ)R‚ЉOú:¨™´wH9şSSń§“®đ+ýţ®Řđ'†äĄýŘŇ>˙ëŇGňKQţ ·_3§Z'ý}eyÚă{ţ5ÍąŮÚŢĐb<¶0:ýń= ˘Đú0Š8FL žBRůľÖ5 ó[yŇôś§°ŁĚë ÷ŕś%˘¬óxôÎŻî ÔLZşđ¬YmT‘낆­dAÔVűsřĂMţËuy=•N¬ęż¤~.ÎxgVí—ľ0g9fdę:h„}Ś?xQĂ8[«YÂ„˘ńbí<€ÔáÉ”ĺŽ VtˇŤşśşLżF8ŮőáĽÓŤö7Úa’tbäRÚÖtńc—JŔa.f0÷´±d'ăäř7[JĄA„‹‚î¦÷ęC†_ô.â-PźŁJˇęZCJŘ(ôUHn,ą¶MźKúc»üÓéM&qŹećWĚýŃ®Úß °Ż^Ŕußié(|¬$ł[F|H…vđ.Ť”-Ú™¨q*ńžZQ xQ_ÓxÓkţ+ú%!t˝äüÉ6[(BlÚIÇôx†,!ďęg&\rąąoIp”¬Ł€¨PŃ\ůýaŠrvWɸ?ý˝Ôűq6ÖăÎýG‹0ľ;O+˙xµ6młúÇą©ŢžŁŁ@v¨Ĺδ8í9nŔdĺ!íüp†_Řń}®dăp˘đSÎÝŐD$˙9}ě`ŤąľË~JNť©•UŔ\+Ś-ŔC˘ÂÓÍ~¨Pší’םŮ9Ł-ר@v?ď­ľ2=Ä—Zëč€ÚÇż›,źÎśŇZ’IŤŕo©µ>z–ę}ę,Ń´÷H_…Ć:Ş:TV»Ă‡—»Ő(xÝa.¸#/6‚¬ďŽ×D=8ˇ7Źsax¸ŚËX¬u‰ŽŁřS»d®áT¶8jďăď2ˇP.gÚˇb<…oMBÔ>Ü˙«?ˇy@,C¦{” †ě« ­Ě앝0ý.˘M¨†ÓcĽ›#\?µ¨ěśO ę:řIS …yxŚ ±–P!Ö®jKwf¸„ě^Öá”±Is˛BşúŤś‘Ů­ŃËx[ţć˙ŕc@ß&'€F'šk<ŕTóGÍDĺ~ áŐÓG&÷[§)0ÍăA”/zŠóëlÂp?ĂpósHŮćxŠd ľŠ 拪+x#Ľ•ĚÁNKz š›= í;^–ž#B¶¶Ţý©“ňv×Őş÷|lŮě°ěÁRµŇk_çĄgđŠÖŠËČU¦/ÝUh'ä˘|´’7˙Ą·Y,QÍ,xśB ß«xk`,_vk®ë¨d}ůď~Őä=0*ą,yúšbI$áL˙;ä•îZlč+ݢ•Ţ: ›P”ŰC!7:ĆťÁVI•(ɲ-‰ŇţĄôťŤ¦+ÄpĄ‘ł/UĎ«KűßV~ś_â¤lb­ţĘîgv ÎÝfA‰E%’®™„#]şüekť„pŘWŞ_Ť»BM¨Ó$f4\T"l×ÝaŢ=§±™crŤĂäFçZňä2N„ĺd#%ŢČ… y;(ÍŁ1ŤOÁźUöy˘o@Ĺü”eÂ,y¶ŔsnľŠß ǵGýŇśˇ±ŰŠE ŇPşŤÁ˛ TŃů–Ţ(Aa›–=5kßwj͢q\`ĺüŁŹ>¸rQ\á2kDĹe+L ź†rp–ŻßŞD#j>4`Ý ŰÝ&+ęúŢŔÓÍkN\­×Hőa2ÔÝÝ~dŢŤ&íÇíą'޶25ĺZ­¸§çłbśíţaq1ŃAÎęä<*ĺ;ŮěE#š˙¶0ZVÓ‰UĐ`@Č€Ľ•`¦7Ä#B¦ĺŁóźÂ9_.N÷24Uőx*†`{ŐJuP›1äARŚ,â?×w ĚŇćW»Ë®Ú"Ľë@Âćů€Ú&úĎňjĺüč8ŤZ oÓĄ˝3YńćŘ•;ó]턇ăÜV”  ¶©IN€Čç5ÁFžŽed’%¤ GpşÇÎ`MŞ«„1h[7Eî©l/ă(uLÖŔx™Ő¦I‹üł~·~ŕ6ľŠ$ oE8&ŃtđÖ:ĹżŤn«Kd'OB˛@®@?{:5†˝*=†_\dT|có6_~ϰˇÖÂyÔPř‘x2aă#-´#Ęp”ŇÁeâ¶÷§v 4+ÍUô ďńÜyrúđâ€)WÖE­ŠÎę†çjMkô&N†=­şÎšćqĘJIŇö0h2V啕GA\díVa͡Ő|ŽQ}G®â÷çsCąţž~Ő·Fň¶lÁ™ĂXL(ňcńGc[¬d¸ÚHp!?hvxĐ—ŃËŠ`gr~/Ře¤´N™Ą1đďŠŇ?…–Ö"yĆum–dyÄŹ|¤G4{Šř®’Á+ĹŁŇEćđGZ®©ˇe»0®?)3úĐćвÉjâ] p˘Ŕ“â°¦''‰&źÉ˙±a )ŻńÖćBŃ{đ˛¤RĘ>Ní1í-Ů2€ňÍQßýČQáOó;`f¦Î ™Rn™k;Ź_%“(ÜoW¨AŤíë5}b~‘Íd^Ą"Ż…>—'ë´´6¬!Gj÷ĚR×ŢëűŮÓbŐ–ţšÓ·#W×ÔlšŞDŐŞ‡*c ˇśÍ‰'ńňŕ«JL@č_*¶Ľś>+ÝaŘ-f3NŤěEÜ“üŁJ•ŹźP©âÔçőč­q›Ć’wĂV9"ÝĄ0 ˘gPFţé-Çĺtˇ d{şé& Ňú5ËMş SokaUÄňwt0MęPa_Á:ţ-äICŕˇ8ĘT÷ąB—ů‡µ©:Ré*Jţ¬Ľ! Ű<+v4«ĄĎ(ů·®nÚŽ›Í”:zŻŚ)‘.ŞŢ&7÷[Óö]<{* ł'7縆8ŤHq,*Ă×1¤IĆVwľ¬™6D¬”«"ćG%›ôÓ«¬“‹MSĐđlEF ©xŘő‡ W@d`ď7ÓűG é5ÉÉśŤÍł8úo,‚g3ÓČ}úů‚L%m ms=ËŽ”€}AöŁč2|:'"U7) u“ťĹcy­Ż´áq6¤S¤pČüČ‹Ĺđ2.C˝kłîĘÚťY%JWhî>ś3D=„üö­¶J e°ěnˇtýó%«:ßLxn`ĂE$ě1ýŕqIHW4¸5 Ş/ŕâÓ¦8´¬öčšá¤ĺ]üžTfň­—o•#Ó”˘÷W,Ö+Y ÖXç>»Ab“1ËyĽţ™{0ąRŇd­LeTş˛‘˙7Ě;vŇ‘X`1kXMŻĎ›3:KlÍ(âk –Ia¤đkGJ˙rC‰T˝ ţOî+> ŻtŁ–(îš; 3Wó ‡Á¶M*±ŕ/‹ÎuŐ?ny†:µµ­šQŃË8ŽhL[âę+ažÂŘűo¨\¸ŃtL‘Ą˘]ô´…Ś4Á˝ßżXË ÍĎ®aő'µLďäQUm˝ÂfďtéA1lĐéTő]jDś!2Üßáľ|,|Íw"V®T!Óö ˙ş°Ń|Í–áĄázϸ¤u†ŕ (ʧWłdűÁš&DÝ‹Nź[¬hwÄď ZáŔy*Á\nÁ$sŔËY˛šŃdű/Xvň¸˘;ŔLŁŠ$ĚËÚ>OŃÁ—đ¬z¦˙˙çî2ÓNG„†Ĺ9É*ą|v‘±›lIY Ů5®w»Ş» ÖJ×ňÄj5’6zâ¶ŁđbÖşĆé_XŁĺ† ^Uł«TĚŽędřęlŠë®Ţ—#ˇń #´đ+D)‚ěZ”Ńľ}TÍoCÝ(xÇÚ"ź×۬®9€nĆÝ·´ÖxxůěGĎ™ÄÉ˝ÉđaĂ=ĚŁę|ťć9ŠěňuŁS`EŚńéyfî1!#ô0ýqŁő_•c@”ŚUňëŘöJ’łĄP늸ü$”Í“ÜFŮř6m&ÝŮ‘0¶:ţĂ×=Bí7Š`•’\óEzÝ-Ošľßç7$täčĆ,s7ĹWlęť&%óÄ"ÜÎĎq^äw×wĎ'‰ŮĄ i ŘX‡°äYPÝšşÝ+Ľ äü·=­ůÜbË–ĆđŚ'čŐ@Íďp§‰Đc˘ˇŇ:µüť¨˝@w. Ś×ńşÖŃ%xN"×§¸ëQbEˇŔRb ˛ęŘsĐ“=G˛Ó΢4rvň*çµz‚ˇŇ+óLŰ·ł¦¬Yś°±Ý(Ë}3A_¶i¬5f(ô–ýxž´mżJ,Ć”x7pÄ&r**ń}ŰűÚôBR ;­}ßůƬęÄ˝ýÍó÷е"q!TčpnŘBśÔAš&h¸Ţ/Ľă/"ŕ©ÜĎ‚P|EJmŠWx‚+}ÔáĂŞ˝ű$¨ů)G©l=RňńĆâpŽhç˛KÚ·ś†đ·4‹şŔe5o€ŕ™`ˇ Ď]}¬G Ž» ÜX‚ŻÚűŰÍ”]ÖJ¬M´­uŻgEyÎjÉĚú-"IáQ´ő˙Ç—~îjÇ‘ęoď:©FGĐÍ´Ű â@Ą×•V—7"+r1Ľo4*@Hášyą€ŕtŃ»ˇ_¶3´JÝ 2ϨýŽ-.ąđ"1îÚ"‚dńś/™ý­ĐŁÓ^µ6QHÓvĐ»Á¸p‚ţţ6?!\ËŁĄTkú•ľů3˘Ž‡ăŐĄ!˘źM^;&s{dQi'¶ş<㽑ĎË/ëyÔĂGżNµŘXGY÷ÜX_čHĚë}rVŞŁŹ›‘¤Şkç›˝yśB$6ť#·ź·(/vh Ä„án“˛OęGä“]h×˝oćÖbŰĺFÖŠRYĽ ŹÂYdj©úř𤅛ć˛o×OżŚ­Ńů-ŃŤp-jXö ë\µ¨GlNź«čđ"@9‹Pţh÷m%sI>Č€Ôřú­5ŞçłĄŔ¬2d(«n*ä@Ř´—çsvť»0˘F]”É… ëśđř»Ď'Áü0Ş+gBX‰{şi‰&=q®WŽş ´?ŘÄ9…Ű.ŕ­€ěph‡Őhę2}yÚťçţĂy>7iN™›Í ýÚJ)Âś…N?¬c_qX4Jεfěz±r…lŇij=u.ťńĽxôŮŚvÍžŹŐ—] 8&čV0ŕâráź<㙿Ż.ěŮn'čűޤy9Ą•łHŕ>ţľŤÖřMřÂTµ‰F.ĐÁĚń5ČĚŔ>E ó¦#V)E{¶—·.Vě]ę fKX‘Á»cz:q4^o§ŐÓ4Eá^Ô–§aŃ$HŮĆ\Wéř†^–U ÉFoEń¨µŽÍę)űČy´>»¦jˇĺĘQgSW;o‰tÁŽĄĆőŰä|”ŕ"clÉIá¦$ÁrX‚8”˘7<ţ7Ęf»í¸f&RČaÓěj]¦”Ň @n5@őšk–©Ů¦|Ţ0!čľubáź®Š9±6”/˘†«­šAŢľžĺ:{9‰ßß`©–ä\Đĺ6y†•źĄÄ$´LźFy‚ b­&úć*Â5`Ľ™ŁńI¤ÖU‚éwŐĘ‚|1P©Á¸rżďÖbZ‘€c¨*g™«g&‡âÉ hA®CóŃrń4üű5WË»†/ŕ)k-O~Ę“#›ir©˙˛…ÎbĄ%Á®U¨~î|ĺŕYňř±FŚ.x†ÔŞYÔ“+=ÔÜxźĚŁ Ćŕ]WBżĂď2˘ĽÖş(2ÜkĎ’Ă)Ůă„)‚ Âáű–ΰ{Ę7ĂO¨^źWpĄć(tTŃŰMąćÇ«`ˇhIs p·Ş<ýÖĽ,g‰6xńřUż%Ľ(Ŕ:§Úü ľí‹g AŔ†1s ”ę/źfeůi$QLŚ•€äk­ÓŁ|ž–.±ŔmW÷_6-Źś|öîş0Şz㲣g1i˝öďšE6urTő˙fsÖQUd‰·Ş÷{VQ76ź«S¤‹6D †;řęW%S­ý0 űťQŃÓ¤*¨O‰ĺ“âdµáqR¸ëçĚĄ&Óµ'nÜ·:Wäů7Cg"íťxłÎơ•M—dś…¶$Ű|Í·oďň(Ľú9t$č9”ş™\ŠSÎźćý†“  _ęN. Ú˙_ŃĹ5V—ď^vąD»7“2`KŮ~8Ą÷çŮmţq@ ph &UÝCW¬Ěyާ™ą­Ř e0rőwł×?Za­±ôPö⣔g&]Ü­05^âţ^†śíů/*Ĺ Ĺ~Ůicä¶ą¸ ' Ă4Ć…„kůe#ú¦‡µ`Ě8öMrY‰iËu^¸ţ‘Ó«M  yH,ĹŇÝ1ëN~f jR~ŮqaśCą×X{k©ł# #‡Íľ¬e¨zuô¬…D?U4ď3F ‘űmÔřyw!učŹă=z"wvŹ^ ÓŻĆíĄ´4”ĺŕÜôF p‹ZM–”ŻSÜôĎ›‚)y$$t÷j±—Hެďî¬Táˇ*XVű”ę'jb-·/(§DŔ¨ŘT0É`vo¶{°ý’w, öz!3ĄW%ćVxÔ›ŢRO˙Üäx¦HB—óW÷‡OÉu5e_°‚/(¸Ô3ŕfłé®Ł›Ľ6R{2gt57—ĆB`Ľce:N9ďŐAŁiéhŽ–y¨ľ,îĂÇxWŇHş ŘѧâfĄËV&ńm•zOóšu»Wp=E!?€Ľoť–eĚRżźUĆ'[źO©łŹ8Tădß•ˇóéć9ôhɉ•Ď‹k˝ť˙Ţ«°–°jŠ#&äőVŇßz„‹ÝĚ é˘şYx±y Šó¶Éíh+‰¦S”KrčdS•ëŐîǩðńŐîŽě”É ˙ÝRaȡyd˘Đů ęžu ŐgČlďűď'ş>„Čâ/o ŚůLÇMĐśIVµü3ďG_c˛‘ăilâ+$¸[R ďöŕĄm¶m*cT>Í™éë3ŘßľG=E °e2‚3® eŽŰŲjµď%^m>š€UpŢ ©‡$7Q‹hł…‘ ĄÜ$Ö([-<䇖~¸± BĆtą+}0óÍoóǰÉMKç@Â<Ôüexçł:µ }2sÓz–š /|Ňb>…‚áŕµrJ&ňSćYvŘv¨ÄjnPŇ7GŃ0ö}Ő´Âôž˘ř»l§ÜŇž÷Ú2ó ˛<¦—˛;˘(uÖÚ“‘ľäě‹©ZZJ´ľ·—‰AVŘu€ Lhť·LĆ“OúňłŃžnőbsNĎUÜfE!A_Ҷ°ćCŠE=Q"»ę¸sî ¨’,µQę" űń°žßD/ń-§˙é.Ą·©‰w`Ë´ÍDÚžď3íţh“–’éýůß~Ŕ˛$ˇ@‰ß8şóc$PÇqŢžűXkOhËy™÷§­1ÄŠâ»çűşłŞŢ)éđÂüĂ{Se™J¦îň†!ľeĆ=m ^ŽEßÄç“qh G¸Pv§ć›ÚL“WRc•ą4AĽ«7ŔŐŻަĺSank•¦ ®ĺ ©ü9Ř4± ˇĘńÄŕÇáśł™šÜÇ{xM«>;îţ)VűŽ8oçdß:ÄAYŃmö Śm?(ŚSÂBi‰m †|šĘ°E†r¤đ7:Tv5~çtü*Ä€,ŞXö Sħ/dQ}Ý,­¤†ÎZÉ×@‘µ=¨ő®ŻňÍ÷ÇÉŁ–işÚŘżâꬳq ÷Ś…Ĺáń×î1ÔBЏ<‰iŐorĎďýůq2dś7čŹJrQşÍŇ&]@”â{čĺ·pyřKŽ3žeáĐłX<ť;™5t®ď2żćşí =«˝FčuňëuŇŻ¨Mľ,A-ĹwˇÇHĹĹřÜ‹M¦x=+˙Ę ł]Bŕ5ôó"±(ĆË»Eď ÜÍŰD¦Yö‚fĘŚŐ =ą`óß)ŮD•! ´/ä‰h´‡ĆŘ€-¦*3h(ß~GÚý.cyçşń¨é†9Ż2މ‘łŽ'Ćß?ˇ'†řbť¶]ç8+—Y¬© yٶřr’ôuţď:Aé@öKÁh"ru ß,\|9kŢŻ0¶Ň‹Mž7Ł.+í)/×O*Ą*Vf—;ˇÝPBCCG‡]荵@ŽÔÓDňŇ íÔUp"ĐĂJŰɡf ş‰y„î„­€˝Çjűo2-!ń Xt›€_xHHB8¶uŕOÍ ˝~—Ŕ+&ZŽę‚ÂÄG·á%9îKŔO2ŽVoęYˇ!ět“ä\ çŻMÎ-ż±Ĺa›ŠŠ/ܱ#oHWIhIŁ â¤¦3ËŔ8ŃKä‡\˝Ëâ|±1Ç-¨qľ†ňIu ĆÚŇ6fĆh·$@=ňőIţý‡Š‹~†Ŕ/1Ś ^d™ŘĐĄ¤ŇˇőWE(&ŁÁo)¤ŤŰscR˙;| řĺnÍŹľ8Ä©ăßeş™Âˇ2ăô‚tkšő癊Óô‹§—ýź˛0Ââűŕ+ł [”"Bă¤̲ä‡Ć~€C4®Á.ťę†rÔé諪©żKĺ…X˝ďń:‘Ř knĺBöMQ]řŤ›îÁWsŤ÷…BĄěĆýVžáRŕ]¸aç$—)\řMnřůqĹËąMcíG3můĺ?µµŚ’UőŚé¤eiéTiZW` Ť¶5Mc¨Z3–%_ *Cf14ó™Ëd­{năčËâTĚ#RK C/(QĐâ›Ú°Äő4µbČwÄ{덆ˇĽŃ€Ę{g6lµ¤jś»r˝5L°±=(&ÝYr%čËKkÂk¬í{'Ő0^íÉyhŹß«‹2;¤ř^=VAčŚúŢñžęűm& ľ]>qçü7äNąöř<-¸D¶µSĐ)íšËř‹á{!h€>Büž&ÁÝĘ3uۇ˛K}÷&âIŘB6Ý~řČ[ŹťvµioAţS ‘áŻeHÁÖqk°ö±zĺc˘]Đ„Ôr˝•i LöZ5âÄl®$łě6ş‚Ď ń ÷#áł„<émŤ„¨ŰĎKI4N(Í„ipŃe(  Y(Gč‘]ľµźŕŮH˛×˛z:.OÍëYJA˛éx ş"M…řJ"rYY˙„ŐÁđ˙ż LĐšóc·JA‚Öî3Ď: ŕ.’Íc)xWf™đDF÷+´Ř“oŇŔ@U˙ŚŁ&ŮzŇ–`2wNý.==fÓ­USĄWę-^_P*¨¸Öšz™®Vŕĺ™ÂN’]Ş-z•DÇ•Žě @ŃnľĽŹ~`/AFyMtď+§Ł9wĐ=ÁTÄ«ćm˝€UDRFUzwűc;LúňÍŐÜN{jľ›·Td‚i›/´žŁű žÜeŕđkŢoäj4Ť^úŞyĘä°Ćµ»đČĄ…!ťĐ8™)u—ůç˝#ů|­gĎČĄÖ.ʧ9 ”Ŕ•âýUň4xbĆ‘đ˘S˘×]BtÜZT‹yî>!<Ë˙‹ö@<ą¨öŢo<Žţ)ýug†7”¤ŁÇ«[]q3a®ĹK#ˇAθş|sqĄfHmĆĆ\´Ň†ÓęJâMÂîUiW«(d÷®Hđ™Ô|Ă˝qžSY'_YőŚěä=^xßE1U‘QJ1ŘŠŤŇ4xçš+ɡ™ËQä.S[ĎÜGA}żVPLxŽ:¶R9ľ`¸lÓ ĆŐˇCĚKɉÆ&ááž Ë«šĺČŇoI–ä űĽ]Ş%×ŕŞsÔ"÷ptőPÂŰrucćőVéM$¸˙±‘^WJÄ•t\…o»ŘnĹśśSňy8Yy ťź8]'—˝1Ą”>|sC ’Ł˙‹sSŇ Çy˙;ÍţżÄń@“óvM^JČŘăďş©˝Áżé<‡•ŕ"Žń‹Me;OúĹr÷*¨®ŚtVp“ĎĹĽŕăIJx ömÄ™ŮË‘WŔJ^pš[oŽŠ.Ô·= Ř”Hśâxo÷3Ř3 0Y.±»÷:cB7†l1Są6˛ĆĄ^,u"Ä/`FáS'ź®ę͆V›IPgűT{ Ţ‘¦Ń2tLh—m‹yf¦ĽR Ń—Ü^dѬ2W‚MÜE¸n˙<ő`¶brh ÷Ň%fĂŹ yź«!Śó%é|gÔ˘óá˘5@č€i»iĺÓÎÖg35wČ7·ěY¬Ô•EÇI\ |D źNź%>ľ®l;ôĐ8nŮo«s||yőÓ xŢ‘MŠĆĎł çýQ‡S@nŽV «ĄFţöČ?Ľő -$•hÓ Ü€:±ŹőC´HP(ÉEvĄ2Čbhě'Ů|uŐŮ^ çϓђp&\Ĺăřçp!ŕŃgT.- lDVsI3AˇEi§í;Ě 7@7iŹY”oůĆ>EŰ-×žŁ˛×ŕ”)ľ[!^Ň8i€Ń…2ĂFv @öś~iĘ)($rë…řŃu 0h\ţ”–—‡˝ťů¨ŞĆDç`MSj»‰ËÉ6.ď×4ęxÜ÷‰ĂI$}Á„WÓ[Jů |ťřŃ8UŹż‘č;ĺăź%Őâ ďł“L¦vű‰M¨©î°¨Uşź˘ë$DN즩/• ,(ä©ůWWđ1a'–ôtr8•1T&ßĘ Ół~śC_@Čé[é®1 +<­Ž»$>~Hő›’dârż–Ôą v¸»Ŕźů9†{rT*±u­¦ä¸•ůtç⽊ŔÔ#ĺţÇŢĄlĹ‘ť˘_"¨çŘ5ę,’BĚe ÉTtŇ\®Ó䲄» ŁÓ ´ŽŤôű ű&kÄ_é0Xűży(˙ǤZĺĘýr Žżł26»lG˛Ťňě’­Üaďíç˝S€ĹYQpbĽ'ŕ~‡Uńs¨nÓÍS¨ĹhÎ:ľ™,†µiŹűńßú‰ß xÓxěű¨@Yb ÂÍŮGw,¸*jJĂňŤźÔDÁŻ+uE7$GÓdčX„(K”鵤xđňî÷É`O’Ž_ N}Xm‰Îř}÷đ«ućHM–ú<ě}Ó„­´v t¸ĎyŹ©} şm!aŁ’X|˙řĘ DńľŁ\6ąĆŚË4oü-|Ig c÷Tm€©ři4…G´çÇö¸`»¨ë6‚äkĺ­ž…*ľ ůbĽ&˝¦Ţ–jVĄ0´™Öë“0zß ý—h˙©łíµ:!qwĹ~’ă*™¨éńŞętt>śAb·ŮĘ ¤ą|0k|°šÂƉ6|>é{ř^Ŕ<*şóŁ„qFlUMńřÓ:V“·j&Q™˘$!H¸3 Ů+X^ŃÉYÂčmYWË:„…Ő7 ÝŇlß­|ÉTĘúaîńÖ©š}Š9ŚÇ—÷>GŤŽOÄmĽ`ŇÔď}oeĺ×R»ŻÓg­‹CqvaÔ`6V_¸™§"IsuűťyŚ@ŤX~ˇ’[Äđ%Ş ĺ…ÍG»1¤x"PŕîĎ˝D™±» RI—Űę3 Şm\‹ĂŞ7+a1`K#÷‚u¬xČĽ·„yË‚CŻ6ĎŐç‡mŮ9Ůt$mž6çäą«/päg4˝A©¸F¶VÜĆĂŇë(htG°@]ĺÉşzk {q LŃű`ôĎže{˝Wie¦ŔíŻëĺv¬b–ŻUöŃQrjÁ§#Ć`ůă‡rŇJfŢIŢ–îk7ëýŠÍmy•ÖßW ľ”Ézź1PĂĄEČ^Ćt¤Ę—¦{ä•:ŽtP@a~şŞ«ĚMŐHÔĚĆy‡źł*™­“ÂZćőÁRN\<ç\¸ßĺůQ]9ůľEÚă6Kčő°ć©r6 Őć˝i®`Ccčşlłu%]9%žĎY‹@QJętŔK- g€utŰĹVhXáŻPťš(H#Dz KŘqś—lϨԡ7ڦJň;\sţöNTúĂ8őĘ3…ˇҤÁfŚ #É!pŠ»‘ëyŤűŠ ‡ĘÄFč8ĺg6*©‡¦–!üŃ' ˸ľYŃ~­0»đ%7€…±MŢaAËv@ÉŻšYRżř¤IgŮh\-Ä™Ţ# DÇ{JŻ=Ň4Żźşë{ŕj2*¶lçk©.©íŕtZ´Ü;Üe7Ő<ĺ[ČűIř­b›*ľ ÉÖăJŔiđ¨' T gżţŇŮĘi? ňN©eWĺŐł$ęšg»g‘+®#LÔ^ö›ť"ç,ićű4,îٍç‘0čň$ $O«„¸ x`ĘŞHť Ĺ’9‰ĽËç–MjĚéĎr91Tű ­%`°Ü= ĄÂŽĘů­Ąc—U§©.Ąô˛u¦cě’âföÂzßµA©Ě4„M&g6š@)E…ör Eă­rťż™+R6r\’Ŕ%ËÜ@›Éô]ŰÍ© ż \Úň*Cđě ¤f!»EáŤíxăĎsc' =hvŤ‘äZxŤşfˇ€ŘË0.7)üZÔwaÍWö(Ż’ĘI‡{űÇgD¤&ÔĽăż(şŕ˘ů$˝PTżA)ţdű4î&± çéîŢ?đĎß ¬U·ŮFFŘLbŠ3NąuŚ2Y!‚×}Ý‹b’˛eťÂšĹűn‚[ŢeeS›×PbŔ—üĘaÖZţâ§żů1jl8%^1Íü~˙Ĺë Ú[ í—üŘÂË Ö?¶·‹/ćě˝(â$`ogŐú2Ĺ.Ű3đtŠ@ÂŃĚ=nѱô§ ŇI$î@XťĚśőź…éKB«‹—Us#µ˙qÉS:˛J¶ÇP'Ĺq#ů¤ĄZD—ý7ENˇ]f‘—?&ÖËś} ů26i˝Éh5ĽxÓzű(C”Źđz uCŘŠpćđo(±c€çEJ˛î¤1…„úĘ,ÇĐTy4V;;0Eć‡óÓČ˝dçEQ·ţ5¸â9¨.%<@-®·±yi]("03TndQĐ)2׌‹/Wjňd¶vąíŤSéďÖ™źíâ… î'U@M WűŰ‚Ł`‘ůž'˙ açjĹîĎĐDc?âÖ?˛ć×B3yćÍ,ő«"4ËÄTuŽ;ĆZµĚŠ ňţţďcľ®;ĐĐţŮHŻIéňÝ’Ľ@őđwŰ•|äÓGgä Hś5‡ŚHOĚuíW™!XşuZ–UĎFDv¤YѲÔr\’wöW^°8Ëéá˘ň/6 íKś8ŇÎxíŇnä)]TĚC€p?“ŮY !D¤8¤Y٬WŚyËJ·ţ ¸l(ďŮä3řĘtÔ™[(V3üQ yÎJăţV üÔţI{?şmi˘˘ëŕKgúépżVĽÚĂźÉC7rm\KńÉ(Ź˙›{őĆÚŕ–qb‚óÍç;ýa|Ž7† SL˙9űĂ­©ÎĐ&iŻ-ŃÂ29gY ĺ/Â,¬Ł%CZOf4˙ÜM€‹ľđ¦–{cźťzď‘ůß‘ž˙ܱ5E71ʬg]6 lPŁwf°Q\ŤUK®!hS—Č;|Ś@zż\C*%ÖiĘËLou°AÝű¬w­ý~˛ íő ţÓ  Q¨Ó™Űť7«áťÖŃz)Sňżđźóť 0ž) lF ˇ†;lęŞďL~ܶ­ ëc_h"á#1Š;ľ-i÷ë_™Ęע?ŁÇŔ\NŤĐ(‰¸%1Dł¨hnMO\lWśC…rśŐŞ‹Ł91҉ ä­Ë`‘+÷ťnQŇ bŢ9T‚!8Ă 8˝}Ärł“ý_gʦ !|T’—kŰF–µŚ~S϶[NK٫ٛÂ:“Ä;O‰Ö;·jäÚˇęi L JůAŚ­@áÔáyµéŻ<Ýó‚Ôuă7#(ćČWj–śXŻT¤dtďŔ×GP˝€Ě{@‹śq šjÔ0čĚTŁŚ#ÉĂţć8X™M_‰ qKSH•ű(Śŕş?†?ŤŤŘYH” ăŇMŃŁ´Ć’Éĺ-1¦ňËśó`ÉÁ¬ňs{ż*1€“,¦ŁĂřᄟ_>P›ŹIČ*Ú;ŤéĄ“ČűUW|›Ľe ”XźˇÔpüÎßp©Ťm€»gŞs´ű_ŃÔ·‡¸™Šn÷IşŠÖřŤö„Hr!‘šü3ďţR­^ş2@==á2×Ŕ™Ń-ĹQ\%¸€ŠF0Ö2ţEÝtă<ݬ¤"¶ÓłO•-#a"z&ČAeɧ “1Xúżbźe±77‰üČspÚYąÓu~ý‡$şĐ-řI~lb`XµoĘymĹ™‘§‹?n˛FŔž›[:Ë(ąŕă26—Űkt’[ő0΋ŹŮ$‚EŔY2Nn[{j[×Őçî D (ëŕ'ÂôĘJp ßy5Q¶Ĺ#Ł”Ź=͸Ţýîş_×îiő1Ä!(â ‹đĽ†[;+ô6¤MűÇĘ9‚»«D¨„żM|óîúAy×'”IyżX¤FĚPCaLŞbźBšçD( ÎŹ¸*ĺ€fł$AÖA ©R;^…<űýjc\Őîm;r±ę rťý¸đyőUWUşś ˝0±š čT3‹Ş‰çż>Ő¶»jwíüÂžŠĽ†’'íµ~d—ö,J'˝Y‰ň_EÜrÓ _ú'ĄÚěň'IU’ýٵĐŞaÎű• ůzLUĐĄ4ĐçqűóŽ+vtâęĂńąČ†ë'k•9Ä;ęŘÇńůď}*‰2d4´“@KI)ÇR>EŔ˙f=Ł}'+7IŽJ_L6|¸‰–h=XD7ä3ú X ľŔÁ†;Ń2xÓ‰§˛\€7c¨ňEGżűĺ­E«ăĎ˝¸z*<*Nv5Ťšwřú˙-ÜúvmČH{Wň2F(°ŚŁ"ŹL}ą«PÁÄÂçlŕ=zK™Ž'–Ň8\_O&Uxq•šż Hç°&~ŁrBÔ’J{÷ëOZé:ś OĚgf6Ť˙shŇöŁÁÖ¦NĽSčdĺLgWuĆZm÷â#LA;jëĚg€UíQIwX{Ź=h"bÓDűľ©€Ú}WÎżĆÚo‚9lçČŞA¦5ăk0¨­hŮýš5gUëŃF-äBąqNEú^ˇ[SË)iü:ťb[Ů7Ý®-@ĆZYÁG®¤„Áîe2÷ĐĎ „5‡lĐ!účű\úĽAL’Sßîť;áę碌Qx·jÉođ§-5(†!řwüC‹*Hte|B¨e›+ü>0,캥łŇÚ§:ŕ|RÝ2GjmYŮ{·ëG™«±ué-]Űő—÷€ ‰vË_ ›č;•‹mţi4&—ý€«Ú„YFOő®@ČQIgUuĚa-ňô= ţ3uÉU‹ńčJ ÝNâ\~Sóŕžíë˝söpWăQ5ý(ȨŢ[†UĽőĂ»îߪشŇ`îĆäŰŕř“ÍóMq_™ůo5E VĚ7c ®ż‘›éFě}ľć…™&š—ŽhŤâŁńžđë?™vxbÎŢDé%;Ýb‰wĎwŁ‚«NK•sb«ňk†!ggŕŔ( ÓÂN”lęă×JV(ÓśÚŠŻ­O@ÚĆn,Érů&NÇ"­“mŘ{7číJťB6–˙Ý(<©BWXJൎŇ~۬ČĂd˙Ě3‘·ď:ÄTZĺ#‚ŁJžK ‚„$4gaóI$$Ü…emŻň‰wP°ÄćĎOçÉWÁAź$[^¶nŹÔ§„㣓°ÁK÷fŻ; ý©‘ŘŽ´ČŢ üÓHzZ‚#5—)@ž»ą:6ă‘Ő4ţďRť¨đ ÍŇľ ·'nÓ7 2íĄ‰¤#Öű™Ľ—Ógřá_•Yá—po/äÉoŘ×enŹ{CÁšgˇđˇĹÚźý-é@63#E顡‘ŽŁN+jq<%úFg Gâpg–őX:˛JŚ<éţ0:5–˘ĹzvšR«Ł±7óçôą‰“ đ‚ŕq9w#ťÎĺS)w+ňb‡J(‰Hw٧7w°Îîůť#4˙hćņnŐ€o˘Z]Ć—•;âRnÚę7®VSá ĆCWBvł©pľü¨ńˇé¶źhÄ× &l¸—ůi±źü"»+ ňÓĽeënÝńź.nŇ>ĆcSÍúí„î –úŽf­vÜălRç€{6n"D=¦w9j‡)ł*Ś{ĆĽ…â~AĂóŘ^jÂŽ* zňhD/wHZťZy¬ĽŻ@!SpÁW ˙Ů× ›Čd˛źĘ-Pć;­źyÎ]L :·5úrRâ&)a%ôÚóSţठE`΋cC†H„ĺI/⚼żś'€zhŁąčŁVЬˇř÷Ć‚Í;šg€Pp.MŐL˘+{ĽĘqŠ—6»Ž:ŕ,ݲł#4_6A8v #€„ůüĄA;ř!h˙$ůfu›­*2ťßňNŘ`Űąä*Żăâń¸Ţ‹Ö¸[XšIĄ„ăŕÉ+?ţ6ĺa*b¦0A+× QFQW}ź>TK˛q6QäęH ł€§ĺ…Ąňâo¨zô• ÇđßTÍW„™ĄßŐZ‡R9jĄEě JÉ5]Ç<ľôuL:äNĆŠY ĺ޲Ňř‰řW›ŞŮQ {R@‹ňâ*tć}űĎ;–EëÂ}ŢdĹ<ď^ Ińâ(ßĎN.eض‘E§Ł­ô«·2Ćx|pĽhC”'’ÂLrpă32Ű€ďĎ…ďŔ<]_ÎĹâ˘}@B‡öĄŽ <•WBŹF¨F|”ż€ŠBˇś]+h ;ĺ&lŔô†"W6öĎ‘Ý.ég­_b, ô>ÓěçëÁ“®Ŕ »N›#7¨“BŃžîqG˙uOXŐxEË5ţεá(@š6ÜRŔ:˛~®Ă˛Ë?_8Ůç"Z[ç´qó”źW Ű/€ÍüóŹ ¬”ˇhÍV'ÉÝ÷˛ÜłU˘8Íôżr-yŹĽ“cŞ`:ä ˛4,¦¤Î|ĂüMS0¦Ö@1yRٰQAÖ‚ĹBđ_"ć˛`+ÖÄÇ÷çľžýOíşá~Ň·ŹĄ×Ž5]WĆ."¦š ňÚĐjÄ~opʇ®xneAEIV‡ß’:”sȱٲOöŰ׺d‰v·ŹÚ.\(20sNĽŐB8,î¨ćS㎱ń€!+ / QĐĽ Ą˛(spCĂ4’Űł ŢChŽ$č–×E¦Ú­Ľ!g*Šę‹ç|Ë˝D\đ1٬Ŕ´V:?­€‚–o˝.Ü4ő†ŃČ V®‡P{fě»fË´8Śh@čłĚA Ňuý|ÇFlűăuŔ›†*Ř r -ç­ÍCm{ Ě †Y}âÚlOC·hŰ]©öaŕf8qô¶ECÓTPl’ţ"ygm%vú4ťňY•G;‡ˇٲ¶ţ"ŕŞÁ€:Ëĺ+[ˇbÜ’v-#¶¸D•őĂĎ8FűŚÓ¸vâw®•Gjüü1(€ ‹Zý”‘z‡yTŞ»ě„íKČŽ|]ĽűáI“ü*óáúłjë.*Ą^bČÔ.Äo·»ýÍ ŔśčßxDŔ…ňůŐ¨ü´Ż ŕéT_Č4Ś…łŘ,Wâ_?†z´ç) |ú­$u|hôíS^ý †¸lĘëÄ9ýđSúîć Ň€îڍŚ~"îQUĹÄ#Řś’ôúhť1v™­b*Ľ¤ď_/¨HP5ľ\.Ëoü­‚¨­đ2¨v„ c¶¶čZŢ엜″qˇ›Ś gŤ2ű-˘áyr˙!ÂyI `ą+t@ňĄB§|,©Ý|r*×Ań•·Ř/Séăg‘(ŞäŁd1=đŻ Ä÷Ű98J}^*ÁBľ+”˛>A)¨‘ă ën˘ ű†1`ýŐtÝKą»Ŕ= ˇ.ń§Ů4]µŁW®ŇÜCÉAPQ$0p:Śo 4›ve±t~p9ű'JâűŢÚµA»Ţ†T4#ž~ĐhâRÇ-Úď!J&n‚ńHsš!í»_•ĚĂŻ¶ -Lâîŕ;+mÎ8—»™luĎÂöĽş$K#{ţ&äö/´«WŠÖěS iv§_;‘ó+čdŐxj©«˙=\s-ŕDˇ=şĽ#‚FÍämJ®žz0Ł@hÂH† ČžĄŔnŽYÄP\–Šgđ„Q«ÂĚ|?’¸ô+zÓ˝čBQ¶ţĆ-‘r€±ŚmŐ¸UÍě-!` >'6Vďęr:źĐ Ji7ŢJí|3ŽÁ‘g‘ü‹Ę'Ď’×RŹŐ‡ęţ†ź–ü[Lşĺěďmf ¤¦˘´óIoYú“ü§»ü«Š>ČŚ®^ÉuűĄž°ŹOŽHł'ÓČyY|_ Ń@Śŕ©_Y éĂ„×ĦÄV‹=óá—şľŇz­´.›ţ)g†Qń.ÉǤDrgwćHÄ9Żqň"ănˇŤŞŘER‰ń9őą3“ľŔć-ţŮTih7ʔߔ!9ŚôIŘXK»ńjj`yÔ'ÎŮ‘‹>«śµlĹ“0 r Ůü|v5~cubϲbśÎč‡÷Lîł cV˘$¸­—乪PUve*ě R#? žK0P±ĹĹÎ ŻŃ §pÇňĽÓ)ô„ń.‹+ĚśŞď¤nyiwë]ưĄ(”©C€ş—ĐĹ0tö/뺺ݰ=ł&tólpŠĆĄo’[ó ůaQ–% ÄqJQ3şŻČüŐ®5ŻŢ·Ň@B=#cMĘR[Ů5Öť :wŕüˇZ[F©ëxňj‚¤PĄőČ ľ ×î< Ú0©*ěWF˛ 0ůÔČä_U +Üíwi–ŐŽĽ'4pSĆF#QËx÷ylUsăčaéŃUaE 16?“´¬PĺźZ%ńźWnŞ?GşuŞß¤.\â­¶±@đŚňŰĹč‡üăŤv‘{S>F%Č-ťOřľxvľII!ĚX7|őćY3“q¬Ä( ›FÔ˙—áşÖ®@eJ>ŰuŐý:¸té+¤ ljXO͆0ga˘@˛ zŢM–Âd Šňš?ÉT7ˇvaN°Wą(gSźm'’#%9BÎݡB(.5çP€†ű(ą'řSáĚéäŘ`˙BTŤÖíTxťíhňq\DřłbvĂ*ZůčÝńCČĺQµfĐ sVq“G“ÚQG÷™q˙ť;•ç°FŤgÍ!y,b¤˘‘śW,ö˘îjäöú‹ž˙jĄĘ ;®ŃZVŻ{™ ˛C&3+ÄŤ·Ź žćA’QlX%^ůLňŹĐ˙—fďě&ZůČ}«2T3aĽťx|UáăŘ–ÍOěŰ X…űänÂꍝĺĘX#˝ \Äţ/–ÇŹyX …ST)ś›¦_­Ą»^)›Ďqż¶,öqý’Rľ łV¨§›Łő´Ůux ôT 4őIä^ÉmőA€â§VĚoě^×ů ¤‘ eNşďĘŮ˙ÁÜÔUfŞËä'ë¶ô¶bá5Łb:J„ĄFI‘"&Ĺ×hÚúA# ËČŽ˝˝5cÇLĚŮEĐ\ RZ¤Ođ`–üCąĆ!µ%˘BŢ#‹ú÷u—~čÁ8‡„ş(: ¶ +ÁEAßŢ37C7Şkž“¦Wy´ŕd˙řyV—%[k`ĄFßşÁŰŤ}fKĽ›a3Űs‘îUr5@ŽtţA¦|ß­E ă¨)><®Ę°J4ËeSýŮľkˇC€5ńÁÔ¦ŘU%j> ŕćLŘťtÔAk†ýËôZŻęX¬P3x˘ĘK/¦Ţ™$+‰“pÓ4ëýÂć9­úíTŔă¦ŔŔ7K|gť\2%÷HĽ”®5Y¤…^(Ź·kúŮáËAŹŇF¤D-;c’@ž„ź]×lÜMżpYţĄŤîA3@ŚI‰XwDˇrU4ßő!8Ńö›—†ĺGSČĚ8ť{°*â9ŔÁrxN›šăő*ä“0«™ ĹâĆ’» ĚvpWËŽ´öŃ(G˘ Z &Z‹ĐŤz@0ĂňŘúY»čJ"<$ŹLDTÚ@‹’«$Wő‰ő°ńµ¸ ä'ĘZ=/Ĺţ/ T>‰ÔőűS€6e/˘kâzY1jÚße.ćkä ë媉.D‰^AŢyuWÖĆ1äS¦_7ůż2ż ÷=\Tce§"¦°’ĹÇ×»ŚŚ·ÍŹ3yśF†oCď­ô“źÔăëÖů_Ŕ¸´(š7jč¨='ŢíŞXÍ.¶ý9“[~•oq+ÍaŔłw”'ĂŽ:ĹČH ¦ésŇ÷6°Ć·uÎó vZd0ą-užĎxÜŚ~®ľ¬NgK‡Š‹éaÄM2×µu™Ó!B’łË_‹ŞfÔ'á_Öµ`ĐX¬Ů#ś§C0Ôwî9_|Ü‚4¬ăüP3’¦ýŽqčßv‘Ěą?e¤ ;!ŁŞÁ“ü”«ŔóűôĘŚ…yµ/;⛹ňđŢ{Jܸɨ0‘ÁĂßc}n”{ĆcELANă¶ß;3‡×öé=ˇ~ E…l)¦Gކő|”ƆÚďca­s™ůÚ—[z.ŤŃ§×€o${^ĎF^GXíi«/ ™gˇ p…Ťçź,ëbÍńç1”3ČŠšk!ďz"ĂsÓ·oĎ„‰vzU][§Ô+s†®ÖŐjŢ|w Ë+u<`lÝÜ´»ęö¶żk>ö$-a…©.áieĎn@p‚ĚĐƦ/ W”ëýbGŘJV4#Ţýĺő)MgiÔÔ˛ Ô‹ú%ůĂçǰ¬€Ă¶o޵b×Vt M˘“Ž!XoúNdwë¸C]Čőwějó § R¦ţ¸«müÁ‡,.˙µ*ĂţXĚĺÁŰľ+ yúýh˙\Ąży°›–-J>čŔĎ×Ń/î4ŕΨT>`Eeµ[v¶ŞYQSŹ Í7¸ţŽ-UÁş|‚Ź]ÚÓ[(˝ś}ŚÝ¶€łß@°šdvvN źŚ MĐ*hćuÜiĐ7ůZˇł?=lc} ^Ń}'‰ŚĹŠČĆ–-‚¦¸;G&J‰’éiÜ˙că ò ÜŢĽ†¬A@NcwË1$®n‚Ň#ŔÚĄÉ Á˝L¬/`¸(<©ě qł"µ+&5–8‡5/Z ;ädły¶‘ÓÄÄm^Ĺ•?7ëö‰Űý‚M’-Ć3P…ě1x˘5Ľ>}@s\Ř—nU|ËÚ*­ÖçĎ˝sť/‘¦Ď&ňŕăVť”•üňěďő…E­Fţä¸h‰pÜ”đ,Ѹ ĺH<ŕ™¸Ń;ÖśýÎČõŚy+ëWŔćä:íóČ 4Ţř WíűŁnă âéžęŻ-R·h\±×®l?0űŰ“ŞŞpµĘÇ čFŘ\ôNř4QSÇN PöMĂżđ „-ĽŽűJg€Ć>Ć«÷îÇĆĂ{8b9Ö)…¬» (ç‹_í§Iµ€ĂX8Č5Ĺ^  c˙"ămäÜćŐ9m`ěš Â°‹äĄŽ„ÔÍ›dÔţ ÂŔuB5ÂDlî稙üƣʰz~\ŮzÎH=+…ŕ+:Ľ-§ýńx*LP¤™ß¦Žű LŢ/bťU(ăěly°~ŕ˝’ęb«÷ţC$łľ†¨órYز Ă·ű,ô[ŇŻ,±q!…}ÖNĺAů}‚•ZqĎ.ÝôX]řo6˛Áµ ϧP$ ŐiV@C|-™#i[óq2Zőř}Ş;•ßÍ2µ€±CŮâĄ!Í Šő8_eTC‡.ĺŞ`0ăĐ…µ$ş=Éö쯣(J76 cÄ?@ L˛Mŕo„m]—±*Š×dÝÓsň\ů—iŠM?ÄزU‰F­:BŚ'ßLú«şĎ•#VA[ˇÎ~AaTˇŁŠÝmcuÝÁť˙‘Ă…kČ(łŻĂgé&0‘Ô“,|‹2¦ŁvbBŇ-Љw1ůyÖ]źÉă\Ô,łBNýĹ_=&ţŻÉb2eD”[ĂT5ÖĐe‚ůč69:˘đšÄŔ×^óC*}M&R\~v‰”Ă‚W[‚N¤ăgAóÖŞlQř{8l1”ďlyq! ‚ćt8ˇuá˙0xŐ˘'š5s˝âýú©*|9Đý-˝EÎŇČÖ3˝Ńí§ĽĐ>Â*ö)H7jgŚák›5ôsFę:ÍÚ'ÄKŔ·Ľ=¨ÂQ¶uڎřÖ„•äo5R±˘ßő¨Ł*ŢHôĐÇ_ůíýŚÜ5”_W.ŻŐ˙ą0˙ Ü*¨ó~ ŢqŹq?Ŕ«ż(nĄ5ë9űɵ?‚uâŃ/qČd˝÷‰®×J$«Ž NLT(’;ěV-ŻíÔZ¬sŰ||wÉţť4ÖňóK_™*šx¤#!ą3ŞÉFT®-‡Dť(Ľą-1a¦ĐöËŠ`Č<ĘO3ź ¶Îs“€aµ 7¦-¬É6d—9űű%9çÎçĺ‡~â˝T 7ů/ůÄA€éčťÉͨ9čĽîŤţoÇŠúv¦hĎ›>BJŮÇ@¨žű~™ü´łF¤Ł°ćýčĹČZq â-ąôÄRáĚö\YĽÚTýrěäć.x¶>)×~îď‡ VóćOR*Ä»¸Ű…ýěADŤťTú}«ŢÓ \,ĹţŤ¬‹©!±Ę˙‡"´—ľ“éţëśÂĆ.Ř`#řiróµńĎćĎ<ŘţŁ•€Ëćʲkëűž%1ąűKŃýSřgur5Eźkě›"J1]ÖtŹ UÚW‹có:[˛á˝–^äYż@y7<{đ!X '•8&*‘O iGCąHŠ -Q©ž|A‘qX‡†‹#3ÝC<™ĆĘ.X2Ď(¸Pá%ťŕçq3 ]ÚwQ&XHBů :ľŘRÎÖ9¤Žč2O€ßHfPçKĽˇĺ´ű”ał* J$ýđµź6>O$6Eĺ—Ĩ©¤n»úËH}†Ę Žf¦h9lÝ*fúΆŠĚ*—@©@j~ł~…–Ř'zﻑßô'±Ŕ7Ą3ě¸ ÇC¨ł˝$+S:F x]ekěń*ć­‹«ˇUÔě<Ýž˛ 6dĎV:ĽŃCĂ8KŢq®Ď ŇĆbű:ˇ4ŽwD˘¬ĘčR˙‡Ë‹u)T°OÝy%8}g)_wř IÚYxŻĺu…tţouĄ'Ő ˛FL®Z&ÇÂ×ŧrËgł±Ĺi–jäÇIfhÎWy|űŃ uâ§ë7ż řßΠřSýäë•'dUâ®ę‚ä˙đÖ3ϰßFJÓÂZšXďť—ää”l˘"łŔú—L-ĺa­T| ş;Ç79€wkËL€Ç÷)ŮęŃq<Öµn/XŇ3ĺńŇšmč8«ˇ<Üvšę“o^ ‹‚ăś{-ˇŞR?—&u!­~ĆŠGH–M‰ą™ěž%Ţ8Ű›Y’•­#i1¸ŻFQí™Ď*ž"Úŕ§ÍŘĚ•H¬z%ć ďIž˛ĹcÉŇCřES’†í˙ť„»ô .ögTG«ę €…?şą P|Łâ˝H;]J L/Hšw$”ÍrßdçL™‚^ÎłkůâkÔbń®ÉżÚÝ·3X—˛Đw'm§Őo”Q™.k»µ?*ĚŘ­ąw{$úVr{›0Č ŠßU‘ňz,Ŕ˝Ń&ĂÖ#ńN ´ŻYę4ÜťäTh?:\Ďt@€ë’ Ű "Ç$c¨:‘+M‡żńĂ#Ą¨:<›u5}ČĚ\”¶ŕýÎM U;Ö^łü_^TßĆ—je7WůąH2ć§#™Đ ŠŢ ?6^bćěy"<=ďĘ Ç4 WáťĺěĺÄbŻl§¸#ëŰ÷>÷M…˘»™!y$JÇUvöýšŢŠŻY:\ýHyŐ‰$?„ńńóćjx Ťä 7‘Ş>źjDLeĽŮčâÍk|¨ÖŰobENu!O6Q¦xhĹigŤĂÚó¤÷Qnőđťâ}ß§Ě·/)áKŹÜ®g÷¬v˙é‡kŽ›ŽbRó 4b:’ĎüÓźó^„.ڏ«¸ĆćíŘŃH˝F 5ö%˛géozpĄž#®âŤ6÷źłî׬0ÄMdľ;Ůóäç_Đţ`UÖ}w±\î őŞ/R_ˇ $ĽŽJŞßU3»§î·BÄ>ĺŽhla˘J"ůLlÔ;†V0gäcĎ>ÄÎó¶9ÇBNŕ„ĹľUxŻCĎWÎ'¤ďíP…·…Ľß Ođ ş!PŰ‹EigČfáN ”¦řQ‹§T3źž+ÚT±O¤IÜFHizŹs´ĘîG3˛đĺ–‘Üz"PvĆ o™.WăÔ~ˇJřr1ĘźL»ĂŢ:€ůµ»Ú‰>/@×rŐ f湢ŢđŻŮŁ»ö.®}yůŤG7ÔŻřęţDu3uMžŠ,›ďâǵl®ŁP! 4ś gfU“Ş·©zţŞ,M˘vn€%‹‡Vˇeď„Zí̲Gţb|ö¸•ćK.U+qő'ç+ŠünM[ŤNZWÓcˇ:˘•˙?°;čý0ťŤŰ!ëtŕř\0O¦;K˝yg$§xC‰Ş—ÍIHLłFźô-ąĘý2+*ćšĐénl“>ýĆÎg4•űéq䲉ďđ8%0F8',T”Ý-Ö•§ś^9ĽWŻć<ÁX9ąž©L/Çlď`Ły|Şp‘ŮnˇĘ©Ą-˝GĎocŹ €Ô^lcś”ą¨ł\´ś"µš?”Ńgeä˙4› Cę?úŃX(Ą Fž†Fh;YCt -–űC8X˛Řfo°‚Oλo˝łĹŰ s?É$#ąmđŠ#źňdćáZ[˙řĘ Cg ˛•€‰h,Z ęL¤CLÓ5+VŕŤóČ{€nj9eŢWÍ#ă0 5é <˘ĺE9 p„¨ídNL÷ňđű„8vd#PYÁĽ^ěšr˝âĚN3˛şR‡ljˇî<4ç·6¤™DU…@Ňâ™UéńÂŐ‰O@ö>°‘üŰPB‘tăuzLU…’h|¦‚¦Ę0čĐ$z™«zŻP(óŘř5K7Ŕ š1"‚žIű@k‰ş0ş=¬ĺ®+vP)E+˝©M ·f·YÁb‡Pú'ů+ţŐÎ=ĹĆpđ5‚ř`FŁX´gŤuL˛Ô&¦°ĘŠěîMĺŹ^;Kf@ţ„Ô*{äÎÉçő Ý«Ď)ŐBŇŠňt‡+awđ(ţQS§ÖúÓ5ńŚ Ęâ±ě¨ó  Ńľ™¸FL¸y¤çŻţŹL±vĂźî7(Ą_běK&Üqú=*é1 0Ż~ĚĽjž®ˇ;iN<Éc”Ť=i Ź•š&Ůşßü˝ĚÚ;0pf|Ě—¦śzW…ýŘ0¨žXL|]ˇš_“m†µ0ćbÓŁŢ_ŐĆ ĚpKO/<7ĺJÄĹg R[ťpéńŹăőŐô8zuuxř–&Dłz!ęD_¦u•íĘŕs"×ţlČĎh}Oݓů{ďq?*Çí®;„ S›ĹWS¨Ćâ6OíčÄ˙ »`g–îĺŕőh°ţőř>Ęg%QZ‚RćTjáâĘč8ŤĺSÔ ­îŤĘšżäËŞ` !lüo<®žaíőü'¨2Żu!Ż˝dětńłÇ6żÄéŇč“Ču÷ŤÎqŞÂłě…aá·iďç3s¦a—Ov<8”Ý˙\fîCçň9äŢť :Y ŃŠŐg đ۵TvŹŰ&‹sĹ™ĹrÎÜz†cÝb[$”‰˝vÍb>ŃQŞđ@es!‹ĺÍćąovÂ,EÔNT3Tůu†¶¨-Ć[»`řOyU+—y9ĹPn´×„üérđÎ%úď×LŮŰP­¶fHO&$8TŻ»ńŤ¦®ľçXW–Uiđ7?[%ŰAäi(ˇÓiáě±D%v!”ËN“_o’É<…oÁŐň»%ÂW‘!€™#”q?˝Ě˙ă_‹±e×Ę +|őî ˙_ůÍ;¦žî˘(3[šrÓíáĆi~›€Â‡ĽÁą 1wĺtí:iŢ”ľŮó˘ą{©<ĺ?­,UÔk±™yßáˇIß“\KŔo0üm:;̶®fż¤î0"ükŃ Ć_ő{2ŚNřŘ|˘íŁň¬ĄzŮr&hełŽŕůoÉmŐ@7çńp!Ž/.g1˛·y=Šu¤sO»wšuaPAł˛®ŁĹ®@iüXk‹2H>­ć¸řXŢ÷±Ř»»˘”ÜGwZ;ÍnîÂ?TÉ˝D~6V;y-Cą‘Ë`¤Q˝'\€šćšóľjÜ{ Uö2¦OŁOÂ~ÁĺČ#żíJt‰âę?ö\ŻÇť ˛FŞoÖXbŐăŘŃfĺ¨~J|OLء4lřéôôŰĐ”$DĆQŢ‘kŹbČ›Ur*5ySlOßiü49Ć $}ńѸ˝â¦`g¤ŘUXW8ý±"üČ ôvĽÓ¨}D˝•LÍWĺúůžÓ‹¸ť‹‹Ńsďk¶ÔMü$*ÎoL°ˇAˇż i˝QE4)șľ>úWŞó*`ĚlüšwęŁgŔÖ­vóň…j[[ıé­ĚJMrúí5§žŻ YJ´_“ĆŢ51+čđ˛Uś Ý_1+nérßöa¶@d‹ş3Ć™ŞšńŔ•Ad8ýµHWÔ§­uÓšŢňüípę“ŃÚOś)eĺÔ»“ô}Üéh_[sÚ'ć] d| ©:ĂËŹÄö~k•*&J*’9C[íü[´µ&H5‹Đm™i.Ľö=ę2N(/ŐX •@”‘Lnď2RtíAwŰÔŹxçń«ő —gm%ĄĺŇŔX÷—lÓă ®™ÇÖ†‡]ţ>ËńĚ8c˝vWś=ꆑő]É´óéI›>ńË|ĄĄEńźˇE–€»˝NÁŠPůórI?”‰î=M,lSč•[iŐ¬™žâŐhř ĆĚďü-›÷AtžyÝL hĽŢÓ>ąCyV{ďÍďŮĹhĎ2Óˇ1Mó×ö˛fk}°ĚĎůŞËÁäĹę–Ă#±V dv9¸Ijĺ1g[’NĘâ!ÎxëÜ‚'D2eˇô·˝nMAň~|—ŇźÉ̡tcMŘsçdńo 65—•”‡"Čť<˘ ÉpL˘4É:×r=ĚÔ/ł„ “ëƉ-OŘ =ćľĂŔŔ­8yXŃN‰‚Žf«ĎÚĐ XB\0DOa÷~řnm>n´ë>Ď_ĺnFKY\ßĂP/k“äĚ禜˙/Ś’ĺ0 @FFW `«]Ť~E)p3Ť8™+7I»°,mŠ~ yLltťĎQ®Ń~JšrÎŘ•a}ÔĹqży­Rw`pü%`‰Ť8áÓ­˘ŢŁ&ň©î¤č6H¬4ÜyŕLJ¦WÖŁ†WNg‘¸hq5 ŁyŞ0FĹŞŁ.ÖkyL©}Ä*đă#Ô_đüg!F® d·ü$}8+»ÜÓušŰüśI °;FpO69v ě÷ĄŚ±Č©6’tzţv' ňäůîÄ3¦çô_Ťm RW:0Ě"k·µŔż ¤ô43gě“#Čk ťD§™q˙L\%|vx‚2đ†BVâDľs©rWKŘyW31îâŃ=Íxv.b,fł>Ľ´4Cş< ±\©<š“·JŽbźBCúCË–eőYů (M76őŘ-¨ĎJăČňź—Ë•‘…݉˛ĚX•CĎÇďż<¨1ĐęÔ‡ŠŰ lťTÉö€!¶KW.ÂăŤň( NźhńF­Ź…+6jdYc˘f"•ť,ôßgÖâ㞠•ŤF>aľňv’¬¸jfgŹOaе5 FäÓ†jâ2##{ [Q‰ÁS§¤žÎú—pEĆęâ’ ŐGęŹ%4Ö”Ş×Çťë@Űk‰ŕ™’ ţ…>×ëo04ď˙tč­Ě!¨\L^"Ôp–Ś­´&Ź€âABó´Zrô€ć04ÝF Ľ6ŤyËO$»6Ç <5âákvü.ࢗĂţu¦jľŹ–ž‘A‰ŤńM!Ćóü’ZšÜ Ш]e!Ś‚ ‰B¶›ë8Ŕę íWÂ{ŻEłXz–M§ŕ÷BĄh7©ÜD;`ÎŘĂ*€d`,Ńi"G7¨“˝,©xĘ ÉŮřVŁYXŻÎY qĚ´Wőł -MSy/Icôň–¬zHÍz2ńH Źá,ÜۉJԢȢ: €ďąĎš§tűÝ$¨˝ŘIľ(ß–z6śţ÷ăňýR4>X(ßŮhĐ\´ßȲýĆOŘĘ.Důř”âs>Ţ7`ó3¬űőF˘ýŤQDEa`÷ß0u&„m’Čłđt„‘I_˙‹ÖŐű— `fÓnŤÁpiY[Sřď9™BĄř,Ć™ľ} „ÂĹŐçËxfuyb¬h ŢÄÇhÖĘlR¶F4Ó‚’xęďđ2(Ä2U˙ýŐÉçpDdĺsAĄdVΊMtŘ­wlI˘?=™.ßxńUđ°ł ¸K-ÄďÓÝf‘i˘§I7 z#‰šÍIî ĎͨµŮ —é,@´Ôő Ú5ńP[˛E#Şřě__vV]ÄĎ‘ \]†fŠç^›UKpUšÁŤôËgÄţČŘC"í•?Pm5 lr_©ń„üɦ—J:ônBť¶C*±‡E¸Ä¨ewąz^ ŻZ@žî=ĹgtÓ+ü ĂNFĐB?·ěÚýń ¸LŢ<Ź2ZG‚Í˙Ă`n!ß)ŢąGeč ŁĂ!sˇßg[ËëM»nŹ2ĎúwçU×[¶€¬ŠđJ&·ü—ń«HŽÚ*t­c\śŰŃ+UCN^ĎžIÍ«kwV1Xď]. ™Ôăk©w‚° 9ĂĎè+ÜΔ.WŁ8`#(ÁśŇ°˘ ěľő>L{ä_m÷ŕwALÄÚG‡*=J¸­íüW=Ă’¨Bb?8ě’sϺܴOűĘCŐPâ]i¶Eiha©řdEݾ§F*'Sâ@đü[ë—/Č=ďÄôÍM@ęNK:?ř*ˇ•EfZs1‘J¤YZçj…gő$Ë w˝¸-¦‚mGÔĂż }áµ ‰!hH¸ß.7(e’w«ndĚ›ţyDŞl¶"´ÎD-Uofî>x“™)ŕbýaŻXÖlRë©â)gK‰†«B­/ű©,ĺŮňc.Î5¸ń­Ó7lŔÇ Ł»:XŐc—®ŠC©°2áę°g¬Żpď×|¨ŞÓgOĺŔ{ěmöżČ,‚Ü˙ĂNĂ‹Ë_Ťä}©JýkŠĘČÖţŮÁmý&7 xł[űtp0ý0]~F‡ ć*gŮ®G¤čŇác™X _Dn§ LÚťŁ)ť¸rČe Ş›Ę±Ö9,Śäőűą˛XólăőŚ*±’”i»´§ZŘób=ŁBçTżjçţňíOHÓőŘçĎŚëöXćđ<·źŐ-ĄD`-2¬‰´[¦"úýČ[ÝiâµĐ ~̨§˙’ĐZ)-Ł„˙$€x;‘xî¦Q$˛rü¦ 4‘śÖţ6čŻqé6ĺŐgô˝…ý.ĂŕbGíŹJJ™Q-×â`Ę÷ Đ-˙•đ`0öźé-µÓ—Ţj–Výľ)éČŹţxĆ™}[dd¸jŽâ˙KÂĹ…ĂB°ß8ÓÂá(‡Z˙ä^^ľˇĂ­ü"s7T9~ʸPˢöx‹s5ť.šŇŞeDŐÄk×´;µ]úĎ=GUT6ŠmÎJ<ż‘ĘÄ’"Ł®?úźęQw(3ŞŐnś— ËŐ:)*Ý˙ÖîŁb‚ęMčrřţ:Í9ÓD‚Ęá |Cm ›ĄÄ’ŮÎŹ#ŮjcW]Y™ż ”tae} đoă)ëČâÍ%#ŃçA?.X+×hn»ß’™.d7á1ÝŔ*#“’,ąŚÄ&=9"ç’r<Ą^ÁÂv6s6ŕ)™Q·µFhˇ01˘q¸NuJ‰rgŻŘ]úŹÁľ*ĐEŞHł+pŤđ#c oę-žZŁ@L;ź$3«±¸µęÇcĹFĂ—ź’-±TPšßöXńâ0/|ň­ţn*†ĐxB%]롯3;~RßqŠ<“·$‡4„?Čů8$Ő9ž1Ż&d¨«#üŐŘIUŐ°čW‹„š=YµĆKíł8ţÜĎ[ŇłTt ĹĽT‰m&gŐ¶sőě(@!" Er饶ăâ:žśC9ܢ'Ě@Ó˛@QĐu'@ÔŇßěݶůŔŠČĘÝ­ĂPo>ĺqŻ€›sÝŢ0găaÇ—ŻčŠšHHvMźďM§ŢËÚó߯ÖimÁŮňe2ŻÝ—"CÎtT™sůđLäg3¬–J:µ žżÚa (Ž.~+J.0I&¦ř»*hÇ‘ ŤËę~´`ě˙ÔóÝĽËk·qçÍ­Ýz|khÂoÓ/±1Čť·tť I)±.ădůqv¦~5-É“BŤIqŁ:Š>Ž>6Q@¬X›e§t­*TPÔ€sÚ$µ uŃb˛ěQýĂđĄ,â‡(Í^3c2„A×Ůt1ŠYŤľ·D•U†«rrgĘ",.ÓŔÁÎ]VŽÔKĄîu»WZúĎîl\†ZK»-čNŮ ĹÎ<ŕÉ–›uś5±SÂn­8'"Čhěˤ_QgDZo#v9 ΓVĆüMY3ËC÷…X±»Í±Kť”«"qqŞd¶í@N“—ŘA•ęvÔö 4W=ťçŇëU÷¤ tí浚T@Ł-ůs=Ńť8đ" ’EöđĆëŃ’4(`5“C7śÖńÂp_W|Q(Фłâę4IĎŹ5tzŹg°«ß}¤AcG#¦=ÔI,zśE„I˛¦_ŢäüWyľČn?ź![ .µAh”fĹ.•ţ4zěď接ĺiQľúµ{iTjÔĆô*°‰ĎLŽóŇ–¦(ŐÍ{V­ůÓlŻdźľŠ]óÝ·ËĆÄçŮ©™ĚMy­QřâEŰ÷3xĚëü"źVdOS¨ŻĎxň“N7˘~Îôj±n!ÖFH–_ű÷Ş[Ś1Očę1"ţ둉(0 ă˝¶$ DźŮg˘źŐv©Ý·ý>oĘ,ŢŢĎG66ĚZŁ˘ĺÜ‚+fí÷wd§˝ëí÷+ôrnůK`L8¤ÂµŐU¶MÇń3´Ř k]ťŠ=›ľřŚ›NČ„qX·@l±ĂBm5.Ť”,®?Ý^ŕŽ$Îu¸Žţ7%ĆÇĚelŠĆňŻÝs‚ŽA î š=tÓc hĄAÉş}ńgŢ‘ ÍAŘ—˛XöÂ,AŻŞÄçĐď‡mřhLd¶éďó 5ĽżŢ¸Gâ„8äZbe´cô‚î®/AĄ"%3ü µ™5óüËŐ'Wß‚yX§q{_O>ď÷:Ę(Ű.Vl–éÄ-gëáŢďÔ©ˇZzKď7Ĺáaź"HRҡŁ0¦`î!I#ŢwÄňŃičţľůšýN žËm˨sş÷ľ6 pVžeWR9vfN¦w?B%xŇ;öúµ^ÔseŠ*ź ĆşŚI+Î…p8šÎ|˝ĺ=K’Ô"†Äµ+^ăî”H~ń$ágÂůČúp}żµç S^}]'őŢš„ÇđŰdҤ­ĂŕŻěşťŠđu˝ć*´ž8ĺëC.e‹‰dɸ˝f#üěA’4Đ‘¦˙ç@`÷†őue&AééŇĹ'@*ň  ň8ĘN´c*ĺ˶V”Ž}ñqžÚšSř°T޵/€á´ügue.m}w¶%»ËDę)ˇc1ćśé×_^vKÔ‹TqDQT¸"ô!ŚÉ‰—"Á±©RĹEÍ,‡ý(Ô°v~}ăËý˝Ę]„ł uMđ#ü[›j=gď ‡`uíKăF‡*đśÎA»1Őgę#Ca`ÖÎe÷|u ĺEvíÂ˙‚×_÷ńqmÁˇâň„_:ŤMŠĂ2;ĺĄqÖ|#ř§tâÎT-t“WŐ#)eňúí¦&×#/K; 7WbQ˝íë‘őâ™XŠÁ§Őµ°—š®hŤp/Ëßý„‡ełć#w™ĘQčúŻFzzrxÔŁ ˙‰±MUnÝPrlĆŞŹg.‘1\JâBŁ‚|¤BpöĆëëßxí”Ě{†3 â8…řägó\‡čÉrÇ™’@Ę#ŁŁ&éşŔ˘Ck­„hS‘"2%ÔUĎő_×9żżÁ6cý‹PÍv>.†á8Gć l ÎW‚›č= ĽýθNźTě’•,hŹ;î˘elnÔBíńĹęŚM7wŠÜ~2ŚváJIoŇđx…zŚmýßwÇ97ÁÁ.@–âżňŢ_Vm;fl úęmbQ/¤hdLîű‘ĂáëF…g™Q†ÓŤ! ¸*•ç0 ľ=Ú”ńA–LŰFY€ńže ŽŘ®‰ô{8¦ŔOáŞŢĺö,|żĺ“°öHö؉+?ą#)´_zI ˛ź¨öQµüĆ€¨ß7EśVăđ=)ŐŃđ+Ő§řąĺńŢňfeöcgš®1˛;ľM˘ýÂH-E°ř”zâşúŘ×T:_GľS”‘˙ÓN3{qi1hkzˇQ†.Pç‰G#K#5JÜéŐĽćĹŔČ™t/ŢXIŇ  qŰŁĂAł q€TřĆD!ˇa̵ĹI•î{śÝLÂř°é#F*:’hŘ7 ÂAüž˝E‡±îXĂîshKrÁ=äa4ĄG`pĎŐ•ÔşÁŞ˘ŕĄ-ă-fqAćÍ˝_ż§±ŮÁŇVLŘA¶ű9.káŹ#ľEË˙ť|Iŕ |>ˇÖÓÚ¶Ú«de-ÓHŐć¬@„<ÇńJ‡†qĂ-Dt‚˘ y˘o´đâÇ×_o˛­!çyJ‹řL­őE‹Úi‡»bJ<’lÓ…ž›SLę O&2I#VĚ—5ëN0;-Í‘®ŐRśĄÄéEîWÎv˝Ý‰ť˘€ů&ŕ#ń ć;ë—î/ITuô_~Ü&ŰŹŃlt ĺ±÷ ă ťŇ( ŰÍČăôý3Ń™ůŹU%ý¨‰EupŁŠ ľÂęh螺~ŐýkĎÁJô¸mŔE†ľÖ€äAA“·ĘWłŠ*fĚBóVýäßŔČ-ĽZ^hśó° ôe©©ę‚ďŹ÷ěG(r“#&WńČ÷~Z›őč¨+G &ţD%zyĽôš.zzŠś ÷ś˘óÉ ?ŢÔźó@Úéžř©™[¦1ô^źÔÉÝa$¨ü·QÍt ČŢĐ-nĹô÷вZ8¨ě÷Zyâ‡Őeđů#í§QŇç&•‡›7ľ$OĂI4¬†şĺűŇžxÉ™Aâ‚Ůą…j­öUÂý¤ź«‡×ľÁbJIěëÔq•:r;ąW¤ĄÇüGĆwţä€ÇŚU.Ćő÷ŮźˇŢq?ÖRqóŚ#Hűź‰GůĆí^˘©w(<ˉÎŐŕËj§VżĽpȉGmű4hC gڞw ä/4°c݉¶«˛x(Ţľ—ŚmŞšč> nËÓÁ3gˇ ôOŐJ¬9GukB<›ę }z«BďŽp†fËâÄkÎL¦%Ü*GÎNL…«ŘÄz·¬~±Bĺó>Ő»ŃUWö®ˇÖ§kś¦3[ölĘرSôQ;ü#7Ýş‘ěÖć%t-Ř9–r6×! ɱŤöĎéV'vňK˘Uú'Ů˝ žÚôO[ŕ 9°s±ěśŔC`.H\o~łń„żž&ęá µŁ1CżrŤłvEGÇ=ŹČ›ĺJşGО5°Ý•ýŞoáLJYźş•,oDć$ć0Ś`4Ç&÷ć´^y»ÔWŽ}‘©Č^Á›`qIăC3Ł/Z…±:„p]f ĽrlčđóĎ+@¦Z—<„á“cŽÉÓĹĄAr[¶_%·÷űÓFWĐ—ݨ¤®”—š»ŘfÚ‚Č*°¤er ­jłýő˙3n—§{€¤»PvŻţC!dąA3Ž[u´đĹ*»yô}‘ŰAávGµä[Ťě~˝G×ĚR¦Ö|xŐ/zčR±MŃ?S•]…´Ýaý>Ç"( Ô#×ì<®˘Ov¨Ľ8ž·ŔaŮ.Ƥ;Up_$aĆ…VN!ö§ĽXµŃŰXčzí&î;Ţ— šy–7ž*1ňó6´ Á‘č®ĹóŁó‹+–žĺ¸ŔIDžÄ|ˇ’ó2vpYą\›ŞJc\żřĆ'ŞôĚR(ň/Ěß—U¬Üćwď1čs«ňšťęÁ™‡°9Ö?‰l˙ÔĎŕĽE^ň(Ş4ťcü¤®Şµ›{\(4xűčr;M+lżQŃôĹ.´'or'<ŞÁ2NöFŰ ş‚OŐ”H$(dú"wU?p+CŔ›ťYť0J–.–Ö˝S®Ĺ$ç—.ó;S n5”üŁÇ»úşŇ¸X®¸é9(žÂŁ»ÔXú«3RŇĂĆQŐąÔRI^vsWŶş1!bŁg• …W—Ůaô ľ u‚­<ŤHžě°řcŐ$w8ĆZîůńu„°ÄĹÓÝŁ?”"Ěž3#şKěř¶‚f]‚ ÷‘˝OákźĆŔ5âÇÄÓoURľ!sx›$E¶śAŐ˝Ą yA´8Ą éŞ:—IL|ŔϱmÄ4đ—A@« Ŕ·SÚ9"ç+Mm“+ý ‘ŔËGćręMŚ9so(Š• çxO-mdű‹; +¶ˇÚ%ŻB>Ú$Ú™Üe9IvaŁŇő„˛ ™3tŢĺăÚꬤ3ěOďöfő·=SU˙ZʎÉ,S+¦ŠĚĆ;Rŕű›rĂE8ř!8FFe휥ˇÖ‡řwĂľÝFfnAP¸¸˘=ŔĽ™üŔ +ÇE—&Ţ­tčÁŤŃEwŐ*m×Óiő¨ö¸DĆű‹ţ\­ć6 F'˘ăLX“Ę$¦‘[´*t=¤úP(źkHm†±y]Ą ±KÔЬ˛®7 ůŻi1Çő¸B;Oîsl&Ą?kľ/jL󢳗ą,cĂŠ´¶ô#¨5ŽĐ6ŢőĚp˝đ’˝fÖA?Ű.W´˝&űLD>c‡úŕu«-žĹmÚYo[Mą©`ŔĎi„Yx€]¨sĎ €±2őîKůŔçń˙fď‚IvŁ1ůŻĎŇyuŃȉÇdő9v'Bť“ĂýdÚ‘r[öYÎd®Ć÷ôăăTőG7ÝU•~0Ľ.ţMël›ľ`λÜů÷)‰L}\`|&đăĄKśäÖF–Ó3•śY†H^Ôj¨^ʶ©ÁŞż´ ©}ě×ůşGôqšrŤ[ďLĺűSVÁń$¨ŮP1Ĺ©ńż# ŕ=ťČÁrUÝ› <«×·;ęnŤz©¸¦Í %ˇ(E%© «l)"ěXťą«NŤ‘ĽĂ„4ęÄ1% ě~0>LßŢ\A™`GÂÁ[@» ü‘Ĺ›Wý3˘“µpąŚó¸iŐä ‚?ÁzJđ Ia¶¶žřtĆm’:wŹŰơG" 4Ľ7‰%/9y´Z”2wřß[‰&I%…ëD™f}돨ŻÇUĹÖÓ}L=÷$U‰Źr&Ş!XÝńswµ•9łHš­LÎż!%¶hÂĺ”»+‹´™$ŁźÔÝ,~Őáĺź7ÂtťĺmĺŰüDFă+‘\ójIpôÚ“Ył wßäĎ7áúµJ(°Ş±ďćS‰Ě[gRůČ|XgÚĐ´­jZĽ v˝,ţB¸1×Ń®Â?GÁŐ¦|‹)ĚĐ^#Ľ«Ŕő»Ö{­·ž»•ŽâźE.`«f—|ôŕyŤ3¨Űý 䋊ŃBzÖydľkWţXËX×I°zď„´Kë7ýrOă ¸§˝—‡LŐu .óĎ s~íôXÇĄf·đ/ę;»ŔŢ[ăФď# „Ýă_¤ä¨RçČ‘P-(Ř`»~+éµŔ×;€D¬ě»8üş;™ű‰ˇ;—cLDJŘJLŕŽ/z® Ö@®á¤.‘Ý4Ĺô}ŮyOIóMxTdŇc»ßeĎMCaÄ÷á|i€ć‚EoS8v`ĄltÖűk—üŤQRÉćd"tő?Ŕۉ>Öíő‰{HŕkŹÁ÷ź5©Pť%–}Q®×ţń¬ůą^DÓÂR¦ČpX:Ę0/;ħ‡RÚrźRvc/^‘äq*~Hą±Ŕ´‘?»Ő ¤ž3ÔsŔÇčłěĚPsÖDż˙1'­i9–4}d¶lĂB§ţN!ćT춤^˝}¸´.¸éîJKËŢa&Ž_23 ‘n±Ş=­SX Śí1Ä8};Ĺ{¸zzE6]ž¤Gł cgő¬ r¦Ăţڱ“[Ęô´m8F™8vě í† ÷‹ x?'U;Ňu,ÓľĹ1™µĄś ŻÜ ľî}úUľynöÝú« ·™E¬ą†•ô“ ëśNlćÇw覟şâĘŘΤ×ĆIVž¬ 6‚ž˛kpĎß™e.ęĎvÍž »·ŤV<%=&qPaŽąp>4CĂß˝ÇP`łůk"E9*¬Pî÷ąü»—y‹»\Ň­ŰĐA,ÇŃxhFk¶ťćäĹeW&ĹYcňJΨ|ŐPnĘ$ § ¶ů\á…ĹA[¦O_4RĎ+Ź‚őz›ŽFT@®LĎÝ1Ď…Ú”OÉ%Z|Š’ ł˛Ű}Eµ $}'Ĺśö;ĐşťŤ9_©Pg;|M‡˙IJiëŇ«Ë-1“¦Ö”źćúĽ4Q`»ˇGň‡ýuX#L#őÔĹÜčă!”ž«ÎŐË„@EqĂ(t*¦Čzv0CËyźŰä;%UoŔ—iŐ‹HÓŔ| '€šŽZ –ġ\>Ţ%ÚPö.0Ű%}„ F”bě,5‹q†7ć§őŇź˛đŢ™bKýQú/¸ç5]Mî~ţŤ9Q$¤§ĆĎĂߍ>Ń·WqHíE˙µ.Í!Ö¦ýó•¤ jż—Í›öŘ ­Jb:űĹÚ^ęď¤ ŃűöëúG2x0ĆýľŹÜ­Fw¦ăz?Ëźf¬ú±|ÉÂÝŻź }ή'śučľűťyóx<×ZńG×@§Ţ?5{˝čjóŽ˘ÁôA4TŢÝöź ĺ3«Pl1Áwş–g’`Îí;Őoă Ĺ4~Uš®ooč+ˇp‚ć¨&©™Ü¦F­ľn´ŽĄkÎÓ±QíJsŤGbŕŔŹV°:âľ÷!IůÉ=RŢŢšL,!ço _8áłÇ¶[hEmÔ€ę/"=K ź0šŕTÂ_Eîđgďˇjv[˘R—ÚNIN㢉II„Ćą"—wdě÷ř r±ÍuŤ•K-6Ăa=e€‹»ď}TNŐ Ţuoă÷í~†'Ęž[ŘřsAnvg8ĐnÖpJßš7fŽĄ:č÷»•ŽŔq¨Łw€•îáýo7Č´g˘Ăsřpg¸™ńô6áĘą¨]Ő:uÇ…ć0Ů—%÷ŰNGRŁĽ ťnUů^(µtî* rӻ̛eŮľµ¶˘Ő‹ŞŇ…¶ŘTëúš»-!O‹‰Ű”NE±A–8p=ź5ű…7}Ąô™ü„E’۰VĂżZŠ7I 3D>©ľçm^AĹó÷ž!{z)R^aµkőlęaÜ–8ó‘Ýa•ťÉ¬uÄ3v‡6u˛ćv’fhN aD˛ ŻcÓŮý”ÓÎhe˘f$Źsä® µímu®GĄ«ĂËÁ˘¤*éüŰĄšůŻÂzUŚŤ-ŮĐćí6ufSé®ůÉqé@?h˙•ُđń×sq&|€=bµn÷˝÷=XţÚ›ĘYb:X )&łu-ëm)<«4]żßŘ˝l‡ąëGrĘw·٨Ŕ‹%Ôý%ö!kżiCLĂÖBăŕ~ç>Ůí(˝ކţyă5ű;»ŹXÓ×0n\ň—Y2–úżl×Ç9‘Ýč54“Cl­ç”ĂLh˛HTňm–yě#§(Žt`.[˝ť]ŇšiŞý „†Ź‡«Ý#›VnµhÁ¸«*Ü©ĂĺDíĘ5cÍÉĂö‘;´ćÖ\L{h}ń«©ZäJ¤jçaŕѢ 0zł*-p׺xÄŮĽL—Ďj3ëxF+ŽÎŁÜrÄâš;ŤFYwÄćL Ěí™ää,ç7wG„DЦßvGFčÓľpš­…ěÇż“‡@‰'á 4™;Á]`%ń×ďĽ#ň¦÷1ůűć6Ó^xňUSĐüȆ .ΩżűS,kk BŞtŕu°ß‡‡¤†[őóĹ<Ô—öÎWRzŹÄ6ĺ˛jI»VX¸h?KŢOÖź_f°ę8ő˝ůłpĺ8/ŕCtÉ)ţ÷Oíľ4ďPx‡„Ără«ßÎwŮ­FÔhZŢ~'*pxĂ"˙ň¨I őŻeyTřÉH",¬¨čô(ŤTť*vu Ëůtď@Çlł*Ś2$ŞFĆyĆY6€ű3sÂJ©_…wŕWÓ?eĐ‘GţíŁßŕ™R?ď;/ľ´˙™“€eŔIwâĘŇť47{sdr@'‰¬%ČIfžŻµé›`ZíA˘IZŢłŠsz6…źż·"=ëóČÔłŻł¸€`˛ ëJŔÖÇBř/ă0ç=‘déŤrá%@¤ĺ©Żť ű °AvŞ+xWńe`9Z‹ „J»ôÄA¬)Nł(GĎ㩜´cg ¦xH“^ŰŇö‹±CnÜ[Ió,Sć„G ą‘Z¦Íď6AW’ľÝѵ–˙‰’s˝8žÓĹ˙ľ+ ňĄNx@™M>/CŐäŘĽÉŰ2®“ÍĆt 6–3¬_Ö2“%Đ’rţÉOߍzĄÝ.čeyfY·˙ĺöÓČa=ŁĽUî<eđĎ§ °LoűĆD.ő‡Oţ4HsřÄ -8ŕz)tÇxfäLëtż`Ô0q¶-ĂôHfOgG¸IĺdD„rĐ&¤.4HÄ‘)“Ź AďĂ6Ďď x‹™*[ďť·=LTŠhÖc6đ\?[ł'ZÍgŞżů °ď·ĘRŐZ>‚:´2[ž˙řĘ JŕXĂĘY­Ëë9dśrY\¤łűŃ1S?Ëł]zzĽÔoožă‘tő@^ޏôź2¦‰*A ¦˝°ÝĚA@ÜŐ”é4t×ŕM )ß«)u˛ ĂŕҢ^yL» Ě.Gú+ôR™eeËT«şrZx8<`)ďŕßY‡iOápŇ_&}D»O 8¸¤łňx­çSIôż7YtýrĽ, ţ« î…ՙеő:˙ŤýK®f™čPŠ’*vČzÜô:iŕŤá[śm„¤ˇ îW‰•É%™cŐŠe—hQ€JHY Vx¬ńŻżÇ%ćŽŔŹł˝„ Ý8@®ä,vĚT˛~ Šm4ß—ÓKkŮGw—DŽ10µą'ąŁ" %­ß1îldinŮ}3»Šk0©b!l¨®}©a aÖţŔ–\ě2“ŔW‘űäő4Ňů[ö‰,Šh9Ăö@‘´X Wú/äţ´­yM> p#ă@¸‹š¦©»íŚÎ(Ľęo‹V.öbu©¬ak'¸˝g'Âî0‚ŚržqCĐ"ńž˙őr/÷sy«Ä:{Ä€TG˙#žÇĽLX:µ+žTy¸~3Ó ĺlKJüJ#çWĂ\Ńđ™ď-ä0)ÚŞßóĘdüO^É”Žw/cÖ.v»Ęć­§1—<ćË#G\s˛&ă"˘VCî†Ümř}ßŘŞ”ľŔ>ÚK¶Ď;´`·Ňa~Wˇďv`˝e6ň^¨Ë‚âÍý»QŰŻ´îŘ‘ĆÇrây€hoc=…Đ‘ř'ő…žŤýí˙{|UÎçݢ#Ô’ú*G»ÖÇ‹µ—ńŤąí˙¸ˇCYŐ'‡´Ś·ží?5é¨w×µ4wŔ/‰€đpeoe‘<ëÇ‘FѶđYçJ‚RţJűmCmtśb™gMRT*͢ŕú~g˝Îňío]şXĂö˛üµ®ßŘÍ„ř^ )\†JéVjˇ°Má‰oőă˝KłR™{~‚D¨Ź*y.ˇĽó ł˝§=k‡»OŹ“čŘö„ä±vAŻü˝WĄčpč&>!@ânk'źP:¸Ş×ąßÁDľF\7[ç¶ž4p#ůĐš @iP%"Ő5& aŔu)·_Â’đÜ!Ű/ây¬ľ'Ť|;§Ůśt°Ü•JжOŕqÍZëĐÔ^’Ëň‚NémăÖóŃŘ6ŃţŁG™i@Úy__çyi“l žÂM9ĺ}١dcçéňäŻúÚ)b€¨Ju,†$Osî•ŕ{HĹÚ®…ŇŹÜĽ•č1ü˝ńŚ‚+ŻĘ8Ř5zô·‰‚Đľ†G¶{Áś_‚i]ŹAg Iy˝I•ø܇­1ŐCţ”ďľŃěĚ*đÄcŹË)Č˝ Żâô˝Z(­'Ë´q¬zŤęáRżDg*íŮ%t© 0‡Z)‰Ęş‰6í’ńŹô«ÍÝXP})şşî+ňGJ3 ŰÎFŇĚ‘µOٞńsc"&™XŠą"Běż írýVnꦫ°e)!îZ+ymM†Zŕ…ń5Ě|“ĹÁŰ`ŕ~“rÇííĘ_*y+âÉ{üČc›ł°ćíĹáj[ď¶ ńڰhđoć(–‰,Âů(Ô˛h·Î.†L&wYNd›ÄŞő2‘ms!H_i¸›› ŔÔ]e ŃtśMßéŢ€C§rÁď»bTڶěUë%Qö– tußîcđŤąLŁ7ú+‡ć a=ź\Ôń+•pˇ+s0@¤„ű‚×(•{?ěš~ş˘ĺŢá»Î‘® ôäÝzúÝGsPk^ KD~±"ťN ćcgyeŹ łKx‰ż†ßMşŞ9ĎŚŤąHrâ{WçÎ)¶ir!~`©qÎŃŽ÷bŁđŔ;véc´`™mmA@ľěyë·Ă¬FO#©QjŠÄÓEzgťÖćIČ™ÜÁzrÄż#©V÷ăîę+†„ÎŻbąőXŐxAĆ Ĺţë/U°™őSăJ‡NZ3|Ş üžŃgG‡: .’'ĄR–f$ţňL™ôÂvTrŐŰ˝ĘeZ“$ÝľŰćúäĎ ŐB/‰–µń\Ć5O/)ѱńţoŘA}áŇ23„>ÚłŁ4.w)N@ł ×ÖřźŽFör0v̆ Hô¬;’tý}¬ó3üá<¦GžĽ ?)Žęźdf„‚ř{Ҹ¤»wdM Ëş™B5˘ľXŇ<`N.ņz˝7m…bĚ’žrÝiéÚč5O+®™H\ƤD[µéműjĽˇB(¦č…GĽáź'ńen©Ż±%ą>ŚZ"e{jęoé–c÷pŰ•’:ë2×u8Yţý){7ÓŻSÂE€ÖŇ/ˇţŮ!fnĄĐ}áăgD^ń^ů¦ŞefďCc_ůŘg+VkkέóŚ×)y~‘S˛Uc{V+ő ""Ę5¬¦*kůköě^_ÜčÜ>î2 ŽěĂÍd#HL&ÍËÜ`ÇcB›bŁm“k UČă»ăKg×ů¶ni"ä¦ÜŁ—>yX%0»lńC›ęÉ ŞĆI‰M7đa\ťYVŽ8ĘQO•jL‹5ÜđaTá#íîÝfn !k‚zŹó—>”=~ľ<[pĺ=řę{Cü?”&Eu¤­L—EqPĚ‚&ę­ĚˇP¬î™\ÓUŚ˝ąÎřXÉŘ 0IÁťtź ¶¶ CHZqüŐÝĺ—FýzUĽśI‰ XiĹúî#óa{ć h&źż‰Čúű]ŁâE˛ÝëęŻĎtÂS˝ČÇľ”Ćő˘˛]”ÇNĚFcIwq!Ą‡©(ďwcAŤfG¤gëĂŻíbA°˘ä!ÄÄuHѡ_8LśéÂCÚă‚l!vü×Ë™@¤V`H/x#Ź… pĚžě,’4 ęB5XoˇmÁŕ3Řf]ľ[C#*„^V^«wľ8d[ř°,c ßÓzä#söS‚kô(Ź˝hâ±@^qB˛0V¤­1*ż@ęČȲf{[Ëąw="*šţo˝Hŕfľˇ,ř*˙)ŵŤWjA\¸=L°; 6&’0›Ö±•‹cÚĺÂÖ9… *ďM&‹ś<ęHŕBĆoĂŮů`“­YČČÚEŔT™˘iď  wL¨ş[ĽÎA+ćź>nÂ);ĎÍ#1źŻ˝Ëks“-‚‰ŁôRKX¦«“‡Ýk§}ołť­(@"^ÇŔq²Ă{ßÉŐŇÖóQözMzł®»2Í3‡moŚ"8bŇÖ0ÖłĎ4Ě .řl.čęB·¬™Ć„ř¶‰ýlt€WBă†`‘ŠšOřâtŁy»Śřn]f ›÷™§[ď)36:(—PjtóÜCúŐc¬KX‘{î.H© Śëéo¶QÔČ»ÝĆE&‘H®ů *fa¨ČÜÄ2˝M]ŐFÍřĽ/kh©cĚv s›÷ŽŃoK ‘cY˛¤ü˝Ţb)ŻîżĘ„m\râDä)Öů6±…¶ĆřóóTčnô'%ÍŚ-éäÜ˝€ΔA[WuN¸‹Í â˘Ź[+˙ŕ­§ čŢ޲šăśôJ ţRŮńÝGŻ9~8őw=.Üd<úEĐH*Ą÷ŁFĺ ĺv ¦})AxVRÎ÷MÔÂmÁ#@$á7=Ç(8ŃZ#ßôv<ÝVň,hW›ú>Şs•w#~Yz6dôŚĚfŘşůY™Ę:ĐŁ$RśŢžŁgbť;¨čěš·ZŕS"ĘÖ@ű•´Ťú?ŃýßÚ¸©će˛ĆćEďÄp×^ >Ń;uN¶f’m- §žŠŽ›5KDńBrňĺFŚ>q6?ćö¨úUT€¨bf}ˇDőY/ňÜü–Ęg)[ml ář&2§ŻÚ ĽŇžš;|ĎőĂ˝-RǶS!(Â)bg–MqŻ ő]-¦¬Ţńńw~hĹtž˘ÖyظzăżK<Éš˘Ri…ĄŮ:@gůę)1»(7Ч~Źľ$»ÉÚk.BšaĽWŢť»®ćĆŕ~ě eę/7ca+ńFőĐ ź•Vl-!<Í:.Žś^&cG…×L<ÍÓĎŮŁ0ř~ŕK3Ż»eB‚=źěą>}Č'tż˘ňËD)íiWÖ†^ŢsUő†1ě˘ŔŢtÔĘlň%čDĄâŕńaŹEĚ‚ŇUękµÄľ69 .…]Ďř~éÚąXŹŐ=>-Âć˙@î+[ZG”Mm%[ąľÍY_YJći¤Ü#í!w)×iu¬8](ÄWđ (h ďşlßČs˛î°ô®˘ąˇ źSzĘöh­X[7%‡S3·ď.uřÄ%¨•ľűhSô/tiř;ö·ř-Î*Đë-2ÓM¸ ݇îôV4 @ÎbŔ)ăű.]ÚŤŮP„BaËs^Ý’d)ü·(Á–H‡áŤ°=jírťIĐđ¨20kÉîjĚ4'6š0pFďE›‡ Lş©ů0Ş&ůĚF)ĂŮ Y79 g]çó*ţ"„̦4Ýkm,g—4_üüźH-\YÇĹ+iÄoţ©ß)Á»%Ľ‡|äŹSy{ř Ţ[›&nžźˇŁsĘ‘Ň#×a¸RĚ_"}CSK,Tď˛70řX\f€şÖ›6-üň8 "Ý8źŞ“–8I\B ćl>ćK‡ĹĺRÝ ­všRň9]¶ź·ýHŠFÜ9Í"ßBIčŻD5s–ĹxxĂŹvă1izĚ‹—ç ŢzAŹÍ):4Ö6=’Ôş–Dö·[Íř…¤Ý·Ç|•’ý ÇäCŐ·PR ä 2q`×éü!›4¨ôIjä͉É9‘yűďîę@z­oZ,˛hĂKƤ&ż–AQŔŇvłăq|ń^dÍ>ŘHĺ«qÁ lîŠ)˙0ÁExËסŔMŽqűM8:;{D ć ŕČ—U(,ß|'Ó&BžśßUÜN­2!’ćŰhž[˘’Řj]ŻÇ‡ŐË2Aďőś‚¬KĎoůÚ/l0`Ďf8ĚęĹ;zꀾňTgd€ ˛`úDÓą){ąąŰŇM»r–ü] öy#q¶,´; ó?ďĄ gvTמ‘ŐúL<ĺČDĎŚń%#ułŘŁ4QűŞTˇ•t ô.%d}Â"}U“t Ć~]¸(‘ç.ń>”HŹ~VĘuÄ+cgţ˛żTĽ×ߤ{sł8ΓŃ9'ăâ)ű‡0ť,s1fČe@’^Qą¸7ě©­ľ˛‘?o ĆŔµ˝;‚D·}9E*Z}¨iŞu˛sÄËZfĄÔš(Nh’+ÚAĐm€ó1@h bGŚr˘Uv,$<ĹbĎ×ŕŽóŹmÔ"pn¦MîŤ=ťĹ8=ś¶;Ȯʑţ÷D[ÚŤÇYĎI7ż{7e¨Ö¦"Ő±¶){:–Šô÷ł Enľv‘űÂѰ›ËšN#ÉÝ5„w0 ~ěňą‘FN×Ŕůľ86»DHµ'đ…r·(O?N›<(묤űĘ(„soÉ$NŔŁy”m|çuo¦}]=• â«ů] ę+,~rű;p—ąŚ—b8¦¬Q!}í~źK«óV'ţfȡ[#˘ô=ş‹L"„»šćę}¸5ęx$e%KÍŹČÔ2ŇSÇ,`\ýÓ°°#át—'6™ŮŠR Ék%ŔY°ńŕXVąT’c‚qoŐńgI˝ů¦™#‹ů5ĘŮďü›)‚”ß<€#Čb€­1 j“.Î˙2@UKá„"3Ó:».¬ ëÁ”D˝ďľú,"Ô¦Tv˝“2‹Dí‚’D‰`±ůäE˝Đ­%d'`wę"ôë˙Ť†°8ÎF/PĂŐú˝zÖűrŰ-¬É}-Žäţ‰*t`‹z­·Ă@eI ŐŁńLZ®1Őý4=ă%+Ď}mW"Ý39W/eó©D‘.źD#üőÉ`\ÍÚó »™:µÁÂ7Ö—P1“(KŐřŚ 0¬Ý©Ď.Ä/Á şzë)˘™+‰»×T˝4‰& ń–BŘN5ŚŁ ÎFßř (Z3×—x;­ęzĂ2%†ž6ĎZq|Aíă–ŞiKŃřb Ç_ٰĂŔí»ÉŻṴ́˝Ďó[!JpVtG6ŤgăV~o*˝Ós  W ]˛ eiBĹŢ~7G @쵓ôňóŚ"ŔOók\˛â[–)_¶őČě‰ý¬żxm!‹Ç™ůŔČŚ\B)ş÷Ą(ý¨ŇÎî1_E™ü‹$Ͷiľtü!ńrĂ›'–ĂS ĆD‹ Ó k,Ěܤ!‚ś%az0wg>⥯­Š{‰ĐćŃ`˝ŕ‰UGÎ]čÁ–[i­ †÷Ď&h.|żöŚj@ł’@ÖŐď¬ąŞ‘? Ř˝•a®ˇ]Ç&S“ d˝*DKş€ß=·¦đ…›şP&'\Ő€?HÖ׼گ©öé¦ËfŘ—šˇ"\9Yčp\$ÎŔmꤑ‹jÖ¸ =ÔĚ=Ň0q˙"XYNĂ=Ł·ěáč[BČÇ´‘é‡ĘŤN`7˝Č’Ďş€źXZ<ńÎÜż‘ăŇ«i:Xrşňü%ˇęóÚߏâ›O¨`ðŔ]qË>!(sMVaĚĚ©«üÇ%Kd 'ÓĂ?ßß‘ČGí*$ÁĄ˙ 'ĹΊ—ĽĐ˙k@ýî“Ţî!”>'QDf’,„ŁV „w÷ÍĺřŚÍşŁa7ś‚ŇĆvpeeÖ6x[—áÖÇwËč l:ă ÁxItýaWU'yuĺÁˇ‡Ť>ŃÎĆço7=ld\’pCh}~sé¬Ęł?\Cζ-µërk5UI—Ţ%n{1!ápĂPô–,ĂWźdhsňř,ĽB|R÷—ßĺŕ˙~Ăž¸O˝şY`“JóI˙őúäŮ]J±hŐżCʉo4´ň7¬…ŕ·€Oeéń+µľnŘGٍ"–&CŽ˙«W‚ű¨&|ďŇĘŐżX{°ţíń/Úo´nJ¦GuŻ—îüv8A Pí_ZT™„L]dçpeÚĐ©ŇóUÍŔ*îYSśZ^'8FrQ  z=~`Ś˝/Ťm¦†P!;es*ĎgŻ}ěČ‚ ˛{sŤţż×Ŕ“!YťU+ÍQ&0)YŤL€~8pâdŢ©­Áě‘`€ÚČqç˘0ě+!|™”n5z#Ż>ü±)ż0–UđŽďĹub Ks›7¬Črîü4ćUíB#a-ň8—Ž mákZďůhsîÚŁYćÎ&§`‘¸Ô6wJŠđv”ŚLÍ… iřPÚSrŇ\–A^č‡@–!“©ŽcÚX)rYůÄ=-«~$Ę΢¨ś.’üÁа}ľŻÓŞNeá…NÝéw˙e|4óÝ3˝¬Quhű”cZ `—É2YĹŕŤ?•DÇg@ö¸Ý§#o»ÝĎwËŽŔ3MŢTŤđ¬|š+@‘!mŚ;·»¶· ŽzXĽ~— ôd%­esvłpł¨žüR‡äĆNĐT„=“ÉphXhAźŇ€ÇĂPÝĘmŮľKěďAîłĎ!ÄRäd8ócĂŮfş{sUŃ~ĽÁ¸góś=áG"»!ÄP{:W1Ľ)šbĄ¤Ss¤•čhśBťö˘śÂ˛( ´ŔŮ3Q›¸‹G^ÉS–ˇt]QŹo¦śĂíČ$=ľ®:ú‰`/żŁIĺĎv‰\ŔôDgüŕĎLĽ¨PŁÂ„śFA{uZ‰íłÔł…´M[9ŽľaL}ŮŰÄ·Ą ĐGâ‹–[ Cő(Ç[Ď~XJ'™ö¤óŇŚ‰F´˙Újg> ;) ›ź…tŻŚ.µĐAĘOÜŃÔć3ÖŚą·îŃPŽ«ŕÁĆi]{Ŕ=ľľgEÄyěđ×ŃŞX±$ë3âôż…ďŮÁ¦ľ¸ůĹWeM:GśŤł•ő˙íL>.ިeWś×ŠŽőZ„oüZqoęˇ9Š1ac†4Ř*1ďS´PŁő“°ž|ÚľaňÜuŃ·´”ŘÉă-kóŤ’âóĚ/®‰LţŞK˝oŤ Ź2ˇÇ5óvśɡ§¸»Ť(˘ÖúťM|Ů+´%ÓC€VN ě‹d“'Ş ,+bÖÇnYǔؒ*‘ČŰK%ÓP,„úő ŹÁAůFŇ]ŃP;włjÁ®ĺÖěťŢч¬±Óiş§ĹĎ…P˛°oiüč …óĚůSŁ€Ůŕ‡É`Š­_ŁwFx«RoÁŢĹ—Mb=ccČĄorĹin c¶Ws3ăůęOřŞC7@¨·řRŻń>›ŮZ}n˛+……«şúËc¶X7S~¬ŹYËK錋l`o‘żŞzŐÇ!®°ĘбńÎŐ·¸fíÇ,˙)Rů˝˛÷"5\(ŹEŇŐ\o¶ęî§Ň¦@CÁUÍEľ„”äpŔ)Ą¶DJę´ňâ =/»ë€‘ŃCÔe0žĎ2E¨3ćg,‘u#H\Ô±ýVNkníĘŠúź s€&çĆ&.4©˛d¤ń7 KPŁ—öÚ(.P‰ÝÓ8ŕY™2»•gPą˛·vÁ°ĐqM‰® ‘„őńaÚü'ś ŃHćÔ3~ϵ)Ę'żÜšŘ#“¨­ĹŰ(†+ĽÂ‚öGyĆĂŃsŕő#±ďěÖĺr挤Ľ~Ň AŤôüĘ8†Ń¸ˇ´Śgń;ćyţN@ËŽOßsă•k¶CńÔ|QÝUđl˙‘˝Ł{w'Î0­ň<6¨f§hr,ošĂ(şjçŻls(ś†äú¤A’ć[Čů€¸ßsËNŮ&…›|HGDéć ˇoElđ‰Ĺmaé†l6BŐh+¸Ýí6}Đá*¤˘$¦>idž[ďűŃŘĆZôVŚFewÄLNîÉţâŻU–ź aąG ÇÉűsŕÉ´ś] )öw?¤bŤ°v?Ö—ÇI-Ř(—A™ůö–ŇË.CT‘ĺéÚ!UŕÓ† žʉ´ęŤ{xľfCä;'ZÄ«O7­”qÂőH‚FĐ'›­źĚË—nß —Bľ`ŁDé©"ĂqĆ‹ĐE Ŕ8Ő!T^˄׷í>ĺ˛uř88äą0IŐ¶úŹŚß6š^Kú|T§Ě\+uś†|„Ś0í÷€{ţ—Ą©ŮŻŻ&Ş‹=Skwú]T:ă876c˛™d”%JqN:č$ͦË[ ËĽęm_ö´Żš2zĹŢöCş~ kż”“•Ĺm=±IÔT‚±c ‚a=ÍF vŹôÂw·Ó,űŐŞËŠÚ5"ó0átžTĘĆX¦ÔÄVBv‚€—Ő·Z,—F•Bf9\Â-ĺţpwŽźpµĂvëŚő]:+PŚkË@đÔ­P˘ÄNąŹ),¶í~YU€AYB”=C´Ź—\ăž^Ř”EÚŕŽÚÝ´ŔChĺŢK˙ą[×4ʲŐ>X‹v䀷Á ĄöüşDř ‹yg±@a\ڬÔ쇡‚ëĚy3ÝBŐĺć”;7áxĽ›­7ĽÖ*,ŢčťęŇ.Ô;Ár‡G^łňcꑦâĆjOőnáŠ<‚§łŔ"=nwé$nô -ĎęE}ůT!3Ć8đk\ÂĄ Ďĺżţ**Ę*©Źň‹HŢř NOUt¦FŇz(ˇnc˙Ő%I-˝!&Ű`,W’ą´3~|_T—’fÝ)őŐĄ¨`ČާŠÉË‚3aÔć öĹćÍć*łc{$ěQ/$? döŢyĆřýË­3đŇd7i§É ÷d3Kôň­źcqú…·$*…bčX˝=,KOŮX2%Uöî<ÉĄB…†Ph8ĆuT[°a.ib>˝®qˇžÂ †Ş^«÷‘yPÁ™xߏÜVgęćM\żĺ/ŢÔdů±¶yťŞĺ%Äď©z$!W?áüj•ÉOŔŰĎËU_ËŁ•F‘83/%‹:âůţ#ŠŃqZ ‹C‹¸¬©Ýíĺ™Ea!~ťŤ7ÝÔř†ŹQJF˛~ aŇ:çßj—Îôu„ßHë“ĐafłĹ¬áűlub&Ŕćk cŕ[”š0(‹TG~^pSô­W3× €Č#ă‰~ć_xą!VDK˛P*(ć^™˘äÓ‰ś¦Ă€âe]g4ƉqÎň#—P'_”Ô<©°×…KYÁŹľÝ—Kž2łAú‡š*6 €ş»ŇsŐć!s岺"kĘ»Ë%Čď¤öŁp›"Íúź<Ę•˝}YwÄ[1Ú°Ę€ţśhŐߢߔýŰĄ»yĐŠžĽÎ@t,i)wh!ž;˝cś–ťíM‚Mâô$;qŰúą÷-5Á8ź±Üt/;ż-q´4"’ç•4„piů˘É\ö±‘7&ÜJgź×R.i ĽßŻÚ<÷ľN± @"®Ż_Ľ°K_ÉËV$źĚ4–$™1ëÓÓ5y}śń22qĆŽţJőüܡ¨{˛w…žŚ{+YŚłU!®cDĘîöTćűcZ'ř&Sě Ë fě𕆠7(}˛Ĺ‚$ mÁ#Źá…[îĚ “ ^H{îcW_Ôě U3}ă;u{#zĆYĽ˙4@|Ą‡ĚßĺCSÄšµ‹°PXŤů§zDŕ¦SŐć7CTßë¸Zm -…¶H jaͦş“LżăŮ ŚA \ý/ĘŠŐ6űqOŐ Ë«+ěúü VĎ’˘:čî(Ők®ňFŢ|¸OÇ&ÂĽf›×ő%ÂŤ~LžĆ·g já˘b=a. bŞâ,MF®'śăîÔsGÝAmŻé´0ţŞ8„„hß˙׌Ʀ«7@"ôÎĄű{Űň<Ěá´Ç%í¬t;Ś”ĄčwA5u˝3©Ę¦ż-Š“.ç{ĺ".ńĆ8˙Ő‡°h,»­ßh-< fí)ĎANs¤`Đ>1® 7ß‘^Ö´1ę Ý†şHĆÁVšňcKg؇%żš·Óű‹šKVOh 8Úoa#5k%qÍ8SP»égý0‰ęĄ ¸d ŚDD[–°ĂÍ—3đxÓ_đśŢĎ+«#v‹‚—"VJę]ĆCš)…™ěIUš®'ôŐ+RW­«—Ýš>뱇ɋ̤UiŐ& ö[ťnX.-•ňŹná´uŁ–ŕX‰émóꍊűůGH0ĹĄż±><Ëo„Żĺ7LK±U‰Ź…}ŢSä„ň›üRY _Pî˙L‰U‰µ÷źŽoKîn„Ýp}…ô¨WC3lCđH91v0°ßşqőřöśe\ü óúg^Fk¬ŽtŇ63éŕďZ„R÷î…ô¬G`o25cA|çX‰GAę\yBü}zăƧ‚ĺAeŃ(˘Ô8·jŹEŤ*µS:ßĂăĐT|™5@áF``źłýYě÷żÄbl˘VË_č1™6aĚ3˛X†!ÉeŞĹuBnĆŻ4ܶ­ĂÖë.©7Ŕ»í>o0U}ŮăťÔ éĺQçߎD»Ă{©/nd¸eŔĄ»57$Ü5uĎ5F8Tżu@Ş{p»¨&_oFĽ˘’2Ť†$cH‘lXu}G4$Ĺ Í|´ä,¦Č9şŹăpľ(˛|ă42Bńę…ÜW¸TşWVŁö˙ťLâŚgĐĹw(şrm`6އKn•Ť@„Ž5ĎIŕľ2Otµ×§%R—žŤË"ś‚ŕłâ­9¬ć‹Kšź%ĐîÖ(q:yfŹÉqΤcßFü3Í©:ťŘ!‚¶ç:ŃŽ™ŃčIî}LYľĚż'»Új­†öÜ ±VłEń7KÖŘĘcĺž+˙ŁýRÂŢnȨuř€U?:q´Đh*†ü4ٱt€íźZtŻDâŁâŁk>C—äyYBi9„ćĆtPrŰ|°őnµ7L×SËWŮ1ł4ŠŹń©NMp hŇÍ@4~·ĺľ˘‘„FüŚ-BŢÝrĄ´ľdË×Óźü ÓYG†yťÉRkÓăLń°ˇ:|ĘţĚř|‘«•˘Vöm^á›j˘i%ěN€7§'ŤŰ[0h]%BÎn¦á:,uG¤`Î3În6ëş”úR%>W+2Ęl•DŐ\âp–ěîmÚŻ(-˙„ľu2ÍÎ˙;Ę·P„vl¤–…[ĎrnůC»ÇW®%Y`{`Qßź1áý~]IöUô“ ĘĽq·Gí(·äÔşőev̵PâąŃHŹšďÜďĂžöbÁëšáăÁ$đ}L•ĎX=ç÷(au ćńH`cPˇF~«t`9śOťE¸ ‚>µÍěŘ—Hľ&’}ĘíjqĎ{ĹëS˝ݶÝí1ĎXşĄ9r…ż•üH)T cKµ«Úç:µ0¦}nxĘĽĆG±8ü‰ô–VTbáŇ{ŠNĽě«‡ĂÝwů q±5ŘÉk´ô ŕ.*†ŐÂÔ˙źrąô69Z“ů ťelJĆá¦kMLţŽĺx‚ń!ŞżŞUSý퍊ăđ}׺ĆoJÓ.čNs‡{ ĽVÖŠ˘Ř>±xdú×Ů÷HÚ~ÖĘ–»ĎBöy‡4mŢU´CÁĽóĄŕűţßSp˝î3ő,ß/UÂW÷4%‡ŁźŃ&Ę‘Ež˛Ęq(^M¸Ś&zÖ!śXăťiÇŐ˘Wă‚ Ę‘—]q7Đ`ę<áË'gíŃ®H`Ó o; âPčŔzź1{ýÝ—-ęö@ăŻ]^‹LBđBE—Ŕ ä<(ĽZÜ{7$ ÷u˙dÄĎ^ůÔ|zëē麵qÔÉ@‚cJšqtś¦<§*‚[éÂ;ď?ń»\&Ěň¬WČę_łĘ.nKm–Č늄EyFů«"ÝëžÓěSHäajü$bxϧ7»!µČ|Û̾˝­NáôyÚĽAhő îšl_j 7>É©ô 9ÜĄŽxIµl9îŐ:ňs0çA9Ł–vąY˛vŔ†,ŮŃÍâĐqßFÇ*ĚÍ7*´†i"'(~†ątČÄd"űÎ>ů¤ĚYíŃô›Ďí_Ť#@IĚÂ>ĆĄĺyٵĂaMf&/ĚŹ!3ŇldL»ň±ĺé¦,=ż/ŐÎŕ tâîLVź3—ŢôŔ`=BEhŰęń©OĄRŽí‘S$ë#AVýřZÓ ×Ő‚}}hÍýDř©5đą VĺŽă9‹0aWŢô,í˘‰YŮbE)Ó2Ź»SŤ«¬¨u‹éÂŢvkĹ‚ˇD°^¬¨Gy®{ŃtA şfđcCŕ8H ÓEz’ľçOH3"Kߦ_1BÇHO»zîBě&|ő˛á–D?©žM÷m_śŐt<“6H‰W$ŹYéŚ{j8[i+ Ş}šVôĂdJ”!…ÝÜG)ÄN­m3EůŚŕ‚±áGďĐ dtŢ“nĄă)¦’™nŇgČîŁ~×AĎ‘kü=Ť™ĂËÄ„§-n°Í 3”« ´nnÖú“'l§HŠŹ<ë§ú~“l4É!őRn>‹ďŚ{$ď\Ęe‰SRc˙–?AjCŤŚŻ=ë˙­$Wţ]OŘ/®ťľ§y¤.dŔ;˛ž”ßË‚i˙oMżK”$§Ů,źföcřË]î’-ňöĹ+0 ndM;CSi‚5ÇVÉSSƶ ŔĐv:YyŻ&ôß#Ţu~Ą¤ŕ}-´/úOą#˙äqD“pę€!v€×GzŢaű…#Ľ‹ĘgB,źŁuŮ vŔ÷…” đKnSŽYâ:â?I¨:Źd,źńÇoĘ=GŰTŤËaŘ—ŞA!HaZxŢűľ<¦Il‡k_€Dm'5ą×íqŇŮôśRŔĐËĺ Ńń°éľŞhíP0ăMë»ÍÓf‡Ąń|üCÔgYWąĚá„VĹťżĄ¸‹ë: ZíÂţŇ5™;w;•ĹU¶;©ÇÝáµu’zÓÉ"@!ŚOWKB"{.Ť‡OÜŚ‰ĐGž{éS޶îőłL‹Ć‚[˘âŢDęL&/$Ż[”)ńżőď÷#BTĄM)ĆnE¬‹öĆa°eúó˝ŇÇü·ö*7÷P5÷ľN6Ä٨ňĚśRb{­ úE„TĆAőÖ_dç˘}Oí{¨¤=.5€pPĄŰĆx™QeH˝KŞ>7K&7¶BíAŔľů ˘«ę˘0)4)řpBeŢ{„íŞś5Q.ÍRq€‡!ĆŮëXŻNôiý~25Ţ~3¸÷©z\’ťŮ·hľŐÂĎí3 0Cśiť„1%†9;ÄHŚšÝŁb@–—ţ,H8źŐČüŕµQÔÁTŠ ´?źG’T[EâÜß(9˛ż]ĹĄą?ŹŘ.ůk-”ăś©ÜxmîAHĎÉnEÝÎe{ç/d iKŮ+ş*Ľť$‚ž¤˙řĘ M]ĄŇ¤)—k{ąŢě„omę2Ë/­F·jL‰•@Ąť/CUĂčÍPŁç‰-Â6ŮO!ƤŤźŐmôŹěݤE\ÉŚpbŘ\xŇş”-ąSâęňů¤Šë‡ÜÇt×ę!Eîuw}$ş˘ošĘţ ňV±Sr'ď1hÉoľi·(šÂ$ô¦RÓ(#QO™âÄ„KĚzđ“ <’äŐŁűáÓOŇ7{ˇ°› a©ĘpZ ¨EĘJ‚•^}j,7ˇWŘ{'şđ†‡S“u¨‰ĺ ´f–2]Ö fTźĐ–ŰujvĎHÜŚ}ţlžń˙‘ †š$4Ńš„ jĹÉ-Ć1oÖ^cXG) z§ {I%!ó`/lJ®’Ĺ䩺šÓęKµ‹âŔ#ń±µąłě5 ™˛ĆOťlÚîcÁ ”ˇFÎB(úlĆ™ç¦Ý‰ZßP§ę(ëóа+N ‰ŔhÝU|łÍDŹĄ¶@yŕűnŢkţpúџ͇\âlľ€»|T÷Ź|řrÝ>L¸śű˝AŃ@˛ µUS‚řC7Â;Bžf®ĽQ@Ďo!W§MdKI*WRűC8ÓlőőX^Č•O7jÔ])Ŕhďcć4!eęiL ˋźóo/ ťŇ3bľ¦~Źk‘8hp[V†¬ɨŤřüHa;”sŤ\z픡âSµtßľŃr"˙Ţ—­Ţ‘ŘTSâŞLZ Úľ±H·ňÜz¨ü"É Q0„”ă@ڍŔYUőřw¶EúÍÎăČFi>d8Ď‘ '’üxÄj0©…$#ľó^pIŽ%Ę*Ârřěaq§Ú–)c̉‰oŔFÔßg2V‘¤çÍçs™ţŔłqéňVŁń¦Ž´Ôx˝X ľQnÇkâw’VűŇČW=Ł˝bƇ÷t0‘RÍdd¸mx¶şŘť{Ýbáŕ H‡Ü  Ń9WúŮëËWîŮ3¸jĄC¦¶Ţu–ďFý˝1Ciąß+j#ë÷Ň˙Ěî¬|vŘKĹ©Ťű@ÍĘšdeĹ4ŇA‚{[6­•ޱ·Ĺ ýĂ'Ą*á«aŃP3îEą/z‰Ó–0tăřG5XĹŰ]‹N*ěô°:Ű$ÖDŐj&UЬăZbŚVźáßîĎĚÄ‹€ş«Ćý=í,.2řVŤăüţşÜ>FSĂ")Ř »ľWěä®cÖąý·Q‰ď–ˇ‡%ý—ô‘…hÔmoNĘÎ×ośát¬ę~ßllŘ5č~ńů˘Čx“ŰşN|¤zšŘž :Îש%,9OâÖ˘šÜ1YŔÖ"«żDÉßÝGT „9ş · ĂŰ)‚—ÓđDĂŰő˙oáĆ…;Bvů˛=Ř©Bő‰čŹ}bN=_HfćĎv"ýőypN;2&9ođ3A;yŚa=”2Úđ€Řäc=ýľ)/Ö)» ĘHs‡ßU]«~î6*\â+€Ś'yŰş;bÖŘ÷řB(ĺgÔh–OyGŤmŢŘkě?ţ)=6¶Řő{ąű«XÍŹˇőݦÄîpP‰Đ8—Ä@_ąqŇÔ(däǤˇ˛íEAŻ ť#›oř §¨¶˝ëÍŰĆ#Čd¦Şćü‰›ŃłZlďŃ’'bŐgWΫëÍł¸ÝŐÔěK1Rˤ,8ŃL –Ú—±0aîWÜĘÔnĚ웑öM–I@ĎťžňČŠčĐPşŻĎ<;|ĂÜČzŕ5ÍŞyV÷2ďă1đ€Á¶žé3q•PN29ÉqÇŐ=IfN]Â:O»‹Ëă®YŻăvą,‰âÜ8ş®v´éŢĎŠ·Ű ±ćFł>T’•Śj*†ÝqmL)ţŮŕăčiśůŞ@ił‚ź6€eNş§` naG*×ú‹?Ź÷uěâSVs'[±ţÄítä#,—uaĺbB'léÖ6µz.Ł ĄÝSŔŹŽrl9!;a 6M 0R!şŁ7ÓĽ3ôDѬGmUŢłZŻrçg’]ř+1Ć+ůŠ,}HšüfĐ ~‰uëĺ©9‡î™á÷[_nóf&Ňo™âO‹Šgý(^+ăRDŽY+Ě$:~°fŠ€ř=™ ˘Ń›î9nî•Ď«7s­«ź"%#+_3şŹ+é c\S>?··(ŐŠ´Ě8Ó÷ąóţ¶MFîÉ›&+o S4ż!6¦ńĆ@Ĺö"«2ÄôH™ŇÎÇ”Ą˙Ę»ŇÝ)¦HDľë0(Nń¨đ»Ţü¤ Ą'4@®nÎ!ÉËnő¦J”Ěă ünűŃ›ű¶ý‰ĽśśFIKçă­dFH‹K-Ĺš˛Ż'ü˘X{˛"Ćt†«L9ҦNÓmÝWÖR  Ď58*k¶ßkk łšsúXâc®ű–cŐĘDö6,†EĘěrĺojLsR$Î,čužď|ŘHkz XÉ?v]ąZ2gÜéL(Ź_­ TöÄ ?¤®eßÇ˝ž nßRđ ă’Öę ™©ŚŽÄ/M~޵7\zŕ˛M*7`tŐˇ'ˇP5őÜ»ă˘13ĺ>IUlB] ^Kn^?K“mu&°Đ^eGKRňáA¬ŽnkV–×”‹áČm$Ă;ä`˛Ć 9„ĂFv¶¸úú6ýEYft‰UŃ_łéŔ‚~—}ë]dŇMÜ$mŐVKölŰśôňDlQn1Áy‚żµń’ď,W7$•¦ř—= ŕŔ0WěņTő}!Ëç“'•lcTÓł¬3ďí1ŞdÍż)—b¦kŇ? †`A™Çî™Ď˘Ťµşüš§ŕż­Ú}E˝Ż|OaĂMH3÷ĚTumîK n˛keüĘTp Š3ćV^;ţ2E2AP*ĄˇçŁ Á`ťýŘ;mŐFXĚĘ7mŰ6 ť×*“ Aóď/Îq˙Ąşs/xş ťÎ]öÍY’ Fa›˙Uűe±ĎŰqĺŹv$‘É×zË]Ă[đŽz d’@Ň-0q¶=ź4nkTă~$ :x?F:‡RŰŠwĐK¨Ś|ÎRN9ů Ĺ]Śé˙‹ÍçřCq4}YCŔ`hŹ|Q/lŕjřhÍĂ›ÓřȲ™'2yĂXoKbce´C9‹«NJM™J‰ď[HHžfŤ*@CŹ:Ů4ü\:-rĽśŕQuÁv6ŮQi záDćNçđŔÖ2®šj)Ƿҡd‡sŞ}..®+1 ÷®9NJ ˝xŕéIDá„Sľ4ýčËí>éąŮ»*E{39‡zGö$M=ŰŞIQÝ:5©xô‚µ]’}Žě–jä±aW§2Čš}pţo§đ+{şT˝íR¸+=A9ëÚw.TŞÎľ‚â$Ě-ŕŔ] ݧÍbł´xÉUę+ú …†k5hťŮv{kŃ÷ٍ]zz’‘d´JÓ1÷ŘZ ˇ0¸paVȰg´D8˙±Šo"ĂËs-ß«)Ď Ç9In#»ęŠ´!* LŠV!3äc®Ś›÷‘śŚ•żüâ* ˛Ö—&X °TĐK»M÷“Ç)×¶ńCł6C1Ô/’/y›`ÇU)źĺĐ'ďýšM\Ý ­pŁjŰßÂAL°€_ŔNj6l ·ó$¶?°ţϲH\±-©)őômş fÍ’ĹŁúű?üóH´çBöÝ©˘đbÚ‹«ĚŕufŚÇQâ ńŁ Ň®J7ó:ÓD)=ËŁ}ݨ4#c« …đqçŃhŤM4)ŮL\¦i‡Úů¦ő)U'wřů~·věöĘS,ć.óbdi†g“˘*Ěitmˇ*Ům0iĐü}đşą#ółŹB×ŕˇdYnŔ‚”mÜ}áĐŢ^sܵŇÖ™چťSs^Ż€[”đ­˛’µŇš´ď„¨N ™Ţ“f×HŁS+]˝:şf}Ŕ-ćf˝„…fQ›Ă$kF(ʏRY”•›1a/~zűv4f¶¤‘ď ŽŃ[­ć{é>™çăąĚ ™Oűĺ‘EĄfdĚ;ţ·Łg4¸„űľţí ŮŠŚ"V"‰ó˝Ąö˘âôdîmáÂib4däĆ…ú:š˝oÁ€mßNÖgj¤ß0áNâ‘Ü Đ=H«rkXadN2ý8& óhű¨ŤJ4 Łlě+uG`řů±Ŕ‰RÔÍFŔś“ľSśĺĘ}­—ź?’ýÄŁ±«â#|†Úťţ [k±—öÂL­…śŢZ\ă2šµ%ŰĹÍ-ou˙âŕ¤G®E¦<Őa ““˝"#ôż0Š˝X;{„cđYË>, Đú µľáłÝń9”ĺ—ś}úm\âĚś S‘ŘVäÜUHyGbm·Ţ€¶B€r}ţQÉŐ+ŽťÁ{˙/kĆĚŰZĂŇff P$ő<ÖfŰ)ÔhFnD6]ź[Wz˘•†źÁˇű-·Ý%n&šo–í·Hx%x ‚Î.łąA: ąĎ)_´äă÷4H¬Tďöž0÷8Ăqáąu§Öż˘WŽŻś;ĽZXŤŠvýolńć;zJCęw® ÜÝć'ČOÓâÎ1Yžü|›‰GőČ×Üę*ŘEŠWxĹwĚó'Ö]™`ŽćďÇž¸ŹrĹsT˛őÔě0ł€ůř6Ü"-^i…Íąś'P\˘®fđ††k«1”r¦†ŤŹ&N]µĺS犇PĹ›4"p† 7şą”†+nŻXPĚů’´ěoYR™Éa;ţí}Ibe骗ąUĐŐ©HʎŘáĘäX D-nąÖŐE‚hô±·éAaµ9űQ4hĽ÷-ʰ® ĄE( É]GĘĽóĘła•¸xL“gíTĽkvĚQRýkVl“]®\Č «ż&źŘ««Q粥:ĘŹX1ż%‚R´lř<Űq&d ?ŽóÖy «8=7ÔěťJ""Źż`BĂew/˝Ýőd‰'b .•ÉíÉĺĆÝH=ĺô°ypó%,1 ‹EON ±Ý1üäÍ7î k ~.µúl_bďAů…ŔŃűÄŢÚűEŘĄ4Lŵ>—!͡¶Öd§ŢťżG­`AˇüÔÁ¸ßŮđKÎşl†őĆ2›$Ź”˛¤řúKđ1CÎä9ŹÜ×Ţ+0ąëä€Ă:<5ÍÂ7öhś®÷·ÉµGqĘ<ŠąâU®T…–ť> Í,aUŹĂNáś:’ŁháĺŰ®tuę-¸BFâňQ2đPbµĎó%ł ]ś@:ró[kĚŚŕÎßŮĺ°˙ ,Yb˝~·hÖş ďj¶łÂŻ đFak.ˇUók„2Vç-ŚĚUŁf»rÍÚ%<„ř'%{G/e-g°=˝Âˇ?^ű*@űjqYĎ-…×k?CQĺŃ>•„Đ”č şÔ>w1ű¤`ăLµßc–žÇ‹7<t%»¸őżÉ\7Xn<$NWĚ-¦RîLŐa&vű—Jmű_“Ô¦Ŕ3rČ%/1Ĺ<<ÍűúŻXĂűÍ™Ő<X24ÄmF$Ô)öJÄ c Ąäu˘ÝM”Ł[ ăŃx°Č ŚĐęy®qöwŽŇęQoÖµä0÷˘…‘‰_nňľĹLwI˝É?Żąfq…/°ŽAG­ůę”?Ą/6O Wň“W!“zšAţ#?ÁPü›3ŕ“ÁgŁE<žf đ3ţťŞBOď˝rçdLkcť>ÔôU Ž"·‚ç8žt’ W4?Ź1I˛›4OźŔ{Kš‡S ]›&“Ô¬3„YSĎ}aókACB” ˇ‡ ĚZqsÂ꧸ץ.r% ?» 6Ľkî O')aý©ů3c>rŞĘi…ŽC¬/FË‹iQ¨E3˛‡/«_E÷řĎÔ˝V€ËŞąŁ÷×KOP‰´Ęöż˛Š*Yϧäč+Ů®î0ŻšS_ĹŰçb&V@Đ×·îą´Wýč +»hÂ-Tî )k=oX&tšuáćšpmh‚\Wcü©ŤwMyEByöZu‡Ňŕ€Ř /f°g·e?˙ĂM‰’đ)Λɂâ0žU’ˇľŽ8pm—EÝţB;ĺ2xé7ö‘)3B覒9ŕňXG;Ůá'Ď˝¤HSŚXézĎśÁ,c·˘L”U ŇlžŁŕď‚gS!Ŕ*§Yől.EţdVŚô ÍçÂîe+Ţ+€uÖ™3Űůcś|úéě;oR›řńÓNÇѨóßr}h@uAg[ăͱđ_Ť^ŞŃŢ +­}Ć݇Î븙aĐ8Ž:j7–m„ʤ˙pz0ĎÓH ěč€ÜŇ/°¶ŢŹăT.»ő…—ŕ~¶é¦VşŐŕĹí¬ĆrO‡€ëeIiń-F_1ŰWHg’(2ŠěŠů€Ŕ)ź0ü+ójŐ‘ˇŮÔ¦,LÍŻRÔâeĎSč¨ćb2(#±ôŚŃ6¬îa–ă:MÚ•â+Ďv)ßU_×-á_LşŕÍ(SVJ%§uE™ůw˛®Ž·•-ÓRĘdp}ťxú#Smčś'á蔼ů:×_˘őô‘3ćj ZNXë`ňÁ}ѧuű¦’|÷•™aA IxzěĆÍx?mW7ež@75ż6i®»‘Wôm¤'·Ćť‰č­k”çqűqR$€–łŔŤuoí:Ňë?Ě%z=ÚvŽę•Ę<ńč…÷Ôŕ„¸îŞ5])–9‡ áYw÷@‡«íuő€@\ j29ITĂÄżCš˝ĆćÂ9ŘeP?®¨Ô d˘Óß­E ă ĽĂĽbŇixź”É,ÓŇx˘ţĺď"<°_Ć“ ®sdĘ 50¦T ÜF{ô.]Ž*ĂdŁMp>?O\ß’wîś"Âč•f"Đ­;wPU¶=f—…¬‚đ’9QbĂ‹ĆEGÝ}¬OnŐNŢä_G‡ľú?ȩɵnWé~Źv.}łďiČÖ™ŚĂ'1{ű±JęZ ¨ÇŠ!ŤS¤ YŚÁ€ůřEŹ0őć¶íâ&žx˘‹ŻX*_ŐŢţ@;-čŐ…ř­ ڎŕâ vO¶˘Ë7˝ýy.ţřQDâX^NWągsĘ‹u©‘÷ńýôž/Dh)Bެs‘“h«®a°ńíţŐ›m–㸒©–ća!iJđ]’ j˛ ÂčW-Y}m´ôŕdĄćýUXłąABŰzŽO¸ť©Ü…/ž!˘Şą‘&ś9'GĽűłŽ9™N´ˇĂp6Ĺ—Ť´3+Ż{eěŚZIdÓ 9Žç‘ÁíěŐôćĽ?l˘rˇőC©‚ĆL˘ÁŘňDb4ĽŁSň†É‰´]ôÄîYępH!+Y§Ş:śvç÷¦´˘l2Cí—*žĹôF\  V™ý”„<*E*đę,Vî|`×'’1LÖ]ĄŠk1~Â1.Ô!Nţ4s MZ ·Ęőbăş„|`Í•Ŕö’›“Ş:-H˝ÉçĂᕡ" ˘)*ôL'Ć’ĘŤ"X©`ăŇ…Ů?)`Ř6eĂ›§bPup ±xHžÉ€Ş•ßFz݉.ńäM”Ę<,<ĂOŘĎÓy†Ítv‹ö=ЇzEäóf.ÂÔ }×›Ŕ÷†˛ÉRFć¸R_d/o@E{]`w $—ű¤+›ĽeţĺX3 A°dö$l·Ä;ÉśćÇK‡ˇČĐc¬ĽÝ¶hýř<+¬ěv·ďÔ ô0!ë'{« řUç”˝Ąůá4(ő#6Ăî€3nż?WH€»eEŤb·]ř$Ř‘•âS¬’\Z`č)˝Ě[(9©»Cůśút¸r3)ö÷ňĂ—DĆŤI÷â·ěąXâń.ł ö¸âúŞ7Łt•75č6¸Î¬ńl‰fą!žQĚCM jBTłwP˛‘¶nD i›ěĐýsÖ ź•Ęą«±KÍ( ‚ţ_~ľŮöDřmŢűB•b¶[‰AHŠú4˛§l‰8T×­Ř”ußü$¬č­}:‹9ę+ C ß1A4fÍďčňćjRJoĹĐńtd/˙Z©Ż‰GŮđá±řť©śítŰÎâ˘Xu@f îj*;Da]™ĺťŐęŘŔđôŕwµůʦčţŞ s{bŕZ‘çľ5čŇתö4Ň>|/|Yé)^sVĹ"‹°Z(LbAq&vt° ŞÚ¸qÁąOTp]áR˝÷; ô5ĚK´uém„z1Vu-ÇÄb-Ę1çÓ؆…šARÎvŃX‘E~DÇüŰ+>ţaaă*擲ËgŇ8ĘĎ!™ę·C˝Iŕ\żęfÔSĄkĚ©ÍxúM@ýÎ ­*ä›ńPŽq¸k=7zbžDN ďA˛ů!ă“)ŃŔc«_üYç‚ĂÝ@dßst[`Ő¨żGWÂ\#ňD*~ÓOľCD%sŞ…ˇ‡}ßBJ™\»Ď˙;Łç¦T{U0*´ÚĘŻg€ţľ®ܱ¦šlĆţ6ěĎśAż‡ üŕ‚B0ÔHy°v|ÚM(ş]­ßŠŤÇÁËo:ŹuÝ÷ň÷ěżiFciş5#&x´Ý ±ow^śP„ÄníÜ*•űŁ„gMËZ6ęň8żĐb™ü8»ŢłÉÓ‚±R–TEúÚ‚ą’5®Ç“nÎ?OĄn,ÔôČĽ¨±ČľąűŢ©/Ěl~CŽ-1 CZ±–^ĹŇ슗±˙ŹŠéצ2·ń«!­3Ř©Ă=µdźĎOŢ0Ź<5ö·{kˇŘť>ň‚®ž{)MĽˇ¦ö˙ŕť(KŹ<‘ś`AîĐ©–1#tŕB6CżÎ˙Uc+['®§g<±ŤąÂýŐD ˇ@˙0´8úµ[ÄÉőuĺŢ1*(ŘĎ#~/«3䨖­‰»*­RoĹm=%ťŞ|Nő™šd ą‡=tćNçď©t3BźâSO•L>MĺAě°NË)–C_çŐP™ 5 …*qči2ÎţĆixw¸¤űńáJ‘iV—áAšçĺZ^5‘˝uąaťő ĚLçżłłüOxk€âĽ?ŇŔ§÷î÷1¶“Ř5%¤]»Ô€CuzłŮgżB'qMđJ»~˛`4۵'ÝßpŮxb•vU€á÷KY=ĎŢô˙»Ţ+—éą2śü^v>.Őĺ±Ň6ô• ‡îŽ`uĚf¦üjśÖ VIă8Ľä˘ŻĂ¶,^Â38‘‚';ţ˙ގĐlÁűJjVěęe_Ň•‚65231¦@;B «Ćü„iBlní Y<Űe1±Şňˇ5Ĺ oĺĆíîfg^Ě$˝Ż!™jČC”†{¨·Ą‰ę_ĐűŇ|ڸІ TůQŘ–a‚LLrż>ü”Ih%Ř `Óđ çĆÄ`ŚČÁČ!˙ŇĂFEŔ] č|±žÜ˘Ż_ř%Ş!”Ż'Ť—u&F ÷áÎ9ł3#÷L×<Îl.]vc98ŠtiĹgg¸yäö<Öˇ,­dI"ˇ‘'*şGť+ÄżŘÁ9»±azËő-›‰äRQ™ÚE”˛š=ĄĆĎÉ“eĂ:ĺÝ˝Ľ[ôz#¦3GÓ9–R,Y>'ŽčăTĨ×Í";Ôţî-˛ăďćĚFp•X T—]} ľ Ą#˙GÄöňHAĆo§©öŘA‹¸2żé@¦]+šmçďá­Hůb‰$zůĺćcRĂ* JĽô÷ b^ějŞ &š=¨Ä™V’Ĺćřv¶.ešć›Fó¸gčNĎ?•źy2uA¦_ÇĹ‚2ąt›A€gĘŽ·ÂµŚČ¤©Žś€L@ĆR_\qŁ©ÁN›„d91,t,<ÉÔkýü™JKî·5~Ó] E6¸Š ŕ<J ­˝©ôc;gÄ)¨Řv…\ZŹ' \Ŭ«ČĚĹë/Ś÷§Lh‚uî*‹ĆfEJIn 2Ą.Hd4"Ě·â$*HŻÝĄHń:B–jKžFôF :ĽĄwűä¨ę~óŐÉq^:ĂËÚ®ÄvćŘ”µ°¦V’ľXĄHaé» –^›vB’‚ąv9ćƇ+¶”|Í(­ľ¦“Ł,<ú>€NČüőĆÔźî`Ł»F©ů]Qüó˛ěÓFŮç6‰®ä-¨p¦išÖ+Ť3Đ]<µâíŹdđ&ŇoÚÄ~Đţ·HŃç"0IbĆĄŤŐć¶[†ăwH1óQ=Ýx<3$‘‘ňäźľĂ_+%Ţ’¤úT±ř+±ý <»ą.®4•za!#Ůďyţ-.ó á_iÖ)‹ĹIËyľ÷§°2Ö˙Hß#´ZM[‡8ű<3Éś<Äz˙?mNŇÁKź¬LŽP®A4ź„[¤k–0ž‰„‡JÓz ďŽľŰěĺ-Ź ü±Ću°jâfäX„ďo Óëč—ő홥­96 E¬šźDR}đÚV6čgÔ#ťť1ŽCňéuřË?řŃÜ˙KorIĂ^ľdȢ, ůőf7łtę8Ä瓦üxÁzÇżüUŇęx˙ŤIjZ3¤©ůęµ%¶>Ó` NTB˙ŕ„Ş‰@Ć:µ+™+ľ `86ľ@TJRśů«`»¸ý꣕ŕlÇ*ě§şţjěĐĹęt:††ßL;¬c_칊^aM&ĎŢćş™Ź_lJžöŐîJŠä˛0Iš‘ÔäçĄJ «š:ąÔž,Ű(-¸Űµ8ń˘AÎËGŔ7Č´sáŠE–˘!*ľ ˘‡MpҤč=Ŕ„ść `fő¶'Ę»Zť<^y¶ZS6CYs†˛eóx¸źÜŘîŹ;L#¶çłYňŘ3¤şOxşMĄíÔĂř±" ő˙ňaöăŔçu±-r{¤´ÇÉxWÖYÖ|wÇ }ł¤Dáyd'Ř©śÎ˛ č«.±mňĐ—oCúa“ó"đ"ĺ¬eDč7B{¦ş&SŰňŚÇ†éÄŔBłÝŮ] |Íße˙g>Źatň} “-2#ó«ÖôőáźŔ&‘U©8ůÔqľŞĄ·Z~łëXęđČQdŤ5Ú\ üËg`ú­Öą¤UKŕĹ310-Tk&“N±Ŕ“»+dSZďů.h,ńQK ś3ť«„i)ŁizmýMQx%8Ď gőŻőÄđP˝şž˘ç źH_[RŰZo—I©dŁüUśŘ%·!žĹsÝANµź-Ó)y#€čsNŠč›ţ÷ŐČ‘˙_ ŤŔšŰŚ7ź÷őcZ Ě9Ĺ&u5DŽÚvhb0©DÎűđŤGʱô’ëŠ*X˛ť1čŔ·ő1ä*ÇKĂ}[ô>fĹČĐ7m|/E)!ä"Éö ­­ ®О„@Č5‡€ Uzčř•& ć„N·ÚUÜeHĚʨżXž“m ¤sÔh)2‡Ş¤T„,¤ŞPű–)N´]8őĽćŇ?ďÎ,VÎgh‰˘–ś˙k™cC®_ť÷ÎéW:‚´îż?«"=Ś6†žĎłµY ‹ťUŹ«…q†ň÷đ>^{8w©kĄÁjŹnĂjoךd]d)Çn(‘zőz·ÎŰ÷©$î,‰EíMŹh‹«­á& űÎ'ĬGÄŁýĎUD]áqňo1¬†NW´µöž-ɉ9HöEÉ  ş˛ľq´[Î ›8(Á6l`Đ Iŕx6NĎĎ®ě+^(N˛'N‡EÔŚťu îÖm€÷®eŰAnNc¤A‰‰.¬!v8sőm!]‹Ő&ś©b".ťńbj›±a2ń `“^Yv$©Źm©+57Ą>ü©Ęe»Ĺ|ËpˇŹžŔňääâ5hJ Z#Ď˙Mĺ‘2˛2Q’ŽÔ˝§A%MřĎŽëq:fŮpŕSš4E5ŞľF'ž…ç2SIćo¶ę" †śM׳YZÔh…"ů‚ ă ÇCŐ˝S‰tżEyŽÜŕé˛#s×;0ę—í˙¨ÜĆfQř)1 ˘ŃŁMËA%&#Ť"*8>lŰ,fׂ¸łęýW“¸EÜŔ,OŇ•É5’ §ťń'R iśpŽŃ”:Ź( śĘŁĎô-w⬆K`^főű•ëĚ “ä#,UőÎ)Ű'©ł…ˇ@fő…˝ŽZš˛‚×],L/¦Ę´3G LO ¶Ź—rŇF¶X«“(~ĂDyé oľü!ÖkjѲŽpéÚ…(=‰2M٤žmPÍÚŞ5ö^u]Ý÷µăµ‚Čśvckű]ňw€›¶ ®™Zﱸ/řĐ3[˙$Öž[ ÝÎ[ ´xćÖÔü•f“ ëčBI(•ĺbČ’|Ş%juU䎂Ŕ˙5)dđüŘĺTFÜż5Îôoó Ż­(‹n®S?ŕŘď>oß>6–妒čé5ŕą— łWR\!ĽU­ĘeGĎ%şçŔ[’>[X•üń:ś8<źĚ6ŠĆKŽ'ľBG/2ż‡Â\4śéçňřGtřąlćxuR?<Ý4Žňî€($5´!cŽťJXÜgłQtűąŃőڏ¸ß/+” áČ&Ŕ”ę»…}¬â%ăĄüţćgđËPŢ$ľÎfú‹|¨«†Řb(DáRóş5üoq»Xf |•›)”ç®ń:];Îf®y˙Ť=˛KrwN‰Žâ7ţXmţöčř“k, ł8ž»,¨ęV‚IYe‚ŕ ĐD®4‹ýgr/W8ˇ{…ë—’›‡ěJÂKĐśÖż} 3*ÚOpĽe^µËÁ˝0Ăß"z=Řv1XÇ ˘›<2µ0}®Ó…““đ"˛řĄ„úÇ̬©S’î»˙â`?Z7ú˛T¨ŁŤ6uŐM“dĹvJÚcßK›˝"ÄLFoŻ|óÍH5SQ{Š$sPăß ü*Ż%µg‹Ş`—SĐť}(ü‘AAÜż,_É1ňö ş.)Á,µ'ÄĄ¸2ú^hŤs ‰KŇ] ˝?l`„˙’‘ęŁÜ(ŠĽ.Vr.čz"˛nÚúBŠ/ia6Xmp0:¨7®%,Ł3Z•|ęjŕâk şô4 5Ž n<ئę•Ü" ž|˙i±řjKşů.–čŘ)lŤj‚顑ş¨ÜĽY5äZĐY®’'HGpźLUÄeWô03ťăţyýŻŘB[“yţęÜâýšô•ą†ś8· Ś{_“2¨A]Ó^Ž€‡˙Ĺł”GŃ©ůµó(ő:Ŕ©Ţ_BÎş«Ç5%št–:b¸ß]ď`]úÓĺl˘`ŢďRĄUť”Ř’0' Ĺ9‹]¨;ĺßĎVN_0TWÍ"ů3^‰ý¸DËâČE.˙µ*+šÍzą¦%ň. űL6Ł|’X@CŻŽ3?ň>㨵PÔXkVňVSʞénî‹wd“Oîúδ˛6-¬Ľ@EjR)´‰Žě•C|D×/ë*ŕ7* 01ĽEů°|âČ%š],Fž Â…O;•‹VŠ:ä{.'3ĺö‰Ă¨‘Ů€“Ö}Éc‚˝—»˘]‰o{‘ByÖřLmIÖŢ­ ·ÉŮ,\ó0!řŮßnć]˝řÚ@Ć5ĄÖřńH~DTíś‚&2-_"ţbň´Ç5µ‰µú•6«BŽ<Á?s|ť+˝$Á›jńŻ×®rP&“K‰xŰş°WßńRho‹ýnŚÔąş´“ANM ăiFR­V!dą¸ ŽČç÷€Ä]Ômź+,Đ"żR)/d­üUw(UŐ?‘bńË’đ|“ŤfäjŇ©î×–ÔMćűȉkLěHKo Mk˙(ĺ° _šń×Ę< ÷’h…jܵݏSćŁ=YK;ĘnĄü7{&‚Śđ­8ůČ×µlöčů‡ť›g8Ú)8–CŤg;\şM˝Gçýó´Ńč©*­č…ą`Űü~@č\Să±—ń¦Vł®74á`öU.¨˘8·k¦Ą'ĎŁŮß„Ěs°)Xí-8!,€¶ŽĂs˘űN‹ˇ§·ud÷’¸šBłŐť‰‡k˝xŘ5ŢqXţAA?˝4aÍ×C G4ł]ôO‘۶ŤŁ˝Đ'›ö‘ż—y®ĺ‚©7žíOYK(Äc@—µżŰ9ĎĄ®ő9Bh˶ÄUč,xĎĽŹw ”[Řŕ7 ÓFŇJäf-#¨Ą°ţK 4˙“&hIâĆ—ťftLť…ŕ¸çÔ ‹€,˘_w Ďś#Ôâ9pÂ…ue¶ńňYŐTf›•jŘ0Ş'>Ämφ—?Ub¦=ľ)zi]—˛Íŕ8…”µéĨá ?yt×ô†ÝîĎčΡO˛‰ ÄŞj¦ ĺ¸vď–zŰĐeť7i†h”sDVűĎ6öš`g`Ŕ·9•Üx]:˙qz€‘$R;ˇLňéÓS綦ËÉŠ&T®$.ěŇçá¸úÖJ+ÎBŽéÇmLK¤$ l9jµ0”D˘Ű¶Ö:˛•&q'뽾[HžKŤ)Y`î§ż`YőíýŁdó‰˝÷GÜá±Ň.˛«öía¶ţś˙¤:¤ěĆ4˛%©jţI\e×ćăÖŐ;FľßŔÍŘĘ̶bĺüGś6wÝeaqčÄmlZm [ Iţ“|ŚÁď==ň­Ă`BľKÂÍiHĐńía…%¬ąÖ)……ĎşĺS Č#Ŕ<ŽýĎÉšrů¬ë©ó™/Dy§?E°žŤ.cţÚP˛™ËA˘‹H Ş•D˝č`DŘ ô`o7.·GŚDl)@¨ŚŤNIţFd,®Éń’JöÁQmnż_g^PďyÍřsś đŢcŘgw…!ˇd¤ÇôÄşŐ®Űäůb3ü/8G#ŹřUŹňʎČÄÉxć˝Ć‰ěC.yxÖv5_Ęyľ‹0íéµK›ÔŔšąŇßęřžúź˘"NŇÍx˙řĘ XŢňăy]ř% ŽY<Ú…|ٱ›qŇ=Î.kĄ;šcÄtî’żw‘Ą`Ý@YÉdS|i)wă1µ˘]3üpÝŕ@‰´ůÎÔAEc¬KĹ˲ĺR´îăąBXŔ™~T«&»gî”ŃC…˝.őm‰‘ŘĽŮťś1.˝€«Š§zŽäŔ€ Tä×*‘D\˛—ěhóĽIŕl$Ü*ŕŔľĚmŰ$¶]ńdŞˇř1)cë\ą`)§%–OIöČ <‡÷ެî­řÜB±BµĚ¦*’t?sË•¶Ą´ˇB9źŁç¦N´”:;§đĚŚ™BZŇ[¸pëwó$Ęz‡}ă‘ŢE€ž…Öˇúą’Ű2ż~®aÓ’%O*@k|) Ó?7‡WěĹü?ŕ¸úÂo•v'Anˡyş?´©ß Ě$­ÂoY’§›ŤX÷ %sřŤ(–5dSm˘'©˛ľ9Áť ŮűłľTa5°ĐBŮs ëÜQÜĄuf—ŢĄJ|,Űî%ŘIŁr“îIĹBW˛Ů’çcć Ý#s˝«YË÷|⬼Gô1,ł mŐŚq—^«Ô6OCĐÓ;…°€˝Ő Č ć#ľÖö#´ŽT˘÷Aęî*Ť’{ţąúň »í˙™&µ4І’‚}ÜÍř¶&9<śÁc˙»Ŕ$Ęp)-&|`v„nQĹ©‹µ(,ůňÍŃž›dH…"ľ8›*GK‹1Ő§.©´Ĺě–/×°ęáŔ¨Ýĺ#N FËŹŕÄĚ‹G&±“ÝŘ8ŕX%4á!ďćý|{Âů>‚’CŞL:ăG5XlĹO UIpŞ^JšöbĹVüĘĐŰd<nÇ`4Z×yăĐŇ˙ęµúÖMÄŞ 9ýÜU]í20y˘vąań2Ťˇň1Ăhň â™<˙fNţ©đôm1 8„PT‰qĆźOs‘ú·I Ëjě'NźnŤď Ǩ›¬Ż{ÝKĄ˝J|ŁĐ8°]zkˇ%ÄjäVü#łB©dß]týg3Ł`aF><+ aßa«ńé"& xc—§ožĐ•üd MžCCÝPňl{ů;¤š/‘Żéb°N$˙2¦ŞČE˙ë´+L » h]^ű±Jä™yČăÉÓ•'.©a¨(.ľ˛ůî”l&íeOđ®ăJŐłZ]‹9čŁ+?SŔnzńó}UËZÓP€ťŠşˇ`Z’Ü ™‘ŽĎ“v˙FľK…öÉÁöBK|öí*˙ßŕ‘E&ścgdGÚ€u33—´‡ţeÚKąxf{PLĐ䬲űŇ&!Vc,wîĽhÉ$ëK6´¨kŃ=·˘¦ĆĚë‚nŕ×{†rm9$Ýšpň_Ůşw)‚ŽBůĄHNM6 ˝ăá:)ů©^3§đˇąčBşż~0r¦µ3 ʡ1\Úń»(>öNAÇD¨ZräFĂl=c”|ssßp|}d„ťŻ3FýhĐ…´>ë u\$O)…UĂá|<î7Lń:zťnaą“sźřĄ|ťS]ESÇ*U™nzOŞËĚöJ…-Ť_“Ćl-dLý” ®LžlÖF7¬9?ý>{¶&ăŤ/•n„ąÉ#*·ňZrZ<•ąJlʇbz^}…\Ľ&Î_F™8ą ·2ęĄ>Çţőµzö˙=w!îr2$ýűÜĂÄÂUňdü$˙=H›i%‘ŘÜ„Č悎ÖęQlËŮ+żú_Ä›ŹśÜ·ó•”V˛ŻyÓ6sXŤ¦ž2ľW*:NK#âhŇvň·OHax€RąŻ ®÷wś˝E„^~ä™Ô3…yĄăşfÔvĚ6á‹oźMäh¬Uc}>s»ß´t-›t/•¸BčL↋ŢŮ–ŕŻ;:»‰M-^l©­DżâGÝ (:GŻl@ű7zőm‚ ˛ýĘŚtâg‡ÇۡeŐ»oÂ=ú'áĽC?˛^´xĆŃ#ŚłM;Ô'éš…g9cmnÄy•ÂŮ—˝o8§ëçTĹ·l=t’kŤEQý,7@Ľ[˘%€ą‡ś9í–Ţ  \Ő¬…ÔŔ š ťXFî›Dáő·tÇđW¨E€´‹=wÁëŽ×|A/,§†î˛içőOe=®@> µ‰ch@Fý…ç0+š!ÔËr˛›]ŠŰ7Öi~¦Śľz€(Ũq(^CMC n$0…~>ś^é{E]Ěś˙×™, : ,;ŻU!•`;ŕҶggP~—M„KP*ŕE?čá3ťë( ÝýşÄ¶]ŢBöSJ1_Čr]¬8łáĂ5]{PŰ#VĐĄĐ2Ző +Ä»ź…cb,#X8ʲzmł,hŕ Ëčă}źât*˙@ŘÍůUhüeÜ;ôʧ&†ŹĆÔ—Í[;DŰ×Ë:^{±ÉýşíĘÁŃ#gÝř…ł:˘…”2uxNŠK˛Öý©MH+Ô úČZńAg†`w0ŇVüői/“ä6›˛OKÚó_ÓZźŹ^m×Ňi÷2CSÉbTĆU©áqŚˇĎŠś-ĚTŔ H˙ Ç·*dÍĺV)w˛ž&ËŞn—Á”ňŤ´—Ył‹ýŰN»ţ¨G+mI]ÚvZp<“~,bü§é°x^Ť ŠÉÚ¨‹–Őř‘hű1˘JĆ›QÎČE™ J¸ńIÓçäˇgŇM•mZŽć9%Ü»v ˇ­ěĚůł–â»ópşKl>JÄŞ~·Đc·jľŔ|§ÚĺD‰;(š˝a¦¤ŻůvFÔŢEAÍvHűăU´x-žá÷räŘ]-@>=7$űĘ8ČÍ'—‡Ű›Ňë>;Ĥ>˝Tźræ}˝ Đk;Ř©Ř^*‹8e¸Ę“÷cŹ…tU#’FÝ®b‰Řó^YmTŘbYC©‚±č˙ŢqëMÖL¦Řv*\ËÇs›Ĺo=Ź×úěÇW!žľ˝6ˇDüZčąÎÚźDę*~^”†T›Ű— ÝNúKĆAzcuŽľő lݤťL×Ţť†^ÜŇ s‡„säbLţË÷ńç-¤7$=eźdMŤm¬ÖľJ¸:,đkŇ?‘ZíŘć]‘Z”†&Č_˛ó2?÷®;ÂĘ\ůßőýK~¶XZ#,Ę˙ŚEf¨Qz yÝ#ňj–Ú–đi \Uvrć,óîăç2“‘𛕓5N M@_ČłńO•µŇBť(ř¶Řg\d=9Qc\DxôŞ!ÉîNHŠ GÚŁ<0źĂlţůµ ă­óÜŻ =*Ąˇ # f{Š'™ň’ě˱¬©?‰p$YLÍfdíŮř.Ż[FTâ%Wć&.ĐmJîŘ—Đ«„1ONaG,? —^Ű…h¸—#=+'b{c!¶Ťpö‰!~·vąŃÂ)ŕ­tP 8ˇ D$Ý&€U"ŽFłFśFŻ©V˙'ža'ßć- ň3čę8É^»hëăvX%l4ă´?÷40ËöVV€^DńoŘ$ŽŔ X€ęśxŮ&¦´„y.g{ ‰Z5BxŻŃ¸ą…–uS!U·UžpżŘ{}˘8»äúbŮ.P…^ —b5` eű‡Ď{Ď{4óbŤžđYĂy öę*Ģj˝cë:˛ÉĽ‰ÁÔŮ řä7o“ ˇÓoŁNĆÚÂ{hnZ"1M*ŤŔ«$óaTŕyN‰^|ŕĹŚ=ŚW‘†.“®łĐqE· ^”2>ľ÷ĐőX4ë –ĹTMPpüłä †Đ“ďQ‘ß•Ŕg˘TT˙şK»µ¨x•`X'˝Ebť\W3˛I-Ěý Z±Fz‘×+hĄ‚b•›aá Ąô·M.ČdçîÓĽĎ4ež…ĚŻšJđAĐů×L &ŃÁ§†ä¦ţ0ŮĚŐ×ÚęÍęÁ1ďí+čT“ţŁ `‰eé,~ÎÉĂ”¸Të6‚EŁÁHŠ%3ü.a s ¶:`AO ¬Bď`–tŰŔ:ďĐÂ€Ó ®ţŢł[şŽ)ícq­yˇşˇ-ĺ%Ý…Ř×Tšg1X±`îäź-®qRnš—=NÖ©ť÷ä4ÂĘ5öúÉHń¶— >َ16‚apF:š+:ňľé^}Ôî_O "BŻŁŽëćŕ1ŕdŘŽşe‡iČ·q’ż˛Ufo=íîwÚG@tDs­H9ĽŤ”&‘`śţ´C™w׏3Ěń„¶?KkŃ)ŔËv§‘BSĹŘqÇuŃĚ»Îvżµ“t(ŔhĹBCĆ”@I«”'$Ű— XîŰ»cĹżůîż`t§EtŻĹ›9ńş§9‹iČë úLŢ@ěBE\PAd&&ĚYNK8§†ý˝™ a?JY¶‰@É"Ő¶>E÷:ü´ł0 çúŃŠpÎ[€QőŁě ?Ł›ssľ.r•@É{&/Ąăú”ŢůNÂ5(sV3«ňdN~ĄĆŘ2YĄ¶ŁCťĽ]asDÎ÷>5B|žtžĎ±Ççi˝âhE]D`6`6{Ý[ęç!ÍyÁXś›)Vb[3i« C¶ßzuTĐ`u=ŰÝ­ZL^đ ë´ţd÷Ç]NĐIf5NI´äTÖç&ü±Ş[9żÖy˘×LżoN\Č·ÓŁĐîwɬÁ§vnŕ]ŻFUÂď))Çxt§#)»]@!ťâňqU˙+±í~f•©§ąŃI!ő§(9 ©‹ůJR‰#?Mz}aá¦L· ęśčńŇA6Ęř'ô`úó ňIz˛=Ý„ş“śËk®ąŁđčh3Ѷ<v_LÍ[ÝÓ˛@7E&K5RL;Ŕ«AâAžž,É@ţAÖ<’>BeÎî ‚!ýźnʶoŻ>îç6o)H˘ß—D]«%™8˛AŕdőűéF2ň1Ąnă0ný±Łîf:Ć(ÄłŽÖ'«¦ßs.»‘ŽĽhżbqÝĺáÜŞ<óň:ÍĚ-Jü˝"?Ě€€m€Ť5M*¨­ś¸jfÝ6żŤŇďËąaéł°áĘMŃ4â±Fß® q/&{vßµ2Ě'JwY_/·]Čo"ˇ%šFzĚ3×D>jKă/ ?đWĽçƇ\&}ńoąłüűd_:+şÖŐúư–XĽëű¸7ȵÂÇ|Ú\P.nÂOŤŕ»~˛\>uDŚ]˝Im¬Šó ŻźzxŚ}d Á8C4$ľaxS٦ď~í&<Ý2`ś 3“ů%¸ď%ś0ĆËĚ_řŞŰěRĹn- ŻŠ±Ť% ˘ €+Š`řŕ’Źh˝ł”ů±”p%˙QI´xç)Ĺ€Ő"ňĄşÂń_…÷1A; M3Äů¦ÝÇÉcŞÇę\Ĺń‹ LťĄęţMµěBGŽ:&ËLť+D ?7N÷–!Ť»3+€łF"•/'^ńźŠ†©˝ˇ˘|;¶uqęxýşµ[*Řa‡”¦H°ÔŘH7Ţ…Ľ±§ŮĐqWěrÖn„Ân‘Oť©đŠ)ŢÓľ!äśMkâŚ{v ç~P9›l+#ayşÉ´›LŘ!˛ n˙6Đëc@ ňJ3˙ꓤ$˝W ŰŻÝ^˸–Á?Őľęôq5ˇ›ŹŔŕTž®ĚެŻa¤oŽ’yŢ˙1^iĂHB5žŻ Ü_°Á}ô%v˘¤(FšlC«Ú°8żl#űS?|—üž1ß8ü”nďéöłĄçü4ôAqĘýĽ©"‹h¨ĐŚ~Ş#á›%»Á˙Šźé’"źď˝XQ[Wä:ţŕşČGk\FJç·šĽŽ†ŔÜŕáĄŰĆŁWě¤|ŰGfÂ>5ţ—©¦xCYßš§…žźş/Nž~'†%Ó·WL®|Ű*č»ĘŘž<]Wŕ°qĚ]ś|ŢŘ«n…í7=HF!H±qö5zöňîĎ 7x|H ň›Çď±2ß©*B} µBĎ:2Ô)ň§köfń7]}‰âę“ +˙Ň{şUüĆ;¤kzL˛ńŚmé±ă<÷iHŔŃćđq–ĆZ«€˛—]©×0L*&®ź@ şđŐś·`˝čyhQő “Üö^Đ*߲ůßµ?÷nń%IÜżěőĐW=ń‚[•(Gşašő€`°gŤ€ĂşrËG·śIů4[Đ›#OŢ—ҤHĽßSvJéŹςן\V@}čTRŚč„s ŘU,)/¨ľkózŻ8™ťă(•`{5ßi©Fńe±SŹÖ´‘(MlŁs& ’$¤'z1:üśl~µŮ(ŇŽAú-›°űÔ cä÷`Î|:ťAĎnôžČmĽ2®˛«ě{ŻźlĐđAíăT¦¬vЧ ˝ÔňS~ńŞŰăîúl7¶Čéßdhął$/ŢRy$Zčĺ“!ńm•ż3‘ÁĎ]oĐ !ţä×WĺWZptŢżG2LLţ§ücL ŐS…•©í°aűËA¬'îţ_§0ičës¸•Böí5PÖ ¶oW)t€Śź–››Ç¤§á‹űőaô“­©aQĎż ¬µÜK€ŘŹj¶ĽÜ…t†ŞŕBíe¬¸LřhLĘČ˝đ¬wđZőW@©[řźEžlŹ6!† xʱ㤅~Ý©WĘľ>f+§"«f6¦tşÝ3ÉKĹHáF)ń‘bÚęhµyÁk—‚á ZÓxc˙„«ńHz…Éj”3"n†\dۇ|;Ź‘h•lÁýšÇŃ Ëş@ˇDzúÄŽ… CöĽ_;4HÎ)TfżRĹÁ:đ‰«óŹj΢C;ŢŻw‘<n S‡Ü$}ZĚÎŔk88pčÍď^)4<“ĘĚtnňýÇl!b Ô‰[—_(ł0Ľd ¨“zÖLYřřŘ Ý¶y ˝“ÂÚAo bä]c©B7bQ$•LMO˘żÍa¨ţNqmÂížÂý˘*źć‚čż<) ţ~ (‚ížůí˙ŇččÄpjDţ{ö›ßűÎÜëÎóWřáިEE§®E;a!Ý?WṼ#ˇ®ľklń2żµç˙†éľ‘˛PkÂřD«–*2Đц$Ňq­GÇ(˛.ßxg;˝Ăę@Ĺ…ţn~ń Ł„ĽéęşŕqÜáƎʡć©U‹aÉM6MTî1<tÁöä®~W)»^:d†$T¦3°eOa¶Ö‰µ˘ëz0éŠCXMgŐĐűŮL”-´ŢJ°¨Ö6[ĺ/ÍO\:2Âcżł. aYQúç~ súRĺ]mą/¬‡lo-xĆo×Ţz¬:Ŕđoú}ĺcľSg.ą5śű뇩«ˇ“¬©ćÂźt3řĺ¤el@Î@«’>ŤNµ.!÷‘áćäüĐ'†©Č­¦ĹWĎ(pÉŘ*ʉwq_ÖvŢ' ­™úšă‡ă Ę™qĹ”ĽíhĽ\Lřß şą˛(8÷”n3ż–¤­as”ÁîmŞýK"Łv˛ż*•xzµíhj¨{ЎĆZ0ÔE¦§łË'öĂĐT^¸ov=Łß|é|¸Ř"äx˝®NÜ.{Ŕ©N :Ä\—*ŕŇŇ.öT›<>eÁËf[* 5vGAH˝Î4z~ÇDÉü n@%íŃF üĚt±s/>>axMN±Îpë¬,pˇË›­g&Ň,0&`ăYć3)™Ż¬ôÓ(ÜOČđ<ý±~!FĚH SĺŃđsŃŽ‹koäźš»F €â‘@é1ĽNdMĂfIćXšĘ6ë!˙Čýłí„–^úÉisX‘\ $p„ĘF·@šîŹbc[ˇŕSĹ]‘ŔE^ÍĽúW˝ŇčJ%9w±U2Ęx/ÔýyŮŁ‰Đľ‹UČ1$ęˇK×gŃZcPHő1Ř^L?ď±V—Nç¶`pBWBČË»ň~7ŘďGŽšŁţ)âíňíDy$€úÝEă#j8É’°×^$Ů‹`ŇńąăŁďcmHč÷Ő2=si•¦R»nęń Q//ŔĄąĆ+Ů<ĆŮXć3ÔŢ»„'ÖUŃpü”6lůĺ~đćµó,™´OĆRV  Ňâúař?‘TîN-|±Źă‘[2ú†#Ď´^˝(gäŢtŞ™; ͢§Ç\ăzJĆÝŤf8Łě!ĘfŞCÝh ÉńâćVŹWKř4N“ť«gĚeo[ †\‚•0v•íŔLwgÍgţ“˛T@í)®IÓ˘X¸Ŕ¤¨ů?H">9Pж\Nj‘Ś(_Ë1ج žđő9¦•.łŘˇÔ]‡e+†ŕ.±†±{ĂnďąqÎad˘¸ýČ5ľľ’j7K­-‰T7±ŢŰĺIArÂŔđ^~`ĎgBRŠk{÷09Ý]‰Ňi—Ű9ĚĐŇ«×^/,(9Ľ>ëҤtľ•üIrĽ$OË o‚Öŧͮ¬ŰF:ÝQ —.ý«B>ÖŹŐąÁĆČ´j ˙ŰąéířŤűómÜŐh˙Ç;6=6ŇłyţÇ~–myÚ¨"ż2Ąµ}€4ĆŘ]’ztĂş$Óü®ŁJ·ćÎ ŻÍ&˘LŁKAĆúKeÂśbMZ÷BęK0F2$7śö 6˘đuň z^©ĺ+ô†ů(b”EÉ]Äc;pkcp ˇ[ýÇ!Ł©Ě M<ť M^ęăĺ#/‚I×.¸ůˇ:a'Śâuśb-$7T/ľüx1úézŔ˛ŕ+… Źä„w4×ęĘ&Ţ­p—žz!@!°–ňae(Ô«ĆM‡RĆnĚzÓ‹b§ˇĂú´—Sµu›>´­qŰi»ĘÂ\ě #|0}$Ť DsĽ“ŕ´ţŚC ó1›đçśÇ–H´ŞŠűH_ Bd^DY¦«ä˘ fé™XTĹł@ăPönVŮ 1jŰżŻŕô+#ů&Ŕîł˝”\O˘†·1Ę|WŮő|řéŚoéZ۲Ž0™ÝŠĆ6 « [¸qéÉ˝óE»ŁFŁ‘O‚ďů#{Ńě3€żÔĎżg{˛s\-cśŹŐ; 2Ĺ Tô0  é˙÷Q·˙%Ö|Đż[ ÎZ2#pEą\šŇÜĚ c“eçśëđ‘ĚѨgĂ K8U»ökU›k÷‰Ŕi5ŰQîďs˙ ť–Ŕ‘µ†(ÁĚ.ěTÖAŇ~0Í*¬m{úš‹u=N2q$jř°@t^ššŔ~6Śît“úkő8 1ô&ľ›WQuR#ŐC™™ 7!CŐ%ęăíĆ@Őš›ÖN÷¨Ś ÓőâŐK±ç­Cź„Ęa(KŁ˙NăňÁ‚Ő9đąK ÔăŰ:Ć%)†ĘB:’“¦ĄŘ+@ĹpĄEö¬é˙íB˛¦/ßx!¬Ł±,Zn®ôOŞúą›±őU‡JşZ ¸XŞ{ŽďŢGŮ^GKBMěţhçď_QâĹW;ËíEăna@ÚžIfëŃ(}BĚő=ŢLXŤć˝Ď­›Đ‚”>táý# űÉžĽ¸$ˇĆ"÷r™Eâ©`Ś€nCŇ›¶ž®ľ”ěG*gʦ9N·Ląń­ĎĄ‡ďüUEýÔśŇĆsB˘‰g´ţ8‹š ͡ĺYx)®ü“ĐAŠ[Ą`QŠ©2«I<6á"vŸßG>+Üń2f{Ô€ @AŮO"aw˝ą2űřnZÄ2şjJ›`j‡%ď‰kY™˙˝[;U۵7:Yô›[uň|C)UTż «é.dSˇ«§Ń.l ŐfݰłóĂoLKC±X,—ĚB´˝‹uXc%U%¬ăiâć¨Ě¦@ň3†Ëd¤9hÁ8Q&[fëŢ„őr.őşj–ŇšŽ]ČÍŚŻłuUbâ¶lÔ÷϶đ·a.©Tçç€ćÇPó_ëÁ Pçl8bőpN>6R%YýK¸^ ¨8ś @5DđŇhll(€ś0.9·„µ~µ¦$ŽHÇĐS~.LăQ›”QW>6¶”@2Ľ*ś»=éĄţYĚńeŔĆ…VŘ€>¤˙yŽŽIĚľsI稰 )ţwi3ŕ0Ç|UνuőťtJfŐyÇHś#ÂQąV©×f ńĘg#¨tŁě~ľăzUŃŐŽ¬żžR %Nd&j«s·+Qą_ěěřtčšČK)7břbPÓ×su·É6F[év_zćőyŕúă­žu^¸ôś6*ýäÜŤ‡C\äĹZ8·\ü|Y˝Ó+Uył×&•T‰ĆÉČE×óÜf Ń*fDÇj2‡Í–¬ůŐ\—,{čSAcĹšĆZčÇ%ůÉ\2ŻBéÔ„YĘ%rŘÚ÷ě 2DňóCčCš™3Žńdť1čP%€›“Nl~G±…¦B¨®[ŘÍWÂŚď±nň{F†Ľ9—Ń-ůE |6rYNK5ň€(ëmÓI©Ěs4“rEńą8Ěçëí{ mVôT‚ "„» ·Ó5çĂź}”rnů‘ęŢp$¬ÇĹÇĺúq6ÁBăßDwv?˘~s&sň^°/Vťu i1Ҥ´ły9[9Ď×ô™Ť˘[ Ă”Q¦ě‚‚ńhŇ;C˙–MiáX·uĽávV]5µîÝŕ]HĎd«•ÜhĹIYfśĹ˝FĽdĺÝʢ ™m™'·Ż‚şVNťL; €ś©ó+=Ć}Cdď8E€˛Ą —›\ţtąőĄŁç9>Íy#Ń0ťFO_ťą®kŚ?gťśj¶óŃM›âO[Ds§Çצ‘ąđx6ĄÔČÓąŢßN",&EĐá?DűŇDŮŽŚł2=`/jaŠEťm±`…˛čŤ`Ž—s -ţ  ”ZÚĂj€4‰ő|¨ąN«¤B4 f*šă^ŘÉąp÷ǰ肫÷oˇZ Ť? Ô‡Muezâ{5aáD®ÍŲÄ|9IěĚF‚u@P–IoE E?y>`Aj‰—ľ'µ°âß*ÄżqđEŚPvč5y$ ęk C"Ňe±µĄýCYWÁęŠöžëÂŤgh˙&Ŕk:,+–ďó¬šaď>·â™—ˇĐf&+č&ËŽ7™0.ž­ÉßüeąÄžp7»ý~SVs2î1”j4±ÚG¸o`’éűś”ÚEÁ4LJŰüOÇpĽüŃźËO|‡mtú¨Ln ô®Ŕ —ŕ=Łm"%u[Ć[ÚâPű)ŢV Gí=Žm†'őâ[Ű<(­üŻJ,ÖřnÜ‘ţ6­ š˙w”Fď+aµ[X~öhhÔź.ăŞL>&%—jĎČünp0Ě,[=Ě~}967îL;$Ě*éfH " Ćhä×ȆO–Ů+™X™Bż9çcDZđJő>&Wă{a âdľ fňâŰ+>ÄB ˘ů]EëEžg”ďLµ“Y†pďßSÚŰăqVşLďŚ]kŁxćš]C ăLť¬˙¤ÉxŽ01R`ěĘxˇ^Q䦏)~¨G,Aç ĘÍ(‘ř༾Ń“Č~Ęz€MÚ*nŰ– B‘fOľâ !hsżl–Šfý°‚(ŰJâćţź“Тy]n(É%ÁJ@‡řÁ(´ďâ !%‘ő;µó>ŇŠÖś0˘Ţ;®~żv©¨%pAçĎ€®k:đha8 {Ą‚©©ěRŚ9ďęĘŻÁ×RGéÚZ«;ľ)om/ŃmŕgG“˙Ď›tą`—Óčc±ŤzŚ€NŃĄSVÇčGŰÖËÎ9C, żóĎNÚ2¤‚Ąz«W„ť‰‹”î!±D-:sftGű„˙ä 6H¶|@0Č o–µ fńáJ`Ł(e|ś§Ru¶+­‰ů ¸¸Xµ›ß`tŻ!9gÝňŇ*îAđ÷I‚˛m§ŐoDzŞ-¶‰®xj[¤%;×هU)ů"áĆj·¬SYŘÓM©`Ő#g(l3ö‘Nöfpwíß§Ę1ţ~Č~SŮLóeú Óâ™8łěŕrqAîdm-<±iI…b±<ç˙ýu÷Ôşyíb&&(Ž/ČóâżE®î…z@Ĺ’XެĂO…÷nZŮ“ú”ëHŚż˛÷/ŕżýăKÍJ°‰ÓoQŹ·‰ƤňŠC9+ťĺćóĄµ#5Ö˘YLęWˇJąKř­ő®EtŘxµOVúŮ/ _ŽWöŹňĄD?§_´¨ŹR__·ă–ł®smÜiZ‚{÷]!¤0Ď=.§7ŻµË“ÇŘ6Ď=ň[Ňę3Eb˘él­[ŕsÉÄŻ~ůý†Ú”Çý' L˙÷ełř)=M°„Řdá$"dvkó.•@NŐp7壆wúg'ŮíĂ\•öĚ·(Łś˝\ĐJo(őź·ÖdşSŠec•ťňç—‚˝¸%ɬÔlâz59BÚ†’—!‡$U,§fĂfyĚô‚ŔvÉ䚪é¸K̬gaą,V=÷yň0ÓżúŕmÁ4Fąż¨^X JÍGÂ×Yâ‘j ”¤ßŃěkÂđ㽉±A%Ďâç ŮTs*$…Q ·c§¸¸?Ů;k#i‚˝.7ć ĄóűRÂąę:—c\u‚ô@śÉˇŚ÷±đš¦B¬BľJÁ…ż0ÎĽăN= î>:Zľľjbďw¤=Ă]t°\ŽÜÜlËť"{Â) ăwF˘P±ŚJL•ÜDCţş[~lŐé¶ýže–ű]ĐŞźš‰®ó—űW™ŘXÇçÖ™1nh\±Î('Š<Ćw›󙺷¤ŘQâĺ­i”B"p yż]]8\ ÷Čp©Ą|Ń3ą‹ĄčSÉ6g×YWĄ=Ťŕ:ßÎB. <&ÔBF5T=V,p«´~ C ľŐ7(;µňi/¦3fx‡q”ΙžŤÖ¤jž}µ–ł|3ăxE±ŢR9o"ů.×˙ăO´Ľ­‘ŃžZĄ˝éýNdŻpŁ`|[ťhĘŁCÝ?˙ńčFUÖ)őµ_ő‰ŰÔ}Ö¤:%tk~‚ßHH”_V’ĺŽ+ß©j¨µZ™}bML0; €ů¦Ź!đ_Ý ýą?ÂOĄ ęsúąĘĺš24"Î˙řĘ _d?ň‚§ÉVŞš¶ÓŚt›ŚŃ0k"Î饻d*©ćHDd$O~Ţcţ}p ™)ÉĆăÖĘy¤´BŘ -k\ŚA,0ŘcÄ"A¤ő»îý˘¸Ű…ť#ő$sc‘{Ü©!|ę÷ ä–•6ŕÄÄq"•ß)´:Aß$QçŁ7a-Ńä· @´ŰşPý”רů©Ă‹tpšődťýěxFŤ!\śŃu ŔjqÜtÁŐ;WÉŞŻúgŁ%- j¨ŕjŹţţá‡LŰ÷g5G˘î˘ć9Ë(’ÖdRťó0Ô 8\ †0§óhµľFđ—.-ťăŤ¨TźöϦ¸5Ä%xşsH łťM~·ç‚-ýÉ^€ÇÉ%˘3i›ň“(‡:Ő„O€r24Ząž „ą,ŃŕÖR¨vŮI!ŚřZą¤ď«ŚÔfů”@BĺM"HŘŽfç\:`)”±qpUÇ›*ެ·î‡©ľ}q•Ó]J¨#»Őµć—<ś˙ őŮĹq_ ¦Ť˝ őZŰN(—(üŢQ$Ľiúá!Óőăő…"٤bă!%[»g`¬°'2o2&wąPá+J,Ž›x˙(I#űŚ» ǵ Ą×úóyŮ`wšźqVÔOŢ Â&bů¬}ó˝%$Óf,Ţ»¬ç±—F‚|yLë÷Ȇ“ÔŹ¨wî(`‚I,ŠD<<.ÉĹ5,Č?mľ7 #§o‹¦nČXĹ&ý·U"Qçx\[’®!íąž ¤$°dAr˙aZđŻă•ß?,5EĐçźÇµ•jý î´˘°ół˛˙ą­™B‡G8Ýî±Őĺóhϡü©Ŕ/dę˛L/×(o|Ě˝Îç¦3Ö''$[)_®ř`q!ŽË9tąy9|Ýö ŹTˇć—ąxVSÜB¸űťC-ü®5ô…ł)Y˛o:¸,gÔ¤â÷$6Ävf{¶“®Ą1®ţíŮâX™×pG0űű›dękŻh˝ä©nLđţ¶d ΚŽ/ŁSĐŠ ĹJĹMűK#e "ŃőRŮęÁ±¤Š żÂź ŮË–ÇojÝ(Iť`ě5: Źćšµâ”Ń›kűMgf!m˝,vŔ‰ÇQ™mąp¸KS·žYXş¨—Đ»ąęÇů }oÖíą„DŠŮLh!۵†N‹Üu<ú}Ëňµ:ňřCx|ÁYS8kű˘kŃšIâ®]VZ.[I›Y«Gwýóş[ř€§CÜä’mţ7KŔýŚŮsŻŞ»;I9rĘ9ă¸'ľůÂTó$˙6c~RA<ÉsË)!(śdÔÝĂކó/}™Ą 6[컾‘öăŤáRÓ¬™‘5Ú˝Ť„*şćĎŮŠŰĎjjµ“aß5ÂtĐWKé^1j›üq°źŞr[>’x‚?bĄB¬Ü¶˛č-–ţ}B‹Â¤%XŢ)ářťlZó˰'/fQ˝Qd m‘“đ_WÝČŕeµ‘ú`t·V<ŁA·›:ŮO¸ Éž?Z~¬|„‘2¤Ś€rĘ%›ľ˝y@_{ŹR™ů}aPŻľç_Ó¸Ţp/¨ŮÝ@¤ÝűnĽRŰ%gŰňĹ‚ţĆĎ“ÁZ|Ăź×ŕ]–„żŠ®ëŞž 0ŠĂ Qßµ`¶·˘&¦bŞ ?±IIęy@N7[7‹Ér•S©yżÍňÚ’G‚Âńxa_§—đČI7Ă.|ĽČdorł˝Ěă1• yjfŚ;ĘÖö¦N§EľôŕW|ĄsP‹Ő@ľq^€<ľí“ŕrElClŰ49~ŁŠ–dđ§çáÜţ•ú,'ˇwţ”"ď(fvß<(ś=ł$OF›š,•#çrÉZrŽa‹6ľţ`Öakr)ÖŮY+ő.I|TBŘĚ= SbőĄ<ß+p›óä:ŘS©t\üëĂ=w ‡ˇ–¨éđYĘôt+É_\‚ĆicŕF·BGÁ"+ňh5zmm_ç“ÎźŹ‹ňč{F)ßą’ɵłČˇW~–K-)cÓż‚Źă§iŔřá4+é)`~›Ŕ'/‡ťĽé•ŢĄ¦«Ń1fzŇu ›f´še0׈*ŮK´nąĺ}U©rCoĄJ†Ř€Ń{ŚŔÓSTÖ’OÄ;槨¸ďąëdKÖźf?ó…7 ,Q?î ÇWś‚R ’Fyr¶7P}X-Ńj\ 3](äs8F»”×ĺNß·Ě…ů]ŤM/Ó˛hů‰gžÉc-[l`ĘU#›§ŽíËWń.ŇJGjs@ă;UżŚ[»MQvUŢ(ÍŚćü˛m~-ąuę_ë7 b5Đă5ÇsŁîá·˙Ž2Ő6 ÂŞŕ Ó¶9ýĆ+¬Fc±Ű–j XőěVTPřËŞů>qÓcÓT¶"j@·_)żd“ô‡rĆD7•÷ę~ŚEEľRĽż hQ¤5"^'WĆ1~¸vĆđ×C–ĆeÝëág?Śy sqĺÁl÷˛â' ‚yô3NÓÖďQ^@lG¦I@ßFNć_MK˙dĘą0ľŽÂ]YÝeÇ|ń_rś+7O9<8”ŤŻ=RSQôŹť†.ݧN‹™´ťDĺŹ7¨Ó÷i'xŰ~E>^ýŃ­pK†ŹÁá°ž3˘Zf€°č{xö™jG‘űŤW®A0JŮ­JÝîÚĂî±˝—X±Śsí°¬ˇ±0fsŻź˛ş†®D{[‘Ż˘2-3čďFÚ)}Jű1šKm27ňŻCĐsű]gv¸ĹŤuůÄQ7f”ß‹n¨»A+âąk#)¸n)š°}Üš! CíýăYA2 Ă䨊Aü¸~ŤĆ/ ‰ăô!ŃÉŇÉĺ±?çăĆëÓ–…ň[ăSVŞ­Oę­nkq©"Ę8ź•“¨5†°ů¤¬„dqpx®ąBÚŽčÜŔźń@<°í7y<1-CĘäĎě{wâ躥Ýgń±$ë?'0šÍkďJ`/—Ů,Y q«S<bńăŢÔskŹÓžc|ĎÝ+Ů6[ŞfÉďąŰ/>M–öV^HŕčAcZđ9Źmtn"}Ćë¶Ľl{YŁŇü=;ÓKÂ%Áňą|†uÓěT^ËÍZ~ß,3:‘ę W4ąKżkŤŤ!5Ę’čµď#0ÉŢá9Őyź˘{ ¤@„"ú.>ĄG7ŻčÓݢ Ç-Ě{<ÝUÜWďúa±kڞŃOYţnő‚¶€ÎŢ಩Zí†bU|gu9óšAh¦łŐD 3ňKŁđň0šB¬ ~c62Ä«Ô\/Ńok÷±A‰á%7ölV†Ts׻ܪLckN4Émíćô[k°*°I»Q*-TcńNŤ«›+űnŚšĘ·Ňň/ĆŰŘ+Á–pń?Ťí3çÇcVQČcáÉÖ µô ĎT›@őÝß5ŽĺpÄ«>Ťü”ÎÍ >ŢűŘĽám*"ďR¶m ęľ×$Ř@aĂ‘ŔĐ·RŇÉí÷Ť°ó§"4^µ™fĺ€čia19I»W‚N•+×aЬ-Ç›ShCŽŃóÔ×H> ÎcĺÔA^/ťˇén;”¤Ź´ö€2ńÓĘőlßÔ ţ쥓Éő=™‚—Řq%–®óLo“Ű™‰řÚ3˝Ľě(  X!1şąĐuĂZxýUâ…¶Á¨ův]ł˝®řţÜţNŮÖëŻvž37=ä˛VíÁΰíVň‡ŁŹ@†nË 8·(ł9ďw@Ss ] †¨‚Íđ†éˇ˘íq_ÄxúH&~X´¸±ŃÍźĆk"ŃÖŽÁ¦ÓC®äô°‚sś)8đŤŮ%m8+ËęGĽÄŤ^RIZő¦áUTŢ[¨qrfx¨ěJ¬Ă÷ŽßH··T0cŔ:„?Ż ěř„5|%3ĺ/$svŚWîĹbŠő™~4)nL1ň*đK,‹z•<’Yş©«?‰E»‡ TÍ2 ÉęJç;<Ąé)‡ŕô•aî1' ň§ę;I8á­źXĎťÔwŃgĂ*¦zTť‹aŚÂ§–y×Ěfl§˛>żKŇGŐ\e­f—MByź«ĎÄâ’ć˛]?Ď@HäݶĂÎÍP^4÷BN¤űhů,LřňšŤ*Ţ%f}- ´Č<1ŤCé‚i€,©1•8ř°ë|+^™B ˙ů NcËć7Z¤;JÉxaŐVo 2Kű–řs ŇAřb•ąöť ĘȆfô‹×Xqżľ 6 ´Ö¤¶§ą0XuŽÖŘ $¶Ş™ű¨nä\Ůý«mő· U»n¤č<Íiá–(ŇOŢccÉń¦G¬ÎĂ]O[·4 g»\ţźöő “ťŤ7 †ómk™6‹F®,‹Ú}Ľčě&‚J#x䩳ěiÖ©;XLÍ|LJžě*q®ĺ÷l´böÜZ^o®HX{Ďä®Î¶3ďóA¤R ď |ą9şF±ÎŘLjƚ¼–- ¦‹ÔLC´ę8'E´E[đüťçs¨f'=¬»rźúˇA€YqOKçĄřY#uâS–:zŞiwÔĎöęřS âbX†ÚöOŚ.ĄŰř)ŕM@gvHÖˇ:˝$PUĂqé!\T:L7 łŐ3:7ÇĺŰ}ókjëź.5Ůś=€)É­Ü>fJ§qظ °ŢŤľ#?UKFŁż´ĆKş:‘¸ÚNx3Ĺ˝4qÉ2 Ź]űp•¶g_đ{uCĚXEĽç2ő+Îę˝g°L˘â‡—b9}gk©č~©Ééł/…;ŻýÔ…pöĎ›`“ř0µ˝+oË}ó ő˘ŤI•*~UJ»~łál˝Ąuq¬ä´™›´FĹФe/q·Ôim­­őŘ łĽěÚMĽsÁÄ b™vů®¸nM°Ą´űÓNť®™Dú>JĂę¬cĽĎ„TĺŻ"Á~mÍŮź 4t\>€ˇe´±N¬ęXd§( ]ŢČ,;W|$µ`2¬´nš±u6”í€Z†Ę'űňT×&‚JXqSőđíľđĹ\S㼧Ęf&g,c¦˙»źP 7>ĺdţ_ž°ë˝”)=&T ´Ź>{ś¬ĐÁfŰɸ¸źÁę´ö×=„NżéGMm§a†!NĘ,Eĺp™ĄÍGV‡çTÉMR…Ýh[Ŕćű‘J§[ ű yÁÎ%ÉS_öĚňŽ3™5®Ţńg;¨°ąô$Ä€ç˘ŰB˙¬E?¬)XřŘĺžŮR`_Ě5ĺĘ0/ÁDLÚlĆ H©ě±¨Fm›‹ :g›qĽ!C}`řčr^ŃJg\. mdňĎW˛cs¸ľć¸ŃzS§Xd·‘<6)tŃ•gR(!T(dăĺPýĺ,DbOáH-ŰŠ8Ă‘4ÓKE |$s­Ô'Ü Ď&6<)!(ş+_0–e˝i%>Ď`úYŤOŐ·ĄZ$ÍŽô‹uąÄk)ďÉw´¬!*źšźžwŔá.KÝŠ1T˛ŘF‹;%\ß $€ˇŚ°§Ź0t–I;m¤/ăĂ {ërĄOř°t =†{2Ţ~ôl±ŃČĽ6®ˇ"ť•PŚ.2Ą~®Šňěf®âŘ—R†´ż­ ˛ĚÓެƿď€Řëxą˘y4ď$ăč‹<’ى®eđ!*ËŇÔ«ÎőYŔ-Á >ű@řűó˙ JX÷”Ţďfäňöµň,CŹaQ¨KÇSł'Eö…zäfp˙c¬  ň‡ ‹hr}®řĎÉ9yřüéío ŹŕŔˇ±ą_Ŕ‹ç?Ч vX{+ďX‘Ů&¶Ýi0 ` ŢŔď &­'ö2ATq?Öëb4N®Ü´šj0I§ťP×Yőĺ zLtVn`ÖŠ,b‰03ćÖGűT(#CRŘ *„ˇd3>ŮnëÎ OkÁ¨EËíŻă˝JA*No„ô ł7PHŘQxďńŽt¤óQ¶ď÷˝ź?m;Ż ±Ë±ÉČ "@Ű+ňČÁ˙kFţŔ›bOřbµÚCň ÜÍw0ÜÇ"~ ‚¤ŐËĹ÷´–“™ž¶÷J°•ńŤ±Ee ngł´ťśŔ»¦/>–a.:B“¬Fjš]Ňlw–Ť%d~DVZ´ÖÇçÜ<ţ“(ÚGB7f§>¨n •o‘_ńiݶÉ.`B‡•;㌬r ‹ Ć«Éűďl‹/–ꬿ±¤&ĄĽ€I™×UŐŕ9·n@2~—ă'”ď•Ă[á®C-=®ÚNhă+„łŮcݤÝbc…yŹMź+Głs§Eˇ$†„ů܇Ds—’Řiň]3BŮ‚ęQĚW´̧F˛ęěĐ8gŁJ”ĺĘOAďşIx„č.ňP$žd6$ję\ ţ¶ĐĘaő»n%B€0Ô¦RĺL>Y<, fÁť!ĺc‘1/őżSŞÜ’”rj­sSç3›‘ćą t3íµA {đ!ĺţdnF&=÷ô*Ŕdţ ±‘ą\I7TS¸s75+píŢ€6xŐďúE2agŤ8 á˘05ÓÚńĽUębÖjŃ7nŻËÎřËqň+ goËKKőÉť….µ§HQßKÜéÝĆ÷徯ň0•‰Ü[wvľâ{}Hj˝Ç8Żěč&W,@@n?­07 w·äë¬Ç·ŽPŞ1é±?«QĘ’ţÓóŰŹ vš˛Ř¬aţ;·o/±kŃ<ĄVź eEĆ÷=žwj«/¬°Ś2á4EąĎŹI»gM\ĺł˙nJ`·č'˝Dtę+ {î×Âú6Ü"ę˙Bś­ěü@ k÷ÓČ/WŇTĂuş“‡¨&xÓ˘,ĐŁ’r/¬Tp–H¦ĺOË?ÁÇźŮ.稸5.סgżjP«‰#Ó]lđđF@Jłx "ë1ýĆ»iš{1…HńĹg92üüÜËNKó@č·.EúM»-°¦-că~ƸäÉoő¬Ăž$ôÓQ%Ěz”‹Ď§™źo®0r±‹/ŤíPÖjÁĽ?Ë'5TenĎĹL™˛l0ną`ţ ©íG`ŞZwŇ€×˝Ś]{‹Ţ ˝/8ňÇSSý­†ůę˝ćbeřîüżd›%†˲úNş Mˇ_ÄrąíĘ[ČŘé˘%Q<¶/!šiź"¤ËÂ%‹YočdĚ:çč Hí“ÝŇ ~!Ç"¨ć_0­nű¬ö:šôŁkŤäŚ­Ť63Ń =*OhĹMÖ‘ßoÍJ^PÔXpŽţQ°ťŐĐ«×:vň}ÁxHŢůδGÉŕÁ˙;»EY:nęO’5ĆĽ.Iđ… 7WŮBĂľ{łbĄéź@}KÔ;N?&[­Yź©ö˙ÁŔ*bz"×i"—LjĄ0ďćĐĽXůUüđSűë6«Űw(ń3ĚŤ(B=˘°nt|80M!Ş'ZEŁ”b>‡T”“!ó?ľs[®°żK¨€Oâ—Ún´®ĘşĺcĬvfU)dX%#ńo/4‰îĎY¨ŘŽt}а"X7‹îaŔ,ˇy;©ŐĂ©Ý^&S9)E‚†ĺ,ŇF6Ż÷Ç{ßx;ĆŤďŻ~ł˛A}áÖt{»źć7ď’ú]Óâ~=mÍh[&óŇęU˘[˙ “X—®)‚G›y !źíţôâ[ČP?‰ł‰"Y» ¸‚öŔ¶¤Ď@1C&A€7\U‰'@;"v„`ÜâB%Vď¨âĆĽ1÷!Ö źźjÂĂZaźŠÄµŚ7…ɨ2aˇr-ŇÓ“Q–áŰ?7+›´fúÖ‰Ů. Ö¤+´I{¬•Ň(€‘˝Îĺˇö†Ż„&ĆţJżÝ Â9ľ°cŕ&Ke׬äŠ{*4h&zŮŘa:«Eĺ%–7Hă#ŢűeÝ+÷»¤Ĺ],Ŕ‹č‚™1Ô~}—v$\ÁĽ )Ç–\ا ʱ´š;ÜI’L¶bé«FÂż7ů-hÄciX1‰zľ¬Ć5*MkłóNŕ„(ąGň4}6ą‹uÝŮ7Rđ2yZ[Ý]43·Âm®SfüŘa`q´Sß‹ ąHd7:övµćá’‹R*qó•‡ÖxAĽ7đ€#㦤959ôčűśůdfŮ™Č RwTňŘeĽáÔĆĎq;™0¸d†ŹXĹD6w#-ă…ëqbLT˛v‹ĐżđI^Íh±­r˛ —ŘÇOS9†€>Ňđ;{n(x5WQµbâÖŰ 7° °R ;sw‡ę`.ĺ[③i‚}Ęžc Ýçţü~É‹/<丄7úĚîqLĺdt7xÝęşöU$Çţ‘qW0‡­ĄÝ(ĄřMŐB¦ŰçI­aĐ z¦Ř«¸Ě}?oK"ľěćĎĄ~Ç‚#5ÔŘqfX®j›.ÜĹ€¸}ŤěAĂ!V4SI}Öo-65őż‡Wç¤ ÜôĽšâĺ L“Fú‘Đydéąú›św‹´Ť‰Fő˛ÚD˙· ł6ýM–§3IĐ•¬ÓgEEJžF4C › ŹMSśłdkŰÄ9 żIťŚôW˙ÄŘfmVIMq†&łÜKógn‡µŐĎĎËHŐgŠËdĘx1đ¬äVX0Ż8Ć[?úĂĎ®íă'™«qQäP-ţfă4őľňüI·–ŃŃĚÚ]¬.Ü*±Ş„˛IăŢűĹęÄTŽQ"ŔK÷‡¬ …3s2M¸„š{ĂâKŁ»kÎÎlŁb\wöÄekž:p7—ŰRć č©Ôl˙űF Ĺźjsăŕ‹ŕ=2ňTˇ>ĺe®Y´şúJżfcűdž¶ÍQ> ń¤%TröŐŔąi_×Çbą±{ŃŞPN‡ă–$P,y‡/ ĎáECśUM ¸“»&5ń= ĹXÖIő9Ů=´Uc ĆÝż_ý÷|ßôŹüČéÚ„ÝâxC'¸ rŰ+”4dý`ĆňŁad瓟őcůˇ„-{ćzX˛Ié[Ő4ߌŽ3óeôó9Ă(âĆ2ˇvË–ŔŢl^7(Ţ “ᥡrÉ™­dŰ—óŠsÎ˙”h•a ›'[š¦ n†L ”0&Ö©[vÝł#ćŮFŇĹđÔ»Zm~(?ů×Y…nh…yuĽj<őéo»şéKuĂ<ż·{/Řk·T˙ źľnň+gŕýhĽ=2<6 gJAYBڵ‡gX¤Ý¸÷€­ŔVÔŰçŕܨÖéwÖżő“/`R ×_0{n<®Ä´Ę“kú"řRîÉo)± FLćâE+÷™@ Ě’¶‹}8†’ýëU)ąőú עś(@v[Ä+ö“5’óI˝¶îř´jNᣠ|·uÁ™c<0aÂ?Těă ÚbzŻZ)úhP7ó ĄĆ`ťŐý2Ř«éÔ˙ćÇ;Ż=Ú“Ëół1ČĂÓöĺÍ«FµI±őzŘJńÄz¶ŚEŻ5ŇU żţ¸ëҵ¶r•X5ĂkDÓĆcMfr ôfłPQăłMËC…'„¦T›NÔĚ·•l’}á˝Ěe|‚ůĆdşŮ)[xŹw˘ńc5.…L(ÓO:ęqwBőJO¨ěę],ޞ0˘5iż’Hs?‹á{“,ČeÜ 8¨{>„;Ţ}CÚ´.’áÝ08ÎţąX¦ ®ŹÄă]°<¤¤JGw…xš°µŘţ†e…hÜjζ"ľčČp„ń;5 ś0ŁvWbgóń"ţÓÜ\ďQSćSőH^}ż_C«DĆBçś5"ERP5ëÍŕwµď‹•qŇ´řAqŹÁ´Ceöĺ(~ż©©Ü3KgÖ[ÓV‰ÁË/mőgűç gˇ5ëÍýďÎýže‚eˇn€EýUi;G'~ě’:+zęTc}ĺwý›•úôI˛Şn«×d-ťçŔě˙ŔágrĎ‹ëéU›2t.#ž¶ 8Ôô”V’Rí\\ T¶e ^NšÓ‡Y¤ÇŔ˛zÜ™]•íëř3fŘه‚•boŹF>ľ+!ôţeÖű§ĺ7sČĚĘť˘Ě˝{%WâYĺě$ĽVÖ;WsMVC#{—Č«‹Źžďôč˛S˙ś™ŕ\9+÷!A±ůŢŠZfę±BxTÓ-.x;ťzNdEřÉę P1§ńJ S ĆŹý•-Jˇ…ëżC{Î żĐč7Č ţGe…Żđéă@«B7,đ$ů€¬P0Úű}Ľt qK0`Á»qąé ¦ËZ<ź”3ż›TiČű8fŃöۇ!t¸ęő,^ů§Śř$ĽËÂ÷s[©ŠC¬Hj öÚT|őEĂňOf$ţ–âŮż#ż%Ď‚G”OH\Ěźo‡ň:ß_7áýöćK ń{§Ń Řż|ś»Ĺ;°Îßłuń”ţ‰Ď9¤†ěfçPt·żü˝÷y:Ź' ĆţÔěźÍÍÎĆѶ˘[ăĄ#áŐ8aüF!)>ÎrŘäÉĐĚź‘mâUŁżEN#PܸQ%^ći.Đ‹z´Ăş_¨t\ü`[Ŕ¸¤rít ×*1ş)Ťň)Ťĺr-Îý·˛tgÖ7ˇQNy¦ď{v¦™Ö28š‘óřۡěţ©ĺ6źŃŇ}í"sű›šµNiŇăĆĐHLá‡sQďÔüŕ,Čůyxyšár‹0 ×gӞłěŮc‰+$şîŢÂF˝ă7k}:•Ł ĎÍČFP™ëł6U±IŐwş‡ä‹ŚĚaŮd˘ę¨dą%Ľxk7ĐŘc’ßšmÇ!ÖTg{«&âÓ%~€<ˇ$ĺ˙V Şű95ë>™?@í$sd!;ąőH€h8ëGÜŔ&ąť uá’dŻć §Jwó“«Ä:…š€íIjńýŐÖ>‡r¸ ďfťľíʇ;RBu†ĽC ňhc̱€ň$÷fIÔž›ß!ˇź±× G!i&Rí?.;<:ËŃŰk7ţj—¤® «‰!Â/čŇŠrôv=&k#Lšçn”Č«©OĎn·ę3Ó<ł”î5ĐJĺâ0?»*+ >,›H~äątc¶Ť‹Jäg‘MŃJĽmË×T›1ż€Š)ĚŃ rŔUg, -^ö®|W:äÇâ@¤…\Aľ˛O!űžoó*K¶îŞnĚ®sbĽŕśŹň=ŤŮ™Ąšgűůßf6ľ8A¤©¬qÂ7“c ­Ł¬v@,íV_y}‘să¦CŚđ¬ţçY]],”„ÇĺíľýR"¸÷ÜsŠôXf*mq EŃ$ÖńMěÓ*EŹş."X\{#Q÷ŰiSkµv™ŃĺÍLŚ=6ö_˛>‹=ĺOG`ĺ T,­:áŮuŮ+‰CÂbMČ:M$~ĺż•ĘRµkýidľôVIÖsI8ŔRÓ•]­ŤI;Iűöq덥ĆĎsÓ *LO«–W·¬»ç\ő!ÜMČóBä-ŠTNŢś6vd6ńɬ„ĄL)nËĂĆ “űěŢ–Ĺ}xÉUć˘R~ݏäý­ž ÓrŞJíy„·Xź86iú°…Dň-úLčí*qTľÝ¬Ť¸l†`&Ş12&o̤ŠĽ-3ľ†F2Xëµ®–á` ĂIâčś°,=LćzU9–cÎ'Zş‹ŔÎ0+€‘»—KH˘Uő»F!‚–gZÁ¶Ňqrňlł@‚öM€Mh¨ŻZi}{ŁoŢ·µyśP‘_9Ľrń?_"©qMęíďGšđç—Đŕř‰ül Î2‡h=éćޢŚřűîˇ'fÔ:z„Ăt(A–˝„Ćj[łűÂpv*ąb2$·–ą°×$ËonĹŃ`TÚLˇLеFŞśvő0p1"˝ |…[’+]Ëę·xí¨5‡"ň§ŮqżĽá’çFřtăś-GČŻ‡>$‘ŕ[dőť0]=QU-—{Ę-ŽX‚ ~Üzó2PLz¦×˙>Ú‘I7b˘'·»a*‚äcN;Bé+˝‡2¸=ç=‚łî¶Í(ş Ćě5Ľ3Ľ ÇJYܤŐUč‡Ý·’ąmšÚidpçí|ş }UĎÔI3/źAťF;6[úKý)sĘŤ[©ÎvűiŤŃ—?I=-UÁ˙Ë‚$ße˘•ËŻçÖkć ±đfĹĎří!űô$Q«Jđş-0 OçÝ„t/PđPÇJ+`»p_Ęž9&’6Úŕ'aƱL[!ĺŘ~#šä‹­a’)x‚}žnß8% Gźď30qćBFŻŞpX¶ ’–·Ź÷⛉ ĄřHŰP-ŘŘ]81<±Dçµ»j‹¸‹FZ]ż.E.­ĚŹt¦FM@´a8"÷ľC× SÂ,$ZŔî¶ĺjyÁ­N•‰BCÚꧦ0ÓyŇĐű°Ď3Üç{iżRĽ!›[w/3ĐŹ™Řů°ő÷řşĹ4M Pťp®µ=í&——×k!€ßSÍŢü*®űžr±´qyI€ůćŰ+yţŘYâźË3—Ä€\•1Ú© ŮᚏE2KY…¸+wĄŻw«u¤w6»GŠ•Ů*µČ\u…Ľ5đĄŮw!ÂO"~MâWîw'”9‚Z–7ŢUé9şAňYŮřžŇ¦«,V,Ą˙xÜöOO^•lŢÖČDÝtJOv?«ň¬¨ŇĽuކŤ„Cßď> ďŐR`ž¨Ĺµ§ę}dčD˛ĐbI!]-|kµ(°äś d×?ěPw`[źŃ$*9pąćĚ ľ ´%A@µY*×Ć8«@˛ÎGxÁ¨ý}ăg<0,)ł‹Ó˝"‰ˇ”Î7IM} ŕ–ş™í:i×µK+$´i<;|!´ÁÍU˙ÍóýźiúćD˛°Ó÷»éúqeô=&6î˙R“‚çhTČ'#Z•TťěVÍo-`Ĺ^ r>mö#dh;Ş'Đ˙WN Mp-Ţ3đ…ZY´ţĹ͵†ĹuĎYÔ3í­V#ň˘W3%3Y>ö,ÝvhŃz S,ý}ľS˝¨…GŕoËÎQ°WRČ:<őg:-ëm¶ V%ŘŰ đ ´c6.Ńó÷›S«¨Täëż5$ś9«Ăv P)PšŽ»*üjZ©u¸ŐűüÔOnr–Z¸ólk9¸WS"ĄĚ +™)é' a Ž%=Ć&ż‡˝oÉ ‘¶ŹÔ) j\Ů턣ŚTU,ČĆtM-ŮŐöť¬Z2Čü&˘1«7ÁR;—1N+ T™Ď–fŚČ$ť×ĆŽ0o‰ ˙ ¶l őIż/´ŠZAڍ u•ăż›;‚3"GĹđ×Cű—´şĹŁuŐŞ]©Čů8›~of‚«ŕ+şTâ±x \9*ůSć°oŢłřŚ>ÄÍ-U/ű'ÇC)±Ś]N¶é°†x~V%€0‹k±î.2§"`Ă}đµä• »ĂůVë•(‡§ÍůJÝDwµ\9€6¦óFr† ŕĂX„Đó_Ŕ~ËłZ…éö·ŞujĄŔŕ‡ …-Ă1 ĎňÎŢo7ókÄŮ^Ąú€·NtşĽ‡o˛9Éňue#U¦_Îôk±źŽ^!R‰0QR XWźTpČ[ŚŢĂă÷"ë|¬ŽďAZŔwoIÚ4+P°§é8ÔpóýW¨)]!SĄäŘMď â;C€ZĂŮ@—™ 8łšĆĂŃč4ěL„óĎWŤłęŕŻôáN-@©ťË{A®«ÄŠ«YlaÄ[ÍYlcĐÝâÁą~v‹Ó»o¨w?xu˛ŞBŢ2Ë>čďIŹŃ€éRŽÍ„á/]¦püIqqÔŇZ%ł_ŚÖŤź‚ZçFečjÓ(T Ľł}yű†»eöTć{Űßűfj*§‚Ýú§±Żh>3Ąęý?)îh7CŻ –‚+ňÜé]płh±S™éę’ÚG€J3ë ąí›oqŁ‚%L ‘IćĆ8b×Ů{|¦uńç:şÜ5wîŽĺ͵ţ“7;duß÷1;4ŰÍ–"U·Ąŕą€*C Ř?#Ľ5Fc‚ä‡Z.|»Ą}ž- łçŘŚ˛†IâÖnîtżÜ?Ëçł_ZL€Z!ŐŞjHş¸UľŚB ˙}čp==m}"3?ŻŹeřĄ&n˛„íŻDĎÂäŞňuéo±šÓ†ĎX>/-u e/<3·Őđi%ÝÝ%Ö‹AҨěŽvŚ€‰@^˙ ŞuąŃéMU˘cMřłĆćţŐé(#Ěr[Ť*ÉžAÔ©7Ä*]Çëgë»§L,.9ZĚO±Ń’bÉűFd1˛†łKÖëµËVߤŔ+?©©ŕQT2Zĺ,k âą7rňő%Ô–ŠśEäŃ'ů¤–114đ/6$Ť\“2{˙; ł_whÍvŁŢ4n-Ĺ !ĄÁĎL×aÉ hDśصc]–ƶn9écÔÔăe~ŚľEßÎ%éČŇą÷, ˇ›ia¶Z Ą3Ł1µ§I)ů"ö‚=–bgôc’Đ)Ľ±ŕmŔOŻĐ{Ćj;7Ą ĹTYĚIďDCxŇ{¬âxÚŃű‡öŔ®ĂÁâ1Můą?X‚ŹŁg°­Ě[Ç-¤Ęvż:·5˘tLpÎ 03Ąś¦úm*„†Ď«öKîTE^Ák…ŠgY¬¶´kšŮ–¨“WM ±:ßU'>1Ű•ťO†—ąFYQv<]î˛Ä¬‚IłĆ=×–î:ńL)Éc!#çoH^@’'ľ„čŕ¸eb^ÄCKLPçč’«+đGĂÔs5k–#_áTýBˇ(»*g¨ţ€·*«6®Qg€řčř{eĎ!†.Á›î;Ţŕ´c*ÇÚKNU—‰e‹=v<Őˇ$GiÎŤ˝±şodO[âgBěz×5şŽđj‰e$ő§±Éů+ň]ť6D1ř«Ž ĎP-amĐ\ĺA˘dăÚJ©“Ząé u#¨Ú‘IS&Ť”|ľöϤŚÁ*k 1ŚOď} •/Ŕ“”Šb«ĆŹ÷ďż p»ŕ™5čĹř› (cý“JÂoŮ ^puM/Ă?É–íţs{‡€Ë¶ ^hÓ©v/€ŔÉR‡E![…`ČşqŁ#„‚ ?—ýźř ][ÔĐĎh6Ř?î÷ RßűhîŘ äě.č*?gţÂűj¬üRéÖůÇáÉȕɶŹŇRM4<µ ˝Gp6iĺ«úÜëYëŠCÍ4sÄA|ëęhő×ó˙ŚŁ(îŮş5ú“u-Ä-<)–‡^ć`ć¤'j`*kP€Ť]Őű;1d€Ľ»]Ů YçÓĚ .çEź1;kŕ&»BŁaBďB´Çű›Ä–ArÔĚĎs0XëhĂMlzpH=bńapWĹq·S«Ť­­ÜÉ÷5đ­YwŻdŞĆoŰIÓ˙^÷¤Ë&´‹"Ü%Ŕ4¦çţË0Îł˘§DÔΔŐ"‘˙ňIďĄCJ/ÚęŚn89Ô &'‹} fŐ‘¸l¤Ţ×Ý\fÁĐE4íđ9ÚĐ&ăiÝE„ü­Bş°\çGA˛—şâ҇Ćg‘ŻEL!‚ľźËÝ$&Ä"ş*‘MŮč¸÷V+Ŕë= Ě^ŮA•P‡Ĺ[’-öşPÄ)`L fű°ů΂˘2ţd)@ˇ‹ ­9Öţ©›+ŃóěBRĽý_ ć©)Ů«¨é5&ëŮ|ůş?˙Ë–«!/«ÇĆŰ=ďYŕ5cF\_Y]ŰI†hČFXŠÔÚY«#fuOćóVBÄęń'Đ«´^ÔÇSĚnÁfĺ]5‘‚'d7B‹0-4Ćp”ś Lţ¤±BÓt†bĹ%}<^×Ĺpső¦żśBDŽĎp=}8ĹEť˝Ĺ _ó»çtN%¶ ŽěJĐljÍcȲ¬3·bRL…Óí†hµgŢëY¤;oćű9YçßK`GWđÎĐ=¤] ÂlôĚ?ű‘ĆŮ ‰ tu§Qţ28|g˙n ş„:­, QX·Ý§zQb]>Ž6R€łś‚ä•(ő Z;…;Äí8hßéÄě üörgkź®T$ńÄ…ůčóš-#»Ż`(ÖS2°ÝŇtrĄqx‘N>ĚwŃbď—ô\$sśn>ÄďŘÇĄNŮ˝é‚#děžj9‘ĽĄÜ\„!l9 †lmlŞĂoíĺŔĚŁśą ë“jTŰ÷»b35ŕbUţë.⪔G6e[eš(Î;~»!MŠ“UżZ‚iPš¶^,ُ?˘ČzR«.o›«ű!ö§‚+öXvb{Gě›ă†Í~?™e‹­EVÚ´e x`9=Ťu—ˇa›™$÷xu)Ž«ĺŚ Čtţć/‡[•ł-˙µA˙ŕgK rZ«ˇYě™~¬‚}č%ű‡+ý=|ggŔ䦞×ô‘ňÎe˛d+]"áŐŽ.>‰śĚ!T Ľ® ßo‰ÝN˝`%U“Şu±¸t”†mYş˝ëÝa/ČˉQ%Ć:Ó§3”¦&ł˛©ůefMÖÉ`w9ITÁeă¸0ň¶]H­ýg.“nÇB¸4ÔZvFßÂśhŤóٵü\÷eb¶néh-·¦°‚¬~‡”_§)VM- ˝đł\ËnăĂ­oÝ(  #Pp`†.=_™Ů“ť–&poŃ®ĐĚŻIąŢş¸ŇŇ­*ËW÷y°l>¬‹ô ­˛ČËňţ;Wc˱Ē´ź!Ŕţ©ś›b lX.+¸#F6–ń¬ôNĎ<Ú"‰€[34Vpö‰îç+k°Ö€/·_˙ş5¤ f˘}hÜÚÚüýÓő ÂýâO®j)Ŕh/Ěéýą˛}ú9ń‹=vJąpת~Ë6gż˙áDy:}j…ŘŤŽËĹ*žńDĐŢ\  íéw„i·“+–ÇúŁ/”¸sűn-űŻnlťk~XˇiO¦ Ârq·ýőžłcGÂĐy4"źimëb&7çÂçóNHg,˘bŔEŚžómrşÂ0O3†v»śPşsXúňs=ŃŻąwŇŻôÓ¦s‹I#ę¬d¶)ŁěŕÁ[z@ć·Éו=–?µÚş—rXĆÁĘ·7„ ´‘˘T%8!©ťŕŹŰU¶ç ëi×…{T]Ć3|ÎxŁ·óA-üĚéyÄ*Ĺ˝öďNJ…?NPf5Zí]|8˙&jŰ€čnó¸r§mŤ'H†4Rü—€©©ř á}SłQŰXXo'‹BlŁËTä§{µE隷 ‰ś,Ó„´é[J<\›Ĺů,8ů3ŚżŻÍ•eĄm­Ů+T±Ćżh—€zůţŚŕ)ŘW śk°îx!'hXń‹Ü¤ţ_*¨>‡“ý[Böďlůxî9}v9¤D6…o×iÚ6sŘ$×ĹCYćÎ3‚ë[“ý»ŤĄ”ÔOdbŢ! &§„ v #F„)—2‹§Ďň:ř‹łg]Ě1™Sv.I^ý ŔóúĽwÚĆÓz~›cQ¸®…§–š{tE+ďć™G9a=üí;¸7@Ú$Ď3w=Vľ÷ä)&čłów &Ňř#Âź±ăTEşD”e@I­N®LÝÚáŰ´ÍŚó:1g3ô̆4|°Îé'fç•”Ő`ńE˘ůáÄ™ůęgVąęD™>j2·¬Ď'8Ö*rg=‹†“{Ó+¬Nýż’TŁ“µu¦ ă°Ö,5ŮMnĎ™í1Ě1âS‰Ůµť•âóŘÄ#|ĎjÚt©‚ă˝Ü¨*Yh:áh,AçGC[ˇd®4P‚Ń…üÖ5Ý)śÁ©` 4Ç,“‘+®éŁ"ą2˘ˇŃYHއKD¬-m»ŤIÁÎRXf–|Ćč€Gd #R'Śp× Ť.ďp§\ßdu}nŤŮşC łÇâ+„ęh-âŰä"Y–M}p'±Ë®ĺÔXń(ÓwD(= ÉŻú"ćPfAŮők_˝\Á‚ş+@úâé ˛–Đj HÔNÄÔkaꡔöĆLdGwâK®ŕäřŞĽZĆŰ ¤,A&-@r%ÔÖŹĎ­ĄYZ•‹Ööň°uS×_’qťDw˝ňŕ\YÎŰÍ~9€˘ › ‰›Y®Ô)Á­h„ů×e™”‹Ůëä‹ů]łéZ#p·Ţ]©]ýxČÉçÁ0¬ňÁŽŇCľúčů.ămíÂa᪓×Ý7¨/îd›Y/e‹ť]Ô÷CŢNŢčS8eŞţ<ü]ůűpGT.5â°mŁyŤmgđÔ`UđÇű¸4…%JŽ\jâĺnĺ?ÍPôÄ9 uÍŐ/Ňťň=ŻÔ˛E;îĚ\i©Ť1ÝÚ0Řspš.¸ßu °Ú„ĺ±·Ă0ňěŰ^ ‹3ÂFPúekކ–!•wEݧ #O­„j&Äl‰sÇ)«Ą1¨ĆřźŰsßW»śż)© »?1i9»űŽČig$Aĺ“h]8+ Nf„8ŕoř˛|fęk ÖR]ŐźˇuEßn‹57/‹4q$´śU•V…fű‚BŰÔ.FXź1˘ #-eu%˝}¬¨Ę]xÂp̯â±Y䌙É:ä&z„P.MGó× Úl† gĎŇ‚TGŰ” YnrěŘOz bOŔÓ›tÇĎBŞé.)î@ŠNđܰî‹ÎLTR–n/?Eś\¦J eKągş! ?1D2ͧÉwN–o:Ç ůŐăůCâT#¤ŤfźŐő)ŕ:XcŚż6{†cŘMé”'×Ýä*:L%hăy1 ›µťµBŢtýxë1}Ő¬Đ5"‹Gl˝Ř˘˝XÔóM!ŁťpŚĐuĆ1˙ îľ}RµB–˘~Z>˛Ś”ÁĂN#»CiňĎÔÉżł¶OĄi4üpslÚr¬†¦Ř»žléůWówY+ífĎѰkKEôúÁÂ"ľ÷Ů…wý˝ĺ3ęawWDI âťĺčgáŇÜ‚‚xŔ C w˘h&Ěé‘âSkµ"Ĺ|Ëš| §MĎßéSĄ®{­ŚhŕÍ'Z™}N†}˙©ööůĂćŃ’ć…r€ÍkKű÷]¬ľw}“rgbÎUUći¦¨×í7M{]uđ­¨Q+ ÜZhŠÚ'Z ůuhGoבÂ(lĂ´F†Śžeyvą„gŽ]˛$}RÇŹ®śŹdxge;ă{h?ŻbE«&ÔjĽ9Ż‘‡˝ËÖm›ëîEJ#fkPбŘč° kř&öˇBüěu˝żnßî_3DÁHŃĽ”/c>ę4ŇŘË,+Ä “&#ř7̸˘beލľř«¸”č] ë=p3Z.N!ňÉ…´@­şôő†ŠDă ršŚŤqľ^÷~Źk>b¬GË—ďT3íę—ŮnSĘ„Ż5Tí›k‡2úŮđőxŇç;&hłĐ8É>p@čZUG¸®Łş±â&Éc“áÉ`;_!Ą‚ś”uÁ´RŢ.†é–i9¸G۸­Ą+Ńr<żŢt7šYGîµÖş–‹1ĎéAŕ66ď%+@ęă'Ęýץ3îu‹˝Cý·W [– ŻŚý#đßŢ3FĆ ňîŕA‚Í^ľňJáK–ő42,đbŰšŽÝ™AîŚ#’Ž×„ĘPVčĐŐÉŁ˙f·ÝĚÔŮn‰˙Ń_› cRylEHť=‡:śďź2FCS˛Z_ĆdîOö6:Fq< c19IŰůč ®O.JďŮ»0mž 9XÂss Ö©·¨ç#DşíYŤČÚSXlý\¨_V^‰ŠM Đ*íá7F;§ň˙6“ŞkF“[€ŁŽ/»”ęíŞ¤•ý }B/A}mf/DÓ±$^çm„|¤żđ˘÷ąç^ÇĹđ(ť@ś1H~ű[eď-·6×Ó `—סĐ⼝zn¸°¤8Tڬi»ZXŃýa }ö~ +÷şí@sä¬çßXž|!ń!xXó›ÉÇäpŤ|AüŠŇ~Ŕ—Ďšž€ľxŃişę˙…OSú5©eKS);:c8En|BŇ cd$­±Ł“]TüłŔťQ›EŐéĚőî7eFFA,$ZÂüĐJ.,ŔLeA¦>ăźg\řĘŃ<üĘă«äX=íBŠzVTFf›/¦MlpŃăĚuXéw6â\8ôN[»łˇţď¢ŚGGkŻé„¦][A›/‹* ŮŰ[Ž{§şĹµ0Pâ[hFG%ÇÁÇöbq'Ł´wp¤řI°o6˝%m*¶ňó çúHmqز˝Á–ś7 »÷;ı±‚CTŇpl<ěRx¨Zz8ÍĹ’¬ĐöA Ř DŻŰřöYűČ ·q3ťq6—$_˘ ‹îÓs(A„­Ż;‹ôŰ%ě‡yOČŹ‡2VgţßeŃ…żä¸aÖlůÎtl|}şĐlƵś˘!˙˝EZ`ą€›ä!Ńúţ­_˝CŻĐ¶(ťäk>ťdQżżk7ެň—\ÖŕBÔđ†’E@\d"ČĘ ׫|ĆĂĐĎ;0 ŻÄ«t!?ŽřBÖß]<q˝…0·±áçÍifź†)ÄÜCj_^08j{“_xfňžř†düÔÜl·šýŻCvyŻóG Ţż0ítsžł¤ÇkWňs˘ëúĄ}č÷"qü YĐţ˛ţGň°Ä—µOé¤v7ÝA¸üé;żđ1- Í«ŠüĹ$蛾Ľp Q)W˙J8»ăű-Ijn)őô5›GÓF‡=R×\â;ëýŰŐ-ĽGźÚ˘ÚuĹś7ŹĂlqXĽaĂ #7.‚rÝ$th™"†ŮČ‘]şŘ ˇý4!Pú­Çś˙żĂĄăŠWÓŹŔĆňč—łűíţ¬~!2_“‘phŻňŚ{"wŰÝ#âLI5ÇYYź†9â“ůl—M˝EXÔŮk»rđ~AíńîŇ\•őD/z)˘Ţň˛­v$1±.ÔsŤŃ-ĺˇ Ox"ĘIÁp^üÖš©9ăSGčŇXd&U+ć’ÇŇľKvŘH©¶«tŚĹ »DźţľZv1›2—ž¤ÉČt†a0\Íłţ5~˛ÎÎ?„n÷É˝®ÔőščIŇđŁĘxzOĎđÜŮŽđÇĆZ)č?{˙s¤sĹQ‘đM;zšH×É•şOč‡4‘Sćč5¤y8±ĺ4—Ŕ#Ö`Ô™âY‡ é0Lů„–wVJůŢ •ý»)>lĽ\H’„vďu.ĄĹŤäj…H 0qą DÔĂ\§f ř.UäÍü}·QÇ­pްvÍVí„6l%]N“Ë(űC÷_”=z˝‘×G?ą»Q7›Ą2)łëű°±ÓŢ`Çur.|•Á|ÓY&§‚)‚ýR„[jŘ ´PÓł‹´™óq »<¤îîŁ]Ą(XłBŰŤü—ţ§¸ÜńAŘaĹÚ»ŁÜqomŮîśĺ2ä×!¨űŞÜmÜ5„hă?ôĄsÓ0Ú¸ôËďŔ ŕÂ8ÔłjkĎĎ{ݦ-®ë}Lzď u§X>‰ HśĘ<*(ű˘lD"  ž"Ŕ× űý´HđC\;Ă„ ßH÷2ł€ÂíŤÓ덩.Ż™Ś$3ľĐak_±+ß}Ç)ă$V‚:Ž5$X‡ď<Ŕßń•H­‘áWŤUXČ>šDňË0; Ě%¬\Ć %"ŤźŁkdĽOeCŢ– ˇ>;j|ČvC5|eY›G–Î[ňƧ…_*ńYM®ŔˇŹ IRď‡4 'R{5ˇ—ű䎅8Ęń{ń]óĂAW‘›‹çk6$Ç˙»Ź˛™´^ŽĘ`§ťçôV!ĘŹO& ˇfŐ®Á„ţµ(«­Ď mLmćÍŃ^*“lńç2‡`©ř~¦áČţsOę›oď9V¦őŐm)ŁĘ«ë†î%9:ž›.Żł"}~H Č'¨JęOQüÜâÔűSOÔ€‡.›Ăr-ňŐŤgćÎŞ­Ś§ÝA÷gëLµ8#%Iš‚˛„×~ŁU‹ź‹w¸6&3•„ÉVĐ?ĆĺT×dDĹša1XJ/…^Ąďî.@ S“zz&/şĚ Đ÷?UŁÎ©ag5Żţß´Nţ0ą«1~Gę*2eND"¶\ÂŞL›YĬHج—¶ŁŽ{đ_ˇ0ÍßQ5ďŚgËę¬á—¦Ę+űšąUŻäzÉT“*Sëy'vˇ7ĆęŹk®Ý/ÔLô|Ě4;Ýl˙çŕ ]řa4Aymś΄3…"î'>-ŕüŐˇJ‰sL%tgżÜráżűX1Uąeâ?–ÜěaÚd´ŔÔŽa/N¤†fçďlç†Ń®”µ_=.rѤ‹Â…g:u˝EoKĎZw€úş†a­RQăO®qó6öďĽ \ŢĹ쨼©t˘€ÄňŻüÚ(ćB3C9˛éŔ2ë «q Ňíîů’㥌†şĽëëf Đ›ĐŤĘ}b/ŃA±Wň©ęÖ¤®6W= #×KŹ6O»€ŽŇ8·…2so}Ď šÂ óőĹ×39)ţőď{vĐľ k™·ű)¤ń«› Ńš*ÁěŮwXđ9őń®3zOĄRőlĐ\R“Ű:°WMébÉ)h2*ŰŻůWďç”bĂ&‚Ş “3Ä{XŻĺ ŠÖŢ2ŹŁ~é÷+ŃńĄ%›d”čaş&}ŇÉžwńž ŽÓ«CqO†ÁYĚíLŽůgŰ­Ü9tŁÄ¤Ý^w°W&#Öb ňĎSŽđSTŐórAަ×MňŢ~Ď®±¬RďUXŃjŃ0Ç„äÜŁX–cĹx· 2Î*"×Ca}2Şt1ş1Ͷ˙™ŁÝ»_Ď˶Gâ(ëa˘uË9ŐiĎöÎŔ mř‰Ž¤t=âőÉDhΑŮ×DlOě©Ůh§”ü¤[6(ő’“´Rb˙,+tجĚ3MĂ۲I’q5ăÔx,Śíeó«ß1tşź€ÁłÎĚ›|5ŐÝc„˙ Ľ <–ÂPŕѤ­ä*g ĘěŮ‘\-|¦tJ°ÎĚXĽ˛Ş”ʬY Üâ9µťŇÉ‹–fřłdA[›Ž»PĹ,¶»™,^Đ[Tc”H|Č„_řÉń°­É[Çr‚.Ôj¤Ý.%óĂWbj8˛cä‘©\Ď–Q/ÂĆrB3tʆü!˘g.ş"ٱ–ĺ<`G5‡iđ𳌚őő}Iʸ¬îŁďő¶Ú׊HT¦hý:Ů·Ö¶” „ŐHýAŃ»r)3Ŕ•!iĆożR1ąó €»á§ĚyQâ;›’2Ë îÝ»€Âű+g_Ďި×8{MĐ‹×Dţ…XéaCS±NöÇńuůa ¶ ‚sř†}±-ň-­8 ęYFpkö‘ÎÍěY.‹şĚ”Ą_ś÷oÜ×±Ä#e-¶§»—ĺőÂNşfš†}Ţ­¸±ďť÷$FvKřŮmĄe›dŠ ±{Ż šPśVűdź'ÂVÓrü.]d5'âú"9^ ‹Ő7ďÔXC|Ă«BňCĆ–NĘśˇ§˘‡ˇĘĎu‰¶ĺqlš^ëČ13bŐ“!ç´`1  ćőĺD·u– öÂ/›qa™ŕĽ†Ň¦¦ôŁ\&äI· 'Z@!ú+ÂťdĂÓ ¬_7/µ‰í}3KŚĎkföżó™üôŔ‚ćűő¸×[{Ţóš]ČA¬ł±â¸&ČŇ?_­E¨ ±x~’U·¨ÜÁŤE`nL5x„©L!¦ÄKˇžěqŠ+M­ÁD SńŹsÜä`–Ôď«!ÎŰ8LűXjCž4đ|*řLy˛ "žô”ŁĚŃŃ ˙ź?6˛‡Ë)sç€b[±·¤Ód”YK«t+XU9LŹs‘R0 %ĚLWň=<8 śŐĐźÓËŹFfáŤ(%´âYCĘôQĽVŻ[´·ĚŠz»aťDˇ7–‘4ô{@Ľ©±ŘŔŁÄ…DÍĺ+ň¬‡Ę׮³"ˇŘÚ&Ö»÷ ë¸ó™4e¶9o.ŠL&IPt8,í/P[Ť©ÔŞVRç¤Ć˙4GU‹¸±2Y÷ËqĹ"(aĆ|{$Š™97e ô.Ż%Cuqĵl‘ŇOyYĆBB×›«‹n@P<ˇî±DMßę”fëµnZ1ë¬ć¸Ůüog*ĂśűfoŁäĄµöhWęz‹ăęó Ѩ•€ÍĄŞ<áŹ&—ŃWź?@Ô xíDŢ+ĚŰ´9ČCË.”D`UË'ËŽ°é­Ą%iˇ|ľëµw`7–!‘d7†‰ ZD~=×"$´7҇-nď%¦›Ůěü‚¬ńŔg/ &dCŁ1¶ÔWRgť?Ě~ăľ ß”<Ó—[HIác Lż‡Éą–oó©hÔŃ´2ăĆ-ÇĺŇ3"ťĄʨůÉÜ…3lXŻÔNĎéŐŃĹ怙ĂţiľWÉިóťRȧj:LŔ—™­070łó+ň¸3«Ń iĽ/6Ň(°Qg ­–|Xʤ*WńŐżZ bÉhC’Ĺ@]Qš ÖŢ™Žme­Ë ďÓŰA* p¦Óyĺ/XűfŰĎOŽ©˛ô©Dz{ČŁĆüÝ­ž[ †ĂoQ!{ÎB4—ą7 Ě_xfŢ ‹·{k°Ăő‘ďҲt3ď©Ö™ ň÷ńîÇEzúĺˇß.ÇnŃ”Rű$k{Ťç|Č@E±Đ`#‡úőJńŹb"XJ°[?P‹m (îŕUůŐűq YÇ˝á}0HÚGáŃÎj]5˙IᏇóT¤ĐŚ5ĚŻĚš¸c•ŇöE[PŠ”§;€í¤bń8ćŢ” č Z ,ĂRĆt)wJlň(cň‘¬kcë$Ĺ Ů wřŕ\컎:—C-ë(•äF$? »Î­PĘ?`€WdČ[?„“ÎjőDĂŹŞ:Ŕ/„ż”ćĆ}'íAZ HEnń÷učŕíŃĹ%ĄLyž ®IRIţ\jMË!K'ŕNHÔĘp<:¨ÚBę١,ŵJ±ůíyc@gîA“%ʇ}GŰĎĺeČ 'ľ=ô0;H_(âm[ˇ ˘ŕÚl4†ńîš$Â0´ –)“b>ß8Cʍô*¨kć*Ć q©şáKNQ | ˙_YŢÝí§ĚÚĄ'ř·uf>sźÚć÷lCuDÓGřŘ0ş+ mây@~N€źđęçü,á§ĹŮJiiEž†yĺöˇ~lÎKRŃţ ô/ŕ´Šú%~|$Ů‚Ł*% dŢQÚlÁ;&‹ČőŹ:¶x©]eSuTڧyĺĺű|=ĘPß«­KVJ„2¶Aθńé Ŕ"Đ0šçQ$†09P|ëc9Ë=ac0*śę„€€ĆOÚ‰Ŕ3Hgp4GV’ź_ëjÚ˘—YtđĆ}B e•‹W46Ů—®łcnŻ&ĎČőŕ!×É-â(—îWgĎÉýÖ‡ÇícÎđ8 b•ď\—‹ő•k^ťĆĽÂüYg’ȇŐ=őÁ`(*EŃŚ÷‚Čx‚6—~Δ"/ęćŔíĆ'čČ]sŕ1aŤp÷ńß&^ű4—nÝšř=ín3(äťĘGě3=`¨8vÍ› śÚÄ{ű.&žë……°q™ÇĚŘ˝b—™ ‡‹ť„]8î˝˝±o%‡¦÷â9öů†„[j©@Č/ŻC 8#|»Ľ˝Ůuďj!ćř °;ó¸Ü,˘ÝúhâĐ ýnq)‡ ˝ ¬Uńě j*ęśů‚Qýđ*ö=’ÖéËűC˙~Öť–…ć$'óMÂ%»‰jôVj6ş°‰®ůͦ ®ĺÉ^4‡t@!x‡a¶¸g»,×č5NŻeěüyq×Fdű‹Ň3B¶,¬u¤iMúSô.šĺ˝RúQ«DQih–KĐXđtćo1 ©Ĺ´Ťř,ű—‘t75!Üö€"*XJy·haĂ‘kT€źë\#X§ÎzFÎĘ,Â:ţ‘ÁŹł|¶’G~ĚSŰ4ęóB˛Oqq&ŃÂÇň-etŠ.±ţĦñoQ˘O’OQ“ 2[4ą`+§&Č„Îwl'@C[7 ™˙Ú¶ÇýÔM5qóÓwLÔţŔ$H[>[ă¦InD+µ%,Ŕµä{q™2k{Şsľ ł¦-E´Á·"Xi!ß×~¶—%CĎNçęy<•&{ÇŹ•˝Oę­ż+Śę; ±r¤Şô}˘P/¬éFO°Éż[IZđľxëý-bĂóĚe_P#ęő–i ÁŠßÚ}ü‡®ą™T’íf#fŢđąĆĽ+ž ”ŇŻ¶â F6R!ŹDâşPV"Ť4ľćuëX٤p•eÎ)ŻVyŰiµ4@ SŁ Č\Éý+Ô<’duŘţť ľmŔUiu´"O„Bl‡¬¤÷UwY?p´[č ŐŹÄs"äIFš"ÂV㌒Á—ŽZCóŤ ˇ‡’łĐ|Ë<{µ‰=PD÷6Ó4§ür-ör!-«ţ$ĽĚ5M× ´"¸[KÚ¨!/–6«qÔ!ń—»˝-Ő˘…~čĂh TKÔÇĹÁŁR†ŠŢµŞSÜČr†ô¦}Őv×fGY– ľťŞ?1Ľ™h“™]~f)gkĺ×ňĹ%™;Ó•#E Y˘đhPÚ ć®ŁľÔä 븆Ů<–ŕÖ_Ó˘PČşŠOü¬;Ç7°¶FC±«łßôm‡Ymľmj@ĆĆť:ß§<5âSyÓ ÓßYÝęňŠL2ĺĎíŤ42%}M15o˙‚Ů”î$Í2/íPú˙‰ý:(,…ÇG…J@ČTŤőË"î~P¤I…jşŮd«úY×6ÔŽu0Ľ•fß‚EtUvÎä ž>ü°Ű Y/äl=Ő•I6nĎe\ćޱ_t ‘éŢlˇPä–źWŃ_cTeÓ&>Î¤ŞŞ⌢Â+Ń$FśAxt*@\ J- bmz`÷9LKŹşô¬ş°ź×çH7ď>íĚ 0¤Ö×1 M)he ŤÔŮET€‡únÎ5BÚۨ˪ĘÎĆ<+Hťl>^žŮśęYŤvł_ GK…˙߲·¸kfO3ďqLÖ‡ĘÓ s–“„:dúLŞš'şíá‹Ĺť†÷ GgNkkw…b­Ě+€>–]ďŹ0ÄMzšŁ˛+†âł[ZÉĽ˝¤fŞćÝi`¸ßťś+›Ułđ˘Đ.…V¬G?Ö˝~) aŘô’U@ŚëžP‡™’p­Ô¨)Ą6đQyľÇŢMĄŽŔË .))â.âűBfß5ěBIOź5nnµ"®36W ŁÄ)A†”VĂX5ařßĘ“ŞOżN—Ş>eˇ­Ň¸˙şw›ôp2OYĄĆţ®«EK4–R.Ľ§ ÜŕĚ÷¬¦ŕ…Î úşI\Ë™…Ďz,őPCEŘÚz,7H‘^‚ă1×™/ůf÷ňôĎ+~CD •˛Ž¦F# jżv¨©Úi ůuź÷<©†9ăĂ ¶÷ť”«zÍć"‰4k*¸˘ęő’Ťn&FńOĎ€^¤N 2ű%e—ĺ\…}XÇníełb;‹ŹÂŕ—mkoľ‚U©ŠZ*DcŽN WŢ.>:DP)c>ăsz’Âň×Â…©•<Íę8|F÷y˘l„éľžP—„¤˙äŮeAh®íÉgB ̆¦ʇŹ(Lĺp{°äTćµ;Ůsá§]`VŔ´żˇ˘qĎ;nŮQ>ëQeđKspÍë÷C›:|eg FçŠŃĘ•Fk~9™ô­hĄ((Ol®Áa0hĎcń#H…ÉŰŁN>dÎŞ,}ÄČ?'Ó—LĂAH‰o…nÇŹ±V‰hç†Éo'${Ă5Ô:ů¸ذź˙k˙dGřl¦EP8.‡ťďm\řLŔIÜ7PLuľŢ¢;–Üş“&t`ëę ±Ă˘”şŐ¨Güc>Úµ>ôVY6´D¶RĦ70S¶®C>™‡®"ŃŁčޤĄ93ŘćÎŻ_‡8Ěń+…ż^ĂPŻšWű;”+çşȡPÁa-gRxřé×±Q?4ž÷ŁŘ Şz%Ę)łr¨dűor€*FŰýłD4°ĽŻRËX§''ůyôĂ ?ÝěZ×u@*áGšGÁ>ńžŮO|%@”ZýéË;!H±Ń6bF÷ŹbŚ“aŹ/P.‚ź…n<ŚŽ= Yůz^ŰŞŻŢŃ”†.T‹[sÁě§â?=΢feď„ivs5X‘bG©Z!čŚR V-…Ţ•6@YďŔ-:Ż ·ů÷M†whuʤŽéőRҨGńOÜĘŚ»ÜĆ9)Ă+Ý‚e„žľ@âÖ‰űî»f|’‚ÁŹ~‘ĄkîŮćw[é´‘wöŕxä8-Ž´˙sá@(\™(ălAs ł°ĄńĚާ.ˇku­Iž“p O•EÝ5Ż”4D=ŽévúŃľ˛7̢śĄ)cúţ°hżNsĎ‚’˙Ý'"pí¶zß·‰ uIŻOŹ-2=5yĂŹŕ_;őë;ąţ•™©ôŚEĽ¸”Ë·#ňh'u˙-ČĆžŇüČ*†9rYmS|ۧ$¸TFlűFŁ­ţňs¤şóťo5á©eśMjž—R“¶Â˙á˙řĘ QzÚ1;iĄé»Č,tǬ. ĂîÜ”[nÉOä»Go g$-AJÉöŚÇŤ Vbş›ÍĆă¬ÝČĐÔäĎ'D^°eśÜžűG ÂᏓ3L2ĘŹqö„-BµĆDe^@A #Č­va›ńń ÔWęlrpĄŻ&TŽÚ/Ö4ŇČđ+cUÉŹěǰ Ż18{öľ…„!Đ»máč}ŰMéZď™TžEdŠ Z(‰mhťŐ¤l(—ďgű"0OEY’ĽµŤ"%Žď§5´Pś.Ůň¦/O“ŔVŐk,´"µĚMȬ·>l‘Ć+Ńd¦۲„ޱqm<ŻßpŁS´i¬†çP5A葉bĂ3ßZ˝ďţQŤľúčh)ŤĽĚŤÁUşń«ŇůIŹ,K«ŮľŹFą¬ťÝ¤~g`?’…Üh†°ÄŃZjBÉŢÁhÚaĘůDŽçČ®ó=QHÇŃőűĹąÝ1\«ýA:Â#®GÇ—¬<4űďü7R{7çF~eܨ8‚ďň2•Ç@ ޚ|„/¦"śĐÍ$…+—R¤‡ kŃĘ;>ĆH‡%Ă$ď5©[ßpŐOüv|·f‰EżÎÇtŽV+ëbeËŞHŻěÝÝGČsú;ţ­‡[ÚcŃ{S§%›ëěĐ%éšž3 ‚'d,îHfCé=džŽĎS·TĆÓß‚XěPG,ʱdh›ţFJ]őiľAÍg–˛âŤ7&*`ĽĂzÁ`ôzięĎo ~ËUâĄîSŐ‡HťVďÔó“Áń" ˛ha^Ż×ţĐŁw“~ôÁźI¶ OŰĆSwöŚě.Ń=î­F.źG˙Ä1oˇHě5Đöľą;nÁU;˘†Ó¤¦ăŇó§Zń<”ÉIhé„cRťf­‰*ešń-FâčŕÖiJ' ŔóŻ&,Ł‹Y¸‰–“&ˇ!Aő»ŮÁëóÖ›ŘTăâzR͇¸ŢHE®0ZÔu·ÍQ;Ćý—PäŠ˙u´¦ŃW« _řGşöuµĎöY»Ľr"‡RCíHL}“şV]d+!Ý.•ĹsąŞuQĺ5µW…äq7;ůFXQîđ,W˛?ŢÄěw·ÚH‹ĹAé<€ZźŮºЙ‚Ł »ĚżîI"AřR^Í'ŕe@f!°~sĐöů‘úÉ-™o’Ž9ŢZ™Š?ťí"ŤŘ4(÷zͬB°ężWÇŞâ#=Ďš —2ŘęđPTËčŤęx7Ułbl§8ȽͺR’ăZÖ…Łľ”ۤYŤőN¸ş—µ^Ń–jď˝'ú&W‘9,Y‚ąÖÉLT×ČAź·0čŽF uzÜŁ†ěJěy!ľ–â:ُiDYÁĽUţşHíN, z]ŢčŚ yV¬iĎŮŞ ™Đxb\|jü÷C`ßů+¨J›iKŰ4/äZ^!łŢĹRí‚ŐóT ét ÉéÁ˝·[}8t¦Ď޶h44}ş°@{¨E^Ťł°&čz-̡äŐ^Ů0JB#Í>‡¤olëűQ~ ťS´¦ŔsĄ”“źëcÎQo ś‘5ŔĹ,ą~{ęNś†‚qž§•PÖäć]Ŕr+UJDÔ~­ •\ú3ëë׀ϻž5ěü×.Z· îwsÍ{±±¬îÖG$ˇáĚIZpđňŔŔ6ÜJřI +öh •kdMHĺ›ţ°ÂAöc Ťj ÓK<]˙§Îç±Wxb4O¢đˇ"NŁâ÷?d\ĂĚćJţ×ői,uč!óżňMý sNV®É- F4k·ÓšďI”Ž<>˛:+X=ŚlÜ8ą‰iB#`/ŁÜË'5é÷ňŠń<;şţ}(ëŮ yÔÂŽp]ă|’µ’$řjqúÇđ¬…ó¨˛u0!¤< 5X‹ž Sˇvf¸…ů˛_›W€ łŕ[C)µ˙żĽü[«:…MW?*ťÇrüŰ)j‘ČIßž“„\dąy}Úę¤âVýć­ ä0bĂŇËë*SŁéŠÎóŚő„껾kŤ{övRS´^ófÉá†avY;ú"řqĆ~E¬ őśô:ž_ĂYťÔ€ZÚ­ßDđgĄ4C“$üSp­JSÖQđĘ÷¬©XN ~ĆJďŰp¶[řŢv{\Q"ĘíkˇgóŹ«ÂćyŘĐ`íɤťÎRdQV—™­~ď]6Ż2Ă9¬ábrü¨ąQýGIšŃD—ŐÎwň—ć”4}Ú]n',ĽŚ”>NaŇl¸¬1źńsÍńqâ Ťi:[ćAúĘ “Ů”µ.x[áüTúN0çF˙‰ ˝ŕ°đ†)XXN=\szS”9Ě.SNď꫞b=OvŻłÇt·GiÍ{ŽX_î¶r)jJe& wP=ž´{/,Pňf g¶łíMt‰jD L  0\ ±˛[űcđ¸3YÜ0˘QJń WÇ@iJ…UĂ@źy¦úąU=ĎkŇŰU ´şµ‡ťËŇ›©ÔşĆuÓÎhup@}ąX,Ż•ţudŞXâ±ŕ˘Çó¦Ëݢ‹Ů໕ŽŐ9®›ž|JŔ&ü â+ŃNR. ´¨Ä߼ŻÍ­űô7ľFo7kzńJ¤P]V‡‹ÔÉĄ€´„}–ĘQ‰ťC‹Ş;õjżźßHG̦ŞfD>Č#¦›'=¤)z^ü´_‘š·Ń´86ůMÜ´„I‰'p6Ł"Ú±‘»Dß,¤_ G†¸n90kß>7ŔŞy™Kx˙ ,šě$“%as3Č'}˛}úmůQű–цh¬XŘ/PŇS6X_}@ézzȸrx“„ŕÂzYś{ĄUÇéŚ#…ů´ąż#7‰kBŠ`/•›rcrá˝t„*n” Ů*Ú¤=,-OÔkÉ -U;ĹAACµ~]đôĺ«˝Ćß,‚$$“Nv+çMFÝ<°ŐĄç»l»L$ýŽeřD·yĆ+›+o/RĽ"5nëí›ŘIfj úô–Y»9KĆ9üu/}SłĽ·\vşăSlťń҇ňô˝Śrčŕ©Vâs Ż•ü¦ÄŁxĘyKcmX^iś;Rž®ŁŐŃ1Ę?i&UĘtł~î,~6™Ëq;2iúňň]ČeÂXL˘Ř'/_b`Ť~x4îö#ź`vvŕ:˛}o ˇżg•·•UMĺńf=2Îś[íö«ŽĽ!·€ť¤C.FUřŚ@vě"ř]żcýAďżlň÷ŘňŘxiuú¬÷ۦ˛TÇÇ:bŠ<28“ë‚;”écľ˘‘Ëő űńÍWä"éŢăÉéw‰@±ô…˘Y~c–ĽŔ‚ÁÜĹßw±•/¨(–s|Ś3©ý9ô2hčøřu9*ř»^7Ł“lÇň3äΩ»LÓéO ·Ë­źQěp«<ŘúČÖ3Ĺ‚CŚŕ˝ŻĽ8Ôť˝Ś!o¦žąÖöUJ6ÍWM­ÝńźMżŹí1ż­ż×řÄ’/ dŽĆŮ KÓŻxH“ĽŔýhD¦#EmX*Ąĺ{APtŘüd–®iŤcřq+Ô+ČpHűżáÚ¶ˇ<÷ČňFÇ{ÎČŽPç»…Ů 7ůNĐŘ—š& Ś\·˝ĺ„+ĄhzzU4E/CtÔ3Hw%>/QH Ě3›–‹44#ĘX>P´‹Ą›_™›©‡ĽĄV1Ÿ- ŠŻ5l­#—ŽD]©—GqŽőµŃHA˝ë2ć«lȤşń=ËOIG‹¨ôăZĚtŢŔ¬!čjéÄĄ:ęÉͱ-ęĎĺQ ’|j ČäŘ´TżÂ >ôKđ`íci!Ćj =g«Ł·6Ábí—=GC ^ň…#ôőâđNamĺ}@ľäÍw†W^|z<ߎ5ý­ßc¸Űp¬—Ů!Äâᙜţl!:śźţąxÝÔznŰh®şv"dLĎţŃłRZňtą·!­ŘŔ6ż5N>.dDap 5˙#8i̤†JÓvăÔŕpčąŮúÔö‚C;¬;Ą2řé®ŐŻ5ź,żWě ąąÎ*ů çK2×ćľőě;#6ÝE.Ś»7CźoŽ2Ű!bĽçÝⲗď+3U(u[ńożłsŔ9ˬ(!&;`Š?O*%®L śŰRŘy"Ö™µSRŔ<ßŐŰ7P5(ĂÄ…jµ’Ę›ĺďw ž«ˇ=÷.dń÷ř-:IUÄęgň´ż:5ŕóEYć$QEĎřç9gŕŘă0}¦ůŚĆM<ëe Ô1ÉĆŤŮf𓝕é“9AĆş“۰~3ŔEůŢk°śqXCÂ1ôĐ™ţ» žl?‚pŠ@*j,řÇĺĹĎÎ87 ;-C|mâLgÁ[1]¦T).ĐőÜ!‘íŮüÁZm®sAIŠR*gŃíťŢź ăâ ÚúŐ Ŕ]ľ¨6üa¬¶Řý’׬›9­î‰¬_$žë^ ßŕŕë3T(@Ězć $¨eŕ‘Hݬ¶Ŕ¤rĹ~"DćěČN]§c;Šč¬Đ)ĺ‚©·dµirÉů›Ięţ H•$ď™Ck%ý\đŤP4[OŠmËÎżŹţĺD$n -‚żJMőő+ ź(xđyĹ:\™^ÚX;2´ĺ<ü9¤×× ČBÍŮŁ)Üjlě(Ąá[l ˝%đôĹD1r’Ďää ţ^kЧ'8Đ#€­Ď8«@jůuYŔµ®|4A—ö6PĹîqP Ë‚GnL|‚×"ÖŞ[ćŁĹd#Rk] •­fŤ®fzÎh¨Âj«µ¨{řYŔ"˛¬’DűČç’ó:iÜßY˛»qF˛mĚömXď§d_înŁO©ľzo\á]{¸ŞÂk^ŠM’†¦(XS-áąâT*]&G^ҧmJFýŤ§×€˛Ö~e^\8K„f×”‡Š-n [~˘ôáLQĽ'jÝşŞw§nĹË;¬V’±KTdóŐX9 ŹHŕą[x·ßÓÂáÜ#ÇxDóŢ Çx…ź´5ř˙¨˙rý=\B[™ŰĄ\ÔF6ă×Bߎr1«SVzŔSCŚŽŽ˛ă뤫"¤° Uů”“¶bř';0´8îMśuµ.qőĄň|UŽŤC >V/a“ٵMŻgý[ë˛âš™ľV+N{ś˛¶K8ÄRah™Üŕłm<)Ö)Á’ĺnÍčMĘ©üĚýh-s¦gyOĂ:C9–«~†ŐULĹiĐëÜňC~ʨß^…CsÇ;űź‹8ÇÎČJ‹ŔnŢř˙Ď͑ў ÍňÖ%™ę$\ řŞpŻ©S§¦`řˇü†–L4xřă™—ż,ČL"ú…”Ž.푪ő:RčęfpˇÚÂB!§y°k먽PLÇOU/éx‘Ť‚J›’OúĄ¸§xy¦Ž%Ť*/wŞU‘x¬HpĘŇą>ÎĂ ±A†Î#‚…Ird€^˛$ŇerŠp罥 ¸ä| —\#‡S[éZ(ň®u”kŃńâ[ •;zM€Óßâź/ż“ěXjÚ•4á°†–îđL‰7Dëk|SŢs×_K˘aĽÜn, ¦’G0Č#Q2N}˝ÇRYή¨­đ ý°/đŁź&rQ¤·ŕĽ ˝H7Ç–řlöH•y^ÖťMĐ€Ľ?áKşţÓ˝MsRŃĚßVöG#ÄnţTÂßPćüGj"ö}–źD Ťě2:Ž ,+{Şż:†]^!§D^ru-ÉěŔŘ<ß3«<ű¨ŞŮEá{ĺeK ‘]Q­"żŚFŐ(ŻCTnhÚńŤS6=DAÖĚé^˝DŤě›"WďĐ—íjĐăźç!ő4űGhŔ¨‹¸Tµ˙ć9Źďň ŐÖ´’]T°íPĐĐ”BK¨­+ńĂßo!ÔÁ9?3ľ5~ţѦ㪶ŽE §˝·Řµ‹¦O<7ĽĂZsćLS\şžŢ í®CĄnHvOŞv¶ ü8Ą3ôgD·AyČ .«Ą÷±’Ä’?:ÖŮ©”¶.W,®,'ueíV8J*Đ lŽPUdÄé¤ źř {usIÖrŞŕŁ·Z…ÇŤńµa7€Ô*Ma+yc}bJS˙,ýŇÔśd¦rŢíjKzÄ~ÝŔÔ$ćüŤQŁĆ}¸V•2’WăÄ/Ď—ńČčÔÜëG* ҤpZzÂÄM†(4nöQZř-¤!‘˝6żFáÄďa|Yßž^Úź±`›Ž-‰ _jó~qĄ§^cśĚłá0k…†ŕ±âJ•ůmĘXĄ´S1‰ŁçŻď“S8†Y9,袎®a?ŹŘ‰ÉvçWnŔ[¤„©" a¬łŮkÄ×3[;ůŐ®iLcS~íŻlʧ«RW-ců«ŕŐQ“›n·oöč;ŘčŮěëÜ0”בŇLĚ_˝‡ŇěÖŃ”\%#ǡ©_ V—ńţĎ>1˝·^/žîcńźČĽžß‹á3Řd1’ŚÍĽ–a#&Nă­ŔĹw…Ř'5ř¸-­rQ|q¸ÜÜËBÍjÔJy§{*ě(gŻ8ȤĚň!úřŹřŔ zş=ˇ~?×ׄůo,î—Q;§r”Ë;nŃÚv=fŐoT¶i„#íV9ÓMsę4Ŕˇo^\…ú?Îş­đůřCJÖ]´ˇ®ÜŠto0)§MF`ď— 7O‚6ő˘Q ŕ'â÷BvB(^ĂiÎĐ§Ź–ŽéEpNá`|”ř7‹ÜCˇřŇó›§Ů*ÍQ‹Ż–pŘäćsŢĽ»ĚQ¤Î9KŘŔĎ=b‚,So%hq1V˛°śK (ĘŮŻý1˛&ÉO™YT.{H*dĄ‰¬ WŠę¦µčí7éxáA©FŢ{ŘńX.ścë•ŔOÔ{éiŰ‘jµIl3­.ZBk‚qą‰ťÖ$«^Ô¶±–>+‘MăV^\÷˘gśZśyź,ń˙á%^­VîŰz®HPŁ D 9:á™Lá@j«úgÇě n*— L#DĹeŚh‡ěe‰˘Ľö°KńŚ›ß<׿vŐ\Ó†\ÄÉ 0$Š> D|˘ 2ŇjĹđ¶ęĹ]áXşÍXé‰nŽá¦ÜŁűĆ+€]¶ë†r±^ý‡¤)ú€ş FÎM·Ś°×ËCkÚR¤J<‚¦µń«ĐeŐ÷ůP  &o%‹vëťU®^˙\—¦=|ŕ-3Ý\EK‘Gp÷®gbMA;&ßáP(Q!Ö5‰ĐsMŔÓ^®˛ńWüŐVpg¤R̢×8ž™Ńţć"'?Ň+Všřjă›`Q9ë…TR´>ms=l5µĐŁR Íg7QÉ+Čgw0Ŕ=K°=Á¦>ꓚµEtyoř=„ěbૌ8ůKËD˝gčŞÓ*[«ŘZí"ҡˇ„§˛_q,őm=*ťL‹4 ţ4đ¶EiSĄX`źi<!8=Ž<áű˘Ě \‚‚ĐFKĘĆŢŘ,̬Ԝ”ÝE`>Cą=Ž5)’čz-,))×ËqwĆ]¶ę3Ŕú’Cń+´|Ą?ďŃŻÝ!™!Ý5,âJ$j’€Š.˙y|¬çž¦6Ý‹”4xAô„ŹçĚ‚N)8L,ɢ¶ÓŇ[ftŢáë˘Ňo"y)LtPX÷[1ubŚWĽ šůa˘–š˛Đ}´uüČzt7*Ň*”O!†{7=5`»‹v(cp­:pʸnş®˘/qsxO\OĐŻ’ç¶_ Đ‚)…W5;±ŇđĂW}=˝ˇĄ8$'Ó9›Byĺx —űNTpkń #Z˙4S¦u+äGéł;:ťž>JHJ0®P4Q˙ĬcŁvřłÜ ÖÇí« ›pĆPą„aĐ;LŽšžvKÉÇĺz°’µQ2g,‹Ř©!X©Đ)XŇfg˝óžZ°ŚvÝL@đ¤3;¸§šš-=)ů ˙Ȃٯ[űBţ ŔÇń6 ÖŮý;čPżÚ‹DqÖD­űŃŹ{[lyŽ[čą^ţ:Ş_CU_˝7ÎĆLą&Z«8CĐ˝–Ň-ńĎńćęĘDůĺ¸ő2#˝ěŐGę‰cőňŢqˇ˙u`÷©hĽŕŹáçô~˝pîhäáĽŃ[#˛ 'µŠčVËŰ ŞĄDjÖ/bÔE0Ü?;—kNîfĺ{řvůŚVą?`C>‘xçqC˘Ô ä»PIďp^‰†ľZ5sc-ÖqÍú“¤’‡Ó®c^ÝŞäŽĂáŮV6A™˝ú ´®.'Ił`ŁŢ/v“7ČPÂXČşSŽ’im4‚yqŚJcĄĹŁÔ(ř°o˘¨5&ůĹ ;{Í!zĘĐ«YŘĆĂÇżAçL%ěőu†@ý–’˘}Iޏ'šĄóŕ¸m"B;~­¶çsđ7=FÓ(RžŢžX‘~őżXęµôrŁč‚ĺ Ťz…ű_4ˇŔřő,Ús(¸T¶VŇ!§ë™ŕÂSDÄűÂ’_·Ď)€Űg×¶B×kĽD\I3yĆŁš Čë§“Ş·wŕ/ú#Ŕ0$Ż U/öÂó*?Ń1fAÖ?OFá†HŇěŞŮ,“ź¦wÁH_´Áţ&ržóqWz ,KŰç (©l¬Î[K_VůňťV2jh}&¨/·&žYZý¸bŮ"ʧë"ř?)¦„U¤y˝|ŘřAďKFŃESÖŔDCÁ©ł{;‘بYž\čň\ęYŞ×—"Í`D—5˝IIŮ5ĐůíŢYíďřJfkÎÓĚs͵ÔfWŕWŮ!t)ó™×8/q|f¬51¶Ť€xS’?íΨ °şb|Lla‰CôáDV×W ‡â]"ŘQ!Ľ^-ŰUC ‚enôˇ$[˙ŘžÔ†şÚËë}ť8W&ŇĎ/{ˇÜţ;ŃS˺ǶtV&xĽ=’ŚNţ(_<VZłËUˇí>čňň%j㯊”F#×C ŹDuĹY”"ÂA2ú‡Tç<®řYřčą)Đhć¶ÚTyŘ)ý!rŐ8Ś(eKŹV›ŁFŻý‹µĎ7`žˇ«G”_Iř}B QŃŽŇJB7ĘŘoŘ'tâ]‚'§ëŽŃ°ŐąqĹD+źšŞQĎBrUËŰ5n§Z¬ÍÁ=¸}Véţ)Ŕ}íąžť#YéĄŰTŕŁÄldnkÝ˙2=(q‹5uĽž&ťvx…Lű~HQćí‹f4JFŕ iéµ*Ľ™ŽŠK⑝ 1ëŤa ;ânNćˇÂ.VWŰŠ=D®şţĺŁ'¤Vů Cd(ŽT-ăôc1ôÖźß—»‡ĘŘü”;ń!*Ň3H+±’¸Z¨Xs¶>ÔµaćęÖŇ=[äG )żĹÖ«˙¶žŮÓ ç¶oŔ jUűÓ•Ľ•őíř’Óő«|4żĘ<2WlÁźuő†&é°Ń‘ßZR•«Ř'E!î‹héĘ(ŞKójs0Ě‹ö]sĎ[Afô“!OšČGĚ̦pâš°Üç/›‹¶+q6c|Ęĺ| 0Ń’$TŐ®ĚeiBä~ÇđÝmkŤLOSý5îĚsşäędú§ĂĚŮnţ˝3ŠA!i†.{qZ¶°8prĹB ôϲ›Ý: $«–€·öß®~β»ZĂ`ߍC›zˇŐÔqŢYÄ“^ŕÓCţăˇ%Ł6ZçúńŚŢń­ş§˛ÓÄÇ1LnI'Ö2,m(äi˙uŢÖX‰˙Ű}'‘Ť%1[v., ÝÇx˘´ŇłÓPť®8‰Şý0'âłc9qâ¦ŽŁµMěŁ-ć8ÎJO7C•…ąvĚľb›MżéhĚő,ł~UĽR«}•xć >ŰČ”ĆSŘ8k$Uţ'° Fţeꀆř’^ö9żhűFé*Ô Yş˘—,÷¦úĆá,\Fľż(µĹŮĆ^z‘XT÷ŕúÓEˇdeŹůP§ź˘ߌŘ/(%¬‰@a#í\•čŠäËŰgÝ›č\ŁZ1ř#’ôw0í;)?‡ŃĂT!*2Ö­j”¨â 㯼Ć( đņŹX·ä DŠöŢűE$|j×öČśvL‘ł¨ Ą@š÷oOŠĎ¶»dčÝ'÷$šcr1}ŽŞ$Id˙ĺ»ůA×8Î`›™3?#&µO߲Đ1őšu¸ěšŃ2ĂŕäŹtąUN čyŃ Č(ˇ)n˘VC^(×L?:ő]QŔĐbžß%Íté}‚É·ű<´®ŘéQĆňJř\TKćܶ8§`oĎę@3ŞÜÔ†_dÂxŐ!‹–f(¶éčjSńęÍŃš=‰+A?0fŁ4!uťĺ`őu oź€g:‡Sů×1‹wtrë–mČĘ«â¦ÓĚQň׸Ódŕj2·GÂň0@2n]AkŤŮڏł¬E…›c"ď<„aĎx§Ö™*xA°+U»nípo6¤ľ5±b4ýďt …qĚF~OäżŐł[ßm ŰŁ3Xď脬 ř›ěUŰסpjk%úľyâ|ŰM„Űř„WÔž˙aĹqĺ0KMě !»\ŽôęíOĄ!Sľ×4ÔńˇšŻĄ` ×%3÷#‰ILŁuŢÚbqĽęđ‡TčÂ?QóÝ ü«±săő•/SťD’0¦•Ě80fcžĽő.ĺşx4˘ Śły—€Ť%+™ Ż#TâúßU_8fɸ\Ô7Áöe&öôŔęe"•ąčŢDZXUó|ť‡mr-!P„Ž ŽťŮ[ü™ ZČý‹˙Ĺ%j şßDĽJc™±˝ µŔJ;Ô÷R=f./`Q’’Ŕ+wáu™ôŇNNlIUXpX:˛Š#2–ňX|ĎâV`ú~L™sÄöXQâOşť.Ž–xTó¨xs4§aU5SÔŹ0ŃŢţ•Kôˇž€[ĹZ×o»č„ń=‚Üî$÷3´^ÄG^ő§3d€v)ĚBËţ>x"¨ž¨¦í–ĚF˘7ŢšÂHzŘRQî{uîÎ%–[ú‹˘_ó„ A0tL ^«ŃôXj8óŚZ(/C4ŠvjËü‹Ë ›wÝ^É\ďX­_†©%Í^ ü‹KćÁMsč™Ă[pz›rŘ”ĐM6A€¤Z·@C ĎŔ˛l_»;t¶ŞśĄ›ňXiz¶ł”|•V,Ů—˙ÂDEáb+L{íÎ*…‚i3@–D/›ÖŐĺ˝ë&>ó>|0˝sŢ RĹ”ÁgßCčBř8m'Á Z°M×1Ü•RÁ‘›ńZ?¤ośAmeţuînvô“.Äú)âVXż#ŚČ›pOť_U†Ĺ:uGŮ6žwWľŰţn Őyô·Ţ¤/€ńâY©Ŕ9 îç„© í _ ܆Ă&íő>üb)0Opr:Ó(;ţÉ|‹rŘ)>Śô!'ŻŃí­†ß.pkM@dWřj5V§uÚśrĂúGĆĂ–ä9ŕOXŘĚ“•3)ŐŚ! ü4qů°VŠÂἼ?¤T*gňpnoÖź›ńůĄl~hFŢoÜ:\Ú’LËŔ~†ŰfćŔůËGDbxĺŃé3Ě{‰Ĺµ§ńoĚ fwÓŠ,`=ŃöfőNľ'áz¬\°­K5„QXHŽ EHW ĄŢř» UVŚÁ?ÂS¶tś˘ýFŠ…ĹÎR\‡‰Ä6uĚ™JěU+”8 X`AóÄÝzAŹ™•D›ő č (F¤¦­Űł‰¨˛ćČşK”3*ăş:{YhW/ť·^ˇD@,pĺ^ť-¨ÂâÝvÜVţ«ŰmĚł Éb7ĺ`—"őžx§ ÁŃ×vIđ@ |ó5Ą_(ęÜnĺëlq°?ă6MńžčҸďŘ«o•©eĎňáŞy)’]¸xĂĹ żhŐ×5ôç#ÝRMUR7îTD …ŞwP@ Ę»’!&8;Rّ֡=@„S˛÷ĽqřňŤUVôlŢ#JÉpY6Ë`5ÄL©§ţ‹¨ś°ôŰ!gCąčaą›ąľPA*mGhjÝ÷Ž-ŽbH$Ü..žb®‚äýfĎ´ČĐ…qďLň±ţͶ*`2>EÔĐt‘rbqO VnŚkeIPÍ2öxşLŞ=„óüm—oë„¶Úl§Ő3‰ŇBnÝižfż©ŞčÍt/yěŠLŔGăÄĐmč‘íR±ź˝5©’Ő! j>$öQ•Ć2iˇľęLłŕµ@6DEÍ~ŰquÔůŚ$ć¤3sF†JśPL†×¶Ą‡“’B]hx}Ťá*+-ţŚKm°É°®\ĹHZÉ»IăZí¬fT‡5ÓťăšőxÁ,aOúŁ,2ťřĹż[/J÷A™éĽřž Xě ›U dÝ˝šôIÁôµ'RGXlď¦}1J†|GmPD{t%e‘Ňbe/Eµ†Îő@,Qi0ŕ<ď%łE‡'ut7®c îńŤZśîÝ!Űr/lWŻÂv$ĺ@(FíůüLň[Ň’ Na™–íF5‡R8ů{:›Ę}er¤038ÜýĄßĘě ‹WPgÇź‡n˙9QśßúÁO{'#Ř]\X8”'¬[}´›H`Tt.ÄádÂśb÷bŁ ďŠ®‰¨ThGZY›]Dě÷5ö¨÷9âkÚ»!|źľűăÄěOD#ÚŻ<>×0%Ô!]d*Ąw)ËÍŽěźP‰(V™d’ZůÚ_ŤS˛Ňş˝ôhŰ ^ š1Ď{¦›ć?ý<ńîč·ů ÎJw«­‹ą6\íŇFlşšxđ]úVŐč”nÂÖ¦âJCm( [!PËň§AěX\w]śÄĽç5ÖXKZ„Ś,y‹*ćÜ฽±[jźŽÜü4KőMübYÓ¸şĆů·§‰üľr|Ú(Çőë‘ďĄ Ô[Na Ać2VŔ'ěá:“R+ú`ś!˘®›+ ň*wéćÍvKŕµQ·CúxĄ4¶1OűÍ‹€VbŻ”†kÚĽ<Ň K“'Ë?˛ą•NłëﺨM= WNR˛g§Ĺ˙řÝYą2Tbúb%RlQÁařš+;ú®Í ʵ2ž “ęA` ?[ŐO®áăA6ĘőVľ˛Îłş{„®c'‚»ű©Š™´Fqq wp•WÖRćtŐJU*5#đgűUŇßw‰Î^®|sć@qŐ¤źŘUĺŢڮކu9=üú®Ö†ěŚ΂¨*ňőúüsFŮꙿ˝›źá勒?bß©"Á‚Q—sp{”#zPţ@.Ó°9*X=VhŐ1 Ć"ö÷…µ»˛+sĂ|śŕÎĹĄ>/ŻÔk é<4óF{¦J!«‚ü„ëďúdý´çÍzȰů3×/::^ß%“´ˇ`\O¬±•Q´kFfÄfyÚ č€Ż%(ăEcź č—Ea5žaĆ÷¨ÓGß§"|çW@Ľő¸GÚ†iúťËżK7 v…ĆĽ‹|ɨď×~Ř~4Qç$jL3–]’çÁňß|.‘Ł¤ÄżŹ%:`őńżČ-żF¨ş… R^6‡ęéw%wܱćÇ9~:Ëč 4:iurT÷|aĐ#ŔÄťÜ;ö¶SlçŮ÷b’7ĺ„Ú“%sÉňĚýë µýs>©ät3•Ť«Y^˝ólŹó,Th=]#‹Ě¸Ogz\ŃصżOŽÖ)˛%ţSwüż!˝‹ [Ł+ĹRŹ»Â–5j4¨ ßö# H‰gm ĹýIxU?$Y™¬!ĆDKĽÇX¦ă”ccđ8t`Ř—Ô^ى™H׳—™Ĺq&üHLJŮżBźö°bî §y[FĹOśW@1jÓżÜIuR¸Bę ¬čŰĆř^”ĽŔgŢţ)*©Ó ňđ:xy©ôX覞SˇP$'2|Ź`+ŕzÍéYÂK‹§·đ.yľq7€ágFó1·±9ç_§çSřÇţZMŢ–Qj–Ř/ŠM ČŻ.¬‹¤žÔţĺš_ëzňŘUTa·ö„~@p€yů ,·ňzţ©‹ť^&"ő˝†H×˙řô ˘%ůGm]‡FËE™^hÁˇzčW’đ¬b—KÚŻ‰®âÔ#ŁRp/ć—2Ë !©‹s –!Y O$ëďLiŘOßůá¦GŰϡµ}ëň˛?7ěĐS3ĺG„@b8˛[*n;ŕ=ˇEžŮ…ăšh`úÍÖŘ(aĄÜNŐFNb°†żl¬ ËčÉŽâë_Ѣ_Ď·SęĄlˇęÁÉŠj´X%+­eTź¤r‘[ ‘ŃT3T/üś™ŇĆKäMäç‘!*´$ąÓs!owćĎŻb–×ÎmĹ׳ bÔýŐ#,PvČ/¤1çźţ‚Ý-^ôŠŠ˙ĽQůÉ0*´2:âá„ í˙řĘ   '#Ö…9ÉK(B|ÚÝĽ¬Űc}´ «¸÷ÚĄÓµśŘ˙Ž+TÝ JŁ™"î’]îiC'ry™śę§<Ş 7ńôSšÍ@—íż’ 5Ä’ŤĹ#Yd}0ŮQ‡ë‹Ő§ĹëŐţ—ĹŰö5ˇ'ŇĹFq¨XÁóěçŤ ÂüśŔB$ľhjÝB„Ţ2ŘŮl3Pô˛‘Tb‰xŇD !´e§ć12áęrP1+HůÁjÖľJ[XĚ™Jme˛üV3çŰLwu<téô‡đH4R>v×”ŕ]ö(¶<Łc"xeÖAy˙2¨ś;»<ĚhŹ-rÖ3ź4\sjP"ů–[}y›µ3DŹą‰÷ˇ˝yW„+ jZżRőájI…Í:`>›G8t-A“ ÷ĹüKTiórß ŔĽ™svĐđüNŐIrxKŔŐÉ‹ę ‰lˇ"÷7ďýZŠŢ˝YŮĚ>íÉ»„ T_ݬµ‰¶>ě/ü¦őJ®§áëćVmüe˘ ć]:ÇÖ`ÎŐĆíuWQđs‚.)ĺ®OÓlÎR : &“ö)”ŕŽŕÄ®'jSóßţzňh ©ŮFšéEŔĽ+EeO'ĆĘ$ęŕŃ›O;ÄS’:mlҬĚ\^Ý˙fRí§CşCŹI«×!á›ZwĚ:+Ă<<ř4*Ű{·€8g;nX×$ąŰiôm¸°dáŮŻ‚@čYć—qŔá_8šÖαF#ŮQ™&é14BBÝ›ę;-”őďńÓ7R÷gŕĂOßćŻŘ{™ä.7mGđdyćâŹ/}”‚{]ńd(äáx@b R4đ™· _ĺ4#Ї˝*lEŰM¬‚×\™â’(oĐ"ŃĹءFGJ8ţ% ™ń¸ô hâŻÉ+pĚmŰTk6ůćV20Ţ-°<Ú§Qu“őŇ*¨˝?8Ěfy6×ÜÎ?qĄcž”i¤_fŹN·¨‘ăMé”¶Ä©Čh߇3f}Ý=هŔ;ŮřÂţ_ëjPš‡Ëv ćÝôđÖ7UCźÔ!pȸK‡Öm ąËö5ńË$Dńá•!LS&đĽi~~NÖ‚{CyCUu&¦×q•;A.0‰R#ˇZ#;±Ş}:{ôĐ5¤›§Ną!śŻŐ*$Oą‹R HKű"= Rků-s€o1¶2>1W<á⛣ITšŘf?řČö#éĹX­yČ]]fÄ-ĘtNQI…Ž„:Ă`l Ş‘kgq•®±đ`[8Ú䂺…傿˝˘DH¶ť<)â íŞ <•nMc[Äéüťů¨ťňˇšĚĺ|JšˇĄ•)ěf…=¤˘čcÖ@JŁ3ĆŢĎěľý¬Î |UnNĽ(ĎŽYqiL§°+#žŚáJfržšE‹C ¸żÂ *^f˝é˙iÎ`ňú«ÔC-ĺ?0Á‰ŠE¤šlKy +ă˙)“s®Ź=2±T•rÉţ™CÄRúŁ š$L>žÍ¸ż¶Ëľ†“Ũ 6‹ô .!’?f•Ń“ÚÝt¤ŘŔŽśtŚNś?ŚśŚVŐZ|˘Ă| ŹČ—%3D;Ça_Fl¶řPEnqŻ‹Ž+韪ŐĚřďŠć,WKÔ­Ę„|dťTD M$˘¨OUIegűY-ź|DÄ"ŃTšŚ? I`…6<ĎĐlŻÁ´4仿µÂ!1 iĺ<ŔĹ˝Py+ë¸Y`LĆ  ť5Ôł8őÚD •µˇé‰üł›â„ĺܦźW`zÝcÜĺ´@”Râظ{ĘpáqtŮÎÍ'=á°‰8ěäˇß |0F¬€Zĺnĺ7X}Ä×˝żrÄ 7‰l_ia”¦đ˛jz§O®uuŽ-sVŕ~ýë!zaĽé>ťçë†ů=fůĂÖĂĚlo«?öbi =pŠŤ2R›gx˘Ž„˘˛pkj«ňöÔ m´¨ĄĽ,Ĺ”ÖstĹ<¬ąĆVh~“µ?fŹYb•™ťĽ8L.÷“1;‹Ý›č˘ öł+‚äč"“*‰”Ą!W^§®Ýęĺ56^PmĘ1ŇâJVĹ8m\CŚÓŐŚ$Ü\Ý †NĆy!Ň~+ gśTo› ÄPRbż03 ›v÷ř_Ç«icˇ)žvëścŘÂĆÚźĽM×arĘ~Z,>}Lş]-}Tű.0úr¬i<¸µŹ•4P[Ę{dk3‡tjdVi8Ü€ĽĎ!_‘,@v':Š‚ŰȨO3tö‚ĐQµŘň—$odőÝőD—Č‹ŹôÄ"u¶„Ź…&ąmÖř°@îJĹÜeľĂ{ŔľéďŮßEĹŹ$rY<– 3÷Ëg@Ť÷ěÁÖY*łľôôSÁŰ 6Ö—ď‚㌋>Ë´ÖyQ{ŕxÉ_IžŮí`f",Î_ čzUŠďY+;ÔßC‹ĂUĺńÎ8Ě7ű¨żůLG/EE}Č&gÇ{]čťşAó̧`{$˘(¦:§I­LkA<ą,b•®ď#,˘©•вyµdúĚJ=§Č€";âëců_¦y;ś[dś:#ŲNKÇŮÝşp‡°ć…űG÷Ău1ťô†´Ë Îţζ˝Śâm+!-Ř©7ÉŹ`¬}Ě€?©őŁQw)=´&ş‡Ű*|č"餡ń›Ć[ţhÓ†;GI W†ÜâiĺÎZĺaîjŰ&’ú–hŁŻřdłFâoô6çJĽ‰Ä.Ţ]ĂävüĎ_‚vâßĚŘ@…!Ô §‹=G w­ÍüšüN$₱Źâ |ź1¤ÂĂOr€r Ü{€ř56ˇ„Ź6toHxi$°~ąţňŮüĘĽ–xhŠOiÖĚ)mżÂźčv˝žCa\őd„ăżTĺžz8»Ną *ż?ĎÖÄo^Vđ©LLaďt’ÁŤ {U•ö`Ńľ|ŽZ=Šá‚ Ş.(.ĚZÝČ+u8d5úACśÉK@©‚9 Šr!ű­íkG!@/ťÉT‘KW•”Í Ů6žók¸ßJ^˘e™Śě i6¬<k˛&JÁú##Ž@ec )Ú¨ć@ĄÇ34ĚťçżĎq<×=Ů›1 Ńž‘ 1‘- #ZظSŁTâkăÓ"ɸTQ‹ˇu=Ö‡ý¤^–ŽS‚i ˛ TĘ[ c5…ŕÔ8¶ŰVRY+.d…1Ďsa6%”íę“*ŔčöcĚwśÉ ·šťčD_ÔĄ÷ź !˝VÔ ĹK5cu†N¸˘śŢ3­®ĆO˘ß–„l:Á š()±˝ź@‰ůć?϶ »HdZÍ|SU_}Ř‹9Ia–ŠóţN ŕqCšöůŮŘí—„‰EżŽvÇ(™ńóKŇ~:Ҭ‚Q”µ[[xŕŘ9ďĄIŢŞ1ą´Ć‹ťĚÇ1|$µ8?ýj1/zäępe»dß˝ĂÍęîěd ý; {ĚŞ¬d4Đ»áS›Ŕ&`›Đ“=Ƕ)šn4 !”Bm¸E,9ďü=ŮOö+zcęP ö\Ě©IŘU¬‘ŽÂş-ň2ň–ż0ż”ťv˛3 {#ŇŃG>.ĐaĽÔ/eŤë“Śł9Q]ó2cÔUYÚ‡8ˇhĂ:źś y_…Čů¸—ŞČz‘tz‡Ăă|âci9:†"Ía}¸“ႬŔekL¦ËĆÂĂűŇhwđŐč&cO µ-‰‹o…U†(f‰gż÷­Ś‘/ĆG@f,Ź#H˘Á 8SžŃ”¨ŠÝc(Kݲ¨¨NôŞŤj6003ŢfWżćŽ©j»˘?(}íĄ—6nú!ó/Ë…Ä5žőĆ}˙§6tŔéÖ ŚĹs°†ŢŽ‡Îłóâ€ahgÚ‹Tťéűn|#% şZŽ ňm¤Ćĺ!¤«O=óĽrŇÖ]d) ^¨ őlJÉ•}ú"Ph”N—éŇíű7ŔPťP–Ŕ|ˇŠ¬gGb„tqZż|o¦DOą”ł—÷G߆pM Qňät{.Ëą4HvÚľŻ·‚˝ď¦óÉFŕŢ"Ú‰nšZ.çÄşł×ÍŘS> (g˙Ď#SíP1·»ĺs[Z?řńeĺ|+‘¨PołŇâ˝®¬÷ZĐ[{;.\ą|í’śě˘r"¨@»#{ëËbl+ŚŇEâ˝ěu!ůć÷č6Üd;¦PzńÓ»?®FČĚ[•ž¦g#ÇŃĂ÷ÉWJkŃ‹iŞ!ŻĐ‰’ ·Ž÷óřqv•ÎVkń;ś(ĺĂ’—GëőŘäí—ęŁOŻ–ďkĆZ&=Ń6(Éę|űsčC{SöYĢë*;ĂY׸OI­YÇ6şLHnj%Îńžwΰé?č6J}ŕëć$0QIŔ­­{9¬,Ć«!.6Lťę}Ř$*d⯣¸Mö¬WtÚ‡ť›öá$ Rm3‰wB3ŚŃPf(ˇgg{Ćg=VĎę€oĂ»˙ßoî;őá8ćVJ†í(sb´6ˇ¶n| «EşôôŹČĆĺ¸|ľR­­{%°™ZO_6†Ň=şÇ3ňíŕvY€)šDÁGőźŤ®´6v‰”HÂwŕ˛ĆŰźÝńíť´ČB~%TZçĆ‘2/+ÚŃWŐKá8¶”K7Š·ëő'Őtľgn]{âÜzZsĂómÇ6=äľńuѸY©Â[^¨–ÖąĚC‰Ó_Şs4Ü9n ă[.ĺ´ĐtĽ‘ß±źď?ą‰É·üKŚŃŽ'eĆĐ>ą¶łgm 2Ţq€él<7”ń„â±^ţ)ĄÖĎ]Ű™ OY÷D‹+^ą…ňńžrĚÖ˘ë~`ěL V®B¬í‰!GvŐĺë/45Ŕ zîóóţNέ1-ţOí$jú/Ym÷ {éW¨ftń”Ţ%ŐÓíµCŻmW}Ű`á7Pšâ§učŇ ”_÷× ß~ĆNAqoʎÝrzęzëő)ăżň/Źuaž)ä)ˇ%2ć ¨ąôÓuIŃ:ň`(ŻoK.a‡Zs”MOFtet @ôŕž]țпC\Ęö řŚ€ąjř şůŇżżPý¬&B ( ~ó]Ŕf˙÷¸Óh3f¨%™‚ Ôőgë0dKŹEř&˝ŘŮamV-ŽŹ´@+łpö?J]vtúEcA©đ:Aő€áđ…_‘lf3·™ń˝Ă%ĺN.Ćռً׀śÚös™łvmp}áě{ĺđőË`3%ÂÜ«61ĘŠ,$‚‘PA+‹»äijł•Äoęf€~›e{É##ßç”&,ł„ĐtNŔ¤wĺ`rj0ö5ŐD”©ť˛˙…–AzË‚FťŢrĎ»&Ž#|Úťtw™˝Ľěş*|k }9^ˇłTľŰ“˘ŔĎŞU|‚ňşóĆ+µ}ŮńÄ‹mŐLDŮ=OĽ7§KSŰiŠŮ}Ž-ŕ$fU´<Ň]S-šxÜĚŮdiŻ÷đ?ÍXß١¬ ×čçŠí!őZ,® $iŽş´ki€ń­"Ö·•Ô<ňŢަq ć©[‰ˇzçů}ؠ˦X“@\~Şë»-"Çöş­+Öµ1±€Ĺˇ9U Ec_ł|ş-XzŹŇP«›€ăš†¨Zö—¶†çOëľÄ/€•!fä’i˘&ó"i݇Ě(ě|,Ńᢂ!f.*°a/xĂĆR÷jďţá'»P¦őĚJ‡x%E=Ś”x„ź«xSöÖÄ—X űT;Ŕ™b?éfćOC~Ĺuü¤ŕĽs_µô+ ¸ű÷šŞuˇtÔhđއ¤ŻĆâŃçâó±6G%WrÄţ%PÝÝđéĘT+­#čŤÇĎR˙¶ý†ľUĎŰŇLd©ĐĽó™]Śń¶ň ‚Č…˝ŮÉIŁŞę2l?îŔť1dťúá¨F€?!„Sż)Ö}D~>‚*e4ĹâůżĎ†7•§ť €EďĺěŻOŞŃĂÁ¬q˝řučTî&ͬbńčUŐAŘ×ĺśzÚdŻ(‘ť!˙Łic4NVIĽ€ŮŢBÂqG_iÚ®ąű7'”tt|˱‚üŐDéA‚§ţŢËĘăcĐ[ †Oů”?&·ßD^bÝÎýŻî’ną$6o!xű†żv†Dń'«îüČNo Ŕ/p—±?Ľň©¦W»tĂÚ×\UOž©™ĐKçžßR2Ť^Hľž7g`Š˙î‹á«pę'$Sv§śPŇđŰ™žhĐzWT„dĹO8}Öa3ć«[ŮŞş{g-KnŰ·^99ŠŰč*ń°™;G[•yM­’©ą‘CčJ–§Y$wěG\žŕ"ÝMEc?rÉůć‚<¸ăä˘vşôĹ2ů¬l˝NĎ}áÁs&Ą ±ô9ZEŔάťÎkpy1fnß•iEwż/!w÷Ö`(X%‘ě%ÓęĺžŃŕ 8”uţ˛1í‰Ó%,t—IČÂůisÓ '˝ż[¨tł˝€ öăܸtçŰ‘üÖ [üě͵ďžĎě¶FSŽ—WćQfŔ“™ŽÍĺFÁîţhi#7ţąşóÉŮ8Ăg Cş éF®‡D)ńůßh5UÎč¬6ľeĂädëÔşdz¨ašÜ?µkhZJŚűąč·’¤ń>WĎĹÄy-äˇęÍ ŘĚhÉ>şńpâ.,fĽŐ‹®.*篯Q´JťP‘1Ö\‰7“ć+;Ăř4.ÎčÁH’+˛Ŕw†ôú§G•bńőť}Ä˝W,ă "ž-q‡ěAőŚ ŚO7ü=&ěŕa›˘—€O7ŻÝtv|Ş_Ęćń’Ĺ5vS>źĐhé~éÝç8¦6R´syÇŹ "Äz2ľ$˘OŐ*OęąöM«\Ş!ďŮą˙¤‡hĹěCĽĆ×GSŻďb•ś[!ÇżŤ|bń§ęĐźÚĐźáúl„z oľHĂ^üˇS|ĆíHV”â‹“° z_ę;đ©ľ>âţnTHřďwf|HÂMÚO šaW@:ăxš )Ýy/UŰiTH3z ŁŐ2cx¬*âੌ›ô h}UűquŁŕŞčmIÓÖŻŤŇ—8®CJ¨ą[ŠÓĘĆ7źQ„ÚžzŞ‚!ž˘ó÷Ő3:D´ÂŹçz˙ĺČčź ±¤ě-[<ßeŢŢ(vYĐ]>ćÍL! -űK-Os( ˝4…ŮA7Źc^K52~r!bP„> çWzÔ}b‹€Îúo9ŕä_«Ąˇr)'Âá)ߦňµ˙Á<˙„tXBćiŔŘşad@¨±j‡DD­w[€…ą ,ýiŞ€ É>ńi2FÁ a(’ 8K!O.,’ŕóTUuAËIÖ@ťbŽH­qE´f8#EOR`jH5źc»¤Ř“QÚďŹÍmö‡aőfফË㵎óWZ6`;ÓgFVč GÉďoąTśŤ÷6ó›ˇ ;ŻfMdE’Tݬf=n¸Ó¬°ŚąKĆA[ě?°B*#:Ł@¨§ŤuJC+÷ÇVŞŞÓŹé–ÝßVsâä5Č€doęŃx=ľŽ7.`ë!Đ<ĽĚÇŬíBÜ—?Ö°01ż,ŇÉ\ $éčZírfŮB†Đ© óĚBN»űą9 úߦh 7iB­Ä˙»Ľí-.Őőđ¬tšŠG¶NŽjR~ą ľOpŠčţ$Ë–šn@9 ćýk ĹÄO4É”ŞŕU—~Á6˘ÖĽl¤o·UDŤ¬%n”«SśŤą^竜 «jZůT•ä|µž‚Á›ÂuL„‹äý?ŁąÔí…§m`«®°D’;‡~â ÝńíĘNŠ,Jú¸ó–ńIś+hް|XÚNeÜkpŇ b&ÁUsJ,ď?ż$|ŹŘľpĹ ś1Ič¬%Cţ0¦ĎľHČl R¶—Ú:Śŕ4óŔŐˇ)DŽŘ4Ď&,ß oâa§×>~í-”ţž™”©ż*Ńo‰IÁ<Äb%÷yőC˝ü/ěŇŁŐ!YßXaďŃ6™žˇ„:Ľ\QÓe„ďZL–<,¸Ź_UŮąfRČ $Ë0yqjá]Ú§z.i$e5“›F]ŚŚ°ČňáYáR÷Ëb‹t¨1 ›O~tv¤đŞ*ăý"÷--1îd†’ĐŕD…ěáŞÄňŇ~oťËŃU1–F¸/5Ľj}Fi&qţ©wI‘Ć…®Š¶LEłoÜůk”¶î&o?řÍÄń•ݰłvč:P 0Đf3„ ôě ’|µF˛Şˇ7ŃS·ŽŤ4ňľŃÂŐ:ÁC|ćµ?˝,^Q"Â*Ł%ÔK &ŰšV–QşŹ}@6Ú˛Ük•¬…Á´•ŮŔuje> Vn¨ŤLł(zŚ“Ől–ń‡MěyB1dŁŚóŘ0I›-GUQ˛¦ŃôÓ˝ĘLÓ@8€5ÔŰé§żuĄĺsNoÁB2µ„‹Qó§ÖW§[ŇZ”ş jR+W;“ÂV—\ř#[ç@ (7y-˛öŕŮ«hę¸q®8I5™´çšOóšŇłz[îÇĂńMĹş0ö/lĐ ú_ˇÉśâ Yŕţ §v5ImçĂwxŘdM$¨‰‚;5Qk+/cˇ,‹ľá«Śj8ÓŇÁÂŘĐĂyŚ X•nmiÇyđ+˛ô´\)<5lüřLd˙MÎ;[;U“¶î×3†Ę„üNŘ…Ţ=ŐDĆNůj.~©pc t,Ś<Ȳ`?IvHpTâ>ĂIÖrLěOţđqőÂ_ty®śDŰłŇ*d‰/î‚)ÂTŤ&…tďííWT@‹áŁŠ^?:çžÄAŃ×TŮ*Ä߆^d‡N7‹¶â?´€¶ş˛Ď&‰XĆ迢BsEO6V_ëzßÖ¤Kş)= ăĚKL2P Ś«{$öp䄡.9h®ˇŮô"čZŁMwrÇţ—7ą‰:ę:ş¶Ż j_Ĺ_*Ő¶fx*hĘ:×?`€ŠçÖŤŻĄyMëkőĆŃAA( ¸@ĚHłá”ôÜ“T´µ5îôäÁPĂűőלÔ4c7ľLkJÜ´ěŐÓ>ěĺĂ۰q¶ &q¶Ő¬Zëě‡ç`‚"ÓXÇH„ ë‚FKŔŮ Ć”îŢĐs "ˇŻé ^˝>Şmëď.łkŠ/f.¬ŤÔ8bâź1qţ1NoŽ5 *ů:Đy@hp¶Hr“÷!ÜĆŕxoÁ<É̵ł1eş¨+wę aË'Á;Üʼnť%a2­F€¬3-ŽëŻě^ň>łĘŮ[> ä˙ł/8ÉăöŽ_ĹČŘÔŚvůăQfD×e‹IŢKE ŞvFą~……ŕr΀Sc•‰e`_°h“Ćca™; Ž#YđS”á%Qm¤! Š.k LcúZ$ŻšŤXď2<3ćĘ’ţ˝‘'žlĹĘ®–·µ Ľ•¤gŢ çqÄ4 »zJ¨mŻ‘tŻ,XľqtK™+Ô䍮ě$ŮĹSë•%ĺ;zë3+k‘ł Í€Ĺ(úÁů^ŞÉ—ř%Řő걄.' PzĚP˛!I?INwăĽŐÉL(¦[0­đF§ä ÁĆL‡ď‡¶.ĆxßĂ’du ă‡Ő^k,1Ţ—4¸ć·gé©(¶®_0~Ř\ů5y‚-ŻKđÖ˙˛Q3ˇ]†±ŚB—UE™ Č“%jéŕRą1©WőĉŔNĺÉ›9ÝŢçŃřßţđ“bţçe~ ”má~±Ž5¸SĎŻ|ET@ŹŠt÷É`ÇŻÖÓë)p•?ĘďłIíč› C4/ý·öűĆâRÁ—Łc©)»°PW°«ý˙÷Ľ^ű›yÎPy‚8ÖrŻ÷”CŻHť)5ř‘5Źeď㼋?R‚ťÎr˘×ż!wśĐüd_÷ÎĄIˇCĂ /hÉT·łŃxXü «±>«ęT’Üvţ_‘e&łCîÝ|9Đě.hŃ×nxfUx€€ű4pŰ@ěB:ć;µc°Ž€Ëe› ń*Ô´żˇŰŃ\ĂßUŮďş´SŐŞNÚL¤±f9·—d’{„›ŠMŻáĐĆe¸Î-%+€ô>Ű&6éúMS55ÔŞDxö[|F&DPß·ö |ÄgúÚ?%3ëSYM/*ĹYÄŠ$łď7ĐĆ @{ýüĺHŢ»¦¦2ćUSě<-ęň{ö˛í‘kĹQď-Yę5ľÚ[v5.ţŠčL#éöw0ĺ“ři&ďŽ9?±G0?¨"Ç8{ăÝ…!ˇetŢ®ž¦„ĄiL‚JęÁsA“ÖŚß©&´ó©ĂĂŢÔ›¤"Ě JzúݸcLIHV?7.>…Ť Ď—®/€Ű[÷îóť,‚§ÄyżŤu]쵤ȑr–Ö˛ăK•âń!ôĚóylÚý˛#™XŇî¬dE›DbÇŠ'ęú'Š!z}ąŠ˘^l—r%Ć R€8 Ë٢‹úBC2$8¬ćšúż7Ű–ě«ÚĹ/| ük•+ôr'0›ż]ĘjÚłťQěÓf¤áăáĘó©`€žPźń%P˙Ż˝‹„Kkr6a˘ĘÔ|¤«¬e‚ŃŞíčÜT¸l2›9 î1zěmyĄg/+|F5´6QýÖp”ÍXţ SŔ•í`÷ľ8śIäë÷ţpęöĹ'Ë&zÖ ¦+TʰG¬Ć5őŘ~S×e0d>Ş÷ůčJqÜßu€ë0I:¬&™h€hWÎîn~ŤNˇľ%ćÓÁXę“’ц7‘V[6ő~?ÔţŞüíĹÝŻźS¸#>¤6 Eë–sŔ šäČ|ĎNô śP©K蹤]*,· !îOĽ9ý‹çŢW§aeECÇEâ<ş–XgĄ7Ĺ {]č•éă5ňC ď\(8•\ţÁODŔpŮ’îťLpk¤ŢgÄÂ@Ó±‘é°A¤Ť, §8˙Ń)‰Ť‹ »˘{™ŹŔŇíyµ~«ŁQ›.ž‚T6ˇěC%íu‹DhYżńLvypZ 6ÍĽXFĆy?řUň<ú"ţĄă•ÖŹx˛ŐlFZ˘ć ôĆOhŔÜü”&„˙­rn·Őü_ü;lHż“«Ň/ߚʏeyż`dďľ÷X…Qljeř©‘8FÜV+}#˝BđöřS×@5cxęsÔŠŢŹy{Nmo'î [aĘ; /Şß8C27něN÷{ĺđj´ď%SF,ţgőô…fÎĐŃęF´ÄŃ7¨Cź¸p´B­Ď;{x*FÇ^nľ9Hłmű¬ şX·źmé:o;f™PmŻ“WD—ĹR¸0îDUrŢÖâČaŁ/ndkíU“ÁŕŢ7 ‹0č> ΔĂ7SQL‹;T"đB+Á– ž¨Ĺ n2«hFq0»ňÝeM\%««®«5¶‘ç”:.q·Š‰PĐÔ_·‡{ë^±ojËca,ě'öţŠu>ôŤ•šk+îf†Á3y(ÓÂŮĎ2/CëŐ^ Ű“í¬9Ýń)Ç}ŢV¨ËÂá2Ä„đŁ,®G†őy˙Éé1…Ë×O”š¸VBŽŔNZ­Moč}äső˛…Č|xŔR+ß``M{4g_–űb¤TM˘uÖîßÉÔ࡯>*śŻdÉ~§:äjwŐÖ#Ö>·ötăx§żě›2ôÜđ?Ý’aŞÍäĘWó@śşŚż¨ÎEˇD¨ÔŐZ,ĺ §dWzCO/Ő™x""đÁĘ%!‰!]Ťö„)C룲¨rCjɦHřT'`ü|Ý%Xâ`­ˇĹ÷m ůĺĆÝľÖJ}âŰ{'2 }˝˝źË/ćÄ…?2ĺšŰ6¨´Ýĺ„KyŐdąL˙päv†çÝŔ«Ű1¦děŰ5Vöś“C`CÚ‚Pś$ç)šVDŕŃ»e¸Aâ:ůŚťŽUfWi!ŇX™}UBb'óŠ}µŞ†Ű†š=­‡ĆײR7Rꇴž@m”YP š3k©J?ČóŽč%»ęíNtá…j:ůžjDg!7$(7©¶›·!V:j+µ¸D#Pu[Ľ„úo^‚oPyĺÜ Ĺ(¶,AÖ¤/…~y~ô¦ŞŞ;ţŚńvMľÔÝ˙d¸…éű¶%g¸}1I_ßb›ńâç=Ď)—ýÉ+ÓwÍÁĽ¨«Î8lA/9„ŮWjZâ÷;öÖ’ľçµlĎěQÎwÚm‘ „ę<*ż÷4W+2D¤ľč´ĎÖrUµs$5ôřĆ»ˇ˝ň*mňÔEď…;%˛Ź;&a°XQPět }VĽ„‹´"±ŚfMµĘőz  ®×¶’őś»Č|·śhüče…bI¨‰C°űŚÁűhŮ&MʸVÍm7Ő¬ .®öaANŰ<ÁqĆGő«Îă@}¨'Ćëë#FĎ|¨”HŢí‹·€9(ôŇ[Cu>"Ů‚ÔĐkl„›Â…7‡ŕ©żmÍŚůf;ÜŞČÍ<'[šlµ7 źĄz.Â^H§ŚZEq± ÇĘ)ęŢJűBďyŽłkŐíEdVţŰnŁg%ń-”5Ę@f >»)C"sűJ‚2őJkÁę‚29ať…ţŃ…ŞŃÔ&1ôô}ĆG{ÉK|±f–ĚĺŚÉ>ťľĄä>’ÚH‚`IÝłÜ?I·˘pL ń˙¸\’wÍÎ…ü釂Uů Ń#Ń"ü©üôѶ/ ;Ŕ]ý_yŠ­žůOWĚЧëŹ&`›_ł´z[őæă¦ŠËą>w2ŇnąVMhźüСČË1h^#!M;eŻĘ{Ë 3“™ËČ:pš,ś&ż.Z¶ eż(5\î4zUÎČ„éK*ŽćŬéŢÁ^ş±zQO ÓY¤v*´3ëýĂţ mW÷_o˛V;̧@§‘´Vş ¨d(•c¤wÇşőŤĎEĺřş–ަ‡ 0Ń‚ěKlÚA^ěŹT.äuôąŮÝĂ·Úˉ˛.´TR÷{ě+´AÄťJN„ZőpJ–ˇőWy0˛·  4Ą»ç»‹÷źNÉWąřËőu9źł(đ ͧ%2ž .iąGlVł]ňdç7,EeÎę¶ĄÉ5µNŮ[›ďü† WŚg·'^?@ŽŽNŮmx’0dł%§™¬c[NÎ˙~Ý.ł“/_۲ĘW†A”:ŮOĝټ Ł«)zJšť˙*eöÔiľ:úGŽľ4&K‚Ĺe¨{i€“Ľúuî3•šHeĹÝáhůš!Źř÷Ö ™ěkô Ö'cqŐ¬BŃžU§«Î-€‰ \îČĄ´ -îíHĄUá[O)ŮŇ,Ý{± /Âm“©4‚˘Ç™'$,X_Ţ«-Š&gŠoó2mdDg’RŘ&Ĺŕďv˛ËŹWâ®˙KÔâ+ćůľs¦ o"ôÝČšĘEŔ®š„ř<}MĚ­ĺ=ČÔÝđ˙3pc?ŘâéITÓ\ĘŃŘ=żO`ÉíÄď6˙Ź2?żÓ3Mŕ9_ĄHňĄĚ›ŢŚrŐYĚúáŮ>FZ¦ŁÍŚăŮ8[éÔ«i›…PŔŔŐkvłZ†“GŻ)|Ő«v?Nű˘cî'‰lýL†ľ’Áµ}†ŠŮ„Wyb|Á:Ô2ůBő·jP9-—fŘ>]k‹y ]=«¶—ĺEňŚů˙b˝ľ‰›žŤb4l0Ŕá™?'öÔkÝíľÇŇ„u†Ţ™ů.iG€vˇ˝_Ň/Ü^\ůn“¤´ qżáâ “ę‹:Pcéź·•NO݇Ä7ęP ĺiYĄM2])ăAďZyÎzËĎîů„Şč:Réö C1Ęź S$ví0ë06_ť+7Ëöa<âݮ緲ö×>…\ V›tŔăŘEPťä 45pq8ŹSF„ĆXG=HŚ řĄőiK đ¤Łě™bđu¶n3®S€Řţ°ë}㎲Z@ég” tXŐWöŕ)ÜĚ®‚ęŻxß ¤řÉ_çŐH*'SÁ÷1dC¸ëXx"ęGzyGTžÖR¦d‡BPPNÓągÂrŃ(ŻE×}ř™ROЍ^‚l›ö8­s_E”pÚ}:߉[7#†Jeç4I`‡Ť˛™ĘĚŃqÔ8IEĂŞe10·ÖÉĹÖČXŃ’KałáId´\ŕíŁ>ůF¦~d>üýúµwëšJě›:ĚŔ^ySy;›¤ţ5_ DéŔŞOňL)ĺqC<@şŕŮŻYÎŕ›f ¸F͡ŤűHŤÄöçj'é>¶ďĚ÷đdg¸Ŕ88ÝM™Zä ÎÚo ëUl¦Ł(ANóŰ0Ł}YŇ ˇ­Ň/{´…8E đ÷yöŕĎ|Pxi”m–+-ę>'»L‰Ü„!1đ`L@]ňŚĂµ/hóĘw8nܵ€¬Eó2ŤĽŚ*±ćăjâĘw˙žŔD2»<1nÄĘͶVNŻ)RýHާ#/AńŹĚE›r}Ľ…±rĚŻŔ]˝ Oŕţş}€*2ĎágP§vˇµ×ţSë÷p°â–ýiѢDnG®ito0¸ÂOˇäV™K(B™'Ű»ÖÉ#éÚdo6ĺFCʦüö~^Í<ć]LVń‰Q­ą°¸x\@Hś° Fě“ug˙‹fFŘ;OH@OůÁŞ~¸Q žFÖTz~A$řS+~»B[¤#lDĚó ˘eŐÔ CjäĚĎ´¨ţŽ…27×YĺY ËÁO˙Á¤ÁŤĐ.Ń@˛üĎeuőăgÚŁžź'zë>HW:UŹF…C>Ófb[U¦<«}Ő{\8‰d©§Ü0xl2VUŽŤ ŚGĎ ßwچ?ötTGô˛ÎNB\ôjż‰čćü©Ú˛^•nOE,Ä^E¦ę}C “ÓÎ9…Ná2ŠPą ^q~źÜđD ¨ţ”ŁÂĺŽËkű4ŕ1|Ş´1Ť,HסK_Ąüáf) Á Ż2k¨ňźČź ř9‚pŢb)î¨ÜÉ®żć'‚ßI,2o†"ôFXł8rŽ˝y™l!Ýí*Ď~˛©\ TFnsőiťÜM=Zw(Ý%Äü>ó)OďC1şC•¦ěM8ĄéçđŐÜlÚŠk¦Yz {čmĐ®öÔ˝O˛x Ż´ŘŠ-ŔNeĚ®8“úzŇj›ÁśT\şÁř7‚*÷ŰѦR´±wÂëS˙v.ĆJVH'ÜFeJrŔ¸Ű'öW˝ŐZIńĆhżKżľÁüÄš$*¬Ä›žťĄIάХłóţÜÉĎ© n¸Ő­•A–±Ű%H,<ę–yU{ś† 9‡•Â뛣‘ë7˝ĚĎ<ĆľáíůŐľ š~MěTQěEBż˛v !~áͱŇv§ëîLáśyĎ.A|ĆjÉŁ•֮Жrh8yę1—±lšˇbň˛řv˙†˙ƲfŤ„»7WŽĚţę S´KÁ\’%Xśg‹orÍĐ·´¬1™ZŮ›!EĚ´Z·ßóÇJMpÄ3"”Ôó‘5÷¬t؆Eź×?\\ůH«Î˘k¤™Nü_ź˘Üf*Źcwę"îMf“s—4sűNěwîH†ËsÜęĐćJf+….kÎoµ°‹TĆĆ, @uÝĆ›Ç×đÝi¸”2«—Ú;Ş‚í7âŠŇfí öPŞ-Ră·TQXý™ëń™n-Náŕ1d¦Öŕ8Ć—-»^éďťťrv)”#Z ĺ˝`Z;ŢwÚ‘~s-2#§(…ťçűřŁŃµĂń“!x®ÎEÖçü kh‹«ęP[„®ęŮ؆ľkű`R´,—E#Ů[r×™$¨ŢČĂ[7͸Ř$Ľ×•,bđŠÄ#Öv+eüĹ_ #·Öë!S)Ş2Îc°śßz*çcĆóŇäá ŁĄŰą5 \ßŐx0…2ńË8şAÁmEž$S&G§đ¶s\úŤD¨odvADâ×g¬Ö–q‹®Lظ(™´Ŕë.Â@FC# tłŻ3Y-GΕ°îm›$řĄWhřżp6XÖJhťN(PošŐXYĺ´ë1bl1˝^ö¬wÔg<BĂ+ř‹ď|q».QşŽ”ů‰Ý›V"zFKŞHuy‹˘®Í# uüř(Ü“cءj¨ˇ¸]űCL/ëö,oý˛‡rÜ(¶śP™A™ [- –ď)tˇ’ Ě“…ŹÔĄŻ€BÉę Ů U•čË-˝ż•łŁ¶I‚Ů_.ćBE*ísE.V¤ű2“ź ν¤­sŰ:nérT7üě3Đ(ĺ6ă˘ĆÓä2ęőôŁ#[ŤL¸ Z&×INü?đţUjΊáZj ”Ó ŽÔoVĎköĘ[QĘ Îfa¸ŘlT°íDü3-Ůę(rz•…vŁ/#Ćľ4+óĂĆÁ3Ž4ˇKuÜ䯮!]w;2% Ů Kn™;ş˛!Ţc=9ől«`shÄ/KřĂ»’g—eŕ ŤýÍŽÄGŠBtŘÚî/\4[(mˇđ‰ś%Â&Žk^‰@÷7mx-BĐU®^3Źą‚Ôaĺ…%€qžg\ ďáDCڍĺ˙;QĂŢČn;=-¬V/IťY’Ń Ę‘Ř°¬Ů;ľU–Ö¨3ŚđÓÓ~ÉÍżs$á,÷MvqmSc˝\˝1š¨ŻSĹă"c˘8O’ô^~]yI=—p>´Ôä¶ßgwfA|·0ż“ű`BP€«_R™1Q€ĎčB®‘ynóu·_łł=rťW Ćî ĎĚƱváŤű±ď™ÄbÄIX”ôîßÔáßÍJVݍS"ĺËłî|'ďŰŤŰJţx1âH4x“iFzĐübúJůD«›xrĺŤ1”š č Ř^H)čAĘět6‰SNÜ÷y¸oŐWD‚˛€mH2€ÄFçeď&%9ńObÍý×.SXÉšżją­ťrÎe0»ř‹c Ë.üDÉP]Śăéó×Č)€ú*Ť”@OL‰ćŠ’l(źÂZ~:h#4–;ˇAĘp=ć2,*&€xűŢ{HÝ$ ss7Şć©ÁŢ»mËTĆŐî  çŤb~őľĂÁ˘ązîŘ+ŤX€ßí®\& şś„Ş€c‡őľ˛éu$7˙ôäZ9ŢŚ5Ú<śË˝ÔŻôüÚ|1źÍÂÓHmlTpak7TřĽŠŮ'M@/ŕn¤_®¶^őmĺÍŚ‹/- w _¶ŞLąJ;˙a˛‰Ş¸ÝÖÚŕ6ůg ŕ¦q˙7˙s±YŤęőćsăP±[á5ÔËÎdMOÝfMşÝ÷>ŘGó®˙Żf=ęA÷ýn•¬ćCM†(Ť˝O”Öîlś¸Ł«ĹBď`‡íXŹ Żu‚űŕßżň`ďůĚé8ĆSŞ5gĘ®l[]˝­ç(ËÂr¸ŔóI\9{&TU¸Ľ ľŠ9iÖ&”ö­ćŮ6ÉŰA-N뛊%Ăőeä3\‹’Ý^ZĚ´ĽóŃyâńýŃŇÁteFVĺ^Ą‹—0őI[feţ…Ő6=ťëłšgÇzűßPGáĹ šÚá‹É“8™ždŢ”™ĐşLË©Ő_ę)Ô-JA÷ď6 n[f@ëR8mŤS¶ßřâĚOţ8˘HÝ–…4±B’ś>zŚőÔŕo?rTÍ˙˝JŞĎY*fÇtKä}WTG2`ÔóWwS±ţ÷K”Ě“cT 苝˙\űŠĽnHżŕË̸bڱ-Lm :_rD?#ź#еŠ Výw“42*ŁńđW("űË×70˝űyéZ·éá2)­úĹ3‚=ąĆ™ĂO?ÓMíoŻk«wݰţ˝%$D6wÍËéjíŮ–›Yż="„5ćĎ©˛Ľ$PÚÁ#yS¤Ó×÷+­ó‚íăŰDÂ]ý7˙Ť¤Ř·˘ Ą8‘®¤8ćŁgeŹîf„/t~\C*Ú˘ŁĐĂĽuŁ2`FčśżçáZŐV<ÚŽF)źçDŤťvĺÁŻ({EËd˘ćsz­§Qµô8-lË™äKGʬ;ęÇUߣÝ|Â+±0łŃ0YÉ’jďüćéR÷|fMľl™W?ďÓ‘~GyB°óŢňýż}!JŕG~]wěäc¦Ŕ†řýĽ#Ůô˝}~?ţĚŤ`°ć)‚ÇÝłRňĐ…§GŮ;‡¨ˇ'Ď‘Ž1mŽoŇń‚˝‹äJ„Bą—eÝ#ź1ŕ1ďsNu“0{âč ¸5â-WNżá¸™ĂÜŮ?-˘~ĄeâgTeüńźŢ¬u˛­qbË„9߬&Ěߤ!-6†Y&ŰŻóz^ŰFa* ±e)|ľ˝śAë­făjeŁ´ř‚;řôN’=ź¤üŻ{2´QŤUmhÓ6hŇŕU˘­>„[„@eCsBŹ Hnç•ĘfíѢxXô)IA;Pů\ź5á®.vňiTšSxž5nÖ Ľ© µ{FYťŹ ĺ;ß zéĘh®S/óµRçUal·ŮIZÝĐĂ"Řč|öô@žxEţZĐăupŃóh1®U%RlmĂ’~D‘t4ţö5pJPSĆyÔ„dîQb”'ţ#ńäć/[7öQβ3˙í9‡j‹Ę‘şł˝]čGn;Ę#BÇj|bň¤6ş‚SšĎJĺba…Ž3Éí|ß#Žś%"N_ŇyFăĐŹÚ`OžHęôdôĺO—‹KČbjIţ9ĄCTgZ#żÄTŘËč‰&`Řyä˛·ę ±m’©ĐD´#9ú‹D ÷ąµç°ň)É%A…6¶‰ĺ·ź>s\77Îč%›kś"@*άEˉň‰S‡¸{o(@?éPCVČĎíđ˙pÉŠF­ę#†ô»Ř˛łŢî8ęýFôµĆrÎ-V­ňNĐ6vDPbJűWßrÜţî@ŤP­–¦>6!Aű’KţG;ńMďF5‹ZŔ(Ý·Žoą /cc'«|ŹÚÉFö0™ţ aZD5-4m~YţOÖ%3養,2ÔtqĄ˘77îlžŢ˘Č.‹‰¤ó¤ć6™(»j0ÂÄ\e0L B2îPΨä%Sľ]NŰxseGnö-[żĎ‘.żÜBń ĽZ]ŕäçq‹—nŔ/™0ö­2p^ąZÍ$/©ăíÎr2C±*óůĂ5ôŽIó{To®çFő§é6é{@h“ŚŻËj”ÉžA'ôt;˝çGőy’bNiČTŕF~č\[Js ôO…YĘ-ÂLüiB$ÉŻ¶IŞqôŚmg1¶1.ä-1öyÚýkI­íťÂŃđ4) !Şż™ĂÁŘ)2>OQŞy«Tő YRÁŽ{¸ő#%2ľĚĐÝ*pTaa×ÓMĺŰăąÁôô;śŮďŻ7ËÖśoňXbJä®ó2@ĺšÝ"ńTk¦nČÁרXző8ěpNÚŻé­&Dα˝EnL– 4Z„KÔĎF6¦k´čsďJĆŃ–n2¤—÷:#źö{ÖđĘÍ?°KJ>â;Q'Ę™*|\đ˘äš‹Ŕ„ äYöG<€„ČŘ(g–óŐĎ!ŠmÔ¨‘#ió´öPʢĺŞĆ<.ޱ5lýÔłfuŕSyĂÔjBX‡×é_¨ˇa3|‚ľşOŢEUyóó çO+n\ ŤăŽ&–TŹz˛ú6âÄŚ2:HŢůŤdŘśú¬>°ţżaőˇĐm®…ĎÁžÁ&‘EÜ˝ýli·“µ%‡]Ńăe5C‡é¤ąTˇđŞę\O›?t¤Ť&‰ZM­.[q€­‹0,>~†ž7\ŤCŘîB’¤M“ŐůK@wnb[ďńł5ťk%ąęĎŢ/eqoFÍä!‘ußK1JVÎ}µ¦xs ŘżĐî&±kÄy§fŚň‚6ŻĹý‘çËC)x7>ďZ3]i3N±€·a##”\Čv«T w(u'ÎÂmÉ( î§#ŕă7%T˘Ty¸Í:qß[Xyě„ &„>ď[VËH¨ĂŘîpëR9ÁJ9ŻΫ48]mĄŢźšĄý8,>·U GÉůěQqă¸]Wń4݉śŕŤ_ŤókyŠÉÂF”ü×€^JMö›v±ŚÁ-–*yśP‡±í!€s7Цoü›•‘-@ÖnǭȸÔâ3ěăpan„ú°ý‡kĄŹ˘ÁŠ‚ľ|á§Îľ“ú^ůŮńtďĎţFxč“0C[l®ě„hD#Ôk an€J"„ Ěę,EŠĚ «°Óďđ"t€&ďŔˇč¶¤ÍmťžwRżŐi57yâÎä+KVî)‰{x üŠú+ÖLoÖ˘ŰädćÂřĚ´¦Ć˘Ľ}‘5«xôďŔ á7ŠOy ˇŘNA2·Ąz’§ă>FqĘí~˘m51ąŢ mHZţőgžBŤz x?t Ľý’ôv”t†ČpÎáä'#] öxk8Ě _цŔŞv®·ÓÁĂ8ý壳 ł˘9d¤×ŘÁk7O„&ÎĹδŕŁquM¦¸jQOD ’«zP›Éh¸ S3 ef2®ŐR,ý 1•A[¨”xś›KÄ2?|-}ĺŰýAR¦°­˙kÄß@!LVf~ÄWfvöë ŃťÄç—9ĂT˘¤Ż‘§=…4L—+Ú(YÓĹoÔ«fʧq0 4ý¨}€DÝłëśrSĚjp{Ś}ëw珚-JmÝ­Çspˇ|Á˙U5ŁMńŕrtNĘwÇ›ö€=Ĺ‘lC>ďWSzqNAI rŘSş»¸ š»ĎóńxŁÜyôÓ~Ô9kŞ VŚľŁ@ŢrElrłVQËÁɸb(Đákaá y–T“Ě•ş Ť9 SĆKĺ˘Řáä‹~Ł­XJs•z úSZńO;#N?é[Í ]ľíY)yTrř›‘żÖzş&XÝe܉®ĂÎäü—BđÍj…3řáßLN´¸UcĄÜZůH=žÖ%J!>#Á‘«ż"gX-jŇFB˛ľ¬¦ŮHÔßßBŘ-â¸áđŘ%wN[uŮűmťťAźSD!Hsb:ďăÔ.ŕ(ôËEŁ>Äż€Ú şGÝ‚…Š~Z˝ĚŢ fj„ ĽÄdÇŞnř9źq˙¦Uěěč„ÔÄĐ1AůŠ‹`]ÂXcę„=Ăhż…ćĽBä§ě<*ěRš wć3=÷…w”ű-Îć|m"qľřd䯒Źű"4łß‘‹ű»ŘrĘŢ®pÉőńYĚ)ö–Ąý«âg6@Č×TŮ&Wpň…®Ę®>ňęF u š9SU˘S ĚJ5ČÓJó#xaÚ¸’>ß/P/ý‚ĄĽń=ŮŰZ>Ć7‚:›ŔJôrźÜ’ů@íšM#)/j‘@ öTŘÚG7ßăä'ęe‚äNjšŻ^R ăÇ(«*ň-ľB÷bŃŮ^ź îâěŚ0z‹{ăĹě%đöĚ˝l’kĘ‹tŃx ]˙‰Dw0ɰ=>cađh[[ޢ' účĎá G)Jfű’ Adü*>v ű@·1SËv)´Qżž­ŁbŢF±‹6bޤś“뙉6B˝ě¤_ýuĎb~[©´^póřą9j¸]Gz“pvëđ€ 2kÚÍî{}Ă­ăGy aŮT™ňoÔĘÇÎjÔďTn.˛ăáńUečŐ%".®Xv ŻŁ~5µÁÍi2óe¸…H¸nŻŔÉK~™q>[ą%Á ń[w‘ˇQáe~EnAŘŘ˙BíÁi§íÇŕĘXhăBŚűs2©đŘŰóńňł{˝K´’/ŕ›žV‰Q:9ý"[Ő­÷Ł›'uÁ˙•ÇX?¬PY¤Ţ×OzÁ;ąQ˛ađ¸>ä§›ĚÔµęɤ§ü”VÝ+6ćÁäyRSY°f Ŕ*{FaÉĽČ€4ŇC;éÄŤ7#¸:—¦="]ĂpŕŐa%iŢ#5ˇ–˝Ę=rĚ-Ĺ»¸čáÚzS‘č%k!jl¦'zÉĎMVjZb»k^â+ҧ%ż۰O†>÷öµy–€¤1ešŕ«™ĘáŢt°Í]ŘrŁëW˝evÇĄcôXSă[Č HK|Đ:ł«ZyŃĄë(KPţů#ĺx —ßjI’o6aýĐ“Źśs†DĄsóěý;sÉÁ_"…0Řxľś™LP›ÖZ¤!`'ÓHu¬ŮXźßtڏ`)ěŁUîwF§üő¤ěXq‰ýţ>đu«ň¬`׍Ů,Ä8ÖůKt >úÜađńwI=r›ĺ›·ÜťÓŚs.CZ1ľ“P§˛´IýIŐ/¤y6ôlů]KF‘gsE22PťR¸‹wƆcwäÂUz4(ćRK©¦Ţž> ·+Á»µÉÎý#üzźÂř?sÁm›XXř ÚvmBő^$Ş÷+Š<Ô5 đ{ŐÄĄŽ jk‹&™´ţĚ]¨+t•`±=ËÉ–‚íáhMŐŤ DT)Qćt-¦rV•lşF;ëśV˝"ô‹,.ކŤÝ>bç(2.ęr3•4kŮ@“ôiVĆrŢú Aë3@e «!řO˛÷"ŮámĹŢĘÄA@o`§żÍL$&šMš_A°ÔĂfucšűŇôŰŰ/ą@Č0°dšD3żôăŻŮOgöÄ ę¨ËH´p|†Q*iŰú¶nµ? {Öü»4«n•~îPŠ·®pS¦’¨{Ď@r)vó‰·ř‚E®Ď©ÖČęĺ÷řŃŔ+U–3érhI÷Áé-Óđ»Dż,ĎmĽČŘď,s¬đGŞÝ:ł8“/ß.Ůhhň…; "‚Ř4‡€feŇŽcD¨V÷8Ňňx•ËŘî5:&ŤâÔ逗¬ěľYËŤuY(Eë-ôŰČÁ莒Ęă«Ô÷ňĽÚ~qĎ?=Eü#B ˘hŔš_ä:Äłxo–ĺnŚ „=UĎësă?ÖUG ŁďfĄŇĹó*öÁŢ6ßÄÁنěg‰Ř"5ý ‹9ŢşóËMiPěüD_p”w©˙ü1+ŰP@ŽZ왟«zĚ ÉŤ{#KŕćóbS‘»Ťós 1égŃ=ϻɏú® t«®é!*ľ¤jIÁô˛¤ŮʶQąxá~ótXŚt Ź‚__ ë‚D±Źrg+p†lJo~Ýu\AÓľč!ĘÓ2łń-LëH»j'{aŹĎMơ„´ŻnÇ!#Řw÷Ú>ŐjćЦŃ"\|@¸Ć|ÉEŠŁ'Ĺš ^ŽaőY@ľůÄ™ă«o”S)Ú(ÄĄĚ{BÜó`¶â~§…ŕcĄ.śx“±—5¦H˝ÄĐEçë(UÝ;…2ÜSĂá,)¶ś0ÎÁ°t8€§ĎÔőźÇ%Q‘˙#ęä·s4>z–ďŔ ę×5Ô( …PŘcuK+Şß<˛Đwüu ŤÝn8ü1$—Đ%Ěźdâg㪳E-pČĽęAvÝ3)âý?IJ Nôk&#WĘO:¶šąń*~•Xţnă&|'ťAO¤!cĐ˙Î*mqĐżŠ  “îHžŐŽżŠFs+ćĎ»ń¬˙R§(öéM2wĄˇĹzË(eŐë›KmǬ9– $;wô´gz±±RˇÖř°u…_Ż:Á[bß ¤˙۔ӌ;˘Ń~ÜöE´B5’M:&7üfdž÷ď%›‚”Ę×X‰Ëŕ7> »ő¶…oEYâ¦R^IË^, ˙ěćeę‡z8‡9$X9jÇÁzŃ©Âß:/ GAő!ŽX71#ů ڎ‡ aŘÝ«b%I E,¬f·âÝÂŽ×-ÜĐĹ&ňŕhúó\˝žůśF!źË$ř~ ,8G×q‡±Íë”Í‹dnĘ÷N`Uäç —’‰zç® ¶yŮzlxg3ROS ,Óh%ÉéjĐ˙(bß}Ü52ôfÍôÓ!Ä‘u<ČĐĂą·>Ű蜲Ţg`%¦žZ• ”kůÍ.6/…őďS1Š˝…PŐüÂÚx†o *P ]»ť–*ŢőÁČniĚë•ND‘¤ĎĹčG[e$5«#íâ‚ )­Ě¸a–”oľA"rf‚8e.$|=őcŕm–ç%ęíu;éŘ›q€ĎúdWały5ж¨,Gf_°LĐ[ÓňdHŹŔčú3bŞJîڅʼnm=ż~ r±ŘJ.K,ţÜČă‚Ufü:đKTrٶ?…DW¨ś8mŘîU<ţŕÓý™aůÓ“ŤË€óš}S‘ WâP´[W4Ë>÷źŘ‹KěÉÓ oŚđËý·M|†cÍ}'_’Tiţ®Đrmňęît‡Î%á p`k[„@ŢCŰí-$ĂÍŁ •]s6#)ŚŇňc·(®®Ö•§ţ i#áVEƦ!m™"ž0˘»Úh{î!éăţt)yYZQ`©;Ż«¦‹Őß.°®Gł>4ü'żÎÚ‡ÍzŢä˘l;RÔN±4ĂK·xáĚéýh“Ó Pŕö/ěÖ|ÂäĎŇÝ*g^‘ (’Äj@ϱľ$VŞ˛ «Ű`Á˙•x®ł]*…¦ŹµŃY¶}Ĺůy|@ĂxB”`-H7,ş ŮŻĎ„E.5’0¶ň>:ś­…´ąŻöd®D˙qi9Ž?4Ş«•ÁWŠk>†ĽúóŤýGH‚3Á4Đ`% Ľ€fú,·ş4Ś´‰čŁÍ줻'ôlťHrÔ\Ů$=ę~’ŃC8řĘÜT_LOĄ{±'‰?2/Ô-’ü†RçmS\—0;»Á(8|Ę’ť%·%űçŚőu€łňR}}í†nnIó¤í}}ó;lĘ×>ŤŘéŤäżäpŐŇőô¬gD×ä5Fd‰= ‰ńKg¬Ő;‰íc´z(î6ŕ,Ę’Hóžk…ü3UÇW Z߉N×ÜľŇFy>&äß•~ŮNó żúĽe ZŔµ)O‘dOKj•#f…î´IGt™Fă22=é7Ť§Ëš÷»¶%"i¸÷Đ—7ýŘZ´úę6É*P±‡DN&™ J;ăÝá ĂÇTH}žÉŮ٬ĚUWeg*żČN_>!‚(C!ŞQ`G˝±č‰úddrçŐCˇ< ÖA±& =)JčŚ]ÓCľłÜ ^Đ©ň±ÇýÎŘĆbÁkÎ Łč¸4¨u{¦1ť3¬˙mĹŃ^8B‘ÝLˇŹ~×ŃŰ›ďBFŞś_m<‘Ł×ĆKn—ąŐM(–UŽčsKŚŚĘŽq}l*zâTrEÜcjTĽ#üĘ|ϸk·ǡÔů‹~†Ö\oÚö3múK,Ő+°Müݵ]ŃďŠÎĺ/™L•R51ů&ÚŠVúˇ'ö^řéű—–áEĐsýć…×NvôU‹UK˛)Ęš'jAűć;|´ °‰ m–ĄÂVgěceëDÖĆÖ;\ďźÄ«jŁ˝8¦ˇđ]9«-ʼn4ůýëR0üpm‡Ěś-Čć@K0ĄM_ˇ—ź·y©Ň›Ź´"ˇĺóŚ–óaW@†2Ű Ńí.Łiâľ1ŇŻ`ĚßŘwp»ÝË˝ę‘örŇÖ™Z˙íÎ>"ěáÉÖ…8ݱ‘:éÍxŞoňő)?ĘßCj˚ޢÎnDÎTA†Ă˘őŢxÓţA†JI(XźŐš€nöřmą•kîşĘł¨Ţ>ćąüŞ;ź¦çH'“\O‰ayG¶Őł÷ŹD}úđ8sŁVJ&’ÝL"őôTf>VđJ[ów’‰=‡ˇîŕ3Ţü­ô4ȉ?SPo1>:ý"kj ü]‰ŕŐ†ixXÖĎ>ÚZBdĹ˙=Ŕ™đń·µcĂ·/чN7™ftčÄ%/4î?˛·9«ľĹ«s/fűÂ/8wÁ›•—UűšášńžŢ—ělě–®č\§ń¸[Ź©žLD"‰Sö´^¶JW2˛ŰR‚άąÄź/X@Z"‘-nčeČćö÷¤ůňü‰kmiőă$ątKF™®E˝›TâąyqNůĐx Đíţ}—eŐJőÚ^jAŠx{·ń\\†ňzé;f"U‡p{óć€ýŐá´-wŁ˝úÁZŃŞŘ ŘSÔ©Ç]ő¶Öloű©˛Y˝nľ¦N?ü=L9!Aň×07Ż8)tW¤"7|>&vDr?a˝Â ”ú b¬•”š–ČmŘÍź®]~˘hdÓµ|X”^ÂGqz]á絪bż-ÝĹCźß3<| qywŞ|,ŽĂâ•ú¬ÖąI ™­GUPA®j?\«ěŠ{´ôtž€ăź`٤$ařŰÁęsľŇ‡÷ĂŃ(×&ęú;‚µ^é }ť=98®Îż€^ő8”Tx·Šúyâ‚“((Ż™yôgî}ŇJm+íoĺ×AŹńu_/Î÷9łĹ(–"Ą_"ď«1§Ců\rĺű Ä0FOÔZ­ČĄĆ–jh[' ŞAžxöoěxđ•Ŕĺá3ˇv˛’ĆŤHhC͉Bcó2Á)̉÷´’TŐĺń/Çč l"‚`F’żîŻS4ůŠhĺt·ö“gR׏´ bűŰzňL x⤾ać`î4-Çŕ /žAÉEÄ’Ygë´•¬ŁŔ”Â7o´ĽWdGśj/ýÎ,ŠéPk–źWČŹx;Ű@˝—ĎëiŃGa $ ň/äą{¸ŚÁ~Ý5×€»ŕwĺÚA%™uŔ,Ż»3¶É—‹„*ýĂ4î[ŤźâČ,ÇZŔE …?ď»-ĘŁ1¬Ý°Ź™_Áˇĺ ¶äřQŰW§Xó×MŃĐSł‰m“ÎÓ”Ŕř Λ˙Ä,zť´Ă< 5˛u!&„óóă?nßnx™ŰůĄ©Ji‡ĽBĆ„5v%%Ô ĆŞ–‰Ă˝÷ç9 §bĺ˘Ń>âZBhw˛2Lľ¬k‚)TóŻ.ňsGN3CŇcOżÉ+,-PŃÁě$‹b‰›GxLčĎçѬ(X°˛l/đuR)W :§ŇŁS¤ŰŔ 3{Qy ńž^Ľ†ĺěůŮű‚9řˇßvů­HČ$’^nýrĺJlnô­'qË5?h®'a˙/h~نóđŚ{`ÍÚŞ\­ř„ÍŘź?Úü±HÄ­6€svřĎŁ¸8ke¸ÔĹ–ě…_$ga´•D Ě›ČpŢ—µ±JoŻWř`G©/ĚÔoĚočBą¶gŔżöÍžłMQ”*Źf E2™ÜXJÉűÚ"ą˛ ˙řĘ :ÁC˝ŮęČkm7s Ü€Ş{łµžúKÚŐúţŰTËüłóšµŔ9U9§łłĆ± ŔÉ’“•=.\>łŔ˦¸´ |`¸§Ťx+/42R0B!•"§húó!Ś–bÇ©Öu­AĺŁYŐöň3Џ!P&`pŽJb Šŕ•ŰÄŠ‹´$¶ňř‡íÓsI?{Łó¤;<C 4[+sˇ¸ŞQPËă‘Ű—Ë®?©O FőíĘú˘şß‡ ±Đîn‡’—o źÔ ´Ą‚”U4ÜÇVť„ɱÖsKR»…(i¤uĎ?úSâFZ`̱â9Bü#cŠ ”_(Ł^ŽéĚ—YˇB<¦'°­â.Ý(°3śöĄ‰Q`ÎÜÓ¤ńÇupR±âv˙ ŽÎąë>ŔóbŘ4zb~ŕ=ů–”ŤÍÔ¤c[8ó%*•)ÔZ PśF]jc±€“#weßśÄ|+čą§^ąç°•o‚3 k”ńźTŁâX†mň!ó^ť+ç\–…ěołGDV“ťŕ›ĎŁ«µpůżđłRÝd|áĘJî„ ~gđCq’Fođe* ZNFrm™{źe= ˝ă&R®$ÎKśž7hĆ«LíAÜkAŁén×4QĐnx”ćYC˙H ľŔ˘…łĺOn17Ćá÷TŘ®Óö“Wć$qL¦Čob`|±r[ˇ»4M̱ňa˙Ć6ď}şCäwb&Ř Őń…Bµ‚µNOO|" áWĚÇĽýŇ,q·“9Ĺ ”Ăq§§ ?ďm„U¶ÔĚ÷ÓŹ´ĐذRżp˘śćd±ĺŁťk©wĆ׆6ç”ăsÄ Ę ·ě˙ôzÓŇšoN˙[yź”Đ­r »Ý$÷Řě9|D^D ¦‰ pĹÖ±Ô1Šr –Ldü!°ÍÓĘÓţĆĂ(đ–řÜ#ž “‹$ä'ÓŚë%‡+ř='v“dŹHÁR­=mśhEl¦SŰŔËćë…˝’ľÇ8Âú9WĹ@™„ĆAKŰ4ÁĚřŁ›żË\•ÂĄ3Ąµ“ áČ©%şAĽ‰\™°&%›W€¦8\şGhX©0–?Ęóy6'nń—kSćÝĽqŻFÎl1Ź„ŢW¬Q!”˛Ë#¬űé%ÚůłŕBÍča¶GŠÍ/ýSýäőšjŤ’rÖ»/?ýZ@ýT€Ę+[λń˝ű¸l[—#]z\ą~h,Ë=u ôJĶ_k ë = wÇ‹AŹž˛$ęÎĎÁZfÉkQ…9 C‘ĺ•ń±mqĂ5~®@>®LĎś%>EBH3_.K ‘Bś¨pK“X,#BjÝ}?Ź««$Y1)ÍĂ š q v2ó t ŃMÝ6Тܪőť: «™$ÍÁłĽ¦N±ŹQH@4ž€3MĄăo·[á9D§l %mEUÖŕ&Vĺx« mú«g“ÍuO_!đqÓ4ü´bE9×ŕQfxm ŔŢwý«úÇ@ăfŇ/ŢzEîŚŰŮv¸ .}pđ©0«qç‘ń2š)o˙Ľ÷ÓýÎ7[Ct?OíÍÇ?Eé`8‡?)‘äĘ{ôąö_meĹ'ĘÔp«â„2°Ô˙ŃIN‰ŞÔ`Ř0phËy8yĽŽfµľ˛§TôňϬŚÁK6ś ţ…_ż‡a“"-Ü…oű;Ë“‚Ŕ›}š€ŔQú ÇÇĹ[Ń3,‹7ŔţÎu*¦Ľk,$+"5’ť×źŞ{ęíSPçt“eĚ[]1úK{?[íÖž^o­dq­.‘v­·ť†Áš5ž‰†Óŕů™˙ůţcÂcŹżŚ–’ *SůfQ–b­xŻ0¦$vëѭ裯’Ě…Ŕu jÔ@\2ĺHj]F®e?”žU °éQ5ś{ÜţSÎŻ¶Q´~ĘÂL¨SIHĺ3©+´Ě÷;.Î%Š éV 6Ŕšňh`ö8Ťł,>ô@,|{Ł%2S™ ˝´‡°¸őŰä“—!lRŰ:ě)LnQÁY>KžĽŻ°®Ś†ú‹=í¶‘$ÝŢ^yZčDť»çĎRľ’¨Ü}ű#%ö5mF{ß»y.§Xëбš1Łú9ÓZÚwáúÝ<ďÁ‚mńší%˛łm¸ť}®‡·´/a”B˛ÇV%f݉Xü™hÜŽLlü1űc˘”߇uyĽÎĺ‘ÉÄxy¨Ą¬Š‚ë[-q °ůŮEÓ¸”Ű‹ű®:vßޢ!Č+÷šëo"u˘›«WiWľČŤĆ9C—G"Čšř¬§ \›‘ămăâłQ’oě‘Hót; ˇçl^5‚Ł%<‡r[#ĹľEÚĄ[ěŮj$ßH$FhńÍó>ᦙŮ3`¸`Î`ËĘŠ´aCoí—Ô†Bm${]]‘ í,U™Ł«™ÚžľFĽ$_eůkÖ¶}d§Ę $ßńâËŃYĄ…ĂkLy!šoo=yÜjĽ ÄîNÁIwłŻPhŰ ¸.•ŻýĂLńjš]C‘Ƶúf˙ Íş‘!Ćzů*sĐf˘Wé/I¦;î ű%zŃ®Tn"Î2ÎϱJP™šÔ_Ć‚č«-÷‡\ŇÜ{mÍYşâbń›·Y˘V)çk÷*YÁ –ÇRć§—šęm:0ľ‚ś/ŃC¦ëRe b“¦VÄPec^t -ĺ|'h&–Ö,Żk:TA‰§ÉE±Lja—)šďwně–ç`gŤq>[kŁľ9ŢŘ >ZÔŰ f¬ŤP`Ăőd¨¬w`ě4uüKbˇŔ›G÷–5†°Öo.ÝO«â-Ŕ´ĄHMú•ěKSÍ˝ďŹ÷Š=Ρ`_=!˛d« ¨ŘV ĽÁV¬˝čo1q¶®ŕ°ĐVăh Iż˙dÜBކą/ZŞ\”Ě8ž .Éj5Â0C"żŕO¨-şHebÄô®ÔýĄ’Üo^p÷.?;„<Đ›«”7ľ‚€ŞŁ|NˇÓ±=_ŃňsbWNs2óŘ5+pÄ\B›'H`3>ă§€˘aÂ@ç{3 !űä–U 9súöűŕSús:ł45ďµP¸/W‰4.ł”ö‰XÜěÚj,g˙0p€ą í%}hôíô×áÎaz Éw8^ôżË”Š˝xmäÝ›óÔ'ůç©»Ô»żŁĐ‘t>ĂâăßŘ•n Q)'űkâ 6ĚÇI-[¦m-wéx2Ş )U CÜY˛‹Í0»ĆÚ­N"3Aî­•ÝN™UHR¦ ˛9´‡@‹M…ĐOµu°/a*/eéÁuř1ľśH̕ŋÄëËmälWqi]ťÖĺt±˘żAřçTA<ŤĽh˘I…®jaŕÔďŃ&lݦV;5sĎýĂôŔkÔkËóÉL7)ćHPÖ°óMžÜ`·Ä+č6‚(¤ ö¸ ±î–8*ü‚^ółä‚fHť‘€ł˝®ľU~íâÔmB5`˘µ®¤ź6]ÝřöţÖZ?["]ľŢÔAsŔ—ŰýŰŢFíë®9/ýŘńĎĘ n$Ośĺ›tż÷Ňăé†NątiC¸dp˝ő—‚Î :ŁNHľOoUç-" *µÓtďv'ť-QpŰ»N¦:łťv7ŘŽŽĺ[“·x¦ÂŔ>ľťŕ°Ř%”ŁÎvMh¬·áP$/­÷­Sőđ <ŕI^tô‰NśFŞ!Ď®¬Ą ™ L¸;^bIş|čAwČ/ü H•élńŞ=pH´ŻjnFň´,+:šđiwo ”u Ëë|Ňŕ‘Šü¦PĆB~Y°Ń·’>çB鏳ŞŤăLlłń3ë6ç6ĽŢůë§IݶĚGĚűé˝Ŕ©ňi;6U–ĆeĂ;Ó—>y‹ů_ç¦ŮÓ ~Mňn:Q˛Wůô’¨Ţß`m„»‰Ë~o¤1°‘Ý×Ä—5M:nt˛.}ń>Ť-1¸PőĆÉ,ĆĂşBěcËÖe˛łŚ‚[­Śş965ő¶|ŤvÇ;‘KC^đh ¸µˇFĽaD%Ebg­é– Z‰ŤđÁ/ѨĄoWw6¤Y:Qu%YŰV9Ă$7č凍§ěĂ‹6Cî‹LÖ BQńÇîjŻF´sÁsz©c´¶J1Óů–Po´&úE#ŮKÜčÇtF}5>âýŃAdçćť:™†ÁqâßMGÉXÂäfľA*îěŁ ť…° –WRĚĺ z»ş*­ÄŇvŤďڬl–¦ ~ÂJ‡Â]¤/˛ř(Źě [o\ŽąۆűXÍię´¬H ×UőâÁíě0Ë )”ëRŻ2N…[ś&[L«EwĘů—”€‹És˝ÜçnnŤş}y‰F:ÂL9­•«vÎQÍ-ö ľ7Łýcvş?Óľx Ś«ŞÔ¸­v~y‹ËfIN.źq4őČP‚#g´čö+NZ%uVń‡Ćý/"ýA\$ČŚĽČÝhńD]qđČČÇţăÚőâ%L~ňYÔŻŽb˘úŞ`3(Ń€Y˙‰ŽjV©®q°•…¬âA±2Dć°äŢ‚9Ţ.·®-Níag‹_5xH?YýyĹ ýaţ)Ť–ˇź`ź´nMť'çďk XDâE˙°ç=ę|‰­a±őŹĚäzËŇS~ÇŽO q+…YÚĂ„>Ö¶˘¶®ÔÔ,x'ĂQt„Ä”’Şv—×d =sŤďeáń˘W.~‰ç0ü´ĆyB˙%=ţ—n[LiI?­—U~Iŕó`Ďú|2B ťĘb´"gŮŞţ±©UžTş˝ků«íGí k^Łç×ďBs¶)+!®×őÄGau zÜI3r§:C§5D¸7Đ„ą©[ĘxFs°F)%|‡í,A9Śú·Ú"Ń" J[ÓDď Ů÷ ‘´Ă Úµ6Ĺ‘ďŽĆ–ľ‡ů ¨Y=‡z÷_śjÔ߲“ůńźÚÂ/ ĂĘ‹›Q! ĺ Ym†?‚›€LYÂĺ=mzrÁÉËIä‚ÇUČ ź–ÓÖŇ´ďRď<´n \†+Ľ2PU \ËÂŢţíN!–A6?r¦T˘71ÎÇŔec  5Ź6ÚGőŁ ‰…?u÷Ť !@2Ű>ť’wá…,}ĄˇN‹­ß_oNJš¤Ň6CW†˘m|7˛q?Đ 9C.NF™Ń‹ŁS«ő­Ż‘\ŠÁás"÷*Źb#Ć-h #Ŕ—•y=Çź—tOő+ACĽ*w-C;wýPčőĄ?¶šęü†ł$Ŕ°Fo p*ŠLŔ~S<Á™‚đݦ6”äžQ°ĂíŹB7;^&}ź„–ĐTYÜÍżî,›4!YëÄv˙KšW"–ŢşĂđ,KN)ě)pšF ®ąVJ ˝XçLZĎR…h€¦Ř CŇĹ a9çÁgę3ĺŞ>SÍŘL=ŚsCĐFŮ@Nŕ2/Ł*ŐĐŘl¦:¸VĄ5źi™_vŽ…]‚•áC@,Gń»čťČĐôsä‘Yť„vß(-íGRĄ(‚Ź!›đ{ľX> m–}Ą¦~ý"§4# Óâ<Çęo}+đ^fÓ•TN¦%ćIv¬#‚áŽvw6Ňx›Ď›üĂŕCmF†`đ­xć ť~Ű4UͨK –›đşâDA=Aô€˙‘­a¨Se âź…QS[göů·!täÔńŰý fj3rĂeÚ­mĹňPMŐ.Ö^0šűý]€Ań6”đJ¬ö’§ŚčyxÂŘҢ_ś`ń„Ü7Üö;J7ß—s0âvsdžŞăËl=Tg ~Ńî ă4,ÉŹŔĎč@qÁ>Źól$ˇó°)-Ľë 3Yőťśaň…î”úĂ$żP:‹–ź×ËöwjÜmH~ř ‘ňÎ˙ř}ÎÖß6G¦™Ň)ĹśVgţHĺěiß7{´ëďľ}_)N Ä‚´ľťłŮ˛piÄL'@'A¦:[Ç>1źÎA—óĽ\×·ËŤ\ÖZĆšz»H÷í¬ď‰Üş@¤‡B^˙V4-ŘßŘĘeĽ3\:+SB:ŚVČp—7šÝ=%L€>ÎŢđőHßäx9öž~Đ(ŚH|CeZŐˇNř4ëâşŘGN(çVű0H‘ÇÚ?|UűV÷mṯą|4<ě‡ŃćÇDö ¬|ˇÂÂîYmž~ßšń§>ÝJ8¨|Z ÎQ0LĆCnë’.Ѝ9ĂĆxŃZňú2ŕT·$?ďŘŐź:SĐú©FW̵Ʒ‡´cźş‰ŕA!ŕŕ1H@L"7«fyü¬„ˇű1żÇľ1óĺśKŠŐ9ůonÝâ]™ňţ˙¶Űšń®¬´uLl;§á$¬¸î‰T˙ťÎśŰŽyž3˙µZMnf1'ڶĚf¸bö\Ě8dw ‡«Z* šŞÜíŢr«čvˇ  ”[RÓň˘E?Ë®žçţxÝM'“(Âe$† €w^w/&Í»®Ą@j™€Ň‚´”1ł öŃ«1|˛‘ÚŻ˝şîú`đÇ›|“Zu ĆXŹ}Iät56Q€W FŞ!ÝÚO-Ło’îĹж¨2:j^Ô)ĽúV¬ mĂ»Ś¶‘Ťć“ŕÍVŔµŞ<\‡d#Ý@––PĚř>öč Îü†6ͬOĺÔçâńëzö4˙§ZúŰCTt_‰`˛¬`:ôńčË9„µ–ć,<\a÷˙@gËÍ™ R”,ËҬIoś»ş š;7źÚרäţÉçÂ<€¬‘‰‰ćÂôˇCđď˙· ˙FN(Ô>Ľ(ÜçëęÁ›˘RÇîBÄŇJ;…Z(çËw=Ŕ»3RôCc”ääéă6]ĺÖ5óUÍ%ť<®DçäˇČ͡dúMa±b˛ş”%Q$XZč›z9čV¨ým4¸qł^ůË Ř!J¬w”ô·†oQűlmŐ˛˝µęş#›ŕ‚ŮÉ=ŞOë |g;Q¬“RQLÇŁL:z^‡Zh›·'Äv‰s`ó|ś,'[X@ř逡Úzńr漡ÓŢňmŐ˝~+".ćÂ}auăWd`ý/˘5©üÇkѸ€Íń§\6~±ĆHIčpŹ^ŃtěuLM!sÍCHפĄľŁ×!%~*ˇĽ1ÄR ®Á<$¤ň¦‡ŕš4k‘źA™ Ĺw0KŮů’˙bÇüŽIč1’Éá˛$cxĎË  41ćĎĎůŃżI'ăRdbîűXŘÓĄťTŠ cęÝŠŢRŰ»a†3I‡ýŔó‚B约“w1©ô]Zë5ÜŕÝx­zŤR`Ô°XĨ·r sé›rżp–D~˛ł_®ŹĄ™°ű˘–wÎq¤Y9Śę+ä-Ńuzž§Ű‰uôüľt\ çŘôʧWAŤöśůŞqdľ*;łĄńTb·eś?dX™  ‡÷“׫áÎý™ ćI K tgńĚŃi+ń]×Ôv Ú°&ď ›w¶jh5ą»áK ŕF«l«oĺF4čôuâÇ÷˙?­Í\Ţę8MÓXů­ 6 µCs~-é~ńv<{µ‰Iť˛6´  :!o:cS•/Ł!č30[YŘĄŽ&’Ρ˛ĺ±đ§eÔ«ă«•u¨ÇYŢ»ż1Ëź7Đáź{6i…‹s-Ŕ{Ć0ý’vŹal2C,/Ň>śě^p„+ߥémXEµ{Đ4ďĎ·hţsúyR0†üaŃI…Q.Ô$ĆŻ+Ä^ÓŢ]˛ Í52˝ úI=ń×駢äć—Fŕcô¬ĎłŠĂ§îÁ´ěx\Zq ĂľCgŮF«2ţB«yČš6ÝŘöM˙®GŮäŞiýWëŇ´«žÜH 1é1Ş—U|ćĺř#¬á5’>cyâ—ŘŰŁśö¨áRŔZď‹ď—‚ń(ĎóĺćÂÓĘM ©ˇĚđŠř&ÉŔĘr};˝ä2ka–QüĺZ—OÖä)@f ĎöcÉ 1~ęÍRëWőŹ‚ř™ß;Śęr¦UEŇD´ÝÖäU˶•cĐéCţ#ZýpĐ]>SçĆ”’Młżçťâ2E…@.ŚńK.Ő¬qN¦ZM˘|tŐë´¸1±q†cގžuë0űR2öĽŁŮN{íűUé …"˛–˘M´8Gzű^Ő¶ýC­ńSÉvtł<ö5¨ľ¨G…‹…„÷-Íרţ;Ć3vJpFˇUôkŻ˘Śhlrús ßx’‰#Tt ޸}‘Pb*3 Vp5úŔż:ś ąć`ÄŹÄLlü.›ŐŤ?Âzž#čݧłe-ćśdPć5č$hďBŐŞęĺí~íęßyúěşGsM7HAó÷,ÎÝ{ţ[űVť™r”B‰!Ĺ“ ›ípθĐĂČ@«ĐÜmUj㪠£bäč2‰AćFoÉşY^)sÓÇŇĘE8n›ubpłŐsŮźl±ÇżGVňxl4ŽťŠŞ<řZ8â’:ö)ߌ·»t¨Ł4´GŰKg(/°ţiÍ_Ő˘ŕ’ę3–sűŠrç•’ÉOŰźąĐž.gA…ącňĂFBŤÎűk>…Ĺ)ˇń`OŠÖAó˘- K&aWy˝v3%Řx#ś™cÓŻÍ.7i b˘™ŕË»ÔâňÜjhçú™mµÎűo”$«Ž_¦Ę˝©ďř©ĚÝłd@U¶t?¶¶¦pÄÉ»Ź9MÍŐŰKT;v[ŢĐ—Ć ˛˝–^ů/vLµ:fžçXĎ+Ľ TqbɎɶáQwĆy6PöS«xč*{Zl΢÷őa ôb Ją·&WK„§&ÖwžT ŮÔáĆP ć‹BqŠe "÷ĹŻ›OŚC˝ůlľŹÝΕŠě3-oÖ‹-éĂD`íˇ6ŇhŮähÁk'ÂyąÂvâš ŐXgJZâÉZÉ­@ÂfóXEţV—HJđľ ÷­˘˘É wu3VÄ™ŠŚÖÉçVá±,N÷kSş´)”VĄ˛…UGäacqb<_ţR;]8ZjyÁSť1óĚýŢu6ă:ŹŰF.u6. Ve'Ă@˘ąôřy˝Łůľ‹ij«,z„ý´śkpęíC2÷N $~ŽEKOŤ“ J†"Î@őô^l‚n ď“hVťsĘąˇS­ŕ©Ü×ϰ4ü4.ß\źU±”&ďHşh %’s,ŕîßfLDnM1G3íĄŔ!ŻxŐŚ'›_g!xšd.jŻR’ăéA‹–Ů×䚲u‰á–¸ ńŞ.8˛ą„źiŞĎ˝ Ë#TYUöĄńna‚…ˇ›RŃ|€¸ÝĆ‚PZŰćµ®V¬z†.t0đ&űBÁ9)Řo+łYš]ĐŘ3řh[Čfj—9Ýv箨ôĺ°X6—+OCSŘ)«óoÓżůNę#ůĎĚROYŢňމ¸Ľ„Í2 Ľ…yŇd Ë·…¦  ŽÎ ¤µ)˝\exáŕWuýyݶ ëxĺłŃô$Ćź–šW QŽ`Ţ_µTő*ĹÇŻHPĽşćĂî,W ě®§ŤSgť{ăĂTt§]} é˙őNÔÄů×ÂąŠHŁť˙i3řëÖ”3Bب]ĂIĚ"®uSóó% ĺbá›ĺ"včýE ‡;sőĐJ c):ĚüµŮ±ř>5ĺHH‰38«óűŞ~Ź´vR‹ěUČ?ó¤r2öDę|K=ĚHČąvĘ­đG«M ŤĽ0fĎäc—D=˙Rآ§:„ŰAS‡‘FŇ<‘6wŚ?ŮőţEż[…ĺJž©Y~`~nk$âŚOeŹ;?wjđé|qč§V»Šwă1cO››TbCx‚mĽZ·‚w&cźo•Gd[Í?ĽXđżű5/˝˝m´#néóŹIČů2ŚW…4Í\ŔÜĆ<®z’‹_gV»dĽX0yěERâcNĎěG1bޢՔ&śđËK;ążˇ±ŘBSľţföpź‚‘ÝouA· ¶”2qÄşL $mÚ$ŐÄj.˙ř„+‘WÔ¨%{±Ë㫺y–ÓkĂJÓ«lÖE·¬\2žÎ¤”‚íAنh÷ĂM"^ ›ünÖâ±m AŐŐd‚ËüąßG‡×€iŚŔr$*u_kĹŇ;5=ÖĎ;‚yäøI'y$fă?š+±…2Ę7Ńd!î”ŐŤČÚMőŘ’>Ů>űŤJ‹2ĂŽˇß KAzŘUSş*Č/lĐnŠKëOˇ8ş’¨ŚĆ.Đß_ vЇÓBŔ)ŐĄä{AOÁţoIEé“p"¸yâ*âVŇ«[6Ćä.—cŐŚô;Á |j˛“~T8ŮŠčHTW%č˙Ŕ©#N}Wú&¤Ž¦!LW‹GŃ™ď+,ë `¤gş-µ–ż´Őë&oż‚ü˙ˇî¸ůű°đ®B¸Á'ʏ¸0­Ł<ëăçžvŠőĺŞČmÁţ€%Ěrľç&Ýk4Ju ËŹ:B5A4óR âI¸X+¤ćäa®{ş˛ËĽ”ĎML¸¸±ť©¶ ”繸{;{|?Ž őb Ţö‹`‚ÄaÄŰ1NHîlpsVµ¬pÁO+ŁOý 7kˇ°kĦŘEČÉj̇`¸tůŇ'+ŞřFC¦0přKC&Őí§VUÇv—‘îN­[a04Š E˛×:hé—% ?łÇ‡©FaŢ0rÉJ•§0÷ĚŢwŐťžZš´^ó™*ˇ]|Ů) }őĎÜŁĚĄ° NwÖ^řřo§G^Q^c['ywł“îqşá±ëż1Äž‹„uuç“}Đs—aŢŽ$,&ě ž¶§L`Š[äţoI,Şđđţvoü‚CŘčůÁeÜËďžnŕý@~Gż[e:ĺ]!Ş;7ÇŻĆ».ŞÝ{L$r¬ci"ü"—µ“aÇáw2´^lŤúEŇe"‰iŕ9ô±ůŘáF]LdřŘť¶Iřđ*´}Âu5/y6˘¶Vz(×;©ż~ ŐÍňcňŤąee*_‡¤–X©ű÷ăíÎâć“C&C*á\1ť'?´<ü¸úőp$ú(ô(„=đű“FPÎ2BWÂGŇ7_ é&ő3â·é5fX:‰»ĄCnířńć6mńOţâ˙ŢŹćňMűć› š —f–8•hvš0ôŮ[?OřZŔDĚŚůŤ3'µ·ŤŃYsd2Š~SĘ:zBjn„—®ő’WV‰ş±}QI«°X´có mĎ0ô)bM¶a\C˝i–dNEŽÍkąŁĄgqĎôOý.ßí Ë="JJU±zT3ş‘h)~QđƢ˙#ĹšßŇţyň=ř4ß= žŽš^05®mKëB¨RâőČ3ÖůÔßŇ®řcŔĚ0RüĚ=Lo©óŘ"vvEaĂ"ŘO6Î)F&I™«í°ŢÍĄ™„ďE%ąĺ¦ő`ÜŕNbNťľŇĄëŢJPR˙UŤT) bnr‚, ěŻ?•„Ć"ôčOH”I3ĎŻŮ%<Ú¶—Ă©»×®Ź Áŕ®ŇĆś´}ą ÉÄK„-y›…<ę7Ý0moDcäÝuv7LÂböĺÇÍš eoťŤvA€ bŚYBJ†ü|ńµ˛ľ)ʍů3‹ťÇ·"ă“Ç×ő4nGĆ­INDMţĂĐšE‚ý°#ęL“ű¬ĺÓ&uť_]ĄţO¶%¶sËś|ű}Őć­… }™Ř°Y9–>ă%ť+ţ{OJZŞĆ@Ó|ăÍ©Ră˙ľűĘQ<«ĚëöÓ}ń´ XŢT4&î™;|®ęjř5AqvľuňpąŚ©°xĘ„&tŤĚ©ŐeÂr‰5@›]Ó\ ·€pśĘ;wżÉgEhJSňŠ©Ô” ăľ.şäUůWpÝÇŰ3V>Á:$kvo•´3šŐY¨Â ,•Ş%x@~XÍď8&™kÓ¬Ü{Őš:w鏥r*Đúź6ŮJŇŽfxߡęźvĄĐŃVśŮĄ·é˛.#bŚ7ń‡Ý-·v‡iÖL¬Ňn‡Éş!ˇż7ŚĂ´ĎtpůŠ'űŁbŕĄw#\/`} ĎpGh’€Ž®©’o\AĄ»–x\íäëŢ*lS¶J’n[-:Ë7aŔÇ!Ęósŕ%'čŰé®Ńűđ‘Ţ+Lz\ŰÓ®¶,ú nÎW Bk@žźMJ/ŐćŤ<;śd50h…ZÇT˝©žPăţâa¤OýmćwXQŽęJˇ”e+CŐ–w÷nł˙J]O[‚üéŚZ3¬H9šă˙Íđ¬a}ĺ¶w-V™Č…)kßҧĘ-8ďs.Ň ‹l7ĺaáä2CjŐD8Ő(\†ß©…#´tM*Zá7茟!ëTěă ]I‹€Šô\\kfĽ6tM„ßşĄű‰1ŐÁĚŔbé8´ŕ3łAĐď,M‡hň~FˇyMHůŞ fčqŤ¨Ý»:µsEË{ÄÍyéáLTQ†ŚO©– éɢBjÇFîCř¬Ń5Ă̶&‡ňÓR*¶ä„s…2eěF&ŕŢ˝ŕĎŻxŘM)„ô!ËN/ –^ á@ż?Ăaečy­i×Ď6˙—Şwn+!„»g¤©}‹S”NÚĚ·é˘}—ąxŹBpă2Ťł&ç)ą $°â_č5mé÷ŮţĹĹ’–ŕ;ţ¤,ÓĚő“Ăš·^+ŞFÍĂ̡,§ř“”ü¤…ôÄ4:°Źp@R`ą‹÷×úM/gÇȵfĚH ßÎIĂĄČ0ś;±ĐžËl§|2,jś< )b–vČÁ±ôĎĹ·a×vĺŮđ†ě„ą(Á¬=(ýmÖnEäő5>¬].;©Řą-e›H k€ŰłÇn€‹¦k^Łó%ä&ř¸đč2¦–ŇĹ˙¬O’2|ޢśÖE9/‘;]/ 'ł+Ť„„W>7§ćt' ž Úú…+TyőUí In[Ž@†î„6p…#¤2ꇢ"dăb†żš Ä=Ç©˝«¤â‚×Ű):°Eﯴ–µ'ÓîŻ)»ÍÖ(G±ď.(?ĚjĄ(ęXP9ćÔ,ď!fśmŇ"čY´WnďHô J ]}mŘ:µI BoŇŻŔăhF ŘďŮt_}ß ¶Wźţ;«w/ĘÜ÷´YPŤ>§ťO$KŽnĂÉ– C°őćşĂŞÉĘO]0DŹüAÁ ¦}~ŘË]FH—5Ĺ]Uł;ŐéF4}…qŢÓ|(bG_§U9?Ś2F'ľV!„Ýz G­RälřzD†Ř­O0î&ž]Xăú SŻG˘é‹ěZBŕŞŔ‘’BěT5˙řĘ ŘSţb©ôgűŤ±lĄěf)K2-˛q›±Ĺ ą_%ŤŻxCźŤĐ® B™Zš˘î“ -ńěű€$ň„0đ§2Ý€k;9ějeŇĂ­rb… 1î[±©W9ËÄŠ–őU ¤AŢ‚č­Ďî …Ť16—ě鲸đÍÇ”Ąő݇tÉSM.˘šsĐ~-CKLŚ©qŰf»š—U§ a&Q+`!˙KŔ:)+nÚŢ”†Ă‹Kě¸Ę;ŢmެX‹×sc§‰pX™ĺő±şeT*ć6LNćśžÔ Ý}ő·Pb# ]’ě¤|¤Wn ýéÝ­Gď(‚âŹ!Şx“'ÎT‚§ÉŕdsᏚń1/©y’vÔ9uüiÄ Kź@Ç„2â‹9şjl˘N @ĎĆŐě Ěä8  *Ĺ)¨RóE/»šm¨ ˘'Ń©¨Đ ‚]ÎW°©ż]%á3í^?ţ5÷„ůgś`ě<z,6PüÂLěůé;ŁŁxUyw›Żżľi”łÂLĹ2šoÖ{}‰9Ă=N»ŔćÖ}‰PŰ5×ĎWäŃŞŚA|zTzß”×uôš6ĂÄ©·±TŻčA›˙y¨´;Fj¬łBä ôťďę˛ýłŇZ>ô›e\łnçăŰŔ,Č‚)8Ír&hfĚ]W@îáÇ)I¤S4—›•jëZĂ—š©Ö—ń0O¨Ňp†Ó«6j*4fXJ ňĚcÍb6jJż{ˇçoB·ßőđżC • NÇ»lŰÝrň…Óm^ĆoňŤ"˘ýήšÓ[?ä‰Ň Ź9}’|ĺ6Eĺ\"ů}ž8†Ô1‡í  űŔőç@‚učş6wézO´•Ó|ĆhäTř.šLO|ńDhˇ)ĂÚŁ%7Ýęɱƥ} AŻEmlT''¨´ęM8]îĚT§Ż™źŕÖ×f´şDĹ3{N÷q#B˙ ™Rř>Ä Óî‡g›}÷HK{ű2×9…$±±F¶óô…ŮM[8aý6p“űŰanÇ?ţ8ÝP.–˙îk¸bn’ łůüę…`&óPőǿŠS±k9í…‘łĽl÷‘0›+ĆŘ÷߸Ëu)*/Ŕ'uaE¬`Âoľú~I-<š~íťľŠ ˝§¶ăa”ŔQPäłó¨94{ Çś°đ8yµŮŽŁÝWąÍĹIuáĐş4ËU˙ŞąëÝđP»ÚŠ×Î_ţńOĺ a+iq•h׬Kx¶% ‡x’g š‡nĆ…ůÔ4&™2Ťő¶×Ź®ń.Vy9Ž÷Ѣϙ+„1“|˙€úS4aÎjMS†Ą™,ëY_…XL&ţz&!íDřúô[94üÚXú7Žű4ş,ć)2čąťuWq·çŮ ˘M9ÄL]ç2 É†:‹™‹[©UR¸C,żžH¸aá¶Ş[Ăs,2«i*ćL *óüLh˝Q ćřMöíjuűđŻî»ŹXž5-ďťťÂřâ "ŚĘ×pS«cvî}‘ŻC®;ŢŰK2Źóę6źkŐ 1+®’çđÜ€@>1ř•BĽ±ĹXÂúWč–9şČn]KĆkŞ–…ĘçľccXu>9‘Ü,–48t*nśŽ±"ë˝|ďś´‹S‡˝<Ľ^~žţ'˛QĐŞ/(fµáA€h~¤Ř»0/ŕŘZ˙J×ţ[qŤ‹;«°­ŤLpČLňĄ;”±ůěŞÓ5‘wëL•ü ~Ɖę€(÷ĺ˘[x– ç—I^‘úśCÝÄáÜŁú¬‡śČSذÚĘľ >*óŐYlŁ (—Ęęż» Ôr tĚJćű›ęÎÎřy’qŢ«¬ŽlM^Ăqh’ëÄÁč:¦űNkěW}wSľCÝ xNÝ1ł#_´ľç®7)?ŁxăkwA3Ů«LPlĆ•*ȇĺ„dŚľ_5­Äý?§k­Ád×ĹŹŐ9'¨rI_űđěÜ'Ć­űő=oy7ž1Âú{k›ßŻIůNúbKž­7Ĺ?ßS‰/,MŃn5çAŐ JzFšŤµ6]“¦ź÷}€Q˝"#Ň0Ýýiľ4pE± ČX8˘!ő\đ"»Ö·Hç¸P^ ÂĺC S­©¨ŐG«óîdŞ´y š]Şě_ ´Âš. `vŮ— Śx|la©1Ů1$ůĄżXŚŐ[PCYźćÍE\G€6wMÂM϶˝BTuh®üá÷´Ô›•;äOúÜ} OÜ--ńIëŢv\٬Á.‚-–pĘ€/Ľ¶¬;8z¤„D0Ŕ 2çU2ĂG©6ěu¬ć“Ň” ęz Ô|†}ȲďÂŔL*dŚâŠ‹ ş`Â} (vbĚ#V$R%·x€iÄn‰˝;‰üă]éMăM3îţń‚ŤčßłIĚI\éÄyo©Ľ_ř8äxđQ8?ĎwĽÓ¬éµs§8ę02psKF"íÝ‘>źŻ,a|rG:s©Âď9!V×î÷ďů^ývq]Ź9Ţ?Ń6V—NŐřoß*đEp¨˙]:Q.ooÔ Î, ˘’ЉX‡cÔŽ$=ŐÎçĘ­WnĺMr€[‚ĐV5¤¨Ë/8Ďţő°pÇţą÷N6-S†ßě˙Á`ű`éjŚ88f;ś6$áĆĺDܻ뱆ű´/â2‹~±„‹ „ž×3ŢsÉďĹjĆ"ĺ~Éź‘téíDäť¶F·WďéXí¨ÎňSęĘ_žÎ‰”‚Q†3,¤ZëÁ/ägŻ|žx‰°#é.šÔN´ńÝěäŞ Ç*ybc‰QTÎŤ@CŻÁHÓ›îJT ˘ţ„…~cô0UřŁ–49x}ksëŐÖvy-čއ纞ČřcO5ŕލő­›„€fť$“@ĆŇ˝_ČŘ=ŤśŁĽ÷°¸›×¶ČˇŇÚ®ľ¬áWzkŠ=Đ~]@‚OFâÖî—Źb ,ŤŘŞź„Xž˙]  ůfŕí8KöĹ8~Św…îÉŞZ@_ä’}ěÔÉ[+–R` ŃvCÁV•©ŕĆgb¶Ş› ĆyúQۦe„ő¶{m87ś­ďĂď-mş—źőů¦OeX8”‹ęhä5ý‘c´öÉN5nBnv)9ŻsăB4ŻaL'Žü#Mˇ/ú+•Ô›ĘQ˙q¶'ízPf{FĘ{€j®Ś{\|HˇcůŘpĚ’Ďn|â/%XuČ®\Ţ÷ŞŇŕ QE vLXV8›á-nBE8 çEA>•ŢS!˙2•ýzł‰\]™­ß‘EŇ8 Ö§±U#LćB“Ŕ+ŮA0íÎ#Ą ŘݵíÓĘůdbđ¶\>Ń9gŞ«2‰ŘŃä„ĘŃ4ó›ŮęÜJś~WcÉjiÂ8yůU"W•`ëvئ´­•pWŠŤnžÚđi\@5ľGŃçK<Ëš­ 6€$ô’٨ÚÝůöJJĐJB”ťťúéçľą<9÷%©ŇJëďěŕ¤üźÁ2+=)+UgóşA‹×W‘¤Í8Ń·Şě´Á[NŕäżőUP-,_ťWĂTÉcĘď7ÔjÂí÷٬^ Ô‰çfIă`ek]~eF)0á/sĐŘ#Ď /Ď3\–1dNâm%Š]˝5›¨ß.Á'ň4÷QnŔCŐz_‘%…«Eţ ÎsM.Ă ’Üäë3U’,1 !úA›Ô~^•Ą:>Ďč"Ľ8›­´vxď.*čI!˝‡ŞÎ˝şôa—¦›tóq©’Ř®NEˇŐ Í»0şJ-çâî[žâő¦Ę8±9cłTŇ—ĐîÚP2«vľxódž9 L/_@Ě0Źľźđă"=ţ}vś,vťë#ť†eŤF4Ć*ĺ¸J!]Ń”řĐťZ8žŐg­GR†ĺwç3ĺň"ĄŔ3yFň¦É­Ç$ő­óľČ'!,Y‘YSŞşyę ÝŮy°˙ ż‡iŇĽĹ”ď©ÚíPľ6‰đ·T¸ářͬ_Wb‚:`fQş¸.ô…H9J:?[•ŃÔP¤(bLď.˙ Ü̦}¦ď|Úgšť|7‚p?¦ I9í±9čĽCÜM˙Ű/+żC9[杸ł@ĂD÷>›ć7T)‰vśű'Ţö™[^‹ó$¬ŘdóŰ0—ČŽĄ×jÖńŃJ÷ŕ›jŰĆđ35Ň‵˛–?h ĆSsĹź?Đ$÷™ĽJ6p/L»ćqĄŻ%4…Ľ.čäkz($˙Đ`ÔrřŞĄĂú#¸~ŐDB¦7VŮ #©IöCps–6ă“ÖÎlGńďäIŮăŰT÷ňŐ\ޢx™f}ÂóŰŤ(´jͰâ<2ťvĎ»s@ß1—;ÓŢJŤç¦ă,r@ŮaýŠcŽË÷ÄbŘŠ’÷Ú÷OóXbiD›Ž%®i«Ťż<Ë=ń´ný§ >¶Üz9óľŰ” Î…^,Ęžr č­Ć“5Kŕ9x˙ÉÁR8šFW”KŃY‡Iăp2b Ů~,cćY‚f°ď¸’;ěE& áćÖ——W·9S×ŔqÎ;â#B;Çnzůęí’ąĽ§Ď,a¨XđŞÁéßÇ7†c*Ł«ÍďžŰďSS˝W€Aç€ń禛ż\‡Ě Ťp†–Ç(y®°5¸żŽńâëĆţH­˛źlS†Űłů&)_(Ö×đÖYŤO:˘Ç“ ś†[i«>PŽg¬€,#©~Ő.îŤo;a]o<ąÓöđŞMĐt—2ĆxžÖ쌄†BŰq«bÍ’{¶UŽz[ăZ&üýňlEÝŇŞ•qlAG´ůsýýfdhç?üM‰ĎpÁąßâ‘«h Ś“ý ’ť8€ŠPśş‘ô ťt&ú+uOćÂŤ9 ’b»QÎý6đrc·Ô®NA“ĂÉa‡3Ç6‡TÖò`” ,ŢĎĚ×ká­ęÍGĆ›Š\*Ćç6”˛źo ‚˝0P%} ]ęú!ť#&ŢIMCś…cZOz—hő “Ş¨ĐŻ1fQuÎb"öĚť©NÍ> aŮ´ dŞqßz `Ë;íc¨múĘ‹őú2yćq/•dż•Ň0Dđťh.łýíŻĆb¶ac%!y°+WëŮuIáYŕęFÎqIŕVŤüŢ ő[¦Ž%w#íšĆ‹Śť@űőMŐ›îoéĺ~P¶©•¶B“<ňsş|¨ą¶F%đ—Żë÷đjŃ™őęž@huő>maOkOĄ n[:jšKÜ+‘çás`ú#8hLµÉjëÎ!…ířýËŻőő îŔĎřŤ$ş˝ĄC?Ő“úż=ÁB>đĺŐ~.Ô‹ĐĺT^˘ód1Á<–ćăú‘řLŁH=Rěˇ"ÉVoĽó1r Y(‡ ťKÚ°Nˇ§ľĆt®Ďš—äVŃÇZ˙|9¨OĽëxß"ßâ‹A•ĆĎ.`î4ž°ć/]žh^ 0_ ĆţĹÚń4Őť˘ ŔĚô€ŻĘ€ĐďVë“á ĆI{lÁÎϡěĎőĎ^'éÎWČľä˝3bÔćěřúĽ9i{6SĚŃ‹¸żvńď™Ď2w’®6mkĎAŻB}Pɡ÷Š6Ó .,•Łp×uŃÖ 95Ç´,ž3VŮdýÄXż5vI6Đ×:F4ý«oWnUBX_ű‚ Ë üąGÚř e |óŤz™˙ĐöĹatJąm—‘Dřä BŞľ^5»Şh9Řx$= ‚Wb)zĎzÝKý¶} Í~÷]ÄPŔ×;Ž×(Ţç±_ A4üú} ŻĄŞť,xâY*ŚŇâmËś9~Ůě‹83ědá|OÉÉp5ĽŚÓÚ Ľö×'ŹÇ5 `ś\g™Ř3‚O{—YÉMĐ´€)fŻĆ$ˇ–9ńŽoČtŞ2éFć‘ŰSđ6|˛ŃÉűäE ‡Çä o‹¸Ó2AW*kéeý&ż6 ň˙CÂ-FuA¸˘Ä‘+=ĽŘq„*Ő™{ămÝk™6Évë—çn*°ö˙ňwl=!W †ą řF‚Üg6Ő&˘ł–ŞöÝŞą¨¬ nx#Wďr±f<ÝFľęĚ)Bz×TÝ~bŻŤý_žž8‘+ă+~šôvßĚN@úTťg–Š şoHŹĘ#„ŠŞźčŻäd…Ŕnź†:÷P=Úâ«XčäŐ'ŮlÁr©02ŞĄă—7ndHă—e̦4GsŤP8bťÁ!ť¸ă}gą»Ô‹m1ś`¶Šn‡Ż q§ŽĘ gkŹę¨a¸u0iC8–á‰î棗âb– ĺ  ă6ĘwX[­6b:ÓPÚĎRŞH4ÚR‡‘–·Ăn”7Şôhł¦®vł­Cş! ÖwÚkËyŘ,Úq‘Ç‹©Ă©*/‘ôłSµJ©8ěí,«ěĎ 8µř…idÂÂÇꤛä9šÉ“ĚÔĄ̟ FlZÁ}!dڶŇJ‚ľ1`˝¨%@ĂÂß“ąZUýxM=|Áaâ!˝śĽ‹O Ó‰ćńě`Ô"ÜXllÍ’}7˝h¸Sşih¤Â:·±ŻţÂSN˝ësŰŠŇ0EQeôŐ’/ZXQ›aŔKÂÍmŹROń¦ŘŻŔînÔČĽUmÝň•+XŞ7tP 4OU&đŽčoŚ&âÍM´zéWň%+}Q¦”ŻiŻŠ‘~šš%˘ß˙—/dŮ vâ,.ńş„ô{Ű”=ZőâA+Ôm˘aú8w YtËŞËwÂhöaíďú°~ű­íâD”±?XX3ŹGŃ2ö,éţÔĹ'g{‰ŕ1C˝ˇ|á)ndik‚Ëřą·O”6Ă´`nY?ĽçV$ÔĂ*Řýz-ű}„ŠŕË«z's©ys·vwĽXڶĘ3ţcZFP·Ö+T}Ú~Â-]9ż_¬ Ü”)éĆÇ˝„ÜMdrWiŞdłSôh—ěĄn8›…ŢńĂ;\ňO9#ˇ Ô< °÷ZRe.Gă~â®c·‘îě@H¶DîY`îôŻřXůM9ľÎKTÝ~¸ ëĆ@x[şőrYşk’é]ëŹRý–ř[Śo:™;ż‚KD›m™1F ŕ&2qÜZ-¶·*ŹŠ5ż‚P¬^±ô´×Ë6wMűě´!7KľČ_.Ő­©şM!§ü›daňBËđ”–FßS[Pł,ŃŁd›_*Ă89aË«v.fc»`8•®Vś: %Ĺ6n¸-)ăIî°ŇéY“éţÄż´!łv:<›ŃßáíÉëLÍŇ­Rhë9ŕŐô¶ găUôÎĽćÓ_űxs÷ŹÁůXÎbY©ŇĂŇúV‰‰ŇA]6gČ-O ~é1Ö9­q{F~aĄK-]ěmÁĆäó°eżŚ¶»o=÷kâÓ‰‘#gµ3éřÝYb§şżČ­'/ńw×·Ű“XÝ6Tµj®Qő®0Z ÂâˇÍDÁ;¶ŕŤ6qÖ_µwů…ˇ÷#b‹Ůâ„q]ÔŠ<çź„i˝ĎPö‡V1Ç„&[sĺç¦ŕ\ ćż(ę IŞc‡ŁdűdBkE6™‡CaG DÄÎŢ˙"|#@ęŽęńŃl|»ĺϰj[˛eł±HĆşYŠLLFUĚéđímÉŠĄo[âÂ2÷rsšDEŠeÉFnGĺŢ&‘=¤hĂ^»ť7˘ôj-8”Ť ý0×8óX‹x©…ôĎ_wňą·|ĎgčÍdťS Ę™iŤDގHĂҡÉ[De4@v&‰ ď‚Ö9/Đś¦m|)Ŕ 3¦® ßşaZ3@ŠwR Ľ|»ë|ö÷Ǹ\°±×čď¨ú,Ľe¬±^ÉĄQ"7†ĆČgŽCĹŘ’Â?žŕ•dĹKOI­Ť¸dĄ®přÉĂĽÖ˝ÔŞ—Ľdx›şťÜ>űG“áú«Ć´¤yą!÷Ń,Cľwh#DDVÚt•H~_¤1]ôeI€ďŔŘľó ‰E`„ĺ™DEÇ˙‹rěĚFy 'eWď(8޲iÝ‘&B{ ¤ęaxaxőÇĄ\%–(ăövDuđĘî©wy3á ë%ź­VľV‘IÇťw^ú®üfŽ~žP–ćú4Î"Ýů˛UÁzŕ­Ź‘\f±2#i\ç+F ¨HBăH# )­·ď™G`ÝÜ)ČŃe0ăW¬h¬ż4Túf}÷Ëł´öĄŢ€ő·x€ŚVŔ‡™Ä-™"t Y†P©šúöÚÍí‹ćÝŚ·,’ĄLńŮŻO&Ű×đnÂ"FŹéËqqî¨Ë%XäąĎMĽ¤¤fţ“S]}qíířô(‚ĆČw<Š{=™jݵsúŤń5Dl„YB"ö‰Ąő&Wü_$…¶jë9cöţq˙őĺ™GŢ‘Ş5gčőűžĂ-r*Ľ‚+đË|®çTy”ß/lj*¨EŃÁ’¸iS©,g“ş/r¤(cŞę=Ş™,P{Ű5ú‘’§U°ZˇÄޤ’‡ ÓPň*°»ŃFü*T'¨Űň3öN±ąu˝řRĽ=Ň1dIëB>mĄ3ó$C;1Ç«¨'Ś+ă“=.¤Qú¨ł¤[`_´­_sGţał˘VsjE1š3ˇw×0˛ó…Čhř3…ŰâzAâIíDń1Ŕ ßSî˘đ±TČš…Ť KŰŘŇł1*ßqSËŤ!öů>+UeXÝ™ů1“H10’ćÁOŢóŚH¦gTnl¸%fŵňg§ŕ› ŇÝ/qf`µłđU\ŚśŞĂ>[ô웿íh\áďďžĚÉ÷=;‡"ząy.Ăć%“ôńźDžČ&µ#AŇ#HîŠŐw&ř sZÚ§Ű)Ż5s°ľ’‹¬ŐîĚa9 (Ć’µ»«ď€>ŽUá“˙?‹¶Áů¦ŇßOyq;ÔjDz`ŐHíl ¦:ĎWl'Ç˙Ô8ő‘-Ŕ»¶M”ĎŐ:\,໣P–9:GżßÁbÚnĹeź(É&0T˘óśĆąÉţěŐUr2›Zź[468J4Őqx4MÇgÇ#NG3a Ż9Ş«H"úyďćçhD!I‚—Éľŕb 6Wó;o«Ľ·67vĺ¶•Éó häÍ˝8Ś[Ž'r*Łg®•ÚX˛ţ@ÎŽĽĺ&ĘC«.x*ţ 2s‡cË=ő.‘ňdż4­!řŔTÚ ééJ ŔT%ů •´C)"NŞďhr§­Ó? i:zÉź‘-­˘Ť›e.Ă*ĂÂWÉ:“[Şĺ…Ü:Ď@<]KŚę`FqçÂŘł sŻŚŁŃDVx´a·l¦)×ÚUv%*5ëÚ<^ki“oCßăżµˇn íKťß+Éz Ń9J ËN#łĺü­}{ëď'ČU2/cî«A‡Ż‰pÇ`Żč¬jaËuŔ97ĉ0b (Ä!NąTĚŘPy]»”EB,qŞŻţî%˘$e/FL«› ćŹčű-‚óqźtA÷ňsśv).ŐëÜŰ·ůzkŻ„0f!BÁ_Đ#q)ŤZTŔ_,—źßŢ;tRqJWB; ŕßŃśS–*h˙ĺâĆţŐŹáóCäęgKsŹěÉ{㥺lüLżVŔ džŘ[ř3Î˙÷pő: č§U üeH%»@tµ¶Ě.«ÖšĎWŃzÓ×řBďÖ–#TŻEźYá a@v6çÎ6zż*Ŕ‹ÖŤŔH BđčÄ•ňŮÝć©]‰ĽbÖJĎÝ“ąś×|_ň!šôˆ‰f´ú]ŇűĄÚ\Ą_- ‰†'8±~ľ˛oÉŽ áčŇúˇkżŃµ:¨ÚčŹíž–!D"ňeAq 2žPĽĆý–Üľ8–ł¤dÇ’a5ů _Íx“+¸W^ç úu"¸ĎăéšC‹‰úßĺc\pF.)ĺ>|ëďD$7„‹F4ŕŤo6ďÔSí¤*khe7 Tj}8RHóQßđľ@€R •¤xťÚąÄş+!‘öµç°¶ś$®áĎ=ć†p›PJKc=&ŞX 2»‚hÝÎu#ŞéË•Ű;|źkňHNyěäŹĆid¸8I ‹_ŐŚëoŮřą°ýČŰŤÔΨRĽcÁY—PëS€ëra3öŢÝź\{łŔmŰ01őN»E×ÓrmŐťDY'r× „Č ˘Bţ»g[i9.¨4ÖÇĺÜd—%0;eE¬FA~geýč¨ zĎ?§e-'?Px>hgbšB+Vcb[ZŰtkkĎŰúĐĄ4.{űUű×n”ńi5dä\ĚĽ°uhź|X\Ă;YjA†čŢl-ţč_®z™'řľC2ăŔ[6Ŕ„q¬.ż¦Ĺ—…-OúL€éĺ.É<%ŘpRxF/¸°o»a?>ys»Ő4@עŐͤOhNđ†uí Jm6]Ť1—´VuC&µĐ’˛žű‡ĄSÖęăy¤‰EAÓ— sć<ĚÇEw]±é¶đwĆm>űzÁ”d2ů˝°ŁBżí1=E\ő!¨‚¨ŮNˇz„^Ó@¬ĚÇűTĄŢi9÷ č|› ňÓ")#Ë#z 1©źŞŢ yžĹˇR€hOőtź«ó迬"ÝWîáë]wĂĽ¸ŕQ&v;××Iź4Ž1żâó¦"·MB€:*3őńźŽJČ<+“Rń8Óvë¤Ţ–lřv-ęyiéc­¶˙ë_‚W€ŠťL”eîGP¨Z3tŻO«Ă“1†’­î•›ŹĹ§ˇ0ł v`fOz„DŔúCO™|ź+ÄâĹň= ±…^ÎŘV4ŞăXĚçivo ¦~ŤŘť>ĺśÜÓÜJĄAAľSˇiVř†Rx N䪝ĆΔ3Î|čSDEQo¦¬îĂ…ľn[·ŻďňăÎ4Éá ¡Ů\ó G€˛v ve@ގŕq燤‚2J±4ŕ4»Ýhgöá)?›µ.[ľşîJÚ6E¶ńŚn# ™ôůdÉbµkŽčPáÚ¦ĚPß–eWá2P.n<›ŃĄĚOOđO„‹źŁżZŤ^ÔŮµŠ¸+Äć§gD“)zg(śÓ^Nďă8˘%{źünÍ2cŮČ Tµ Ł'<ŚGyô=şô@áÄŞO]Ť°2€X ;jhv~"ËFĚ‚Ń,›±ÓsQŮ9ÍJ奬~¸v+[hWĹ{3MÝÖŚĹĐô¬!}ÝŢş6"<§m2éŰa VĘą‰ěŚ#Ĺ"ů¬­™A-_ß[Éu—Lóče vŐÖ–©¨V*%â juBÎ ępgVŚĆĚ&˝˛ł…|‹,Q‹•°:xưĚN}¨˛ńDHiě>\tV®›Rďď˛ß¨x „F$Q<ě»ÚZ”Řv‡µÝ)>5MJĐ&+~ă,iÝ?˙Z¨ŠéŮ#Č8L—I ÚΤ$ÜÚm Mxc3óeg şIć§F‹a†ŘW…Re¨[䦙 ő )ˇý@™mŞ<Ď|%eŹ6Dz?µYzîő/uňšě·-¦îaŕnpďäζň<&-Mh—ű:ˇy3 ‘Ňţ„ˇ¶Ř-ÇL9N´Ű·ľďĂŘŹKq‹7wç Q†ĘÚ§üĆ1«?*ŕÜwÁB g”¸ž±;yŻ1ě‚ő#LJĂË´đnIšŁŹ„˘(­ăÇAĘD6ç5´ĐYą™˝ł„HŘu ™8Ô•«aU`žŃ@ę“:‚ ,übcň¸|*~žpľăŃ ťI†ĺY ^u+řXđ(•-çŇpH9Iź^7ńâéŘhýÔM¨bťq…p1A»,hćÉR­<2Ň^÷ăŚň'·BţíUsuăa¬Ş¸‚ś;Ń)~‡˘ĎćÄ䊶 ÄáśN çQ˝É—ž@çű€^™­l0Ëŵ*±ýŞk*Îć•ĐX&sj‰Í~Q݇éµ7ŞŢűDò·ÜŽcJÂ~٬Îp*vÂsý—»Ëę^$NÂjO*Xż¤&˘ôőş·˙9ČPpS®zőęeúĆŕ!ÉŹF˝ŐEÓW„\§(ąhľ(9"e˛=Ptxß'|×ý˘°ďŢy7M‹†ć‚bq‰—’z5†dY9ŐqţT‹˛Ě¶¨ üTfד/ń{ľ_Ż˝ Ŕ(ÓTűÖ¬Ó ô2‰n×Ďă6~Ď˝[z371«×m҆Ő4ĹCÔ’©đŞGŘ Eé¸NĹ$ŕjďָᏰ$ě; r0úɲě@¬ÉŢî‰wP?a ló‘á#§'WrZB„­„%‹×ťŤüűÝRNÝk1ú"řĹzxˡʷ•„ŔŚMźIĹhÝŤBˇŞĘ_ËÝŁ˝Z¶Ŕ’‚^WáOÉ JűPň&–•3ßARâ§`5*ý‚)¸5çń¶ő ,Ńż·S”ˇ„\ÇĘ0’Ö/ŠţŃ% ˝©ůׂ:µĚ_#<ççt~¨[Up73ň“ÎQcüĽÓáZ“ź9°Ś -HîâfÝP®­r‰·?¨łŻx¶|<ĺ¨GňU˝âujJŢüÚ±…ľęçÂn,RS3S ){VtŻQĚ—ü„n™Š…”€Ąô1«¶jJ}wý'Ż[0/ÓđÓ…uĄĘ¬îN|¶Ľ­÷Ĺ,µz­‡˝cě·í„,Ö+ç+0©UťT¨Tl»-8Í:´fv4ś‰)ĚQ-Zz9F}–¨’HQÜţß’zîVś8Ş.h´Łvú§)Ś4¸Ař&)JNš_ĽKL4Ä©Ń÷”ŔˇÇ«Cü{ť”Ĺ8'h¬7ŹżxxQ.rUuJ ž.=žńY!č_ÉŤ~&ëżÖ8´E\WI™¶G4ŕäĎÇ”÷źžŃ Ż[Ź“.§Ăaf˙ µeąý·™7˙4!Ë)ňăVó’ýh@ÜJOĆ…uŰ&莭6Á÷üć­ÚĽ=Cy•°´™`•~TG1RĚćűŁČ"0˛ť Ľ0ÖŢɏ˱ ô"–AWfcÎŚJ*˙âß¶SOi¸S8lą…ËiÓ—§Žr”LUi·=€’ŘU*4DŁč™}ŐćÓ€>hÚé~2R”¸­ż5bí(«ż1WŽ]š@hÖWĎMÎ q‹)ŢŚq–ö0â—ĆľQ˙·çŤ ™S!©On¬Űŕwhm›säŠ >ě\a QÇ2D´“6J_|pąŽSFeR2SęWńÜ–ff˘V ĹĐ@dJ¤zăĄö‡ąÇÇú†#Ľkťż%ĎV-™ßĄ”>ş‰m3Ę«A@9˙€jŻĹzĂ"ŕąRnE˙řĘ y[bs¬x˘‹+Ľ5üż¨”VľŮčë ´'âô‹?ĺ<”heßřŮĽxÓs©Nk±}Üáµ|#P`oZ4íeT×ĺ€)ľ,s^JřŁŇ¦›c9ĹÁáˇd»+0ďçÝÁkě:˝tĆńĎXÂŚ°aô|O&߉TyąłdĺÎŞÚóÄźłor%ž_”¶”őí\F@&rKŤĄŮnp?!+ܱ¶ţkV59aŽ©›mKšű\ÚDđ­Ťí=Ö©'XŔ·eŁ@´3)ðE$ŕtçy¶G -hoöyňŁţĄĘ’Eź`ŻKÜŰ…}ż-ĎX÷ZÖtťÁ«ś9ҽʊżtÇrĎüúő–7ďÜQ$mYQĚĂ»ŞËÍ˙ŽP"ŁŠ#ay˛EÜ•Đ5öÚ9I8ˇ<1ö©Ź0°ÜüăłŔw17@§2¸čÄv*`Y]ŕ“Îx®kšö¤"0ę,aíĂ aŘč ExtČ‚u,Ôq<őĹŠč»!Ę`—®ş=3VK’ŤHđ9ÚĘ>ź,1Ň‘Ţ5Âć‡&rŚĄ]“@Kř%6íu ޸gnY3kÂş|ßKă8‰ÍSŃ… j—ĄŐČd@ńŹ6a‚z˙ ľfśů3?t'-,a"aÎRër Łu·Qk'Ŕ|™8˘YC‡a÷8i¦un"§ů<”4r]^r”¤Uđîęď(X?×+ÝJxÚ §ţ†4E‘Đ&(廒 ÄÖú{ćdÔĘŞ1,‘U\YŞŽ¬Y&(ąX˛%Qň¬óň¤[„"ëŚońcÂ0d1w5g¤'!Sc˘ Q‘Š5ŮăźëbXÇĆ#¬JŤ,U·–š†TĎĐM˛h@Â…5Šéńf3sV-N4Ę}"¨Pk2ç $”šo9ő±r”nžzgí´ÝexÎü‡°x"¦¸q›8gŠŁ˛¸©[¶¦â<ö  ĐA–)gâŢz¨Ňs?*âǰň„>Ç;«É-Ç! ú@'K3Z;~ÓνaŁY];bŤĆ!J`îE ĺE1BjX‘ŮÝőa¶¶f"űPľ[ÂŚ:şăcµ4ßEźŮÖ÷ňÉIPá.ď|!‰O_» 1GKÚ·ůź¶PĚŞjŇŁVeR‘|ćŠü«}Łx’”nôâ°(śöëq»ě!ţCT‰Đ€Ł I)ŮĽí´_žÚ•" ¶Ö’3đÎůvză Löę^ę¦Ô‹ďńź.É\UúźŔ&4ýŕ¸s~Ý=č".ęŽÔÖ ŁDG) ÝÎőçHAŕRľÎÍŠáß8iO¶Ą=ă°'' Q*ă‡@ś”^mě`»Et®KČhîÎÍ+˛kíHI`kÓĚőoФ‡ßxŮĘâ§ůö IÁ'§v)Ý^©®2;i_mĄ.q/Éő•Ú» ąyo"Ó°ŘîăáqŻ+ŐZ7Rě˛+9ä¶Úő@Ť#¬ áëöÄ}ƨ¸™LAµâË«nY -K"Źĵĺ˙×čłËdŽu~~ó^Ë42š!ß!™Đ„]eůŐ{ű桨)­6sIţsË%Ł(8R®†8s&Ńb—ě&îô1ĽÉé• Žjřč6‰ÓX;çŕßfßÂ&”é`~ß ÖTG¨ŮبYP$9ˇôŚr…üŞ@k˙zŁŹ¦´l¸™čІĘŢyó f5Ű[L'cáüă<~¤!]t%Üß­şÂICÇÜ%»¶[RÔ"DČ»Ä!ź/–Âg\Őđ­ ,ŕÝÉ˝IH†í;š ­ëÔ»@`¸Ś˙°ĘKZ¬‰˘,Ę/·´şÁ”î˘@°dťv˛®G>w–»­y"Ě.Ş_pËëLśę<ĆĄŕąC§ľ^§•Żŕ«&ۢ“¨s¨ŰńZ9üDě?âiŘݰQÜK‰NfGFżŚŢ ˛‚ďľď~Dăú[˝rÔn„‚łsÔ{ÓôDć\Ő¶<›Äú¤)ČŤkĄZů[-ŻGZ÷«ÔŃ+´YŽzőAAŃŹ 6R [2ˇ´SRkŘ|E÷ĘצÜĎŢdböVKÎëř›[8ńx.ŢN8Ůh˙ôű*Xä€d¶~™äµKq žáÍIÉ,nIňó 4sV >·Í¬Ës÷ ”Qó»Ć6 ±eކ.Ľ cQżźJ¨şkÎ/†|˛D`¶ŕ˝2ާ ;č7XZ÷‡9wUŠB’R¨í:ĂĽŢŔűFňÄ Űs»µińďćËîjˇú˛•Ymmîĺžl‚kŐŠr8µôwŠ ŽŢ~cYŔČ"ńG9w°¸Éˇ^é(ÜŢ›ĎÜ‹5“\XĆł1ĹTš6R"p$@Ĺ's $AtŕÔńü•¬î»n»-éÇťĐóĽMęŔ€ł5VyâUž‘î }#f„är2Ľ¦ )FÓ0·Ć›Ăěe†Şb;Ćjů¨ |š ÚˇĘrC‘Ň3¨ŞđđJĐóF"Xć4ʏ'łZý=ne1l;ŻSÂgÉČ d.aS"çčř‡>¶Řą 6xPÍj<ŰTd»Sńľ°#u$Ľ={ăč}vŔąÁcO9huǬĎo˙Żđ ©,ťľč‹2Xś Ţ,·:«Ź• Ôaśz(ĽRŢuÄď|¤Aţ«.ŢKpűMáńú—ڬ/ÔĹŕ» Y6š;Ń>aÍpaʲŮ`OhĽe›^kc] č:ýĐÝă÷€ĺ\»żËňâHÄţ-ÝÚaý)ÝW‰Đű‹ÖôP%WuwÜŞëlOYâ$§Ů!ÉJ_ňě%«;lŠř bú`cüń€ÉíNš_éô„ĺň*tľ¬'Vů[Ąî¤Űfé[Y2K'ťŢĎpIĄgn‰„QŇH]Ěë“Óo"’ąSřšĚĐ}Ŕ_ČŁTď1HµÂŞ€á,éS“y›MÉuâ’Í7:U™JŘZŞSGhÇśX0ĆvWőÓRrÓĎ,)KV Ťj ˘Lcľ ňëˇ#ą©Źíťü•O‘÷‘¦I´*ÜŇj>Nŕ4¦‰L}PS;:âÇ eÉĺü[Mfđ˘Ŕólă#íD÷]é *“I7 H3Ułé 2aFcvMý E¸Ę#/Č d:ŞRőímue…Ú]  ‰"Öl˙3hź;Á…ńí»ě.5­ČD–ý©đOW•ęH ű›˝ef »Ź!=­{%´Ä㎓ř‚ŹËşĹ1ËMGŮ{š¬4ľ’üw =X«ŔL©·›~KĽiMIľnQL¨»¦ żRŁcţňŠxţ4»OR2X\Ý9E„Çĺw.Iď–„˛bŁĹXÇ˙Ââďăs;áíwĆâ¦$Änô>‚g~tńĎń€}§%çö+‡yĺű×4I>ß ÝŠÚ%9ďáZűŹÄh`ţţY<–[O‹­y™’ÂhĹőh›ü „d0xďXÔĚć9SŰ&„dH#nÜp·Č%ÝÜĄydW: p,2±=k^ş¬dě6*\Ő’Y§ehÚ ç†AH)t żr1m4A˙űy@|§!7>šŁüsJ-§ MŹ„°ŇăyúľŔrş‰[MçpVÜÖšň€tđcţp‡(ĹhŢ •Í“†ÍfYó3łx4ŕÁWĺw̲Ü×Üđóą­¨ Öľř{G‹I€šČňëÇí#ĽÔ&CŃÓz˙kbś<\+¸˝†‚"%E˙öłŚúŇ ¦uť $$1?'ľŕÜŁ„ĎNmy®›OĆ;L¦úN§‘íoôjPË[€'Ď8ŠázćľŢŁčť#ŁSM§Á;ä$} +1v´+ýˇ?öépQ¸F`ôđ¸|˘Ფ鄻ôqĆSHĆŘkÖ/Ž_¸a‹ ŢhŚáIDIčŰö7ěáÔ4[-´µőKPÖřÄÎX{e#nŘKëzß¦Ř %xčńC  ´ňŐ/!k90¦ô÷Őď›ŐT ? Sý 6ž÷Ńz(ۮݥŢ>=á ŕ‰P|J·űŘI´,ß ĄBŮtIřŢmj/C(Ą<ăşUg—Ą' wYb9dضZ˘  ú#mĎZ8éčřöŽ#ÍéŢAő;s›x—ßSÎ vű¤^Fę>˝ŹF׺殠•3Ď%ĹJŢ—Zľ‹P™´ÖłšÄÍzNďQ€Í·‹óţŔčW§H1”#Ź).ĽłhJDÎW­|k$Ć{ě±r~ Ő˛lžś<Ôŕ袭V¬ Đ˝ęXxŐO"ŇŽĹüUh:Ťu‘ZQŹNg»Řëż°Ł1L°‹:FN1ć¬úÜďÖđ†‡6LŻ­ŢJŮÓ$ô^S^9 Ń9žţÁ&ż'ýިç¶đ ,î,ÉĆżŰ"{'ĺv[ćó¦ŻÔq~‘ŰGÜ @}x’˙$qČU÷ȲFc×éĄa—kúŠŹ(°P_>üĐÜ7ĺ@[\Śş9.śů-WĆŘG˛yňččŇcš ´ĹkŠÚ…R©,”:Ś3Z‹lVÓ6F±ć-ü1ŇË[šŐôűću[cRąhM $ó«ňŁ©AÎ`?ŇČ=÷ot2 @¶nÔ•.Â{ I$’µ×65B*-Çîă±f×(Ő«#S@nf)´ ;E(´”ÎůViwä™dčŽî ™Sů›§‰0~eűxŢŢ,ńé‰(©‚˝dG&kR‘Ëýîňy©śŃaw§‹¸*˝çP•Ď#ąřfěBĚ~‹Ú®ze«š­z´·ZĺĚWÝFÂÔâý¦»Ű}ARĐŻŠVünŮ®­#{(x„}ŠrkgöKőčŽáőiĽ.Qę$4ϰĎkÖ%¬ţĽŢcímŰdüšŞŮ 1 M˙xę-$„™âbŮP€Y,5G_í?cµâ\@¸'CČN ś'[´‹ßâ%-ä·ôů˝×¨šcŻ‘Đod_Đž!žÔÖ´ËôČ5™×É$§^ťňcŘ]´ť¬[}ý#1ă©هč4ž/7‚PĹžŠ@tA%®‹b›µŰSěŃłT"s.$óÉă,P>żĚÂőQÍ$”ů~NZŮLW3ďMşű±Q嫚†ŐŹ #Fť:ÉĺrťxcI7ukĆđĹ€]Č™łÝ~â ‰ b^ßŰą,8@Âߍě·\K›oשŐ/c 2ŕă€8mŮ.-4;‹®HÜ]ÍVľčŰ‘1ÍdLz-94¨ŁWYrvü˙B;Kµ©HÎ6X4µ§ľÎqŽËs±čśáŠž»ł©ş%+i©U5˙Ď .ˇ9¤#VeĹš`-`X޶ĺ˘çM-„ŃęŢlM8#ţ9ó÷ĘŻ˘7Ás»ĄědŤ)_ŮBW0óó‡ł1Žäňq%9(˛şĽ7g„Ë–ÝD‹Äš5÷a·ż°˙Pµ®górcŃoÝ ·Dű<iQ´MÁqžÂ”Há7Đâfké?ëÂm+ů˛7Q6ő˝rś]v.ËĽşŤÝý~ČwÇ9“¨ĘŚĹĎR d¸:•~A{Đ “đŮą:‚Uy%WĹ_đáHÇýP”ŕIC'ßkaÍšSBg©•üônÝ©®ŐŔĽbúýŐéN1>ćŚp'ŘĺÉa' Âp “fÓ7•ľHSúĚřb'ń6ŞÚkPqęŔ”wy$¶ŕĎÓ ´Żúčr]°eCsëČH#„$čHä6-öJ \ô¬Ŕ2ý¦“ŇÇ@â¤bĐź"ĎŔ“–VüR{ÁýntޡˇR]Ű6Ąë0ţÓg€ ©{_´B˝ ,ż›ąb`öś{%ş˛ď%5ËťlÔîŰ ˙n#ă–ş źDţ“VAć&kś…Z›<ŤaăŤfw#*hrXLPS|±+˘nią_cKKÍH)(@w7n]®×@˙o˘űá×0â°Í3č—Pۇo_®žŠ¸ë¦» `΢ŹiÝɰťßă1Ý4§˘÷"äćĆ,®łžÄľ>Ňcţ âş×00FĹ ¦¦Íđ'ć|2ôŢ‚;s:yB˵ko6·Ť"B{˙Yń(TîkD K:÷ç1żŚöÝ‚í ;s˘¦ Á‡řy7ăß9¸S¶JâW˛Ô˘š'ŁEnŢQ]!˛D% ¤‚zhŐni°Ş—KxöxSOZń­EBʼnľH˙Ř9ĄKÎ"ŹęđŕŔ=š»…1¬¤écÉvifG`Eö™ˇ¸ :©Ţ>U°ĹŤů–Ey¦žAäGw¸*Í4Ü9ş O{#G~Í5ěRǢ†ŻĚËĹÄv·ö9 g:µLÁě˘*٦ŕŰ'sšx=čűëö·4kÜt;yÚjŕ€iç0jäÇQHň&,đ€‡śWBwyÜhěwă ľ/SŮ8dsŁÍcĽ’±“ö0%ţ›fxmŁ˙Ú¸°4×7ĚKL2Q—LE§ăU3r4¤ÚJÖS¤j7×čD[ď€ĄŞ“ň›y„ľçEjňtY!䋎Őđ‹Laß.Ş4ďPÖNs^`9°[ÂCčŔ¶Ź /{Ž\9ł0NŁ•ö{ $PŻśÖÄćZĘ ?ÁŁ­I–˙‹ý©·˝~±™V  îÄ w¸fgl׳EUń”=ç‰) yč”í WŁţk›/QŹf{ĺß*˘[®=GÄŃÂeqß`w.ĆSĆĆgŕC1Š,“čGľ­'MĹĺuţôµ[łh¸Ëb‚Ęrˇ·âÉĎjwůkęaŃ`WS;čäOÁ—çú˙%$“ «˘¤Ü‘hľžĽvQç€3A€*$QôT:ŘŹĹąD÷‰‚Ç=#Ľ™żÉÔžWÓŰşr6*ÄQꪯ"ůˇ”¶/·;˝ľŤˇ][ąŮ5ŕ€ő[¸;§h-ŰoĐXŞłô)L;r )ětcľŹŰł¨‡{< ŮäöB( xrĚŚyŁ®ŠšúęX˛Á@‘ćĐŢŃ«ĂL.âÖË#t„]7Ďlď?>0(Ęđm×ó}\ˇ‡hmwŃ~ŚŚYŹm—śmßhq`D˙ţĹôvÂçžÚ=\ľksäëa%řÄw,'°i»‡ę˘e—ż„ :bP ZëQ˛´śgĆ;i@Ľ1[”÷Ä—R¬ŻŞŹQă<¤ďJńÉ'Ç·`„čf›+ří=OjŔŽ ~´SrÁnüh\i_>ąaĐGF [!?˙”Őt qă€-ŽÍ|l°${ôJyc’‚}‰6ŻŢÍpäńŚÓ=‚/ŰĎy×6x:4«áú\n ÄÎż“›Öď{ŽŮŕ×^˘q)}ü‡wů®ĺYÜ!»B®3)áđÄ{·NŤ”ÓíRę®ë1ëřWä=,„ ŁÖ{ńäýçaµ7Šk§ĺ—é´±ů)†ů b8łRŕ$F$Ŕ:ź*Ĺ^żťďĎů. aů9|ér!‘,ó×đ„đÖŠ˙nŰ˙Gŕł[^é¨-ŠÓᆯůJĽtEÚÜ‚đ>Čä·^ĺ­†2·ţ^öľewÉÁšř’S•oäHáÉŻÂk8ëPXw1l] RYîuą‹šP–W ­¬ë}ącÁkÓDô9Ľ|ag;)¤ţ3Ťmbš3ÓDż .A‹Ćš]ç‘»}¦‘„Š]Wŕb9S?EstéĆZ ţm_{Ýö\ˇW©W·“©R6J~˙·[p}>î>5G>´l„Ü7<ô š¤~c-ˇDžbg|É.N´–áŢĚ`,Ęş·{ÂśCá&–T”úűj~ŽĹßţ¤˙v·7"ÎőO`ůFôe&˝Vâ0ˇ…—;sşŹAä÷ň‹ö}šú#.ĘŚő(ß Ě8 d^ÉŻ†ľWSŁyp"áĚ[b›ă/v·Ń`?NrřD z+Wúě«`éŻ`›ź±&÷ŘjGm^ˇ¨ˇE‹Ŕ4hýä Ç+QžřÜT:L5® nŔ!¤94a0Ň4Uőź4–X;`ěŰűČŁä>ţ´IŠ+Ąó]Žfk}Ťém±)f†¦P*äÝÇŻ Â!Ľ2Ä$q+_ęŠ ů4Ô™řrŃbLď»=žŻKę@/Ľr«0e¦dCmĺ^Tv"÷«°qćŽÇBuHGi¦8Á&â€űH÷&íÓžäń=¸ýý|D[ő?÷EÉKčYX*ˇ«2ńâadm©í¬SÁĘE]ÎŐű,€DjxéŠMŔ^ó†Ź ďü65P<Ś®µĆ8‘ţW^\Ą ®ÇT] ë aů˝gĆwpRŻ˘E¸¤fEđÉ™Y7Ƣ{\JÎŮ˙0±xO×<‘pRaQeÉ’sKęO:Z–ďeÚ—Iž{,ć +tü¬¨OĂ}fŐoK1ŠăOŇI@f°«rŕ‡#ŠŻ…ď×/ď’Z˙Ĭµ®ÇŮá¤$śµ,¤îďt*ŇI%ˇĹ,:p«Ç·I}8XÉěňžXń×±e|ń´fBÜą˛ďňŠß€Ăo¸–í\?gH5o…†ďřLĽMŤdMĆ´xŇĂżµ+Ç5›«¶ÎŁta önr)ihÂSqž1éŢu—¦SG„Hś:0 Âş™sĹ >‚{â„»Í7d©´–“±zpú¨5¬ÉEI%Đx–^Sů8A=qyŇW'RyJř°!:äňJuHÇ“ŐRŤM‹Ň¬k ýč¦ţ–ľđÚÍmŇ \•mđü€7…r ‡>Cn#Ł÷×óž÷fÓ,1\b5Gđx}¶d ±űIňjî»}¨=QłngD´PđK[cįТ Y0pÄÚĹĎľ–=Ľ¨;śG#éVM˘‚Nűδ—lŻ?Ă,?ÜĐüKŰ9–tUvٱÚăÔÓ.‰Ú i«ůŇóĄI¶ü†y@‰÷OfB˛b>˙=5ÜG%\·ü Ě ĺě#)&oĹQAB‡Wą Ş<{@ęLáë@üˇµ4ăŃ]óČ=ˇłbÖ§!â®Dx•ZíŔbR¸S±)W,Vľzqëőő#Î%¤ #1“hő`©";Č1róS°ęč|ŚWnĄôÓ×SŇ=!ĂÝ{ŚhˇĂ ĄĆ¸moŔRă•ýwkhŚNuĺr„\ˇ›‡xg^+"ŕ7¨ęfsU#Uň`pÜ?š˝?dĚŁFŠx¶>ş©f&,¶W·!ÁĆő%¤±-xnŃ 2‘Ľ_éD-ˇq¬p5.¦d*°Äň:uHuĘ—vŚÍË*±_p,ŢYŇ»ČBśÎ0…~9¦o_ţb »;ÍĹ„ÔâĘóĺ]y¨ĺ“¶ 1"‚)Î̦ÄfěŐNĺ–÷;8ÓaÚďŠ:,¦¸¤6ĚĘiw)ăĹ`qßR®í[/AQ.hĄkýçýŠąoQĚpĘ,ۤrčŐ§TóEcé"Ü4Ćčąt+äMaŘy|.lÄOÇÜ#eĹ*ŁĘAŐaE/[^=~«‘7˛%TŘ—ŔoYâéZ:˛qLw+qDC-:T%îxëndn®—ٸB=ă›Ö»5ěîŮ™ĹÜHśËQ© @p›+ýžä°?"—ěQřć l:ŹŽ1Ö?{ůînËĂ$Ţ«Ś ÂN s ărĄżé8'ő\” —rBëK·ľş°ąvϦBÓkZu6Ďu)‘NŹ”3NĚő/ĺĂŐ·ß7اx¨fîç7Űł*آg˙‡fĺĐgěk'/χ§ě )u»bż±\ĄG¨~Jď8ä((ż«âÝŔŕŰp ětƨŔ4—­Í®cŽýwţ±–&ÂôČ*ÓĺŮ˙‹ŃQ"Đ–“ľGÉý#<aä“Ć ~LŘÄ@­QÖŚ%$Ń€OžKş˝H°(;śjZ÷ćaë×üčĄ=áÚyäć-Q…F†hAg»ÖF÷ćIĎ(R_#żf—Vť¬öŐ((OÝbŇ ŚűäÖcB07ŃZ€ř-lyJxË›i¸RËX?â>ĐĐî W§ňö†SIP\ŰłšßÉÁv řŹ".ڎ…±ú˙-ü¶ă›s‘ 1m…p EöG8ŹĐ|·ě>¸›ŐsjW UÄ›żH㵏f\“íR=ćÄw𱕢‹ÄUŰ߬U–ÓV@EřÚěWĄ:jH¤—0{Tl±‘ŻHĄ0ßXI]wᬖ>Ľ.7‚n¬8ů_ÜŁ÷čł =Ęgą“É®ę.€¸˘w^XnIĄw‘rGO,¶ÚĽ÷úSŞş0˝‡Su§­‚Ĺ\ í˝5‡H¸UpŻéˇí¸‘»ď @!†5®űÓ‰Ă;ĹŢúyţßÂ{D§H­šÓ&ئOřĹJ…ߢ'˝"ü’ăµnĄ“p• ě ÜZiopřUK"ŻÔ˝“׍ă&”•b‚Ŕ]2yÝű+€®¸,Yď-†ŁÄpĽ­–ZŽĆěž•ëîôŃ](ÓăF±7EČrS˝qź »şžŇu^ŐśŁmM,ß?"ż}…ŇŐŇok¸żŹs rhŁëŞĄrçÁĺÂĐÖ™ÔŚě5t1 čÁ“—5ż5 B<0m@íۉƣxą¨PäiĹ72޶ŮbĹ•Ź+łÉî–)oUł—ňĹß?¸óĽjEźmźtş·Őé˘íŚĚcµţ|Ź.ď‚ęjšî¤&4D­hz—ş·*ic 8÷¶Ş#˛ÜňÂr([/E)ďąé?É$ š3FóÜ{HVʸ (ç„pć˘gmMIn*ç†Ü»4‡ŹÜ %†a-*^ŤaŰěťüÚ57I]aőǧ#+ńč§ÓĚ9ç5+”Ń{<Ő˛fs"Z'íęd7j•9µ?qq‰«E.¤rP㽇lý<1oŐuĹ=ł%VžPRô@@$¤Żą«űú¶-±éOĆ,p„ęj#ćlş˘C6†/:bQŰC”'}m–ňüÚř‹ŽT;˘9z[Í"~¦©bŕ“ż-˙•ňY…3PĐĽë;ęě,Ć^Ýíś§<|‘+ßżźónw¬j§Źl†ÚĚęTK1ˇ'Ň)+vZ´ř¤ŞŢ– „TŃwž—hĂ`v#Ëۡµ–uTŤ őK bµ•—ņź,Ź~´ąÁËLÎöX3>ďĹ4Ëż¸ďżÉ ę ŕ=d0I»ţľ÷?—€Ć‡¦ö˘™ů>‰ÄBóÁ˘”Ň1P(|šůר }“kÖ- \pčuAű6¬–ipż]Śĺ›ôĂ7ŐTĂúO©D¬÷˛°·Zđ­¨tI/ô|/ÄDŹř^©„~2ýĽČô¬ľ˛WłpVY˘8¸á Qj IńĐĹőGB{<űđ3¦GHJëťüÁ©ôŠşĚž޶ĺ@\` ťíĚÝö $ŰÜ®Ľ`ͧsčĄő˘ďŇÉčďÜq/¨VÔŚ#říŐ®lŔ@˛K¨ü7ž­Ź[<ĺ<‡0,^†+Čő“˝âďŕcqĂĽh{‡+Ę‚)y# ÷ţ¶%D˛™ç¬SŰl‹vç±čŽß®‹đĚ*Ҧ’!ęŃ|ŰŢśFh¬O0MÍşgMžă9̸ô… =MµiiŻt‹cFµEXślϰŤ«R‚…ok>Ľőäţ„2ŞÝu–x¦(™ű1ebĂ8‹/źqGąŞ…Â…yôÝĆR”Ĺ÷<ť5•˘ńž_ă·[ŔňO®ÖŮĚâđÝR3#D?sÁ îąűť-i÷ń5°Fŕ74řWĆĐJő"{)ţÂQJWe;ŐŮQ–ŤH.>J¨ťşpZ2ęí˘\ŹOWŹpбj UVňéš”üćHqÉô‹~Ź`”:ůŹ,©¶ŇA—ݡ¨GVaĐUĄ źşĆ^s(a ůذlµ^<žţ}őö¶lĄá/Ř—żQ°ź#QI¸ç4’€ż‡ôë»ŕřôhf6ă~úŮjÍÚş%!Ö5ddž)KÓiƬçĂž^ Î+$©CúHd, żRn†÷“˙Á»¤đő{zR‰ÍŇÉ?nżDŠý­5N‹pÄ$ŕ·@ĺÄŰ(d˘6Rđ öj8‰?5Ž“ŕ2>/8aç­r©çnŃ‚ ą–Š92č˛'Řé…Cë*• ý[ŽzLŰ{ĽĆšĚŔˇhwgďäηeŽŐ۸‘I¤öíçWŁ€ÂU ľ3ZóÔé yÇ‘ _×VZSTCÁâÝąAÝP(Ą é@–zru‡€@ňŚ•A“¤é+㩸$üěV.ŐŢRĄŹâZzݨ:a™‡7?ĹŘsÔRĘ?ŕŰyŁĽ‚íŹ-´ć'$majJÇß gHYÍťĺńµ› qDđl„ńo`Ł­Bué_âőf[™‰ÄU¶Ümň&¦+”ŻÇr8 „Év“>‡ťF› ^ĽÓĎiÍíăľy/kĹe@Ěţ0`_bÝî)W řzď#z7(qŤY•$ÉÄ8~<sŮ+ىŰ#膉˙Oť4ˇpĹŹµ›-˛6/g»XÍIĎM„˙ 7ŚĂÓy2ĺĺÇB2—ô[1iËĹ–ş:śe~ŕůć¬ä±ËZ@żśďs,ţÚ^™Ź?–A3ŞČüš#ˇTuh}÷ëj?]—âŁ]hŢŻ[G‰>•ŚÇNÝŇ'ĽAüÉű¨—湼R)Ľ’WŹ=řS{y€I§PËXĚŃHž,aö”¦…Ý9Mőýę¸ÉeÓĚ˵˘ľ[¬6?!n(fö,«>čţEŐRĆaV8x‡uIµSΞI2nÚdîç:äÎQ±†ŘĐŔ.ÓG#Ëđ¸Łöj÷w ÇÍ Çg)5ę%ă?šŤőÍiÉu/¶”ݡ+MÄ‹Î(Tń(ž)„ŁBŕřîd•x..h\7ź÷Úwz\ˇ“Oľ/'w¶5"čFrNBşŰtł{ż"Îl`4 Dqcxa †3˛ŰĆ}¨ůÍSůĺw¨ô1Lă8˘ĽéöŐsnżŁ'đß$Öí™Çk§Ž(DU÷k7×°˙P)Šď<ĄjäuŢ šl2ag§˘pޤţa+bšYşď´•Ü«ĂĹĄůX8¨Ž"Ćů—Űe+H¬Ä…D8nYŽvhç›K­!®w!NźĂ6°!:zĄ›íĆ 'J: MÇá]Ů<·ćךí`ůšëŠźqËđ ź°|PK‹onóŘu¤"Ďśô#ă:‡ÓŕY‘ŁTw=j·NŠsBz†ľźxĽą§Üyt !I(GťŚ2çą&v8Â×mA ŻĚc“ÎoŤĆşµ­>Źp5˝-ôćó €ěę÷˘ˇšCaĚc]#ĆŤ×Đ©lĘŐ€-x§©:Ő&í¦x6ŰÚĺŁoNOŕÖss÷ő×ČŘ*FUľýîNż1@(§Ľ6Ěü>@€#€bE ˙śFĂTďŞO›şAÝ€öˇ¶ ńŁűś :”¨htIW0w×( ú#_AâÁÖ…˝Đ&Ă>eÔŞĚru+řîţŕ;Ž+˘Uo#¨ŹXsçP°Byĺš$ůüˇž?üU^Ũy^űŘůůÖ^d'>cĽá=sÔP‘ţÚm¤hĽ}Ź/JËŕ3jű‡$RożśbhŠÄÖŠnu§Úu)S–µ%¤qôĚa‰˙ȤŕgeOö^Żł(j ±>ÍIb»›EZ&f†oÚ÷ŽŇt}›o.Ďȉ7yŞJ)ޞ’†˘eA˙řĘ ¨söIó§ʦ ŐL Ť&ëÚ§N•`;ëŁ*JfÄ °ĎŤäő=đĹ_©ÜŰcyÔů›<Â@ެ9y “ž°'4DµŘ$š@I|ęQ[k[˘ĐňŢ$˝*‹‡ü6w•¶eQAToD‹­˘Nń¨ů*Ŕ˛ZŃŘg·f(QtpYź4I¨:¬`:(ł°CČďż?ä¤Ćl3u}ŰäőOĐ€äŰńYA±»p0IK®y{ŹĄë ęÎęĺĄ}űĚhĆwBÇă˝´bąÉz•Ü…îȆ\÷°ŇÚ¸ Fs) 5Y¦”­‡pđt¬˝x íß-Ř&f¤D;P:‰ í̀ͥ ¤ŘJö™GĹd"i2č—5éçşšjĎ×&±ă^ÚÔƉǬ‚ŽařhŇçX*Ř Ąź9¨oä ÚDlŕŔüeč˘Čqĺ˝0şgZ©ůđ˛Hř*ľęn”(,üúT˝e(U­d™‚Ľu‹syš™?ńű˙&q'ŚpĂ[µw.ř—mä^Ő=BńaS¦RĆžví›"ubf±Űľ‰†Čm0žřđŢ:÷!Zسʡ}đ©ě»ŁäË•}:şl5÷Wc)s —áRGÇT©ľ‹EŚâüÍ,ČŹś=ŹłSi^;ÂŘ1|‡lTű"˙ĚܰIżĺ [‡xuömî¨WÉÎŢÄĄ†ŻQŤ´töľ¸iÖ'ĂśţEHĹ©¦QŐ‚`˘ĺřąH!$mľŠvąŁ9ŘŔXéáöŮ0ńd_ą¨¶™#h9ř5<á|Xó4ĹësL«~ĐW’÷ĄÂÄé·>/0ě,s!d!‡)řŻ Y2WĚWšaüµ ť-ţ%~ú¦Ü¤AßX6ßpM•lĘŐlšx.5÷ićn3„D1hr:˝~ť—6ĺ=I›CćČţĚ j˘ĹńŔâfXŚw Çâ6É"KěLóÁÁąKŚvř‡¶‡!(:~¬Ó%§8@·ÂżU.Zr—G¨ç4ŽË» R—]Č› Mw`źC6Äćęłlw°±ŻmĎŘŘGVf°EtŁ :;č¶)G p3O6©3ŚűĄ(+ÓRx‰ĺgEĘm•»óĺVąP\÷Y91."Ś˙±†L!‰—zpę]ŽČy<ŕ…ßşH8fRDa.Ď ÎNVˇz;=;@Ř|GŁQ—ńůÓýŮť»)ĚžPw ™y(ŐýEm“ŕ•¶=Z±x˘‹ž,3 ÓÖđ1úTJvÇIńď ­+JĘ:µ»’LzUdŘ­W˛I!:Ú:ć/s8gą|!-,4÷´Ő±Ě´N!Ú[Gńˇ9×é6\ô3EH7ç!˙,s.nyľmú‹Ľţ˘K3Źţ x™¶źD1Aľ•[Ě{`îśt†& /ú~ÚĚiÉ^WŮ8Ůö d9vŐxë½dT¦}*@‚ŮZ3’*8»É‘żóŇcHň> ‚°ˇĄ®ăş8ÓîÍJW!©ŁŤŽď^ö7D­–•çś‘ÖŰşâ>ŕëĐřaP<,šßµŘÖĐ`cÚťÍÝO‡\‰ę•. ý ŤNő˝yË3±\b|OĹ ň´™k¤ŰÍ€řK[á¶´®­6\µgN;}0ť m<¤ţ^–Žx].ŮŘÔK-ŤŻ…”[ű䇏–"p„›ŘŰ`¦¤–ô“…0ťÁjSą7ĎÝ „Ľ‹xŞěđÖUmăD źEˇĄĎqSąŚf25®ő•őowA 8¤R˘Űö“ëTtr ĚË0Xń¤ťŞúż·.“8űo®ăt >Ζ_jiÍU>ź{čTźoňŔĎšKۦNýĆѲ4Ň˙óJş&>÷Ŕ4ę°5•oá[>vSvđŐYöCă]VÝîV•ZůČ[®¬ŕ{OtëŮZ<‚®®XçR Ź\*VżŻ#†&Ě^Ř™6„€ź´c&—ŚŤ®1Ę™púa"·ç%‰AÔI[CÓV@S˙…WZĎs€M¬Ň řÚąß&”D4@ą“ÓÎüîäf„&(LˇšŰÇ«c ŁťV›VĎÖţĹĺy`—7%~É í_>QëámN ńô2Ăăďµđ‰#…䌖gSů~;zľ÷†©~ŕ8˘‚ćBw˝ÂZśs_¤D 謉–u§Iţ 3·UM~Ě”n}ň˛Ć ŹŞÉňÉŐd<Ó¶ň€D®«lWužů'*’€]ٶBOů ŢéŽxÉʆçNĚJżd‡ŻLTđÓ$% ·«Ĺšt'Xĺ«ôFl ä …ž€r"u‘ie(J©đ«rßéłFgÚ`é»ÂŮű˘gęčÄîRlg°qBĚr5ÁĆ„ľ_HđĽHĹţśç­ZVSZÓY˝é7”ëŰ´H©˝S›ţĘu<»fiÍIÄĎ„Ĺ;+ ?ć™ĹR¬ź%ŐFćÂK±ĄYĎěîsĂýěőňěűďĘ_ P ŰťÓŕĹs<› Ű8żJk˘Wvĺ4 ’ŽÝ¸¸Ş“í¬nĽ‹–řJxŠ“:ůvÝ6¶¸ţó|QX‰đ/¨Ú#˙…=<4\d} _¤E~-Üz $>[”2ÔŔ włHß"®V™ě ¸Ő1@ămîŇ×UÄ”dîĆçÍÁĎÓÖJŰąůŃ _F® ]‚|ˇýÖC9u’߉ō¶rQâő…Ź·ó%b…Äp϶{+>Ś/‚¸Á_¨Ć«—Lă¸0çSŘ´×Đyi()©đňktR Ś˘»%8Ä?élv…ňGÄ~vź ě>Ć“¶©Ś±PŇć™@Ú§˙˛\č÷âgW_›FŐ ]`bŢ çôB‘83ěmŤźtQŮS÷ĐM®ě÷BÉŃĆ{6Ţ b“ď9 ™ľ.BşCÚ’G3ŕIÓáĐĚđ¸¸.ťzÔÖtYŹ’Á/)ż ŚkĎ*#Sܡ§ô\‘ż}ŕQýđËŮŠâ–`[?ęÚi/0WřqËę("{®-n”ĘŢô-4˘Ź¶l’ömZ%aR<Ü1ćhm-!ńćŰţ Ĺ·Ŕű˘B/r±E¸hy™§ˇýůi{÷ľʞřsŘ Pݨ8ÂőľčD/·7_@?'|ת.]ëű}›7PîčÜ\ţ;\ Ú$mqxb™u˛Nncé3-Š›ő» }B6zcuXŽR¬ŕ1 cĺ%k8c ľc\W*ŐVjĚÂë3óŁ×žÂ(~’v¬#U”ăFř’śDÝZ´Á\ožp )1é9ů‡Q­‡qe`‡eöCŃ:´«çy-Ł”j„o§iMóˇ«%±€]¬hż‚ŤBĂ ęs‹XWýj <¦dlˇÝßťLĘ Ţ˝$®Â’Ń=é„Sďeě Š‰{0ŔIľ.‘4äXzás̲—`n ⤳ĚlENŤÓý±_ěOś÷\Úa“·pbÝAŰwŹíĘ3ä?ť™~W~ZÎVrő`›¶h˝É®˙ä Öqr¦ž÷Űr[^Ďţî—'໵3đ÷żô®ŐÝä”sŤMďąn!ą đ ŇDOĘȢŘ×g4e P®)–|~VW„%$.S[/t¨ĆÓął6YŞűą;†KZ<érDH”„ Ű®đ&y Ó•ş(č>žiŰ(×K,e vŕ3”•8!°†î… ŕża‰¸ R¦ e„ľŹŰO "Vn!O [›ţáIî[ň±ęźp mËő #c„–ť )|pĽ:ÔólډâĘS»gDH™–‹©˙!tÖŢŕŐ”±”«sC°jQ·¸ŚJÝÂłČQ~C„˝'/+z &ćřM Ż'Rľl2đüUV-‰żÎ® oŚDn%e§rÝ …Ŕľ)]u{B×ĐĚwý›$yKyOžĚyy*˙^T0ýĽT4q±»$ ţ}¬˙±mťw9ü#·vŔŚŔf Îĺ Ń†žó% ËÔâŕXľź>Q‹€H|§ĘóF‚ŔąY<˘v•ÄJ8ţć-ŞWĺţ@iB™%Ë46W#*»›I:×tňÄŹÜôŤ˘—7ęÔ>î[Xë+ŢŽ7šÍŐŢŤ>‰Ş÷Ö‡ZńÜÜhşĚô j©˛®Hö#ýżˇęVGjn`´ţ,˝n‡Ű\«€—ă˙ňe6‹şK Î@˘J¤ľG{Ë?E0Ëţ˘.ZGJ÷éEGäń§(Ś‹â{ŽO„ßß–™ö-ĐLřh„·MŽ=Şť Ö+÷˙(ĆaŰ®%jDń±qd+” ˙Ebĺ¸>7ÁËíz,ޤ=Ă$ÍÍîÇÍýô'1mÚ’[tĹ©ś©Ç:şgŠYÎÜ^˛ŔA8ŕ1?éĘ`ŮŠMxů¬PĄ0ó߬ór‚ł-µ´GqĐŠ)жtß–—\!Ö·›î˙żŢˇ× ö”…+Yç‚ß猵ż±ŐŐÄfäěÇ‘ ?lÂÄĆŐŕŰşůŞZ`ˇ×઱iÇR˙Ü•Ěî¨ÓP˝ăđO9…‘8W"ŇćäÍĽ;ô…–ů¨Ĺ©€Ý–  ~cŠĂ©K„ÄsĐźH®4 ŐP—Ý ŠFRż UVŹ Í6¶5Ý©Č “ľĹßzŰő»é\+Oĺü†™­ĐŠ^%]™M¦ß ›˘Ezřo]ó’Yţ îĐÎiĹaĂ…ĽÇ—4ĄmĂjL>",µĚő4a{îÓj’­rŃ5„Bt@ďuYrÂpßw  R-ňîŐ}ťP0‡R¦oj‹_€+7F¨ňŘ•-MÇł¦Ě©ďúŮť<®,%céŞó 2YŇů<‡’ĄőŠóÓˇŁĹÎoŤ-‘I\ÂsÝÎFލ)G)5#í)#—Ţ=4F§% $ĚYt˘#©ëďĺ3§Ćt}čsĆ'ö[ Ű­YŮ÷Ίń•{ęśRŕy‘Í„aÔů_DĎÇŔ2Ţe€’‚Ć–öuHö^ČX‚áxY'GÚčŞb‡dY® ë2z%ĆŘĺşřôŕȢ- >O/śo°ÖüĘ‘8íoÚbŠHűŐyóXŚwe††.Şi_¸írĚät¶˙˘8aĐc˛T-«‘¨@ć[bë_ŇÝă>!j1ČSí˝F–ƸĎřŁ<ůöoáâh ĽĚÜ †pRŃK/IBHąu@¨­žnżřĽŹóNs<Ę?nIóćź-wůę¬:-á~´Ë‹ö3b†vSËú/űň‰ś¦´ŚPyrÖ<‘Z}ç•Í“ ť í¸¬Ň!m‡ß‚D˙7ä1ĆŔ@,ÍťÂ?' Ř˘Toř Ză7Ľű°CĐžG”šĆosˇ,´.˛c2ńĐV<ëI´Î#c4ś>°Q'0™V6Ŕu-é¬ĚËćďć- čÇ LNfR {g¦‡ŻŇ,Ź©M% Yľ˘bť 8ŠÖÔAöĹňČm¶<Çń_DďěxĎ4+ČČöŃ*|q —$§7Cľ=wSěߣ˛–1Ü Is8ţ«}Î[x‰HŁëěX•X™J Î?5¬s±˝,ݰ űˇŕ(Ť‰°$ÜZb44 ¦€§wç‡R2«_Š}@îżaWé"•[¦®RE/‚şçŢ,Ú3Bíó~…žT׋VŕĚ\ ™µ˛Č,5ćp+( p‹7 ÁrR7ëŕępĺ˛ î‹ÚóFú{ňă ›j‹ú1É%l[˙¦XµUŚz7`_ŔGm§ŘřĘ`Ąß­«ˇZącć›4Đ]<·â¨7>ÍΆˬóőôe¦ Âmŕ®\±‚¬śćčiżrĹÂăňŹk Đś3G'3ëjËP ´#wf˘”Ďóv:dMoůqdmÎä¤ÜĆÜ,E~…G3 áëî&]MmSćІ¸ľbľ¬AĘHe8XŐšuĽ*¬ŕ”ďnęgEŽ"—łíf™śŤ%înć-Áúšo\r~ŘÇJţvđ„1xg)ڶ9z7·”pśĚ*ót«„_“Ű0pݲ˝& Gşa¨ňµ*»q»—ň—V@{··XL"‡PÄđQš«˝ńčŮĚŮävÔ n'Ť°_ř©iź)wvż.6ť|ÝŽĆĄ÷‹‡c쫛ˤë+y¸ŕýg·-­’Á!Ú.^ ޶eý•kş #ʱ.ż´+÷ýJúř~JÂ[^‰íj +‚u»ď3ëë×4ťuőß äôixZŢ÷̰‡(Ä,Ľ˛&<ťŞ(OĄ+ĘľY͢64„ĘÜĺ=ŞŚž\iڰX¤<¶‰=› j3ö3Ç,–$ÁCzv „ŻádŐžĺ€2Jójřp’!{ #ŽPŻť đ»xÉpeĎąĆAűtY #ĽKÁçę•ÓgÍ8Ěó1Q¸=ń·ź±ÓĂP›kŰ,['„ Şź˘Ż6aů´ Ǧ†ż`ĐeŢ—cv~f—)âŽä$čÜk9‘Ë2©0sąĺ«eńŠ+it[·’ßËź6uEźA™Ü'~Q7smĺMŠű Üu1Ë4ÇmJĘóŘL..Ž˙÷IjŃô–}× úÓâuAt˘Ö>­d>W WŠąĺoşu3ü_ľě> ڵϥZgČŻ•e$˘Qc”˛Âá\¬WăIwơńë·¶řŞ­p%H;xĂcă%O·¸&6QĂż˘o ŮfµÔAçÇźŞŤŤ…˝;ëžhÜ#ř«UtAĄĆ+ü=$q‡›Ź´¸rľMw@:„Ľp‚%x“= ć§?vDąËa č¦Ď±—J6š˛Kp4¸xs„á˘ĐNÄâá×Ŕîi=[!2ë5Řçö©p5=j2ĽA‚+‡ŰĂ ‡–Ľ„ď+u'ĎPSGs´­“Č—°Ôü•{µă3: źŽX|·M)5Ďę~«‚ľĄ­;±^üś˛âMú2íJ’ÎűĄ19öâ®c4«×źfS3O•*›ă¬š˛˙(¬8ů—Pc ĺ)řÇŚŇŚ«ž…ˇC%JµŤ•bÚÔ+EŘ\ˇLĄ‘ŚHtXă$WJâ¨7Šdkşxj9OË1ndKYży1 %kťTČ,¤ˇj=ĐÍůČĆ9{óţ ÷ú•덉Ė<đĂ0ĺĐ«±Ţ8<Ś˝˛ôý§řJŁk$ YŘŠ cn­Ń„>bYNmąl€ŇňqĺYůUŘLOVIřQúËß˝1Śťďďgř]>`8Ž÷p\°ţ°˝ěűx,‰ńU¬·Ç™Č® ,úżP‰±€âŐçn÷R®J&Ĺń¨šĎł¨ 9± i;f}Â}96Áú‹‚Žůg“VĘ(!úm>`stTó,çg”ą"kĐ•·ÚŽrÚĐ‘NßRGQĽîx”ź0ÚTňÂŤ–+Í0ő*eg€›íćĐ›™Ţ \C‰„U‰łűÓ˘-XNn™Ţ5°ůäúk˙ëŘ_¶ń‡ŢpE÷ÉĂŢšî*ü˘ŞKÇŠ¤‘;®_i8|őWÝ|?"[+¬SGLt p}˛ĘŁHťÂŁŁ)Ö}˙â2ű”CÉ\qçŢő|Ü’äCJá€t%“7)ÖĹ4Ü;mí& vc¤·9$˙ďoqCtÔ;Ń2*ú\XNÇüÓ®°Íy1˝„ ¦›WŠ"ŰşÔٲZuŸ@îćźżÝXÂ‹ŁĄjꤰ8 PĄyŤžŇÔxő¶4[Érăé ô´˝#€í’ą'ńˇHŮß<qýAüµżžó¬1+€(iúaä‚î<=Iä!ăwľő]E©AŽĄ‘ç'ëîí č-Ůy¤›#×Ň‹1&^·×Báľw!‘â…ŕŘU.\Ř‚ČIŞJYRŤĐaĎ‚’z_®(GňFź37Ĺ2ڵţśŔôč¤ ‘–czV&bymíaŢŰgřk$Ő,TŽź™öe0Ě(ŰůCUˇ‚~lŰXż"" fö;a –AÓuLÍ$cd“§+-]š†­4 ·iŔn‚Ł]ů‘bnOÖśŃb4ʱﳡ´3ľŰBŻáÎ` ö0 Jł¨(G®!‚SaŃyhZ× «|~ĺ” ÜŤ€Ş@{űj&QŰ×Đ3eńÜŽHE~ĺ,]žDą®Pă~DĄ*ZWęO ’µ9tśâÖXÄ +©•¦ĄŚ·†g¤Iµ’ Uś¦/ßG˝ŽËóx:ąŃţĺÂ&hŚUAŁ˝d´ďŕämŞKŻßJ>-ŢĎ”ĹĆćR´pf,űcFYŮ@›«eSËä»XݰfŚ·pdëXą? ‹ÜyĄµş\µVÍú]im˙Aé·Ňůč"&ăŻX˛Ę>µm—ëM»ÍD˘…9¤´G:mL\Hc·ОeQÚSŰÝłłäÄš5ÎÓŁY,Ôńô>_¸ń‰1ă·vĺÉ53žDŃäu†°ĎňÎř3Z·ÜŚŁ[I$‹uşTWgv0’Ýîć#˘G°ŔřBŹXQŻü%F¸‡»ćU8Za“'™b—DE±KDÝtŃmtË-˛+Ď­ł{@ÍhADŁĽ#á"Nď°“Ýđ°vMáĺ’ę»jKŇB§gsmS×Ę{áSš Mę©Pd–ľ`ĂrÁ÷żiĘ-ťĎ;~Ł?áâ{óó6ÉÓľ]’$˝zó‡ĘAÓŢg/…k±9ëxQ|Đ ˙ŚŁBÔ¬-Ő+ŚŚ$˙Ĺ=(Őt07ÎĐ:aüqÝ4‘‘cVŽŇu˛Ç˝_qâ™8•Źß2Ťü™Ę#i×]ů6öd™…4‡ {xäô•FëMá. g.uH6Ó–­ĺÖű čÉG U/^F˛łÄuż.%¨ Ʋ\}K-^9¨€Ňń_VÂ`Šýň|UuZ3Ę Tł€Š­č*9‹í%Ú«’ŚźvÁ.Ö¦TÚŔČ4ÓĎZX €?BoĚ Ú[K˙ žżĄŰŹ ”BfčQôşvĽW"iŁü;Ă‘ő5W».Aű_8X+%Ô>Ľ‘µ°î.{ç„č{a€ň¶ZÉn—Á?Esř­ą%bü7*¬čęÖ‹ý\<ď\µeWF VRfň„lNWŞűcu.‡âDĺ‡NzxÓś˘o¬C©`×Ĺi/bÚ±uŔë˙¦«ŐÓ•őë'ÔNxbtyD#MŞŔřüEQ™ŕ‡µ_]Čn1łfÎc´"w-7ݏ Ŕ±ÜBů… cŁfâ–ŃŰÖD{a·,!%WŕŞ^}¸Ç,=ńŽ‡Ś 0Ďq§4d#CđŞvá¤%WĹcČČç’uż>ËÁřúłÉ •˝jĂfÔ3?9©k#ëYz™ĄŹŮ{ëń{S»/ÍĎÔ‹ g3ţVŃ382*K`M§]Őb÷Î%‡LpĄë T`×°ĺř/žä9XGůŰz!LµB+Ě˙GĎ Ôžâ_ÂÔĎ%l6ݬÍâ°#µŽÄt­ż„a>–9IĽ~ŚÝčűŐĘ»#ĐЉL„á ëĂĂP®=`Ş€¸ĺ~1}%E‰ýÝb雀–ÜĽ-ćĘ>Řű€ž¬Ľ5(7Ž ęëPr“TQh+áu§şHĺöö­šçWϢBKSż÷Ý@ŃÓĽÚuD2ý´*´Ś+wóF%ÄTĹS¤“wŚkÄD÷Ă!oQíđ¨3x˙Ţ(6—eşĄ@ž*-lł\ĐVa!Ö•ľKć@u¬hŘ‘‰%¶ę}®G~Í„q-î‘h}s)»üíţľ¦ş…kaë…ÓšuJyyJ ±*PXű&XŽ]ľ?ňë’ {ÉŐñ5®&I6ť®ąÜĄ’ŞĐ’2¤ŇůLÜú‹6´Íß&•SÎ$b1x©;řmőřłSmXˇtZ͢*đőtľŔ«L§~/ç|ý»·ĹçIŤeyĄ<'#×…Ffí ¤HwUËýó|ĘĂwfɱ+7^pqhS¦~,Ę`ݬ•%’ězÍ‚Ú ‰1R„ÔĎ®ŽFĽńŔ‚Ţâä=©FŇiůľ±9ŤĘ`ŇkíGQĘ+`kN[úUL?=hö Ť¦§ć…Ąi(k+4_¬ß3yëhĐ7m{˛,ôŐńWtŕ,5đq!ź÷Şś‰ í?EÓ}O{M;Q»Á[ĄiôĄ™u8;yśżx%®¤Á"B&×Űś" «ÉmváŔű#wĹc†ućŔ žŐžµB?ö ĄD~Ć !ČU|fŤş·vv‚iˇ}ű­ô gű‚E¦ĹOú˝ő—yŽ4í6ë¸îůlM·ĐŔN@gÁ|şQůš'§'…~ľ™ćN˛6ŕÁKc¬Îa§ŕáQÔ Č{N#ëd¦ÁM*Çě+# W6tÂuG댩şń—«@6۬hÝŞ–üťO»7DaĚ“$·ŚÁy4BS –Řď¤"Ë5®n4¶íPóZU<®ăŇ \$$ęp{싏Ă °¸Áő)ĂBÚ&V> ~Ľ+ć´ĽNYÁŘńZ¦şű҄ҋ˙©Ĺţ‚«—éI¤$ Í7Ő´ _RiTĺěRŹ%ľą˝˛*‚ÔFđú–úŹľđ˙¨…QŚŇ˙Ą2íÓÝ@őĹłäď𨤬Ň?ćz\~s—aŐ¤Ę.ŽĆáŽ3T÷‡X·r i6LBĎ:Ŕáămńů+‡ôp*Âú«,uwfĎ{Ý»ątęd^|áŇżÍx~ źÉFć"‰ń¸R-§qRźl’ÎĆV­Ä(ŘŚüžţx^+Ň•÷Ň8e6Ěťç¶K2.˝V‹Ć»÷ŠÓ-Z°ť¬ÍĂ,Í)Í=J˙#"§S5 ë˝…‹ uvČČăˇÉęy@Ľ†Z-zşGŕvŕ­lG~FrSü÷«lR+µx7´bg—i˘ż8Hh+d“ü Ë^<öçgqF"ŹŤ %І©ÄCdŤ>ťzúýŐŹŻ"š 0WÁä…}:;Ňe*\=ůřÍŘű%·ďD{˘ťÄ€l{ín‡łÔz=†#t8Íw´Q+ćZ¦EŰ$ŻÁ…Aż˛eöĹ•âú.đ[ŕçűÉúÇ x…ő/fŹźŐŹÂęO0%:- MĂÚ—ŽË}ŹŽ2Ö‘Ćűć‰8Ů RúńrOÁ˝xÍ÷íç.ŐŮqűÚ]ÍřŐź6<}ĂżJÓ)[ÁÜ/Ĺł†¶Ě°ĚímóÉŰĺ‚U{ĎÚRÄ!ÉC­ŐÉ…•DVŁ,Đ„‡ëë´/—˛ë/5‰~ŽS†Úď5KpřX-ţŕ e ZÖ&5/ůfzĎ.OÚłhëN?ŚVˇ—3÷Ľ2Pź…„bü0wwĽk´9Š=g—5껦%Wµs›¦Ó„U}%$.QĆŞ48É>“úŔ[vş}Ý bů4K"DÓe3 Z‡¦Y Ťx‚šyp†Pú€0«Ăő÷â÷hçĆQŘYHHÍ\Îęo€MF÷‘!BFŢţ}t×7禡đCÚ·łXŐ…r;3ĐŮIEt‰+¬Öq+›ŰřoŔĎňMŢC]®÷HŁô±ß÷•sů R(ć'^o˛vš”WŇ:?(EÜ&CqäWŻ2gĘÉô«­ţh5Ýd L‹iW7pĐTŰŁQáHą˛:X LŇÎ>/zĄ©U5ôcŞëN¸Ś/2őęǬ4)ąNcұÎ3.='"!Ś·ůý+˘šL›ITÓnę÷ł5bŞ«űBéhwüűÇAżFj*}Ĺ.zś?2=Śvăgi.=äh˝¶&·µő°čܲÔkď˘ĂŐËHÖv!­ŐßHľÚ¤ĽGOąUÎů9đlŕ˛Â2o+Ůź)7·‡mnť>ÎôĹÖTŇhn8Ý)šs[˛TmŘsî¬č‡ \Ď}׾?)ůÜ_VOĄ¸ť~ÁHÁŮu%‚ţ~křĚś”zĄŔµŠŐŽIˇ¶)ţs:" (ÂËlŞé¤˙d*;žź 7Đ{K:/Žęu—Ě8¸@|YNĎk>ňu97k¸ęPeŤ ś×Ő˘şzMâöN1¦DŚdĄ°ř§ŚPďĆIŽö7ŕî©7ÂŰ)XmB†ĺÖÔ71HĂšÍŢ÷YĘa²he{ž˛µb· Ľą'Îś$Ő›ŠLHNĽWĚ›FRRî˝Ęě|ě Éx:4žâÁz–ź˛cć2R´›ťÇi•WžVŮ·:ńaőu5)kŢÜ©yeţšž¨V`„bÔ?÷5ąÚ Ô© đV3PľǬBÉŻ†Gö1ťżź O7É·ďŇ×_ĘžUSëň>DОdÔߎo»ë Ç“Ö)m¨n¦L›7]8z5|ąiŐs<ëňúPŻC¸Pç˛@Eü3Jüiă 7ţŻ“€&ao‘¬ÁÓ ý2˙N$ďEsÇOZł %.ď7Kµ‘ˇő× . — Ű ěRXDEQŮ“Q¤[BPśőŢŞSt‘N7ż<2DW^°ŐżţwÉ‹ #FĹ Şč·ĹŢ|âň¸mS2ä3Č9VĄP šhÚ¬ĺYźRRţŠV[üꊹ˘vŘ%fĚ`ŃśęBlÝő'gç@’%e·}Ĺ‹‚€h¸`µŚ&Ö«r'i936F2ŇőŹĽť,X k»žm„ ń`|–#Ě÷}ɏȸvřYô#'mfg¶Ą]o`n\’­•M˙QOkä‚{`]5»®NŤžcV%8%8sĚ©Ŕ`µě÷2 dÝb®ľ*&W:®¦g) ÷śÉŰúî O?ôľ»&3ě†Ě朡 é°ˇ9ós Ż4xđ”Kî/ŢłîB!đx}[ĄB÷€)ĹŽűdcĐôs»˛}SQO×9‹Żíő/®b|7*žéyş]Ă Üc(Řäös¤ZŢn_ڦÓkW_FóŮ2¨‰eŹô BXSń˙řĘ Çőú@éF«ç \ÓbÎĄ»÷Ţä׋N’9 ę”ÚB_Ĺ3F˙†¸ya=ň6Jě Ň7Ś|5<śWí#TňJô5N ęÔĚĺDk’s‹ˇöb+Ů:¦TĚ[}a«´:–%˘!¦•2řsČĐvÁ €G€Ç˘g ­‹ŃĘQĚds8TńgóýÉ4Wź|TÚਉlzE 2ţ6ÁŘŐŇâe +YéÎJ€ 6<Ě:d¤ůzZMú \ăÇ/zוÖáX’I#Dĺ×,•jVVŘ@ ˘=PšCcAĹĽşĺťo€%ť|jť0ną6¦rhĘĺŘه×Ý«_ąP“ ł™{śWNëńŃřy°ťc”Ę]źýB7¤)Š+š“I|đŰ"••i‘ÜýGÉUľë…T=JP̸µîŘî%¶ëJ‚3ůŢă:墺Ű0ľV8›?͆ähxÄe–TiŔLę‘ܵ-Ş=ÄŔ‘ť·G”ܲ0l `ç¸'Tţ® MČ2­â:µĹěŞ2ůĄ3±clXíÖge@ńżu˝†č[ÔI¤Úúë ˙9Ł©óňĹĄe- R@Än+¦‚<„Čď±/śŻ+Ѥúšů˛ź3Ľ>]ß“N'#®pď[׋W–@ßWkŤ‹˛?,ÇX‘păµUŚ‚G*n¸€i™Ĺ)TěŻţ4†ÄÇüŹ÷FHĎlw !“@•F6 Ą%T{±#ĄČ¦ĺ˘ „˝ ‚šűt Qݧ2śV@˝/jřE]Ť·_>D›ĐMˇóĹÄRĂ1ĘŽcKbâçRćžś7AxőBšv˛¬ŘyľaÇ»?ç Ld­Łŕö*_7±|ôÝdđ^°R3%lÓ%ŚÍ«úk`vĂÔÁ‡B\” ě-hI4Ę Yµ_‘ŤŤş`;Ôş˛wâ:ieĆŘĆüĽkížÚ}DÔĘÉobFă´µmqşI wŁPÓČC[Ă€>IgM8’äß’ăË=wgţ‡+4ZÇz,SGN¶ú’¤^2}Ř—¨®ç>ą Ł+ĚŃV xŻ yt%€Łč“Ö»F^ů<ăL¶Y‹jÖłűĺ ő*l!«ëęĽç‚±Uś‘xđ'űŰ áçmĚú÷›™¶ĂpS ÁÍvs615«őp=4±Ąâ “Đ[–ß‘&CÁ1cż­)ř[9˝ Px((ŰÉ{ď «=ń,$žsŞđ±a&nBďš,Ę@Um ýŞGŢżň@°‘;Đ?š8]Eďśň¨Š2~–ÇB¸+Ši ő±¦DI(} tŐi©ß;v)gp4 ÄĆĺ„ŢiGj(ńwŔ(üľšč~üŚ&ܵ«ObNÎ~oß6Ňĺ=č28‹âËlWśŞ»’€#ëę+™jiŚGARI„LIuÔRÓÇ´íŤ=&‚éEŽzzkŮžĂőóő Ž&3‘ćuĄîsŰż#öZ=Śy5٬Ž2®Ž‚xÝe‹S% ěá Ęëľ§ĚÉ Ą˛é=Ą …UÖ şS@P­š<ţżëšá;Ś}$¬apş@…U.VÁ›L͇°ęőéÜŐë©^É&râ× çF\7”P\”ʵĄŻ.ć$')˘P\g:4,$ Ń4(Ęž€´@ÖăgÖݰ¦¤"cÝÄĐ“iҪʦ ­°5M Ä8şĺ¦ßýśL”ŘU“ćěČ;ţŹÜü(BéĹ3Ń˙lÔZ2˙b1ŰĹ—q|*Â’ĐÜSíŔ :r˝P÷ĚŤ˙?=\xTĄE™9t•Íç#8qţŕôź§ÍkCLÁÂ0ŰĎ’YŰĆ·ŽPÂĎ9—ß}é˘î1.JNÍ™#§€D>©,Y .K-ë~ăÝ«ĆTlp(Ňľ±ß‹*(’6'?ńĄí¤Y}j:—Ö€ŘŃ›ŻďFHn,¶8(ÝîÚMaÉÇ=H»ŢٵZâYŁ^QĎŘ%Ö!UjQ~P‡’ ďHG$îßĺeТ©ăgă S´Že®uś+g%‘SŐ‚ťj—ĄAěަý•LIďů.uŇ8úcĎü‘•%ľWů “4ŇDajsÖoôŠ7¤=J^ĘQ—N]m2dŽ6MęÍż’lÜ5üˇl愼(Öf©mŮĚ’_*ÇIN[z# Bä’łv{†‹YB‹¨.J°~Ąü7aîP9ŇA-9`Ń1^ŇBžŕŻš®rŢyÁĆFÓŠąídöĂu2»z:CE$çěDőA|ŕç .iqĂnyý ;öf/ YVń©0 R¦BóiřżěľŃ ÷Žok4Ij–0ď8NJXJÎ˙»<şt¤ =2€ą'á$‡7]„ąđÄëç™+2˛m‹!Lü9ć ›”Ô ;:A¶Ľâ-‘kS*‰ý¶DŰ1©rsţ˙ÍAß‚\8?‚Ű®ďĐôŠś÷#c"Ţ(Ň>Ţ+4ˇ‹<{_M&–věâhÇ8cC9ţŚćă,,­}[ŐY‰ʆئ °ć"+ÉůŠÚÜ ĘLô Ż“Ô8Ń­‰Ažą"`ł B¬8Ú۵ŮŁţČ ýhÖĄ6ŁŞäo˝ĹLçlWdYCVy˙fĺ™"«{ĎaźPŻ|j–oM¨ŽX‰'‡ďÇ—]Âůá™7Ő ö.¬1|ťRŹ:NJ\ÜüIëĐ]Ćş^­łşú¦Đý(Ą-çjHŐÓşšBPvb$r'AČô—Ź ĹĂY!TAxÇăÎÁu#OćŰ·Mâ=i¦Ş8OÖ&eÖf‡¬]¶p¬QÜׄĽ‘'ŔBW¶\dˇĎŞŁ¬x᜼/6YůrŔîDĹOÓɨ”^ŃQĎëŞF÷×›Ľ0Âź-áÚ agGW~»äđŔD»qÎS~L ôÁěr ś2¤KH¶ÝĐ,´›OűĽ˘úŰśş×ŽđńöTnsëS…y“{2:64‹€řţ¶AZT| M ;|aUę+BčÁ¦MĎ$3¦ý¤qëĄöSđ –\QJť@ ׂJČřd7ęsQ ˇ­LŘŞ §# Ô2nʢ“§ż¦łGkŻhŮ7 zŮŇ{1ČëËţě_é7RŤÁ4ń¦} 3Ă|B#łHôülżbťŽ$h Řso~çfâC?€‚WsęŃ;Fü•곯w;dW9˛­{f<ňjš/©çřp]–ébtś¶ôŮaÉÍĎ-PŞK|l/~AEfŔ‚şJBµ˝yć4A/‘CFAîJŔ…·,4-ôÔŇU¸ú •”[Ńńg(ăéŕ#űk*h´¸˝5ż#ŚŞvË­YŐi2«>Zuę@'Őżfć=mŇVą}>ę…żi{čúň®Î z/Á}'«)k ŚS‹ŇĹ[Ůę‘â r#"n™dĽőž~oÜŤŠR``¶iŰđÂݨpE=ö¸¦ŕ´Ľn/@qä5×í$=›ĐÔ8+>nVÓx¤ `xDÖÁëEQmÚG¨;Mq-[ĆfŢ˙öľ"ţ-ťú§p“ďůź˝ţÄô%Żnőé®f·ÔúĄ…ĂŢĘf0H‘fD ‚‚akăhPˇoö‡f‘1 ď}ĆŇÂsJ'€žş,N3ĄGŢÝŚH÷€óŢźIAË®z´Yňĺúš‹äJ'ľ”űĘý¬ZEŐâl^•»˝=Ŕ2yOÚÁ˝‡wW¦°sńҦ­„I™˙•‘óîÄď’Ĺcż{ő-ź°Ě±ą^;[tB —$±ĹOłp›Dů0Ŕ;§MhЇ…ß’€"ňa•cŔť† ×ŕ*Ä˝„ęˇL-±¸42ńĹŐš_'’…űA{Ůe}Ţä.~ŻÍĚ@'ö0änµ‘­î€l—€±w=ÓŚ5\¤aî”ZVŁEóüś!Z,ą(=]…(K7č -čŐ$g™ĂÜ…äS™ŮŢ˙Î|’ä]ĹyAî’ WW¬gçŞnN#eÓ±úQ+Ç^·ĂŻí°-Ž(čN~LĺĽçFĘčDZ˛Ť(NŽ^đ%‡ Ń%o›,ĎŐ_íţÚ«ţFĂ`ÖG€ě-.­ö–Fîť@â˝kń]Í€f ”ܤŘ;‡¨±Ĺt— 8ˇ/çáfɳ窜vĽ@>k'˙”î«—É Ý‹iE­>ďŃ|Ť5T©űUD&knŹu~ăÖĄřy.„Gy&«nĚ™b\N¨íţm§+®Ë‹Łr˘}TŁ>-®×yČ–› €đdR3Ü“áÚ8j)îźů` „ɰ´‡Aś\Ă‘zž…ĂťŻ`mÁ˛aŹÁR¶ç nŃ}]mĺ)ÉÝXw­>Î+ÎÓvŇĺŻŐ~€$Ţ đęÔY—µř í ĽŃíÇj“* ŕcĎG×´ŁĘFÜ3‰ę ąî´ M=DPç‹:©ť/bRŃ‚˝a*€ŞŞd LtÂ)ڰ]şs°řőđD:®ôŽĄ[0P0[+Żb;[Ě ăĺBź¸Ń[wĺ.Ű™ öÎ'ąŻĐř¦ę5ĂDlOó[>+Ő<(ćXb_6Řî…‡°ä)Qľ«Őéë{ˇG%Ą„’¸,éž ´ćom_ĚŁ3 ’\ů—&Ň3č_eÄץ űŻý.đ=m™ó×%3zšĄ[Đ.ĽĐÁÍ[ř šR=ĺtŢćń,Uň !ޥžĚLÍZşŮúí˘v€µ˝Źí”ĽŃjSžjC§ůrőv†>UP,˘řě)ČK›/ÉŬßÓé» —w˛Rl(V3Wµ+úYD2GvŘ[UîĚÍ÷ńBvťĽËIĎŕŞz1[y©čŘ[%űČM´óH¤LŠ ő9C€®ů9đţ;r(©†IĽ~i\á­Č{Ý]ôĆć㲍¬e™`SĄˇä`,ÝJÝÖHđŁ„C¶{Š- pwşŇú‚G”ľÍÖJW?>ě“#ç‰+€Ć\˝xť.đňCxÎř ›ţVĎń>Ů/ ‘*Ŕ*/Ŕ•ë;Ěç\Ís ŚÔÄďu]ĹҡÜćlŤć …v.9Nż&N˛î!'SeÍ‹ą‹zî´yxŐsI |úU¦Â`‚ä#9?×ű9Cţ>ͭʼn‚^HŇş ’ĄjŽ$(±›Ś62ëÁ¨Éôsj«6UŢ.µËWÇůx=ĹŁ=?_S–rÍ—îˇUQRô±Bš>Šc6˙m" >ľr'‹›Î„";QVrŇS7ę?‹\Ďoégćđ÷ůěs¨ńř&Éy¤iřň0mw QRcË®u5ÎBś«>qÓD=ČjůץQ‹ł e„çdÔRýÓVŹ@őhR"j?LGĐb6Đü–sŹáb O„Ój-.!Ľ ťMüŰPíÍ)Éý†îw s®QzĹ_ÍMs-Ž!wÝGDömg–Ë1âťŐk”č˝…Wq@¶Ł ŢáG.~ Đ#y4Ú Z™Ć‰ őďçď-¸A_aCi±¦ô§łů¶¶pđçŠ2żsÓ¨L‘W¶đčZd;čt'.SÄĹ$É€ěT$&<9Â/(vDkĄUĎxű‘8^\ĺi`ŤÝV”]…ęŔ É˝i9Úꢡ­†čaF™I` uĺepľřĆ_yžé#™Y/6&±»ş%ßpśŐS±ß*ňő ŢŁÜ[2_Tň@R"¨đyV)ŃDîŠň0- ĹöDˇ“ÍĎv `†KĆ…ţ™Î¤űâłâăTzöămĹ˝ĽŇ›×ɰ¤ß }%Qä!Ť¬ăhOľčp>¨ýßóş}$Öś»ÇŠ´XíűŤľ <3Ŕň;Co‚Ń: í#cż ońÔ•Ţ`}ýcëÜ--ÂŹžĽ?/0ł†Ojţiîś!ąµ,f߲ yXÁ]vÁ÷‹LčCĸ¨đ&zi0ŁÇaŞ€±v:ţ2bÍIqžčĚI“łŕAÄĺ őëÖŃ@ďR!ÇHŞč¸ÂÇ«d8Q“Şáíň+2^ŻčްZWšń %g‘‹aÓâ™+ç€- ŔíIšť˙}CO˛Ő «ÂĐôÖ(×QLÚP/ŃQ}„ë`ëĆŃkrÍ»`¸}ś_>(Q` ëu4d˛_¸âŇÉqőÁŇ>ŁÁźWű ĎV̇ZpIŻqĂ‚8}K®Y˙Ş€Łś×Ó¶VĹO…˝ŽşlĚĂâŰć<äyTutÇtiĽŁ˛†V\^Š‚űî†$tĘ[m’Üşíś#ÇBâ!Oś7ě7ŁBŃm·˛I­-ŘW…}ćßŠŹ ĚzlOjęl´•¸ôf(99v˘)ňÚĆîüţŻ™Kjfî˙}·µÂńnÁ|ąć!'S¸¤Ţűôžy@Ómw5fÜvFYKs¨Ř˙ ×ŰBS™éѶt+‹W¦=b±úXY0ô^Ź=,)%ÚąÁR `–sýűPŚÉ§“ŹăŔűiÂy!bĘ€sâ1=Ý|Ą3g+ŃW»Ä«čiÎĺ‡Zmĺü Á2Ú^×ţômP ú¬¬®ĺ¦ľVĄçKMJ‡|~¬*h†ĽŞžž,Ö”Vçď&…čěßldyM×su¶ËH¸PŰ}Ô M–AxĆ V0±m&dëÜáľÝ•°Ŕ‹}QÚ’űAá€˙ľ5"Îиgčźé¦”G¨‘uĄ§Š>÷sy$ď¸0†Ó=3_Ş‹Csgţlí©ł2"Úuž–1PRM~•Ë0{–4Ö%ş:^.Á„ŁiC‚»ÚšşŽ#ś+zy whrď;0Ѧ\îs; ó­şď‰u„=ȇڙXúvĽ—@Ý­Ŕ¶›?Z¸śŕD$‡š(ŕ‘ţťŤ©O®”±]x çei)f:\Ť PĚ|ýIĐÔĽGöĽ?ě—% SŹ+@×>ÓţArVĐ3—̽ʀüő•ß!—Ăîé˝ 0j÷s‹˘ť(É ůňę ď܇~cëu¦‚掙‹hµ€RhKČtĎ-ÓoŻ8Ć”F0q©ŠŔiôÄ[󔜦Ľˇ©WkVěH:±ůBgӑ̵Ý™&é•ă´¬‘Řŕřşa˘-Ź‘]°äË~÷:y6ť–f;Qně™ŇČ@)ő5 %˘° ę&U†ę3|Ç GűŕĐî– KC5Á QTv‰ĄŹ#’n1P 6Őěç ćGš1Ů”ď€ÉIńMź qÎT€‡(p3x8<Ő'Š{{«77€b' »LcŃlSnë­ŢÝhŕKU zwß_ÄO=BĚV}`żŢiŃx%\3–Ś0n*^çiGŕŚ†­ŐűűR8“'•¬yň‘K‚^ĚŠşšľ e–stÝKí$ĺ#O %Ä3щ¯¬}‹'¶'\äáď;6ćń•öZĆ‹e°že”(z{\ś\Ű(čWšt”=ó(ďZzˇJ‚ЏçB/;3D OO&\Ŕ­ľ Há°#žÄ+ÍŇŤ]Ĺ ľă۱ä‹ăĺyit©ľYŘüŰ“(Ěä˱ÇŘňŰYˇAoťß—/Ú3ŰAŚŃ‹r©Ň9Öč‰éFŇĆ^! dşA¸Ů”JŽěqŻŞDžś#g›nĄ=Ţ;˛˙V–ÝZJŕ.mČ‘÷ ˇ×‰ŐđÓć‘?AU5ÝŤ,ýę Ť¤îNLQ†—4šź‡˛Ë‡)aěţzK„tfóëUĎ —đH Ö¦U¶šO*¬An˝±»ß7$<3źţŕaHoî'¦ţĎs\{ťbąéÓvń^ˇÓB­d¶r3« AAOCF Ş›™ËÇw­­Í?˘°5ç\(‡=KŐf. ×ô¶8łŰuI7ż`©Óq^ÄćRŕݶËQŢĆor.ŔpZĺÎśwĦçMÄ˙w Üˇ· 'ß))=€R”~âNÎ/ 9é°‹4kŤ4ĺÔdŁř¸Üü™"…Ů”ČV{á żBŕkaLöǵűô {juÚPEJ&ćř/t»HÚŮ«Ľů"!źGýęjyŻ6?t•µęR^‘łşśóvxzr—đĄíĽWN&s˛· nřŰľő |aÉÉJ$ň`áôR\ŕra­Ę{®uÖĺŕaĐĎ]ĘšŽ&ĺK—?‘átŐ$ˇZç#ąëĎQTÍ.Ę‘ŢT`ĺQ] »ęś,»7ĹĘOËäx=@MŇ ĎTfsŹao(rÜž lVŹ(¦°…w©w/┨_ş˛ń.ą1v@  ´1‰w´mî§d—ăEkÝUBŰěťC Ąšţʼnčć1–M*¤o$]ô×¶¤Rpű@"¸A/±ŕţ÷/ IsH!Eî#ą§5v‚@µË?Ź$QY©•ľ+ĐQ+Aßâ­qŘ@qćű•ż~&ÝQě2Ń(ëąTóÖŁÍă°Ăčę ’•Ú/VĺÖ.ňgKCĺş=ŤîDA0P_e3 o0˛ŞÓţm«2‘¸,%Ď“Ź‰3iäƦ; Q áľqźµŇ^,¬yĎż/Ů1‰Ë…ëç„ý†OôWé#8öşpěbVÚ9šH¦î“9ÔŽ§ó Ő'¤vrěŚÍk1)ĺn”ÉRö‡±HšV_[•‘łŁřěW±~ ˘„Ăâ˙ą+,;˛69HTR\_\:ŚK“Řb|AáÔőĺTUĚ'žÖuNzyě*•žDó| uČ8č«üZąýkôç„° µŮ˘ŹüVľ RŠA.O»RKŰ€Ćd™Ć4Â{čĐŁÁ/ •Ă Ô·áJ \µN+sĘĹ<ţlWÝśąŁ•^ZeóHJłuBs7 DäĄC„+卶ŐsĦĺ ď ĚhힽιH°mć“ :)îlÖ Č%®e´5{Ő$c1bwÚE&˝¨3Řý ío™´Řć:î®SÝÂŐĐEÁ9f±řˇĆĂťŢaßî­QŃřjn H1I©‚1’i\wjńDÚ2ĐC7ŽÔśaeŞO°2hďî$ë«vĚkd[Ěi¤ÚQw˙dř)K˛ Źť\ć{UZřt‹Äü8LÝVą„J¨ÚĹ,ęK6Kúąi ¤Č°!Ňo>H÷bv·uFÄö"ÚŢ?’Şźř€X…5]ąq"ŹĘP&,kń‘,Ş|ç+ś¦´#”mU‘–č]şK”? ľŤ j*ÉaâŮÓ>Šž9ĐŃ>¸r/čj±PebÖ˝“ eŻ—˘f)$e#'A„Iqń˛˝Ű»PUöißĆERëŠ,~B ŘÉĆ^©îsG€öęŃAS’ި•™O4•¸rtV*ťÜ‹Ş/Cµ30}ďŚÓ„üŠudś×Ö¦?‹ŁăĹ Ădn“Ć]†¤ŃMUSˇn…QËšť˙ŠŤ~\±Ň Ě5T¶eÖł|XOĆS*…ď)J2űŃ'#ŰÉ]·ŰB‡CWˇK"Âćá›őć·‚úŕ¦Ă_Â=śŮÇű„o1üít ŠRYôRî(P…ŢúqiçÄ. śÜ_ť±îb|ŇyňźŁüz9ü'Čżî'…dŁT5#Cqď-ăÉŘARŰ‘ Rł‹Ž`)„Uh%ń.y“bd=,o°6*ÇčOµÄ$a rÎăQ(bLŮĎÄőÝn®RĹ;-Đßłé,»"Ş#4¸jO ž‹ŤM¬(ÍFęa÷NKrktăvt,Ýăa«D%­Ť Ă`5;Ťé„­k[Ö¤w<ĺ‡ţäżüąĚăH9«ź˘ uvę üą±šđýfk˦=ÚwĹ6˘wlß`/VEÄ_ÍXŤ}gŃtź~ ŚÍáË}¦Î:éJfFşŐś• 1jö˝¬'É&ÓúäčHsm +ź5ÖČ)‚˘ \Ĺĺő.Ô\?ŕž†áăČ·bC!'" ĘěÂR`Ćnű”Ł(é3?k:b©Ä˝ 8Ăd(Ü~—ź¶fçäĚ:ŽÉ;«ýĎ;)Śrv†rCŘ?xl€L&٧»Łő@ôKűe(ĂÄąT;ăXî;v×í‚€ůĐĎ0Đp°^|ęć'·Ęaé‹=¶†eâCţŁĚFIQüµt¶ş`"“yžě‚-ÁZb"¨ŕ"¶ä/vZ–›»2? đ4źâůŇÜ-łő:ţÔ‚[ůË{$¤Ľ'IÚŔ`R?ËŞ»ĎlÂúýqó…@ú°‹ĹцҰ22ŹßkäÉŔKqŐá<úŞŰ3 /E{· UeÇš´ë­îÄk…Z)ďŮoF¤®ŚŔčŹ÷–ÍZ¶•Ś.)ÍO:WuuV»śF’¨«ä×"TJÁ‘‡e†źíq틸o X Űö'ŢXEëJžRęŘ-ŻHrGĽü‘ŕÁłv “ÝIř·U=NťÎŻŁ¨ž!ÎFňŽhj‘ó‡Ă±T5ń††qžbŢT ´”_]Ć.˝V2˛Eß’lZ›ŕĎöŔý ·ŰDŽŻ'ókÁĐ0°bH`<ârxÓ‚śŐZŢäÓ=K‡«GĄ–†& bu-ăř&\×eĄJŘ„żťŽĐŢY÷ôy×÷¬¦5gâ:eh-/Ń9|çeń*ĆäěR6Kvőc±Ěswfg…z•;ĹŇżé°OŃ ôDZŰ(E$úxť~ǶaH\ t˘%†Řýâ7§°DŹgÔrkĹÓŮŘN­ËN˛çBÚ•~:ŕń—ŻŮg?L"Ě[X˙sÂĽĐ ů§±GaIäóüŻYá0G[&éŔ1p¬"đ*ĎĽQŤ<Ű"{ŕ=‡¦ŁëOň ¨ěíŰjÉB˘Ä›CÉGËÄÍý¨sx—÷Î'x¤\ąľ‹°C«‚nÄ_25?@7.ú1a¸`ë%ô˛˝3˛­Ç4¦›˘˘Żý\•¤»ĎňěůJµ¦¦n =MŐ-“ Đ ‹#“ńŹýtĎŇŃ/#iöT0@?,AÚ˘>şř~C ]Lr«ŢFäťFuuq‹Tů^!v“‡ď#˙9Pk°ŠŁZąpŞß<¬ÜŚ‹?Rwůa5¦¶48ˇ6ůbďäĐęK]ÍfҧŮô6đúĺ¤ŕ/tGů/ä5Íy!Ž<©IŕT墡YeqŠ"8?•±•b˘Í`Â+ş,ů‡ŐŐŻżĆŐ4^uŽÍ@ŠŇD,Źdnɉb׌ł¶ÄĂgö­ÖPŇť˝6f ‹FÄŁ»SBb|ó¦w?®rŠŁLî´Ţ—Ť¨z˘up[Ř©Żę„¶ĂŤ*Fˇ@Á=R= ĂUßýŞ[č%Ëptč„#ňcE4ţ\z‡ťB6 ŤfłVaYŮ4ű=ż*™ö<c‹QÝÄśbdŹDÎeÔóE±źĽ5(éŇĹLáŮOG™ÂÄŃăĽfË´>aşŠ ťč ĽpąV2Ň#îµ;JR{ë\GßW6 ¸2••HČt UŮI:Fiͱ_Đ@µBŤů…Skř\µ˙ ŐfY. _xĄFá±úó⎓M­Î°ŚC{0h P¨¨ŞH _ËRŕÔĽ4KăţM\$c©—śQ;Ť–XÖz.˙¶JśôvyŚr äŐę…e#âľő‹0ŐBŮçÂě»"f+¶¤OĺÇm_0äß«>ŠS2] OŃ‹s,ßČäć|–93ćĹ)‡Zc¶!B-mQţÉá÷©šŞP‹O‡­AËFČŕÇ5ĚąI:_Ł:Ö$W¶to Lc•°)ůW3`Írĺč]yśŞFĺtZ·0e$†zź%”ł2ҦSny†xŘH®I Qt4żÁeę gŠś I™Q0 ˛jZÁś89¶żlR­orŕŚMÄśŤ_ŚýĽ{E!Ű­fHÇhĆ_JBçéĂ»Źäă"ž»Hí«­•Đ™ŃYÎż5á_×j^"ÖŐcŻÓ;ˇv÷§Elţú3ç6˝]†ż&éŰź=Ý€o(ęJęß~82ţ\VO¦9´–ĄĚŃ»{™Ž—Źš‰eoÍţl›wÇ5DжSś˙:;3ĎĚ”,íÉaÚ»“?‚ě`k%plä9 iżŞóç°]xfnúËÍ&Śő_eą"3Qa‚”ĆäÇ›Ťvb×*m’¨ńK>ŁâĂľV}ĺ4veőq3ЉŐ~4%É˙ď®{ňb‘Rö—&mÇÉý“ůÄ_:Aa@™”>~·’˙P±DQĎ—í`ńl®HşH>óbó}jď±\ăÇń˙kh"Eę¶)˝řú'› \.#0ű3oV żjR’ܺܙ†ł5"ŘWj`ôžv˛í!ăśH=`r3§ţŮČsđéâĹ´Š[\’ŞÄ«ę§ĐMůţ¤ŰÝĂ—!ťĹ{F'NˇZ%č(’s*©ĎFB1ČŔľ®éZŰej »ö×»‰RůĘĹ_ůŽ7űJĎ ‰ńhŻ÷Ň Ł®<ą< Y´ĄmřtEęUv¸Ăç8˝ëĽµăe$LF„ľ@n)Dzë™W§#·T«ň"Ž(I2V¦ÚyS”/Ć$ î*ĺyé)«_đěŞi2Ő˘‘ÄŰ€$GŢęÇ‘-W$Ŕ©]ÂXxŐs@˙éČĐf,©#‡ÖŽ}g'“·-¦ŇH!Č%JůJś Şą.ŻBĹ{YśćĂĽlĹ|ř¶îŃ.,×ÖD°Ň‰­CÄ™â_vq-Î?ă*‚č"§s8ŕöuś‚Ě·Ů}P§Ĺn´DšÓł(8a`I˛+6`âě‰\FÉËíś}FN=óuŘWĹläů_żČMöNWl˘xBČr±e6"ĂŮŮŤąµ2­d‡<ĎŤÉÚę•W)y@číUz5ȅןĽ ą/˙Ś1 ‘Ż„Źç±M+ç lţ‚á#‡ňs8ö·u=ő >&Ć4VOţ˛’•\%h˛OI¤˛ôfĄ7ţ¨×ď*ĂŽ-÷4 Ö L›_Z•žćW€NÁjv(©Ş©Ř_ô×¶k®XÄfĄăŔRľyŁ’î C†ÜCTU(lŤĽˇ¸âH°ŘÍÁFO¸LůŠnđÓ'źB9¬mŻôÍ־ҩ{4/kG_5÷°Ú‘«žfI…ËdşÂl­Ř9ş<Ę`4ĺÉzŁŻŮ YbĘę!: g§_#Ö‰ Ü^üćűÂ{-AŢo¬ćO?r±ÔÝ—ďj¤ş·Őe\r 4ů^öÍb˘Çůďmě5h={˝ $*…X-?ť75éBîmAĎÁFoLţBPťŤŖ̌fž 5eőÁfş´Y>'GěÖ$×>öbŹň•ĽŹ,Zé[„ssТTp5óŮ@kcěĂ´Đ—@ çˇgĐjJ`ŮĹ+*ą·-ď…‰ôŽĎĽˇ.‡ °áw䴱Ⱥ@Iiî#óţ˙'<})MĄ&ýŇ€żÝ2©ŹÁůëďFoG·Ő ˝faîkěú¤dT±VC‹0~°lŐäIÜÂ&Ł(¸µ†±tíđú…OŕÉwÝĐ ęUÉB;$ÉĄđŕ€<‘đâC3 9â¬Ç6ătÓ5’Ö+s<;§ď”Ű ŔHĐ,Ŕ©Ď®5_}“É"%gDóµK6‰ĘbÚ"ë4Ú164UDŠc°$ů.âź’”˘¶XićĎ%\XÝ/Hر°MüŠř Ýő0q WŃ„¨Č®V\zá;üąÉňDš á0óbHšŮĹ; úý@ŽWÂŁ›vŰY^é«|jÍ7÷Tř"­čť&Öa(¤Ü‚{c¤qôÎ*0+öK÷=ž˝ů®O ő%&É,A.ŕĎq‹…ŁĘpłş#ľpWőôn>ꤷŇóŠ?™}}pŠŹ×Î@,\-dđ>ůçž É)ťVŮĎ©V)-˝6CŢ_ë~ăňuß•ěyxâsäěĹgl™˘ÜźčÂü*ś…Čh˙EH4ąć5íŻ\aµ8ŕoĆ„_ľ.ŽJť~#:c ď™d)ň<š®sę"‹Ň2!BĚś˙řz ˙¦uB’ ‰é‚ć;Wš«x,„$‹HnČNŰ5J«ne·ÓďX‰Zî<II ‚Ş—™@ŢâbŠLŐ,p;Pbčd˘˝Ä:0)`d7zlËB3<”˛öőJµ!śĂ—Aˇ'ë5#Ňäe|Á6H2fŕŻ(q<×/fń@^0µvÔ}[ZË8´şÓHę1ăPRxŚŞ@±ß”ű~p”” ‹‘’…RbV[0&i;ĆbŚŠżI FěÉ ×ç{đ° ÁŮŃ5R¤Yf&4¸ĐÚiXę&IîÁcUóĎ5QWNĆL­]-‡AżăšF?äHĽZĎw*HÁěIS/±•# Žé„źhŚsÂ^ľžł?Ú¤o”ÓŇźézC©ŃËLI}€yMqYCxkE«8đY•ţ†ň-ăĽ#1łŕUu·‚č_6…ÚĄÚ± IĄĚŠočw>IKwÝxI~jśnsÝkČř*u3D9•ťÜ3˘+şáşÖI‘‹ŢśW!˝ŤĂĆąĂÇřD*‡˙І€Xµ@Űuqşp§Ő«W+ţ$C Q„µŰľ­˝Ť[…ÇŮiC5â8Ză( ˘bĺ6x­k­ŃĹh­Vé_­ŚďČ.±ö”ëÓŮÇ8Úo¦SŔŇLYBs`XŮţcK«Më’8ßzČ•.·Úeś7E»oh—ąd‹Ř†®r4ŁěQš[ŽUVxĽ?w«pPŇ::ˇ•ß|Če=€Öâx¬ŽçŻÎMózô)ŐneńŠ5Ŕ(rđŢŕyԲѸ¬×pżÂqlĚ:7%) …¨iXJ4JY?b†Č(|]:oŇmpÁEÝďě`ô:çźżˇio1JóVYyűIˇźđŤâąĚĄÓQ ňw´Ú©úrĽÁ%Á>=¬*˝¨[ ÄźRňŹuKx4jš«Ö^L칏¸ăŚM96Ň{5Ú=¨5̨Ů/Írł©ýQ©wo/Ęy7LrŮNVŘÖÔ{ŠW!6Á”S”wŰŤq‘\ç«lËĆź3‘·F?ž–ą@ZĂŻĽâ}÷m4ť!ŕĚ,z+ŠĘ€ęža“|‚µ$ŢÍ\ˇOŤDAĘQEsB1i—÷J¬&=!uýŞÍ'a 6˛ µ=1_ľű¶kި/TS·–Mă3TvŐ°ť¤`ƽښďZp{¬8‹Ě3˙Ĺ@ 2ŔÂkíŽŃ ÔŻJ@÷79Rűą<ĄŁĂ(×!VĎ[ľdžes«?âÄFţYą•z/Ěk”çP°ŇµË¦„ÎŻźFÍ*÷ëŕs˘1—$”ň ¤5śażÚćĎo|P?ş™\ŔřĘŃ2v|jŤČµăe>,ĽŽ¦‘ýf, ęRB$•µ®ŹögޱZstŰ•;O ÓµßÔëk‚ŰzĘţşWŢ !ę ś˝ŽĎęČ­őÚ¶źČ ˙Ö]ÂĘ]Ź(pŮNű87śaŃ äź•¬kçHŢ®ŕrEŻďś°lE”^»ňŤLFůůÁĽ1̲ýŁ)E%úVŕ{%Ą˘čoµ6yůĆŁńq÷ É5Gö©ŻxJLíOq¦jţ‰gVż¸?öE¬Ĺ-ńŃęőR™‘Wؤ),fiťosßM_°L&/'zG‰ ¤…!ÝŇ7ŹţMµ4˘Ćš3ż÷"¸„icŁJÂěą45—[#Ł˘-…‘ö,‚îęüe—€ď%Ĺ©§,»fßr ßj%Q'äcé´ŐćKĂ+Á»’€ź­-‹«§ňôľˇÂU™†Ý]ÝpĘjçIüý b& ®É›®|•jyţm{7^Í(ă dëéó\öĹ­ËâÚ’"x,”he‹§M¸˘że¬ÁK‰Ę[ę˙OËΰt˙fďx:|âŠBéoŔŤs݇?™ľWś’Qf Łqč§~hDažŽĚn’ó)§.C‰üĄ´¶’T÷Ʉ̼ŘŢ»:š˝QÍ\1  cöنZ$uKźC†I´rĽ,:˛´†NmkDůűj;ťşÉźtÄxę>€źAĹ,jPsęń2*ޡäůĽŻgŘ›.‹0÷ö›92ó×kŠ3ĽÇŽ/¬5`=‚+{xĽ·×ţ“˘OňćשgdH zŰD=„7˙c$§u}Ľs."°Î„Rw±©Ł¸iF%M=| űôuŕ;9Ţ ßĆ÷öę41—çĎ$a˛ůößú{a¤ńrţv&,Ă\›oHmŮÝ|˙9i-spŁz%Î4I3a5-ČNV©JJV,®uŮ2x ÖĆĘ!Ĺ6Φx̦÷7‹ćJŢ‘yxr›‚Bw^Wwj߲˘$_’Ź>ĺŘYó',…•Ţd#˛ý$,ÁJň~w8°yČ0íQ†1Żě((óŕ‚ ź¸*ÂAąN¬čĎü8ąSą wx@·ëKxł©Ł*ĺS7—‹z-ZôŃŃ8đí©ĽLrz¨:ł¨7Úĺa0.sĄS = ÁžîÍËv´–95׏ü]*îHŮ?¸ť°Ł¤?"|’ŘLŻŰÝŇ/XdŘSĄšÄź‡}Y'.Oí–ł&őiÁ¦‘nä­~éHúwnĐT"đŘ”?o™6:évŃ´Ć8\MvôĎ5;|ľX ŕy_XćÖ¦«žÝÄ®db$a>úÂ.ĆűôË‘Č-ńľrłČ: L˘ł°ŘŚÎĐş$&÷ßÉN)’Í%ůEô…˘%/ÝE-l– ŕJ*=ž4’s\QĐĽ ¨kUĎ+U—$Tm˝±ˇÉXĽ(§YŰ×ĐôDű,!ŕęlc´I„ń”Ż0€÷yѸűrŠSXžń?†ţľśp‹ăFFHĺ˝ű€+GőµŹlzĆĚjŢßkŁ`©uÎz‰í‘‹Ŕ{d™ ŚÎýó`Cz}­YăđŻ‹ÔnD Ä~ěášşzt}'ăťH&Td[őE.´¨˘iôĚ!UQ˶# śIÍ9V -Ř@÷@zîľ*ŠÄáCëź˝ażş%,»Ç›s˙’<îŕq–µĹ–[ňé}Ăń÷±a`\ : »T–HÄďŔ»u=w\r.ň/4»­ŽsדŢ9ŇdŽŕŞy[AĘâ´W!}tNçŁsŘ˙%®8!¶NÓď8vˇuKgKb(Şo #ŐŔ«Űă4?Ĺn@r„, ¦ű- ÷±«A®Ě*˘ť…¦‰©ĂŁĄŢăÓŚĽ$[S`´¨™ś9ię0-I但euŚęŠ–!\®_w‰ˇ…3֨Ҕćs·;şq2›UuٵšŃ…ÔyrsćÁŃc &Ë™FrßkŤaUpFNmĺcňś°GN0[[/.„A˘\z1<ëôwrpĂptW2ý‰ŘĚżlW Uô©©Wyl •ľŢO$ó ŰŔIěíË—ú¬B˙«ÝOŚ¨ÉŻvŕn Ľ|Úě«ůÁ@^ŇŇôč"6UMŠńľŠę-UoPݲ'‡k}Ç7˛VPV+ä¦,cˇBLßťĹk\_±Kk·Ľ¨úÝ ĘzĺŇ‘f¦,ó›ľ$=˙!•›sl¨pÝH#”—Ç 5ÁOľđÄ27ŽŁžŹI^ É#Ήľ+Ą_=ťůĹĄÎőpuł†®Đý0Kˇ §Kť(K¤o·­gĘ­„vLGs‹źjFo’‘„"„‘R»ĚmeZ…qW$ŤŐCM`ŕ#ěÝ]€·myn`Řšlj׎Eˇó?űNDdöÖŢ‚ ŐHŇŁĎÎO°Qź*ßyHD@Z–üšÂr¶án$€‰·řÇ'Pý3ˇ˘ŰCBDż/Ň…˙ޞB¨­ úúó'Cr›w4ĺxH˛˘óý)ěŹms\‚\Ý2 ťĆ˙°LáßźT?p¸ß2™"4Ň>Ŕ}Ý·TÝńK+™ŰŢH@Íą×ď˘Ř]faAŔÔvÝÎ)Ţ'í„•Ç b'ĐK2§Ććb ¸‚`çűÇ˙˝î±ţ-Y`•fŇ6ę×9* .ńDrŰ˙˘}?Óş^Vťˇ‘˛ÝÉ@µ”VK7ű„BҢ|üźÇ®_?÷Řľ4xÔËüÖ~PgZäőgŤŻó@ET o ĺ <Ľ_OŮ&O»<‰Á®€żW‰$LńT>ü7˝ŤaËor7śX˙;Ř Śň· „ś\Ü–@” ¶&H ČĎÜ5¶·ž "c5ľŁĺ p¶ČÎ’ZŞd-1đ7骀Ţ0ůáu*;ŔôşÖüSIYŲě~5‘6!»‰ćoĎ=&ѵđż‡ĹFvm´Ů«ÍeĆěë˛ S@GÜ:Hs˝ŽF‚0Ô\HUSŐc»O_ž±µ“Ö3Ó6!ŠŤ2eô#(‘#•©”Ůěfâ9$¦U4FČÍŰ´ő`lđŇrţ)Łń$Ö"ßÍ6×ćjlqźă«=ţ›Î\SRúĘ'>Ň̡Qż2Şó AÉeîEnÍó~{ ć|şŘ®TçŮ,Csuą;ţo+Ô€EŕëlřJĂĄI)şË"öGś ’±¸wĂG$ö@uGxum–Ę!pŠ9Čr.5nŁĂ™L"ďoÇŇ2…+!G"ú—–Ťy¤µH{1‚#®Hb*$ĐŇĘŽćđüZŐÚˇé80)(íЧĐ÷–jĆ *Ĺ#ó‹Ý“í[šË'Éj&~6ţję + ŻÓbÖ<×B‡ńáiR¨?N™1ý–nnŘňű®ŢfQč´óŮfÔ{ ŘÉĄŁ™ÂěŹY"Üc\¨ŁÎç:ënfb7ôbbÍť˘_ ŻůăAę ´ŚµĽrčĚżŐ‚±÷ëÓYV–éšęŕ#Úě=_v¬+‘㱺†ˇłě”=EvŘA‰'ŤUîŻŇĐÝ´Ńň`ť= ěGä+xÔŹ(ŁúŇVĽŕPú,ż_9ŹPÍ?<Ň3Ş‘ţŁvŮcŁë”îȄԳĐiĆÇ=kv¶,( źŰ9lÉËĎ‚«ŕ)U¶›t†'hĘń °J'Ť@Čc_<źĽ[* ®b^Ęe—GŽ5ęÜ]Ł4‚ŹîY¨{ýÓPćč>q~ß‘ő_ÄüV`ŻÝŐY ĐĄ˘›xQčŰét,Y á{Ó®č[M ťŻ54~ą}ĐnxşUčwC50Ś·Q?ČÁ\S-sšC%ýˇńCT‹*ąa#–ôµTĂQLŢ*ÄoÝy!čt=D;4k¦ÎO·ËÚY0Ě'%:Ă ŤäJ.ÜC%ÎX÷C€ö|G?łŔččnµ†JfHĎ1+1k4ŻV;“|j5%­TŤ«¶Ç擆›{ŔŞôśŠ‹ČŃş]EŤB •)xő_ŠV¤ńa887áGú4ř¦ ĆÍúWÍ]`Ó6Q}Ş®‚!}%xmÉ–Ëg“}>@±ć¤›|ŕ|"n¦7*‰%î’†ęcŽŻęNp[-SeÇW9Š!‚đ‚ÇCË0@´îç×ÚS$Ůě?ńDˇ=ü˝kS¶ĐynŁ ĽĂtţđ%†ß­LíŻať’čŐ1Ƶ˛Ôbeetbox-beets-01f1faf/test/rsrc/whitenoise.mp3000066400000000000000000000403201472325477400214750ustar00rootroot00000000000000ID3FTXXXCommentProcessed by SoXTSSELavf60.3.100˙űTŔInfoU@€  #&&),/2588;>ADGGJMPSVYY\_behkknqtwzz}€‚…‹‹Ž‘”—šťť Ł¦©¬¬Ż˛µ¸»ľľÁÄÇĘÍĐĐÓÖŮÜßßâĺčëîńńô÷úý˙Lavc60.3.$@@€íŚ{s˙űTÄs”p˛w `t‚]ŕ@Ͱ†| cĘ:Á÷ń!Áđ~ á˙n°~\'ßĺz9wř sĘŕţ đ@Áű¶Ú]´ÚidqĆšE’@ĹŃÁx-žM3’d Nź űyYNäiV’¬^SbˇŐ­pđôwŘ˙šó!ô’ŤX$*ËZ€l͇>Áp˛„Ó˛ĺX˛ouö˙˙ţŐş™·eKm’ L¶Â˙űTÄ TMŹüń€OŤ±|Ć (\¤’¶qC.‡Áł4 AŹ:ú!7c… e\ “‡Ę‡®BE„ĆEMâUĚ„X! 1’·­*ëf‡˙ˇţŹMUTD9ŞÇD€‚xR?Ź#‹Ă!L {Eće{sŐÖlűšÜ#h*MĐ”ÎR‚1& H!†B–••ą˘âŠKš2WďŔ5© Š7eČŁîWłŘîµ™•ucGjMáü@\~"˙űTÄ ¸«‹ćiÁZ‹ńĽö( ‡%…‚„ ™ăOT-őü1vŮÚa¬ Iv+ €dÄiČ/Ź·§čgÖV{%AőŽź®ç 9LI`L0® @M2E@¦i“ĹŚYSíߤFH¤’Îl>ĹŹ2±ĺ˘ÓçŚ5w~‹ĄźóKy¦ żôĚĹCÂş%I$q śb'4’ÔŽľ>Ź%ôĎ`g:‹kĽ0@ą‹ ˇp<ąhM@„*ő(L*h9[Q›/$(ĆĽµLłÝ{ęKR‹ŰR=5l©•ge>ĺęÂ:ŇĐ;—‹¬čÉ˙űTÄ € @c‰Ç°cC‘ň<ö ¨ZgĚQ¬TM:®™Ó쌶Á$QŹ 88‚+:ăť&.ÇRĎ(ž4{,]=izî‚lďJ é÷1čĚĘƉV[dŤ”MD@Et Bű§ŁůŔű`Ŕ›wxhě»G6"χ;Ň+ž ŚönĆ\M#ë@ě2ÁŔ˘9i¶űôŃqqK®ˇ_˙һ۪šxK$l˘pA©)™–,2vx(2lIĐ"˙űTÄ€ u‘ć$l(Źr|ŔŠXiŃ[E[(–|úPŚ›‘™9˝Č:Ţçö©çn*°¸öôÓň¨ú¬łUź]ć]USĘY$m  GŔ”°8…B Ů‚y8KLńhôĎůµô"8‹P•˘™Is­÷ńyădĹŘ5JEŚśÄŻxP/íQ5=»Zš¬ęľŞ—‰‡gml‘c°TĽRp„ Í…y~H„ś’kNMkńIńŁÓëßýHÚ4+I „˙űTÄ€ P•­ćjń r<Ć(á@DÉ=ąťeßóú~ű›Ú™‡V•¶Ň óD2€ É$Ŕd©*ŃFˇšŃеz(B,ă"Ă^ö›@ŃâEě8Ä%!ćBŐ¬ęŘÝ ęCÓ­WĹú9ŞiVedC+ŞP1U‚Ěd9!!/ĹF†ČŔĂЎ=…UZ™ž€##Ŕw€cÇ­÷Tˇ hí‚d–ůś×ÍnŰ\•ć˙/«{číÝÔËĂ˙űTÄ'€ TM‘ǤhŮ&ň<Ä™»kl« <}đđôĚ:mŁŠ šöČŹ™mzŇď˘tPyCă@ śJ”5·´ak ‹‡^ĄĐsţ­[z×őÇUÎľ™x†I\iç™>Q‰øż‹M,2…ÚiĆů{ť2 ńö+’’fŤÝáJĄ ‘.M'ŹŹS”8íépF·Ní§ÝM•ţż\34+2ˇÇ#m ăuą LÔá‚HX™jŚXv M˙űTÄ2€ 4wŹç¤gA.3|ôŤŠĚc•n–©‚Yş¦~ 'O÷ěÄÎÉäz|štĽ%‡EĄŘ*8¨€öB}ź‹*ůś»vKli$ÎW´°$C­!›< ›ŕ'0Â&‰ -ťĄ3č·?rŮĽQ7 9Lž%şşňI‘m®Ë[řşűżRUÓW•SÖHÚ ‹Ŕ˛ň5ގ©ŃŘŚM,¬kŇS’5ĐrE ‚I"˙űTÄ= q‘ě$hÁ,†˛<ĂČIKG#$‘iu¬ržąó˝ZŰ˙ýśU‹ëę‰Ţt],i˘ ä#á[[‹ËÁČô`°)Z–;ó …P¨-çďZ()cŽ ‹ —ą$pJ§,»J ¨@P«ĺ,°I—Zű>w‡…TEŤ @4Ž cŕä$BX|`®%¨˛ĎÇČc€NHđ*RjzNxäJó -8Ô€’‡nü{ŐťýŇż¸-g˙űTÄH€ Pi‘ćj.Ť1<ÄŚŕ‹×ÝâôU¨©Š‰uWmřCEA¤xP=6t` 0búVy8˝Â콪ÜX LĂbf‡ŘŘs&hµ..Ö˘źJżTçú˙ÝWY2ě†RĂX@@P8ř€ŇŐ°é– ŕá@E‡`ŕIčYdŃĚ$‚w%ĎĂl„ĆZ"í uhK,}ý•ÇMl½Žô*ŢÍĘ»‡kmL€!Ř"˙űTÄR€ \SŹćlÁ1ńx¶ 0;"¬DheuĂs& m3j5%ľů=4ÍÉxrY4˝Â¤ÂăŹ4 6¦˘‘d&ťMU^MMÄ3ŞHŰh€GĆKC yD(5Lt[`ńUŁfÇ xí"#jd®ü›ŹK¦R)¬żrÉ­R řą¤2}ö†JZ†×űµýżŰ˙÷"¨VfD?–@¸ötNIB„ (¸ix©PŇV€08˙űTÄ\ k—ć$hˇ0’qüö 8ŕŇÂ#(Ă}ć’â*Zkw˝yČ«Vf٦¸oŘ÷u‹÷ř¦ţÄa Íş«–vÓ[ ^SŠU sŃ ’•Ôˇ…b‡ †@Ě ë!íhŠŹá@¤DÁń@˛É˘ŔäÔ6 ů;ĹťěF¸Ťä,5"ŻŐśěʸui,Ť’TűYm2‰*Y5éQŞĄr…0CeEjwJJVK ®¤h~tÜńӫב˘Xšpű˙űTÄg P9‹Ć$fÁ1‹r|ÄŤ€aňĎýĎ/W^Le%¦’.Şâ‰ţ[×ÚżîÚ»»©Ek$‘˘qU¤ňĐđ;ˇbđü¨lhąb“oN!!ˇă€„C :…”"Ô :LB§Qh"@‚9ńK rC—SW”˙ˇ™Ę‰–EnęPą‚Ź ĂÓâ8´–:é% čÄZ%;ç!í›5}˘:j‡ŇKďíO3NĎTŢ­ĺMł¶„lµ=RBČ»é˙űTÄq ŕĎ‘çlÁ#˛|ŔŤŔשTľ}ĽšĚÎL´E"§ÝH @nŦX‘¨IDÁ.ä±)â´,Aż}“ ™&*q˘Čq€„>ui*Ä­?¬L]ó&V<Ű(°ŚHř¦˙÷} ±GŠ*i›wf2:™B8Ü~`gdçe䮡:XgVm›("źş7`Ś:ÔL´¦]­ÂŤőkÓ…”ĘQîZ‚‘•!¬zŇA/[T–C­˙űTÄz€ Dą‹ĆlÁXqxĆ xôíi6Qµ›&¦aU«¤㊄IÁ0ôŔx”L (–BňoµÁwTi–ąÍIs)öţćŮRŁőN!YöIK…ˇČ56]`h€©V‹3ě·SK>ĺĚŃWŃÜ™śĄU[$ho bÁ0:*ŤŚ #˘u‚9Řłv\L”…J%s1OŕMfÔ,4 Ć ›´+ űR˛/©*;č8ĹÄť)˙űTÄ{€ 赇Ć0aÁN“qxĂ ŐŻ»÷ToV]ËÄł˛XâHz ©ľx®MôŇUIč E& W+\XőĆďÓW9“ŘyŔ661'Ă+FóÎűgŮ]\ł<ËÔózwťÚŐń`*Šťd{&+îäŞý=7uöެ¨V†TTâD€‡7p>-¶CË#hX*ĘP[UQ‡¬SŃnFéŞŰ<Ô“”‚b°µb8ć Ô(-qđöŹgŮ®-»w˙űTÄ{€ TMŤç°aAf±|ö řęKlîFfŐLK™ŰZI"˘Ô‡¬DŦĘHců™—–XIýďzĎ'ćQ¨¦ĂŠbč¦^4QÄ,bmç %ÁPz+z»ńu´78IăěbŽłżęšąyBYH cH®UŘ–›P’v˛-‰h7pSÚ^®ćÓ ”;˝ëśÂd”zšm"â-jB±vą|bóßFŻ_j=”ĂĚCł*­‘˙űTÄz€ Ôk‹ç¤cÁMڱüö0¶Đ77á—‚JĐ řC$‘ŇŕtęYç† DŁÜŻ¬ÖŽ&]ܵ_DćłµpŞEPTjŘlřŘóNjK¸±­uş«ę»şš¦M,qV+®ĚäŻ$Gɢi!Ť'— †F8Ń–5u’Č‚Śé HÍ!<ăa`hJzdT@$Ć „A÷]ća ?®µ2®©›™™eD±˘‘™ŞK„Ł7ŃÔਠ˙űTÄ „cŤćnA/Ť3˝†˘8.Wa–šn.T ]ŁĎt :#ŔENšĹŹ“¨€ć2µB*j&TýĘ·Nű–Ž˙ôUŞěޤfIdM’ ” ŕář7?$ ‡’›0WP) #¨Z}Ž0HŁIˇSQVŤKĺ­&¨Ť‡&8Y–$ěÇn‡+ŁNĎBň˛¦e™RČŰDŢs*ú.+(ă…ŬitZ‘VÜűiDA}›žÉjP˙űTÄ€ i‘ç°i2бĽö xt"~ˇ áĹĹá!ńˇďIp*GĄĘ’u‘—˝ßOî˙ţµhdeTB*©U•#ěÄ6ŚWh°¸E‚TbY«ŹÂ:颖¬Z–Ńź\Üăćfۨ1SB)ą“iDrĄ °˛…`˛ámÂëîzŞęaâ8Ú Ľspľî‡ŁÖ.KÂBTDëŹ|…ŽŞéžäź S(¤6.Ŕ/rd„A×Q© -Îʦ˙űTÄ‘ h[Źć A) ±üôŤ ţ˝©®ĺUěo'żŃr›¸ą§TI"h€mVeŶź=âˇH$&ń$u,†Ľv\ŢÔ­Ďy[¦¤đčhp©vC̾ёy Y‚ݵĹ˙«×S::ţşşą™D®FŃAzÜM”j#‚‡L4B=ÉѤ‡<;y{ωźQBpŔĘ[¬Kä ĹŇÇťi3ë}6lj[ˇµoîVęuÔďýuĚŞąhf7"i˙űTÄ›€ ›ŹÇ°g1Ś1Ľö p˘ ÂĐđK¤Â“Đn$@L0‰TIŤzý}!-«ęŞNlŤąE –iň&Ň]aĹ! #’Âq9ńŞ…ŞxÂx4Šś»ňĘüŐ¨Űv]TĚĚ!ĆÚI@Éů–äÂD”xđĽŔť¨źĂ±Lć}s'ixg\‹_3p`éSĹ †„r'ç¶Áă‡\˛&ăŹ`pŽe­RFÚ˙ęĘʉffHŰI I(’óŐ ? Ë&˙űTĤ€ P_Ťć<ÁA+‰2<ô™@ULL+·cÖŕôdâFłľÇ©5ßmIYÚÂ"ŞęĹdíó?ŢwS‰‡3§ůče;Ďň|Őčm`4t{>®Çż’¸Ş¨š…;H€ńX¤śAŘ|"BqqŞŘť&·˛­:cgnďHţ廸`űFŇóihp`¨}ĹĎ™Ú+(ůwjoß÷}[ď]·}YE¨™™xT%D Ŕhr†đ€ § źTĽX«*ľě5˙űTÄŻ ÔaŹç°cÁ=Ť1Ľö ŕ”jmĺ$S:2 ć Ä® F €Ŕ ,©őX?!k]M1k?űR‹młúýoK™¦!ťŮ•Q$m¶&ÄčWÉĘe†YÝ*Şťęř›„ íJď|ÄĂ Ń]ŽÁ4âęH°UŚ‹¸€ő°PŐbĘ Ş $Ýö ]őőqEŞŞČ†u[#I ‚ҰĹ«˛ˇŃ<’m.`f˘bBő«”ň°M7Ľ†´B˙űTĵ€ áŤçl: qĽĂ Č0ů2Ć@_•ö©ď"÷ĹŐ˝ 6Úb l¦d!HÍÖ%^ô˙©›/:ňba–mbiá-Łő:XL¤,Ą<DeXËBÖ“0nôŞ0¨fś¦şrrÚ5ióѵ ›Č’ ĐÚ6Ň QÄ’~‚Ä:¬m5ň(ëcżĄĘ©™¸v8ëd’ Ĺ!$~Z†č%*i'ŤaiĹ w·}¸ä*dgô)*ˇ.‚Đó˙űTÄ» ¸[‹ćr; ó|ó ’Âŕ„˘˘ÇDˇë*ZícąMÇl;Ońěźßu•tň̶ĆŇ$'D5}LVĚDZ`jYˇÓŚŇH;Q*|Lš‘Ůž],h’—ĹŔÁ Ac1˘Ťr‚Ó(BČR:…š!R×}:˙Ý죪ßéËŞŞ©vkd‘$PŘ}¤Bř<Ż—ŠŞBRÁ‰Íd;$pyŇsAf”iădyląčě0ą"<ů€9âBpŘ˙űTÄ H‹ŤćmJ2<öP&Ü|a–Z,?EŇš/?÷ĺ/b¦ć’¸ÚL€‡¤‚!@íéŔřz4tGy‚ëż·7# á® &…lE• X MŹU'@©c<(KR ˇOK“zë%‰ŔLÉźŮK¸˙ţĹŞ§yx†T–ńrc™up¬ôł@?&5ZŰěÝ2_ŻCC„1˝ď„ĂćĂĄJ‰†Cčś~G[ioeoé n»˙űTÄĹ kŤç°l<ŚqüöhSQűß&˙KUSYî•8’@€`x=¦…Ŕ'Łéh¬W=rätHÖť¨9ʲĎBÍ4͙ѱđi,#‰ ŘMŤĐ#aůái NU,Xި˝ßżŻîÝôUďíĚşxkc$ŕZÇÁ3F‘ˇYáXş˛S,Ds#w§ů‰ HPő ‘6ćËX°›ŻcŹ ńő(ľö)VŁmMMÓO˙űTÄĘ€ (w‘ć0aÁGŤqüö 8=SŰ\K1šDH^ŕŃQÁ:ĂC Ó:(ęÝŹ»^Ň±Ł…(˘G<ô†ÓEIJ 4]âz«‚ŇűÜőKľâš[ŠPĘîćä`wM·úU›­x†„HÚDr¨%0.fS|I!%9‘в:–٤˝XĄĚ2y¸wR‰2 Xë’ˬQ(m´ë‹%ž>/Dm|u™ß×^ąąÉ§‰–b˙űTÄÎ ÜY‰Ç°aÁU1ĽĆ xş”GA|\4’" ú¤–ř€ĄQ]BÓ¶0DÝq'áPpL\ x‹Ť¸Ş‹><…bĐęa—±oźBš[Đ,ôVw9ö¨ł˙úĽ™¸—fJš@"ĚbŘLŹĺYÔŹ=Ő”^Dł˘D&4¨bRhéčöŠ ¸|ذć˝Ó¶¦Eеć ůBĐńĹ„’[ڍ˛ Ů+b˙[i}(ţĚľ_úę«©¬wTWcl€ŔĚč r(Tz~"q™„ÂĹ fXŔ‚ ć !Zd( ¶8L¬ĐÔ‹ĘFXx„ %‡ąŁjc'[_÷9íë˝×ßďU\ÜÔĂş8ăI˘ŃĚ(#)'r Ţp„á\¬śé‹@)˙űTÄÖ Y‹ćlÁMqx¶ 8M >˙UŞxDŚäŹ:qŤ"v| ŔG8ě˝é¸ÍC)´śmĄ¨]»ţbĘ«ÚxvtO©@Ś8 ,Brħ2*óĐXŤ’„©Şęnb,GĚ·ÇF :`Îئ#ĽŢRÉDQT ľtU"%łC†]§›©«0—lyź|ŇĆ!*yőiĽuÝULş±§H‚Qí¤—4šĹŢ-v䇝)„ČR§‡$‰ÓÇHĆ˙űTÄŮ€ @=ŤÇĽÁA11ĽĂ DoX«/^®ÜA P`p94y‚§ÓĂdHŔg.ĂÁdŤj•ř˝č®˝á·®ÄŐi·sC/Ą€Q ŕč\ÉŘ8 ‹z,>TĎĄ‚ĚAWŘŕĎčÁ‚Źa*Jr‘:\K´´8aBĂRć9h]ă@ śh»l5”´ýč¸J SI0Óa ’“Ý!;łS.®‘¦’ Áů †@Řu  ĚÇň22Ůĺ˙űTÄ〠‰Ťć$jA/бü¶ 8 q)O§`0±d¤‚‹Á…%OŠ­ qd-&.ÜFҬ^ďŞső˙·Z©—htKˇYUĽKśÇyĚMš`AÉ  ‰é N OmŚó»şsf𠋎a@č,CÂy@ËÜđ’CcÝ4›whk÷Y-[TúLÓ[ŠÎq‹ HŤeLTËłˇüĘ, K@€Ä€€aÜž9 ĹsżłÇč?¬żxÚťŢú˙űTÄę€ T—‰Ç¤jR ńĽó 2E¤ÁYŕA`и©âĚ#2xŰ\–Á‚îS­ZĚbĎQZQzlM-‚Ó÷ěŞŔ5,ŽÉUÉą‰d/ꀒâ©8ŃÖl3˘xü QQ™\rs#ޱl[›HŢèL.}«†Ü°91@pî1U,Á*\—jǵö-ĄŇýžĄ—UĐŚ’jííáÝ Ä‹‚lNFÂ~+iĄä,žś¬Ě«r^<^ h˙űTÄč |‡Ç°aÁ#1üĆ 0 ÉŤA:ě%: ś#Ú6^Eš'ÎśĎ>`ČłLm›yCµ4búŸEksDĚ;Ň´'a‘ą…‡†gveI#Ť¤Ä„—ÄZ\MÍ[Ç‚ą™˙-]KĎ@$‹]Î…:¨¦ňQ"&ŕ˘I&N±ëD L•Ŕ( cµń¨čőDÓ»ł2T€ O^(щĂÁ!ÇMd¤§GlQąEŽÄhÄW"˙űTÄë 4Y‡Ç¤ČÁb 18ŔŽ@ó÷¢ÝÍ™UT†… ś eÓbą[Ĭ…€Ž}ŰT ¬]ź-K ¨‰yĄ¦U±×Ş™Š¨‡d8ŰH€ â…ˇ¦‹X[$t\]Ξ!Y90˛Z´DŚť[†K3÷˘˛±ŹOĽűat´8pićĂQçĺ÷ŹŢĽ´ŹÝVA=R5t]ÔĚĚ;Ť´I ň( ¨„¤GD"9T>“ Ć_HÜ˙űTÄç ČU‹Ç°eAa±|ö fĚíľ%ę ŕđé‹4%*ˇ)˘ ¸¨()\“čzÄO6uń•Ż›^úüż§ď˙ä—ľ—‹f–U7DcË«€`3<#2Z>(* “‚“2ÜŁáYâĆ Ç–űfÜ©~ЍVC˝Gś6Ě˙ń÷39ĽËćEub *E͉±…—>śŤ*,ˇ‚Üv”ľ Ű™)Io4ć„_Ň4%pŕ>¦âJś1u˙űTÄĺ dcťç°cŃtđřôŚŕ[fD‰g =8uhÎÁâF¦šY†B‚éň76ĂĘŽ&ð“ Ü˝|ďĆť¨zɻϴ"1ŽŻU˺əwmc­Fĺ‘çČ´« 44ĺ;Ć‹đ=u˵%É›eŢH$Á gŤ“ł—$gÖó(ôőˇ† …J…ܲ%Š6Ç©‹»˛ż˙˙Ą ±ť552ôę‹tȆÔ8JłŔ ‡„BYˇÉIW×Ö#uö[˙űTÄć ¤Ą‹ç°gJ‹1Ľö x·»×uDě™EŻFĚ—'…ţ´Żyڱsˇ˘+—(d†,nűN…ŞöM)NĽ·ŞÉöŹąőŞuŞŞx–EKµÁqv)I‘Řö„Id*l÷uŁG !zË3aÉçJˇş®qýĄDě*’ŐŢÖńşśĐpőŚŻu“µŐžę˝ő;Öż¨Č§vRúˇġhŰ7Q¶k+Č''€™Lľ©Ś$–1ç˙űTÄç€ üهćmAA 18ÄŤőҬÝ ÄćŚ~15Č–äRă97 ¦ŃhŕJiIĹń­ZňŽ’rďŮ˝Ďy¶Huâµ°¤`ş¬©Šv[,m qŶâ<¬Ľ@<*—Lś0+R«–·gÔŘŐ-T‹rĘť0tđé Xýŕfc€ŔqrdÝMkm,bËĽ˛ëëŃJ;=U.ďĆL€„¦:ÍÖCÜÝNˇAú°´°<<_K-€Ĺ°…˝%˙űTÄä€ Ě™‘ç¤iq“ń8ö ¸"ą¶+v‡Q Ě„d  8s¶ť;Qr»Lwľ‹+ż~ľç‘ÚFéÔŽ…ĄŃQj\v<Hć‹ÖY–şm5 ­ă)»»şş¦dŤ´((ćŠMÁą›Ç„‹$ć@%SSÇk˛NI9C$źÍ}ůś(ŔŘ(x€Yď* ÖŰ*RLcŤ˝CëJ©Źh"ŻűÚô*nć#m˛@/é^đ|"‘÷O(Óc‡˙űTÄä€ đw‰Ć$lAdń9– č(RS¶<Óݏ˝ ČW[Réł1ŽDw1.Đ@*ĐÁh¶ł‰˛âäµO5i˙ížOőUşşx—vXäMă”Ę2Q‚TTsĄý0°đłŃ˘…ŰJqXťVY[ ôS8FĘś®áŹoLhŁĘ X@Š ‡VÔąĆĐýßîwOm?뙉ŞWrFUD0ŕ† tü#)ޑʓ®5$€’Ói‚ŠÎfJ˙űTÄĺ€ cŹç°cÁޤ°¸ńŠ0ë¸M”ŰË0Y#ššĚ˛Ecţ‘d…güš ‘íM«(ă–¤éŐY ´Ç›Ybg  ÍŽőµ'łîJ›şťb_úCچ!ëXE!©Á°µ@ŘĐPÔ\oŚbG0ĚiHH —Ç,¦)€cDëÖŠ†ŕ1Â×<€č0i“$Xç^ÚŐŰô˙ú™îMÎ˙TŮ$ňFâA‘ˇ®=Bk{ލ¨:U`ű—j{©XµS°×Ĺní˙kńŰ}xń«ýô¨ĄĹ ˇŘ•q‘®5¨&˙űTÄä€  {Źç°eAr–0řĂ ĐâćT·řˇÄ5„¶š>¬Q µô6)tË/ĚŚżľ`ÓĎmr#88y#Ăď*'ebč.•ăf×>3ˇ6ý˙újÚ»›vd8šH€ #y Öš'Ś\u=WPëęD¨ŇbI}óŢ“÷:§9Ęđč{BŹ@'áä“"‡•I!bôJ}=rX¨ŢŹżo}˘Ş%â]Ôú”V`Í@D Áˇ©jĹĺ˘ ŽZZWŤŁ¸‰˙űTÄĺ TeŤÇ¤j€šđřôŤP*ć RµsË0ĄíGŘË78ľ“ý¦§S%?(ow"”şÔŹs&>e*0‘•L Ě…c€q]ËSж‡*Ţ««§†mdŽ4 fÂ1±0Źâ1ŚeE–ű/…k*dC>Ť Ł ?(ŻÝĂEĺ|Í;ţ¬ěŰÓ?a÷ÎÉŘńßîąîŢ˙­»W“.ě˛H𠂍F%`Ĺ!(i; ŰI Ë*˙űTÄá ,]‹ç°cÁO–ń¸ö °:ŢRSŢĘcő‰“«“D­ aXé• ‹Ś,ÂcŔnW?cTßßw˙öţýu™ŠxS(™$iŔ„#‰á}qBlQRyá8žăVęPŠóíćLÉ‘~^îŰŘPwǵÎĎŰ8ĚŻ"CťňúŽßźť×˙üś-kÍÚ<ż7Ż˝z+ůŰm;^ŢŻć?Ů«š›–uG#I({ŽDęŇó¤ŠC,/G(®‚–Üp˙űTÄ〠ř…‹ç°cr°řö 8J ´ăNy:(…±ÁÂ0‹Ę.öI »"yzÔ%VĘËѱ[z:żÇˇ˙éúŐĘťwt7$€¨—'(/ČuÔú5[výĐK,ăW‡˛ L›¨UËü$Qá‘&€č !arÁtZ)´Ľ(#Äé6 …Ž‚Î,ö,Qö·ÍKu Ŕ‘D»JEµş× RbfiÖÜiŽ`*ÔÓ9Pý<Ŕ,A2Í˙űTĆÜK“ć0aÉ*±üÄŤP’2 *ř2t,ÔĐĘZ ߤtX N (&.“ŚdĂQ~<Ż™ÜĹhýt»Gm­GĚéű*şš·†g[D€p¨q KăčÁqäxŃĚOlrE‘@,zˇŢ,‰"Đ9 •ŢŢô‰+1BK‚@H6Cščjl‡ĄŰź{Łtw(ĺjľ‹om=kbzę¦naMF’ P !ëCLÔ  '*¸¨…¬¦}Ě+d˙űTÄë ‡ćOI1 ±Ľö ŕŢhOĄ C‹ô„¤@WcCŐľö`‚Ä‚­žńq8aĂÚ<úÚ}ŽSY´Q,!ĄWŘÓWŠ}n“-(±řÚ«™şTYŤO5yŘ6Ëb˛ĐÜé1€‘bę|†B•Éĺc›źĘ­µLüéŇ>VĚńĚĺŇć.|\ áVźpą ŻĆ&ôűYwűżşŐ0mĚÔĚĂ™ŘŃ$€)3ŻqĂ’@±`' .NĺŹĺŚ˙űTÄę X[‡ě<ÁA=Śq|ôŤXÄ­v [ě`ÁV¦&Ć€‚ë‘CŹŚhç†HÉĎpd:€;c\¬ ~^éľÄoWW×˙GM¦©ŠwE.Ą€IÚŐĺĚĐ=ŚC*tr=Š›`a™; #:›‘Šł›Ő~`Źą‘1ß-ŚVćů'ÚdfD06*‘ŔşsŽ:I©…ÁĚx«-]ĐÎę:(IP¨óJ lÔA*ˬş™šu–6Ń Á6Lĺ(Ć/ÇëaśźFˇń“ç3*ť˙űTÄć ¨q‹ćmAqń<Ć x˙$ű‰“$ Ë,.!őUˇ•bN–ץ)]%—Š ŔxABQÚ}îzµq<öĎJő*ĘŮ»—¦Ydq”eaˇ(@>¬cáÂ%(M6č„őź~…)ďTÆV˛/ŘDPÁJOis˛µŐÍĂąµŚHád©¬v]˘s6żw}S>Úk®nbŘ̦•ň ěV”„PéŔą±r´fćK\¸ÝÎ*3`ňI˙űTĆԑŹç°cA< ń|ÄŚŕtĎ•—ł$K{şe€(ÍŁ5@‹›b$·;żJćďz<1/͆ý_ýw˙éwż^׿Ěv긚š†U>ş:RjsÜÎW˛‹Ľř¨INKIÁh¬NĎ Ŕ3âTź ˘†­‡!e5’˛ŕ,x„ę­ńIM‡ Ö&ÚĺT¦Qݧc_oB6!š)śÎ–P̉ÂÁŇ€dDXQ¸rĐ$ľ·\GGLPŞ`ĆáYŃ©˙űTÄé ĐĄ‡ÇnAJ”qüó Č”8^UŠ=M‘¦QĹŐ‡Hňŕ‰‘ÍX«ÎŹ$ íeí`P¸˛łÉ$äÖćI,Y ’}ą¨›¶eISH€ Ią®S‹ÎPÄbÁ$=B„ÁbņVu\4ŢaB6|‰©‘f×ʆö|,°4E!đŔ˝€!ŁCEŽĐŃU®®˙­hů[]ýFďjjŐVČÚD`÷Ô=”“źçAË ˘ ŽąWZl&˙űTÄĺ€ ¸ˇ‘ć Sr °řö ˇŤŘ„ŐPP4q4‚Aô VcšČ«@ë’ V,÷ý5ô˙úéöô*¸Éwc;U@™úÄa ę(ś%#«\€vx˛§oŻÇ+é’Ú‚X06 «:2@L\ă˘EĹĂzpD±aŹ!)ÜU©>›6EHcŠyR—Y®ć´˛@©USS’Îě©ý*ÉET'ć ‹zddC-cH»˙űTÄĺ€ 3ŤÇ°Ć|‘pů† 8“…s}&´ŕ– '`qěĹŻH˛¦ćąĂQ˝çÇ=RŤZP/”§GÓŻýÍîUxxi”F)Đ ŕtřľ#ăá4Bň]ś9G4#6röĄś1FŻŇ ĠȤP&|; *AH;Á ¸łťy±ˇÔ8Údáë¬'2÷˛ŃN·˝§ŚARńÔË4qËx†4#éP 0DH„P ô˘FsŹ­í 6,E˙űTÄĺ ¨‹ç°e+1üôŤ hA˘Čŕl %° źŘ  Ä% K0âMŠ&DmM}Äάţ×ĆŔŠ?cµL^¶jZ•ą™¶–‡ZŰI#š 2dŐ™($8B¬Ęy±ŹżŢŚ/+» §µ©¦AâüŒۆR*U@‡ČdăPĺ<ęÎĆi’)“IŐ„ŠóŁĘ­ł“Ń%­qlŠRŔ-Ă{⣾Ŕč>k~˛CUmOŞŮeô{đŹwëüÉż?ő}ö_ŕ{žăŢm÷Í˙ŰćÜío°đďĚËiBň‘2eµ#vČu¤‘˙űTÄĺ€ @3Źç¤Ę0¸Ć 1ž]R"…“šž’§ZZXţDh«–TÉ*ľň3=W&8Dĺ#Ŕ !\’«ęó‹s]MŞ©†vuc8$Á5.¬íćZeYc °zµôĆ«“¤9Y¬ĆíťFÎ_>ˇ_‚”< ‹ ÓŁäßl\di˛A0Ŕ™pµöúö‘>ˇYʆIż™(w—•pꎸÚ$I„ĘT,)Ś„Ł+€(ĘŠô˙űTÄĺ€ ¸GŹć$jSŠđřó!PYÂ7*ÎvđŘ…ČEEÓ2Lȡ®†Ea"g…Rő2Ş ö±­ŻTŹŁ¦—¨xgC>ĄCĞ̂AI(H˝ˇŃ(u[ ;†Şx˘ŤşQÝŞ·@Er/†>‘Plŕ8Ŕ0,}®@P¦ŠĹś545-AdgyŃTĹą |,üß•˘âňZ”Űq˘? Sľë Rt‰K„¶ŐŠtőo,Ž˙űTÄé€ ěK‡Ěât¦e9϶TcT­:1Ľč"@fLŃîÄrťń3bd4€ä °Đä€Z=DŢZăU#]±żu©˙ý˛śĘŞxuDQ¤­a5ô*Ä‚5äń\daů…ʱěGgĂË˙űTÄé€ x‰‡Ć QAa1|ö č-§L@˘qę4t•?Z”p¸”đ&K^,L° °hJŕüđÓ€aȵ˙_u2ܢڅző©óUÚĽ¨¨eYŤ”!¨ŇrŤ%€p4H2 < W”1ŞŃT &1A€ą'’<™3arà ʢčÇ·oë˙^Şxzxe2őqG–âňOĚŁĺĘľ“ ź 2páda]ěTŃäAJć&,N*±±˙űTÄĺ 4e‹ć0ĂAl•±|ôŤ0שWJ÷Ě˙žĘúš«ÍłŠ“˙lZżúçg˻ǿÎMĚźć$nmSŻo~1ť@“™é–Ň™‡yxDJĄb # Đ?Đ!bÓČŔ@LXFýh&BJďî–om$!Wr”Ä`4(ąe&k,0Á±{jEhE-MÎlqlęMQ*¸ŘňOdęEQ3U0’4Ń‹đ‚€8ŔtĐĚi„f]XGBőv—˙űTÄä aŤçjkŹq|ö °OdĎg!-pZ"[ ĺ.o™ĺi`E`3c ©đm3ŐE2vţĎí˙˙¦Ű*ЉwE&Y. ¶aI8ÔÇ[2€…&puL„Ë,ĘŽUj‘IçrŘč 68]‡ź¨ÄPČ.$‹ ¨NqË?á׿(…˘aŽ+1f÷Ů‹{ň¨±EśaĹć^ŞbJ&¨x.Ë Ě€€ôYŕč´dă Íj¸MZ˙űTÄĺ€Đ7‘ç¤ĆÁ„Ž0¸ôŤQ*•#ś—ÉtëŘcw›\aM/ř‹ë߯íý1J—çť}hÇ˙ßX3}ĎÎĚ^şąĹ™Ę™–dG$ ő!VťŇčĐu¨jÁîI‘¶Gµ”-*¸ÚgŤżc<8X± »N*‚…Ř^«lµ CGz§9ˇŇźĹ˙˙•Č©ş¶vi#m€ďi‚ô°*łU8 x «k•W˙űTÄä€ ™‰Ĺ°am‰p¸Ă ‰aŘAŘĐۆkç†E…Č AŔ“Z…B C‚Š+y‡µĎŠĎŰĐ}}źŮč™™Čc'R$€Lő ;ŃĆBÁ4X¸I_uDĂŐ%Ü÷A˛É·ÇBRęĂ©°É“4ž ’kĹPm '2<đŔëÉć…^Úű.Wą5MşSßěÓGž‰™wT?é9 AąéV$7Hă`ěžX K`ęŠ*˘fđĆ˙űTÄ〠ČO‹ć$ČA} 0¸ó ™ŽaPƆ˘‚Á—z\T\Ů×A†‚ (p}Só9ĄWKH˝µgťPŞĺżgyT»›–U;éB H·Šů#'%ÄzĎô„Göp9LĹÚî{Ţî ]ŇĐÔ48!g”ż­>t l $.Őcáć GSа‚O®>şěĹajż‹^ăéľÇ!ÍĽ-ÖŽ†±´I$CĂÂx Ł$€6&îšII:ĚÇx˙űTÄâ e‹ç¤lA< ±üôŤ%dN´i¶ăÖYĹ5`ĆAĘ DdĎžHâuŽayz c6śejÔŹžˇä»>ř¦Žěe©š…E)DzŮ8 ‡r@˘Qxä~v¦Ág¤60KâĘq·OĄ+özU¨†wS)P%ÄÔÔ€»Pd•Ă‚%ČL©eq–&UP®OÓr„%\ÄáÎÄ1?«nĆloçoąđýŰÝíocűŢěJ§‘?«~µ??ëř–ţîµÁím-7WŹ2ŞQ¤Pă@[ŮÄ„Â'%ÜÁN«ă±źöČ{ÜşJ˙űTÄč <‹‰Çl? ±<Ă `˛´ÓPŃŽš¨×ŃŮ2'çž ŰŢ‹Ňd@|D$—Ě©ţ$Öö^{eŢó:hFG˛AäÖj1Ü»­›f[q˘Č? óŐ ö5 wY=E ćgŠh ‰y ˛‰±fŞj™h™á’w—ëăďăű›îUę}÷äqĚAŕŰÖ)K&á%sůŁĄŐ©ÎżĐó5s5U Á6ş;‰S’ ˝–SřŞ@3źąJ˙űTÄč€ `‰ç°c) r|ö 0V‚öŽDč[b(—P ˘zűŰ f(Ś_üĽĚ(mŚíĐÜW_#B„ůęˇ]ĎÖźŚÖľżŢňĎfą˘ˇđâkâŮ˙?Ţ×®­â±çqZ[@.$Aˇ0JQż„ŚË¬JŁŻ}ŢuÄ›˙ý*[#Ť„$ŘČČÉäˇ(Tr$“OhŇĺĚSÁYQ,Pw‚§~X9AR ­ażPwň*żőťňÁÚŹ|YÜíO˙űTÄë Ľ[…ǰaÉ_Žń<ôŤ• VtK®LAME3.100ŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞ˙űTÄĺ€ śĄŹôô&ńżđŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞ˙űTÄËČř-5śÁ4€ŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞbeetbox-beets-01f1faf/test/rsrc/whitenoise.opus000066400000000000000000000421121472325477400217650ustar00rootroot00000000000000OggS’”ô'•ÚOpusHead8€»OggS’”ô ěŽ\OpusTags Lavf60.3.100encoder=Lavc60.3.100 libopusDESCRIPTION=Processed by SoXOggS€»’”ô &·Ä2ř®­­­­®­®Ż®®°°Ż­±°°­®°·µ·´¶˛±Ż®Żłµ´˛±®°®®ŻŻ°®®®Ż¬¬ř|™=ęîX=#+Đ)1ăĐGÍ?ň‡„w7•Ľ´”C 3ťˇ§TL~wI>ˇ>!é\ ĹëÇÂÍĽ2ôÚŢĹĽő śö˘őˇkćű'ô_y}>Ź´ˇSm‰ş«ĂXś´2ZRžÚ0剖ŐYëůŢĽÔÇ;úĽ”ńk©ŚđÜěŮľÁë4PŇ=#UĽ;¤Ĺő; çŃÎŚxÍěĐľ¤ŔŻ“|R/u»O¤EZ ÄËĄ\ä˛PĐde´X ÂDĹ./Ű žÄs=L†]J9fękm?+?ý$JÄý‚$•ćÇqí ęQ®ř&şwPÍMalĐ-Ş„Äž¨ÔęőŢS}ů˘el n¸V˙^J€|ú¶¶ËJ _"…`©RŻé&'0ăüzćäď‚óŘľŠ 5ýŽa™?¬cadFś[‡×7?Ô1‰¬ĐuĽ;›1€ŮDf&ĎP|lŤh{hU(ĽĎÁH…ç-:m˙9 ˝N/„äůa€Š›•ěá0–3 çă.L)‚Ĺ©dväëř3_2ő~o YÄÜŃ?¸›s°%rŹžď÷ş›ř+´š8§ŢÎ7e̶lן±F„ţb©ß°vó÷†żŁ!–ŘútŐVŢ0ŤČ*‡‘ťúhł28ţÇ9ŚfꆣđѤ4=źĺk˛±¶•UűÝ` gç¨Új÷ |ĚŐ¦˙Çřň)^«!í‹AÖËď,ďÇQŤ,VŰaďd(ëżQÝ÷Ŕ^łK¶µ°Âř+*ÖD|ł»´3)*÷4ĂuM„<—ĂŠJČÜ ÚS{™Źzmç~Ą‚•Ţ|­;Ć`2™řĎaĎŹ9 D]ŘÔÉ›Ř÷ŮLŰŹgŔÂ\ü3.ˇHÉ'$KY“„ěP‹\/Ź řhő’pUŮÓDlůÉ_ßş\Ügîč-ĺ¤Ŕ/Ĺś8ÁJ¸6Ąąë|of»go”ť%çŘ>@NuëęEsR%ł‘ěu_×Lůäř(7 WJČd0‹\ xŻŢ¨Á)#ŁßĽÓ¤žµ, ĘzBhđŚ¨ŹŘ¦¬ĂĂý8ą*E/™zGů×Ěswn›(ĺŹ&Ür_@šµŘ@.*ŰkSŠI™‹k„ŃŞ"W3±&Ů +zl‡‰Ş/Ćç$iľ†Ł„{•=řźz#\ćÖK¸–ş­ÎľĆú´úI†Śń«ć¬ď,ŰżP8!?…®‚\őRŇz{ëř3qLÚĂ´NDţ›Ä6…ëŃ~:í©ČtC)ĂâźÇíů†ášJ…ŞUşDĐ ^šoPë.é¤fNNë î:,ĹŞÚxł"o)Ľ6(Ž»ŠĂÖËi1˙wu ŕîî[M÷–;…·&ňc<@”äş\ ťçDďƇŹSĄ]őŔ÷ĄR¨Ďá”N–AŹ2®äóOk¤ŹqÚƬ‘í0^{ô‡Č)lř+%Eí,Gr鎎ÎOWöŻ$á‘“î9zµíI0íÜ" ë’|PA.)VÍ_ĎŔ—‘‚ďŻ9a®Ô6ͦý¶ÓNÄ …şsłVM˝Qâ°­ÝĆŚ|ö)›ş U(S˘É¦ÖKś/BíIč=Y‘ć/.i­É–•Ń_{Ľóď ©Éę>2ţź/jőz÷˘ÚZµĺ@ţ0»fiÂ-”ş~ŔňąĆĄ$§&>¤?b[®ř3_®đďÇs^• ME‘¤Ë¦Â~‰ňs<q†włč¶“ôu¤&Żł®` vOÚąeA-ť}ś–ţ§=~z®Ő0<‡«a©TĽŤč2ž*hç:uĚšJV%ŻÖźčP{˘âÚ†e.eŹŔ ¬/|NQłjMpŚ‚ăË #ć˝eĐhčNÇ-Żż?Ű^R © ó3Řt>÷d B‹„ŐŮ`­ŚÁŤ§\|G¶V˛–Bř ?Z°/‰fÜaµ:ă&śEß´:áË ‹D˝€WZąµn+ôę~°Ź(‹ esŠVäĐ…Óć‡q?8^µłź˙@żô4…şy·Hŕó.,w ńăĂj[^Ë0Ť~Ś®ş™‡djŚF~Úîś±élĎä“÷!Y6ĎŻ&Ńm|2ă,›©`HüÄ<ô•ěÇ•śÔ݉<&¨şµ'öňX¶áş>$¨Qř¶ť ›ŠZ{YnÂN\`hŃ„%)asĆ ÁÄgŇŘ…pšłçÖ]j´ôÓá5¨ô$€ÇjXˇ!"’c5ĺ=€őÚö Ů:Ä IËŐůĐB5WXçÚrŹ7'ęť0DÉ~‚°ë9ě5Ţr=xw pÉ#/¬ÚĎ$lĄ}t¬y§Ěń·*U4“”®S–(ĚËm%•ľu¦dŻpţoř Ä…fdh0U=b™†N;ßúQ,Ť’;âgguheÜř 'OůtÉD+—ô]¸;Ńfšóx·}«Ç8IŢŠđ4[<ýDdXd~‚í»«Žl<ř%W˝ł˘˘[ť2m—8Vn¤%ńëçž˙í¨Ŕ S—x,·ň˘‘Śuž´Y/ x€?K˝‘Vőy®\ĚÇÝ­ú÷ĺRîe´¦˘:?1…»yW8ÂĹaĄO$ł\´ÁőĹXóřF´ĺŮřÔń_†ByţWŘůPš}TUç§Ňš3|ŐRúĚ9{´˘¸o·Ý/:śt ąě´ý\{ĺĽřZ]Ŕń'‰ńR‘…‹G™¸)εŞŐŕg Ç€‚}±rň™ČV÷ŁłŁľÜi’ ŮĆ3:rúń׸INÔ0î÷Ţ2\ČyěF‡ă¤ő¶Őž±ĆV^Ä‚›‡úÔęŘ“>?‰W z–މ6ˇÔ—–ŻŇaľ+_ÇrUÝĄř5Áb)OßáeÉG3É÷~Ś’Ed2yFAksícző2A˘·N·IŞŕńĘdş+¶ż VÍŇśŢ^Ľ/‡LÉ‘čŰ›…UŞĘĘ˝qśŐčUű˝9yéŹÝAÖg¶ßŠý˛çQsS Ϭ»ÂNކ˘6đ„ÄtŹUŕźąCęĄÖLîV#¦N·ŽvąHŕď`ţ`Jq7¨¦ĂnlŠď-ýżcăpë1Šř2‡ŮR€ţťł0pLBę”[«°ЍÉ=ĺx 1`´şb0™Ň©Ńç^T #ÂÉ˝ď]_żć`»ĘK)EŻRL7˘ Iቲ§…ăÜúĄźę\Ąô\‹čätÝ|ÂŮš¨âp˙HőEîĆFбmđY‹“@o=Áé™Ĺdç$łMĹÝŰĎoK*hżăÖÝrR(¦ţÄ'ćHIdcs7I“´ $&5Čf¨čĹŕ^JŇť*ř3^öŇŻi Ĺ÷µ4sůŚĄxł{ÍĎ;˘®fĆłíńM}uŹf+ůFůsaŔ1\˙hČL_5ś—ňűdŁięyîŤ ĆJŕr>|›d_7±a{tęB“˙Í\Ś’Ţ–§k 6©($Ę,ŚŠšE…ŢŇţYٞănâď5¶ vxŢXV•Ě› vl˝jú^Ţ„gJáŮ÷‹Ëáw çę;·@Ř|H7÷±ŚśřI‘/0=6Śođ㥠TăŹ!ä8¨`@%±˝őÓ—d“×ďeÎŻŢáäÂZ”ńhţŠi›űŰzó‡Í`ĽĐđg;;H’‹8}%ű±ÖŽ3Ŕ—DÜîxi¨3‹îBĎ€Tegó¤C´ŕżÝW‡0?,IVµÓľ—/Ůý_ü Ş2щnVw%¨áť9ň•â(” Îç·ëËź€\ż==\)óRšZ˝;˛ö6ÇŮř4y%˘ůő,Bl6ĺ!`6áűT(ó –R†Hž»#´P/rçS.…‰‚ىo0ÎŢ˙S¬ß8ý@w5ł#AdÓ*~ţýČv kć“ię)Ăd6¨‰?ˇŐúˇËl˘„A¨â†áÂÉ$6‰o_ě÷ţ‹Íy«z¸ €wXęÚéäeß şmb¤ŽďŹ©»ö{«ą8"Ż3€°™zlüdŠş˘“&Ów5u{H†Rb;‡őúęxřI‘/0FŻ˝y3&PÜ3{+?qŇŮď{“šŰ…śzśăüB0rÄŐéţęęď‚CÍĽ±;ë‡üă‹tŰ(,RUw-«I‡Oç!Q𻏾“(71^ůfă;o~Űš%?ój@Eý—Év€Ü,Ü7yźIĘŠd±ę“ř’Éš6Ąu?A]áÎĄ K(‹(áęüM…»łÔťâ©ÄٶU] şš¬^Č=÷utř3 ´}ĄGŤ’zźŮUş ł/¨¤Lҧ–m„ŹEĘu6“ Á'Ń«đSů*µ9ą©aźXšŕc1µÜO©şP“\k<~"ĂHUbtŽË®j’ˇ“Rř‚Zę†pŘQ”~°—!(OĹŠréHŰŇ•ożC}\ŮnM%'’Ź©cÓq \RG•·ŇE±qÉRx.UęÁŐ,÷v+Ä$OÖ1T=*°>llKF>TřÁ.¨ęĹöe¸!ŐŇ8¶íÄçţž|´ @‘­%‚Ő˛`]°¬Ţ`!ŇNňpçŸe -‡”Ve÷T¬0zđĘĚPÔE¨Ď˛ « LŰD;ń6OđPĘ {­Ď)cí\lŐţČĺú,W¶K0Sä]ęŃäź4CCb»d9–t÷1Śů»xŤ¦?ěFŚđHě™wmpЇUays9(›„L›P©H°›ßÂćĺ¸qąő—˝÷i\řGZăî Źľ¶Ě¶ÎîóO„ŕI«V Ĺ ]˘ąlŻ˝“ŁÉČNřr҉k˘Qőö9˘WfůÄWČ·öJ…q×üťŇ"+‘D‰0ü5&Ń7•\_ʬZçd.łO?Ó)é aŃŮs/íęő‘¶9{±ÂŇ TO'®±ĺ›‰`em˙6DÎH˝Őů8ିćđ…P©_gpGR¨ÔµúƛѦ<;Ě7»ă˝Ü[é‘AĹßłťDř(ďŤ*!í ÎăÍ,ç§~î¶Eł ‡…›Rd “SGŞÜĹ|Ö=čĺPźG©PŚ'ŔiŽŁĹ ‚ J˙Ĺ+Ł`W‰_ŐĂß§Í•gNĺřÉ_CÚ«˘˛ťS2nŃë¸eďü8ŕÉß$âßđÉ †ř°ÖVNďĆŰPoRÉelkKö$›¦§˙IČiVň缗â1¬P"Y!@3–š%yv…aá-żWř áQłs I N†)·íoB‡“xió@É~ôŇĎéí‘[c¸“řφů2ŕZ% q° Ô,!<ŹW>˝ K‰ú­ĆA~^…¦§Pa`ýÉdžątB=Ó)bÍź^KňY'L„Š´ 7"Ó>1Dj—6uođŰ †S±ü†íő%¤*üf}E>¶ţvŽÝHVYĹ=úĺ3“S–‘p›Â5QjYC{r9řXţ-~О!ńÖ“Úekř3 ­E$.ˇÎˇň%Řš<@ZśÁťî+QGšîÁnŹn-°qř^îÉÔJkEp»Ś×§?ż)eöł‘U-ŮĎ\µ2„oŮÝgę?:ěq@?ÚÔÁ®ĹęŚ÷Ń$;ĺ¦Oá@(uĂŃ©”©YB6đ‹Wq0żę!Vʱ2ÄÔ@ ö€Í/UżŐ˙QőÓwEßDţÖó…Žk-W˝ľS¨ńŰ]ŕĆ)XMńňŃÍžŔXśř+KlZ ¨’«¶Żs%>WrÍ^jş]1Đp2ÉÚeç%ŢW±Nt8.5#ݱTNüX•Đ31á{űˇAňşTÚäW”Ë‹[™Îu+Śż,.ëU„_lóźD­€S6ß#ă %'٨äRE>\­ĺ6rkŐ“Ú~x7â°§bYIćfÜż‚Ď…Y2Ë‚ľäŻčtëŢŘ–šĄ4[Ýb h”ô†„}fŘĂŇúsř+ š9.o» `çÎJ2§‹<ť°(´éä^RŰćtp­ď´]ÂŘ $ uŚqČĚȨłÖ`Ź{~¬˛ J Í—Ry·oÇj<”©%CFxc·­¦{ Đ,ŁĂi¸pţ`¨?ŮAň2Ę,Ęţ"ţ,»ôžd¬ĆËßżĄdĆ]yQÖGĐŽÚ¨Ó=>¶řDTz,ÇçśĎ(ŮÄĎćÔ‘í‹Ô=wôř3 ×ŕE+Ş.Ĺ0áü˘±ńk!㽩óËeäٵͮ{ÇvO¨tÉkO2ŽşčŹí¶úNWőęťĐ#tY›úDĽ=€Âią—Ŕú8 °{‹¤¶§˙şŚ«=¦ľĘĽ/áüó&Ď]˙ˇ•űé")‹˙¨´ŇBŹ%1yjčaŚřŐÓë{ti9.Ʊ{2˛ä")&#µzeRęč_༤m‡í]irăř xŻŞ/b°¸—>Îi_ŕ&¸ÜŰţFĄŐüĚ`IŰ=๩,ťSČ*{ŁÁ— O‹´łíAQiţ«kÚ:/2ŕiwe‚ŞĐi‘'˙ «ĺAůô'\“·™-‹#"éĐňß~˘§&Č•kó,ĄçďćŘv=đ¶MÓK+˝7ţf7訳ho0˛ňÚ&޲ă˝&Źş µFľÇ<ŐeěŐ†Ź3m…0Żž˝D,˘˛rÝř+XެÔzĂÝ«űݱcµ˛eßCÂÇY†…e,¦ZşkvuČk1ţ ŹęÄ%?gIá1™]µó,ŢÄ'?űáď­ŢMľŕ„Ĺj–˘Gµź¦ź˛E75Ę lŃ‚¶ ř­sśŤ(°`Í,‹!نŔ˛Ď§žĹíŕÉť_`T`?ž¨ďëŔZR=—Ôń}©ŢâóÔ·XężpđDŰ­2—ö4žŽÂ6Ą&ŕ$^{ý˘jĘ1A¦Yövř+—Ŕ Y‘4â\ ŮÖ+b¦$řű羌™‘Vv‹›^ť$Â…çÖeQFô\Ś1QbËć~<ôöýżvůr{S¤r ty f}z.•©×űYęŠŘ©ď“Č™ޢĽ¤HuĄ·}¸˛cś›Á¦Ä2Rł¶‹ŮnĽĘęLÂÂVĹŻű{\šgą’KEgÇu;g·,› Ź6,+k(ó“‚ß!ŞÝ“ń±ëăŽ4›řv:6'Řií´,ý{€Ďc/6˙§÷^«¦§j\d ›O.KěÝčA—Ź€mOTî—.śűa‡bŕç,PMö“›W-˝IĹň˝•ŔR”p”u•n}>_nŔz<+ń¶wĹ~ÜăEkFhXj܆¨mçwÜ豣n:ÎW™đO˘Ŕ„…o:dĄ†G]"Ă.ÝŤ0ö,Ćň‰¶–:ďl<ÚŃë¬ĹáĹřF´ćSDĎ2‹5]Đş˙îz¬źI`ľ’Ţ÷:ë·¶í¸g Đ\Ť\śôs‡ÉlámW ĐĄÖTzúöi$}ňÚá¤ŮÝo Ş5ٱH÷q/Ç`k…yUčrĄ$ŤĂÔů–kc×xűM¨­Č®Ŕ˙ËşcŚ,w]ôSíĚלôYŽSč{ź>çĽý‰ĆÚÉÄYV xü6mĽĎů"/r<Ň»äÄŘÖoţłŕř(‚ŠLĄ­†±CZË•ü“9*-uFĺÂÎV*Ó|ňş6ÔçĄÄpwf p茍±čš›öuik »$.LţR©ň#9ľadPĐ čÖťăJ—âĆ.¸0[ÜÍžşoĄPŐRHp±î§ŻÝÂÔ ^úňú睬uɱN«ČKsµC*ĽÜc[LěD„Z2<‚‚0ŰŃĘňS°2*ĺmĽżL˙şu¨d+¬eôř+ďÓ?}#é!ą¨Bc]Ľ¦Ŕißq+IÇ‚bmkK ŕM­şJhÍ€´±g4|¶Ş$H)•Ă`ŔUżÓˇ\ĐUW ÉXER8ťŐóuCjŔ¶ÁMĄúf’đŰßŕŇěe-?ć°¶P‘aß~Slŕ颸C•$/~q^MšB–3ČląXU@đsÄ>ÜGź6Ĺą°yG‘­7ţlr'{ µvŹ®ŚP¸şlk2Á;čř Ő‡Q/Ây.>Âřyô€ůlL{÷Ťx˝ë6Ë»xU^mÇéîx´š¬ 0cY+J‚köĺZ7ÎQÓü­VjĚMkYCL¬Žm3B•ż}OgĹŔ‹6̸¸Ë‰mô(‹Äđfw¶Í†3±ŮVjz/ Ěµ”!QXREşZMĘÁ«ŽIŔPÁę„keă’q‰ ­ŔĘ=ECC•~ófT’Š-.ą+„Hz7%+ĆŃ\uhČ$>\[ř+JYÇd®¶Ůjsöd˛n4±É ë4jú…EÍźĘ;[]ŐżNÁ‡T ¦~KŐÜP˝ĘÄ’IťŚ…2R0Đčßy„WVmY•”‹n‡ŁÉâ”ĺ»$IýŚ$nţ©Ľ*®ŕTĄš…aĺ ZZÝ /ş|J%Ď[›4ţj‰Ź‚!Ó8ôBě¶ň>&Y0í™ň5vK˝H?§*^:í 0Kb}dgvP`ř+%G&cO§ümmÇĄľ»´EłˇnáÉK^1ţÓĎ;ÇT<ó]˙Úş‚v/c_Ďü´pţe‘•ĚŚ ¤§)đw~˘?âĄAż¬ÇLĽŚ%ߤ¶;ѱ˙#• Ź1Ťj¸çĂW{&ĽcY["¶y¬WˇĚf'ýť—ô&źw°2ŕdÍý´ ÓzC’7¦<¦tµ N…çd\GOE§»„Úôg@7óŇ9ř(:yăódc8<Č`[hb [»ŮĂ„‘î©”h‘ ´žcÇ™đş©ÔÉż/¤3Ą(-w ­žôĆ- %-ąŠe†“H,ťD’50ĺĘ«„4H&W9P&µpsŤ08őÎÁBlĺžŰ™ľ˛ xÁIFŇĐŢRóąú“Hq*1 e¬^ ¶B:Q°^ !OfŚ{°˛;ŁÂʰ–©™çmÉ®ţ5‚HÔvőř+%Ăîרb˝&@‚Ôľëé¤a}#Á˘DľHŕNŻU]``źÎ±sţSzUłĄ.‚älbŹĘşhuI&[çLý+Ďń[/ô`A*)~Wýo+Măz1×řÄAN_ýňjŁI®Đ„ľ>R˛ńQ{ôdn¬’)Śk ăFěĽeŹËđ§Ä/ę«ä°ô†ĽýŹŰL¬ÓVE‰ö+A·łÖŁm_ń9vř1ôŘ-¨ë\3•Rěëě3Ú[úńeˇdÇë⯥Kdüis—Ä{ ‘FV¦ńJÇÁÇÝ!ĺłÁMy'Ţţ‘.‹ĹÓ-“.¶ĹÁ>č '1ďj{Çťv¤ęü<čÂ&Ľ•˛IśN‚J±HAŮ+ëŻüÁÁË„äď»O3Ćhß6>Ú†tř_©¶ÝZîż˝XéŃłŃůôď—ˇÂęü´‰T{t‰űř(CF­4gĺO^Lˇ&ť¨öĹÁ-15oëęuCŰ"3aĹŽCë=˝eů$Q68Oh¦Ţ¦ŃA˛)ZŤü †jˇteŠ5Nq#eă]‡FłŇŇŠĂ7•ďEnşč¨µ­msČšuMű\˛fěn’} ĂłĽOµł$yâMŚź ęzˇZşČď·Ę7zůJśĚŻQ(ާV–ÇhßĐ$éP)جőpżŞľsLJTOµř2ăž/—ůÉű˝âNc;n»ĚÚ`yY ËŞŤ¤.xô7[Ő—˛°uřąű·ćxş€˛IűL[îKŞzÔâ÷ćvěý@"·›_Çáµz,ĹçÚäŇĂç™ď˝°Yčśľl'&¬ÉŚńV&®«<ď–ö#ä!ô¦ZŰn&-Đ[g¨ß±Ĺ7ëťµf7.x/Ú€á¤ŔűĽ´căő»üř,Bđ«ş,ȇRWáĂw,[VÓOM…:.ľ-Ihµ„ý«wľu6­©"Q…Ž;«a`±Ďnňkĺw躏âb]lX«C(3Ď_2ťe bß% Ěç‘ęď.W– 0Ę<Eß5+• @ąĎk3*í¸´+ńď‡vZNÖ+3WžUš­!8‰ßÄmëş^SÚÁu#Ą Ă(Ţ[3™›“ť><(ž@|?8Ďb»řGď}Éú…ŠhcŇBŚ'·©˙7‘4M‘Qô2ŠI©Uá˱ĄňNÎŞßÓ_<¶ę ÁŞy¶Ă_](ÁTł(‹W´~ŇęÜI}ísV˘śoH‹Ĺ¤ÎkťÎ_vŐÇkô0,z•ż ¬:Í››qă?‹Ő7__‰mIe ĺĐö“{Iuue9Z˛ď᪇QYáU0çt}I~1é˛Čžü1–ARüuc˙Qqř'ű…Zľ řÓŹL‰Z#ŐĽ9Ó;cs/H»RuČéÇč¸FGŇĚ:sťsŠÍĆ6ľ< ŘŽ[K–GwvÍ]‰°Űą=ö‡ľŮ­Šľ5¤§DóÎgŁćc•áźňźÓ“p-őH¦~T"4RFĹ'Üf\¤[iŔSz„ĹÁ=í2l‰Ô: ˙ ˇ/g20U&ŃŻiÎţňGâäebátâdCČ»=ű…ř€?ËâÖ&v}łwoUáÜŹYýđ"DCąäk¶p(¦ÔA„NTUcéÝâšWŰÓVş6yi:âľîó:ciç~»áń} 6ş‰co#Î2ŚŹŢÜźu—¸aČÜ8®ş˛Ć:W¦¬^.şfě7\:ˇ¨ˇ3đäâ+Y iŽČk™—ěł´sňe őgHIčĺ”Â<ÝM4‘ß¶H˙çŢü+|~n°oâ'OggSw’”ôi—Ů\2«¬Ş©Ş¨©§Ą¦ŁŁ˘Ł¤˘˘ˇŁˇź źźžžťťžŞĄˇ žťšśťś›śśšš™šš™——řFf´§ć×­’qăl~PóJUű^ż `é\÷ńiř«‚2níw f~áAg ¶ßď´’‡#D¦ş}¤6űÜ.®‡ ťÇˇ–ÉrŇ,Jz€Xŕ`ÇC.řć%úC…Š~›Ą3ŕáb@`uFÇVP.§k¸äŹô(ë°ÄĚ?iŻ€ 4`D(OźŰ’ąTňˇđyRš8sŞä.ŮşÎ-L}Ľł‹ř”Ýř(ˇßšâ°Ű‰ĆŹO©ôxcÉ¬ź ™$NżýYŕfb˘e¤J9űâMHܸ_"whj—IbéŰlM?·l‰ńú`ĺf ˘{š~>¨Y ß))ĹäůHgŰv3ŤĂj]€)?¤čŰÔ3 u<Ź—˙ˇçs¦a†&!{ŤŐáiUÄć÷^2~ć’eµĆßtZ–‰'S§Y0q®iĎiŐŔ-ítGGŐ…ŚÓř+W˘r[ÍüšD"°řFkÖ©%éřę&aíČcˇŁîM8|Ř( Ö`[Ű »ĎLŰz¨şąGś±ej|PYÝ$ÁĚE|ОΔ´‘í;/Ç'ˇ8wÄ· , ű}…ÚĐIýáZ·€”D©¶Š×>#ÍĚĽ×3rî‰ĐŐëŚLYi’X˙˙ëyÝ Č~AďYa”K‹Ů^đvĹ8/ŕ¤ÔnÍ?ř+$ëŽGŕ™Qđ…Ńć–ta÷ ´'Z)Đ^]ô~Ŕ/´@~S_ÇžR­Ö“yâ6•…Ë‹QĄ{|]¶Çôś&Ě@M?»ç$骛ĺ2„©ľ!ŘŚ×ÖĎ’ˇŻĽ„•ěŇmX4qőŐ8jD\ćÔ†­ …!iˇŁQ«ŰÇÇŤš *ŮYëduĎĎd2kžt¤nî8oV)¨Îk¶î$|4§ďäM&üř(–L=L÷9ÇŔ€SśŃ0$+±qwńÓ‹ËQÚ2{Häâ+Ţ’ ńδ€ĐK±bół*p`kp (Ć,n÷âŕw D˘ŔgŰ•<˛߇®Ŕś1Í96rĺĆJG*˛ů1S¶Ľö[Iôä˘đ綡×Čm}dÚźŤ!$âÍŞ —öĘ1ĂÖuˇIu9?d˛*i« Uť>ţaMÔv>oR<¶DÚ¸˝ýř+JYĆÁt˛ÂďrĺSÁí˙¸0cćJŹ ĚiŤő“8Cp€ąTîÂö´—‘˘1ęß‹v×/y*žIN*•ľŕ™ćÎe1ăôŇ'Ő'ŃcL¶©iÎ#ćxź“ąoŇş°×g{˛Żt5îëÉ 7»ÍŘr€çőv±·YŇuÖ–cĚ‘&c(-;™ůiŘ:B®»Ż*nř7ňBl¨v»†jŞř(s—ŹMĎë3±Ý3Â÷ł=ćšúůěŐ%‘XąRóH˛ĹĚöU͆X82WGÁćńş ÔŕB6 ~çRj¸Ë9`đÁĘţŮYTč;+UíśűßÝËLďYa«ĚOůMŔëČD‰ô»~ä*ˇŘł`79eĄL “žj¶őÇÖú ,˙+soí7V_TOćÓ­ú|šlĆ´§ “˛ů:uŠÖ6{µř3_ß–~EEˇű4-cÇŐRl˙‘[„p”±ĺŚĐf ˘•¶NBřR¬r’DO?aĎúŁCZ†ń?vNqXzE0éděŔYcF%yiٰ°wŕöŞ­ÁµŠţł·Ëź‹;űM\(Ą±—ôňëć-0ýㄡç&Ĺ ˇŃr~Ä1DÁ)*u]Ďžc[J˛UX±'°i&ŕş«DµÉz„Žř&@ M‚+´·?ęUxĚ EZň¸Ë]šťÍ#ŢöĽ÷vż˘?™)Ş ­L”S3‚2' (Ť¬KCŢß >:F:‹ö˛UÁ˘W\šr7đ‚%ŇAlď2„x™{:Ţ4ńúh«i7ěż ¤ű“fB"éřâ®ęÂn;-fl7WzpŇKÂjëh­+JQ×§’K HŤ^cĹ í”4ůřWő*Ţř4gĺŔnzěÖâŮŰbŰ>lëąTĽ&c'ęuB€€7˝×ňÇF†]—‡H; Ë’9ɰ)óJ)>g¦˘#tŃ“NE˘K#…6—_Ëĺlu,čW »˝„Ŕi¸&-ň˘‡ĚnpśY„ŇžüÍwç|ř˙Ľb€2ç0Î_îYŔ S°~¸’OÁC†×'CSćDZ„”˝zz=Ë Ť·”ăĎť2˘…Čř ŘŃDâ!t^c"Ů?5#ČzĂ~Î"ČiŠŤ4łíȶ9ŰýĘ2µIče!'üűĺjŐ?ĐŐé@ 4é{„ą˙AuMü_gžC&n+ˇÄJ%ǵ毜$®Áż ¤•đ楯©Sëž$§ř++š˘Bϱă‡ëx‰üEěc=CyQ/ëíÁwő?ŽW¬í,żg͡huÇ §zŹz ßШ«Ń~˘a~Ë‘3X‘bJđř—!ĺÂë‹ Éăą t·¦Âô5’`ôÁ/;KLBŤ%¦Ř/ľ-d=€'ą2ěGŤÚŕ÷ĐmSQCďHMf@#ŮŘśO®dí1Ŕ 2‘Ů_.Ş%mŢGoř(ĆVőĄţÄAc6‹Ů˛ÉmóA¶ëBÚĐÔ^¸Ö’k•ΡUj˝ÄîdŽ/pŮŻ ¶G$# ÔŢĄ·véQŁ(>[ö*)¶MçóiĂsőâ­áŞ›çÉh…Šă ”i °ŰʦN* Ľ¶’Ŕ·*ł`VMöˇÂřş6O_}áä ĎrnĆ•§­ŢúB$%6Ö8®Řąkü ^ř+&’őŔ7ňä+hËÂçßLkćő´ĹJ 0‰Ůű@׬ޭ˝$¤?_ÂĆ,¨y:&ëł9‘¨YšÜ÷=ýő,3_ĂauG"97‚[†3 Đ,»ÂîŐ’)¤?4LV|­"†zĄD§§iůo:2Ř/C`Ý˝xnoćۧŢJćü/?d;ŢtćĐ9ؤi‡0Zť;ĹRŘ‹¦¬V»Y¤÷ zř+XKiš(‘Ń3 Íw”“>2XĚŞYa‘Jçä!“ -,€żxĐR|ÁŐ¦ę¬ÁěŤp›c 7ě­á}éj@”~[—@×*)]“N!žĚoľ/ŃD#ÂČŢř(­”Rě }ęO'1č ľ`G?jŔ˛BěŐlş«ęQĽ›“š±ĹnAă\‰×]ʉŻ9 uä!*oŻ(‹2ŃćéM(ř3f2wG´H:ů~z[L~č©Ĺ¸ŔŚq/Až8‚‘gźŤSMéĺupyt]˘ŃŮhk$‘+É™śĄjxž™ ĹđŇ-ç¦C<…ůU•¤ń‹ĽÁR0ů†\-UÝE‘,Îcš ˝<˘‰¬"öĄ"éˇáç EžÜŚ›Łř+<ŕ [áö”â~™ě[€ß«”ŢÚíc(4PHńzw ë­‹ib˝[lćvaá|äç¨đ+%+”ńĹš±ŤějZ;"”+„@9Ą•2#˝€·Úۢ_ź8ôná(gő:~B{P%"‡CDk”9o/˝OĽ’ i)O¸@)ěÓ¬l˘?ČěD7e2ëźŮX˘ăH9űűKĄ Č5öŚ=óvĆgełř XÜĚëÝď  n°Ô]! |ŕď×6}NĺJηT°©([Y…× +S×ţ•ţ~×ô꫿˙ĐčΛßćó/ťůŤZ§[ÉÉ_Źś|=ń /ŁßRN­W+äÔ\o%öŃCdŢ­Ŕ¦PĚ!>şĐżç¤üŁÇęq›öZ€·e]‰ĂŇöďŰŞ¦@ĺcśyë„˙m`“Ążw`S§řů&wřJŰĚö–ĚÉqXiËB¨ Ľ±*€K›7B}*Ó8;eU2[żżŐdiČĆŠŐF1.ś¦.­Š/7;wpđbWą2`~(NpAÄj:†ŻĘdrÍ3ű÷°“_Ľĺe¬¬~ť;™C<˛®éF Ľ¨Ő B+¶ú“®&!wźß7bňůů¦ęŹýť4ěŮO ú^Ü-ZCř ˛€¸ř+U¶h^ŞĚŐ*˘˘o¤E¦özOCgŘŹať·îʬż«[—]łČšŢNę§°qa/“Ł-ť\Óí8ż·J(5č|Łů¤8g–Dut“%Ůŕ KŕľSAĹ]G8K퍣 PĂeÜ%yâCNŕw /c0›.‹íeký«­Ë< ŹlübŽ ďS'‰§áPyľŤ6±Úü[ĽřGŘnOÜöúĆçČÓ‡/@ˇé‚ĎÜ"ĽZřäMNypËz€+ Ľ‰YÁ(¸ĺ,ٲířlńśx§ěräkúVşEZňçŢN±€h^YŕűĆvŰ— ˘ě˘ÁꎯľGu<˘•ĎŽG„§q°Č¸/·hrĂîgťŰŘËť˝Ź_L„÷DZŹj›ůł„=:Nc#®ÁHŐťZ6,‰ř+RÍEJdť#ť†*Ź4 2ÂľM;‰íÖż-wsÄ—0WVžćš˛±r—©¶Óý'H€ĆA‰Á'"•; •ÄĘń’ű°ŃC4h˛Bä4ň}˙:#ńňłĘQ.ł»Q’}«Ř§ 賺ƹn‹łáTëi ĄŔ©Ő{˝đc‚­´® Jň°µż2pŢäč[&/¤„ŚĹ^Ź.Zď5ř3˘rČŰ®íÇqĚuö7 %Ż(TvbZ—=ËąĹ^-°ľťá8ňzkÜ–Źm1ŐmRc…Š…jď~Ô*Ś‹Ď-ť”›»(ř2pZü@'¸>,ü T»+—,řŘaÁ(x‰¨ţ câ}_Ö6FĽ†ˇĚé}lőa“=v»Ń~RÜĘ©qÓ©NŠŃÍîUTůÇo¨ř+WŁŮ˝Čş—Ôż˘Á:Ę·yo˝ ę— šÎ˙‹$ͦéř˝ jJ?é`:îXâ @ëBÉňVté"ŘçčgőÇ'y#ÁziEzł!-űęš`P2@ZˇőďČm†Pŕj¸\s†?qŹ®šŰ™H1pÎŹđhHęGż9Ü3%ť´ĘeqX„ÂŽ:ftfY¶ű_A"5ř ŢÔ"ĂOQ2Ó~\'÷㪩lńWÜLkĹVŢä6;şŢCÍ5NŹČü'Ď®e¬Ł:» â[’-Ůvcžx–¸ö`Bćş›Ďȉ0˘âőż;«'ĘŠ˝üÎłŹ´H%cŔ1¨Hmˇ&*5µvń0(ů7‰…ާ†Ţń€‘ŘHȱAŕ·›‹í§9Ůŕ‡73ë$u»—ř( Uh'ńôađÖĄ ŽŐż$@›”FH®RfäŞËtY'N›Mg·Á 9ŁůV¦ŮČ jžZíFŢÇŁi†wmW^Ň읪í+çÇŢźÂ03¸-'˘!EJ¨–č–#<µ I˙…!>ů3—RÎŇj˙ďĽŐú)´OŻdB  ‘¬´•gaKYÂă(??óűĆ7zž6Ú˙ڏľřJÜŕŰ,˛ÉáS€đ—™¤“ŇmĆqc· źá/jč ôv`ď=›V‹b—ćÁ‰Fyë…Ú4!BśćRvîジţÍ]HqIíe\ ŮYAA(ę«&ţ»J7(;Éq·ß‹‚+莂GÖ °¤¬XId&ŽMT3lĄ€>Š~žáĂ&|m2zëf‰’C&ł˝ H9±ó[Âřř ŘüĎ]Y§ÍS±Ĺ‘'¬—k!x ´ Č5d~ĚWNwÂŮňv® ]†˝łw®ŃhÄ`ähÔTŘ(:É™HĆáĎŚ©4Fבíﱬu˛‡DŕTćn{˛UUÁ3˘,™/´(ܬ,A˘.°^x~1p8”–©Důá:6%xctëadGvËŁ@G…â°Ř!nh´´U·7V}.7âŹRşďř+©˘Můî›|ćŞOrÚĹÖöjÄ@ýx0éÚ°ČŐh_ ŹîK@šE‹ŇL;ó±%Ąܶ3ČcVcőµŐr­ÚęůgÉ"Ý·órjŐŘaE5ăľűç‘Vq+¤_5<đ¸%SĆp´Šł·DY"VLz%}&UvěZ˘ĺÓ`ô"jŁ´trwî!ÉÖř×Ő× )ţ ž?>*“_\­Uߩ־ř+W˝_–U¤\l$ÇÓ súś(q¶RţéJ3 Ěư!\É3]ç)βts7š:ˇ›óűu1˛{ްĘ:ô›<łŘ—¤¨<ŰU5 ĆÂä[·(ăÚ§węĺKTáŢ3ůůŰ+§ô*AűbĄ6őŔň,®¶l4éZşÝE«´%B O¶úĺŞäÉ“¬G%~ŻÇ3GP‘UťŮŤ˛|˛ř(6vßJŚBľeˇ9÷ÔŐ&?O©$÷¨Ý%±jĚ^!˙ËžÁ˘{M<':pńű ł[E<~ÓvŞ„VµŢMęĹŇQҨŤ äüłŢśTUĹ*‰JŃě˙Î{.†|Zăl Ix&Mε· R$ˇËůŘH8g»”nLěU{ńŹň™~Ů9RěĐ)žŰ•8/÷Űţ62(“Öý5ř3 Ź(Ëafďs!ˇ? ·a’0BÁ[·ÚéRůJhVKV†mĆŚKIU®@@KŁ˛Źť^ő´çżŐůÉłĎ6¬^ą‰ŇÂfWŘĺ ˘ďYĄ^ L–ăw›gq^x#‹5WCä¸ŰĄŞ­Yň[6˘…T^6…׼e,zŚßÖZ&íő±â‰ů˙[As0ÄeÍŚ8>4·Ä×Ë#ôźôlYŻűáEp›ř+‹’Yš÷Ksř†ť¦Yą.ćËFf»Ď¦€6+MšPřé'Ť°ś H'uŁľÝľ¶“Ă]3ĺ|ĐĹ\žWy ˘y"—¸XR«7ä˙‘ÚPĚăľwů´"uL˘¨a<ľµRFLß‚ĐJžhí׳6řË:Őéb}Ë?‘­řtě,ŚâĺN^ AęAŇ&}¤s0\‹•É@›¤c4;DZµ`v7ťüř5Ĺ®¤ű€–†x˘ŤŮ:Ť3ż­«5Ţžµ!ʦéë™/<‚3µÂń&@ʼnCg+&Ň{FÝ`şb O?â®Ë ąÎ·GËH†cO—63:¬N¨źĽ0x'W»÷›ěS«F•sčBeÚ`\1ÝUÔçÇŽ ňů‹ňă3…T„gŐCbš3Š?Ýßś÷­_2ł«Ó[„ř++™G‹łí™¨%2µüĂ]şŔ&„Lj9ׯ#šŚ:=i%k#!ăß±CG &p·1Ŕ©™×fZZxĐ˙É~[,٧ýĚü¶Ăę®˙4¤#źýväd=YţÓ/ ĺK™yzďÎ1ŕľ ż„=Á—± ¤˛Ĺ¬)˙ݬ _ëÔΠŞmq$ŢŁÓU>–‡7(ęäN[/“ćs^Ńîzśzř+—WHG¸†ĄĹ„ë]nĂWcĄď}cZqˇčóeč÷žGÔŕ1z”ł>đ{şM4Ô†*î)tŇ>ͨçš$ą$1E‘mŞb8ŇŇ~aRç €§Psuŕź±Şç5Ăn$ň/~†ŕ±(q8¬că<[ĐNUŔÚ…®Kr?ob‚oóÂFÔ˘ ů[Ń)Ł,·|­ˇ¦¤–~ř(?*Íń)Î](‘AŰBN¶ĎżfĽuL4Ź%)Ňq•–Ă.F ŘM1Ţ×*/p˙“×ćÄÄNé#ŮŤ¬‡óďX«Á1í%Ř›˙J÷䌴ńF>j°Ö&ęÔ°‹ţwľq6“…Uß(}hĘ!˙~ ´qąm ëő[—Ő3Yt~]‹Rđ$3¸˙Ô Çźsú7p˙sŕ·—rgŹ ,ŻĹlωźf”k †­yXm‡1~îeř(çŰ'9¦GófĐm {2Ë$¸I‘A[ÇŰŁSm}ç‰yÓĆâöP2…Č!űÇ€±,-rÇ««.z€–•Ő1š4YŕĆ\#Ä`/‹—}XýŇĽZzk¬c]wS0ŘĆϦ~§_äΊ%›M S"xÄLCÖ`­[fe5¸s!řl5 yő<¨šöë¨K۵€ ső6lL9JŃř6ŞäŔNn.Óü—>KTUµ" ćŹ+Ó©_XŤUe—4–`řЎťv>‘Ůgł.G>Ó›Vn'¶Äis&.Φ§8GŮaNŮHĚF™_§Ř>1ˇęČ _íűˇŚĚxWÓď¨NdŕÉ—?˝ăŘr %Ďů‘ëwÔ‡ş"Ç-Ś‹śvĂd>2eş&pžŘÁFö„H>ÍĘř&ç4kb%·ŚJÓéýnvĚOµśV_”`›ńńË”LŽ”Y~L¸ěšj;±é×lł5ĺ¶\¬ĚĚG%©éKĎ`nD˛ľŁŞ“đĚY_iő;M†•»]˝61ˇ·j=łBÜeł–sĽĎă6a©RČhńŇÉó#›FĺôciŢ hP'jĘq6鼭‚gząř2ä´Ć@ŔcOxňňĽůĘ 4aŔ›T˝ô§żTßńHź˝Ŕş ßâď‡)+ӳײçźÍ˝ţuÇÍŐ24˝'†a÷:展DĎăvŢVŁÂíŘ’µý€ (©Kš6—'âŐR¬ű`•s®ąkË#©ŤĄĽĽ°™Źć`äĂyw/Ż+תnCJXĘ.žń7H>+ŹfÁč Óř+±®ňć<¶oiĆWcżv§ÇÇ´‰¦ÓŻűŔ\¶ Őßó.ÓĽ†«"ńŚ»«L”5ÇĐLXé‹ń˘Ý÷‡ű” d5'b*:Ťo»ą—ŐG÷S„éc·°·¬„ zŮynŕ5ł™ŠuŃićřö÷mŰŃc†%`­T°q˝Ć™ëĐĽb¦źGÜËꯎŻâ®dĎ}Ţu‰Ăş%†cřGĽQ:kťq6<˛šuî«H=»Ç†÷Z]G0®™ˇm_%+e×Iç/ßĹ3¤›SňásŤ>sJňóÄ«69î­±$V„˙ĆçyŠoË—bćšł7ArüĎsx $.Wż®$,đËU|CEÉsÖúzNŹłŚşÔd»0“™:+9ŕŢÔ˝ËÇÔň2[&Ĺ‹Ssú‹‘Ä~]V¶łř2ăĐÚÎTs“b ›Ţĺßs@ś0ë!˛˛ě}Ń]žŻaÖµr‚ůMÖ=íRI~yD­{^T7ľŕF=0K °X {ruwTRwz=CjŮÇÜ*;çbÓŃřÂTřŇеٹ«@é®Y0jß^ŕ§ uAÍ»® çT WbP„_ţŽgąI¶•«śUĽ›p˛‚¨5Ýř(ľ1Q<~ŕv­A’&ÍĘą^Ĺč–Ž&–ĽN[÷/Găŕµ˙ó‹G>×ÇK”űáóJUđ„‘g,Ě›0fqî™6+é˛ĹhĐ’m¦ťT9Qáôółâ‹‹(ţ çĂĘBă[‚CćĹam߼A1ć\9ˇŠ±Çř5šŹ9U©(‘‹[} µľ&&ć§ŻťÄó®“·Uř57Ö_wó.T)9T૽3F$ÂÜłö@3|uŽóŇňżÍ$öç[łďźţoéǤĐ_ä†ECa<=?0âîŘ›Rđńf?­Ę oÄva:Fčě‚ÁŠő@„L˙›qµłRŐĐ:ĺöľ-˙é8ˇ5]AšŠĘDq ŘÖ0«ňy"‹ľP—[€Ć*Ő(ÎÚĘźJĄÎgĂř+-śĐ1ň]0żůa×lZnHçĆ‘?ŽUآl´j‹çG5 KSŐżúÜźŹŽ¬PÉ;"\t [‹H{ě«TGCX(îoúoĹčA|¶bęĄfTĆBóuňD<ć·;@_“¶âEOř°hť‹Á• \WĆ’Ó‚74łU-§đ0iüúŔwŢő–‰-€QŃöŕüJOggS8x’”ôď$‚ţřxQ‡„‰" ż&11AF0eSö"+‘}¨;Łtҡ\Q ;Uţţ=ÂB9&Áp:ÜšJH˙•ŔÝ«§evŘ%…qőżÂ¨ ‡ŚÂö.nÓßś•ś4fJńúĐó˝Â3B.¬±°ÝtúOśŹhFźE,Ŕ’eŢ(8d!źhř`0SHöÁśŢäsęLM§eÜŻK3{çŤ!1â¤Ělé•yMÍË< ź[Š@ mÓrŰ©ž-^–¸ÜjWÇ áŇvůšCv9ńękwň¦ÖĄb:•Jş*jóRs"â&a‚<ŇöĐť—.őbeetbox-beets-01f1faf/test/rsrc/year.ogg000066400000000000000000000205541472325477400203430ustar00rootroot00000000000000OggSţ–ç=Ý[vorbisD¬€»€»€»¸OggSţ–ç=EËÖ—:˙˙˙˙˙˙˙˙˙˙˙˙˙2vorbisXiph.Org libVorbis I 20050304 YEAR=2000vorbisBCVcT)F™RŇJ‰s”1F™b’J‰Ą„BHťsS©9לk¬ąµ „SP)™RŽRic)™RKI%t:'ťc[IÁÖk‹A¶„ šRL)Ä”RŠBSŚ)Ĺ”RJB%t:ćSŽJ(A¸śs«µ––c‹©t’Jç$dLBH)…’JĄSNBH5–ÖR)sRRjAč „B¶ „ ‚ĐUŔ@˛ PЎŠ„†¬2 (Žâ(Ž#9’cI˛ ŔpI‘ɱ$KŇ,KÓDQU}Ő6UUöu]×u]×u 4d@H§™Ą  d Y F(ÂBCVb(9&´ć|sŽf9h*Ĺćtp"ŐćIn*ććśsÎ9'›sĆ8çśsŠrf1h&´ćśsf)h&´ćśsžÄćAkŞ´ćśsĆ9§qFçśsš´ćAj6Öćśs´¦9j.Ĺćśs"ĺćIm.ŐćśsÎ9çśsÎ9çśsާspN8çśs˘öćZnBçśs>§{sB8çśsÎ9çśsÎ9çśs‚ĐUA6†q§ HźŁEiȤÝŁĂ$h r ©GŁŁ‘Rę ”TĆI)ť 4d!„RH!…RH!…R!†bČ)§ś‚ *©¤˘Š2Ę,łĚ2Ë,łĚ2ë°łÎ:ě0ÄC ­´KMµŐXc­ąçśkŇZi­µÖJ)Ą”RJ) Y€dAF!…R!¦śrĘ)¨ BCV€<ÉsDGtDGtDGtDGtDÇsUő}Sv…áteß×…ßYn]8–Ńu}a•máXeY9~áX–Ý÷•et]_XmŮVY†_řťĺö}ăxu]nÝçĚşď Çď¤űĘÓŐmc™}ÝYf_wŽá:żđă©ŞŻ›®+ §, żíëĆłűľ˛Ś®ëűŞ, ż*۱ëľóüľ°,ŁěúÂj˰ڶ1Üľn,żpËkëĘ1ëľQ¶u|_x Ăótu]yf]ÇöutăG8~Ę€€Ę@ˇ!+€8Ź$‰˘dY˘(Y–(Š¦čş˘hş®¤i¦©ižiZšgš¦iŞ˛)š®,išiZžfšš§™¦hš®kš¦¬Š¦)˦jʲiš˛ěş˛m»®lۢiʲiš˛lš¦,»˛«Ű®ě꺤Y¦©yžijžgš¦jʲiš®«yžjzžhŞž(ŞŞjŞŞ­ŞŞ,[žgššč©¦'ŠŞjަ­šŞ*˦ŞÚ˛i޶lŞŞm»Şěú˛mëşiŞ˛mަ-›ŞjŰ®ěę˛,Űş/išijžgššç™¦iš˛lšŞ+[ž§šž(ŞŞć‰¦jŞŞ,›¦ŞĘ–癪'ŠŞę‰žkšŞ*˦jÚŞiš¶lŞŞ-›¦*Ë®műľëʲnŞŞl›Şjë¦jʲlËľďʪ)˦ŞÚ˛iŞ˛-۲ď˲¬ű˘iʲiŞ˛mŞŞ.˲młlűşhš˛mަ-›Ş*۲-űş,ŰşďĘ®o«Ş¬ë˛-űşîú®pëş0Ľ˛lűެúş+Űşoë2Űö}DÓ”eS5mŰTUYveŮöeŰö}Ń4m[UU[6MŐ¶eYö}Y¶ma4MŮ6UUÖMŐ´mY–ma¶eáveŮ·e[öuוu_×}ă×eÝ溲í˲­űŞ«ú¶îűÂpë®đ p0ˇ ˛ŚaŚ1ŤRÎ9ˇQĘ9ç dÎA!•Ě9!”’9ˇ””2ç ”’Rˇ””Z !””Rk8Ř )±8@ˇ!+€TăX–癢jÚ˛cIž'ŠŞ©Ş¶íH–牢iŞŞm[ž'Ц©Ş®ëëšç‰˘iŞŞëęşhš¦©Ş®ëşş.š˘©ŞŞëş˛®›¦ŞŞ®+»˛ěë¦ŞŞŞëĘ®,űÂŞş®+˲më°ޮëʲl۶oÜş®ëľďű‘­ëş.üÂ1 Gŕ @6¬ŽpR4XhČJ €0!B!„RJ!Ą”0ŕ`B(4dE'C)¤”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RH)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ©¤”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)•RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”RJ)Ą”R Špz0ˇ ˛HŚQJ)Ćś1ćcĐI()bĚ9Ć”’Rĺ„Ri-·Ę9!¤ÔRm™sRZ‹1ć3礤[Í9‡RR‹±ćškVk®5çZZ«5לsÍą´k®9לsË1לsÎ9çsÎ9çśsÎŕ48€ذ:ÂIŃX`ˇ!+€TĄsÎ9čRŚ9ç„"…sÎ9!TŚ9çtB¨sĚ9!„9ç„B!s:č „B„Bˇ”ÎA!„J(!„B!„:!„B!„B!„RJ!„B ˇ”P`@€ «#śŤ˛€– R΄AŽAŹ AĘQ3 BL9Ń™bNj3S9ťtjAŮ^2 € Ŕ (řB1AĚ …U°Ŕ  ćŔD„D H»¸€.\ĐĹ]BB‚X@ 88á†'Ţđ„ś STę ŕŕ ""š«°¸ŔČĐŘŕčđř8>€ć*,.02468:<€€€@€€OggS@–ţ–ç=ý‚ĺ'02310276;:?CCBl…‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹¬Ř%ô B8FkF#ëśzĘýýžĺJI`ź76BU5Ŕ: š†îK.Ţ[/IŔ´€Ž€ ~ĘýýžĺJĽçŰJŮ@!TŐ€ €z wP{@Ű‚š_,ß5đč@ 4>Şý˙üĺ žíkĄ°@!TUč40ŃMtô™ŕÉĆ+ÚŮÁ…WÇŃŕĐ>Şý˙üšäâvy¨…PUˇô…|G×OӱЂ††ŤĘpBŔ&€>Şý˙üšäîô4Ş…PU'Đ@$¸O}Ç%ĺMŇ;†:€ žä>Şý˙üšäŇ´đ&”ŞĘ* @t𨴯ůçaďĄ)t (4 šý˙Ore1éM0BU 1>ŢY(ĄÚsYŔ&-RJgčˇŐMkňŘŔ‚Všý˙OrĄ2ŘĺIŘUUĹ@EO5š‚nE~IôĆÓ´JĘqŘ)d˘U)°$ @šý˙OrĄ28ĺMčŞĘj ĚY¬"ćeç…d×Ôdţ‘ňcw˘ ±“¦‘€d :šý˙OrĄ`°Ű›¸TU:Ě]ą¤ż\Ďôł\ĎŇ{QFH4¬T05Ţyýök”‹2¸ŰŰXaE…P + &EŇć^ ô#©”¶&Ú!ir}Ď””Ωaku¨“)lY ‚& HŢyýök”‹2íMP*jT¦Ţ1{Ă1^_Y•˘~vçt×Zź}ą.󭡣Ş9“5\1á)čŕÁ_#Ţyý˙ţĺR Ny“B1ę,r(¶Řňł4ptmp sR›¦ŁL/g9‚čŢ?®Í0—M)°”,ZýÎuą‘Ţ8 4´X‰ h©u Ŕ÷ßŇ‹ö~>SŁČü†ý$=cr¦Čß7˛üĽöŽĄĎĂŚóů0Ę«®bĄ*Żß·ţQť˙Šá¨Y4®#öO¤Ĺ± Š«=ˇ P{údX]ÎÔýÎ59 ŁĽ „f)6đÖ”łA`fś“€˝^ťÄ!<©¬i0/dĄ·?/Ôhł=é«m¤ň¶Ţ­™žŹŤwŰŐZ:Ý–˙‰^®¦×Ö‘&uVwű’CĹ_Ĺjđ-mŚýÎu9 %˝ Đ"++IŔ@âĘAGŐŤrý5]óo”X€Ż/săÎŹ\ú çHÇ‘ŮqoĹóČĐ3ú.+Ľđ¬qÖ\Eť¬SF’ż1R-ŕ"ç ÜI¦ĎlÂjÂÉĚěćýÎwŮ4-= ĤĽĚdŔ˘ĐIŕ,1ć8‘qü]âj­_č÷⥧Թéô''ďsçőu|‰ěwűćąĹÇ덳Î勝/óˇ<:Ť0ŃŰĺ$ˇÍ˝'—3Śí®Źý ýÎu9q­< BˇˇËÂÉgPŕ€~57kg§Pżd´]ťŮŰüąśÎoż’_ÚąJs)ęă5÷Ňä¤Ĺp–ô•őÚc\^;ÇĄQĺtFôb[D®áě §­“ýÎ5ąVŢh’ʦˇ°@vvż´ÄNľd ¦˛˛Ki*Żu67[`&· 'nV‡§Ż‹“ËČcşt“źq’C>-VţFźÄĽVŮ ]ęďtGNĘy49ŞéaśŮMăýÎ5ąäŤˇXd‰‘\%`8Ź'\ŕÁÁű5ôŹ7‰M?/'x‘,ׯ×}YżŽ˝nmcfFU)¸¸đ“‡0ŮëŢľŐŃřÚťť·•ęP[©+T˛űëĽý2š\ÍüÓSś ÂG.WąčX©;·ĺUFŔhkôo†FŘ/÷ŕÂu cĘ:ýÎ7Ů–Ţ8€š• ›0Řd7•\´×vÎ}ií˝E«MÝ M3‹Š"äđşĎ~µ˝bł¶#Ah©'ňR‚ŃÖ&y,]~ŹMG“-9¶#8…ţůäëĽ#s/yýÎ7Ů8Qľ1•Ö%8€iup;š¤_I5…ŔÖ˙?»ŘÄ&ňҵäduˇn±{˝ď…é!‰ź»:•¬Ď…úzĹU.25žBÉ–+Ęčq0ďîQ®*/CUë&^ܱé-ýÎ59q#} „&ĺ68ZŔ€ôĐ»t3ü´WÜ™ohm™äP»-¨Y?Ç{ž°ęŽLXř8&MöŘ f%ŞO©›÷NÉ”‰ÍÍŻ®b˘ě˝›ěÎČPwˇëWI;–ýÎwŮ(–ž„*hRdÁPLŹKşá?7śŹI—»BťŹÁ¦Ń|_O&±arúäşvQŕ$ąk×°d­Áĺ˛l­Lc/["°R#lcú,čVŔÝťŠ…«äŕHďbz¬Ű{8TýÎ7Ů8QŢ1ÉJQ3=HĐi—Ť ‚©ćĘě•ä¸óM6żÝRmöµ|fę «ö’ôŃZ*Ěü(NbÎŃŮ&ç˛ýÎuą–ž@-jIÁ…&&čh‹o\ś‰[˘ĆüJx2ÝĎÚ ^Cí7^:«YGcF¦ňO}žÝşž‡žŹ™‰°±<É~ľ¬2Pâü"SÔމíőŞŔö™!Gć&ŰóS’‰ťÎI…¨ýÎuą8QŢ(BĚR¬H¦Đ‡YB,’˛-oĹx3CT§XČčňßŃ| éČÓůĆűŃł^^Ş„^‰)5=‚%G˝pďb€{fy](€>(Ö÷:;ubjŮ:kGG:OggSD¬ţ–ç=‡ż˛¬‹‹‹‹‹‹ýÎ59 %˝ ‚”. :>Źţč]µ¤?÷TăĚxL€çsÔžM›PĎXšÁ„—+q!^#§ĂaâčŃ|S«ă.Ź„™ăµ•«Q®%(Îsnĺ¶©lő]”N-{~ýÎwŮ8™ž0B“b…qÄń–X9vń)l}¬ĹĽŹb‘Id=!ű…t,W˘7Ĺć˙9±¸őń­Ű˙â® Rńçf©ÄŔoé<ďËÓ—·§ľUÚe±”׫üpő0Úé[(wÉÎ"…ýÎuą8–Ţ„2h–˘"hrđČ)=|éP›Ű"ýr:Iň"qŤö°Ě蹨šŻŕŽľRß"ŕ€"«•uŢ˝‰ ţąÚ‡żš.řAsń©Ďnµďn7…Ł'ňčeŽ~ ýÎuąŢ0B Í c@2 °8čä>h»ď_6zyu—YĆü|$Ô•»ąŽëö¬ĘŔĹ…÷ńSěŮ n'E˛0!¸¶˛ö>~v3üayč™zź›9óMÔk ¦1×5„éW7ń#ŢežČiýÎwŮ8‘ž1h–bŢFh‚¦zmśňp4<Żĺ˛é*é´ ´ÚäÂśÉ"N¨„Şń|oâSđjl’ĚôřśxŽMqIß–Š'S´Î—WřKw®˛:&U0TĚn;Ť‘bbž ĆPČeýÎݞ 8P‹<Üŕ0P8ťÓą­j xz}]Übeetbox-beets-01f1faf/test/test_art_resize.py000066400000000000000000000125111472325477400215060ustar00rootroot00000000000000# This file is part of beets. # Copyright 2020, David Swarbrick. # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the # "Software"), to deal in the Software without restriction, including # without limitation the rights to use, copy, modify, merge, publish, # distribute, sublicense, and/or sell copies of the Software, and to # permit persons to whom the Software is furnished to do so, subject to # the following conditions: # # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. """Tests for image resizing based on filesize.""" import os import unittest from unittest.mock import patch from beets.test import _common from beets.test.helper import BeetsTestCase, CleanupModulesMixin from beets.util import command_output, syspath from beets.util.artresizer import IMBackend, PILBackend class DummyIMBackend(IMBackend): """An `IMBackend` which pretends that ImageMagick is available. The version is sufficiently recent to support image comparison. """ def __init__(self): """Init a dummy backend class for mocked ImageMagick tests.""" self.version = (7, 0, 0) self.legacy = False self.convert_cmd = ["magick"] self.identify_cmd = ["magick", "identify"] self.compare_cmd = ["magick", "compare"] class DummyPILBackend(PILBackend): """An `PILBackend` which pretends that PIL is available.""" def __init__(self): """Init a dummy backend class for mocked PIL tests.""" pass class ArtResizerFileSizeTest(CleanupModulesMixin, BeetsTestCase): """Unittest test case for Art Resizer to a specific filesize.""" modules = (IMBackend.__module__,) IMG_225x225 = os.path.join(_common.RSRC, b"abbey.jpg") IMG_225x225_SIZE = os.stat(syspath(IMG_225x225)).st_size def _test_img_resize(self, backend): """Test resizing based on file size, given a resize_func.""" # Check quality setting unaffected by new parameter im_95_qual = backend.resize( 225, self.IMG_225x225, quality=95, max_filesize=0, ) # check valid path returned - max_filesize hasn't broken resize command self.assertExists(im_95_qual) # Attempt a lower filesize with same quality im_a = backend.resize( 225, self.IMG_225x225, quality=95, max_filesize=0.9 * os.stat(syspath(im_95_qual)).st_size, ) self.assertExists(im_a) # target size was achieved assert ( os.stat(syspath(im_a)).st_size < os.stat(syspath(im_95_qual)).st_size ) # Attempt with lower initial quality im_75_qual = backend.resize( 225, self.IMG_225x225, quality=75, max_filesize=0, ) self.assertExists(im_75_qual) im_b = backend.resize( 225, self.IMG_225x225, quality=95, max_filesize=0.9 * os.stat(syspath(im_75_qual)).st_size, ) self.assertExists(im_b) # Check high (initial) quality still gives a smaller filesize assert ( os.stat(syspath(im_b)).st_size < os.stat(syspath(im_75_qual)).st_size ) @unittest.skipUnless(PILBackend.available(), "PIL not available") def test_pil_file_resize(self): """Test PIL resize function is lowering file size.""" self._test_img_resize(PILBackend()) @unittest.skipUnless(IMBackend.available(), "ImageMagick not available") def test_im_file_resize(self): """Test IM resize function is lowering file size.""" self._test_img_resize(IMBackend()) @unittest.skipUnless(PILBackend.available(), "PIL not available") def test_pil_file_deinterlace(self): """Test PIL deinterlace function. Check if the `PILBackend.deinterlace()` function returns images that are non-progressive """ path = PILBackend().deinterlace(self.IMG_225x225) from PIL import Image with Image.open(path) as img: assert "progression" not in img.info @unittest.skipUnless(IMBackend.available(), "ImageMagick not available") def test_im_file_deinterlace(self): """Test ImageMagick deinterlace function. Check if the `IMBackend.deinterlace()` function returns images that are non-progressive. """ im = IMBackend() path = im.deinterlace(self.IMG_225x225) cmd = im.identify_cmd + [ "-format", "%[interlace]", syspath(path, prefix=False), ] out = command_output(cmd).stdout assert out == b"None" @patch("beets.util.artresizer.util") def test_write_metadata_im(self, mock_util): """Test writing image metadata.""" metadata = {"a": "A", "b": "B"} im = DummyIMBackend() im.write_metadata("foo", metadata) try: command = im.convert_cmd + "foo -set a A -set b B foo".split() mock_util.command_output.assert_called_once_with(command) except AssertionError: command = im.convert_cmd + "foo -set b B -set a A foo".split() mock_util.command_output.assert_called_once_with(command) beetbox-beets-01f1faf/test/test_autotag.py000066400000000000000000001042441472325477400210100ustar00rootroot00000000000000# This file is part of beets. # Copyright 2016, Adrian Sampson. # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the # "Software"), to deal in the Software without restriction, including # without limitation the rights to use, copy, modify, merge, publish, # distribute, sublicense, and/or sell copies of the Software, and to # permit persons to whom the Software is furnished to do so, subject to # the following conditions: # # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. """Tests for autotagging functionality.""" import re import unittest import pytest from beets import autotag, config from beets.autotag import AlbumInfo, TrackInfo, match from beets.autotag.hooks import Distance, string_dist from beets.library import Item from beets.test.helper import BeetsTestCase from beets.util import plurality class PluralityTest(BeetsTestCase): def test_plurality_consensus(self): objs = [1, 1, 1, 1] obj, freq = plurality(objs) assert obj == 1 assert freq == 4 def test_plurality_near_consensus(self): objs = [1, 1, 2, 1] obj, freq = plurality(objs) assert obj == 1 assert freq == 3 def test_plurality_conflict(self): objs = [1, 1, 2, 2, 3] obj, freq = plurality(objs) assert obj in (1, 2) assert freq == 2 def test_plurality_empty_sequence_raises_error(self): with pytest.raises(ValueError, match="must be non-empty"): plurality([]) def test_current_metadata_finds_pluralities(self): items = [ Item(artist="The Beetles", album="The White Album"), Item(artist="The Beatles", album="The White Album"), Item(artist="The Beatles", album="Teh White Album"), ] likelies, consensus = match.current_metadata(items) assert likelies["artist"] == "The Beatles" assert likelies["album"] == "The White Album" assert not consensus["artist"] def test_current_metadata_artist_consensus(self): items = [ Item(artist="The Beatles", album="The White Album"), Item(artist="The Beatles", album="The White Album"), Item(artist="The Beatles", album="Teh White Album"), ] likelies, consensus = match.current_metadata(items) assert likelies["artist"] == "The Beatles" assert likelies["album"] == "The White Album" assert consensus["artist"] def test_albumartist_consensus(self): items = [ Item(artist="tartist1", album="album", albumartist="aartist"), Item(artist="tartist2", album="album", albumartist="aartist"), Item(artist="tartist3", album="album", albumartist="aartist"), ] likelies, consensus = match.current_metadata(items) assert likelies["artist"] == "aartist" assert not consensus["artist"] def test_current_metadata_likelies(self): fields = [ "artist", "album", "albumartist", "year", "disctotal", "mb_albumid", "label", "barcode", "catalognum", "country", "media", "albumdisambig", ] items = [Item(**{f: f"{f}_{i or 1}" for f in fields}) for i in range(5)] likelies, _ = match.current_metadata(items) for f in fields: if isinstance(likelies[f], int): assert likelies[f] == 0 else: assert likelies[f] == f"{f}_1" def _make_item(title, track, artist="some artist"): return Item( title=title, track=track, artist=artist, album="some album", length=1, mb_trackid="", mb_albumid="", mb_artistid="", ) def _make_trackinfo(): return [ TrackInfo( title="one", track_id=None, artist="some artist", length=1, index=1 ), TrackInfo( title="two", track_id=None, artist="some artist", length=1, index=2 ), TrackInfo( title="three", track_id=None, artist="some artist", length=1, index=3, ), ] def _clear_weights(): """Hack around the lazy descriptor used to cache weights for Distance calculations. """ Distance.__dict__["_weights"].cache = {} class DistanceTest(BeetsTestCase): def tearDown(self): super().tearDown() _clear_weights() def test_add(self): dist = Distance() dist.add("add", 1.0) assert dist._penalties == {"add": [1.0]} def test_add_equality(self): dist = Distance() dist.add_equality("equality", "ghi", ["abc", "def", "ghi"]) assert dist._penalties["equality"] == [0.0] dist.add_equality("equality", "xyz", ["abc", "def", "ghi"]) assert dist._penalties["equality"] == [0.0, 1.0] dist.add_equality("equality", "abc", re.compile(r"ABC", re.I)) assert dist._penalties["equality"] == [0.0, 1.0, 0.0] def test_add_expr(self): dist = Distance() dist.add_expr("expr", True) assert dist._penalties["expr"] == [1.0] dist.add_expr("expr", False) assert dist._penalties["expr"] == [1.0, 0.0] def test_add_number(self): dist = Distance() # Add a full penalty for each number of difference between two numbers. dist.add_number("number", 1, 1) assert dist._penalties["number"] == [0.0] dist.add_number("number", 1, 2) assert dist._penalties["number"] == [0.0, 1.0] dist.add_number("number", 2, 1) assert dist._penalties["number"] == [0.0, 1.0, 1.0] dist.add_number("number", -1, 2) assert dist._penalties["number"] == [0.0, 1.0, 1.0, 1.0, 1.0, 1.0] def test_add_priority(self): dist = Distance() dist.add_priority("priority", "abc", "abc") assert dist._penalties["priority"] == [0.0] dist.add_priority("priority", "def", ["abc", "def"]) assert dist._penalties["priority"] == [0.0, 0.5] dist.add_priority( "priority", "gh", ["ab", "cd", "ef", re.compile("GH", re.I)] ) assert dist._penalties["priority"] == [0.0, 0.5, 0.75] dist.add_priority("priority", "xyz", ["abc", "def"]) assert dist._penalties["priority"] == [0.0, 0.5, 0.75, 1.0] def test_add_ratio(self): dist = Distance() dist.add_ratio("ratio", 25, 100) assert dist._penalties["ratio"] == [0.25] dist.add_ratio("ratio", 10, 5) assert dist._penalties["ratio"] == [0.25, 1.0] dist.add_ratio("ratio", -5, 5) assert dist._penalties["ratio"] == [0.25, 1.0, 0.0] dist.add_ratio("ratio", 5, 0) assert dist._penalties["ratio"] == [0.25, 1.0, 0.0, 0.0] def test_add_string(self): dist = Distance() sdist = string_dist("abc", "bcd") dist.add_string("string", "abc", "bcd") assert dist._penalties["string"] == [sdist] assert dist._penalties["string"] != [0] def test_add_string_none(self): dist = Distance() dist.add_string("string", None, "string") assert dist._penalties["string"] == [1] def test_add_string_both_none(self): dist = Distance() dist.add_string("string", None, None) assert dist._penalties["string"] == [0] def test_distance(self): config["match"]["distance_weights"]["album"] = 2.0 config["match"]["distance_weights"]["medium"] = 1.0 _clear_weights() dist = Distance() dist.add("album", 0.5) dist.add("media", 0.25) dist.add("media", 0.75) assert dist.distance == 0.5 # __getitem__() assert dist["album"] == 0.25 assert dist["media"] == 0.25 def test_max_distance(self): config["match"]["distance_weights"]["album"] = 3.0 config["match"]["distance_weights"]["medium"] = 1.0 _clear_weights() dist = Distance() dist.add("album", 0.5) dist.add("medium", 0.0) dist.add("medium", 0.0) assert dist.max_distance == 5.0 def test_operators(self): config["match"]["distance_weights"]["source"] = 1.0 config["match"]["distance_weights"]["album"] = 2.0 config["match"]["distance_weights"]["medium"] = 1.0 _clear_weights() dist = Distance() dist.add("source", 0.0) dist.add("album", 0.5) dist.add("medium", 0.25) dist.add("medium", 0.75) assert len(dist) == 2 assert list(dist) == [("album", 0.2), ("medium", 0.2)] assert dist == 0.4 assert dist < 1.0 assert dist > 0.0 assert dist - 0.4 == 0.0 assert 0.4 - dist == 0.0 assert float(dist) == 0.4 def test_raw_distance(self): config["match"]["distance_weights"]["album"] = 3.0 config["match"]["distance_weights"]["medium"] = 1.0 _clear_weights() dist = Distance() dist.add("album", 0.5) dist.add("medium", 0.25) dist.add("medium", 0.5) assert dist.raw_distance == 2.25 def test_items(self): config["match"]["distance_weights"]["album"] = 4.0 config["match"]["distance_weights"]["medium"] = 2.0 _clear_weights() dist = Distance() dist.add("album", 0.1875) dist.add("medium", 0.75) assert dist.items() == [("medium", 0.25), ("album", 0.125)] # Sort by key if distance is equal. dist = Distance() dist.add("album", 0.375) dist.add("medium", 0.75) assert dist.items() == [("album", 0.25), ("medium", 0.25)] def test_update(self): dist1 = Distance() dist1.add("album", 0.5) dist1.add("media", 1.0) dist2 = Distance() dist2.add("album", 0.75) dist2.add("album", 0.25) dist2.add("media", 0.05) dist1.update(dist2) assert dist1._penalties == { "album": [0.5, 0.75, 0.25], "media": [1.0, 0.05], } class TrackDistanceTest(BeetsTestCase): def test_identical_tracks(self): item = _make_item("one", 1) info = _make_trackinfo()[0] dist = match.track_distance(item, info, incl_artist=True) assert dist == 0.0 def test_different_title(self): item = _make_item("foo", 1) info = _make_trackinfo()[0] dist = match.track_distance(item, info, incl_artist=True) assert dist != 0.0 def test_different_artist(self): item = _make_item("one", 1) item.artist = "foo" info = _make_trackinfo()[0] dist = match.track_distance(item, info, incl_artist=True) assert dist != 0.0 def test_various_artists_tolerated(self): item = _make_item("one", 1) item.artist = "Various Artists" info = _make_trackinfo()[0] dist = match.track_distance(item, info, incl_artist=True) assert dist == 0.0 class AlbumDistanceTest(BeetsTestCase): def _mapping(self, items, info): out = {} for i, t in zip(items, info.tracks): out[i] = t return out def _dist(self, items, info): return match.distance(items, info, self._mapping(items, info)) def test_identical_albums(self): items = [] items.append(_make_item("one", 1)) items.append(_make_item("two", 2)) items.append(_make_item("three", 3)) info = AlbumInfo( artist="some artist", album="some album", tracks=_make_trackinfo(), va=False, ) assert self._dist(items, info) == 0 def test_incomplete_album(self): items = [] items.append(_make_item("one", 1)) items.append(_make_item("three", 3)) info = AlbumInfo( artist="some artist", album="some album", tracks=_make_trackinfo(), va=False, ) dist = self._dist(items, info) assert dist != 0 # Make sure the distance is not too great assert dist < 0.2 def test_global_artists_differ(self): items = [] items.append(_make_item("one", 1)) items.append(_make_item("two", 2)) items.append(_make_item("three", 3)) info = AlbumInfo( artist="someone else", album="some album", tracks=_make_trackinfo(), va=False, ) assert self._dist(items, info) != 0 def test_comp_track_artists_match(self): items = [] items.append(_make_item("one", 1)) items.append(_make_item("two", 2)) items.append(_make_item("three", 3)) info = AlbumInfo( artist="should be ignored", album="some album", tracks=_make_trackinfo(), va=True, ) assert self._dist(items, info) == 0 def test_comp_no_track_artists(self): # Some VA releases don't have track artists (incomplete metadata). items = [] items.append(_make_item("one", 1)) items.append(_make_item("two", 2)) items.append(_make_item("three", 3)) info = AlbumInfo( artist="should be ignored", album="some album", tracks=_make_trackinfo(), va=True, ) info.tracks[0].artist = None info.tracks[1].artist = None info.tracks[2].artist = None assert self._dist(items, info) == 0 def test_comp_track_artists_do_not_match(self): items = [] items.append(_make_item("one", 1)) items.append(_make_item("two", 2, "someone else")) items.append(_make_item("three", 3)) info = AlbumInfo( artist="some artist", album="some album", tracks=_make_trackinfo(), va=True, ) assert self._dist(items, info) != 0 def test_tracks_out_of_order(self): items = [] items.append(_make_item("one", 1)) items.append(_make_item("three", 2)) items.append(_make_item("two", 3)) info = AlbumInfo( artist="some artist", album="some album", tracks=_make_trackinfo(), va=False, ) dist = self._dist(items, info) assert 0 < dist < 0.2 def test_two_medium_release(self): items = [] items.append(_make_item("one", 1)) items.append(_make_item("two", 2)) items.append(_make_item("three", 3)) info = AlbumInfo( artist="some artist", album="some album", tracks=_make_trackinfo(), va=False, ) info.tracks[0].medium_index = 1 info.tracks[1].medium_index = 2 info.tracks[2].medium_index = 1 dist = self._dist(items, info) assert dist == 0 def test_per_medium_track_numbers(self): items = [] items.append(_make_item("one", 1)) items.append(_make_item("two", 2)) items.append(_make_item("three", 1)) info = AlbumInfo( artist="some artist", album="some album", tracks=_make_trackinfo(), va=False, ) info.tracks[0].medium_index = 1 info.tracks[1].medium_index = 2 info.tracks[2].medium_index = 1 dist = self._dist(items, info) assert dist == 0 class AssignmentTest(unittest.TestCase): def item(self, title, track): return Item( title=title, track=track, mb_trackid="", mb_albumid="", mb_artistid="", ) def test_reorder_when_track_numbers_incorrect(self): items = [] items.append(self.item("one", 1)) items.append(self.item("three", 2)) items.append(self.item("two", 3)) trackinfo = [] trackinfo.append(TrackInfo(title="one")) trackinfo.append(TrackInfo(title="two")) trackinfo.append(TrackInfo(title="three")) mapping, extra_items, extra_tracks = match.assign_items( items, trackinfo ) assert extra_items == [] assert extra_tracks == [] assert mapping == { items[0]: trackinfo[0], items[1]: trackinfo[2], items[2]: trackinfo[1], } def test_order_works_with_invalid_track_numbers(self): items = [] items.append(self.item("one", 1)) items.append(self.item("three", 1)) items.append(self.item("two", 1)) trackinfo = [] trackinfo.append(TrackInfo(title="one")) trackinfo.append(TrackInfo(title="two")) trackinfo.append(TrackInfo(title="three")) mapping, extra_items, extra_tracks = match.assign_items( items, trackinfo ) assert extra_items == [] assert extra_tracks == [] assert mapping == { items[0]: trackinfo[0], items[1]: trackinfo[2], items[2]: trackinfo[1], } def test_order_works_with_missing_tracks(self): items = [] items.append(self.item("one", 1)) items.append(self.item("three", 3)) trackinfo = [] trackinfo.append(TrackInfo(title="one")) trackinfo.append(TrackInfo(title="two")) trackinfo.append(TrackInfo(title="three")) mapping, extra_items, extra_tracks = match.assign_items( items, trackinfo ) assert extra_items == [] assert extra_tracks == [trackinfo[1]] assert mapping == {items[0]: trackinfo[0], items[1]: trackinfo[2]} def test_order_works_with_extra_tracks(self): items = [] items.append(self.item("one", 1)) items.append(self.item("two", 2)) items.append(self.item("three", 3)) trackinfo = [] trackinfo.append(TrackInfo(title="one")) trackinfo.append(TrackInfo(title="three")) mapping, extra_items, extra_tracks = match.assign_items( items, trackinfo ) assert extra_items == [items[1]] assert extra_tracks == [] assert mapping == {items[0]: trackinfo[0], items[2]: trackinfo[1]} def test_order_works_when_track_names_are_entirely_wrong(self): # A real-world test case contributed by a user. def item(i, length): return Item( artist="ben harper", album="burn to shine", title=f"ben harper - Burn to Shine {i}", track=i, length=length, mb_trackid="", mb_albumid="", mb_artistid="", ) items = [] items.append(item(1, 241.37243007106997)) items.append(item(2, 342.27781704375036)) items.append(item(3, 245.95070222338137)) items.append(item(4, 472.87662515485437)) items.append(item(5, 279.1759535763187)) items.append(item(6, 270.33333768012)) items.append(item(7, 247.83435613222923)) items.append(item(8, 216.54504531525072)) items.append(item(9, 225.72775379800484)) items.append(item(10, 317.7643606963552)) items.append(item(11, 243.57001238834192)) items.append(item(12, 186.45916150485752)) def info(index, title, length): return TrackInfo(title=title, length=length, index=index) trackinfo = [] trackinfo.append(info(1, "Alone", 238.893)) trackinfo.append(info(2, "The Woman in You", 341.44)) trackinfo.append(info(3, "Less", 245.59999999999999)) trackinfo.append(info(4, "Two Hands of a Prayer", 470.49299999999999)) trackinfo.append(info(5, "Please Bleed", 277.86599999999999)) trackinfo.append(info(6, "Suzie Blue", 269.30599999999998)) trackinfo.append(info(7, "Steal My Kisses", 245.36000000000001)) trackinfo.append(info(8, "Burn to Shine", 214.90600000000001)) trackinfo.append(info(9, "Show Me a Little Shame", 224.0929999999999)) trackinfo.append(info(10, "Forgiven", 317.19999999999999)) trackinfo.append(info(11, "Beloved One", 243.733)) trackinfo.append(info(12, "In the Lord's Arms", 186.13300000000001)) mapping, extra_items, extra_tracks = match.assign_items( items, trackinfo ) assert extra_items == [] assert extra_tracks == [] for item, info in mapping.items(): assert items.index(item) == trackinfo.index(info) class ApplyTestUtil: def _apply(self, info=None, per_disc_numbering=False, artist_credit=False): info = info or self.info mapping = {} for i, t in zip(self.items, info.tracks): mapping[i] = t config["per_disc_numbering"] = per_disc_numbering config["artist_credit"] = artist_credit autotag.apply_metadata(info, mapping) class ApplyTest(BeetsTestCase, ApplyTestUtil): def setUp(self): super().setUp() self.items = [] self.items.append(Item({})) self.items.append(Item({})) trackinfo = [] trackinfo.append( TrackInfo( title="oneNew", track_id="dfa939ec-118c-4d0f-84a0-60f3d1e6522c", medium=1, medium_index=1, medium_total=1, index=1, artist_credit="trackArtistCredit", artists_credit=["trackArtistCredit"], artist_sort="trackArtistSort", artists_sort=["trackArtistSort"], ) ) trackinfo.append( TrackInfo( title="twoNew", track_id="40130ed1-a27c-42fd-a328-1ebefb6caef4", medium=2, medium_index=1, index=2, medium_total=1, ) ) self.info = AlbumInfo( tracks=trackinfo, artist="artistNew", artists=["artistNew", "artistNew2"], album="albumNew", album_id="7edb51cb-77d6-4416-a23c-3a8c2994a2c7", artist_id="a6623d39-2d8e-4f70-8242-0a9553b91e50", artists_ids=[ "a6623d39-2d8e-4f70-8242-0a9553b91e50", "a6623d39-2d8e-4f70-8242-0a9553b91e51", ], artist_credit="albumArtistCredit", artists_credit=["albumArtistCredit", "albumArtistCredit2"], artist_sort="albumArtistSort", artists_sort=["albumArtistSort", "albumArtistSort2"], albumtype="album", va=False, mediums=2, ) def test_titles_applied(self): self._apply() assert self.items[0].title == "oneNew" assert self.items[1].title == "twoNew" def test_album_and_artist_applied_to_all(self): self._apply() assert self.items[0].album == "albumNew" assert self.items[1].album == "albumNew" assert self.items[0].artist == "artistNew" assert self.items[1].artist == "artistNew" assert self.items[0].artists == ["artistNew", "artistNew2"] assert self.items[1].artists == ["artistNew", "artistNew2"] assert self.items[0].albumartists == ["artistNew", "artistNew2"] assert self.items[1].albumartists == ["artistNew", "artistNew2"] def test_track_index_applied(self): self._apply() assert self.items[0].track == 1 assert self.items[1].track == 2 def test_track_total_applied(self): self._apply() assert self.items[0].tracktotal == 2 assert self.items[1].tracktotal == 2 def test_disc_index_applied(self): self._apply() assert self.items[0].disc == 1 assert self.items[1].disc == 2 def test_disc_total_applied(self): self._apply() assert self.items[0].disctotal == 2 assert self.items[1].disctotal == 2 def test_per_disc_numbering(self): self._apply(per_disc_numbering=True) assert self.items[0].track == 1 assert self.items[1].track == 1 def test_per_disc_numbering_track_total(self): self._apply(per_disc_numbering=True) assert self.items[0].tracktotal == 1 assert self.items[1].tracktotal == 1 def test_artist_credit(self): self._apply(artist_credit=True) assert self.items[0].artist == "trackArtistCredit" assert self.items[1].artist == "albumArtistCredit" assert self.items[0].albumartist == "albumArtistCredit" assert self.items[1].albumartist == "albumArtistCredit" assert self.items[0].albumartists == [ "albumArtistCredit", "albumArtistCredit2", ] assert self.items[1].albumartists == [ "albumArtistCredit", "albumArtistCredit2", ] def test_artist_credit_prefers_artist_over_albumartist_credit(self): self.info.tracks[0].artist = "oldArtist" self.info.tracks[0].artist_credit = None self._apply(artist_credit=True) assert self.items[0].artist == "oldArtist" def test_artist_credit_falls_back_to_albumartist(self): self.info.artist_credit = None self._apply(artist_credit=True) assert self.items[1].artist == "artistNew" def test_mb_trackid_applied(self): self._apply() assert ( self.items[0].mb_trackid == "dfa939ec-118c-4d0f-84a0-60f3d1e6522c" ) assert ( self.items[1].mb_trackid == "40130ed1-a27c-42fd-a328-1ebefb6caef4" ) def test_mb_albumid_and_artistid_applied(self): self._apply() for item in self.items: assert item.mb_albumid == "7edb51cb-77d6-4416-a23c-3a8c2994a2c7" assert item.mb_artistid == "a6623d39-2d8e-4f70-8242-0a9553b91e50" assert item.mb_artistids == [ "a6623d39-2d8e-4f70-8242-0a9553b91e50", "a6623d39-2d8e-4f70-8242-0a9553b91e51", ] def test_albumtype_applied(self): self._apply() assert self.items[0].albumtype == "album" assert self.items[1].albumtype == "album" def test_album_artist_overrides_empty_track_artist(self): my_info = self.info.copy() self._apply(info=my_info) assert self.items[0].artist == "artistNew" assert self.items[1].artist == "artistNew" assert self.items[0].artists == ["artistNew", "artistNew2"] assert self.items[1].artists == ["artistNew", "artistNew2"] def test_album_artist_overridden_by_nonempty_track_artist(self): my_info = self.info.copy() my_info.tracks[0].artist = "artist1!" my_info.tracks[1].artist = "artist2!" my_info.tracks[0].artists = ["artist1!", "artist1!!"] my_info.tracks[1].artists = ["artist2!", "artist2!!"] self._apply(info=my_info) assert self.items[0].artist == "artist1!" assert self.items[1].artist == "artist2!" assert self.items[0].artists == ["artist1!", "artist1!!"] assert self.items[1].artists == ["artist2!", "artist2!!"] def test_artist_credit_applied(self): self._apply() assert self.items[0].albumartist_credit == "albumArtistCredit" assert self.items[0].albumartists_credit == [ "albumArtistCredit", "albumArtistCredit2", ] assert self.items[0].artist_credit == "trackArtistCredit" assert self.items[0].artists_credit == ["trackArtistCredit"] assert self.items[1].albumartist_credit == "albumArtistCredit" assert self.items[1].albumartists_credit == [ "albumArtistCredit", "albumArtistCredit2", ] assert self.items[1].artist_credit == "albumArtistCredit" assert self.items[1].artists_credit == [ "albumArtistCredit", "albumArtistCredit2", ] def test_artist_sort_applied(self): self._apply() assert self.items[0].albumartist_sort == "albumArtistSort" assert self.items[0].albumartists_sort == [ "albumArtistSort", "albumArtistSort2", ] assert self.items[0].artist_sort == "trackArtistSort" assert self.items[0].artists_sort == ["trackArtistSort"] assert self.items[1].albumartist_sort == "albumArtistSort" assert self.items[1].albumartists_sort == [ "albumArtistSort", "albumArtistSort2", ] assert self.items[1].artist_sort == "albumArtistSort" assert self.items[1].artists_sort == [ "albumArtistSort", "albumArtistSort2", ] def test_full_date_applied(self): my_info = self.info.copy() my_info.year = 2013 my_info.month = 12 my_info.day = 18 self._apply(info=my_info) assert self.items[0].year == 2013 assert self.items[0].month == 12 assert self.items[0].day == 18 def test_date_only_zeros_month_and_day(self): self.items = [] self.items.append(Item(year=1, month=2, day=3)) self.items.append(Item(year=4, month=5, day=6)) my_info = self.info.copy() my_info.year = 2013 self._apply(info=my_info) assert self.items[0].year == 2013 assert self.items[0].month == 0 assert self.items[0].day == 0 def test_missing_date_applies_nothing(self): self.items = [] self.items.append(Item(year=1, month=2, day=3)) self.items.append(Item(year=4, month=5, day=6)) self._apply() assert self.items[0].year == 1 assert self.items[0].month == 2 assert self.items[0].day == 3 def test_data_source_applied(self): my_info = self.info.copy() my_info.data_source = "MusicBrainz" self._apply(info=my_info) assert self.items[0].data_source == "MusicBrainz" class ApplyCompilationTest(BeetsTestCase, ApplyTestUtil): def setUp(self): super().setUp() self.items = [] self.items.append(Item({})) self.items.append(Item({})) trackinfo = [] trackinfo.append( TrackInfo( title="oneNew", track_id="dfa939ec-118c-4d0f-84a0-60f3d1e6522c", artist="artistOneNew", artist_id="a05686fc-9db2-4c23-b99e-77f5db3e5282", index=1, ) ) trackinfo.append( TrackInfo( title="twoNew", track_id="40130ed1-a27c-42fd-a328-1ebefb6caef4", artist="artistTwoNew", artist_id="80b3cf5e-18fe-4c59-98c7-e5bb87210710", index=2, ) ) self.info = AlbumInfo( tracks=trackinfo, artist="variousNew", album="albumNew", album_id="3b69ea40-39b8-487f-8818-04b6eff8c21a", artist_id="89ad4ac3-39f7-470e-963a-56509c546377", albumtype="compilation", ) def test_album_and_track_artists_separate(self): self._apply() assert self.items[0].artist == "artistOneNew" assert self.items[1].artist == "artistTwoNew" assert self.items[0].albumartist == "variousNew" assert self.items[1].albumartist == "variousNew" def test_mb_albumartistid_applied(self): self._apply() assert ( self.items[0].mb_albumartistid == "89ad4ac3-39f7-470e-963a-56509c546377" ) assert ( self.items[1].mb_albumartistid == "89ad4ac3-39f7-470e-963a-56509c546377" ) assert ( self.items[0].mb_artistid == "a05686fc-9db2-4c23-b99e-77f5db3e5282" ) assert ( self.items[1].mb_artistid == "80b3cf5e-18fe-4c59-98c7-e5bb87210710" ) def test_va_flag_cleared_does_not_set_comp(self): self._apply() assert not self.items[0].comp assert not self.items[1].comp def test_va_flag_sets_comp(self): va_info = self.info.copy() va_info.va = True self._apply(info=va_info) assert self.items[0].comp assert self.items[1].comp class StringDistanceTest(unittest.TestCase): def test_equal_strings(self): dist = string_dist("Some String", "Some String") assert dist == 0.0 def test_different_strings(self): dist = string_dist("Some String", "Totally Different") assert dist != 0.0 def test_punctuation_ignored(self): dist = string_dist("Some String", "Some.String!") assert dist == 0.0 def test_case_ignored(self): dist = string_dist("Some String", "sOME sTring") assert dist == 0.0 def test_leading_the_has_lower_weight(self): dist1 = string_dist("XXX Band Name", "Band Name") dist2 = string_dist("The Band Name", "Band Name") assert dist2 < dist1 def test_parens_have_lower_weight(self): dist1 = string_dist("One .Two.", "One") dist2 = string_dist("One (Two)", "One") assert dist2 < dist1 def test_brackets_have_lower_weight(self): dist1 = string_dist("One .Two.", "One") dist2 = string_dist("One [Two]", "One") assert dist2 < dist1 def test_ep_label_has_zero_weight(self): dist = string_dist("My Song (EP)", "My Song") assert dist == 0.0 def test_featured_has_lower_weight(self): dist1 = string_dist("My Song blah Someone", "My Song") dist2 = string_dist("My Song feat Someone", "My Song") assert dist2 < dist1 def test_postfix_the(self): dist = string_dist("The Song Title", "Song Title, The") assert dist == 0.0 def test_postfix_a(self): dist = string_dist("A Song Title", "Song Title, A") assert dist == 0.0 def test_postfix_an(self): dist = string_dist("An Album Title", "Album Title, An") assert dist == 0.0 def test_empty_strings(self): dist = string_dist("", "") assert dist == 0.0 def test_solo_pattern(self): # Just make sure these don't crash. string_dist("The ", "") string_dist("(EP)", "(EP)") string_dist(", An", "") def test_heuristic_does_not_harm_distance(self): dist = string_dist("Untitled", "[Untitled]") assert dist == 0.0 def test_ampersand_expansion(self): dist = string_dist("And", "&") assert dist == 0.0 def test_accented_characters(self): dist = string_dist("\xe9\xe1\xf1", "ean") assert dist == 0.0 beetbox-beets-01f1faf/test/test_config_command.py000066400000000000000000000106121472325477400223020ustar00rootroot00000000000000import os from unittest.mock import patch import pytest import yaml from beets import config, ui from beets.test.helper import BeetsTestCase class ConfigCommandTest(BeetsTestCase): def setUp(self): super().setUp() for k in ("VISUAL", "EDITOR"): if k in os.environ: del os.environ[k] temp_dir = self.temp_dir.decode() self.config_path = os.path.join(temp_dir, "config.yaml") with open(self.config_path, "w") as file: file.write("library: lib\n") file.write("option: value\n") file.write("password: password_value") self.cli_config_path = os.path.join(temp_dir, "cli_config.yaml") with open(self.cli_config_path, "w") as file: file.write("option: cli overwrite") config.clear() config["password"].redact = True config._materialized = False def _run_with_yaml_output(self, *args): output = self.run_with_output(*args) return yaml.safe_load(output) def test_show_user_config(self): output = self._run_with_yaml_output("config", "-c") assert output["option"] == "value" assert output["password"] == "password_value" def test_show_user_config_with_defaults(self): output = self._run_with_yaml_output("config", "-dc") assert output["option"] == "value" assert output["password"] == "password_value" assert output["library"] == "lib" assert not output["import"]["timid"] def test_show_user_config_with_cli(self): output = self._run_with_yaml_output( "--config", self.cli_config_path, "config" ) assert output["library"] == "lib" assert output["option"] == "cli overwrite" def test_show_redacted_user_config(self): output = self._run_with_yaml_output("config") assert output["option"] == "value" assert output["password"] == "REDACTED" def test_show_redacted_user_config_with_defaults(self): output = self._run_with_yaml_output("config", "-d") assert output["option"] == "value" assert output["password"] == "REDACTED" assert not output["import"]["timid"] def test_config_paths(self): output = self.run_with_output("config", "-p") paths = output.split("\n") assert len(paths) == 2 assert paths[0] == self.config_path def test_config_paths_with_cli(self): output = self.run_with_output( "--config", self.cli_config_path, "config", "-p" ) paths = output.split("\n") assert len(paths) == 3 assert paths[0] == self.cli_config_path def test_edit_config_with_visual_or_editor_env(self): os.environ["EDITOR"] = "myeditor" with patch("os.execlp") as execlp: self.run_command("config", "-e") execlp.assert_called_once_with("myeditor", "myeditor", self.config_path) os.environ["VISUAL"] = "" # empty environment variables gets ignored with patch("os.execlp") as execlp: self.run_command("config", "-e") execlp.assert_called_once_with("myeditor", "myeditor", self.config_path) os.environ["VISUAL"] = "myvisual" with patch("os.execlp") as execlp: self.run_command("config", "-e") execlp.assert_called_once_with("myvisual", "myvisual", self.config_path) def test_edit_config_with_automatic_open(self): with patch("beets.util.open_anything") as open: open.return_value = "please_open" with patch("os.execlp") as execlp: self.run_command("config", "-e") execlp.assert_called_once_with( "please_open", "please_open", self.config_path ) def test_config_editor_not_found(self): msg_match = "Could not edit configuration.*here is problem" with patch( "os.execlp", side_effect=OSError("here is problem") ), pytest.raises(ui.UserError, match=msg_match): self.run_command("config", "-e") def test_edit_invalid_config_file(self): with open(self.config_path, "w") as file: file.write("invalid: [") config.clear() config._materialized = False os.environ["EDITOR"] = "myeditor" with patch("os.execlp") as execlp: self.run_command("config", "-e") execlp.assert_called_once_with("myeditor", "myeditor", self.config_path) beetbox-beets-01f1faf/test/test_datequery.py000066400000000000000000000264571472325477400213600ustar00rootroot00000000000000# This file is part of beets. # Copyright 2016, Adrian Sampson. # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the # "Software"), to deal in the Software without restriction, including # without limitation the rights to use, copy, modify, merge, publish, # distribute, sublicense, and/or sell copies of the Software, and to # permit persons to whom the Software is furnished to do so, subject to # the following conditions: # # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. """Test for dbcore's date-based queries.""" import time import unittest from datetime import datetime, timedelta import pytest from beets.dbcore.query import ( DateInterval, DateQuery, InvalidQueryArgumentValueError, _parse_periods, ) from beets.test.helper import ItemInDBTestCase def _date(string): return datetime.strptime(string, "%Y-%m-%dT%H:%M:%S") def _datepattern(datetimedate): return datetimedate.strftime("%Y-%m-%dT%H:%M:%S") class DateIntervalTest(unittest.TestCase): def test_year_precision_intervals(self): self.assertContains("2000..2001", "2000-01-01T00:00:00") self.assertContains("2000..2001", "2001-06-20T14:15:16") self.assertContains("2000..2001", "2001-12-31T23:59:59") self.assertExcludes("2000..2001", "1999-12-31T23:59:59") self.assertExcludes("2000..2001", "2002-01-01T00:00:00") self.assertContains("2000..", "2000-01-01T00:00:00") self.assertContains("2000..", "2099-10-11T00:00:00") self.assertExcludes("2000..", "1999-12-31T23:59:59") self.assertContains("..2001", "2001-12-31T23:59:59") self.assertExcludes("..2001", "2002-01-01T00:00:00") self.assertContains("-1d..1d", _datepattern(datetime.now())) self.assertExcludes("-2d..-1d", _datepattern(datetime.now())) def test_day_precision_intervals(self): self.assertContains("2000-06-20..2000-06-20", "2000-06-20T00:00:00") self.assertContains("2000-06-20..2000-06-20", "2000-06-20T10:20:30") self.assertContains("2000-06-20..2000-06-20", "2000-06-20T23:59:59") self.assertExcludes("2000-06-20..2000-06-20", "2000-06-19T23:59:59") self.assertExcludes("2000-06-20..2000-06-20", "2000-06-21T00:00:00") def test_month_precision_intervals(self): self.assertContains("1999-12..2000-02", "1999-12-01T00:00:00") self.assertContains("1999-12..2000-02", "2000-02-15T05:06:07") self.assertContains("1999-12..2000-02", "2000-02-29T23:59:59") self.assertExcludes("1999-12..2000-02", "1999-11-30T23:59:59") self.assertExcludes("1999-12..2000-02", "2000-03-01T00:00:00") def test_hour_precision_intervals(self): # test with 'T' separator self.assertExcludes( "2000-01-01T12..2000-01-01T13", "2000-01-01T11:59:59" ) self.assertContains( "2000-01-01T12..2000-01-01T13", "2000-01-01T12:00:00" ) self.assertContains( "2000-01-01T12..2000-01-01T13", "2000-01-01T12:30:00" ) self.assertContains( "2000-01-01T12..2000-01-01T13", "2000-01-01T13:30:00" ) self.assertContains( "2000-01-01T12..2000-01-01T13", "2000-01-01T13:59:59" ) self.assertExcludes( "2000-01-01T12..2000-01-01T13", "2000-01-01T14:00:00" ) self.assertExcludes( "2000-01-01T12..2000-01-01T13", "2000-01-01T14:30:00" ) # test non-range query self.assertContains("2008-12-01T22", "2008-12-01T22:30:00") self.assertExcludes("2008-12-01T22", "2008-12-01T23:30:00") def test_minute_precision_intervals(self): self.assertExcludes( "2000-01-01T12:30..2000-01-01T12:31", "2000-01-01T12:29:59" ) self.assertContains( "2000-01-01T12:30..2000-01-01T12:31", "2000-01-01T12:30:00" ) self.assertContains( "2000-01-01T12:30..2000-01-01T12:31", "2000-01-01T12:30:30" ) self.assertContains( "2000-01-01T12:30..2000-01-01T12:31", "2000-01-01T12:31:59" ) self.assertExcludes( "2000-01-01T12:30..2000-01-01T12:31", "2000-01-01T12:32:00" ) def test_second_precision_intervals(self): self.assertExcludes( "2000-01-01T12:30:50..2000-01-01T12:30:55", "2000-01-01T12:30:49" ) self.assertContains( "2000-01-01T12:30:50..2000-01-01T12:30:55", "2000-01-01T12:30:50" ) self.assertContains( "2000-01-01T12:30:50..2000-01-01T12:30:55", "2000-01-01T12:30:55" ) self.assertExcludes( "2000-01-01T12:30:50..2000-01-01T12:30:55", "2000-01-01T12:30:56" ) def test_unbounded_endpoints(self): self.assertContains("..", date=datetime.max) self.assertContains("..", date=datetime.min) self.assertContains("..", "1000-01-01T00:00:00") def assertContains(self, interval_pattern, date_pattern=None, date=None): if date is None: date = _date(date_pattern) (start, end) = _parse_periods(interval_pattern) interval = DateInterval.from_periods(start, end) assert interval.contains(date) def assertExcludes(self, interval_pattern, date_pattern): date = _date(date_pattern) (start, end) = _parse_periods(interval_pattern) interval = DateInterval.from_periods(start, end) assert not interval.contains(date) def _parsetime(s): return time.mktime(datetime.strptime(s, "%Y-%m-%d %H:%M").timetuple()) class DateQueryTest(ItemInDBTestCase): def setUp(self): super().setUp() self.i.added = _parsetime("2013-03-30 22:21") self.i.store() def test_single_month_match_fast(self): query = DateQuery("added", "2013-03") matched = self.lib.items(query) assert len(matched) == 1 def test_single_month_nonmatch_fast(self): query = DateQuery("added", "2013-04") matched = self.lib.items(query) assert len(matched) == 0 def test_single_month_match_slow(self): query = DateQuery("added", "2013-03") assert query.match(self.i) def test_single_month_nonmatch_slow(self): query = DateQuery("added", "2013-04") assert not query.match(self.i) def test_single_day_match_fast(self): query = DateQuery("added", "2013-03-30") matched = self.lib.items(query) assert len(matched) == 1 def test_single_day_nonmatch_fast(self): query = DateQuery("added", "2013-03-31") matched = self.lib.items(query) assert len(matched) == 0 class DateQueryTestRelative(ItemInDBTestCase): def setUp(self): super().setUp() # We pick a date near a month changeover, which can reveal some time # zone bugs. self._now = datetime(2017, 12, 31, 22, 55, 4, 101332) self.i.added = _parsetime(self._now.strftime("%Y-%m-%d %H:%M")) self.i.store() def test_single_month_match_fast(self): query = DateQuery("added", self._now.strftime("%Y-%m")) matched = self.lib.items(query) assert len(matched) == 1 def test_single_month_nonmatch_fast(self): query = DateQuery( "added", (self._now + timedelta(days=30)).strftime("%Y-%m") ) matched = self.lib.items(query) assert len(matched) == 0 def test_single_month_match_slow(self): query = DateQuery("added", self._now.strftime("%Y-%m")) assert query.match(self.i) def test_single_month_nonmatch_slow(self): query = DateQuery( "added", (self._now + timedelta(days=30)).strftime("%Y-%m") ) assert not query.match(self.i) def test_single_day_match_fast(self): query = DateQuery("added", self._now.strftime("%Y-%m-%d")) matched = self.lib.items(query) assert len(matched) == 1 def test_single_day_nonmatch_fast(self): query = DateQuery( "added", (self._now + timedelta(days=1)).strftime("%Y-%m-%d") ) matched = self.lib.items(query) assert len(matched) == 0 class DateQueryTestRelativeMore(ItemInDBTestCase): def setUp(self): super().setUp() self.i.added = _parsetime(datetime.now().strftime("%Y-%m-%d %H:%M")) self.i.store() def test_relative(self): for timespan in ["d", "w", "m", "y"]: query = DateQuery("added", "-4" + timespan + "..+4" + timespan) matched = self.lib.items(query) assert len(matched) == 1 def test_relative_fail(self): for timespan in ["d", "w", "m", "y"]: query = DateQuery("added", "-2" + timespan + "..-1" + timespan) matched = self.lib.items(query) assert len(matched) == 0 def test_start_relative(self): for timespan in ["d", "w", "m", "y"]: query = DateQuery("added", "-4" + timespan + "..") matched = self.lib.items(query) assert len(matched) == 1 def test_start_relative_fail(self): for timespan in ["d", "w", "m", "y"]: query = DateQuery("added", "4" + timespan + "..") matched = self.lib.items(query) assert len(matched) == 0 def test_end_relative(self): for timespan in ["d", "w", "m", "y"]: query = DateQuery("added", "..+4" + timespan) matched = self.lib.items(query) assert len(matched) == 1 def test_end_relative_fail(self): for timespan in ["d", "w", "m", "y"]: query = DateQuery("added", "..-4" + timespan) matched = self.lib.items(query) assert len(matched) == 0 class DateQueryConstructTest(unittest.TestCase): def test_long_numbers(self): with pytest.raises(InvalidQueryArgumentValueError): DateQuery("added", "1409830085..1412422089") def test_too_many_components(self): with pytest.raises(InvalidQueryArgumentValueError): DateQuery("added", "12-34-56-78") def test_invalid_date_query(self): q_list = [ "2001-01-0a", "2001-0a", "200a", "2001-01-01..2001-01-0a", "2001-0a..2001-01", "200a..2002", "20aa..", "..2aa", ] for q in q_list: with pytest.raises(InvalidQueryArgumentValueError): DateQuery("added", q) def test_datetime_uppercase_t_separator(self): date_query = DateQuery("added", "2000-01-01T12") assert date_query.interval.start == datetime(2000, 1, 1, 12) assert date_query.interval.end == datetime(2000, 1, 1, 13) def test_datetime_lowercase_t_separator(self): date_query = DateQuery("added", "2000-01-01t12") assert date_query.interval.start == datetime(2000, 1, 1, 12) assert date_query.interval.end == datetime(2000, 1, 1, 13) def test_datetime_space_separator(self): date_query = DateQuery("added", "2000-01-01 12") assert date_query.interval.start == datetime(2000, 1, 1, 12) assert date_query.interval.end == datetime(2000, 1, 1, 13) def test_datetime_invalid_separator(self): with pytest.raises(InvalidQueryArgumentValueError): DateQuery("added", "2000-01-01x12") beetbox-beets-01f1faf/test/test_dbcore.py000066400000000000000000000543251472325477400206060ustar00rootroot00000000000000# This file is part of beets. # Copyright 2016, Adrian Sampson. # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the # "Software"), to deal in the Software without restriction, including # without limitation the rights to use, copy, modify, merge, publish, # distribute, sublicense, and/or sell copies of the Software, and to # permit persons to whom the Software is furnished to do so, subject to # the following conditions: # # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. """Tests for the DBCore database abstraction.""" import os import shutil import sqlite3 import unittest from tempfile import mkstemp import pytest from beets import dbcore from beets.test import _common # Fixture: concrete database and model classes. For migration tests, we # have multiple models with different numbers of fields. class SortFixture(dbcore.query.FieldSort): pass class QueryFixture(dbcore.query.FieldQuery): def __init__(self, pattern): self.pattern = pattern def clause(self): return None, () def match(self): return True class ModelFixture1(dbcore.Model): _table = "test" _flex_table = "testflex" _fields = { "id": dbcore.types.PRIMARY_ID, "field_one": dbcore.types.INTEGER, "field_two": dbcore.types.STRING, } _types = { "some_float_field": dbcore.types.FLOAT, } _sorts = { "some_sort": SortFixture, } _queries = { "some_query": QueryFixture, } @classmethod def _getters(cls): return {} def _template_funcs(self): return {} class DatabaseFixture1(dbcore.Database): _models = (ModelFixture1,) pass class ModelFixture2(ModelFixture1): _fields = { "id": dbcore.types.PRIMARY_ID, "field_one": dbcore.types.INTEGER, "field_two": dbcore.types.INTEGER, } class DatabaseFixture2(dbcore.Database): _models = (ModelFixture2,) pass class ModelFixture3(ModelFixture1): _fields = { "id": dbcore.types.PRIMARY_ID, "field_one": dbcore.types.INTEGER, "field_two": dbcore.types.INTEGER, "field_three": dbcore.types.INTEGER, } class DatabaseFixture3(dbcore.Database): _models = (ModelFixture3,) pass class ModelFixture4(ModelFixture1): _fields = { "id": dbcore.types.PRIMARY_ID, "field_one": dbcore.types.INTEGER, "field_two": dbcore.types.INTEGER, "field_three": dbcore.types.INTEGER, "field_four": dbcore.types.INTEGER, } class DatabaseFixture4(dbcore.Database): _models = (ModelFixture4,) pass class AnotherModelFixture(ModelFixture1): _table = "another" _flex_table = "anotherflex" _fields = { "id": dbcore.types.PRIMARY_ID, "foo": dbcore.types.INTEGER, } class ModelFixture5(ModelFixture1): _fields = { "some_string_field": dbcore.types.STRING, "some_float_field": dbcore.types.FLOAT, "some_boolean_field": dbcore.types.BOOLEAN, } class DatabaseFixture5(dbcore.Database): _models = (ModelFixture5,) pass class DatabaseFixtureTwoModels(dbcore.Database): _models = (ModelFixture2, AnotherModelFixture) pass class ModelFixtureWithGetters(dbcore.Model): @classmethod def _getters(cls): return {"aComputedField": (lambda s: "thing")} def _template_funcs(self): return {} @_common.slow_test() class MigrationTest(unittest.TestCase): """Tests the ability to change the database schema between versions. """ @classmethod def setUpClass(cls): handle, cls.orig_libfile = mkstemp("orig_db") os.close(handle) # Set up a database with the two-field schema. old_lib = DatabaseFixture2(cls.orig_libfile) # Add an item to the old library. old_lib._connection().execute( "insert into test (field_one, field_two) values (4, 2)" ) old_lib._connection().commit() old_lib._connection().close() del old_lib @classmethod def tearDownClass(cls): os.remove(cls.orig_libfile) def setUp(self): handle, self.libfile = mkstemp("db") os.close(handle) shutil.copyfile(self.orig_libfile, self.libfile) def tearDown(self): os.remove(self.libfile) def test_open_with_same_fields_leaves_untouched(self): new_lib = DatabaseFixture2(self.libfile) c = new_lib._connection().cursor() c.execute("select * from test") row = c.fetchone() c.connection.close() assert len(row.keys()) == len(ModelFixture2._fields) def test_open_with_new_field_adds_column(self): new_lib = DatabaseFixture3(self.libfile) c = new_lib._connection().cursor() c.execute("select * from test") row = c.fetchone() c.connection.close() assert len(row.keys()) == len(ModelFixture3._fields) def test_open_with_fewer_fields_leaves_untouched(self): new_lib = DatabaseFixture1(self.libfile) c = new_lib._connection().cursor() c.execute("select * from test") row = c.fetchone() c.connection.close() assert len(row.keys()) == len(ModelFixture2._fields) def test_open_with_multiple_new_fields(self): new_lib = DatabaseFixture4(self.libfile) c = new_lib._connection().cursor() c.execute("select * from test") row = c.fetchone() c.connection.close() assert len(row.keys()) == len(ModelFixture4._fields) def test_extra_model_adds_table(self): new_lib = DatabaseFixtureTwoModels(self.libfile) try: c = new_lib._connection() c.execute("select * from another") c.close() except sqlite3.OperationalError: self.fail("select failed") class TransactionTest(unittest.TestCase): def setUp(self): self.db = DatabaseFixture1(":memory:") def tearDown(self): self.db._connection().close() def test_mutate_increase_revision(self): old_rev = self.db.revision with self.db.transaction() as tx: tx.mutate( f"INSERT INTO {ModelFixture1._table} (field_one) VALUES (?);", (111,), ) assert self.db.revision > old_rev def test_query_no_increase_revision(self): old_rev = self.db.revision with self.db.transaction() as tx: tx.query("PRAGMA table_info(%s)" % ModelFixture1._table) assert self.db.revision == old_rev class ModelTest(unittest.TestCase): def setUp(self): self.db = DatabaseFixture1(":memory:") def tearDown(self): self.db._connection().close() def test_add_model(self): model = ModelFixture1() model.add(self.db) rows = self.db._connection().execute("select * from test").fetchall() assert len(rows) == 1 def test_store_fixed_field(self): model = ModelFixture1() model.add(self.db) model.field_one = 123 model.store() row = self.db._connection().execute("select * from test").fetchone() assert row["field_one"] == 123 def test_revision(self): old_rev = self.db.revision model = ModelFixture1() model.add(self.db) model.store() assert model._revision == self.db.revision assert self.db.revision > old_rev mid_rev = self.db.revision model2 = ModelFixture1() model2.add(self.db) model2.store() assert model2._revision > mid_rev assert self.db.revision > model._revision # revision changed, so the model should be re-loaded model.load() assert model._revision == self.db.revision # revision did not change, so no reload mod2_old_rev = model2._revision model2.load() assert model2._revision == mod2_old_rev def test_retrieve_by_id(self): model = ModelFixture1() model.add(self.db) other_model = self.db._get(ModelFixture1, model.id) assert model.id == other_model.id def test_store_and_retrieve_flexattr(self): model = ModelFixture1() model.add(self.db) model.foo = "bar" model.store() other_model = self.db._get(ModelFixture1, model.id) assert other_model.foo == "bar" def test_delete_flexattr(self): model = ModelFixture1() model["foo"] = "bar" assert "foo" in model del model["foo"] assert "foo" not in model def test_delete_flexattr_via_dot(self): model = ModelFixture1() model["foo"] = "bar" assert "foo" in model del model.foo assert "foo" not in model def test_delete_flexattr_persists(self): model = ModelFixture1() model.add(self.db) model.foo = "bar" model.store() model = self.db._get(ModelFixture1, model.id) del model["foo"] model.store() model = self.db._get(ModelFixture1, model.id) assert "foo" not in model def test_delete_non_existent_attribute(self): model = ModelFixture1() with pytest.raises(KeyError): del model["foo"] def test_delete_fixed_attribute(self): model = ModelFixture5() model.some_string_field = "foo" model.some_float_field = 1.23 model.some_boolean_field = True for field, type_ in model._fields.items(): assert model[field] != type_.null for field, type_ in model._fields.items(): del model[field] assert model[field] == type_.null def test_null_value_normalization_by_type(self): model = ModelFixture1() model.field_one = None assert model.field_one == 0 def test_null_value_stays_none_for_untyped_field(self): model = ModelFixture1() model.foo = None assert model.foo is None def test_normalization_for_typed_flex_fields(self): model = ModelFixture1() model.some_float_field = None assert model.some_float_field == 0.0 def test_load_deleted_flex_field(self): model1 = ModelFixture1() model1["flex_field"] = True model1.add(self.db) model2 = self.db._get(ModelFixture1, model1.id) assert "flex_field" in model2 del model1["flex_field"] model1.store() model2.load() assert "flex_field" not in model2 def test_check_db_fails(self): with pytest.raises(ValueError, match="no database"): dbcore.Model()._check_db() with pytest.raises(ValueError, match="no id"): ModelFixture1(self.db)._check_db() dbcore.Model(self.db)._check_db(need_id=False) def test_missing_field(self): with pytest.raises(AttributeError): ModelFixture1(self.db).nonExistingKey def test_computed_field(self): model = ModelFixtureWithGetters() assert model.aComputedField == "thing" with pytest.raises(KeyError, match="computed field .+ deleted"): del model.aComputedField def test_items(self): model = ModelFixture1(self.db) model.id = 5 assert {("id", 5), ("field_one", 0), ("field_two", "")} == set( model.items() ) def test_delete_internal_field(self): model = dbcore.Model() del model._db with pytest.raises(AttributeError): model._db def test_parse_nonstring(self): with pytest.raises(TypeError, match="must be a string"): dbcore.Model._parse(None, 42) class FormatTest(unittest.TestCase): def test_format_fixed_field_integer(self): model = ModelFixture1() model.field_one = 155 value = model.formatted().get("field_one") assert value == "155" def test_format_fixed_field_integer_normalized(self): """The normalize method of the Integer class rounds floats""" model = ModelFixture1() model.field_one = 142.432 value = model.formatted().get("field_one") assert value == "142" model.field_one = 142.863 value = model.formatted().get("field_one") assert value == "143" def test_format_fixed_field_string(self): model = ModelFixture1() model.field_two = "caf\xe9" value = model.formatted().get("field_two") assert value == "caf\xe9" def test_format_flex_field(self): model = ModelFixture1() model.other_field = "caf\xe9" value = model.formatted().get("other_field") assert value == "caf\xe9" def test_format_flex_field_bytes(self): model = ModelFixture1() model.other_field = "caf\xe9".encode() value = model.formatted().get("other_field") assert isinstance(value, str) assert value == "caf\xe9" def test_format_unset_field(self): model = ModelFixture1() value = model.formatted().get("other_field") assert value == "" def test_format_typed_flex_field(self): model = ModelFixture1() model.some_float_field = 3.14159265358979 value = model.formatted().get("some_float_field") assert value == "3.1" class FormattedMappingTest(unittest.TestCase): def test_keys_equal_model_keys(self): model = ModelFixture1() formatted = model.formatted() assert set(model.keys(True)) == set(formatted.keys()) def test_get_unset_field(self): model = ModelFixture1() formatted = model.formatted() with pytest.raises(KeyError): formatted["other_field"] def test_get_method_with_default(self): model = ModelFixture1() formatted = model.formatted() assert formatted.get("other_field") == "" def test_get_method_with_specified_default(self): model = ModelFixture1() formatted = model.formatted() assert formatted.get("other_field", "default") == "default" class ParseTest(unittest.TestCase): def test_parse_fixed_field(self): value = ModelFixture1._parse("field_one", "2") assert isinstance(value, int) assert value == 2 def test_parse_flex_field(self): value = ModelFixture1._parse("some_float_field", "2") assert isinstance(value, float) assert value == 2.0 def test_parse_untyped_field(self): value = ModelFixture1._parse("field_nine", "2") assert value == "2" class QueryParseTest(unittest.TestCase): def pqp(self, part): return dbcore.queryparse.parse_query_part( part, {"year": dbcore.query.NumericQuery}, {":": dbcore.query.RegexpQuery}, )[:-1] # remove the negate flag def test_one_basic_term(self): q = "test" r = (None, "test", dbcore.query.SubstringQuery) assert self.pqp(q) == r def test_one_keyed_term(self): q = "test:val" r = ("test", "val", dbcore.query.SubstringQuery) assert self.pqp(q) == r def test_colon_at_end(self): q = "test:" r = ("test", "", dbcore.query.SubstringQuery) assert self.pqp(q) == r def test_one_basic_regexp(self): q = r":regexp" r = (None, "regexp", dbcore.query.RegexpQuery) assert self.pqp(q) == r def test_keyed_regexp(self): q = r"test::regexp" r = ("test", "regexp", dbcore.query.RegexpQuery) assert self.pqp(q) == r def test_escaped_colon(self): q = r"test\:val" r = (None, "test:val", dbcore.query.SubstringQuery) assert self.pqp(q) == r def test_escaped_colon_in_regexp(self): q = r":test\:regexp" r = (None, "test:regexp", dbcore.query.RegexpQuery) assert self.pqp(q) == r def test_single_year(self): q = "year:1999" r = ("year", "1999", dbcore.query.NumericQuery) assert self.pqp(q) == r def test_multiple_years(self): q = "year:1999..2010" r = ("year", "1999..2010", dbcore.query.NumericQuery) assert self.pqp(q) == r def test_empty_query_part(self): q = "" r = (None, "", dbcore.query.SubstringQuery) assert self.pqp(q) == r class QueryFromStringsTest(unittest.TestCase): def qfs(self, strings): return dbcore.queryparse.query_from_strings( dbcore.query.AndQuery, ModelFixture1, {":": dbcore.query.RegexpQuery}, strings, ) def test_zero_parts(self): q = self.qfs([]) assert isinstance(q, dbcore.query.AndQuery) assert len(q.subqueries) == 1 assert isinstance(q.subqueries[0], dbcore.query.TrueQuery) def test_two_parts(self): q = self.qfs(["foo", "bar:baz"]) assert isinstance(q, dbcore.query.AndQuery) assert len(q.subqueries) == 2 assert isinstance(q.subqueries[0], dbcore.query.AnyFieldQuery) assert isinstance(q.subqueries[1], dbcore.query.SubstringQuery) def test_parse_fixed_type_query(self): q = self.qfs(["field_one:2..3"]) assert isinstance(q.subqueries[0], dbcore.query.NumericQuery) def test_parse_flex_type_query(self): q = self.qfs(["some_float_field:2..3"]) assert isinstance(q.subqueries[0], dbcore.query.NumericQuery) def test_empty_query_part(self): q = self.qfs([""]) assert isinstance(q.subqueries[0], dbcore.query.TrueQuery) class SortFromStringsTest(unittest.TestCase): def sfs(self, strings): return dbcore.queryparse.sort_from_strings( ModelFixture1, strings, ) def test_zero_parts(self): s = self.sfs([]) assert isinstance(s, dbcore.query.NullSort) assert s == dbcore.query.NullSort() def test_one_parts(self): s = self.sfs(["field+"]) assert isinstance(s, dbcore.query.Sort) def test_two_parts(self): s = self.sfs(["field+", "another_field-"]) assert isinstance(s, dbcore.query.MultipleSort) assert len(s.sorts) == 2 def test_fixed_field_sort(self): s = self.sfs(["field_one+"]) assert isinstance(s, dbcore.query.FixedFieldSort) assert s == dbcore.query.FixedFieldSort("field_one") def test_flex_field_sort(self): s = self.sfs(["flex_field+"]) assert isinstance(s, dbcore.query.SlowFieldSort) assert s == dbcore.query.SlowFieldSort("flex_field") def test_special_sort(self): s = self.sfs(["some_sort+"]) assert isinstance(s, SortFixture) class ParseSortedQueryTest(unittest.TestCase): def psq(self, parts): return dbcore.parse_sorted_query( ModelFixture1, parts.split(), ) def test_and_query(self): q, s = self.psq("foo bar") assert isinstance(q, dbcore.query.AndQuery) assert isinstance(s, dbcore.query.NullSort) assert len(q.subqueries) == 2 def test_or_query(self): q, s = self.psq("foo , bar") assert isinstance(q, dbcore.query.OrQuery) assert isinstance(s, dbcore.query.NullSort) assert len(q.subqueries) == 2 def test_no_space_before_comma_or_query(self): q, s = self.psq("foo, bar") assert isinstance(q, dbcore.query.OrQuery) assert isinstance(s, dbcore.query.NullSort) assert len(q.subqueries) == 2 def test_no_spaces_or_query(self): q, s = self.psq("foo,bar") assert isinstance(q, dbcore.query.AndQuery) assert isinstance(s, dbcore.query.NullSort) assert len(q.subqueries) == 1 def test_trailing_comma_or_query(self): q, s = self.psq("foo , bar ,") assert isinstance(q, dbcore.query.OrQuery) assert isinstance(s, dbcore.query.NullSort) assert len(q.subqueries) == 3 def test_leading_comma_or_query(self): q, s = self.psq(", foo , bar") assert isinstance(q, dbcore.query.OrQuery) assert isinstance(s, dbcore.query.NullSort) assert len(q.subqueries) == 3 def test_only_direction(self): q, s = self.psq("-") assert isinstance(q, dbcore.query.AndQuery) assert isinstance(s, dbcore.query.NullSort) assert len(q.subqueries) == 1 class ResultsIteratorTest(unittest.TestCase): def setUp(self): self.db = DatabaseFixture1(":memory:") model = ModelFixture1() model["foo"] = "baz" model.add(self.db) model = ModelFixture1() model["foo"] = "bar" model.add(self.db) def tearDown(self): self.db._connection().close() def test_iterate_once(self): objs = self.db._fetch(ModelFixture1) assert len(list(objs)) == 2 def test_iterate_twice(self): objs = self.db._fetch(ModelFixture1) list(objs) assert len(list(objs)) == 2 def test_concurrent_iterators(self): results = self.db._fetch(ModelFixture1) it1 = iter(results) it2 = iter(results) next(it1) list(it2) assert len(list(it1)) == 1 def test_slow_query(self): q = dbcore.query.SubstringQuery("foo", "ba", False) objs = self.db._fetch(ModelFixture1, q) assert len(list(objs)) == 2 def test_slow_query_negative(self): q = dbcore.query.SubstringQuery("foo", "qux", False) objs = self.db._fetch(ModelFixture1, q) assert len(list(objs)) == 0 def test_iterate_slow_sort(self): s = dbcore.query.SlowFieldSort("foo") res = self.db._fetch(ModelFixture1, sort=s) objs = list(res) assert objs[0].foo == "bar" assert objs[1].foo == "baz" def test_unsorted_subscript(self): objs = self.db._fetch(ModelFixture1) assert objs[0].foo == "baz" assert objs[1].foo == "bar" def test_slow_sort_subscript(self): s = dbcore.query.SlowFieldSort("foo") objs = self.db._fetch(ModelFixture1, sort=s) assert objs[0].foo == "bar" assert objs[1].foo == "baz" def test_length(self): objs = self.db._fetch(ModelFixture1) assert len(objs) == 2 def test_out_of_range(self): objs = self.db._fetch(ModelFixture1) with pytest.raises(IndexError): objs[100] def test_no_results(self): assert ( self.db._fetch(ModelFixture1, dbcore.query.FalseQuery()).get() is None ) beetbox-beets-01f1faf/test/test_files.py000066400000000000000000000531161472325477400204470ustar00rootroot00000000000000# This file is part of beets. # Copyright 2016, Adrian Sampson. # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the # "Software"), to deal in the Software without restriction, including # without limitation the rights to use, copy, modify, merge, publish, # distribute, sublicense, and/or sell copies of the Software, and to # permit persons to whom the Software is furnished to do so, subject to # the following conditions: # # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. """Test file manipulation functionality of Item.""" import os import shutil import stat import unittest from os.path import join import pytest import beets.library from beets import util from beets.test import _common from beets.test._common import item, touch from beets.test.helper import NEEDS_REFLINK, BeetsTestCase from beets.util import MoveOperation, bytestring_path, syspath class MoveTest(BeetsTestCase): def setUp(self): super().setUp() # make a temporary file self.path = join(self.temp_dir, b"temp.mp3") shutil.copy( syspath(join(_common.RSRC, b"full.mp3")), syspath(self.path), ) # add it to a temporary library self.i = beets.library.Item.from_path(self.path) self.lib.add(self.i) # set up the destination self.lib.path_formats = [ ("default", join("$artist", "$album", "$title")) ] self.i.artist = "one" self.i.album = "two" self.i.title = "three" self.dest = join(self.libdir, b"one", b"two", b"three.mp3") self.otherdir = join(self.temp_dir, b"testotherdir") def test_move_arrives(self): self.i.move() self.assertExists(self.dest) def test_move_to_custom_dir(self): self.i.move(basedir=self.otherdir) self.assertExists(join(self.otherdir, b"one", b"two", b"three.mp3")) def test_move_departs(self): self.i.move() self.assertNotExists(self.path) def test_move_in_lib_prunes_empty_dir(self): self.i.move() old_path = self.i.path self.assertExists(old_path) self.i.artist = "newArtist" self.i.move() self.assertNotExists(old_path) self.assertNotExists(os.path.dirname(old_path)) def test_copy_arrives(self): self.i.move(operation=MoveOperation.COPY) self.assertExists(self.dest) def test_copy_does_not_depart(self): self.i.move(operation=MoveOperation.COPY) self.assertExists(self.path) def test_reflink_arrives(self): self.i.move(operation=MoveOperation.REFLINK_AUTO) self.assertExists(self.dest) def test_reflink_does_not_depart(self): self.i.move(operation=MoveOperation.REFLINK_AUTO) self.assertExists(self.path) @NEEDS_REFLINK def test_force_reflink_arrives(self): self.i.move(operation=MoveOperation.REFLINK) self.assertExists(self.dest) @NEEDS_REFLINK def test_force_reflink_does_not_depart(self): self.i.move(operation=MoveOperation.REFLINK) self.assertExists(self.path) def test_move_changes_path(self): self.i.move() assert self.i.path == util.normpath(self.dest) def test_copy_already_at_destination(self): self.i.move() old_path = self.i.path self.i.move(operation=MoveOperation.COPY) assert self.i.path == old_path def test_move_already_at_destination(self): self.i.move() old_path = self.i.path self.i.move() assert self.i.path == old_path def test_move_file_with_colon(self): self.i.artist = "C:DOS" self.i.move() assert "C_DOS" in self.i.path.decode() def test_move_file_with_multiple_colons(self): # print(beets.config["replace"]) self.i.artist = "COM:DOS" self.i.move() assert "COM_DOS" in self.i.path.decode() def test_move_file_with_colon_alt_separator(self): old = beets.config["drive_sep_replace"] beets.config["drive_sep_replace"] = "0" self.i.artist = "C:DOS" self.i.move() assert "C0DOS" in self.i.path.decode() beets.config["drive_sep_replace"] = old def test_read_only_file_copied_writable(self): # Make the source file read-only. os.chmod(syspath(self.path), 0o444) try: self.i.move(operation=MoveOperation.COPY) assert os.access(syspath(self.i.path), os.W_OK) finally: # Make everything writable so it can be cleaned up. os.chmod(syspath(self.path), 0o777) os.chmod(syspath(self.i.path), 0o777) def test_move_avoids_collision_with_existing_file(self): # Make a conflicting file at the destination. dest = self.i.destination() os.makedirs(syspath(os.path.dirname(dest))) touch(dest) self.i.move() assert self.i.path != dest assert os.path.dirname(self.i.path) == os.path.dirname(dest) @unittest.skipUnless(_common.HAVE_SYMLINK, "need symlinks") def test_link_arrives(self): self.i.move(operation=MoveOperation.LINK) self.assertExists(self.dest) assert os.path.islink(syspath(self.dest)) assert bytestring_path(os.readlink(syspath(self.dest))) == self.path @unittest.skipUnless(_common.HAVE_SYMLINK, "need symlinks") def test_link_does_not_depart(self): self.i.move(operation=MoveOperation.LINK) self.assertExists(self.path) @unittest.skipUnless(_common.HAVE_SYMLINK, "need symlinks") def test_link_changes_path(self): self.i.move(operation=MoveOperation.LINK) assert self.i.path == util.normpath(self.dest) @unittest.skipUnless(_common.HAVE_HARDLINK, "need hardlinks") def test_hardlink_arrives(self): self.i.move(operation=MoveOperation.HARDLINK) self.assertExists(self.dest) s1 = os.stat(syspath(self.path)) s2 = os.stat(syspath(self.dest)) assert (s1[stat.ST_INO], s1[stat.ST_DEV]) == ( s2[stat.ST_INO], s2[stat.ST_DEV], ) @unittest.skipUnless(_common.HAVE_HARDLINK, "need hardlinks") def test_hardlink_does_not_depart(self): self.i.move(operation=MoveOperation.HARDLINK) self.assertExists(self.path) @unittest.skipUnless(_common.HAVE_HARDLINK, "need hardlinks") def test_hardlink_changes_path(self): self.i.move(operation=MoveOperation.HARDLINK) assert self.i.path == util.normpath(self.dest) class HelperTest(BeetsTestCase): def test_ancestry_works_on_file(self): p = "/a/b/c" a = ["/", "/a", "/a/b"] assert util.ancestry(p) == a def test_ancestry_works_on_dir(self): p = "/a/b/c/" a = ["/", "/a", "/a/b", "/a/b/c"] assert util.ancestry(p) == a def test_ancestry_works_on_relative(self): p = "a/b/c" a = ["a", "a/b"] assert util.ancestry(p) == a def test_components_works_on_file(self): p = "/a/b/c" a = ["/", "a", "b", "c"] assert util.components(p) == a def test_components_works_on_dir(self): p = "/a/b/c/" a = ["/", "a", "b", "c"] assert util.components(p) == a def test_components_works_on_relative(self): p = "a/b/c" a = ["a", "b", "c"] assert util.components(p) == a def test_forward_slash(self): p = rb"C:\a\b\c" a = rb"C:/a/b/c" assert util.path_as_posix(p) == a class AlbumFileTest(BeetsTestCase): def setUp(self): super().setUp() # Make library and item. self.lib.path_formats = [ ("default", join("$albumartist", "$album", "$title")) ] self.i = item(self.lib) # Make a file for the item. self.i.path = self.i.destination() util.mkdirall(self.i.path) touch(self.i.path) # Make an album. self.ai = self.lib.add_album((self.i,)) # Alternate destination dir. self.otherdir = os.path.join(self.temp_dir, b"testotherdir") def test_albuminfo_move_changes_paths(self): self.ai.album = "newAlbumName" self.ai.move() self.ai.store() self.i.load() assert b"newAlbumName" in self.i.path def test_albuminfo_move_moves_file(self): oldpath = self.i.path self.ai.album = "newAlbumName" self.ai.move() self.ai.store() self.i.load() self.assertNotExists(oldpath) self.assertExists(self.i.path) def test_albuminfo_move_copies_file(self): oldpath = self.i.path self.ai.album = "newAlbumName" self.ai.move(operation=MoveOperation.COPY) self.ai.store() self.i.load() self.assertExists(oldpath) self.assertExists(self.i.path) @NEEDS_REFLINK def test_albuminfo_move_reflinks_file(self): oldpath = self.i.path self.ai.album = "newAlbumName" self.ai.move(operation=MoveOperation.REFLINK) self.ai.store() self.i.load() assert os.path.exists(oldpath) assert os.path.exists(self.i.path) def test_albuminfo_move_to_custom_dir(self): self.ai.move(basedir=self.otherdir) self.i.load() self.ai.store() assert b"testotherdir" in self.i.path class ArtFileTest(BeetsTestCase): def setUp(self): super().setUp() # Make library and item. self.i = item(self.lib) self.i.path = self.i.destination() # Make a music file. util.mkdirall(self.i.path) touch(self.i.path) # Make an album. self.ai = self.lib.add_album((self.i,)) # Make an art file too. self.art = self.lib.get_album(self.i).art_destination("something.jpg") touch(self.art) self.ai.artpath = self.art self.ai.store() # Alternate destination dir. self.otherdir = os.path.join(self.temp_dir, b"testotherdir") def test_art_deleted_when_items_deleted(self): self.assertExists(self.art) self.ai.remove(True) self.assertNotExists(self.art) def test_art_moves_with_album(self): self.assertExists(self.art) oldpath = self.i.path self.ai.album = "newAlbum" self.ai.move() self.i.load() assert self.i.path != oldpath self.assertNotExists(self.art) newart = self.lib.get_album(self.i).art_destination(self.art) self.assertExists(newart) def test_art_moves_with_album_to_custom_dir(self): # Move the album to another directory. self.ai.move(basedir=self.otherdir) self.ai.store() self.i.load() # Art should be in new directory. self.assertNotExists(self.art) newart = self.lib.get_album(self.i).artpath self.assertExists(newart) assert b"testotherdir" in newart def test_setart_copies_image(self): util.remove(self.art) newart = os.path.join(self.libdir, b"newart.jpg") touch(newart) i2 = item() i2.path = self.i.path i2.artist = "someArtist" ai = self.lib.add_album((i2,)) i2.move(operation=MoveOperation.COPY) assert ai.artpath is None ai.set_art(newart) self.assertExists(ai.artpath) def test_setart_to_existing_art_works(self): util.remove(self.art) # Original art. newart = os.path.join(self.libdir, b"newart.jpg") touch(newart) i2 = item() i2.path = self.i.path i2.artist = "someArtist" ai = self.lib.add_album((i2,)) i2.move(operation=MoveOperation.COPY) ai.set_art(newart) # Set the art again. ai.set_art(ai.artpath) self.assertExists(ai.artpath) def test_setart_to_existing_but_unset_art_works(self): newart = os.path.join(self.libdir, b"newart.jpg") touch(newart) i2 = item() i2.path = self.i.path i2.artist = "someArtist" ai = self.lib.add_album((i2,)) i2.move(operation=MoveOperation.COPY) # Copy the art to the destination. artdest = ai.art_destination(newart) shutil.copy(syspath(newart), syspath(artdest)) # Set the art again. ai.set_art(artdest) self.assertExists(ai.artpath) def test_setart_to_conflicting_file_gets_new_path(self): newart = os.path.join(self.libdir, b"newart.jpg") touch(newart) i2 = item() i2.path = self.i.path i2.artist = "someArtist" ai = self.lib.add_album((i2,)) i2.move(operation=MoveOperation.COPY) # Make a file at the destination. artdest = ai.art_destination(newart) touch(artdest) # Set the art. ai.set_art(newart) assert artdest != ai.artpath assert os.path.dirname(artdest) == os.path.dirname(ai.artpath) def test_setart_sets_permissions(self): util.remove(self.art) newart = os.path.join(self.libdir, b"newart.jpg") touch(newart) os.chmod(syspath(newart), 0o400) # read-only try: i2 = item() i2.path = self.i.path i2.artist = "someArtist" ai = self.lib.add_album((i2,)) i2.move(operation=MoveOperation.COPY) ai.set_art(newart) mode = stat.S_IMODE(os.stat(syspath(ai.artpath)).st_mode) assert mode & stat.S_IRGRP assert os.access(syspath(ai.artpath), os.W_OK) finally: # Make everything writable so it can be cleaned up. os.chmod(syspath(newart), 0o777) os.chmod(syspath(ai.artpath), 0o777) def test_move_last_file_moves_albumart(self): oldartpath = self.lib.albums()[0].artpath self.assertExists(oldartpath) self.ai.album = "different_album" self.ai.store() self.ai.items()[0].move() artpath = self.lib.albums()[0].artpath assert b"different_album" in artpath self.assertExists(artpath) self.assertNotExists(oldartpath) def test_move_not_last_file_does_not_move_albumart(self): i2 = item() i2.albumid = self.ai.id self.lib.add(i2) oldartpath = self.lib.albums()[0].artpath self.assertExists(oldartpath) self.i.album = "different_album" self.i.album_id = None # detach from album self.i.move() artpath = self.lib.albums()[0].artpath assert b"different_album" not in artpath assert artpath == oldartpath self.assertExists(oldartpath) class RemoveTest(BeetsTestCase): def setUp(self): super().setUp() # Make library and item. self.i = item(self.lib) self.i.path = self.i.destination() # Make a music file. util.mkdirall(self.i.path) touch(self.i.path) # Make an album with the item. self.ai = self.lib.add_album((self.i,)) def test_removing_last_item_prunes_empty_dir(self): parent = os.path.dirname(self.i.path) self.assertExists(parent) self.i.remove(True) self.assertNotExists(parent) def test_removing_last_item_preserves_nonempty_dir(self): parent = os.path.dirname(self.i.path) touch(os.path.join(parent, b"dummy.txt")) self.i.remove(True) self.assertExists(parent) def test_removing_last_item_prunes_dir_with_blacklisted_file(self): parent = os.path.dirname(self.i.path) touch(os.path.join(parent, b".DS_Store")) self.i.remove(True) self.assertNotExists(parent) def test_removing_without_delete_leaves_file(self): path = self.i.path self.i.remove(False) self.assertExists(path) def test_removing_last_item_preserves_library_dir(self): self.i.remove(True) self.assertExists(self.libdir) def test_removing_item_outside_of_library_deletes_nothing(self): self.lib.directory = os.path.join(self.temp_dir, b"xxx") parent = os.path.dirname(self.i.path) self.i.remove(True) self.assertExists(parent) def test_removing_last_item_in_album_with_albumart_prunes_dir(self): artfile = os.path.join(self.temp_dir, b"testart.jpg") touch(artfile) self.ai.set_art(artfile) self.ai.store() parent = os.path.dirname(self.i.path) self.i.remove(True) self.assertNotExists(parent) # Tests that we can "delete" nonexistent files. class SoftRemoveTest(BeetsTestCase): def setUp(self): super().setUp() self.path = os.path.join(self.temp_dir, b"testfile") touch(self.path) def test_soft_remove_deletes_file(self): util.remove(self.path, True) self.assertNotExists(self.path) def test_soft_remove_silent_on_no_file(self): try: util.remove(self.path + b"XXX", True) except OSError: self.fail("OSError when removing path") class SafeMoveCopyTest(BeetsTestCase): def setUp(self): super().setUp() self.path = os.path.join(self.temp_dir, b"testfile") touch(self.path) self.otherpath = os.path.join(self.temp_dir, b"testfile2") touch(self.otherpath) self.dest = self.path + b".dest" def test_successful_move(self): util.move(self.path, self.dest) self.assertExists(self.dest) self.assertNotExists(self.path) def test_successful_copy(self): util.copy(self.path, self.dest) self.assertExists(self.dest) self.assertExists(self.path) @NEEDS_REFLINK def test_successful_reflink(self): util.reflink(self.path, self.dest) self.assertExists(self.dest) self.assertExists(self.path) def test_unsuccessful_move(self): with pytest.raises(util.FilesystemError): util.move(self.path, self.otherpath) def test_unsuccessful_copy(self): with pytest.raises(util.FilesystemError): util.copy(self.path, self.otherpath) def test_unsuccessful_reflink(self): with pytest.raises(util.FilesystemError, match="target exists"): util.reflink(self.path, self.otherpath) def test_self_move(self): util.move(self.path, self.path) self.assertExists(self.path) def test_self_copy(self): util.copy(self.path, self.path) self.assertExists(self.path) class PruneTest(BeetsTestCase): def setUp(self): super().setUp() self.base = os.path.join(self.temp_dir, b"testdir") os.mkdir(syspath(self.base)) self.sub = os.path.join(self.base, b"subdir") os.mkdir(syspath(self.sub)) def test_prune_existent_directory(self): util.prune_dirs(self.sub, self.base) self.assertExists(self.base) self.assertNotExists(self.sub) def test_prune_nonexistent_directory(self): util.prune_dirs(os.path.join(self.sub, b"another"), self.base) self.assertExists(self.base) self.assertNotExists(self.sub) class WalkTest(BeetsTestCase): def setUp(self): super().setUp() self.base = os.path.join(self.temp_dir, b"testdir") os.mkdir(syspath(self.base)) touch(os.path.join(self.base, b"y")) touch(os.path.join(self.base, b"x")) os.mkdir(syspath(os.path.join(self.base, b"d"))) touch(os.path.join(self.base, b"d", b"z")) def test_sorted_files(self): res = list(util.sorted_walk(self.base)) assert len(res) == 2 assert res[0] == (self.base, [b"d"], [b"x", b"y"]) assert res[1] == (os.path.join(self.base, b"d"), [], [b"z"]) def test_ignore_file(self): res = list(util.sorted_walk(self.base, (b"x",))) assert len(res) == 2 assert res[0] == (self.base, [b"d"], [b"y"]) assert res[1] == (os.path.join(self.base, b"d"), [], [b"z"]) def test_ignore_directory(self): res = list(util.sorted_walk(self.base, (b"d",))) assert len(res) == 1 assert res[0] == (self.base, [], [b"x", b"y"]) def test_ignore_everything(self): res = list(util.sorted_walk(self.base, (b"*",))) assert len(res) == 1 assert res[0] == (self.base, [], []) class UniquePathTest(BeetsTestCase): def setUp(self): super().setUp() self.base = os.path.join(self.temp_dir, b"testdir") os.mkdir(syspath(self.base)) touch(os.path.join(self.base, b"x.mp3")) touch(os.path.join(self.base, b"x.1.mp3")) touch(os.path.join(self.base, b"x.2.mp3")) touch(os.path.join(self.base, b"y.mp3")) def test_new_file_unchanged(self): path = util.unique_path(os.path.join(self.base, b"z.mp3")) assert path == os.path.join(self.base, b"z.mp3") def test_conflicting_file_appends_1(self): path = util.unique_path(os.path.join(self.base, b"y.mp3")) assert path == os.path.join(self.base, b"y.1.mp3") def test_conflicting_file_appends_higher_number(self): path = util.unique_path(os.path.join(self.base, b"x.mp3")) assert path == os.path.join(self.base, b"x.3.mp3") def test_conflicting_file_with_number_increases_number(self): path = util.unique_path(os.path.join(self.base, b"x.1.mp3")) assert path == os.path.join(self.base, b"x.3.mp3") class MkDirAllTest(BeetsTestCase): def test_parent_exists(self): path = os.path.join(self.temp_dir, b"foo", b"bar", b"baz", b"qux.mp3") util.mkdirall(path) self.assertIsDir(os.path.join(self.temp_dir, b"foo", b"bar", b"baz")) def test_child_does_not_exist(self): path = os.path.join(self.temp_dir, b"foo", b"bar", b"baz", b"qux.mp3") util.mkdirall(path) self.assertNotExists(path) beetbox-beets-01f1faf/test/test_hidden.py000066400000000000000000000045141472325477400205760ustar00rootroot00000000000000# This file is part of beets. # Copyright 2016, Fabrice Laporte. # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the # "Software"), to deal in the Software without restriction, including # without limitation the rights to use, copy, modify, merge, publish, # distribute, sublicense, and/or sell copies of the Software, and to # permit persons to whom the Software is furnished to do so, subject to # the following conditions: # # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. """Tests for the 'hidden' utility.""" import ctypes import errno import subprocess import sys import tempfile import unittest from beets import util from beets.util import hidden class HiddenFileTest(unittest.TestCase): def setUp(self): pass def test_osx_hidden(self): if not sys.platform == "darwin": self.skipTest("sys.platform is not darwin") return with tempfile.NamedTemporaryFile(delete=False) as f: try: command = ["chflags", "hidden", f.name] subprocess.Popen(command).wait() except OSError as e: if e.errno == errno.ENOENT: self.skipTest("unable to find chflags") else: raise e assert hidden.is_hidden(f.name) def test_windows_hidden(self): if not sys.platform == "win32": self.skipTest("sys.platform is not windows") return # FILE_ATTRIBUTE_HIDDEN = 2 (0x2) from GetFileAttributes documentation. hidden_mask = 2 with tempfile.NamedTemporaryFile() as f: # Hide the file using success = ctypes.windll.kernel32.SetFileAttributesW( f.name, hidden_mask ) if not success: self.skipTest("unable to set file attributes") assert hidden.is_hidden(f.name) def test_other_hidden(self): if sys.platform == "darwin" or sys.platform == "win32": self.skipTest("sys.platform is known") return with tempfile.NamedTemporaryFile(prefix=".tmp") as f: fn = util.bytestring_path(f.name) assert hidden.is_hidden(fn) beetbox-beets-01f1faf/test/test_importer.py000066400000000000000000002010151472325477400211770ustar00rootroot00000000000000# This file is part of beets. # Copyright 2016, Adrian Sampson. # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the # "Software"), to deal in the Software without restriction, including # without limitation the rights to use, copy, modify, merge, publish, # distribute, sublicense, and/or sell copies of the Software, and to # permit persons to whom the Software is furnished to do so, subject to # the following conditions: # # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. """Tests for the general importer functionality.""" import os import re import shutil import stat import sys import unicodedata import unittest from io import StringIO from pathlib import Path from tarfile import TarFile from tempfile import mkstemp from unittest.mock import Mock, patch from zipfile import ZipFile import pytest from mediafile import MediaFile from beets import config, importer, logging, util from beets.autotag import AlbumInfo, AlbumMatch, TrackInfo from beets.importer import albums_in_dir from beets.test import _common from beets.test.helper import ( NEEDS_REFLINK, AsIsImporterMixin, AutotagStub, BeetsTestCase, ImportTestCase, PluginMixin, capture_log, has_program, ) from beets.util import bytestring_path, displayable_path, syspath class ScrubbedImportTest(AsIsImporterMixin, PluginMixin, ImportTestCase): db_on_disk = True plugin = "scrub" def test_tags_not_scrubbed(self): config["plugins"] = ["scrub"] config["scrub"]["auto"] = False config["import"]["write"] = True for mediafile in self.import_media: assert mediafile.artist == "Tag Artist" assert mediafile.album == "Tag Album" self.run_asis_importer() for item in self.lib.items(): imported_file = os.path.join(item.path) imported_file = MediaFile(imported_file) assert imported_file.artist == "Tag Artist" assert imported_file.album == "Tag Album" def test_tags_restored(self): config["plugins"] = ["scrub"] config["scrub"]["auto"] = True config["import"]["write"] = True for mediafile in self.import_media: assert mediafile.artist == "Tag Artist" assert mediafile.album == "Tag Album" self.run_asis_importer() for item in self.lib.items(): imported_file = os.path.join(item.path) imported_file = MediaFile(imported_file) assert imported_file.artist == "Tag Artist" assert imported_file.album == "Tag Album" def test_tags_not_restored(self): config["plugins"] = ["scrub"] config["scrub"]["auto"] = True config["import"]["write"] = False for mediafile in self.import_media: assert mediafile.artist == "Tag Artist" assert mediafile.album == "Tag Album" self.run_asis_importer() for item in self.lib.items(): imported_file = os.path.join(item.path) imported_file = MediaFile(imported_file) assert imported_file.artist is None assert imported_file.album is None @_common.slow_test() class NonAutotaggedImportTest(AsIsImporterMixin, ImportTestCase): db_on_disk = True def test_album_created_with_track_artist(self): self.run_asis_importer() albums = self.lib.albums() assert len(albums) == 1 assert albums[0].albumartist == "Tag Artist" def test_import_copy_arrives(self): self.run_asis_importer() for mediafile in self.import_media: self.assert_file_in_lib( b"Tag Artist", b"Tag Album", util.bytestring_path(f"{mediafile.title}.mp3"), ) def test_threaded_import_copy_arrives(self): config["threaded"] = True self.run_asis_importer() for mediafile in self.import_media: self.assert_file_in_lib( b"Tag Artist", b"Tag Album", util.bytestring_path(f"{mediafile.title}.mp3"), ) def test_import_with_move_deletes_import_files(self): for mediafile in self.import_media: self.assertExists(mediafile.path) self.run_asis_importer(move=True) for mediafile in self.import_media: self.assertNotExists(mediafile.path) def test_import_with_move_prunes_directory_empty(self): self.assertExists(os.path.join(self.import_dir, b"album")) self.run_asis_importer(move=True) self.assertNotExists(os.path.join(self.import_dir, b"album")) def test_import_with_move_prunes_with_extra_clutter(self): self.touch(os.path.join(self.import_dir, b"album", b"alog.log")) config["clutter"] = ["*.log"] self.assertExists(os.path.join(self.import_dir, b"album")) self.run_asis_importer(move=True) self.assertNotExists(os.path.join(self.import_dir, b"album")) def test_threaded_import_move_arrives(self): self.run_asis_importer(move=True, threaded=True) for mediafile in self.import_media: self.assert_file_in_lib( b"Tag Artist", b"Tag Album", util.bytestring_path(f"{mediafile.title}.mp3"), ) def test_threaded_import_move_deletes_import(self): self.run_asis_importer(move=True, threaded=True) for mediafile in self.import_media: self.assertNotExists(mediafile.path) def test_import_without_delete_retains_files(self): self.run_asis_importer(delete=False) for mediafile in self.import_media: self.assertExists(mediafile.path) def test_import_with_delete_removes_files(self): self.run_asis_importer(delete=True) for mediafile in self.import_media: self.assertNotExists(mediafile.path) def test_import_with_delete_prunes_directory_empty(self): self.assertExists(os.path.join(self.import_dir, b"album")) self.run_asis_importer(delete=True) self.assertNotExists(os.path.join(self.import_dir, b"album")) @unittest.skipUnless(_common.HAVE_SYMLINK, "need symlinks") def test_import_link_arrives(self): self.run_asis_importer(link=True) for mediafile in self.import_media: filename = os.path.join( self.libdir, b"Tag Artist", b"Tag Album", util.bytestring_path(f"{mediafile.title}.mp3"), ) self.assertExists(filename) assert os.path.islink(syspath(filename)) self.assert_equal_path( util.bytestring_path(os.readlink(syspath(filename))), mediafile.path, ) @unittest.skipUnless(_common.HAVE_HARDLINK, "need hardlinks") def test_import_hardlink_arrives(self): self.run_asis_importer(hardlink=True) for mediafile in self.import_media: filename = os.path.join( self.libdir, b"Tag Artist", b"Tag Album", util.bytestring_path(f"{mediafile.title}.mp3"), ) self.assertExists(filename) s1 = os.stat(syspath(mediafile.path)) s2 = os.stat(syspath(filename)) assert (s1[stat.ST_INO], s1[stat.ST_DEV]) == ( s2[stat.ST_INO], s2[stat.ST_DEV], ) @NEEDS_REFLINK def test_import_reflink_arrives(self): # Detecting reflinks is currently tricky due to various fs # implementations, we'll just check the file exists. self.run_asis_importer(reflink=True) for mediafile in self.import_media: self.assert_file_in_lib( b"Tag Artist", b"Tag Album", util.bytestring_path(f"{mediafile.title}.mp3"), ) def test_import_reflink_auto_arrives(self): # Should pass regardless of reflink support due to fallback. self.run_asis_importer(reflink="auto") for mediafile in self.import_media: self.assert_file_in_lib( b"Tag Artist", b"Tag Album", util.bytestring_path(f"{mediafile.title}.mp3"), ) def create_archive(session): (handle, path) = mkstemp(dir=os.fsdecode(session.temp_dir)) path = bytestring_path(path) os.close(handle) archive = ZipFile(os.fsdecode(path), mode="w") archive.write(syspath(os.path.join(_common.RSRC, b"full.mp3")), "full.mp3") archive.close() path = bytestring_path(path) return path class RmTempTest(BeetsTestCase): """Tests that temporarily extracted archives are properly removed after usage. """ def setUp(self): super().setUp() self.want_resume = False self.config["incremental"] = False self._old_home = None def test_rm(self): zip_path = create_archive(self) archive_task = importer.ArchiveImportTask(zip_path) archive_task.extract() tmp_path = archive_task.toppath self.assertExists(tmp_path) archive_task.finalize(self) self.assertNotExists(tmp_path) class ImportZipTest(AsIsImporterMixin, ImportTestCase): def test_import_zip(self): zip_path = create_archive(self) assert len(self.lib.items()) == 0 assert len(self.lib.albums()) == 0 self.run_asis_importer(import_dir=zip_path) assert len(self.lib.items()) == 1 assert len(self.lib.albums()) == 1 class ImportTarTest(ImportZipTest): def create_archive(self): (handle, path) = mkstemp(dir=syspath(self.temp_dir)) path = bytestring_path(path) os.close(handle) archive = TarFile(os.fsdecode(path), mode="w") archive.add( syspath(os.path.join(_common.RSRC, b"full.mp3")), "full.mp3" ) archive.close() return path @unittest.skipIf(not has_program("unrar"), "unrar program not found") class ImportRarTest(ImportZipTest): def create_archive(self): return os.path.join(_common.RSRC, b"archive.rar") class Import7zTest(ImportZipTest): def create_archive(self): return os.path.join(_common.RSRC, b"archive.7z") @unittest.skip("Implement me!") class ImportPasswordRarTest(ImportZipTest): def create_archive(self): return os.path.join(_common.RSRC, b"password.rar") class ImportSingletonTest(ImportTestCase): """Test ``APPLY`` and ``ASIS`` choices for an import session with singletons config set to True. """ def setUp(self): super().setUp() self.prepare_album_for_import(1) self.importer = self.setup_singleton_importer() self.matcher = AutotagStub().install() def tearDown(self): super().tearDown() self.matcher.restore() def test_apply_asis_adds_track(self): assert self.lib.items().get() is None self.importer.add_choice(importer.action.ASIS) self.importer.run() assert self.lib.items().get().title == "Tag Track 1" def test_apply_asis_does_not_add_album(self): assert self.lib.albums().get() is None self.importer.add_choice(importer.action.ASIS) self.importer.run() assert self.lib.albums().get() is None def test_apply_asis_adds_singleton_path(self): self.assert_lib_dir_empty() self.importer.add_choice(importer.action.ASIS) self.importer.run() self.assert_file_in_lib(b"singletons", b"Tag Track 1.mp3") def test_apply_candidate_adds_track(self): assert self.lib.items().get() is None self.importer.add_choice(importer.action.APPLY) self.importer.run() assert self.lib.items().get().title == "Applied Track 1" def test_apply_candidate_does_not_add_album(self): self.importer.add_choice(importer.action.APPLY) self.importer.run() assert self.lib.albums().get() is None def test_apply_candidate_adds_singleton_path(self): self.assert_lib_dir_empty() self.importer.add_choice(importer.action.APPLY) self.importer.run() self.assert_file_in_lib(b"singletons", b"Applied Track 1.mp3") def test_skip_does_not_add_first_track(self): self.importer.add_choice(importer.action.SKIP) self.importer.run() assert self.lib.items().get() is None def test_skip_adds_other_tracks(self): self.prepare_album_for_import(2) self.importer.add_choice(importer.action.SKIP) self.importer.add_choice(importer.action.ASIS) self.importer.run() assert len(self.lib.items()) == 1 def test_import_single_files(self): resource_path = os.path.join(_common.RSRC, b"empty.mp3") single_path = os.path.join(self.import_dir, b"track_2.mp3") util.copy(resource_path, single_path) import_files = [ os.path.join(self.import_dir, b"album"), single_path, ] self.setup_importer() self.importer.paths = import_files self.importer.add_choice(importer.action.ASIS) self.importer.add_choice(importer.action.ASIS) self.importer.run() assert len(self.lib.items()) == 2 assert len(self.lib.albums()) == 2 def test_set_fields(self): genre = "\U0001f3b7 Jazz" collection = "To Listen" disc = 0 config["import"]["set_fields"] = { "collection": collection, "genre": genre, "title": "$title - formatted", "disc": disc, } # As-is item import. assert self.lib.albums().get() is None self.importer.add_choice(importer.action.ASIS) self.importer.run() for item in self.lib.items(): item.load() # TODO: Not sure this is necessary. assert item.genre == genre assert item.collection == collection assert item.title == "Tag Track 1 - formatted" assert item.disc == disc # Remove item from library to test again with APPLY choice. item.remove() # Autotagged. assert self.lib.albums().get() is None self.importer.clear_choices() self.importer.add_choice(importer.action.APPLY) self.importer.run() for item in self.lib.items(): item.load() assert item.genre == genre assert item.collection == collection assert item.title == "Applied Track 1 - formatted" assert item.disc == disc class ImportTest(ImportTestCase): """Test APPLY, ASIS and SKIP choices.""" def setUp(self): super().setUp() self.prepare_album_for_import(1) self.setup_importer() self.matcher = AutotagStub().install() self.matcher.macthin = AutotagStub.GOOD def tearDown(self): super().tearDown() self.matcher.restore() def test_apply_asis_adds_album(self): assert self.lib.albums().get() is None self.importer.add_choice(importer.action.ASIS) self.importer.run() assert self.lib.albums().get().album == "Tag Album" def test_apply_asis_adds_tracks(self): assert self.lib.items().get() is None self.importer.add_choice(importer.action.ASIS) self.importer.run() assert self.lib.items().get().title == "Tag Track 1" def test_apply_asis_adds_album_path(self): self.assert_lib_dir_empty() self.importer.add_choice(importer.action.ASIS) self.importer.run() self.assert_file_in_lib(b"Tag Artist", b"Tag Album", b"Tag Track 1.mp3") def test_apply_candidate_adds_album(self): assert self.lib.albums().get() is None self.importer.add_choice(importer.action.APPLY) self.importer.run() assert self.lib.albums().get().album == "Applied Album" def test_apply_candidate_adds_tracks(self): assert self.lib.items().get() is None self.importer.add_choice(importer.action.APPLY) self.importer.run() assert self.lib.items().get().title == "Applied Track 1" def test_apply_candidate_adds_album_path(self): self.assert_lib_dir_empty() self.importer.add_choice(importer.action.APPLY) self.importer.run() self.assert_file_in_lib( b"Applied Artist", b"Applied Album", b"Applied Track 1.mp3" ) def test_apply_from_scratch_removes_other_metadata(self): config["import"]["from_scratch"] = True for mediafile in self.import_media: mediafile.genre = "Tag Genre" mediafile.save() self.importer.add_choice(importer.action.APPLY) self.importer.run() assert self.lib.items().get().genre == "" def test_apply_from_scratch_keeps_format(self): config["import"]["from_scratch"] = True self.importer.add_choice(importer.action.APPLY) self.importer.run() assert self.lib.items().get().format == "MP3" def test_apply_from_scratch_keeps_bitrate(self): config["import"]["from_scratch"] = True bitrate = 80000 self.importer.add_choice(importer.action.APPLY) self.importer.run() assert self.lib.items().get().bitrate == bitrate def test_apply_with_move_deletes_import(self): config["import"]["move"] = True import_file = os.path.join(self.import_dir, b"album", b"track_1.mp3") self.assertExists(import_file) self.importer.add_choice(importer.action.APPLY) self.importer.run() self.assertNotExists(import_file) def test_apply_with_delete_deletes_import(self): config["import"]["delete"] = True import_file = os.path.join(self.import_dir, b"album", b"track_1.mp3") self.assertExists(import_file) self.importer.add_choice(importer.action.APPLY) self.importer.run() self.assertNotExists(import_file) def test_skip_does_not_add_track(self): self.importer.add_choice(importer.action.SKIP) self.importer.run() assert self.lib.items().get() is None def test_skip_non_album_dirs(self): self.assertIsDir(os.path.join(self.import_dir, b"album")) self.touch(b"cruft", dir=self.import_dir) self.importer.add_choice(importer.action.APPLY) self.importer.run() assert len(self.lib.albums()) == 1 def test_unmatched_tracks_not_added(self): self.prepare_album_for_import(2) self.matcher.matching = self.matcher.MISSING self.importer.add_choice(importer.action.APPLY) self.importer.run() assert len(self.lib.items()) == 1 def test_empty_directory_warning(self): import_dir = os.path.join(self.temp_dir, b"empty") self.touch(b"non-audio", dir=import_dir) self.setup_importer(import_dir=import_dir) with capture_log() as logs: self.importer.run() import_dir = displayable_path(import_dir) assert f"No files imported from {import_dir}" in logs def test_empty_directory_singleton_warning(self): import_dir = os.path.join(self.temp_dir, b"empty") self.touch(b"non-audio", dir=import_dir) self.setup_singleton_importer(import_dir=import_dir) with capture_log() as logs: self.importer.run() import_dir = displayable_path(import_dir) assert f"No files imported from {import_dir}" in logs def test_asis_no_data_source(self): assert self.lib.items().get() is None self.importer.add_choice(importer.action.ASIS) self.importer.run() with pytest.raises(AttributeError): self.lib.items().get().data_source def test_set_fields(self): genre = "\U0001f3b7 Jazz" collection = "To Listen" comments = "managed by beets" disc = 0 config["import"]["set_fields"] = { "genre": genre, "collection": collection, "comments": comments, "album": "$album - formatted", "disc": disc, } # As-is album import. assert self.lib.albums().get() is None self.importer.add_choice(importer.action.ASIS) self.importer.run() for album in self.lib.albums(): album.load() # TODO: Not sure this is necessary. assert album.genre == genre assert album.comments == comments for item in album.items(): assert item.get("genre", with_album=False) == genre assert item.get("collection", with_album=False) == collection assert item.get("comments", with_album=False) == comments assert ( item.get("album", with_album=False) == "Tag Album - formatted" ) assert item.disc == disc # Remove album from library to test again with APPLY choice. album.remove() # Autotagged. assert self.lib.albums().get() is None self.importer.clear_choices() self.importer.add_choice(importer.action.APPLY) self.importer.run() for album in self.lib.albums(): album.load() assert album.genre == genre assert album.comments == comments for item in album.items(): assert item.get("genre", with_album=False) == genre assert item.get("collection", with_album=False) == collection assert item.get("comments", with_album=False) == comments assert ( item.get("album", with_album=False) == "Applied Album - formatted" ) assert item.disc == disc class ImportTracksTest(ImportTestCase): """Test TRACKS and APPLY choice.""" def setUp(self): super().setUp() self.prepare_album_for_import(1) self.setup_importer() self.matcher = AutotagStub().install() def tearDown(self): super().tearDown() self.matcher.restore() def test_apply_tracks_adds_singleton_track(self): assert self.lib.items().get() is None assert self.lib.albums().get() is None self.importer.add_choice(importer.action.TRACKS) self.importer.add_choice(importer.action.APPLY) self.importer.add_choice(importer.action.APPLY) self.importer.run() assert self.lib.items().get().title == "Applied Track 1" assert self.lib.albums().get() is None def test_apply_tracks_adds_singleton_path(self): self.assert_lib_dir_empty() self.importer.add_choice(importer.action.TRACKS) self.importer.add_choice(importer.action.APPLY) self.importer.add_choice(importer.action.APPLY) self.importer.run() self.assert_file_in_lib(b"singletons", b"Applied Track 1.mp3") class ImportCompilationTest(ImportTestCase): """Test ASIS import of a folder containing tracks with different artists.""" def setUp(self): super().setUp() self.prepare_album_for_import(3) self.setup_importer() self.matcher = AutotagStub().install() def tearDown(self): super().tearDown() self.matcher.restore() def test_asis_homogenous_sets_albumartist(self): self.importer.add_choice(importer.action.ASIS) self.importer.run() assert self.lib.albums().get().albumartist == "Tag Artist" for item in self.lib.items(): assert item.albumartist == "Tag Artist" def test_asis_heterogenous_sets_various_albumartist(self): self.import_media[0].artist = "Other Artist" self.import_media[0].save() self.import_media[1].artist = "Another Artist" self.import_media[1].save() self.importer.add_choice(importer.action.ASIS) self.importer.run() assert self.lib.albums().get().albumartist == "Various Artists" for item in self.lib.items(): assert item.albumartist == "Various Artists" def test_asis_heterogenous_sets_compilation(self): self.import_media[0].artist = "Other Artist" self.import_media[0].save() self.import_media[1].artist = "Another Artist" self.import_media[1].save() self.importer.add_choice(importer.action.ASIS) self.importer.run() for item in self.lib.items(): assert item.comp def test_asis_sets_majority_albumartist(self): self.import_media[0].artist = "Other Artist" self.import_media[0].save() self.import_media[1].artist = "Other Artist" self.import_media[1].save() self.importer.add_choice(importer.action.ASIS) self.importer.run() assert self.lib.albums().get().albumartist == "Other Artist" for item in self.lib.items(): assert item.albumartist == "Other Artist" def test_asis_albumartist_tag_sets_albumartist(self): self.import_media[0].artist = "Other Artist" self.import_media[1].artist = "Another Artist" for mediafile in self.import_media: mediafile.albumartist = "Album Artist" mediafile.mb_albumartistid = "Album Artist ID" mediafile.save() self.importer.add_choice(importer.action.ASIS) self.importer.run() assert self.lib.albums().get().albumartist == "Album Artist" assert self.lib.albums().get().mb_albumartistid == "Album Artist ID" for item in self.lib.items(): assert item.albumartist == "Album Artist" assert item.mb_albumartistid == "Album Artist ID" def test_asis_albumartists_tag_sets_multi_albumartists(self): self.import_media[0].artist = "Other Artist" self.import_media[0].artists = ["Other Artist", "Other Artist 2"] self.import_media[1].artist = "Another Artist" self.import_media[1].artists = ["Another Artist", "Another Artist 2"] for mediafile in self.import_media: mediafile.albumartist = "Album Artist" mediafile.albumartists = ["Album Artist 1", "Album Artist 2"] mediafile.mb_albumartistid = "Album Artist ID" mediafile.save() self.importer.add_choice(importer.action.ASIS) self.importer.run() assert self.lib.albums().get().albumartist == "Album Artist" assert self.lib.albums().get().albumartists == [ "Album Artist 1", "Album Artist 2", ] assert self.lib.albums().get().mb_albumartistid == "Album Artist ID" # Make sure both custom media items get tested asserted_multi_artists_0 = False asserted_multi_artists_1 = False for item in self.lib.items(): assert item.albumartist == "Album Artist" assert item.albumartists == ["Album Artist 1", "Album Artist 2"] assert item.mb_albumartistid == "Album Artist ID" if item.artist == "Other Artist": asserted_multi_artists_0 = True assert item.artists == ["Other Artist", "Other Artist 2"] if item.artist == "Another Artist": asserted_multi_artists_1 = True assert item.artists == ["Another Artist", "Another Artist 2"] assert asserted_multi_artists_0 assert asserted_multi_artists_1 class ImportExistingTest(ImportTestCase): """Test importing files that are already in the library directory.""" def setUp(self): super().setUp() self.prepare_album_for_import(1) self.matcher = AutotagStub().install() self.reimporter = self.setup_importer(import_dir=self.libdir) self.importer = self.setup_importer() def tearDown(self): super().tearDown() self.matcher.restore() def test_does_not_duplicate_item(self): self.importer.run() assert len(self.lib.items()) == 1 self.reimporter.add_choice(importer.action.APPLY) self.reimporter.run() assert len(self.lib.items()) == 1 def test_does_not_duplicate_album(self): self.importer.run() assert len(self.lib.albums()) == 1 self.reimporter.add_choice(importer.action.APPLY) self.reimporter.run() assert len(self.lib.albums()) == 1 def test_does_not_duplicate_singleton_track(self): self.importer.add_choice(importer.action.TRACKS) self.importer.add_choice(importer.action.APPLY) self.importer.run() assert len(self.lib.items()) == 1 self.reimporter.add_choice(importer.action.TRACKS) self.reimporter.add_choice(importer.action.APPLY) self.reimporter.run() assert len(self.lib.items()) == 1 def test_asis_updates_metadata(self): self.importer.run() medium = MediaFile(self.lib.items().get().path) medium.title = "New Title" medium.save() self.reimporter.add_choice(importer.action.ASIS) self.reimporter.run() assert self.lib.items().get().title == "New Title" def test_asis_updated_moves_file(self): self.importer.run() medium = MediaFile(self.lib.items().get().path) medium.title = "New Title" medium.save() old_path = os.path.join( b"Applied Artist", b"Applied Album", b"Applied Track 1.mp3" ) self.assert_file_in_lib(old_path) self.reimporter.add_choice(importer.action.ASIS) self.reimporter.run() self.assert_file_in_lib( b"Applied Artist", b"Applied Album", b"New Title.mp3" ) self.assert_file_not_in_lib(old_path) def test_asis_updated_without_copy_does_not_move_file(self): self.importer.run() medium = MediaFile(self.lib.items().get().path) medium.title = "New Title" medium.save() old_path = os.path.join( b"Applied Artist", b"Applied Album", b"Applied Track 1.mp3" ) self.assert_file_in_lib(old_path) config["import"]["copy"] = False self.reimporter.add_choice(importer.action.ASIS) self.reimporter.run() self.assert_file_not_in_lib( b"Applied Artist", b"Applied Album", b"New Title.mp3" ) self.assert_file_in_lib(old_path) def test_outside_file_is_copied(self): config["import"]["copy"] = False self.importer.run() self.assert_equal_path( self.lib.items().get().path, self.import_media[0].path ) self.reimporter = self.setup_importer() self.reimporter.add_choice(importer.action.APPLY) self.reimporter.run() new_path = os.path.join( b"Applied Artist", b"Applied Album", b"Applied Track 1.mp3" ) self.assert_file_in_lib(new_path) self.assert_equal_path( self.lib.items().get().path, os.path.join(self.libdir, new_path) ) def test_outside_file_is_moved(self): config["import"]["copy"] = False self.importer.run() self.assert_equal_path( self.lib.items().get().path, self.import_media[0].path ) self.reimporter = self.setup_importer(move=True) self.reimporter.add_choice(importer.action.APPLY) self.reimporter.run() self.assertNotExists(self.import_media[0].path) class GroupAlbumsImportTest(ImportTestCase): def setUp(self): super().setUp() self.prepare_album_for_import(3) self.matcher = AutotagStub().install() self.matcher.matching = AutotagStub.NONE self.setup_importer() # Split tracks into two albums and use both as-is self.importer.add_choice(importer.action.ALBUMS) self.importer.add_choice(importer.action.ASIS) self.importer.add_choice(importer.action.ASIS) def tearDown(self): super().tearDown() self.matcher.restore() def test_add_album_for_different_artist_and_different_album(self): self.import_media[0].artist = "Artist B" self.import_media[0].album = "Album B" self.import_media[0].save() self.importer.run() albums = {album.album for album in self.lib.albums()} assert albums == {"Album B", "Tag Album"} def test_add_album_for_different_artist_and_same_albumartist(self): self.import_media[0].artist = "Artist B" self.import_media[0].albumartist = "Album Artist" self.import_media[0].save() self.import_media[1].artist = "Artist C" self.import_media[1].albumartist = "Album Artist" self.import_media[1].save() self.importer.run() artists = {album.albumartist for album in self.lib.albums()} assert artists == {"Album Artist", "Tag Artist"} def test_add_album_for_same_artist_and_different_album(self): self.import_media[0].album = "Album B" self.import_media[0].save() self.importer.run() albums = {album.album for album in self.lib.albums()} assert albums == {"Album B", "Tag Album"} def test_add_album_for_same_album_and_different_artist(self): self.import_media[0].artist = "Artist B" self.import_media[0].save() self.importer.run() artists = {album.albumartist for album in self.lib.albums()} assert artists == {"Artist B", "Tag Artist"} def test_incremental(self): config["import"]["incremental"] = True self.import_media[0].album = "Album B" self.import_media[0].save() self.importer.run() albums = {album.album for album in self.lib.albums()} assert albums == {"Album B", "Tag Album"} class GlobalGroupAlbumsImportTest(GroupAlbumsImportTest): def setUp(self): super().setUp() self.importer.clear_choices() self.importer.default_choice = importer.action.ASIS config["import"]["group_albums"] = True class ChooseCandidateTest(ImportTestCase): def setUp(self): super().setUp() self.prepare_album_for_import(1) self.setup_importer() self.matcher = AutotagStub().install() self.matcher.matching = AutotagStub.BAD def tearDown(self): super().tearDown() self.matcher.restore() def test_choose_first_candidate(self): self.importer.add_choice(1) self.importer.run() assert self.lib.albums().get().album == "Applied Album M" def test_choose_second_candidate(self): self.importer.add_choice(2) self.importer.run() assert self.lib.albums().get().album == "Applied Album MM" class InferAlbumDataTest(BeetsTestCase): def setUp(self): super().setUp() i1 = _common.item() i2 = _common.item() i3 = _common.item() i1.title = "first item" i2.title = "second item" i3.title = "third item" i1.comp = i2.comp = i3.comp = False i1.albumartist = i2.albumartist = i3.albumartist = "" i1.mb_albumartistid = i2.mb_albumartistid = i3.mb_albumartistid = "" self.items = [i1, i2, i3] self.task = importer.ImportTask( paths=["a path"], toppath="top path", items=self.items ) def test_asis_homogenous_single_artist(self): self.task.set_choice(importer.action.ASIS) self.task.align_album_level_fields() assert not self.items[0].comp assert self.items[0].albumartist == self.items[2].artist def test_asis_heterogenous_va(self): self.items[0].artist = "another artist" self.items[1].artist = "some other artist" self.task.set_choice(importer.action.ASIS) self.task.align_album_level_fields() assert self.items[0].comp assert self.items[0].albumartist == "Various Artists" def test_asis_comp_applied_to_all_items(self): self.items[0].artist = "another artist" self.items[1].artist = "some other artist" self.task.set_choice(importer.action.ASIS) self.task.align_album_level_fields() for item in self.items: assert item.comp assert item.albumartist == "Various Artists" def test_asis_majority_artist_single_artist(self): self.items[0].artist = "another artist" self.task.set_choice(importer.action.ASIS) self.task.align_album_level_fields() assert not self.items[0].comp assert self.items[0].albumartist == self.items[2].artist def test_asis_track_albumartist_override(self): self.items[0].artist = "another artist" self.items[1].artist = "some other artist" for item in self.items: item.albumartist = "some album artist" item.mb_albumartistid = "some album artist id" self.task.set_choice(importer.action.ASIS) self.task.align_album_level_fields() assert self.items[0].albumartist == "some album artist" assert self.items[0].mb_albumartistid == "some album artist id" def test_apply_gets_artist_and_id(self): self.task.set_choice(AlbumMatch(0, None, {}, set(), set())) # APPLY self.task.align_album_level_fields() assert self.items[0].albumartist == self.items[0].artist assert self.items[0].mb_albumartistid == self.items[0].mb_artistid def test_apply_lets_album_values_override(self): for item in self.items: item.albumartist = "some album artist" item.mb_albumartistid = "some album artist id" self.task.set_choice(AlbumMatch(0, None, {}, set(), set())) # APPLY self.task.align_album_level_fields() assert self.items[0].albumartist == "some album artist" assert self.items[0].mb_albumartistid == "some album artist id" def test_small_single_artist_album(self): self.items = [self.items[0]] self.task.items = self.items self.task.set_choice(importer.action.ASIS) self.task.align_album_level_fields() assert not self.items[0].comp def match_album_mock(*args, **kwargs): """Create an AlbumInfo object for testing.""" track_info = TrackInfo( title="new title", track_id="trackid", index=0, ) album_info = AlbumInfo( artist="artist", album="album", tracks=[track_info], album_id="albumid", artist_id="artistid", flex="flex", ) return iter([album_info]) @patch("beets.autotag.mb.match_album", Mock(side_effect=match_album_mock)) class ImportDuplicateAlbumTest(ImportTestCase): def setUp(self): super().setUp() # Original album self.add_album_fixture(albumartist="artist", album="album") # Create import session self.prepare_album_for_import(1) self.importer = self.setup_importer( duplicate_keys={"album": "albumartist album"} ) def test_remove_duplicate_album(self): item = self.lib.items().get() assert item.title == "t\xeftle 0" self.assertExists(item.path) self.importer.default_resolution = self.importer.Resolution.REMOVE self.importer.run() self.assertNotExists(item.path) assert len(self.lib.albums()) == 1 assert len(self.lib.items()) == 1 item = self.lib.items().get() assert item.title == "new title" def test_no_autotag_keeps_duplicate_album(self): config["import"]["autotag"] = False item = self.lib.items().get() assert item.title == "t\xeftle 0" self.assertExists(item.path) # Imported item has the same artist and album as the one in the # library. import_file = os.path.join( self.importer.paths[0], b"album", b"track_1.mp3" ) import_file = MediaFile(import_file) import_file.artist = item["artist"] import_file.albumartist = item["artist"] import_file.album = item["album"] import_file.title = "new title" self.importer.default_resolution = self.importer.Resolution.REMOVE self.importer.run() self.assertExists(item.path) assert len(self.lib.albums()) == 2 assert len(self.lib.items()) == 2 def test_keep_duplicate_album(self): self.importer.default_resolution = self.importer.Resolution.KEEPBOTH self.importer.run() assert len(self.lib.albums()) == 2 assert len(self.lib.items()) == 2 def test_skip_duplicate_album(self): item = self.lib.items().get() assert item.title == "t\xeftle 0" self.importer.default_resolution = self.importer.Resolution.SKIP self.importer.run() assert len(self.lib.albums()) == 1 assert len(self.lib.items()) == 1 item = self.lib.items().get() assert item.title == "t\xeftle 0" def test_merge_duplicate_album(self): self.importer.default_resolution = self.importer.Resolution.MERGE self.importer.run() assert len(self.lib.albums()) == 1 def test_twice_in_import_dir(self): self.skipTest("write me") def test_keep_when_extra_key_is_different(self): config["import"]["duplicate_keys"]["album"] = "albumartist album flex" item = self.lib.items().get() import_file = MediaFile( os.path.join(self.importer.paths[0], b"album", b"track_1.mp3") ) import_file.artist = item["artist"] import_file.albumartist = item["artist"] import_file.album = item["album"] import_file.title = item["title"] import_file.flex = "different" self.importer.default_resolution = self.importer.Resolution.SKIP self.importer.run() assert len(self.lib.albums()) == 2 assert len(self.lib.items()) == 2 def add_album_fixture(self, **kwargs): # TODO move this into upstream album = super().add_album_fixture() album.update(kwargs) album.store() return album def match_track_mock(*args, **kwargs): return iter( [ TrackInfo( artist="artist", title="title", track_id="new trackid", index=0, ) ] ) @patch("beets.autotag.mb.match_track", Mock(side_effect=match_track_mock)) class ImportDuplicateSingletonTest(ImportTestCase): def setUp(self): super().setUp() # Original file in library self.add_item_fixture( artist="artist", title="title", mb_trackid="old trackid" ) # Import session self.prepare_album_for_import(1) self.importer = self.setup_singleton_importer( duplicate_keys={"album": "artist title"} ) def test_remove_duplicate(self): item = self.lib.items().get() assert item.mb_trackid == "old trackid" self.assertExists(item.path) self.importer.default_resolution = self.importer.Resolution.REMOVE self.importer.run() self.assertNotExists(item.path) assert len(self.lib.items()) == 1 item = self.lib.items().get() assert item.mb_trackid == "new trackid" def test_keep_duplicate(self): assert len(self.lib.items()) == 1 self.importer.default_resolution = self.importer.Resolution.KEEPBOTH self.importer.run() assert len(self.lib.items()) == 2 def test_skip_duplicate(self): item = self.lib.items().get() assert item.mb_trackid == "old trackid" self.importer.default_resolution = self.importer.Resolution.SKIP self.importer.run() assert len(self.lib.items()) == 1 item = self.lib.items().get() assert item.mb_trackid == "old trackid" def test_keep_when_extra_key_is_different(self): config["import"]["duplicate_keys"]["item"] = "artist title flex" item = self.lib.items().get() item.flex = "different" item.store() assert len(self.lib.items()) == 1 self.importer.default_resolution = self.importer.Resolution.SKIP self.importer.run() assert len(self.lib.items()) == 2 def test_twice_in_import_dir(self): self.skipTest("write me") def add_item_fixture(self, **kwargs): # Move this to TestHelper item = self.add_item_fixtures()[0] item.update(kwargs) item.store() return item class TagLogTest(BeetsTestCase): def test_tag_log_line(self): sio = StringIO() handler = logging.StreamHandler(sio) session = _common.import_session(loghandler=handler) session.tag_log("status", "path") assert "status path" in sio.getvalue() def test_tag_log_unicode(self): sio = StringIO() handler = logging.StreamHandler(sio) session = _common.import_session(loghandler=handler) session.tag_log("status", "caf\xe9") # send unicode assert "status caf\xe9" in sio.getvalue() class ResumeImportTest(ImportTestCase): @patch("beets.plugins.send") def test_resume_album(self, plugins_send): self.prepare_albums_for_import(2) self.importer = self.setup_importer(autotag=False, resume=True) # Aborts import after one album. This also ensures that we skip # the first album in the second try. def raise_exception(event, **kwargs): if event == "album_imported": raise importer.ImportAbortError plugins_send.side_effect = raise_exception self.importer.run() assert len(self.lib.albums()) == 1 assert self.lib.albums("album:'Album 1'").get() is not None self.importer.run() assert len(self.lib.albums()) == 2 assert self.lib.albums("album:'Album 2'").get() is not None @patch("beets.plugins.send") def test_resume_singleton(self, plugins_send): self.prepare_album_for_import(2) self.importer = self.setup_singleton_importer( autotag=False, resume=True ) # Aborts import after one track. This also ensures that we skip # the first album in the second try. def raise_exception(event, **kwargs): if event == "item_imported": raise importer.ImportAbortError plugins_send.side_effect = raise_exception self.importer.run() assert len(self.lib.items()) == 1 assert self.lib.items("title:'Track 1'").get() is not None self.importer.run() assert len(self.lib.items()) == 2 assert self.lib.items("title:'Track 1'").get() is not None class IncrementalImportTest(AsIsImporterMixin, ImportTestCase): def test_incremental_album(self): importer = self.run_asis_importer(incremental=True) # Change album name so the original file would be imported again # if incremental was off. album = self.lib.albums().get() album["album"] = "edited album" album.store() importer.run() assert len(self.lib.albums()) == 2 def test_incremental_item(self): importer = self.run_asis_importer(incremental=True, singletons=True) # Change track name so the original file would be imported again # if incremental was off. item = self.lib.items().get() item["artist"] = "edited artist" item.store() importer.run() assert len(self.lib.items()) == 2 def test_invalid_state_file(self): with open(self.config["statefile"].as_filename(), "wb") as f: f.write(b"000") self.run_asis_importer(incremental=True) assert len(self.lib.albums()) == 1 def _mkmp3(path): shutil.copyfile( syspath(os.path.join(_common.RSRC, b"min.mp3")), syspath(path), ) class AlbumsInDirTest(BeetsTestCase): def setUp(self): super().setUp() # create a directory structure for testing self.base = os.path.abspath(os.path.join(self.temp_dir, b"tempdir")) os.mkdir(syspath(self.base)) os.mkdir(syspath(os.path.join(self.base, b"album1"))) os.mkdir(syspath(os.path.join(self.base, b"album2"))) os.mkdir(syspath(os.path.join(self.base, b"more"))) os.mkdir(syspath(os.path.join(self.base, b"more", b"album3"))) os.mkdir(syspath(os.path.join(self.base, b"more", b"album4"))) _mkmp3(os.path.join(self.base, b"album1", b"album1song1.mp3")) _mkmp3(os.path.join(self.base, b"album1", b"album1song2.mp3")) _mkmp3(os.path.join(self.base, b"album2", b"album2song.mp3")) _mkmp3(os.path.join(self.base, b"more", b"album3", b"album3song.mp3")) _mkmp3(os.path.join(self.base, b"more", b"album4", b"album4song.mp3")) def test_finds_all_albums(self): albums = list(albums_in_dir(self.base)) assert len(albums) == 4 def test_separates_contents(self): found = [] for _, album in albums_in_dir(self.base): found.append(re.search(rb"album(.)song", album[0]).group(1)) assert b"1" in found assert b"2" in found assert b"3" in found assert b"4" in found def test_finds_multiple_songs(self): for _, album in albums_in_dir(self.base): n = re.search(rb"album(.)song", album[0]).group(1) if n == b"1": assert len(album) == 2 else: assert len(album) == 1 class MultiDiscAlbumsInDirTest(BeetsTestCase): def create_music(self, files=True, ascii=True): """Create some music in multiple album directories. `files` indicates whether to create the files (otherwise, only directories are made). `ascii` indicates ACII-only filenames; otherwise, we use Unicode names. """ self.base = os.path.abspath(os.path.join(self.temp_dir, b"tempdir")) os.mkdir(syspath(self.base)) name = b"CAT" if ascii else util.bytestring_path("C\xc1T") name_alt_case = b"CAt" if ascii else util.bytestring_path("C\xc1t") self.dirs = [ # Nested album, multiple subdirs. # Also, false positive marker in root dir, and subtitle for disc 3. os.path.join(self.base, b"ABCD1234"), os.path.join(self.base, b"ABCD1234", b"cd 1"), os.path.join(self.base, b"ABCD1234", b"cd 3 - bonus"), # Nested album, single subdir. # Also, punctuation between marker and disc number. os.path.join(self.base, b"album"), os.path.join(self.base, b"album", b"cd _ 1"), # Flattened album, case typo. # Also, false positive marker in parent dir. os.path.join(self.base, b"artist [CD5]"), os.path.join(self.base, b"artist [CD5]", name + b" disc 1"), os.path.join( self.base, b"artist [CD5]", name_alt_case + b" disc 2" ), # Single disc album, sorted between CAT discs. os.path.join(self.base, b"artist [CD5]", name + b"S"), ] self.files = [ os.path.join(self.base, b"ABCD1234", b"cd 1", b"song1.mp3"), os.path.join(self.base, b"ABCD1234", b"cd 3 - bonus", b"song2.mp3"), os.path.join(self.base, b"ABCD1234", b"cd 3 - bonus", b"song3.mp3"), os.path.join(self.base, b"album", b"cd _ 1", b"song4.mp3"), os.path.join( self.base, b"artist [CD5]", name + b" disc 1", b"song5.mp3" ), os.path.join( self.base, b"artist [CD5]", name_alt_case + b" disc 2", b"song6.mp3", ), os.path.join(self.base, b"artist [CD5]", name + b"S", b"song7.mp3"), ] if not ascii: self.dirs = [self._normalize_path(p) for p in self.dirs] self.files = [self._normalize_path(p) for p in self.files] for path in self.dirs: os.mkdir(syspath(path)) if files: for path in self.files: _mkmp3(util.syspath(path)) def _normalize_path(self, path): """Normalize a path's Unicode combining form according to the platform. """ path = path.decode("utf-8") norm_form = "NFD" if sys.platform == "darwin" else "NFC" path = unicodedata.normalize(norm_form, path) return path.encode("utf-8") def test_coalesce_nested_album_multiple_subdirs(self): self.create_music() albums = list(albums_in_dir(self.base)) assert len(albums) == 4 root, items = albums[0] assert root == self.dirs[0:3] assert len(items) == 3 def test_coalesce_nested_album_single_subdir(self): self.create_music() albums = list(albums_in_dir(self.base)) root, items = albums[1] assert root == self.dirs[3:5] assert len(items) == 1 def test_coalesce_flattened_album_case_typo(self): self.create_music() albums = list(albums_in_dir(self.base)) root, items = albums[2] assert root == self.dirs[6:8] assert len(items) == 2 def test_single_disc_album(self): self.create_music() albums = list(albums_in_dir(self.base)) root, items = albums[3] assert root == self.dirs[8:] assert len(items) == 1 def test_do_not_yield_empty_album(self): self.create_music(files=False) albums = list(albums_in_dir(self.base)) assert len(albums) == 0 def test_single_disc_unicode(self): self.create_music(ascii=False) albums = list(albums_in_dir(self.base)) root, items = albums[3] assert root == self.dirs[8:] assert len(items) == 1 def test_coalesce_multiple_unicode(self): self.create_music(ascii=False) albums = list(albums_in_dir(self.base)) assert len(albums) == 4 root, items = albums[0] assert root == self.dirs[0:3] assert len(items) == 3 class ReimportTest(ImportTestCase): """Test "re-imports", in which the autotagging machinery is used for music that's already in the library. This works by importing new database entries for the same files and replacing the old data with the new data. We also copy over flexible attributes and the added date. """ def setUp(self): super().setUp() # The existing album. album = self.add_album_fixture() album.added = 4242.0 album.foo = "bar" # Some flexible attribute. album.data_source = "original_source" album.store() item = album.items().get() item.baz = "qux" item.added = 4747.0 item.store() # Set up an import pipeline with a "good" match. self.matcher = AutotagStub().install() self.matcher.matching = AutotagStub.GOOD def tearDown(self): super().tearDown() self.matcher.restore() def _setup_session(self, singletons=False): self.setup_importer(import_dir=self.libdir, singletons=singletons) self.importer.add_choice(importer.action.APPLY) def _album(self): return self.lib.albums().get() def _item(self): return self.lib.items().get() def test_reimported_album_gets_new_metadata(self): self._setup_session() assert self._album().album == "\xe4lbum" self.importer.run() assert self._album().album == "the album" def test_reimported_album_preserves_flexattr(self): self._setup_session() self.importer.run() assert self._album().foo == "bar" def test_reimported_album_preserves_added(self): self._setup_session() self.importer.run() assert self._album().added == 4242.0 def test_reimported_album_preserves_item_flexattr(self): self._setup_session() self.importer.run() assert self._item().baz == "qux" def test_reimported_album_preserves_item_added(self): self._setup_session() self.importer.run() assert self._item().added == 4747.0 def test_reimported_item_gets_new_metadata(self): self._setup_session(True) assert self._item().title == "t\xeftle 0" self.importer.run() assert self._item().title == "full" def test_reimported_item_preserves_flexattr(self): self._setup_session(True) self.importer.run() assert self._item().baz == "qux" def test_reimported_item_preserves_added(self): self._setup_session(True) self.importer.run() assert self._item().added == 4747.0 def test_reimported_item_preserves_art(self): self._setup_session() art_source = os.path.join(_common.RSRC, b"abbey.jpg") replaced_album = self._album() replaced_album.set_art(art_source) replaced_album.store() old_artpath = replaced_album.artpath self.importer.run() new_album = self._album() new_artpath = new_album.art_destination(art_source) assert new_album.artpath == new_artpath self.assertExists(new_artpath) if new_artpath != old_artpath: self.assertNotExists(old_artpath) def test_reimported_album_has_new_flexattr(self): self._setup_session() assert self._album().get("bandcamp_album_id") is None self.importer.run() assert self._album().bandcamp_album_id == "bc_url" def test_reimported_album_not_preserves_flexattr(self): self._setup_session() assert self._album().data_source == "original_source" self.importer.run() assert self._album().data_source == "match_source" class ImportPretendTest(ImportTestCase): """Test the pretend commandline option""" def setUp(self): super().setUp() self.matcher = AutotagStub().install() self.io.install() self.album_track_path = self.prepare_album_for_import(1)[0] self.single_path = self.prepare_track_for_import(2, self.import_path) self.album_path = self.album_track_path.parent def tearDown(self): super().tearDown() self.matcher.restore() def __run(self, importer): with capture_log() as logs: importer.run() assert len(self.lib.items()) == 0 assert len(self.lib.albums()) == 0 return [line for line in logs if not line.startswith("Sending event:")] def test_import_singletons_pretend(self): assert self.__run(self.setup_singleton_importer(pretend=True)) == [ f"Singleton: {self.single_path}", f"Singleton: {self.album_track_path}", ] def test_import_album_pretend(self): assert self.__run(self.setup_importer(pretend=True)) == [ f"Album: {self.import_path}", f" {self.single_path}", f"Album: {self.album_path}", f" {self.album_track_path}", ] def test_import_pretend_empty(self): empty_path = Path(os.fsdecode(self.temp_dir)) / "empty" empty_path.mkdir() importer = self.setup_importer(pretend=True, import_dir=empty_path) assert self.__run(importer) == [f"No files imported from {empty_path}"] # Helpers for ImportMusicBrainzIdTest. def mocked_get_release_by_id( id_, includes=[], release_status=[], release_type=[] ): """Mimic musicbrainzngs.get_release_by_id, accepting only a restricted list of MB ids (ID_RELEASE_0, ID_RELEASE_1). The returned dict differs only in the release title and artist name, so that ID_RELEASE_0 is a closer match to the items created by ImportHelper.prepare_album_for_import().""" # Map IDs to (release title, artist), so the distances are different. releases = { ImportMusicBrainzIdTest.ID_RELEASE_0: ("VALID_RELEASE_0", "TAG ARTIST"), ImportMusicBrainzIdTest.ID_RELEASE_1: ( "VALID_RELEASE_1", "DISTANT_MATCH", ), } return { "release": { "title": releases[id_][0], "id": id_, "medium-list": [ { "track-list": [ { "id": "baz", "recording": { "title": "foo", "id": "bar", "length": 59, }, "position": 9, "number": "A2", } ], "position": 5, } ], "artist-credit": [ { "artist": { "name": releases[id_][1], "id": "some-id", }, } ], "release-group": { "id": "another-id", }, "status": "Official", } } def mocked_get_recording_by_id( id_, includes=[], release_status=[], release_type=[] ): """Mimic musicbrainzngs.get_recording_by_id, accepting only a restricted list of MB ids (ID_RECORDING_0, ID_RECORDING_1). The returned dict differs only in the recording title and artist name, so that ID_RECORDING_0 is a closer match to the items created by ImportHelper.prepare_album_for_import(). """ # Map IDs to (recording title, artist), so the distances are different. releases = { ImportMusicBrainzIdTest.ID_RECORDING_0: ( "VALID_RECORDING_0", "TAG ARTIST", ), ImportMusicBrainzIdTest.ID_RECORDING_1: ( "VALID_RECORDING_1", "DISTANT_MATCH", ), } return { "recording": { "title": releases[id_][0], "id": id_, "length": 59, "artist-credit": [ { "artist": { "name": releases[id_][1], "id": "some-id", }, } ], } } @patch( "musicbrainzngs.get_recording_by_id", Mock(side_effect=mocked_get_recording_by_id), ) @patch( "musicbrainzngs.get_release_by_id", Mock(side_effect=mocked_get_release_by_id), ) class ImportMusicBrainzIdTest(ImportTestCase): """Test the --musicbrainzid argument.""" MB_RELEASE_PREFIX = "https://musicbrainz.org/release/" MB_RECORDING_PREFIX = "https://musicbrainz.org/recording/" ID_RELEASE_0 = "00000000-0000-0000-0000-000000000000" ID_RELEASE_1 = "11111111-1111-1111-1111-111111111111" ID_RECORDING_0 = "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa" ID_RECORDING_1 = "bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb" def setUp(self): super().setUp() self.prepare_album_for_import(1) def test_one_mbid_one_album(self): self.setup_importer( search_ids=[self.MB_RELEASE_PREFIX + self.ID_RELEASE_0] ) self.importer.add_choice(importer.action.APPLY) self.importer.run() assert self.lib.albums().get().album == "VALID_RELEASE_0" def test_several_mbid_one_album(self): self.setup_importer( search_ids=[ self.MB_RELEASE_PREFIX + self.ID_RELEASE_0, self.MB_RELEASE_PREFIX + self.ID_RELEASE_1, ] ) self.importer.add_choice(2) # Pick the 2nd best match (release 1). self.importer.add_choice(importer.action.APPLY) self.importer.run() assert self.lib.albums().get().album == "VALID_RELEASE_1" def test_one_mbid_one_singleton(self): self.setup_singleton_importer( search_ids=[self.MB_RECORDING_PREFIX + self.ID_RECORDING_0] ) self.importer.add_choice(importer.action.APPLY) self.importer.run() assert self.lib.items().get().title == "VALID_RECORDING_0" def test_several_mbid_one_singleton(self): self.setup_singleton_importer( search_ids=[ self.MB_RECORDING_PREFIX + self.ID_RECORDING_0, self.MB_RECORDING_PREFIX + self.ID_RECORDING_1, ] ) self.importer.add_choice(2) # Pick the 2nd best match (recording 1). self.importer.add_choice(importer.action.APPLY) self.importer.run() assert self.lib.items().get().title == "VALID_RECORDING_1" def test_candidates_album(self): """Test directly ImportTask.lookup_candidates().""" task = importer.ImportTask( paths=self.import_dir, toppath="top path", items=[_common.item()] ) task.search_ids = [ self.MB_RELEASE_PREFIX + self.ID_RELEASE_0, self.MB_RELEASE_PREFIX + self.ID_RELEASE_1, "an invalid and discarded id", ] task.lookup_candidates() assert {"VALID_RELEASE_0", "VALID_RELEASE_1"} == { c.info.album for c in task.candidates } def test_candidates_singleton(self): """Test directly SingletonImportTask.lookup_candidates().""" task = importer.SingletonImportTask( toppath="top path", item=_common.item() ) task.search_ids = [ self.MB_RECORDING_PREFIX + self.ID_RECORDING_0, self.MB_RECORDING_PREFIX + self.ID_RECORDING_1, "an invalid and discarded id", ] task.lookup_candidates() assert {"VALID_RECORDING_0", "VALID_RECORDING_1"} == { c.info.title for c in task.candidates } beetbox-beets-01f1faf/test/test_library.py000066400000000000000000001337231472325477400210140ustar00rootroot00000000000000# This file is part of beets. # Copyright 2016, Adrian Sampson. # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the # "Software"), to deal in the Software without restriction, including # without limitation the rights to use, copy, modify, merge, publish, # distribute, sublicense, and/or sell copies of the Software, and to # permit persons to whom the Software is furnished to do so, subject to # the following conditions: # # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. """Tests for non-query database functions of Item.""" import os import os.path import re import shutil import stat import sys import time import unicodedata import unittest import pytest from mediafile import MediaFile, UnreadableFileError import beets.dbcore.query import beets.library from beets import config, plugins, util from beets.library import Album from beets.test import _common from beets.test._common import item from beets.test.helper import BeetsTestCase, ItemInDBTestCase from beets.util import bytestring_path, syspath # Shortcut to path normalization. np = util.normpath class LoadTest(ItemInDBTestCase): def test_load_restores_data_from_db(self): original_title = self.i.title self.i.title = "something" self.i.load() assert original_title == self.i.title def test_load_clears_dirty_flags(self): self.i.artist = "something" assert "artist" in self.i._dirty self.i.load() assert "artist" not in self.i._dirty class StoreTest(ItemInDBTestCase): def test_store_changes_database_value(self): self.i.year = 1987 self.i.store() new_year = ( self.lib._connection() .execute("select year from items where title = ?", (self.i.title,)) .fetchone()["year"] ) assert new_year == 1987 def test_store_only_writes_dirty_fields(self): original_genre = self.i.genre self.i._values_fixed["genre"] = "beatboxing" # change w/o dirtying self.i.store() new_genre = ( self.lib._connection() .execute("select genre from items where title = ?", (self.i.title,)) .fetchone()["genre"] ) assert new_genre == original_genre def test_store_clears_dirty_flags(self): self.i.composer = "tvp" self.i.store() assert "composer" not in self.i._dirty def test_store_album_cascades_flex_deletes(self): album = Album(flex1="Flex-1") self.lib.add(album) item = _common.item() item.album_id = album.id item.flex1 = "Flex-1" self.lib.add(item) del album.flex1 album.store() assert "flex1" not in album assert "flex1" not in album.items()[0] class AddTest(BeetsTestCase): def setUp(self): super().setUp() self.i = item() def test_item_add_inserts_row(self): self.lib.add(self.i) new_grouping = ( self.lib._connection() .execute( "select grouping from items where composer = ?", (self.i.composer,), ) .fetchone()["grouping"] ) assert new_grouping == self.i.grouping def test_library_add_path_inserts_row(self): i = beets.library.Item.from_path( os.path.join(_common.RSRC, b"full.mp3") ) self.lib.add(i) new_grouping = ( self.lib._connection() .execute( "select grouping from items where composer = ?", (self.i.composer,), ) .fetchone()["grouping"] ) assert new_grouping == self.i.grouping class RemoveTest(ItemInDBTestCase): def test_remove_deletes_from_db(self): self.i.remove() c = self.lib._connection().execute("select * from items") assert c.fetchone() is None class GetSetTest(BeetsTestCase): def setUp(self): super().setUp() self.i = item() def test_set_changes_value(self): self.i.bpm = 4915 assert self.i.bpm == 4915 def test_set_sets_dirty_flag(self): self.i.comp = not self.i.comp assert "comp" in self.i._dirty def test_set_does_not_dirty_if_value_unchanged(self): self.i.title = self.i.title assert "title" not in self.i._dirty def test_invalid_field_raises_attributeerror(self): with pytest.raises(AttributeError): self.i.xyzzy def test_album_fallback(self): # integration test of item-album fallback i = item(self.lib) album = self.lib.add_album([i]) album["flex"] = "foo" album.store() assert "flex" in i assert "flex" not in i.keys(with_album=False) assert i["flex"] == "foo" assert i.get("flex") == "foo" assert i.get("flex", with_album=False) is None assert i.get("flexx") is None class DestinationTest(BeetsTestCase): """Confirm tests handle temporary directory path containing '.'""" def create_temp_dir(self, **kwargs): kwargs["prefix"] = "." super().create_temp_dir(**kwargs) def setUp(self): super().setUp() self.i = item(self.lib) def test_directory_works_with_trailing_slash(self): self.lib.directory = b"one/" self.lib.path_formats = [("default", "two")] assert self.i.destination() == np("one/two") def test_directory_works_without_trailing_slash(self): self.lib.directory = b"one" self.lib.path_formats = [("default", "two")] assert self.i.destination() == np("one/two") def test_destination_substitutes_metadata_values(self): self.lib.directory = b"base" self.lib.path_formats = [("default", "$album/$artist $title")] self.i.title = "three" self.i.artist = "two" self.i.album = "one" assert self.i.destination() == np("base/one/two three") def test_destination_preserves_extension(self): self.lib.directory = b"base" self.lib.path_formats = [("default", "$title")] self.i.path = "hey.audioformat" assert self.i.destination() == np("base/the title.audioformat") def test_lower_case_extension(self): self.lib.directory = b"base" self.lib.path_formats = [("default", "$title")] self.i.path = "hey.MP3" assert self.i.destination() == np("base/the title.mp3") def test_destination_pads_some_indices(self): self.lib.directory = b"base" self.lib.path_formats = [ ("default", "$track $tracktotal $disc $disctotal $bpm") ] self.i.track = 1 self.i.tracktotal = 2 self.i.disc = 3 self.i.disctotal = 4 self.i.bpm = 5 assert self.i.destination() == np("base/01 02 03 04 5") def test_destination_pads_date_values(self): self.lib.directory = b"base" self.lib.path_formats = [("default", "$year-$month-$day")] self.i.year = 1 self.i.month = 2 self.i.day = 3 assert self.i.destination() == np("base/0001-02-03") def test_destination_escapes_slashes(self): self.i.album = "one/two" dest = self.i.destination() assert b"one" in dest assert b"two" in dest assert b"one/two" not in dest def test_destination_escapes_leading_dot(self): self.i.album = ".something" dest = self.i.destination() assert b"something" in dest assert b"/.something" not in dest def test_destination_preserves_legitimate_slashes(self): self.i.artist = "one" self.i.album = "two" dest = self.i.destination() assert os.path.join(b"one", b"two") in dest def test_destination_long_names_truncated(self): self.i.title = "X" * 300 self.i.artist = "Y" * 300 for c in self.i.destination().split(util.PATH_SEP): assert len(c) <= 255 def test_destination_long_names_keep_extension(self): self.i.title = "X" * 300 self.i.path = b"something.extn" dest = self.i.destination() assert dest[-5:] == b".extn" def test_distination_windows_removes_both_separators(self): self.i.title = "one \\ two / three.mp3" with _common.platform_windows(): p = self.i.destination() assert b"one \\ two" not in p assert b"one / two" not in p assert b"two \\ three" not in p assert b"two / three" not in p def test_path_with_format(self): self.lib.path_formats = [("default", "$artist/$album ($format)")] p = self.i.destination() assert b"(FLAC)" in p def test_heterogeneous_album_gets_single_directory(self): i1, i2 = item(), item() self.lib.add_album([i1, i2]) i1.year, i2.year = 2009, 2010 self.lib.path_formats = [("default", "$album ($year)/$track $title")] dest1, dest2 = i1.destination(), i2.destination() assert os.path.dirname(dest1) == os.path.dirname(dest2) def test_default_path_for_non_compilations(self): self.i.comp = False self.lib.add_album([self.i]) self.lib.directory = b"one" self.lib.path_formats = [("default", "two"), ("comp:true", "three")] assert self.i.destination() == np("one/two") def test_singleton_path(self): i = item(self.lib) self.lib.directory = b"one" self.lib.path_formats = [ ("default", "two"), ("singleton:true", "four"), ("comp:true", "three"), ] assert i.destination() == np("one/four") def test_comp_before_singleton_path(self): i = item(self.lib) i.comp = True self.lib.directory = b"one" self.lib.path_formats = [ ("default", "two"), ("comp:true", "three"), ("singleton:true", "four"), ] assert i.destination() == np("one/three") def test_comp_path(self): self.i.comp = True self.lib.add_album([self.i]) self.lib.directory = b"one" self.lib.path_formats = [("default", "two"), ("comp:true", "three")] assert self.i.destination() == np("one/three") def test_albumtype_query_path(self): self.i.comp = True self.lib.add_album([self.i]) self.i.albumtype = "sometype" self.lib.directory = b"one" self.lib.path_formats = [ ("default", "two"), ("albumtype:sometype", "four"), ("comp:true", "three"), ] assert self.i.destination() == np("one/four") def test_albumtype_path_fallback_to_comp(self): self.i.comp = True self.lib.add_album([self.i]) self.i.albumtype = "sometype" self.lib.directory = b"one" self.lib.path_formats = [ ("default", "two"), ("albumtype:anothertype", "four"), ("comp:true", "three"), ] assert self.i.destination() == np("one/three") def test_get_formatted_does_not_replace_separators(self): with _common.platform_posix(): name = os.path.join("a", "b") self.i.title = name newname = self.i.formatted().get("title") assert name == newname def test_get_formatted_pads_with_zero(self): with _common.platform_posix(): self.i.track = 1 name = self.i.formatted().get("track") assert name.startswith("0") def test_get_formatted_uses_kbps_bitrate(self): with _common.platform_posix(): self.i.bitrate = 12345 val = self.i.formatted().get("bitrate") assert val == "12kbps" def test_get_formatted_uses_khz_samplerate(self): with _common.platform_posix(): self.i.samplerate = 12345 val = self.i.formatted().get("samplerate") assert val == "12kHz" def test_get_formatted_datetime(self): with _common.platform_posix(): self.i.added = 1368302461.210265 val = self.i.formatted().get("added") assert val.startswith("2013") def test_get_formatted_none(self): with _common.platform_posix(): self.i.some_other_field = None val = self.i.formatted().get("some_other_field") assert val == "" def test_artist_falls_back_to_albumartist(self): self.i.artist = "" self.i.albumartist = "something" self.lib.path_formats = [("default", "$artist")] p = self.i.destination() assert p.rsplit(util.PATH_SEP, 1)[1] == b"something" def test_albumartist_falls_back_to_artist(self): self.i.artist = "trackartist" self.i.albumartist = "" self.lib.path_formats = [("default", "$albumartist")] p = self.i.destination() assert p.rsplit(util.PATH_SEP, 1)[1] == b"trackartist" def test_artist_overrides_albumartist(self): self.i.artist = "theartist" self.i.albumartist = "something" self.lib.path_formats = [("default", "$artist")] p = self.i.destination() assert p.rsplit(util.PATH_SEP, 1)[1] == b"theartist" def test_albumartist_overrides_artist(self): self.i.artist = "theartist" self.i.albumartist = "something" self.lib.path_formats = [("default", "$albumartist")] p = self.i.destination() assert p.rsplit(util.PATH_SEP, 1)[1] == b"something" def test_unicode_normalized_nfd_on_mac(self): instr = unicodedata.normalize("NFC", "caf\xe9") self.lib.path_formats = [("default", instr)] dest = self.i.destination(platform="darwin", fragment=True) assert dest == unicodedata.normalize("NFD", instr) def test_unicode_normalized_nfc_on_linux(self): instr = unicodedata.normalize("NFD", "caf\xe9") self.lib.path_formats = [("default", instr)] dest = self.i.destination(platform="linux", fragment=True) assert dest == unicodedata.normalize("NFC", instr) def test_non_mbcs_characters_on_windows(self): oldfunc = sys.getfilesystemencoding sys.getfilesystemencoding = lambda: "mbcs" try: self.i.title = "h\u0259d" self.lib.path_formats = [("default", "$title")] p = self.i.destination() assert b"?" not in p # We use UTF-8 to encode Windows paths now. assert "h\u0259d".encode() in p finally: sys.getfilesystemencoding = oldfunc def test_unicode_extension_in_fragment(self): self.lib.path_formats = [("default", "foo")] self.i.path = util.bytestring_path("bar.caf\xe9") dest = self.i.destination(platform="linux", fragment=True) assert dest == "foo.caf\xe9" def test_asciify_and_replace(self): config["asciify_paths"] = True self.lib.replacements = [(re.compile('"'), "q")] self.lib.directory = b"lib" self.lib.path_formats = [("default", "$title")] self.i.title = "\u201c\u00f6\u2014\u00cf\u201d" assert self.i.destination() == np("lib/qo--Iq") def test_asciify_character_expanding_to_slash(self): config["asciify_paths"] = True self.lib.directory = b"lib" self.lib.path_formats = [("default", "$title")] self.i.title = "ab\xa2\xbdd" assert self.i.destination() == np("lib/abC_ 1_2d") def test_destination_with_replacements(self): self.lib.directory = b"base" self.lib.replacements = [(re.compile(r"a"), "e")] self.lib.path_formats = [("default", "$album/$title")] self.i.title = "foo" self.i.album = "bar" assert self.i.destination() == np("base/ber/foo") def test_destination_with_replacements_argument(self): self.lib.directory = b"base" self.lib.replacements = [(re.compile(r"a"), "f")] self.lib.path_formats = [("default", "$album/$title")] self.i.title = "foo" self.i.album = "bar" replacements = [(re.compile(r"a"), "e")] assert self.i.destination(replacements=replacements) == np( "base/ber/foo" ) @unittest.skip("unimplemented: #359") def test_destination_with_empty_component(self): self.lib.directory = b"base" self.lib.replacements = [(re.compile(r"^$"), "_")] self.lib.path_formats = [("default", "$album/$artist/$title")] self.i.title = "three" self.i.artist = "" self.i.albumartist = "" self.i.album = "one" assert self.i.destination() == np("base/one/_/three") @unittest.skip("unimplemented: #359") def test_destination_with_empty_final_component(self): self.lib.directory = b"base" self.lib.replacements = [(re.compile(r"^$"), "_")] self.lib.path_formats = [("default", "$album/$title")] self.i.title = "" self.i.album = "one" self.i.path = "foo.mp3" assert self.i.destination() == np("base/one/_.mp3") def test_legalize_path_one_for_one_replacement(self): # Use a replacement that should always replace the last X in any # path component with a Z. self.lib.replacements = [ (re.compile(r"X$"), "Z"), ] # Construct an item whose untruncated path ends with a Y but whose # truncated version ends with an X. self.i.title = "X" * 300 + "Y" # The final path should reflect the replacement. dest = self.i.destination() assert dest[-2:] == b"XZ" def test_legalize_path_one_for_many_replacement(self): # Use a replacement that should always replace the last X in any # path component with four Zs. self.lib.replacements = [ (re.compile(r"X$"), "ZZZZ"), ] # Construct an item whose untruncated path ends with a Y but whose # truncated version ends with an X. self.i.title = "X" * 300 + "Y" # The final path should ignore the user replacement and create a path # of the correct length, containing Xs. dest = self.i.destination() assert dest[-2:] == b"XX" def test_album_field_query(self): self.lib.directory = b"one" self.lib.path_formats = [("default", "two"), ("flex:foo", "three")] album = self.lib.add_album([self.i]) assert self.i.destination() == np("one/two") album["flex"] = "foo" album.store() assert self.i.destination() == np("one/three") def test_album_field_in_template(self): self.lib.directory = b"one" self.lib.path_formats = [("default", "$flex/two")] album = self.lib.add_album([self.i]) album["flex"] = "foo" album.store() assert self.i.destination() == np("one/foo/two") class ItemFormattedMappingTest(ItemInDBTestCase): def test_formatted_item_value(self): formatted = self.i.formatted() assert formatted["artist"] == "the artist" def test_get_unset_field(self): formatted = self.i.formatted() with pytest.raises(KeyError): formatted["other_field"] def test_get_method_with_default(self): formatted = self.i.formatted() assert formatted.get("other_field") == "" def test_get_method_with_specified_default(self): formatted = self.i.formatted() assert formatted.get("other_field", "default") == "default" def test_item_precedence(self): album = self.lib.add_album([self.i]) album["artist"] = "foo" album.store() assert "foo" != self.i.formatted().get("artist") def test_album_flex_field(self): album = self.lib.add_album([self.i]) album["flex"] = "foo" album.store() assert "foo" == self.i.formatted().get("flex") def test_album_field_overrides_item_field_for_path(self): # Make the album inconsistent with the item. album = self.lib.add_album([self.i]) album.album = "foo" album.store() self.i.album = "bar" self.i.store() # Ensure the album takes precedence. formatted = self.i.formatted(for_path=True) assert formatted["album"] == "foo" def test_artist_falls_back_to_albumartist(self): self.i.artist = "" formatted = self.i.formatted() assert formatted["artist"] == "the album artist" def test_albumartist_falls_back_to_artist(self): self.i.albumartist = "" formatted = self.i.formatted() assert formatted["albumartist"] == "the artist" def test_both_artist_and_albumartist_empty(self): self.i.artist = "" self.i.albumartist = "" formatted = self.i.formatted() assert formatted["albumartist"] == "" class PathFormattingMixin: """Utilities for testing path formatting.""" def _setf(self, fmt): self.lib.path_formats.insert(0, ("default", fmt)) def _assert_dest(self, dest, i=None): if i is None: i = self.i with _common.platform_posix(): actual = i.destination() assert actual == dest class DestinationFunctionTest(BeetsTestCase, PathFormattingMixin): def setUp(self): super().setUp() self.lib.directory = b"/base" self.lib.path_formats = [("default", "path")] self.i = item(self.lib) def test_upper_case_literal(self): self._setf("%upper{foo}") self._assert_dest(b"/base/FOO") def test_upper_case_variable(self): self._setf("%upper{$title}") self._assert_dest(b"/base/THE TITLE") def test_capitalize_variable(self): self._setf("%capitalize{$title}") self._assert_dest(b"/base/The title") def test_title_case_variable(self): self._setf("%title{$title}") self._assert_dest(b"/base/The Title") def test_title_case_variable_aphostrophe(self): self._setf("%title{I can't}") self._assert_dest(b"/base/I Can't") def test_asciify_variable(self): self._setf("%asciify{ab\xa2\xbdd}") self._assert_dest(b"/base/abC_ 1_2d") def test_left_variable(self): self._setf("%left{$title, 3}") self._assert_dest(b"/base/the") def test_right_variable(self): self._setf("%right{$title,3}") self._assert_dest(b"/base/tle") def test_if_false(self): self._setf("x%if{,foo}") self._assert_dest(b"/base/x") def test_if_false_value(self): self._setf("x%if{false,foo}") self._assert_dest(b"/base/x") def test_if_true(self): self._setf("%if{bar,foo}") self._assert_dest(b"/base/foo") def test_if_else_false(self): self._setf("%if{,foo,baz}") self._assert_dest(b"/base/baz") def test_if_else_false_value(self): self._setf("%if{false,foo,baz}") self._assert_dest(b"/base/baz") def test_if_int_value(self): self._setf("%if{0,foo,baz}") self._assert_dest(b"/base/baz") def test_nonexistent_function(self): self._setf("%foo{bar}") self._assert_dest(b"/base/%foo{bar}") def test_if_def_field_return_self(self): self.i.bar = 3 self._setf("%ifdef{bar}") self._assert_dest(b"/base/3") def test_if_def_field_not_defined(self): self._setf(" %ifdef{bar}/$artist") self._assert_dest(b"/base/the artist") def test_if_def_field_not_defined_2(self): self._setf("$artist/%ifdef{bar}") self._assert_dest(b"/base/the artist") def test_if_def_true(self): self._setf("%ifdef{artist,cool}") self._assert_dest(b"/base/cool") def test_if_def_true_complete(self): self.i.series = "Now" self._setf("%ifdef{series,$series Series,Albums}/$album") self._assert_dest(b"/base/Now Series/the album") def test_if_def_false_complete(self): self._setf("%ifdef{plays,$plays,not_played}") self._assert_dest(b"/base/not_played") def test_first(self): self.i.genres = "Pop; Rock; Classical Crossover" self._setf("%first{$genres}") self._assert_dest(b"/base/Pop") def test_first_skip(self): self.i.genres = "Pop; Rock; Classical Crossover" self._setf("%first{$genres,1,2}") self._assert_dest(b"/base/Classical Crossover") def test_first_different_sep(self): self._setf("%first{Alice / Bob / Eve,2,0, / , & }") self._assert_dest(b"/base/Alice & Bob") class DisambiguationTest(BeetsTestCase, PathFormattingMixin): def setUp(self): super().setUp() self.lib.directory = b"/base" self.lib.path_formats = [("default", "path")] self.i1 = item() self.i1.year = 2001 self.lib.add_album([self.i1]) self.i2 = item() self.i2.year = 2002 self.lib.add_album([self.i2]) self.lib._connection().commit() self._setf("foo%aunique{albumartist album,year}/$title") def test_unique_expands_to_disambiguating_year(self): self._assert_dest(b"/base/foo [2001]/the title", self.i1) def test_unique_with_default_arguments_uses_albumtype(self): album2 = self.lib.get_album(self.i1) album2.albumtype = "bar" album2.store() self._setf("foo%aunique{}/$title") self._assert_dest(b"/base/foo [bar]/the title", self.i1) def test_unique_expands_to_nothing_for_distinct_albums(self): album2 = self.lib.get_album(self.i2) album2.album = "different album" album2.store() self._assert_dest(b"/base/foo/the title", self.i1) def test_use_fallback_numbers_when_identical(self): album2 = self.lib.get_album(self.i2) album2.year = 2001 album2.store() self._assert_dest(b"/base/foo [1]/the title", self.i1) self._assert_dest(b"/base/foo [2]/the title", self.i2) def test_unique_falls_back_to_second_distinguishing_field(self): self._setf("foo%aunique{albumartist album,month year}/$title") self._assert_dest(b"/base/foo [2001]/the title", self.i1) def test_unique_sanitized(self): album2 = self.lib.get_album(self.i2) album2.year = 2001 album1 = self.lib.get_album(self.i1) album1.albumtype = "foo/bar" album2.store() album1.store() self._setf("foo%aunique{albumartist album,albumtype}/$title") self._assert_dest(b"/base/foo [foo_bar]/the title", self.i1) def test_drop_empty_disambig_string(self): album1 = self.lib.get_album(self.i1) album1.albumdisambig = None album2 = self.lib.get_album(self.i2) album2.albumdisambig = "foo" album1.store() album2.store() self._setf("foo%aunique{albumartist album,albumdisambig}/$title") self._assert_dest(b"/base/foo/the title", self.i1) def test_change_brackets(self): self._setf("foo%aunique{albumartist album,year,()}/$title") self._assert_dest(b"/base/foo (2001)/the title", self.i1) def test_remove_brackets(self): self._setf("foo%aunique{albumartist album,year,}/$title") self._assert_dest(b"/base/foo 2001/the title", self.i1) def test_key_flexible_attribute(self): album1 = self.lib.get_album(self.i1) album1.flex = "flex1" album2 = self.lib.get_album(self.i2) album2.flex = "flex2" album1.store() album2.store() self._setf("foo%aunique{albumartist album flex,year}/$title") self._assert_dest(b"/base/foo/the title", self.i1) class SingletonDisambiguationTest(BeetsTestCase, PathFormattingMixin): def setUp(self): super().setUp() self.lib.directory = b"/base" self.lib.path_formats = [("default", "path")] self.i1 = item() self.i1.year = 2001 self.lib.add(self.i1) self.i2 = item() self.i2.year = 2002 self.lib.add(self.i2) self.lib._connection().commit() self._setf("foo/$title%sunique{artist title,year}") def test_sunique_expands_to_disambiguating_year(self): self._assert_dest(b"/base/foo/the title [2001]", self.i1) def test_sunique_with_default_arguments_uses_trackdisambig(self): self.i1.trackdisambig = "live version" self.i1.year = self.i2.year self.i1.store() self._setf("foo/$title%sunique{}") self._assert_dest(b"/base/foo/the title [live version]", self.i1) def test_sunique_expands_to_nothing_for_distinct_singletons(self): self.i2.title = "different track" self.i2.store() self._assert_dest(b"/base/foo/the title", self.i1) def test_sunique_does_not_match_album(self): self.lib.add_album([self.i2]) self._assert_dest(b"/base/foo/the title", self.i1) def test_sunique_use_fallback_numbers_when_identical(self): self.i2.year = self.i1.year self.i2.store() self._assert_dest(b"/base/foo/the title [1]", self.i1) self._assert_dest(b"/base/foo/the title [2]", self.i2) def test_sunique_falls_back_to_second_distinguishing_field(self): self._setf("foo/$title%sunique{albumartist album,month year}") self._assert_dest(b"/base/foo/the title [2001]", self.i1) def test_sunique_sanitized(self): self.i2.year = self.i1.year self.i1.trackdisambig = "foo/bar" self.i2.store() self.i1.store() self._setf("foo/$title%sunique{artist title,trackdisambig}") self._assert_dest(b"/base/foo/the title [foo_bar]", self.i1) def test_drop_empty_disambig_string(self): self.i1.trackdisambig = None self.i2.trackdisambig = "foo" self.i1.store() self.i2.store() self._setf("foo/$title%sunique{albumartist album,trackdisambig}") self._assert_dest(b"/base/foo/the title", self.i1) def test_change_brackets(self): self._setf("foo/$title%sunique{artist title,year,()}") self._assert_dest(b"/base/foo/the title (2001)", self.i1) def test_remove_brackets(self): self._setf("foo/$title%sunique{artist title,year,}") self._assert_dest(b"/base/foo/the title 2001", self.i1) def test_key_flexible_attribute(self): self.i1.flex = "flex1" self.i2.flex = "flex2" self.i1.store() self.i2.store() self._setf("foo/$title%sunique{artist title flex,year}") self._assert_dest(b"/base/foo/the title", self.i1) class PluginDestinationTest(BeetsTestCase): def setUp(self): super().setUp() # Mock beets.plugins.item_field_getters. self._tv_map = {} def field_getters(): getters = {} for key, value in self._tv_map.items(): getters[key] = lambda _: value return getters self.old_field_getters = plugins.item_field_getters plugins.item_field_getters = field_getters self.lib.directory = b"/base" self.lib.path_formats = [("default", "$artist $foo")] self.i = item(self.lib) def tearDown(self): super().tearDown() plugins.item_field_getters = self.old_field_getters def _assert_dest(self, dest): with _common.platform_posix(): the_dest = self.i.destination() assert the_dest == b"/base/" + dest def test_undefined_value_not_substituted(self): self._assert_dest(b"the artist $foo") def test_plugin_value_not_substituted(self): self._tv_map = { "foo": "bar", } self._assert_dest(b"the artist bar") def test_plugin_value_overrides_attribute(self): self._tv_map = { "artist": "bar", } self._assert_dest(b"bar $foo") def test_plugin_value_sanitized(self): self._tv_map = { "foo": "bar/baz", } self._assert_dest(b"the artist bar_baz") class AlbumInfoTest(BeetsTestCase): def setUp(self): super().setUp() self.i = item() self.lib.add_album((self.i,)) def test_albuminfo_reflects_metadata(self): ai = self.lib.get_album(self.i) assert ai.mb_albumartistid == self.i.mb_albumartistid assert ai.albumartist == self.i.albumartist assert ai.album == self.i.album assert ai.year == self.i.year def test_albuminfo_stores_art(self): ai = self.lib.get_album(self.i) ai.artpath = "/my/great/art" ai.store() new_ai = self.lib.get_album(self.i) assert new_ai.artpath == b"/my/great/art" def test_albuminfo_for_two_items_doesnt_duplicate_row(self): i2 = item(self.lib) self.lib.get_album(self.i) self.lib.get_album(i2) c = self.lib._connection().cursor() c.execute("select * from albums where album=?", (self.i.album,)) # Cursor should only return one row. assert c.fetchone() is not None assert c.fetchone() is None def test_individual_tracks_have_no_albuminfo(self): i2 = item() i2.album = "aTotallyDifferentAlbum" self.lib.add(i2) ai = self.lib.get_album(i2) assert ai is None def test_get_album_by_id(self): ai = self.lib.get_album(self.i) ai = self.lib.get_album(self.i.id) assert ai is not None def test_album_items_consistent(self): ai = self.lib.get_album(self.i) for i in ai.items(): if i.id == self.i.id: break else: self.fail("item not found") def test_albuminfo_changes_affect_items(self): ai = self.lib.get_album(self.i) ai.album = "myNewAlbum" ai.store() i = self.lib.items()[0] assert i.album == "myNewAlbum" def test_albuminfo_change_albumartist_changes_items(self): ai = self.lib.get_album(self.i) ai.albumartist = "myNewArtist" ai.store() i = self.lib.items()[0] assert i.albumartist == "myNewArtist" assert i.artist != "myNewArtist" def test_albuminfo_change_artist_does_change_items(self): ai = self.lib.get_album(self.i) ai.artist = "myNewArtist" ai.store(inherit=True) i = self.lib.items()[0] assert i.artist == "myNewArtist" def test_albuminfo_change_artist_does_not_change_items(self): ai = self.lib.get_album(self.i) ai.artist = "myNewArtist" ai.store(inherit=False) i = self.lib.items()[0] assert i.artist != "myNewArtist" def test_albuminfo_remove_removes_items(self): item_id = self.i.id self.lib.get_album(self.i).remove() c = self.lib._connection().execute( "SELECT id FROM items WHERE id=?", (item_id,) ) assert c.fetchone() is None def test_removing_last_item_removes_album(self): assert len(self.lib.albums()) == 1 self.i.remove() assert len(self.lib.albums()) == 0 def test_noop_albuminfo_changes_affect_items(self): i = self.lib.items()[0] i.album = "foobar" i.store() ai = self.lib.get_album(self.i) ai.album = ai.album ai.store() i = self.lib.items()[0] assert i.album == ai.album class ArtDestinationTest(BeetsTestCase): def setUp(self): super().setUp() config["art_filename"] = "artimage" config["replace"] = {"X": "Y"} self.lib.replacements = [(re.compile("X"), "Y")] self.i = item(self.lib) self.i.path = self.i.destination() self.ai = self.lib.add_album((self.i,)) def test_art_filename_respects_setting(self): art = self.ai.art_destination("something.jpg") new_art = bytestring_path("%sartimage.jpg" % os.path.sep) assert new_art in art def test_art_path_in_item_dir(self): art = self.ai.art_destination("something.jpg") track = self.i.destination() assert os.path.dirname(art) == os.path.dirname(track) def test_art_path_sanitized(self): config["art_filename"] = "artXimage" art = self.ai.art_destination("something.jpg") assert b"artYimage" in art class PathStringTest(BeetsTestCase): def setUp(self): super().setUp() self.i = item(self.lib) def test_item_path_is_bytestring(self): assert isinstance(self.i.path, bytes) def test_fetched_item_path_is_bytestring(self): i = list(self.lib.items())[0] assert isinstance(i.path, bytes) def test_unicode_path_becomes_bytestring(self): self.i.path = "unicodepath" assert isinstance(self.i.path, bytes) def test_unicode_in_database_becomes_bytestring(self): self.lib._connection().execute( """ update items set path=? where id=? """, (self.i.id, "somepath"), ) i = list(self.lib.items())[0] assert isinstance(i.path, bytes) def test_special_chars_preserved_in_database(self): path = "b\xe1r".encode() self.i.path = path self.i.store() i = list(self.lib.items())[0] assert i.path == path def test_special_char_path_added_to_database(self): self.i.remove() path = "b\xe1r".encode() i = item() i.path = path self.lib.add(i) i = list(self.lib.items())[0] assert i.path == path def test_destination_returns_bytestring(self): self.i.artist = "b\xe1r" dest = self.i.destination() assert isinstance(dest, bytes) def test_art_destination_returns_bytestring(self): self.i.artist = "b\xe1r" alb = self.lib.add_album([self.i]) dest = alb.art_destination("image.jpg") assert isinstance(dest, bytes) def test_artpath_stores_special_chars(self): path = b"b\xe1r" alb = self.lib.add_album([self.i]) alb.artpath = path alb.store() alb = self.lib.get_album(self.i) assert path == alb.artpath def test_sanitize_path_with_special_chars(self): path = "b\xe1r?" new_path = util.sanitize_path(path) assert new_path.startswith("b\xe1r") def test_sanitize_path_returns_unicode(self): path = "b\xe1r?" new_path = util.sanitize_path(path) assert isinstance(new_path, str) def test_unicode_artpath_becomes_bytestring(self): alb = self.lib.add_album([self.i]) alb.artpath = "somep\xe1th" assert isinstance(alb.artpath, bytes) def test_unicode_artpath_in_database_decoded(self): alb = self.lib.add_album([self.i]) self.lib._connection().execute( "update albums set artpath=? where id=?", ("somep\xe1th", alb.id) ) alb = self.lib.get_album(alb.id) assert isinstance(alb.artpath, bytes) class MtimeTest(BeetsTestCase): def setUp(self): super().setUp() self.ipath = os.path.join(self.temp_dir, b"testfile.mp3") shutil.copy( syspath(os.path.join(_common.RSRC, b"full.mp3")), syspath(self.ipath), ) self.i = beets.library.Item.from_path(self.ipath) self.lib.add(self.i) def tearDown(self): super().tearDown() if os.path.exists(self.ipath): os.remove(self.ipath) def _mtime(self): return int(os.path.getmtime(self.ipath)) def test_mtime_initially_up_to_date(self): assert self.i.mtime >= self._mtime() def test_mtime_reset_on_db_modify(self): self.i.title = "something else" assert self.i.mtime < self._mtime() def test_mtime_up_to_date_after_write(self): self.i.title = "something else" self.i.write() assert self.i.mtime >= self._mtime() def test_mtime_up_to_date_after_read(self): self.i.title = "something else" self.i.read() assert self.i.mtime >= self._mtime() class ImportTimeTest(BeetsTestCase): def added(self): self.track = item() self.album = self.lib.add_album((self.track,)) assert self.album.added > 0 assert self.track.added > 0 def test_atime_for_singleton(self): self.singleton = item(self.lib) assert self.singleton.added > 0 class TemplateTest(ItemInDBTestCase): def test_year_formatted_in_template(self): self.i.year = 123 self.i.store() assert self.i.evaluate_template("$year") == "0123" def test_album_flexattr_appears_in_item_template(self): self.album = self.lib.add_album([self.i]) self.album.foo = "baz" self.album.store() assert self.i.evaluate_template("$foo") == "baz" def test_album_and_item_format(self): config["format_album"] = "foö $foo" album = beets.library.Album() album.foo = "bar" album.tagada = "togodo" assert f"{album}" == "foö bar" assert f"{album:$tagada}" == "togodo" assert str(album) == "foö bar" assert bytes(album) == b"fo\xc3\xb6 bar" config["format_item"] = "bar $foo" item = beets.library.Item() item.foo = "bar" item.tagada = "togodo" assert f"{item}" == "bar bar" assert f"{item:$tagada}" == "togodo" class UnicodePathTest(ItemInDBTestCase): def test_unicode_path(self): self.i.path = os.path.join(_common.RSRC, "unicode\u2019d.mp3".encode()) # If there are any problems with unicode paths, we will raise # here and fail. self.i.read() self.i.write() class WriteTest(BeetsTestCase): def test_write_nonexistant(self): item = self.create_item() item.path = b"/path/does/not/exist" with pytest.raises(beets.library.ReadError): item.write() def test_no_write_permission(self): item = self.add_item_fixture() path = syspath(item.path) os.chmod(path, stat.S_IRUSR) try: with pytest.raises(beets.library.WriteError): item.write() finally: # Restore write permissions so the file can be cleaned up. os.chmod(path, stat.S_IRUSR | stat.S_IWUSR) def test_write_with_custom_path(self): item = self.add_item_fixture() custom_path = os.path.join(self.temp_dir, b"custom.mp3") shutil.copy(syspath(item.path), syspath(custom_path)) item["artist"] = "new artist" assert MediaFile(syspath(custom_path)).artist != "new artist" assert MediaFile(syspath(item.path)).artist != "new artist" item.write(custom_path) assert MediaFile(syspath(custom_path)).artist == "new artist" assert MediaFile(syspath(item.path)).artist != "new artist" def test_write_custom_tags(self): item = self.add_item_fixture(artist="old artist") item.write(tags={"artist": "new artist"}) assert item.artist != "new artist" assert MediaFile(syspath(item.path)).artist == "new artist" def test_write_multi_tags(self): item = self.add_item_fixture(artist="old artist") item.write(tags={"artists": ["old artist", "another artist"]}) assert MediaFile(syspath(item.path)).artists == [ "old artist", "another artist", ] def test_write_multi_tags_id3v23(self): item = self.add_item_fixture(artist="old artist") item.write( tags={"artists": ["old artist", "another artist"]}, id3v23=True ) assert MediaFile(syspath(item.path)).artists == [ "old artist/another artist" ] def test_write_date_field(self): # Since `date` is not a MediaField, this should do nothing. item = self.add_item_fixture() clean_year = item.year item.date = "foo" item.write() assert MediaFile(syspath(item.path)).year == clean_year class ItemReadTest(unittest.TestCase): def test_unreadable_raise_read_error(self): unreadable = os.path.join(_common.RSRC, b"image-2x3.png") item = beets.library.Item() with pytest.raises(beets.library.ReadError) as exc_info: item.read(unreadable) assert isinstance(exc_info.value.reason, UnreadableFileError) def test_nonexistent_raise_read_error(self): item = beets.library.Item() with pytest.raises(beets.library.ReadError): item.read("/thisfiledoesnotexist") class FilesizeTest(BeetsTestCase): def test_filesize(self): item = self.add_item_fixture() assert item.filesize != 0 def test_nonexistent_file(self): item = beets.library.Item() assert item.filesize == 0 class ParseQueryTest(unittest.TestCase): def test_parse_invalid_query_string(self): with pytest.raises(beets.dbcore.query.ParsingError): beets.library.parse_query_string('foo"', None) def test_parse_bytes(self): with pytest.raises(AssertionError): beets.library.parse_query_string(b"query", None) class LibraryFieldTypesTest(unittest.TestCase): """Test format() and parse() for library-specific field types""" def test_datetype(self): t = beets.library.DateType() # format time_format = beets.config["time_format"].as_str() time_local = time.strftime(time_format, time.localtime(123456789)) assert time_local == t.format(123456789) # parse assert 123456789.0 == t.parse(time_local) assert 123456789.0 == t.parse("123456789.0") assert t.null == t.parse("not123456789.0") assert t.null == t.parse("1973-11-29") def test_pathtype(self): t = beets.library.PathType() # format assert "/tmp" == t.format("/tmp") assert "/tmp/\xe4lbum" == t.format("/tmp/\u00e4lbum") # parse assert np(b"/tmp") == t.parse("/tmp") assert np(b"/tmp/\xc3\xa4lbum") == t.parse("/tmp/\u00e4lbum/") def test_musicalkey(self): t = beets.library.MusicalKey() # parse assert "C#m" == t.parse("c#m") assert "Gm" == t.parse("g minor") assert "Not c#m" == t.parse("not C#m") def test_durationtype(self): t = beets.library.DurationType() # format assert "1:01" == t.format(61.23) assert "60:01" == t.format(3601.23) assert "0:00" == t.format(None) # parse assert 61.0 == t.parse("1:01") assert 61.23 == t.parse("61.23") assert 3601.0 == t.parse("60:01") assert t.null == t.parse("1:00:01") assert t.null == t.parse("not61.23") # config format_raw_length beets.config["format_raw_length"] = True assert 61.23 == t.format(61.23) assert 3601.23 == t.format(3601.23) beetbox-beets-01f1faf/test/test_logging.py000066400000000000000000000220071472325477400207660ustar00rootroot00000000000000"""Stupid tests that ensure logging works as expected""" import logging as log import sys import threading from io import StringIO import beets.logging as blog import beetsplug from beets import plugins, ui from beets.test import _common, helper from beets.test.helper import ( AsIsImporterMixin, BeetsTestCase, ImportTestCase, PluginMixin, ) class LoggingTest(BeetsTestCase): def test_logging_management(self): l1 = log.getLogger("foo123") l2 = blog.getLogger("foo123") assert l1 == l2 assert l1.__class__ == log.Logger l3 = blog.getLogger("bar123") l4 = log.getLogger("bar123") assert l3 == l4 assert l3.__class__ == blog.BeetsLogger assert isinstance( l3, (blog.StrFormatLogger, blog.ThreadLocalLevelLogger) ) l5 = l3.getChild("shalala") assert l5.__class__ == blog.BeetsLogger l6 = blog.getLogger() assert l1 != l6 def test_str_format_logging(self): logger = blog.getLogger("baz123") stream = StringIO() handler = log.StreamHandler(stream) logger.addHandler(handler) logger.propagate = False logger.warning("foo {0} {bar}", "oof", bar="baz") handler.flush() assert stream.getvalue(), "foo oof baz" class LoggingLevelTest(AsIsImporterMixin, PluginMixin, ImportTestCase): plugin = "dummy" class DummyModule: class DummyPlugin(plugins.BeetsPlugin): def __init__(self): plugins.BeetsPlugin.__init__(self, "dummy") self.import_stages = [self.import_stage] self.register_listener("dummy_event", self.listener) def log_all(self, name): self._log.debug("debug " + name) self._log.info("info " + name) self._log.warning("warning " + name) def commands(self): cmd = ui.Subcommand("dummy") cmd.func = lambda _, __, ___: self.log_all("cmd") return (cmd,) def import_stage(self, session, task): self.log_all("import_stage") def listener(self): self.log_all("listener") def setUp(self): sys.modules["beetsplug.dummy"] = self.DummyModule beetsplug.dummy = self.DummyModule super().setUp() def test_command_level0(self): self.config["verbose"] = 0 with helper.capture_log() as logs: self.run_command("dummy") assert "dummy: warning cmd" in logs assert "dummy: info cmd" in logs assert "dummy: debug cmd" not in logs def test_command_level1(self): self.config["verbose"] = 1 with helper.capture_log() as logs: self.run_command("dummy") assert "dummy: warning cmd" in logs assert "dummy: info cmd" in logs assert "dummy: debug cmd" in logs def test_command_level2(self): self.config["verbose"] = 2 with helper.capture_log() as logs: self.run_command("dummy") assert "dummy: warning cmd" in logs assert "dummy: info cmd" in logs assert "dummy: debug cmd" in logs def test_listener_level0(self): self.config["verbose"] = 0 with helper.capture_log() as logs: plugins.send("dummy_event") assert "dummy: warning listener" in logs assert "dummy: info listener" not in logs assert "dummy: debug listener" not in logs def test_listener_level1(self): self.config["verbose"] = 1 with helper.capture_log() as logs: plugins.send("dummy_event") assert "dummy: warning listener" in logs assert "dummy: info listener" in logs assert "dummy: debug listener" not in logs def test_listener_level2(self): self.config["verbose"] = 2 with helper.capture_log() as logs: plugins.send("dummy_event") assert "dummy: warning listener" in logs assert "dummy: info listener" in logs assert "dummy: debug listener" in logs def test_import_stage_level0(self): self.config["verbose"] = 0 with helper.capture_log() as logs: self.run_asis_importer() assert "dummy: warning import_stage" in logs assert "dummy: info import_stage" not in logs assert "dummy: debug import_stage" not in logs def test_import_stage_level1(self): self.config["verbose"] = 1 with helper.capture_log() as logs: self.run_asis_importer() assert "dummy: warning import_stage" in logs assert "dummy: info import_stage" in logs assert "dummy: debug import_stage" not in logs def test_import_stage_level2(self): self.config["verbose"] = 2 with helper.capture_log() as logs: self.run_asis_importer() assert "dummy: warning import_stage" in logs assert "dummy: info import_stage" in logs assert "dummy: debug import_stage" in logs @_common.slow_test() class ConcurrentEventsTest(AsIsImporterMixin, ImportTestCase): """Similar to LoggingLevelTest but lower-level and focused on multiple events interaction. Since this is a bit heavy we don't do it in LoggingLevelTest. """ db_on_disk = True class DummyPlugin(plugins.BeetsPlugin): def __init__(self, test_case): plugins.BeetsPlugin.__init__(self, "dummy") self.register_listener("dummy_event1", self.listener1) self.register_listener("dummy_event2", self.listener2) self.lock1 = threading.Lock() self.lock2 = threading.Lock() self.test_case = test_case self.exc = None self.t1_step = self.t2_step = 0 def log_all(self, name): self._log.debug("debug " + name) self._log.info("info " + name) self._log.warning("warning " + name) def listener1(self): try: assert self._log.level == log.INFO self.t1_step = 1 self.lock1.acquire() assert self._log.level == log.INFO self.t1_step = 2 except Exception as e: self.exc = e def listener2(self): try: assert self._log.level == log.DEBUG self.t2_step = 1 self.lock2.acquire() assert self._log.level == log.DEBUG self.t2_step = 2 except Exception as e: self.exc = e def test_concurrent_events(self): dp = self.DummyPlugin(self) def check_dp_exc(): if dp.exc: raise dp.exc try: dp.lock1.acquire() dp.lock2.acquire() assert dp._log.level == log.NOTSET self.config["verbose"] = 1 t1 = threading.Thread(target=dp.listeners["dummy_event1"][0]) t1.start() # blocked. t1 tested its log level while dp.t1_step != 1: check_dp_exc() assert t1.is_alive() assert dp._log.level == log.NOTSET self.config["verbose"] = 2 t2 = threading.Thread(target=dp.listeners["dummy_event2"][0]) t2.start() # blocked. t2 tested its log level while dp.t2_step != 1: check_dp_exc() assert t2.is_alive() assert dp._log.level == log.NOTSET dp.lock1.release() # dummy_event1 tests its log level + finishes while dp.t1_step != 2: check_dp_exc() t1.join(0.1) assert not t1.is_alive() assert t2.is_alive() assert dp._log.level == log.NOTSET dp.lock2.release() # dummy_event2 tests its log level + finishes while dp.t2_step != 2: check_dp_exc() t2.join(0.1) assert not t2.is_alive() except Exception: print("Alive threads:", threading.enumerate()) if dp.lock1.locked(): print("Releasing lock1 after exception in test") dp.lock1.release() if dp.lock2.locked(): print("Releasing lock2 after exception in test") dp.lock2.release() print("Alive threads:", threading.enumerate()) raise def test_root_logger_levels(self): """Root logger level should be shared between threads.""" self.config["threaded"] = True blog.getLogger("beets").set_global_level(blog.WARNING) with helper.capture_log() as logs: self.run_asis_importer() assert logs == [] blog.getLogger("beets").set_global_level(blog.INFO) with helper.capture_log() as logs: self.run_asis_importer() for line in logs: assert "import" in line assert "album" in line blog.getLogger("beets").set_global_level(blog.DEBUG) with helper.capture_log() as logs: self.run_asis_importer() assert "Sending event: database_change" in logs beetbox-beets-01f1faf/test/test_m3ufile.py000066400000000000000000000125221472325477400207050ustar00rootroot00000000000000# This file is part of beets. # Copyright 2022, J0J0 Todos. # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the # "Software"), to deal in the Software without restriction, including # without limitation the rights to use, copy, modify, merge, publish, # distribute, sublicense, and/or sell copies of the Software, and to # permit persons to whom the Software is furnished to do so, subject to # the following conditions: # # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. """Testsuite for the M3UFile class.""" import sys import unittest from os import path from shutil import rmtree from tempfile import mkdtemp import pytest from beets.test._common import RSRC from beets.util import bytestring_path from beets.util.m3u import EmptyPlaylistError, M3UFile class M3UFileTest(unittest.TestCase): """Tests the M3UFile class.""" def test_playlist_write_empty(self): """Test whether saving an empty playlist file raises an error.""" tempdir = bytestring_path(mkdtemp()) the_playlist_file = path.join(tempdir, b"playlist.m3u8") m3ufile = M3UFile(the_playlist_file) with pytest.raises(EmptyPlaylistError): m3ufile.write() rmtree(tempdir) def test_playlist_write(self): """Test saving ascii paths to a playlist file.""" tempdir = bytestring_path(mkdtemp()) the_playlist_file = path.join(tempdir, b"playlist.m3u") m3ufile = M3UFile(the_playlist_file) m3ufile.set_contents( [ bytestring_path("/This/is/a/path/to_a_file.mp3"), bytestring_path("/This/is/another/path/to_a_file.mp3"), ] ) m3ufile.write() assert path.exists(the_playlist_file) rmtree(tempdir) def test_playlist_write_unicode(self): """Test saving unicode paths to a playlist file.""" tempdir = bytestring_path(mkdtemp()) the_playlist_file = path.join(tempdir, b"playlist.m3u8") m3ufile = M3UFile(the_playlist_file) m3ufile.set_contents( [ bytestring_path("/This/is/ĂĄ/path/to_a_file.mp3"), bytestring_path("/This/is/another/path/tö_a_file.mp3"), ] ) m3ufile.write() assert path.exists(the_playlist_file) rmtree(tempdir) @unittest.skipUnless(sys.platform == "win32", "win32") def test_playlist_write_and_read_unicode_windows(self): """Test saving unicode paths to a playlist file on Windows.""" tempdir = bytestring_path(mkdtemp()) the_playlist_file = path.join( tempdir, b"playlist_write_and_read_windows.m3u8" ) m3ufile = M3UFile(the_playlist_file) m3ufile.set_contents( [ bytestring_path(r"x:\This\is\ĂĄ\path\to_a_file.mp3"), bytestring_path(r"x:\This\is\another\path\tö_a_file.mp3"), ] ) m3ufile.write() assert path.exists(the_playlist_file) m3ufile_read = M3UFile(the_playlist_file) m3ufile_read.load() assert m3ufile.media_list[0] == bytestring_path( path.join("x:\\", "This", "is", "ĂĄ", "path", "to_a_file.mp3") ) assert m3ufile.media_list[1] == bytestring_path( r"x:\This\is\another\path\tö_a_file.mp3" ), bytestring_path( path.join("x:\\", "This", "is", "another", "path", "tö_a_file.mp3") ) rmtree(tempdir) @unittest.skipIf(sys.platform == "win32", "win32") def test_playlist_load_ascii(self): """Test loading ascii paths from a playlist file.""" the_playlist_file = path.join(RSRC, b"playlist.m3u") m3ufile = M3UFile(the_playlist_file) m3ufile.load() assert m3ufile.media_list[0] == bytestring_path( "/This/is/a/path/to_a_file.mp3" ) @unittest.skipIf(sys.platform == "win32", "win32") def test_playlist_load_unicode(self): """Test loading unicode paths from a playlist file.""" the_playlist_file = path.join(RSRC, b"playlist.m3u8") m3ufile = M3UFile(the_playlist_file) m3ufile.load() assert m3ufile.media_list[0] == bytestring_path( "/This/is/ĂĄ/path/to_a_file.mp3" ) @unittest.skipUnless(sys.platform == "win32", "win32") def test_playlist_load_unicode_windows(self): """Test loading unicode paths from a playlist file.""" the_playlist_file = path.join(RSRC, b"playlist_windows.m3u8") winpath = bytestring_path( path.join("x:\\", "This", "is", "ĂĄ", "path", "to_a_file.mp3") ) m3ufile = M3UFile(the_playlist_file) m3ufile.load() assert m3ufile.media_list[0] == winpath def test_playlist_load_extm3u(self): """Test loading a playlist with an #EXTM3U header.""" the_playlist_file = path.join(RSRC, b"playlist.m3u") m3ufile = M3UFile(the_playlist_file) m3ufile.load() assert m3ufile.extm3u def test_playlist_load_non_extm3u(self): """Test loading a playlist without an #EXTM3U header.""" the_playlist_file = path.join(RSRC, b"playlist_non_ext.m3u") m3ufile = M3UFile(the_playlist_file) m3ufile.load() assert not m3ufile.extm3u beetbox-beets-01f1faf/test/test_mb.py000066400000000000000000001147031472325477400177430ustar00rootroot00000000000000# This file is part of beets. # Copyright 2016, Adrian Sampson. # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the # "Software"), to deal in the Software without restriction, including # without limitation the rights to use, copy, modify, merge, publish, # distribute, sublicense, and/or sell copies of the Software, and to # permit persons to whom the Software is furnished to do so, subject to # the following conditions: # # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. """Tests for MusicBrainz API wrapper.""" from unittest import mock from beets import config from beets.autotag import mb from beets.test.helper import BeetsTestCase class MBAlbumInfoTest(BeetsTestCase): def _make_release( self, date_str="2009", tracks=None, track_length=None, track_artist=False, multi_artist_credit=False, data_tracks=None, medium_format="FORMAT", ): release = { "title": "ALBUM TITLE", "id": "ALBUM ID", "asin": "ALBUM ASIN", "disambiguation": "R_DISAMBIGUATION", "release-group": { "type": "Album", "first-release-date": date_str, "id": "RELEASE GROUP ID", "disambiguation": "RG_DISAMBIGUATION", }, "artist-credit": [ { "artist": { "name": "ARTIST NAME", "id": "ARTIST ID", "sort-name": "ARTIST SORT NAME", }, "name": "ARTIST CREDIT", } ], "date": "3001", "medium-list": [], "label-info-list": [ { "catalog-number": "CATALOG NUMBER", "label": {"name": "LABEL NAME"}, } ], "text-representation": { "script": "SCRIPT", "language": "LANGUAGE", }, "country": "COUNTRY", "status": "STATUS", "barcode": "BARCODE", } if multi_artist_credit: release["artist-credit"].append(" & ") # add join phase release["artist-credit"].append( { "artist": { "name": "ARTIST 2 NAME", "id": "ARTIST 2 ID", "sort-name": "ARTIST 2 SORT NAME", }, "name": "ARTIST MULTI CREDIT", } ) i = 0 track_list = [] if tracks: for recording in tracks: i += 1 track = { "id": "RELEASE TRACK ID %d" % i, "recording": recording, "position": i, "number": "A1", } if track_length: # Track lengths are distinct from recording lengths. track["length"] = track_length if track_artist: # Similarly, track artists can differ from recording # artists. track["artist-credit"] = [ { "artist": { "name": "TRACK ARTIST NAME", "id": "TRACK ARTIST ID", "sort-name": "TRACK ARTIST SORT NAME", }, "name": "TRACK ARTIST CREDIT", } ] if multi_artist_credit: track["artist-credit"].append(" & ") # add join phase track["artist-credit"].append( { "artist": { "name": "TRACK ARTIST 2 NAME", "id": "TRACK ARTIST 2 ID", "sort-name": "TRACK ARTIST 2 SORT NAME", }, "name": "TRACK ARTIST 2 CREDIT", } ) track_list.append(track) data_track_list = [] if data_tracks: for recording in data_tracks: i += 1 data_track = { "id": "RELEASE TRACK ID %d" % i, "recording": recording, "position": i, "number": "A1", } data_track_list.append(data_track) release["medium-list"].append( { "position": "1", "track-list": track_list, "data-track-list": data_track_list, "format": medium_format, "title": "MEDIUM TITLE", } ) return release def _make_track( self, title, tr_id, duration, artist=False, video=False, disambiguation=None, remixer=False, multi_artist_credit=False, ): track = { "title": title, "id": tr_id, } if duration is not None: track["length"] = duration if artist: track["artist-credit"] = [ { "artist": { "name": "RECORDING ARTIST NAME", "id": "RECORDING ARTIST ID", "sort-name": "RECORDING ARTIST SORT NAME", }, "name": "RECORDING ARTIST CREDIT", } ] if multi_artist_credit: track["artist-credit"].append(" & ") # add join phase track["artist-credit"].append( { "artist": { "name": "RECORDING ARTIST 2 NAME", "id": "RECORDING ARTIST 2 ID", "sort-name": "RECORDING ARTIST 2 SORT NAME", }, "name": "RECORDING ARTIST 2 CREDIT", } ) if remixer: track["artist-relation-list"] = [ { "type": "remixer", "type-id": "RELATION TYPE ID", "target": "RECORDING REMIXER ARTIST ID", "direction": "RECORDING RELATION DIRECTION", "artist": { "id": "RECORDING REMIXER ARTIST ID", "type": "RECORDING REMIXER ARTIST TYPE", "name": "RECORDING REMIXER ARTIST NAME", "sort-name": "RECORDING REMIXER ARTIST SORT NAME", }, } ] if video: track["video"] = "true" if disambiguation: track["disambiguation"] = disambiguation return track def test_parse_release_with_year(self): release = self._make_release("1984") d = mb.album_info(release) assert d.album == "ALBUM TITLE" assert d.album_id == "ALBUM ID" assert d.artist == "ARTIST NAME" assert d.artist_id == "ARTIST ID" assert d.original_year == 1984 assert d.year == 3001 assert d.artist_credit == "ARTIST CREDIT" def test_parse_release_type(self): release = self._make_release("1984") d = mb.album_info(release) assert d.albumtype == "album" def test_parse_release_full_date(self): release = self._make_release("1987-03-31") d = mb.album_info(release) assert d.original_year == 1987 assert d.original_month == 3 assert d.original_day == 31 def test_parse_tracks(self): tracks = [ self._make_track("TITLE ONE", "ID ONE", 100.0 * 1000.0), self._make_track("TITLE TWO", "ID TWO", 200.0 * 1000.0), ] release = self._make_release(tracks=tracks) d = mb.album_info(release) t = d.tracks assert len(t) == 2 assert t[0].title == "TITLE ONE" assert t[0].track_id == "ID ONE" assert t[0].length == 100.0 assert t[1].title == "TITLE TWO" assert t[1].track_id == "ID TWO" assert t[1].length == 200.0 def test_parse_track_indices(self): tracks = [ self._make_track("TITLE ONE", "ID ONE", 100.0 * 1000.0), self._make_track("TITLE TWO", "ID TWO", 200.0 * 1000.0), ] release = self._make_release(tracks=tracks) d = mb.album_info(release) t = d.tracks assert t[0].medium_index == 1 assert t[0].index == 1 assert t[1].medium_index == 2 assert t[1].index == 2 def test_parse_medium_numbers_single_medium(self): tracks = [ self._make_track("TITLE ONE", "ID ONE", 100.0 * 1000.0), self._make_track("TITLE TWO", "ID TWO", 200.0 * 1000.0), ] release = self._make_release(tracks=tracks) d = mb.album_info(release) assert d.mediums == 1 t = d.tracks assert t[0].medium == 1 assert t[1].medium == 1 def test_parse_medium_numbers_two_mediums(self): tracks = [ self._make_track("TITLE ONE", "ID ONE", 100.0 * 1000.0), self._make_track("TITLE TWO", "ID TWO", 200.0 * 1000.0), ] release = self._make_release(tracks=[tracks[0]]) second_track_list = [ { "id": "RELEASE TRACK ID 2", "recording": tracks[1], "position": "1", "number": "A1", } ] release["medium-list"].append( { "position": "2", "track-list": second_track_list, } ) d = mb.album_info(release) assert d.mediums == 2 t = d.tracks assert t[0].medium == 1 assert t[0].medium_index == 1 assert t[0].index == 1 assert t[1].medium == 2 assert t[1].medium_index == 1 assert t[1].index == 2 def test_parse_release_year_month_only(self): release = self._make_release("1987-03") d = mb.album_info(release) assert d.original_year == 1987 assert d.original_month == 3 def test_no_durations(self): tracks = [self._make_track("TITLE", "ID", None)] release = self._make_release(tracks=tracks) d = mb.album_info(release) assert d.tracks[0].length is None def test_track_length_overrides_recording_length(self): tracks = [self._make_track("TITLE", "ID", 1.0 * 1000.0)] release = self._make_release(tracks=tracks, track_length=2.0 * 1000.0) d = mb.album_info(release) assert d.tracks[0].length == 2.0 def test_no_release_date(self): release = self._make_release(None) d = mb.album_info(release) assert not d.original_year assert not d.original_month assert not d.original_day def test_various_artists_defaults_false(self): release = self._make_release(None) d = mb.album_info(release) assert not d.va def test_detect_various_artists(self): release = self._make_release(None) release["artist-credit"][0]["artist"]["id"] = mb.VARIOUS_ARTISTS_ID d = mb.album_info(release) assert d.va def test_parse_artist_sort_name(self): release = self._make_release(None) d = mb.album_info(release) assert d.artist_sort == "ARTIST SORT NAME" def test_parse_releasegroupid(self): release = self._make_release(None) d = mb.album_info(release) assert d.releasegroup_id == "RELEASE GROUP ID" def test_parse_asin(self): release = self._make_release(None) d = mb.album_info(release) assert d.asin == "ALBUM ASIN" def test_parse_catalognum(self): release = self._make_release(None) d = mb.album_info(release) assert d.catalognum == "CATALOG NUMBER" def test_parse_textrepr(self): release = self._make_release(None) d = mb.album_info(release) assert d.script == "SCRIPT" assert d.language == "LANGUAGE" def test_parse_country(self): release = self._make_release(None) d = mb.album_info(release) assert d.country == "COUNTRY" def test_parse_status(self): release = self._make_release(None) d = mb.album_info(release) assert d.albumstatus == "STATUS" def test_parse_barcode(self): release = self._make_release(None) d = mb.album_info(release) assert d.barcode == "BARCODE" def test_parse_media(self): tracks = [ self._make_track("TITLE ONE", "ID ONE", 100.0 * 1000.0), self._make_track("TITLE TWO", "ID TWO", 200.0 * 1000.0), ] release = self._make_release(None, tracks=tracks) d = mb.album_info(release) assert d.media == "FORMAT" def test_parse_disambig(self): release = self._make_release(None) d = mb.album_info(release) assert d.albumdisambig == "R_DISAMBIGUATION" assert d.releasegroupdisambig == "RG_DISAMBIGUATION" def test_parse_disctitle(self): tracks = [ self._make_track("TITLE ONE", "ID ONE", 100.0 * 1000.0), self._make_track("TITLE TWO", "ID TWO", 200.0 * 1000.0), ] release = self._make_release(None, tracks=tracks) d = mb.album_info(release) t = d.tracks assert t[0].disctitle == "MEDIUM TITLE" assert t[1].disctitle == "MEDIUM TITLE" def test_missing_language(self): release = self._make_release(None) del release["text-representation"]["language"] d = mb.album_info(release) assert d.language is None def test_parse_recording_artist(self): tracks = [self._make_track("a", "b", 1, True)] release = self._make_release(None, tracks=tracks) track = mb.album_info(release).tracks[0] assert track.artist == "RECORDING ARTIST NAME" assert track.artist_id == "RECORDING ARTIST ID" assert track.artist_sort == "RECORDING ARTIST SORT NAME" assert track.artist_credit == "RECORDING ARTIST CREDIT" def test_parse_recording_artist_multi(self): tracks = [self._make_track("a", "b", 1, True, multi_artist_credit=True)] release = self._make_release(None, tracks=tracks) track = mb.album_info(release).tracks[0] assert track.artist == "RECORDING ARTIST NAME & RECORDING ARTIST 2 NAME" assert track.artist_id == "RECORDING ARTIST ID" assert ( track.artist_sort == "RECORDING ARTIST SORT NAME & RECORDING ARTIST 2 SORT NAME" ) assert ( track.artist_credit == "RECORDING ARTIST CREDIT & RECORDING ARTIST 2 CREDIT" ) assert track.artists == [ "RECORDING ARTIST NAME", "RECORDING ARTIST 2 NAME", ] assert track.artists_ids == [ "RECORDING ARTIST ID", "RECORDING ARTIST 2 ID", ] assert track.artists_sort == [ "RECORDING ARTIST SORT NAME", "RECORDING ARTIST 2 SORT NAME", ] assert track.artists_credit == [ "RECORDING ARTIST CREDIT", "RECORDING ARTIST 2 CREDIT", ] def test_track_artist_overrides_recording_artist(self): tracks = [self._make_track("a", "b", 1, True)] release = self._make_release(None, tracks=tracks, track_artist=True) track = mb.album_info(release).tracks[0] assert track.artist == "TRACK ARTIST NAME" assert track.artist_id == "TRACK ARTIST ID" assert track.artist_sort == "TRACK ARTIST SORT NAME" assert track.artist_credit == "TRACK ARTIST CREDIT" def test_track_artist_overrides_recording_artist_multi(self): tracks = [self._make_track("a", "b", 1, True, multi_artist_credit=True)] release = self._make_release( None, tracks=tracks, track_artist=True, multi_artist_credit=True ) track = mb.album_info(release).tracks[0] assert track.artist == "TRACK ARTIST NAME & TRACK ARTIST 2 NAME" assert track.artist_id == "TRACK ARTIST ID" assert ( track.artist_sort == "TRACK ARTIST SORT NAME & TRACK ARTIST 2 SORT NAME" ) assert ( track.artist_credit == "TRACK ARTIST CREDIT & TRACK ARTIST 2 CREDIT" ) assert track.artists == ["TRACK ARTIST NAME", "TRACK ARTIST 2 NAME"] assert track.artists_ids == ["TRACK ARTIST ID", "TRACK ARTIST 2 ID"] assert track.artists_sort == [ "TRACK ARTIST SORT NAME", "TRACK ARTIST 2 SORT NAME", ] assert track.artists_credit == [ "TRACK ARTIST CREDIT", "TRACK ARTIST 2 CREDIT", ] def test_parse_recording_remixer(self): tracks = [self._make_track("a", "b", 1, remixer=True)] release = self._make_release(None, tracks=tracks) track = mb.album_info(release).tracks[0] assert track.remixer == "RECORDING REMIXER ARTIST NAME" def test_data_source(self): release = self._make_release() d = mb.album_info(release) assert d.data_source == "MusicBrainz" def test_ignored_media(self): config["match"]["ignored_media"] = ["IGNORED1", "IGNORED2"] tracks = [ self._make_track("TITLE ONE", "ID ONE", 100.0 * 1000.0), self._make_track("TITLE TWO", "ID TWO", 200.0 * 1000.0), ] release = self._make_release(tracks=tracks, medium_format="IGNORED1") d = mb.album_info(release) assert len(d.tracks) == 0 def test_no_ignored_media(self): config["match"]["ignored_media"] = ["IGNORED1", "IGNORED2"] tracks = [ self._make_track("TITLE ONE", "ID ONE", 100.0 * 1000.0), self._make_track("TITLE TWO", "ID TWO", 200.0 * 1000.0), ] release = self._make_release(tracks=tracks, medium_format="NON-IGNORED") d = mb.album_info(release) assert len(d.tracks) == 2 def test_skip_data_track(self): tracks = [ self._make_track("TITLE ONE", "ID ONE", 100.0 * 1000.0), self._make_track("[data track]", "ID DATA TRACK", 100.0 * 1000.0), self._make_track("TITLE TWO", "ID TWO", 200.0 * 1000.0), ] release = self._make_release(tracks=tracks) d = mb.album_info(release) assert len(d.tracks) == 2 assert d.tracks[0].title == "TITLE ONE" assert d.tracks[1].title == "TITLE TWO" def test_skip_audio_data_tracks_by_default(self): tracks = [ self._make_track("TITLE ONE", "ID ONE", 100.0 * 1000.0), self._make_track("TITLE TWO", "ID TWO", 200.0 * 1000.0), ] data_tracks = [ self._make_track( "TITLE AUDIO DATA", "ID DATA TRACK", 100.0 * 1000.0 ) ] release = self._make_release(tracks=tracks, data_tracks=data_tracks) d = mb.album_info(release) assert len(d.tracks) == 2 assert d.tracks[0].title == "TITLE ONE" assert d.tracks[1].title == "TITLE TWO" def test_no_skip_audio_data_tracks_if_configured(self): config["match"]["ignore_data_tracks"] = False tracks = [ self._make_track("TITLE ONE", "ID ONE", 100.0 * 1000.0), self._make_track("TITLE TWO", "ID TWO", 200.0 * 1000.0), ] data_tracks = [ self._make_track( "TITLE AUDIO DATA", "ID DATA TRACK", 100.0 * 1000.0 ) ] release = self._make_release(tracks=tracks, data_tracks=data_tracks) d = mb.album_info(release) assert len(d.tracks) == 3 assert d.tracks[0].title == "TITLE ONE" assert d.tracks[1].title == "TITLE TWO" assert d.tracks[2].title == "TITLE AUDIO DATA" def test_skip_video_tracks_by_default(self): tracks = [ self._make_track("TITLE ONE", "ID ONE", 100.0 * 1000.0), self._make_track( "TITLE VIDEO", "ID VIDEO", 100.0 * 1000.0, False, True ), self._make_track("TITLE TWO", "ID TWO", 200.0 * 1000.0), ] release = self._make_release(tracks=tracks) d = mb.album_info(release) assert len(d.tracks) == 2 assert d.tracks[0].title == "TITLE ONE" assert d.tracks[1].title == "TITLE TWO" def test_skip_video_data_tracks_by_default(self): tracks = [ self._make_track("TITLE ONE", "ID ONE", 100.0 * 1000.0), self._make_track("TITLE TWO", "ID TWO", 200.0 * 1000.0), ] data_tracks = [ self._make_track( "TITLE VIDEO", "ID VIDEO", 100.0 * 1000.0, False, True ) ] release = self._make_release(tracks=tracks, data_tracks=data_tracks) d = mb.album_info(release) assert len(d.tracks) == 2 assert d.tracks[0].title == "TITLE ONE" assert d.tracks[1].title == "TITLE TWO" def test_no_skip_video_tracks_if_configured(self): config["match"]["ignore_data_tracks"] = False config["match"]["ignore_video_tracks"] = False tracks = [ self._make_track("TITLE ONE", "ID ONE", 100.0 * 1000.0), self._make_track( "TITLE VIDEO", "ID VIDEO", 100.0 * 1000.0, False, True ), self._make_track("TITLE TWO", "ID TWO", 200.0 * 1000.0), ] release = self._make_release(tracks=tracks) d = mb.album_info(release) assert len(d.tracks) == 3 assert d.tracks[0].title == "TITLE ONE" assert d.tracks[1].title == "TITLE VIDEO" assert d.tracks[2].title == "TITLE TWO" def test_no_skip_video_data_tracks_if_configured(self): config["match"]["ignore_data_tracks"] = False config["match"]["ignore_video_tracks"] = False tracks = [ self._make_track("TITLE ONE", "ID ONE", 100.0 * 1000.0), self._make_track("TITLE TWO", "ID TWO", 200.0 * 1000.0), ] data_tracks = [ self._make_track( "TITLE VIDEO", "ID VIDEO", 100.0 * 1000.0, False, True ) ] release = self._make_release(tracks=tracks, data_tracks=data_tracks) d = mb.album_info(release) assert len(d.tracks) == 3 assert d.tracks[0].title == "TITLE ONE" assert d.tracks[1].title == "TITLE TWO" assert d.tracks[2].title == "TITLE VIDEO" def test_track_disambiguation(self): tracks = [ self._make_track("TITLE ONE", "ID ONE", 100.0 * 1000.0), self._make_track( "TITLE TWO", "ID TWO", 200.0 * 1000.0, disambiguation="SECOND TRACK", ), ] release = self._make_release(tracks=tracks) d = mb.album_info(release) t = d.tracks assert len(t) == 2 assert t[0].trackdisambig is None assert t[1].trackdisambig == "SECOND TRACK" class ParseIDTest(BeetsTestCase): def test_parse_id_correct(self): id_string = "28e32c71-1450-463e-92bf-e0a46446fc11" out = mb._parse_id(id_string) assert out == id_string def test_parse_id_non_id_returns_none(self): id_string = "blah blah" out = mb._parse_id(id_string) assert out is None def test_parse_id_url_finds_id(self): id_string = "28e32c71-1450-463e-92bf-e0a46446fc11" id_url = "https://musicbrainz.org/entity/%s" % id_string out = mb._parse_id(id_url) assert out == id_string class ArtistFlatteningTest(BeetsTestCase): def _credit_dict(self, suffix=""): return { "artist": { "name": "NAME" + suffix, "sort-name": "SORT" + suffix, }, "name": "CREDIT" + suffix, } def _add_alias(self, credit_dict, suffix="", locale="", primary=False): alias = { "alias": "ALIAS" + suffix, "locale": locale, "sort-name": "ALIASSORT" + suffix, } if primary: alias["primary"] = "primary" if "alias-list" not in credit_dict["artist"]: credit_dict["artist"]["alias-list"] = [] credit_dict["artist"]["alias-list"].append(alias) def test_single_artist(self): credit = [self._credit_dict()] a, s, c = mb._flatten_artist_credit(credit) assert a == "NAME" assert s == "SORT" assert c == "CREDIT" a, s, c = mb._multi_artist_credit(credit, include_join_phrase=False) assert a == ["NAME"] assert s == ["SORT"] assert c == ["CREDIT"] def test_two_artists(self): credit = [self._credit_dict("a"), " AND ", self._credit_dict("b")] a, s, c = mb._flatten_artist_credit(credit) assert a == "NAMEa AND NAMEb" assert s == "SORTa AND SORTb" assert c == "CREDITa AND CREDITb" a, s, c = mb._multi_artist_credit(credit, include_join_phrase=False) assert a == ["NAMEa", "NAMEb"] assert s == ["SORTa", "SORTb"] assert c == ["CREDITa", "CREDITb"] def test_alias(self): credit_dict = self._credit_dict() self._add_alias(credit_dict, suffix="en", locale="en", primary=True) self._add_alias( credit_dict, suffix="en_GB", locale="en_GB", primary=True ) self._add_alias(credit_dict, suffix="fr", locale="fr") self._add_alias(credit_dict, suffix="fr_P", locale="fr", primary=True) self._add_alias(credit_dict, suffix="pt_BR", locale="pt_BR") # test no alias config["import"]["languages"] = [""] flat = mb._flatten_artist_credit([credit_dict]) assert flat == ("NAME", "SORT", "CREDIT") # test en primary config["import"]["languages"] = ["en"] flat = mb._flatten_artist_credit([credit_dict]) assert flat == ("ALIASen", "ALIASSORTen", "CREDIT") # test en_GB en primary config["import"]["languages"] = ["en_GB", "en"] flat = mb._flatten_artist_credit([credit_dict]) assert flat == ("ALIASen_GB", "ALIASSORTen_GB", "CREDIT") # test en en_GB primary config["import"]["languages"] = ["en", "en_GB"] flat = mb._flatten_artist_credit([credit_dict]) assert flat == ("ALIASen", "ALIASSORTen", "CREDIT") # test fr primary config["import"]["languages"] = ["fr"] flat = mb._flatten_artist_credit([credit_dict]) assert flat == ("ALIASfr_P", "ALIASSORTfr_P", "CREDIT") # test for not matching non-primary config["import"]["languages"] = ["pt_BR", "fr"] flat = mb._flatten_artist_credit([credit_dict]) assert flat == ("ALIASfr_P", "ALIASSORTfr_P", "CREDIT") class MBLibraryTest(BeetsTestCase): def test_match_track(self): with mock.patch("musicbrainzngs.search_recordings") as p: p.return_value = { "recording-list": [ { "title": "foo", "id": "bar", "length": 42, } ], } ti = list(mb.match_track("hello", "there"))[0] p.assert_called_with(artist="hello", recording="there", limit=5) assert ti.title == "foo" assert ti.track_id == "bar" def test_match_album(self): mbid = "d2a6f856-b553-40a0-ac54-a321e8e2da99" with mock.patch("musicbrainzngs.search_releases") as sp: sp.return_value = { "release-list": [ { "id": mbid, } ], } with mock.patch("musicbrainzngs.get_release_by_id") as gp: gp.return_value = { "release": { "title": "hi", "id": mbid, "status": "status", "medium-list": [ { "track-list": [ { "id": "baz", "recording": { "title": "foo", "id": "bar", "length": 42, }, "position": 9, "number": "A1", } ], "position": 5, } ], "artist-credit": [ { "artist": { "name": "some-artist", "id": "some-id", }, } ], "release-group": { "id": "another-id", }, } } ai = list(mb.match_album("hello", "there"))[0] sp.assert_called_with(artist="hello", release="there", limit=5) gp.assert_called_with(mbid, mock.ANY) assert ai.tracks[0].title == "foo" assert ai.album == "hi" def test_match_track_empty(self): with mock.patch("musicbrainzngs.search_recordings") as p: til = list(mb.match_track(" ", " ")) assert not p.called assert til == [] def test_match_album_empty(self): with mock.patch("musicbrainzngs.search_releases") as p: ail = list(mb.match_album(" ", " ")) assert not p.called assert ail == [] def test_follow_pseudo_releases(self): side_effect = [ { "release": { "title": "pseudo", "id": "d2a6f856-b553-40a0-ac54-a321e8e2da02", "status": "Pseudo-Release", "medium-list": [ { "track-list": [ { "id": "baz", "recording": { "title": "translated title", "id": "bar", "length": 42, }, "position": 9, "number": "A1", } ], "position": 5, } ], "artist-credit": [ { "artist": { "name": "some-artist", "id": "some-id", }, } ], "release-group": { "id": "another-id", }, "release-relation-list": [ { "type": "transl-tracklisting", "target": "d2a6f856-b553-40a0-ac54-a321e8e2da01", "direction": "backward", } ], } }, { "release": { "title": "actual", "id": "d2a6f856-b553-40a0-ac54-a321e8e2da01", "status": "Official", "medium-list": [ { "track-list": [ { "id": "baz", "recording": { "title": "original title", "id": "bar", "length": 42, }, "position": 9, "number": "A1", } ], "position": 5, } ], "artist-credit": [ { "artist": { "name": "some-artist", "id": "some-id", }, } ], "release-group": { "id": "another-id", }, "country": "COUNTRY", } }, ] with mock.patch("musicbrainzngs.get_release_by_id") as gp: gp.side_effect = side_effect album = mb.album_for_id("d2a6f856-b553-40a0-ac54-a321e8e2da02") assert album.country == "COUNTRY" def test_pseudo_releases_with_empty_links(self): side_effect = [ { "release": { "title": "pseudo", "id": "d2a6f856-b553-40a0-ac54-a321e8e2da02", "status": "Pseudo-Release", "medium-list": [ { "track-list": [ { "id": "baz", "recording": { "title": "translated title", "id": "bar", "length": 42, }, "position": 9, "number": "A1", } ], "position": 5, } ], "artist-credit": [ { "artist": { "name": "some-artist", "id": "some-id", }, } ], "release-group": { "id": "another-id", }, "release-relation-list": [], } }, ] with mock.patch("musicbrainzngs.get_release_by_id") as gp: gp.side_effect = side_effect album = mb.album_for_id("d2a6f856-b553-40a0-ac54-a321e8e2da02") assert album.country is None def test_pseudo_releases_without_links(self): side_effect = [ { "release": { "title": "pseudo", "id": "d2a6f856-b553-40a0-ac54-a321e8e2da02", "status": "Pseudo-Release", "medium-list": [ { "track-list": [ { "id": "baz", "recording": { "title": "translated title", "id": "bar", "length": 42, }, "position": 9, "number": "A1", } ], "position": 5, } ], "artist-credit": [ { "artist": { "name": "some-artist", "id": "some-id", }, } ], "release-group": { "id": "another-id", }, } }, ] with mock.patch("musicbrainzngs.get_release_by_id") as gp: gp.side_effect = side_effect album = mb.album_for_id("d2a6f856-b553-40a0-ac54-a321e8e2da02") assert album.country is None def test_pseudo_releases_with_unsupported_links(self): side_effect = [ { "release": { "title": "pseudo", "id": "d2a6f856-b553-40a0-ac54-a321e8e2da02", "status": "Pseudo-Release", "medium-list": [ { "track-list": [ { "id": "baz", "recording": { "title": "translated title", "id": "bar", "length": 42, }, "position": 9, "number": "A1", } ], "position": 5, } ], "artist-credit": [ { "artist": { "name": "some-artist", "id": "some-id", }, } ], "release-group": { "id": "another-id", }, "release-relation-list": [ { "type": "remaster", "target": "d2a6f856-b553-40a0-ac54-a321e8e2da01", "direction": "backward", } ], } }, ] with mock.patch("musicbrainzngs.get_release_by_id") as gp: gp.side_effect = side_effect album = mb.album_for_id("d2a6f856-b553-40a0-ac54-a321e8e2da02") assert album.country is None beetbox-beets-01f1faf/test/test_metasync.py000066400000000000000000000103121472325477400211570ustar00rootroot00000000000000# This file is part of beets. # Copyright 2016, Tom Jaspers. # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the # "Software"), to deal in the Software without restriction, including # without limitation the rights to use, copy, modify, merge, publish, # distribute, sublicense, and/or sell copies of the Software, and to # permit persons to whom the Software is furnished to do so, subject to # the following conditions: # # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. import os import platform import time from datetime import datetime from beets.library import Item from beets.test import _common from beets.test.helper import PluginTestCase def _parsetime(s): return time.mktime(datetime.strptime(s, "%Y-%m-%d %H:%M:%S").timetuple()) def _is_windows(): return platform.system() == "Windows" class MetaSyncTest(PluginTestCase): plugin = "metasync" itunes_library_unix = os.path.join(_common.RSRC, b"itunes_library_unix.xml") itunes_library_windows = os.path.join( _common.RSRC, b"itunes_library_windows.xml" ) def setUp(self): super().setUp() self.config["metasync"]["source"] = "itunes" if _is_windows(): self.config["metasync"]["itunes"]["library"] = os.fsdecode( self.itunes_library_windows ) else: self.config["metasync"]["itunes"]["library"] = os.fsdecode( self.itunes_library_unix ) self._set_up_data() def _set_up_data(self): items = [_common.item() for _ in range(2)] items[0].title = "Tessellate" items[0].artist = "alt-J" items[0].albumartist = "alt-J" items[0].album = "An Awesome Wave" items[0].itunes_rating = 60 items[1].title = "Breezeblocks" items[1].artist = "alt-J" items[1].albumartist = "alt-J" items[1].album = "An Awesome Wave" if _is_windows(): items[ 0 ].path = "G:\\Music\\Alt-J\\An Awesome Wave\\03 Tessellate.mp3" items[ 1 ].path = "G:\\Music\\Alt-J\\An Awesome Wave\\04 Breezeblocks.mp3" else: items[0].path = "/Music/Alt-J/An Awesome Wave/03 Tessellate.mp3" items[1].path = "/Music/Alt-J/An Awesome Wave/04 Breezeblocks.mp3" for item in items: self.lib.add(item) def test_load_item_types(self): # This test also verifies that the MetaSources have loaded correctly assert "amarok_score" in Item._types assert "itunes_rating" in Item._types def test_pretend_sync_from_itunes(self): out = self.run_with_output("metasync", "-p") assert "itunes_rating: 60 -> 80" in out assert "itunes_rating: 100" in out assert "itunes_playcount: 31" in out assert "itunes_skipcount: 3" in out assert "itunes_lastplayed: 2015-05-04 12:20:51" in out assert "itunes_lastskipped: 2015-02-05 15:41:04" in out assert "itunes_dateadded: 2014-04-24 09:28:38" in out assert self.lib.items()[0].itunes_rating == 60 def test_sync_from_itunes(self): self.run_command("metasync") assert self.lib.items()[0].itunes_rating == 80 assert self.lib.items()[0].itunes_playcount == 0 assert self.lib.items()[0].itunes_skipcount == 3 assert not hasattr(self.lib.items()[0], "itunes_lastplayed") assert self.lib.items()[0].itunes_lastskipped == _parsetime( "2015-02-05 15:41:04" ) assert self.lib.items()[0].itunes_dateadded == _parsetime( "2014-04-24 09:28:38" ) assert self.lib.items()[1].itunes_rating == 100 assert self.lib.items()[1].itunes_playcount == 31 assert self.lib.items()[1].itunes_skipcount == 0 assert self.lib.items()[1].itunes_lastplayed == _parsetime( "2015-05-04 12:20:51" ) assert self.lib.items()[1].itunes_dateadded == _parsetime( "2014-04-24 09:28:38" ) assert not hasattr(self.lib.items()[1], "itunes_lastskipped") beetbox-beets-01f1faf/test/test_pipeline.py000066400000000000000000000150411472325477400211450ustar00rootroot00000000000000# This file is part of beets. # Copyright 2016, Adrian Sampson. # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the # "Software"), to deal in the Software without restriction, including # without limitation the rights to use, copy, modify, merge, publish, # distribute, sublicense, and/or sell copies of the Software, and to # permit persons to whom the Software is furnished to do so, subject to # the following conditions: # # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. """Test the "pipeline.py" restricted parallel programming library.""" import unittest import pytest from beets.util import pipeline # Some simple pipeline stages for testing. def _produce(num=5): yield from range(num) def _work(): i = None while True: i = yield i i *= 2 def _consume(result): while True: i = yield result.append(i) # A worker that raises an exception. class PipelineError(Exception): pass def _exc_work(num=3): i = None while True: i = yield i if i == num: raise PipelineError() i *= 2 # A worker that yields a bubble. def _bub_work(num=3): i = None while True: i = yield i if i == num: i = pipeline.BUBBLE else: i *= 2 # Yet another worker that yields multiple messages. def _multi_work(): i = None while True: i = yield i i = pipeline.multiple([i, -i]) class SimplePipelineTest(unittest.TestCase): def setUp(self): self.result = [] self.pl = pipeline.Pipeline( (_produce(), _work(), _consume(self.result)) ) def test_run_sequential(self): self.pl.run_sequential() assert self.result == [0, 2, 4, 6, 8] def test_run_parallel(self): self.pl.run_parallel() assert self.result == [0, 2, 4, 6, 8] def test_pull(self): pl = pipeline.Pipeline((_produce(), _work())) assert list(pl.pull()) == [0, 2, 4, 6, 8] def test_pull_chain(self): pl = pipeline.Pipeline((_produce(), _work())) pl2 = pipeline.Pipeline((pl.pull(), _work())) assert list(pl2.pull()) == [0, 4, 8, 12, 16] class ParallelStageTest(unittest.TestCase): def setUp(self): self.result = [] self.pl = pipeline.Pipeline( (_produce(), (_work(), _work()), _consume(self.result)) ) def test_run_sequential(self): self.pl.run_sequential() assert self.result == [0, 2, 4, 6, 8] def test_run_parallel(self): self.pl.run_parallel() # Order possibly not preserved; use set equality. assert set(self.result) == {0, 2, 4, 6, 8} def test_pull(self): pl = pipeline.Pipeline((_produce(), (_work(), _work()))) assert list(pl.pull()) == [0, 2, 4, 6, 8] class ExceptionTest(unittest.TestCase): def setUp(self): self.result = [] self.pl = pipeline.Pipeline( (_produce(), _exc_work(), _consume(self.result)) ) def test_run_sequential(self): with pytest.raises(PipelineError): self.pl.run_sequential() def test_run_parallel(self): with pytest.raises(PipelineError): self.pl.run_parallel() def test_pull(self): pl = pipeline.Pipeline((_produce(), _exc_work())) pull = pl.pull() for i in range(3): next(pull) with pytest.raises(PipelineError): next(pull) class ParallelExceptionTest(unittest.TestCase): def setUp(self): self.result = [] self.pl = pipeline.Pipeline( (_produce(), (_exc_work(), _exc_work()), _consume(self.result)) ) def test_run_parallel(self): with pytest.raises(PipelineError): self.pl.run_parallel() class ConstrainedThreadedPipelineTest(unittest.TestCase): def setUp(self): self.result = [] def test_constrained(self): # Do a "significant" amount of work... self.pl = pipeline.Pipeline( (_produce(1000), _work(), _consume(self.result)) ) # ... with only a single queue slot. self.pl.run_parallel(1) assert self.result == [i * 2 for i in range(1000)] def test_constrained_exception(self): # Raise an exception in a constrained pipeline. self.pl = pipeline.Pipeline( (_produce(1000), _exc_work(), _consume(self.result)) ) with pytest.raises(PipelineError): self.pl.run_parallel(1) def test_constrained_parallel(self): self.pl = pipeline.Pipeline( (_produce(1000), (_work(), _work()), _consume(self.result)) ) self.pl.run_parallel(1) assert set(self.result) == {i * 2 for i in range(1000)} class BubbleTest(unittest.TestCase): def setUp(self): self.result = [] self.pl = pipeline.Pipeline( (_produce(), _bub_work(), _consume(self.result)) ) def test_run_sequential(self): self.pl.run_sequential() assert self.result == [0, 2, 4, 8] def test_run_parallel(self): self.pl.run_parallel() assert self.result == [0, 2, 4, 8] def test_pull(self): pl = pipeline.Pipeline((_produce(), _bub_work())) assert list(pl.pull()) == [0, 2, 4, 8] class MultiMessageTest(unittest.TestCase): def setUp(self): self.result = [] self.pl = pipeline.Pipeline( (_produce(), _multi_work(), _consume(self.result)) ) def test_run_sequential(self): self.pl.run_sequential() assert self.result == [0, 0, 1, -1, 2, -2, 3, -3, 4, -4] def test_run_parallel(self): self.pl.run_parallel() assert self.result == [0, 0, 1, -1, 2, -2, 3, -3, 4, -4] def test_pull(self): pl = pipeline.Pipeline((_produce(), _multi_work())) assert list(pl.pull()) == [0, 0, 1, -1, 2, -2, 3, -3, 4, -4] class StageDecoratorTest(unittest.TestCase): def test_stage_decorator(self): @pipeline.stage def add(n, i): return i + n pl = pipeline.Pipeline([iter([1, 2, 3]), add(2)]) assert list(pl.pull()) == [3, 4, 5] def test_mutator_stage_decorator(self): @pipeline.mutator_stage def setkey(key, item): item[key] = True pl = pipeline.Pipeline( [iter([{"x": False}, {"a": False}]), setkey("x")] ) assert list(pl.pull()) == [{"x": True}, {"a": False, "x": True}] beetbox-beets-01f1faf/test/test_plugins.py000066400000000000000000000467741472325477400210420ustar00rootroot00000000000000# This file is part of beets. # Copyright 2016, Thomas Scholtes. # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the # "Software"), to deal in the Software without restriction, including # without limitation the rights to use, copy, modify, merge, publish, # distribute, sublicense, and/or sell copies of the Software, and to # permit persons to whom the Software is furnished to do so, subject to # the following conditions: # # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. import itertools import os import unittest from unittest.mock import ANY, Mock, patch import pytest from mediafile import MediaFile from beets import config, plugins, ui from beets.dbcore import types from beets.importer import ( ArchiveImportTask, SentinelImportTask, SingletonImportTask, action, ) from beets.library import Item from beets.plugins import MetadataSourcePlugin from beets.test import helper from beets.test.helper import AutotagStub, ImportHelper, TerminalImportMixin from beets.test.helper import PluginTestCase as BasePluginTestCase from beets.util import displayable_path, syspath from beets.util.id_extractors import ( beatport_id_regex, deezer_id_regex, spotify_id_regex, ) class PluginLoaderTestCase(BasePluginTestCase): def setup_plugin_loader(self): # FIXME the mocking code is horrific, but this is the lowest and # earliest level of the plugin mechanism we can hook into. self._plugin_loader_patch = patch("beets.plugins.load_plugins") self._plugin_classes = set() load_plugins = self._plugin_loader_patch.start() def myload(names=()): plugins._classes.update(self._plugin_classes) load_plugins.side_effect = myload def teardown_plugin_loader(self): self._plugin_loader_patch.stop() def register_plugin(self, plugin_class): self._plugin_classes.add(plugin_class) def setUp(self): self.setup_plugin_loader() super().setUp() def tearDown(self): self.teardown_plugin_loader() super().tearDown() class PluginImportTestCase(ImportHelper, PluginLoaderTestCase): def setUp(self): super().setUp() self.prepare_album_for_import(2) class ItemTypesTest(PluginLoaderTestCase): def test_flex_field_type(self): class RatingPlugin(plugins.BeetsPlugin): item_types = {"rating": types.Float()} self.register_plugin(RatingPlugin) self.config["plugins"] = "rating" item = Item(path="apath", artist="aaa") item.add(self.lib) # Do not match unset values out = self.run_with_output("ls", "rating:1..3") assert "aaa" not in out self.run_command("modify", "rating=2", "--yes") # Match in range out = self.run_with_output("ls", "rating:1..3") assert "aaa" in out # Don't match out of range out = self.run_with_output("ls", "rating:3..5") assert "aaa" not in out class ItemWriteTest(PluginLoaderTestCase): def setUp(self): super().setUp() class EventListenerPlugin(plugins.BeetsPlugin): pass self.event_listener_plugin = EventListenerPlugin() self.register_plugin(EventListenerPlugin) def test_change_tags(self): def on_write(item=None, path=None, tags=None): if tags["artist"] == "XXX": tags["artist"] = "YYY" self.register_listener("write", on_write) item = self.add_item_fixture(artist="XXX") item.write() mediafile = MediaFile(syspath(item.path)) assert mediafile.artist == "YYY" def register_listener(self, event, func): self.event_listener_plugin.register_listener(event, func) class ItemTypeConflictTest(PluginLoaderTestCase): def test_mismatch(self): class EventListenerPlugin(plugins.BeetsPlugin): item_types = {"duplicate": types.INTEGER} class AdventListenerPlugin(plugins.BeetsPlugin): item_types = {"duplicate": types.FLOAT} self.event_listener_plugin = EventListenerPlugin self.advent_listener_plugin = AdventListenerPlugin self.register_plugin(EventListenerPlugin) self.register_plugin(AdventListenerPlugin) with pytest.raises(plugins.PluginConflictError): plugins.types(Item) def test_match(self): class EventListenerPlugin(plugins.BeetsPlugin): item_types = {"duplicate": types.INTEGER} class AdventListenerPlugin(plugins.BeetsPlugin): item_types = {"duplicate": types.INTEGER} self.event_listener_plugin = EventListenerPlugin self.advent_listener_plugin = AdventListenerPlugin self.register_plugin(EventListenerPlugin) self.register_plugin(AdventListenerPlugin) assert plugins.types(Item) is not None class EventsTest(PluginImportTestCase): def setUp(self): super().setUp() def test_import_task_created(self): self.importer = self.setup_importer(pretend=True) with helper.capture_log() as logs: self.importer.run() # Exactly one event should have been imported (for the album). # Sentinels do not get emitted. assert logs.count("Sending event: import_task_created") == 1 logs = [line for line in logs if not line.startswith("Sending event:")] assert logs == [ f'Album: {displayable_path(os.path.join(self.import_dir, b"album"))}', f" {displayable_path(self.import_media[0].path)}", f" {displayable_path(self.import_media[1].path)}", ] def test_import_task_created_with_plugin(self): class ToSingletonPlugin(plugins.BeetsPlugin): def __init__(self): super().__init__() self.register_listener( "import_task_created", self.import_task_created_event ) def import_task_created_event(self, session, task): if ( isinstance(task, SingletonImportTask) or isinstance(task, SentinelImportTask) or isinstance(task, ArchiveImportTask) ): return task new_tasks = [] for item in task.items: new_tasks.append(SingletonImportTask(task.toppath, item)) return new_tasks to_singleton_plugin = ToSingletonPlugin self.register_plugin(to_singleton_plugin) self.importer = self.setup_importer(pretend=True) with helper.capture_log() as logs: self.importer.run() # Exactly one event should have been imported (for the album). # Sentinels do not get emitted. assert logs.count("Sending event: import_task_created") == 1 logs = [line for line in logs if not line.startswith("Sending event:")] assert logs == [ f"Singleton: {displayable_path(self.import_media[0].path)}", f"Singleton: {displayable_path(self.import_media[1].path)}", ] class HelpersTest(unittest.TestCase): def test_sanitize_choices(self): assert plugins.sanitize_choices(["A", "Z"], ("A", "B")) == ["A"] assert plugins.sanitize_choices(["A", "A"], ("A")) == ["A"] assert plugins.sanitize_choices( ["D", "*", "A"], ("A", "B", "C", "D") ) == ["D", "B", "C", "A"] class ListenersTest(PluginLoaderTestCase): def test_register(self): class DummyPlugin(plugins.BeetsPlugin): def __init__(self): super().__init__() self.register_listener("cli_exit", self.dummy) self.register_listener("cli_exit", self.dummy) def dummy(self): pass d = DummyPlugin() assert DummyPlugin._raw_listeners["cli_exit"] == [d.dummy] d2 = DummyPlugin() assert DummyPlugin._raw_listeners["cli_exit"] == [d.dummy, d2.dummy] d.register_listener("cli_exit", d2.dummy) assert DummyPlugin._raw_listeners["cli_exit"] == [d.dummy, d2.dummy] @patch("beets.plugins.find_plugins") @patch("inspect.getfullargspec") def test_events_called(self, mock_gfa, mock_find_plugins): mock_gfa.return_value = Mock( args=(), varargs="args", varkw="kwargs", ) class DummyPlugin(plugins.BeetsPlugin): def __init__(self): super().__init__() self.foo = Mock(__name__="foo") self.register_listener("event_foo", self.foo) self.bar = Mock(__name__="bar") self.register_listener("event_bar", self.bar) d = DummyPlugin() mock_find_plugins.return_value = (d,) plugins.send("event") d.foo.assert_has_calls([]) d.bar.assert_has_calls([]) plugins.send("event_foo", var="tagada") d.foo.assert_called_once_with(var="tagada") d.bar.assert_has_calls([]) @patch("beets.plugins.find_plugins") def test_listener_params(self, mock_find_plugins): class DummyPlugin(plugins.BeetsPlugin): def __init__(self): super().__init__() for i in itertools.count(1): try: meth = getattr(self, f"dummy{i}") except AttributeError: break self.register_listener(f"event{i}", meth) def dummy1(self, foo): assert foo == 5 def dummy2(self, foo=None): assert foo == 5 def dummy3(self): # argument cut off pass def dummy4(self, bar=None): # argument cut off pass def dummy5(self, bar): assert not True # more complex examples def dummy6(self, foo, bar=None): assert foo == 5 assert bar is None def dummy7(self, foo, **kwargs): assert foo == 5 assert kwargs == {} def dummy8(self, foo, bar, **kwargs): assert not True def dummy9(self, **kwargs): assert kwargs == {"foo": 5} d = DummyPlugin() mock_find_plugins.return_value = (d,) plugins.send("event1", foo=5) plugins.send("event2", foo=5) plugins.send("event3", foo=5) plugins.send("event4", foo=5) with pytest.raises(TypeError): plugins.send("event5", foo=5) plugins.send("event6", foo=5) plugins.send("event7", foo=5) with pytest.raises(TypeError): plugins.send("event8", foo=5) plugins.send("event9", foo=5) class PromptChoicesTest(TerminalImportMixin, PluginImportTestCase): def setUp(self): super().setUp() self.setup_importer() self.matcher = AutotagStub().install() # keep track of ui.input_option() calls self.input_options_patcher = patch( "beets.ui.input_options", side_effect=ui.input_options ) self.mock_input_options = self.input_options_patcher.start() def tearDown(self): super().tearDown() self.input_options_patcher.stop() self.matcher.restore() def test_plugin_choices_in_ui_input_options_album(self): """Test the presence of plugin choices on the prompt (album).""" class DummyPlugin(plugins.BeetsPlugin): def __init__(self): super().__init__() self.register_listener( "before_choose_candidate", self.return_choices ) def return_choices(self, session, task): return [ ui.commands.PromptChoice("f", "Foo", None), ui.commands.PromptChoice("r", "baR", None), ] self.register_plugin(DummyPlugin) # Default options + extra choices by the plugin ('Foo', 'Bar') opts = ( "Apply", "More candidates", "Skip", "Use as-is", "as Tracks", "Group albums", "Enter search", "enter Id", "aBort", ) + ("Foo", "baR") self.importer.add_choice(action.SKIP) self.importer.run() self.mock_input_options.assert_called_once_with( opts, default="a", require=ANY ) def test_plugin_choices_in_ui_input_options_singleton(self): """Test the presence of plugin choices on the prompt (singleton).""" class DummyPlugin(plugins.BeetsPlugin): def __init__(self): super().__init__() self.register_listener( "before_choose_candidate", self.return_choices ) def return_choices(self, session, task): return [ ui.commands.PromptChoice("f", "Foo", None), ui.commands.PromptChoice("r", "baR", None), ] self.register_plugin(DummyPlugin) # Default options + extra choices by the plugin ('Foo', 'Bar') opts = ( "Apply", "More candidates", "Skip", "Use as-is", "Enter search", "enter Id", "aBort", ) + ("Foo", "baR") config["import"]["singletons"] = True self.importer.add_choice(action.SKIP) self.importer.run() self.mock_input_options.assert_called_with( opts, default="a", require=ANY ) def test_choices_conflicts(self): """Test the short letter conflict solving.""" class DummyPlugin(plugins.BeetsPlugin): def __init__(self): super().__init__() self.register_listener( "before_choose_candidate", self.return_choices ) def return_choices(self, session, task): return [ ui.commands.PromptChoice("a", "A foo", None), # dupe ui.commands.PromptChoice("z", "baZ", None), # ok ui.commands.PromptChoice("z", "Zupe", None), # dupe ui.commands.PromptChoice("z", "Zoo", None), ] # dupe self.register_plugin(DummyPlugin) # Default options + not dupe extra choices by the plugin ('baZ') opts = ( "Apply", "More candidates", "Skip", "Use as-is", "as Tracks", "Group albums", "Enter search", "enter Id", "aBort", ) + ("baZ",) self.importer.add_choice(action.SKIP) self.importer.run() self.mock_input_options.assert_called_once_with( opts, default="a", require=ANY ) def test_plugin_callback(self): """Test that plugin callbacks are being called upon user choice.""" class DummyPlugin(plugins.BeetsPlugin): def __init__(self): super().__init__() self.register_listener( "before_choose_candidate", self.return_choices ) def return_choices(self, session, task): return [ui.commands.PromptChoice("f", "Foo", self.foo)] def foo(self, session, task): pass self.register_plugin(DummyPlugin) # Default options + extra choices by the plugin ('Foo', 'Bar') opts = ( "Apply", "More candidates", "Skip", "Use as-is", "as Tracks", "Group albums", "Enter search", "enter Id", "aBort", ) + ("Foo",) # DummyPlugin.foo() should be called once with patch.object(DummyPlugin, "foo", autospec=True) as mock_foo: with helper.control_stdin("\n".join(["f", "s"])): self.importer.run() assert mock_foo.call_count == 1 # input_options should be called twice, as foo() returns None assert self.mock_input_options.call_count == 2 self.mock_input_options.assert_called_with( opts, default="a", require=ANY ) def test_plugin_callback_return(self): """Test that plugin callbacks that return a value exit the loop.""" class DummyPlugin(plugins.BeetsPlugin): def __init__(self): super().__init__() self.register_listener( "before_choose_candidate", self.return_choices ) def return_choices(self, session, task): return [ui.commands.PromptChoice("f", "Foo", self.foo)] def foo(self, session, task): return action.SKIP self.register_plugin(DummyPlugin) # Default options + extra choices by the plugin ('Foo', 'Bar') opts = ( "Apply", "More candidates", "Skip", "Use as-is", "as Tracks", "Group albums", "Enter search", "enter Id", "aBort", ) + ("Foo",) # DummyPlugin.foo() should be called once with helper.control_stdin("f\n"): self.importer.run() # input_options should be called once, as foo() returns SKIP self.mock_input_options.assert_called_once_with( opts, default="a", require=ANY ) class ParseSpotifyIDTest(unittest.TestCase): def test_parse_id_correct(self): id_string = "39WqpoPgZxygo6YQjehLJJ" out = MetadataSourcePlugin._get_id("album", id_string, spotify_id_regex) assert out == id_string def test_parse_id_non_id_returns_none(self): id_string = "blah blah" out = MetadataSourcePlugin._get_id("album", id_string, spotify_id_regex) assert out is None def test_parse_id_url_finds_id(self): id_string = "39WqpoPgZxygo6YQjehLJJ" id_url = "https://open.spotify.com/album/%s" % id_string out = MetadataSourcePlugin._get_id("album", id_url, spotify_id_regex) assert out == id_string class ParseDeezerIDTest(unittest.TestCase): def test_parse_id_correct(self): id_string = "176356382" out = MetadataSourcePlugin._get_id("album", id_string, deezer_id_regex) assert out == id_string def test_parse_id_non_id_returns_none(self): id_string = "blah blah" out = MetadataSourcePlugin._get_id("album", id_string, deezer_id_regex) assert out is None def test_parse_id_url_finds_id(self): id_string = "176356382" id_url = "https://www.deezer.com/album/%s" % id_string out = MetadataSourcePlugin._get_id("album", id_url, deezer_id_regex) assert out == id_string class ParseBeatportIDTest(unittest.TestCase): def test_parse_id_correct(self): id_string = "3089651" out = MetadataSourcePlugin._get_id( "album", id_string, beatport_id_regex ) assert out == id_string def test_parse_id_non_id_returns_none(self): id_string = "blah blah" out = MetadataSourcePlugin._get_id( "album", id_string, beatport_id_regex ) assert out is None def test_parse_id_url_finds_id(self): id_string = "3089651" id_url = "https://www.beatport.com/release/album-name/%s" % id_string out = MetadataSourcePlugin._get_id("album", id_url, beatport_id_regex) assert out == id_string beetbox-beets-01f1faf/test/test_query.py000066400000000000000000001150761472325477400205160ustar00rootroot00000000000000# This file is part of beets. # Copyright 2016, Adrian Sampson. # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the # "Software"), to deal in the Software without restriction, including # without limitation the rights to use, copy, modify, merge, publish, # distribute, sublicense, and/or sell copies of the Software, and to # permit persons to whom the Software is furnished to do so, subject to # the following conditions: # # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. """Various tests for querying the library database.""" import os import sys import unittest from contextlib import contextmanager from functools import partial import pytest import beets.library from beets import dbcore, util from beets.dbcore import types from beets.dbcore.query import ( InvalidQueryArgumentValueError, NoneQuery, ParsingError, ) from beets.library import Item from beets.test import _common from beets.test.helper import BeetsTestCase, ItemInDBTestCase from beets.util import syspath # Because the absolute path begins with something like C:, we # can't disambiguate it from an ordinary query. WIN32_NO_IMPLICIT_PATHS = "Implicit paths are not supported on Windows" class AssertsMixin: def assert_items_matched(self, results, titles): assert {i.title for i in results} == set(titles) def assert_albums_matched(self, results, albums): assert {a.album for a in results} == set(albums) def assertInResult(self, item, results): result_ids = [i.id for i in results] assert item.id in result_ids def assertNotInResult(self, item, results): result_ids = [i.id for i in results] assert item.id not in result_ids class AnyFieldQueryTest(ItemInDBTestCase): def test_no_restriction(self): q = dbcore.query.AnyFieldQuery( "title", beets.library.Item._fields.keys(), dbcore.query.SubstringQuery, ) assert self.lib.items(q).get().title == "the title" def test_restriction_completeness(self): q = dbcore.query.AnyFieldQuery( "title", ["title"], dbcore.query.SubstringQuery ) assert self.lib.items(q).get().title == "the title" def test_restriction_soundness(self): q = dbcore.query.AnyFieldQuery( "title", ["artist"], dbcore.query.SubstringQuery ) assert self.lib.items(q).get() is None def test_eq(self): q1 = dbcore.query.AnyFieldQuery( "foo", ["bar"], dbcore.query.SubstringQuery ) q2 = dbcore.query.AnyFieldQuery( "foo", ["bar"], dbcore.query.SubstringQuery ) assert q1 == q2 q2.query_class = None assert q1 != q2 # A test case class providing a library with some dummy data and some # assertions involving that data. class DummyDataTestCase(BeetsTestCase, AssertsMixin): def setUp(self): super().setUp() items = [_common.item() for _ in range(3)] items[0].title = "foo bar" items[0].artist = "one" items[0].artists = ["one", "eleven"] items[0].album = "baz" items[0].year = 2001 items[0].comp = True items[0].genre = "rock" items[1].title = "baz qux" items[1].artist = "two" items[1].artists = ["two", "twelve"] items[1].album = "baz" items[1].year = 2002 items[1].comp = True items[1].genre = "Rock" items[2].title = "beets 4 eva" items[2].artist = "three" items[2].artists = ["three", "one"] items[2].album = "foo" items[2].year = 2003 items[2].comp = False items[2].genre = "Hard Rock" for item in items: self.lib.add(item) self.album = self.lib.add_album(items[:2]) def assert_items_matched_all(self, results): self.assert_items_matched( results, [ "foo bar", "baz qux", "beets 4 eva", ], ) class GetTest(DummyDataTestCase): def test_get_empty(self): q = "" results = self.lib.items(q) self.assert_items_matched_all(results) def test_get_none(self): q = None results = self.lib.items(q) self.assert_items_matched_all(results) def test_get_one_keyed_term(self): q = "title:qux" results = self.lib.items(q) self.assert_items_matched(results, ["baz qux"]) def test_get_one_keyed_exact(self): q = "genre:=rock" results = self.lib.items(q) self.assert_items_matched(results, ["foo bar"]) q = "genre:=Rock" results = self.lib.items(q) self.assert_items_matched(results, ["baz qux"]) q = 'genre:="Hard Rock"' results = self.lib.items(q) self.assert_items_matched(results, ["beets 4 eva"]) def test_get_one_keyed_exact_nocase(self): q = 'genre:=~"hard rock"' results = self.lib.items(q) self.assert_items_matched(results, ["beets 4 eva"]) def test_get_one_keyed_regexp(self): q = "artist::t.+r" results = self.lib.items(q) self.assert_items_matched(results, ["beets 4 eva"]) def test_get_one_unkeyed_term(self): q = "three" results = self.lib.items(q) self.assert_items_matched(results, ["beets 4 eva"]) def test_get_one_unkeyed_exact(self): q = "=rock" results = self.lib.items(q) self.assert_items_matched(results, ["foo bar"]) def test_get_one_unkeyed_exact_nocase(self): q = '=~"hard rock"' results = self.lib.items(q) self.assert_items_matched(results, ["beets 4 eva"]) def test_get_one_unkeyed_regexp(self): q = ":x$" results = self.lib.items(q) self.assert_items_matched(results, ["baz qux"]) def test_get_no_matches(self): q = "popebear" results = self.lib.items(q) self.assert_items_matched(results, []) def test_invalid_key(self): q = "pope:bear" results = self.lib.items(q) # Matches nothing since the flexattr is not present on the # objects. self.assert_items_matched(results, []) def test_get_no_matches_exact(self): q = 'genre:="hard rock"' results = self.lib.items(q) self.assert_items_matched(results, []) def test_term_case_insensitive(self): q = "oNE" results = self.lib.items(q) self.assert_items_matched(results, ["foo bar"]) def test_regexp_case_sensitive(self): q = ":oNE" results = self.lib.items(q) self.assert_items_matched(results, []) q = ":one" results = self.lib.items(q) self.assert_items_matched(results, ["foo bar"]) def test_term_case_insensitive_with_key(self): q = "artist:thrEE" results = self.lib.items(q) self.assert_items_matched(results, ["beets 4 eva"]) def test_term_case_regex_with_multi_key_matches(self): q = "artists::eleven" results = self.lib.items(q) self.assert_items_matched(results, ["foo bar"]) def test_term_case_regex_with_multi_key_matches_multiple_columns(self): q = "artists::one" results = self.lib.items(q) self.assert_items_matched(results, ["foo bar", "beets 4 eva"]) def test_key_case_insensitive(self): q = "ArTiST:three" results = self.lib.items(q) self.assert_items_matched(results, ["beets 4 eva"]) def test_keyed_matches_exact_nocase(self): q = "genre:=~rock" results = self.lib.items(q) self.assert_items_matched( results, [ "foo bar", "baz qux", ], ) def test_unkeyed_term_matches_multiple_columns(self): q = "baz" results = self.lib.items(q) self.assert_items_matched( results, [ "foo bar", "baz qux", ], ) def test_unkeyed_regexp_matches_multiple_columns(self): q = ":z$" results = self.lib.items(q) self.assert_items_matched( results, [ "foo bar", "baz qux", ], ) def test_keyed_term_matches_only_one_column(self): q = "title:baz" results = self.lib.items(q) self.assert_items_matched(results, ["baz qux"]) def test_keyed_regexp_matches_only_one_column(self): q = "title::baz" results = self.lib.items(q) self.assert_items_matched( results, [ "baz qux", ], ) def test_multiple_terms_narrow_search(self): q = "qux baz" results = self.lib.items(q) self.assert_items_matched( results, [ "baz qux", ], ) def test_multiple_regexps_narrow_search(self): q = ":baz :qux" results = self.lib.items(q) self.assert_items_matched(results, ["baz qux"]) def test_mixed_terms_regexps_narrow_search(self): q = ":baz qux" results = self.lib.items(q) self.assert_items_matched(results, ["baz qux"]) def test_single_year(self): q = "year:2001" results = self.lib.items(q) self.assert_items_matched(results, ["foo bar"]) def test_year_range(self): q = "year:2000..2002" results = self.lib.items(q) self.assert_items_matched( results, [ "foo bar", "baz qux", ], ) def test_singleton_true(self): q = "singleton:true" results = self.lib.items(q) self.assert_items_matched(results, ["beets 4 eva"]) def test_singleton_1(self): q = "singleton:1" results = self.lib.items(q) self.assert_items_matched(results, ["beets 4 eva"]) def test_singleton_false(self): q = "singleton:false" results = self.lib.items(q) self.assert_items_matched(results, ["foo bar", "baz qux"]) def test_singleton_0(self): q = "singleton:0" results = self.lib.items(q) self.assert_items_matched(results, ["foo bar", "baz qux"]) def test_compilation_true(self): q = "comp:true" results = self.lib.items(q) self.assert_items_matched(results, ["foo bar", "baz qux"]) def test_compilation_false(self): q = "comp:false" results = self.lib.items(q) self.assert_items_matched(results, ["beets 4 eva"]) def test_unknown_field_name_no_results(self): q = "xyzzy:nonsense" results = self.lib.items(q) titles = [i.title for i in results] assert titles == [] def test_unknown_field_name_no_results_in_album_query(self): q = "xyzzy:nonsense" results = self.lib.albums(q) names = [a.album for a in results] assert names == [] def test_item_field_name_matches_nothing_in_album_query(self): q = "format:nonsense" results = self.lib.albums(q) names = [a.album for a in results] assert names == [] def test_unicode_query(self): item = self.lib.items().get() item.title = "caf\xe9" item.store() q = "title:caf\xe9" results = self.lib.items(q) self.assert_items_matched(results, ["caf\xe9"]) def test_numeric_search_positive(self): q = dbcore.query.NumericQuery("year", "2001") results = self.lib.items(q) assert results def test_numeric_search_negative(self): q = dbcore.query.NumericQuery("year", "1999") results = self.lib.items(q) assert not results def test_album_field_fallback(self): self.album["albumflex"] = "foo" self.album.store() q = "albumflex:foo" results = self.lib.items(q) self.assert_items_matched(results, ["foo bar", "baz qux"]) def test_invalid_query(self): with pytest.raises(InvalidQueryArgumentValueError, match="not an int"): dbcore.query.NumericQuery("year", "199a") msg_match = r"not a regular expression.*unterminated subpattern" with pytest.raises(ParsingError, match=msg_match): dbcore.query.RegexpQuery("year", "199(") class MatchTest(BeetsTestCase): def setUp(self): super().setUp() self.item = _common.item() def test_regex_match_positive(self): q = dbcore.query.RegexpQuery("album", "^the album$") assert q.match(self.item) def test_regex_match_negative(self): q = dbcore.query.RegexpQuery("album", "^album$") assert not q.match(self.item) def test_regex_match_non_string_value(self): q = dbcore.query.RegexpQuery("disc", "^6$") assert q.match(self.item) def test_substring_match_positive(self): q = dbcore.query.SubstringQuery("album", "album") assert q.match(self.item) def test_substring_match_negative(self): q = dbcore.query.SubstringQuery("album", "ablum") assert not q.match(self.item) def test_substring_match_non_string_value(self): q = dbcore.query.SubstringQuery("disc", "6") assert q.match(self.item) def test_exact_match_nocase_positive(self): q = dbcore.query.StringQuery("genre", "the genre") assert q.match(self.item) q = dbcore.query.StringQuery("genre", "THE GENRE") assert q.match(self.item) def test_exact_match_nocase_negative(self): q = dbcore.query.StringQuery("genre", "genre") assert not q.match(self.item) def test_year_match_positive(self): q = dbcore.query.NumericQuery("year", "1") assert q.match(self.item) def test_year_match_negative(self): q = dbcore.query.NumericQuery("year", "10") assert not q.match(self.item) def test_bitrate_range_positive(self): q = dbcore.query.NumericQuery("bitrate", "100000..200000") assert q.match(self.item) def test_bitrate_range_negative(self): q = dbcore.query.NumericQuery("bitrate", "200000..300000") assert not q.match(self.item) def test_open_range(self): dbcore.query.NumericQuery("bitrate", "100000..") def test_eq(self): q1 = dbcore.query.MatchQuery("foo", "bar") q2 = dbcore.query.MatchQuery("foo", "bar") q3 = dbcore.query.MatchQuery("foo", "baz") q4 = dbcore.query.StringFieldQuery("foo", "bar") assert q1 == q2 assert q1 != q3 assert q1 != q4 assert q3 != q4 class PathQueryTest(ItemInDBTestCase, AssertsMixin): def setUp(self): super().setUp() # This is the item we'll try to match. self.i.path = util.normpath("/a/b/c.mp3") self.i.title = "path item" self.i.album = "path album" self.i.store() self.lib.add_album([self.i]) # A second item for testing exclusion. i2 = _common.item() i2.path = util.normpath("/x/y/z.mp3") i2.title = "another item" i2.album = "another album" self.lib.add(i2) self.lib.add_album([i2]) @contextmanager def force_implicit_query_detection(self): # Unadorned path queries with path separators in them are considered # path queries only when the path in question actually exists. So we # mock the existence check to return true. beets.library.PathQuery.force_implicit_query_detection = True yield beets.library.PathQuery.force_implicit_query_detection = False def test_path_exact_match(self): q = "path:/a/b/c.mp3" results = self.lib.items(q) self.assert_items_matched(results, ["path item"]) results = self.lib.albums(q) self.assert_albums_matched(results, ["path album"]) # FIXME: fails on windows @unittest.skipIf(sys.platform == "win32", "win32") def test_parent_directory_no_slash(self): q = "path:/a" results = self.lib.items(q) self.assert_items_matched(results, ["path item"]) results = self.lib.albums(q) self.assert_albums_matched(results, ["path album"]) # FIXME: fails on windows @unittest.skipIf(sys.platform == "win32", "win32") def test_parent_directory_with_slash(self): q = "path:/a/" results = self.lib.items(q) self.assert_items_matched(results, ["path item"]) results = self.lib.albums(q) self.assert_albums_matched(results, ["path album"]) def test_no_match(self): q = "path:/xyzzy/" results = self.lib.items(q) self.assert_items_matched(results, []) results = self.lib.albums(q) self.assert_albums_matched(results, []) def test_fragment_no_match(self): q = "path:/b/" results = self.lib.items(q) self.assert_items_matched(results, []) results = self.lib.albums(q) self.assert_albums_matched(results, []) def test_nonnorm_path(self): q = "path:/x/../a/b" results = self.lib.items(q) self.assert_items_matched(results, ["path item"]) results = self.lib.albums(q) self.assert_albums_matched(results, ["path album"]) @unittest.skipIf(sys.platform == "win32", WIN32_NO_IMPLICIT_PATHS) def test_slashed_query_matches_path(self): with self.force_implicit_query_detection(): q = "/a/b" results = self.lib.items(q) self.assert_items_matched(results, ["path item"]) results = self.lib.albums(q) self.assert_albums_matched(results, ["path album"]) @unittest.skipIf(sys.platform == "win32", WIN32_NO_IMPLICIT_PATHS) def test_path_query_in_or_query(self): with self.force_implicit_query_detection(): q = "/a/b , /a/b" results = self.lib.items(q) self.assert_items_matched(results, ["path item"]) def test_non_slashed_does_not_match_path(self): with self.force_implicit_query_detection(): q = "c.mp3" results = self.lib.items(q) self.assert_items_matched(results, []) results = self.lib.albums(q) self.assert_albums_matched(results, []) def test_slashes_in_explicit_field_does_not_match_path(self): with self.force_implicit_query_detection(): q = "title:/a/b" results = self.lib.items(q) self.assert_items_matched(results, []) def test_path_item_regex(self): q = "path::c\\.mp3$" results = self.lib.items(q) self.assert_items_matched(results, ["path item"]) results = self.lib.albums(q) self.assert_albums_matched(results, ["path album"]) def test_path_album_regex(self): q = "path::b" results = self.lib.albums(q) self.assert_albums_matched(results, ["path album"]) def test_escape_underscore(self): self.add_album( path=b"/a/_/title.mp3", title="with underscore", album="album with underscore", ) q = "path:/a/_" results = self.lib.items(q) self.assert_items_matched(results, ["with underscore"]) results = self.lib.albums(q) self.assert_albums_matched(results, ["album with underscore"]) def test_escape_percent(self): self.add_album( path=b"/a/%/title.mp3", title="with percent", album="album with percent", ) q = "path:/a/%" results = self.lib.items(q) self.assert_items_matched(results, ["with percent"]) results = self.lib.albums(q) self.assert_albums_matched(results, ["album with percent"]) def test_escape_backslash(self): self.add_album( path=rb"/a/\x/title.mp3", title="with backslash", album="album with backslash", ) q = "path:/a/\\\\x" results = self.lib.items(q) self.assert_items_matched(results, ["with backslash"]) results = self.lib.albums(q) self.assert_albums_matched(results, ["album with backslash"]) def test_case_sensitivity(self): self.add_album(path=b"/A/B/C2.mp3", title="caps path") makeq = partial(beets.library.PathQuery, "path", "/A/B") results = self.lib.items(makeq(case_sensitive=True)) self.assert_items_matched(results, ["caps path"]) results = self.lib.items(makeq(case_sensitive=False)) self.assert_items_matched(results, ["path item", "caps path"]) # FIXME: Also create a variant of this test for windows, which tests # both os.sep and os.altsep @unittest.skipIf(sys.platform == "win32", "win32") def test_path_sep_detection(self): is_path_query = beets.library.PathQuery.is_path_query with self.force_implicit_query_detection(): assert is_path_query("/foo/bar") assert is_path_query("foo/bar") assert is_path_query("foo/") assert not is_path_query("foo") assert is_path_query("foo/:bar") assert not is_path_query("foo:bar/") assert not is_path_query("foo:/bar") # FIXME: shouldn't this also work on windows? @unittest.skipIf(sys.platform == "win32", WIN32_NO_IMPLICIT_PATHS) def test_detect_absolute_path(self): """Test detection of implicit path queries based on whether or not the path actually exists, when using an absolute path query. Thus, don't use the `force_implicit_query_detection()` contextmanager which would disable the existence check. """ is_path_query = beets.library.PathQuery.is_path_query path = self.touch(os.path.join(b"foo", b"bar")) assert os.path.isabs(util.syspath(path)) path_str = path.decode("utf-8") # The file itself. assert is_path_query(path_str) # The parent directory. parent = os.path.dirname(path_str) assert is_path_query(parent) # Some non-existent path. assert not is_path_query(f"{path_str}baz") def test_detect_relative_path(self): """Test detection of implicit path queries based on whether or not the path actually exists, when using a relative path query. Thus, don't use the `force_implicit_query_detection()` contextmanager which would disable the existence check. """ is_path_query = beets.library.PathQuery.is_path_query self.touch(os.path.join(b"foo", b"bar")) # Temporarily change directory so relative paths work. cur_dir = os.getcwd() try: os.chdir(syspath(self.temp_dir)) assert is_path_query("foo/") assert is_path_query("foo/bar") assert is_path_query("foo/bar:tagada") assert not is_path_query("bar") finally: os.chdir(cur_dir) class IntQueryTest(BeetsTestCase): def tearDown(self): super().tearDown() Item._types = {} def test_exact_value_match(self): item = self.add_item(bpm=120) matched = self.lib.items("bpm:120").get() assert item.id == matched.id def test_range_match(self): item = self.add_item(bpm=120) self.add_item(bpm=130) matched = self.lib.items("bpm:110..125") assert 1 == len(matched) assert item.id == matched.get().id def test_flex_range_match(self): Item._types = {"myint": types.Integer()} item = self.add_item(myint=2) matched = self.lib.items("myint:2").get() assert item.id == matched.id def test_flex_dont_match_missing(self): Item._types = {"myint": types.Integer()} self.add_item() matched = self.lib.items("myint:2").get() assert matched is None def test_no_substring_match(self): self.add_item(bpm=120) matched = self.lib.items("bpm:12").get() assert matched is None class BoolQueryTest(BeetsTestCase, AssertsMixin): def setUp(self): super().setUp() Item._types = {"flexbool": types.Boolean()} def tearDown(self): super().tearDown() Item._types = {} def test_parse_true(self): item_true = self.add_item(comp=True) item_false = self.add_item(comp=False) matched = self.lib.items("comp:true") self.assertInResult(item_true, matched) self.assertNotInResult(item_false, matched) def test_flex_parse_true(self): item_true = self.add_item(flexbool=True) item_false = self.add_item(flexbool=False) matched = self.lib.items("flexbool:true") self.assertInResult(item_true, matched) self.assertNotInResult(item_false, matched) def test_flex_parse_false(self): item_true = self.add_item(flexbool=True) item_false = self.add_item(flexbool=False) matched = self.lib.items("flexbool:false") self.assertInResult(item_false, matched) self.assertNotInResult(item_true, matched) def test_flex_parse_1(self): item_true = self.add_item(flexbool=True) item_false = self.add_item(flexbool=False) matched = self.lib.items("flexbool:1") self.assertInResult(item_true, matched) self.assertNotInResult(item_false, matched) def test_flex_parse_0(self): item_true = self.add_item(flexbool=True) item_false = self.add_item(flexbool=False) matched = self.lib.items("flexbool:0") self.assertInResult(item_false, matched) self.assertNotInResult(item_true, matched) def test_flex_parse_any_string(self): # TODO this should be the other way around item_true = self.add_item(flexbool=True) item_false = self.add_item(flexbool=False) matched = self.lib.items("flexbool:something") self.assertInResult(item_false, matched) self.assertNotInResult(item_true, matched) class DefaultSearchFieldsTest(DummyDataTestCase): def test_albums_matches_album(self): albums = list(self.lib.albums("baz")) assert len(albums) == 1 def test_albums_matches_albumartist(self): albums = list(self.lib.albums(["album artist"])) assert len(albums) == 1 def test_items_matches_title(self): items = self.lib.items("beets") self.assert_items_matched(items, ["beets 4 eva"]) def test_items_does_not_match_year(self): items = self.lib.items("2001") self.assert_items_matched(items, []) class NoneQueryTest(BeetsTestCase, AssertsMixin): def test_match_singletons(self): singleton = self.add_item() album_item = self.add_album().items().get() matched = self.lib.items(NoneQuery("album_id")) self.assertInResult(singleton, matched) self.assertNotInResult(album_item, matched) def test_match_after_set_none(self): item = self.add_item(rg_track_gain=0) matched = self.lib.items(NoneQuery("rg_track_gain")) self.assertNotInResult(item, matched) item["rg_track_gain"] = None item.store() matched = self.lib.items(NoneQuery("rg_track_gain")) self.assertInResult(item, matched) def test_match_slow(self): item = self.add_item() matched = self.lib.items(NoneQuery("rg_track_peak", fast=False)) self.assertInResult(item, matched) def test_match_slow_after_set_none(self): item = self.add_item(rg_track_gain=0) matched = self.lib.items(NoneQuery("rg_track_gain", fast=False)) self.assertNotInResult(item, matched) item["rg_track_gain"] = None item.store() matched = self.lib.items(NoneQuery("rg_track_gain", fast=False)) self.assertInResult(item, matched) class NotQueryMatchTest(BeetsTestCase): """Test `query.NotQuery` matching against a single item, using the same cases and assertions as on `MatchTest`, plus assertion on the negated queries (ie. assert q -> assert not NotQuery(q)). """ def setUp(self): super().setUp() self.item = _common.item() def test_regex_match_positive(self): q = dbcore.query.RegexpQuery("album", "^the album$") assert q.match(self.item) assert not dbcore.query.NotQuery(q).match(self.item) def test_regex_match_negative(self): q = dbcore.query.RegexpQuery("album", "^album$") assert not q.match(self.item) assert dbcore.query.NotQuery(q).match(self.item) def test_regex_match_non_string_value(self): q = dbcore.query.RegexpQuery("disc", "^6$") assert q.match(self.item) assert not dbcore.query.NotQuery(q).match(self.item) def test_substring_match_positive(self): q = dbcore.query.SubstringQuery("album", "album") assert q.match(self.item) assert not dbcore.query.NotQuery(q).match(self.item) def test_substring_match_negative(self): q = dbcore.query.SubstringQuery("album", "ablum") assert not q.match(self.item) assert dbcore.query.NotQuery(q).match(self.item) def test_substring_match_non_string_value(self): q = dbcore.query.SubstringQuery("disc", "6") assert q.match(self.item) assert not dbcore.query.NotQuery(q).match(self.item) def test_year_match_positive(self): q = dbcore.query.NumericQuery("year", "1") assert q.match(self.item) assert not dbcore.query.NotQuery(q).match(self.item) def test_year_match_negative(self): q = dbcore.query.NumericQuery("year", "10") assert not q.match(self.item) assert dbcore.query.NotQuery(q).match(self.item) def test_bitrate_range_positive(self): q = dbcore.query.NumericQuery("bitrate", "100000..200000") assert q.match(self.item) assert not dbcore.query.NotQuery(q).match(self.item) def test_bitrate_range_negative(self): q = dbcore.query.NumericQuery("bitrate", "200000..300000") assert not q.match(self.item) assert dbcore.query.NotQuery(q).match(self.item) def test_open_range(self): q = dbcore.query.NumericQuery("bitrate", "100000..") dbcore.query.NotQuery(q) class NotQueryTest(DummyDataTestCase): """Test `query.NotQuery` against the dummy data: - `test_type_xxx`: tests for the negation of a particular XxxQuery class. - `test_get_yyy`: tests on query strings (similar to `GetTest`) """ def assertNegationProperties(self, q): """Given a Query `q`, assert that: - q OR not(q) == all items - q AND not(q) == 0 - not(not(q)) == q """ not_q = dbcore.query.NotQuery(q) # assert using OrQuery, AndQuery q_or = dbcore.query.OrQuery([q, not_q]) q_and = dbcore.query.AndQuery([q, not_q]) self.assert_items_matched_all(self.lib.items(q_or)) self.assert_items_matched(self.lib.items(q_and), []) # assert manually checking the item titles all_titles = {i.title for i in self.lib.items()} q_results = {i.title for i in self.lib.items(q)} not_q_results = {i.title for i in self.lib.items(not_q)} assert q_results.union(not_q_results) == all_titles assert q_results.intersection(not_q_results) == set() # round trip not_not_q = dbcore.query.NotQuery(not_q) assert {i.title for i in self.lib.items(q)} == { i.title for i in self.lib.items(not_not_q) } def test_type_and(self): # not(a and b) <-> not(a) or not(b) q = dbcore.query.AndQuery( [ dbcore.query.BooleanQuery("comp", True), dbcore.query.NumericQuery("year", "2002"), ], ) not_results = self.lib.items(dbcore.query.NotQuery(q)) self.assert_items_matched(not_results, ["foo bar", "beets 4 eva"]) self.assertNegationProperties(q) def test_type_anyfield(self): q = dbcore.query.AnyFieldQuery( "foo", ["title", "artist", "album"], dbcore.query.SubstringQuery ) not_results = self.lib.items(dbcore.query.NotQuery(q)) self.assert_items_matched(not_results, ["baz qux"]) self.assertNegationProperties(q) def test_type_boolean(self): q = dbcore.query.BooleanQuery("comp", True) not_results = self.lib.items(dbcore.query.NotQuery(q)) self.assert_items_matched(not_results, ["beets 4 eva"]) self.assertNegationProperties(q) def test_type_date(self): q = dbcore.query.DateQuery("added", "2000-01-01") not_results = self.lib.items(dbcore.query.NotQuery(q)) # query date is in the past, thus the 'not' results should contain all # items self.assert_items_matched( not_results, ["foo bar", "baz qux", "beets 4 eva"] ) self.assertNegationProperties(q) def test_type_false(self): q = dbcore.query.FalseQuery() not_results = self.lib.items(dbcore.query.NotQuery(q)) self.assert_items_matched_all(not_results) self.assertNegationProperties(q) def test_type_match(self): q = dbcore.query.MatchQuery("year", "2003") not_results = self.lib.items(dbcore.query.NotQuery(q)) self.assert_items_matched(not_results, ["foo bar", "baz qux"]) self.assertNegationProperties(q) def test_type_none(self): q = dbcore.query.NoneQuery("rg_track_gain") not_results = self.lib.items(dbcore.query.NotQuery(q)) self.assert_items_matched(not_results, []) self.assertNegationProperties(q) def test_type_numeric(self): q = dbcore.query.NumericQuery("year", "2001..2002") not_results = self.lib.items(dbcore.query.NotQuery(q)) self.assert_items_matched(not_results, ["beets 4 eva"]) self.assertNegationProperties(q) def test_type_or(self): # not(a or b) <-> not(a) and not(b) q = dbcore.query.OrQuery( [ dbcore.query.BooleanQuery("comp", True), dbcore.query.NumericQuery("year", "2002"), ] ) not_results = self.lib.items(dbcore.query.NotQuery(q)) self.assert_items_matched(not_results, ["beets 4 eva"]) self.assertNegationProperties(q) def test_type_regexp(self): q = dbcore.query.RegexpQuery("artist", "^t") not_results = self.lib.items(dbcore.query.NotQuery(q)) self.assert_items_matched(not_results, ["foo bar"]) self.assertNegationProperties(q) def test_type_substring(self): q = dbcore.query.SubstringQuery("album", "ba") not_results = self.lib.items(dbcore.query.NotQuery(q)) self.assert_items_matched(not_results, ["beets 4 eva"]) self.assertNegationProperties(q) def test_type_true(self): q = dbcore.query.TrueQuery() not_results = self.lib.items(dbcore.query.NotQuery(q)) self.assert_items_matched(not_results, []) self.assertNegationProperties(q) def test_get_prefixes_keyed(self): """Test both negation prefixes on a keyed query.""" q0 = "-title:qux" q1 = "^title:qux" results0 = self.lib.items(q0) results1 = self.lib.items(q1) self.assert_items_matched(results0, ["foo bar", "beets 4 eva"]) self.assert_items_matched(results1, ["foo bar", "beets 4 eva"]) def test_get_prefixes_unkeyed(self): """Test both negation prefixes on an unkeyed query.""" q0 = "-qux" q1 = "^qux" results0 = self.lib.items(q0) results1 = self.lib.items(q1) self.assert_items_matched(results0, ["foo bar", "beets 4 eva"]) self.assert_items_matched(results1, ["foo bar", "beets 4 eva"]) def test_get_one_keyed_regexp(self): q = "-artist::t.+r" results = self.lib.items(q) self.assert_items_matched(results, ["foo bar", "baz qux"]) def test_get_one_unkeyed_regexp(self): q = "-:x$" results = self.lib.items(q) self.assert_items_matched(results, ["foo bar", "beets 4 eva"]) def test_get_multiple_terms(self): q = "baz -bar" results = self.lib.items(q) self.assert_items_matched(results, ["baz qux"]) def test_get_mixed_terms(self): q = "baz -title:bar" results = self.lib.items(q) self.assert_items_matched(results, ["baz qux"]) def test_fast_vs_slow(self): """Test that the results are the same regardless of the `fast` flag for negated `FieldQuery`s. TODO: investigate NoneQuery(fast=False), as it is raising AttributeError: type object 'NoneQuery' has no attribute 'field' at NoneQuery.match() (due to being @classmethod, and no self?) """ classes = [ (dbcore.query.DateQuery, ["added", "2001-01-01"]), (dbcore.query.MatchQuery, ["artist", "one"]), # (dbcore.query.NoneQuery, ['rg_track_gain']), (dbcore.query.NumericQuery, ["year", "2002"]), (dbcore.query.StringFieldQuery, ["year", "2001"]), (dbcore.query.RegexpQuery, ["album", "^.a"]), (dbcore.query.SubstringQuery, ["title", "x"]), ] for klass, args in classes: q_fast = dbcore.query.NotQuery(klass(*(args + [True]))) q_slow = dbcore.query.NotQuery(klass(*(args + [False]))) try: assert [i.title for i in self.lib.items(q_fast)] == [ i.title for i in self.lib.items(q_slow) ] except NotImplementedError: # ignore classes that do not provide `fast` implementation pass class RelatedQueriesTest(BeetsTestCase, AssertsMixin): """Test album-level queries with track-level filters and vice-versa.""" def setUp(self): super().setUp() albums = [] for album_idx in range(1, 3): album_name = f"Album{album_idx}" album_items = [] for item_idx in range(1, 3): item = _common.item() item.album = album_name item.title = f"{album_name} Item{item_idx}" self.lib.add(item) album_items.append(item) album = self.lib.add_album(album_items) album.artpath = f"{album_name} Artpath" album.catalognum = "ABC" album.store() albums.append(album) self.album, self.another_album = albums def test_get_albums_filter_by_track_field(self): q = "title:Album1" results = self.lib.albums(q) self.assert_albums_matched(results, ["Album1"]) def test_get_items_filter_by_album_field(self): q = "artpath::Album1" results = self.lib.items(q) self.assert_items_matched(results, ["Album1 Item1", "Album1 Item2"]) def test_filter_by_common_field(self): q = "catalognum:ABC Album1" results = self.lib.albums(q) self.assert_albums_matched(results, ["Album1"]) beetbox-beets-01f1faf/test/test_sort.py000066400000000000000000000441741472325477400203400ustar00rootroot00000000000000# This file is part of beets. # Copyright 2016, Adrian Sampson. # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the # "Software"), to deal in the Software without restriction, including # without limitation the rights to use, copy, modify, merge, publish, # distribute, sublicense, and/or sell copies of the Software, and to # permit persons to whom the Software is furnished to do so, subject to # the following conditions: # # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. """Various tests for querying the library database.""" import beets.library from beets import config, dbcore from beets.library import Album from beets.test import _common from beets.test.helper import BeetsTestCase # A test case class providing a library with some dummy data and some # assertions involving that data. class DummyDataTestCase(BeetsTestCase): def setUp(self): super().setUp() albums = [ Album( album="Album A", genre="Rock", year=2001, flex1="Flex1-1", flex2="Flex2-A", albumartist="Foo", ), Album( album="Album B", genre="Rock", year=2001, flex1="Flex1-2", flex2="Flex2-A", albumartist="Bar", ), Album( album="Album C", genre="Jazz", year=2005, flex1="Flex1-1", flex2="Flex2-B", albumartist="Baz", ), ] for album in albums: self.lib.add(album) items = [_common.item() for _ in range(4)] items[0].title = "Foo bar" items[0].artist = "One" items[0].album = "Baz" items[0].year = 2001 items[0].comp = True items[0].flex1 = "Flex1-0" items[0].flex2 = "Flex2-A" items[0].album_id = albums[0].id items[0].artist_sort = None items[0].path = "/path0.mp3" items[0].track = 1 items[1].title = "Baz qux" items[1].artist = "Two" items[1].album = "Baz" items[1].year = 2002 items[1].comp = True items[1].flex1 = "Flex1-1" items[1].flex2 = "Flex2-A" items[1].album_id = albums[0].id items[1].artist_sort = None items[1].path = "/patH1.mp3" items[1].track = 2 items[2].title = "Beets 4 eva" items[2].artist = "Three" items[2].album = "Foo" items[2].year = 2003 items[2].comp = False items[2].flex1 = "Flex1-2" items[2].flex2 = "Flex1-B" items[2].album_id = albums[1].id items[2].artist_sort = None items[2].path = "/paTH2.mp3" items[2].track = 3 items[3].title = "Beets 4 eva" items[3].artist = "Three" items[3].album = "Foo2" items[3].year = 2004 items[3].comp = False items[3].flex1 = "Flex1-2" items[3].flex2 = "Flex1-C" items[3].album_id = albums[2].id items[3].artist_sort = None items[3].path = "/PATH3.mp3" items[3].track = 4 for item in items: self.lib.add(item) class SortFixedFieldTest(DummyDataTestCase): def test_sort_asc(self): q = "" sort = dbcore.query.FixedFieldSort("year", True) results = self.lib.items(q, sort) assert results[0]["year"] <= results[1]["year"] assert results[0]["year"] == 2001 # same thing with query string q = "year+" results2 = self.lib.items(q) for r1, r2 in zip(results, results2): assert r1.id == r2.id def test_sort_desc(self): q = "" sort = dbcore.query.FixedFieldSort("year", False) results = self.lib.items(q, sort) assert results[0]["year"] >= results[1]["year"] assert results[0]["year"] == 2004 # same thing with query string q = "year-" results2 = self.lib.items(q) for r1, r2 in zip(results, results2): assert r1.id == r2.id def test_sort_two_field_asc(self): q = "" s1 = dbcore.query.FixedFieldSort("album", True) s2 = dbcore.query.FixedFieldSort("year", True) sort = dbcore.query.MultipleSort() sort.add_sort(s1) sort.add_sort(s2) results = self.lib.items(q, sort) assert results[0]["album"] <= results[1]["album"] assert results[1]["album"] <= results[2]["album"] assert results[0]["album"] == "Baz" assert results[1]["album"] == "Baz" assert results[0]["year"] <= results[1]["year"] # same thing with query string q = "album+ year+" results2 = self.lib.items(q) for r1, r2 in zip(results, results2): assert r1.id == r2.id def test_sort_path_field(self): q = "" sort = dbcore.query.FixedFieldSort("path", True) results = self.lib.items(q, sort) assert results[0]["path"] == b"/path0.mp3" assert results[1]["path"] == b"/patH1.mp3" assert results[2]["path"] == b"/paTH2.mp3" assert results[3]["path"] == b"/PATH3.mp3" class SortFlexFieldTest(DummyDataTestCase): def test_sort_asc(self): q = "" sort = dbcore.query.SlowFieldSort("flex1", True) results = self.lib.items(q, sort) assert results[0]["flex1"] <= results[1]["flex1"] assert results[0]["flex1"] == "Flex1-0" # same thing with query string q = "flex1+" results2 = self.lib.items(q) for r1, r2 in zip(results, results2): assert r1.id == r2.id def test_sort_desc(self): q = "" sort = dbcore.query.SlowFieldSort("flex1", False) results = self.lib.items(q, sort) assert results[0]["flex1"] >= results[1]["flex1"] assert results[1]["flex1"] >= results[2]["flex1"] assert results[2]["flex1"] >= results[3]["flex1"] assert results[0]["flex1"] == "Flex1-2" # same thing with query string q = "flex1-" results2 = self.lib.items(q) for r1, r2 in zip(results, results2): assert r1.id == r2.id def test_sort_two_field(self): q = "" s1 = dbcore.query.SlowFieldSort("flex2", False) s2 = dbcore.query.SlowFieldSort("flex1", True) sort = dbcore.query.MultipleSort() sort.add_sort(s1) sort.add_sort(s2) results = self.lib.items(q, sort) assert results[0]["flex2"] >= results[1]["flex2"] assert results[1]["flex2"] >= results[2]["flex2"] assert results[0]["flex2"] == "Flex2-A" assert results[1]["flex2"] == "Flex2-A" assert results[0]["flex1"] <= results[1]["flex1"] # same thing with query string q = "flex2- flex1+" results2 = self.lib.items(q) for r1, r2 in zip(results, results2): assert r1.id == r2.id class SortAlbumFixedFieldTest(DummyDataTestCase): def test_sort_asc(self): q = "" sort = dbcore.query.FixedFieldSort("year", True) results = self.lib.albums(q, sort) assert results[0]["year"] <= results[1]["year"] assert results[0]["year"] == 2001 # same thing with query string q = "year+" results2 = self.lib.albums(q) for r1, r2 in zip(results, results2): assert r1.id == r2.id def test_sort_desc(self): q = "" sort = dbcore.query.FixedFieldSort("year", False) results = self.lib.albums(q, sort) assert results[0]["year"] >= results[1]["year"] assert results[0]["year"] == 2005 # same thing with query string q = "year-" results2 = self.lib.albums(q) for r1, r2 in zip(results, results2): assert r1.id == r2.id def test_sort_two_field_asc(self): q = "" s1 = dbcore.query.FixedFieldSort("genre", True) s2 = dbcore.query.FixedFieldSort("album", True) sort = dbcore.query.MultipleSort() sort.add_sort(s1) sort.add_sort(s2) results = self.lib.albums(q, sort) assert results[0]["genre"] <= results[1]["genre"] assert results[1]["genre"] <= results[2]["genre"] assert results[1]["genre"] == "Rock" assert results[2]["genre"] == "Rock" assert results[1]["album"] <= results[2]["album"] # same thing with query string q = "genre+ album+" results2 = self.lib.albums(q) for r1, r2 in zip(results, results2): assert r1.id == r2.id class SortAlbumFlexFieldTest(DummyDataTestCase): def test_sort_asc(self): q = "" sort = dbcore.query.SlowFieldSort("flex1", True) results = self.lib.albums(q, sort) assert results[0]["flex1"] <= results[1]["flex1"] assert results[1]["flex1"] <= results[2]["flex1"] # same thing with query string q = "flex1+" results2 = self.lib.albums(q) for r1, r2 in zip(results, results2): assert r1.id == r2.id def test_sort_desc(self): q = "" sort = dbcore.query.SlowFieldSort("flex1", False) results = self.lib.albums(q, sort) assert results[0]["flex1"] >= results[1]["flex1"] assert results[1]["flex1"] >= results[2]["flex1"] # same thing with query string q = "flex1-" results2 = self.lib.albums(q) for r1, r2 in zip(results, results2): assert r1.id == r2.id def test_sort_two_field_asc(self): q = "" s1 = dbcore.query.SlowFieldSort("flex2", True) s2 = dbcore.query.SlowFieldSort("flex1", True) sort = dbcore.query.MultipleSort() sort.add_sort(s1) sort.add_sort(s2) results = self.lib.albums(q, sort) assert results[0]["flex2"] <= results[1]["flex2"] assert results[1]["flex2"] <= results[2]["flex2"] assert results[0]["flex2"] == "Flex2-A" assert results[1]["flex2"] == "Flex2-A" assert results[0]["flex1"] <= results[1]["flex1"] # same thing with query string q = "flex2+ flex1+" results2 = self.lib.albums(q) for r1, r2 in zip(results, results2): assert r1.id == r2.id class SortAlbumComputedFieldTest(DummyDataTestCase): def test_sort_asc(self): q = "" sort = dbcore.query.SlowFieldSort("path", True) results = self.lib.albums(q, sort) assert results[0]["path"] <= results[1]["path"] assert results[1]["path"] <= results[2]["path"] # same thing with query string q = "path+" results2 = self.lib.albums(q) for r1, r2 in zip(results, results2): assert r1.id == r2.id def test_sort_desc(self): q = "" sort = dbcore.query.SlowFieldSort("path", False) results = self.lib.albums(q, sort) assert results[0]["path"] >= results[1]["path"] assert results[1]["path"] >= results[2]["path"] # same thing with query string q = "path-" results2 = self.lib.albums(q) for r1, r2 in zip(results, results2): assert r1.id == r2.id class SortCombinedFieldTest(DummyDataTestCase): def test_computed_first(self): q = "" s1 = dbcore.query.SlowFieldSort("path", True) s2 = dbcore.query.FixedFieldSort("year", True) sort = dbcore.query.MultipleSort() sort.add_sort(s1) sort.add_sort(s2) results = self.lib.albums(q, sort) assert results[0]["path"] <= results[1]["path"] assert results[1]["path"] <= results[2]["path"] q = "path+ year+" results2 = self.lib.albums(q) for r1, r2 in zip(results, results2): assert r1.id == r2.id def test_computed_second(self): q = "" s1 = dbcore.query.FixedFieldSort("year", True) s2 = dbcore.query.SlowFieldSort("path", True) sort = dbcore.query.MultipleSort() sort.add_sort(s1) sort.add_sort(s2) results = self.lib.albums(q, sort) assert results[0]["year"] <= results[1]["year"] assert results[1]["year"] <= results[2]["year"] assert results[0]["path"] <= results[1]["path"] q = "year+ path+" results2 = self.lib.albums(q) for r1, r2 in zip(results, results2): assert r1.id == r2.id class ConfigSortTest(DummyDataTestCase): def test_default_sort_item(self): results = list(self.lib.items()) assert results[0].artist < results[1].artist def test_config_opposite_sort_item(self): config["sort_item"] = "artist-" results = list(self.lib.items()) assert results[0].artist > results[1].artist def test_default_sort_album(self): results = list(self.lib.albums()) assert results[0].albumartist < results[1].albumartist def test_config_opposite_sort_album(self): config["sort_album"] = "albumartist-" results = list(self.lib.albums()) assert results[0].albumartist > results[1].albumartist class CaseSensitivityTest(DummyDataTestCase, BeetsTestCase): """If case_insensitive is false, lower-case values should be placed after all upper-case values. E.g., `Foo Qux bar` """ def setUp(self): super().setUp() album = Album( album="album", genre="alternative", year="2001", flex1="flex1", flex2="flex2-A", albumartist="bar", ) self.lib.add(album) item = _common.item() item.title = "another" item.artist = "lowercase" item.album = "album" item.year = 2001 item.comp = True item.flex1 = "flex1" item.flex2 = "flex2-A" item.album_id = album.id item.artist_sort = None item.track = 10 self.lib.add(item) self.new_album = album self.new_item = item def tearDown(self): self.new_item.remove(delete=True) self.new_album.remove(delete=True) super().tearDown() def test_smart_artist_case_insensitive(self): config["sort_case_insensitive"] = True q = "artist+" results = list(self.lib.items(q)) assert results[0].artist == "lowercase" assert results[1].artist == "One" def test_smart_artist_case_sensitive(self): config["sort_case_insensitive"] = False q = "artist+" results = list(self.lib.items(q)) assert results[0].artist == "One" assert results[-1].artist == "lowercase" def test_fixed_field_case_insensitive(self): config["sort_case_insensitive"] = True q = "album+" results = list(self.lib.albums(q)) assert results[0].album == "album" assert results[1].album == "Album A" def test_fixed_field_case_sensitive(self): config["sort_case_insensitive"] = False q = "album+" results = list(self.lib.albums(q)) assert results[0].album == "Album A" assert results[-1].album == "album" def test_flex_field_case_insensitive(self): config["sort_case_insensitive"] = True q = "flex1+" results = list(self.lib.items(q)) assert results[0].flex1 == "flex1" assert results[1].flex1 == "Flex1-0" def test_flex_field_case_sensitive(self): config["sort_case_insensitive"] = False q = "flex1+" results = list(self.lib.items(q)) assert results[0].flex1 == "Flex1-0" assert results[-1].flex1 == "flex1" def test_case_sensitive_only_affects_text(self): config["sort_case_insensitive"] = True q = "track+" results = list(self.lib.items(q)) # If the numerical values were sorted as strings, # then ['1', '10', '2'] would be valid. # print([r.track for r in results]) assert results[0].track == 1 assert results[1].track == 2 assert results[-1].track == 10 class NonExistingFieldTest(DummyDataTestCase): """Test sorting by non-existing fields""" def test_non_existing_fields_not_fail(self): qs = ["foo+", "foo-", "--", "-+", "+-", "++", "-foo-", "-foo+", "---"] q0 = "foo+" results0 = list(self.lib.items(q0)) for q1 in qs: results1 = list(self.lib.items(q1)) for r1, r2 in zip(results0, results1): assert r1.id == r2.id def test_combined_non_existing_field_asc(self): all_results = list(self.lib.items("id+")) q = "foo+ id+" results = list(self.lib.items(q)) assert len(all_results) == len(results) for r1, r2 in zip(all_results, results): assert r1.id == r2.id def test_combined_non_existing_field_desc(self): all_results = list(self.lib.items("id+")) q = "foo- id+" results = list(self.lib.items(q)) assert len(all_results) == len(results) for r1, r2 in zip(all_results, results): assert r1.id == r2.id def test_field_present_in_some_items(self): """Test ordering by a field not present on all items.""" # append 'foo' to two to items (1,2) items = self.lib.items("id+") ids = [i.id for i in items] items[1].foo = "bar1" items[2].foo = "bar2" items[1].store() items[2].store() results_asc = list(self.lib.items("foo+ id+")) # items without field first assert [i.id for i in results_asc] == [ids[0], ids[3], ids[1], ids[2]] results_desc = list(self.lib.items("foo- id+")) # items without field last assert [i.id for i in results_desc] == [ids[2], ids[1], ids[0], ids[3]] def test_negation_interaction(self): """Test the handling of negation and sorting together. If a string ends with a sorting suffix, it takes precedence over the NotQuery parsing. """ query, sort = beets.library.parse_query_string( "-bar+", beets.library.Item ) assert len(query.subqueries) == 1 assert isinstance(query.subqueries[0], dbcore.query.TrueQuery) assert isinstance(sort, dbcore.query.SlowFieldSort) assert sort.field == "-bar" beetbox-beets-01f1faf/test/test_template.py000066400000000000000000000225041472325477400211550ustar00rootroot00000000000000# This file is part of beets. # Copyright 2016, Adrian Sampson. # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the # "Software"), to deal in the Software without restriction, including # without limitation the rights to use, copy, modify, merge, publish, # distribute, sublicense, and/or sell copies of the Software, and to # permit persons to whom the Software is furnished to do so, subject to # the following conditions: # # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. """Tests for template engine.""" import unittest from beets.util import functemplate def _normexpr(expr): """Normalize an Expression object's parts, collapsing multiple adjacent text blocks and removing empty text blocks. Generates a sequence of parts. """ textbuf = [] for part in expr.parts: if isinstance(part, str): textbuf.append(part) else: if textbuf: text = "".join(textbuf) if text: yield text textbuf = [] yield part if textbuf: text = "".join(textbuf) if text: yield text def _normparse(text): """Parse a template and then normalize the resulting Expression.""" return _normexpr(functemplate._parse(text)) class ParseTest(unittest.TestCase): def test_empty_string(self): assert list(_normparse("")) == [] def _assert_symbol(self, obj, ident): """Assert that an object is a Symbol with the given identifier.""" assert isinstance(obj, functemplate.Symbol), f"not a Symbol: {obj}" assert obj.ident == ident, f"wrong identifier: {obj.ident} vs. {ident}" def _assert_call(self, obj, ident, numargs): """Assert that an object is a Call with the given identifier and argument count. """ assert isinstance(obj, functemplate.Call), f"not a Call: {obj}" assert obj.ident == ident, f"wrong identifier: {obj.ident} vs. {ident}" assert ( len(obj.args) == numargs ), f"wrong argument count in {obj.ident}: {len(obj.args)} vs. {numargs}" def test_plain_text(self): assert list(_normparse("hello world")) == ["hello world"] def test_escaped_character_only(self): assert list(_normparse("$$")) == ["$"] def test_escaped_character_in_text(self): assert list(_normparse("a $$ b")) == ["a $ b"] def test_escaped_character_at_start(self): assert list(_normparse("$$ hello")) == ["$ hello"] def test_escaped_character_at_end(self): assert list(_normparse("hello $$")) == ["hello $"] def test_escaped_function_delim(self): assert list(_normparse("a $% b")) == ["a % b"] def test_escaped_sep(self): assert list(_normparse("a $, b")) == ["a , b"] def test_escaped_close_brace(self): assert list(_normparse("a $} b")) == ["a } b"] def test_bare_value_delim_kept_intact(self): assert list(_normparse("a $ b")) == ["a $ b"] def test_bare_function_delim_kept_intact(self): assert list(_normparse("a % b")) == ["a % b"] def test_bare_opener_kept_intact(self): assert list(_normparse("a { b")) == ["a { b"] def test_bare_closer_kept_intact(self): assert list(_normparse("a } b")) == ["a } b"] def test_bare_sep_kept_intact(self): assert list(_normparse("a , b")) == ["a , b"] def test_symbol_alone(self): parts = list(_normparse("$foo")) assert len(parts) == 1 self._assert_symbol(parts[0], "foo") def test_symbol_in_text(self): parts = list(_normparse("hello $foo world")) assert len(parts) == 3 assert parts[0] == "hello " self._assert_symbol(parts[1], "foo") assert parts[2] == " world" def test_symbol_with_braces(self): parts = list(_normparse("hello${foo}world")) assert len(parts) == 3 assert parts[0] == "hello" self._assert_symbol(parts[1], "foo") assert parts[2] == "world" def test_unclosed_braces_symbol(self): assert list(_normparse("a ${ b")) == ["a ${ b"] def test_empty_braces_symbol(self): assert list(_normparse("a ${} b")) == ["a ${} b"] def test_call_without_args_at_end(self): assert list(_normparse("foo %bar")) == ["foo %bar"] def test_call_without_args(self): assert list(_normparse("foo %bar baz")) == ["foo %bar baz"] def test_call_with_unclosed_args(self): assert list(_normparse("foo %bar{ baz")) == ["foo %bar{ baz"] def test_call_with_unclosed_multiple_args(self): assert list(_normparse("foo %bar{bar,bar baz")) == [ "foo %bar{bar,bar baz" ] def test_call_empty_arg(self): parts = list(_normparse("%foo{}")) assert len(parts) == 1 self._assert_call(parts[0], "foo", 1) assert list(_normexpr(parts[0].args[0])) == [] def test_call_single_arg(self): parts = list(_normparse("%foo{bar}")) assert len(parts) == 1 self._assert_call(parts[0], "foo", 1) assert list(_normexpr(parts[0].args[0])) == ["bar"] def test_call_two_args(self): parts = list(_normparse("%foo{bar,baz}")) assert len(parts) == 1 self._assert_call(parts[0], "foo", 2) assert list(_normexpr(parts[0].args[0])) == ["bar"] assert list(_normexpr(parts[0].args[1])) == ["baz"] def test_call_with_escaped_sep(self): parts = list(_normparse("%foo{bar$,baz}")) assert len(parts) == 1 self._assert_call(parts[0], "foo", 1) assert list(_normexpr(parts[0].args[0])) == ["bar,baz"] def test_call_with_escaped_close(self): parts = list(_normparse("%foo{bar$}baz}")) assert len(parts) == 1 self._assert_call(parts[0], "foo", 1) assert list(_normexpr(parts[0].args[0])) == ["bar}baz"] def test_call_with_symbol_argument(self): parts = list(_normparse("%foo{$bar,baz}")) assert len(parts) == 1 self._assert_call(parts[0], "foo", 2) arg_parts = list(_normexpr(parts[0].args[0])) assert len(arg_parts) == 1 self._assert_symbol(arg_parts[0], "bar") assert list(_normexpr(parts[0].args[1])) == ["baz"] def test_call_with_nested_call_argument(self): parts = list(_normparse("%foo{%bar{},baz}")) assert len(parts) == 1 self._assert_call(parts[0], "foo", 2) arg_parts = list(_normexpr(parts[0].args[0])) assert len(arg_parts) == 1 self._assert_call(arg_parts[0], "bar", 1) assert list(_normexpr(parts[0].args[1])) == ["baz"] def test_nested_call_with_argument(self): parts = list(_normparse("%foo{%bar{baz}}")) assert len(parts) == 1 self._assert_call(parts[0], "foo", 1) arg_parts = list(_normexpr(parts[0].args[0])) assert len(arg_parts) == 1 self._assert_call(arg_parts[0], "bar", 1) assert list(_normexpr(arg_parts[0].args[0])) == ["baz"] def test_sep_before_call_two_args(self): parts = list(_normparse("hello, %foo{bar,baz}")) assert len(parts) == 2 assert parts[0] == "hello, " self._assert_call(parts[1], "foo", 2) assert list(_normexpr(parts[1].args[0])) == ["bar"] assert list(_normexpr(parts[1].args[1])) == ["baz"] def test_sep_with_symbols(self): parts = list(_normparse("hello,$foo,$bar")) assert len(parts) == 4 assert parts[0] == "hello," self._assert_symbol(parts[1], "foo") assert parts[2] == "," self._assert_symbol(parts[3], "bar") def test_newline_at_end(self): parts = list(_normparse("foo\n")) assert len(parts) == 1 assert parts[0] == "foo\n" class EvalTest(unittest.TestCase): def _eval(self, template): values = { "foo": "bar", "baz": "BaR", } functions = { "lower": str.lower, "len": len, } return functemplate.Template(template).substitute(values, functions) def test_plain_text(self): assert self._eval("foo") == "foo" def test_subtitute_value(self): assert self._eval("$foo") == "bar" def test_subtitute_value_in_text(self): assert self._eval("hello $foo world") == "hello bar world" def test_not_subtitute_undefined_value(self): assert self._eval("$bar") == "$bar" def test_function_call(self): assert self._eval("%lower{FOO}") == "foo" def test_function_call_with_text(self): assert self._eval("A %lower{FOO} B") == "A foo B" def test_nested_function_call(self): assert self._eval("%lower{%lower{FOO}}") == "foo" def test_symbol_in_argument(self): assert self._eval("%lower{$baz}") == "bar" def test_function_call_exception(self): res = self._eval("%lower{a,b,c,d,e}") assert isinstance(res, str) def test_function_returning_integer(self): assert self._eval("%len{foo}") == "3" def test_not_subtitute_undefined_func(self): assert self._eval("%bar{}") == "%bar{}" def test_not_subtitute_func_with_no_args(self): assert self._eval("%lower") == "%lower" def test_function_call_with_empty_arg(self): assert self._eval("%len{}") == "0" beetbox-beets-01f1faf/test/test_ui.py000066400000000000000000001553351472325477400177700ustar00rootroot00000000000000# This file is part of beets. # Copyright 2016, Adrian Sampson. # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the # "Software"), to deal in the Software without restriction, including # without limitation the rights to use, copy, modify, merge, publish, # distribute, sublicense, and/or sell copies of the Software, and to # permit persons to whom the Software is furnished to do so, subject to # the following conditions: # # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. """Tests for the command-line interface.""" import os import platform import re import shutil import subprocess import sys import unittest from unittest.mock import Mock, patch import pytest from confuse import ConfigError from mediafile import MediaFile from beets import autotag, config, library, plugins, ui, util from beets.autotag.match import distance from beets.test import _common from beets.test.helper import ( BeetsTestCase, PluginTestCase, capture_stdout, control_stdin, has_program, ) from beets.ui import commands from beets.util import MoveOperation, syspath class ListTest(BeetsTestCase): def setUp(self): super().setUp() self.item = _common.item() self.item.path = "xxx/yyy" self.lib.add(self.item) self.lib.add_album([self.item]) def _run_list(self, query="", album=False, path=False, fmt=""): with capture_stdout() as stdout: commands.list_items(self.lib, query, album, fmt) return stdout def test_list_outputs_item(self): stdout = self._run_list() assert "the title" in stdout.getvalue() def test_list_unicode_query(self): self.item.title = "na\xefve" self.item.store() self.lib._connection().commit() stdout = self._run_list(["na\xefve"]) out = stdout.getvalue() assert "na\xefve" in out def test_list_item_path(self): stdout = self._run_list(fmt="$path") assert stdout.getvalue().strip() == "xxx/yyy" def test_list_album_outputs_something(self): stdout = self._run_list(album=True) assert len(stdout.getvalue()) > 0 def test_list_album_path(self): stdout = self._run_list(album=True, fmt="$path") assert stdout.getvalue().strip() == "xxx" def test_list_album_omits_title(self): stdout = self._run_list(album=True) assert "the title" not in stdout.getvalue() def test_list_uses_track_artist(self): stdout = self._run_list() assert "the artist" in stdout.getvalue() assert "the album artist" not in stdout.getvalue() def test_list_album_uses_album_artist(self): stdout = self._run_list(album=True) assert "the artist" not in stdout.getvalue() assert "the album artist" in stdout.getvalue() def test_list_item_format_artist(self): stdout = self._run_list(fmt="$artist") assert "the artist" in stdout.getvalue() def test_list_item_format_multiple(self): stdout = self._run_list(fmt="$artist - $album - $year") assert "the artist - the album - 0001" == stdout.getvalue().strip() def test_list_album_format(self): stdout = self._run_list(album=True, fmt="$genre") assert "the genre" in stdout.getvalue() assert "the album" not in stdout.getvalue() class RemoveTest(BeetsTestCase): def setUp(self): super().setUp() self.io.install() # Copy a file into the library. self.item_path = os.path.join(_common.RSRC, b"full.mp3") self.i = library.Item.from_path(self.item_path) self.lib.add(self.i) self.i.move(operation=MoveOperation.COPY) def test_remove_items_no_delete(self): self.io.addinput("y") commands.remove_items(self.lib, "", False, False, False) items = self.lib.items() assert len(list(items)) == 0 self.assertExists(self.i.path) def test_remove_items_with_delete(self): self.io.addinput("y") commands.remove_items(self.lib, "", False, True, False) items = self.lib.items() assert len(list(items)) == 0 self.assertNotExists(self.i.path) def test_remove_items_with_force_no_delete(self): commands.remove_items(self.lib, "", False, False, True) items = self.lib.items() assert len(list(items)) == 0 self.assertExists(self.i.path) def test_remove_items_with_force_delete(self): commands.remove_items(self.lib, "", False, True, True) items = self.lib.items() assert len(list(items)) == 0 self.assertNotExists(self.i.path) def test_remove_items_select_with_delete(self): i2 = library.Item.from_path(self.item_path) self.lib.add(i2) i2.move(operation=MoveOperation.COPY) for s in ("s", "y", "n"): self.io.addinput(s) commands.remove_items(self.lib, "", False, True, False) items = self.lib.items() assert len(list(items)) == 1 # There is probably no guarantee that the items are queried in any # spcecific order, thus just ensure that exactly one was removed. # To improve upon this, self.io would need to have the capability to # generate input that depends on previous output. num_existing = 0 num_existing += 1 if os.path.exists(syspath(self.i.path)) else 0 num_existing += 1 if os.path.exists(syspath(i2.path)) else 0 assert num_existing == 1 def test_remove_albums_select_with_delete(self): a1 = self.add_album_fixture() a2 = self.add_album_fixture() path1 = a1.items()[0].path path2 = a2.items()[0].path items = self.lib.items() assert len(list(items)) == 3 for s in ("s", "y", "n"): self.io.addinput(s) commands.remove_items(self.lib, "", True, True, False) items = self.lib.items() assert len(list(items)) == 2 # incl. the item from setUp() # See test_remove_items_select_with_delete() num_existing = 0 num_existing += 1 if os.path.exists(syspath(path1)) else 0 num_existing += 1 if os.path.exists(syspath(path2)) else 0 assert num_existing == 1 class ModifyTest(BeetsTestCase): def setUp(self): super().setUp() self.album = self.add_album_fixture() [self.item] = self.album.items() def modify_inp(self, inp, *args): with control_stdin(inp): self.run_command("modify", *args) def modify(self, *args): self.modify_inp("y", *args) # Item tests def test_modify_item(self): self.modify("title=newTitle") item = self.lib.items().get() assert item.title == "newTitle" def test_modify_item_abort(self): item = self.lib.items().get() title = item.title self.modify_inp("n", "title=newTitle") item = self.lib.items().get() assert item.title == title def test_modify_item_no_change(self): title = "Tracktitle" item = self.add_item_fixture(title=title) self.modify_inp("y", "title", f"title={title}") item = self.lib.items(title).get() assert item.title == title def test_modify_write_tags(self): self.modify("title=newTitle") item = self.lib.items().get() item.read() assert item.title == "newTitle" def test_modify_dont_write_tags(self): self.modify("--nowrite", "title=newTitle") item = self.lib.items().get() item.read() assert item.title != "newTitle" def test_move(self): self.modify("title=newTitle") item = self.lib.items().get() assert b"newTitle" in item.path def test_not_move(self): self.modify("--nomove", "title=newTitle") item = self.lib.items().get() assert b"newTitle" not in item.path def test_no_write_no_move(self): self.modify("--nomove", "--nowrite", "title=newTitle") item = self.lib.items().get() item.read() assert b"newTitle" not in item.path assert item.title != "newTitle" def test_update_mtime(self): item = self.item old_mtime = item.mtime self.modify("title=newTitle") item.load() assert old_mtime != item.mtime assert item.current_mtime() == item.mtime def test_reset_mtime_with_no_write(self): item = self.item self.modify("--nowrite", "title=newTitle") item.load() assert 0 == item.mtime def test_selective_modify(self): title = "Tracktitle" album = "album" original_artist = "composer" new_artist = "coverArtist" for i in range(0, 10): self.add_item_fixture( title=f"{title}{i}", artist=original_artist, album=album ) self.modify_inp( "s\ny\ny\ny\nn\nn\ny\ny\ny\ny\nn", title, f"artist={new_artist}" ) original_items = self.lib.items(f"artist:{original_artist}") new_items = self.lib.items(f"artist:{new_artist}") assert len(list(original_items)) == 3 assert len(list(new_items)) == 7 def test_modify_formatted(self): for i in range(0, 3): self.add_item_fixture( title=f"title{i}", artist="artist", album="album" ) items = list(self.lib.items()) self.modify("title=${title} - append") for item in items: orig_title = item.title item.load() assert item.title == f"{orig_title} - append" # Album Tests def test_modify_album(self): self.modify("--album", "album=newAlbum") album = self.lib.albums().get() assert album.album == "newAlbum" def test_modify_album_write_tags(self): self.modify("--album", "album=newAlbum") item = self.lib.items().get() item.read() assert item.album == "newAlbum" def test_modify_album_dont_write_tags(self): self.modify("--album", "--nowrite", "album=newAlbum") item = self.lib.items().get() item.read() assert item.album == "the album" def test_album_move(self): self.modify("--album", "album=newAlbum") item = self.lib.items().get() item.read() assert b"newAlbum" in item.path def test_album_not_move(self): self.modify("--nomove", "--album", "album=newAlbum") item = self.lib.items().get() item.read() assert b"newAlbum" not in item.path def test_modify_album_formatted(self): item = self.lib.items().get() orig_album = item.album self.modify("--album", "album=${album} - append") item.load() assert item.album == f"{orig_album} - append" # Misc def test_write_initial_key_tag(self): self.modify("initial_key=C#m") item = self.lib.items().get() mediafile = MediaFile(syspath(item.path)) assert mediafile.initial_key == "C#m" def test_set_flexattr(self): self.modify("flexattr=testAttr") item = self.lib.items().get() assert item.flexattr == "testAttr" def test_remove_flexattr(self): item = self.lib.items().get() item.flexattr = "testAttr" item.store() self.modify("flexattr!") item = self.lib.items().get() assert "flexattr" not in item @unittest.skip("not yet implemented") def test_delete_initial_key_tag(self): item = self.lib.items().get() item.initial_key = "C#m" item.write() item.store() mediafile = MediaFile(syspath(item.path)) assert mediafile.initial_key == "C#m" self.modify("initial_key!") mediafile = MediaFile(syspath(item.path)) assert mediafile.initial_key is None def test_arg_parsing_colon_query(self): (query, mods, dels) = commands.modify_parse_args( ["title:oldTitle", "title=newTitle"] ) assert query == ["title:oldTitle"] assert mods == {"title": "newTitle"} def test_arg_parsing_delete(self): (query, mods, dels) = commands.modify_parse_args( ["title:oldTitle", "title!"] ) assert query == ["title:oldTitle"] assert dels == ["title"] def test_arg_parsing_query_with_exclaimation(self): (query, mods, dels) = commands.modify_parse_args( ["title:oldTitle!", "title=newTitle!"] ) assert query == ["title:oldTitle!"] assert mods == {"title": "newTitle!"} def test_arg_parsing_equals_in_value(self): (query, mods, dels) = commands.modify_parse_args( ["title:foo=bar", "title=newTitle"] ) assert query == ["title:foo=bar"] assert mods == {"title": "newTitle"} class WriteTest(BeetsTestCase): def write_cmd(self, *args): return self.run_with_output("write", *args) def test_update_mtime(self): item = self.add_item_fixture() item["title"] = "a new title" item.store() item = self.lib.items().get() assert item.mtime == 0 self.write_cmd() item = self.lib.items().get() assert item.mtime == item.current_mtime() def test_non_metadata_field_unchanged(self): """Changing a non-"tag" field like `bitrate` and writing should have no effect. """ # An item that starts out "clean". item = self.add_item_fixture() item.read() # ... but with a mismatched bitrate. item.bitrate = 123 item.store() output = self.write_cmd() assert output == "" def test_write_metadata_field(self): item = self.add_item_fixture() item.read() old_title = item.title item.title = "new title" item.store() output = self.write_cmd() assert f"{old_title} -> new title" in output class MoveTest(BeetsTestCase): def setUp(self): super().setUp() self.io.install() self.itempath = os.path.join(self.libdir, b"srcfile") shutil.copy( syspath(os.path.join(_common.RSRC, b"full.mp3")), syspath(self.itempath), ) # Add a file to the library but don't copy it in yet. self.i = library.Item.from_path(self.itempath) self.lib.add(self.i) self.album = self.lib.add_album([self.i]) # Alternate destination directory. self.otherdir = os.path.join(self.temp_dir, b"testotherdir") def _move( self, query=(), dest=None, copy=False, album=False, pretend=False, export=False, ): commands.move_items( self.lib, dest, query, copy, album, pretend, export=export ) def test_move_item(self): self._move() self.i.load() assert b"libdir" in self.i.path self.assertExists(self.i.path) self.assertNotExists(self.itempath) def test_copy_item(self): self._move(copy=True) self.i.load() assert b"libdir" in self.i.path self.assertExists(self.i.path) self.assertExists(self.itempath) def test_move_album(self): self._move(album=True) self.i.load() assert b"libdir" in self.i.path self.assertExists(self.i.path) self.assertNotExists(self.itempath) def test_copy_album(self): self._move(copy=True, album=True) self.i.load() assert b"libdir" in self.i.path self.assertExists(self.i.path) self.assertExists(self.itempath) def test_move_item_custom_dir(self): self._move(dest=self.otherdir) self.i.load() assert b"testotherdir" in self.i.path self.assertExists(self.i.path) self.assertNotExists(self.itempath) def test_move_album_custom_dir(self): self._move(dest=self.otherdir, album=True) self.i.load() assert b"testotherdir" in self.i.path self.assertExists(self.i.path) self.assertNotExists(self.itempath) def test_pretend_move_item(self): self._move(dest=self.otherdir, pretend=True) self.i.load() assert b"srcfile" in self.i.path def test_pretend_move_album(self): self._move(album=True, pretend=True) self.i.load() assert b"srcfile" in self.i.path def test_export_item_custom_dir(self): self._move(dest=self.otherdir, export=True) self.i.load() assert self.i.path == self.itempath self.assertExists(self.otherdir) def test_export_album_custom_dir(self): self._move(dest=self.otherdir, album=True, export=True) self.i.load() assert self.i.path == self.itempath self.assertExists(self.otherdir) def test_pretend_export_item(self): self._move(dest=self.otherdir, pretend=True, export=True) self.i.load() assert b"srcfile" in self.i.path self.assertNotExists(self.otherdir) class UpdateTest(BeetsTestCase): def setUp(self): super().setUp() self.io.install() # Copy a file into the library. item_path = os.path.join(_common.RSRC, b"full.mp3") item_path_two = os.path.join(_common.RSRC, b"full.flac") self.i = library.Item.from_path(item_path) self.i2 = library.Item.from_path(item_path_two) self.lib.add(self.i) self.lib.add(self.i2) self.i.move(operation=MoveOperation.COPY) self.i2.move(operation=MoveOperation.COPY) self.album = self.lib.add_album([self.i, self.i2]) # Album art. artfile = os.path.join(self.temp_dir, b"testart.jpg") _common.touch(artfile) self.album.set_art(artfile) self.album.store() util.remove(artfile) def _update( self, query=(), album=False, move=False, reset_mtime=True, fields=None, exclude_fields=None, ): self.io.addinput("y") if reset_mtime: self.i.mtime = 0 self.i.store() commands.update_items( self.lib, query, album, move, False, fields=fields, exclude_fields=exclude_fields, ) def test_delete_removes_item(self): assert list(self.lib.items()) util.remove(self.i.path) util.remove(self.i2.path) self._update() assert not list(self.lib.items()) def test_delete_removes_album(self): assert self.lib.albums() util.remove(self.i.path) util.remove(self.i2.path) self._update() assert not self.lib.albums() def test_delete_removes_album_art(self): artpath = self.album.artpath self.assertExists(artpath) util.remove(self.i.path) util.remove(self.i2.path) self._update() self.assertNotExists(artpath) def test_modified_metadata_detected(self): mf = MediaFile(syspath(self.i.path)) mf.title = "differentTitle" mf.save() self._update() item = self.lib.items().get() assert item.title == "differentTitle" def test_modified_metadata_moved(self): mf = MediaFile(syspath(self.i.path)) mf.title = "differentTitle" mf.save() self._update(move=True) item = self.lib.items().get() assert b"differentTitle" in item.path def test_modified_metadata_not_moved(self): mf = MediaFile(syspath(self.i.path)) mf.title = "differentTitle" mf.save() self._update(move=False) item = self.lib.items().get() assert b"differentTitle" not in item.path def test_selective_modified_metadata_moved(self): mf = MediaFile(syspath(self.i.path)) mf.title = "differentTitle" mf.genre = "differentGenre" mf.save() self._update(move=True, fields=["title"]) item = self.lib.items().get() assert b"differentTitle" in item.path assert item.genre != "differentGenre" def test_selective_modified_metadata_not_moved(self): mf = MediaFile(syspath(self.i.path)) mf.title = "differentTitle" mf.genre = "differentGenre" mf.save() self._update(move=False, fields=["title"]) item = self.lib.items().get() assert b"differentTitle" not in item.path assert item.genre != "differentGenre" def test_modified_album_metadata_moved(self): mf = MediaFile(syspath(self.i.path)) mf.album = "differentAlbum" mf.save() self._update(move=True) item = self.lib.items().get() assert b"differentAlbum" in item.path def test_modified_album_metadata_art_moved(self): artpath = self.album.artpath mf = MediaFile(syspath(self.i.path)) mf.album = "differentAlbum" mf.save() self._update(move=True) album = self.lib.albums()[0] assert artpath != album.artpath assert album.artpath is not None def test_selective_modified_album_metadata_moved(self): mf = MediaFile(syspath(self.i.path)) mf.album = "differentAlbum" mf.genre = "differentGenre" mf.save() self._update(move=True, fields=["album"]) item = self.lib.items().get() assert b"differentAlbum" in item.path assert item.genre != "differentGenre" def test_selective_modified_album_metadata_not_moved(self): mf = MediaFile(syspath(self.i.path)) mf.album = "differentAlbum" mf.genre = "differentGenre" mf.save() self._update(move=True, fields=["genre"]) item = self.lib.items().get() assert b"differentAlbum" not in item.path assert item.genre == "differentGenre" def test_mtime_match_skips_update(self): mf = MediaFile(syspath(self.i.path)) mf.title = "differentTitle" mf.save() # Make in-memory mtime match on-disk mtime. self.i.mtime = os.path.getmtime(syspath(self.i.path)) self.i.store() self._update(reset_mtime=False) item = self.lib.items().get() assert item.title == "full" def test_multivalued_albumtype_roundtrip(self): # https://github.com/beetbox/beets/issues/4528 # albumtypes is empty for our test fixtures, so populate it first album = self.album correct_albumtypes = ["album", "live"] # Setting albumtypes does not set albumtype, currently. # Using x[0] mirrors https://github.com/beetbox/mediafile/blob/057432ad53b3b84385e5582f69f44dc00d0a725d/mediafile.py#L1928 # noqa: E501 correct_albumtype = correct_albumtypes[0] album.albumtype = correct_albumtype album.albumtypes = correct_albumtypes album.try_sync(write=True, move=False) album.load() assert album.albumtype == correct_albumtype assert album.albumtypes == correct_albumtypes self._update() album.load() assert album.albumtype == correct_albumtype assert album.albumtypes == correct_albumtypes def test_modified_metadata_excluded(self): mf = MediaFile(syspath(self.i.path)) mf.lyrics = "new lyrics" mf.save() self._update(exclude_fields=["lyrics"]) item = self.lib.items().get() assert item.lyrics != "new lyrics" class PrintTest(BeetsTestCase): def setUp(self): super().setUp() self.io.install() def test_print_without_locale(self): lang = os.environ.get("LANG") if lang: del os.environ["LANG"] try: ui.print_("something") except TypeError: self.fail("TypeError during print") finally: if lang: os.environ["LANG"] = lang def test_print_with_invalid_locale(self): old_lang = os.environ.get("LANG") os.environ["LANG"] = "" old_ctype = os.environ.get("LC_CTYPE") os.environ["LC_CTYPE"] = "UTF-8" try: ui.print_("something") except ValueError: self.fail("ValueError during print") finally: if old_lang: os.environ["LANG"] = old_lang else: del os.environ["LANG"] if old_ctype: os.environ["LC_CTYPE"] = old_ctype else: del os.environ["LC_CTYPE"] class ImportTest(BeetsTestCase): def test_quiet_timid_disallowed(self): config["import"]["quiet"] = True config["import"]["timid"] = True with pytest.raises(ui.UserError): commands.import_files(None, [], None) def test_parse_paths_from_logfile(self): if os.path.__name__ == "ntpath": logfile_content = ( "import started Wed Jun 15 23:08:26 2022\n" "asis C:\\music\\Beatles, The\\The Beatles; C:\\music\\Beatles, The\\The Beatles\\CD 01; C:\\music\\Beatles, The\\The Beatles\\CD 02\n" # noqa: E501 "duplicate-replace C:\\music\\Bill Evans\\Trio '65\n" "skip C:\\music\\Michael Jackson\\Bad\n" "skip C:\\music\\Soulwax\\Any Minute Now\n" ) expected_paths = [ "C:\\music\\Beatles, The\\The Beatles", "C:\\music\\Michael Jackson\\Bad", "C:\\music\\Soulwax\\Any Minute Now", ] else: logfile_content = ( "import started Wed Jun 15 23:08:26 2022\n" "asis /music/Beatles, The/The Beatles; /music/Beatles, The/The Beatles/CD 01; /music/Beatles, The/The Beatles/CD 02\n" # noqa: E501 "duplicate-replace /music/Bill Evans/Trio '65\n" "skip /music/Michael Jackson/Bad\n" "skip /music/Soulwax/Any Minute Now\n" ) expected_paths = [ "/music/Beatles, The/The Beatles", "/music/Michael Jackson/Bad", "/music/Soulwax/Any Minute Now", ] logfile = os.path.join(self.temp_dir, b"logfile.log") with open(logfile, mode="w") as fp: fp.write(logfile_content) actual_paths = list(commands._paths_from_logfile(logfile)) assert actual_paths == expected_paths @_common.slow_test() class TestPluginTestCase(PluginTestCase): plugin = "test" def setUp(self): super().setUp() config["pluginpath"] = [_common.PLUGINPATH] class ConfigTest(TestPluginTestCase): def setUp(self): super().setUp() # Don't use the BEETSDIR from `helper`. Instead, we point the home # directory there. Some tests will set `BEETSDIR` themselves. del os.environ["BEETSDIR"] # Also set APPDATA, the Windows equivalent of setting $HOME. appdata_dir = os.fsdecode( os.path.join(self.temp_dir, b"AppData", b"Roaming") ) self._orig_cwd = os.getcwd() self.test_cmd = self._make_test_cmd() commands.default_commands.append(self.test_cmd) # Default user configuration if platform.system() == "Windows": self.user_config_dir = os.fsencode( os.path.join(appdata_dir, "beets") ) else: self.user_config_dir = os.path.join( self.temp_dir, b".config", b"beets" ) os.makedirs(syspath(self.user_config_dir)) self.user_config_path = os.path.join( self.user_config_dir, b"config.yaml" ) # Custom BEETSDIR self.beetsdir = os.path.join(self.temp_dir, b"beetsdir") os.makedirs(syspath(self.beetsdir)) self.env_patcher = patch( "os.environ", {"HOME": os.fsdecode(self.temp_dir), "APPDATA": appdata_dir}, ) self.env_patcher.start() self._reset_config() def tearDown(self): self.env_patcher.stop() commands.default_commands.pop() os.chdir(syspath(self._orig_cwd)) super().tearDown() def _make_test_cmd(self): test_cmd = ui.Subcommand("test", help="test") def run(lib, options, args): test_cmd.lib = lib test_cmd.options = options test_cmd.args = args test_cmd.func = run return test_cmd def _reset_config(self): # Config should read files again on demand config.clear() config._materialized = False def write_config_file(self): return open(self.user_config_path, "w") def test_paths_section_respected(self): with self.write_config_file() as config: config.write("paths: {x: y}") self.run_command("test", lib=None) key, template = self.test_cmd.lib.path_formats[0] assert key == "x" assert template.original == "y" def test_default_paths_preserved(self): default_formats = ui.get_path_formats() self._reset_config() with self.write_config_file() as config: config.write("paths: {x: y}") self.run_command("test", lib=None) key, template = self.test_cmd.lib.path_formats[0] assert key == "x" assert template.original == "y" assert self.test_cmd.lib.path_formats[1:] == default_formats def test_nonexistant_db(self): with self.write_config_file() as config: config.write("library: /xxx/yyy/not/a/real/path") with pytest.raises(ui.UserError): self.run_command("test", lib=None) def test_user_config_file(self): with self.write_config_file() as file: file.write("anoption: value") self.run_command("test", lib=None) assert config["anoption"].get() == "value" def test_replacements_parsed(self): with self.write_config_file() as config: config.write("replace: {'[xy]': z}") self.run_command("test", lib=None) replacements = self.test_cmd.lib.replacements repls = [(p.pattern, s) for p, s in replacements] # Compare patterns. assert repls == [("[xy]", "z")] def test_multiple_replacements_parsed(self): with self.write_config_file() as config: config.write("replace: {'[xy]': z, foo: bar}") self.run_command("test", lib=None) replacements = self.test_cmd.lib.replacements repls = [(p.pattern, s) for p, s in replacements] assert repls == [("[xy]", "z"), ("foo", "bar")] def test_cli_config_option(self): config_path = os.path.join(self.temp_dir, b"config.yaml") with open(config_path, "w") as file: file.write("anoption: value") self.run_command("--config", config_path, "test", lib=None) assert config["anoption"].get() == "value" def test_cli_config_file_overwrites_user_defaults(self): with open(self.user_config_path, "w") as file: file.write("anoption: value") cli_config_path = os.path.join(self.temp_dir, b"config.yaml") with open(cli_config_path, "w") as file: file.write("anoption: cli overwrite") self.run_command("--config", cli_config_path, "test", lib=None) assert config["anoption"].get() == "cli overwrite" def test_cli_config_file_overwrites_beetsdir_defaults(self): os.environ["BEETSDIR"] = os.fsdecode(self.beetsdir) env_config_path = os.path.join(self.beetsdir, b"config.yaml") with open(env_config_path, "w") as file: file.write("anoption: value") cli_config_path = os.path.join(self.temp_dir, b"config.yaml") with open(cli_config_path, "w") as file: file.write("anoption: cli overwrite") self.run_command("--config", cli_config_path, "test", lib=None) assert config["anoption"].get() == "cli overwrite" # @unittest.skip('Difficult to implement with optparse') # def test_multiple_cli_config_files(self): # cli_config_path_1 = os.path.join(self.temp_dir, b'config.yaml') # cli_config_path_2 = os.path.join(self.temp_dir, b'config_2.yaml') # # with open(cli_config_path_1, 'w') as file: # file.write('first: value') # # with open(cli_config_path_2, 'w') as file: # file.write('second: value') # # self.run_command('--config', cli_config_path_1, # '--config', cli_config_path_2, 'test', lib=None) # assert config['first'].get() == 'value' # assert config['second'].get() == 'value' # # @unittest.skip('Difficult to implement with optparse') # def test_multiple_cli_config_overwrite(self): # cli_config_path = os.path.join(self.temp_dir, b'config.yaml') # cli_overwrite_config_path = os.path.join(self.temp_dir, # b'overwrite_config.yaml') # # with open(cli_config_path, 'w') as file: # file.write('anoption: value') # # with open(cli_overwrite_config_path, 'w') as file: # file.write('anoption: overwrite') # # self.run_command('--config', cli_config_path, # '--config', cli_overwrite_config_path, 'test') # assert config['anoption'].get() == 'cli overwrite' # FIXME: fails on windows @unittest.skipIf(sys.platform == "win32", "win32") def test_cli_config_paths_resolve_relative_to_user_dir(self): cli_config_path = os.path.join(self.temp_dir, b"config.yaml") with open(cli_config_path, "w") as file: file.write("library: beets.db\n") file.write("statefile: state") self.run_command("--config", cli_config_path, "test", lib=None) self.assert_equal_path( util.bytestring_path(config["library"].as_filename()), os.path.join(self.user_config_dir, b"beets.db"), ) self.assert_equal_path( util.bytestring_path(config["statefile"].as_filename()), os.path.join(self.user_config_dir, b"state"), ) def test_cli_config_paths_resolve_relative_to_beetsdir(self): os.environ["BEETSDIR"] = os.fsdecode(self.beetsdir) cli_config_path = os.path.join(self.temp_dir, b"config.yaml") with open(cli_config_path, "w") as file: file.write("library: beets.db\n") file.write("statefile: state") self.run_command("--config", cli_config_path, "test", lib=None) self.assert_equal_path( util.bytestring_path(config["library"].as_filename()), os.path.join(self.beetsdir, b"beets.db"), ) self.assert_equal_path( util.bytestring_path(config["statefile"].as_filename()), os.path.join(self.beetsdir, b"state"), ) def test_command_line_option_relative_to_working_dir(self): config.read() os.chdir(syspath(self.temp_dir)) self.run_command("--library", "foo.db", "test", lib=None) self.assert_equal_path( config["library"].as_filename(), os.path.join(os.getcwd(), "foo.db") ) def test_cli_config_file_loads_plugin_commands(self): cli_config_path = os.path.join(self.temp_dir, b"config.yaml") with open(cli_config_path, "w") as file: file.write("pluginpath: %s\n" % _common.PLUGINPATH) file.write("plugins: test") self.run_command("--config", cli_config_path, "plugin", lib=None) assert plugins.find_plugins()[0].is_test_plugin self.unload_plugins() def test_beetsdir_config(self): os.environ["BEETSDIR"] = os.fsdecode(self.beetsdir) env_config_path = os.path.join(self.beetsdir, b"config.yaml") with open(env_config_path, "w") as file: file.write("anoption: overwrite") config.read() assert config["anoption"].get() == "overwrite" def test_beetsdir_points_to_file_error(self): beetsdir = os.path.join(self.temp_dir, b"beetsfile") open(beetsdir, "a").close() os.environ["BEETSDIR"] = os.fsdecode(beetsdir) with pytest.raises(ConfigError): self.run_command("test") def test_beetsdir_config_does_not_load_default_user_config(self): os.environ["BEETSDIR"] = os.fsdecode(self.beetsdir) with open(self.user_config_path, "w") as file: file.write("anoption: value") config.read() assert not config["anoption"].exists() def test_default_config_paths_resolve_relative_to_beetsdir(self): os.environ["BEETSDIR"] = os.fsdecode(self.beetsdir) config.read() self.assert_equal_path( util.bytestring_path(config["library"].as_filename()), os.path.join(self.beetsdir, b"library.db"), ) self.assert_equal_path( util.bytestring_path(config["statefile"].as_filename()), os.path.join(self.beetsdir, b"state.pickle"), ) def test_beetsdir_config_paths_resolve_relative_to_beetsdir(self): os.environ["BEETSDIR"] = os.fsdecode(self.beetsdir) env_config_path = os.path.join(self.beetsdir, b"config.yaml") with open(env_config_path, "w") as file: file.write("library: beets.db\n") file.write("statefile: state") config.read() self.assert_equal_path( util.bytestring_path(config["library"].as_filename()), os.path.join(self.beetsdir, b"beets.db"), ) self.assert_equal_path( util.bytestring_path(config["statefile"].as_filename()), os.path.join(self.beetsdir, b"state"), ) class ShowModelChangeTest(BeetsTestCase): def setUp(self): super().setUp() self.io.install() self.a = _common.item() self.b = _common.item() self.a.path = self.b.path def _show(self, **kwargs): change = ui.show_model_changes(self.a, self.b, **kwargs) out = self.io.getoutput() return change, out def test_identical(self): change, out = self._show() assert not change assert out == "" def test_string_fixed_field_change(self): self.b.title = "x" change, out = self._show() assert change assert "title" in out def test_int_fixed_field_change(self): self.b.track = 9 change, out = self._show() assert change assert "track" in out def test_floats_close_to_identical(self): self.a.length = 1.00001 self.b.length = 1.00005 change, out = self._show() assert not change assert out == "" def test_floats_different(self): self.a.length = 1.00001 self.b.length = 2.00001 change, out = self._show() assert change assert "length" in out def test_both_values_shown(self): self.a.title = "foo" self.b.title = "bar" change, out = self._show() assert "foo" in out assert "bar" in out class ShowChangeTest(BeetsTestCase): def setUp(self): super().setUp() self.io.install() self.items = [_common.item()] self.items[0].track = 1 self.items[0].path = b"/path/to/file.mp3" self.info = autotag.AlbumInfo( album="the album", album_id="album id", artist="the artist", artist_id="artist id", tracks=[ autotag.TrackInfo( title="the title", track_id="track id", index=1 ) ], ) def _show_change( self, items=None, info=None, color=False, cur_artist="the artist", cur_album="the album", dist=0.1, ): """Return an unicode string representing the changes""" items = items or self.items info = info or self.info mapping = dict(zip(items, info.tracks)) config["ui"]["color"] = color config["import"]["detail"] = True change_dist = distance(items, info, mapping) change_dist._penalties = {"album": [dist], "artist": [dist]} commands.show_change( cur_artist, cur_album, autotag.AlbumMatch(change_dist, info, mapping, set(), set()), ) return self.io.getoutput().lower() def test_null_change(self): msg = self._show_change() assert "match (90.0%)" in msg assert "album, artist" in msg def test_album_data_change(self): msg = self._show_change( cur_artist="another artist", cur_album="another album" ) assert "another artist -> the artist" in msg assert "another album -> the album" in msg def test_item_data_change(self): self.items[0].title = "different" msg = self._show_change() assert "different" in msg assert "the title" in msg def test_item_data_change_with_unicode(self): self.items[0].title = "caf\xe9" msg = self._show_change() assert "caf\xe9" in msg assert "the title" in msg def test_album_data_change_with_unicode(self): msg = self._show_change(cur_artist="caf\xe9", cur_album="another album") assert "caf\xe9" in msg assert "the artist" in msg def test_item_data_change_title_missing(self): self.items[0].title = "" msg = re.sub(r" +", " ", self._show_change()) assert "file.mp3" in msg assert "the title" in msg def test_item_data_change_title_missing_with_unicode_filename(self): self.items[0].title = "" self.items[0].path = "/path/to/caf\xe9.mp3".encode() msg = re.sub(r" +", " ", self._show_change()) assert "caf\xe9.mp3" in msg or "caf.mp3" in msg def test_colorize(self): assert "test" == ui.uncolorize("test") txt = ui.uncolorize("\x1b[31mtest\x1b[39;49;00m") assert "test" == txt txt = ui.uncolorize("\x1b[31mtest\x1b[39;49;00m test") assert "test test" == txt txt = ui.uncolorize("\x1b[31mtest\x1b[39;49;00mtest") assert "testtest" == txt txt = ui.uncolorize("test \x1b[31mtest\x1b[39;49;00m test") assert "test test test" == txt def test_color_split(self): exp = ("test", "") res = ui.color_split("test", 5) assert exp == res exp = ("\x1b[31mtes\x1b[39;49;00m", "\x1b[31mt\x1b[39;49;00m") res = ui.color_split("\x1b[31mtest\x1b[39;49;00m", 3) assert exp == res def test_split_into_lines(self): # Test uncolored text txt = ui.split_into_lines("test test test", [5, 5, 5]) assert txt == ["test", "test", "test"] # Test multiple colored texts colored_text = "\x1b[31mtest \x1b[39;49;00m" * 3 split_txt = [ "\x1b[31mtest\x1b[39;49;00m", "\x1b[31mtest\x1b[39;49;00m", "\x1b[31mtest\x1b[39;49;00m", ] txt = ui.split_into_lines(colored_text, [5, 5, 5]) assert txt == split_txt # Test single color, multi space text colored_text = "\x1b[31m test test test \x1b[39;49;00m" txt = ui.split_into_lines(colored_text, [5, 5, 5]) assert txt == split_txt # Test single color, different spacing colored_text = "\x1b[31mtest\x1b[39;49;00mtest test test" # ToDo: fix color_len to handle mid-text color escapes, and thus # split colored texts over newlines (potentially with dashes?) split_txt = ["\x1b[31mtest\x1b[39;49;00mt", "est", "test", "test"] txt = ui.split_into_lines(colored_text, [5, 5, 5]) assert txt == split_txt def test_album_data_change_wrap_newline(self): # Patch ui.term_width to force wrapping with patch("beets.ui.commands.ui.term_width", return_value=30): # Test newline layout config["ui"]["import"]["layout"] = "newline" long_name = "another artist with a" + (" very" * 10) + " long name" msg = self._show_change( cur_artist=long_name, cur_album="another album" ) # _common.log.info("Message:{}".format(msg)) assert "artist: another artist" in msg assert " -> the artist" in msg assert "another album -> the album" not in msg def test_item_data_change_wrap_column(self): # Patch ui.term_width to force wrapping with patch("beets.ui.commands.ui.term_width", return_value=54): # Test Column layout config["ui"]["import"]["layout"] = "column" long_title = "a track with a" + (" very" * 10) + " long name" self.items[0].title = long_title msg = self._show_change() assert "(#1) a track (1:00) -> (#1) the title (0:00)" in msg def test_item_data_change_wrap_newline(self): # Patch ui.term_width to force wrapping with patch("beets.ui.commands.ui.term_width", return_value=30): config["ui"]["import"]["layout"] = "newline" long_title = "a track with a" + (" very" * 10) + " long name" self.items[0].title = long_title msg = self._show_change() assert "(#1) a track with" in msg assert " -> (#1) the title (0:00)" in msg @patch("beets.library.Item.try_filesize", Mock(return_value=987)) class SummarizeItemsTest(BeetsTestCase): def setUp(self): super().setUp() item = library.Item() item.bitrate = 4321 item.length = 10 * 60 + 54 item.format = "F" self.item = item def test_summarize_item(self): summary = commands.summarize_items([], True) assert summary == "" summary = commands.summarize_items([self.item], True) assert summary == "F, 4kbps, 10:54, 987.0 B" def test_summarize_items(self): summary = commands.summarize_items([], False) assert summary == "0 items" summary = commands.summarize_items([self.item], False) assert summary == "1 items, F, 4kbps, 10:54, 987.0 B" # make a copy of self.item i2 = self.item.copy() summary = commands.summarize_items([self.item, i2], False) assert summary == "2 items, F, 4kbps, 21:48, 1.9 KiB" i2.format = "G" summary = commands.summarize_items([self.item, i2], False) assert summary == "2 items, F 1, G 1, 4kbps, 21:48, 1.9 KiB" summary = commands.summarize_items([self.item, i2, i2], False) assert summary == "3 items, G 2, F 1, 4kbps, 32:42, 2.9 KiB" class PathFormatTest(BeetsTestCase): def test_custom_paths_prepend(self): default_formats = ui.get_path_formats() config["paths"] = {"foo": "bar"} pf = ui.get_path_formats() key, tmpl = pf[0] assert key == "foo" assert tmpl.original == "bar" assert pf[1:] == default_formats @_common.slow_test() class PluginTest(TestPluginTestCase): def test_plugin_command_from_pluginpath(self): self.run_command("test", lib=None) @_common.slow_test() class CompletionTest(TestPluginTestCase): def test_completion(self): # Do not load any other bash completion scripts on the system. env = dict(os.environ) env["BASH_COMPLETION_DIR"] = os.devnull env["BASH_COMPLETION_COMPAT_DIR"] = os.devnull # Open a `bash` process to run the tests in. We'll pipe in bash # commands via stdin. cmd = os.environ.get("BEETS_TEST_SHELL", "/bin/bash --norc").split() if not has_program(cmd[0]): self.skipTest("bash not available") tester = subprocess.Popen( cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE, env=env ) # Load bash_completion library. for path in commands.BASH_COMPLETION_PATHS: if os.path.exists(syspath(path)): bash_completion = path break else: self.skipTest("bash-completion script not found") try: with open(util.syspath(bash_completion), "rb") as f: tester.stdin.writelines(f) except OSError: self.skipTest("could not read bash-completion script") # Load completion script. self.io.install() self.run_command("completion", lib=None) completion_script = self.io.getoutput().encode("utf-8") self.io.restore() tester.stdin.writelines(completion_script.splitlines(True)) # Load test suite. test_script_name = os.path.join(_common.RSRC, b"test_completion.sh") with open(test_script_name, "rb") as test_script_file: tester.stdin.writelines(test_script_file) out, err = tester.communicate() assert tester.returncode == 0 assert out == b"completion tests passed\n", ( "test/test_completion.sh did not execute properly. " f'Output:{out.decode("utf-8")}' ) class CommonOptionsParserCliTest(BeetsTestCase): """Test CommonOptionsParser and formatting LibModel formatting on 'list' command. """ def setUp(self): super().setUp() self.item = _common.item() self.item.path = b"xxx/yyy" self.lib.add(self.item) self.lib.add_album([self.item]) def test_base(self): output = self.run_with_output("ls") assert output == "the artist - the album - the title\n" output = self.run_with_output("ls", "-a") assert output == "the album artist - the album\n" def test_path_option(self): output = self.run_with_output("ls", "-p") assert output == "xxx/yyy\n" output = self.run_with_output("ls", "-a", "-p") assert output == "xxx\n" def test_format_option(self): output = self.run_with_output("ls", "-f", "$artist") assert output == "the artist\n" output = self.run_with_output("ls", "-a", "-f", "$albumartist") assert output == "the album artist\n" def test_format_option_unicode(self): output = self.run_with_output( b"ls", b"-f", "caf\xe9".encode(util.arg_encoding()) ) assert output == "caf\xe9\n" def test_root_format_option(self): output = self.run_with_output( "--format-item", "$artist", "--format-album", "foo", "ls" ) assert output == "the artist\n" output = self.run_with_output( "--format-item", "foo", "--format-album", "$albumartist", "ls", "-a" ) assert output == "the album artist\n" def test_help(self): output = self.run_with_output("help") assert "Usage:" in output output = self.run_with_output("help", "list") assert "Usage:" in output with pytest.raises(ui.UserError): self.run_command("help", "this.is.not.a.real.command") def test_stats(self): output = self.run_with_output("stats") assert "Approximate total size:" in output # # Need to have more realistic library setup for this to work # output = self.run_with_output('stats', '-e') # assert 'Total size:' in output def test_version(self): output = self.run_with_output("version") assert "Python version" in output assert "no plugins loaded" in output # # Need to have plugin loaded # output = self.run_with_output('version') # assert 'plugins: ' in output class CommonOptionsParserTest(BeetsTestCase): def test_album_option(self): parser = ui.CommonOptionsParser() assert not parser._album_flags parser.add_album_option() assert bool(parser._album_flags) assert parser.parse_args([]) == ({"album": None}, []) assert parser.parse_args(["-a"]) == ({"album": True}, []) assert parser.parse_args(["--album"]) == ({"album": True}, []) def test_path_option(self): parser = ui.CommonOptionsParser() parser.add_path_option() assert not parser._album_flags config["format_item"].set("$foo") assert parser.parse_args([]) == ({"path": None}, []) assert config["format_item"].as_str() == "$foo" assert parser.parse_args(["-p"]) == ( {"path": True, "format": "$path"}, [], ) assert parser.parse_args(["--path"]) == ( {"path": True, "format": "$path"}, [], ) assert config["format_item"].as_str() == "$path" assert config["format_album"].as_str() == "$path" def test_format_option(self): parser = ui.CommonOptionsParser() parser.add_format_option() assert not parser._album_flags config["format_item"].set("$foo") assert parser.parse_args([]) == ({"format": None}, []) assert config["format_item"].as_str() == "$foo" assert parser.parse_args(["-f", "$bar"]) == ({"format": "$bar"}, []) assert parser.parse_args(["--format", "$baz"]) == ( {"format": "$baz"}, [], ) assert config["format_item"].as_str() == "$baz" assert config["format_album"].as_str() == "$baz" def test_format_option_with_target(self): with pytest.raises(KeyError): ui.CommonOptionsParser().add_format_option(target="thingy") parser = ui.CommonOptionsParser() parser.add_format_option(target="item") config["format_item"].set("$item") config["format_album"].set("$album") assert parser.parse_args(["-f", "$bar"]) == ({"format": "$bar"}, []) assert config["format_item"].as_str() == "$bar" assert config["format_album"].as_str() == "$album" def test_format_option_with_album(self): parser = ui.CommonOptionsParser() parser.add_album_option() parser.add_format_option() config["format_item"].set("$item") config["format_album"].set("$album") parser.parse_args(["-f", "$bar"]) assert config["format_item"].as_str() == "$bar" assert config["format_album"].as_str() == "$album" parser.parse_args(["-a", "-f", "$foo"]) assert config["format_item"].as_str() == "$bar" assert config["format_album"].as_str() == "$foo" parser.parse_args(["-f", "$foo2", "-a"]) assert config["format_album"].as_str() == "$foo2" def test_add_all_common_options(self): parser = ui.CommonOptionsParser() parser.add_all_common_options() assert parser.parse_args([]) == ( {"album": None, "path": None, "format": None}, [], ) class EncodingTest(BeetsTestCase): """Tests for the `terminal_encoding` config option and our `_in_encoding` and `_out_encoding` utility functions. """ def out_encoding_overridden(self): config["terminal_encoding"] = "fake_encoding" assert ui._out_encoding() == "fake_encoding" def in_encoding_overridden(self): config["terminal_encoding"] = "fake_encoding" assert ui._in_encoding() == "fake_encoding" def out_encoding_default_utf8(self): with patch("sys.stdout") as stdout: stdout.encoding = None assert ui._out_encoding() == "utf-8" def in_encoding_default_utf8(self): with patch("sys.stdin") as stdin: stdin.encoding = None assert ui._in_encoding() == "utf-8" beetbox-beets-01f1faf/test/test_ui_commands.py000066400000000000000000000062541472325477400216440ustar00rootroot00000000000000# This file is part of beets. # Copyright 2016, Adrian Sampson. # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the # "Software"), to deal in the Software without restriction, including # without limitation the rights to use, copy, modify, merge, publish, # distribute, sublicense, and/or sell copies of the Software, and to # permit persons to whom the Software is furnished to do so, subject to # the following conditions: # # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. """Test module for file ui/commands.py""" import os import shutil import pytest from beets import library, ui from beets.test import _common from beets.test.helper import BeetsTestCase, ItemInDBTestCase from beets.ui import commands from beets.util import syspath class QueryTest(BeetsTestCase): def add_item(self, filename=b"srcfile", templatefile=b"full.mp3"): itempath = os.path.join(self.libdir, filename) shutil.copy( syspath(os.path.join(_common.RSRC, templatefile)), syspath(itempath), ) item = library.Item.from_path(itempath) self.lib.add(item) return item, itempath def add_album(self, items): album = self.lib.add_album(items) return album def check_do_query( self, num_items, num_albums, q=(), album=False, also_items=True ): items, albums = commands._do_query(self.lib, q, album, also_items) assert len(items) == num_items assert len(albums) == num_albums def test_query_empty(self): with pytest.raises(ui.UserError): commands._do_query(self.lib, (), False) def test_query_empty_album(self): with pytest.raises(ui.UserError): commands._do_query(self.lib, (), True) def test_query_item(self): self.add_item() self.check_do_query(1, 0, album=False) self.add_item() self.check_do_query(2, 0, album=False) def test_query_album(self): item, itempath = self.add_item() self.add_album([item]) self.check_do_query(1, 1, album=True) self.check_do_query(0, 1, album=True, also_items=False) item, itempath = self.add_item() item2, itempath = self.add_item() self.add_album([item, item2]) self.check_do_query(3, 2, album=True) self.check_do_query(0, 2, album=True, also_items=False) class FieldsTest(ItemInDBTestCase): def setUp(self): super().setUp() self.io.install() def tearDown(self): super().tearDown() self.io.restore() def remove_keys(self, keys, text): for i in text: try: keys.remove(i) except ValueError: pass def test_fields_func(self): commands.fields_func(self.lib, [], []) items = library.Item.all_keys() albums = library.Album.all_keys() output = self.io.stdout.get().split() self.remove_keys(items, output) self.remove_keys(albums, output) assert len(items) == 0 assert len(albums) == 0 beetbox-beets-01f1faf/test/test_ui_importer.py000066400000000000000000000034211472325477400216750ustar00rootroot00000000000000# This file is part of beets. # Copyright 2016, Adrian Sampson. # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the # "Software"), to deal in the Software without restriction, including # without limitation the rights to use, copy, modify, merge, publish, # distribute, sublicense, and/or sell copies of the Software, and to # permit persons to whom the Software is furnished to do so, subject to # the following conditions: # # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. """Tests the TerminalImportSession. The tests are the same as in the test_importer module. But here the test importer inherits from ``TerminalImportSession``. So we test this class, too. """ from beets.test.helper import TerminalImportMixin from test import test_importer class NonAutotaggedImportTest( TerminalImportMixin, test_importer.NonAutotaggedImportTest ): pass class ImportTest(TerminalImportMixin, test_importer.ImportTest): pass class ImportSingletonTest( TerminalImportMixin, test_importer.ImportSingletonTest ): pass class ImportTracksTest(TerminalImportMixin, test_importer.ImportTracksTest): pass class ImportCompilationTest( TerminalImportMixin, test_importer.ImportCompilationTest ): pass class ImportExistingTest(TerminalImportMixin, test_importer.ImportExistingTest): pass class ChooseCandidateTest( TerminalImportMixin, test_importer.ChooseCandidateTest ): pass class GroupAlbumsImportTest( TerminalImportMixin, test_importer.GroupAlbumsImportTest ): pass class GlobalGroupAlbumsImportTest( TerminalImportMixin, test_importer.GlobalGroupAlbumsImportTest ): pass beetbox-beets-01f1faf/test/test_ui_init.py000066400000000000000000000117061472325477400210040ustar00rootroot00000000000000# This file is part of beets. # Copyright 2016, Adrian Sampson. # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the # "Software"), to deal in the Software without restriction, including # without limitation the rights to use, copy, modify, merge, publish, # distribute, sublicense, and/or sell copies of the Software, and to # permit persons to whom the Software is furnished to do so, subject to # the following conditions: # # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. """Test module for file ui/__init__.py""" import os import shutil from copy import deepcopy from random import random from beets import config, ui from beets.test import _common from beets.test.helper import BeetsTestCase, ItemInDBTestCase, control_stdin class InputMethodsTest(BeetsTestCase): def setUp(self): super().setUp() self.io.install() def _print_helper(self, s): print(s) def _print_helper2(self, s, prefix): print(prefix, s) def test_input_select_objects(self): full_items = ["1", "2", "3", "4", "5"] # Test no self.io.addinput("n") items = ui.input_select_objects( "Prompt", full_items, self._print_helper ) assert items == [] # Test yes self.io.addinput("y") items = ui.input_select_objects( "Prompt", full_items, self._print_helper ) assert items == full_items # Test selective 1 self.io.addinput("s") self.io.addinput("n") self.io.addinput("y") self.io.addinput("n") self.io.addinput("y") self.io.addinput("n") items = ui.input_select_objects( "Prompt", full_items, self._print_helper ) assert items == ["2", "4"] # Test selective 2 self.io.addinput("s") self.io.addinput("y") self.io.addinput("y") self.io.addinput("n") self.io.addinput("y") self.io.addinput("n") items = ui.input_select_objects( "Prompt", full_items, lambda s: self._print_helper2(s, "Prefix") ) assert items == ["1", "2", "4"] # Test selective 3 self.io.addinput("s") self.io.addinput("y") self.io.addinput("n") self.io.addinput("y") self.io.addinput("q") items = ui.input_select_objects( "Prompt", full_items, self._print_helper ) assert items == ["1", "3"] class InitTest(ItemInDBTestCase): def test_human_bytes(self): tests = [ (0, "0.0 B"), (30, "30.0 B"), (pow(2, 10), "1.0 KiB"), (pow(2, 20), "1.0 MiB"), (pow(2, 30), "1.0 GiB"), (pow(2, 40), "1.0 TiB"), (pow(2, 50), "1.0 PiB"), (pow(2, 60), "1.0 EiB"), (pow(2, 70), "1.0 ZiB"), (pow(2, 80), "1.0 YiB"), (pow(2, 90), "1.0 HiB"), (pow(2, 100), "big"), ] for i, h in tests: assert h == ui.human_bytes(i) def test_human_seconds(self): tests = [ (0, "0.0 seconds"), (30, "30.0 seconds"), (60, "1.0 minutes"), (90, "1.5 minutes"), (125, "2.1 minutes"), (3600, "1.0 hours"), (86400, "1.0 days"), (604800, "1.0 weeks"), (31449600, "1.0 years"), (314496000, "1.0 decades"), ] for i, h in tests: assert h == ui.human_seconds(i) class ParentalDirCreation(BeetsTestCase): def test_create_yes(self): non_exist_path = _common.os.fsdecode( os.path.join(self.temp_dir, b"nonexist", str(random()).encode()) ) # Deepcopy instead of recovering because exceptions might # occur; wish I can use a golang defer here. test_config = deepcopy(config) test_config["library"] = non_exist_path with control_stdin("y"): lib = ui._open_library(test_config) lib._close() def test_create_no(self): non_exist_path_parent = _common.os.fsdecode( os.path.join(self.temp_dir, b"nonexist") ) non_exist_path = _common.os.fsdecode( os.path.join(non_exist_path_parent.encode(), str(random()).encode()) ) test_config = deepcopy(config) test_config["library"] = non_exist_path with control_stdin("n"): try: lib = ui._open_library(test_config) except ui.UserError: if os.path.exists(non_exist_path_parent): shutil.rmtree(non_exist_path_parent) raise OSError("Parent directories should not be created.") else: if lib: lib._close() raise OSError("Parent directories should not be created.") beetbox-beets-01f1faf/test/test_util.py000066400000000000000000000160361472325477400203220ustar00rootroot00000000000000# This file is part of beets. # Copyright 2016, Adrian Sampson. # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the # "Software"), to deal in the Software without restriction, including # without limitation the rights to use, copy, modify, merge, publish, # distribute, sublicense, and/or sell copies of the Software, and to # permit persons to whom the Software is furnished to do so, subject to # the following conditions: # # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. """Tests for base utils from the beets.util package.""" import os import platform import re import subprocess import sys import unittest from unittest.mock import Mock, patch import pytest from beets import util from beets.test import _common from beets.test.helper import BeetsTestCase class UtilTest(unittest.TestCase): def test_open_anything(self): with _common.system_mock("Windows"): assert util.open_anything() == "start" with _common.system_mock("Darwin"): assert util.open_anything() == "open" with _common.system_mock("Tagada"): assert util.open_anything() == "xdg-open" @patch("os.execlp") @patch("beets.util.open_anything") def test_interactive_open(self, mock_open, mock_execlp): mock_open.return_value = "tagada" util.interactive_open(["foo"], util.open_anything()) mock_execlp.assert_called_once_with("tagada", "tagada", "foo") mock_execlp.reset_mock() util.interactive_open(["foo"], "bar") mock_execlp.assert_called_once_with("bar", "bar", "foo") def test_sanitize_unix_replaces_leading_dot(self): with _common.platform_posix(): p = util.sanitize_path("one/.two/three") assert "." not in p def test_sanitize_windows_replaces_trailing_dot(self): with _common.platform_windows(): p = util.sanitize_path("one/two./three") assert "." not in p def test_sanitize_windows_replaces_illegal_chars(self): with _common.platform_windows(): p = util.sanitize_path(':*?"<>|') assert ":" not in p assert "*" not in p assert "?" not in p assert '"' not in p assert "<" not in p assert ">" not in p assert "|" not in p def test_sanitize_windows_replaces_trailing_space(self): with _common.platform_windows(): p = util.sanitize_path("one/two /three") assert " " not in p def test_sanitize_path_works_on_empty_string(self): with _common.platform_posix(): p = util.sanitize_path("") assert p == "" def test_sanitize_with_custom_replace_overrides_built_in_sub(self): with _common.platform_posix(): p = util.sanitize_path("a/.?/b", [(re.compile(r"foo"), "bar")]) assert p == "a/.?/b" def test_sanitize_with_custom_replace_adds_replacements(self): with _common.platform_posix(): p = util.sanitize_path("foo/bar", [(re.compile(r"foo"), "bar")]) assert p == "bar/bar" @unittest.skip("unimplemented: #359") def test_sanitize_empty_component(self): with _common.platform_posix(): p = util.sanitize_path("foo//bar", [(re.compile(r"^$"), "_")]) assert p == "foo/_/bar" @unittest.skipIf(sys.platform == "win32", "win32") def test_convert_command_args_keeps_undecodeable_bytes(self): arg = b"\x82" # non-ascii bytes cmd_args = util.convert_command_args([arg]) assert cmd_args[0] == arg.decode(util.arg_encoding(), "surrogateescape") @patch("beets.util.subprocess.Popen") def test_command_output(self, mock_popen): def popen_fail(*args, **kwargs): m = Mock(returncode=1) m.communicate.return_value = "foo", "bar" return m mock_popen.side_effect = popen_fail with pytest.raises(subprocess.CalledProcessError) as exc_info: util.command_output(["taga", "\xc3\xa9"]) assert exc_info.value.returncode == 1 assert exc_info.value.cmd == "taga \xc3\xa9" def test_case_sensitive_default(self): path = util.bytestring_path( util.normpath( "/this/path/does/not/exist", ) ) assert util.case_sensitive(path) == (platform.system() != "Windows") @unittest.skipIf(sys.platform == "win32", "fs is not case sensitive") def test_case_sensitive_detects_sensitive(self): # FIXME: Add tests for more code paths of case_sensitive() # when the filesystem on the test runner is not case sensitive pass @unittest.skipIf(sys.platform != "win32", "fs is case sensitive") def test_case_sensitive_detects_insensitive(self): # FIXME: Add tests for more code paths of case_sensitive() # when the filesystem on the test runner is case sensitive pass class PathConversionTest(BeetsTestCase): def test_syspath_windows_format(self): with _common.platform_windows(): path = os.path.join("a", "b", "c") outpath = util.syspath(path) assert isinstance(outpath, str) assert outpath.startswith("\\\\?\\") def test_syspath_windows_format_unc_path(self): # The \\?\ prefix on Windows behaves differently with UNC # (network share) paths. path = "\\\\server\\share\\file.mp3" with _common.platform_windows(): outpath = util.syspath(path) assert isinstance(outpath, str) assert outpath == "\\\\?\\UNC\\server\\share\\file.mp3" def test_syspath_posix_unchanged(self): with _common.platform_posix(): path = os.path.join("a", "b", "c") outpath = util.syspath(path) assert path == outpath def _windows_bytestring_path(self, path): old_gfse = sys.getfilesystemencoding sys.getfilesystemencoding = lambda: "mbcs" try: with _common.platform_windows(): return util.bytestring_path(path) finally: sys.getfilesystemencoding = old_gfse def test_bytestring_path_windows_encodes_utf8(self): path = "caf\xe9" outpath = self._windows_bytestring_path(path) assert path == outpath.decode("utf-8") def test_bytesting_path_windows_removes_magic_prefix(self): path = "\\\\?\\C:\\caf\xe9" outpath = self._windows_bytestring_path(path) assert outpath == "C:\\caf\xe9".encode() class PathTruncationTest(BeetsTestCase): def test_truncate_bytestring(self): with _common.platform_posix(): p = util.truncate_path(b"abcde/fgh", 4) assert p == b"abcd/fgh" def test_truncate_unicode(self): with _common.platform_posix(): p = util.truncate_path("abcde/fgh", 4) assert p == "abcd/fgh" def test_truncate_preserves_extension(self): with _common.platform_posix(): p = util.truncate_path("abcde/fgh.ext", 5) assert p == "abcde/f.ext" beetbox-beets-01f1faf/test/test_vfs.py000066400000000000000000000026221472325477400201370ustar00rootroot00000000000000# This file is part of beets. # Copyright 2016, Adrian Sampson. # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the # "Software"), to deal in the Software without restriction, including # without limitation the rights to use, copy, modify, merge, publish, # distribute, sublicense, and/or sell copies of the Software, and to # permit persons to whom the Software is furnished to do so, subject to # the following conditions: # # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. """Tests for the virtual filesystem builder..""" from beets import vfs from beets.test import _common from beets.test.helper import BeetsTestCase class VFSTest(BeetsTestCase): def setUp(self): super().setUp() self.lib.path_formats = [ ("default", "albums/$album/$title"), ("singleton:true", "tracks/$artist/$title"), ] self.lib.add(_common.item()) self.lib.add_album([_common.item()]) self.tree = vfs.libtree(self.lib) def test_singleton_item(self): assert ( self.tree.dirs["tracks"].dirs["the artist"].files["the title"] == 1 ) def test_album_item(self): assert ( self.tree.dirs["albums"].dirs["the album"].files["the title"] == 2 ) beetbox-beets-01f1faf/test/testall.py000077500000000000000000000014411472325477400177530ustar00rootroot00000000000000#!/usr/bin/env python3 # This file is part of beets. # Copyright 2016, Adrian Sampson. # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the # "Software"), to deal in the Software without restriction, including # without limitation the rights to use, copy, modify, merge, publish, # distribute, sublicense, and/or sell copies of the Software, and to # permit persons to whom the Software is furnished to do so, subject to # the following conditions: # # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. import os import sys pkgpath = os.path.dirname(os.path.dirname(os.path.realpath(__file__))) or ".." sys.path.insert(0, pkgpath)