mirror of
https://github.com/beetbox/beets.git
synced 2025-12-09 18:12:19 +01:00
Merge branch 'master' into add-ytimport-plugin-link
This commit is contained in:
commit
94d75a759f
22 changed files with 96 additions and 43 deletions
6
.github/workflows/formatting_check.yml
vendored
6
.github/workflows/formatting_check.yml
vendored
|
|
@ -6,7 +6,9 @@ jobs:
|
|||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Install dependencies
|
||||
- uses: actions/checkout@v3
|
||||
- uses: paolorechia/pox@v1.0.1
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Run formatting check
|
||||
uses: paolorechia/pox@v1.0.1
|
||||
with:
|
||||
tox_env: "format_check"
|
||||
|
|
|
|||
|
|
@ -42,12 +42,12 @@ Non-Programming
|
|||
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 `our
|
||||
forums <https://discourse.beets.io/>`__ under the “Show and Tell”
|
||||
category for a chance to get featured in `the
|
||||
hear about it! Submit a post to our `discussion board
|
||||
<https://github.com/beetbox/beets/discussions/categories/show-and-tell>`__
|
||||
under the “Show and Tell” category for a chance to get featured in `the
|
||||
docs <https://beets.readthedocs.io/en/stable/guides/advanced.html>`__.
|
||||
- Consider helping out in `our forums <https://discourse.beets.io/>`__
|
||||
by responding to support requests or driving some new discussions.
|
||||
- Consider helping out fellow users by by `responding to support requests
|
||||
<https://github.com/beetbox/beets/discussions/categories/q-a>`__ .
|
||||
|
||||
Programming
|
||||
-----------
|
||||
|
|
@ -119,7 +119,8 @@ 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 <http://makeapullrequest.com/>`__, or stop by our
|
||||
`forums <https://discourse.beets.io/>`__ if you have any questions.
|
||||
`discussion board <https://github.com/beetbox/beets/discussions/>`__
|
||||
if you have any questions.
|
||||
|
||||
We maintain a list of issues we reserved for those new to open source
|
||||
labeled `“first timers
|
||||
|
|
|
|||
|
|
@ -115,12 +115,11 @@ Contact
|
|||
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`_ or `Discourse`_!
|
||||
`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
|
||||
.. _Discourse: https://discourse.beets.io/
|
||||
|
||||
Authors
|
||||
-------
|
||||
|
|
|
|||
|
|
@ -104,5 +104,5 @@ Read More
|
|||
`Adrian Sampson`_ 와 많은 사람들의 지지를 받아 Beets를 만들었다.
|
||||
돕고 싶다면 `forum`_.를 방문하면 된다.
|
||||
|
||||
.. _forum: https://discourse.beets.io
|
||||
.. _forum: https://github.com/beetbox/beets/discussions/
|
||||
.. _Adrian Sampson: https://www.cs.cornell.edu/~asampson/
|
||||
|
|
|
|||
|
|
@ -166,8 +166,8 @@ class FieldQuery(Query, Generic[P]):
|
|||
|
||||
def __repr__(self) -> str:
|
||||
return (
|
||||
"{0.__class__.__name__}({0.field!r}, {0.pattern!r}, "
|
||||
"{0.fast})".format(self)
|
||||
f"{self.__class__.__name__}({self.field!r}, {self.pattern!r}, "
|
||||
f"fast={self.fast})"
|
||||
)
|
||||
|
||||
def __eq__(self, other) -> bool:
|
||||
|
|
@ -205,7 +205,7 @@ class NoneQuery(FieldQuery[None]):
|
|||
return obj.get(self.field) is None
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return "{0.__class__.__name__}({0.field!r}, {0.fast})".format(self)
|
||||
return f"{self.__class__.__name__}({self.field!r}, {self.fast})"
|
||||
|
||||
|
||||
class StringFieldQuery(FieldQuery[P]):
|
||||
|
|
@ -471,7 +471,7 @@ class CollectionQuery(Query):
|
|||
return clause, subvals
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return "{0.__class__.__name__}({0.subqueries!r})".format(self)
|
||||
return f"{self.__class__.__name__}({self.subqueries!r})"
|
||||
|
||||
def __eq__(self, other) -> bool:
|
||||
return super().__eq__(other) and self.subqueries == other.subqueries
|
||||
|
|
@ -511,8 +511,8 @@ class AnyFieldQuery(CollectionQuery):
|
|||
|
||||
def __repr__(self) -> str:
|
||||
return (
|
||||
"{0.__class__.__name__}({0.pattern!r}, {0.fields!r}, "
|
||||
"{0.query_class.__name__})".format(self)
|
||||
f"{self.__class__.__name__}({self.pattern!r}, {self.fields!r}, "
|
||||
f"{self.query_class.__name__})"
|
||||
)
|
||||
|
||||
def __eq__(self, other) -> bool:
|
||||
|
|
@ -577,7 +577,7 @@ class NotQuery(Query):
|
|||
return not self.subquery.match(obj)
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return "{0.__class__.__name__}({0.subquery!r})".format(self)
|
||||
return f"{self.__class__.__name__}({self.subquery!r})"
|
||||
|
||||
def __eq__(self, other) -> bool:
|
||||
return super().__eq__(other) and self.subquery == other.subquery
|
||||
|
|
@ -883,6 +883,9 @@ class Sort:
|
|||
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."""
|
||||
|
|
@ -934,7 +937,7 @@ class MultipleSort(Sort):
|
|||
return items
|
||||
|
||||
def __repr__(self):
|
||||
return f"MultipleSort({self.sorts!r})"
|
||||
return f"{self.__class__.__name__}({self.sorts!r})"
|
||||
|
||||
def __hash__(self):
|
||||
return hash(tuple(self.sorts))
|
||||
|
|
@ -972,10 +975,9 @@ class FieldSort(Sort):
|
|||
return sorted(objs, key=key, reverse=not self.ascending)
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return "<{}: {}{}>".format(
|
||||
type(self).__name__,
|
||||
self.field,
|
||||
"+" if self.ascending else "-",
|
||||
return (
|
||||
f"{self.__class__.__name__}"
|
||||
f"({self.field!r}, ascending={self.ascending!r})"
|
||||
)
|
||||
|
||||
def __hash__(self) -> int:
|
||||
|
|
|
|||
|
|
@ -412,7 +412,7 @@ class ImportSession:
|
|||
|
||||
def ask_resume(self, toppath):
|
||||
"""If import of `toppath` was aborted in an earlier session, ask
|
||||
user if she wants to resume the import.
|
||||
user if they want to resume the import.
|
||||
|
||||
Determines the return value of `is_resuming(toppath)`.
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -151,6 +151,12 @@ class PathQuery(dbcore.FieldQuery):
|
|||
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.
|
||||
|
||||
|
|
@ -1518,9 +1524,12 @@ def parse_query_parts(parts, model_cls):
|
|||
|
||||
case_insensitive = beets.config["sort_case_insensitive"].get(bool)
|
||||
|
||||
return dbcore.parse_sorted_query(
|
||||
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):
|
||||
|
|
|
|||
|
|
@ -425,6 +425,8 @@ class DiscogsPlugin(BeetsPlugin):
|
|||
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()
|
||||
|
|
@ -474,8 +476,19 @@ class DiscogsPlugin(BeetsPlugin):
|
|||
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 (
|
||||
|
|
|
|||
|
|
@ -220,7 +220,7 @@ class EditPlugin(plugins.BeetsPlugin):
|
|||
- `fields`: The set of field names to edit (or None to edit
|
||||
everything).
|
||||
"""
|
||||
# Present the YAML to the user and let her change it.
|
||||
# Present the YAML to the user and let them change it.
|
||||
success = self.edit_objects(objs, fields)
|
||||
|
||||
# Save the new data.
|
||||
|
|
@ -370,7 +370,7 @@ class EditPlugin(plugins.BeetsPlugin):
|
|||
if not obj._db or obj.id is None:
|
||||
obj.id = -i
|
||||
|
||||
# Present the YAML to the user and let her change it.
|
||||
# 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)
|
||||
|
||||
|
|
|
|||
|
|
@ -1098,7 +1098,7 @@ class CoverArtUrl(RemoteArtSource):
|
|||
image_url = None
|
||||
try:
|
||||
# look for cover_art_url on album or first track
|
||||
if album.cover_art_url:
|
||||
if album.get("cover_art_url"):
|
||||
image_url = album.cover_art_url
|
||||
else:
|
||||
image_url = album.items().get().cover_art_url
|
||||
|
|
|
|||
12
docs/_static/beets.css
vendored
Normal file
12
docs/_static/beets.css
vendored
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
html[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 */
|
||||
BIN
docs/_static/beets_logo.png
vendored
Normal file
BIN
docs/_static/beets_logo.png
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 10 KiB |
|
|
@ -17,6 +17,8 @@ Major new features:
|
|||
|
||||
New features:
|
||||
|
||||
* :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`
|
||||
|
|
|
|||
15
docs/conf.py
15
docs/conf.py
|
|
@ -59,5 +59,16 @@ man_pages = [
|
|||
),
|
||||
]
|
||||
|
||||
# Options for Alabaster theme
|
||||
html_theme_options = {"fixed_sidebar": True}
|
||||
# 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.png"
|
||||
html_static_path = ['_static']
|
||||
html_css_files = ['beets.css']
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ 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://discourse.beets.io
|
||||
.. _discussion board: https://github.com/beetbox/beets/discussions/
|
||||
|
||||
.. contents::
|
||||
:local:
|
||||
|
|
|
|||
|
|
@ -309,4 +309,4 @@ 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://discourse.beets.io
|
||||
.. _the discussion board: https://github.com/beetbox/beets/discussions/
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ 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://discourse.beets.io
|
||||
.. _the discussion board: https://github.com/beetbox/beets/discussions/
|
||||
|
||||
Contents
|
||||
--------
|
||||
|
|
|
|||
2
setup.py
2
setup.py
|
|
@ -132,7 +132,7 @@ setup(
|
|||
],
|
||||
"docs": [
|
||||
"sphinx",
|
||||
"sphinx_rtd_theme",
|
||||
"pydata_sphinx_theme",
|
||||
],
|
||||
# Plugin (optional) dependencies:
|
||||
"absubmit": ["requests"],
|
||||
|
|
|
|||
|
|
@ -72,8 +72,8 @@ class ModifyFileMocker:
|
|||
class EditMixin:
|
||||
"""Helper containing some common functionality used for the Edit tests."""
|
||||
|
||||
def assertItemFieldsModified(
|
||||
self, library_items, items, fields=[], allowed=["path"] # noqa
|
||||
def assertItemFieldsModified( # noqa
|
||||
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
|
||||
|
|
@ -135,11 +135,11 @@ class EditCommandTest(unittest.TestCase, TestHelper, EditMixin):
|
|||
self.teardown_beets()
|
||||
self.unload_plugins()
|
||||
|
||||
def assertCounts(
|
||||
def assertCounts( # noqa
|
||||
self,
|
||||
mock_write,
|
||||
album_count=ALBUM_COUNT,
|
||||
track_count=TRACK_COUNT, # noqa
|
||||
track_count=TRACK_COUNT,
|
||||
write_call_count=TRACK_COUNT,
|
||||
title_starts_with="",
|
||||
):
|
||||
|
|
|
|||
|
|
@ -1078,9 +1078,9 @@ class EnumTest(_common.TestCase):
|
|||
"""
|
||||
|
||||
def test_ordered_enum(self):
|
||||
OrderedEnumClass = match.OrderedEnum(
|
||||
OrderedEnumClass = match.OrderedEnum( # noqa
|
||||
"OrderedEnumTest", ["a", "b", "c"]
|
||||
) # noqa
|
||||
)
|
||||
self.assertLess(OrderedEnumClass.a, OrderedEnumClass.b)
|
||||
self.assertLess(OrderedEnumClass.a, OrderedEnumClass.c)
|
||||
self.assertLess(OrderedEnumClass.b, OrderedEnumClass.c)
|
||||
|
|
|
|||
|
|
@ -132,9 +132,9 @@ class DateIntervalTest(unittest.TestCase):
|
|||
self.assertContains("..", date=datetime.min)
|
||||
self.assertContains("..", "1000-01-01T00:00:00")
|
||||
|
||||
def assertContains(
|
||||
def assertContains( # noqa
|
||||
self, interval_pattern, date_pattern=None, date=None
|
||||
): # noqa
|
||||
):
|
||||
if date is None:
|
||||
date = _date(date_pattern)
|
||||
(start, end) = _parse_periods(interval_pattern)
|
||||
|
|
|
|||
4
tox.ini
4
tox.ini
|
|
@ -33,7 +33,9 @@ commands =
|
|||
|
||||
[testenv:docs]
|
||||
basepython = python3.10
|
||||
deps = sphinx<4.4.0
|
||||
deps =
|
||||
sphinx<4.4.0
|
||||
pydata_sphinx_theme
|
||||
commands = sphinx-build -W -q -b html docs {envtmpdir}/html {posargs}
|
||||
|
||||
# checks all links in the docs
|
||||
|
|
|
|||
Loading…
Reference in a new issue