Skip to content

Commit

Permalink
Merge branch 'mopidy:main' into musepack-scanning
Browse files Browse the repository at this point in the history
  • Loading branch information
schmaller authored Nov 1, 2023
2 parents 9d690de + 5906887 commit acdf4fa
Show file tree
Hide file tree
Showing 15 changed files with 301 additions and 63 deletions.
41 changes: 20 additions & 21 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -1,52 +1,51 @@
name: CI

on: [push, pull_request]
on:
pull_request:
push:
branches:
- main

jobs:
main:
strategy:
fail-fast: false
matrix:
include:
- name: "Test: Python 3.7"
python: "3.7"
tox: py37
- name: "Test: Python 3.8"
python: "3.8"
tox: py38
- name: "Test: Python 3.9"
python: "3.9"
tox: py39
- name: "Test: Python 3.10"
python: "3.10"
tox: py310
- name: "Test: Python 3.11"
python: "3.11"
tox: py311
coverage: true
- name: "Lint: check-manifest"
python: "3.9"
python: "3.11"
tox: check-manifest
- name: "Lint: flake8"
python: "3.9"
python: "3.11"
tox: flake8

name: ${{ matrix.name }}
runs-on: ubuntu-20.04
runs-on: ubuntu-22.04
container: ghcr.io/mopidy/ci:latest

steps:
- uses: actions/checkout@v2
- uses: actions/setup-python@v2
with:
python-version: ${{ matrix.python }}
- uses: actions/checkout@v3
- name: Fix home dir permissions to enable pip caching
run: chown -R root /github/home
- name: Cache pip
uses: actions/cache@v2
- uses: actions/setup-python@v4
with:
path: ~/.cache/pip
key: ${{ runner.os }}-${{ matrix.python }}-${{ matrix.tox }}-pip-${{ hashFiles('setup.cfg') }}-${{ hashFiles('tox.ini') }}
restore-keys: |
${{ runner.os }}-${{ matrix.python }}-${{ matrix.tox }}-pip-
python-version: ${{ matrix.python }}
cache: pip
cache-dependency-path: setup.cfg
- run: python -m pip install pygobject tox
- run: python -m tox -e ${{ matrix.tox }}
if: ${{ ! matrix.coverage }}
- run: python -m tox -e ${{ matrix.tox }} -- --cov-report=xml
if: ${{ matrix.coverage }}
- uses: codecov/codecov-action@v1
- uses: codecov/codecov-action@v3
if: ${{ matrix.coverage }}
121 changes: 119 additions & 2 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ Mopidy-Local
:target: https://pypi.org/project/Mopidy-Local/
:alt: Latest PyPI version

.. image:: https://img.shields.io/github/workflow/status/mopidy/mopidy-local/CI
.. image:: https://img.shields.io/github/actions/workflow/status/mopidy/mopidy-local/ci.yml?branch=main
:target: https://github.com/mopidy/mopidy-local/actions
:alt: CI build status

Expand All @@ -30,6 +30,7 @@ Table of contents
- `Generating a library`_
- `Updating the library`_
- `Clearing the library`_
- `Library layout`_

- `Project resources`_
- Credits_
Expand Down Expand Up @@ -106,7 +107,7 @@ The following configuration values are available:
This config value has no effect if ``local/included_file_extensions`` is set.

- ``local/directories``: List of top-level directory names and URIs
for browsing.
for browsing. See below.

- ``local/timeout``: Database connection timeout in seconds.

Expand Down Expand Up @@ -173,6 +174,122 @@ To delete your local images and clear your local library::
A prompt will ask you to confirm this irreversible operation.


Library layout
--------------

The exposed library has a root directory and nine top-level directories defined
under the root directory:

- Albums
- Artists
- Composers
- Genres
- Performers
- Release Years
- Tracks
- Last Week's Updates
- Last Month's Updates

This can be configured through the ``directories`` setting. It's expected to be a
list of space separated name and URI supported for browsing, eg::

directories =
Albums local:directory?type=album
Artists local:directory?type=artist
Composers local:directory?type=artist&role=composer
Tracks local:directory?type=track
Last Week's Updates local:directory?max-age=604800

URIs supported for browsing
~~~~~~~~~~~~~~~~~~~~~~~~~~~

*Remember that URIs are opaque values that neither Mopidy’s core layer or Mopidy
frontends should attempt to derive any meaning from.* That said, it's necessary
to have a sufficient knowledge of Mopidy-Local URIs to customize the
``directories`` setting properly.

Browsing URIs starting with ``local:artist`` returns references to
albums and tracks with the given artist. Browsing URIs starting with
``local:album`` returns references to the album tracks. Browsing URIs
starting with ``local:track`` is not supported.

Other URIs supported for browsing start with ``local:directory``. The returned
references are specified through "query parameters":

- ``local:directory``: References to the top levels directories.

