Update all references of saved 'matadata' to 'tags'
This commit is contained in:
parent
24002c66e7
commit
69a9566f42
@ -3,7 +3,7 @@ from __future__ import annotations
|
||||
from PyInstaller.utils.hooks import collect_data_files, collect_entry_point
|
||||
|
||||
datas, hiddenimports = collect_entry_point("comicapi.archiver")
|
||||
mdatas, mhiddenimports = collect_entry_point("comicapi.metadata")
|
||||
mdatas, mhiddenimports = collect_entry_point("comicapi.tags")
|
||||
|
||||
hiddenimports += mhiddenimports
|
||||
datas += mdatas
|
||||
|
@ -30,97 +30,94 @@ from typing import TYPE_CHECKING
|
||||
from comicapi import utils
|
||||
from comicapi.archivers import Archiver, UnknownArchiver, ZipArchiver
|
||||
from comicapi.genericmetadata import GenericMetadata
|
||||
from comicapi.metadata import Metadata
|
||||
from comicapi.tags import Tag
|
||||
from comictaggerlib.ctversion import version
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from importlib.machinery import ModuleSpec
|
||||
from importlib.metadata import EntryPoint
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
archivers: list[type[Archiver]] = []
|
||||
metadata_styles: dict[str, Metadata] = {}
|
||||
tags: dict[str, Tag] = {}
|
||||
|
||||
|
||||
def load_archive_plugins(local_plugins: Iterable[EntryPoint] = tuple()) -> None:
|
||||
if not archivers:
|
||||
if sys.version_info < (3, 10):
|
||||
from importlib_metadata import entry_points
|
||||
else:
|
||||
from importlib.metadata import entry_points
|
||||
builtin: list[type[Archiver]] = []
|
||||
# A list is used first matching plugin wins
|
||||
for ep in itertools.chain(local_plugins, entry_points(group="comicapi.archiver")):
|
||||
try:
|
||||
archiver: type[Archiver] = ep.load()
|
||||
if ep.module.startswith("comicapi"):
|
||||
builtin.append(archiver)
|
||||
else:
|
||||
archivers.append(archiver)
|
||||
except Exception:
|
||||
try:
|
||||
spec = importlib.util.find_spec(ep.module)
|
||||
except ValueError:
|
||||
spec = None
|
||||
if spec and spec.has_location:
|
||||
logger.exception("Failed to load archive plugin: %s from %s", ep.name, spec.origin)
|
||||
else:
|
||||
logger.exception("Failed to load archive plugin: %s", ep.name)
|
||||
archivers.extend(builtin)
|
||||
if archivers:
|
||||
return
|
||||
if sys.version_info < (3, 10):
|
||||
from importlib_metadata import entry_points
|
||||
else:
|
||||
from importlib.metadata import entry_points
|
||||
builtin: list[type[Archiver]] = []
|
||||
archive_plugins: list[type[Archiver]] = []
|
||||
# A list is used first matching plugin wins
|
||||
|
||||
for ep in itertools.chain(local_plugins, entry_points(group="comicapi.archiver")):
|
||||
try:
|
||||
spec = importlib.util.find_spec(ep.module)
|
||||
except ValueError:
|
||||
spec = None
|
||||
try:
|
||||
archiver: type[Archiver] = ep.load()
|
||||
|
||||
def load_metadata_plugins(version: str = f"ComicAPI/{version}", local_plugins: Iterable[EntryPoint] = tuple()) -> None:
|
||||
if not metadata_styles:
|
||||
if sys.version_info < (3, 10):
|
||||
from importlib_metadata import entry_points
|
||||
else:
|
||||
from importlib.metadata import entry_points
|
||||
builtin: dict[str, Metadata] = {}
|
||||
styles: dict[str, tuple[Metadata, ModuleSpec | None]] = {}
|
||||
# A dict is used, last plugin wins
|
||||
for ep in itertools.chain(entry_points(group="comicapi.metadata"), local_plugins):
|
||||
try:
|
||||
spec = importlib.util.find_spec(ep.module)
|
||||
except ValueError:
|
||||
spec = None
|
||||
try:
|
||||
style: type[Metadata] = ep.load()
|
||||
if style.enabled:
|
||||
if ep.module.startswith("comicapi"):
|
||||
builtin[style.short_name] = style(version)
|
||||
else:
|
||||
if style.short_name in styles:
|
||||
if spec and spec.has_location:
|
||||
logger.warning(
|
||||
"Plugin %s from %s is overriding the existing metadata plugin for %s tags",
|
||||
ep.module,
|
||||
spec.origin,
|
||||
style.short_name,
|
||||
)
|
||||
else:
|
||||
logger.warning(
|
||||
"Plugin %s is overriding the existing metadata plugin for %s tags",
|
||||
ep.module,
|
||||
style.short_name,
|
||||
)
|
||||
styles[style.short_name] = (style(version), spec)
|
||||
except Exception:
|
||||
if spec and spec.has_location:
|
||||
logger.exception("Failed to load metadata plugin: %s from %s", ep.name, spec.origin)
|
||||
else:
|
||||
logger.exception("Failed to load metadata plugin: %s", ep.name)
|
||||
for style_name in set(builtin.keys()).intersection(styles):
|
||||
spec = styles[style_name][1]
|
||||
if spec and spec.has_location:
|
||||
logger.warning(
|
||||
"Builtin metadata for %s tags are being overridden by a plugin from %s", style_name, spec.origin
|
||||
)
|
||||
if ep.module.startswith("comicapi"):
|
||||
builtin.append(archiver)
|
||||
else:
|
||||
logger.warning("Builtin metadata for %s tags are being overridden by a plugin", style_name)
|
||||
metadata_styles.clear()
|
||||
metadata_styles.update(builtin)
|
||||
metadata_styles.update({s[0]: s[1][0] for s in styles.items()})
|
||||
archive_plugins.append(archiver)
|
||||
except Exception:
|
||||
if spec and spec.has_location:
|
||||
logger.exception("Failed to load archive plugin: %s from %s", ep.name, spec.origin)
|
||||
else:
|
||||
logger.exception("Failed to load archive plugin: %s", ep.name)
|
||||
archivers.clear()
|
||||
archivers.extend(archive_plugins)
|
||||
archivers.extend(builtin)
|
||||
|
||||
|
||||
def load_tag_plugins(version: str = f"ComicAPI/{version}", local_plugins: Iterable[EntryPoint] = tuple()) -> None:
|
||||
if tags:
|
||||
return
|
||||
if sys.version_info < (3, 10):
|
||||
from importlib_metadata import entry_points
|
||||
else:
|
||||
from importlib.metadata import entry_points
|
||||
builtin: dict[str, Tag] = {}
|
||||
tag_plugins: dict[str, tuple[Tag, str]] = {}
|
||||
# A dict is used, last plugin wins
|
||||
for ep in itertools.chain(entry_points(group="comicapi.tags"), local_plugins):
|
||||
location = "Unknown"
|
||||
try:
|
||||
_spec = importlib.util.find_spec(ep.module)
|
||||
if _spec and _spec.has_location and _spec.origin:
|
||||
location = _spec.origin
|
||||
except ValueError:
|
||||
location = "Unknown"
|
||||
|
||||
try:
|
||||
tag: type[Tag] = ep.load()
|
||||
|
||||
if ep.module.startswith("comicapi"):
|
||||
builtin[tag.id] = tag(version)
|
||||
else:
|
||||
if tag.id in tag_plugins:
|
||||
logger.warning(
|
||||
"Plugin %s from %s is overriding the existing plugin for %s tags",
|
||||
ep.module,
|
||||
location,
|
||||
tag.id,
|
||||
)
|
||||
tag_plugins[tag.id] = (tag(version), location)
|
||||
except Exception:
|
||||
logger.exception("Failed to load tag plugin: %s from %s", ep.name, location)
|
||||
|
||||
for tag_id in set(builtin.keys()).intersection(tag_plugins):
|
||||
location = tag_plugins[tag_id][1]
|
||||
logger.warning("Builtin plugin for %s tags are being overridden by a plugin from %s", tag_id, location)
|
||||
|
||||
tags.clear()
|
||||
tags.update(builtin)
|
||||
tags.update({s[0]: s[1][0] for s in tag_plugins.items()})
|
||||
|
||||
|
||||
class ComicArchive:
|
||||
@ -145,7 +142,7 @@ class ComicArchive:
|
||||
self.archiver = UnknownArchiver.open(self.path)
|
||||
|
||||
load_archive_plugins()
|
||||
load_metadata_plugins()
|
||||
load_tag_plugins()
|
||||
for archiver in archivers:
|
||||
if archiver.enabled and archiver.is_valid(self.path):
|
||||
self.archiver = archiver.open(self.path)
|
||||
@ -162,15 +159,19 @@ class ComicArchive:
|
||||
self.page_list.clear()
|
||||
self.md.clear()
|
||||
|
||||
def load_cache(self, style_list: Iterable[str]) -> None:
|
||||
for style in style_list:
|
||||
if style in metadata_styles:
|
||||
md = metadata_styles[style].get_metadata(self.archiver)
|
||||
if not md.is_empty:
|
||||
self.md[style] = md
|
||||
def load_cache(self, tag_ids: Iterable[str]) -> None:
|
||||
for tag_id in tag_ids:
|
||||
if tag_id not in tags:
|
||||
continue
|
||||
tag = tags[tag_id]
|
||||
if not tag.enabled:
|
||||
continue
|
||||
md = tag.read_tags(self.archiver)
|
||||
if not md.is_empty:
|
||||
self.md[tag_id] = md
|
||||
|
||||
def get_supported_metadata(self) -> list[str]:
|
||||
return [style[0] for style in metadata_styles.items() if style[1].supports_metadata(self.archiver)]
|
||||
def get_supported_tags(self) -> list[str]:
|
||||
return [tag_id for tag_id, tag in tags.items() if tag.enabled and tag.supports_tags(self.archiver)]
|
||||
|
||||
def rename(self, path: pathlib.Path | str) -> None:
|
||||
new_path = pathlib.Path(path).absolute()
|
||||
@ -193,11 +194,6 @@ class ComicArchive:
|
||||
|
||||
return True
|
||||
|
||||
def is_writable_for_style(self, style: str) -> bool:
|
||||
if style in metadata_styles:
|
||||
return self.archiver.is_writable() and metadata_styles[style].supports_metadata(self.archiver)
|
||||
return False
|
||||
|
||||
def is_zip(self) -> bool:
|
||||
return self.archiver.name() == "ZIP"
|
||||
|
||||
@ -210,33 +206,42 @@ class ComicArchive:
|
||||
def extension(self) -> str:
|
||||
return self.archiver.extension()
|
||||
|
||||
def read_metadata(self, style: str) -> GenericMetadata:
|
||||
if style in self.md:
|
||||
return self.md[style]
|
||||
def read_tags(self, tag_id: str) -> GenericMetadata:
|
||||
if tag_id in self.md:
|
||||
return self.md[tag_id]
|
||||
md = GenericMetadata()
|
||||
if metadata_styles[style].has_metadata(self.archiver):
|
||||
md = metadata_styles[style].get_metadata(self.archiver)
|
||||
tag = tags[tag_id]
|
||||
if tag.enabled and tag.has_tags(self.archiver):
|
||||
md = tag.read_tags(self.archiver)
|
||||
md.apply_default_page_list(self.get_page_name_list())
|
||||
return md
|
||||
|
||||
def read_metadata_string(self, style: str) -> str:
|
||||
return metadata_styles[style].get_metadata_string(self.archiver)
|
||||
def read_raw_tags(self, tag_id: str) -> str:
|
||||
if not tags[tag_id].enabled:
|
||||
return ""
|
||||
return tags[tag_id].read_raw_tags(self.archiver)
|
||||
|
||||
def write_metadata(self, metadata: GenericMetadata, style: str) -> bool:
|
||||
if style in self.md:
|
||||
del self.md[style]
|
||||
def write_tags(self, metadata: GenericMetadata, tag_id: str) -> bool:
|
||||
if tag_id in self.md:
|
||||
del self.md[tag_id]
|
||||
if not tags[tag_id].enabled:
|
||||
return False
|
||||
metadata.apply_default_page_list(self.get_page_name_list())
|
||||
return metadata_styles[style].set_metadata(metadata, self.archiver)
|
||||
return tags[tag_id].write_tags(metadata, self.archiver)
|
||||
|
||||
def has_metadata(self, style: str) -> bool:
|
||||
if style in self.md:
|
||||
def has_tags(self, tag_id: str) -> bool:
|
||||
if tag_id in self.md:
|
||||
return True
|
||||
return metadata_styles[style].has_metadata(self.archiver)
|
||||
if not tags[tag_id].enabled:
|
||||
return False
|
||||
return tags[tag_id].has_tags(self.archiver)
|
||||
|
||||
def remove_metadata(self, style: str) -> bool:
|
||||
if style in self.md:
|
||||
del self.md[style]
|
||||
return metadata_styles[style].remove_metadata(self.archiver)
|
||||
def remove_tags(self, tag_id: str) -> bool:
|
||||
if tag_id in self.md:
|
||||
del self.md[tag_id]
|
||||
if not tags[tag_id].enabled:
|
||||
return False
|
||||
return tags[tag_id].remove_tags(self.archiver)
|
||||
|
||||
def get_page(self, index: int) -> bytes:
|
||||
image_data = b""
|
||||
|
@ -92,7 +92,7 @@ class ComicSeries:
|
||||
return copy.deepcopy(self)
|
||||
|
||||
|
||||
class TagOrigin(NamedTuple):
|
||||
class MetadataOrigin(NamedTuple):
|
||||
id: str
|
||||
name: str
|
||||
|
||||
@ -109,7 +109,7 @@ class GenericMetadata:
|
||||
translator_synonyms = ("translator", "translation")
|
||||
|
||||
is_empty: bool = True
|
||||
tag_origin: TagOrigin | None = None
|
||||
data_origin: MetadataOrigin | None = None
|
||||
issue_id: str | None = None
|
||||
series_id: str | None = None
|
||||
|
||||
@ -233,7 +233,7 @@ class GenericMetadata:
|
||||
if not new_md.is_empty:
|
||||
self.is_empty = False
|
||||
|
||||
self.tag_origin = assign(self.tag_origin, new_md.tag_origin) # TODO use and purpose now?
|
||||
self.data_origin = assign(self.data_origin, new_md.data_origin) # TODO use and purpose now?
|
||||
self.issue_id = assign(self.issue_id, new_md.issue_id)
|
||||
self.series_id = assign(self.series_id, new_md.series_id)
|
||||
|
||||
@ -471,7 +471,7 @@ class GenericMetadata:
|
||||
|
||||
md_test: GenericMetadata = GenericMetadata(
|
||||
is_empty=False,
|
||||
tag_origin=TagOrigin("comicvine", "Comic Vine"),
|
||||
data_origin=MetadataOrigin("comicvine", "Comic Vine"),
|
||||
series="Cory Doctorow's Futuristic Tales of the Here and Now",
|
||||
series_id="23437",
|
||||
issue="1",
|
||||
@ -573,6 +573,6 @@ __all__ = (
|
||||
"ImageMetadata",
|
||||
"Credit",
|
||||
"ComicSeries",
|
||||
"TagOrigin",
|
||||
"MetadataOrigin",
|
||||
"GenericMetadata",
|
||||
)
|
||||
|
@ -1,5 +0,0 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from comicapi.metadata.metadata import Metadata
|
||||
|
||||
__all__ = ["Metadata"]
|
5
comicapi/tags/__init__.py
Normal file
5
comicapi/tags/__init__.py
Normal file
@ -0,0 +1,5 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from comicapi.tags.tag import Tag
|
||||
|
||||
__all__ = ["Tag"]
|
@ -25,15 +25,15 @@ from comicapi import utils
|
||||
from comicapi.archivers import Archiver
|
||||
from comicapi.comicarchive import ComicArchive
|
||||
from comicapi.genericmetadata import GenericMetadata, ImageMetadata, PageType
|
||||
from comicapi.metadata import Metadata
|
||||
from comicapi.tags import Tag
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class CoMet(Metadata):
|
||||
class CoMet(Tag):
|
||||
enabled = True
|
||||
|
||||
short_name = "comet"
|
||||
id = "comet"
|
||||
|
||||
def __init__(self, version: str) -> None:
|
||||
super().__init__(version)
|
||||
@ -71,13 +71,13 @@ class CoMet(Metadata):
|
||||
def supports_credit_role(self, role: str) -> bool:
|
||||
return role.casefold() in self._get_parseable_credits()
|
||||
|
||||
def supports_metadata(self, archive: Archiver) -> bool:
|
||||
def supports_tags(self, archive: Archiver) -> bool:
|
||||
return archive.supports_files()
|
||||
|
||||
def has_metadata(self, archive: Archiver) -> bool:
|
||||
if not self.supports_metadata(archive):
|
||||
def has_tags(self, archive: Archiver) -> bool:
|
||||
if not self.supports_tags(archive):
|
||||
return False
|
||||
has_metadata = False
|
||||
has_tags = False
|
||||
# look at all xml files in root, and search for CoMet data, get first
|
||||
for n in archive.get_filename_list():
|
||||
if os.path.dirname(n) == "" and os.path.splitext(n)[1].casefold() == ".xml":
|
||||
@ -90,33 +90,33 @@ class CoMet(Metadata):
|
||||
if self._validate_bytes(data):
|
||||
# since we found it, save it!
|
||||
self.file = n
|
||||
has_metadata = True
|
||||
has_tags = True
|
||||
break
|
||||
return has_metadata
|
||||
return has_tags
|
||||
|
||||
def remove_metadata(self, archive: Archiver) -> bool:
|
||||
return self.has_metadata(archive) and archive.remove_file(self.file)
|
||||
def remove_tags(self, archive: Archiver) -> bool:
|
||||
return self.has_tags(archive) and archive.remove_file(self.file)
|
||||
|
||||
def get_metadata(self, archive: Archiver) -> GenericMetadata:
|
||||
if self.has_metadata(archive):
|
||||
def read_tags(self, archive: Archiver) -> GenericMetadata:
|
||||
if self.has_tags(archive):
|
||||
metadata = archive.read_file(self.file) or b""
|
||||
if self._validate_bytes(metadata):
|
||||
return self._metadata_from_bytes(metadata, archive)
|
||||
return GenericMetadata()
|
||||
|
||||
def get_metadata_string(self, archive: Archiver) -> str:
|
||||
if self.has_metadata(archive):
|
||||
def read_raw_tags(self, archive: Archiver) -> str:
|
||||
if self.has_tags(archive):
|
||||
return ET.tostring(ET.fromstring(archive.read_file(self.file)), encoding="unicode", xml_declaration=True)
|
||||
return ""
|
||||
|
||||
def set_metadata(self, metadata: GenericMetadata, archive: Archiver) -> bool:
|
||||
if self.supports_metadata(archive):
|
||||
def write_tags(self, metadata: GenericMetadata, archive: Archiver) -> bool:
|
||||
if self.supports_tags(archive):
|
||||
success = True
|
||||
xml = b""
|
||||
if self.has_metadata(archive):
|
||||
if self.has_tags(archive):
|
||||
xml = archive.read_file(self.file)
|
||||
if self.file != self.comet_filename:
|
||||
success = self.remove_metadata(archive)
|
||||
success = self.remove_tags(archive)
|
||||
|
||||
return success and archive.write_file(self.comet_filename, self._bytes_from_metadata(metadata, xml))
|
||||
else:
|
@ -23,7 +23,7 @@ from typing import Any, Literal, TypedDict
|
||||
from comicapi import utils
|
||||
from comicapi.archivers import Archiver
|
||||
from comicapi.genericmetadata import Credit, GenericMetadata
|
||||
from comicapi.metadata import Metadata
|
||||
from comicapi.tags import Tag
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@ -75,10 +75,10 @@ class _ComicBookInfoJson(TypedDict, total=False):
|
||||
_CBIContainer = TypedDict("_CBIContainer", {"appID": str, "lastModified": str, "ComicBookInfo/1.0": _ComicBookInfoJson})
|
||||
|
||||
|
||||
class ComicBookInfo(Metadata):
|
||||
class ComicBookInfo(Tag):
|
||||
enabled = True
|
||||
|
||||
short_name = "cbi"
|
||||
id = "cbi"
|
||||
|
||||
def __init__(self, version: str) -> None:
|
||||
super().__init__(version)
|
||||
@ -108,29 +108,29 @@ class ComicBookInfo(Metadata):
|
||||
def supports_credit_role(self, role: str) -> bool:
|
||||
return True
|
||||
|
||||
def supports_metadata(self, archive: Archiver) -> bool:
|
||||
def supports_tags(self, archive: Archiver) -> bool:
|
||||
return archive.supports_comment()
|
||||
|
||||
def has_metadata(self, archive: Archiver) -> bool:
|
||||
return self.supports_metadata(archive) and self._validate_string(archive.get_comment())
|
||||
def has_tags(self, archive: Archiver) -> bool:
|
||||
return self.supports_tags(archive) and self._validate_string(archive.get_comment())
|
||||
|
||||
def remove_metadata(self, archive: Archiver) -> bool:
|
||||
def remove_tags(self, archive: Archiver) -> bool:
|
||||
return archive.set_comment("")
|
||||
|
||||
def get_metadata(self, archive: Archiver) -> GenericMetadata:
|
||||
if self.has_metadata(archive):
|
||||
def read_tags(self, archive: Archiver) -> GenericMetadata:
|
||||
if self.has_tags(archive):
|
||||
comment = archive.get_comment()
|
||||
if self._validate_string(comment):
|
||||
return self._metadata_from_string(comment)
|
||||
return GenericMetadata()
|
||||
|
||||
def get_metadata_string(self, archive: Archiver) -> str:
|
||||
if self.has_metadata(archive):
|
||||
def read_raw_tags(self, archive: Archiver) -> str:
|
||||
if self.has_tags(archive):
|
||||
return json.dumps(json.loads(archive.get_comment()), indent=2)
|
||||
return ""
|
||||
|
||||
def set_metadata(self, metadata: GenericMetadata, archive: Archiver) -> bool:
|
||||
if self.supports_metadata(archive):
|
||||
def write_tags(self, metadata: GenericMetadata, archive: Archiver) -> bool:
|
||||
if self.supports_tags(archive):
|
||||
return archive.set_comment(self._string_from_metadata(metadata))
|
||||
else:
|
||||
logger.warning(f"Archive ({archive.name()}) does not support {self.name()} metadata")
|
@ -23,15 +23,15 @@ from typing import Any
|
||||
from comicapi import utils
|
||||
from comicapi.archivers import Archiver
|
||||
from comicapi.genericmetadata import GenericMetadata, ImageMetadata
|
||||
from comicapi.metadata import Metadata
|
||||
from comicapi.tags import Tag
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class ComicRack(Metadata):
|
||||
class ComicRack(Tag):
|
||||
enabled = True
|
||||
|
||||
short_name = "cr"
|
||||
id = "cr"
|
||||
|
||||
def __init__(self, version: str) -> None:
|
||||
super().__init__(version)
|
||||
@ -84,35 +84,35 @@ class ComicRack(Metadata):
|
||||
def supports_credit_role(self, role: str) -> bool:
|
||||
return role.casefold() in self._get_parseable_credits()
|
||||
|
||||
def supports_metadata(self, archive: Archiver) -> bool:
|
||||
def supports_tags(self, archive: Archiver) -> bool:
|
||||
return archive.supports_files()
|
||||
|
||||
def has_metadata(self, archive: Archiver) -> bool:
|
||||
def has_tags(self, archive: Archiver) -> bool:
|
||||
return (
|
||||
self.supports_metadata(archive)
|
||||
self.supports_tags(archive)
|
||||
and self.file in archive.get_filename_list()
|
||||
and self._validate_bytes(archive.read_file(self.file))
|
||||
)
|
||||
|
||||
def remove_metadata(self, archive: Archiver) -> bool:
|
||||
return self.has_metadata(archive) and archive.remove_file(self.file)
|
||||
def remove_tags(self, archive: Archiver) -> bool:
|
||||
return self.has_tags(archive) and archive.remove_file(self.file)
|
||||
|
||||
def get_metadata(self, archive: Archiver) -> GenericMetadata:
|
||||
if self.has_metadata(archive):
|
||||
def read_tags(self, archive: Archiver) -> GenericMetadata:
|
||||
if self.has_tags(archive):
|
||||
metadata = archive.read_file(self.file) or b""
|
||||
if self._validate_bytes(metadata):
|
||||
return self._metadata_from_bytes(metadata)
|
||||
return GenericMetadata()
|
||||
|
||||
def get_metadata_string(self, archive: Archiver) -> str:
|
||||
if self.has_metadata(archive):
|
||||
def read_raw_tags(self, archive: Archiver) -> str:
|
||||
if self.has_tags(archive):
|
||||
return ET.tostring(ET.fromstring(archive.read_file(self.file)), encoding="unicode", xml_declaration=True)
|
||||
return ""
|
||||
|
||||
def set_metadata(self, metadata: GenericMetadata, archive: Archiver) -> bool:
|
||||
if self.supports_metadata(archive):
|
||||
def write_tags(self, metadata: GenericMetadata, archive: Archiver) -> bool:
|
||||
if self.supports_tags(archive):
|
||||
xml = b""
|
||||
if self.has_metadata(archive):
|
||||
if self.has_tags(archive):
|
||||
xml = archive.read_file(self.file)
|
||||
return archive.write_file(self.file, self._bytes_from_metadata(metadata, xml))
|
||||
else:
|
@ -4,14 +4,14 @@ from comicapi.archivers import Archiver
|
||||
from comicapi.genericmetadata import GenericMetadata
|
||||
|
||||
|
||||
class Metadata:
|
||||
class Tag:
|
||||
enabled: bool = False
|
||||
short_name: str = ""
|
||||
id: str = ""
|
||||
|
||||
def __init__(self, version: str) -> None:
|
||||
self.version: str = version
|
||||
self.supported_attributes = {
|
||||
"tag_origin",
|
||||
"data_origin",
|
||||
"issue_id",
|
||||
"series_id",
|
||||
"series",
|
||||
@ -71,44 +71,44 @@ class Metadata:
|
||||
def supports_credit_role(self, role: str) -> bool:
|
||||
return False
|
||||
|
||||
def supports_metadata(self, archive: Archiver) -> bool:
|
||||
def supports_tags(self, archive: Archiver) -> bool:
|
||||
"""
|
||||
Checks the given archive for the ability to save this metadata style.
|
||||
Checks the given archive for the ability to save these tags.
|
||||
Should always return a bool. Failures should return False.
|
||||
Typically consists of a call to either `archive.supports_comment` or `archive.supports_file`
|
||||
"""
|
||||
return False
|
||||
|
||||
def has_metadata(self, archive: Archiver) -> bool:
|
||||
def has_tags(self, archive: Archiver) -> bool:
|
||||
"""
|
||||
Checks the given archive for metadata.
|
||||
Checks the given archive for tags.
|
||||
Should always return a bool. Failures should return False.
|
||||
"""
|
||||
return False
|
||||
|
||||
def remove_metadata(self, archive: Archiver) -> bool:
|
||||
def remove_tags(self, archive: Archiver) -> bool:
|
||||
"""
|
||||
Removes the metadata from the given archive.
|
||||
Removes the tags from the given archive.
|
||||
Should always return a bool. Failures should return False.
|
||||
"""
|
||||
return False
|
||||
|
||||
def get_metadata(self, archive: Archiver) -> GenericMetadata:
|
||||
def read_tags(self, archive: Archiver) -> GenericMetadata:
|
||||
"""
|
||||
Returns a GenericMetadata representing the data saved in the given archive.
|
||||
Returns a GenericMetadata representing the tags saved in the given archive.
|
||||
Should always return a GenericMetadata. Failures should return an empty metadata object.
|
||||
"""
|
||||
return GenericMetadata()
|
||||
|
||||
def get_metadata_string(self, archive: Archiver) -> str:
|
||||
def read_raw_tags(self, archive: Archiver) -> str:
|
||||
"""
|
||||
Returns the raw metadata as a string.
|
||||
If the metadata is a binary format a roughly similar text format should be used.
|
||||
Returns the raw tags as a string.
|
||||
If the tags are a binary format a roughly similar text format should be used.
|
||||
Should always return a string. Failures should return the empty string.
|
||||
"""
|
||||
return ""
|
||||
|
||||
def set_metadata(self, metadata: GenericMetadata, archive: Archiver) -> bool:
|
||||
def write_tags(self, metadata: GenericMetadata, archive: Archiver) -> bool:
|
||||
"""
|
||||
Saves the given metadata to the given archive.
|
||||
Should always return a bool. Failures should return False.
|
||||
@ -117,7 +117,7 @@ class Metadata:
|
||||
|
||||
def name(self) -> str:
|
||||
"""
|
||||
Returns the name of this metadata for display purposes eg "Comic Rack".
|
||||
Returns the name of these tags for display purposes eg "Comic Rack".
|
||||
Should always return a string. Failures should return the empty string.
|
||||
"""
|
||||
return ""
|
@ -32,10 +32,9 @@ from comicfn2dict import comicfn2dict
|
||||
|
||||
import comicapi.data
|
||||
from comicapi import filenamelexer, filenameparser
|
||||
|
||||
from ._url import LocationParseError as LocationParseError # noqa: F401
|
||||
from ._url import Url as Url
|
||||
from ._url import parse_url as parse_url
|
||||
from comicapi._url import LocationParseError as LocationParseError # noqa: F401
|
||||
from comicapi._url import Url as Url
|
||||
from comicapi._url import parse_url as parse_url
|
||||
|
||||
try:
|
||||
import icu
|
||||
|
@ -22,7 +22,7 @@ from typing import Callable
|
||||
|
||||
from PyQt5 import QtCore, QtGui, QtWidgets, uic
|
||||
|
||||
from comicapi.comicarchive import ComicArchive, metadata_styles
|
||||
from comicapi.comicarchive import ComicArchive, tags
|
||||
from comicapi.genericmetadata import GenericMetadata
|
||||
from comictaggerlib.coverimagewidget import CoverImageWidget
|
||||
from comictaggerlib.ctsettings import ct_ns
|
||||
@ -40,7 +40,7 @@ class AutoTagMatchWindow(QtWidgets.QDialog):
|
||||
self,
|
||||
parent: QtWidgets.QWidget,
|
||||
match_set_list: list[Result],
|
||||
load_styles: list[str],
|
||||
read_tags: list[str],
|
||||
fetch_func: Callable[[IssueResult], GenericMetadata],
|
||||
config: ct_ns,
|
||||
talker: ComicTalker,
|
||||
@ -82,7 +82,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 = load_styles
|
||||
self._tags = read_tags
|
||||
self.fetch_func = fetch_func
|
||||
|
||||
self.current_match_set_idx = 0
|
||||
@ -230,14 +230,14 @@ 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)
|
||||
md, error = self.parent().read_all_tags(self._tags, ca)
|
||||
if error is not None:
|
||||
logger.error("Failed to load metadata for %s: %s", ca.path, error)
|
||||
logger.error("Failed to load tags 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",
|
||||
f"One or more of the read tags failed to load for {ca.path}, check log for details",
|
||||
)
|
||||
return
|
||||
|
||||
@ -264,14 +264,14 @@ class AutoTagMatchWindow(QtWidgets.QDialog):
|
||||
|
||||
QtWidgets.QApplication.setOverrideCursor(QtGui.QCursor(QtCore.Qt.CursorShape.WaitCursor))
|
||||
md = prepare_metadata(md, ct_md, self.config)
|
||||
for style in self._styles:
|
||||
success = ca.write_metadata(md, style)
|
||||
for tag_id in self._tags:
|
||||
success = ca.write_tags(md, tag_id)
|
||||
QtWidgets.QApplication.restoreOverrideCursor()
|
||||
if not success:
|
||||
QtWidgets.QMessageBox.warning(
|
||||
self,
|
||||
"Write Error",
|
||||
f"Saving {metadata_styles[style].name()} the tags to the archive seemed to fail!",
|
||||
f"Saving {tags[tag_id].name()} the tags to the archive seemed to fail!",
|
||||
)
|
||||
break
|
||||
|
||||
|
@ -22,13 +22,13 @@ import json
|
||||
import logging
|
||||
import os
|
||||
import pathlib
|
||||
import re
|
||||
import sys
|
||||
from collections.abc import Collection
|
||||
from typing import Any, TextIO
|
||||
|
||||
from comicapi import merge, utils
|
||||
from comicapi.comicarchive import ComicArchive
|
||||
from comicapi.comicarchive import metadata_styles as md_styles
|
||||
from comicapi.comicarchive import ComicArchive, tags
|
||||
from comicapi.genericmetadata import GenericMetadata
|
||||
from comictaggerlib.cbltransformer import CBLTransformer
|
||||
from comictaggerlib.ctsettings import ct_ns
|
||||
@ -119,7 +119,7 @@ class CLI:
|
||||
)
|
||||
return return_code
|
||||
|
||||
def actual_issue_data_fetch(self, issue_id: str) -> GenericMetadata:
|
||||
def fetch_metadata(self, issue_id: str) -> GenericMetadata:
|
||||
# now get the particular issue data
|
||||
try:
|
||||
ct_md = self.current_talker().fetch_comic_data(issue_id)
|
||||
@ -132,12 +132,12 @@ class CLI:
|
||||
|
||||
return ct_md
|
||||
|
||||
def actual_metadata_save(self, ca: ComicArchive, md: GenericMetadata) -> bool:
|
||||
def write_tags(self, ca: ComicArchive, md: GenericMetadata) -> bool:
|
||||
if not self.config.Runtime_Options__dryrun:
|
||||
for style in self.config.Runtime_Options__type_modify:
|
||||
for tag_id in self.config.Runtime_Options__tags_write:
|
||||
# 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())
|
||||
if not ca.write_tags(md, tag_id):
|
||||
logger.error("The tag save seemed to fail for: %s!", tags[tag_id].name())
|
||||
return False
|
||||
|
||||
self.output("Save complete.")
|
||||
@ -177,12 +177,12 @@ class CLI:
|
||||
# save the data!
|
||||
# we know at this point, that the file is all good to go
|
||||
ca = ComicArchive(match_set.original_path)
|
||||
md = self.create_local_metadata(ca)
|
||||
ct_md = self.actual_issue_data_fetch(match_set.online_results[int(i) - 1].issue_id)
|
||||
md, match_set.tags_read = self.create_local_metadata(ca, self.config.Runtime_Options__tags_read)
|
||||
ct_md = self.fetch_metadata(match_set.online_results[int(i) - 1].issue_id)
|
||||
|
||||
match_set.md = prepare_metadata(md, ct_md, self.config)
|
||||
|
||||
self.actual_metadata_save(ca, md)
|
||||
self.write_tags(ca, md)
|
||||
|
||||
def post_process_matches(self, match_results: OnlineMatchResults) -> None:
|
||||
def print_header(header: str) -> None:
|
||||
@ -231,12 +231,14 @@ class CLI:
|
||||
|
||||
self.display_match_set_for_choice(label, match_set)
|
||||
|
||||
def create_local_metadata(self, ca: ComicArchive) -> GenericMetadata:
|
||||
def create_local_metadata(
|
||||
self, ca: ComicArchive, tags_to_read: list[str], /, tags_only: bool = False
|
||||
) -> tuple[GenericMetadata, list[str]]:
|
||||
md = GenericMetadata()
|
||||
md.apply_default_page_list(ca.get_page_name_list())
|
||||
|
||||
# now, overlay the parsed filename info
|
||||
if self.config.Auto_Tag__parse_filename:
|
||||
if self.config.Auto_Tag__parse_filename and not tags_only:
|
||||
f_md = ca.metadata_from_filename(
|
||||
self.config.Filename_Parsing__filename_parser,
|
||||
self.config.Filename_Parsing__remove_c2c,
|
||||
@ -249,24 +251,29 @@ class CLI:
|
||||
|
||||
md.overlay(f_md)
|
||||
|
||||
for style in self.config.Runtime_Options__type_read:
|
||||
if ca.has_metadata(style):
|
||||
tags_used = []
|
||||
for tag_id in tags_to_read:
|
||||
if ca.has_tags(tag_id):
|
||||
try:
|
||||
t_md = ca.read_metadata(style)
|
||||
md.overlay(
|
||||
t_md, self.config.Metadata_Options__comic_merge, self.config.Metadata_Options__comic_merge_lists
|
||||
)
|
||||
break
|
||||
t_md = ca.read_tags(tag_id)
|
||||
if not t_md.is_empty:
|
||||
md.overlay(
|
||||
t_md,
|
||||
self.config.Metadata_Options__tag_merge,
|
||||
self.config.Metadata_Options__tag_merge_lists,
|
||||
)
|
||||
tags_used.append(tag_id)
|
||||
except Exception as e:
|
||||
logger.error("Failed to load metadata for %s: %s", ca.path, e)
|
||||
logger.error("Failed to read tags from %s: %s", ca.path, e)
|
||||
|
||||
# finally, use explicit stuff (always 'overlay' mode)
|
||||
md.overlay(self.config.Auto_Tag__metadata, mode=merge.Mode.OVERLAY, merge_lists=True)
|
||||
if not tags_only:
|
||||
# finally, use explicit stuff (always 'overlay' mode)
|
||||
md.overlay(self.config.Auto_Tag__metadata, mode=merge.Mode.OVERLAY, merge_lists=True)
|
||||
|
||||
return md
|
||||
return (md, tags_used)
|
||||
|
||||
def print(self, ca: ComicArchive) -> Result:
|
||||
if not self.config.Runtime_Options__type_read:
|
||||
if not self.config.Runtime_Options__tags_read:
|
||||
page_count = ca.get_number_of_pages()
|
||||
|
||||
brief = ""
|
||||
@ -279,8 +286,8 @@ class CLI:
|
||||
brief += f"({page_count: >3} pages)"
|
||||
brief += " tags:[ "
|
||||
|
||||
metadata_styles = [md_styles[style].name() for style in md_styles if ca.has_metadata(style)]
|
||||
brief += " ".join(metadata_styles)
|
||||
tag_names = [tags[tag_id].name() for tag_id in tags if ca.has_tags(tag_id)]
|
||||
brief += " ".join(tag_names)
|
||||
brief += " ]"
|
||||
|
||||
self.output(brief)
|
||||
@ -291,105 +298,110 @@ class CLI:
|
||||
self.output()
|
||||
|
||||
md = None
|
||||
for style, style_obj in md_styles.items():
|
||||
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 ---------")
|
||||
for tag_id, tag in tags.items():
|
||||
if not self.config.Runtime_Options__tags_read or tag_id in self.config.Runtime_Options__tags_read:
|
||||
if ca.has_tags(tag_id):
|
||||
self.output(f"--------- {tag.name()} tags ---------")
|
||||
try:
|
||||
if self.config.Runtime_Options__raw:
|
||||
self.output(ca.read_metadata_string(style))
|
||||
self.output(ca.read_raw_tags(tag_id))
|
||||
else:
|
||||
md = ca.read_metadata(style)
|
||||
md = ca.read_tags(tag_id)
|
||||
self.output(md)
|
||||
except Exception as e:
|
||||
logger.error("Failed to load metadata for %s: %s", ca.path, e)
|
||||
logger.error("Failed to read tags from %s: %s", ca.path, e)
|
||||
return Result(Action.print, Status.success, ca.path, md=md)
|
||||
|
||||
def delete_style(self, ca: ComicArchive, style: str) -> Status:
|
||||
style_name = md_styles[style].name()
|
||||
def delete_tags(self, ca: ComicArchive, tag_id: str) -> Status:
|
||||
tag_name = tags[tag_id].name()
|
||||
|
||||
if ca.has_metadata(style):
|
||||
if ca.has_tags(tag_id):
|
||||
if not self.config.Runtime_Options__dryrun:
|
||||
if ca.remove_metadata(style):
|
||||
self.output(f"{ca.path}: Removed {style_name} tags.")
|
||||
if ca.remove_tags(tag_id):
|
||||
self.output(f"{ca.path}: Removed {tag_name} tags.")
|
||||
return Status.success
|
||||
else:
|
||||
self.output(f"{ca.path}: Tag removal seemed to fail!")
|
||||
return Status.write_failure
|
||||
else:
|
||||
self.output(f"{ca.path}: dry-run. {style_name} tags not removed")
|
||||
self.output(f"{ca.path}: dry-run. {tag_name} tags not removed")
|
||||
return Status.success
|
||||
self.output(f"{ca.path}: This archive doesn't have {style_name} tags to remove.")
|
||||
self.output(f"{ca.path}: This archive doesn't have {tag_name} tags to remove.")
|
||||
return Status.success
|
||||
|
||||
def delete(self, ca: ComicArchive) -> Result:
|
||||
res = Result(Action.delete, Status.success, ca.path)
|
||||
for style in self.config.Runtime_Options__type_modify:
|
||||
status = self.delete_style(ca, style)
|
||||
for tag_id in self.config.Runtime_Options__tags_write:
|
||||
status = self.delete_tags(ca, tag_id)
|
||||
if status == Status.success:
|
||||
res.tags_deleted.append(style)
|
||||
res.tags_deleted.append(tag_id)
|
||||
else:
|
||||
res.status = status
|
||||
return res
|
||||
|
||||
def copy_style(self, ca: ComicArchive, md: GenericMetadata, style: str) -> Status:
|
||||
dst_style_name = md_styles[style].name()
|
||||
if not self.config.Runtime_Options__skip_existing_metadata and ca.has_metadata(style):
|
||||
self.output(f"{ca.path}: Already has {dst_style_name} tags. Not overwriting.")
|
||||
return Status.existing_tags
|
||||
if self.config.Commands__copy == style:
|
||||
self.output(f"{ca.path}: Destination and source are same: {dst_style_name}. Nothing to do.")
|
||||
def _copy_tags(self, ca: ComicArchive, md: GenericMetadata, source_names: str, dst_tag_id: str) -> Status:
|
||||
dst_tag_name = tags[dst_tag_id].name()
|
||||
if not self.config.Runtime_Options__skip_existing_tags and ca.has_tags(dst_tag_id):
|
||||
self.output(f"{ca.path}: Already has {dst_tag_name} tags. Not overwriting.")
|
||||
return Status.existing_tags
|
||||
|
||||
if len(self.config.Commands__copy) == 1 and dst_tag_id in self.config.Commands__copy:
|
||||
self.output(f"{ca.path}: Destination and source are same: {dst_tag_name}. Nothing to do.")
|
||||
return Status.existing_tags
|
||||
|
||||
src_style_name = md_styles[self.config.Commands__copy].name()
|
||||
if not self.config.Runtime_Options__dryrun:
|
||||
if self.config.Metadata_Options__apply_transform_on_bulk_operation and style == "cbi":
|
||||
if self.config.Metadata_Options__apply_transform_on_bulk_operation and dst_tag_id == "cbi":
|
||||
md = CBLTransformer(md, self.config).apply()
|
||||
|
||||
if ca.write_metadata(md, style):
|
||||
self.output(f"{ca.path}: Copied {src_style_name} tags to {dst_style_name}.")
|
||||
return Status.success
|
||||
if ca.write_tags(md, dst_tag_id):
|
||||
self.output(f"{ca.path}: Copied {source_names} tags to {dst_tag_name}.")
|
||||
else:
|
||||
self.output(f"{ca.path}: Tag copy seemed to fail!")
|
||||
return Status.write_failure
|
||||
else:
|
||||
self.output(f"{ca.path}: dry-run. {src_style_name} tags not copied")
|
||||
return Status.success
|
||||
return Status.read_failure
|
||||
self.output(f"{ca.path}: dry-run. {source_names} tags not copied")
|
||||
return Status.success
|
||||
|
||||
def copy(self, ca: ComicArchive) -> Result:
|
||||
src_style_name = md_styles[self.config.Commands__copy].name()
|
||||
res = Result(Action.copy, Status.success, ca.path)
|
||||
if not ca.has_metadata(self.config.Commands__copy):
|
||||
self.output(f"{ca.path}: This archive doesn't have {src_style_name} tags to copy.")
|
||||
src_tag_names = []
|
||||
for src_tag_id in self.config.Commands__copy:
|
||||
src_tag_names.append(tags[src_tag_id].name())
|
||||
if ca.has_tags(src_tag_id):
|
||||
res.tags_read.append(src_tag_id)
|
||||
|
||||
if not res.tags_read:
|
||||
self.output(f"{ca.path}: This archive doesn't have any {', '.join(src_tag_names)} tags to copy.")
|
||||
res.status = Status.read_failure
|
||||
return res
|
||||
try:
|
||||
res.md = ca.read_metadata(self.config.Commands__copy)
|
||||
res.md, res.tags_read = self.create_local_metadata(ca, res.tags_read, tags_only=True)
|
||||
except Exception as e:
|
||||
logger.error("Failed to load metadata for %s: %s", ca.path, e)
|
||||
logger.error("Failed to read tags from %s: %s", ca.path, e)
|
||||
return res
|
||||
for style in self.config.Runtime_Options__type_modify:
|
||||
if style == src_style_name:
|
||||
|
||||
for dst_tag_id in self.config.Runtime_Options__tags_write:
|
||||
if dst_tag_id in self.config.Commands__copy:
|
||||
continue
|
||||
status = self.copy_style(ca, res.md, style)
|
||||
|
||||
status = self._copy_tags(ca, res.md, ", ".join(src_tag_names), dst_tag_id)
|
||||
if status == Status.success:
|
||||
res.tags_written.append(style)
|
||||
res.tags_written.append(dst_tag_id)
|
||||
else:
|
||||
res.status = status
|
||||
return res
|
||||
|
||||
def save(self, ca: ComicArchive, match_results: OnlineMatchResults) -> tuple[Result, OnlineMatchResults]:
|
||||
if not self.config.Runtime_Options__skip_existing_metadata:
|
||||
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.")
|
||||
if not self.config.Runtime_Options__skip_existing_tags:
|
||||
for tag_id in self.config.Runtime_Options__tags_write:
|
||||
if ca.has_tags(tag_id):
|
||||
self.output(f"{ca.path}: Already has {tags[tag_id].name()} tags. Not overwriting.")
|
||||
return (
|
||||
Result(
|
||||
Action.save,
|
||||
original_path=ca.path,
|
||||
status=Status.existing_tags,
|
||||
tags_written=self.config.Runtime_Options__type_modify,
|
||||
tags_written=self.config.Runtime_Options__tags_write,
|
||||
),
|
||||
match_results,
|
||||
)
|
||||
@ -397,7 +409,7 @@ class CLI:
|
||||
if self.batch_mode:
|
||||
self.output(f"Processing {utils.path_to_short_str(ca.path)}...")
|
||||
|
||||
md = self.create_local_metadata(ca)
|
||||
md, tags_read = self.create_local_metadata(ca, self.config.Runtime_Options__tags_read)
|
||||
if md.issue is None or md.issue == "":
|
||||
if self.config.Auto_Tag__assume_issue_one:
|
||||
md.issue = "1"
|
||||
@ -417,7 +429,8 @@ class CLI:
|
||||
Action.save,
|
||||
original_path=ca.path,
|
||||
status=Status.fetch_data_failure,
|
||||
tags_written=self.config.Runtime_Options__type_modify,
|
||||
tags_written=self.config.Runtime_Options__tags_write,
|
||||
tags_read=tags_read,
|
||||
)
|
||||
match_results.fetch_data_failures.append(res)
|
||||
return res, match_results
|
||||
@ -429,7 +442,8 @@ class CLI:
|
||||
status=Status.match_failure,
|
||||
original_path=ca.path,
|
||||
match_status=MatchStatus.no_match,
|
||||
tags_written=self.config.Runtime_Options__type_modify,
|
||||
tags_written=self.config.Runtime_Options__tags_write,
|
||||
tags_read=tags_read,
|
||||
)
|
||||
match_results.no_matches.append(res)
|
||||
return res, match_results
|
||||
@ -442,7 +456,8 @@ class CLI:
|
||||
status=Status.match_failure,
|
||||
original_path=ca.path,
|
||||
match_status=MatchStatus.no_match,
|
||||
tags_written=self.config.Runtime_Options__type_modify,
|
||||
tags_written=self.config.Runtime_Options__tags_write,
|
||||
tags_read=tags_read,
|
||||
)
|
||||
match_results.no_matches.append(res)
|
||||
return res, match_results
|
||||
@ -454,10 +469,10 @@ class CLI:
|
||||
self.output(text)
|
||||
|
||||
ii.set_output_function(functools.partial(self.output, already_logged=True))
|
||||
# use our overlaid MD to search
|
||||
|
||||
if not self.config.Auto_Tag__use_year_when_identifying:
|
||||
md.year = None
|
||||
if self.config.Auto_Tag__ignore_leading_numbers_in_filename and md.series is not None:
|
||||
md.series = re.sub(r"^([\d.]+)(.*)", r"\2", md.series)
|
||||
result, matches = ii.identify(ca, md)
|
||||
|
||||
found_match = False
|
||||
@ -488,7 +503,8 @@ class CLI:
|
||||
original_path=ca.path,
|
||||
online_results=matches,
|
||||
match_status=MatchStatus.low_confidence_match,
|
||||
tags_written=self.config.Runtime_Options__type_modify,
|
||||
tags_written=self.config.Runtime_Options__tags_write,
|
||||
tags_read=tags_read,
|
||||
)
|
||||
match_results.low_confidence_matches.append(res)
|
||||
return res, match_results
|
||||
@ -500,7 +516,8 @@ class CLI:
|
||||
original_path=ca.path,
|
||||
online_results=matches,
|
||||
match_status=MatchStatus.multiple_match,
|
||||
tags_written=self.config.Runtime_Options__type_modify,
|
||||
tags_written=self.config.Runtime_Options__tags_write,
|
||||
tags_read=tags_read,
|
||||
)
|
||||
match_results.multiple_matches.append(res)
|
||||
return res, match_results
|
||||
@ -512,7 +529,8 @@ class CLI:
|
||||
original_path=ca.path,
|
||||
online_results=matches,
|
||||
match_status=MatchStatus.low_confidence_match,
|
||||
tags_written=self.config.Runtime_Options__type_modify,
|
||||
tags_written=self.config.Runtime_Options__tags_write,
|
||||
tags_read=tags_read,
|
||||
)
|
||||
match_results.low_confidence_matches.append(res)
|
||||
return res, match_results
|
||||
@ -524,7 +542,8 @@ class CLI:
|
||||
original_path=ca.path,
|
||||
online_results=matches,
|
||||
match_status=MatchStatus.no_match,
|
||||
tags_written=self.config.Runtime_Options__type_modify,
|
||||
tags_written=self.config.Runtime_Options__tags_write,
|
||||
tags_read=tags_read,
|
||||
)
|
||||
match_results.no_matches.append(res)
|
||||
return res, match_results
|
||||
@ -532,7 +551,7 @@ class CLI:
|
||||
# we got here, so we have a single match
|
||||
|
||||
# now get the particular issue data
|
||||
ct_md = self.actual_issue_data_fetch(matches[0].issue_id)
|
||||
ct_md = self.fetch_metadata(matches[0].issue_id)
|
||||
if ct_md.is_empty:
|
||||
res = Result(
|
||||
Action.save,
|
||||
@ -540,7 +559,8 @@ class CLI:
|
||||
original_path=ca.path,
|
||||
online_results=matches,
|
||||
match_status=MatchStatus.good_match,
|
||||
tags_written=self.config.Runtime_Options__type_modify,
|
||||
tags_written=self.config.Runtime_Options__tags_write,
|
||||
tags_read=tags_read,
|
||||
)
|
||||
match_results.fetch_data_failures.append(res)
|
||||
return res, match_results
|
||||
@ -552,11 +572,12 @@ 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_modify,
|
||||
tags_written=self.config.Runtime_Options__tags_write,
|
||||
tags_read=tags_read,
|
||||
)
|
||||
assert res.md
|
||||
# ok, done building our metadata. time to save
|
||||
if self.actual_metadata_save(ca, res.md):
|
||||
if self.write_tags(ca, res.md):
|
||||
match_results.good_matches.append(res)
|
||||
else:
|
||||
res.status = Status.write_failure
|
||||
@ -569,7 +590,7 @@ class CLI:
|
||||
if self.batch_mode:
|
||||
msg_hdr = f"{ca.path}: "
|
||||
|
||||
md = self.create_local_metadata(ca)
|
||||
md, tags_read = self.create_local_metadata(ca, self.config.Runtime_Options__tags_read)
|
||||
|
||||
if md.series is None:
|
||||
logger.error(msg_hdr + "Can't rename without series name")
|
||||
@ -629,7 +650,7 @@ class CLI:
|
||||
suffix = " (dry-run, no change)"
|
||||
|
||||
self.output(f"renamed '{original_path.name}' -> '{new_name}' {suffix}")
|
||||
return Result(Action.rename, Status.success, original_path, md=md)
|
||||
return Result(Action.rename, Status.success, original_path, tags_read=tags_read, md=md)
|
||||
|
||||
def export(self, ca: ComicArchive) -> Result:
|
||||
msg_hdr = ""
|
||||
|
@ -26,10 +26,10 @@ import subprocess
|
||||
import settngs
|
||||
|
||||
from comicapi import utils
|
||||
from comicapi.comicarchive import metadata_styles
|
||||
from comicapi.comicarchive import tags
|
||||
from comictaggerlib import ctversion
|
||||
from comictaggerlib.ctsettings.settngs_namespace import SettngsNS as ct_ns
|
||||
from comictaggerlib.ctsettings.types import ComicTaggerPaths, metadata_type, metadata_type_single
|
||||
from comictaggerlib.ctsettings.types import ComicTaggerPaths, tag
|
||||
from comictaggerlib.resulttypes import Action
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
@ -138,26 +138,26 @@ def register_runtime(parser: settngs.Manager) -> None:
|
||||
)
|
||||
parser.add_setting(
|
||||
"-t",
|
||||
"--type-read",
|
||||
metavar=f"{{{','.join(metadata_styles).upper()}}}",
|
||||
"--tags-read",
|
||||
metavar=f"{{{','.join(tags).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""",
|
||||
type=tag,
|
||||
help="""Specify the tags to read.\nUse commas for multiple tags.\nSee --list-plugins for the available tags.\nThe tags used will be 'overlaid' 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(
|
||||
"--type-modify",
|
||||
metavar=f"{{{','.join(metadata_styles).upper()}}}",
|
||||
"--tags-write",
|
||||
metavar=f"{{{','.join(tags).upper()}}}",
|
||||
default=[],
|
||||
type=metadata_type,
|
||||
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""",
|
||||
type=tag,
|
||||
help="""Specify the tags to write.\nUse commas for multiple tags.\nRead tags will be used if unspecified\nSee --list-plugins for the available tags.\n\n""",
|
||||
file=False,
|
||||
)
|
||||
parser.add_setting(
|
||||
"--skip-existing-metadata",
|
||||
"--skip-existing-tags",
|
||||
action=argparse.BooleanOptionalAction,
|
||||
default=True,
|
||||
help="""Skip archives that already have tags specified with -t,\notherwise merges new metadata with existing metadata (relevant for -s or -c).\ndefault: %(default)s""",
|
||||
help="""Skip archives that already have tags specified with -t,\notherwise merges new tags with existing tags (relevant for -s or -c).\ndefault: %(default)s""",
|
||||
file=False,
|
||||
)
|
||||
parser.add_setting("files", nargs="*", default=[], file=False)
|
||||
@ -173,7 +173,7 @@ def register_commands(parser: settngs.Manager) -> None:
|
||||
action="store_const",
|
||||
const=Action.print,
|
||||
default=Action.gui,
|
||||
help="""Print out tag info from file. Specify type\n(via --type-read) to get only info of that tag type.\n\n""",
|
||||
help="""Print out tag info from file. Specify via -t to only print specific tags.\n\n""",
|
||||
file=False,
|
||||
)
|
||||
parser.add_setting(
|
||||
@ -182,16 +182,16 @@ 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).",
|
||||
help="Deletes the tags specified via -t.",
|
||||
file=False,
|
||||
)
|
||||
parser.add_setting(
|
||||
"-c",
|
||||
"--copy",
|
||||
type=metadata_type_single,
|
||||
type=tag,
|
||||
default=[],
|
||||
metavar=f"{{{','.join(metadata_styles).upper()}}}",
|
||||
help="Copy the specified source tag block to\ndestination style specified via --type-modify\n(potentially lossy operation).\n\n",
|
||||
metavar=f"{{{','.join(tags).upper()}}}",
|
||||
help="Copy the specified source tags to\ndestination tags specified via --tags-write\n(potentially lossy operation).\n\n",
|
||||
file=False,
|
||||
)
|
||||
parser.add_setting(
|
||||
@ -200,7 +200,7 @@ def register_commands(parser: settngs.Manager) -> None:
|
||||
dest="command",
|
||||
action="store_const",
|
||||
const=Action.save,
|
||||
help="Save out tags as specified type (via --type-modify).\nMust specify also at least -o, -f, or -m.\n\n",
|
||||
help="Save out tags as specified tags (via --tags-write).\nMust specify also at least -o, -f, or -m.\n\n",
|
||||
file=False,
|
||||
)
|
||||
parser.add_setting(
|
||||
@ -209,7 +209,7 @@ def register_commands(parser: settngs.Manager) -> None:
|
||||
dest="command",
|
||||
action="store_const",
|
||||
const=Action.rename,
|
||||
help="Rename the file based on specified tag style.",
|
||||
help="Rename the file based on specified tags.",
|
||||
file=False,
|
||||
)
|
||||
parser.add_setting(
|
||||
@ -269,8 +269,8 @@ 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].Runtime_Options__tags_read and not config[0].Runtime_Options__tags_write:
|
||||
config[0].Runtime_Options__tags_write = config[0].Runtime_Options__tags_read
|
||||
|
||||
if (
|
||||
config[0].Commands__command not in (Action.save_config, Action.list_plugins)
|
||||
@ -279,16 +279,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_modify:
|
||||
parser.exit(message="Please specify the type to delete with --type-modify\n", status=1)
|
||||
if config[0].Commands__command == Action.delete and not config[0].Runtime_Options__tags_write:
|
||||
parser.exit(message="Please specify the tags to delete with --tags-write\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__command == Action.save and not config[0].Runtime_Options__tags_write:
|
||||
parser.exit(message="Please specify the tags to save with --tags-write\n", status=1)
|
||||
|
||||
if config[0].Commands__copy:
|
||||
config[0].Commands__command = Action.copy
|
||||
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 not config[0].Runtime_Options__tags_write:
|
||||
parser.exit(message="Please specify the tags to copy to with --tags-write\n", status=1)
|
||||
|
||||
if config[0].Runtime_Options__recursive:
|
||||
config[0].Runtime_Options__files = utils.get_recursive_filelist(config[0].Runtime_Options__files)
|
||||
|
@ -26,8 +26,8 @@ def general(parser: settngs.Manager) -> None:
|
||||
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("write_tags", default=["cbi"], cmdline=False)
|
||||
parser.add_setting("read_tags", 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)
|
||||
@ -154,20 +154,20 @@ def md_options(parser: settngs.Manager) -> None:
|
||||
parser.add_setting("--apply-transform-on-import", default=False, action=argparse.BooleanOptionalAction)
|
||||
parser.add_setting("--apply-transform-on-bulk-operation", default=False, action=argparse.BooleanOptionalAction)
|
||||
|
||||
parser.add_setting("use_short_metadata_names", default=False, action=argparse.BooleanOptionalAction, cmdline=False)
|
||||
parser.add_setting("use_short_tag_names", default=False, action=argparse.BooleanOptionalAction, cmdline=False)
|
||||
parser.add_setting(
|
||||
"--cr",
|
||||
default=True,
|
||||
action=argparse.BooleanOptionalAction,
|
||||
help="Enable the ComicRack metadata type. Turn off to only use the CIX metadata type.\ndefault: %(default)s",
|
||||
help="Enable ComicRack tags. Turn off to only use CIX tags.\ndefault: %(default)s",
|
||||
)
|
||||
parser.add_setting(
|
||||
"--comic-merge",
|
||||
"--tag-merge",
|
||||
metavar=f"{{{','.join(merge.Mode)}}}",
|
||||
default=merge.Mode.OVERLAY,
|
||||
choices=merge.Mode,
|
||||
type=merge.Mode,
|
||||
help="How to merge additional metadata for enabled read styles (CR, CBL, etc.) See -t, --type-read default: %(default)s",
|
||||
help="How to merge fields when reading enabled tags (CR, CBL, etc.) See -t, --tags-read default: %(default)s",
|
||||
)
|
||||
parser.add_setting(
|
||||
"--metadata-merge",
|
||||
@ -175,19 +175,19 @@ def md_options(parser: settngs.Manager) -> None:
|
||||
default=merge.Mode.OVERLAY,
|
||||
choices=merge.Mode,
|
||||
type=merge.Mode,
|
||||
help="How to merge new metadata from a data source (CV, Metron, GCD, etc.) default: %(default)s",
|
||||
help="How to merge fields when downloading new metadata (CV, Metron, GCD, etc.) default: %(default)s",
|
||||
)
|
||||
parser.add_setting(
|
||||
"--comic-merge-lists",
|
||||
"--tag-merge-lists",
|
||||
action=argparse.BooleanOptionalAction,
|
||||
default=True,
|
||||
help="Merge all items of lists when merging new metadata (genres, characters, etc.) default: %(default)s",
|
||||
help="Merge lists when reading enabled tags (genres, characters, etc.) default: %(default)s",
|
||||
)
|
||||
parser.add_setting(
|
||||
"--metadata-merge-lists",
|
||||
action=argparse.BooleanOptionalAction,
|
||||
default=True,
|
||||
help="Merge all items of lists when merging new metadata (genres, characters, etc.) default: %(default)s",
|
||||
help="Merge lists when downloading new metadata (genres, characters, etc.) default: %(default)s",
|
||||
)
|
||||
|
||||
|
||||
@ -242,14 +242,15 @@ def autotag(parser: settngs.Manager) -> None:
|
||||
"-o",
|
||||
"--online",
|
||||
action="store_true",
|
||||
help="""Search online and attempt to identify file\nusing existing metadata and images in archive.\nMay be used in conjunction with -f and -m.\n\n""",
|
||||
help="""Search online and attempt to identify file\nusing existing tags and images in archive.\nMay be used in conjunction with -f and -m.\n\n""",
|
||||
file=False,
|
||||
)
|
||||
parser.add_setting(
|
||||
"--save-on-low-confidence",
|
||||
default=False,
|
||||
action=argparse.BooleanOptionalAction,
|
||||
help="Automatically save metadata on low-confidence matches.\ndefault: %(default)s",
|
||||
help="Automatically save tags on low-confidence matches.\ndefault: %(default)s",
|
||||
cmdline=False,
|
||||
)
|
||||
parser.add_setting(
|
||||
"--use-year-when-identifying",
|
||||
@ -290,14 +291,14 @@ def autotag(parser: settngs.Manager) -> None:
|
||||
"--metadata",
|
||||
default=GenericMetadata(),
|
||||
type=parse_metadata_from_string,
|
||||
help="""Explicitly define some tags to be used in YAML syntax. Use @file.yaml to read from a file. e.g.:\n"series: Plastic Man, publisher: Quality Comics, year: "\n"series: 'Kickers, Inc.', issue: '1', year: 1986"\nIf you want to erase a tag leave the value blank.\nSome names that can be used: series, issue, issue_count, year,\npublisher, title\n\n""",
|
||||
help="""Explicitly define some metadata to be used in YAML syntax. Use @file.yaml to read from a file. e.g.:\n"series: Plastic Man, publisher: Quality Comics, year: "\n"series: 'Kickers, Inc.', issue: '1', year: 1986"\nIf you want to erase a tag leave the value blank.\nSome names that can be used: series, issue, issue_count, year,\npublisher, title\n\n""",
|
||||
file=False,
|
||||
)
|
||||
parser.add_setting(
|
||||
"--clear-metadata",
|
||||
"--clear-tags",
|
||||
default=False,
|
||||
action=argparse.BooleanOptionalAction,
|
||||
help="Clears all existing metadata during import, default is to merge metadata.\nMay be used in conjunction with -o, -f and -m.\ndefault: %(default)s\n\n",
|
||||
help="Clears all existing tags during import, default is to merge tags.\nMay be used in conjunction with -o, -f and -m.\ndefault: %(default)s\n\n",
|
||||
)
|
||||
parser.add_setting(
|
||||
"--publisher-filter",
|
||||
@ -341,23 +342,23 @@ def parse_filter(config: settngs.Config[ct_ns]) -> settngs.Config[ct_ns]:
|
||||
|
||||
def migrate_settings(config: settngs.Config[ct_ns]) -> settngs.Config[ct_ns]:
|
||||
original_types = ("cbi", "cr", "comet")
|
||||
save_style = config[0].internal__save_data_style
|
||||
if not isinstance(save_style, list):
|
||||
if isinstance(save_style, int) and save_style in (0, 1, 2):
|
||||
config[0].internal__save_data_style = [original_types[save_style]]
|
||||
elif isinstance(save_style, str):
|
||||
config[0].internal__save_data_style = [save_style]
|
||||
write_Tags = config[0].internal__write_tags
|
||||
if not isinstance(write_Tags, list):
|
||||
if isinstance(write_Tags, int) and write_Tags in (0, 1, 2):
|
||||
config[0].internal__write_tags = [original_types[write_Tags]]
|
||||
elif isinstance(write_Tags, str):
|
||||
config[0].internal__write_tags = [write_Tags]
|
||||
else:
|
||||
config[0].internal__save_data_style = ["cbi"]
|
||||
config[0].internal__write_tags = ["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]
|
||||
read_tags = config[0].internal__read_tags
|
||||
if not isinstance(read_tags, list):
|
||||
if isinstance(read_tags, int) and read_tags in (0, 1, 2):
|
||||
config[0].internal__read_tags = [original_types[read_tags]]
|
||||
elif isinstance(read_tags, str):
|
||||
config[0].internal__read_tags = [read_tags]
|
||||
else:
|
||||
config[0].internal__load_data_style = ["cbi"]
|
||||
config[0].internal__read_tags = ["cbi"]
|
||||
|
||||
return config
|
||||
|
||||
|
@ -15,7 +15,7 @@ from typing import Any, NamedTuple
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
NORMALIZE_PACKAGE_NAME_RE = re.compile(r"[-_.]+")
|
||||
PLUGIN_GROUPS = frozenset(("comictagger.talker", "comicapi.archiver", "comicapi.metadata"))
|
||||
PLUGIN_GROUPS = frozenset(("comictagger.talker", "comicapi.archiver", "comicapi.tags"))
|
||||
|
||||
|
||||
class FailedToLoadPlugin(Exception):
|
||||
@ -72,13 +72,13 @@ class Plugins(NamedTuple):
|
||||
"""Classified plugins."""
|
||||
|
||||
archivers: list[Plugin]
|
||||
metadata: list[Plugin]
|
||||
tags: list[Plugin]
|
||||
talkers: list[Plugin]
|
||||
|
||||
def all_plugins(self) -> Generator[Plugin, None, None]:
|
||||
"""Return an iterator over all :class:`LoadedPlugin`s."""
|
||||
yield from self.archivers
|
||||
yield from self.metadata
|
||||
yield from self.tags
|
||||
yield from self.talkers
|
||||
|
||||
def versions_str(self) -> str:
|
||||
@ -133,21 +133,21 @@ def find_plugins(plugin_folder: pathlib.Path) -> Plugins:
|
||||
|
||||
def _classify_plugins(plugins: list[Plugin]) -> Plugins:
|
||||
archivers = []
|
||||
metadata = []
|
||||
tags = []
|
||||
talkers = []
|
||||
|
||||
for p in plugins:
|
||||
if p.entry_point.group == "comictagger.talker":
|
||||
talkers.append(p)
|
||||
elif p.entry_point.group == "comicapi.metadata":
|
||||
metadata.append(p)
|
||||
elif p.entry_point.group == "comicapi.tags":
|
||||
tags.append(p)
|
||||
elif p.entry_point.group == "comicapi.archiver":
|
||||
archivers.append(p)
|
||||
else:
|
||||
logger.warning(NotImplementedError(f"what plugin type? {p}"))
|
||||
|
||||
return Plugins(
|
||||
metadata=metadata,
|
||||
tags=tags,
|
||||
archivers=archivers,
|
||||
talkers=talkers,
|
||||
)
|
||||
|
@ -15,7 +15,7 @@ import comictaggerlib.resulttypes
|
||||
class SettngsNS(settngs.TypedNS):
|
||||
Commands__version: bool
|
||||
Commands__command: comictaggerlib.resulttypes.Action
|
||||
Commands__copy: str
|
||||
Commands__copy: list[str]
|
||||
|
||||
Runtime_Options__config: comictaggerlib.ctsettings.types.ComicTaggerPaths
|
||||
Runtime_Options__verbose: int
|
||||
@ -32,14 +32,14 @@ class SettngsNS(settngs.TypedNS):
|
||||
Runtime_Options__no_gui: bool
|
||||
Runtime_Options__abort_on_conflict: bool
|
||||
Runtime_Options__delete_original: bool
|
||||
Runtime_Options__type_read: list[str]
|
||||
Runtime_Options__type_modify: list[str]
|
||||
Runtime_Options__skip_existing_metadata: bool
|
||||
Runtime_Options__tags_read: list[str]
|
||||
Runtime_Options__tags_write: list[str]
|
||||
Runtime_Options__skip_existing_tags: bool
|
||||
Runtime_Options__files: list[str]
|
||||
|
||||
internal__install_id: str
|
||||
internal__save_data_style: list[str]
|
||||
internal__load_data_style: list[str]
|
||||
internal__write_tags: list[str]
|
||||
internal__read_tags: list[str]
|
||||
internal__last_opened_folder: str
|
||||
internal__window_width: int
|
||||
internal__window_height: int
|
||||
@ -76,11 +76,11 @@ class SettngsNS(settngs.TypedNS):
|
||||
Metadata_Options__copy_weblink_to_comments: bool
|
||||
Metadata_Options__apply_transform_on_import: bool
|
||||
Metadata_Options__apply_transform_on_bulk_operation: bool
|
||||
Metadata_Options__use_short_metadata_names: bool
|
||||
Metadata_Options__use_short_tag_names: bool
|
||||
Metadata_Options__cr: bool
|
||||
Metadata_Options__comic_merge: comicapi.merge.Mode
|
||||
Metadata_Options__tag_merge: comicapi.merge.Mode
|
||||
Metadata_Options__metadata_merge: comicapi.merge.Mode
|
||||
Metadata_Options__comic_merge_lists: bool
|
||||
Metadata_Options__tag_merge_lists: bool
|
||||
Metadata_Options__metadata_merge_lists: bool
|
||||
|
||||
File_Rename__template: str
|
||||
@ -102,7 +102,7 @@ class SettngsNS(settngs.TypedNS):
|
||||
Auto_Tag__parse_filename: bool
|
||||
Auto_Tag__issue_id: str | None
|
||||
Auto_Tag__metadata: comicapi.genericmetadata.GenericMetadata
|
||||
Auto_Tag__clear_metadata: bool
|
||||
Auto_Tag__clear_tags: bool
|
||||
Auto_Tag__publisher_filter: list[str]
|
||||
Auto_Tag__use_publisher_filter: bool
|
||||
Auto_Tag__auto_imprint: bool
|
||||
@ -124,7 +124,7 @@ class SettngsNS(settngs.TypedNS):
|
||||
class Commands(typing.TypedDict):
|
||||
version: bool
|
||||
command: comictaggerlib.resulttypes.Action
|
||||
copy: str
|
||||
copy: list[str]
|
||||
|
||||
|
||||
class Runtime_Options(typing.TypedDict):
|
||||
@ -143,16 +143,16 @@ class Runtime_Options(typing.TypedDict):
|
||||
no_gui: bool
|
||||
abort_on_conflict: bool
|
||||
delete_original: bool
|
||||
type_read: list[str]
|
||||
type_modify: list[str]
|
||||
skip_existing_metadata: bool
|
||||
tags_read: list[str]
|
||||
tags_write: list[str]
|
||||
skip_existing_tags: bool
|
||||
files: list[str]
|
||||
|
||||
|
||||
class internal(typing.TypedDict):
|
||||
install_id: str
|
||||
save_data_style: list[str]
|
||||
load_data_style: list[str]
|
||||
write_tags: list[str]
|
||||
read_tags: list[str]
|
||||
last_opened_folder: str
|
||||
window_width: int
|
||||
window_height: int
|
||||
@ -197,11 +197,11 @@ class Metadata_Options(typing.TypedDict):
|
||||
copy_weblink_to_comments: bool
|
||||
apply_transform_on_import: bool
|
||||
apply_transform_on_bulk_operation: bool
|
||||
use_short_metadata_names: bool
|
||||
use_short_tag_names: bool
|
||||
cr: bool
|
||||
comic_merge: comicapi.merge.Mode
|
||||
tag_merge: comicapi.merge.Mode
|
||||
metadata_merge: comicapi.merge.Mode
|
||||
comic_merge_lists: bool
|
||||
tag_merge_lists: bool
|
||||
metadata_merge_lists: bool
|
||||
|
||||
|
||||
@ -227,7 +227,7 @@ class Auto_Tag(typing.TypedDict):
|
||||
parse_filename: bool
|
||||
issue_id: str | None
|
||||
metadata: comicapi.genericmetadata.GenericMetadata
|
||||
clear_metadata: bool
|
||||
clear_tags: bool
|
||||
publisher_filter: list[str]
|
||||
use_publisher_filter: bool
|
||||
auto_imprint: bool
|
||||
|
@ -12,7 +12,7 @@ import yaml
|
||||
from appdirs import AppDirs
|
||||
|
||||
from comicapi import utils
|
||||
from comicapi.comicarchive import metadata_styles
|
||||
from comicapi.comicarchive import tags
|
||||
from comicapi.genericmetadata import REMOVE, GenericMetadata
|
||||
|
||||
if sys.version_info < (3, 10):
|
||||
@ -151,21 +151,14 @@ class ComicTaggerPaths(AppDirs):
|
||||
return f"logs: {self.user_log_dir}, config: {self.user_config_dir}, cache: {self.user_cache_dir}"
|
||||
|
||||
|
||||
def metadata_type_single(types: str) -> str:
|
||||
result = metadata_type(types)
|
||||
if len(result) > 1:
|
||||
raise argparse.ArgumentTypeError(f"invalid choice: {result} (only one metadata style allowed)")
|
||||
return result[0]
|
||||
|
||||
|
||||
def metadata_type(types: str) -> list[str]:
|
||||
def tag(types: str) -> list[str]:
|
||||
result = []
|
||||
types = types.casefold()
|
||||
for typ in utils.split(types, ","):
|
||||
if typ not in metadata_styles:
|
||||
choices = ", ".join(metadata_styles)
|
||||
if typ not in tags:
|
||||
choices = ", ".join(tags)
|
||||
raise argparse.ArgumentTypeError(f"invalid choice: {typ} (choose from {choices.upper()})")
|
||||
result.append(metadata_styles[typ].short_name)
|
||||
result.append(tags[typ].id)
|
||||
return result
|
||||
|
||||
|
||||
|
@ -71,7 +71,7 @@ class FileSelectionList(QtWidgets.QWidget):
|
||||
self.separator.setSeparator(True)
|
||||
|
||||
select_all_action.setShortcut("Ctrl+A")
|
||||
remove_action.setShortcut("Ctrl+X")
|
||||
remove_action.setShortcut("Backspace" if platform.system() == "Darwin" else "Delete")
|
||||
|
||||
select_all_action.triggered.connect(self.select_all)
|
||||
remove_action.triggered.connect(self.remove_selection)
|
||||
@ -246,7 +246,7 @@ class FileSelectionList(QtWidgets.QWidget):
|
||||
self,
|
||||
"RAR Files are Read-Only",
|
||||
"It looks like you have opened a RAR/CBR archive,\n"
|
||||
"however ComicTagger cannot currently write to them without the rar program and are marked read only!\n\n"
|
||||
"however ComicTagger cannot write to them without the rar program and are marked read only!\n\n"
|
||||
f"{rar_help}",
|
||||
)
|
||||
self.rar_ro_shown = True
|
||||
@ -326,8 +326,7 @@ class FileSelectionList(QtWidgets.QWidget):
|
||||
type_item.setText(item_text)
|
||||
type_item.setData(QtCore.Qt.ItemDataRole.ToolTipRole, item_text)
|
||||
|
||||
styles = ", ".join(x for x in ca.get_supported_metadata() if ca.has_metadata(x))
|
||||
md_item.setText(styles)
|
||||
md_item.setText(", ".join(x for x in ca.get_supported_tags() if ca.has_tags(x)))
|
||||
|
||||
if not ca.is_writable():
|
||||
readonly_item.setCheckState(QtCore.Qt.CheckState.Checked)
|
||||
|
24
comictaggerlib/graphics/graphics.qrc
Normal file
24
comictaggerlib/graphics/graphics.qrc
Normal file
@ -0,0 +1,24 @@
|
||||
<RCC>
|
||||
<qresource prefix="graphics">
|
||||
<file>about.png</file>
|
||||
<file>app.png</file>
|
||||
<file>auto.png</file>
|
||||
<file>autotag.png</file>
|
||||
<file>browse.png</file>
|
||||
<file>clear.png</file>
|
||||
<file>down.png</file>
|
||||
<file>eye.svg</file>
|
||||
<file>hidden.svg</file>
|
||||
<file>left.png</file>
|
||||
<file>longbox.png</file>
|
||||
<file>nocover.png</file>
|
||||
<file>open.png</file>
|
||||
<file>parse.png</file>
|
||||
<file>popup_bg.png</file>
|
||||
<file>right.png</file>
|
||||
<file>save.png</file>
|
||||
<file>search.png</file>
|
||||
<file>tags.png</file>
|
||||
<file>up.png</file>
|
||||
</qresource>
|
||||
</RCC>
|
19657
comictaggerlib/graphics/resources.py
Normal file
19657
comictaggerlib/graphics/resources.py
Normal file
File diff suppressed because it is too large
Load Diff
@ -127,8 +127,8 @@ class App:
|
||||
self._extend_plugin_paths(local_plugins)
|
||||
|
||||
comicapi.comicarchive.load_archive_plugins(local_plugins=[p.entry_point for p in local_plugins.archivers])
|
||||
comicapi.comicarchive.load_metadata_plugins(
|
||||
version=version, local_plugins=[p.entry_point for p in local_plugins.metadata]
|
||||
comicapi.comicarchive.load_tag_plugins(
|
||||
version=version, local_plugins=[p.entry_point for p in local_plugins.tags]
|
||||
)
|
||||
self.talkers = comictalker.get_talkers(
|
||||
version, opts.config.user_cache_dir, local_plugins=[p.entry_point for p in local_plugins.talkers]
|
||||
@ -141,7 +141,7 @@ class App:
|
||||
self,
|
||||
talkers: Collection[comictalker.ComicTalker],
|
||||
archivers: Collection[type[comicapi.comicarchive.Archiver]],
|
||||
metadata_styles: Collection[comicapi.comicarchive.Metadata],
|
||||
tags: Collection[comicapi.comicarchive.Tag],
|
||||
) -> None:
|
||||
if self.config[0].Runtime_Options__json:
|
||||
for talker in talkers:
|
||||
@ -183,14 +183,14 @@ class App:
|
||||
)
|
||||
)
|
||||
|
||||
for style in metadata_styles:
|
||||
for tag in tags:
|
||||
print( # noqa: T201
|
||||
json.dumps(
|
||||
{
|
||||
"type": "metadata",
|
||||
"enabled": style.enabled,
|
||||
"name": style.name(),
|
||||
"short_name": style.short_name,
|
||||
"type": "tag",
|
||||
"enabled": tag.enabled,
|
||||
"name": tag.name(),
|
||||
"id": tag.id,
|
||||
}
|
||||
)
|
||||
)
|
||||
@ -199,14 +199,14 @@ class App:
|
||||
for talker in talkers:
|
||||
print(f"{talker.id:<10}: {talker.name:<21}, {talker.website}") # noqa: T201
|
||||
|
||||
print("\nComic Archive: (Name: extension, exe)") # noqa: T201
|
||||
print("\nComic Archive: (Enabled, Name: extension, exe)") # noqa: T201
|
||||
for archiver in archivers:
|
||||
a = archiver()
|
||||
print(f"{a.name():<10}: {a.extension():<5}, {a.exe}") # noqa: T201
|
||||
print(f"{a.enabled!s:<5}, {a.name():<10}: {a.extension():<5}, {a.exe}") # noqa: T201
|
||||
|
||||
print("\nMetadata Style: (Short Name: Name)") # noqa: T201
|
||||
for style in metadata_styles:
|
||||
print(f"{style.short_name:<10}: {style.name()}") # noqa: T201
|
||||
print("\nTags: (Enabled, ID: Name)") # noqa: T201
|
||||
for tag in tags:
|
||||
print(f"{tag.enabled!s:<5}, {tag.id:<10}: {tag.name()}") # noqa: T201
|
||||
|
||||
def initialize(self) -> argparse.Namespace:
|
||||
conf, _ = self.initial_arg_parser.parse_known_intermixed_args()
|
||||
@ -252,9 +252,12 @@ class App:
|
||||
# config already loaded
|
||||
error = None
|
||||
|
||||
if not self.config[0].Metadata_Options__cr:
|
||||
if "cr" in comicapi.comicarchive.metadata_styles:
|
||||
del comicapi.comicarchive.metadata_styles["cr"]
|
||||
if (
|
||||
not self.config[0].Metadata_Options__cr
|
||||
and "cr" in comicapi.comicarchive.tags
|
||||
and comicapi.comicarchive.tags["cr"].enabled
|
||||
):
|
||||
comicapi.comicarchive.tags["cr"].enabled = False
|
||||
|
||||
if len(self.talkers) < 1:
|
||||
error = error = (
|
||||
@ -277,7 +280,7 @@ class App:
|
||||
self.list_plugins(
|
||||
list(self.talkers.values()),
|
||||
comicapi.comicarchive.archivers,
|
||||
comicapi.comicarchive.metadata_styles.values(),
|
||||
comicapi.comicarchive.tags.values(),
|
||||
)
|
||||
return
|
||||
|
||||
|
@ -10,18 +10,18 @@ from comictaggerlib.ctsettings.settngs_namespace import SettngsNS
|
||||
from comictalker.talker_utils import cleanup_html
|
||||
|
||||
|
||||
def prepare_metadata(md: GenericMetadata, new_md: GenericMetadata, opts: SettngsNS) -> GenericMetadata:
|
||||
if opts.Metadata_Options__apply_transform_on_import:
|
||||
new_md = CBLTransformer(new_md, opts).apply()
|
||||
def prepare_metadata(md: GenericMetadata, new_md: GenericMetadata, config: SettngsNS) -> GenericMetadata:
|
||||
if config.Metadata_Options__apply_transform_on_import:
|
||||
new_md = CBLTransformer(new_md, config).apply()
|
||||
|
||||
final_md = md.copy()
|
||||
if opts.Auto_Tag__clear_metadata:
|
||||
if config.Auto_Tag__clear_tags:
|
||||
final_md = GenericMetadata()
|
||||
|
||||
final_md.overlay(new_md, opts.Metadata_Options__metadata_merge, opts.Metadata_Options__metadata_merge_lists)
|
||||
if final_md.tag_origin is not None:
|
||||
final_md.overlay(new_md, config.Metadata_Options__metadata_merge, config.Metadata_Options__metadata_merge_lists)
|
||||
if final_md.data_origin is not None:
|
||||
notes = (
|
||||
f"Tagged with ComicTagger {ctversion.version} using info from {final_md.tag_origin.name} on"
|
||||
f"Tagged with ComicTagger {ctversion.version} using info from {final_md.data_origin.name} on"
|
||||
+ f" {datetime.now():%Y-%m-%d %H:%M:%S}. [Issue ID {final_md.issue_id}]"
|
||||
)
|
||||
else:
|
||||
@ -31,11 +31,11 @@ def prepare_metadata(md: GenericMetadata, new_md: GenericMetadata, opts: Settngs
|
||||
+ (f"[Issue ID {final_md.issue_id}]" if final_md.issue_id else "")
|
||||
)
|
||||
|
||||
if opts.Auto_Tag__auto_imprint:
|
||||
if config.Auto_Tag__auto_imprint:
|
||||
final_md.fix_publisher()
|
||||
|
||||
return final_md.replace(
|
||||
is_empty=False,
|
||||
notes=utils.combine_notes(final_md.notes, notes, "Tagged with ComicTagger"),
|
||||
description=cleanup_html(final_md.description, opts.Sources__remove_html_tables) or None,
|
||||
description=cleanup_html(final_md.description, config.Sources__remove_html_tables) or None,
|
||||
)
|
||||
|
@ -20,7 +20,7 @@ import logging
|
||||
|
||||
from PyQt5 import QtCore, QtWidgets, uic
|
||||
|
||||
from comicapi.comicarchive import ComicArchive, metadata_styles
|
||||
from comicapi.comicarchive import ComicArchive, tags
|
||||
from comicapi.genericmetadata import ImageMetadata, PageType
|
||||
from comictaggerlib.coverimagewidget import CoverImageWidget
|
||||
from comictaggerlib.ui import ui_path
|
||||
@ -116,7 +116,7 @@ class PageListEditor(QtWidgets.QWidget):
|
||||
|
||||
self.comic_archive: ComicArchive | None = None
|
||||
self.pages_list: list[ImageMetadata] = []
|
||||
self.data_styles: list[str] = []
|
||||
self.tag_ids: list[str] = []
|
||||
|
||||
def reset_page(self) -> None:
|
||||
self.pageWidget.clear()
|
||||
@ -343,7 +343,7 @@ class PageListEditor(QtWidgets.QWidget):
|
||||
self.comic_archive = comic_archive
|
||||
self.pages_list = pages_list
|
||||
if pages_list:
|
||||
self.set_metadata_style(self.data_styles)
|
||||
self.select_read_tags(self.tag_ids)
|
||||
else:
|
||||
self.cbPageType.setEnabled(False)
|
||||
self.chkDoublePage.setEnabled(False)
|
||||
@ -386,15 +386,16 @@ class PageListEditor(QtWidgets.QWidget):
|
||||
self.first_front_page = self.get_first_front_cover()
|
||||
self.firstFrontCoverChanged.emit(self.first_front_page)
|
||||
|
||||
def set_metadata_style(self, data_styles: list[str]) -> None:
|
||||
# depending on the current data style, certain fields are disabled
|
||||
if data_styles:
|
||||
styles = [metadata_styles[style] for style in data_styles]
|
||||
enabled_widgets = set()
|
||||
for style in styles:
|
||||
enabled_widgets.update(style.supported_attributes)
|
||||
def select_read_tags(self, tag_ids: list[str]) -> None:
|
||||
# depending on the current tags, certain fields are disabled
|
||||
if not tag_ids:
|
||||
return
|
||||
|
||||
self.data_styles = data_styles
|
||||
enabled_widgets = set()
|
||||
for tag_id in tag_ids:
|
||||
enabled_widgets.update(tags[tag_id].supported_attributes)
|
||||
|
||||
for metadata, widget in self.md_attributes.items():
|
||||
enable_widget(widget, metadata in enabled_widgets)
|
||||
self.tag_ids = tag_ids
|
||||
|
||||
for md_field, widget in self.md_attributes.items():
|
||||
enable_widget(widget, md_field in enabled_widgets)
|
||||
|
@ -22,7 +22,7 @@ import settngs
|
||||
from PyQt5 import QtCore, QtWidgets, uic
|
||||
|
||||
from comicapi import utils
|
||||
from comicapi.comicarchive import ComicArchive, metadata_styles
|
||||
from comicapi.comicarchive import ComicArchive, tags
|
||||
from comicapi.genericmetadata import GenericMetadata
|
||||
from comictaggerlib.ctsettings import ct_ns
|
||||
from comictaggerlib.filerenamer import FileRenamer, get_rename_dir
|
||||
@ -39,7 +39,7 @@ class RenameWindow(QtWidgets.QDialog):
|
||||
self,
|
||||
parent: QtWidgets.QWidget,
|
||||
comic_archive_list: list[ComicArchive],
|
||||
load_data_styles: list[str],
|
||||
read_tag_ids: list[str],
|
||||
config: settngs.Config[ct_ns],
|
||||
talkers: dict[str, ComicTalker],
|
||||
) -> None:
|
||||
@ -48,9 +48,7 @@ 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 {', '.join(metadata_styles[style].name() for style in load_data_styles)} tags):"
|
||||
)
|
||||
self.label.setText(f"Preview (based on {', '.join(tags[tag].name() for tag in read_tag_ids)} tags):")
|
||||
|
||||
self.setWindowFlags(
|
||||
QtCore.Qt.WindowType(
|
||||
@ -63,7 +61,7 @@ class RenameWindow(QtWidgets.QDialog):
|
||||
self.config = config
|
||||
self.talkers = talkers
|
||||
self.comic_archive_list = comic_archive_list
|
||||
self.load_data_styles = load_data_styles
|
||||
self.read_tag_ids = read_tag_ids
|
||||
self.rename_list: list[str] = []
|
||||
|
||||
self.btnSettings.clicked.connect(self.modify_settings)
|
||||
@ -84,13 +82,13 @@ class RenameWindow(QtWidgets.QDialog):
|
||||
new_ext = ca.extension()
|
||||
|
||||
if md is None or md.is_empty:
|
||||
md, error = self.parent().overlay_ca_read_style(self.load_data_styles, ca)
|
||||
md, error = self.parent().read_all_tags(self.read_tag_ids, ca)
|
||||
if error is not None:
|
||||
logger.error("Failed to load metadata for %s: %s", ca.path, error)
|
||||
logger.error("Failed to load tags from %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",
|
||||
f"One or more of the read tags failed to load for {ca.path}, check log for details",
|
||||
)
|
||||
|
||||
if md.is_empty:
|
||||
|
@ -82,6 +82,7 @@ class Result:
|
||||
|
||||
md: GenericMetadata | None = None
|
||||
|
||||
tags_read: list[str] = dataclasses.field(default_factory=list)
|
||||
tags_deleted: list[str] = dataclasses.field(default_factory=list)
|
||||
tags_written: list[str] = dataclasses.field(default_factory=list)
|
||||
|
||||
|
@ -71,7 +71,7 @@ template_tooltip = """
|
||||
The template for the new filename. Uses python format strings https://docs.python.org/3/library/string.html#format-string-syntax
|
||||
Accepts the following variables:
|
||||
{is_empty} (boolean)
|
||||
{tag_origin} (string)
|
||||
{data_origin} (string)
|
||||
{series} (string)
|
||||
{issue} (string)
|
||||
{title} (string)
|
||||
@ -195,8 +195,8 @@ class SettingsWindow(QtWidgets.QDialog):
|
||||
self.cbFilenameParser.clear()
|
||||
self.cbFilenameParser.addItems(utils.Parser)
|
||||
for mode in merge.Mode:
|
||||
self.cbMergeModeComic.addItem(mode.name.capitalize().replace("_", " "), mode)
|
||||
self.cbMergeModeMetadata.addItem(mode.name.capitalize().replace("_", " "), mode)
|
||||
self.cbTagsMergeMode.addItem(mode.name.capitalize().replace("_", " "), mode)
|
||||
self.cbDownloadMergeMode.addItem(mode.name.capitalize().replace("_", " "), mode)
|
||||
self.connect_signals()
|
||||
self.settings_to_form()
|
||||
self.rename_test()
|
||||
@ -418,7 +418,7 @@ class SettingsWindow(QtWidgets.QDialog):
|
||||
self.cbxUseFilter.setChecked(self.config[0].Auto_Tag__use_publisher_filter)
|
||||
self.cbxSortByYear.setChecked(self.config[0].Issue_Identifier__sort_series_by_year)
|
||||
self.cbxExactMatches.setChecked(self.config[0].Issue_Identifier__exact_series_matches_first)
|
||||
self.cbxClearFormBeforePopulating.setChecked(self.config[0].Auto_Tag__clear_metadata)
|
||||
self.cbxClearFormBeforePopulating.setChecked(self.config[0].Auto_Tag__clear_tags)
|
||||
|
||||
self.cbxAssumeLoneCreditIsPrimary.setChecked(self.config[0].Metadata_Options__assume_lone_credit_is_primary)
|
||||
self.cbxCopyCharactersToTags.setChecked(self.config[0].Metadata_Options__copy_characters_to_tags)
|
||||
@ -432,15 +432,13 @@ class SettingsWindow(QtWidgets.QDialog):
|
||||
self.config[0].Metadata_Options__apply_transform_on_bulk_operation
|
||||
)
|
||||
|
||||
self.cbMergeModeComic.setCurrentIndex(
|
||||
self.cbMergeModeComic.findData(self.config[0].Metadata_Options__comic_merge)
|
||||
self.cbTagsMergeMode.setCurrentIndex(self.cbTagsMergeMode.findData(self.config[0].Metadata_Options__tag_merge))
|
||||
self.cbDownloadMergeMode.setCurrentIndex(
|
||||
self.cbDownloadMergeMode.findData(self.config[0].Metadata_Options__metadata_merge)
|
||||
)
|
||||
self.cbMergeModeMetadata.setCurrentIndex(
|
||||
self.cbMergeModeMetadata.findData(self.config[0].Metadata_Options__metadata_merge)
|
||||
)
|
||||
self.cbxMergeListsComic.setChecked(self.config[0].Metadata_Options__comic_merge_lists)
|
||||
self.cbxTagsMergeLists.setChecked(self.config[0].Metadata_Options__tag_merge_lists)
|
||||
self.cbxMergeListsMetadata.setChecked(self.config[0].Metadata_Options__metadata_merge_lists)
|
||||
self.cbxShortMetadataNames.setChecked(self.config[0].Metadata_Options__use_short_metadata_names)
|
||||
self.cbxShortTagNames.setChecked(self.config[0].Metadata_Options__use_short_tag_names)
|
||||
self.cbxEnableCR.setChecked(self.config[0].Metadata_Options__cr)
|
||||
|
||||
self.leRenameTemplate.setText(self.config[0].File_Rename__template)
|
||||
@ -550,7 +548,7 @@ class SettingsWindow(QtWidgets.QDialog):
|
||||
self.config[0].Auto_Tag__use_publisher_filter = self.cbxUseFilter.isChecked()
|
||||
self.config[0].Issue_Identifier__sort_series_by_year = self.cbxSortByYear.isChecked()
|
||||
self.config[0].Issue_Identifier__exact_series_matches_first = self.cbxExactMatches.isChecked()
|
||||
self.config[0].Auto_Tag__clear_metadata = self.cbxClearFormBeforePopulating.isChecked()
|
||||
self.config[0].Auto_Tag__clear_tags = self.cbxClearFormBeforePopulating.isChecked()
|
||||
|
||||
self.config[0].Metadata_Options__assume_lone_credit_is_primary = self.cbxAssumeLoneCreditIsPrimary.isChecked()
|
||||
self.config[0].Metadata_Options__copy_characters_to_tags = self.cbxCopyCharactersToTags.isChecked()
|
||||
@ -564,17 +562,17 @@ class SettingsWindow(QtWidgets.QDialog):
|
||||
self.cbxApplyCBLTransformOnBatchOperation.isChecked()
|
||||
)
|
||||
|
||||
self.config[0].Metadata_Options__comic_merge = merge.Mode(self.cbMergeModeComic.currentData())
|
||||
self.config[0].Metadata_Options__metadata_merge = merge.Mode(self.cbMergeModeMetadata.currentData())
|
||||
self.config[0].Metadata_Options__comic_merge_lists = self.cbxMergeListsComic.isChecked()
|
||||
self.config[0].Metadata_Options__tag_merge = merge.Mode(self.cbTagsMergeMode.currentData())
|
||||
self.config[0].Metadata_Options__metadata_merge = merge.Mode(self.cbDownloadMergeMode.currentData())
|
||||
self.config[0].Metadata_Options__tag_merge_lists = self.cbxTagsMergeLists.isChecked()
|
||||
self.config[0].Metadata_Options__metadata_merge_lists = self.cbxMergeListsMetadata.isChecked()
|
||||
self.config[0].Metadata_Options__cr = self.cbxEnableCR.isChecked()
|
||||
|
||||
# Update metadata style names if required
|
||||
if self.config[0].Metadata_Options__use_short_metadata_names != self.cbxShortMetadataNames.isChecked():
|
||||
self.config[0].Metadata_Options__use_short_metadata_names = self.cbxShortMetadataNames.isChecked()
|
||||
self.parent().populate_style_names()
|
||||
self.parent().adjust_save_style_combo()
|
||||
# Update tag names if required
|
||||
if self.config[0].Metadata_Options__use_short_tag_names != self.cbxShortTagNames.isChecked():
|
||||
self.config[0].Metadata_Options__use_short_tag_names = self.cbxShortTagNames.isChecked()
|
||||
self.parent().populate_tag_names()
|
||||
self.parent().adjust_tags_combo()
|
||||
|
||||
self.config[0].File_Rename__template = str(self.leRenameTemplate.text())
|
||||
self.config[0].File_Rename__issue_number_padding = int(self.leIssueNumPadding.text())
|
||||
|
@ -35,7 +35,7 @@ import comicapi.merge
|
||||
import comictaggerlib.graphics.resources
|
||||
import comictaggerlib.ui
|
||||
from comicapi import utils
|
||||
from comicapi.comicarchive import ComicArchive, metadata_styles
|
||||
from comicapi.comicarchive import ComicArchive, tags
|
||||
from comicapi.filenameparser import FileNameParser
|
||||
from comicapi.genericmetadata import GenericMetadata
|
||||
from comicapi.issuestring import IssueString
|
||||
@ -90,7 +90,7 @@ class TaggerWindow(QtWidgets.QMainWindow):
|
||||
uic.loadUi(uifile, self)
|
||||
|
||||
self.md_attributes = {
|
||||
"tag_origin": None,
|
||||
"data_origin": None,
|
||||
"issue_id": None,
|
||||
"series": self.leSeries,
|
||||
"issue": self.leIssueNum,
|
||||
@ -213,21 +213,23 @@ class TaggerWindow(QtWidgets.QMainWindow):
|
||||
|
||||
self.setWindowIcon(QtGui.QIcon(str(graphics_path / "app.png")))
|
||||
|
||||
# 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
|
||||
# respect the command line selected tags
|
||||
if config[0].Runtime_Options__tags_write:
|
||||
config[0].internal__write_tags = config[0].Runtime_Options__tags_write
|
||||
|
||||
for style in config[0].internal__save_data_style:
|
||||
if style not in metadata_styles:
|
||||
config[0].internal__save_data_style.remove(style)
|
||||
for style in config[0].internal__load_data_style:
|
||||
if style not in metadata_styles:
|
||||
config[0].internal__load_data_style.remove(style)
|
||||
if config[0].Runtime_Options__tags_read:
|
||||
config[0].internal__read_tags = config[0].Runtime_Options__tags_read
|
||||
|
||||
self.save_data_styles: list[str] = config[0].internal__save_data_style
|
||||
self.load_data_styles: list[str] = config[0].internal__load_data_style
|
||||
for tag_id in config[0].internal__write_tags:
|
||||
if tag_id not in tags:
|
||||
config[0].internal__write_tags.remove(tag_id)
|
||||
|
||||
for tag_id in config[0].internal__read_tags:
|
||||
if tag_id not in tags:
|
||||
config[0].internal__read_tags.remove(tag_id)
|
||||
|
||||
self.selected_write_tags: list[str] = config[0].internal__write_tags
|
||||
self.selected_read_tags: list[str] = config[0].internal__read_tags
|
||||
|
||||
self.setAcceptDrops(True)
|
||||
self.view_tag_actions, self.remove_tag_actions = self.tag_actions()
|
||||
@ -276,9 +278,9 @@ class TaggerWindow(QtWidgets.QMainWindow):
|
||||
self.cbMaturityRating.lineEdit().setAcceptDrops(False)
|
||||
|
||||
# hook up the callbacks
|
||||
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.cbSelectedReadTags.dropdownClosed.connect(self.select_read_tags)
|
||||
self.cbSelectedWriteTags.itemChecked.connect(self.select_write_tags)
|
||||
self.cbx_sources.currentIndexChanged.connect(self.select_source)
|
||||
self.btnEditCredit.clicked.connect(self.edit_credit)
|
||||
self.btnAddCredit.clicked.connect(self.add_credit)
|
||||
self.btnRemoveCredit.clicked.connect(self.remove_credit)
|
||||
@ -290,7 +292,7 @@ class TaggerWindow(QtWidgets.QMainWindow):
|
||||
self.page_list_editor.listOrderChanged.connect(self.page_list_order_changed)
|
||||
self.tabWidget.currentChanged.connect(self.tab_changed)
|
||||
|
||||
self.update_metadata_style_tweaks()
|
||||
self.update_tag_tweaks()
|
||||
|
||||
self.show()
|
||||
self.set_app_position()
|
||||
@ -339,14 +341,14 @@ class TaggerWindow(QtWidgets.QMainWindow):
|
||||
def tag_actions(self) -> tuple[dict[str, QtGui.QAction], dict[str, QtGui.QAction]]:
|
||||
view_raw_tags = {}
|
||||
remove_raw_tags = {}
|
||||
for style in metadata_styles.values():
|
||||
view_raw_tags[style.short_name] = self.menuViewRawTags.addAction(f"View Raw {style.name()} Tags")
|
||||
view_raw_tags[style.short_name].setStatusTip(f"View raw {style.name()} tag block from file")
|
||||
view_raw_tags[style.short_name].triggered.connect(functools.partial(self.view_raw_tags, style.short_name))
|
||||
for tag in tags.values():
|
||||
view_raw_tags[tag.id] = self.menuViewRawTags.addAction(f"View Raw {tag.name()} Tags")
|
||||
view_raw_tags[tag.id].setStatusTip(f"View raw {tag.name()} tag block from file")
|
||||
view_raw_tags[tag.id].triggered.connect(functools.partial(self.view_raw_tags, tag.id))
|
||||
|
||||
remove_raw_tags[style.short_name] = self.menuRemove.addAction(f"Remove Raw {style.name()} Tags")
|
||||
remove_raw_tags[style.short_name].setStatusTip(f"Remove {style.name()} tags from comic archive")
|
||||
remove_raw_tags[style.short_name].triggered.connect(functools.partial(self.remove_tags, style.short_name))
|
||||
remove_raw_tags[tag.id] = self.menuRemove.addAction(f"Remove Raw {tag.name()} Tags")
|
||||
remove_raw_tags[tag.id].setStatusTip(f"Remove {tag.name()} tags from comic archive")
|
||||
remove_raw_tags[tag.id].triggered.connect(functools.partial(self.remove_tags, tag.id))
|
||||
|
||||
return view_raw_tags, remove_raw_tags
|
||||
|
||||
@ -423,7 +425,7 @@ class TaggerWindow(QtWidgets.QMainWindow):
|
||||
self.actionRename.triggered.connect(self.rename_archive)
|
||||
self.actionRepackage.triggered.connect(self.repackage_archive)
|
||||
self.actionSettings.triggered.connect(self.show_settings)
|
||||
self.actionWrite_Tags.triggered.connect(self.commit_metadata)
|
||||
self.actionWrite_Tags.triggered.connect(self.write_tags)
|
||||
# Tag Menu
|
||||
self.actionApplyCBLTransform.triggered.connect(self.apply_cbl_transform)
|
||||
self.actionAutoIdentify.triggered.connect(self.auto_identify_search)
|
||||
@ -660,9 +662,9 @@ class TaggerWindow(QtWidgets.QMainWindow):
|
||||
self.menuRemove.setEnabled(enabled)
|
||||
self.menuViewRawTags.setEnabled(enabled)
|
||||
if self.comic_archive is not None:
|
||||
for style in metadata_styles:
|
||||
self.view_tag_actions[style].setEnabled(self.comic_archive.has_metadata(style))
|
||||
self.remove_tag_actions[style].setEnabled(self.comic_archive.has_metadata(style))
|
||||
for tag_id in tags:
|
||||
self.view_tag_actions[tag_id].setEnabled(self.comic_archive.has_tags(tag_id))
|
||||
self.remove_tag_actions[tag_id].setEnabled(self.comic_archive.has_tags(tag_id))
|
||||
|
||||
self.actionWrite_Tags.setEnabled(writeable)
|
||||
|
||||
@ -687,11 +689,11 @@ class TaggerWindow(QtWidgets.QMainWindow):
|
||||
page_count = f" ({ca.get_number_of_pages()} pages)"
|
||||
self.lblPageCount.setText(page_count)
|
||||
|
||||
supported_md = ca.get_supported_metadata()
|
||||
supported_md = ca.get_supported_tags()
|
||||
tag_info = ""
|
||||
for md in supported_md:
|
||||
if ca.has_metadata(md):
|
||||
tag_info += "• " + metadata_styles[md].name() + "\n"
|
||||
if ca.has_tags(md):
|
||||
tag_info += "• " + tags[md].name() + "\n"
|
||||
|
||||
self.lblTagList.setText(tag_info)
|
||||
|
||||
@ -850,7 +852,7 @@ class TaggerWindow(QtWidgets.QMainWindow):
|
||||
self.add_new_credit_entry(row, credit.role.title(), credit.person, credit.primary)
|
||||
|
||||
self.twCredits.setSortingEnabled(True)
|
||||
self.update_metadata_credit_colors()
|
||||
self.update_credit_colors()
|
||||
|
||||
def add_new_credit_entry(self, row: int, role: str, name: str, primary_flag: bool = False) -> None:
|
||||
self.twCredits.insertRow(row)
|
||||
@ -1073,13 +1075,13 @@ class TaggerWindow(QtWidgets.QMainWindow):
|
||||
# Now push the new combined data into the edit controls
|
||||
self.metadata_to_form()
|
||||
|
||||
def commit_metadata(self) -> None:
|
||||
def write_tags(self) -> None:
|
||||
if self.metadata is not None and self.comic_archive is not None:
|
||||
if self.config[0].General__prompt_on_save:
|
||||
reply = QtWidgets.QMessageBox.question(
|
||||
self,
|
||||
"Save Tags",
|
||||
f"Are you sure you wish to save {', '.join([metadata_styles[style].name() for style in self.save_data_styles])} tags to this archive?",
|
||||
f"Are you sure you wish to save {', '.join([tags[tag_id].name() for tag_id in self.selected_write_tags])} tags to this archive?",
|
||||
QtWidgets.QMessageBox.StandardButton.Yes,
|
||||
QtWidgets.QMessageBox.StandardButton.No,
|
||||
)
|
||||
@ -1091,22 +1093,22 @@ class TaggerWindow(QtWidgets.QMainWindow):
|
||||
QtWidgets.QApplication.setOverrideCursor(QtGui.QCursor(QtCore.Qt.CursorShape.WaitCursor))
|
||||
self.form_to_metadata()
|
||||
|
||||
failed_style: str = ""
|
||||
# Save each style
|
||||
for style in self.save_data_styles:
|
||||
success = self.comic_archive.write_metadata(self.metadata, style)
|
||||
failed_tag: str = ""
|
||||
# Save each tag
|
||||
for tag_id in self.selected_write_tags:
|
||||
success = self.comic_archive.write_tags(self.metadata, tag_id)
|
||||
if not success:
|
||||
failed_style = metadata_styles[style].name()
|
||||
failed_tag = tags[tag_id].name()
|
||||
break
|
||||
|
||||
self.comic_archive.load_cache(set(metadata_styles))
|
||||
self.comic_archive.load_cache(set(tags))
|
||||
QtWidgets.QApplication.restoreOverrideCursor()
|
||||
|
||||
if failed_style:
|
||||
if failed_tag:
|
||||
QtWidgets.QMessageBox.warning(
|
||||
self,
|
||||
"Save failed",
|
||||
f"The tag save operation seemed to fail for: {failed_style}",
|
||||
f"The tag save operation seemed to fail for: {failed_tag}",
|
||||
)
|
||||
else:
|
||||
self.clear_dirty_flag()
|
||||
@ -1114,56 +1116,56 @@ class TaggerWindow(QtWidgets.QMainWindow):
|
||||
self.update_menus()
|
||||
|
||||
# Only try to read if write was successful
|
||||
self.metadata, error = self.overlay_ca_read_style(self.load_data_styles, self.comic_archive)
|
||||
self.metadata, error = self.read_selected_tags(self.selected_read_tags, 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",
|
||||
f"One or more of the selected read tags 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!")
|
||||
QtWidgets.QMessageBox.information(self, "Whoops!", "No data to write!")
|
||||
|
||||
def set_load_data_style(self, load_data_styles: list[str]) -> None:
|
||||
def select_read_tags(self, tag_ids: 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(s) now, data in the form will be lost. Are you sure?",
|
||||
"Change Read Tags",
|
||||
"If you change read tag(s) now, data in the form will be lost. Are you sure?",
|
||||
):
|
||||
self.load_data_styles = list(reversed(load_data_styles))
|
||||
self.config[0].internal__load_data_style = self.load_data_styles
|
||||
self.selected_read_tags = list(reversed(tag_ids))
|
||||
self.config[0].internal__read_tags = self.selected_read_tags
|
||||
self.update_menus()
|
||||
if self.comic_archive is not None:
|
||||
self.load_archive(self.comic_archive)
|
||||
else:
|
||||
self.cbLoadDataStyle.itemChanged.disconnect()
|
||||
self.adjust_load_style_combo()
|
||||
self.cbLoadDataStyle.itemChanged.connect(self.set_load_data_style)
|
||||
self.cbSelectedReadTags.itemChanged.disconnect()
|
||||
self.adjust_tags_combo()
|
||||
self.cbSelectedReadTags.itemChanged.connect(self.select_read_tags)
|
||||
|
||||
def set_save_data_style(self) -> None:
|
||||
self.save_data_styles = self.cbSaveDataStyle.currentData()
|
||||
self.config[0].internal__save_data_style = self.save_data_styles
|
||||
self.update_metadata_style_tweaks()
|
||||
def select_write_tags(self) -> None:
|
||||
self.selected_write_tags = self.cbSelectedWriteTags.currentData()
|
||||
self.config[0].internal__write_tags = self.selected_write_tags
|
||||
self.update_tag_tweaks()
|
||||
self.update_menus()
|
||||
|
||||
def set_source(self, s: int) -> None:
|
||||
def select_source(self, s: int) -> None:
|
||||
self.config[0].Sources__source = self.cbx_sources.itemData(s)
|
||||
|
||||
def update_metadata_credit_colors(self) -> None:
|
||||
styles = [metadata_styles[style] for style in self.save_data_styles]
|
||||
def update_credit_colors(self) -> None:
|
||||
selected_tags = [tags[tag_id] for tag_id in self.selected_write_tags]
|
||||
enabled = set()
|
||||
for style in styles:
|
||||
enabled.update(style.supported_attributes)
|
||||
for tag in selected_tags:
|
||||
enabled.update(tag.supported_attributes)
|
||||
|
||||
credit_attributes = [x for x in self.md_attributes.items() if "credits." in x[0]]
|
||||
|
||||
for r in range(self.twCredits.rowCount()):
|
||||
w = self.twCredits.item(r, 1)
|
||||
supports_role = any(style.supports_credit_role(str(w.text())) for style in styles)
|
||||
supports_role = any(tag.supports_credit_role(str(w.text())) for tag in selected_tags)
|
||||
for credit in credit_attributes:
|
||||
widget_enabled = credit[0] in enabled
|
||||
widget = self.twCredits.item(r, credit[1])
|
||||
@ -1171,18 +1173,18 @@ class TaggerWindow(QtWidgets.QMainWindow):
|
||||
widget_enabled = widget_enabled and supports_role
|
||||
enable_widget(widget, widget_enabled)
|
||||
|
||||
def update_metadata_style_tweaks(self) -> None:
|
||||
# depending on the current data style, certain fields are disabled
|
||||
def update_tag_tweaks(self) -> None:
|
||||
# depending on the current data tag, certain fields are disabled
|
||||
enabled_widgets = set()
|
||||
for style in self.save_data_styles:
|
||||
enabled_widgets.update(metadata_styles[style].supported_attributes)
|
||||
for tag_id in self.selected_write_tags:
|
||||
enabled_widgets.update(tags[tag_id].supported_attributes)
|
||||
|
||||
for metadata, widget in self.md_attributes.items():
|
||||
for md_field, widget in self.md_attributes.items():
|
||||
if widget is not None and not isinstance(widget, (int)):
|
||||
enable_widget(widget, metadata in enabled_widgets)
|
||||
enable_widget(widget, md_field in enabled_widgets)
|
||||
|
||||
self.update_metadata_credit_colors()
|
||||
self.page_list_editor.set_metadata_style(self.save_data_styles)
|
||||
self.update_credit_colors()
|
||||
self.page_list_editor.select_read_tags(self.selected_write_tags)
|
||||
|
||||
def cell_double_clicked(self, r: int, c: int) -> None:
|
||||
self.edit_credit()
|
||||
@ -1272,7 +1274,7 @@ class TaggerWindow(QtWidgets.QMainWindow):
|
||||
row = self.twCredits.rowCount()
|
||||
self.add_new_credit_entry(row, new_role, new_name, new_primary)
|
||||
|
||||
self.update_metadata_credit_colors()
|
||||
self.update_credit_colors()
|
||||
self.set_dirty_flag()
|
||||
|
||||
def remove_credit(self) -> None:
|
||||
@ -1313,45 +1315,43 @@ class TaggerWindow(QtWidgets.QMainWindow):
|
||||
def adjust_source_combo(self) -> None:
|
||||
self.cbx_sources.setCurrentIndex(self.cbx_sources.findData(self.config[0].Sources__source))
|
||||
|
||||
def adjust_load_style_combo(self) -> None:
|
||||
"""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)
|
||||
def adjust_tags_combo(self) -> None:
|
||||
"""Select the enabled tags. Since tags are merged in an overlay fashion the last item in the list takes priority. We reverse the order for display to the user"""
|
||||
unchecked = set(tags.keys()) - set(self.selected_read_tags)
|
||||
for i, tag_id in enumerate(reversed(self.selected_read_tags)):
|
||||
item_idx = self.cbSelectedReadTags.findData(tag_id)
|
||||
self.cbSelectedReadTags.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)
|
||||
self.cbSelectedReadTags.moveItem(item_idx, row=i)
|
||||
for tag_id in unchecked:
|
||||
self.cbSelectedReadTags.setItemChecked(self.cbSelectedReadTags.findData(tag_id), 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.cbSaveDataStyle.findData(style), True)
|
||||
for style in unchecked:
|
||||
self.cbSaveDataStyle.setItemChecked(self.cbSaveDataStyle.findData(style), False)
|
||||
self.update_metadata_style_tweaks()
|
||||
# select the current tag_id
|
||||
unchecked = set(tags.keys()) - set(self.selected_write_tags)
|
||||
for tag_id in self.selected_write_tags:
|
||||
self.cbSelectedWriteTags.setItemChecked(self.cbSelectedWriteTags.findData(tag_id), True)
|
||||
for tag_id in unchecked:
|
||||
self.cbSelectedWriteTags.setItemChecked(self.cbSelectedWriteTags.findData(tag_id), False)
|
||||
self.update_tag_tweaks()
|
||||
|
||||
def populate_style_names(self) -> None:
|
||||
def populate_tag_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():
|
||||
if self.config[0].Metadata_Options__use_short_metadata_names:
|
||||
self.cbSaveDataStyle.addItem(style.short_name.upper(), style.short_name)
|
||||
self.cbLoadDataStyle.addItem(style.short_name.upper(), style.short_name)
|
||||
self.cbSelectedWriteTags.clear()
|
||||
self.cbSelectedReadTags.clear()
|
||||
# Add the entries to the tag comboboxes
|
||||
for tag in tags.values():
|
||||
if self.config[0].Metadata_Options__use_short_tag_names:
|
||||
self.cbSelectedWriteTags.addItem(tag.id.upper(), tag.id)
|
||||
self.cbSelectedReadTags.addItem(tag.id.upper(), tag.id)
|
||||
else:
|
||||
self.cbSaveDataStyle.addItem(style.name(), style.short_name)
|
||||
self.cbLoadDataStyle.addItem(style.name(), style.short_name)
|
||||
self.cbSelectedWriteTags.addItem(tag.name(), tag.id)
|
||||
self.cbSelectedReadTags.addItem(tag.name(), tag.id)
|
||||
|
||||
def populate_combo_boxes(self) -> None:
|
||||
self.populate_style_names()
|
||||
self.populate_tag_names()
|
||||
|
||||
self.adjust_load_style_combo()
|
||||
self.adjust_save_style_combo()
|
||||
self.adjust_tags_combo()
|
||||
|
||||
# Add talker entries
|
||||
for t_id, talker in self.talkers.items():
|
||||
@ -1449,26 +1449,26 @@ class TaggerWindow(QtWidgets.QMainWindow):
|
||||
self.cbFormat.addItem("Year One")
|
||||
|
||||
def remove_auto(self) -> None:
|
||||
self.remove_tags(self.save_data_styles)
|
||||
self.remove_tags(self.selected_write_tags)
|
||||
|
||||
def remove_tags(self, styles: list[str]) -> None:
|
||||
# remove the indicated tags from the archive
|
||||
def remove_tags(self, tag_ids: list[str]) -> None:
|
||||
# remove the indicated tag_ids from the archive
|
||||
ca_list = self.fileSelectionList.get_selected_archive_list()
|
||||
has_md_count = 0
|
||||
file_md_count = {}
|
||||
for style in styles:
|
||||
file_md_count[style] = 0
|
||||
for tag_id in tag_ids:
|
||||
file_md_count[tag_id] = 0
|
||||
for ca in ca_list:
|
||||
for style in styles:
|
||||
if ca.has_metadata(style):
|
||||
for tag_id in tag_ids:
|
||||
if ca.has_tags(tag_id):
|
||||
has_md_count += 1
|
||||
file_md_count[style] += 1
|
||||
file_md_count[tag_id] += 1
|
||||
|
||||
if has_md_count == 0:
|
||||
QtWidgets.QMessageBox.information(
|
||||
self,
|
||||
"Remove Tags",
|
||||
f"No archives with {', '.join([metadata_styles[style].name() for style in styles])} tags selected!",
|
||||
f"No archives with {', '.join([tags[tag_id].name() for tag_id in tag_ids])} tags selected!",
|
||||
)
|
||||
return
|
||||
|
||||
@ -1481,7 +1481,7 @@ class TaggerWindow(QtWidgets.QMainWindow):
|
||||
reply = QtWidgets.QMessageBox.question(
|
||||
self,
|
||||
"Remove Tags",
|
||||
f"Are you sure you wish to remove {', '.join([f'{metadata_styles[style].name()} tags from {count} files' for style, count in file_md_count.items()])} removing a total of {has_md_count} tag(s)?",
|
||||
f"Are you sure you wish to remove {', '.join([f'{tags[tag_id].name()} tags from {count} files' for tag_id, count in file_md_count.items()])} removing a total of {has_md_count} tag(s)?",
|
||||
QtWidgets.QMessageBox.StandardButton.Yes,
|
||||
QtWidgets.QMessageBox.StandardButton.No,
|
||||
)
|
||||
@ -1503,16 +1503,16 @@ class TaggerWindow(QtWidgets.QMainWindow):
|
||||
progdialog.setValue(prog_idx)
|
||||
progdialog.setLabelText(str(ca.path))
|
||||
QtCore.QCoreApplication.processEvents()
|
||||
for style in styles:
|
||||
if ca.has_metadata(style) and ca.is_writable():
|
||||
if ca.remove_metadata(style):
|
||||
for tag_id in tag_ids:
|
||||
if ca.has_tags(tag_id) and ca.is_writable():
|
||||
if ca.remove_tags(tag_id):
|
||||
success_count += 1
|
||||
else:
|
||||
failed_list.append(ca.path)
|
||||
# Abandon any further tag removals to prevent any greater damage to archive
|
||||
break
|
||||
ca.reset_cache()
|
||||
ca.load_cache(set(metadata_styles))
|
||||
ca.load_cache(set(tags))
|
||||
|
||||
progdialog.hide()
|
||||
QtCore.QCoreApplication.processEvents()
|
||||
@ -1536,22 +1536,22 @@ class TaggerWindow(QtWidgets.QMainWindow):
|
||||
ca_list = self.fileSelectionList.get_selected_archive_list()
|
||||
has_src_count = 0
|
||||
|
||||
src_styles: list[str] = self.load_data_styles
|
||||
dest_styles: list[str] = self.save_data_styles
|
||||
src_tag_ids: list[str] = self.selected_read_tags
|
||||
dest_tag_ids: list[str] = self.selected_write_tags
|
||||
|
||||
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 len(src_tag_ids) == 1 and src_tag_ids[0] in dest_tag_ids:
|
||||
# Remove the read tag from the write tag
|
||||
dest_tag_ids.remove(src_tag_ids[0])
|
||||
|
||||
if not dest_styles:
|
||||
if not dest_tag_ids:
|
||||
QtWidgets.QMessageBox.information(
|
||||
self, "Copy Tags", "Can't copy tag style onto itself. Read style and modify style must be different."
|
||||
self, "Copy Tags", "Can't copy tag tag onto itself. Read tag and modify tag must be different."
|
||||
)
|
||||
return
|
||||
|
||||
for ca in ca_list:
|
||||
for style in src_styles:
|
||||
if ca.has_metadata(style):
|
||||
for tag_id in src_tag_ids:
|
||||
if ca.has_tags(tag_id):
|
||||
has_src_count += 1
|
||||
continue
|
||||
|
||||
@ -1559,7 +1559,7 @@ class TaggerWindow(QtWidgets.QMainWindow):
|
||||
QtWidgets.QMessageBox.information(
|
||||
self,
|
||||
"Copy Tags",
|
||||
f"No archives with {', '.join([metadata_styles[style].name() for style in src_styles])} tags selected!",
|
||||
f"No archives with {', '.join([tags[tag_id].name() for tag_id in src_tag_ids])} tags selected!",
|
||||
)
|
||||
return
|
||||
|
||||
@ -1573,8 +1573,8 @@ class TaggerWindow(QtWidgets.QMainWindow):
|
||||
self,
|
||||
"Copy Tags",
|
||||
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"{', '.join([tags[tag_id].name() for tag_id in src_tag_ids])} "
|
||||
f"to {', '.join([tags[tag_id].name() for tag_id in dest_tag_ids])} tags in "
|
||||
f"{has_src_count} archive(s)?",
|
||||
QtWidgets.QMessageBox.StandardButton.Yes,
|
||||
QtWidgets.QMessageBox.StandardButton.No,
|
||||
@ -1592,15 +1592,15 @@ class TaggerWindow(QtWidgets.QMainWindow):
|
||||
success_count = 0
|
||||
for prog_idx, ca in enumerate(ca_list, 1):
|
||||
ca_saved = False
|
||||
md, error = self.overlay_ca_read_style(src_styles, ca)
|
||||
md, error = self.read_selected_tags(src_tag_ids, ca)
|
||||
if error is not None:
|
||||
failed_list.append(ca.path)
|
||||
continue
|
||||
if md.is_empty:
|
||||
continue
|
||||
|
||||
for style in dest_styles:
|
||||
if ca.has_metadata(style):
|
||||
for tag_id in dest_tag_ids:
|
||||
if ca.has_tags(tag_id):
|
||||
QtCore.QCoreApplication.processEvents()
|
||||
if prog_dialog.wasCanceled():
|
||||
break
|
||||
@ -1610,10 +1610,10 @@ class TaggerWindow(QtWidgets.QMainWindow):
|
||||
center_window_on_parent(prog_dialog)
|
||||
QtCore.QCoreApplication.processEvents()
|
||||
|
||||
if style == "cbi" and self.config[0].Metadata_Options__apply_transform_on_bulk_operation:
|
||||
if tag_id == "cbi" and self.config[0].Metadata_Options__apply_transform_on_bulk_operation:
|
||||
md = CBLTransformer(md, self.config[0]).apply()
|
||||
|
||||
if ca.write_metadata(md, style):
|
||||
if ca.write_tags(md, tag_id):
|
||||
if not ca_saved:
|
||||
success_count += 1
|
||||
ca_saved = True
|
||||
@ -1621,7 +1621,7 @@ class TaggerWindow(QtWidgets.QMainWindow):
|
||||
failed_list.append(ca.path)
|
||||
|
||||
ca.reset_cache()
|
||||
ca.load_cache({*self.load_data_styles, *self.save_data_styles})
|
||||
ca.load_cache({*self.selected_read_tags, *self.selected_write_tags})
|
||||
|
||||
prog_dialog.hide()
|
||||
QtCore.QCoreApplication.processEvents()
|
||||
@ -1651,28 +1651,19 @@ class TaggerWindow(QtWidgets.QMainWindow):
|
||||
def identify_and_tag_single_archive(
|
||||
self, ca: ComicArchive, match_results: OnlineMatchResults, dlg: AutoTagStartWindow
|
||||
) -> tuple[bool, OnlineMatchResults]:
|
||||
def metadata_save() -> bool:
|
||||
for style in self.save_data_styles:
|
||||
# write out the new data
|
||||
if not ca.write_metadata(md, style):
|
||||
self.auto_tag_log(
|
||||
f"{metadata_styles[style].name()} save failed! Aborting any additional style saves.\n"
|
||||
)
|
||||
return False
|
||||
return True
|
||||
|
||||
success = False
|
||||
ii = IssueIdentifier(ca, self.config[0], self.current_talker())
|
||||
|
||||
# read in metadata, and parse file name if not there
|
||||
md, error = self.overlay_ca_read_style(self.load_data_styles, ca)
|
||||
# read in tags, and parse file name if not there
|
||||
md, error = self.read_selected_tags(self.selected_read_tags, 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.",
|
||||
f"One or more of the read tags 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)
|
||||
logger.error("Failed to load tags from %s: %s", self.ca.path, error)
|
||||
return False, match_results
|
||||
|
||||
if md.is_empty:
|
||||
@ -1687,7 +1678,7 @@ class TaggerWindow(QtWidgets.QMainWindow):
|
||||
)
|
||||
if dlg.ignore_leading_digits_in_filename and md.series is not None:
|
||||
# remove all leading numbers
|
||||
md.series = re.sub(r"([\d.]*)(.*)", r"\2", md.series)
|
||||
md.series = re.sub(r"(^[\d.]*)(.*)", r"\2", md.series)
|
||||
|
||||
# use the dialog specified search string
|
||||
if dlg.search_string:
|
||||
@ -1806,8 +1797,8 @@ class TaggerWindow(QtWidgets.QMainWindow):
|
||||
|
||||
if ct_md is not None:
|
||||
temp_opts = cast(ct_ns, settngs.get_namespace(self.config, True, True, True, False)[0])
|
||||
if dlg.cbxRemoveMetadata.isChecked():
|
||||
temp_opts.Auto_Tag__clear_metadata
|
||||
if dlg.cbxClearMetadata.isChecked():
|
||||
temp_opts.Auto_Tag__clear_tags
|
||||
|
||||
md = prepare_metadata(md, ct_md, temp_opts)
|
||||
|
||||
@ -1818,11 +1809,21 @@ class TaggerWindow(QtWidgets.QMainWindow):
|
||||
online_results=matches,
|
||||
match_status=MatchStatus.good_match,
|
||||
md=md,
|
||||
tags_written=self.save_data_styles,
|
||||
tags_written=self.selected_write_tags,
|
||||
)
|
||||
|
||||
# Save styles
|
||||
if metadata_save():
|
||||
def write_Tags() -> bool:
|
||||
for tag_id in self.selected_write_tags:
|
||||
# write out the new data
|
||||
if not ca.write_tags(md, tag_id):
|
||||
self.auto_tag_log(
|
||||
f"{tags[tag_id].name()} save failed! Aborting any additional tag saves.\n"
|
||||
)
|
||||
return False
|
||||
return True
|
||||
|
||||
# Save tags
|
||||
if write_Tags():
|
||||
match_results.good_matches.append(res)
|
||||
success = True
|
||||
self.auto_tag_log("Save complete!\n")
|
||||
@ -1831,13 +1832,13 @@ class TaggerWindow(QtWidgets.QMainWindow):
|
||||
match_results.write_failures.append(res)
|
||||
|
||||
ca.reset_cache()
|
||||
ca.load_cache({*self.load_data_styles, *self.save_data_styles})
|
||||
ca.load_cache({*self.selected_read_tags, *self.selected_write_tags})
|
||||
|
||||
return success, match_results
|
||||
|
||||
def auto_tag(self) -> None:
|
||||
ca_list = self.fileSelectionList.get_selected_archive_list()
|
||||
styles = self.save_data_styles
|
||||
tag_names = ", ".join([tags[tag_id].name() for tag_id in self.selected_write_tags])
|
||||
|
||||
if len(ca_list) == 0:
|
||||
QtWidgets.QMessageBox.information(self, "Auto-Tag", "No archives selected!")
|
||||
@ -1853,8 +1854,7 @@ class TaggerWindow(QtWidgets.QMainWindow):
|
||||
self.config[0],
|
||||
(
|
||||
f"You have selected {len(ca_list)} archive(s) to automatically identify and write "
|
||||
+ ", ".join([metadata_styles[style].name() for style in styles])
|
||||
+ " tags to.\n\nPlease choose config below, and select OK to Auto-Tag."
|
||||
+ f"{tag_names} tags to.\n\nPlease choose config below, and select OK to Auto-Tag."
|
||||
),
|
||||
)
|
||||
|
||||
@ -1880,7 +1880,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_styles[0]).get_cover_page_index_list()[0]
|
||||
cover_idx = ca.read_tags(self.selected_read_tags[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)
|
||||
@ -1914,7 +1914,7 @@ class TaggerWindow(QtWidgets.QMainWindow):
|
||||
self.atprogdialog = None
|
||||
|
||||
summary = ""
|
||||
summary += f"Successfully added {', '.join([metadata_styles[style].name() for style in self.save_data_styles])} tags to {len(match_results.good_matches)} archive(s)\n"
|
||||
summary += f"Successfully added {tag_names} tags to {len(match_results.good_matches)} archive(s)\n"
|
||||
|
||||
if len(match_results.multiple_matches) > 0:
|
||||
summary += f"Archives with multiple matches: {len(match_results.multiple_matches)}\n"
|
||||
@ -1950,7 +1950,7 @@ class TaggerWindow(QtWidgets.QMainWindow):
|
||||
matchdlg = AutoTagMatchWindow(
|
||||
self,
|
||||
match_results.multiple_matches,
|
||||
styles,
|
||||
self.selected_write_tags,
|
||||
lambda match: self.current_talker().fetch_comic_data(match.issue_id),
|
||||
self.config[0],
|
||||
self.current_talker(),
|
||||
@ -1988,7 +1988,7 @@ class TaggerWindow(QtWidgets.QMainWindow):
|
||||
if reply == QtWidgets.QMessageBox.StandardButton.Discard:
|
||||
return True
|
||||
if reply == QtWidgets.QMessageBox.StandardButton.Save:
|
||||
self.commit_metadata()
|
||||
self.write_tags()
|
||||
return True
|
||||
return False
|
||||
return True
|
||||
@ -2024,12 +2024,12 @@ class TaggerWindow(QtWidgets.QMainWindow):
|
||||
def page_browser_closed(self) -> None:
|
||||
self.page_browser = None
|
||||
|
||||
def view_raw_tags(self, style: str) -> None:
|
||||
if self.comic_archive is not None and self.comic_archive.has_metadata(style):
|
||||
md_style = metadata_styles[style]
|
||||
def view_raw_tags(self, tag_id: str) -> None:
|
||||
tag = tags[tag_id]
|
||||
if self.comic_archive is not None and self.comic_archive.has_tags(tag.id):
|
||||
dlg = LogWindow(self)
|
||||
dlg.set_text(self.comic_archive.read_metadata_string(style))
|
||||
dlg.setWindowTitle(f"Raw {md_style.name()} Tag View")
|
||||
dlg.set_text(self.comic_archive.read_raw_tags(tag.id))
|
||||
dlg.setWindowTitle(f"Raw {tag.name()} Tag View")
|
||||
dlg.exec()
|
||||
|
||||
def show_wiki(self) -> None:
|
||||
@ -2075,7 +2075,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_styles, self.config, self.talkers)
|
||||
dlg = RenameWindow(self, ca_list, self.selected_write_tags, self.config, self.talkers)
|
||||
dlg.setModal(True)
|
||||
if dlg.exec() and self.comic_archive is not None:
|
||||
self.fileSelectionList.update_selected_rows()
|
||||
@ -2095,25 +2095,23 @@ 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
|
||||
|
||||
self.metadata, error = self.overlay_ca_read_style(self.load_data_styles, self.comic_archive)
|
||||
self.metadata, error = self.read_selected_tags(self.selected_write_tags, 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")
|
||||
logger.error("Failed to load tags from %s: %s", self.comic_archive.path, error)
|
||||
self.exception(f"Failed to load tags from {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]:
|
||||
def read_selected_tags(self, tag_ids: list[str], ca: ComicArchive) -> tuple[GenericMetadata, Exception | None]:
|
||||
md = GenericMetadata()
|
||||
error = None
|
||||
try:
|
||||
for style in load_data_styles:
|
||||
metadata = ca.read_metadata(style)
|
||||
for tag_id in tag_ids:
|
||||
metadata = ca.read_tags(tag_id)
|
||||
md.overlay(
|
||||
metadata,
|
||||
mode=self.config[0].Metadata_Options__comic_merge,
|
||||
merge_lists=self.config[0].Metadata_Options__comic_merge_lists,
|
||||
mode=self.config[0].Metadata_Options__tag_merge,
|
||||
merge_lists=self.config[0].Metadata_Options__tag_merge_lists,
|
||||
)
|
||||
except Exception as e:
|
||||
error = e
|
||||
|
@ -78,12 +78,12 @@
|
||||
</widget>
|
||||
</item>
|
||||
<item row="6" column="0">
|
||||
<widget class="QCheckBox" name="cbxRemoveMetadata">
|
||||
<widget class="QCheckBox" name="cbxClearMetadata">
|
||||
<property name="toolTip">
|
||||
<string>Removes existing metadata before applying retrieved metadata</string>
|
||||
<string>Removes existing tags before applying downloaded metadata</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Clear Existing Metadata during import</string>
|
||||
<string>Clear Existing tags when downloading metadata</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
@ -129,7 +129,7 @@
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Specify series search string for all selected archives:</string>
|
||||
<string>Specify series search for all selected archives:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
|
@ -73,7 +73,7 @@ class CheckableComboBox(QtWidgets.QComboBox):
|
||||
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)
|
||||
# Expected that state of *all* checkboxes will be set ('adjust_tags_combo' in taggerwindow.py)
|
||||
if self.count() == 1:
|
||||
self.model().item(0).setCheckState(Qt.CheckState.Checked)
|
||||
|
||||
@ -276,7 +276,7 @@ class CheckableOrderComboBox(QtWidgets.QComboBox):
|
||||
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"
|
||||
"Select which read tag(s) to use", "Move item up in priority", "Move item down in priority"
|
||||
)
|
||||
self.setItemDelegate(itemDelegate)
|
||||
|
||||
@ -344,7 +344,7 @@ class CheckableOrderComboBox(QtWidgets.QComboBox):
|
||||
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)
|
||||
# Expected that state of *all* checkboxes will be set ('adjust_tags_combo' in taggerwindow.py)
|
||||
if self.count() == 1:
|
||||
self.model().item(0).setCheckState(Qt.CheckState.Checked)
|
||||
|
||||
|
@ -50,10 +50,10 @@
|
||||
</column>
|
||||
<column>
|
||||
<property name="text">
|
||||
<string>MD</string>
|
||||
<string>Tags</string>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Metadata</string>
|
||||
<string>Tags</string>
|
||||
</property>
|
||||
<property name="textAlignment">
|
||||
<set>AlignCenter</set>
|
||||
|
@ -6,8 +6,8 @@
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>703</width>
|
||||
<height>615</height>
|
||||
<width>1095</width>
|
||||
<height>642</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
@ -190,7 +190,7 @@
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="label_9">
|
||||
<property name="text">
|
||||
<string>Always use Publisher Filter on manual searches:</string>
|
||||
<string>Enable the publisher filter:</string>
|
||||
</property>
|
||||
<property name="buddy">
|
||||
<cstring>cbxUseFilter</cstring>
|
||||
@ -211,7 +211,7 @@
|
||||
<widget class="QLabel" name="label_4">
|
||||
<property name="text">
|
||||
<string>Publisher Filter:
|
||||
One Publisher per line</string>
|
||||
1 Publisher per line</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
|
||||
@ -274,7 +274,8 @@ One Publisher per line</string>
|
||||
<item row="4" column="0">
|
||||
<widget class="QLabel" name="label_5">
|
||||
<property name="text">
|
||||
<string><html><head/><body><p>These settings are for the automatic issue identifier which searches online for matches. </p><p>Hover the mouse over an entry field for more info.</p></body></html></string>
|
||||
<string>These settings are for the automatic issue identifier which searches online for matches.
|
||||
Hover the mouse over an option for more info.</string>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>true</bool>
|
||||
@ -302,13 +303,6 @@ One Publisher per line</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QCheckBox" name="cbxClearFormBeforePopulating">
|
||||
<property name="text">
|
||||
<string>Clear all existing metadata during import, default is to merge metadata.</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<widget class="QWidget" name="tFilenameParser">
|
||||
@ -417,7 +411,7 @@ One Publisher per line</string>
|
||||
</widget>
|
||||
<widget class="QWidget" name="tComicTalkers">
|
||||
<attribute name="title">
|
||||
<string>Metadata Sources</string>
|
||||
<string>Metadata Download</string>
|
||||
</attribute>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_4"/>
|
||||
</widget>
|
||||
@ -460,7 +454,7 @@ One Publisher per line</string>
|
||||
<number>6</number>
|
||||
</property>
|
||||
<item row="0" column="0">
|
||||
<widget class="QCheckBox" name="cbxShortMetadataNames">
|
||||
<widget class="QCheckBox" name="cbxShortTagNames">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Minimum" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
@ -468,20 +462,27 @@ One Publisher per line</string>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Use the short name for the metadata styles (CBI, CR, etc.)</string>
|
||||
<string>Use the short name for tags (CBI, CR, etc.)</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Use "short" names for metadata styles</string>
|
||||
<string>Use "short" names for tags</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QCheckBox" name="cbxEnableCR">
|
||||
<property name="toolTip">
|
||||
<string>Turn off to only use the CIX metadata type</string>
|
||||
<string>Turn off to only use the CIX tags</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Enable ComicRack Metadata Type (needs a restart)</string>
|
||||
<string>Enable the ComicRack metadata tags (needs a restart)</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QCheckBox" name="cbxClearFormBeforePopulating">
|
||||
<property name="text">
|
||||
<string>Clear all existing tags during metadata download, default is to merge downloaded metadata with existing tags.</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
@ -524,7 +525,7 @@ One Publisher per line</string>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="cbxApplyCBLTransformOnCVIMport">
|
||||
<property name="text">
|
||||
<string>Apply CBL Transforms on ComicVine Import</string>
|
||||
<string>Apply CBL Transforms on metadata download</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
@ -640,14 +641,17 @@ One Publisher per line</string>
|
||||
</property>
|
||||
<item row="0" column="0">
|
||||
<widget class="QGroupBox" name="groupBox_7">
|
||||
<property name="toolTip">
|
||||
<string>The merge mode to use when reading multiple tags from a single comic book archive. ComicRack, ComicBookInfo, etc...</string>
|
||||
</property>
|
||||
<property name="title">
|
||||
<string>Comic Merge</string>
|
||||
<string>Tags Merge</string>
|
||||
</property>
|
||||
<layout class="QFormLayout" name="formLayout">
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="lblOverlayComic">
|
||||
<widget class="QLabel" name="lblTagsMergeMode">
|
||||
<property name="toolTip">
|
||||
<string>The merge mode to use when reading multiple metadata styles from a single comic archive (ComicRack, ComicBookInfo, etc.)</string>
|
||||
<string>The merge mode to use when reading multiple tags from a single comic book archive. ComicRack, ComicBookInfo, etc...</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Merge Mode:</string>
|
||||
@ -656,12 +660,12 @@ One Publisher per line</string>
|
||||
<number>6</number>
|
||||
</property>
|
||||
<property name="buddy">
|
||||
<cstring>cbxOverlayReadStyle</cstring>
|
||||
<cstring>cbTagsMergeMode</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QComboBox" name="cbMergeModeComic">
|
||||
<widget class="QComboBox" name="cbTagsMergeMode">
|
||||
<property name="enabled">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
@ -672,14 +676,14 @@ One Publisher per line</string>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>The merge mode to use when reading multiple metadata styles from a comic archive (ComicRack, ComicBookInfo, etc.)</string>
|
||||
<string>The merge mode to use when reading multiple metadata types from a single comic book archive. ComicRack, ComicBookInfo, etc...</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QCheckBox" name="cbxMergeListsComic">
|
||||
<widget class="QCheckBox" name="cbxTagsMergeLists">
|
||||
<property name="toolTip">
|
||||
<string>Merge lists (characters, tags, locations, etc.) together or the "new" list replaces the old</string>
|
||||
<string>Merge lists (characters, tags, locations, etc.) together or follow the Merge Mode</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Merge Lists</string>
|
||||
@ -691,14 +695,17 @@ One Publisher per line</string>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QGroupBox" name="groupBox_8">
|
||||
<property name="toolTip">
|
||||
<string>The merge mode to use when downloading metadata from Comic Vine, Metron, GCD, etc...</string>
|
||||
</property>
|
||||
<property name="title">
|
||||
<string>Metadata Merge</string>
|
||||
<string>Download Merge</string>
|
||||
</property>
|
||||
<layout class="QFormLayout" name="formLayout_4">
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="lblOverlayMetadata">
|
||||
<widget class="QLabel" name="lblDownloadMergeMode">
|
||||
<property name="toolTip">
|
||||
<string>The merge mode to use when fetching metadata from a Metadata Source (Comic Vine, Metron, GCD, etc.)</string>
|
||||
<string>The merge mode to use when downloading metadata from Comic Vine, Metron, GCD, etc...</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Merge Mode:</string>
|
||||
@ -707,12 +714,12 @@ One Publisher per line</string>
|
||||
<number>6</number>
|
||||
</property>
|
||||
<property name="buddy">
|
||||
<cstring>cbxOverlaySource</cstring>
|
||||
<cstring>cbDownloadMergeMode</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QComboBox" name="cbMergeModeMetadata">
|
||||
<widget class="QComboBox" name="cbDownloadMergeMode">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
@ -720,14 +727,14 @@ One Publisher per line</string>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>The merge mode to use when fetching data from a Metadata Source (Comic Vine, Metron, GCD, etc.)</string>
|
||||
<string>The merge mode to use when downloading metadata from Comic Vine, Metron, GCD, etc...</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QCheckBox" name="cbxMergeListsMetadata">
|
||||
<property name="toolTip">
|
||||
<string>Merge lists (characters, tags, locations, etc.) together or the "new" list replaces the old</string>
|
||||
<string>Merge lists (characters, tags, locations, etc.) together or follow the Merge Mode</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Merge Lists</string>
|
||||
@ -795,13 +802,13 @@ One Publisher per line</string>
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="plainText">
|
||||
<string>Overlays all the non-empty values of the new metadata (e.g. Comic Vine) on top of the current metadata.
|
||||
<string>Overlays all non-empty values of the new metadata (e.g. Comic Vine) on top of the existing metadata.
|
||||
|
||||
See the Lists tab for controlling how lists are handled.
|
||||
|
||||
Example:
|
||||
(Series=the batman, Issue=1, Tags=[batman,joker,robin])
|
||||
+ (Series=Batman, Publisher=DC Comics, Tags=[mystery,action])
|
||||
existing(Series=the batman, Issue=1, Tags=[batman,joker,robin])
|
||||
+ new(Series=Batman, Publisher=DC Comics, Tags=[mystery,action])
|
||||
|
||||
= (Series=Batman, Issue=1, Publisher=DC Comics, Tags=[mystery,action])</string>
|
||||
</property>
|
||||
@ -835,13 +842,13 @@ Example:
|
||||
<item>
|
||||
<widget class="QPlainTextEdit" name="addTextEdit">
|
||||
<property name="plainText">
|
||||
<string>Adds any metadata that is is missing in the current metadata but present in the new metadata (e.g. Comic Vine).
|
||||
<string>Adds any metadata that is is missing in the existing metadata but present in the new metadata.
|
||||
|
||||
See the Lists tab for controlling how lists are handled.
|
||||
|
||||
Example:
|
||||
(Series=batman, Issue=1, Tags=[batman,joker,robin])
|
||||
+ (Series=Superman, Issue=10, Publisher=DC Comics, Tags=[mystery,action])
|
||||
existing(Series=batman, Issue=1, Tags=[batman,joker,robin])
|
||||
+ new(Series=Superman, Issue=10, Publisher=DC Comics, Tags=[mystery,action])
|
||||
|
||||
= (Series=batman, Issue=1, Tags=[batman,joker,robin], Publisher=DC Comics)</string>
|
||||
</property>
|
||||
@ -880,6 +887,7 @@ Example:
|
||||
Example Merge:
|
||||
(Tags=[batman,joker,robin])
|
||||
+ (Tags=[mystery,action])
|
||||
|
||||
= (Tags=[batman,joker,robin,mystery,action])</string>
|
||||
</property>
|
||||
<property name="textInteractionFlags">
|
||||
|
@ -79,11 +79,12 @@ border-radius: 4px;
|
||||
</property>
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="lbl_md_source">
|
||||
<property name="text">
|
||||
<string>Data Source</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter</set>
|
||||
<set>Qt::AlignRight|Qt::AlignVCenter</set>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Metadata
|
||||
Source</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
@ -91,26 +92,32 @@ border-radius: 4px;
|
||||
<widget class="QComboBox" name="cbx_sources"/>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="CheckableOrderComboBox" name="cbLoadDataStyle">
|
||||
<widget class="CheckableOrderComboBox" name="cbSelectedReadTags">
|
||||
<property name="toolTip">
|
||||
<string>At least one read style must be selected</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<widget class="CheckableComboBox" name="cbSaveDataStyle"/>
|
||||
<widget class="CheckableComboBox" name="cbSelectedWriteTags"/>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="lbl_read_style">
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignRight|Qt::AlignVCenter</set>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Read Style</string>
|
||||
<string>Read Tags</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="lbl_modify_style">
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignRight|Qt::AlignVCenter</set>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Modify Styles</string>
|
||||
<string>Write Tags</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
@ -1300,7 +1307,7 @@ border-radius: 4px;
|
||||
</widget>
|
||||
<widget class="QMenu" name="menuTags">
|
||||
<property name="title">
|
||||
<string>Tags</string>
|
||||
<string>Metadata</string>
|
||||
</property>
|
||||
<addaction name="actionClearEntryForm"/>
|
||||
<addaction name="actionParse_Filename"/>
|
||||
@ -1442,7 +1449,7 @@ border-radius: 4px;
|
||||
<string>Clear Form</string>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Clear all metadata for the current comic</string>
|
||||
<string>Clear all the data on the screen</string>
|
||||
</property>
|
||||
<property name="statusTip">
|
||||
<string>Clear all the data on the screen</string>
|
||||
@ -1461,10 +1468,10 @@ border-radius: 4px;
|
||||
<string>Copy Tags</string>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Copy metadata from the selected 'read' tags to the selected 'modify' tags</string>
|
||||
<string>Copy the selected 'read' tags to the selected 'write' tags</string>
|
||||
</property>
|
||||
<property name="statusTip">
|
||||
<string>Copy metadata from one type of tag to another</string>
|
||||
<string>Copy the selected 'read' tags to the selected 'write' tags</string>
|
||||
</property>
|
||||
<property name="shortcut">
|
||||
<string>Ctrl+C</string>
|
||||
@ -1721,8 +1728,8 @@ border-radius: 4px;
|
||||
<slot>trigger()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>900</x>
|
||||
<y>536</y>
|
||||
<x>359</x>
|
||||
<y>108</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>-1</x>
|
||||
@ -1737,8 +1744,8 @@ border-radius: 4px;
|
||||
<slot>trigger()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>900</x>
|
||||
<y>576</y>
|
||||
<x>359</x>
|
||||
<y>108</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>-1</x>
|
||||
|
@ -31,7 +31,7 @@ from pyrate_limiter import Limiter, RequestRate
|
||||
from typing_extensions import Required, TypedDict
|
||||
|
||||
from comicapi import utils
|
||||
from comicapi.genericmetadata import ComicSeries, GenericMetadata, TagOrigin
|
||||
from comicapi.genericmetadata import ComicSeries, GenericMetadata, MetadataOrigin
|
||||
from comicapi.issuestring import IssueString
|
||||
from comicapi.utils import LocationParseError, parse_url
|
||||
from comictalker import talker_utils
|
||||
@ -633,7 +633,7 @@ class ComicVineTalker(ComicTalker):
|
||||
|
||||
def _map_comic_issue_to_metadata(self, issue: CVIssue, series: ComicSeries) -> GenericMetadata:
|
||||
md = GenericMetadata(
|
||||
tag_origin=TagOrigin(self.id, self.name),
|
||||
data_origin=MetadataOrigin(self.id, self.name),
|
||||
issue_id=utils.xlate(issue.get("id")),
|
||||
series_id=series.id,
|
||||
title_aliases=set(utils.split(issue.get("aliases"), "\n")),
|
||||
|
@ -64,10 +64,10 @@ comicapi.archiver =
|
||||
sevenzip = comicapi.archivers.sevenzip:SevenZipArchiver
|
||||
rar = comicapi.archivers.rar:RarArchiver
|
||||
folder = comicapi.archivers.folder:FolderArchiver
|
||||
comicapi.metadata =
|
||||
cr = comicapi.metadata.comicrack:ComicRack
|
||||
cbi = comicapi.metadata.comicbookinfo:ComicBookInfo
|
||||
comet = comicapi.metadata.comet:CoMet
|
||||
comicapi.tags =
|
||||
cr = comicapi.tags.comicrack:ComicRack
|
||||
cbi = comicapi.tags.comicbookinfo:ComicBookInfo
|
||||
comet = comicapi.tags.comet:CoMet
|
||||
comictagger.talker =
|
||||
comicvine = comictalker.talkers.comicvine:ComicVineTalker
|
||||
pyinstaller40 =
|
||||
|
@ -271,13 +271,13 @@ metadata_prepared = (
|
||||
(
|
||||
(
|
||||
comicapi.genericmetadata.GenericMetadata(
|
||||
issue_id="123", tag_origin=comicapi.genericmetadata.TagOrigin("SOURCE", "Source")
|
||||
issue_id="123", data_origin=comicapi.genericmetadata.MetadataOrigin("SOURCE", "Source")
|
||||
),
|
||||
comicapi.genericmetadata.GenericMetadata(),
|
||||
),
|
||||
comicapi.genericmetadata.GenericMetadata(
|
||||
issue_id="123",
|
||||
tag_origin=comicapi.genericmetadata.TagOrigin("SOURCE", "Source"),
|
||||
data_origin=comicapi.genericmetadata.MetadataOrigin("SOURCE", "Source"),
|
||||
notes="Tagged with ComicTagger 1.3.2a5 using info from Source on 2022-04-16 15:52:26. [Issue ID 123]",
|
||||
),
|
||||
),
|
||||
|
@ -170,7 +170,7 @@ comic_series_result = comicapi.genericmetadata.ComicSeries(
|
||||
)
|
||||
date = utils.parse_date_str(cv_issue_result["results"]["cover_date"])
|
||||
comic_issue_result = comicapi.genericmetadata.GenericMetadata(
|
||||
tag_origin=comicapi.genericmetadata.TagOrigin("comicvine", "Comic Vine"),
|
||||
data_origin=comicapi.genericmetadata.MetadataOrigin("comicvine", "Comic Vine"),
|
||||
title_aliases=set(),
|
||||
month=date[1],
|
||||
year=date[2],
|
||||
@ -190,7 +190,7 @@ comic_issue_result = comicapi.genericmetadata.GenericMetadata(
|
||||
|
||||
cv_md = comicapi.genericmetadata.GenericMetadata(
|
||||
is_empty=False,
|
||||
tag_origin=comicapi.genericmetadata.TagOrigin("comicvine", "Comic Vine"),
|
||||
data_origin=comicapi.genericmetadata.MetadataOrigin("comicvine", "Comic Vine"),
|
||||
issue_id=str(cv_issue_result["results"]["id"]),
|
||||
series=cv_issue_result["results"]["volume"]["name"],
|
||||
series_id=str(cv_issue_result["results"]["volume"]["id"]),
|
||||
|
@ -32,32 +32,32 @@ def test_getPageNameList():
|
||||
|
||||
|
||||
def test_page_type_read(cbz):
|
||||
md = cbz.read_metadata("cr")
|
||||
md = cbz.read_tags("cr")
|
||||
|
||||
assert isinstance(md.pages[0]["type"], str)
|
||||
|
||||
|
||||
def test_metadata_read(cbz, md_saved):
|
||||
md = cbz.read_metadata("cr")
|
||||
def test_read_tags(cbz, md_saved):
|
||||
md = cbz.read_tags("cr")
|
||||
assert md == md_saved
|
||||
|
||||
|
||||
def test_save_cr(tmp_comic):
|
||||
md = tmp_comic.read_metadata("cr")
|
||||
def test_write_cr(tmp_comic):
|
||||
md = tmp_comic.read_tags("cr")
|
||||
md.apply_default_page_list(tmp_comic.get_page_name_list())
|
||||
|
||||
assert tmp_comic.write_metadata(md, "cr")
|
||||
assert tmp_comic.write_tags(md, "cr")
|
||||
|
||||
md = tmp_comic.read_metadata("cr")
|
||||
md = tmp_comic.read_tags("cr")
|
||||
|
||||
|
||||
def test_save_cbi(tmp_comic):
|
||||
md = tmp_comic.read_metadata("cr")
|
||||
def test_write_cbi(tmp_comic):
|
||||
md = tmp_comic.read_tags("cr")
|
||||
md.apply_default_page_list(tmp_comic.get_page_name_list())
|
||||
|
||||
assert tmp_comic.write_metadata(md, "cbi")
|
||||
assert tmp_comic.write_tags(md, "cbi")
|
||||
|
||||
md = tmp_comic.read_metadata("cbi")
|
||||
md = tmp_comic.read_tags("cbi")
|
||||
|
||||
|
||||
@pytest.mark.xfail(not (comicapi.archivers.rar.rar_support and shutil.which("rar")), reason="rar support")
|
||||
@ -67,9 +67,9 @@ def test_save_cr_rar(tmp_path, md_saved):
|
||||
|
||||
tmp_comic = comicapi.comicarchive.ComicArchive(tmp_path / cbr_path.name)
|
||||
assert tmp_comic.seems_to_be_a_comic_archive()
|
||||
assert tmp_comic.write_metadata(comicapi.genericmetadata.md_test, "cr")
|
||||
assert tmp_comic.write_tags(comicapi.genericmetadata.md_test, "cr")
|
||||
|
||||
md = tmp_comic.read_metadata("cr")
|
||||
md = tmp_comic.read_tags("cr")
|
||||
|
||||
# This is a fake CBR we don't need to care about the pages for this test
|
||||
md.pages = []
|
||||
@ -84,28 +84,28 @@ def test_save_cbi_rar(tmp_path, md_saved):
|
||||
|
||||
tmp_comic = comicapi.comicarchive.ComicArchive(tmp_path / cbr_path.name)
|
||||
assert tmp_comic.seems_to_be_a_comic_archive()
|
||||
assert tmp_comic.write_metadata(comicapi.genericmetadata.md_test, "cbi")
|
||||
assert tmp_comic.write_tags(comicapi.genericmetadata.md_test, "cbi")
|
||||
|
||||
md = tmp_comic.read_metadata("cbi")
|
||||
supported_attributes = comicapi.comicarchive.metadata_styles["cbi"].supported_attributes
|
||||
md = tmp_comic.read_tags("cbi")
|
||||
supported_attributes = comicapi.comicarchive.tags["cbi"].supported_attributes
|
||||
assert md.get_clean_metadata(*supported_attributes) == md_saved.get_clean_metadata(*supported_attributes)
|
||||
|
||||
|
||||
def test_page_type_save(tmp_comic):
|
||||
md = tmp_comic.read_metadata("cr")
|
||||
def test_page_type_write(tmp_comic):
|
||||
md = tmp_comic.read_tags("cr")
|
||||
t = md.pages[0]
|
||||
t["type"] = ""
|
||||
|
||||
assert tmp_comic.write_metadata(md, "cr")
|
||||
assert tmp_comic.write_tags(md, "cr")
|
||||
|
||||
md = tmp_comic.read_metadata("cr")
|
||||
md = tmp_comic.read_tags("cr")
|
||||
|
||||
|
||||
def test_invalid_zip(tmp_comic):
|
||||
with open(tmp_comic.path, mode="b+r") as f:
|
||||
f.write(b"PK\000\000")
|
||||
|
||||
result = tmp_comic.write_metadata(comicapi.genericmetadata.md_test, "cr")
|
||||
result = tmp_comic.write_tags(comicapi.genericmetadata.md_test, "cr")
|
||||
assert not result
|
||||
|
||||
|
||||
@ -135,7 +135,7 @@ def test_copy_from_archive(archiver, tmp_path, cbz, md_saved):
|
||||
assert comic_archive.seems_to_be_a_comic_archive()
|
||||
assert set(cbz.archiver.get_filename_list()) == set(comic_archive.archiver.get_filename_list())
|
||||
|
||||
md = comic_archive.read_metadata("cr")
|
||||
md = comic_archive.read_tags("cr")
|
||||
assert md == md_saved
|
||||
|
||||
|
||||
|
@ -161,7 +161,7 @@ def md():
|
||||
|
||||
@pytest.fixture
|
||||
def md_saved():
|
||||
yield comicapi.genericmetadata.md_test.replace(tag_origin=None, issue_id=None, series_id=None)
|
||||
yield comicapi.genericmetadata.md_test.replace(data_origin=None, issue_id=None, series_id=None)
|
||||
|
||||
|
||||
# manually seeds publishers
|
||||
|
@ -18,14 +18,14 @@ def test_save(
|
||||
mock_now,
|
||||
) -> None:
|
||||
# Overwrite the series so it has definitely changed
|
||||
tmp_comic.write_metadata(md_saved.replace(series="nothing"), "cr")
|
||||
tmp_comic.write_tags(md_saved.replace(series="nothing"), "cr")
|
||||
|
||||
md = tmp_comic.read_metadata("cr")
|
||||
md = tmp_comic.read_tags("cr")
|
||||
|
||||
# Check that it changed
|
||||
assert md != md_saved
|
||||
|
||||
# Clear the cached metadata
|
||||
# Clear the cached tags
|
||||
tmp_comic.reset_cache()
|
||||
|
||||
# Setup the app
|
||||
@ -40,15 +40,15 @@ def test_save(
|
||||
# Use the temporary comic we created
|
||||
config[0].Runtime_Options__files = [tmp_comic.path]
|
||||
# Read and save ComicRack tags
|
||||
config[0].Runtime_Options__type_read = ["cr"]
|
||||
config[0].Runtime_Options__type_modify = ["cr"]
|
||||
config[0].Runtime_Options__tags_read = ["cr"]
|
||||
config[0].Runtime_Options__tags_write = ["cr"]
|
||||
# Search using the correct series since we just put the wrong series name in the CBZ
|
||||
config[0].Auto_Tag__metadata = comicapi.genericmetadata.GenericMetadata(series=md_saved.series)
|
||||
# Run ComicTagger
|
||||
CLI(config[0], talkers).run()
|
||||
|
||||
# Read the CBZ
|
||||
md = tmp_comic.read_metadata("cr")
|
||||
md = tmp_comic.read_tags("cr")
|
||||
|
||||
# This is inserted here because otherwise several other tests
|
||||
# unrelated to comicvine need to be re-worked
|
||||
@ -72,7 +72,7 @@ def test_delete(
|
||||
md_saved,
|
||||
mock_now,
|
||||
) -> None:
|
||||
md = tmp_comic.read_metadata("cr")
|
||||
md = tmp_comic.read_tags("cr")
|
||||
|
||||
# Check that the metadata starts correct
|
||||
assert md == md_saved
|
||||
@ -90,14 +90,14 @@ 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_modify = ["cr"]
|
||||
config[0].Runtime_Options__tags_write = ["cr"]
|
||||
# Run ComicTagger
|
||||
CLI(config[0], talkers).run()
|
||||
|
||||
# Read the CBZ
|
||||
md = tmp_comic.read_metadata("cr")
|
||||
md = tmp_comic.read_tags("cr")
|
||||
|
||||
# The default page list is set on load if the comic has the requested metadata style
|
||||
# The default page list is set on load if the comic has the requested tags
|
||||
empty_md = comicapi.genericmetadata.GenericMetadata()
|
||||
|
||||
# Validate that we got an empty metadata back
|
||||
@ -111,7 +111,7 @@ def test_rename(
|
||||
md_saved,
|
||||
mock_now,
|
||||
) -> None:
|
||||
md = tmp_comic.read_metadata("cr")
|
||||
md = tmp_comic.read_tags("cr")
|
||||
|
||||
# Check that the metadata starts correct
|
||||
assert md == md_saved
|
||||
@ -140,7 +140,7 @@ def test_rename(
|
||||
tmp_comic.path = tmp_comic.path.parent / (md.series + ".cbz")
|
||||
|
||||
# Read the CBZ
|
||||
md = tmp_comic.read_metadata("cr")
|
||||
md = tmp_comic.read_tags("cr")
|
||||
|
||||
# Validate that we got the correct metadata back
|
||||
assert md == md_saved
|
||||
|
@ -57,7 +57,7 @@ def test_get_issue_cover_match_score(cbz, config, comicvine_api):
|
||||
def test_search(cbz, config, comicvine_api):
|
||||
config, definitions = config
|
||||
ii = comictaggerlib.issueidentifier.IssueIdentifier(cbz, config, comicvine_api)
|
||||
result, issues = ii.identify(cbz, cbz.read_metadata("cr"))
|
||||
result, issues = ii.identify(cbz, cbz.read_tags("cr"))
|
||||
cv_expected = IssueResult(
|
||||
series=f"{testing.comicvine.cv_volume_result['results']['name']} ({testing.comicvine.cv_volume_result['results']['start_year']})",
|
||||
distance=0,
|
||||
|
@ -7,27 +7,25 @@ import comicapi.genericmetadata
|
||||
import testing.comicdata
|
||||
from comictaggerlib.md import prepare_metadata
|
||||
|
||||
metadata_styles = []
|
||||
tags = []
|
||||
|
||||
for x in entry_points(group="comicapi.metadata"):
|
||||
meetadata = x.load()
|
||||
supported = meetadata.enabled
|
||||
for x in entry_points(group="comicapi.tag"):
|
||||
tag = x.load()
|
||||
supported = tag.enabled
|
||||
exe_found = True
|
||||
metadata_styles.append(
|
||||
pytest.param(meetadata, marks=pytest.mark.xfail(not supported, reason="metadata not enabled"))
|
||||
)
|
||||
tags.append(pytest.param(tag, marks=pytest.mark.xfail(not supported, reason="tags not enabled")))
|
||||
|
||||
|
||||
@pytest.mark.parametrize("metadata", metadata_styles)
|
||||
def test_metadata(mock_version, tmp_comic, md_saved, metadata):
|
||||
md_style = metadata(mock_version[0])
|
||||
supported_attributes = md_style.supported_attributes
|
||||
md_style.set_metadata(comicapi.genericmetadata.md_test, tmp_comic.archiver)
|
||||
written_metadata = md_style.get_metadata(tmp_comic.archiver)
|
||||
@pytest.mark.parametrize("tag_type", tags)
|
||||
def test_metadata(mock_version, tmp_comic, md_saved, tag_type):
|
||||
tag = tag_type(mock_version[0])
|
||||
supported_attributes = tag.supported_attributes
|
||||
tag.write_tags(comicapi.genericmetadata.md_test, tmp_comic.archiver)
|
||||
written_metadata = tag.read_tags(tmp_comic.archiver)
|
||||
md = md_saved.get_clean_metadata(*supported_attributes)
|
||||
|
||||
# Hack back in the pages variable because CoMet supports identifying the cover by the filename
|
||||
if md_style.short_name == "comet":
|
||||
if tag.id == "comet":
|
||||
md.pages = [
|
||||
comicapi.genericmetadata.ImageMetadata(
|
||||
image_index=0, filename="!cover.jpg", type=comicapi.genericmetadata.PageType.FrontCover
|
Loading…
Reference in New Issue
Block a user