Remove comet and cbl tags

This commit is contained in:
Timmy Welch 2024-09-12 12:06:03 -07:00
parent 582224abec
commit 006f3cbd1f
5 changed files with 8 additions and 560 deletions

View File

@ -1,323 +0,0 @@
"""A class to encapsulate CoMet data"""
#
# Copyright 2012-2014 ComicTagger Authors
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from __future__ import annotations
import logging
import os
import xml.etree.ElementTree as ET
from typing import Any
from comicapi import utils
from comicapi.archivers import Archiver
from comicapi.comicarchive import ComicArchive
from comicapi.genericmetadata import GenericMetadata, PageMetadata, PageType
from comicapi.tags import Tag
logger = logging.getLogger(__name__)
class CoMet(Tag):
enabled = True
id = "comet"
def __init__(self, version: str) -> None:
super().__init__(version)
self.comet_filename = "CoMet.xml"
self.file = "CoMet.xml"
self.supported_attributes = {
"series",
"issue",
"title",
"volume",
"genres",
"description",
"publisher",
"language",
"format",
"maturity_rating",
"month",
"year",
"page_count",
"characters",
"credits",
"credits.person",
"credits.primary",
"credits.role",
"price",
"is_version_of",
"rights",
"identifier",
"last_mark",
"pages.type", # This is required for setting the cover image none of the other types will be saved
"pages",
}
def supports_credit_role(self, role: str) -> bool:
return role.casefold() in self._get_parseable_credits()
def supports_tags(self, archive: Archiver) -> bool:
return archive.supports_files()
def has_tags(self, archive: Archiver) -> bool:
if not self.supports_tags(archive):
return 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":
# read in XML file, and validate it
data = b""
try:
data = archive.read_file(n)
except Exception as e:
logger.warning("Error reading in Comet XML for validation! from %s: %s", archive.path, e)
if self._validate_bytes(data):
# since we found it, save it!
self.file = n
has_tags = True
break
return has_tags
def remove_tags(self, archive: Archiver) -> bool:
return self.has_tags(archive) and archive.remove_file(self.file)
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 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 write_tags(self, metadata: GenericMetadata, archive: Archiver) -> bool:
if self.supports_tags(archive):
success = True
xml = b""
if self.has_tags(archive):
xml = archive.read_file(self.file)
if self.file != self.comet_filename:
success = self.remove_tags(archive)
return success and archive.write_file(self.comet_filename, self._bytes_from_metadata(metadata, xml))
else:
logger.warning(f"Archive ({archive.name()}) does not support {self.name()} metadata")
return False
def name(self) -> str:
return "Comic Metadata (CoMet)"
@classmethod
def _get_parseable_credits(cls) -> list[str]:
parsable_credits: list[str] = []
parsable_credits.extend(GenericMetadata.writer_synonyms)
parsable_credits.extend(GenericMetadata.penciller_synonyms)
parsable_credits.extend(GenericMetadata.inker_synonyms)
parsable_credits.extend(GenericMetadata.colorist_synonyms)
parsable_credits.extend(GenericMetadata.letterer_synonyms)
parsable_credits.extend(GenericMetadata.cover_synonyms)
parsable_credits.extend(GenericMetadata.editor_synonyms)
return parsable_credits
def _metadata_from_bytes(self, string: bytes, archive: Archiver) -> GenericMetadata:
tree = ET.ElementTree(ET.fromstring(string))
return self._convert_xml_to_metadata(tree, archive)
def _bytes_from_metadata(self, metadata: GenericMetadata, xml: bytes = b"") -> bytes:
tree = self._convert_metadata_to_xml(metadata, xml)
return ET.tostring(tree.getroot(), encoding="utf-8", xml_declaration=True)
def _convert_metadata_to_xml(self, metadata: GenericMetadata, xml: bytes = b"") -> ET.ElementTree:
# shorthand for the metadata
md = metadata
if xml:
root = ET.fromstring(xml)
else:
# build a tree structure
root = ET.Element("comet")
root.attrib["xmlns:comet"] = "http://www.denvog.com/comet/"
root.attrib["xmlns:xsi"] = "http://www.w3.org/2001/XMLSchema-instance"
root.attrib["xsi:schemaLocation"] = "http://www.denvog.com http://www.denvog.com/comet/comet.xsd"
# helper func
def assign(comet_entry: str, md_entry: Any) -> None:
if md_entry is not None:
ET.SubElement(root, comet_entry).text = str(md_entry)
# title is manditory
assign("title", md.title or "")
assign("series", md.series)
assign("issue", md.issue) # must be int??
assign("volume", md.volume)
assign("description", md.description)
assign("publisher", md.publisher)
assign("pages", md.page_count)
assign("format", md.format)
assign("language", md.language)
assign("rating", md.maturity_rating)
assign("price", md.price)
assign("isVersionOf", md.is_version_of)
assign("rights", md.rights)
assign("identifier", md.identifier)
assign("lastMark", md.last_mark)
assign("genre", ",".join(md.genres)) # TODO repeatable
for c in md.characters:
assign("character", c.strip())
if md.manga is not None and md.manga == "YesAndRightToLeft":
assign("readingDirection", "rtl")
if md.year is not None:
date_str = f"{md.year:04}"
if md.month is not None:
date_str += f"-{md.month:02}"
assign("date", date_str)
cover_index = md.get_cover_page_index_list()[0]
assign("coverImage", md.pages[cover_index].filename)
# loop thru credits, and build a list for each role that CoMet supports
for credit in metadata.credits:
if credit.role.casefold() in set(GenericMetadata.writer_synonyms):
ET.SubElement(root, "writer").text = str(credit.person)
if credit.role.casefold() in set(GenericMetadata.penciller_synonyms):
ET.SubElement(root, "penciller").text = str(credit.person)
if credit.role.casefold() in set(GenericMetadata.inker_synonyms):
ET.SubElement(root, "inker").text = str(credit.person)
if credit.role.casefold() in set(GenericMetadata.colorist_synonyms):
ET.SubElement(root, "colorist").text = str(credit.person)
if credit.role.casefold() in set(GenericMetadata.letterer_synonyms):
ET.SubElement(root, "letterer").text = str(credit.person)
if credit.role.casefold() in set(GenericMetadata.cover_synonyms):
ET.SubElement(root, "coverDesigner").text = str(credit.person)
if credit.role.casefold() in set(GenericMetadata.editor_synonyms):
ET.SubElement(root, "editor").text = str(credit.person)
ET.indent(root)
# wrap it in an ElementTree instance, and save as XML
tree = ET.ElementTree(root)
return tree
def _convert_xml_to_metadata(self, tree: ET.ElementTree, archive: Archiver) -> GenericMetadata:
root = tree.getroot()
if root.tag != "comet":
raise Exception("Not a CoMet file")
metadata = GenericMetadata()
md = metadata
# Helper function
def get(tag: str) -> Any:
node = root.find(tag)
if node is not None:
return node.text
return None
md.series = utils.xlate(get("series"))
md.title = utils.xlate(get("title"))
md.issue = utils.xlate(get("issue"))
md.volume = utils.xlate_int(get("volume"))
md.description = utils.xlate(get("description"))
md.publisher = utils.xlate(get("publisher"))
md.language = utils.xlate(get("language"))
md.format = utils.xlate(get("format"))
md.page_count = utils.xlate_int(get("pages"))
md.maturity_rating = utils.xlate(get("rating"))
md.price = utils.xlate_float(get("price"))
md.is_version_of = utils.xlate(get("isVersionOf"))
md.rights = utils.xlate(get("rights"))
md.identifier = utils.xlate(get("identifier"))
md.last_mark = utils.xlate(get("lastMark"))
_, md.month, md.year = utils.parse_date_str(utils.xlate(get("date")))
ca = ComicArchive(archive)
cover_filename = utils.xlate(get("coverImage"))
page_list = ca.get_page_name_list()
if cover_filename in page_list:
cover_index = page_list.index(cover_filename)
md.pages = [
PageMetadata(
archive_index=cover_index,
display_index=0,
filename=cover_filename,
type=PageType.FrontCover,
bookmark="",
)
]
reading_direction = utils.xlate(get("readingDirection"))
if reading_direction is not None and reading_direction == "rtl":
md.manga = "YesAndRightToLeft"
# loop for genre tags
for n in root:
if n.tag == "genre":
md.genres.add((n.text or "").strip())
# loop for character tags
for n in root:
if n.tag == "character":
md.characters.add((n.text or "").strip())
# Now extract the credit info
for n in root:
if any(
[
n.tag == "writer",
n.tag == "penciller",
n.tag == "inker",
n.tag == "colorist",
n.tag == "letterer",
n.tag == "editor",
]
):
metadata.add_credit((n.text or "").strip(), n.tag.title())
if n.tag == "coverDesigner":
metadata.add_credit((n.text or "").strip(), "Cover")
metadata.is_empty = False
return metadata
# verify that the string actually contains CoMet data in XML format
def _validate_bytes(self, string: bytes) -> bool:
try:
tree = ET.ElementTree(ET.fromstring(string))
root = tree.getroot()
if root.tag != "comet":
return False
except ET.ParseError:
return False
return True

