Improve formatting and consistency
This commit is contained in:
parent
6e7660c3d9
commit
40314367c9
@ -868,7 +868,7 @@ class ComicArchive:
|
||||
|
||||
def get_page_name(self, index: int) -> str:
|
||||
if index is None:
|
||||
return None
|
||||
return ""
|
||||
|
||||
page_list = self.get_page_name_list()
|
||||
|
||||
@ -1153,8 +1153,8 @@ class ComicArchive:
|
||||
for n in self.archiver.get_filename_list():
|
||||
if os.path.dirname(n) == "" and os.path.splitext(n)[1].casefold() == ".xml":
|
||||
# read in XML file, and validate it
|
||||
data = ""
|
||||
try:
|
||||
data = ""
|
||||
d = self.archiver.read_file(n)
|
||||
if d:
|
||||
data = d.decode("utf-8")
|
||||
|
@ -131,7 +131,7 @@ class ComicBookInfo:
|
||||
|
||||
cbi_container = CBIContainer(
|
||||
{
|
||||
"appID": "ComicTagger/" + "1.0.0",
|
||||
"appID": "ComicTagger/1.0.0",
|
||||
"lastModified": str(datetime.now()),
|
||||
"ComicBookInfo/1.0": {},
|
||||
}
|
||||
|
@ -54,13 +54,11 @@ class ComicInfoXml:
|
||||
return self.convert_xml_to_metadata(tree)
|
||||
|
||||
def string_from_metadata(self, metadata: GenericMetadata, xml: bytes = b"") -> str:
|
||||
tree = self.convert_metadata_to_xml(self, metadata, xml)
|
||||
tree = self.convert_metadata_to_xml(metadata, xml)
|
||||
tree_str = ET.tostring(tree.getroot(), encoding="utf-8", xml_declaration=True).decode("utf-8")
|
||||
return str(tree_str)
|
||||
|
||||
def convert_metadata_to_xml(
|
||||
self, filename: ComicInfoXml, metadata: GenericMetadata, xml: bytes = b""
|
||||
) -> ElementTree:
|
||||
def convert_metadata_to_xml(self, metadata: GenericMetadata, xml: bytes = b"") -> ElementTree:
|
||||
|
||||
# shorthand for the metadata
|
||||
md = metadata
|
||||
@ -261,6 +259,8 @@ class ComicInfoXml:
|
||||
p: dict[str, Any] = page.attrib
|
||||
if "Image" in p:
|
||||
p["Image"] = int(p["Image"])
|
||||
if "DoublePage" in p:
|
||||
p["DoublePage"] = True if p["DoublePage"].casefold() in ("yes", "true", "1") else False
|
||||
md.pages.append(cast(ImageMetadata, p))
|
||||
|
||||
md.is_empty = False
|
||||
@ -268,7 +268,7 @@ class ComicInfoXml:
|
||||
return md
|
||||
|
||||
def write_to_external_file(self, filename: str, metadata: GenericMetadata, xml: bytes = b"") -> None:
|
||||
tree = self.convert_metadata_to_xml(self, metadata, xml)
|
||||
tree = self.convert_metadata_to_xml(metadata, xml)
|
||||
tree.write(filename, encoding="utf-8", xml_declaration=True)
|
||||
|
||||
def read_from_external_file(self, filename: str) -> GenericMetadata:
|
||||
|
@ -1,5 +1,5 @@
|
||||
# Extracted and mutilated from https://github.com/lordwelch/wsfmt
|
||||
# Which was extracted and mutliated from https://github.com/golang/go/tree/master/src/text/template/parse
|
||||
# Which was extracted and mutilated from https://github.com/golang/go/tree/master/src/text/template/parse
|
||||
from __future__ import annotations
|
||||
|
||||
import calendar
|
||||
@ -138,7 +138,7 @@ class Lexer:
|
||||
# AcceptRun consumes a run of runes from the valid set.
|
||||
def accept_run(self, valid: str) -> None:
|
||||
while self.get() in valid:
|
||||
pass
|
||||
continue
|
||||
|
||||
self.backup()
|
||||
|
||||
|
@ -114,7 +114,7 @@ class FileNameParser:
|
||||
|
||||
# remove any "of NN" phrase with spaces (problem: this could break on
|
||||
# some titles)
|
||||
filename = re.sub(r"of [\d]+", self.repl, filename)
|
||||
filename = re.sub(r"of \d+", self.repl, filename)
|
||||
|
||||
# we should now have a cleaned up filename version with all the words in
|
||||
# the same positions as original filename
|
||||
@ -143,7 +143,7 @@ class FileNameParser:
|
||||
# first look for a word with "#" followed by digits with optional suffix
|
||||
# this is almost certainly the issue number
|
||||
for w in reversed(word_list):
|
||||
if re.match(r"#[-]?(([0-9]*\.[0-9]+|[0-9]+)(\w*))", w[0]):
|
||||
if re.match(r"#-?((\d*\.\d+|\d+)(\w*))", w[0]):
|
||||
found = True
|
||||
break
|
||||
|
||||
@ -151,7 +151,7 @@ class FileNameParser:
|
||||
# list
|
||||
if not found:
|
||||
w = word_list[-1]
|
||||
if re.match(r"[-]?(([0-9]*\.[0-9]+|[0-9]+)(\w*))", w[0]):
|
||||
if re.match(r"-?((\d*\.\d+|\d+)(\w*))", w[0]):
|
||||
found = True
|
||||
|
||||
# now try to look for a # followed by any characters
|
||||
@ -245,7 +245,7 @@ class FileNameParser:
|
||||
if match:
|
||||
year = match.group()
|
||||
# remove non-digits
|
||||
year = re.sub(r"[^0-9]", "", year)
|
||||
year = re.sub(r"\D", "", year)
|
||||
return year
|
||||
|
||||
def get_remainder(self, filename: str, year: str, count: str, volume: str, issue_end: int) -> str:
|
||||
@ -332,7 +332,7 @@ eof = filenamelexer.Item(filenamelexer.ItemType.EOF, -1, "")
|
||||
|
||||
|
||||
# Extracted and mutilated from https://github.com/lordwelch/wsfmt
|
||||
# Which was extracted and mutliated from https://github.com/golang/go/tree/master/src/text/template/parse
|
||||
# Which was extracted and mutilated from https://github.com/golang/go/tree/master/src/text/template/parse
|
||||
class Parser:
|
||||
"""docstring for FilenameParser"""
|
||||
|
||||
|
@ -137,7 +137,7 @@ class GenericMetadata:
|
||||
def copy(self) -> GenericMetadata:
|
||||
return copy.deepcopy(self)
|
||||
|
||||
def replace(self, /, **kwargs) -> GenericMetadata:
|
||||
def replace(self, /, **kwargs: Any) -> GenericMetadata:
|
||||
tmp = self.copy()
|
||||
tmp.__dict__.update(kwargs)
|
||||
return tmp
|
||||
@ -253,7 +253,7 @@ class GenericMetadata:
|
||||
|
||||
def add_credit(self, person: str, role: str, primary: bool = False) -> None:
|
||||
|
||||
credit: CreditMetadata = {"person": person, "role": role, "primary": primary}
|
||||
credit = CreditMetadata(person=person, role=role, primary=primary)
|
||||
|
||||
# look to see if it's not already there...
|
||||
found = False
|
||||
@ -369,91 +369,91 @@ class GenericMetadata:
|
||||
self.imprint = imprint
|
||||
|
||||
|
||||
md_test = GenericMetadata()
|
||||
|
||||
md_test.is_empty = False
|
||||
md_test.tag_origin = None
|
||||
md_test.series = "Cory Doctorow's Futuristic Tales of the Here and Now"
|
||||
md_test.issue = "1"
|
||||
md_test.title = "Anda's Game"
|
||||
md_test.publisher = "IDW Publishing"
|
||||
md_test.month = 10
|
||||
md_test.year = 2007
|
||||
md_test.day = 1
|
||||
md_test.issue_count = 6
|
||||
md_test.volume = 1
|
||||
md_test.genre = "Sci-Fi"
|
||||
md_test.language = "en"
|
||||
md_test.comments = (
|
||||
"For 12-year-old Anda, getting paid real money to kill the characters of players who were cheating"
|
||||
" in her favorite online computer game was a win-win situation. Until she found out who was paying her,"
|
||||
" and what those characters meant to the livelihood of children around the world."
|
||||
md_test: GenericMetadata = GenericMetadata(
|
||||
is_empty=False,
|
||||
tag_origin=None,
|
||||
series="Cory Doctorow's Futuristic Tales of the Here and Now",
|
||||
issue="1",
|
||||
title="Anda's Game",
|
||||
publisher="IDW Publishing",
|
||||
month=10,
|
||||
year=2007,
|
||||
day=1,
|
||||
issue_count=6,
|
||||
volume=1,
|
||||
genre="Sci-Fi",
|
||||
language="en",
|
||||
comments=(
|
||||
"For 12-year-old Anda, getting paid real money to kill the characters of players who were cheating"
|
||||
" in her favorite online computer game was a win-win situation. Until she found out who was paying her,"
|
||||
" and what those characters meant to the livelihood of children around the world."
|
||||
),
|
||||
volume_count=None,
|
||||
critical_rating=3.0,
|
||||
country=None,
|
||||
alternate_series="Tales",
|
||||
alternate_number="2",
|
||||
alternate_count=7,
|
||||
imprint="craphound.com",
|
||||
notes="Tagged with ComicTagger 1.3.2a5 using info from Comic Vine on 2022-04-16 15:52:26. [Issue ID 140529]",
|
||||
web_link="https://comicvine.gamespot.com/cory-doctorows-futuristic-tales-of-the-here-and-no/4000-140529/",
|
||||
format="Series",
|
||||
manga="No",
|
||||
black_and_white=None,
|
||||
page_count=24,
|
||||
maturity_rating="Everyone 10+",
|
||||
story_arc="Here and Now",
|
||||
series_group="Futuristic Tales",
|
||||
scan_info="(CC BY-NC-SA 3.0)",
|
||||
characters="Anda",
|
||||
teams="Fahrenheit",
|
||||
locations="lonely cottage ",
|
||||
credits=[
|
||||
CreditMetadata(primary=False, person="Dara Naraghi", role="Writer"),
|
||||
CreditMetadata(primary=False, person="Esteve Polls", role="Penciller"),
|
||||
CreditMetadata(primary=False, person="Esteve Polls", role="Inker"),
|
||||
CreditMetadata(primary=False, person="Neil Uyetake", role="Letterer"),
|
||||
CreditMetadata(primary=False, person="Sam Kieth", role="Cover"),
|
||||
CreditMetadata(primary=False, person="Ted Adams", role="Editor"),
|
||||
],
|
||||
tags=set(),
|
||||
pages=[
|
||||
ImageMetadata(Image=0, ImageHeight="1280", ImageSize="195977", ImageWidth="800", Type=PageType.FrontCover),
|
||||
ImageMetadata(Image=1, ImageHeight="2039", ImageSize="611993", ImageWidth="1327"),
|
||||
ImageMetadata(Image=2, ImageHeight="2039", ImageSize="783726", ImageWidth="1327"),
|
||||
ImageMetadata(Image=3, ImageHeight="2039", ImageSize="679584", ImageWidth="1327"),
|
||||
ImageMetadata(Image=4, ImageHeight="2039", ImageSize="788179", ImageWidth="1327"),
|
||||
ImageMetadata(Image=5, ImageHeight="2039", ImageSize="864433", ImageWidth="1327"),
|
||||
ImageMetadata(Image=6, ImageHeight="2039", ImageSize="765606", ImageWidth="1327"),
|
||||
ImageMetadata(Image=7, ImageHeight="2039", ImageSize="876427", ImageWidth="1327"),
|
||||
ImageMetadata(Image=8, ImageHeight="2039", ImageSize="852622", ImageWidth="1327"),
|
||||
ImageMetadata(Image=9, ImageHeight="2039", ImageSize="800205", ImageWidth="1327"),
|
||||
ImageMetadata(Image=10, ImageHeight="2039", ImageSize="746243", ImageWidth="1326"),
|
||||
ImageMetadata(Image=11, ImageHeight="2039", ImageSize="718062", ImageWidth="1327"),
|
||||
ImageMetadata(Image=12, ImageHeight="2039", ImageSize="532179", ImageWidth="1326"),
|
||||
ImageMetadata(Image=13, ImageHeight="2039", ImageSize="686708", ImageWidth="1327"),
|
||||
ImageMetadata(Image=14, ImageHeight="2039", ImageSize="641907", ImageWidth="1327"),
|
||||
ImageMetadata(Image=15, ImageHeight="2039", ImageSize="805388", ImageWidth="1327"),
|
||||
ImageMetadata(Image=16, ImageHeight="2039", ImageSize="668927", ImageWidth="1326"),
|
||||
ImageMetadata(Image=17, ImageHeight="2039", ImageSize="710605", ImageWidth="1327"),
|
||||
ImageMetadata(Image=18, ImageHeight="2039", ImageSize="761398", ImageWidth="1326"),
|
||||
ImageMetadata(Image=19, ImageHeight="2039", ImageSize="743807", ImageWidth="1327"),
|
||||
ImageMetadata(Image=20, ImageHeight="2039", ImageSize="552911", ImageWidth="1326"),
|
||||
ImageMetadata(Image=21, ImageHeight="2039", ImageSize="556827", ImageWidth="1327"),
|
||||
ImageMetadata(Image=22, ImageHeight="2039", ImageSize="675078", ImageWidth="1326"),
|
||||
ImageMetadata(
|
||||
Bookmark="Interview",
|
||||
Image=23,
|
||||
ImageHeight="2032",
|
||||
ImageSize="800965",
|
||||
ImageWidth="1338",
|
||||
Type=PageType.Letters,
|
||||
),
|
||||
],
|
||||
price=None,
|
||||
is_version_of=None,
|
||||
rights=None,
|
||||
identifier=None,
|
||||
last_mark=None,
|
||||
cover_image=None,
|
||||
)
|
||||
md_test.volume_count = None
|
||||
md_test.critical_rating = 3.0
|
||||
md_test.country = None
|
||||
md_test.alternate_series = "Tales"
|
||||
md_test.alternate_number = "2"
|
||||
md_test.alternate_count = 7
|
||||
md_test.imprint = "craphound.com"
|
||||
md_test.notes = "Tagged with ComicTagger 1.3.2a5 using info from Comic Vine on 2022-04-16 15:52:26. [Issue ID 140529]"
|
||||
md_test.web_link = "https://comicvine.gamespot.com/cory-doctorows-futuristic-tales-of-the-here-and-no/4000-140529/"
|
||||
md_test.format = "Series"
|
||||
md_test.manga = "No"
|
||||
md_test.black_and_white = None
|
||||
md_test.page_count = 24
|
||||
md_test.maturity_rating = "Everyone 10+"
|
||||
md_test.story_arc = "Here and Now"
|
||||
md_test.series_group = "Futuristic Tales"
|
||||
md_test.scan_info = "(CC BY-NC-SA 3.0)"
|
||||
md_test.characters = "Anda"
|
||||
md_test.teams = "Fahrenheit"
|
||||
md_test.locations = "lonely cottage "
|
||||
md_test.credits = [
|
||||
CreditMetadata({"primary": False, "person": "Dara Naraghi", "role": "Writer"}),
|
||||
CreditMetadata({"primary": False, "person": "Esteve Polls", "role": "Penciller"}),
|
||||
CreditMetadata({"primary": False, "person": "Esteve Polls", "role": "Inker"}),
|
||||
CreditMetadata({"primary": False, "person": "Neil Uyetake", "role": "Letterer"}),
|
||||
CreditMetadata({"primary": False, "person": "Sam Kieth", "role": "Cover"}),
|
||||
CreditMetadata({"primary": False, "person": "Ted Adams", "role": "Editor"}),
|
||||
]
|
||||
md_test.tags = set()
|
||||
md_test.pages = [
|
||||
{"Image": 0, "ImageHeight": "1280", "ImageSize": "195977", "ImageWidth": "800", "Type": PageType.FrontCover},
|
||||
{"Image": 1, "ImageHeight": "2039", "ImageSize": "611993", "ImageWidth": "1327"},
|
||||
{"Image": 2, "ImageHeight": "2039", "ImageSize": "783726", "ImageWidth": "1327"},
|
||||
{"Image": 3, "ImageHeight": "2039", "ImageSize": "679584", "ImageWidth": "1327"},
|
||||
{"Image": 4, "ImageHeight": "2039", "ImageSize": "788179", "ImageWidth": "1327"},
|
||||
{"Image": 5, "ImageHeight": "2039", "ImageSize": "864433", "ImageWidth": "1327"},
|
||||
{"Image": 6, "ImageHeight": "2039", "ImageSize": "765606", "ImageWidth": "1327"},
|
||||
{"Image": 7, "ImageHeight": "2039", "ImageSize": "876427", "ImageWidth": "1327"},
|
||||
{"Image": 8, "ImageHeight": "2039", "ImageSize": "852622", "ImageWidth": "1327"},
|
||||
{"Image": 9, "ImageHeight": "2039", "ImageSize": "800205", "ImageWidth": "1327"},
|
||||
{"Image": 10, "ImageHeight": "2039", "ImageSize": "746243", "ImageWidth": "1326"},
|
||||
{"Image": 11, "ImageHeight": "2039", "ImageSize": "718062", "ImageWidth": "1327"},
|
||||
{"Image": 12, "ImageHeight": "2039", "ImageSize": "532179", "ImageWidth": "1326"},
|
||||
{"Image": 13, "ImageHeight": "2039", "ImageSize": "686708", "ImageWidth": "1327"},
|
||||
{"Image": 14, "ImageHeight": "2039", "ImageSize": "641907", "ImageWidth": "1327"},
|
||||
{"Image": 15, "ImageHeight": "2039", "ImageSize": "805388", "ImageWidth": "1327"},
|
||||
{"Image": 16, "ImageHeight": "2039", "ImageSize": "668927", "ImageWidth": "1326"},
|
||||
{"Image": 17, "ImageHeight": "2039", "ImageSize": "710605", "ImageWidth": "1327"},
|
||||
{"Image": 18, "ImageHeight": "2039", "ImageSize": "761398", "ImageWidth": "1326"},
|
||||
{"Image": 19, "ImageHeight": "2039", "ImageSize": "743807", "ImageWidth": "1327"},
|
||||
{"Image": 20, "ImageHeight": "2039", "ImageSize": "552911", "ImageWidth": "1326"},
|
||||
{"Image": 21, "ImageHeight": "2039", "ImageSize": "556827", "ImageWidth": "1327"},
|
||||
{"Image": 22, "ImageHeight": "2039", "ImageSize": "675078", "ImageWidth": "1326"},
|
||||
{
|
||||
"Bookmark": "Interview",
|
||||
"Image": 23,
|
||||
"ImageHeight": "2032",
|
||||
"ImageSize": "800965",
|
||||
"ImageWidth": "1338",
|
||||
"Type": PageType.Letters,
|
||||
},
|
||||
]
|
||||
md_test.price = None
|
||||
md_test.is_version_of = None
|
||||
md_test.rights = None
|
||||
md_test.identifier = None
|
||||
md_test.last_mark = None
|
||||
md_test.cover_image = None
|
||||
|
@ -113,6 +113,4 @@ class IssueString:
|
||||
# return the float, with no suffix
|
||||
if len(self.suffix) == 1 and self.suffix.isnumeric():
|
||||
return (self.num or 0) + unicodedata.numeric(self.suffix)
|
||||
|
||||
return 0.5
|
||||
return self.num
|
||||
|
@ -137,9 +137,9 @@ def sanitize_title(text: str, basic: bool = False) -> str:
|
||||
# replace any "dash punctuation" with a space
|
||||
# makes sure that batman-superman and self-proclaimed stay separate words
|
||||
text = "".join(
|
||||
c if not unicodedata.category(c) in ("Pd") else " "
|
||||
c if not unicodedata.category(c) in ("Pd",) else " "
|
||||
for c in text
|
||||
if unicodedata.category(c)[0] in "LZN" or unicodedata.category(c) in ("Pd")
|
||||
if unicodedata.category(c)[0] in "LZN" or unicodedata.category(c) in ("Pd",)
|
||||
)
|
||||
# remove extra space and articles and all lower case
|
||||
text = remove_articles(text).strip()
|
||||
@ -147,7 +147,7 @@ def sanitize_title(text: str, basic: bool = False) -> str:
|
||||
return text
|
||||
|
||||
|
||||
def titles_match(search_title: str, record_title: str, threshold: int = 90):
|
||||
def titles_match(search_title: str, record_title: str, threshold: int = 90) -> int:
|
||||
sanitized_search = sanitize_title(search_title)
|
||||
sanitized_record = sanitize_title(record_title)
|
||||
ratio = thefuzz.fuzz.ratio(sanitized_search, sanitized_record)
|
||||
@ -231,7 +231,7 @@ class ImprintDict(dict):
|
||||
if the key does not exist the key is returned as the publisher unchanged
|
||||
"""
|
||||
|
||||
def __init__(self, publisher: str, mapping=(), **kwargs) -> None:
|
||||
def __init__(self, publisher: str, mapping: tuple | Mapping = (), **kwargs: dict) -> None:
|
||||
super().__init__(mapping, **kwargs)
|
||||
self.publisher = publisher
|
||||
|
||||
|
@ -12,18 +12,18 @@ logger = logging.getLogger(__name__)
|
||||
class QTextEditLogger(QtCore.QObject, logging.Handler):
|
||||
qlog = QtCore.pyqtSignal(str)
|
||||
|
||||
def __init__(self, formatter: logging.Formatter, level: int):
|
||||
def __init__(self, formatter: logging.Formatter, level: int) -> None:
|
||||
super().__init__()
|
||||
self.setFormatter(formatter)
|
||||
self.setLevel(level)
|
||||
|
||||
def emit(self, record):
|
||||
def emit(self, record: logging.LogRecord) -> None:
|
||||
msg = self.format(record)
|
||||
self.qlog.emit(msg.strip())
|
||||
|
||||
|
||||
class ApplicationLogWindow(QtWidgets.QDialog):
|
||||
def __init__(self, log_handler: QTextEditLogger, parent=None):
|
||||
def __init__(self, log_handler: QTextEditLogger, parent: QtCore.QObject = None) -> None:
|
||||
super().__init__(parent)
|
||||
uic.loadUi(ComicTaggerSettings.get_ui_file("logwindow.ui"), self)
|
||||
|
||||
@ -43,7 +43,7 @@ class ApplicationLogWindow(QtWidgets.QDialog):
|
||||
self._button.clicked.connect(self.test)
|
||||
self.textEdit.setTabStopDistance(self.textEdit.tabStopDistance() * 2)
|
||||
|
||||
def test(self):
|
||||
def test(self) -> None:
|
||||
logger.debug("damn, a bug")
|
||||
logger.info("something to remember")
|
||||
logger.warning("that's not right")
|
||||
|
@ -22,7 +22,7 @@ import sqlite3 as lite
|
||||
from typing import Any
|
||||
|
||||
from comictaggerlib import ctversion
|
||||
from comictaggerlib.resulttypes import CVIssuesResults, CVVolumeResults, SelectDetails
|
||||
from comictaggerlib.resulttypes import CVImage, CVIssuesResults, CVPublisher, CVVolumeResults, SelectDetails
|
||||
from comictaggerlib.settings import ComicTaggerSettings
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
@ -194,16 +194,14 @@ class ComicCacher:
|
||||
# now process the results
|
||||
for record in rows:
|
||||
result = CVVolumeResults(
|
||||
{
|
||||
"id": record[1],
|
||||
"name": record[2],
|
||||
"start_year": record[3],
|
||||
"count_of_issues": record[5],
|
||||
"description": record[7],
|
||||
"publisher": {"name": record[4]},
|
||||
"image": {"super_url": record[6]},
|
||||
"aliases": record[10],
|
||||
}
|
||||
id=record[1],
|
||||
name=record[2],
|
||||
start_year=record[3],
|
||||
count_of_issues=record[5],
|
||||
description=record[7],
|
||||
publisher=CVPublisher(name=record[4]),
|
||||
image=CVImage(super_url=record[6]),
|
||||
aliases=record[10],
|
||||
)
|
||||
|
||||
results.append(result)
|
||||
@ -248,7 +246,7 @@ class ComicCacher:
|
||||
url_list_str = row[0]
|
||||
if not url_list_str:
|
||||
return []
|
||||
url_list = url_list_str.split(",")
|
||||
url_list = str(url_list_str).split(",")
|
||||
return url_list
|
||||
|
||||
def add_volume_info(self, source_name: str, cv_volume_record: CVVolumeResults) -> None:
|
||||
@ -333,14 +331,12 @@ class ComicCacher:
|
||||
|
||||
# since ID is primary key, there is only one row
|
||||
result = CVVolumeResults(
|
||||
{
|
||||
"id": row[1],
|
||||
"name": row[2],
|
||||
"count_of_issues": row[4],
|
||||
"start_year": row[5],
|
||||
"publisher": {"name": row[3]},
|
||||
"aliases": row[6],
|
||||
}
|
||||
id=row[1],
|
||||
name=row[2],
|
||||
count_of_issues=row[4],
|
||||
start_year=row[5],
|
||||
publisher=CVPublisher(name=row[3]),
|
||||
aliases=row[6],
|
||||
)
|
||||
|
||||
return result
|
||||
@ -372,16 +368,14 @@ class ComicCacher:
|
||||
# now process the results
|
||||
for row in rows:
|
||||
record = CVIssuesResults(
|
||||
{
|
||||
"id": row[1],
|
||||
"name": row[2],
|
||||
"issue_number": row[3],
|
||||
"site_detail_url": row[4],
|
||||
"cover_date": row[5],
|
||||
"image": {"super_url": row[6], "thumb_url": row[7]},
|
||||
"description": row[8],
|
||||
"aliases": row[9],
|
||||
}
|
||||
id=row[1],
|
||||
name=row[2],
|
||||
issue_number=row[3],
|
||||
site_detail_url=row[4],
|
||||
cover_date=row[5],
|
||||
image=CVImage(super_url=row[6], thumb_url=row[7]),
|
||||
description=row[8],
|
||||
aliases=row[9],
|
||||
)
|
||||
|
||||
results.append(record)
|
||||
@ -430,12 +424,10 @@ class ComicCacher:
|
||||
row = cur.fetchone()
|
||||
|
||||
details = SelectDetails(
|
||||
{
|
||||
"image_url": None,
|
||||
"thumb_image_url": None,
|
||||
"cover_date": None,
|
||||
"site_detail_url": None,
|
||||
}
|
||||
image_url=None,
|
||||
thumb_image_url=None,
|
||||
cover_date=None,
|
||||
site_detail_url=None,
|
||||
)
|
||||
if row is not None and row[0] is not None:
|
||||
details["image_url"] = row[0]
|
||||
|
@ -28,9 +28,8 @@ from bs4 import BeautifulSoup
|
||||
from comicapi import utils
|
||||
from comicapi.genericmetadata import GenericMetadata
|
||||
from comicapi.issuestring import IssueString
|
||||
from comictaggerlib import ctversion
|
||||
from comictaggerlib import ctversion, resulttypes
|
||||
from comictaggerlib.comiccacher import ComicCacher
|
||||
from comictaggerlib.resulttypes import CVIssueDetailResults, CVIssuesResults, CVResult, CVVolumeResults, SelectDetails
|
||||
from comictaggerlib.settings import ComicTaggerSettings
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
@ -140,7 +139,7 @@ class ComicVineTalker:
|
||||
try:
|
||||
test_url = url + "/issue/1/?api_key=" + key + "&format=json&field_list=name"
|
||||
|
||||
cv_response: CVResult = requests.get(
|
||||
cv_response: resulttypes.CVResult = requests.get(
|
||||
test_url, headers={"user-agent": "comictagger/" + ctversion.version}
|
||||
).json()
|
||||
|
||||
@ -149,7 +148,7 @@ class ComicVineTalker:
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
def get_cv_content(self, url: str, params: dict[str, Any]) -> CVResult:
|
||||
def get_cv_content(self, url: str, params: dict[str, Any]) -> resulttypes.CVResult:
|
||||
"""
|
||||
Get the content from the CV server. If we're in "wait mode" and status code is a rate limit error
|
||||
sleep for a bit and retry.
|
||||
@ -159,7 +158,7 @@ class ComicVineTalker:
|
||||
counter = 0
|
||||
wait_times = [1, 2, 3, 4]
|
||||
while True:
|
||||
cv_response: CVResult = self.get_url_content(url, params)
|
||||
cv_response: resulttypes.CVResult = self.get_url_content(url, params)
|
||||
if self.wait_for_rate_limit and cv_response["status_code"] == ComicVineTalkerException.RateLimit:
|
||||
self.write_log(f"Rate limit encountered. Waiting for {limit_wait_time} minutes\n")
|
||||
time.sleep(limit_wait_time * 60)
|
||||
@ -208,7 +207,7 @@ class ComicVineTalker:
|
||||
callback: Callable[[int, int], None] | None = None,
|
||||
refresh_cache: bool = False,
|
||||
literal: bool = False,
|
||||
) -> list[CVVolumeResults]:
|
||||
) -> list[resulttypes.CVVolumeResults]:
|
||||
|
||||
# Sanitize the series name for comicvine searching, comicvine search ignore symbols
|
||||
search_series_name = utils.sanitize_title(series_name, literal)
|
||||
@ -235,7 +234,7 @@ class ComicVineTalker:
|
||||
|
||||
cv_response = self.get_cv_content(self.api_base_url + "/search", params)
|
||||
|
||||
search_results: list[CVVolumeResults] = []
|
||||
search_results: list[resulttypes.CVVolumeResults] = []
|
||||
|
||||
# see http://api.comicvine.com/documentation/#handling_responses
|
||||
|
||||
@ -255,21 +254,20 @@ class ComicVineTalker:
|
||||
self.write_log(
|
||||
f"Found {cv_response['number_of_page_results']} of {cv_response['number_of_total_results']} results\n"
|
||||
)
|
||||
search_results.extend(cast(list[CVVolumeResults], cv_response["results"]))
|
||||
search_results.extend(cast(list[resulttypes.CVVolumeResults], cv_response["results"]))
|
||||
page = 1
|
||||
|
||||
if callback is not None:
|
||||
callback(current_result_count, total_result_count)
|
||||
|
||||
# see if we need to keep asking for more pages...
|
||||
stop_searching = False
|
||||
while current_result_count < total_result_count:
|
||||
|
||||
if not literal:
|
||||
# Stop searching once any entry falls below the threshold
|
||||
stop_searching = any(
|
||||
not utils.titles_match(search_series_name, volume["name"], self.series_match_thresh)
|
||||
for volume in cast(list[CVVolumeResults], cv_response["results"])
|
||||
for volume in cast(list[resulttypes.CVVolumeResults], cv_response["results"])
|
||||
)
|
||||
|
||||
if stop_searching:
|
||||
@ -282,7 +280,7 @@ class ComicVineTalker:
|
||||
params["page"] = page
|
||||
cv_response = self.get_cv_content(self.api_base_url + "/search", params)
|
||||
|
||||
search_results.extend(cast(list[CVVolumeResults], cv_response["results"]))
|
||||
search_results.extend(cast(list[resulttypes.CVVolumeResults], cv_response["results"]))
|
||||
current_result_count += cv_response["number_of_page_results"]
|
||||
|
||||
if callback is not None:
|
||||
@ -294,7 +292,7 @@ class ComicVineTalker:
|
||||
|
||||
return search_results
|
||||
|
||||
def fetch_volume_data(self, series_id: int) -> CVVolumeResults:
|
||||
def fetch_volume_data(self, series_id: int) -> resulttypes.CVVolumeResults:
|
||||
|
||||
# before we search online, look in our cache, since we might already have this info
|
||||
cvc = ComicCacher()
|
||||
@ -312,14 +310,14 @@ class ComicVineTalker:
|
||||
}
|
||||
cv_response = self.get_cv_content(volume_url, params)
|
||||
|
||||
volume_results = cast(CVVolumeResults, cv_response["results"])
|
||||
volume_results = cast(resulttypes.CVVolumeResults, cv_response["results"])
|
||||
|
||||
if volume_results:
|
||||
cvc.add_volume_info(self.source_name, volume_results)
|
||||
|
||||
return volume_results
|
||||
|
||||
def fetch_issues_by_volume(self, series_id: int) -> list[CVIssuesResults]:
|
||||
def fetch_issues_by_volume(self, series_id: int) -> list[resulttypes.CVIssuesResults]:
|
||||
# before we search online, look in our cache, since we might already have this info
|
||||
cvc = ComicCacher()
|
||||
cached_volume_issues_result = cvc.get_volume_issues_info(series_id, self.source_name)
|
||||
@ -339,7 +337,7 @@ class ComicVineTalker:
|
||||
current_result_count = cv_response["number_of_page_results"]
|
||||
total_result_count = cv_response["number_of_total_results"]
|
||||
|
||||
volume_issues_result = cast(list[CVIssuesResults], cv_response["results"])
|
||||
volume_issues_result = cast(list[resulttypes.CVIssuesResults], cv_response["results"])
|
||||
page = 1
|
||||
offset = 0
|
||||
|
||||
@ -351,7 +349,7 @@ class ComicVineTalker:
|
||||
params["offset"] = offset
|
||||
cv_response = self.get_cv_content(self.api_base_url + "/issues/", params)
|
||||
|
||||
volume_issues_result.extend(cast(list[CVIssuesResults], cv_response["results"]))
|
||||
volume_issues_result.extend(cast(list[resulttypes.CVIssuesResults], cv_response["results"]))
|
||||
current_result_count += cv_response["number_of_page_results"]
|
||||
|
||||
self.repair_urls(volume_issues_result)
|
||||
@ -362,7 +360,7 @@ class ComicVineTalker:
|
||||
|
||||
def fetch_issues_by_volume_issue_num_and_year(
|
||||
self, volume_id_list: list[int], issue_number: str, year: str | int | None
|
||||
) -> list[CVIssuesResults]:
|
||||
) -> list[resulttypes.CVIssuesResults]:
|
||||
volume_filter = ""
|
||||
for vid in volume_id_list:
|
||||
volume_filter += str(vid) + "|"
|
||||
@ -384,7 +382,7 @@ class ComicVineTalker:
|
||||
current_result_count = cv_response["number_of_page_results"]
|
||||
total_result_count = cv_response["number_of_total_results"]
|
||||
|
||||
filtered_issues_result = cast(list[CVIssuesResults], cv_response["results"])
|
||||
filtered_issues_result = cast(list[resulttypes.CVIssuesResults], cv_response["results"])
|
||||
page = 1
|
||||
offset = 0
|
||||
|
||||
@ -396,7 +394,7 @@ class ComicVineTalker:
|
||||
params["offset"] = offset
|
||||
cv_response = self.get_cv_content(self.api_base_url + "/issues/", params)
|
||||
|
||||
filtered_issues_result.extend(cast(list[CVIssuesResults], cv_response["results"]))
|
||||
filtered_issues_result.extend(cast(list[resulttypes.CVIssuesResults], cv_response["results"]))
|
||||
current_result_count += cv_response["number_of_page_results"]
|
||||
|
||||
self.repair_urls(filtered_issues_result)
|
||||
@ -422,7 +420,7 @@ class ComicVineTalker:
|
||||
issue_url = self.api_base_url + "/issue/" + CVTypeID.Issue + "-" + str(f_record["id"])
|
||||
params = {"api_key": self.api_key, "format": "json"}
|
||||
cv_response = self.get_cv_content(issue_url, params)
|
||||
issue_results = cast(CVIssueDetailResults, cv_response["results"])
|
||||
issue_results = cast(resulttypes.CVIssueDetailResults, cv_response["results"])
|
||||
|
||||
else:
|
||||
return GenericMetadata()
|
||||
@ -436,7 +434,7 @@ class ComicVineTalker:
|
||||
params = {"api_key": self.api_key, "format": "json"}
|
||||
cv_response = self.get_cv_content(issue_url, params)
|
||||
|
||||
issue_results = cast(CVIssueDetailResults, cv_response["results"])
|
||||
issue_results = cast(resulttypes.CVIssueDetailResults, cv_response["results"])
|
||||
|
||||
volume_results = self.fetch_volume_data(issue_results["volume"]["id"])
|
||||
|
||||
@ -446,7 +444,10 @@ class ComicVineTalker:
|
||||
return md
|
||||
|
||||
def map_cv_data_to_metadata(
|
||||
self, volume_results: CVVolumeResults, issue_results: CVIssueDetailResults, settings: ComicTaggerSettings
|
||||
self,
|
||||
volume_results: resulttypes.CVVolumeResults,
|
||||
issue_results: resulttypes.CVIssueDetailResults,
|
||||
settings: ComicTaggerSettings,
|
||||
) -> GenericMetadata:
|
||||
|
||||
# Now, map the Comic Vine data to generic metadata
|
||||
@ -613,7 +614,7 @@ class ComicVineTalker:
|
||||
details = self.fetch_issue_select_details(issue_id)
|
||||
return details["site_detail_url"]
|
||||
|
||||
def fetch_issue_select_details(self, issue_id: int) -> SelectDetails:
|
||||
def fetch_issue_select_details(self, issue_id: int) -> resulttypes.SelectDetails:
|
||||
cached_details = self.fetch_cached_issue_select_details(issue_id)
|
||||
if cached_details["image_url"] is not None:
|
||||
return cached_details
|
||||
@ -623,14 +624,14 @@ class ComicVineTalker:
|
||||
params = {"api_key": self.api_key, "format": "json", "field_list": "image,cover_date,site_detail_url"}
|
||||
|
||||
cv_response = self.get_cv_content(issue_url, params)
|
||||
results = cast(CVIssueDetailResults, cv_response["results"])
|
||||
results = cast(resulttypes.CVIssueDetailResults, cv_response["results"])
|
||||
|
||||
details: SelectDetails = {
|
||||
"image_url": results["image"]["super_url"],
|
||||
"thumb_image_url": results["image"]["thumb_url"],
|
||||
"cover_date": results["cover_date"],
|
||||
"site_detail_url": results["site_detail_url"],
|
||||
}
|
||||
details = resulttypes.SelectDetails(
|
||||
image_url=results["image"]["super_url"],
|
||||
thumb_image_url=results["image"]["thumb_url"],
|
||||
cover_date=results["cover_date"],
|
||||
site_detail_url=results["site_detail_url"],
|
||||
)
|
||||
|
||||
if (
|
||||
details["image_url"] is not None
|
||||
@ -647,7 +648,7 @@ class ComicVineTalker:
|
||||
)
|
||||
return details
|
||||
|
||||
def fetch_cached_issue_select_details(self, issue_id: int) -> SelectDetails:
|
||||
def fetch_cached_issue_select_details(self, issue_id: int) -> resulttypes.SelectDetails:
|
||||
|
||||
# before we search online, look in our cache, since we might already have this info
|
||||
cvc = ComicCacher()
|
||||
@ -736,7 +737,7 @@ class ComicVineTalker:
|
||||
data = reply.readAll()
|
||||
|
||||
try:
|
||||
cv_response = cast(CVResult, json.loads(bytes(data)))
|
||||
cv_response = cast(resulttypes.CVResult, json.loads(bytes(data)))
|
||||
except Exception:
|
||||
logger.exception("Comic Vine query failed to get JSON data\n%s", str(data))
|
||||
return
|
||||
@ -745,7 +746,7 @@ class ComicVineTalker:
|
||||
logger.error("Comic Vine query failed with error: [%s]. ", cv_response["error"])
|
||||
return
|
||||
|
||||
result = cast(CVIssuesResults, cv_response["results"])
|
||||
result = cast(resulttypes.CVIssuesResults, cv_response["results"])
|
||||
|
||||
image_url = result["image"]["super_url"]
|
||||
thumb_url = result["image"]["thumb_url"]
|
||||
@ -778,12 +779,15 @@ class ComicVineTalker:
|
||||
ComicVineTalker.alt_url_list_fetch_complete(alt_cover_url_list)
|
||||
|
||||
def repair_urls(
|
||||
self, issue_list: list[CVIssuesResults] | list[CVVolumeResults] | list[CVIssueDetailResults]
|
||||
self,
|
||||
issue_list: list[resulttypes.CVIssuesResults]
|
||||
| list[resulttypes.CVVolumeResults]
|
||||
| list[resulttypes.CVIssueDetailResults],
|
||||
) -> None:
|
||||
# make sure there are URLs for the image fields
|
||||
for issue in issue_list:
|
||||
if issue["image"] is None:
|
||||
issue["image"] = {
|
||||
"super_url": ComicVineTalker.logo_url,
|
||||
"thumb_url": ComicVineTalker.logo_url,
|
||||
}
|
||||
issue["image"] = resulttypes.CVImage(
|
||||
super_url=ComicVineTalker.logo_url,
|
||||
thumb_url=ComicVineTalker.logo_url,
|
||||
)
|
||||
|
@ -90,6 +90,7 @@ class CoverImageWidget(QtWidgets.QWidget):
|
||||
def __init__(self, parent: QtWidgets.QWidget, mode: int, expand_on_click: bool = True) -> None:
|
||||
super().__init__(parent)
|
||||
|
||||
self.cover_fetcher = ImageFetcher()
|
||||
uic.loadUi(ComicTaggerSettings.get_ui_file("coverimagewidget.ui"), self)
|
||||
|
||||
reduce_widget_font_size(self.label)
|
||||
@ -193,7 +194,7 @@ class CoverImageWidget(QtWidgets.QWidget):
|
||||
|
||||
self.update_content()
|
||||
|
||||
def primary_url_fetch_complete(self, primary_url: str, thumb_url: str | None) -> None:
|
||||
def primary_url_fetch_complete(self, primary_url: str, thumb_url: str | None = None) -> None:
|
||||
self.url_list.append(str(primary_url))
|
||||
self.imageIndex = 0
|
||||
self.imageCount = len(self.url_list)
|
||||
|
@ -170,16 +170,17 @@ class FileRenamer:
|
||||
md_dict["month_name"] = ""
|
||||
md_dict["month_abbr"] = ""
|
||||
|
||||
for Component in pathlib.PureWindowsPath(template).parts:
|
||||
new_basename = ""
|
||||
for component in pathlib.PureWindowsPath(template).parts:
|
||||
if (
|
||||
self.platform.casefold() in ["universal", "windows"] or sys.platform.casefold() in ["windows"]
|
||||
) and self.smart_cleanup:
|
||||
# colons get special treatment
|
||||
Component = Component.replace(": ", " - ")
|
||||
Component = Component.replace(":", "-")
|
||||
component = component.replace(": ", " - ")
|
||||
component = component.replace(":", "-")
|
||||
|
||||
new_basename = str(
|
||||
sanitize_filename(fmt.vformat(Component, args=[], kwargs=Default(md_dict)), platform=self.platform)
|
||||
sanitize_filename(fmt.vformat(component, args=[], kwargs=Default(md_dict)), platform=self.platform)
|
||||
).strip()
|
||||
new_name = os.path.join(new_name, new_basename)
|
||||
|
||||
|
@ -246,12 +246,12 @@ class FileSelectionList(QtWidgets.QWidget):
|
||||
if platform.system() == "Windows":
|
||||
rar_help = windowsRarHelp
|
||||
|
||||
elif platform.system() == "Linux":
|
||||
rar_help = linuxRarHelp
|
||||
|
||||
elif platform.system() == "Darwin":
|
||||
rar_help = macRarHelp
|
||||
|
||||
else:
|
||||
rar_help = linuxRarHelp
|
||||
|
||||
OptionalMessageDialog.msg_no_checkbox(
|
||||
self,
|
||||
"RAR Files are Read-Only",
|
||||
@ -376,7 +376,7 @@ class FileSelectionList(QtWidgets.QWidget):
|
||||
try:
|
||||
fi.ca.read_cix()
|
||||
except Exception:
|
||||
...
|
||||
pass
|
||||
fi.ca.has_cbi()
|
||||
|
||||
def get_selected_archive_list(self) -> list[ComicArchive]:
|
||||
|
@ -38,7 +38,7 @@ logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class ImageFetcherException(Exception):
|
||||
pass
|
||||
...
|
||||
|
||||
|
||||
def fetch_complete(image_data: bytes | QtCore.QByteArray) -> None:
|
||||
|
@ -57,11 +57,11 @@ class Score(TypedDict):
|
||||
|
||||
|
||||
class IssueIdentifierNetworkError(Exception):
|
||||
pass
|
||||
...
|
||||
|
||||
|
||||
class IssueIdentifierCancelled(Exception):
|
||||
pass
|
||||
...
|
||||
|
||||
|
||||
class IssueIdentifier:
|
||||
@ -175,35 +175,29 @@ class IssueIdentifier:
|
||||
def get_search_keys(self) -> SearchKeys:
|
||||
|
||||
ca = self.comic_archive
|
||||
search_keys: SearchKeys = {
|
||||
"series": None,
|
||||
"issue_number": None,
|
||||
"month": None,
|
||||
"year": None,
|
||||
"issue_count": None,
|
||||
}
|
||||
|
||||
if ca is None:
|
||||
return None
|
||||
|
||||
search_keys: SearchKeys
|
||||
if self.only_use_additional_meta_data:
|
||||
search_keys["series"] = self.additional_metadata.series
|
||||
search_keys["issue_number"] = self.additional_metadata.issue
|
||||
search_keys["year"] = self.additional_metadata.year
|
||||
search_keys["month"] = self.additional_metadata.month
|
||||
search_keys["issue_count"] = self.additional_metadata.issue_count
|
||||
search_keys = SearchKeys(
|
||||
series=self.additional_metadata.series,
|
||||
issue_number=self.additional_metadata.issue,
|
||||
year=self.additional_metadata.year,
|
||||
month=self.additional_metadata.month,
|
||||
issue_count=self.additional_metadata.issue_count,
|
||||
)
|
||||
return search_keys
|
||||
|
||||
# see if the archive has any useful meta data for searching with
|
||||
if ca.has_cix():
|
||||
try:
|
||||
try:
|
||||
if ca.has_cix():
|
||||
internal_metadata = ca.read_cix()
|
||||
except Exception as e:
|
||||
logger.error("Failed to load metadata for %s: %s", ca.path, e)
|
||||
elif ca.has_cbi():
|
||||
internal_metadata = ca.read_cbi()
|
||||
else:
|
||||
internal_metadata = ca.read_cbi()
|
||||
else:
|
||||
internal_metadata = ca.read_cbi()
|
||||
except Exception as e:
|
||||
internal_metadata = GenericMetadata()
|
||||
logger.error("Failed to load metadata for %s: %s", ca.path, e)
|
||||
|
||||
# try to get some metadata from filename
|
||||
md_from_filename = ca.metadata_from_filename(
|
||||
@ -213,45 +207,22 @@ class IssueIdentifier:
|
||||
self.settings.remove_publisher,
|
||||
)
|
||||
|
||||
working_md = md_from_filename.copy()
|
||||
|
||||
working_md.overlay(internal_metadata)
|
||||
working_md.overlay(self.additional_metadata)
|
||||
|
||||
# preference order:
|
||||
# 1. Additional metadata
|
||||
# 1. Internal metadata
|
||||
# 1. Filename metadata
|
||||
|
||||
if self.additional_metadata.series is not None:
|
||||
search_keys["series"] = self.additional_metadata.series
|
||||
elif internal_metadata.series is not None:
|
||||
search_keys["series"] = internal_metadata.series
|
||||
else:
|
||||
search_keys["series"] = md_from_filename.series
|
||||
|
||||
if self.additional_metadata.issue is not None:
|
||||
search_keys["issue_number"] = self.additional_metadata.issue
|
||||
elif internal_metadata.issue is not None:
|
||||
search_keys["issue_number"] = internal_metadata.issue
|
||||
else:
|
||||
search_keys["issue_number"] = md_from_filename.issue
|
||||
|
||||
if self.additional_metadata.year is not None:
|
||||
search_keys["year"] = self.additional_metadata.year
|
||||
elif internal_metadata.year is not None:
|
||||
search_keys["year"] = internal_metadata.year
|
||||
else:
|
||||
search_keys["year"] = md_from_filename.year
|
||||
|
||||
if self.additional_metadata.month is not None:
|
||||
search_keys["month"] = self.additional_metadata.month
|
||||
elif internal_metadata.month is not None:
|
||||
search_keys["month"] = internal_metadata.month
|
||||
else:
|
||||
search_keys["month"] = md_from_filename.month
|
||||
|
||||
if self.additional_metadata.issue_count is not None:
|
||||
search_keys["issue_count"] = self.additional_metadata.issue_count
|
||||
elif internal_metadata.issue_count is not None:
|
||||
search_keys["issue_count"] = internal_metadata.issue_count
|
||||
else:
|
||||
search_keys["issue_count"] = md_from_filename.issue_count
|
||||
search_keys = SearchKeys(
|
||||
series=working_md.series,
|
||||
issue_number=working_md.issue,
|
||||
year=working_md.year,
|
||||
month=working_md.month,
|
||||
issue_count=working_md.issue_count,
|
||||
)
|
||||
|
||||
return search_keys
|
||||
|
||||
@ -293,9 +264,7 @@ class IssueIdentifier:
|
||||
if self.cover_url_callback is not None:
|
||||
self.cover_url_callback(url_image_data)
|
||||
|
||||
remote_cover_list = []
|
||||
|
||||
remote_cover_list.append(Score({"url": primary_img_url, "hash": self.calculate_hash(url_image_data)}))
|
||||
remote_cover_list = [Score(url=primary_img_url, hash=self.calculate_hash(url_image_data))]
|
||||
|
||||
if self.cancel:
|
||||
raise IssueIdentifierCancelled
|
||||
@ -316,7 +285,7 @@ class IssueIdentifier:
|
||||
if self.cover_url_callback is not None:
|
||||
self.cover_url_callback(alt_url_image_data)
|
||||
|
||||
remote_cover_list.append(Score({"url": alt_url, "hash": self.calculate_hash(alt_url_image_data)}))
|
||||
remote_cover_list.append(Score(url=alt_url, hash=self.calculate_hash(alt_url_image_data)))
|
||||
|
||||
if self.cancel:
|
||||
raise IssueIdentifierCancelled
|
||||
@ -331,9 +300,7 @@ class IssueIdentifier:
|
||||
for local_cover_hash in local_cover_hash_list:
|
||||
for remote_cover_item in remote_cover_list:
|
||||
score = ImageHasher.hamming_distance(local_cover_hash, remote_cover_item["hash"])
|
||||
score_list.append(
|
||||
Score({"score": score, "url": remote_cover_item["url"], "hash": remote_cover_item["hash"]})
|
||||
)
|
||||
score_list.append(Score(score=score, url=remote_cover_item["url"], hash=remote_cover_item["hash"]))
|
||||
if use_log:
|
||||
self.log_msg(score, False)
|
||||
|
||||
|
@ -132,18 +132,18 @@ class IssueSelectionWindow(QtWidgets.QDialog):
|
||||
if len(parts) > 1:
|
||||
item_text = parts[0] + "-" + parts[1]
|
||||
|
||||
QTW_item = QtWidgets.QTableWidgetItem(item_text)
|
||||
QTW_item.setData(QtCore.Qt.ItemDataRole.ToolTipRole, item_text)
|
||||
QTW_item.setFlags(QtCore.Qt.ItemFlag.ItemIsSelectable | QtCore.Qt.ItemFlag.ItemIsEnabled)
|
||||
self.twList.setItem(row, 1, QTW_item)
|
||||
qtw_item = QtWidgets.QTableWidgetItem(item_text)
|
||||
qtw_item.setData(QtCore.Qt.ItemDataRole.ToolTipRole, item_text)
|
||||
qtw_item.setFlags(QtCore.Qt.ItemFlag.ItemIsSelectable | QtCore.Qt.ItemFlag.ItemIsEnabled)
|
||||
self.twList.setItem(row, 1, qtw_item)
|
||||
|
||||
item_text = record["name"]
|
||||
if item_text is None:
|
||||
item_text = ""
|
||||
QTW_item = QtWidgets.QTableWidgetItem(item_text)
|
||||
QTW_item.setData(QtCore.Qt.ItemDataRole.ToolTipRole, item_text)
|
||||
QTW_item.setFlags(QtCore.Qt.ItemFlag.ItemIsSelectable | QtCore.Qt.ItemFlag.ItemIsEnabled)
|
||||
self.twList.setItem(row, 2, QTW_item)
|
||||
qtw_item = QtWidgets.QTableWidgetItem(item_text)
|
||||
qtw_item.setData(QtCore.Qt.ItemDataRole.ToolTipRole, item_text)
|
||||
qtw_item.setFlags(QtCore.Qt.ItemFlag.ItemIsSelectable | QtCore.Qt.ItemFlag.ItemIsEnabled)
|
||||
self.twList.setItem(row, 2, qtw_item)
|
||||
|
||||
if (
|
||||
IssueString(record["issue_number"]).as_string().casefold()
|
||||
|
@ -92,7 +92,7 @@ try:
|
||||
except ImportError as e:
|
||||
|
||||
def show_exception_box(log_msg: str) -> None:
|
||||
pass
|
||||
...
|
||||
|
||||
logger.error(str(e))
|
||||
qt_available = False
|
||||
@ -115,7 +115,7 @@ def update_publishers() -> None:
|
||||
|
||||
def ctmain() -> None:
|
||||
opts = parse_cmd_line()
|
||||
SETTINGS = ComicTaggerSettings(opts.config_path)
|
||||
settings = ComicTaggerSettings(opts.config_path)
|
||||
|
||||
os.makedirs(ComicTaggerSettings.get_settings_folder() / "logs", exist_ok=True)
|
||||
stream_handler = logging.StreamHandler()
|
||||
@ -138,15 +138,15 @@ def ctmain() -> None:
|
||||
# manage the CV API key
|
||||
# None comparison is used so that the empty string can unset the value
|
||||
if opts.cv_api_key is not None or opts.cv_url is not None:
|
||||
SETTINGS.cv_api_key = opts.cv_api_key if opts.cv_api_key is not None else SETTINGS.cv_api_key
|
||||
SETTINGS.cv_url = opts.cv_url if opts.cv_url is not None else SETTINGS.cv_url
|
||||
SETTINGS.save()
|
||||
settings.cv_api_key = opts.cv_api_key if opts.cv_api_key is not None else settings.cv_api_key
|
||||
settings.cv_url = opts.cv_url if opts.cv_url is not None else settings.cv_url
|
||||
settings.save()
|
||||
if opts.only_set_cv_key:
|
||||
print("Key set") # noqa: T201
|
||||
return
|
||||
|
||||
ComicVineTalker.api_key = SETTINGS.cv_api_key
|
||||
ComicVineTalker.api_base_url = SETTINGS.cv_url
|
||||
ComicVineTalker.api_key = settings.cv_api_key
|
||||
ComicVineTalker.api_base_url = settings.cv_url
|
||||
|
||||
signal.signal(signal.SIGINT, signal.SIG_DFL)
|
||||
|
||||
@ -166,11 +166,11 @@ def ctmain() -> None:
|
||||
|
||||
if not qt_available and not opts.no_gui:
|
||||
opts.no_gui = True
|
||||
logger.warn("PyQt5 is not available. ComicTagger is limited to command-line mode.")
|
||||
logger.warning("PyQt5 is not available. ComicTagger is limited to command-line mode.")
|
||||
|
||||
if opts.no_gui:
|
||||
try:
|
||||
cli.cli_mode(opts, SETTINGS)
|
||||
cli.cli_mode(opts, settings)
|
||||
except Exception:
|
||||
logger.exception("CLI mode failed")
|
||||
else:
|
||||
@ -206,7 +206,7 @@ def ctmain() -> None:
|
||||
QtWidgets.QApplication.processEvents()
|
||||
|
||||
try:
|
||||
tagger_window = TaggerWindow(opts.files, SETTINGS, opts=opts)
|
||||
tagger_window = TaggerWindow(opts.files, settings, opts=opts)
|
||||
tagger_window.setWindowIcon(QtGui.QIcon(ComicTaggerSettings.get_graphic("app.png")))
|
||||
tagger_window.show()
|
||||
|
||||
|
@ -119,10 +119,10 @@ class PageListEditor(QtWidgets.QWidget):
|
||||
if show_shortcut:
|
||||
text = text + " (" + shortcut + ")"
|
||||
self.cbPageType.addItem(text, user_data)
|
||||
actionItem = QtWidgets.QAction(shortcut, self)
|
||||
actionItem.triggered.connect(lambda: self.select_page_type_item(self.cbPageType.findData(user_data)))
|
||||
actionItem.setShortcut(shortcut)
|
||||
self.addAction(actionItem)
|
||||
action_item = QtWidgets.QAction(shortcut, self)
|
||||
action_item.triggered.connect(lambda: self.select_page_type_item(self.cbPageType.findData(user_data)))
|
||||
action_item.setShortcut(shortcut)
|
||||
self.addAction(action_item)
|
||||
|
||||
def select_page_type_item(self, idx: int) -> None:
|
||||
if self.cbPageType.isEnabled():
|
||||
@ -132,19 +132,19 @@ class PageListEditor(QtWidgets.QWidget):
|
||||
def get_new_indexes(self, movement: int) -> list[tuple[int, int]]:
|
||||
selection = self.listWidget.selectionModel().selectedRows()
|
||||
selection.sort(reverse=movement > 0)
|
||||
newindexes: list[int] = []
|
||||
oldindexes: list[int] = []
|
||||
new_indexes: list[int] = []
|
||||
old_indexes: list[int] = []
|
||||
for x in selection:
|
||||
current = x.row()
|
||||
oldindexes.append(current)
|
||||
old_indexes.append(current)
|
||||
if 0 <= current + movement <= self.listWidget.count() - 1:
|
||||
if len(newindexes) < 1 or current + movement != newindexes[-1]:
|
||||
if len(new_indexes) < 1 or current + movement != new_indexes[-1]:
|
||||
current += movement
|
||||
|
||||
newindexes.append(current)
|
||||
oldindexes.sort()
|
||||
newindexes.sort()
|
||||
return list(zip(newindexes, oldindexes))
|
||||
new_indexes.append(current)
|
||||
old_indexes.sort()
|
||||
new_indexes.sort()
|
||||
return list(zip(new_indexes, old_indexes))
|
||||
|
||||
def set_selection(self, indexes: list[tuple[int, int]]) -> list[tuple[int, int]]:
|
||||
selection_ranges: list[tuple[int, int]] = []
|
||||
|
@ -106,7 +106,7 @@ Accepts the following variables:
|
||||
{cover artist} (string)
|
||||
{editor} (string)
|
||||
{tags} (list of str)
|
||||
{pages} (list of dict({'Image': string(int), 'Type': string, 'Bookmark': string, 'DoublePage': string}))
|
||||
{pages} (list of dict({'Image': string(int), 'Type': string, 'Bookmark': string, 'DoublePage': boolean}))
|
||||
|
||||
CoMet-only items:
|
||||
{price} (float)
|
||||
|
@ -1014,7 +1014,7 @@ Have fun!
|
||||
|
||||
self.query_online(autoselect=True)
|
||||
|
||||
def literal_search(self):
|
||||
def literal_search(self) -> None:
|
||||
self.query_online(autoselect=False, literal=True)
|
||||
|
||||
def query_online(self, autoselect: bool = False, literal: bool = False) -> None:
|
||||
@ -1155,7 +1155,6 @@ Have fun!
|
||||
|
||||
if self.save_data_style == MetaDataStyle.CIX:
|
||||
# loop over credit table, mark selected rows
|
||||
r = 0
|
||||
for r in range(self.twCredits.rowCount()):
|
||||
if str(self.twCredits.item(r, 1).text()).casefold() not in cix_credits:
|
||||
self.twCredits.item(r, 1).setBackground(inactive_brush)
|
||||
@ -1166,7 +1165,6 @@ Have fun!
|
||||
|
||||
if self.save_data_style == MetaDataStyle.CBI:
|
||||
# loop over credit table, make all active color
|
||||
r = 0
|
||||
for r in range(self.twCredits.rowCount()):
|
||||
self.twCredits.item(r, 0).setBackground(active_brush)
|
||||
self.twCredits.item(r, 1).setBackground(active_brush)
|
||||
@ -1350,16 +1348,11 @@ Have fun!
|
||||
def open_web_link(self) -> None:
|
||||
if self.leWebLink is not None:
|
||||
web_link = self.leWebLink.text().strip()
|
||||
valid = False
|
||||
try:
|
||||
result = urlparse(web_link)
|
||||
valid = all([result.scheme in ["http", "https"], result.netloc])
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
if valid:
|
||||
all([result.scheme in ["http", "https"], result.netloc])
|
||||
webbrowser.open_new_tab(web_link)
|
||||
else:
|
||||
except ValueError:
|
||||
QtWidgets.QMessageBox.warning(self, self.tr("Web Link"), self.tr("Web Link is invalid."))
|
||||
|
||||
def show_settings(self) -> None:
|
||||
@ -1367,8 +1360,7 @@ Have fun!
|
||||
settingswin = SettingsWindow(self, self.settings)
|
||||
settingswin.setModal(True)
|
||||
settingswin.exec()
|
||||
if settingswin.result():
|
||||
pass
|
||||
settingswin.result()
|
||||
|
||||
def set_app_position(self) -> None:
|
||||
if self.settings.last_main_window_width != 0:
|
||||
@ -1938,7 +1930,7 @@ Have fun!
|
||||
QtWidgets.QMessageBox.information(self, self.tr("Auto-Tag Summary"), self.tr(summary))
|
||||
logger.info(summary)
|
||||
|
||||
def exception(self, message):
|
||||
def exception(self, message: str) -> None:
|
||||
errorbox = QtWidgets.QMessageBox()
|
||||
errorbox.setText(message)
|
||||
errorbox.exec()
|
||||
|
@ -106,7 +106,7 @@ tr:nth-child(even) {
|
||||
<tr><td>{cover artist}</td><td>(string)</td></tr>
|
||||
<tr><td>{editor}</td><td>(string)</td></tr>
|
||||
<tr><td>{tags}</td><td>list of str</td></tr>
|
||||
<tr><td>{pages}</td><td>list of dict({'Image': string(int), 'Type': string, 'Bookmark': string, 'DoublePage': string})</td></tr>
|
||||
<tr><td>{pages}</td><td>list of dict({'Image': string(int), 'Type': string, 'Bookmark': string, 'DoublePage': boolean})</td></tr>
|
||||
<tr><td>{price}</td><td>float</td></tr>
|
||||
<tr><td>{is_version_of}</td><td>string</td></tr>
|
||||
<tr><td>{rights}</td><td>string</td></tr>
|
||||
|
@ -394,7 +394,7 @@ class VolumeSelectionWindow(QtWidgets.QDialog):
|
||||
|
||||
deques: list[deque[CVVolumeResults]] = [deque(), deque(), deque()]
|
||||
|
||||
def categorize(result):
|
||||
def categorize(result: CVVolumeResults) -> int:
|
||||
# We don't remove anything on this one so that we only get exact matches
|
||||
if utils.sanitize_title(result["name"], True).casefold() == sanitized_no_articles:
|
||||
return 0
|
||||
@ -454,7 +454,7 @@ class VolumeSelectionWindow(QtWidgets.QDialog):
|
||||
self.twList.selectRow(0)
|
||||
self.twList.resizeColumnsToContents()
|
||||
|
||||
def showEvent(self, event: QtGui.QShowEvent):
|
||||
def showEvent(self, event: QtGui.QShowEvent) -> None:
|
||||
if not self.cv_search_results:
|
||||
QtCore.QCoreApplication.processEvents()
|
||||
QtWidgets.QMessageBox.information(self, "Search Result", "No matches found!")
|
||||
|
Loading…
Reference in New Issue
Block a user