- ``local:directory?type=tracks``: References all tracks. Multiple
parameters can be added to filter the referenced tracks: ``album``,
``albumartist``, ``artist``, ``composer``, ``date``, ``genre``,
``performer``, and ``max-age``.

- ``local:directory?type=date``: References to directories grouping tracks by
date and album. Dates are transformed according to the optional parameter
``FORMAT`` which default to ``%Y-%m-%d``. The URIs of the references start
with ``local:directory?date=``.

- ``local:directory?type=genre``: References to directories named after genres
found among all tracks. Their URIs start with ``local:directory?genre=``.

- ``local:directory?type=album``: References to all albums.

- ``local:directory?type=album&PARAM=VALUE``: References to
directories grouping tracks matching the given criteria. ``PARAM``
must be one of ``albumartist``, ``artist``, ``composer``, ``date``,
``genre``, ``performer``, ``max-age``. The referenced directories
group the selected tracks by album; Their URIs start with
``local:directory?PARAM=VALUE&type=track&album=local:album:``.

- ``local:directory?type=artist``: References to all artists.

- ``local:directory?type=artist&role=ROLE``: References to directories with URIs
``local:directory?ROLE=URI`` where ``URI`` varies among all URIs starting with
``local:artist`` build from all tracks tag corresponding to ``ROLE``. ``ROLE``
is one of ``albumartist``, ``artist``, ``composer``, or ``performer``.

- ``local:directory?album=URI``: A reference to a directory grouping the tracks
of the album with given URI. Its URI starts with
``local:directory?album=URI&type=track``.

- ``local:directory?albumartist=URI``: References to directories
grouping tracks whose albumartist tag has given URI. The referenced
directories group the selected tracks by album; Their URIs start
with
``local:directory?albumartist=URI&type=track&album=local:album:``.

- ``local:directory?artist=URI``: References to directories grouping
tracks whose artist has given URI. The referenced directories group
the selected tracks by album; Their URIs start with
``local:directory?artist=URI&type=track&album=local:album:``.

- ``local:directory?composer=URI``: References to directories grouping
tracks whose composer has given URI. The referenced directories
group the selected tracks by album; Their URIs start with
``local:directory?composer=URI&type=track&album=local:album:``.

- ``local:directory?date=DATE``: References to directories grouping
tracks whose date match DATE. The referenced directories group the
selected tracks by album; Their URIs start with
``local:directory?date=DATE&type=track&album=local:album:``. The
match is to be interpreted as in a ``LIKE`` SQL statement.

- ``local:directory?genre=GENRE``: References to directories grouping
tracks whose genre is GENRE. The referenced directories group the
selected tracks by album; Their URIs start with
``local:directory?genre=GENRE&type=track&album=local:album:``.

- ``local:directory?performer=URI``: References to directories
grouping tracks whose performer has given URI. The referenced
directories group the selected tracks by album; Their URIs start
with
``local:directory?performer=URI&type=track&album=local:album:``.

- ``local:directory?max-age=SECONDS``: References to directories
grouping tracks whose "last modified" date is newer than SECONDS
seconds. The referenced directories group the selected tracks by
album; Their URIs start with
``local:directory?max-age=SECONDS&type=track&album=local:album:``.

Project resources
=================

Expand Down
8 changes: 6 additions & 2 deletions mopidy_local/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,9 @@ def get_config_schema(self):
schema["data_dir"] = config.Deprecated()
schema["playlists_dir"] = config.Deprecated()
schema["tag_cache_file"] = config.Deprecated()
schema["scan_timeout"] = config.Integer(minimum=1000, maximum=1000 * 60 * 60)
schema["scan_timeout"] = config.Integer(
minimum=1000, maximum=1000 * 60 * 60
)
schema["scan_flush_threshold"] = config.Integer(minimum=0)
schema["scan_follow_symlinks"] = config.Boolean()
schema["included_file_extensions"] = config.List(optional=True)
Expand All @@ -38,7 +40,9 @@ def setup(self, registry):
from .actor import LocalBackend

registry.add("backend", LocalBackend)
registry.add("http:app", {"name": self.ext_name, "factory": self.webapp})
registry.add(
"http:app", {"name": self.ext_name, "factory": self.webapp}
)

def get_command(self):
from .commands import LocalCommand
Expand Down
35 changes: 28 additions & 7 deletions mopidy_local/commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,9 @@ def run(self, args, config):

def _find_files(self, *, media_dir, follow_symlinks):
logger.info(f"Finding files in {media_dir.as_uri()} ...")
file_mtimes, file_errors = mtimes.find_mtimes(media_dir, follow=follow_symlinks)
file_mtimes, file_errors = mtimes.find_mtimes(
media_dir, follow=follow_symlinks
)
logger.info(f"Found {len(file_mtimes)} files in {media_dir.as_uri()}")

