diff --git a/comictaggerlib/autotagmatchwindow.py b/comictaggerlib/autotagmatchwindow.py
index fe62cbe..0b0acb7 100644
--- a/comictaggerlib/autotagmatchwindow.py
+++ b/comictaggerlib/autotagmatchwindow.py
@@ -39,7 +39,7 @@ class AutoTagMatchWindow(QtWidgets.QDialog):
self,
parent: QtWidgets.QWidget,
match_set_list: list[Result],
- styles: list[str],
+ load_styles: list[str],
fetch_func: Callable[[IssueResult], GenericMetadata],
config: ct_ns,
talker: ComicTalker,
@@ -81,7 +81,7 @@ class AutoTagMatchWindow(QtWidgets.QDialog):
self.buttonBox.button(QtWidgets.QDialogButtonBox.StandardButton.Ok).setText("Accept and Write Tags")
self.match_set_list = match_set_list
- self._styles = styles
+ self.load_data_styles = load_styles
self.fetch_func = fetch_func
self.current_match_set_idx = 0
@@ -229,8 +229,17 @@ class AutoTagMatchWindow(QtWidgets.QDialog):
def save_match(self) -> None:
match = self.current_match()
ca = ComicArchive(self.current_match_set.original_path)
+ md, error = self.parent().overlay_ca_read_style(self.load_data_styles, ca)
+ if error is not None:
+ logger.error("Failed to load metadata for %s: %s", ca.path, error)
+ QtWidgets.QApplication.restoreOverrideCursor()
+ QtWidgets.QMessageBox.critical(
+ self,
+ "Read Failed!",
+ f"One or more of the read styles failed to load for {ca.path}, check log for details",
+ )
+ return
- md = ca.read_metadata(self.config.internal__load_data_style)
if md.is_empty:
md = ca.metadata_from_filename(
self.config.Filename_Parsing__filename_parser,
@@ -254,7 +263,7 @@ class AutoTagMatchWindow(QtWidgets.QDialog):
QtWidgets.QApplication.setOverrideCursor(QtGui.QCursor(QtCore.Qt.CursorShape.WaitCursor))
md.overlay(ct_md)
- for style in self._styles:
+ for style in self.load_data_styles:
success = ca.write_metadata(md, style)
QtWidgets.QApplication.restoreOverrideCursor()
if not success:
@@ -265,4 +274,4 @@ class AutoTagMatchWindow(QtWidgets.QDialog):
)
break
- ca.load_cache(list(metadata_styles))
+ ca.reset_cache()
diff --git a/comictaggerlib/cli.py b/comictaggerlib/cli.py
index fe385a6..7b7dee4 100644
--- a/comictaggerlib/cli.py
+++ b/comictaggerlib/cli.py
@@ -134,7 +134,7 @@ class CLI:
def actual_metadata_save(self, ca: ComicArchive, md: GenericMetadata) -> bool:
if not self.config.Runtime_Options__dryrun:
- for style in self.config.Runtime_Options__type:
+ for style in self.config.Runtime_Options__type_modify:
# write out the new data
if not ca.write_metadata(md, style):
logger.error("The tag save seemed to fail for style: %s!", md_styles[style].name())
@@ -249,12 +249,11 @@ class CLI:
md.overlay(f_md)
- for style in self.config.Runtime_Options__type:
+ for style in self.config.Runtime_Options__type_read:
if ca.has_metadata(style):
try:
t_md = ca.read_metadata(style)
md.overlay(t_md)
- break
except Exception as e:
logger.error("Failed to load metadata for %s: %s", ca.path, e)
@@ -264,7 +263,7 @@ class CLI:
return md
def print(self, ca: ComicArchive) -> Result:
- if not self.config.Runtime_Options__type:
+ if not self.config.Runtime_Options__type_read:
page_count = ca.get_number_of_pages()
brief = ""
@@ -290,7 +289,7 @@ class CLI:
md = None
for style, style_obj in md_styles.items():
- if not self.config.Runtime_Options__type or style in self.config.Runtime_Options__type:
+ if not self.config.Runtime_Options__type_read or style in self.config.Runtime_Options__type_read:
if ca.has_metadata(style):
self.output(f"--------- {style_obj.name()} tags ---------")
try:
@@ -322,7 +321,7 @@ class CLI:
def delete(self, ca: ComicArchive) -> Result:
res = Result(Action.delete, Status.success, ca.path)
- for style in self.config.Runtime_Options__type:
+ for style in self.config.Runtime_Options__type_modify:
status = self.delete_style(ca, style)
if status == Status.success:
res.tags_deleted.append(style)
@@ -367,7 +366,9 @@ class CLI:
except Exception as e:
logger.error("Failed to load metadata for %s: %s", ca.path, e)
return res
- for style in self.config.Runtime_Options__type:
+ for style in self.config.Runtime_Options__type_modify:
+ if style == src_style_name:
+ continue
status = self.copy_style(ca, res.md, style)
if status == Status.success:
res.tags_written.append(style)
@@ -377,7 +378,7 @@ class CLI:
def save(self, ca: ComicArchive, match_results: OnlineMatchResults) -> tuple[Result, OnlineMatchResults]:
if not self.config.Runtime_Options__overwrite:
- for style in self.config.Runtime_Options__type:
+ for style in self.config.Runtime_Options__type_modify:
if ca.has_metadata(style):
self.output(f"{ca.path}: Already has {md_styles[style].name()} tags. Not overwriting.")
return (
@@ -385,7 +386,7 @@ class CLI:
Action.save,
original_path=ca.path,
status=Status.existing_tags,
- tags_written=self.config.Runtime_Options__type,
+ tags_written=self.config.Runtime_Options__type_modify,
),
match_results,
)
@@ -413,7 +414,7 @@ class CLI:
Action.save,
original_path=ca.path,
status=Status.fetch_data_failure,
- tags_written=self.config.Runtime_Options__type,
+ tags_written=self.config.Runtime_Options__type_modify,
)
match_results.fetch_data_failures.append(res)
return res, match_results
@@ -425,7 +426,7 @@ class CLI:
status=Status.match_failure,
original_path=ca.path,
match_status=MatchStatus.no_match,
- tags_written=self.config.Runtime_Options__type,
+ tags_written=self.config.Runtime_Options__type_modify,
)
match_results.no_matches.append(res)
return res, match_results
@@ -438,7 +439,7 @@ class CLI:
status=Status.match_failure,
original_path=ca.path,
match_status=MatchStatus.no_match,
- tags_written=self.config.Runtime_Options__type,
+ tags_written=self.config.Runtime_Options__type_modify,
)
match_results.no_matches.append(res)
return res, match_results
@@ -481,7 +482,7 @@ class CLI:
original_path=ca.path,
online_results=matches,
match_status=MatchStatus.low_confidence_match,
- tags_written=self.config.Runtime_Options__type,
+ tags_written=self.config.Runtime_Options__type_modify,
)
match_results.low_confidence_matches.append(res)
return res, match_results
@@ -493,7 +494,7 @@ class CLI:
original_path=ca.path,
online_results=matches,
match_status=MatchStatus.multiple_match,
- tags_written=self.config.Runtime_Options__type,
+ tags_written=self.config.Runtime_Options__type_modify,
)
match_results.multiple_matches.append(res)
return res, match_results
@@ -505,7 +506,7 @@ class CLI:
original_path=ca.path,
online_results=matches,
match_status=MatchStatus.low_confidence_match,
- tags_written=self.config.Runtime_Options__type,
+ tags_written=self.config.Runtime_Options__type_modify,
)
match_results.low_confidence_matches.append(res)
return res, match_results
@@ -517,7 +518,7 @@ class CLI:
original_path=ca.path,
online_results=matches,
match_status=MatchStatus.no_match,
- tags_written=self.config.Runtime_Options__type,
+ tags_written=self.config.Runtime_Options__type_modify,
)
match_results.no_matches.append(res)
return res, match_results
@@ -533,7 +534,7 @@ class CLI:
original_path=ca.path,
online_results=matches,
match_status=MatchStatus.good_match,
- tags_written=self.config.Runtime_Options__type,
+ tags_written=self.config.Runtime_Options__type_modify,
)
match_results.fetch_data_failures.append(res)
return res, match_results
@@ -545,7 +546,7 @@ class CLI:
online_results=matches,
match_status=MatchStatus.good_match,
md=prepare_metadata(md, ct_md, self.config),
- tags_written=self.config.Runtime_Options__type,
+ tags_written=self.config.Runtime_Options__type_modify,
)
assert res.md
# ok, done building our metadata. time to save
diff --git a/comictaggerlib/ctsettings/commandline.py b/comictaggerlib/ctsettings/commandline.py
index 3920f3c..3884718 100644
--- a/comictaggerlib/ctsettings/commandline.py
+++ b/comictaggerlib/ctsettings/commandline.py
@@ -160,14 +160,21 @@ def register_runtime(parser: settngs.Manager) -> None:
parser.add_setting(
"--json", "-j", action="store_true", help="Output json on stdout. Ignored in interactive mode.", file=False
)
-
parser.add_setting(
- "-t",
- "--type",
+ "--type-modify",
metavar=f"{{{','.join(metadata_styles).upper()}}}",
default=[],
type=metadata_type,
- help="""Specify TYPE as either CR, CBL or COMET\n(as either ComicRack, ComicBookLover,\nor CoMet style tags, respectively).\nUse commas for multiple types.\nFor searching the metadata will use the first listed:\neg '-t cbl,cr' with no CBL tags, CR will be used if they exist\n\n""",
+ help="""Specify the type of tags to write.\nUse commas for multiple types.\nRead types will be used if unspecified\nSee --list-plugins for the available types.\n\n""",
+ file=False,
+ )
+ parser.add_setting(
+ "-t",
+ "--type-read",
+ metavar=f"{{{','.join(metadata_styles).upper()}}}",
+ default=[],
+ type=metadata_type,
+ help="""Specify the type of tags to read.\nUse commas for multiple types.\nSee --list-plugins for the available types.\nThe tag use will be 'overlayed' in order:\ne.g. '-t cbl,cr' with no CBL tags, CR will be used if they exist and CR will overwrite any shared CBL tags.\n\n""",
file=False,
)
parser.add_setting(
@@ -190,7 +197,7 @@ def register_commands(parser: settngs.Manager) -> None:
dest="command",
action="store_const",
const=Action.print,
- help="""Print out tag info from file. Specify type\n(via -t) to get only info of that tag type.\n\n""",
+ help="""Print out tag info from file. Specify type\n(via --type-read) to get only info of that tag type.\n\n""",
file=False,
)
parser.add_setting(
@@ -199,7 +206,7 @@ def register_commands(parser: settngs.Manager) -> None:
dest="command",
action="store_const",
const=Action.delete,
- help="Deletes the tag block of specified type (via -t).\n",
+ help="Deletes the tag block of specified type (via --type-modify).\n",
file=False,
)
parser.add_setting(
@@ -207,7 +214,7 @@ def register_commands(parser: settngs.Manager) -> None:
"--copy",
type=metadata_type_single,
metavar=f"{{{','.join(metadata_styles).upper()}}}",
- help="Copy the specified source tag block to\ndestination style specified via -t\n(potentially lossy operation).\n\n",
+ help="Copy the specified source tag block to\ndestination style specified via --type-modify\n(potentially lossy operation).\n\n",
file=False,
)
parser.add_setting(
@@ -216,7 +223,7 @@ def register_commands(parser: settngs.Manager) -> None:
dest="command",
action="store_const",
const=Action.save,
- help="Save out tags as specified type (via -t).\nMust specify also at least -o, -f, or -m.\n\n",
+ help="Save out tags as specified type (via --type-modify).\nMust specify also at least -o, -f, or -m.\n\n",
file=False,
)
parser.add_setting(
@@ -284,6 +291,9 @@ def validate_commandline_settings(config: settngs.Config[ct_ns], parser: settngs
if config[0].Runtime_Options__json and config[0].Runtime_Options__interactive:
config[0].Runtime_Options__json = False
+ if config[0].Runtime_Options__type_read and not config[0].Runtime_Options__type_modify:
+ config[0].Runtime_Options__type_modify = config[0].Runtime_Options__type_read
+
if (
config[0].Commands__command not in (Action.save_config, Action.list_plugins)
and config[0].Runtime_Options__no_gui
@@ -291,16 +301,16 @@ def validate_commandline_settings(config: settngs.Config[ct_ns], parser: settngs
):
parser.exit(message="Command requires at least one filename!\n", status=1)
- if config[0].Commands__command == Action.delete and not config[0].Runtime_Options__type:
- parser.exit(message="Please specify the type to delete with -t\n", status=1)
+ if config[0].Commands__command == Action.delete and not config[0].Runtime_Options__type_modify:
+ parser.exit(message="Please specify the type to delete with --type-modify\n", status=1)
- if config[0].Commands__command == Action.save and not config[0].Runtime_Options__type:
- parser.exit(message="Please specify the type to save with -t\n", status=1)
+ if config[0].Commands__command == Action.save and not config[0].Runtime_Options__type_modify:
+ parser.exit(message="Please specify the type to save with --type-modify\n", status=1)
if config[0].Commands__copy:
config[0].Commands__command = Action.copy
- if not config[0].Runtime_Options__type:
- parser.exit(message="Please specify the type to copy to with -t\n", status=1)
+ if not config[0].Runtime_Options__type_modify:
+ parser.exit(message="Please specify the type to copy to with --type-modify\n", status=1)
if config[0].Runtime_Options__recursive:
config[0].Runtime_Options__files = utils.get_recursive_filelist(config[0].Runtime_Options__files)
diff --git a/comictaggerlib/ctsettings/file.py b/comictaggerlib/ctsettings/file.py
index d1bb276..a6066e6 100644
--- a/comictaggerlib/ctsettings/file.py
+++ b/comictaggerlib/ctsettings/file.py
@@ -32,7 +32,7 @@ def internal(parser: settngs.Manager) -> None:
# automatic settings
parser.add_setting("install_id", default=uuid.uuid4().hex, cmdline=False)
parser.add_setting("save_data_style", default=["cbi"], cmdline=False)
- parser.add_setting("load_data_style", default="cbi", cmdline=False)
+ parser.add_setting("load_data_style", default=["cbi"], cmdline=False)
parser.add_setting("last_opened_folder", default="", cmdline=False)
parser.add_setting("window_width", default=0, cmdline=False)
parser.add_setting("window_height", default=0, cmdline=False)
@@ -279,6 +279,15 @@ def migrate_settings(config: settngs.Config[ct_ns]) -> settngs.Config[ct_ns]:
else:
config[0].internal__save_data_style = ["cbi"]
+ load_style = config[0].internal__load_data_style
+ if not isinstance(load_style, list):
+ if isinstance(load_style, int) and load_style in (0, 1, 2):
+ config[0].internal__load_data_style = [original_types[load_style]]
+ elif isinstance(load_style, str):
+ config[0].internal__load_data_style = [load_style]
+ else:
+ config[0].internal__load_data_style = ["cbi"]
+
return config
diff --git a/comictaggerlib/ctsettings/settngs_namespace.py b/comictaggerlib/ctsettings/settngs_namespace.py
index 5f1aaf8..a0677fb 100644
--- a/comictaggerlib/ctsettings/settngs_namespace.py
+++ b/comictaggerlib/ctsettings/settngs_namespace.py
@@ -34,14 +34,15 @@ class SettngsNS(settngs.TypedNS):
Runtime_Options__glob: bool
Runtime_Options__quiet: bool
Runtime_Options__json: bool
- Runtime_Options__type: list[str]
+ Runtime_Options__type_modify: list[str]
+ Runtime_Options__type_read: list[str]
Runtime_Options__overwrite: bool
Runtime_Options__no_gui: bool
Runtime_Options__files: list[str]
internal__install_id: str
internal__save_data_style: list[str]
- internal__load_data_style: str
+ internal__load_data_style: list[str]
internal__last_opened_folder: str
internal__window_width: int
internal__window_height: int
@@ -149,7 +150,7 @@ class Runtime_Options(typing.TypedDict):
class internal(typing.TypedDict):
install_id: str
save_data_style: list[str]
- load_data_style: str
+ load_data_style: list[str]
last_opened_folder: str
window_width: int
window_height: int
diff --git a/comictaggerlib/graphics/down.png b/comictaggerlib/graphics/down.png
new file mode 100644
index 0000000..bc6ee2d
Binary files /dev/null and b/comictaggerlib/graphics/down.png differ
diff --git a/comictaggerlib/graphics/up.png b/comictaggerlib/graphics/up.png
new file mode 100644
index 0000000..5f4a1e2
Binary files /dev/null and b/comictaggerlib/graphics/up.png differ
diff --git a/comictaggerlib/renamewindow.py b/comictaggerlib/renamewindow.py
index ae7efdf..62e6be0 100644
--- a/comictaggerlib/renamewindow.py
+++ b/comictaggerlib/renamewindow.py
@@ -39,7 +39,7 @@ class RenameWindow(QtWidgets.QDialog):
self,
parent: QtWidgets.QWidget,
comic_archive_list: list[ComicArchive],
- data_style: str,
+ load_data_styles: list[str],
config: settngs.Config[ct_ns],
talkers: dict[str, ComicTalker],
) -> None:
@@ -48,7 +48,9 @@ class RenameWindow(QtWidgets.QDialog):
with (ui_path / "renamewindow.ui").open(encoding="utf-8") as uifile:
uic.loadUi(uifile, self)
- self.label.setText(f"Preview (based on {metadata_styles[data_style].name()} tags):")
+ self.label.setText(
+ f"Preview (based on {', '.join(metadata_styles[style].name() for style in load_data_styles)} tags):"
+ )
self.setWindowFlags(
QtCore.Qt.WindowType(
@@ -61,7 +63,7 @@ class RenameWindow(QtWidgets.QDialog):
self.config = config
self.talkers = talkers
self.comic_archive_list = comic_archive_list
- self.data_style = data_style
+ self.load_data_styles = load_data_styles
self.rename_list: list[str] = []
self.btnSettings.clicked.connect(self.modify_settings)
@@ -70,7 +72,7 @@ class RenameWindow(QtWidgets.QDialog):
self.do_preview()
- def config_renamer(self, ca: ComicArchive, md: GenericMetadata | None = None) -> str:
+ def config_renamer(self, ca: ComicArchive, md: GenericMetadata = GenericMetadata()) -> str:
self.renamer.set_template(self.config[0].File_Rename__template)
self.renamer.set_issue_zero_padding(self.config[0].File_Rename__issue_number_padding)
self.renamer.set_smart_cleanup(self.config[0].File_Rename__use_smart_string_cleanup)
@@ -82,7 +84,15 @@ class RenameWindow(QtWidgets.QDialog):
new_ext = ca.extension()
if md is None or md.is_empty:
- md = ca.read_metadata(self.data_style)
+ md, error = self.parent().overlay_ca_read_style(self.load_data_styles, ca)
+ if error is not None:
+ logger.error("Failed to load metadata for %s: %s", ca.path, error)
+ QtWidgets.QMessageBox.warning(
+ self,
+ "Read Failed!",
+ f"One or more of the read styles failed to load for {ca.path}, check log for details",
+ )
+
if md.is_empty:
md = ca.metadata_from_filename(
self.config[0].Filename_Parsing__filename_parser,
diff --git a/comictaggerlib/settingswindow.py b/comictaggerlib/settingswindow.py
index dc2d2bc..7c8d136 100644
--- a/comictaggerlib/settingswindow.py
+++ b/comictaggerlib/settingswindow.py
@@ -523,6 +523,7 @@ class SettingsWindow(QtWidgets.QDialog):
if self.cbxShortMetadataNames.isChecked() != self.config[0].General__use_short_metadata_names:
self.config[0].General__use_short_metadata_names = self.cbxShortMetadataNames.isChecked()
self.parent().populate_style_names()
+ self.parent().adjust_load_style_combo()
self.parent().adjust_save_style_combo()
self.config[0].Issue_Identifier__series_match_identify_thresh = self.sbNameMatchIdentifyThresh.value()
diff --git a/comictaggerlib/taggerwindow.py b/comictaggerlib/taggerwindow.py
index b9d3faa..790eba4 100644
--- a/comictaggerlib/taggerwindow.py
+++ b/comictaggerlib/taggerwindow.py
@@ -211,18 +211,20 @@ class TaggerWindow(QtWidgets.QMainWindow):
self.setWindowIcon(QtGui.QIcon(str(graphics_path / "app.png")))
- if config[0].Runtime_Options__type and isinstance(config[0].Runtime_Options__type[0], str):
- # respect the command line option tag type
- config[0].internal__save_data_style = config[0].Runtime_Options__type
- config[0].internal__load_data_style = config[0].Runtime_Options__type[0]
+ # respect the command line option tag type
+ if config[0].Runtime_Options__type_modify:
+ config[0].internal__save_data_style = config[0].Runtime_Options__type_modify
+ if config[0].Runtime_Options__type_read:
+ config[0].internal__load_data_style = config[0].Runtime_Options__type_read
for style in config[0].internal__save_data_style:
if style not in metadata_styles:
config[0].internal__save_data_style.remove(style)
- if config[0].internal__load_data_style not in metadata_styles:
- config[0].internal__load_data_style = list(metadata_styles.keys())[0]
+ for style in config[0].internal__load_data_style:
+ if style not in metadata_styles:
+ config[0].internal__load_data_style.remove(style)
self.save_data_styles: list[str] = config[0].internal__save_data_style
- self.load_data_style: str = config[0].internal__load_data_style
+ self.load_data_styles: list[str] = config[0].internal__load_data_style
self.setAcceptDrops(True)
self.view_tag_actions, self.remove_tag_actions = self.tag_actions()
@@ -271,7 +273,7 @@ class TaggerWindow(QtWidgets.QMainWindow):
self.cbMaturityRating.lineEdit().setAcceptDrops(False)
# hook up the callbacks
- self.cbLoadDataStyle.currentIndexChanged.connect(self.set_load_data_style)
+ self.cbLoadDataStyle.dropdownClosed.connect(self.set_load_data_style)
self.cbSaveDataStyle.itemChecked.connect(self.set_save_data_style)
self.cbx_sources.currentIndexChanged.connect(self.set_source)
self.btnEditCredit.clicked.connect(self.edit_credit)
@@ -433,7 +435,7 @@ class TaggerWindow(QtWidgets.QMainWindow):
self.actionAutoTag.triggered.connect(self.auto_tag)
self.actionCopyTags.setShortcut("Ctrl+C")
- self.actionCopyTags.setStatusTip("Copy one tag style to another")
+ self.actionCopyTags.setStatusTip("Copy one tag style tags to enabled modify style(s)")
self.actionCopyTags.triggered.connect(self.copy_tags)
self.actionRemoveAuto.setShortcut("Ctrl+D")
@@ -1188,7 +1190,7 @@ class TaggerWindow(QtWidgets.QMainWindow):
failed_style = metadata_styles[style].name()
break
- self.comic_archive.load_cache(list(metadata_styles))
+ self.comic_archive.load_cache(set(metadata_styles))
QtWidgets.QApplication.restoreOverrideCursor()
if failed_style:
@@ -1201,26 +1203,37 @@ class TaggerWindow(QtWidgets.QMainWindow):
self.clear_dirty_flag()
self.update_info_box()
self.update_menus()
- self.fileSelectionList.update_current_row()
- self.metadata = self.comic_archive.read_metadata(self.load_data_style)
+ # Only try to read if write was successful
+ self.metadata, error = self.overlay_ca_read_style(self.load_data_styles, self.comic_archive)
+ if error is not None:
+ QtWidgets.QMessageBox.warning(
+ self,
+ "Read Failed!",
+ f"One or more of the read styles failed to load for {self.comic_archive.path}, check log for details",
+ )
+ logger.error("Failed to load metadata for %s: %s", self.ca.path, error)
+
+ self.fileSelectionList.update_current_row()
self.update_ui_for_archive()
else:
QtWidgets.QMessageBox.information(self, "Whoops!", "No data to commit!")
- def set_load_data_style(self, s: str) -> None:
+ def set_load_data_style(self, load_data_styles: list[str]) -> None:
+ """Should only be called from the combobox signal"""
if self.dirty_flag_verification(
- "Change Tag Read Style", "If you change read tag style now, data in the form will be lost. Are you sure?"
+ "Change Tag Read Style",
+ "If you change read tag style(s) now, data in the form will be lost. Are you sure?",
):
- self.load_data_style = self.cbLoadDataStyle.itemData(s)
- self.config[0].internal__load_data_style = self.load_data_style
+ self.load_data_styles = list(reversed(load_data_styles))
+ self.config[0].internal__load_data_style = self.load_data_styles
self.update_menus()
if self.comic_archive is not None:
self.load_archive(self.comic_archive)
else:
- self.cbLoadDataStyle.currentIndexChanged.disconnect(self.set_load_data_style)
+ self.cbLoadDataStyle.itemChanged.disconnect()
self.adjust_load_style_combo()
- self.cbLoadDataStyle.currentIndexChanged.connect(self.set_load_data_style)
+ self.cbLoadDataStyle.itemChanged.connect(self.set_load_data_style)
def set_save_data_style(self) -> None:
self.save_data_styles = self.cbSaveDataStyle.currentData()
@@ -1392,28 +1405,38 @@ class TaggerWindow(QtWidgets.QMainWindow):
self.cbx_sources.setCurrentIndex(self.cbx_sources.findData(self.config[0].Sources__source))
def adjust_load_style_combo(self) -> None:
- # select the current style
- self.cbLoadDataStyle.setCurrentIndex(self.cbLoadDataStyle.findData(self.load_data_style))
+ """Select the enabled styles. Since metadata is merged in an overlay fashion the last item in the list takes priority. We reverse the order for display to the user"""
+ unchecked = set(metadata_styles.keys()) - set(self.load_data_styles)
+ for i, style in enumerate(reversed(self.load_data_styles)):
+ item_idx = self.cbLoadDataStyle.findData(style)
+ self.cbLoadDataStyle.setItemChecked(item_idx, True)
+ # Order matters, move items to list order
+ if item_idx != i:
+ self.cbLoadDataStyle.moveItem(item_idx, row=i)
+ for style in unchecked:
+ self.cbLoadDataStyle.setItemChecked(self.cbLoadDataStyle.findData(style), False)
def adjust_save_style_combo(self) -> None:
# select the current style
unchecked = set(metadata_styles.keys()) - set(self.save_data_styles)
for style in self.save_data_styles:
- self.cbSaveDataStyle.setItemChecked(self.cbLoadDataStyle.findData(style), True)
+ self.cbSaveDataStyle.setItemChecked(self.cbSaveDataStyle.findData(style), True)
for style in unchecked:
- self.cbSaveDataStyle.setItemChecked(self.cbLoadDataStyle.findData(style), False)
+ self.cbSaveDataStyle.setItemChecked(self.cbSaveDataStyle.findData(style), False)
self.update_metadata_style_tweaks()
def populate_style_names(self) -> None:
# First clear all entries (called from settingswindow.py)
self.cbSaveDataStyle.clear()
+ self.cbLoadDataStyle.clear()
# Add the entries to the tag style combobox
for style in metadata_styles.values():
- self.cbLoadDataStyle.addItem(style.name(), style.short_name)
if self.config[0].General__use_short_metadata_names:
self.cbSaveDataStyle.addItem(style.short_name.upper(), style.short_name)
+ self.cbLoadDataStyle.addItem(style.short_name.upper(), style.short_name)
else:
self.cbSaveDataStyle.addItem(style.name(), style.short_name)
+ self.cbLoadDataStyle.addItem(style.name(), style.short_name)
def populate_combo_boxes(self) -> None:
self.populate_style_names()
@@ -1580,7 +1603,7 @@ class TaggerWindow(QtWidgets.QMainWindow):
# Abandon any further tag removals to prevent any greater damage to archive
break
ca.reset_cache()
- ca.load_cache(list(metadata_styles))
+ ca.load_cache(set(metadata_styles))
progdialog.hide()
QtCore.QCoreApplication.processEvents()
@@ -1604,12 +1627,12 @@ class TaggerWindow(QtWidgets.QMainWindow):
ca_list = self.fileSelectionList.get_selected_archive_list()
has_src_count = 0
- src_style = self.load_data_style
- dest_styles = self.save_data_styles
+ src_styles: list[str] = self.load_data_styles
+ dest_styles: list[str] = self.save_data_styles
- # Remove the read style from the write style
- if src_style in dest_styles:
- dest_styles.remove(src_style)
+ if len(src_styles) == 1 and src_styles[0] in dest_styles:
+ # Remove the read style from the write style
+ dest_styles.remove(src_styles[0])
if not dest_styles:
QtWidgets.QMessageBox.information(
@@ -1618,12 +1641,16 @@ class TaggerWindow(QtWidgets.QMainWindow):
return
for ca in ca_list:
- if ca.has_metadata(src_style):
- has_src_count += 1
+ for style in src_styles:
+ if ca.has_metadata(style):
+ has_src_count += 1
+ continue
if has_src_count == 0:
QtWidgets.QMessageBox.information(
- self, "Copy Tags", f"No archives with {metadata_styles[src_style].name()} tags selected!"
+ self,
+ "Copy Tags",
+ f"No archives with {', '.join([metadata_styles[style].name() for style in src_styles])} tags selected!",
)
return
@@ -1636,8 +1663,9 @@ class TaggerWindow(QtWidgets.QMainWindow):
reply = QtWidgets.QMessageBox.question(
self,
"Copy Tags",
- f"Are you sure you wish to copy the {metadata_styles[src_style].name()} "
- f"tags to {', '.join([metadata_styles[style].name() for style in dest_styles])} tags in "
+ f"Are you sure you wish to copy the combined (with overlay order) tags of "
+ f"{', '.join([metadata_styles[style].name() for style in src_styles])} "
+ f"to {', '.join([metadata_styles[style].name() for style in dest_styles])} tags in "
f"{has_src_count} archive(s)?",
QtWidgets.QMessageBox.StandardButton.Yes,
QtWidgets.QMessageBox.StandardButton.No,
@@ -1655,10 +1683,11 @@ class TaggerWindow(QtWidgets.QMainWindow):
success_count = 0
for prog_idx, ca in enumerate(ca_list, 1):
ca_saved = False
-
- if ca.has_metadata(src_style) and ca.is_writable():
- md = ca.read_metadata(src_style)
- else:
+ md, error = self.overlay_ca_read_style(src_styles, ca)
+ if error is not None:
+ failed_list.append(ca.path)
+ continue
+ if md.is_empty:
continue
for style in dest_styles:
@@ -1683,7 +1712,7 @@ class TaggerWindow(QtWidgets.QMainWindow):
failed_list.append(ca.path)
ca.reset_cache()
- ca.load_cache([self.load_data_style, *self.save_data_styles])
+ ca.load_cache({*self.load_data_styles, *self.save_data_styles})
prog_dialog.hide()
QtCore.QCoreApplication.processEvents()
@@ -1727,11 +1756,16 @@ class TaggerWindow(QtWidgets.QMainWindow):
ii = IssueIdentifier(ca, self.config[0], self.current_talker())
# read in metadata, and parse file name if not there
- try:
- md = ca.read_metadata(self.load_data_style)
- except Exception as e:
- md = GenericMetadata()
- logger.error("Failed to load metadata for %s: %s", ca.path, e)
+ md, error = self.overlay_ca_read_style(self.load_data_styles, ca)
+ if error is not None:
+ QtWidgets.QMessageBox.warning(
+ self,
+ "Aborting...",
+ f"One or more of the read styles failed to load for {ca.path}. Aborting to prevent any possible further damage. Check log for details.",
+ )
+ logger.error("Failed to load metadata for %s: %s", self.ca.path, error)
+ return False, match_results
+
if md.is_empty:
md = ca.metadata_from_filename(
self.config[0].Filename_Parsing__filename_parser,
@@ -1888,7 +1922,7 @@ class TaggerWindow(QtWidgets.QMainWindow):
match_results.write_failures.append(res)
ca.reset_cache()
- ca.load_cache([self.load_data_style] + self.save_data_styles)
+ ca.load_cache({*self.load_data_styles, *self.save_data_styles})
return success, match_results
@@ -1937,7 +1971,7 @@ class TaggerWindow(QtWidgets.QMainWindow):
self.auto_tag_log(f"Auto-Tagging {prog_idx} of {len(ca_list)}\n")
self.auto_tag_log(f"{ca.path}\n")
try:
- cover_idx = ca.read_metadata(self.load_data_style).get_cover_page_index_list()[0]
+ cover_idx = ca.read_metadata(self.load_data_styles[0]).get_cover_page_index_list()[0]
except Exception as e:
cover_idx = 0
logger.error("Failed to load metadata for %s: %s", ca.path, e)
@@ -2132,7 +2166,7 @@ class TaggerWindow(QtWidgets.QMainWindow):
if self.dirty_flag_verification(
"File Rename", "If you rename files now, unsaved data in the form will be lost. Are you sure?"
):
- dlg = RenameWindow(self, ca_list, self.load_data_style, self.config, self.talkers)
+ dlg = RenameWindow(self, ca_list, self.load_data_styles, self.config, self.talkers)
dlg.setModal(True)
if dlg.exec() and self.comic_archive is not None:
self.fileSelectionList.update_selected_rows()
@@ -2151,15 +2185,28 @@ class TaggerWindow(QtWidgets.QMainWindow):
self.config[0].internal__last_opened_folder = os.path.abspath(os.path.split(comic_archive.path)[0])
self.comic_archive = comic_archive
- try:
- self.metadata = self.comic_archive.read_metadata(self.load_data_style)
- except Exception as e:
- logger.error("Failed to load metadata for %s: %s", self.comic_archive.path, e)
- self.exception(f"Failed to load metadata for {self.comic_archive.path}:\n\n{e}")
- self.metadata = GenericMetadata()
+
+ self.metadata, error = self.overlay_ca_read_style(self.load_data_styles, self.comic_archive)
+ if error is not None:
+ logger.error("Failed to load metadata for %s: %s", self.comic_archive.path, error)
+ self.exception(f"Failed to load metadata for {self.comic_archive.path}, see log for details\n\n")
self.update_ui_for_archive()
+ def overlay_ca_read_style(
+ self, load_data_styles: list[str], ca: ComicArchive
+ ) -> tuple[GenericMetadata, Exception | None]:
+ md = GenericMetadata()
+ error = None
+ try:
+ for style in load_data_styles:
+ metadata = ca.read_metadata(style)
+ md.overlay(metadata)
+ except Exception as e:
+ error = e
+
+ return md, error
+
def file_list_cleared(self) -> None:
self.reset_app()
diff --git a/comictaggerlib/ui/customwidgets.py b/comictaggerlib/ui/customwidgets.py
index b4efabf..2401ccf 100644
--- a/comictaggerlib/ui/customwidgets.py
+++ b/comictaggerlib/ui/customwidgets.py
@@ -2,10 +2,21 @@
from __future__ import annotations
+from enum import auto
+from sys import platform
from typing import Any
from PyQt5 import QtGui, QtWidgets
-from PyQt5.QtCore import QEvent, QRect, Qt, pyqtSignal
+from PyQt5.QtCore import QEvent, QModelIndex, QPoint, QRect, QSize, Qt, pyqtSignal
+
+from comicapi.utils import StrEnum
+from comictaggerlib.graphics import graphics_path
+
+
+class ClickedButtonEnum(StrEnum):
+ up = auto()
+ down = auto()
+ main = auto()
# Multiselect combobox from: https://gis.stackexchange.com/a/351152 (with custom changes)
@@ -112,3 +123,309 @@ class CheckableComboBox(QtWidgets.QComboBox):
self.setItemChecked(index, False)
else:
self.setItemChecked(index, True)
+
+
+# Inspiration from https://github.com/marcel-goldschen-ohm/ModelViewPyQt and https://github.com/zxt50330/qitemdelegate-example
+class ReadStyleItemDelegate(QtWidgets.QStyledItemDelegate):
+ buttonClicked = pyqtSignal(QModelIndex, ClickedButtonEnum)
+
+ def __init__(self, parent: QtWidgets.QWidget):
+ super().__init__()
+ self.combobox = parent
+
+ self.down_icon = QtGui.QImage(str(graphics_path / "down.png"))
+ self.up_icon = QtGui.QImage(str(graphics_path / "up.png"))
+
+ self.button_width = self.down_icon.width()
+ self.button_padding = 5
+
+ # Tooltip messages
+ self.item_help: str = ""
+ self.up_help: str = ""
+ self.down_help: str = ""
+
+ # Connect the signal to a slot in the delegate
+ self.combobox.itemClicked.connect(self.itemClicked)
+
+ def paint(self, painter: QtGui.QPainter, option: QtWidgets.QStyleOptionViewItem, index: QModelIndex) -> None:
+ options = QtWidgets.QStyleOptionViewItem(option)
+ self.initStyleOption(options, index)
+ style = self.combobox.style()
+
+ # Draw background with the same color as other widgets
+ palette = self.combobox.palette()
+ background_color = palette.color(QtGui.QPalette.Window)
+ painter.fillRect(options.rect, background_color)
+
+ style.drawPrimitive(QtWidgets.QStyle.PE_PanelItemViewItem, options, painter, self.combobox)
+
+ painter.save()
+
+ # Checkbox drawing logic
+ checked = index.data(Qt.CheckStateRole)
+ opts = QtWidgets.QStyleOptionButton()
+ opts.state |= QtWidgets.QStyle.State_Active
+ opts.rect = self.getCheckBoxRect(options)
+ opts.state |= QtWidgets.QStyle.State_ReadOnly
+ if checked:
+ opts.state |= QtWidgets.QStyle.State_On
+ style.drawPrimitive(
+ QtWidgets.QStyle.PrimitiveElement.PE_IndicatorMenuCheckMark, opts, painter, self.combobox
+ )
+ else:
+ opts.state |= QtWidgets.QStyle.State_Off
+ if platform != "darwin":
+ style.drawControl(QtWidgets.QStyle.CE_CheckBox, opts, painter, self.combobox)
+
+ label = index.data(Qt.DisplayRole)
+ rectangle = options.rect
+ rectangle.setX(opts.rect.width() + 10)
+ painter.drawText(rectangle, Qt.AlignVCenter, label)
+
+ # Draw buttons
+ if checked and (options.state & QtWidgets.QStyle.State_Selected):
+ up_rect = self._button_up_rect(options.rect)
+ down_rect = self._button_down_rect(options.rect)
+
+ painter.drawImage(up_rect, self.up_icon)
+ painter.drawImage(down_rect, self.down_icon)
+
+ painter.restore()
+
+ def _button_up_rect(self, rect: QRect) -> QRect:
+ return QRect(
+ self.combobox.view().width() - (self.button_width * 2) - (self.button_padding * 2),
+ rect.top() + (rect.height() - self.button_width) // 2,
+ self.button_width,
+ self.button_width,
+ )
+
+ def _button_down_rect(self, rect: QRect = QRect(10, 1, 12, 12)) -> QRect:
+ return QRect(
+ self.combobox.view().width() - self.button_padding - self.button_width,
+ rect.top() + (rect.height() - self.button_width) // 2,
+ self.button_width,
+ self.button_width,
+ )
+
+ def getCheckBoxRect(self, option: QtWidgets.QStyleOptionViewItem) -> QRect:
+ # Get size of a standard checkbox.
+ opts = QtWidgets.QStyleOptionButton()
+ style = option.widget.style()
+ checkBoxRect = style.subElementRect(QtWidgets.QStyle.SE_CheckBoxIndicator, opts, None)
+ y = option.rect.y()
+ h = option.rect.height()
+ checkBoxTopLeftCorner = QPoint(5, int(y + h / 2 - checkBoxRect.height() / 2))
+
+ return QRect(checkBoxTopLeftCorner, checkBoxRect.size())
+
+ def itemClicked(self, index: QModelIndex, pos: QPoint) -> None:
+ item_rect = self.combobox.view().visualRect(index)
+ checked = index.data(Qt.CheckStateRole)
+ button_up_rect = self._button_up_rect(item_rect)
+ button_down_rect = self._button_down_rect(item_rect)
+
+ if checked and button_up_rect.contains(pos):
+ self.buttonClicked.emit(index, ClickedButtonEnum.up)
+ elif checked and button_down_rect.contains(pos):
+ self.buttonClicked.emit(index, ClickedButtonEnum.down)
+ else:
+ self.buttonClicked.emit(index, ClickedButtonEnum.main)
+
+ def setToolTip(self, item: str = "", up: str = "", down: str = "") -> None:
+ if item:
+ self.item_help = item
+ if up:
+ self.up_help = up
+ if down:
+ self.down_help = down
+
+ def helpEvent(
+ self,
+ event: QtGui.QHelpEvent,
+ view: QtWidgets.QAbstractItemView,
+ option: QtWidgets.QStyleOptionViewItem,
+ index: QModelIndex,
+ ) -> bool:
+ item_rect = view.visualRect(index)
+ button_up_rect = self._button_up_rect(item_rect)
+ button_down_rect = self._button_down_rect(item_rect)
+ checked = index.data(Qt.CheckStateRole)
+
+ if checked == Qt.Checked and button_up_rect.contains(event.pos()):
+ QtWidgets.QToolTip.showText(event.globalPos(), self.up_help, self.combobox, QRect(), 3000)
+ elif checked == Qt.Checked and button_down_rect.contains(event.pos()):
+ QtWidgets.QToolTip.showText(event.globalPos(), self.down_help, self.combobox, QRect(), 3000)
+ else:
+ QtWidgets.QToolTip.showText(event.globalPos(), self.item_help, self.combobox, QRect(), 3000)
+ return True
+
+ def sizeHint(self, option: QtWidgets.QStyleOptionViewItem, index: QModelIndex) -> QSize:
+ # Reimpliment standard combobox sizeHint. Only height is used by view, width is ignored
+ menu_option = QtWidgets.QStyleOptionMenuItem()
+ return self.combobox.style().sizeFromContents(
+ QtWidgets.QStyle.ContentsType.CT_MenuItem, menu_option, option.rect.size(), self.combobox
+ )
+
+
+# Multiselect combobox from: https://gis.stackexchange.com/a/351152 (with custom changes)
+class CheckableOrderComboBox(QtWidgets.QComboBox):
+ itemClicked = pyqtSignal(QModelIndex, QPoint)
+ dropdownClosed = pyqtSignal(list)
+
+ def __init__(self, *args: Any, **kwargs: Any):
+ super().__init__(*args, **kwargs)
+ itemDelegate = ReadStyleItemDelegate(self)
+ itemDelegate.setToolTip(
+ "Select which read style(s) to use", "Move item up in priority", "Move item down in priority"
+ )
+ self.setItemDelegate(itemDelegate)
+
+ # Prevent popup from closing when clicking on an item
+ self.view().viewport().installEventFilter(self)
+
+ # Go on a bit of a merry-go-round with the signals to avoid custom model/view
+ self.itemDelegate().buttonClicked.connect(self.buttonClicked)
+
+ # Keeps track of when the combobox list is shown
+ self.justShown = False
+
+ def buttonClicked(self, index: QModelIndex, button: ClickedButtonEnum) -> None:
+ if button == ClickedButtonEnum.up:
+ self.moveItem(index.row(), up=True)
+ elif button == ClickedButtonEnum.down:
+ self.moveItem(index.row(), up=False)
+ else:
+ self.toggleItem(index.row())
+
+ def resizeEvent(self, event: Any) -> None:
+ # Recompute text to elide as needed
+ super().resizeEvent(event)
+ self._updateText()
+
+ def eventFilter(self, obj: Any, event: Any) -> bool:
+ # Allow events before the combobox list is shown
+ if obj == self.view().viewport():
+ # We record that the combobox list has been shown
+ if event.type() == QEvent.Show:
+ self.justShown = True
+ # We record that the combobox list has hidden,
+ # this will happen if the user does not make a selection
+ # but clicks outside of the combobox list or presses escape
+ if event.type() == QEvent.Hide:
+ self._updateText()
+ self.justShown = False
+ # Reverse as the display order is in "priority" order for the user whereas overlay requires reversed
+ self.dropdownClosed.emit(self.currentData())
+ # QEvent.MouseButtonPress is inconsistent on activation because double clicks are a thing
+ if event.type() == QEvent.MouseButtonRelease:
+ # If self.justShown is true it means that they clicked on the combobox to change the checked items
+ # This is standard behavior (on macos) but I think it is surprising when it has a multiple select
+ if self.justShown:
+ self.justShown = False
+ return True
+
+ # Find the current index and item
+ index = self.view().indexAt(event.pos())
+ if index.isValid():
+ self.itemClicked.emit(index, event.pos())
+ return True
+
+ return False
+
+ def currentData(self) -> list[Any]:
+ # Return the list of all checked items data
+ res = []
+ for i in range(self.count()):
+ item = self.model().item(i)
+ if item.checkState() == Qt.Checked:
+ res.append(self.itemData(i))
+ return res
+
+ def addItem(self, text: str, data: Any = None) -> None:
+ super().addItem(text, data)
+ # Need to enable the checkboxes and require one checked item
+ # Expected that state of *all* checkboxes will be set ('adjust_save_style_combo' in taggerwindow.py)
+ if self.count() == 1:
+ self.model().item(0).setCheckState(Qt.CheckState.Checked)
+
+ # Add room for "move" arrows
+ text_width = self.fontMetrics().width(text)
+ checkbox_width = 40
+ total_width = text_width + checkbox_width + (self.itemDelegate().button_width * 2)
+ if total_width > self.view().minimumWidth():
+ self.view().setMinimumWidth(total_width)
+
+ def moveItem(self, index: int, up: bool = False, row: int | None = None) -> None:
+ """'Move' an item. Really swap the data and titles around on the two items"""
+ if row is None:
+ adjust = -1 if up else 1
+ row = index + adjust
+
+ # TODO Disable buttons at top and bottom. Do a check here for now
+ if up and index == 0:
+ return
+ if up is False and row == self.count():
+ return
+
+ # Grab values for the rows to swap
+ cur_data = self.model().item(index).data(Qt.UserRole)
+ cur_title = self.model().item(index).data(Qt.DisplayRole)
+ cur_state = self.model().item(index).data(Qt.CheckStateRole)
+
+ swap_data = self.model().item(row).data(Qt.UserRole)
+ swap_title = self.model().item(row).data(Qt.DisplayRole)
+ swap_state = self.model().item(row).checkState()
+
+ self.model().item(row).setData(cur_data, Qt.UserRole)
+ self.model().item(row).setCheckState(cur_state)
+ self.model().item(row).setText(cur_title)
+
+ self.model().item(index).setData(swap_data, Qt.UserRole)
+ self.model().item(index).setCheckState(swap_state)
+ self.model().item(index).setText(swap_title)
+
+ def _updateText(self) -> None:
+ texts = []
+ for i in range(self.count()):
+ item = self.model().item(i)
+ if item.checkState() == Qt.Checked:
+ texts.append(item.text())
+ text = ", ".join(texts)
+
+ # Compute elided text (with "...")
+
+ # The QStyleOptionComboBox is needed for the call to subControlRect
+ so = QtWidgets.QStyleOptionComboBox()
+ # init with the current widget
+ so.initFrom(self)
+
+ # Ask the style for the size of the text field
+ rect = self.style().subControlRect(QtWidgets.QStyle.CC_ComboBox, so, QtWidgets.QStyle.SC_ComboBoxEditField)
+
+ # Compute the elided text
+ elidedText = self.fontMetrics().elidedText(text, Qt.ElideRight, rect.width())
+
+ # This CheckableComboBox does not use the index, so we clear it and set the placeholder text
+ self.setCurrentIndex(-1)
+ self.setPlaceholderText(elidedText)
+
+ def setItemChecked(self, index: Any, state: bool) -> None:
+ qt_state = Qt.Checked if state else Qt.Unchecked
+ item = self.model().item(index)
+ current = self.currentData()
+ # If we have at least one item checked emit itemChecked with the current check state and update text
+ # Require at least one item to be checked and provide a tooltip
+ if len(current) == 1 and not state and item.checkState() == Qt.Checked:
+ QtWidgets.QToolTip.showText(QtGui.QCursor.pos(), self.toolTip(), self, QRect(), 3000)
+ return
+
+ if len(current) > 0:
+ item.setCheckState(qt_state)
+ self._updateText()
+
+ def toggleItem(self, index: int) -> None:
+ if self.model().item(index).checkState() == Qt.Checked:
+ self.setItemChecked(index, False)
+ else:
+ self.setItemChecked(index, True)
diff --git a/comictaggerlib/ui/taggerwindow.ui b/comictaggerlib/ui/taggerwindow.ui
index cdf9bd3..2f25ac2 100644
--- a/comictaggerlib/ui/taggerwindow.ui
+++ b/comictaggerlib/ui/taggerwindow.ui
@@ -7,7 +7,7 @@
0
0
1096
- 658
+ 660
@@ -76,7 +76,11 @@
-
-
+
+
+ At least one read style must be selected
+
+
-
@@ -1524,6 +1528,11 @@
QComboBox
comictaggerlib.ui.customwidgets
+
+ CheckableOrderComboBox
+ QComboBox
+ comictaggerlib.ui.customwidgets
+
diff --git a/tests/integration_test.py b/tests/integration_test.py
index b375835..57ff9c1 100644
--- a/tests/integration_test.py
+++ b/tests/integration_test.py
@@ -39,8 +39,9 @@ def test_save(
config[0].Runtime_Options__online = True
# Use the temporary comic we created
config[0].Runtime_Options__files = [tmp_comic.path]
- # Save ComicRack tags
- config[0].Runtime_Options__type = ["cr"]
+ # Read and save ComicRack tags
+ config[0].Runtime_Options__type_read = ["cr"]
+ config[0].Runtime_Options__type_modify = ["cr"]
# Search using the correct series since we just put the wrong series name in the CBZ
config[0].Runtime_Options__metadata = comicapi.genericmetadata.GenericMetadata(series=md_saved.series)
# Run ComicTagger
@@ -89,7 +90,7 @@ def test_delete(
# Use the temporary comic we created
config[0].Runtime_Options__files = [tmp_comic.path]
# Delete ComicRack tags
- config[0].Runtime_Options__type = ["cr"]
+ config[0].Runtime_Options__type_modify = ["cr"]
# Run ComicTagger
CLI(config[0], talkers).run()