diff --git a/comictaggerlib/autotagmatchwindow.py b/comictaggerlib/autotagmatchwindow.py
index 26ba997..34ebc72 100644
--- a/comictaggerlib/autotagmatchwindow.py
+++ b/comictaggerlib/autotagmatchwindow.py
@@ -27,6 +27,7 @@ from comictaggerlib.coverimagewidget import CoverImageWidget
from comictaggerlib.resulttypes import IssueResult, MultipleMatch
from comictaggerlib.settings import ComicTaggerSettings
from comictaggerlib.ui.qtutils import reduce_widget_font_size
+from comictalker.comictalker import ComicTalker
logger = logging.getLogger(__name__)
@@ -41,6 +42,7 @@ class AutoTagMatchWindow(QtWidgets.QDialog):
style: int,
fetch_func: Callable[[IssueResult], GenericMetadata],
settings: ComicTaggerSettings,
+ talker_api: ComicTalker,
) -> None:
super().__init__(parent)
@@ -50,12 +52,12 @@ class AutoTagMatchWindow(QtWidgets.QDialog):
self.current_match_set: MultipleMatch = match_set_list[0]
- self.altCoverWidget = CoverImageWidget(self.altCoverContainer, CoverImageWidget.AltCoverMode)
+ self.altCoverWidget = CoverImageWidget(self.altCoverContainer, talker_api, CoverImageWidget.AltCoverMode)
gridlayout = QtWidgets.QGridLayout(self.altCoverContainer)
gridlayout.addWidget(self.altCoverWidget)
gridlayout.setContentsMargins(0, 0, 0, 0)
- self.archiveCoverWidget = CoverImageWidget(self.archiveCoverContainer, CoverImageWidget.ArchiveMode)
+ self.archiveCoverWidget = CoverImageWidget(self.archiveCoverContainer, talker_api, CoverImageWidget.ArchiveMode)
gridlayout = QtWidgets.QGridLayout(self.archiveCoverContainer)
gridlayout.addWidget(self.archiveCoverWidget)
gridlayout.setContentsMargins(0, 0, 0, 0)
@@ -241,15 +243,13 @@ class AutoTagMatchWindow(QtWidgets.QDialog):
)
# now get the particular issue data
- cv_md = self.fetch_func(match)
- if cv_md is None:
- QtWidgets.QMessageBox.critical(
- self, "Network Issue", "Could not connect to Comic Vine to get issue details!"
- )
+ ct_md = self.fetch_func(match)
+ if ct_md is None:
+ QtWidgets.QMessageBox.critical(self, "Network Issue", "Could not retrieve issue details!")
return
QtWidgets.QApplication.setOverrideCursor(QtGui.QCursor(QtCore.Qt.CursorShape.WaitCursor))
- md.overlay(cv_md)
+ md.overlay(ct_md)
success = ca.write_metadata(md, self._style)
ca.load_cache([MetaDataStyle.CBI, MetaDataStyle.CIX])
diff --git a/comictaggerlib/autotagprogresswindow.py b/comictaggerlib/autotagprogresswindow.py
index f8decb3..beeb585 100644
--- a/comictaggerlib/autotagprogresswindow.py
+++ b/comictaggerlib/autotagprogresswindow.py
@@ -22,22 +22,25 @@ from PyQt5 import QtCore, QtWidgets, uic
from comictaggerlib.coverimagewidget import CoverImageWidget
from comictaggerlib.settings import ComicTaggerSettings
from comictaggerlib.ui.qtutils import reduce_widget_font_size
+from comictalker.comictalker import ComicTalker
logger = logging.getLogger(__name__)
class AutoTagProgressWindow(QtWidgets.QDialog):
- def __init__(self, parent: QtWidgets.QWidget) -> None:
+ def __init__(self, parent: QtWidgets.QWidget, talker_api: ComicTalker) -> None:
super().__init__(parent)
uic.loadUi(ComicTaggerSettings.get_ui_file("autotagprogresswindow.ui"), self)
- self.archiveCoverWidget = CoverImageWidget(self.archiveCoverContainer, CoverImageWidget.DataMode, False)
+ self.archiveCoverWidget = CoverImageWidget(
+ self.archiveCoverContainer, talker_api, CoverImageWidget.DataMode, False
+ )
gridlayout = QtWidgets.QGridLayout(self.archiveCoverContainer)
gridlayout.addWidget(self.archiveCoverWidget)
gridlayout.setContentsMargins(0, 0, 0, 0)
- self.testCoverWidget = CoverImageWidget(self.testCoverContainer, CoverImageWidget.DataMode, False)
+ self.testCoverWidget = CoverImageWidget(self.testCoverContainer, talker_api, CoverImageWidget.DataMode, False)
gridlayout = QtWidgets.QGridLayout(self.testCoverContainer)
gridlayout.addWidget(self.testCoverWidget)
gridlayout.setContentsMargins(0, 0, 0, 0)
diff --git a/comictaggerlib/autotagstartwindow.py b/comictaggerlib/autotagstartwindow.py
index 492187b..a48d418 100644
--- a/comictaggerlib/autotagstartwindow.py
+++ b/comictaggerlib/autotagstartwindow.py
@@ -47,7 +47,6 @@ class AutoTagStartWindow(QtWidgets.QDialog):
self.cbxAssumeIssueOne.setChecked(self.settings.assume_1_if_no_issue_num)
self.cbxIgnoreLeadingDigitsInFilename.setChecked(self.settings.ignore_leading_numbers_in_filename)
self.cbxRemoveAfterSuccess.setChecked(self.settings.remove_archive_after_successful_match)
- self.cbxWaitForRateLimit.setChecked(self.settings.wait_and_retry_on_rate_limit)
self.cbxAutoImprint.setChecked(self.settings.auto_imprint)
nlmt_tip = """ The Name Length Match Tolerance is for eliminating automatic
@@ -76,7 +75,6 @@ class AutoTagStartWindow(QtWidgets.QDialog):
self.assume_issue_one = False
self.ignore_leading_digits_in_filename = False
self.remove_after_success = False
- self.wait_and_retry_on_rate_limit = False
self.search_string = ""
self.name_length_match_tolerance = self.settings.id_length_delta_thresh
self.split_words = self.cbxSplitWords.isChecked()
@@ -94,7 +92,6 @@ class AutoTagStartWindow(QtWidgets.QDialog):
self.ignore_leading_digits_in_filename = self.cbxIgnoreLeadingDigitsInFilename.isChecked()
self.remove_after_success = self.cbxRemoveAfterSuccess.isChecked()
self.name_length_match_tolerance = int(self.leNameLengthMatchTolerance.text())
- self.wait_and_retry_on_rate_limit = self.cbxWaitForRateLimit.isChecked()
self.split_words = self.cbxSplitWords.isChecked()
# persist some settings
@@ -103,7 +100,6 @@ class AutoTagStartWindow(QtWidgets.QDialog):
self.settings.assume_1_if_no_issue_num = self.assume_issue_one
self.settings.ignore_leading_numbers_in_filename = self.ignore_leading_digits_in_filename
self.settings.remove_archive_after_successful_match = self.remove_after_success
- self.settings.wait_and_retry_on_rate_limit = self.wait_and_retry_on_rate_limit
if self.cbxSpecifySearchString.isChecked():
self.search_string = self.leSearchString.text()
diff --git a/comictaggerlib/cli.py b/comictaggerlib/cli.py
index c09ed42..84681b0 100644
--- a/comictaggerlib/cli.py
+++ b/comictaggerlib/cli.py
@@ -28,31 +28,30 @@ from comicapi import utils
from comicapi.comicarchive import ComicArchive, MetaDataStyle
from comicapi.genericmetadata import GenericMetadata
from comictaggerlib.cbltransformer import CBLTransformer
-from comictaggerlib.comicvinetalker import ComicVineTalker, ComicVineTalkerException
from comictaggerlib.filerenamer import FileRenamer
from comictaggerlib.issueidentifier import IssueIdentifier
from comictaggerlib.resulttypes import IssueResult, MultipleMatch, OnlineMatchResults
from comictaggerlib.settings import ComicTaggerSettings
+from comictalker.comictalker import ComicTalker
+from comictalker.talkerbase import TalkerError
logger = logging.getLogger(__name__)
def actual_issue_data_fetch(
- match: IssueResult, settings: ComicTaggerSettings, opts: argparse.Namespace
+ match: IssueResult, settings: ComicTaggerSettings, opts: argparse.Namespace, talker_api: ComicTalker
) -> GenericMetadata:
# now get the particular issue data
try:
- comic_vine = ComicVineTalker()
- comic_vine.wait_for_rate_limit = opts.wait_on_cv_rate_limit
- cv_md = comic_vine.fetch_issue_data(match["volume_id"], match["issue_number"], settings)
- except ComicVineTalkerException:
- logger.exception("Network error while getting issue details. Save aborted")
+ ct_md = talker_api.fetch_issue_data(match["volume_id"], match["issue_number"])
+ except TalkerError as e:
+ logger.exception(f"Error retrieving issue details. Save aborted.\n{e}")
return GenericMetadata()
- if settings.apply_cbl_transform_on_cv_import:
- cv_md = CBLTransformer(cv_md, settings).apply()
+ if settings.apply_cbl_transform_on_ct_import:
+ ct_md = CBLTransformer(ct_md, settings).apply()
- return cv_md
+ return ct_md
def actual_metadata_save(ca: ComicArchive, opts: argparse.Namespace, md: GenericMetadata) -> bool:
@@ -77,7 +76,11 @@ def actual_metadata_save(ca: ComicArchive, opts: argparse.Namespace, md: Generic
def display_match_set_for_choice(
- label: str, match_set: MultipleMatch, opts: argparse.Namespace, settings: ComicTaggerSettings
+ label: str,
+ match_set: MultipleMatch,
+ opts: argparse.Namespace,
+ settings: ComicTaggerSettings,
+ talker_api: ComicTalker,
) -> None:
print(f"{match_set.ca.path} -- {label}:")
@@ -107,11 +110,11 @@ def display_match_set_for_choice(
# we know at this point, that the file is all good to go
ca = match_set.ca
md = create_local_metadata(opts, ca, settings)
- cv_md = actual_issue_data_fetch(match_set.matches[int(i) - 1], settings, opts)
+ ct_md = actual_issue_data_fetch(match_set.matches[int(i) - 1], settings, opts, talker_api)
if opts.overwrite:
- md = cv_md
+ md = ct_md
else:
- md.overlay(cv_md)
+ md.overlay(ct_md)
if opts.auto_imprint:
md.fix_publisher()
@@ -120,7 +123,7 @@ def display_match_set_for_choice(
def post_process_matches(
- match_results: OnlineMatchResults, opts: argparse.Namespace, settings: ComicTaggerSettings
+ match_results: OnlineMatchResults, opts: argparse.Namespace, settings: ComicTaggerSettings, talker_api: ComicTalker
) -> None:
# now go through the match results
if opts.show_save_summary:
@@ -151,7 +154,7 @@ def post_process_matches(
if len(match_results.multiple_matches) > 0:
print("\nArchives with multiple high-confidence matches:\n------------------")
for match_set in match_results.multiple_matches:
- display_match_set_for_choice("Multiple high-confidence matches", match_set, opts, settings)
+ display_match_set_for_choice("Multiple high-confidence matches", match_set, opts, settings, talker_api)
if len(match_results.low_confidence_matches) > 0:
print("\nArchives with low-confidence matches:\n------------------")
@@ -161,10 +164,10 @@ def post_process_matches(
else:
label = "Multiple low-confidence matches"
- display_match_set_for_choice(label, match_set, opts, settings)
+ display_match_set_for_choice(label, match_set, opts, settings, talker_api)
-def cli_mode(opts: argparse.Namespace, settings: ComicTaggerSettings) -> None:
+def cli_mode(opts: argparse.Namespace, settings: ComicTaggerSettings, talker_api: ComicTalker) -> None:
if len(opts.files) < 1:
logger.error("You must specify at least one filename. Use the -h option for more info")
return
@@ -172,10 +175,10 @@ def cli_mode(opts: argparse.Namespace, settings: ComicTaggerSettings) -> None:
match_results = OnlineMatchResults()
for f in opts.files:
- process_file_cli(f, opts, settings, match_results)
+ process_file_cli(f, opts, settings, talker_api, match_results)
sys.stdout.flush()
- post_process_matches(match_results, opts, settings)
+ post_process_matches(match_results, opts, settings, talker_api)
def create_local_metadata(opts: argparse.Namespace, ca: ComicArchive, settings: ComicTaggerSettings) -> GenericMetadata:
@@ -210,7 +213,11 @@ def create_local_metadata(opts: argparse.Namespace, ca: ComicArchive, settings:
def process_file_cli(
- filename: str, opts: argparse.Namespace, settings: ComicTaggerSettings, match_results: OnlineMatchResults
+ filename: str,
+ opts: argparse.Namespace,
+ settings: ComicTaggerSettings,
+ talker_api: ComicTalker,
+ match_results: OnlineMatchResults,
) -> None:
batch_mode = len(opts.files) > 1
@@ -373,31 +380,29 @@ def process_file_cli(
# now, search online
if opts.online:
if opts.issue_id is not None:
- # we were given the actual ID to search with
+ # we were given the actual issue ID to search with.
try:
- comic_vine = ComicVineTalker()
- comic_vine.wait_for_rate_limit = opts.wait_on_cv_rate_limit
- cv_md = comic_vine.fetch_issue_data_by_issue_id(opts.issue_id, settings)
- except ComicVineTalkerException:
- logger.exception("Network error while getting issue details. Save aborted")
+ ct_md = talker_api.fetch_issue_data_by_issue_id(opts.issue_id)
+ except TalkerError as e:
+ logger.exception(f"Error retrieving issue details. Save aborted.\n{e}")
match_results.fetch_data_failures.append(str(ca.path.absolute()))
return
- if cv_md is None:
+ if ct_md is None:
logger.error("No match for ID %s was found.", opts.issue_id)
match_results.no_matches.append(str(ca.path.absolute()))
return
- if settings.apply_cbl_transform_on_cv_import:
- cv_md = CBLTransformer(cv_md, settings).apply()
+ if settings.apply_cbl_transform_on_ct_import:
+ ct_md = CBLTransformer(ct_md, settings).apply()
else:
- ii = IssueIdentifier(ca, settings)
-
if md is None or md.is_empty:
logger.error("No metadata given to search online with!")
match_results.no_matches.append(str(ca.path.absolute()))
return
+ ii = IssueIdentifier(ca, settings, talker_api)
+
def myoutput(text: str) -> None:
if opts.verbose:
IssueIdentifier.default_write_output(text)
@@ -405,7 +410,6 @@ def process_file_cli(
# use our overlaid MD struct to search
ii.set_additional_metadata(md)
ii.only_use_additional_meta_data = True
- ii.wait_and_retry_on_rate_limit = opts.wait_on_cv_rate_limit
ii.set_output_function(myoutput)
ii.cover_page_index = md.get_cover_page_index_list()[0]
matches = ii.search()
@@ -452,15 +456,15 @@ def process_file_cli(
# we got here, so we have a single match
# now get the particular issue data
- cv_md = actual_issue_data_fetch(matches[0], settings, opts)
- if cv_md.is_empty:
+ ct_md = actual_issue_data_fetch(matches[0], settings, opts, talker_api)
+ if ct_md.is_empty:
match_results.fetch_data_failures.append(str(ca.path.absolute()))
return
if opts.overwrite:
- md = cv_md
+ md = ct_md
else:
- md.overlay(cv_md)
+ md.overlay(ct_md)
if opts.auto_imprint:
md.fix_publisher()
diff --git a/comictaggerlib/coverimagewidget.py b/comictaggerlib/coverimagewidget.py
index 80b6ba2..3300ed2 100644
--- a/comictaggerlib/coverimagewidget.py
+++ b/comictaggerlib/coverimagewidget.py
@@ -26,12 +26,12 @@ from PyQt5 import QtCore, QtGui, QtWidgets, uic
from comicapi import utils
from comicapi.comicarchive import ComicArchive
-from comictaggerlib.comicvinetalker import ComicVineTalker
from comictaggerlib.imagefetcher import ImageFetcher
from comictaggerlib.imagepopup import ImagePopup
from comictaggerlib.pageloader import PageLoader
from comictaggerlib.settings import ComicTaggerSettings
from comictaggerlib.ui.qtutils import get_qimage_from_data, reduce_widget_font_size
+from comictalker.comictalker import ComicTalker
logger = logging.getLogger(__name__)
@@ -87,7 +87,9 @@ class CoverImageWidget(QtWidgets.QWidget):
URLMode = 1
DataMode = 3
- def __init__(self, parent: QtWidgets.QWidget, mode: int, expand_on_click: bool = True) -> None:
+ def __init__(
+ self, parent: QtWidgets.QWidget, talker_api: ComicTalker, mode: int, expand_on_click: bool = True
+ ) -> None:
super().__init__(parent)
uic.loadUi(ComicTaggerSettings.get_ui_file("coverimagewidget.ui"), self)
@@ -98,6 +100,7 @@ class CoverImageWidget(QtWidgets.QWidget):
self.alt_cover_url_list_fetch_complete, self.primary_url_fetch_complete, self.cover_remote_fetch_complete
)
+ self.talker_api = talker_api
self.mode: int = mode
self.page_loader: PageLoader | None = None
self.showControls = True
@@ -177,9 +180,8 @@ class CoverImageWidget(QtWidgets.QWidget):
self.update_content()
self.issue_id = issue_id
- comic_vine = ComicVineTalker()
- ComicVineTalker.url_fetch_complete = self.sig.emit_url
- comic_vine.async_fetch_issue_cover_urls(self.issue_id)
+ ComicTalker.url_fetch_complete = self.sig.emit_url
+ self.talker_api.async_fetch_issue_cover_urls(self.issue_id)
def set_image_data(self, image_data: bytes) -> None:
if self.mode == CoverImageWidget.DataMode:
@@ -209,10 +211,9 @@ class CoverImageWidget(QtWidgets.QWidget):
self.label.setText("Searching for alt. covers...")
# page URL should already be cached, so no need to defer
- comic_vine = ComicVineTalker()
- issue_page_url = comic_vine.fetch_issue_page_url(self.issue_id)
- ComicVineTalker.alt_url_list_fetch_complete = self.sig.emit_list
- comic_vine.async_fetch_alternate_cover_urls(utils.xlate(self.issue_id), cast(str, issue_page_url))
+ issue_page_url = self.talker_api.fetch_issue_page_url(self.issue_id)
+ ComicTalker.alt_url_list_fetch_complete = self.sig.emit_list
+ self.talker_api.async_fetch_alternate_cover_urls(utils.xlate(self.issue_id), cast(str, issue_page_url))
def alt_cover_url_list_fetch_complete(self, url_list: list[str]) -> None:
if url_list:
@@ -269,6 +270,8 @@ class CoverImageWidget(QtWidgets.QWidget):
self.cover_fetcher = ImageFetcher()
ImageFetcher.image_fetch_complete = self.sig.emit_image
self.cover_fetcher.fetch(self.url_list[self.imageIndex])
+ # TODO: Figure out async
+ self.sig.emit_image(self.cover_fetcher.fetch(self.url_list[self.imageIndex]))
# called when the image is done loading from internet
def cover_remote_fetch_complete(self, image_data: bytes) -> None:
diff --git a/comictaggerlib/imagefetcher.py b/comictaggerlib/imagefetcher.py
index 076cb2d..f88d66a 100644
--- a/comictaggerlib/imagefetcher.py
+++ b/comictaggerlib/imagefetcher.py
@@ -81,7 +81,8 @@ class ImageFetcher:
# first look in the DB
image_data = self.get_image_from_cache(url)
- if blocking or not qt_available:
+ # TODO: figure out async
+ if True: # if blocking or not qt_available:
if not image_data:
try:
image_data = requests.get(url, headers={"user-agent": "comictagger/" + ctversion.version}).content
diff --git a/comictaggerlib/issueidentifier.py b/comictaggerlib/issueidentifier.py
index 1fce66b..35704a4 100644
--- a/comictaggerlib/issueidentifier.py
+++ b/comictaggerlib/issueidentifier.py
@@ -26,11 +26,13 @@ from comicapi import utils
from comicapi.comicarchive import ComicArchive
from comicapi.genericmetadata import GenericMetadata
from comicapi.issuestring import IssueString
-from comictaggerlib.comicvinetalker import ComicVineTalker, ComicVineTalkerException
from comictaggerlib.imagefetcher import ImageFetcher, ImageFetcherException
from comictaggerlib.imagehasher import ImageHasher
from comictaggerlib.resulttypes import IssueResult
from comictaggerlib.settings import ComicTaggerSettings
+from comictalker.comictalker import ComicTalker
+from comictalker.talkerbase import TalkerError
+from comictalker.utils import parse_date_str
logger = logging.getLogger(__name__)
@@ -72,8 +74,9 @@ class IssueIdentifier:
result_one_good_match = 4
result_multiple_good_matches = 5
- def __init__(self, comic_archive: ComicArchive, settings: ComicTaggerSettings) -> None:
+ def __init__(self, comic_archive: ComicArchive, settings: ComicTaggerSettings, talker_api: ComicTalker) -> None:
self.settings = settings
+ self.talker_api = talker_api
self.comic_archive: ComicArchive = comic_archive
self.image_hasher = 1
@@ -107,7 +110,6 @@ class IssueIdentifier:
self.search_result = self.result_no_matches
self.cover_page_index = 0
self.cancel = False
- self.wait_and_retry_on_rate_limit = False
self.match_list: list[IssueResult] = []
@@ -268,7 +270,6 @@ class IssueIdentifier:
def get_issue_cover_match_score(
self,
- comic_vine: ComicVineTalker,
issue_id: int,
primary_img_url: str,
primary_thumb_url: str,
@@ -301,7 +302,7 @@ class IssueIdentifier:
raise IssueIdentifierCancelled
if use_remote_alternates:
- alt_img_url_list = comic_vine.fetch_alternate_cover_urls(issue_id, page_url)
+ alt_img_url_list = self.talker_api.fetch_alternate_cover_urls(issue_id, page_url)
for alt_url in alt_img_url_list:
try:
alt_url_image_data = ImageFetcher().fetch(alt_url, blocking=True)
@@ -398,27 +399,24 @@ class IssueIdentifier:
if keys["month"] is not None:
self.log_msg("\tMonth: " + str(keys["month"]))
- comic_vine = ComicVineTalker()
- comic_vine.wait_for_rate_limit = self.wait_and_retry_on_rate_limit
-
- comic_vine.set_log_func(self.output_function)
+ self.talker_api.set_log_func(self.output_function)
self.log_msg(f"Searching for {keys['series']} #{keys['issue_number']} ...")
try:
- cv_search_results = comic_vine.search_for_series(keys["series"])
- except ComicVineTalkerException:
- self.log_msg("Network issue while searching for series. Aborting...")
+ ct_search_results = self.talker_api.search_for_series(keys["series"])
+ except TalkerError as e:
+ self.log_msg(f"Error searching for series.\n{e}")
return []
if self.cancel:
return []
- if cv_search_results is None:
+ if ct_search_results is None:
return []
series_second_round_list = []
- for item in cv_search_results:
+ for item in ct_search_results:
length_approved = False
publisher_approved = True
date_approved = True
@@ -444,7 +442,7 @@ class IssueIdentifier:
# remove any series from publishers on the filter
if item["publisher"] is not None:
- publisher = item["publisher"]["name"]
+ publisher = item["publisher"]
if publisher is not None and publisher.casefold() in self.publisher_filter:
publisher_approved = False
@@ -459,104 +457,150 @@ class IssueIdentifier:
# now sort the list by name length
series_second_round_list.sort(key=lambda x: len(x["name"]), reverse=False)
- # build a list of volume IDs
- volume_id_list = []
- for series in series_second_round_list:
- volume_id_list.append(series["id"])
-
- issue_list = None
- try:
- if len(volume_id_list) > 0:
- issue_list = comic_vine.fetch_issues_by_volume_issue_num_and_year(
- volume_id_list, keys["issue_number"], keys["year"]
- )
-
- except ComicVineTalkerException:
- self.log_msg("Network issue while searching for series details. Aborting...")
- return []
-
- if issue_list is None:
- return []
-
- shortlist = []
- # now re-associate the issues and volumes
- for issue in issue_list:
+ # If the sources lacks issue level data, don't look for it.
+ if not self.talker_api.static_options.has_issues:
for series in series_second_round_list:
- if series["id"] == issue["volume"]["id"]:
- shortlist.append((series, issue))
- break
+ hash_list = [cover_hash]
+ if narrow_cover_hash is not None:
+ hash_list.append(narrow_cover_hash)
- if keys["year"] is None:
- self.log_msg(f"Found {len(shortlist)} series that have an issue #{keys['issue_number']}")
+ try:
+ image_url = series["image"]
+ thumb_url = series["image"]
+ page_url = ""
+
+ score_item = self.get_issue_cover_match_score(
+ series["id"],
+ image_url,
+ thumb_url,
+ page_url, # Only required for alt covers
+ hash_list,
+ use_remote_alternates=False,
+ )
+ except Exception:
+ self.match_list = []
+ return self.match_list
+
+ match: IssueResult = {
+ "series": f"{series['name']} ({series['start_year']})",
+ "distance": score_item["score"],
+ "issue_number": "",
+ "cv_issue_count": series["count_of_issues"],
+ "url_image_hash": score_item["hash"],
+ "issue_title": series["name"],
+ "issue_id": 0,
+ "volume_id": series["id"],
+ "month": 0,
+ "year": int(series["start_year"]),
+ "publisher": series["publisher"],
+ "image_url": image_url,
+ "thumb_url": thumb_url,
+ "page_url": page_url,
+ "description": series["description"],
+ }
+
+ self.match_list.append(match)
+
+ self.log_msg(f" --> {match['distance']}", newline=False)
+
+ self.log_msg("")
else:
- self.log_msg(
- f"Found {len(shortlist)} series that have an issue #{keys['issue_number']} from {keys['year']}"
- )
-
- # now we have a shortlist of volumes with the desired issue number
- # Do first round of cover matching
- counter = len(shortlist)
- for series, issue in shortlist:
- if self.callback is not None:
- self.callback(counter, len(shortlist) * 3)
- counter += 1
-
- self.log_msg(
- f"Examining covers for ID: {series['id']} {series['name']} ({series['start_year']}) ...",
- newline=False,
- )
-
- # parse out the cover date
- _, month, year = comic_vine.parse_date_str(issue["cover_date"])
-
- # Now check the cover match against the primary image
- hash_list = [cover_hash]
- if narrow_cover_hash is not None:
- hash_list.append(narrow_cover_hash)
+ # build a list of volume IDs
+ volume_id_list = []
+ for series in series_second_round_list:
+ volume_id_list.append(series["id"])
+ issue_list = None
try:
- image_url = issue["image"]["super_url"]
- thumb_url = issue["image"]["thumb_url"]
- page_url = issue["site_detail_url"]
+ if len(volume_id_list) > 0:
+ issue_list = self.talker_api.fetch_issues_by_volume_issue_num_and_year(
+ volume_id_list, keys["issue_number"], keys["year"]
+ )
+ except TalkerError as e:
+ self.log_msg(f"Issue with while searching for series details. Aborting...\n{e}")
+ return []
- score_item = self.get_issue_cover_match_score(
- comic_vine,
- issue["id"],
- image_url,
- thumb_url,
- page_url,
- hash_list,
- use_remote_alternates=False,
+ if issue_list is None:
+ return []
+
+ shortlist = []
+ # now re-associate the issues and volumes
+ for issue in issue_list:
+ for series in series_second_round_list:
+ if series["id"] == issue["volume"]["id"]:
+ shortlist.append((series, issue))
+ break
+
+ if keys["year"] is None:
+ self.log_msg(f"Found {len(shortlist)} series that have an issue #{keys['issue_number']}")
+ else:
+ self.log_msg(
+ f"Found {len(shortlist)} series that have an issue #{keys['issue_number']} from {keys['year']}"
)
- except Exception:
- self.match_list = []
- return self.match_list
- match: IssueResult = {
- "series": f"{series['name']} ({series['start_year']})",
- "distance": score_item["score"],
- "issue_number": keys["issue_number"],
- "cv_issue_count": series["count_of_issues"],
- "url_image_hash": score_item["hash"],
- "issue_title": issue["name"],
- "issue_id": issue["id"],
- "volume_id": series["id"],
- "month": month,
- "year": year,
- "publisher": None,
- "image_url": image_url,
- "thumb_url": thumb_url,
- "page_url": page_url,
- "description": issue["description"],
- }
- if series["publisher"] is not None:
- match["publisher"] = series["publisher"]["name"]
+ # now we have a shortlist of volumes with the desired issue number
+ # Do first round of cover matching
+ counter = len(shortlist)
+ for series, issue in shortlist:
+ if self.callback is not None:
+ self.callback(counter, len(shortlist) * 3)
+ counter += 1
- self.match_list.append(match)
+ self.log_msg(
+ f"Examining covers for ID: {series['id']} {series['name']} ({series['start_year']}) ...",
+ newline=False,
+ )
- self.log_msg(f" --> {match['distance']}", newline=False)
+ # parse out the cover date
+ _, month, year = parse_date_str(issue["cover_date"])
- self.log_msg("")
+ # Now check the cover match against the primary image
+ hash_list = [cover_hash]
+ if narrow_cover_hash is not None:
+ hash_list.append(narrow_cover_hash)
+
+ try:
+ image_url = issue["image"]
+ thumb_url = issue["image_thumb"]
+ page_url = issue["site_detail_url"]
+
+ score_item = self.get_issue_cover_match_score(
+ issue["id"],
+ image_url,
+ thumb_url,
+ page_url,
+ hash_list,
+ use_remote_alternates=False,
+ )
+ except Exception:
+ self.match_list = []
+ return self.match_list
+
+ match: IssueResult = {
+ "series": f"{series['name']} ({series['start_year']})",
+ "distance": score_item["score"],
+ "issue_number": keys["issue_number"],
+ "cv_issue_count": series["count_of_issues"],
+ "url_image_hash": score_item["hash"],
+ "issue_title": issue["name"],
+ "issue_id": issue["id"],
+ "volume_id": series["id"],
+ "month": month,
+ "year": year,
+ "publisher": None,
+ "image_url": image_url,
+ "thumb_url": thumb_url,
+ "page_url": page_url,
+ "description": issue["description"],
+ }
+ if series["publisher"] is not None:
+ match["publisher"] = series["publisher"]
+
+ self.match_list.append(match)
+
+ self.log_msg(f" --> {match['distance']}", newline=False)
+
+ self.log_msg("")
if len(self.match_list) == 0:
self.log_msg(":-(no matches!")
@@ -608,7 +652,6 @@ class IssueIdentifier:
self.log_msg(f"Examining alternate covers for ID: {m['volume_id']} {m['series']} ...", newline=False)
try:
score_item = self.get_issue_cover_match_score(
- comic_vine,
m["issue_id"],
m["image_url"],
m["thumb_url"],
diff --git a/comictaggerlib/issueselectionwindow.py b/comictaggerlib/issueselectionwindow.py
index 1e773b9..1ec92ce 100644
--- a/comictaggerlib/issueselectionwindow.py
+++ b/comictaggerlib/issueselectionwindow.py
@@ -20,11 +20,12 @@ import logging
from PyQt5 import QtCore, QtGui, QtWidgets, uic
from comicapi.issuestring import IssueString
-from comictaggerlib.comicvinetalker import ComicVineTalker, ComicVineTalkerException
from comictaggerlib.coverimagewidget import CoverImageWidget
-from comictaggerlib.resulttypes import CVIssuesResults
from comictaggerlib.settings import ComicTaggerSettings
from comictaggerlib.ui.qtutils import reduce_widget_font_size
+from comictalker.comictalker import ComicTalker
+from comictalker.resulttypes import ComicIssue
+from comictalker.talkerbase import TalkerError
logger = logging.getLogger(__name__)
@@ -41,13 +42,18 @@ class IssueSelectionWindow(QtWidgets.QDialog):
volume_id = 0
def __init__(
- self, parent: QtWidgets.QWidget, settings: ComicTaggerSettings, series_id: int, issue_number: str
+ self,
+ parent: QtWidgets.QWidget,
+ settings: ComicTaggerSettings,
+ talker_api: ComicTalker,
+ series_id: int,
+ issue_number: str,
) -> None:
super().__init__(parent)
uic.loadUi(ComicTaggerSettings.get_ui_file("issueselectionwindow.ui"), self)
- self.coverWidget = CoverImageWidget(self.coverImageContainer, CoverImageWidget.AltCoverMode)
+ self.coverWidget = CoverImageWidget(self.coverImageContainer, talker_api, CoverImageWidget.AltCoverMode)
gridlayout = QtWidgets.QGridLayout(self.coverImageContainer)
gridlayout.addWidget(self.coverWidget)
gridlayout.setContentsMargins(0, 0, 0, 0)
@@ -66,8 +72,9 @@ class IssueSelectionWindow(QtWidgets.QDialog):
self.series_id = series_id
self.issue_id: int | None = None
self.settings = settings
+ self.talker_api = talker_api
self.url_fetch_thread = None
- self.issue_list: list[CVIssuesResults] = []
+ self.issue_list: list[ComicIssue] = []
if issue_number is None or issue_number == "":
self.issue_number = "1"
@@ -97,15 +104,14 @@ class IssueSelectionWindow(QtWidgets.QDialog):
QtWidgets.QApplication.setOverrideCursor(QtGui.QCursor(QtCore.Qt.CursorShape.WaitCursor))
try:
- comic_vine = ComicVineTalker()
- comic_vine.fetch_volume_data(self.series_id)
- self.issue_list = comic_vine.fetch_issues_by_volume(self.series_id)
- except ComicVineTalkerException as e:
+ self.issue_list = self.talker_api.fetch_issues_by_volume(self.series_id)
+ except TalkerError as e:
QtWidgets.QApplication.restoreOverrideCursor()
- if e.code == ComicVineTalkerException.RateLimit:
- QtWidgets.QMessageBox.critical(self, "Comic Vine Error", ComicVineTalker.get_rate_limit_message())
- else:
- QtWidgets.QMessageBox.critical(self, "Network Issue", "Could not connect to Comic Vine to list issues!")
+ QtWidgets.QMessageBox.critical(
+ self,
+ f"{e.source} {e.code_name} Error",
+ f"{e}",
+ )
return
self.twList.setRowCount(0)
diff --git a/comictaggerlib/main.py b/comictaggerlib/main.py
index d35fed1..97e0bac 100755
--- a/comictaggerlib/main.py
+++ b/comictaggerlib/main.py
@@ -27,10 +27,10 @@ import types
from comicapi import utils
from comictaggerlib import cli
-from comictaggerlib.comicvinetalker import ComicVineTalker
from comictaggerlib.ctversion import version
from comictaggerlib.options import parse_cmd_line
from comictaggerlib.settings import ComicTaggerSettings
+from comictalker.comictalker import ComicTalker
if sys.version_info < (3, 10):
import importlib_metadata
@@ -40,6 +40,7 @@ else:
logger = logging.getLogger("comictagger")
logging.getLogger("comicapi").setLevel(logging.DEBUG)
logging.getLogger("comictaggerlib").setLevel(logging.DEBUG)
+logging.getLogger("sourcesapi").setLevel(logging.DEBUG)
logger.setLevel(logging.DEBUG)
try:
@@ -135,19 +136,6 @@ def ctmain() -> None:
)
# Need to load setting before anything else
- # 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()
- 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
-
signal.signal(signal.SIGINT, signal.SIG_DFL)
logger.info(
@@ -161,6 +149,8 @@ def ctmain() -> None:
for pkg in sorted(importlib_metadata.distributions(), key=lambda x: x.name):
logger.debug("%s\t%s", pkg.name, pkg.version)
+ talker_api = ComicTalker(SETTINGS.comic_info_source)
+
utils.load_publishers()
update_publishers()
@@ -170,7 +160,7 @@ def ctmain() -> None:
if opts.no_gui:
try:
- cli.cli_mode(opts, SETTINGS)
+ cli.cli_mode(opts, SETTINGS, talker_api)
except Exception:
logger.exception("CLI mode failed")
else:
@@ -206,7 +196,7 @@ def ctmain() -> None:
QtWidgets.QApplication.processEvents()
try:
- tagger_window = TaggerWindow(opts.files, SETTINGS, opts=opts)
+ tagger_window = TaggerWindow(opts.files, SETTINGS, talker_api, opts=opts)
tagger_window.setWindowIcon(QtGui.QIcon(ComicTaggerSettings.get_graphic("app.png")))
tagger_window.show()
diff --git a/comictaggerlib/matchselectionwindow.py b/comictaggerlib/matchselectionwindow.py
index c60ecb7..1861b34 100644
--- a/comictaggerlib/matchselectionwindow.py
+++ b/comictaggerlib/matchselectionwindow.py
@@ -25,6 +25,7 @@ from comictaggerlib.coverimagewidget import CoverImageWidget
from comictaggerlib.resulttypes import IssueResult
from comictaggerlib.settings import ComicTaggerSettings
from comictaggerlib.ui.qtutils import reduce_widget_font_size
+from comictalker.comictalker import ComicTalker
logger = logging.getLogger(__name__)
@@ -32,17 +33,23 @@ logger = logging.getLogger(__name__)
class MatchSelectionWindow(QtWidgets.QDialog):
volume_id = 0
- def __init__(self, parent: QtWidgets.QWidget, matches: list[IssueResult], comic_archive: ComicArchive) -> None:
+ def __init__(
+ self,
+ parent: QtWidgets.QWidget,
+ matches: list[IssueResult],
+ comic_archive: ComicArchive,
+ talker_api: ComicTalker,
+ ) -> None:
super().__init__(parent)
uic.loadUi(ComicTaggerSettings.get_ui_file("matchselectionwindow.ui"), self)
- self.altCoverWidget = CoverImageWidget(self.altCoverContainer, CoverImageWidget.AltCoverMode)
+ self.altCoverWidget = CoverImageWidget(self.altCoverContainer, talker_api, CoverImageWidget.AltCoverMode)
gridlayout = QtWidgets.QGridLayout(self.altCoverContainer)
gridlayout.addWidget(self.altCoverWidget)
gridlayout.setContentsMargins(0, 0, 0, 0)
- self.archiveCoverWidget = CoverImageWidget(self.archiveCoverContainer, CoverImageWidget.ArchiveMode)
+ self.archiveCoverWidget = CoverImageWidget(self.archiveCoverContainer, talker_api, CoverImageWidget.ArchiveMode)
gridlayout = QtWidgets.QGridLayout(self.archiveCoverContainer)
gridlayout.addWidget(self.archiveCoverWidget)
gridlayout.setContentsMargins(0, 0, 0, 0)
diff --git a/comictaggerlib/options.py b/comictaggerlib/options.py
index 03f52b4..5281fa8 100644
--- a/comictaggerlib/options.py
+++ b/comictaggerlib/options.py
@@ -80,6 +80,7 @@ def define_args() -> argparse.ArgumentParser:
action="store_true",
help="Export RAR archive to Zip format.",
)
+ # TODO: update for new api
commands.add_argument(
"--only-set-cv-key",
action="store_true",
@@ -107,10 +108,12 @@ def define_args() -> argparse.ArgumentParser:
dest="config_path",
help="""Config directory defaults to ~/.ComicTagger\non Linux/Mac and %%APPDATA%% on Windows\n""",
)
+ # TODO: update for new api
parser.add_argument(
"--cv-api-key",
help="Use the given Comic Vine API Key (persisted in settings).",
)
+ # TODO: update for new api
parser.add_argument(
"--cv-url",
help="Use the given Comic Vine URL (persisted in settings).",
@@ -214,6 +217,7 @@ def define_args() -> argparse.ArgumentParser:
action="store_true",
help="Be noisy when doing what it does.",
)
+ # TODO: update for new api
parser.add_argument(
"-w",
"--wait-on-cv-rate-limit",
@@ -369,7 +373,7 @@ def parse_cmd_line() -> argparse.Namespace:
opts.copy,
opts.rename,
opts.export_to_zip,
- opts.only_set_cv_key,
+ opts.only_set_cv_key, # TODO: update for new api
]
)
@@ -385,9 +389,11 @@ def parse_cmd_line() -> argparse.Namespace:
for item in globs:
opts.files.extend(glob.glob(item))
+ # TODO: update for new api
if opts.only_set_cv_key and opts.cv_api_key is None and opts.cv_url is None:
parser.exit(message="Key not given!\n", status=1)
+ # TODO: update for new api
if not opts.only_set_cv_key and opts.no_gui and not opts.files:
parser.exit(message="Command requires at least one filename!\n", status=1)
diff --git a/comictaggerlib/pagebrowser.py b/comictaggerlib/pagebrowser.py
index 1071206..7913336 100644
--- a/comictaggerlib/pagebrowser.py
+++ b/comictaggerlib/pagebrowser.py
@@ -24,17 +24,18 @@ from comicapi.comicarchive import ComicArchive
from comicapi.genericmetadata import GenericMetadata
from comictaggerlib.coverimagewidget import CoverImageWidget
from comictaggerlib.settings import ComicTaggerSettings
+from comictalker.comictalker import ComicTalker
logger = logging.getLogger(__name__)
class PageBrowserWindow(QtWidgets.QDialog):
- def __init__(self, parent: QtWidgets.QWidget, metadata: GenericMetadata) -> None:
+ def __init__(self, parent: QtWidgets.QWidget, talker_api: ComicTalker, metadata: GenericMetadata) -> None:
super().__init__(parent)
uic.loadUi(ComicTaggerSettings.get_ui_file("pagebrowser.ui"), self)
- self.pageWidget = CoverImageWidget(self.pageContainer, CoverImageWidget.ArchiveMode)
+ self.pageWidget = CoverImageWidget(self.pageContainer, talker_api, CoverImageWidget.ArchiveMode)
gridlayout = QtWidgets.QGridLayout(self.pageContainer)
gridlayout.addWidget(self.pageWidget)
gridlayout.setContentsMargins(0, 0, 0, 0)
diff --git a/comictaggerlib/pagelisteditor.py b/comictaggerlib/pagelisteditor.py
index 9a68a52..ca93d4f 100644
--- a/comictaggerlib/pagelisteditor.py
+++ b/comictaggerlib/pagelisteditor.py
@@ -23,6 +23,7 @@ from comicapi.comicarchive import ComicArchive, MetaDataStyle
from comicapi.genericmetadata import ImageMetadata, PageType
from comictaggerlib.coverimagewidget import CoverImageWidget
from comictaggerlib.settings import ComicTaggerSettings
+from comictalker.comictalker import ComicTalker
logger = logging.getLogger(__name__)
@@ -67,12 +68,12 @@ class PageListEditor(QtWidgets.QWidget):
PageType.Deleted: "Deleted",
}
- def __init__(self, parent: QtWidgets.QWidget) -> None:
+ def __init__(self, parent: QtWidgets.QWidget, talker_api: ComicTalker) -> None:
super().__init__(parent)
uic.loadUi(ComicTaggerSettings.get_ui_file("pagelisteditor.ui"), self)
- self.pageWidget = CoverImageWidget(self.pageContainer, CoverImageWidget.ArchiveMode)
+ self.pageWidget = CoverImageWidget(self.pageContainer, talker_api, CoverImageWidget.ArchiveMode)
gridlayout = QtWidgets.QGridLayout(self.pageContainer)
gridlayout.addWidget(self.pageWidget)
gridlayout.setContentsMargins(0, 0, 0, 0)
diff --git a/comictaggerlib/renamewindow.py b/comictaggerlib/renamewindow.py
index 6bf04b5..7f5af28 100644
--- a/comictaggerlib/renamewindow.py
+++ b/comictaggerlib/renamewindow.py
@@ -28,6 +28,7 @@ from comictaggerlib.filerenamer import FileRenamer
from comictaggerlib.settings import ComicTaggerSettings
from comictaggerlib.settingswindow import SettingsWindow
from comictaggerlib.ui.qtutils import center_window_on_parent
+from comictalker.comictalker import ComicTalker
logger = logging.getLogger(__name__)
@@ -44,6 +45,7 @@ class RenameWindow(QtWidgets.QDialog):
comic_archive_list: list[ComicArchive],
data_style: int,
settings: ComicTaggerSettings,
+ talker_api: ComicTalker,
) -> None:
super().__init__(parent)
@@ -59,6 +61,7 @@ class RenameWindow(QtWidgets.QDialog):
)
self.settings = settings
+ self.talker_api = talker_api
self.comic_archive_list = comic_archive_list
self.data_style = data_style
self.rename_list: list[RenameItem] = []
@@ -157,7 +160,7 @@ class RenameWindow(QtWidgets.QDialog):
self.twList.setSortingEnabled(True)
def modify_settings(self) -> None:
- settingswin = SettingsWindow(self, self.settings)
+ settingswin = SettingsWindow(self, self.settings, self.talker_api)
settingswin.setModal(True)
settingswin.show_rename_tab()
settingswin.exec()
diff --git a/comictaggerlib/resulttypes.py b/comictaggerlib/resulttypes.py
index 31062d4..1982dd7 100644
--- a/comictaggerlib/resulttypes.py
+++ b/comictaggerlib/resulttypes.py
@@ -1,6 +1,6 @@
from __future__ import annotations
-from typing_extensions import NotRequired, Required, TypedDict
+from typing_extensions import TypedDict
from comicapi.comicarchive import ComicArchive
@@ -44,117 +44,3 @@ class SelectDetails(TypedDict):
thumb_image_url: str | None
cover_date: str | None
site_detail_url: str | None
-
-
-class CVResult(TypedDict):
- error: str
- limit: int
- offset: int
- number_of_page_results: int
- number_of_total_results: int
- status_code: int
- results: (
- CVIssuesResults
- | CVIssueDetailResults
- | CVVolumeResults
- | list[CVIssuesResults]
- | list[CVVolumeResults]
- | list[CVIssueDetailResults]
- )
- version: str
-
-
-class CVImage(TypedDict, total=False):
- icon_url: str
- medium_url: str
- screen_url: str
- screen_large_url: str
- small_url: str
- super_url: Required[str]
- thumb_url: str
- tiny_url: str
- original_url: str
- image_tags: str
-
-
-class CVVolume(TypedDict):
- api_detail_url: str
- id: int
- name: str
- site_detail_url: str
-
-
-class CVIssuesResults(TypedDict):
- cover_date: str
- description: str
- id: int
- image: CVImage
- issue_number: str
- name: str
- site_detail_url: str
- volume: NotRequired[CVVolume]
-
-
-class CVPublisher(TypedDict, total=False):
- api_detail_url: str
- id: int
- name: Required[str]
-
-
-class CVVolumeResults(TypedDict):
- count_of_issues: int
- description: NotRequired[str]
- id: int
- image: NotRequired[CVImage]
- name: str
- publisher: CVPublisher
- start_year: str
- resource_type: NotRequired[str]
-
-
-class CVCredits(TypedDict):
- api_detail_url: str
- id: int
- name: str
- site_detail_url: str
-
-
-class CVPersonCredits(TypedDict):
- api_detail_url: str
- id: int
- name: str
- site_detail_url: str
- role: str
-
-
-class CVIssueDetailResults(TypedDict):
- aliases: None
- api_detail_url: str
- character_credits: list[CVCredits]
- character_died_in: None
- concept_credits: list[CVCredits]
- cover_date: str
- date_added: str
- date_last_updated: str
- deck: None
- description: str
- first_appearance_characters: None
- first_appearance_concepts: None
- first_appearance_locations: None
- first_appearance_objects: None
- first_appearance_storyarcs: None
- first_appearance_teams: None
- has_staff_review: bool
- id: int
- image: CVImage
- issue_number: str
- location_credits: list[CVCredits]
- name: str
- object_credits: list[CVCredits]
- person_credits: list[CVPersonCredits]
- site_detail_url: str
- store_date: str
- story_arc_credits: list[CVCredits]
- team_credits: list[CVCredits]
- team_disbanded_in: None
- volume: CVVolume
diff --git a/comictaggerlib/settings.py b/comictaggerlib/settings.py
index 26a75d1..39ed6d7 100644
--- a/comictaggerlib/settings.py
+++ b/comictaggerlib/settings.py
@@ -56,6 +56,30 @@ class ComicTaggerSettings:
def get_ui_file(filename: str | pathlib.Path) -> pathlib.Path:
return ComicTaggerSettings.base_dir() / "ui" / filename
+ @staticmethod
+ def get_source_settings(source_name: str, source_settings: dict):
+ def get_type(section: str, setting: dict):
+ if setting["type"] is str:
+ return config.get(section, setting["name"])
+ elif setting["type"] is int:
+ return config.getint(section, setting["name"])
+ elif setting["type"] is float:
+ return config.getfloat(section, setting["name"])
+ elif setting["type"] is bool:
+ return config.getboolean(section, setting["name"])
+
+ settings_dir = ComicTaggerSettings.get_settings_folder()
+ settings_file = os.path.join(settings_dir, "settings")
+ config = configparser.RawConfigParser()
+ try:
+ with open(settings_file, encoding="utf-8") as f:
+ config.read_file(f)
+ for setting in source_settings.values():
+ setting["value"] = get_type(source_name, setting)
+ return True
+ except Exception:
+ return False
+
def __init__(self, folder: str | pathlib.Path | None) -> None:
# General Settings
self.rar_exe_path = ""
@@ -80,6 +104,7 @@ class ComicTaggerSettings:
# identifier settings
self.id_length_delta_thresh = 5
self.id_publisher_filter = "Panini Comics, Abril, Planeta DeAgostini, Editorial Televisa, Dino Comics"
+ self.comic_info_source = "comicvine" # Default to CV as should always be present
# Show/ask dialog flags
self.ask_about_cbi_in_rar = True
@@ -93,12 +118,8 @@ class ComicTaggerSettings:
self.remove_fcbd = False
self.remove_publisher = False
- # Comic Vine settings
- self.use_series_start_as_volume = False
- self.clear_form_before_populating_from_cv = False
- self.remove_html_tables = False
- self.cv_api_key = ""
- self.cv_url = ""
+ # Comic source general settings
+ self.clear_form_before_populating = False
self.auto_imprint = False
self.sort_series_by_year = True
@@ -114,7 +135,7 @@ class ComicTaggerSettings:
self.copy_storyarcs_to_tags = False
self.copy_notes_to_comments = False
self.copy_weblink_to_comments = False
- self.apply_cbl_transform_on_cv_import = False
+ self.apply_cbl_transform_on_ct_import = False
self.apply_cbl_transform_on_bulk_operation = False
# Rename settings
@@ -219,6 +240,8 @@ class ComicTaggerSettings:
self.id_length_delta_thresh = self.config.getint("identifier", "id_length_delta_thresh")
if self.config.has_option("identifier", "id_publisher_filter"):
self.id_publisher_filter = self.config.get("identifier", "id_publisher_filter")
+ if self.config.has_option("identifier", "always_use_publisher_filter"):
+ self.always_use_publisher_filter = self.config.getboolean("identifier", "always_use_publisher_filter")
if self.config.has_option("filenameparser", "complicated_parser"):
self.complicated_parser = self.config.getboolean("filenameparser", "complicated_parser")
@@ -238,27 +261,18 @@ class ComicTaggerSettings:
if self.config.has_option("dialogflags", "ask_about_usage_stats"):
self.ask_about_usage_stats = self.config.getboolean("dialogflags", "ask_about_usage_stats")
- if self.config.has_option("comicvine", "use_series_start_as_volume"):
- self.use_series_start_as_volume = self.config.getboolean("comicvine", "use_series_start_as_volume")
- if self.config.has_option("comicvine", "clear_form_before_populating_from_cv"):
- self.clear_form_before_populating_from_cv = self.config.getboolean(
- "comicvine", "clear_form_before_populating_from_cv"
+ if self.config.has_option("comic_source_general", "clear_form_before_populating"):
+ self.clear_form_before_populating = self.config.getboolean(
+ "comic_source_general", "clear_form_before_populating"
)
- if self.config.has_option("comicvine", "remove_html_tables"):
- self.remove_html_tables = self.config.getboolean("comicvine", "remove_html_tables")
-
- if self.config.has_option("comicvine", "sort_series_by_year"):
- self.sort_series_by_year = self.config.getboolean("comicvine", "sort_series_by_year")
- if self.config.has_option("comicvine", "exact_series_matches_first"):
- self.exact_series_matches_first = self.config.getboolean("comicvine", "exact_series_matches_first")
- if self.config.has_option("comicvine", "always_use_publisher_filter"):
- self.always_use_publisher_filter = self.config.getboolean("comicvine", "always_use_publisher_filter")
-
- if self.config.has_option("comicvine", "cv_api_key"):
- self.cv_api_key = self.config.get("comicvine", "cv_api_key")
-
- if self.config.has_option("comicvine", "cv_url"):
- self.cv_url = self.config.get("comicvine", "cv_url")
+ if self.config.has_option("comic_source_general", "sort_series_by_year"):
+ self.sort_series_by_year = self.config.getboolean("comic_source_general", "sort_series_by_year")
+ if self.config.has_option("comic_source_general", "exact_series_matches_first"):
+ self.exact_series_matches_first = self.config.getboolean(
+ "comic_source_general", "exact_series_matches_first"
+ )
+ if self.config.has_option("comic_source_general", "comic_info_source"):
+ self.comic_info_source = self.config.get("identifier", "comic_info_source")
if self.config.has_option("cbl_transform", "assume_lone_credit_is_primary"):
self.assume_lone_credit_is_primary = self.config.getboolean(
@@ -276,9 +290,9 @@ class ComicTaggerSettings:
self.copy_storyarcs_to_tags = self.config.getboolean("cbl_transform", "copy_storyarcs_to_tags")
if self.config.has_option("cbl_transform", "copy_weblink_to_comments"):
self.copy_weblink_to_comments = self.config.getboolean("cbl_transform", "copy_weblink_to_comments")
- if self.config.has_option("cbl_transform", "apply_cbl_transform_on_cv_import"):
- self.apply_cbl_transform_on_cv_import = self.config.getboolean(
- "cbl_transform", "apply_cbl_transform_on_cv_import"
+ if self.config.has_option("cbl_transform", "apply_cbl_transform_on_ct_import"):
+ self.apply_cbl_transform_on_ct_import = self.config.getboolean(
+ "cbl_transform", "apply_cbl_transform_on_ct_import"
)
if self.config.has_option("cbl_transform", "apply_cbl_transform_on_bulk_operation"):
self.apply_cbl_transform_on_bulk_operation = self.config.getboolean(
@@ -352,6 +366,7 @@ class ComicTaggerSettings:
self.config.set("identifier", "id_length_delta_thresh", self.id_length_delta_thresh)
self.config.set("identifier", "id_publisher_filter", self.id_publisher_filter)
+ self.config.set("identifier", "always_use_publisher_filter", self.always_use_publisher_filter)
if not self.config.has_section("dialogflags"):
self.config.add_section("dialogflags")
@@ -369,19 +384,13 @@ class ComicTaggerSettings:
self.config.set("filenameparser", "remove_fcbd", self.remove_fcbd)
self.config.set("filenameparser", "remove_publisher", self.remove_publisher)
- if not self.config.has_section("comicvine"):
- self.config.add_section("comicvine")
+ if not self.config.has_section("comic_source_general"):
+ self.config.add_section("comic_source_general")
- self.config.set("comicvine", "use_series_start_as_volume", self.use_series_start_as_volume)
- self.config.set("comicvine", "clear_form_before_populating_from_cv", self.clear_form_before_populating_from_cv)
- self.config.set("comicvine", "remove_html_tables", self.remove_html_tables)
-
- self.config.set("comicvine", "sort_series_by_year", self.sort_series_by_year)
- self.config.set("comicvine", "exact_series_matches_first", self.exact_series_matches_first)
- self.config.set("comicvine", "always_use_publisher_filter", self.always_use_publisher_filter)
-
- self.config.set("comicvine", "cv_api_key", self.cv_api_key)
- self.config.set("comicvine", "cv_url", self.cv_url)
+ self.config.set("comic_source_general", "clear_form_before_populating", self.clear_form_before_populating)
+ self.config.set("comic_source_general", "sort_series_by_year", self.sort_series_by_year)
+ self.config.set("comic_source_general", "exact_series_matches_first", self.exact_series_matches_first)
+ self.config.set("comic_source_general", "comic_info_source", self.comic_info_source)
if not self.config.has_section("cbl_transform"):
self.config.add_section("cbl_transform")
@@ -393,7 +402,7 @@ class ComicTaggerSettings:
self.config.set("cbl_transform", "copy_storyarcs_to_tags", self.copy_storyarcs_to_tags)
self.config.set("cbl_transform", "copy_notes_to_comments", self.copy_notes_to_comments)
self.config.set("cbl_transform", "copy_weblink_to_comments", self.copy_weblink_to_comments)
- self.config.set("cbl_transform", "apply_cbl_transform_on_cv_import", self.apply_cbl_transform_on_cv_import)
+ self.config.set("cbl_transform", "apply_cbl_transform_on_ct_import", self.apply_cbl_transform_on_ct_import)
self.config.set(
"cbl_transform",
"apply_cbl_transform_on_bulk_operation",
@@ -421,5 +430,7 @@ class ComicTaggerSettings:
self.config.set("autotag", "wait_and_retry_on_rate_limit", self.wait_and_retry_on_rate_limit)
self.config.set("autotag", "auto_imprint", self.auto_imprint)
+ # NOTE: Source settings are added in settingswindow module
+
with open(self.settings_file, "w", encoding="utf-8") as configfile:
self.config.write(configfile)
diff --git a/comictaggerlib/settingswindow.py b/comictaggerlib/settingswindow.py
index ab40957..43ecfdf 100644
--- a/comictaggerlib/settingswindow.py
+++ b/comictaggerlib/settingswindow.py
@@ -24,11 +24,11 @@ from PyQt5 import QtCore, QtGui, QtWidgets, uic
from comicapi import utils
from comicapi.genericmetadata import md_test
-from comictaggerlib.comicvinecacher import ComicVineCacher
-from comictaggerlib.comicvinetalker import ComicVineTalker
from comictaggerlib.filerenamer import FileRenamer
from comictaggerlib.imagefetcher import ImageFetcher
from comictaggerlib.settings import ComicTaggerSettings
+from comictalker.comiccacher import ComicCacher
+from comictalker.comictalker import ComicTalker
logger = logging.getLogger(__name__)
@@ -126,7 +126,7 @@ Spider-Geddon #1 - New Players; Check In
class SettingsWindow(QtWidgets.QDialog):
- def __init__(self, parent: QtWidgets.QWidget, settings: ComicTaggerSettings) -> None:
+ def __init__(self, parent: QtWidgets.QWidget, settings: ComicTaggerSettings, talker_api: ComicTalker) -> None:
super().__init__(parent)
uic.loadUi(ComicTaggerSettings.get_ui_file("settingswindow.ui"), self)
@@ -136,6 +136,7 @@ class SettingsWindow(QtWidgets.QDialog):
)
self.settings = settings
+ self.talker_api = talker_api
self.name = "Settings"
if platform.system() == "Windows":
@@ -183,7 +184,6 @@ class SettingsWindow(QtWidgets.QDialog):
self.btnBrowseRar.clicked.connect(self.select_rar)
self.btnClearCache.clicked.connect(self.clear_cache)
self.btnResetSettings.clicked.connect(self.reset_settings)
- self.btnTestKey.clicked.connect(self.test_api_key)
self.btnTemplateHelp.clicked.connect(self.show_template_help)
self.leRenameTemplate.textEdited.connect(self.rename__test)
self.cbxMoveFiles.clicked.connect(self.rename_test)
@@ -191,6 +191,96 @@ class SettingsWindow(QtWidgets.QDialog):
self.leDirectory.textEdited.connect(self.rename_test)
self.cbxComplicatedParser.clicked.connect(self.switch_parser)
+ self.sources: dict = {}
+ self.generate_source_option_tabs()
+
+ def generate_source_option_tabs(self) -> None:
+ # Add source sub tabs to Comic Sources tab
+ for source in self.talker_api.sources.values():
+ # Add source to general tab dropdown list
+ self.cobxInfoSource.addItem(source.source_details.name, source.source_details.id)
+ # Use a dict to make a var name from var
+ source_info = {}
+ tab_name = source.source_details.id
+ source_info[tab_name] = {"tab": QtWidgets.QWidget(), "widgets": {}}
+ layout_grid = QtWidgets.QGridLayout()
+ row = 0
+ for option in source.source_details.settings_options.values():
+ if not option["hidden"]:
+ current_widget = None
+ if option["type"] is bool:
+ # bool equals a checkbox (QCheckBox)
+ current_widget = QtWidgets.QCheckBox(option["text"])
+ # Set widget status
+ # This works because when a talker class is initialised it loads its settings from disk
+ if option["value"]:
+ current_widget.setChecked(option["value"])
+ # Add widget and span all columns
+ layout_grid.addWidget(current_widget, row, 0, 1, -1)
+ if option["type"] is int:
+ # int equals a spinbox (QSpinBox)
+ lbl = QtWidgets.QLabel(option["text"])
+ # Create a label
+ layout_grid.addWidget(lbl, row, 0)
+ current_widget = QtWidgets.QSpinBox()
+ current_widget.setRange(0, 9999)
+ if option["value"]:
+ current_widget.setValue(option["value"])
+ layout_grid.addWidget(current_widget, row, 1, alignment=QtCore.Qt.AlignLeft)
+ if option["type"] is float:
+ # float equals a spinbox (QDoubleSpinBox)
+ lbl = QtWidgets.QLabel(option["text"])
+ # Create a label
+ layout_grid.addWidget(lbl, row, 0)
+ current_widget = QtWidgets.QDoubleSpinBox()
+ current_widget.setRange(0, 9999.99)
+ if option["value"]:
+ current_widget.setValue(option["value"])
+ layout_grid.addWidget(current_widget, row, 1, alignment=QtCore.Qt.AlignLeft)
+ if option["type"] is str:
+ # str equals a text field (QLineEdit)
+ lbl = QtWidgets.QLabel(option["text"])
+ # Create a label
+ layout_grid.addWidget(lbl, row, 0)
+ current_widget = QtWidgets.QLineEdit()
+ # Set widget status
+ if option["value"]:
+ current_widget.setText(option["value"])
+ layout_grid.addWidget(current_widget, row, 1)
+ # Special case for api_key, make a test button
+ if option["name"] == "api_key":
+ btn = QtWidgets.QPushButton("Test Key")
+ layout_grid.addWidget(btn, row, 2)
+ btn.clicked.connect(lambda: self.test_api_key(source.source_details.id))
+ row += 1
+
+ if current_widget:
+ # Add tooltip text
+ current_widget.setToolTip(option["help_text"])
+
+ source_info[tab_name]["widgets"][option["name"]] = current_widget
+ else:
+ # An empty current_widget implies an unsupported type
+ logger.info(
+ "Unsupported talker option found. Name: "
+ + str(option["name"])
+ + " Type: "
+ + str(option["type"])
+ )
+
+ # Add vertical spacer
+ vspacer = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding)
+ layout_grid.addItem(vspacer, row, 0)
+ # Display the new widgets
+ source_info[tab_name]["tab"].setLayout(layout_grid)
+
+ # Add new sub tab to Comic Source tab
+ self.tComicSourcesOptions.addTab(source_info[tab_name]["tab"], source.source_details.name)
+ self.sources.update(source_info)
+
+ # Select active source in dropdown
+ self.cobxInfoSource.setCurrentIndex(self.cobxInfoSource.findData(self.settings.comic_info_source))
+
def rename_test(self) -> None:
self.rename__test(self.leRenameTemplate.text())
@@ -228,17 +318,12 @@ class SettingsWindow(QtWidgets.QDialog):
self.cbxRemovePublisher.setChecked(self.settings.remove_publisher)
self.switch_parser()
- self.cbxUseSeriesStartAsVolume.setChecked(self.settings.use_series_start_as_volume)
- self.cbxClearFormBeforePopulating.setChecked(self.settings.clear_form_before_populating_from_cv)
- self.cbxRemoveHtmlTables.setChecked(self.settings.remove_html_tables)
+ self.cbxClearFormBeforePopulating.setChecked(self.settings.clear_form_before_populating)
self.cbxUseFilter.setChecked(self.settings.always_use_publisher_filter)
self.cbxSortByYear.setChecked(self.settings.sort_series_by_year)
self.cbxExactMatches.setChecked(self.settings.exact_series_matches_first)
- self.leKey.setText(self.settings.cv_api_key)
- self.leURL.setText(self.settings.cv_url)
-
self.cbxAssumeLoneCreditIsPrimary.setChecked(self.settings.assume_lone_credit_is_primary)
self.cbxCopyCharactersToTags.setChecked(self.settings.copy_characters_to_tags)
self.cbxCopyTeamsToTags.setChecked(self.settings.copy_teams_to_tags)
@@ -246,7 +331,7 @@ class SettingsWindow(QtWidgets.QDialog):
self.cbxCopyStoryArcsToTags.setChecked(self.settings.copy_storyarcs_to_tags)
self.cbxCopyNotesToComments.setChecked(self.settings.copy_notes_to_comments)
self.cbxCopyWebLinkToComments.setChecked(self.settings.copy_weblink_to_comments)
- self.cbxApplyCBLTransformOnCVIMport.setChecked(self.settings.apply_cbl_transform_on_cv_import)
+ self.cbxApplyCBLTransformOnCVIMport.setChecked(self.settings.apply_cbl_transform_on_ct_import)
self.cbxApplyCBLTransformOnBatchOperation.setChecked(self.settings.apply_cbl_transform_on_bulk_operation)
self.leRenameTemplate.setText(self.settings.rename_template)
@@ -289,24 +374,20 @@ class SettingsWindow(QtWidgets.QDialog):
self.settings.id_length_delta_thresh = int(self.leNameLengthDeltaThresh.text())
self.settings.id_publisher_filter = str(self.tePublisherFilter.toPlainText())
+ self.settings.comic_info_source = str(self.cobxInfoSource.itemData(self.cobxInfoSource.currentIndex()))
+ # Also change current talker_api object
+ self.talker_api.source = self.settings.comic_info_source
self.settings.complicated_parser = self.cbxComplicatedParser.isChecked()
self.settings.remove_c2c = self.cbxRemoveC2C.isChecked()
self.settings.remove_fcbd = self.cbxRemoveFCBD.isChecked()
self.settings.remove_publisher = self.cbxRemovePublisher.isChecked()
- self.settings.use_series_start_as_volume = self.cbxUseSeriesStartAsVolume.isChecked()
- self.settings.clear_form_before_populating_from_cv = self.cbxClearFormBeforePopulating.isChecked()
- self.settings.remove_html_tables = self.cbxRemoveHtmlTables.isChecked()
-
+ self.settings.clear_form_before_populating = self.cbxClearFormBeforePopulating.isChecked()
self.settings.always_use_publisher_filter = self.cbxUseFilter.isChecked()
self.settings.sort_series_by_year = self.cbxSortByYear.isChecked()
self.settings.exact_series_matches_first = self.cbxExactMatches.isChecked()
- self.settings.cv_api_key = self.leKey.text().strip()
- ComicVineTalker.api_key = self.settings.cv_api_key
- self.settings.cv_url = self.leURL.text().strip()
- ComicVineTalker.api_base_url = self.settings.cv_url
self.settings.assume_lone_credit_is_primary = self.cbxAssumeLoneCreditIsPrimary.isChecked()
self.settings.copy_characters_to_tags = self.cbxCopyCharactersToTags.isChecked()
self.settings.copy_teams_to_tags = self.cbxCopyTeamsToTags.isChecked()
@@ -314,7 +395,7 @@ class SettingsWindow(QtWidgets.QDialog):
self.settings.copy_storyarcs_to_tags = self.cbxCopyStoryArcsToTags.isChecked()
self.settings.copy_notes_to_comments = self.cbxCopyNotesToComments.isChecked()
self.settings.copy_weblink_to_comments = self.cbxCopyWebLinkToComments.isChecked()
- self.settings.apply_cbl_transform_on_cv_import = self.cbxApplyCBLTransformOnCVIMport.isChecked()
+ self.settings.apply_cbl_transform_on_ct_import = self.cbxApplyCBLTransformOnCVIMport.isChecked()
self.settings.apply_cbl_transform_on_bulk_operation = self.cbxApplyCBLTransformOnBatchOperation.isChecked()
self.settings.rename_template = str(self.leRenameTemplate.text())
@@ -326,6 +407,51 @@ class SettingsWindow(QtWidgets.QDialog):
self.settings.rename_strict = self.cbxRenameStrict.isChecked()
+ # Read settings from sources tabs and generate self.settings.config data
+ for source in self.talker_api.sources.values():
+ source_info = self.sources[source.source_details.id]
+ if not self.settings.config.has_section(source.source_details.id):
+ self.settings.config.add_section(source.source_details.id)
+ # Iterate over sources options and get the tab setting
+ for option in source.source_details.settings_options.values():
+ # Only save visible here
+ if option["name"] in source_info["widgets"]:
+ # Set the tab setting for the talker class var
+ if option["type"] is bool:
+ current_widget: QtWidgets.QCheckBox = source_info["widgets"][option["name"]]
+ option["value"] = current_widget.isChecked()
+ if option["type"] is int:
+ current_widget: QtWidgets.QSpinBox = source_info["widgets"][option["name"]]
+ option["value"] = current_widget.value()
+ if option["type"] is float:
+ current_widget: QtWidgets.QDoubleSpinBox = source_info["widgets"][option["name"]]
+ option["value"] = current_widget.value()
+ if option["type"] is str:
+ current_widget: QtWidgets.QLineEdit = source_info["widgets"][option["name"]]
+ option["value"] = current_widget.text().strip()
+
+ else:
+ # Handle hidden, assume changed programmatically
+ if option["name"] == "enabled":
+ # Set to disabled if is not the selected talker
+ if source.source_details.id != self.settings.comic_info_source:
+ source.source_details.settings_options["enabled"]["value"] = False
+ else:
+ source.source_details.settings_options["enabled"]["value"] = True
+ else:
+ # Ensure correct type
+ if option["type"] is bool:
+ option["value"] = bool(option["value"])
+ if option["type"] is int:
+ option["value"] = int(option["value"])
+ if option["type"] is float:
+ option["value"] = float(option["value"])
+ if option["type"] is str:
+ option["value"] = str(option["value"]).strip()
+
+ # Save out option
+ self.settings.config.set(source.source_details.id, option["name"], option["value"])
+
self.settings.save()
QtWidgets.QDialog.accept(self)
@@ -334,11 +460,13 @@ class SettingsWindow(QtWidgets.QDialog):
def clear_cache(self) -> None:
ImageFetcher().clear_cache()
- ComicVineCacher().clear_cache()
+ ComicCacher().clear_cache()
QtWidgets.QMessageBox.information(self, self.name, "Cache has been cleared.")
- def test_api_key(self) -> None:
- if ComicVineTalker().test_key(self.leKey.text().strip(), self.leURL.text().strip()):
+ def test_api_key(self, source_id) -> None:
+ key = self.sources[source_id]["widgets"]["api_key"].text().strip()
+ url = self.sources[source_id]["widgets"]["url_root"].text().strip()
+ if self.talker_api.check_api_key(key, url, source_id):
QtWidgets.QMessageBox.information(self, "API Key Test", "Key is valid!")
else:
QtWidgets.QMessageBox.warning(self, "API Key Test", "Key is NOT valid.")
diff --git a/comictaggerlib/taggerwindow.py b/comictaggerlib/taggerwindow.py
index 8aa69db..53bfb9c 100644
--- a/comictaggerlib/taggerwindow.py
+++ b/comictaggerlib/taggerwindow.py
@@ -44,7 +44,6 @@ from comictaggerlib.autotagmatchwindow import AutoTagMatchWindow
from comictaggerlib.autotagprogresswindow import AutoTagProgressWindow
from comictaggerlib.autotagstartwindow import AutoTagStartWindow
from comictaggerlib.cbltransformer import CBLTransformer
-from comictaggerlib.comicvinetalker import ComicVineTalker, ComicVineTalkerException
from comictaggerlib.coverimagewidget import CoverImageWidget
from comictaggerlib.crediteditorwindow import CreditEditorWindow
from comictaggerlib.exportwindow import ExportConflictOpts, ExportWindow
@@ -61,6 +60,8 @@ from comictaggerlib.settingswindow import SettingsWindow
from comictaggerlib.ui.qtutils import center_window_on_parent, reduce_widget_font_size
from comictaggerlib.versionchecker import VersionChecker
from comictaggerlib.volumeselectionwindow import VolumeSelectionWindow
+from comictalker.comictalker import ComicTalker
+from comictalker.talkerbase import TalkerError
logger = logging.getLogger(__name__)
@@ -77,6 +78,7 @@ class TaggerWindow(QtWidgets.QMainWindow):
self,
file_list: list[str],
settings: ComicTaggerSettings,
+ talker_api: ComicTalker,
parent: QtWidgets.QWidget | None = None,
opts: argparse.Namespace | None = None,
) -> None:
@@ -84,6 +86,7 @@ class TaggerWindow(QtWidgets.QMainWindow):
uic.loadUi(ComicTaggerSettings.get_ui_file("taggerwindow.ui"), self)
self.settings = settings
+ self.talker_api = talker_api
self.log_window = self.setup_logger()
# prevent multiple instances
@@ -117,12 +120,12 @@ class TaggerWindow(QtWidgets.QMainWindow):
)
sys.exit()
- self.archiveCoverWidget = CoverImageWidget(self.coverImageContainer, CoverImageWidget.ArchiveMode)
+ self.archiveCoverWidget = CoverImageWidget(self.coverImageContainer, talker_api, CoverImageWidget.ArchiveMode)
grid_layout = QtWidgets.QGridLayout(self.coverImageContainer)
grid_layout.addWidget(self.archiveCoverWidget)
grid_layout.setContentsMargins(0, 0, 0, 0)
- self.page_list_editor = PageListEditor(self.tabPages)
+ self.page_list_editor = PageListEditor(self.tabPages, self.talker_api)
grid_layout = QtWidgets.QGridLayout(self.tabPages)
grid_layout.addWidget(self.page_list_editor)
@@ -1021,7 +1024,8 @@ Have fun!
issue_number = str(self.leIssueNum.text()).strip()
- if autoselect and issue_number == "":
+ # Only need this check is the source has issue level data.
+ if autoselect and issue_number == "" and self.talker_api.static_options.has_issues:
QtWidgets.QMessageBox.information(
self, "Automatic Identify Search", "Can't auto-identify without an issue number (yet!)"
)
@@ -1047,6 +1051,7 @@ Have fun!
cover_index_list,
cast(ComicArchive, self.comic_archive),
self.settings,
+ self.talker_api,
autoselect,
literal,
)
@@ -1064,23 +1069,25 @@ Have fun!
self.form_to_metadata()
try:
- comic_vine = ComicVineTalker()
- new_metadata = comic_vine.fetch_issue_data(selector.volume_id, selector.issue_number, self.settings)
- except ComicVineTalkerException as e:
- QtWidgets.QApplication.restoreOverrideCursor()
- if e.code == ComicVineTalkerException.RateLimit:
- QtWidgets.QMessageBox.critical(self, "Comic Vine Error", ComicVineTalker.get_rate_limit_message())
+ # Does the source support issue level data?
+ if self.talker_api.static_options.has_issues:
+ new_metadata = self.talker_api.fetch_issue_data(selector.volume_id, selector.issue_number)
else:
- QtWidgets.QMessageBox.critical(
- self, "Network Issue", "Could not connect to Comic Vine to get issue details.!"
- )
+ new_metadata = self.talker_api.fetch_volume_data(selector.volume_id)
+ except TalkerError as e:
+ QtWidgets.QApplication.restoreOverrideCursor()
+ QtWidgets.QMessageBox.critical(
+ self,
+ f"{e.source} {e.code_name} Error",
+ f"{e}",
+ )
else:
QtWidgets.QApplication.restoreOverrideCursor()
if new_metadata is not None:
- if self.settings.apply_cbl_transform_on_cv_import:
+ if self.settings.apply_cbl_transform_on_ct_import:
new_metadata = CBLTransformer(new_metadata, self.settings).apply()
- if self.settings.clear_form_before_populating_from_cv:
+ if self.settings.clear_form_before_populating:
self.clear_form()
self.metadata.overlay(new_metadata)
@@ -1364,7 +1371,7 @@ Have fun!
def show_settings(self) -> None:
- settingswin = SettingsWindow(self, self.settings)
+ settingswin = SettingsWindow(self, self.settings, self.talker_api)
settingswin.setModal(True)
settingswin.exec()
if settingswin.result():
@@ -1669,24 +1676,25 @@ Have fun!
def actual_issue_data_fetch(self, match: IssueResult) -> GenericMetadata:
- # now get the particular issue data
- cv_md = GenericMetadata()
+ # now get the particular issue data OR series data
+ ct_md = GenericMetadata()
QtWidgets.QApplication.setOverrideCursor(QtGui.QCursor(QtCore.Qt.CursorShape.WaitCursor))
try:
- comic_vine = ComicVineTalker()
- comic_vine.wait_for_rate_limit = self.settings.wait_and_retry_on_rate_limit
- cv_md = comic_vine.fetch_issue_data(match["volume_id"], match["issue_number"], self.settings)
- except ComicVineTalkerException:
- logger.exception("Network error while getting issue details. Save aborted")
+ if self.talker_api.static_options.has_issues:
+ ct_md = self.talker_api.fetch_issue_data(match["volume_id"], match["issue_number"])
+ else:
+ ct_md = self.talker_api.fetch_volume_data(match["volume_id"])
+ except TalkerError as e:
+ logger.exception(f"Save aborted.\n{e}")
- if not cv_md.is_empty:
- if self.settings.apply_cbl_transform_on_cv_import:
- cv_md = CBLTransformer(cv_md, self.settings).apply()
+ if not ct_md.is_empty:
+ if self.settings.apply_cbl_transform_on_ct_import:
+ ct_md = CBLTransformer(ct_md, self.settings).apply()
QtWidgets.QApplication.restoreOverrideCursor()
- return cv_md
+ return ct_md
def auto_tag_log(self, text: str) -> None:
IssueIdentifier.default_write_output(text)
@@ -1701,7 +1709,7 @@ Have fun!
self, ca: ComicArchive, match_results: OnlineMatchResults, dlg: AutoTagStartWindow
) -> tuple[bool, OnlineMatchResults]:
success = False
- ii = IssueIdentifier(ca, self.settings)
+ ii = IssueIdentifier(ca, self.settings, self.talker_api)
# read in metadata, and parse file name if not there
try:
@@ -1738,7 +1746,6 @@ Have fun!
md.issue = utils.xlate(md.volume)
ii.set_additional_metadata(md)
ii.only_use_additional_meta_data = True
- ii.wait_and_retry_on_rate_limit = dlg.wait_and_retry_on_rate_limit
ii.set_output_function(self.auto_tag_log)
ii.cover_page_index = md.get_cover_page_index_list()[0]
if self.atprogdialog is not None:
@@ -1787,15 +1794,15 @@ Have fun!
self.auto_tag_log("Online search: Low confidence match, but saving anyways, as indicated...\n")
# now get the particular issue data
- cv_md = self.actual_issue_data_fetch(matches[0])
- if cv_md is None:
+ ct_md = self.actual_issue_data_fetch(matches[0])
+ if ct_md is None:
match_results.fetch_data_failures.append(str(ca.path.absolute()))
- if cv_md is not None:
+ if ct_md is not None:
if dlg.cbxRemoveMetadata.isChecked():
- md = cv_md
+ md = ct_md
else:
- md.overlay(cv_md)
+ md.overlay(ct_md)
if self.settings.auto_imprint:
md.fix_publisher()
@@ -1838,7 +1845,7 @@ Have fun!
if not atstartdlg.exec():
return
- self.atprogdialog = AutoTagProgressWindow(self)
+ self.atprogdialog = AutoTagProgressWindow(self, self.talker_api)
self.atprogdialog.setModal(True)
self.atprogdialog.show()
self.atprogdialog.progressBar.setMaximum(len(ca_list))
@@ -1925,7 +1932,12 @@ Have fun!
match_results.multiple_matches.extend(match_results.low_confidence_matches)
if reply == QtWidgets.QMessageBox.StandardButton.Yes:
matchdlg = AutoTagMatchWindow(
- self, match_results.multiple_matches, style, self.actual_issue_data_fetch, self.settings
+ self,
+ match_results.multiple_matches,
+ style,
+ self.actual_issue_data_fetch,
+ self.settings,
+ self.talker_api,
)
matchdlg.setModal(True)
matchdlg.exec()
@@ -1988,7 +2000,7 @@ Have fun!
def show_page_browser(self) -> None:
if self.page_browser is None:
- self.page_browser = PageBrowserWindow(self, self.metadata)
+ self.page_browser = PageBrowserWindow(self, self.talker_api, self.metadata)
if self.comic_archive is not None:
self.page_browser.set_comic_archive(self.comic_archive)
self.page_browser.finished.connect(self.page_browser_closed)
@@ -2055,7 +2067,7 @@ Have fun!
"File Rename", "If you rename files now, unsaved data in the form will be lost. Are you sure?"
):
- dlg = RenameWindow(self, ca_list, self.load_data_style, self.settings)
+ dlg = RenameWindow(self, ca_list, self.load_data_style, self.settings, self.talker_api)
dlg.setModal(True)
if dlg.exec() and self.comic_archive is not None:
self.fileSelectionList.update_selected_rows()
diff --git a/comictaggerlib/ui/autotagstartwindow.ui b/comictaggerlib/ui/autotagstartwindow.ui
index 09d8450..bb3d543 100644
--- a/comictaggerlib/ui/autotagstartwindow.ui
+++ b/comictaggerlib/ui/autotagstartwindow.ui
@@ -10,7 +10,7 @@
", "\n")
+ string = string.replace("", "\n")
+ string = string.replace("