View File

@ -1,229 +0,0 @@
"""A class to encapsulate the ComicBookInfo data"""
# Copyright 2012-2014 ComicTagger Authors
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from __future__ import annotations
import json
import logging
from datetime import datetime
from typing import Any, Literal, TypedDict
from comicapi import utils
from comicapi.archivers import Archiver
from comicapi.genericmetadata import Credit, GenericMetadata
from comicapi.tags import Tag
logger = logging.getLogger(__name__)
_CBILiteralType = Literal[
"series",
"title",
"issue",
"publisher",
"publicationMonth",
"publicationYear",
"numberOfIssues",
"comments",
"genre",
"volume",
"numberOfVolumes",
"language",
"country",
"rating",
"credits",
"tags",
]
class credit(TypedDict):
person: str
role: str
primary: bool
class _ComicBookInfoJson(TypedDict, total=False):
series: str
title: str
publisher: str
publicationMonth: int
publicationYear: int
issue: int
numberOfIssues: int
volume: int
numberOfVolumes: int
rating: int
genre: str
language: str
country: str
credits: list[credit]
tags: list[str]
comments: str
_CBIContainer = TypedDict("_CBIContainer", {"appID": str, "lastModified": str, "ComicBookInfo/1.0": _ComicBookInfoJson})
class ComicBookInfo(Tag):
enabled = True
id = "cbi"
def __init__(self, version: str) -> None:
super().__init__(version)
self.supported_attributes = {
"series",
"issue",
"issue_count",
"title",
"volume",
"volume_count",
"genres",
"description",
"publisher",
"month",
"year",
"language",
"country",
"critical_rating",
"tags",
"credits",
"credits.person",
"credits.primary",
"credits.role",
}
def supports_credit_role(self, role: str) -> bool:
return True
def supports_tags(self, archive: Archiver) -> bool:
return archive.supports_comment()
def has_tags(self, archive: Archiver) -> bool:
return self.supports_tags(archive) and self._validate_string(archive.get_comment())
def remove_tags(self, archive: Archiver) -> bool:
return archive.set_comment("")
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 read_raw_tags(self, archive: Archiver) -> str:
if self.has_tags(archive):
return json.dumps(json.loads(archive.get_comment()), indent=2)
return ""
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")
return False
def name(self) -> str:
return "ComicBookInfo"
def _metadata_from_string(self, string: str) -> GenericMetadata:
cbi_container: _CBIContainer = json.loads(string)
metadata = GenericMetadata()
cbi = cbi_container["ComicBookInfo/1.0"]
metadata.series = utils.xlate(cbi.get("series"))
metadata.title = utils.xlate(cbi.get("title"))
metadata.issue = utils.xlate(cbi.get("issue"))
metadata.publisher = utils.xlate(cbi.get("publisher"))
metadata.month = utils.xlate_int(cbi.get("publicationMonth"))
metadata.year = utils.xlate_int(cbi.get("publicationYear"))
metadata.issue_count = utils.xlate_int(cbi.get("numberOfIssues"))
metadata.description = utils.xlate(cbi.get("comments"))
metadata.genres = set(utils.split(cbi.get("genre"), ","))
metadata.volume = utils.xlate_int(cbi.get("volume"))
metadata.volume_count = utils.xlate_int(cbi.get("numberOfVolumes"))
metadata.language = utils.xlate(cbi.get("language"))
metadata.country = utils.xlate(cbi.get("country"))
metadata.critical_rating = utils.xlate_int(cbi.get("rating"))
metadata.credits = [
Credit(
person=x["person"] if "person" in x else "",
role=x["role"] if "role" in x else "",
primary=x["primary"] if "primary" in x else False,
)
for x in cbi.get("credits", [])
]
metadata.tags.update(cbi.get("tags", set()))
# need the language string to be ISO
if metadata.language:
metadata.language = utils.get_language_iso(metadata.language)
metadata.is_empty = False
return metadata
def _string_from_metadata(self, metadata: GenericMetadata) -> str:
cbi_container = self._create_json_dictionary(metadata)
return json.dumps(cbi_container)
def _validate_string(self, string: bytes | str) -> bool:
"""Verify that the string actually contains CBI data in JSON format"""
try:
cbi_container = json.loads(string)
except json.JSONDecodeError:
return False
return "ComicBookInfo/1.0" in cbi_container
def _create_json_dictionary(self, metadata: GenericMetadata) -> _CBIContainer:
"""Create the dictionary that we will convert to JSON text"""
cbi_container = _CBIContainer(
{
"appID": "ComicTagger/1.0.0",
"lastModified": str(datetime.now()),
"ComicBookInfo/1.0": {},
}
) # TODO: ctversion.version,
# helper func
def assign(cbi_entry: _CBILiteralType, md_entry: Any) -> None:
if md_entry is not None or isinstance(md_entry, str) and md_entry != "":
cbi_container["ComicBookInfo/1.0"][cbi_entry] = md_entry
assign("series", utils.xlate(metadata.series))
assign("title", utils.xlate(metadata.title))
assign("issue", utils.xlate(metadata.issue))
assign("publisher", utils.xlate(metadata.publisher))
assign("publicationMonth", utils.xlate_int(metadata.month))
assign("publicationYear", utils.xlate_int(metadata.year))
assign("numberOfIssues", utils.xlate_int(metadata.issue_count))
assign("comments", utils.xlate(metadata.description))
assign("genre", utils.xlate(",".join(metadata.genres)))
assign("volume", utils.xlate_int(metadata.volume))
assign("numberOfVolumes", utils.xlate_int(metadata.volume_count))
assign("language", utils.xlate(utils.get_language_from_iso(metadata.language)))
assign("country", utils.xlate(metadata.country))
assign("rating", utils.xlate_int(metadata.critical_rating))
assign("credits", [credit(person=c.person, role=c.role, primary=c.primary) for c in metadata.credits])
assign("tags", list(metadata.tags))
return cbi_container

