Fixes an issue where each spotify query was converted to ascii before sending. Adds a
new config option to enable legacy behaviour.
A file called japanese_track_request.json was made to mimic the Spotify
API response since I don't have the credentials. Entries in that will
need to be modified with the actual entries.
Co-authored-by: Sebastian Mohr <sebastian@mohrenclan.de>
Co-authored-by: Sebastian Mohr <39738318+semohr@users.noreply.github.com>
Co-authored-by: J0J0 Todos <2733783+JOJ0@users.noreply.github.com>
Adds replace plugin. The plugin allows the user to replace the audio
file of a song, while keeping the tags and file name.
Some music servers keep track of favourite songs via paths and tags. Now
there won't be a need to 'refavourite'. Plus, this skips the
import/merge steps.
- Instead of checking for empty `artist` query, use `va_likely`
parameter to determine whether we should query for Various Artists or
not.
- `album` / `title` is always a truthy string - no need to handle empty
criteria case
- `tracks` list always has at least one track - no need to check for
`len(items)`
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`
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.
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
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.
- 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"
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.
- 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
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.
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>
## 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.
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.
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).
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`.