if file_errors:
Expand Down Expand Up @@ -169,7 +171,9 @@ def _extension_filters(
):
if included_file_exts:
if relative_path.suffix.lower() in included_file_exts:
logger.debug(f"Added {file_uri}: File extension on included list")
logger.debug(
f"Added {file_uri}: File extension on included list"
)
return True
else:
logger.debug(
Expand All @@ -178,7 +182,9 @@ def _extension_filters(
return False
else:
if relative_path.suffix.lower() in excluded_file_exts:
logger.debug(f"Skipped {file_uri}: File extension on excluded list")
logger.debug(
f"Skipped {file_uri}: File extension on excluded list"
)
return False
else:
logger.debug(
Expand All @@ -193,17 +199,30 @@ def _extension_filters(
if (
not _is_hidden_file(relative_path, file_uri)
and _extension_filters(
relative_path, file_uri, included_file_exts, excluded_file_exts
relative_path,
file_uri,
included_file_exts,
excluded_file_exts,
)
and absolute_path not in files_in_library
):
files_to_update.add(absolute_path)

logger.info(f"Found {len(files_to_update)} tracks which need to be updated")
logger.info(
f"Found {len(files_to_update)} tracks which need to be updated"
)
return files_to_update

def _scan_metadata(
self, *, media_dir, file_mtimes, files, library, timeout, flush_threshold, limit
self,
*,
media_dir,
file_mtimes,
files,
library,
timeout,
flush_threshold,
limit,
):
logger.info("Scanning...")

Expand Down Expand Up @@ -237,7 +256,9 @@ def _scan_metadata(
)
mtime = file_mtimes.get(absolute_path)
track = tags.convert_tags_to_track(result.tags).replace(
uri=local_uri, length=result.duration, last_modified=mtime
uri=local_uri,
length=result.duration,
last_modified=mtime,
)
library.add(track, result.tags, result.duration)
logger.debug(f"Added {track.uri}")
Expand Down
20 changes: 15 additions & 5 deletions mopidy_local/library.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@

def date_ref(date):
return Ref.directory(
uri=uritools.uricompose("local", None, "directory", {"date": date}), name=date
uri=uritools.uricompose("local", None, "directory", {"date": date}),
name=date,
)


Expand All @@ -27,7 +28,9 @@ def genre_ref(genre):
class LocalLibraryProvider(backend.LibraryProvider):
ROOT_DIRECTORY_URI = "local:directory"

root_directory = models.Ref.directory(uri=ROOT_DIRECTORY_URI, name="Local media")
root_directory = models.Ref.directory(
uri=ROOT_DIRECTORY_URI, name="Local media"
)

def __init__(self, backend, config):
super().__init__(backend)
Expand Down Expand Up @@ -152,9 +155,13 @@ def _browse_directory(self, uri, order=("type", "name COLLATE NOCASE")):
# TODO: handle these in schema (generically)?
if type == "date":
format = query.get("format", "%Y-%m-%d")
return list(map(date_ref, schema.dates(self._connect(), format=format)))
return list(
map(date_ref, schema.dates(self._connect(), format=format))
)
if type == "genre":
return list(map(genre_ref, schema.list_distinct(self._connect(), "genre")))
return list(
map(genre_ref, schema.list_distinct(self._connect(), "genre"))
)

# Fix #38: keep sort order of album tracks; this also applies
# to composers and performers
Expand Down Expand Up @@ -186,7 +193,10 @@ def _browse_directory(self, uri, order=("type", "name COLLATE NOCASE")):
refs.append(
Ref.directory(
uri=uritools.uricompose(
"local", None, "directory", dict(query, **{role: ref.uri})
"local",
None,
"directory",
dict(query, **{role: ref.uri}),
),
name=ref.name,
)
Expand Down
7 changes: 5 additions & 2 deletions mopidy_local/schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -236,7 +236,7 @@ def dates(c, format="%Y-%m-%d"):
operator.itemgetter(0),
c.execute(
"""
SELECT DISTINCT strftime(?, date) AS date
SELECT DISTINCT(strftime(?, substr(date || '-01-01', 1, 10))) AS date
FROM track
WHERE date IS NOT NULL
ORDER BY date
Expand All @@ -258,7 +258,10 @@ def exists(c, uri):

def browse(c, type=None, order=("type", "name COLLATE NOCASE"), **kwargs):
filters, params = _filters(_BROWSE_FILTERS[type], **kwargs)
sql = _BROWSE_QUERIES[type] % (" AND ".join(filters) or "1", ", ".join(order))
sql = _BROWSE_QUERIES[type] % (
" AND ".join(filters) or "1",
", ".join(order),
)
logger.debug("SQLite browse query %r: %s", params, sql)
return [Ref(**row) for row in c.execute(sql, params)]

Expand Down
Loading

0 comments on commit acdf4fa

Please sign in to comment.