View File

@ -27,8 +27,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("write_tags", default=["cbi"], cmdline=False)
parser.add_setting("read_tags", default=["cbi"], cmdline=False)
parser.add_setting("write_tags", default=["cr"], cmdline=False)
parser.add_setting("read_tags", default=["cr"], 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)
@ -356,7 +356,7 @@ def migrate_settings(config: settngs.Config[ct_ns]) -> settngs.Config[ct_ns]:
elif isinstance(write_Tags, str):
config[0].internal__write_tags = [write_Tags]
else:
config[0].internal__write_tags = ["cbi"]
config[0].internal__write_tags = ["cr"]
read_tags = config[0].internal__read_tags
if not isinstance(read_tags, list):
@ -365,7 +365,7 @@ def migrate_settings(config: settngs.Config[ct_ns]) -> settngs.Config[ct_ns]:
elif isinstance(read_tags, str):
config[0].internal__read_tags = [read_tags]
else:
config[0].internal__read_tags = ["cbi"]
config[0].internal__read_tags = ["cr"]
return config

View File

@ -228,8 +228,8 @@ class TaggerWindow(QtWidgets.QMainWindow):
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.selected_write_tags: list[str] = config[0].internal__write_tags or ["cr"]
self.selected_read_tags: list[str] = config[0].internal__read_tags or ["cr"]
self.setAcceptDrops(True)
self.view_tag_actions, self.remove_tag_actions = self.tag_actions()

View File

@ -66,8 +66,6 @@ comicapi.archiver =
folder = comicapi.archivers.folder:FolderArchiver
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 =
@ -96,6 +94,8 @@ all =
py7zr
rarfile>=4.0
pyicu;sys_platform == 'linux' or sys_platform == 'darwin'
archived_tags =
ct-archived-tags
avif =
pillow-avif-plugin>=1.4.1
cix =