Added the force option to convert and update docs/tests

This commit is contained in:
Guy Bloom 2025-11-20 20:29:07 -05:00
parent 2740c69287
commit 9353cfffcf
4 changed files with 41 additions and 31 deletions

View file

@ -95,18 +95,19 @@ def in_no_convert(item: Item) -> bool:
return False
def should_transcode(item, fmt, override_never_lossy: bool = False):
def should_transcode(item, fmt, force: bool = False):
"""Determine whether the item should be transcoded as part of
conversion (i.e., its bitrate is high or it has the wrong format).
If ``override_never_lossy`` is True, the ``never_convert_lossy_files``
check is ignored (used when the user explicitly requests a format
on the CLI).
If ``force`` is True, safety checks like ``no_convert`` and
``never_convert_lossy_files`` are ignored and the item is always
transcoded.
"""
if force:
return True
if in_no_convert(item) or (
config["convert"]["never_convert_lossy_files"].get(bool)
and item.format.lower() not in LOSSLESS_FORMATS
and not override_never_lossy
):
return False
maxbr = config["convert"]["max_bitrate"].get(Optional(int))
@ -241,6 +242,16 @@ class ConvertPlugin(BeetsPlugin):
drive, relative paths pointing to media files
will be used.""",
)
cmd.parser.add_option(
"-F",
"--force",
action="store_true",
dest="force",
help=(
"force transcoding, overriding safety checks such as "
"never_convert_lossy_files and no_convert"
),
)
cmd.parser.add_album_option()
cmd.func = self.convert_func
return [cmd]
@ -264,7 +275,7 @@ class ConvertPlugin(BeetsPlugin):
hardlink,
link,
playlist,
fmt_from_cli,
force,
) = self._get_opts_and_config(empty_opts)
items = task.imported_items()
@ -278,7 +289,7 @@ class ConvertPlugin(BeetsPlugin):
hardlink,
threads,
items,
fmt_from_cli,
force,
)
# Utilities converted from functions to methods on logging overhaul
@ -354,7 +365,7 @@ class ConvertPlugin(BeetsPlugin):
pretend=False,
link=False,
hardlink=False,
fmt_from_cli=False,
force=False,
):
"""A pipeline thread that converts `Item` objects from a
library.
@ -380,15 +391,11 @@ class ConvertPlugin(BeetsPlugin):
if keep_new:
original = dest
converted = item.path
if should_transcode(
item, fmt, override_never_lossy=fmt_from_cli
):
if should_transcode(item, fmt, force):
converted = replace_ext(converted, ext)
else:
original = item.path
if should_transcode(
item, fmt, override_never_lossy=fmt_from_cli
):
if should_transcode(item, fmt, force):
dest = replace_ext(dest, ext)
converted = dest
@ -418,7 +425,7 @@ class ConvertPlugin(BeetsPlugin):
)
util.move(item.path, original)
if should_transcode(item, fmt, override_never_lossy=fmt_from_cli):
if should_transcode(item, fmt, force):
linked = False
try:
self.encode(command, original, converted, pretend)
@ -589,7 +596,7 @@ class ConvertPlugin(BeetsPlugin):
hardlink,
link,
playlist,
fmt_from_cli,
force,
) = self._get_opts_and_config(opts)
if opts.album:
@ -626,7 +633,7 @@ class ConvertPlugin(BeetsPlugin):
hardlink,
threads,
items,
fmt_from_cli,
force,
)
if playlist:
@ -729,7 +736,6 @@ class ConvertPlugin(BeetsPlugin):
path_formats = ui.get_path_formats(self.config["paths"] or None)
fmt_from_cli = opts.format is not None
fmt = opts.format or self.config["format"].as_str().lower()
playlist = opts.playlist or self.config["playlist"].get()
@ -750,7 +756,7 @@ class ConvertPlugin(BeetsPlugin):
else:
hardlink = self.config["hardlink"].get(bool)
link = self.config["link"].get(bool)
force = getattr(opts, "force", False)
return (
dest,
threads,
@ -760,7 +766,7 @@ class ConvertPlugin(BeetsPlugin):
hardlink,
link,
playlist,
fmt_from_cli,
force,
)
def _parallel_convert(
@ -774,7 +780,7 @@ class ConvertPlugin(BeetsPlugin):
hardlink,
threads,
items,
fmt_from_cli,
force,
):
"""Run the convert_item function for every items on as many thread as
defined in threads
@ -788,7 +794,7 @@ class ConvertPlugin(BeetsPlugin):
pretend,
link,
hardlink,
fmt_from_cli,
force,
)
for _ in range(threads)
]

View file

@ -48,8 +48,9 @@ Bug fixes:
query, the query eg. `GET /item/values/albumartist` would return the literal
"albumartist" instead of a list of unique album artists. -
:doc:`/plugins/convert`: beet convert ignored an explicit --format option when
never_convert_lossy_files was enabled. Lossy files selected by a query and
given a target format on the CLI are now transcoded instead of being copied
never_convert_lossy_files was enabled. Now, --force wil override this config
setting. Lossy files selected by a query and given a target format on the CLI
are now transcoded when given the --force flag instead of being copied
unchanged. :bug:`5625`
For plugin developers:

View file

@ -51,6 +51,11 @@ instead, passing ``-H`` (``--hardlink``) creates hard links. Note that album art
embedding is disabled for files that are linked. Refer to the ``link`` and
``hardlink`` options below.
The ``-F`` (or ``--force``) option forces transcoding even when safety options
such as ``no_convert`` or ``never_convert_lossy_files`` would normally cause a
file to be copied or skipped instead. This can be combined with ``--format`` to
explicitly transcode lossy inputs to a chosen target format.
The ``-m`` (or ``--playlist``) option enables the plugin to create an m3u8
playlist file in the destination folder given by the ``-d`` (``--dest``) option
or the ``dest`` configuration. The path to the playlist file can either be
@ -114,10 +119,9 @@ The available options are:
even further. If set to ``yes``, lossy files are always copied. Default:
``no``. When ``never_convert_lossy_files`` is enabled, lossy source files (for
example MP3 or Ogg Vorbis) are normally not transcoded and are instead copied
or linked as-is. If you explicitly pass a target format on the command line
with ``--format``, this override is respected: lossy files matching the query
will be transcoded to the requested format even when
``never_convert_lossy_files`` is enabled.
or linked as-is. To explicitly transcode lossy files in spite of this, use the
``--force`` option with the ``convert`` command (optionally together with
``--format`` to choose a target format)
- **paths**: The directory structure and naming scheme for the converted files.
Uses the same format as the top-level ``paths`` section (see
:ref:`path-format-config`). Default: Reuse your top-level path format

View file

@ -301,16 +301,15 @@ class NeverConvertLossyFilesTest(ConvertTestCase, ConvertCommand):
converted = self.convert_dest / "converted.ogg"
assert not self.file_endswith(converted, "mp3")
def test_cli_format_overrides_never_convert_lossy_files(self):
def test_force_overrides_never_convert_lossy_files(self):
self.config["convert"]["formats"]["opus"] = {
"command": self.tagged_copy_cmd("opus"),
"extension": "ops",
}
[item] = self.add_item_fixtures(ext="ogg")
with control_stdin("y"):
self.run_convert_path(item, "--format", "opus")
self.run_convert_path(item, "--format", "opus", "--force")
converted = self.convert_dest / "converted.ops"
assert self.file_endswith(converted, "opus")