├── tests ├── __init__.py ├── test_playback.py ├── test_extension.py ├── test_search.py ├── test_images.py ├── test_lookup.py ├── conftest.py ├── test_browse.py ├── test_configmap.py └── test_translator.py ├── docs ├── .gitignore ├── changelog.rst ├── install.rst ├── license.rst ├── conf.py ├── index.rst ├── config.rst └── Makefile ├── setup.py ├── .gitignore ├── .readthedocs.yml ├── mopidy_internetarchive ├── playback.py ├── ext.conf ├── backend.py ├── __init__.py ├── client.py ├── translator.py └── library.py ├── MANIFEST.in ├── pyproject.toml ├── tox.ini ├── .github └── workflows │ └── ci.yml ├── setup.cfg ├── README.rst ├── CHANGELOG.rst └── LICENSE /tests/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/.gitignore: -------------------------------------------------------------------------------- 1 | _build 2 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup 2 | 3 | setup() 4 | -------------------------------------------------------------------------------- /docs/changelog.rst: -------------------------------------------------------------------------------- 1 | ********** 2 | Change Log 3 | ********** 4 | 5 | .. include:: ../CHANGELOG.rst 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | /*.egg-info 3 | /.coverage 4 | /.mypy_cache/ 5 | /.pytest_cache/ 6 | /.tox/ 7 | /MANIFEST 8 | /build/ 9 | /dist/ 10 | /docs/_build/ 11 | -------------------------------------------------------------------------------- /.readthedocs.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | 3 | python: 4 | version: 3.7 5 | install: 6 | - method: pip 7 | path: . 8 | extra_requirements: 9 | - docs 10 | 11 | sphinx: 12 | builder: htmldir 13 | configuration: docs/conf.py 14 | 15 | formats: 16 | - pdf 17 | - epub 18 | -------------------------------------------------------------------------------- /mopidy_internetarchive/playback.py: -------------------------------------------------------------------------------- 1 | from mopidy import backend 2 | 3 | from . import translator 4 | 5 | 6 | class InternetArchivePlaybackProvider(backend.PlaybackProvider): 7 | def translate_uri(self, uri): 8 | identifier, filename, _ = translator.parse_uri(uri) 9 | return self.backend.client.geturl(identifier, filename) 10 | -------------------------------------------------------------------------------- /tests/test_playback.py: -------------------------------------------------------------------------------- 1 | def test_translate_url(playback, client_mock): 2 | url = "http://archive.org/download/item/file.mp3" 3 | client_mock.geturl.return_value = url 4 | result = playback.translate_uri("internetarchive:item#file.mp3") 5 | assert client_mock.geturl.called_once() 6 | assert client_mock.geturl.call_args == (("item", "file.mp3"),) 7 | assert result == url 8 | -------------------------------------------------------------------------------- /docs/install.rst: -------------------------------------------------------------------------------- 1 | ************ 2 | Installation 3 | ************ 4 | 5 | Debian/Ubuntu/Raspbian: Install the ``mopidy-internetarchive`` package 6 | from `apt.mopidy.com `_:: 7 | 8 | apt-get install mopidy-internetarchive 9 | 10 | Otherwise, install the package from `PyPI 11 | `_:: 12 | 13 | pip install Mopidy-Internetarchive 14 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include *.py 2 | include *.rst 3 | include .mailmap 4 | include .readthedocs.yml 5 | include LICENSE 6 | include MANIFEST.in 7 | include pyproject.toml 8 | include tox.ini 9 | 10 | recursive-include .circleci * 11 | recursive-include .github * 12 | 13 | include mopidy_*/ext.conf 14 | 15 | recursive-include tests *.py 16 | recursive-include tests/data * 17 | 18 | recursive-include docs * 19 | prune docs/_build 20 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["setuptools >= 30.3.0", "wheel"] 3 | 4 | 5 | [tool.black] 6 | target-version = ["py37", "py38"] 7 | line-length = 80 8 | 9 | 10 | [tool.isort] 11 | multi_line_output = 3 12 | include_trailing_comma = true 13 | force_grid_wrap = 0 14 | use_parentheses = true 15 | line_length = 88 16 | known_tests = "tests" 17 | sections = "FUTURE,STDLIB,THIRDPARTY,FIRSTPARTY,TESTS,LOCALFOLDER" 18 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | [tox] 2 | envlist = py37, py38, py39, py310, check-manifest, docs, flake8 3 | 4 | [testenv] 5 | sitepackages = true 6 | deps = .[test] 7 | commands = 8 | python -m pytest \ 9 | --basetemp={envtmpdir} \ 10 | --cov=mopidy_internetarchive --cov-report=term-missing \ 11 | {posargs} 12 | 13 | [testenv:check-manifest] 14 | deps = .[lint] 15 | commands = python -m check_manifest 16 | 17 | [testenv:docs] 18 | deps = .[docs] 19 | changedir = docs 20 | commands = python -m sphinx -b html -d {envtmpdir}/doctrees . {envtmpdir}/html 21 | 22 | [testenv:flake8] 23 | deps = .[lint] 24 | commands = python -m flake8 --show-source --statistics 25 | -------------------------------------------------------------------------------- /docs/license.rst: -------------------------------------------------------------------------------- 1 | ******* 2 | License 3 | ******* 4 | 5 | Mopidy-InternetArchive is Copyright (c) 2014-2019 Thomas Kemmer. 6 | 7 | Licensed under the Apache License, Version 2.0 (the "License"); you 8 | may not use this software except in compliance with the License. You 9 | may obtain a copy of the License at 10 | 11 | http://www.apache.org/licenses/LICENSE-2.0 12 | 13 | Unless required by applicable law or agreed to in writing, software 14 | distributed under the License is distributed on an "AS IS" BASIS, 15 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 16 | implied. See the License for the specific language governing 17 | permissions and limitations under the License. 18 | -------------------------------------------------------------------------------- /tests/test_extension.py: -------------------------------------------------------------------------------- 1 | from unittest import mock 2 | 3 | from mopidy_internetarchive import Extension, backend 4 | 5 | 6 | def test_get_default_config(): 7 | ext = Extension() 8 | 9 | config = ext.get_default_config() 10 | 11 | assert "[internetarchive]" in config 12 | assert "enabled = true" in config 13 | 14 | 15 | def test_get_config_schema(): 16 | ext = Extension() 17 | 18 | schema = ext.get_config_schema() 19 | 20 | assert "audio_formats" in schema 21 | assert "base_url" in schema 22 | assert "browse_limit" in schema 23 | assert "browse_order" in schema 24 | assert "cache_size" in schema 25 | assert "cache_ttl" in schema 26 | assert "collections" in schema 27 | assert "exclude_collections" in schema 28 | assert "exclude_mediatypes" in schema 29 | assert "image_formats" in schema 30 | assert "retries" in schema 31 | assert "search_limit" in schema 32 | assert "search_order" in schema 33 | assert "timeout" in schema 34 | 35 | 36 | def test_setup(): 37 | registry = mock.Mock() 38 | 39 | ext = Extension() 40 | ext.setup(registry) 41 | 42 | registry.add.assert_called_with("backend", backend.InternetArchiveBackend) 43 | -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | import configparser 2 | import pathlib 3 | 4 | 5 | def setup(app): 6 | app.add_object_type( 7 | "confval", 8 | "confval", 9 | objname="configuration value", 10 | indextemplate="pair: %s; configuration value", 11 | ) 12 | 13 | 14 | def get_version(): 15 | # Get current library version without requiring the library to be 16 | # installed, like ``pkg_resources.get_distribution(...).version`` requires. 17 | cp = configparser.ConfigParser() 18 | cp.read(pathlib.Path(__file__).parent.parent / "setup.cfg") 19 | return cp["metadata"]["version"] 20 | 21 | 22 | project = "Mopidy-InternetArchive" 23 | copyright = "2014-2022 Thomas Kemmer" 24 | version = get_version() 25 | release = version 26 | 27 | exclude_patterns = ["_build"] 28 | master_doc = "index" 29 | html_theme = "default" 30 | 31 | latex_documents = [ 32 | ( 33 | "index", 34 | "Mopidy-InternetArchive.tex", 35 | "Mopidy-InternetArchive Documentation", 36 | "Thomas Kemmer", 37 | "manual", 38 | ) 39 | ] 40 | 41 | man_pages = [ 42 | ( 43 | "index", 44 | "mopidy-internetarchive", 45 | "Mopidy-InternetArchive Documentation", 46 | ["Thomas Kemmer"], 47 | 1, 48 | ) 49 | ] 50 | -------------------------------------------------------------------------------- /mopidy_internetarchive/ext.conf: -------------------------------------------------------------------------------- 1 | [internetarchive] 2 | enabled = true 3 | 4 | # archive.org base URL 5 | base_url = http://archive.org 6 | 7 | # top-level collections for browsing 8 | collections = 9 | audio 10 | etree 11 | librivoxaudio 12 | audio_bookspoetry 13 | audio_tech 14 | audio_music 15 | audio_news 16 | audio_foreign 17 | audio_podcast 18 | audio_religion 19 | 20 | # audio file formats in order of preference 21 | audio_formats = VBR MP3, 64Kbps MP3 22 | 23 | # image file formats in order of preference 24 | image_formats = JPEG, JPEG Thumb 25 | 26 | # maximum number of browse results 27 | browse_limit = 100 28 | 29 | # list of collection browse views: (asc|desc) | 30 | browse_views = 31 | downloads desc | Views 32 | titleSorter asc | Title 33 | publicdate desc | Date Archived 34 | date desc | Date Published 35 | creatorSorter asc | Creator 36 | 37 | # maximum number of search results 38 | search_limit = 20 39 | 40 | # sort order for searching: (asc|desc); default is score 41 | search_order = 42 | 43 | # number of items to cache 44 | cache_size = 128 45 | 46 | # cache time-to-live in seconds 47 | cache_ttl = 86400 48 | 49 | # maximum number of HTTP connection retries 50 | retries = 3 51 | 52 | # HTTP request timeout in seconds 53 | timeout = 10 54 | -------------------------------------------------------------------------------- /tests/test_search.py: -------------------------------------------------------------------------------- 1 | from mopidy import models 2 | 3 | 4 | def test_search_any(library, client_mock): 5 | client_mock.search.return_value = client_mock.SearchResult( 6 | { 7 | "responseHeader": {"params": {"query": "album"}}, 8 | "response": { 9 | "docs": [ 10 | { 11 | "identifier": "album1", 12 | "title": "Album #1", 13 | "mediatype": "audio", 14 | }, 15 | { 16 | "identifier": "album2", 17 | "title": "Album #2", 18 | "mediatype": "etree", 19 | }, 20 | ], 21 | "numFound": 2, 22 | }, 23 | } 24 | ) 25 | result = library.search(dict(any=["album"])) 26 | assert client_mock.search.called_once() 27 | assert result == models.SearchResult( 28 | uri="internetarchive:?q=album", 29 | albums=[ 30 | models.Album(name="Album #1", uri="internetarchive:album1"), 31 | models.Album(name="Album #2", uri="internetarchive:album2"), 32 | ], 33 | ) 34 | 35 | 36 | def test_search_unknown(library, client_mock): 37 | result = library.search(dict(foo=["bar"])) 38 | client_mock.search.assert_not_called() 39 | assert result is None 40 | -------------------------------------------------------------------------------- /mopidy_internetarchive/backend.py: -------------------------------------------------------------------------------- 1 | import pykka 2 | from mopidy import backend, httpclient 3 | 4 | import cachetools 5 | 6 | from . import Extension 7 | from .client import InternetArchiveClient 8 | from .library import InternetArchiveLibraryProvider 9 | from .playback import InternetArchivePlaybackProvider 10 | 11 | 12 | def _cache(cache_size=None, cache_ttl=None, **kwargs): 13 | if cache_size is None: 14 | return None 15 | elif cache_ttl is None: 16 | return cachetools.LRUCache(cache_size) 17 | else: 18 | return cachetools.TTLCache(cache_size, cache_ttl) 19 | 20 | 21 | class InternetArchiveBackend(pykka.ThreadingActor, backend.Backend): 22 | 23 | uri_schemes = [Extension.ext_name] 24 | 25 | def __init__(self, config, audio): 26 | super().__init__() 27 | ext_config = config[Extension.ext_name] 28 | 29 | self.client = client = InternetArchiveClient( 30 | ext_config["base_url"], 31 | retries=ext_config["retries"], 32 | timeout=ext_config["timeout"], 33 | ) 34 | product = f"{Extension.dist_name}/{Extension.version}" 35 | client.useragent = httpclient.format_user_agent(product) 36 | proxy = httpclient.format_proxy(config["proxy"]) 37 | client.proxies.update({"http": proxy, "https": proxy}) 38 | client.cache = _cache(**ext_config) 39 | 40 | self.library = InternetArchiveLibraryProvider(ext_config, self) 41 | self.playback = InternetArchivePlaybackProvider(audio, self) 42 | -------------------------------------------------------------------------------- /tests/test_images.py: -------------------------------------------------------------------------------- 1 | from mopidy import models 2 | 3 | URL = "http://archive.org/download/album/cover.jpg" 4 | 5 | ITEM = { 6 | "files": [ 7 | { 8 | "name": "track02.mp3", 9 | "title": "Track #2", 10 | "format": "VBR MP3", 11 | "track": "02", 12 | }, 13 | { 14 | "name": "track01.mp3", 15 | "title": "Track #1", 16 | "format": "VBR MP3", 17 | "track": "01", 18 | }, 19 | {"name": "cover.jpg", "format": "JPEG"}, 20 | ], 21 | "metadata": {"identifier": "album", "title": "Album", "mediatype": "audio"}, 22 | } 23 | 24 | IMAGES = [models.Image(uri=URL)] 25 | 26 | 27 | def test_root_images(library, client_mock): 28 | results = library.get_images(["internetarchive:"]) 29 | client_mock.getitem.assert_not_called() 30 | assert results == {} 31 | 32 | 33 | def test_album_images(library, client_mock): 34 | client_mock.getitem.return_value = ITEM 35 | client_mock.geturl.return_value = URL 36 | results = library.get_images(["internetarchive:album"]) 37 | client_mock.getitem.assert_called_once_with("album") 38 | assert results == {"internetarchive:album": IMAGES} 39 | 40 | 41 | def test_track_images(library, client_mock): 42 | client_mock.getitem.return_value = ITEM 43 | client_mock.geturl.return_value = URL 44 | results = library.get_images( 45 | [ 46 | "internetarchive:album#track01.jpg", 47 | "internetarchive:album#track02.jpg", 48 | ] 49 | ) 50 | client_mock.getitem.assert_called_once_with("album") 51 | assert results == { 52 | "internetarchive:album#track01.jpg": IMAGES, 53 | "internetarchive:album#track02.jpg": IMAGES, 54 | } 55 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | main: 7 | strategy: 8 | fail-fast: false 9 | matrix: 10 | include: 11 | - name: "Test: Python 3.7" 12 | python: "3.7" 13 | tox: py37 14 | - name: "Test: Python 3.8" 15 | python: "3.8" 16 | tox: py38 17 | - name: "Test: Python 3.9" 18 | python: "3.9" 19 | tox: py39 20 | - name: "Test: Python 3.10" 21 | python: "3.10" 22 | tox: py310 23 | coverage: true 24 | - name: "Lint: check-manifest" 25 | python: "3.10" 26 | tox: check-manifest 27 | - name: "Lint: flake8" 28 | python: "3.10" 29 | tox: flake8 30 | - name: "Docs" 31 | python: "3.10" 32 | tox: docs 33 | 34 | name: ${{ matrix.name }} 35 | runs-on: ubuntu-20.04 36 | container: ghcr.io/mopidy/ci:latest 37 | 38 | steps: 39 | - uses: actions/checkout@v2 40 | - uses: actions/setup-python@v2 41 | with: 42 | python-version: ${{ matrix.python }} 43 | - name: Fix home dir permissions to enable pip caching 44 | run: chown -R root /github/home 45 | - name: Cache pip 46 | uses: actions/cache@v2 47 | with: 48 | path: ~/.cache/pip 49 | key: ${{ runner.os }}-${{ matrix.python }}-${{ matrix.tox }}-pip-${{ hashFiles('setup.cfg') }}-${{ hashFiles('tox.ini') }} 50 | restore-keys: | 51 | ${{ runner.os }}-${{ matrix.python }}-${{ matrix.tox }}-pip- 52 | - run: python -m pip install pygobject tox 53 | - run: python -m tox -e ${{ matrix.tox }} 54 | if: ${{ ! matrix.coverage }} 55 | - run: python -m tox -e ${{ matrix.tox }} -- --cov-report=xml 56 | if: ${{ matrix.coverage }} 57 | - uses: codecov/codecov-action@v1 58 | if: ${{ matrix.coverage }} 59 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [metadata] 2 | name = Mopidy-InternetArchive 3 | version = 3.0.1 4 | url = https://github.com/tkem/mopidy-internetarchive 5 | author = Thomas Kemmer 6 | author_email = tkemmer@computer.org 7 | license = Apache License, Version 2.0 8 | license_file = LICENSE 9 | description = Mopidy extension for playing music from the Internet Archive 10 | long_description = file: README.rst 11 | classifiers = 12 | Environment :: No Input/Output (Daemon) 13 | Intended Audience :: End Users/Desktop 14 | License :: OSI Approved :: Apache Software License 15 | Operating System :: OS Independent 16 | Programming Language :: Python :: 3 17 | Programming Language :: Python :: 3.7 18 | Programming Language :: Python :: 3.8 19 | Programming Language :: Python :: 3.9 20 | Programming Language :: Python :: 3.10 21 | Topic :: Multimedia :: Sound/Audio :: Players 22 | 23 | 24 | [options] 25 | zip_safe = False 26 | include_package_data = True 27 | packages = find: 28 | python_requires = >= 3.7 29 | install_requires = 30 | Mopidy >= 3.0.0 31 | Pykka >= 2.0.1 32 | cachetools >= 1.0 33 | requests >= 2.0 34 | setuptools 35 | uritools >= 1.0 36 | 37 | 38 | [options.extras_require] 39 | docs = 40 | sphinx 41 | lint = 42 | black 43 | check-manifest 44 | flake8 45 | flake8-black 46 | flake8-bugbear 47 | flake8-import-order 48 | isort[pyproject] 49 | release = 50 | twine 51 | wheel 52 | test = 53 | pytest 54 | pytest-cov 55 | dev = 56 | %(docs)s 57 | %(lint)s 58 | %(release)s 59 | %(test)s 60 | 61 | 62 | [options.packages.find] 63 | exclude = 64 | tests 65 | tests.* 66 | 67 | 68 | [options.entry_points] 69 | mopidy.ext = 70 | internetarchive = mopidy_internetarchive:Extension 71 | 72 | 73 | [flake8] 74 | application-import-names = mopidy_internetarchive, tests 75 | max-line-length = 80 76 | exclude = .git, .tox, build 77 | select = 78 | # Regular flake8 rules 79 | C, E, F, W 80 | # flake8-bugbear rules 81 | B 82 | # B950: line too long (soft speed limit) 83 | B950 84 | # pep8-naming rules 85 | N 86 | ignore = 87 | # E203: whitespace before ':' (not PEP8 compliant) 88 | E203 89 | # E501: line too long (replaced by B950) 90 | E501 91 | # W503: line break before binary operator (not PEP8 compliant) 92 | W503 93 | 94 | 95 | [build_sphinx] 96 | source-dir = docs/ 97 | build-dir = docs/_build 98 | all_files = 1 99 | -------------------------------------------------------------------------------- /tests/test_lookup.py: -------------------------------------------------------------------------------- 1 | from mopidy import models 2 | 3 | import pytest 4 | 5 | ITEM = { 6 | "files": [ 7 | { 8 | "name": "track02.mp3", 9 | "title": "Track #2", 10 | "format": "VBR MP3", 11 | "track": "02", 12 | }, 13 | { 14 | "name": "track01.mp3", 15 | "title": "Track #1", 16 | "format": "VBR MP3", 17 | "track": "01", 18 | }, 19 | ], 20 | "metadata": {"identifier": "album", "title": "Album", "mediatype": "audio"}, 21 | } 22 | 23 | ALBUM = models.Album(name="Album", uri="internetarchive:album") 24 | 25 | TRACK1 = models.Track( 26 | album=ALBUM, 27 | name="Track #1", 28 | track_no=1, 29 | uri="internetarchive:album#track01.mp3", 30 | ) 31 | 32 | TRACK2 = models.Track( 33 | album=ALBUM, 34 | name="Track #2", 35 | track_no=2, 36 | uri="internetarchive:album#track02.mp3", 37 | ) 38 | 39 | 40 | def test_lookup_root(library, client_mock): 41 | assert library.lookup("internetarchive:") == [] 42 | 43 | 44 | def test_lookup_album(library, client_mock): 45 | client_mock.getitem.return_value = ITEM 46 | results = library.lookup("internetarchive:album") 47 | client_mock.getitem.assert_called_once_with("album") 48 | assert results == [TRACK1, TRACK2] 49 | 50 | 51 | def test_lookup_track(library, client_mock): 52 | client_mock.getitem.return_value = ITEM 53 | results = library.lookup("internetarchive:album#track01.mp3") 54 | client_mock.getitem.assert_called_once_with("album") 55 | assert results == [TRACK1] 56 | # assert lookup cache is used 57 | client_mock.reset_mock() 58 | results = library.lookup("internetarchive:album#track02.mp3") 59 | client_mock.getitem.assert_not_called() 60 | assert results == [TRACK2] 61 | 62 | 63 | def test_lookup_refresh(library, client_mock): 64 | client_mock.getitem.return_value = ITEM 65 | results = library.lookup("internetarchive:album#track01.mp3") 66 | client_mock.getitem.assert_called_once_with("album") 67 | assert results == [TRACK1] 68 | # clear lookup cache 69 | library.refresh() 70 | assert client_mock.cache.clear.called 71 | # assert lookup cache is cleared 72 | client_mock.reset_mock() 73 | results = library.lookup("internetarchive:album#track02.mp3") 74 | client_mock.getitem.assert_called_once_with("album") 75 | assert results == [TRACK2] 76 | 77 | 78 | def test_lookup_unknown(library, client_mock): 79 | client_mock.getitem.side_effect = LookupError("null") 80 | with pytest.raises(LookupError): 81 | library.lookup("internetarchive:null") 82 | client_mock.getitem.assert_called_once_with("null") 83 | -------------------------------------------------------------------------------- /tests/conftest.py: -------------------------------------------------------------------------------- 1 | import collections 2 | 3 | from unittest import mock 4 | 5 | import mopidy_internetarchive as ext 6 | import pytest 7 | 8 | 9 | @pytest.fixture 10 | def config(): 11 | return { 12 | "internetarchive": { 13 | "base_url": "http://archive.org", 14 | "collections": ("audio", "etree", "foo"), 15 | "audio_formats": ("Flac", "VBR MP3"), 16 | "image_formats": ("JPEG", "PNG"), 17 | "browse_limit": None, 18 | "browse_views": collections.OrderedDict( 19 | [("title asc", "Title"), ("creator asc", "Creator")] 20 | ), 21 | "search_limit": None, 22 | "search_order": None, 23 | "cache_size": None, 24 | "cache_ttl": None, 25 | "retries": 0, 26 | "timeout": None, 27 | }, 28 | "proxy": {}, 29 | } 30 | 31 | 32 | @pytest.fixture 33 | def root_collections(): 34 | from mopidy.models import Ref 35 | 36 | return [ 37 | Ref.directory(name="Audio Archive", uri="internetarchive:audio"), 38 | Ref.directory(name="Live Music Archive", uri="internetarchive:etree"), 39 | ] 40 | 41 | 42 | @pytest.fixture 43 | def audio_mock(): 44 | audio_mock = mock.Mock() 45 | return audio_mock 46 | 47 | 48 | @pytest.fixture 49 | def client_mock(): 50 | client_mock = mock.Mock(spec=ext.client.InternetArchiveClient) 51 | client_mock.SearchResult = ext.client.InternetArchiveClient.SearchResult 52 | client_mock.cache = mock.Mock(spec=dict) 53 | client_mock.search.return_value = client_mock.SearchResult( 54 | { 55 | "responseHeader": {"params": {"q": "album"}}, 56 | "response": { 57 | "numFound": 2, 58 | "docs": [ 59 | { 60 | "identifier": "etree", 61 | "title": "Live Music Archive", 62 | "mediatype": "collection", 63 | }, 64 | { 65 | "identifier": "audio", 66 | "title": "Audio Archive", 67 | "mediatype": "collection", 68 | }, 69 | ], 70 | }, 71 | } 72 | ) 73 | return client_mock 74 | 75 | 76 | @pytest.fixture 77 | def backend_mock(client_mock, config): 78 | backend_mock = mock.Mock(spec=ext.backend.InternetArchiveBackend) 79 | backend_mock.client = client_mock 80 | return backend_mock 81 | 82 | 83 | @pytest.fixture 84 | def library(backend_mock, config): 85 | return ext.library.InternetArchiveLibraryProvider( 86 | config["internetarchive"], backend_mock 87 | ) 88 | 89 | 90 | @pytest.fixture 91 | def playback(audio_mock, backend_mock): 92 | return ext.playback.InternetArchivePlaybackProvider( 93 | audio_mock, backend_mock 94 | ) 95 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | ********************** 2 | Mopidy-InternetArchive 3 | ********************** 4 | 5 | .. image:: https://img.shields.io/pypi/v/Mopidy-InternetArchive 6 | :target: https://pypi.org/project/Mopidy-InternetArchive/ 7 | :alt: Latest PyPI version 8 | 9 | .. image:: https://img.shields.io/github/workflow/status/tkem/mopidy-internetarchive/CI 10 | :target: https://github.com/tkem/mopidy-internetarchive/actions 11 | :alt: CI build status 12 | 13 | .. image:: https://img.shields.io/readthedocs/mopidy-internetarchive 14 | :target: https://mopidy-internetarchive.readthedocs.io/ 15 | :alt: Read the Docs build status 16 | 17 | .. image:: https://img.shields.io/codecov/c/gh/tkem/mopidy-internetarchive 18 | :target: https://codecov.io/gh/tkem/mopidy-internetarchive 19 | :alt: Test coverage 20 | 21 | .. image:: https://img.shields.io/github/license/tkem/mopidy-internetarchive 22 | :target: https://raw.github.com/tkem/mopidy-internetarchive/master/LICENSE 23 | :alt: License 24 | 25 | .. image:: https://img.shields.io/badge/code%20style-black-000000.svg 26 | :target: https://github.com/psf/black 27 | :alt: Code style: black 28 | 29 | Mopidy-InternetArchive is a Mopidy_ extension for playing music from 30 | the `Internet Archive`_. 31 | 32 | This extension lets you search for and stream recordings ranging from 33 | `alternative news programming`_, to `Grateful Dead concerts`_, to `Old 34 | Time Radio shows`_, to `book and poetry readings`_, to `original 35 | music`_ uploaded by Internet Archive users. It also gives you access 36 | to a vast number of high-quality live recordings from the `Live Music 37 | Archive`_, and thousands of free audiobooks from the LibriVox_ 38 | collection. 39 | 40 | For more information and installation instructions, please see 41 | Mopidy-InternetArchive's online documentation_. 42 | 43 | .. _Mopidy: http://www.mopidy.com/ 44 | .. _Internet Archive: http://archive.org 45 | .. _alternative news programming: https://archive.org/details/audio_news 46 | .. _Grateful Dead concerts: https://archive.org/details/GratefulDead 47 | .. _Old Time Radio shows: https://archive.org/details/radioprograms 48 | .. _book and poetry readings: https://archive.org/details/audio_bookspoetry 49 | .. _original music: https://archive.org/details/opensource_audio 50 | .. _Live Music Archive: https://archive.org/details/etree 51 | .. _LibriVox: https://archive.org/details/librivoxaudio 52 | .. _Documentation: http://mopidy-internetarchive.readthedocs.org/en/latest/ 53 | 54 | 55 | Project resources 56 | ================= 57 | 58 | - `Source code `_ 59 | - `Issue tracker `_ 60 | - `Changelog `_ 61 | 62 | 63 | Credits 64 | ======= 65 | 66 | - Original author: `Thomas Kemmer `__ 67 | - Current maintainer: `Thomas Kemmer `__ 68 | - `Contributors `_ 69 | -------------------------------------------------------------------------------- /mopidy_internetarchive/__init__.py: -------------------------------------------------------------------------------- 1 | import collections 2 | import pathlib 3 | 4 | import pkg_resources 5 | 6 | from mopidy import config, ext 7 | 8 | __version__ = pkg_resources.get_distribution("Mopidy-InternetArchive").version 9 | 10 | SORT_FIELDS = [ 11 | f"{f} {o}" 12 | for o in ("asc", "desc") 13 | for f in ( 14 | "addeddate", 15 | "avg_rating", 16 | "call_number", 17 | "createdate", 18 | "creatorSorter", 19 | "date", 20 | "downloads", 21 | "foldoutcount", 22 | "headerImage", 23 | "identifier", 24 | "imagecount", 25 | "indexdate", 26 | "languageSorter", 27 | "licenseurl", 28 | "month", 29 | "nav_order", 30 | "num_reviews", 31 | "publicdate", 32 | "reviewdate", 33 | "stars", 34 | "titleSorter", 35 | "week", 36 | "year", 37 | ) 38 | ] 39 | 40 | 41 | class ConfigMap(config.ConfigValue): 42 | 43 | default_keys = config.String() 44 | 45 | default_values = config.String() 46 | 47 | def __init__( 48 | self, 49 | keys=default_keys, 50 | values=default_values, 51 | delim="|", 52 | optional=False, 53 | ): 54 | self.__keys = keys 55 | self.__values = values 56 | self.__delim = delim 57 | self.__optional = optional 58 | 59 | def deserialize(self, value): 60 | dict = collections.OrderedDict() 61 | for s in config.List(optional=self.__optional).deserialize(value): 62 | parts = s.partition(self.__delim) 63 | key = self.__keys.deserialize(parts[0]) 64 | val = self.__values.deserialize(parts[2]) 65 | dict[key] = val 66 | return dict 67 | 68 | def serialize(self, value, display=False): 69 | if not value: 70 | return "" 71 | d = config.String().serialize(self.__delim) 72 | return config.List().serialize( 73 | [ 74 | self.__keys.serialize(k) + d + self.__values.serialize(v) 75 | for k, v in value.items() 76 | ] 77 | ) 78 | 79 | 80 | class Extension(ext.Extension): 81 | 82 | dist_name = "Mopidy-InternetArchive" 83 | ext_name = "internetarchive" 84 | version = __version__ 85 | 86 | def get_default_config(self): 87 | return config.read(pathlib.Path(__file__).parent / "ext.conf") 88 | 89 | def get_config_schema(self): 90 | schema = super().get_config_schema() 91 | schema.update( 92 | base_url=config.String(), 93 | collections=config.List(), 94 | audio_formats=config.List(), 95 | image_formats=config.List(), 96 | browse_limit=config.Integer(minimum=1, optional=True), 97 | browse_views=ConfigMap(keys=config.String(choices=SORT_FIELDS)), 98 | search_limit=config.Integer(minimum=1, optional=True), 99 | search_order=config.String(choices=SORT_FIELDS, optional=True), 100 | cache_size=config.Integer(minimum=1, optional=True), 101 | cache_ttl=config.Integer(minimum=0, optional=True), 102 | retries=config.Integer(minimum=0), 103 | timeout=config.Integer(minimum=0, optional=True), 104 | # no longer used 105 | browse_order=config.Deprecated(), 106 | exclude_collections=config.Deprecated(), 107 | exclude_mediatypes=config.Deprecated(), 108 | username=config.Deprecated(), 109 | ) 110 | return schema 111 | 112 | def setup(self, registry): 113 | from .backend import InternetArchiveBackend 114 | 115 | registry.add("backend", InternetArchiveBackend) 116 | -------------------------------------------------------------------------------- /tests/test_browse.py: -------------------------------------------------------------------------------- 1 | from mopidy import models 2 | 3 | COLLECTION = { 4 | "metadata": { 5 | "identifier": "directory", 6 | "title": "Directory", 7 | "mediatype": "collection", 8 | } 9 | } 10 | 11 | ITEM = { 12 | "files": [ 13 | { 14 | "name": "track02.mp3", 15 | "title": "Track #2", 16 | "format": "VBR MP3", 17 | "track": "02", 18 | }, 19 | { 20 | "name": "track01.mp3", 21 | "title": "Track #1", 22 | "format": "VBR MP3", 23 | "track": "01", 24 | }, 25 | ], 26 | "metadata": {"identifier": "album", "title": "Album", "mediatype": "audio"}, 27 | } 28 | 29 | TRACKS = [ 30 | models.Ref.track(name="Track #1", uri="internetarchive:album#track01.mp3"), 31 | models.Ref.track(name="Track #2", uri="internetarchive:album#track02.mp3"), 32 | ] 33 | 34 | VIEWS = [ 35 | models.Ref.directory( 36 | name="Title", uri="internetarchive:directory?sort=title%20asc" 37 | ), 38 | models.Ref.directory( 39 | name="Creator", uri="internetarchive:directory?sort=creator%20asc" 40 | ), 41 | ] 42 | 43 | 44 | def test_has_root_directory(library): 45 | assert library.root_directory == models.Ref.directory( 46 | name="Internet Archive", uri="internetarchive:" 47 | ) 48 | 49 | 50 | def test_browse_root_directory(library, client_mock, root_collections): 51 | results = library.browse(library.root_directory.uri) 52 | assert client_mock.search.called_once() 53 | assert results == root_collections 54 | 55 | 56 | def test_browse_collection(library, client_mock): 57 | client_mock.getitem.return_value = COLLECTION 58 | results = library.browse("internetarchive:directory") 59 | client_mock.getitem.assert_called_once_with("directory") 60 | assert results == VIEWS 61 | 62 | 63 | def test_browse_audio(library, client_mock): 64 | client_mock.getitem.return_value = ITEM 65 | results = library.browse("internetarchive:album") 66 | client_mock.getitem.assert_called_once_with("album") 67 | assert results == TRACKS 68 | 69 | 70 | def test_browse_video(library, client_mock): 71 | client_mock.getitem.return_value = { 72 | "files": [], 73 | "metadata": {"identifier": "item", "mediatype": "video"}, 74 | } 75 | results = library.browse("internetarchive:album") 76 | client_mock.getitem.assert_called_once_with("album") 77 | assert results == [] 78 | 79 | 80 | def test_browse_view(library, client_mock): 81 | client_mock.search.return_value = client_mock.SearchResult( 82 | { 83 | "responseHeader": {"params": {"q": "album"}}, 84 | "response": { 85 | "docs": [ 86 | { 87 | "identifier": "album", 88 | "title": "Album", 89 | "mediatype": "audio", 90 | }, 91 | { 92 | "identifier": "directory", 93 | "title": "Directory", 94 | "mediatype": "collection", 95 | }, 96 | ], 97 | "numFound": 2, 98 | }, 99 | } 100 | ) 101 | results = library.browse("internetarchive:audio?sort=title%20asc") 102 | assert library.backend.client.search.called_once() 103 | assert results == [ 104 | models.Ref.album(name="Album", uri="internetarchive:album"), 105 | models.Ref.directory(name="Directory", uri="internetarchive:directory"), 106 | ] 107 | 108 | 109 | def test_browse_file(library, client_mock): 110 | results = library.browse("internetarchive:album#file.mp3") 111 | client_mock.getitem.assert_not_called() 112 | client_mock.search.assert_not_called() 113 | assert results == [] 114 | -------------------------------------------------------------------------------- /CHANGELOG.rst: -------------------------------------------------------------------------------- 1 | v3.0.1 (2022-04-03) 2 | =================== 3 | 4 | - Officially support Python 3.9 and 3.10. 5 | 6 | 7 | v3.0.0 (2019-12-26) 8 | =================== 9 | 10 | - Depend on final release of Mopidy 3.0.0. 11 | 12 | 13 | v3.0.0a1 (2019-12-06) 14 | ===================== 15 | 16 | - Require Python >= 3.7. 17 | 18 | - Require Mopidy >= 3.0.0a5. 19 | 20 | - Require Pykka >= 2.0.1. 21 | 22 | - Update project setup. 23 | 24 | 25 | v2.0.3 (2017-06-16) 26 | =================== 27 | 28 | - Handle archive.org JSON API changes. 29 | 30 | 31 | v2.0.2 (2017-01-09) 32 | =================== 33 | 34 | - Fix file name handling. 35 | 36 | 37 | v2.0.1 (2017-01-09) 38 | =================== 39 | 40 | - Handle multiple item titles. 41 | 42 | 43 | v2.0.0 (2015-12-08) 44 | =================== 45 | 46 | - Support configurable sort criteria when browsing collections via 47 | "browse views". 48 | 49 | - Include collections in browse results. 50 | 51 | - Add support for ``LibraryProvider.get_images()``. 52 | 53 | - Drop support for deprecated ``Album.images``. 54 | 55 | - Drop special handling of bookmarks. 56 | 57 | - Cache root collections. 58 | 59 | - Update documentation. 60 | 61 | 62 | v1.3.0 (2015-09-11) 63 | =================== 64 | 65 | - Require Mopidy >= 1.1. 66 | 67 | - Use Mopidy proxy settings and HTTP User-Agent. 68 | 69 | - Fix track bitrates represented in Kbit/s. 70 | 71 | - Drop exact search support. 72 | 73 | - Only cache items. 74 | 75 | 76 | v1.2.1 (2015-03-25) 77 | =================== 78 | 79 | - Remove search query normalization. 80 | 81 | - Prepare for Mopidy v1.0 exact search API. 82 | 83 | 84 | v1.2.0 (2015-03-19) 85 | =================== 86 | 87 | - Remove playlists provider. 88 | 89 | - Add bookmarks to root directory for browsing. 90 | 91 | 92 | v1.1.0 (2014-11-19) 93 | =================== 94 | 95 | - Load bookmarks as individual playlists. 96 | 97 | - Clear library cache when refreshing playlists. 98 | 99 | - Encode filenames in URIs. 100 | 101 | - Add HTTP connection retries. 102 | 103 | 104 | v1.0.3 (2014-11-14) 105 | =================== 106 | 107 | - Fix handling of re-derived VBR MP3 files. 108 | 109 | - Remove Ogg Vorbis from default audio formats. 110 | 111 | 112 | v1.0.2 (2014-11-07) 113 | =================== 114 | 115 | - Update dependencies. 116 | 117 | - Browse Internet Archive items as albums. 118 | 119 | - Make caching optional. 120 | 121 | - Disable PNG image format in default configuration. 122 | 123 | - Temporarily disable VBR MP3 and track comments. 124 | 125 | 126 | v1.0.1 (2014-09-29) 127 | =================== 128 | 129 | - Add item descriptions as track comments. 130 | 131 | - Filter search results for exact queries. 132 | 133 | 134 | v1.0.0 (2014-09-26) 135 | =================== 136 | 137 | - Major rewrite for version 1.0.0. 138 | 139 | 140 | v0.5.0 (2014-02-28) 141 | =================== 142 | 143 | - Update `README` with link to documentation. 144 | 145 | - New config values: ``search_order``, ``browse_order``. 146 | 147 | - Allow empty queries for searching. 148 | 149 | 150 | v0.4.0 (2014-02-25) 151 | =================== 152 | 153 | - Various performance and stability improvements. 154 | 155 | - Option to exclude specific collections from searching/browsing. 156 | 157 | - Add image URLs to albums. 158 | 159 | 160 | v0.3.1 (2014-02-21) 161 | =================== 162 | 163 | - Fix default configuration. 164 | 165 | 166 | v0.3.0 (2014-02-21) 167 | =================== 168 | 169 | - Add bookmark browsing support. 170 | 171 | - Better filtering of search results. 172 | 173 | - Stability and performance improvements. 174 | 175 | 176 | v0.2.0 (2014-01-31) 177 | =================== 178 | 179 | - Add library browsing support. 180 | 181 | - Cache search results and metadata. 182 | 183 | - Properly quote/encode query terms. 184 | 185 | 186 | v0.1.0 (2014-01-24) 187 | =================== 188 | 189 | - Initial release. 190 | -------------------------------------------------------------------------------- /tests/test_configmap.py: -------------------------------------------------------------------------------- 1 | import collections 2 | import re 3 | 4 | from mopidy.config import types 5 | 6 | import pytest 7 | from mopidy_internetarchive import ConfigMap 8 | 9 | 10 | def test_deserialize(): 11 | expected = collections.OrderedDict([("a", "1"), ("b", "2"), ("c", "3")]) 12 | type = ConfigMap() 13 | assert type.deserialize("a|1, b|2 , c|3") == expected 14 | assert type.deserialize("a|1\n b|2 \nc|3") == expected 15 | 16 | with pytest.raises(ValueError): 17 | type.deserialize("") 18 | with pytest.raises(ValueError): 19 | type.deserialize("a") 20 | with pytest.raises(ValueError): 21 | type.deserialize("a|") 22 | with pytest.raises(ValueError): 23 | type.deserialize("|1") 24 | 25 | 26 | def test_deserialize_choice_keys(): 27 | expected = collections.OrderedDict([("a", "1"), ("b", "2"), ("c", "3")]) 28 | type = ConfigMap(keys=types.String(choices=["a", "b", "c"])) 29 | assert type.deserialize("a|1, b|2 , c|3") == expected 30 | assert type.deserialize("a|1\n b|2 \nc|3") == expected 31 | 32 | with pytest.raises(ValueError): 33 | type.deserialize("x|0") 34 | 35 | 36 | def test_deserialize_integer_keys(): 37 | expected = collections.OrderedDict([(1, "a"), (2, "b"), (3, "c")]) 38 | type = ConfigMap(keys=types.Integer()) 39 | assert type.deserialize("1|a, 2|b , 3|c") == expected 40 | assert type.deserialize("1|a\n 2|b \n3|c") == expected 41 | 42 | 43 | def test_deserialize_list_keys(): 44 | expected = collections.OrderedDict([(("a",), "1"), (("b", "c"), "2")]) 45 | type = ConfigMap(keys=types.List()) 46 | assert type.deserialize("a|1\n b, c|2") == expected 47 | 48 | 49 | def test_deserialize_choice_values(): 50 | expected = collections.OrderedDict([("a", "1"), ("b", "2"), ("c", "3")]) 51 | type = ConfigMap(values=types.String(choices=["1", "2", "3"])) 52 | assert type.deserialize("a|1, b|2 , c|3") == expected 53 | assert type.deserialize("a|1\n b|2 \nc|3") == expected 54 | 55 | with pytest.raises(ValueError): 56 | type.deserialize("x|0") 57 | 58 | 59 | def test_deserialize_integer_values(): 60 | expected = collections.OrderedDict([("a", 1), ("b", 2), ("c", 3)]) 61 | type = ConfigMap(values=types.Integer()) 62 | assert type.deserialize("a|1, b|2 , c|3") == expected 63 | assert type.deserialize("a|1\n b|2 \nc|3") == expected 64 | 65 | with pytest.raises(ValueError): 66 | type.deserialize("x|y") 67 | 68 | 69 | def test_deserialize_list_values(): 70 | expected = collections.OrderedDict([("a", ("1",)), ("b", ("2", "3"))]) 71 | type = ConfigMap(values=types.List()) 72 | assert type.deserialize("a|1\n b|2,3") == expected 73 | 74 | 75 | def test_deserialize_optional_values(): 76 | expected = collections.OrderedDict([("a", "1"), ("b", None), ("c", None)]) 77 | type = ConfigMap(values=types.String(optional=True)) 78 | assert type.deserialize("a|1, b| , c") == expected 79 | assert type.deserialize("a|1\n b| \nc") == expected 80 | 81 | 82 | def test_delim(): 83 | expected = collections.OrderedDict([("a", "1"), ("b", "2"), ("c", "3")]) 84 | type = ConfigMap(delim=":") 85 | assert type.deserialize("a:1, b: 2 , c :3") == expected 86 | assert type.deserialize("a:1\n b: 2 \nc :3") == expected 87 | 88 | 89 | def test_optional(): 90 | expected = collections.OrderedDict() 91 | assert ConfigMap(optional=True).deserialize("") == expected 92 | 93 | with pytest.raises(ValueError): 94 | ConfigMap(optional=False).deserialize("") 95 | with pytest.raises(ValueError): 96 | ConfigMap().deserialize("") 97 | 98 | 99 | def test_serialize(): 100 | type = ConfigMap(keys=types.String(), values=types.Integer()) 101 | value = collections.OrderedDict([("a", 1), ("b", 2), ("c", 3)]) 102 | result = type.serialize(value) 103 | assert re.match(r"\s*a|1\n\s*b|2\n\s*c|3", result) 104 | 105 | 106 | def test_serialize_none(): 107 | type = ConfigMap(keys=types.String(), values=types.Integer()) 108 | result = type.serialize(None) 109 | assert result == "" 110 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | ********************** 2 | Mopidy-InternetArchive 3 | ********************** 4 | 5 | Mopidy-InternetArchive is a Mopidy_ extension for playing music from 6 | the `Internet Archive`_. 7 | 8 | This extension lets you search for and stream recordings ranging from 9 | `alternative news programming`_, to `Grateful Dead concerts`_, to `Old 10 | Time Radio shows`_, to `book and poetry readings`_, to `original 11 | music`_ uploaded by Internet Archive users. It also gives you access 12 | to a vast number of high-quality live recordings from the `Live Music 13 | Archive`_, and thousands of free audiobooks from the LibriVox_ 14 | collection. 15 | 16 | 17 | Browsing the Internet Archive 18 | ============================= 19 | 20 | If your Mopidy client supports browsing, there should be a top-level 21 | directory named *Internet Archive*. Beneath that, you will find the 22 | Internet Archive collections listed in 23 | :confval:`internetarchive/collections`, and you should be able to 24 | browse individual audio items (albums) and files (tracks) within 25 | these. 26 | 27 | For practical and performance reasons, the number of items that will 28 | be shown within a collection is limited, e.g. you will not see all 29 | 167,967 audio items of the Live Music Archive [#footnote1]_. The 30 | :ref:`default configuration ` sets this limit to 100, but 31 | this can be changed using :confval:`internetarchive/browse_limit`. 32 | 33 | To allow browsing collections using different sort criteria, every 34 | collection provides a number of *views*, virtual subdirectories which 35 | let you browse the collection's items by popularity, title, publish 36 | date, and so on. The default views are set up to resemble the 37 | archive.org_ Web interface, but can be changed at your own discretion 38 | with :confval:`internetarchive/browse_views`. 39 | 40 | 41 | Searching the Internet Archive 42 | ============================== 43 | 44 | The Internet Archive only supports searching for *items*, but not for 45 | individual files or tracks. Therefore, only *albums* will show up 46 | when searching in Mopidy. This also means that only album-related 47 | search fields are supported, so searching for track names or numbers 48 | will yield no results from the Internet Archive. 49 | 50 | The number and ordering of search results returned from the Internet 51 | Archive can be changed with :confval:`internetarchive/search_limit` 52 | and :confval:`internetarchive/search_order`. Unless you explicitly 53 | specify an Internet Archive collection to search within, search scope 54 | will also be limited to the collections listed in 55 | :confval:`internetarchive/collections`. 56 | 57 | 58 | Archive Favorites 59 | ================= 60 | 61 | If you have an Internet Archive account - also termed a `Virtual 62 | Library Card`_ - you can access your `Archive Favorites`_ from Mopidy. 63 | To do so, you just need to add the identifier of your favorites 64 | collection to :confval:`internetarchive/collections`. Typically, the 65 | identifier is *fav-{username}*, but you should be able to figure it 66 | out from the archive.org_ Web site. When added to 67 | :confval:`internetarchive/collections`, you will be able to browse and 68 | search your Archive Favorites just like the other collections listed 69 | there. 70 | 71 | 72 | .. toctree:: 73 | :hidden: 74 | 75 | install 76 | config 77 | changelog 78 | license 79 | 80 | 81 | .. rubric:: Footnotes 82 | 83 | .. [#footnote1] As of Jan. 9, 2017. 84 | 85 | 86 | .. _Mopidy: http://www.mopidy.com/ 87 | .. _Internet Archive: http://archive.org 88 | .. _alternative news programming: https://archive.org/details/audio_news 89 | .. _Grateful Dead concerts: https://archive.org/details/GratefulDead 90 | .. _Old Time Radio shows: https://archive.org/details/radioprograms 91 | .. _book and poetry readings: https://archive.org/details/audio_bookspoetry 92 | .. _original music: https://archive.org/details/opensource_audio 93 | .. _Live Music Archive: https://archive.org/details/etree 94 | .. _LibriVox: https://archive.org/details/librivoxaudio 95 | 96 | .. _archive.org: https://archive.org/ 97 | .. _Virtual Library Card: https://archive.org/account/login.createaccount.php 98 | .. _Archive Favorites: https://archive.org/bookmarks.php 99 | -------------------------------------------------------------------------------- /docs/config.rst: -------------------------------------------------------------------------------- 1 | ************* 2 | Configuration 3 | ************* 4 | 5 | This extension has a number of configuration values that can be 6 | tweaked. However, the :ref:`default configuration ` contains 7 | everything to get you up and running, and will usually require only a 8 | few modifications to match personal preferences. 9 | 10 | 11 | .. _confvals: 12 | 13 | Configuration Values 14 | ==================== 15 | 16 | .. confval:: internetarchive/enabled 17 | 18 | Whether this extension should be enabled or not. 19 | 20 | .. confval:: internetarchive/base_url 21 | 22 | Base URL to access the Internet Archive. 23 | 24 | .. confval:: internetarchive/collections 25 | 26 | A list of collection identifiers to show as top-level directories 27 | when browsing. These are also used to limit the search scope when 28 | no search base is given by Mopidy clients explicitly. 29 | 30 | .. confval:: internetarchive/audio_formats 31 | 32 | A list of audio file formats, in order of preference. 33 | 34 | This entry contains a list of `Internet Archive file formats`_. By 35 | default, only audio formats suitable for streaming are requested. 36 | Note that the Internet Archive also contains a large number of 37 | high-quality media files in FLAC_ and other lossless formats, but 38 | for sake of bandwidth (both your's and the Archive's), it is 39 | recommended that you stick to lossy audio formats for streaming 40 | through Mopidy. 41 | 42 | .. confval:: internetarchive/image_formats 43 | 44 | A list of image file formats, in order of preference. 45 | 46 | This entry contains a list of `Internet Archive file formats`_ to 47 | be considered when providing images for Internet Archive items. 48 | Note that some Mopidy clients, especially MPD clients, will ignore 49 | album art provided by Mopidy-InternetArchive or other Mopidy 50 | extensions. 51 | 52 | .. confval:: internetarchive/browse_limit 53 | 54 | The maximum number of browse results. 55 | 56 | This is used to limit the number of items returned when browsing 57 | the Internet Archive. 58 | 59 | .. confval:: internetarchive/browse_views 60 | 61 | When browsing Internet Archive collections (or *directories* in 62 | Mopidy), this provides a list of virtual subdirectories so results 63 | can be retrieved using a particular :ref:`sort order`. 64 | 65 | The format for each entry is `` (asc|desc) |