Commit graph

3959 commits

Author SHA1 Message Date
Šarūnas Nejus
fdc1aba603
Replace typing.cast with explicit type definitions and ignore TC006 2025-05-07 20:39:33 +01:00
Šarūnas Nejus
99dc0861c2
Redact sensitive fields
Redacted fields reported by GitHub secrets scanner[1] and a couple of others.

1: https://github.com/beetbox/beets/security/secret-scanning?query=is%3Aclosed
2025-05-07 20:39:32 +01:00
Šarūnas Nejus
c490ac5810
Fix formatting 2025-05-07 10:41:01 +01:00
Šarūnas Nejus
52951bf719
Fix legalize_path types
Background
  The `_legalize_stage` function was causing issues with Mypy due to
  inconsistent type usage between the `path` and `extension` parameters.
  This inconsistency stemmed from the `fragment` parameter influencing the
  types of these variables.

Key issues
  1. `path` was defined as `str`, while `extension` was `bytes`.
  2. Depending on `fragment`, `extension` could be either `str` or `bytes`.
  3. `path` was sometimes converted to `bytes` within `_legalize_stage`.

Item.destination` method
  - The `fragment` parameter determined the output format:
    - `False`: Returned absolute path as bytes (default)
    - `True`: Returned path relative to library directory as str

Thus
  - Rename `fragment` parameter to `relative_to_libdir` for clarity
  - Ensure `Item.destination` returns `bytes` in all cases
  - Code expecting strings now converts the output to `str`
  - Use only `str` type in `_legalize_stage` and `_legalize_path`
    functions
  - These functions are no longer dependent on `relative_to_libdir`
2025-05-04 12:23:22 +01:00
Aidan Epstein
ecdff785f7
Only output verbose details for parentwork plugin when running explicitly (#5135)
Fixes #4120.
2025-05-04 09:34:37 +02:00
Šarūnas Nejus
9acfa3c175
Remove arg_encoding 2025-04-21 12:41:57 +01:00
Šarūnas Nejus
179ed13e09
Say bye to util._fsencoding 2025-04-21 12:41:57 +01:00
wisp3rwind
b495286127 ftintitle: flatten code
linear code with early exits instead of more complicated nested
conditionals
2025-04-14 20:50:01 +02:00
wisp3rwind
8413de1a85 ftintitle: add typings 2025-04-14 20:42:59 +02:00
Peter Dolan
447cc82e04
Do not write unchanged items to the library in FtInTitle (#5718)
FtInTitle performs a library store operation for every item it
processes, whether or not the item has changed. By limiting the
`item.store()` call to only those cases when the item has changed, the
plugin’s performance when processing an entire library improves by two
to three orders of magnitude.
2025-04-14 18:22:41 +00:00
Šarūnas Nejus
8f209d85b9
Tidy up mbsync logs
No need to call `format` since this is done automatically at the point
the object is logged (if required).
2025-04-14 02:58:58 +01:00
Šarūnas Nejus
d1d681c1ff
mbsync: support other data sources 2025-04-14 02:28:43 +01:00
Šarūnas Nejus
441cd36e8a
missing: clarify that only musicbrainz backend supports missing albums for artist
And give this functionality a small refactor.
2025-04-14 02:28:43 +01:00
Šarūnas Nejus
4c1f217ce0
missing: support non-musicbrainz data sources 2025-04-14 02:28:42 +01:00
Skia
225c21b90f plugins/thumbnails: fix FFI with GIO on s390x
Using the correct function signature for g_file_new_for_path fixes the
tests on s390x.
I do not have the full story on why this failed consistently only on
s390x, but I guess the big endian might have something to play with
this.

Here is how the tests were failing:
```
169s ___________________________ ThumbnailsTest.test_uri ____________________________
169s
169s self = <test.plugins.test_thumbnails.ThumbnailsTest testMethod=test_uri>
169s
169s     def test_uri(self):
169s         gio = GioURI()
169s         if not gio.available:
169s             self.skipTest("GIO library not found")
169s
169s >       assert gio.uri("/foo") == "file:///"  # silent fail
169s E       AssertionError: assert '' == 'file:///'
169s E
169s E         - file:///
169s
169s test/plugins/test_thumbnails.py:268: AssertionError
```
You can see a full log here [1] and a history of consistent failure
here [2]. Both links are bound to expire at some point, sorry future
archeologist 🤷.

[1]: https://autopkgtest.ubuntu.com/results/autopkgtest-plucky/plucky/s390x/b/beets/20250403_162414_5d1da@/log.gz#S5
[2]: https://autopkgtest.ubuntu.com/packages/beets/plucky/s390x
2025-04-14 02:19:10 +01:00
J0J0 Todos
eb83491788 lastgenre: Fix "original fallback" conditions
This was not thought through clearly before. It now behaves as follows
which I suppose is least surprising to a user:

- force is on, keep_existing is on, but the whitelist is DISABLED
- no stage found anything on last.fm
- fall back to the original genre

If in this example the whitelist would be ENABLED, the behaviour
changes: Only if the existing genre passes the whitelist test the
original is kept.
2025-04-09 22:52:07 +02:00
J0J0 Todos
f4d22a83b5 lastgenre: Catch NoneType errors in _fitler_valid_genres 2025-04-09 22:52:07 +02:00
J0J0 Todos
702ddf493e lastgenre: Early validate genres, new debug option
- Revert/fix last.fm fetcher methods to validate genres.

  - In past versions (<=2.2) _resolve_genres which included whitelist
    checks ran instantly after fetching last.fm tags which made sure the
    next stage is hit when nothing worthwhile was found (e.g fallback
    album -> artist).

  - Bring back this behavior but don't run a full _resolve_genres but a
    quick valid (whitelist) check only!

- Introduce an extended config/CLI option that allows to really log what
  each stage fetches (prior to validation/whitelist filtering).

  - Since this potentially is verbose especially with VA albums (a lot
    of artist tag fetches) for performance and debug log clutter reasons
    this is disabled by default.

- Clarify final last.fm tags debug log message to "valid last.fm genres"
2025-04-09 22:52:07 +02:00
J0J0 Todos
c57e5a1fb8 lastgenre: Tiny fix in early no-force return 2025-04-09 22:52:07 +02:00
J0J0 Todos
edd366e766 lastgenre: Fix album not falling back to artist stage
which was the usual behaviour in lastgenre ever since and it should be
kept that way. Also refactor "if track" to use a similar notation for
overall code readability.
2025-04-09 22:52:07 +02:00
J0J0 Todos
2b276e07f1 lastgenre: Log unconfigured fallback 2025-04-09 22:52:07 +02:00
J0J0 Todos
e20cf7f20b lastgenre: Rework combine, stringify, count reduction
- Rename method from _combine_genres() to _combine_resolve_and_log() to
  make clear that it not only combines new and old genres but also
  resolves them (which in this plugin's wording means "do the magic" of
  canonicalizationm, whitelist checking and reducing to a configured
  genre count).

- Clarify in _resolve docstring that a possible outcome might be all
  genres being removed.

- Add an additional log message telling which existing genres are taken
  into account BEFORE "the magic happens".

- Rename _to_delimited_genre_string() to _format_and_stringify()

- Move count reduction logic to _resolve_genres()

- Fix and rename a test
2025-04-09 22:52:07 +02:00
J0J0 Todos
3291aa03e7 lastgenre: Describe in docstring what _resolve_genres does 2025-04-09 22:52:07 +02:00
J0J0 Todos
15f4b2ac29 lastgenre: Place to_delim. func near _get_genre
and the other helpers.
2025-04-09 22:52:07 +02:00
J0J0 Todos
eba3dc15fd lastgenre: Final label only if required in _get_genre() 2025-04-09 22:52:07 +02:00
J0J0 Todos
94f78ae70f lastgenre: Prevent returning empty genre list
As reported in #5649 when new last.fm genres were found, they still might
get kicked out by the whitelist check in _resolve_genres(). This might
lead to _combine_genres() returning an empty list.

The desired outcome though is that since still nothing worthwhile was
found, the next stage should be entered - which in this case is,
returning with the configured fallback genre (or the default fallback
None).

The any() check makes sure this is the case and moving out the
string conversion from _combine_genres() makes this code slightly more
readable.
2025-04-09 22:52:07 +02:00
Allen
b7521f9a0b
fix: plugins/listenbrainz: Fix UnboundLocalError in cases where 'mbid' is not defined (#5651)
Fix ocurrence of `UnboundLocalError` in plugins/listenbrainz >
`get_tracks_from_listens()` when `mbid` is not available.
Removed a print statment.
Fix link to config.yaml.
Fix link to Listenbrainz "get the token" documentation.

Co-authored-by: Šarūnas Nejus <snejus@protonmail.com>
2025-03-12 07:08:53 +00:00
Pierre Ayoub
5c8f1c1ee5
Fix convert plugin attempting to process a non-media file (#5261)
## Description

My library is managed using Beets for organization and
[git-annex](https://git-annex.branchable.com/) as storage backend.
Therefore when using this system, while my library files always exists
on my filesystem, some files may be empty (without content). In this
case, when I'm running the `convert` plugin, I don't wants it to process
files which are empty (same apply for any Beets plugin). Hence, I added
a check that the file is readable as a `MediaFile` before doing any
process.

Before this fix, trying to encode an empty file would have lead to an
error while leaving `convert` doing its side-effects **and** `convert`
would also copy empty files to destination for files that doesn't need
to be re-encoded.

In my case, this is empty files, but the problem can be anything else
(depending on the storage backend) and/or corrupted files. Conclusion, I
think **checking that the file is readable is always recommended before
proceeding to heavy operation** like this.
2025-02-20 16:23:14 +00:00
Šarūnas Nejus
b713d72612
translations: use a more distinctive separator
I found that the translator would sometimes replace the pipe character
with another symbol (maybe it got confused thinking the character is
part of the text?).

Added spaces around the pipe to make it more clear that it's definitely
the separator.
2025-02-20 03:47:04 +00:00
Šarūnas Nejus
43032f7bc7
translations: make sure we do not re-translate 2025-02-20 03:47:04 +00:00
Šarūnas Nejus
7893766e4c
Improve flags structure and add tests 2025-02-20 03:47:04 +00:00
Šarūnas Nejus
c95156adcd
Refactor writing rest files 2025-02-20 03:47:04 +00:00
Šarūnas Nejus
d7201062a8
Resurrect translation functionality 2025-02-20 03:47:04 +00:00
J0J0 Todos
2286511ebe
Merge branch 'master' into smartplaylist-attr-url-encoding 2025-02-17 21:16:09 +01:00
Max Goltzsche
5d96509cfe
smartplaylist: change encoding of additional field
URL-encode additional item `fields` within generated EXTM3U playlists instead of JSON-encoding them.
This is because JSON-encoding additional fields/attributes made it difficult to parse the `EXTINF` line but using URL-encoding for these values makes parsing easy (because URL-encoded values cannot contain commas, quotation marks and spaces).

I introduced the generation of additional EXTM3U item fields earlier this year and I want to correct that now.

**Design/definition background:**
Unfortunately, I didn't find a clear definition of how additional playlist item attributes should be encoded - apparently there is none.
Given that item URIs within an M3U playlist can be URL-encoded already, defining the values of additional attributes to be URL-encoded is consistent design.
I didn't find examples of additional EXTM3U item attributes in the web where the attribute value contains a comma, space or quotation mark but examples that specified numeric IDs and URLs as attribute values.
Because the URL attribute examples I found didn't contain URL-encoded characters and because it is more readable and unproblematic for parsing, I've let the attribute URL encoding treat `:` and `/` as safe characters.

**Breaking change:**
While this is a breaking change in theory, in practice it is not since afaik all integrations of the smartplaylist plugin's additional EXTM3U item attribute generation feature (beets-webm3u) work with simple attribute values such as the item ID (numeric) whose formatting/encoding is not affected when changing from JSON to URL-encoding.
In other words the change is backward-compatible with the beets-webm3u plugin (which I'll adjust correspondingly after this beets PR was merged).
2025-02-01 01:14:27 +01:00
Šarūnas Nejus
916d40f86f
Remove outdated namespace package definition and update docs
See https://realpython.com/python-namespace-package.

This setup is backwards-compatible, so plugins using the old
pkgutil-based setup will continue working fine.

This setup has an advantage where external plugins will now be able to
import modules from 'beetsplug' package for typing purposes. Previously,
mypy could not resolve these modules due to presence of `__init__.py`.
2025-01-30 12:20:11 +00:00
Šarūnas Nejus
dab9a0d7c4
Bring back Tekstowo search
It was my mistake to remove search earlier - I found that in many cases
it works fine.
2025-01-27 10:56:54 +00:00
Šarūnas Nejus
7389f241f4
Do not search for Various Artists, split titles by ' / ' 2025-01-27 10:56:53 +00:00
Šarūnas Nejus
39c479fcab
Google: add support for dainuzodziai.lt 2025-01-27 10:56:53 +00:00
Šarūnas Nejus
734bcc28a8
Append source to the lyrics 2025-01-27 10:56:53 +00:00
Šarūnas Nejus
bdc564a573
Tidy up handling of backends 2025-01-27 10:56:53 +00:00
Šarūnas Nejus
04054cac5c
Remove dependency existence checks
I think we can make our life easier by removing these checks assuming
that users follow the instructions in the docs.
2025-01-27 10:56:53 +00:00
Šarūnas Nejus
b2402b1634
Google: make sure we do not return the captcha text
If we get caught by Cloudfare, it forwards our request somewhere else
and returns some validation text response. To make sure that this text
does not get assumed for lyrics, we can disable redirects for the Google
backend, check the response code and raise if there's a redirect
attempt. This source will then be skipped and the backend continues with
the next one.
2025-01-27 10:56:53 +00:00
Šarūnas Nejus
07d372c13d
Google: prioritise Songlyrics and AZlyrics sources 2025-01-27 10:56:53 +00:00
Šarūnas Nejus
70554640e5
Create Html class for cleaning up the html text
Additionally, improve HTML pre-processing:

* Ensure a new line between blocks of lyrics text from letras.mus.br.
* Parse a missing last block of lyrics text from lacocinelle.net.
* Parse a missing last block of lyrics text from paroles.net.
* Fix encoding issues with AZLyrics by setting response encoding to
  None, allowing `requests` to handle it.
2025-01-27 10:56:52 +00:00
Šarūnas Nejus
c5c4138d66
Google: Refactor and improve
* Type the response data that Google Custom Search API return.
* Exclude some 'letras.mus.br' pages that do not contain lyric.
* Exclude results from Musixmatch as we cannot access their pages.
* Improve parsing of the URL title:
  - Handle long URL titles that get truncated (end with ellipsis) for
    long searches
  - Remove domains starting with 'www'
  - Parse the title AND the artist. Previously this would only parse the
    title, and fetch lyrics even when the artist did not match.
* Remove now redundant credits cleanup and checks for valid lyrics.
2025-01-27 10:56:52 +00:00
Šarūnas Nejus
12c5eaae5e
Unite Genius, Tekstowo and Google backends under the same interface 2025-01-27 10:56:52 +00:00
Šarūnas Nejus
745c5eb9f0
Genius: refactor and simplify 2025-01-27 10:56:52 +00:00
Šarūnas Nejus
54fc67b30a
Remove extract_text_between 2025-01-27 08:50:50 +00:00
Šarūnas Nejus
55b7824948
Replace custom unescape implementation by html.unescape 2025-01-27 08:50:50 +00:00