E.g., `colorize('text_success', 'hello world')`
To ensure compatibility with 3rd party plugins, a valid color ('red') can still be passed,
but it will be logged.
- Colors are mapped on to a dictionary using abstract names (e.g., text_success)
- Add `colors` option under `ui` to allow users to choose their own color scheme
- Move configuration option `color` from top-level to `ui`
- Show deprecation warning if top-level `color` configuration is used (but respect it)
Fix#1238
Code now relies on `format()` for items and albums displaying/logging.
`ui.print_()` calls `unicode()` or `str()` on the strings so for most
usages calling `ui.print_(obj)` replaces `ui.print_(obj, lib, None)`.
Where there is a special format `ui.print_(format(obj, fmt))` is fine,
but when `fmt` can be None then one has to call
`ui.print_(ui.format_(obj, fmt))` -- which is what `ui.print_obj` now
does.
Include import of __future__ features division, absolute_imports and
print_function everywhere. Don't add unicode_literals yet for it is
harder to convert.
Goal is smoothing the transition to python 3.
This was well-intentioned but ended up being more confusing than it was worth.
It's always confused me when one digit gets un-highlighted in one of these
displays. The straw that broke the camel's back was when I got a "#1 -> #16"
change where the numeral "1" was un-highlighted. To fix this right would be
way more trouble than it's worth; I'm glad to be rid of this detail.
Conflicts:
docs/changelog.rst
This makes sure we store the updated file `mtime`. By providing the
same interface on `Album` and `item` we can also reduce some code
duplication in the `modify` command.
Items might have the `album` field set without belonging to an album in
the beets database. We only count the albums that are represented in
the database.
Forces a write of tags to file even if the file's tags match the database.
This is useful to force plugins that respond to write (e.g., Scrub and Zero) to run on those tags.
This may also make the TODO comment in zero.py less important since creates a way to manually run the zero plugin on a file imported as-is.
Added a one-line summary of each album (in lib, and import target) so
you can easily tell if (for example) you are about to overwrite your
FLAC copy with a low-bitrate mp3 from somewhere else.
* Control flow and implementation of help command is now
similar to the other commands.
* Simplifies and flattens some code and removes unused method.
* Makes SubcommandOptionParser agnostic of Subcommand.parser.
This groups together all of the optparse setup calls separately from the
functions. This comes at the expense of showing the command-line options above
the code that interprets them.
Making me wish for a more declarative CLI setup style...
Many commands and plugins use `item.write()` to update tags. Since the success
of the call is not critical to the functionality of most consumers we want to
catch any exceptions, log an error and continue with our task. The new method
encapsulates this logic.
This fixes#675.
This puts the OrderedEnum generic class next to where it is actually used. It
also refers to the recipe it is taken from on docs.python.org. I also took the
opportunity to give this a capitalized name (since it's a proper type).
Following the convention of the other field sets and such. This helps avoid
any confusion with user-specified fields (although it's unlikely people will
want to name a flexible field "media_fields" :).
This makes the errors fully self-descriptive, which simplifies logging them as
errors (you just have to `log.error(exc)` to get a reasonable message). We
also now handle these at the top level in case someone forgets to add a
handler. But in this case, we also send the full traceback to the debug log.
This makes sure to print out the user's configuration path location even if no
user config file exists. (This makes "BEETSCONFIG=xxx beet config -p" behave
as expected, for instance.) It's a little hacky. Does the approach make sense
to you, @geigerzaehler?
We actually added more full-blown YAML dumping to the Confit library a while
back but it looks like it never made it into beets. It offers a few benefits
over the hand-rolled flattening that the `config` command was previously
using, including printing ordered dicts in the right order. But it also
appears to have broken logic when attempting to hide defaults. I'll fix this
right quick.
This new alternative to _showdiff takes care of formatting and is better at
highlighting differences for non-string fields. This takes care of the issue
where "True -> False" would have everything but the "e" highlighted.
The command prints a shell script that provides completion for the `beet`
command. To test it run `eval "$(beet completion)"` in your shell.
I also included some crude testing for this. The `test/test_completion.sh`
script runs tests in a shell and exit with a non-zero status code if the tests
fail. It assumes that the completion script is already loaded in the executing
shell.
As of now the completion only works for bash 4.1 and newer.
Perhaps we should use this for other commands also. One outstanding issue is
strange highlighting (e.g., "True -> False" helpfully shows that the "e" did
not change).
If a directory contains multiple albums we can select the ALBUMS action to group
the tracks by album artist and album name and import those seperately.
This way, _showdiff returns the information that _different used to provide
(since _showdiff needs to calculate it anyway). Using a `changed` set also
makes it easier to avoid unnecessary work.
- don't sanitize paths (this is already done separately)
- album.path (or album['path']) is now an alias for album.item_dir(), which
restores the formatting of $path in templates
A second base class, LibModel, maintains a reference to the Library and should
take care of database-related tasks like load and store. This is the beginning
of the end of the terrible incongruity between Item and Album objects (only
the latter had a library reference). More refactoring to come.
One large side effect: Album objects no longer automatically store
modifications. You have to call album.store(). Several places in the code
assume otherwise; they need cleaning up.
ResultIterator is now polymorphic (it takes a type parameter, which must be a
subclass of LibModel).
Namespaces were a worthy idea, but they added a lot of complexity to both the
library code itself and every client of the flexattrs interfaces. Getting rid
of them, and having one flat namespace of both traditional fields and
flexattrs, has one huge benefit: we can "promote" flexattrs to real attributes
(and vice versa) without code changes in every client.
This frees us to have a somewhat less efficient implementation of flexattrs
because we have a smooth upgrade path for making attributes more efficient via
promotion.
This is an effort to make the distance object feel slightly more dict-like.
The name changed and order of tuples is reversed: we now yield (key, value)
instead of (value, key), which I think is a little more intuitive.
Saves paranoid and interested users from having to either force all max
recommendations to none or constantly go back to candidate selection
from a recommendation to see if there is another slightly less similar
but more preferred (by the user) candidate.
The new Distance object knows how to perform various types of distance
calculations (expression, equality, number, priority, string).
It will keep track of each individual penalty that has been applied so
that we can utilise that information in the UI and when making decisions
about the recommendation level.
We now display the top 3 penalties (sorted by weight) on the release
list (and "..." if there are more than 3), and we display all penalties
on the album info line and track change line.
The implementation of the `max_rec` setting has been simplified by
removing duplicate validation and instead looking at the penalties that
have been applied to a distance. As a result, we can now configure a
maximum recommendation for any penalty that might be applied.
We have a few new checks when calculating album distance:
`match: preferred: countries` and `match: preferred: media` can each be
set to a list of countries and media in order of your preference. These
are empty by default. A value that matches the first item will have no
penalty, and a value that doesn't match any item will have an unweighted
penalty of 1.0.
If `match: preferred: original_year` is set to "yes", beets will apply
an unweighted penalty of 1.0 for each year of difference between the
release year and the original year.
We now configure individual weights for `mediums` (disctotal), `label`,
`catalognum`, `country` and `albumdisambig` instead of a single generic
`minor` weight. This gives more control, but more importantly separates
and names the applied penalties so that the UI can convey exactly which
fields have contributed to the overall distance penalty.
Likewise, `missing tracks` and `unmatched tracks` are penalised and
displayed in the UI separately, instead of a combined `partial` penalty.
Display non-MusicBrainz source in the disambiguation string, and
"source" in the list of penalties if a release is penalised for being
a non-MusicBrainz.
An earlier change (due to @pedros) added the ability for plugins to define
template fields that work with Albums as well as Items. This enables some
cool new use cases but required that every template field definition check the
type of its arguments. Instead, this iteration on the idea distinguishes
between fields meant for Items and those meant for Albums.
In addition to simplifying the implementation of these functions, this also
enables the creation of album fields with identical names to item fields.
(For example, a user contacted me recently about adding a $bitrate field for
albums, which would be the average bitrate of the items. They can do this now
using a plugin.)
I also changed the docs to stop using the decorator approach to registering
template fields. We're moving toward removing those.
That's 371cc72f2d09 in hg. This makes the patch slightly more general by
reusing our type conversion infrastructure. It also uses "bytes" as a synonym
for "str" that I find a little bit clearer.
Basically, it uses the helpers that handle conversion bytestring <-> unicode with the item's path, but weren't being used with artpath. For now, it just fixes the behavior for `modify`
Display similar title lines for missing and unmatched tracks, to
separate them from matched tracks. When media numbers or titles are
shown, it looks like missing tracks are grouped under the last media.
With missing and unmatched tracks grouped under a heading now, we don't
need to display "missing track" or "unmatched track" on every line,
making it easier to read the track titles.
Change the prefix from " * " (same as matched tracks) to " ! " for
missing and " ? " for unmatched tracks.
Consistently format track index for both missing and unmatched tracks.
Previously we were omitting the # for missing tracks (compared to
matched tracks), and we were just showing the index (no # or media
number) for unmatched tracks.
Also display track length, if available.
This necessitated a slight tweaking of the line printing code so that
the " * " prefix is not hard coded for all lines. The prefix is now
included in the `lhs` part of lines that actually need it, and this
allows us to alter or omit the prefix for individual lines.
This is a little more accurate than the previous method (check if track
is in index or medium_index) by looking at the `per_disc_numbering`
setting and comparing the index or medium index accordingly.
It's also a little more accurate in the display output by diffing the
combined `disc-track` to `medium-medium_index` (if using per disc
numbering) and intelligently colorizing the either the whole track
number or just the suffix.
I thought having "MusicBrainz" colored green was a little distracting since
it's the common case (and universal without the discogs plugin), so this just
makes it neutral-color in that case.
This is a refactor of the plugin developed by `imenem`.
- Pass `artist`, `album` and `va_likely` to `candidates()` so that
plugins don't have to work this out from `items` all over again.
- Pass `artist` and `title` to `item_candidates()`.
- Silence spurious `urllib3` info log lines.
- Use a proper "beets" user agent with `discogs_client`.
- Remove `abstract_search` plugin. It seems unnecessary. How many
music databases are there? How many will beets support? How much
common code might there be between them? We can add some abstraction
if or when more databases are supported.
- Derive more AlbumInfo and TrackInfo properties from discogs Release
objects, especially album ID so that beets doesn't just use the first
release and think all subsequent releases are duplicates.
- Add basic documentation, doc strings and code comments.
- Sanitise search query. Remove non-word characters and medium info that
might filter out good search results.
- Use artist `join` strings from discogs Release object when an album
or track has multiple artists.
- Don't rely on discogs track position, which is unreliable. But tracks
are in order, so we can recalculate medium and medium_index as long as
we can extract a consistent medium across tracks from the position.
- Add "various" as a known signal to indicate various artists.
- Prevent `chroma` plugin from returning a a huge track distance for any
track that is missing an ID (e.g. all discog tracks).
- `TrackInfo.index` should be the release index (calculated by beets),
not the medium index (derived from discogs track position).
- Add `AlbumInfo.data_source`. It's "Unknown" by default which is shown
in red when displaying a suggested or selected match. The built in
auto tagger sets it to "MusicBrainz" which is shown in green. Anything
else (e.g. "Discogs") is shown in yellow.
- Remove double spaces from album titles (bad data from Discogs).
I've removed the -p option. The command now always shows plugin-provided
template fields if any are available. We also avoid printing out blank lines
for plugins that don't provide fields.
Here's another little experiment: to make flexattrs a little easier to use for
end users, you can now get and set them by using 'namespace-key' as the
argument to __getattr__ or __setattr__.
For example, try:
$ beet mod foo-bar=baz
$ beet ls -f '${foo-bar}'
baz
baz
baz
...
- Remove "part", "volume", "vol." multi-disc markers. These are often
part of album titles, and not necessarily indicative of a multi-disc
album. Only look for "CD X" and "disc X" (case insensitive), ignoring
white space and other non-word characters.
- Don't only expect each disc to be in a subdirectory of a common parent
directory, with all siblings belonging to the same release. Also match
any consecutive siblings (even when the parent contains other albums)
that are named with the same prefix and multi-disc marker.
- The `albums_in_dir(path)` function now always yields a list of paths
along with each list of items. `ItemTask.path` is now always a list of
paths.
- The `displayable_path(path)` function now accepts a list of paths, and
will join them with "; " by default. This can be changed with the
`separator` argument.
- The `sorted_walk()` function now does a case insensitive sort on
directories, but still returns case sensitive results. This allows
better multi-disc album detection.
- The `art_for_album()` function now takes a list of paths as its second
argument, instead of a single path.
This refactoring helps alleviate some of my own copypasta where we handle quiet
mode and its fallback. It has the added benefit of making none_rec_action work
for singletons as well as albums.
- Partial matches are always downgraded to a "medium" match.
- The config option, now called "default_action", lets you choose what to do
with "medium" matches.
- Expanded the "low" recommendation level to include cases with just one
match.
Add `import: none_rec_action` setting with a default of "ask" (current
behaviour). If set to "asis" or "skip", matches with no recommendation will be
imported as-is or skipped automatically.
Remove default option and require selection on confirmation prompts for:
- Partial matches, if `import: confirm_partial` setting is "yes".
- Matches that are below the medium recommendation threshold, but above the
gap threshold.
- Matches that have no recommendation.
- Matches other than the best and auto-suggested match.
We need plugins to set their config values at run time instead of module import
time. That is, defaults should be put in the __init__ method. This is easy
enough, but to make it even more convenient, I added a BeetsPlugin.config
field, which is a Confit view into a subsection of the configuration named
after the plugin.
With the new centralized print_obj function, we can greatly simplify the code
for the list command. This necessitated a couple of additional tweaks:
- For performance reasons, print_obj can now take a compiled template. (There's
still an issue with using the default/configured template, but we can cross
that bridge later).
- When listing albums, $path now expands to the album's item dir. So the format
string '$path' now exactly corresponds to passing the -p switch.
As an added bonus, we can now also reduce copypasta in the random plugin (which
behaves almost exactly the same as list).
The list_format_album and list_format_item options now *actually* affect the
display in commands other than "beet list". This replaces the -f/--format flags
-- if any users want to control this on the command line, we can reconsider this
decision.
Note that this involved passing around a "config" object, which we previously
haven't done. This seems a little bit messy, but configuration is about to
change entirely to be more like this style -- so this isn't a huge liability.
This version of the (renamed) _print_obj function uses introspection to
determine whether we're printing an Album or an Item. It's like function
overloading for Python! 😁
ImportSession will replace ImportConfig. It will have a *sane* number of fields
that are specific to the particular invocation of the importer -- e.g., lib and
paths. It also has a little bit of logic attached. Finally, it provides a method
for hooking the callbacks into the UI that is more elegant than assigning
callback functions -- OO inheritance to the rescue!
This adds a snapshot of the current Confit source (not a crime because Confit is
currently unreleased). It also changes around the bootstrapping mechanisms
enough to let "beet ls" run with the new Confit-based configuration. There's
much more to do.
This allows matches to indicate both missing and unmatched tracks in their
candidates and solves some of the spaghetti tuples that were passed around
during autotagging.
This is the first of several commits that will modernize the beets codebase for
Python 2.6 conventions. (Compatibility with Python 2.5 is hereby abandoned.)
In an attempt to finally address the longstanding SQLite locking issues, I'm
introducing a way to explicitly, lexically scope transactions. The Transaction
class is a context manager that always fully fetches after SELECTs and
automatically commits on exit. No direct access to the library is allowed, so
all changes will eventually be committed and all queries will be completed. This
will also provide a debugging mechanism to show where concurrent transactions
are beginning and ending.
To support composition (transaction reentrancy), an internal, per-Library stack
of transactions is maintained. Commits only happen when the outermost
transaction exits. This means that, while it's possible to introduce atomicity
bugs by invoking Library methods outside of a transaction, you can conveniently
call them *without* a currently-active transaction to get a single atomic
action.
Note that this "transaction stack" concepts assumes a single Library object per
thread. Because we need to duplicate Library objects for concurrent access due
to sqlite3 limitation already, this is fine for now. Later, the interface should
provide one transaction stack per thread for shared Library objects.
- Copying and moving are mutually exclusive. Moving overrides copying so the
user only has to add one line ("import_move: true") to disable copying and
enable moving in its place.
- Deleting is only possible when copying.
- Deprecating the "delete" option (moving is almost always better).
- Removed command-line switch for moving. It's somewhat "unsafe", so this
removes some potential for accidental irreversible changes.
- Changelog & thanks.
- Update docs to refer to import_move instead of import_delete as the
correct solution for ending up with only one copy of the file.