Merge branch 'AutoImprint' into develop
This commit is contained in:
commit
d3f552173e
130
comicapi/data/publishers.json
Normal file
130
comicapi/data/publishers.json
Normal file
@ -0,0 +1,130 @@
|
||||
{
|
||||
"Marvel":{
|
||||
"marvel comics": "",
|
||||
"aircel comics": "Aircel Comics",
|
||||
"aircel": "Aircel Comics",
|
||||
"atlas comics": "Atlas Comics",
|
||||
"atlas": "Atlas Comics",
|
||||
"crossgen comics": "CrossGen comics",
|
||||
"crossgen": "CrossGen comics",
|
||||
"curtis magazines": "Curtis Magazines",
|
||||
"disney books group": "Disney Books Group",
|
||||
"disney books": "Disney Books Group",
|
||||
"disney kingdoms": "Disney Kingdoms",
|
||||
"epic comics group": "Epic Comics",
|
||||
"epic comics": "Epic Comics",
|
||||
"epic": "Epic Comics",
|
||||
"eternity comics": "Eternity Comics",
|
||||
"humorama": "Humorama",
|
||||
"icon comics": "Icon Comics",
|
||||
"infinite comics": "Infinite Comics",
|
||||
"malibu comics": "Malibu Comics",
|
||||
"malibu": "Malibu Comics",
|
||||
"marvel 2099": "Marvel 2099",
|
||||
"marvel absurd": "Marvel Absurd",
|
||||
"marvel adventures": "Marvel Adventures",
|
||||
"marvel age": "Marvel Age",
|
||||
"marvel books": "Marvel Books",
|
||||
"marvel comics 2": "Marvel Comics 2",
|
||||
"marvel digital comics unlimited": "Marvel Unlimited",
|
||||
"marvel edge": "Marvel Edge",
|
||||
"marvel frontier": "Marvel Frontier",
|
||||
"marvel illustrated": "Marvel Illustrated",
|
||||
"marvel knights": "Marvel Knights",
|
||||
"marvel magazine group": "Marvel Magazine Group",
|
||||
"marvel mangaverse": "Marvel Mangaverse",
|
||||
"marvel monsters group": "Marvel Monsters Group",
|
||||
"marvel music": "Marvel Music",
|
||||
"marvel next": "Marvel Next",
|
||||
"marvel noir": "Marvel Noir",
|
||||
"marvel press": "Marvel Press",
|
||||
"marvel uk": "Marvel UK",
|
||||
"marvel unlimited": "Marvel Unlimited",
|
||||
"max": "MAX",
|
||||
"mc2": "Marvel Comics 2",
|
||||
"new universe": "New Universe",
|
||||
"non-pareil publishing corp.": "Non-Pareil Publishing Corp.",
|
||||
"paramount comics": "Paramount Comics",
|
||||
"power comics": "Power Comics",
|
||||
"razorline": "Razorline",
|
||||
"star comics": "Star Comics",
|
||||
"timely comics": "Timely Comics",
|
||||
"timely": "Timely Comics",
|
||||
"tsunami": "Tsunami",
|
||||
"ultimate comics": "Ultimate Comics",
|
||||
"ultimate marvel": "Ultimate Marvel",
|
||||
"vital publications, inc.": "Vital Publications, Inc."
|
||||
},
|
||||
|
||||
"DC Comics":{
|
||||
"dc_comics": "",
|
||||
"dc": "",
|
||||
"dccomics": "",
|
||||
"!mpact comics": "Impact Comics",
|
||||
"all star dc": "All-Star",
|
||||
"all star": "All-Star",
|
||||
"all-star dc": "All-Star",
|
||||
"all-star": "All-Star",
|
||||
"america's best comics": "America's Best Comics",
|
||||
"black label": "DC Black Label",
|
||||
"cliffhanger": "Cliffhanger",
|
||||
"cmx manga": "CMX Manga",
|
||||
"dc black label": "DC Black Label",
|
||||
"dc focus": "DC Focus",
|
||||
"dc ink": "DC Ink",
|
||||
"dc zoom": "DC Zoom",
|
||||
"earth m": "Earth M",
|
||||
"earth one": "Earth One",
|
||||
"earth-m": "Earth M",
|
||||
"elseworlds": "Elseworlds",
|
||||
"eo": "Earth One",
|
||||
"first wave": "First Wave",
|
||||
"focus": "DC Focus",
|
||||
"helix": "Helix",
|
||||
"homage comics": "Homage Comics",
|
||||
"impact comics": "Impact Comics",
|
||||
"impact! comics": "Impact Comics",
|
||||
"johnny dc": "Johnny DC",
|
||||
"mad": "Mad",
|
||||
"minx": "Minx",
|
||||
"paradox press": "Paradox Press",
|
||||
"piranha press": "Piranha Press",
|
||||
"sandman universe": "Sandman Universe",
|
||||
"tangent comics": "Tangent Comics",
|
||||
"tsr": "TSR",
|
||||
"vertigo": "Vertigo",
|
||||
"wildstorm productions": "WildStorm Productions",
|
||||
"wildstorm signature": "WildStorm Productions",
|
||||
"wildstorm": "WildStorm Productions",
|
||||
"wonder comics": "Wonder Comics",
|
||||
"young animal": "Young Animal",
|
||||
"zuda comics": "Zuda Comics",
|
||||
"zuda": "Zuda Comics"
|
||||
},
|
||||
|
||||
"Dark Horse Comics":{
|
||||
"berger books": "Berger Books",
|
||||
"comics' greatest world": "Dark Horse Heroes",
|
||||
"dark horse digital": "Dark Horse Digital",
|
||||
"dark horse heroes": "Dark Horse Heroes",
|
||||
"dark horse manga": "Dark Horse Manga",
|
||||
"dh deluxe": "DH Deluxe",
|
||||
"dh press": "DH Press",
|
||||
"kitchen sink books": "Kitchen Sink Books",
|
||||
"legend": "Legend",
|
||||
"m press": "M Press",
|
||||
"maverick": "Maverick"
|
||||
},
|
||||
|
||||
"Archie Comics":{
|
||||
"archie action": "Archie Action",
|
||||
"archie adventure Series": "Archie Adventure Series",
|
||||
"archie horror": "Archie Horror",
|
||||
"dark circle Comics": "Dark Circle Comics",
|
||||
"dark circle": "Dark Circle Comics",
|
||||
"mighty comics Group": "Mighty Comics Group",
|
||||
"radio comics": "Mighty Comics Group",
|
||||
"red circle Comics": "Dark Circle Comics",
|
||||
"red circle": "Dark Circle Comics"
|
||||
}
|
||||
}
|
@ -339,6 +339,24 @@ class GenericMetadata:
|
||||
|
||||
return outstr
|
||||
|
||||
def fix_publisher(self) -> None:
|
||||
if self.publisher is None:
|
||||
return
|
||||
if self.imprint is None:
|
||||
self.imprint = ""
|
||||
|
||||
imprint, publisher = utils.get_publisher(self.publisher)
|
||||
|
||||
self.publisher = publisher
|
||||
|
||||
if self.imprint.lower() in publisher.lower():
|
||||
self.imprint = None
|
||||
|
||||
if self.imprint is None or self.imprint == "":
|
||||
self.imprint = imprint
|
||||
elif self.imprint.lower() in imprint.lower():
|
||||
self.imprint = imprint
|
||||
|
||||
|
||||
md_test = GenericMetadata()
|
||||
|
||||
|
@ -14,9 +14,11 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import json
|
||||
import locale
|
||||
import logging
|
||||
import os
|
||||
import pathlib
|
||||
import platform
|
||||
import re
|
||||
import unicodedata
|
||||
@ -221,3 +223,56 @@ def get_language(string: Optional[str]) -> Optional[str]:
|
||||
except:
|
||||
return None
|
||||
return lang
|
||||
|
||||
|
||||
def get_publisher(publisher: str) -> tuple[str, str]:
|
||||
if publisher is None:
|
||||
return ("", "")
|
||||
imprint = ""
|
||||
|
||||
for pub in publishers.values():
|
||||
imprint, publisher, ok = pub[publisher]
|
||||
if ok:
|
||||
break
|
||||
|
||||
return (imprint, publisher)
|
||||
|
||||
|
||||
def update_publishers(new_publishers: dict[str, dict[str, str]]) -> None:
|
||||
for publisher in new_publishers:
|
||||
if publisher in publishers:
|
||||
publishers[publisher].update(new_publishers[publisher])
|
||||
else:
|
||||
publishers[publisher] = ImprintDict(publisher, new_publishers[publisher])
|
||||
|
||||
|
||||
class ImprintDict(dict):
|
||||
"""
|
||||
ImprintDict takes a publisher and a dict or mapping of lowercased
|
||||
imprint names to the proper imprint name. Retreiving a value from an
|
||||
ImprintDict returns a tuple of (imprint, publisher, keyExists).
|
||||
if the key does not exist the key is returned as the publisher unchanged
|
||||
"""
|
||||
|
||||
def __init__(self, publisher, mapping=(), **kwargs):
|
||||
super().__init__(mapping, **kwargs)
|
||||
self.publisher = publisher
|
||||
|
||||
def __missing__(self, key: str) -> None:
|
||||
return None
|
||||
|
||||
def __getitem__(self, k: str) -> tuple[str, str, bool]:
|
||||
item = super().__getitem__(k.casefold())
|
||||
if k.casefold() == self.publisher.casefold():
|
||||
return ("", self.publisher, True)
|
||||
if item is None:
|
||||
return ("", k, False)
|
||||
else:
|
||||
return (item, self.publisher, True)
|
||||
|
||||
|
||||
publishers: dict[str, ImprintDict] = {}
|
||||
|
||||
|
||||
def load_publishers() -> None:
|
||||
update_publishers(json.loads((pathlib.Path(__file__).parent / "data" / "publishers.json").read_text("utf-8")))
|
||||
|
@ -43,6 +43,7 @@ class AutoTagStartWindow(QtWidgets.QDialog):
|
||||
self.cbxIgnoreLeadingDigitsInFilename.setCheckState(QtCore.Qt.CheckState.Unchecked)
|
||||
self.cbxRemoveAfterSuccess.setCheckState(QtCore.Qt.CheckState.Unchecked)
|
||||
self.cbxSpecifySearchString.setCheckState(QtCore.Qt.CheckState.Unchecked)
|
||||
self.cbxAutoImprint.setCheckState(QtCore.Qt.CheckState.Unchecked)
|
||||
self.leNameLengthMatchTolerance.setText(str(self.settings.id_length_delta_thresh))
|
||||
self.leSearchString.setEnabled(False)
|
||||
|
||||
@ -58,6 +59,8 @@ class AutoTagStartWindow(QtWidgets.QDialog):
|
||||
self.cbxRemoveAfterSuccess.setCheckState(QtCore.Qt.CheckState.Checked)
|
||||
if self.settings.wait_and_retry_on_rate_limit:
|
||||
self.cbxWaitForRateLimit.setCheckState(QtCore.Qt.CheckState.Checked)
|
||||
if self.settings.auto_imprint:
|
||||
self.cbxAutoImprint.setCheckState(QtCore.Qt.CheckState.Checked)
|
||||
|
||||
nlmt_tip = """ <html>The <b>Name Length Match Tolerance</b> is for eliminating automatic
|
||||
search matches that are too long compared to your series name search. The higher
|
||||
|
@ -109,6 +109,10 @@ def display_match_set_for_choice(
|
||||
)
|
||||
cv_md = actual_issue_data_fetch(match_set.matches[int(i) - 1], settings, opts)
|
||||
md.overlay(cv_md)
|
||||
|
||||
if opts.auto_imprint:
|
||||
md.fix_publisher()
|
||||
|
||||
actual_metadata_save(ca, opts, md)
|
||||
|
||||
|
||||
@ -425,6 +429,9 @@ def process_file_cli(
|
||||
|
||||
md.overlay(cv_md)
|
||||
|
||||
if opts.auto_imprint:
|
||||
md.fix_publisher()
|
||||
|
||||
# ok, done building our metadata. time to save
|
||||
if not actual_metadata_save(ca, opts, md):
|
||||
match_results.write_failures.append(str(ca.path.absolute()))
|
||||
|
@ -14,6 +14,7 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import json
|
||||
import logging
|
||||
import logging.handlers
|
||||
import os
|
||||
@ -27,6 +28,7 @@ from typing import Optional
|
||||
|
||||
import pkg_resources
|
||||
|
||||
from comicapi import utils
|
||||
from comictaggerlib import cli
|
||||
from comictaggerlib.comicvinetalker import ComicVineTalker
|
||||
from comictaggerlib.ctversion import version
|
||||
@ -94,6 +96,15 @@ def rotate(handler: logging.handlers.RotatingFileHandler, filename: pathlib.Path
|
||||
handler.doRollover()
|
||||
|
||||
|
||||
def update_publishers() -> None:
|
||||
json_file = ComicTaggerSettings.get_settings_folder() / "publishers.json"
|
||||
if json_file.exists():
|
||||
try:
|
||||
utils.update_publishers(json.loads(json_file.read_text("utf-8")))
|
||||
except Exception:
|
||||
logger.exception("Failed to load publishers from %s", json_file)
|
||||
|
||||
|
||||
def ctmain() -> None:
|
||||
opts = Options()
|
||||
opts.parse_cmd_line_args()
|
||||
@ -141,6 +152,9 @@ def ctmain() -> None:
|
||||
for pkg in sorted(pkg_resources.working_set, key=lambda x: x.project_name):
|
||||
logger.debug("%s\t%s", pkg.project_name, pkg.version)
|
||||
|
||||
utils.load_publishers()
|
||||
update_publishers()
|
||||
|
||||
if not qt_available and not opts.no_gui:
|
||||
opts.no_gui = True
|
||||
print("PyQt5 is not available. ComicTagger is limited to command-line mode.")
|
||||
|
@ -77,6 +77,10 @@ If no options are given, {0} will run in windowed mode.
|
||||
the example above. Some names that can be
|
||||
used: series, issue, issueCount, year,
|
||||
publisher, title
|
||||
-a, --auto-imprint Enables the auto imprint functionality.
|
||||
e.g. if the publisher is set to 'vertigo' it
|
||||
will be updated to 'DC Comics' and the imprint
|
||||
property will be set to 'Vertigo'.
|
||||
-r, --rename Rename the file based on specified tag style.
|
||||
--noabort Don't abort save operation when online match
|
||||
is of low confidence.
|
||||
@ -113,6 +117,7 @@ For more help visit the wiki at: https://github.com/comictagger/comictagger/wiki
|
||||
self.verbose = False
|
||||
self.terse = False
|
||||
self.metadata = GenericMetadata()
|
||||
self.auto_imprint = False
|
||||
self.print_tags = False
|
||||
self.copy_tags = False
|
||||
self.delete_tags = False
|
||||
@ -310,6 +315,8 @@ For more help visit the wiki at: https://github.com/comictagger/comictagger/wiki
|
||||
self.delete_tags = True
|
||||
if o in ("-i", "--interactive"):
|
||||
self.interactive = True
|
||||
if o in ("-a", "--auto-imprint"):
|
||||
self.auto_imprint = True
|
||||
if o in ("-c", "--copy"):
|
||||
self.copy_tags = True
|
||||
|
||||
|
@ -97,6 +97,7 @@ class ComicTaggerSettings:
|
||||
self.clear_form_before_populating_from_cv = False
|
||||
self.remove_html_tables = False
|
||||
self.cv_api_key = ""
|
||||
self.auto_imprint = False
|
||||
|
||||
self.sort_series_by_year = True
|
||||
self.exact_series_matches_first = True
|
||||
@ -308,6 +309,8 @@ class ComicTaggerSettings:
|
||||
)
|
||||
if self.config.has_option("autotag", "wait_and_retry_on_rate_limit"):
|
||||
self.wait_and_retry_on_rate_limit = self.config.getboolean("autotag", "wait_and_retry_on_rate_limit")
|
||||
if self.config.has_option("autotag", "auto_imprint"):
|
||||
self.auto_imprint = self.config.getboolean("autotag", "auto_imprint")
|
||||
|
||||
@no_type_check
|
||||
def save(self) -> None:
|
||||
@ -406,6 +409,7 @@ class ComicTaggerSettings:
|
||||
self.config.set("autotag", "ignore_leading_numbers_in_filename", self.ignore_leading_numbers_in_filename)
|
||||
self.config.set("autotag", "remove_archive_after_successful_match", self.remove_archive_after_successful_match)
|
||||
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)
|
||||
|
||||
with open(self.settings_file, "w", encoding="utf-8") as configfile:
|
||||
self.config.write(configfile)
|
||||
|
@ -360,6 +360,8 @@ Have fun!
|
||||
self.actionSearchOnline.setStatusTip("Search online for tags")
|
||||
self.actionSearchOnline.triggered.connect(self.query_online)
|
||||
|
||||
self.actionAutoImprint.triggered.connect(self.auto_imprint)
|
||||
|
||||
self.actionAutoIdentify.setShortcut("Ctrl+I")
|
||||
self.actionAutoIdentify.triggered.connect(self.auto_identify_search)
|
||||
|
||||
@ -400,6 +402,7 @@ Have fun!
|
||||
self.actionSearchOnline.setIcon(QtGui.QIcon(ComicTaggerSettings.get_graphic("search.png")))
|
||||
self.actionAutoIdentify.setIcon(QtGui.QIcon(ComicTaggerSettings.get_graphic("auto.png")))
|
||||
self.actionAutoTag.setIcon(QtGui.QIcon(ComicTaggerSettings.get_graphic("autotag.png")))
|
||||
self.actionAutoImprint.setIcon(QtGui.QIcon(ComicTaggerSettings.get_graphic("autotag.png")))
|
||||
self.actionClearEntryForm.setIcon(QtGui.QIcon(ComicTaggerSettings.get_graphic("clear.png")))
|
||||
self.actionPageBrowser.setIcon(QtGui.QIcon(ComicTaggerSettings.get_graphic("browse.png")))
|
||||
|
||||
@ -411,6 +414,7 @@ Have fun!
|
||||
self.toolBar.addAction(self.actionAutoTag)
|
||||
self.toolBar.addAction(self.actionClearEntryForm)
|
||||
self.toolBar.addAction(self.actionPageBrowser)
|
||||
self.toolBar.addAction(self.actionAutoImprint)
|
||||
|
||||
def repackage_archive(self) -> None:
|
||||
ca_list = self.fileSelectionList.get_selected_archive_list()
|
||||
@ -1748,6 +1752,9 @@ Please choose options below, and select OK.
|
||||
if cv_md is not None:
|
||||
md.overlay(cv_md)
|
||||
|
||||
if self.settings.auto_imprint:
|
||||
md.fix_publisher()
|
||||
|
||||
if not ca.write_metadata(md, self.save_data_style):
|
||||
match_results.write_failures.append(str(ca.path.absolute()))
|
||||
self.auto_tag_log("Save failed ;-(\n")
|
||||
@ -2106,3 +2113,8 @@ Please choose options below, and select OK to Auto-Tag.
|
||||
QtCore.QCoreApplication.processEvents()
|
||||
self.setWindowFlags(flags)
|
||||
self.show()
|
||||
|
||||
def auto_imprint(self):
|
||||
self.form_to_metadata()
|
||||
self.metadata.fix_publisher()
|
||||
self.metadata_to_form()
|
||||
|
@ -44,7 +44,7 @@
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<layout class="QGridLayout" name="gridLayout">
|
||||
<item row="6" column="0">
|
||||
<item row="7" column="0">
|
||||
<widget class="QCheckBox" name="cbxSpecifySearchString">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Minimum" vsizetype="Fixed">
|
||||
@ -129,6 +129,16 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="6" column="0">
|
||||
<widget class="QCheckBox" name="cbxAutoImprint">
|
||||
<property name="toolTip">
|
||||
<string>Checks the publisher against a list of imprints.</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Auto Imprint</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="10" column="0">
|
||||
<widget class="QLineEdit" name="leNameLengthMatchTolerance">
|
||||
<property name="sizePolicy">
|
||||
@ -145,7 +155,7 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="7" column="0">
|
||||
<item row="8" column="0">
|
||||
<widget class="QLineEdit" name="leSearchString">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
|
||||
@ -155,7 +165,7 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="8" column="0">
|
||||
<item row="9" column="0">
|
||||
<widget class="QLabel" name="label_3">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
|
||||
|
@ -1234,6 +1234,7 @@
|
||||
<addaction name="actionParse_Filename"/>
|
||||
<addaction name="actionSearchOnline"/>
|
||||
<addaction name="actionAutoIdentify"/>
|
||||
<addaction name="actionAutoImprint"/>
|
||||
<addaction name="separator"/>
|
||||
<addaction name="actionApplyCBLTransform"/>
|
||||
<addaction name="actionReCalcPageDims"/>
|
||||
@ -1437,6 +1438,14 @@
|
||||
<string>Search online for tags,auto-identify best match, and save to archive</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionAutoImprint">
|
||||
<property name="text">
|
||||
<string>Auto Imprint</string>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Normalize the publisher and map imprints to their parent publisher (e.g. Vertigo is an imprint of DC Comics)</string>
|
||||
</property>
|
||||
</action>
|
||||
</widget>
|
||||
<layoutdefault spacing="6" margin="11"/>
|
||||
<resources/>
|
||||
|
Loading…
Reference in New Issue
Block a user