mirror of
https://github.com/beetbox/beets.git
synced 2026-02-08 08:25:23 +01:00
Merge f66ab37898 into cdfb813910
This commit is contained in:
commit
3acc448391
1 changed files with 121 additions and 2 deletions
|
|
@ -17,12 +17,13 @@
|
|||
import codecs
|
||||
import os
|
||||
import shlex
|
||||
import re
|
||||
import subprocess
|
||||
from tempfile import NamedTemporaryFile
|
||||
|
||||
import yaml
|
||||
|
||||
from beets import plugins, ui, util
|
||||
from beets import plugins, ui, util, config
|
||||
from beets.dbcore import types
|
||||
from beets.importer import Action
|
||||
from beets.ui.commands.utils import do_query
|
||||
|
|
@ -137,6 +138,112 @@ def apply_(obj, data):
|
|||
obj.set_parse(key, str(value))
|
||||
|
||||
|
||||
def dump_and_prune_fields(data):
|
||||
"""For a set of "set fields", plus `id` and `path`, make sure that
|
||||
those fields are presented in a way that makes sense
|
||||
(this is for import sessions, where fields affected by the `--set` flag are
|
||||
effectively read-only, and both `id` and `path` can only contain placeholders
|
||||
for now)
|
||||
This function takes the data for the editable text, and returns a modified
|
||||
version of said text.
|
||||
"""
|
||||
|
||||
# this does the following:
|
||||
# take set fields into account, by setting their values, commenting them out,
|
||||
# and annotating them
|
||||
# also pruning the "id" and "path" fields, which can only contain placeholders
|
||||
# at this time
|
||||
|
||||
data = [obj.copy() for obj in data]
|
||||
|
||||
# prepare regex matching
|
||||
# the following regex detects yaml lines setting values for 'id' or 'path'
|
||||
placeholder_field_detector = re.compile(r"\s*(-\s+)?(id|path):(\s+.*)?")
|
||||
set_fields = config["import"]["set_fields"]
|
||||
if set_fields:
|
||||
# a similar regex, but for setting values for any field
|
||||
# in the set_fields list
|
||||
ro_field_detector = re.compile(
|
||||
r"\s*(-\s+)?({0:s}):(\s+.*)?".format(
|
||||
"|".join(re.escape(key) for key in set_fields.keys())
|
||||
)
|
||||
)
|
||||
|
||||
# deal with the values of the set fields
|
||||
for obj in data:
|
||||
obj.update({k: v.get() for k, v in set_fields.items()})
|
||||
else:
|
||||
# will in theory not be read
|
||||
# this line is just to make static analysis happy
|
||||
ro_field_detector = None
|
||||
|
||||
# convert to text, then comment/annotate/prune lines (only on import)
|
||||
data_lines = dump(data).split("\n")
|
||||
|
||||
line_i = 0
|
||||
while line_i < len(data_lines):
|
||||
line = data_lines[line_i]
|
||||
if set_fields and ro_field_detector.fullmatch(line):
|
||||
line = "#" + line + " # [read-only field]"
|
||||
data_lines[line_i] = line
|
||||
line_i += 1
|
||||
elif placeholder_field_detector.fullmatch(line):
|
||||
del data_lines[line_i]
|
||||
else:
|
||||
line_i += 1
|
||||
|
||||
return "\n".join(data_lines)
|
||||
|
||||
|
||||
def fix_warn_ro_fields(old_data, new_data):
|
||||
"""For a set of new data obtained from edited text,
|
||||
take care of the read-only fields that were commented out or
|
||||
pruned out of the text prior to editing:
|
||||
- first re-add the fields that are now missing
|
||||
- then warned if those fields were changed when the text was edited
|
||||
"""
|
||||
|
||||
set_fields = config["import"]["set_fields"].keys()
|
||||
set_fields = list(set_fields) + ["id", "path"]
|
||||
for field in set_fields:
|
||||
changed = False
|
||||
for old_obj, new_obj in zip(old_data, new_data):
|
||||
if field in old_obj and field not in new_obj:
|
||||
# only copy 'missing fields' if they are missing
|
||||
# due to us pruning/commenting them out in the editable
|
||||
# text
|
||||
new_obj[field] = old_obj[field]
|
||||
if old_obj.get(field, "") != new_obj.get(field, ""):
|
||||
changed = True
|
||||
if field in old_obj:
|
||||
new_obj[field] = old_obj[field]
|
||||
else:
|
||||
del new_obj[field]
|
||||
if changed:
|
||||
if field in ("id", "path"):
|
||||
ui.print_(
|
||||
ui.colorize(
|
||||
"text_warning",
|
||||
f'NOTICE: the field "{field:s}" is read-only '
|
||||
"because it can only contain placeholder values "
|
||||
"at this time.\nThe values you have manually set "
|
||||
"will have to be discarded.",
|
||||
)
|
||||
)
|
||||
else:
|
||||
ui.print_(
|
||||
ui.colorize(
|
||||
"text_warning",
|
||||
f'NOTICE: the field "{field:s}" is read-only '
|
||||
"because it was set through the `--set` "
|
||||
"command line arguemnt or an equivalent in beets's "
|
||||
"configuration.\nThe manual changes you have made "
|
||||
"to it will have to be discarded.",
|
||||
)
|
||||
)
|
||||
return new_data
|
||||
|
||||
|
||||
class EditPlugin(plugins.BeetsPlugin):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
|
|
@ -154,6 +261,7 @@ class EditPlugin(plugins.BeetsPlugin):
|
|||
self.register_listener(
|
||||
"before_choose_candidate", self.before_choose_candidate_listener
|
||||
)
|
||||
self.session_is_import = False
|
||||
|
||||
def commands(self):
|
||||
edit_command = ui.Subcommand("edit", help="interactively edit metadata")
|
||||
|
|
@ -231,11 +339,15 @@ class EditPlugin(plugins.BeetsPlugin):
|
|||
# Get the content to edit as raw data structures.
|
||||
old_data = [flatten(o, fields) for o in objs]
|
||||
|
||||
if self.session_is_import:
|
||||
old_str = dump_and_prune_fields(old_data)
|
||||
else:
|
||||
old_str = dump(old_data)
|
||||
|
||||
# Set up a temporary file with the initial data for editing.
|
||||
new = NamedTemporaryFile(
|
||||
mode="w", suffix=".yaml", delete=False, encoding="utf-8"
|
||||
)
|
||||
old_str = dump(old_data)
|
||||
new.write(old_str)
|
||||
new.close()
|
||||
|
||||
|
|
@ -263,6 +375,10 @@ class EditPlugin(plugins.BeetsPlugin):
|
|||
else:
|
||||
return False
|
||||
|
||||
# see if any changes to the data need to be discarded:
|
||||
if self.session_is_import:
|
||||
new_data = fix_warn_ro_fields(old_data, new_data)
|
||||
|
||||
# Show the changes.
|
||||
# If the objects are not on the DB yet, we need a copy of their
|
||||
# original state for show_model_changes.
|
||||
|
|
@ -360,6 +476,8 @@ class EditPlugin(plugins.BeetsPlugin):
|
|||
if not obj._db or obj.id is None:
|
||||
obj.id = -i
|
||||
|
||||
self.session_is_import = True
|
||||
|
||||
# Present the YAML to the user and let them change it.
|
||||
fields = self._get_fields(album=False, extra=[])
|
||||
success = self.edit_objects(task.items, fields)
|
||||
|
|
@ -388,4 +506,5 @@ class EditPlugin(plugins.BeetsPlugin):
|
|||
task.match = task.candidates[sel - 1]
|
||||
task.apply_metadata()
|
||||
|
||||
self.session_is_import = True
|
||||
return self.importer_edit(session, task)
|
||||
|
|
|
|||
Loading…
Reference in a new issue