This commit is contained in:
Arne Christian Beer 2026-03-21 15:27:12 +00:00 committed by GitHub
commit 893f814b64
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 88 additions and 6 deletions

3
.gitignore vendored
View file

@ -80,6 +80,9 @@ venv/
.venv/
ENV/
# uv
uv.lock
# Spyder project settings
.spyderproject

View file

@ -37,7 +37,7 @@ from beets import config, library, logging, plugins, util
from beets.dbcore import db
from beets.dbcore import query as db_query
from beets.util import as_string
from beets.util.color import colorize
from beets.util.color import colorize, terminal_link
from beets.util.deprecation import deprecate_for_maintainers
from beets.util.diff import get_model_changes
from beets.util.functemplate import template

View file

@ -156,6 +156,20 @@ def colorize(color_name: ColorName, text: str) -> str:
return text
def terminal_link(url: str, text: str | None = None) -> str:
"""Create a clickable terminal hyperlink using a OSC 8 escape sequence.
`text` falls back to `url` if `None`.
"""
display = text if text is not None else url
return (
f"{COLOR_ESCAPE}]8;;{url}{COLOR_ESCAPE}\\"
f"{display}"
f"{COLOR_ESCAPE}]8;;{COLOR_ESCAPE}\\"
)
def uncolorize(colored_text: str) -> str:
"""Remove colors from a string."""
# Define a regular expression to match ANSI codes.

View file

@ -23,6 +23,20 @@ from beets.library import Item
from beets.plugins import BeetsPlugin
from beets.util import displayable_path, normpath, syspath
# Mapping from beets field names to URL templates.
FIELD_LINK_TEMPLATES: dict[str, str] = {
"mb_trackid": "https://musicbrainz.org/recording/{value}",
"mb_albumid": "https://musicbrainz.org/release/{value}",
"mb_artistid": "https://musicbrainz.org/artist/{value}",
"mb_albumartistid": "https://musicbrainz.org/artist/{value}",
"mb_releasetrackid": "https://musicbrainz.org/track/{value}",
"mb_releasegroupid": "https://musicbrainz.org/release-group/{value}",
"mb_workid": "https://musicbrainz.org/work/{value}",
"discogs_albumid": "https://www.discogs.com/release/{value}",
"discogs_artistid": "https://www.discogs.com/artist/{value}",
"discogs_labelid": "https://www.discogs.com/label/{value}",
}
def tag_data(lib, args, album=False):
query = []
@ -92,13 +106,16 @@ def update_summary(summary, tags):
return summary
def print_data(data, item=None, fmt=None):
def print_data(data, item=None, fmt=None, links=False):
"""Print, with optional formatting, the fields of a single element.
If no format string `fmt` is passed, the entries on `data` are printed one
in each line, with the format 'field: value'. If `fmt` is not `None`, the
`item` is printed according to `fmt`, using the `Item.__format__`
machinery.
When `links == True`, external ID fields will be rendered as clickable
terminal hyperlinks using OSC 8 escape sequences.
"""
if fmt:
# use fmt specified by the user
@ -110,7 +127,11 @@ def print_data(data, item=None, fmt=None):
for key, value in data.items():
if isinstance(value, list):
formatted[key] = "; ".join(value)
if value is not None:
elif value is not None:
if links and key in FIELD_LINK_TEMPLATES:
value_str = str(value)
url = FIELD_LINK_TEMPLATES[key].format(value=value_str)
value = ui.terminal_link(url, value_str)
formatted[key] = value
if len(formatted) == 0:
@ -181,6 +202,11 @@ class InfoPlugin(BeetsPlugin):
action="store_true",
help="show only the keys",
)
cmd.parser.add_option(
"--links",
action="store_true",
help="make ID fields (MusicBrainz, Discogs) clickable terminal hyperlinks",
)
cmd.parser.add_format_option(target="item")
return [cmd]
@ -231,8 +257,8 @@ class InfoPlugin(BeetsPlugin):
print_data_keys(data, item)
else:
fmt = [opts.format][0] if opts.format else None
print_data(data, item, fmt)
print_data(data, item, fmt, links=opts.links)
first = False
if opts.summarize:
print_data(summary)
print_data(summary, links=opts.links)

View file

@ -26,6 +26,8 @@ New features
:bug:`2661`
- :doc:`plugins/play`: Added ``-R``/``--randomize`` flag to shuffle the playlist
order before passing it to the player.
- :doc:`plugins/info`: Added ``--links`` command-line flag, which results in
external IDs (MusicBrainz, Discogs) being clickable links in the terminal.
Bug fixes
~~~~~~~~~
@ -103,7 +105,6 @@ Bug fixes
New features
~~~~~~~~~~~~
- :doc:`plugins/lastgenre`: Added ``cleanup_existing`` configuration flag to
allow whitelist canonicalization of existing genres.
- Add native support for multiple genres per album/track. The ``genres`` field

View file

@ -46,6 +46,8 @@ Additional command-line options include:
item. This uses the same template syntax as beets :doc:`path formats
</reference/pathformat>`.
- ``--keys-only`` or ``-k``: Show the name of the tags without the values.
- ``--links``: Make external IDs (Discogs/MusicBrainz) clickable links in the
terminal.
.. _id3v2: https://sourceforge.net/projects/id3v2/

View file

@ -15,6 +15,7 @@
from mediafile import MediaFile
from beets import ui
from beets.test.helper import IOMixin, PluginTestCase
from beets.util import displayable_path
@ -116,3 +117,38 @@ class InfoTest(IOMixin, PluginTestCase):
"$track. $title - $artist ($length)",
)
assert "02. tïtle 0 - the artist (0:01)\n" == out
def test_links(self):
(item,) = self.add_item_fixtures()
item.mb_albumid = "album-uuid"
item.mb_trackid = "track-uuid"
item.discogs_albumid = 99999
item.album = "MyAlbum"
item.store()
out = self.run_with_output(
"info",
"--library",
"--include-keys",
"mb_albumid,mb_trackid,discogs_albumid,album",
"--links",
)
# ID fields are wrapped in terminal hyperlinks
mb_album_link = ui.terminal_link(
"https://musicbrainz.org/release/album-uuid", "album-uuid"
)
assert f"mb_albumid: {mb_album_link}" in out
mb_track_link = ui.terminal_link(
"https://musicbrainz.org/recording/track-uuid", "track-uuid"
)
assert f"mb_trackid: {mb_track_link}" in out
discogs_link = ui.terminal_link(
"https://www.discogs.com/release/99999", "99999"
)
assert f"discogs_albumid: {discogs_link}" in out
# Non-ID fields remain plain text
assert "album: MyAlbum" in out