Code cleanup

Remove no longer used google scripts
Remove convenience files from comicataggerlib and import comicapi directly
Add type-hints to facilitate auto-complete tools
Make PyQt5 code more compatible with PyQt6

Implement automatic tooling
isort and black for code formatting
Line length has been set to 120
flake8 for code standards with exceptions:
E203 - Whitespace before ':'  - format compatiblity with black
E501 - Line too long          - flake8 line limit cannot be set
E722 - Do not use bare except - fixing bare except statements is a
                                lot of overhead and there are already
                                many in the codebase

These changes, along with some manual fixes creates much more readable code.
See examples below:

diff --git a/comicapi/comet.py b/comicapi/comet.py
index d1741c5..52dc195 100644
--- a/comicapi/comet.py
+++ b/comicapi/comet.py
@@ -166,7 +166,2 @@ class CoMet:

-            if credit['role'].lower() in set(self.editor_synonyms):
-                ET.SubElement(
-                    root,
-                    'editor').text = "{0}".format(
-                    credit['person'])

@@ -174,2 +169,4 @@ class CoMet:
         self.indent(root)
+            if credit["role"].lower() in set(self.editor_synonyms):
+                ET.SubElement(root, "editor").text = str(credit["person"])

diff --git a/comictaggerlib/autotagmatchwindow.py b/comictaggerlib/autotagmatchwindow.py
index 4338176..9219f01 100644
--- a/comictaggerlib/autotagmatchwindow.py
+++ b/comictaggerlib/autotagmatchwindow.py
@@ -63,4 +63,3 @@ class AutoTagMatchWindow(QtWidgets.QDialog):
             self.skipButton, QtWidgets.QDialogButtonBox.ActionRole)
-        self.buttonBox.button(QtWidgets.QDialogButtonBox.Ok).setText(
-            "Accept and Write Tags")
+        self.buttonBox.button(QtWidgets.QDialogButtonBox.StandardButton.Ok).setText("Accept and Write Tags")

diff --git a/comictaggerlib/cli.py b/comictaggerlib/cli.py
index 688907d..dbd0c2e 100644
--- a/comictaggerlib/cli.py
+++ b/comictaggerlib/cli.py
@@ -293,7 +293,3 @@ def process_file_cli(filename, opts, settings, match_results):
                 if opts.raw:
-                    print((
-                        "{0}".format(
-                            str(
-                                ca.readRawCIX(),
-                                errors='ignore'))))
+                    print(ca.read_raw_cix())
                 else:
This commit is contained in:
Timmy Welch 2022-04-01 16:50:46 -07:00
parent 6f4de04a00
commit e96d1d5c97
9 changed files with 1006 additions and 1517 deletions

View File

@ -1 +1 @@
__author__ = 'dromanin' __author__ = "dromanin"

View File

@ -15,175 +15,121 @@
# limitations under the License. # limitations under the License.
import xml.etree.ElementTree as ET import xml.etree.ElementTree as ET
#from datetime import datetime
#from pprint import pprint
#import zipfile
from .genericmetadata import GenericMetadata from comicapi import utils
from . import utils from comicapi.genericmetadata import GenericMetadata
class CoMet: class CoMet:
writer_synonyms = ['writer', 'plotter', 'scripter'] writer_synonyms = ["writer", "plotter", "scripter"]
penciller_synonyms = ['artist', 'penciller', 'penciler', 'breakdowns'] penciller_synonyms = ["artist", "penciller", "penciler", "breakdowns"]
inker_synonyms = ['inker', 'artist', 'finishes'] inker_synonyms = ["inker", "artist", "finishes"]
colorist_synonyms = ['colorist', 'colourist', 'colorer', 'colourer'] colorist_synonyms = ["colorist", "colourist", "colorer", "colourer"]
letterer_synonyms = ['letterer'] letterer_synonyms = ["letterer"]
cover_synonyms = ['cover', 'covers', 'coverartist', 'cover artist'] cover_synonyms = ["cover", "covers", "coverartist", "cover artist"]
editor_synonyms = ['editor'] editor_synonyms = ["editor"]
def metadataFromString(self, string): def metadata_from_string(self, string):
tree = ET.ElementTree(ET.fromstring(string)) tree = ET.ElementTree(ET.fromstring(string))
return self.convertXMLToMetadata(tree) return self.convert_xml_to_metadata(tree)
def stringFromMetadata(self, metadata): def string_from_metadata(self, metadata):
header = '<?xml version="1.0" encoding="UTF-8"?>\n' header = '<?xml version="1.0" encoding="UTF-8"?>\n'
tree = self.convertMetadataToXML(self, metadata) tree = self.convert_metadata_to_xml(metadata)
return header + ET.tostring(tree.getroot()) return header + ET.tostring(tree.getroot())
def indent(self, elem, level=0): def convert_metadata_to_xml(self, metadata):
# for making the XML output readable
i = "\n" + level * " "
if len(elem):
if not elem.text or not elem.text.strip():
elem.text = i + " "
if not elem.tail or not elem.tail.strip():
elem.tail = i
for elem in elem:
self.indent(elem, level + 1)
if not elem.tail or not elem.tail.strip():
elem.tail = i
else:
if level and (not elem.tail or not elem.tail.strip()):
elem.tail = i
def convertMetadataToXML(self, filename, metadata):
# shorthand for the metadata # shorthand for the metadata
md = metadata md = metadata
# build a tree structure # build a tree structure
root = ET.Element("comet") root = ET.Element("comet")
root.attrib['xmlns:comet'] = "http://www.denvog.com/comet/" root.attrib["xmlns:comet"] = "http://www.denvog.com/comet/"
root.attrib['xmlns:xsi'] = "http://www.w3.org/2001/XMLSchema-instance" root.attrib["xmlns:xsi"] = "http://www.w3.org/2001/XMLSchema-instance"
root.attrib[ root.attrib["xsi:schemaLocation"] = "http://www.denvog.com http://www.denvog.com/comet/comet.xsd"
'xsi:schemaLocation'] = "http://www.denvog.com http://www.denvog.com/comet/comet.xsd"
# helper func # helper func
def assign(comet_entry, md_entry): def assign(comet_entry, md_entry):
if md_entry is not None: if md_entry is not None:
ET.SubElement(root, comet_entry).text = "{0}".format(md_entry) ET.SubElement(root, comet_entry).text = str(md_entry)
# title is manditory # title is manditory
if md.title is None: if md.title is None:
md.title = "" md.title = ""
assign('title', md.title) assign("title", md.title)
assign('series', md.series) assign("series", md.series)
assign('issue', md.issue) # must be int?? assign("issue", md.issue) # must be int??
assign('volume', md.volume) assign("volume", md.volume)
assign('description', md.comments) assign("description", md.comments)
assign('publisher', md.publisher) assign("publisher", md.publisher)
assign('pages', md.pageCount) assign("pages", md.page_count)
assign('format', md.format) assign("format", md.format)
assign('language', md.language) assign("language", md.language)
assign('rating', md.maturityRating) assign("rating", md.maturity_rating)
assign('price', md.price) assign("price", md.price)
assign('isVersionOf', md.isVersionOf) assign("isVersionOf", md.is_version_of)
assign('rights', md.rights) assign("rights", md.rights)
assign('identifier', md.identifier) assign("identifier", md.identifier)
assign('lastMark', md.lastMark) assign("lastMark", md.last_mark)
assign('genre', md.genre) # TODO repeatable assign("genre", md.genre) # TODO repeatable
if md.characters is not None: if md.characters is not None:
char_list = [c.strip() for c in md.characters.split(',')] char_list = [c.strip() for c in md.characters.split(",")]
for c in char_list: for c in char_list:
assign('character', c) assign("character", c)
if md.manga is not None and md.manga == "YesAndRightToLeft": if md.manga is not None and md.manga == "YesAndRightToLeft":
assign('readingDirection', "rtl") assign("readingDirection", "rtl")
date_str = ""
if md.year is not None: if md.year is not None:
date_str = str(md.year).zfill(4) date_str = str(md.year).zfill(4)
if md.month is not None: if md.month is not None:
date_str += "-" + str(md.month).zfill(2) date_str += "-" + str(md.month).zfill(2)
assign('date', date_str) assign("date", date_str)
assign('coverImage', md.coverImage) assign("coverImage", md.cover_image)
# need to specially process the credits, since they are structured
# differently than CIX
credit_writer_list = list()
credit_penciller_list = list()
credit_inker_list = list()
credit_colorist_list = list()
credit_letterer_list = list()
credit_cover_list = list()
credit_editor_list = list()
# loop thru credits, and build a list for each role that CoMet supports # loop thru credits, and build a list for each role that CoMet supports
for credit in metadata.credits: for credit in metadata.credits:
if credit['role'].lower() in set(self.writer_synonyms): if credit["role"].lower() in set(self.writer_synonyms):
ET.SubElement( ET.SubElement(root, "writer").text = str(credit["person"])
root,
'writer').text = "{0}".format(
credit['person'])
if credit['role'].lower() in set(self.penciller_synonyms): if credit["role"].lower() in set(self.penciller_synonyms):
ET.SubElement( ET.SubElement(root, "penciller").text = str(credit["person"])
root,
'penciller').text = "{0}".format(
credit['person'])
if credit['role'].lower() in set(self.inker_synonyms): if credit["role"].lower() in set(self.inker_synonyms):
ET.SubElement( ET.SubElement(root, "inker").text = str(credit["person"])
root,
'inker').text = "{0}".format(
credit['person'])
if credit['role'].lower() in set(self.colorist_synonyms): if credit["role"].lower() in set(self.colorist_synonyms):
ET.SubElement( ET.SubElement(root, "colorist").text = str(credit["person"])
root,
'colorist').text = "{0}".format(
credit['person'])
if credit['role'].lower() in set(self.letterer_synonyms): if credit["role"].lower() in set(self.letterer_synonyms):
ET.SubElement( ET.SubElement(root, "letterer").text = str(credit["person"])
root,
'letterer').text = "{0}".format(
credit['person'])
if credit['role'].lower() in set(self.cover_synonyms): if credit["role"].lower() in set(self.cover_synonyms):
ET.SubElement( ET.SubElement(root, "coverDesigner").text = str(credit["person"])
root,
'coverDesigner').text = "{0}".format(
credit['person'])
if credit['role'].lower() in set(self.editor_synonyms): if credit["role"].lower() in set(self.editor_synonyms):
ET.SubElement( ET.SubElement(root, "editor").text = str(credit["person"])
root,
'editor').text = "{0}".format(
credit['person'])
# self pretty-print utils.indent(root)
self.indent(root)
# wrap it in an ElementTree instance, and save as XML # wrap it in an ElementTree instance, and save as XML
tree = ET.ElementTree(root) tree = ET.ElementTree(root)
return tree return tree
def convertXMLToMetadata(self, tree): def convert_xml_to_metadata(self, tree):
root = tree.getroot() root = tree.getroot()
if root.tag != 'comet': if root.tag != "comet":
raise 1 raise "1"
return None
metadata = GenericMetadata() metadata = GenericMetadata()
md = metadata md = metadata
@ -193,84 +139,85 @@ class CoMet:
node = root.find(tag) node = root.find(tag)
if node is not None: if node is not None:
return node.text return node.text
else:
return None return None
md.series = xlate('series') md.series = xlate("series")
md.title = xlate('title') md.title = xlate("title")
md.issue = xlate('issue') md.issue = xlate("issue")
md.volume = xlate('volume') md.volume = xlate("volume")
md.comments = xlate('description') md.comments = xlate("description")
md.publisher = xlate('publisher') md.publisher = xlate("publisher")
md.language = xlate('language') md.language = xlate("language")
md.format = xlate('format') md.format = xlate("format")
md.pageCount = xlate('pages') md.page_count = xlate("pages")
md.maturityRating = xlate('rating') md.maturity_rating = xlate("rating")
md.price = xlate('price') md.price = xlate("price")
md.isVersionOf = xlate('isVersionOf') md.is_version_of = xlate("isVersionOf")
md.rights = xlate('rights') md.rights = xlate("rights")
md.identifier = xlate('identifier') md.identifier = xlate("identifier")
md.lastMark = xlate('lastMark') md.last_mark = xlate("lastMark")
md.genre = xlate('genre') # TODO - repeatable field md.genre = xlate("genre") # TODO - repeatable field
date = xlate('date') date = xlate("date")
if date is not None: if date is not None:
parts = date.split('-') parts = date.split("-")
if len(parts) > 0: if len(parts) > 0:
md.year = parts[0] md.year = parts[0]
if len(parts) > 1: if len(parts) > 1:
md.month = parts[1] md.month = parts[1]
md.coverImage = xlate('coverImage') md.cover_image = xlate("coverImage")
readingDirection = xlate('readingDirection') reading_direction = xlate("readingDirection")
if readingDirection is not None and readingDirection == "rtl": if reading_direction is not None and reading_direction == "rtl":
md.manga = "YesAndRightToLeft" md.manga = "YesAndRightToLeft"
# loop for character tags # loop for character tags
char_list = [] char_list = []
for n in root: for n in root:
if n.tag == 'character': if n.tag == "character":
char_list.append(n.text.strip()) char_list.append(n.text.strip())
md.characters = utils.listToString(char_list) md.characters = utils.list_to_string(char_list)
# Now extract the credit info # Now extract the credit info
for n in root: for n in root:
if (n.tag == 'writer' or if any(
n.tag == 'penciller' or [
n.tag == 'inker' or n.tag == "writer",
n.tag == 'colorist' or n.tag == "penciller",
n.tag == 'letterer' or n.tag == "inker",
n.tag == 'editor' n.tag == "colorist",
n.tag == "letterer",
n.tag == "editor",
]
): ):
metadata.addCredit(n.text.strip(), n.tag.title()) metadata.add_credit(n.text.strip(), n.tag.title())
if n.tag == 'coverDesigner': if n.tag == "coverDesigner":
metadata.addCredit(n.text.strip(), "Cover") metadata.add_credit(n.text.strip(), "Cover")
metadata.isEmpty = False metadata.is_empty = False
return metadata return metadata
# verify that the string actually contains CoMet data in XML format # verify that the string actually contains CoMet data in XML format
def validateString(self, string): def validate_string(self, string):
try: try:
tree = ET.ElementTree(ET.fromstring(string)) tree = ET.ElementTree(ET.fromstring(string))
root = tree.getroot() root = tree.getroot()
if root.tag != 'comet': if root.tag != "comet":
raise Exception raise Exception
except: except:
return False return False
return True return True
def writeToExternalFile(self, filename, metadata): def write_to_external_file(self, filename, metadata):
tree = self.convertMetadataToXML(self, metadata) tree = self.convert_metadata_to_xml(metadata)
# ET.dump(tree) tree.write(filename, encoding="utf-8")
tree.write(filename, encoding='utf-8')
def readFromExternalFile(self, filename): def read_from_external_file(self, filename):
tree = ET.parse(filename) tree = ET.parse(filename)
return self.convertXMLToMetadata(tree) return self.convert_xml_to_metadata(tree)

File diff suppressed because it is too large Load Diff

View File

@ -15,42 +15,39 @@
# limitations under the License. # limitations under the License.
import json import json
from collections import defaultdict
from datetime import datetime from datetime import datetime
#import zipfile
from .genericmetadata import GenericMetadata from comicapi import utils
from . import utils from comicapi.genericmetadata import GenericMetadata
#import ctversion
class ComicBookInfo: class ComicBookInfo:
def metadataFromString(self, string): def metadata_from_string(self, string):
class Default(dict):
def __missing__(self, key): cbi_container = json.loads(str(string, "utf-8"))
return None
cbi_container = json.loads(str(string, 'utf-8'))
metadata = GenericMetadata() metadata = GenericMetadata()
cbi = Default(cbi_container['ComicBookInfo/1.0']) cbi = defaultdict(lambda: None, cbi_container["ComicBookInfo/1.0"])
metadata.series = utils.xlate(cbi['series']) metadata.series = utils.xlate(cbi["series"])
metadata.title = utils.xlate(cbi['title']) metadata.title = utils.xlate(cbi["title"])
metadata.issue = utils.xlate(cbi['issue']) metadata.issue = utils.xlate(cbi["issue"])
metadata.publisher = utils.xlate(cbi['publisher']) metadata.publisher = utils.xlate(cbi["publisher"])
metadata.month = utils.xlate(cbi['publicationMonth'], True) metadata.month = utils.xlate(cbi["publicationMonth"], True)
metadata.year = utils.xlate(cbi['publicationYear'], True) metadata.year = utils.xlate(cbi["publicationYear"], True)
metadata.issueCount = utils.xlate(cbi['numberOfIssues'], True) metadata.issue_count = utils.xlate(cbi["numberOfIssues"], True)
metadata.comments = utils.xlate(cbi['comments']) metadata.comments = utils.xlate(cbi["comments"])
metadata.genre = utils.xlate(cbi['genre']) metadata.genre = utils.xlate(cbi["genre"])
metadata.volume = utils.xlate(cbi['volume'], True) metadata.volume = utils.xlate(cbi["volume"], True)
metadata.volumeCount = utils.xlate(cbi['numberOfVolumes'], True) metadata.volume_count = utils.xlate(cbi["numberOfVolumes"], True)
metadata.language = utils.xlate(cbi['language']) metadata.language = utils.xlate(cbi["language"])
metadata.country = utils.xlate(cbi['country']) metadata.country = utils.xlate(cbi["country"])
metadata.criticalRating = utils.xlate(cbi['rating']) metadata.critical_rating = utils.xlate(cbi["rating"])
metadata.credits = cbi['credits'] metadata.credits = cbi["credits"]
metadata.tags = cbi['tags'] metadata.tags = cbi["tags"]
# make sure credits and tags are at least empty lists and not None # make sure credits and tags are at least empty lists and not None
if metadata.credits is None: if metadata.credits is None:
@ -58,26 +55,20 @@ class ComicBookInfo:
if metadata.tags is None: if metadata.tags is None:
metadata.tags = [] metadata.tags = []
# need to massage the language string to be ISO # need the language string to be ISO
if metadata.language is not None: if metadata.language is not None:
# reverse look-up metadata.language = utils.get_language(metadata.language)
pattern = metadata.language
metadata.language = None
for key in utils.getLanguageDict():
if utils.getLanguageDict()[key] == pattern.encode('utf-8'):
metadata.language = key
break
metadata.isEmpty = False metadata.is_empty = False
return metadata return metadata
def stringFromMetadata(self, metadata): def string_from_metadata(self, metadata):
cbi_container = self.createJSONDictionary(metadata) cbi_container = self.create_json_dictionary(metadata)
return json.dumps(cbi_container) return json.dumps(cbi_container)
def validateString(self, string): def validate_string(self, string):
"""Verify that the string actually contains CBI data in JSON format""" """Verify that the string actually contains CBI data in JSON format"""
try: try:
@ -85,44 +76,45 @@ class ComicBookInfo:
except: except:
return False return False
return ('ComicBookInfo/1.0' in cbi_container) return "ComicBookInfo/1.0" in cbi_container
def createJSONDictionary(self, metadata): def create_json_dictionary(self, metadata):
"""Create the dictionary that we will convert to JSON text""" """Create the dictionary that we will convert to JSON text"""
cbi = dict() cbi = {}
cbi_container = {'appID': 'ComicTagger/' + '1.0.0', # ctversion.version, cbi_container = {
'lastModified': str(datetime.now()), "appID": "ComicTagger/" + "1.0.0",
'ComicBookInfo/1.0': cbi} "lastModified": str(datetime.now()),
"ComicBookInfo/1.0": cbi,
} # TODO: ctversion.version,
# helper func # helper func
def assign(cbi_entry, md_entry): def assign(cbi_entry, md_entry):
if md_entry is not None or isinstance(md_entry, str) and md_entry != "": if md_entry is not None or isinstance(md_entry, str) and md_entry != "":
cbi[cbi_entry] = md_entry cbi[cbi_entry] = md_entry
assign('series', utils.xlate(metadata.series)) assign("series", utils.xlate(metadata.series))
assign('title', utils.xlate(metadata.title)) assign("title", utils.xlate(metadata.title))
assign('issue', utils.xlate(metadata.issue)) assign("issue", utils.xlate(metadata.issue))
assign('publisher', utils.xlate(metadata.publisher)) assign("publisher", utils.xlate(metadata.publisher))
assign('publicationMonth', utils.xlate(metadata.month, True)) assign("publicationMonth", utils.xlate(metadata.month, True))
assign('publicationYear', utils.xlate(metadata.year, True)) assign("publicationYear", utils.xlate(metadata.year, True))
assign('numberOfIssues', utils.xlate(metadata.issueCount, True)) assign("numberOfIssues", utils.xlate(metadata.issue_count, True))
assign('comments', utils.xlate(metadata.comments)) assign("comments", utils.xlate(metadata.comments))
assign('genre', utils.xlate(metadata.genre)) assign("genre", utils.xlate(metadata.genre))
assign('volume', utils.xlate(metadata.volume, True)) assign("volume", utils.xlate(metadata.volume, True))
assign('numberOfVolumes', utils.xlate(metadata.volumeCount, True)) assign("numberOfVolumes", utils.xlate(metadata.volume_count, True))
assign('language', utils.xlate(utils.getLanguageFromISO(metadata.language))) assign("language", utils.xlate(utils.get_language_from_iso(metadata.language)))
assign('country', utils.xlate(metadata.country)) assign("country", utils.xlate(metadata.country))
assign('rating', utils.xlate(metadata.criticalRating)) assign("rating", utils.xlate(metadata.critical_rating))
assign('credits', metadata.credits) assign("credits", metadata.credits)
assign('tags', metadata.tags) assign("tags", metadata.tags)
return cbi_container return cbi_container
def writeToExternalFile(self, filename, metadata): def write_to_external_file(self, filename, metadata):
cbi_container = self.createJSONDictionary(metadata) cbi_container = self.create_json_dictionary(metadata)
f = open(filename, 'w') with open(filename, "w") as f:
f.write(json.dumps(cbi_container, indent=4)) f.write(json.dumps(cbi_container, indent=4))
f.close

View File

@ -15,26 +15,23 @@
# limitations under the License. # limitations under the License.
import xml.etree.ElementTree as ET import xml.etree.ElementTree as ET
#from datetime import datetime
#from pprint import pprint
#import zipfile
from .genericmetadata import GenericMetadata from comicapi import utils
from .issuestring import IssueString from comicapi.genericmetadata import GenericMetadata
from . import utils from comicapi.issuestring import IssueString
class ComicInfoXml: class ComicInfoXml:
writer_synonyms = ['writer', 'plotter', 'scripter'] writer_synonyms = ["writer", "plotter", "scripter"]
penciller_synonyms = ['artist', 'penciller', 'penciler', 'breakdowns'] penciller_synonyms = ["artist", "penciller", "penciler", "breakdowns"]
inker_synonyms = ['inker', 'artist', 'finishes'] inker_synonyms = ["inker", "artist", "finishes"]
colorist_synonyms = ['colorist', 'colourist', 'colorer', 'colourer'] colorist_synonyms = ["colorist", "colourist", "colorer", "colourer"]
letterer_synonyms = ['letterer'] letterer_synonyms = ["letterer"]
cover_synonyms = ['cover', 'covers', 'coverartist', 'cover artist'] cover_synonyms = ["cover", "covers", "coverartist", "cover artist"]
editor_synonyms = ['editor'] editor_synonyms = ["editor"]
def getParseableCredits(self): def get_parseable_credits(self):
parsable_credits = [] parsable_credits = []
parsable_credits.extend(self.writer_synonyms) parsable_credits.extend(self.writer_synonyms)
parsable_credits.extend(self.penciller_synonyms) parsable_credits.extend(self.penciller_synonyms)
@ -45,33 +42,17 @@ class ComicInfoXml:
parsable_credits.extend(self.editor_synonyms) parsable_credits.extend(self.editor_synonyms)
return parsable_credits return parsable_credits
def metadataFromString(self, string): def metadata_from_string(self, string):
tree = ET.ElementTree(ET.fromstring(string)) tree = ET.ElementTree(ET.fromstring(string))
return self.convertXMLToMetadata(tree) return self.convert_xml_to_metadata(tree)
def stringFromMetadata(self, metadata, xml=None): def string_from_metadata(self, metadata, xml=None):
tree = self.convertMetadataToXML(self, metadata, xml) tree = self.convert_metadata_to_xml(self, metadata, xml)
tree_str = ET.tostring(tree.getroot(), encoding="utf-8", xml_declaration=True).decode() tree_str = ET.tostring(tree.getroot(), encoding="utf-8", xml_declaration=True).decode()
return tree_str return tree_str
def indent(self, elem, level=0): def convert_metadata_to_xml(self, filename, metadata, xml=None):
# for making the XML output readable
i = "\n" + level * " "
if len(elem):
if not elem.text or not elem.text.strip():
elem.text = i + " "
if not elem.tail or not elem.tail.strip():
elem.tail = i
for elem in elem:
self.indent(elem, level + 1)
if not elem.tail or not elem.tail.strip():
elem.tail = i
else:
if level and (not elem.tail or not elem.tail.strip()):
elem.tail = i
def convertMetadataToXML(self, filename, metadata, xml=None):
# shorthand for the metadata # shorthand for the metadata
md = metadata md = metadata
@ -81,125 +62,123 @@ class ComicInfoXml:
else: else:
# build a tree structure # build a tree structure
root = ET.Element("ComicInfo") root = ET.Element("ComicInfo")
root.attrib['xmlns:xsi'] = "http://www.w3.org/2001/XMLSchema-instance" root.attrib["xmlns:xsi"] = "http://www.w3.org/2001/XMLSchema-instance"
root.attrib['xmlns:xsd'] = "http://www.w3.org/2001/XMLSchema" root.attrib["xmlns:xsd"] = "http://www.w3.org/2001/XMLSchema"
# helper func # helper func
def assign(cix_entry, md_entry): def assign(cix_entry, md_entry):
if md_entry is not None: if md_entry is not None:
et_entry = root.find(cix_entry) et_entry = root.find(cix_entry)
if et_entry is not None: if et_entry is not None:
et_entry.text = "{0}".format(md_entry) et_entry.text = str(md_entry)
else: else:
ET.SubElement(root, cix_entry).text = "{0}".format(md_entry) ET.SubElement(root, cix_entry).text = str(md_entry)
assign('Title', md.title) assign("Title", md.title)
assign('Series', md.series) assign("Series", md.series)
assign('Number', md.issue) assign("Number", md.issue)
assign('Count', md.issueCount) assign("Count", md.issue_count)
assign('Volume', md.volume) assign("Volume", md.volume)
assign('AlternateSeries', md.alternateSeries) assign("AlternateSeries", md.alternate_series)
assign('AlternateNumber', md.alternateNumber) assign("AlternateNumber", md.alternate_number)
assign('StoryArc', md.storyArc) assign("StoryArc", md.story_arc)
assign('SeriesGroup', md.seriesGroup) assign("SeriesGroup", md.series_group)
assign('AlternateCount', md.alternateCount) assign("AlternateCount", md.alternate_count)
assign('Summary', md.comments) assign("Summary", md.comments)
assign('Notes', md.notes) assign("Notes", md.notes)
assign('Year', md.year) assign("Year", md.year)
assign('Month', md.month) assign("Month", md.month)
assign('Day', md.day) assign("Day", md.day)
# need to specially process the credits, since they are structured # need to specially process the credits, since they are structured
# differently than CIX # differently than CIX
credit_writer_list = list() credit_writer_list = []
credit_penciller_list = list() credit_penciller_list = []
credit_inker_list = list() credit_inker_list = []
credit_colorist_list = list() credit_colorist_list = []
credit_letterer_list = list() credit_letterer_list = []
credit_cover_list = list() credit_cover_list = []
credit_editor_list = list() credit_editor_list = []
# first, loop thru credits, and build a list for each role that CIX # first, loop thru credits, and build a list for each role that CIX
# supports # supports
for credit in metadata.credits: for credit in metadata.credits:
if credit['role'].lower() in set(self.writer_synonyms): if credit["role"].lower() in set(self.writer_synonyms):
credit_writer_list.append(credit['person'].replace(",", "")) credit_writer_list.append(credit["person"].replace(",", ""))
if credit['role'].lower() in set(self.penciller_synonyms): if credit["role"].lower() in set(self.penciller_synonyms):
credit_penciller_list.append(credit['person'].replace(",", "")) credit_penciller_list.append(credit["person"].replace(",", ""))
if credit['role'].lower() in set(self.inker_synonyms): if credit["role"].lower() in set(self.inker_synonyms):
credit_inker_list.append(credit['person'].replace(",", "")) credit_inker_list.append(credit["person"].replace(",", ""))
if credit['role'].lower() in set(self.colorist_synonyms): if credit["role"].lower() in set(self.colorist_synonyms):
credit_colorist_list.append(credit['person'].replace(",", "")) credit_colorist_list.append(credit["person"].replace(",", ""))
if credit['role'].lower() in set(self.letterer_synonyms): if credit["role"].lower() in set(self.letterer_synonyms):
credit_letterer_list.append(credit['person'].replace(",", "")) credit_letterer_list.append(credit["person"].replace(",", ""))
if credit['role'].lower() in set(self.cover_synonyms): if credit["role"].lower() in set(self.cover_synonyms):
credit_cover_list.append(credit['person'].replace(",", "")) credit_cover_list.append(credit["person"].replace(",", ""))
if credit['role'].lower() in set(self.editor_synonyms): if credit["role"].lower() in set(self.editor_synonyms):
credit_editor_list.append(credit['person'].replace(",", "")) credit_editor_list.append(credit["person"].replace(",", ""))
# second, convert each list to string, and add to XML struct # second, convert each list to string, and add to XML struct
assign('Writer', utils.listToString(credit_writer_list)) assign("Writer", utils.list_to_string(credit_writer_list))
assign('Penciller', utils.listToString(credit_penciller_list)) assign("Penciller", utils.list_to_string(credit_penciller_list))
assign('Inker', utils.listToString(credit_inker_list)) assign("Inker", utils.list_to_string(credit_inker_list))
assign('Colorist', utils.listToString(credit_colorist_list)) assign("Colorist", utils.list_to_string(credit_colorist_list))
assign('Letterer', utils.listToString(credit_letterer_list)) assign("Letterer", utils.list_to_string(credit_letterer_list))
assign('CoverArtist', utils.listToString(credit_cover_list)) assign("CoverArtist", utils.list_to_string(credit_cover_list))
assign('Editor', utils.listToString(credit_editor_list)) assign("Editor", utils.list_to_string(credit_editor_list))
assign('Publisher', md.publisher) assign("Publisher", md.publisher)
assign('Imprint', md.imprint) assign("Imprint", md.imprint)
assign('Genre', md.genre) assign("Genre", md.genre)
assign('Web', md.webLink) assign("Web", md.web_link)
assign('PageCount', md.pageCount) assign("PageCount", md.page_count)
assign('LanguageISO', md.language) assign("LanguageISO", md.language)
assign('Format', md.format) assign("Format", md.format)
assign('AgeRating', md.maturityRating) assign("AgeRating", md.maturity_rating)
if md.blackAndWhite is not None and md.blackAndWhite: if md.black_and_white is not None and md.black_and_white:
assign('BlackAndWhite', "Yes") ET.SubElement(root, "BlackAndWhite").text = "Yes"
assign('Manga', md.manga) assign("Manga", md.manga)
assign('Characters', md.characters) assign("Characters", md.characters)
assign('Teams', md.teams) assign("Teams", md.teams)
assign('Locations', md.locations) assign("Locations", md.locations)
assign('ScanInformation', md.scanInfo) assign("ScanInformation", md.scan_info)
# loop and add the page entries under pages node # loop and add the page entries under pages node
pages_node = root.find('Pages') pages_node = root.find("Pages")
if pages_node is not None: if pages_node is not None:
pages_node.clear() pages_node.clear()
else: else:
pages_node = ET.SubElement(root, 'Pages') pages_node = ET.SubElement(root, "Pages")
for page_dict in md.pages: for page_dict in md.pages:
page_node = ET.SubElement(pages_node, 'Page') page_node = ET.SubElement(pages_node, "Page")
page_node.attrib = page_dict page_node.attrib = page_dict
# self pretty-print utils.indent(root)
self.indent(root)
# wrap it in an ElementTree instance, and save as XML # wrap it in an ElementTree instance, and save as XML
tree = ET.ElementTree(root) tree = ET.ElementTree(root)
return tree return tree
def convertXMLToMetadata(self, tree): def convert_xml_to_metadata(self, tree):
root = tree.getroot() root = tree.getroot()
if root.tag != 'ComicInfo': if root.tag != "ComicInfo":
raise 1 raise "1"
return None
def get(name): def get(name):
tag = root.find(name) tag = root.find(name)
@ -209,74 +188,75 @@ class ComicInfoXml:
md = GenericMetadata() md = GenericMetadata()
md.series = utils.xlate(get('Series')) md.series = utils.xlate(get("Series"))
md.title = utils.xlate(get('Title')) md.title = utils.xlate(get("Title"))
md.issue = IssueString(utils.xlate(get('Number'))).asString() md.issue = IssueString(utils.xlate(get("Number"))).as_string()
md.issueCount = utils.xlate(get('Count'), True) md.issue_count = utils.xlate(get("Count"), True)
md.volume = utils.xlate(get('Volume'), True) md.volume = utils.xlate(get("Volume"), True)
md.alternateSeries = utils.xlate(get('AlternateSeries')) md.alternate_series = utils.xlate(get("AlternateSeries"))
md.alternateNumber = IssueString(utils.xlate(get('AlternateNumber'))).asString() md.alternate_number = IssueString(utils.xlate(get("AlternateNumber"))).as_string()
md.alternateCount = utils.xlate(get('AlternateCount'), True) md.alternate_count = utils.xlate(get("AlternateCount"), True)
md.comments = utils.xlate(get('Summary')) md.comments = utils.xlate(get("Summary"))
md.notes = utils.xlate(get('Notes')) md.notes = utils.xlate(get("Notes"))
md.year = utils.xlate(get('Year'), True) md.year = utils.xlate(get("Year"), True)
md.month = utils.xlate(get('Month'), True) md.month = utils.xlate(get("Month"), True)
md.day = utils.xlate(get('Day'), True) md.day = utils.xlate(get("Day"), True)
md.publisher = utils.xlate(get('Publisher')) md.publisher = utils.xlate(get("Publisher"))
md.imprint = utils.xlate(get('Imprint')) md.imprint = utils.xlate(get("Imprint"))
md.genre = utils.xlate(get('Genre')) md.genre = utils.xlate(get("Genre"))
md.webLink = utils.xlate(get('Web')) md.web_link = utils.xlate(get("Web"))
md.language = utils.xlate(get('LanguageISO')) md.language = utils.xlate(get("LanguageISO"))
md.format = utils.xlate(get('Format')) md.format = utils.xlate(get("Format"))
md.manga = utils.xlate(get('Manga')) md.manga = utils.xlate(get("Manga"))
md.characters = utils.xlate(get('Characters')) md.characters = utils.xlate(get("Characters"))
md.teams = utils.xlate(get('Teams')) md.teams = utils.xlate(get("Teams"))
md.locations = utils.xlate(get('Locations')) md.locations = utils.xlate(get("Locations"))
md.pageCount = utils.xlate(get('PageCount'), True) md.page_count = utils.xlate(get("PageCount"), True)
md.scanInfo = utils.xlate(get('ScanInformation')) md.scan_info = utils.xlate(get("ScanInformation"))
md.storyArc = utils.xlate(get('StoryArc')) md.story_arc = utils.xlate(get("StoryArc"))
md.seriesGroup = utils.xlate(get('SeriesGroup')) md.series_group = utils.xlate(get("SeriesGroup"))
md.maturityRating = utils.xlate(get('AgeRating')) md.maturity_rating = utils.xlate(get("AgeRating"))
tmp = utils.xlate(get('BlackAndWhite')) tmp = utils.xlate(get("BlackAndWhite"))
if tmp is not None and tmp.lower() in ["yes", "true", "1"]: if tmp is not None and tmp.lower() in ["yes", "true", "1"]:
md.blackAndWhite = True md.black_and_white = True
# Now extract the credit info # Now extract the credit info
for n in root: for n in root:
if (n.tag == 'Writer' or if any(
n.tag == 'Penciller' or [
n.tag == 'Inker' or n.tag == "Writer",
n.tag == 'Colorist' or n.tag == "Penciller",
n.tag == 'Letterer' or n.tag == "Inker",
n.tag == 'Editor' n.tag == "Colorist",
n.tag == "Letterer",
n.tag == "Editor",
]
): ):
if n.text is not None: if n.text is not None:
for name in n.text.split(','): for name in n.text.split(","):
md.addCredit(name.strip(), n.tag) md.add_credit(name.strip(), n.tag)
if n.tag == 'CoverArtist': if n.tag == "CoverArtist":
if n.text is not None: if n.text is not None:
for name in n.text.split(','): for name in n.text.split(","):
md.addCredit(name.strip(), "Cover") md.add_credit(name.strip(), "Cover")
# parse page data now # parse page data now
pages_node = root.find("Pages") pages_node = root.find("Pages")
if pages_node is not None: if pages_node is not None:
for page in pages_node: for page in pages_node:
md.pages.append(page.attrib) md.pages.append(page.attrib)
# print page.attrib
md.isEmpty = False md.is_empty = False
return md return md
def writeToExternalFile(self, filename, metadata, xml=None): def write_to_external_file(self, filename, metadata, xml=None):
tree = self.convertMetadataToXML(self, metadata, xml) tree = self.convert_metadata_to_xml(self, metadata, xml)
# ET.dump(tree)
tree.write(filename, encoding="utf-8", xml_declaration=True) tree.write(filename, encoding="utf-8", xml_declaration=True)
def readFromExternalFile(self, filename): def read_from_external_file(self, filename):
tree = ET.parse(filename) tree = ET.parse(filename)
return self.convertXMLToMetadata(tree) return self.convert_xml_to_metadata(tree)

View File

@ -20,56 +20,62 @@ This should probably be re-written, but, well, it mostly works!
# Some portions of this code were modified from pyComicMetaThis project # Some portions of this code were modified from pyComicMetaThis project
# http://code.google.com/p/pycomicmetathis/ # http://code.google.com/p/pycomicmetathis/
import re
import os import os
import re
from urllib.parse import unquote from urllib.parse import unquote
class FileNameParser: class FileNameParser:
def __init__(self):
self.series = ""
self.volume = ""
self.year = ""
self.issue_count = ""
self.remainder = ""
self.issue = ""
def repl(self, m): def repl(self, m):
return ' ' * len(m.group()) return " " * len(m.group())
def fixSpaces(self, string, remove_dashes=True): def fix_spaces(self, string, remove_dashes=True):
if remove_dashes: if remove_dashes:
placeholders = ['[-_]', ' +'] placeholders = [r"[-_]", r" +"]
else: else:
placeholders = ['[_]', ' +'] placeholders = [r"[_]", r" +"]
for ph in placeholders: for ph in placeholders:
string = re.sub(ph, self.repl, string) string = re.sub(ph, self.repl, string)
return string # .strip() return string # .strip()
def getIssueCount(self, filename, issue_end): def get_issue_count(self, filename, issue_end):
count = "" count = ""
filename = filename[issue_end:] filename = filename[issue_end:]
# replace any name separators with spaces # replace any name separators with spaces
tmpstr = self.fixSpaces(filename) tmpstr = self.fix_spaces(filename)
found = False found = False
match = re.search('(?<=\sof\s)\d+(?=\s)', tmpstr, re.IGNORECASE) match = re.search(r"(?<=\sof\s)\d+(?=\s)", tmpstr, re.IGNORECASE)
if match: if match:
count = match.group() count = match.group()
found = True found = True
if not found: if not found:
match = re.search('(?<=\(of\s)\d+(?=\))', tmpstr, re.IGNORECASE) match = re.search(r"(?<=\(of\s)\d+(?=\))", tmpstr, re.IGNORECASE)
if match: if match:
count = match.group() count = match.group()
found = True
count = count.lstrip("0") count = count.lstrip("0")
return count return count
def getIssueNumber(self, filename): def get_issue_number(self, filename):
"""Returns a tuple of issue number string, and start and end indexes in the filename """Returns a tuple of issue number string, and start and end indexes in the filename
(The indexes will be used to split the string up for further parsing) (The indexes will be used to split the string up for further parsing)
""" """
found = False found = False
issue = '' issue = ""
start = 0 start = 0
end = 0 end = 0
@ -78,25 +84,25 @@ class FileNameParser:
if "--" in filename: if "--" in filename:
# the pattern seems to be that anything to left of the first "--" # the pattern seems to be that anything to left of the first "--"
# is the series name followed by issue # is the series name followed by issue
filename = re.sub("--.*", self.repl, filename) filename = re.sub(r"--.*", self.repl, filename)
elif "__" in filename: elif "__" in filename:
# the pattern seems to be that anything to left of the first "__" # the pattern seems to be that anything to left of the first "__"
# is the series name followed by issue # is the series name followed by issue
filename = re.sub("__.*", self.repl, filename) filename = re.sub(r"__.*", self.repl, filename)
filename = filename.replace("+", " ") filename = filename.replace("+", " ")
# replace parenthetical phrases with spaces # replace parenthetical phrases with spaces
filename = re.sub("\(.*?\)", self.repl, filename) filename = re.sub(r"\(.*?\)", self.repl, filename)
filename = re.sub("\[.*?\]", self.repl, filename) filename = re.sub(r"\[.*?]", self.repl, filename)
# replace any name separators with spaces # replace any name separators with spaces
filename = self.fixSpaces(filename) filename = self.fix_spaces(filename)
# remove any "of NN" phrase with spaces (problem: this could break on # remove any "of NN" phrase with spaces (problem: this could break on
# some titles) # some titles)
filename = re.sub("of [\d]+", self.repl, filename) filename = re.sub(r"of [\d]+", self.repl, filename)
# print u"[{0}]".format(filename) # print u"[{0}]".format(filename)
@ -104,8 +110,8 @@ class FileNameParser:
# the same positions as original filename # the same positions as original filename
# make a list of each word and its position # make a list of each word and its position
word_list = list() word_list = []
for m in re.finditer("\S+", filename): for m in re.finditer(r"\S+", filename):
word_list.append((m.group(0), m.start(), m.end())) word_list.append((m.group(0), m.start(), m.end()))
# remove the first word, since it can't be the issue number # remove the first word, since it can't be the issue number
@ -120,7 +126,7 @@ class FileNameParser:
# first look for a word with "#" followed by digits with optional suffix # first look for a word with "#" followed by digits with optional suffix
# this is almost certainly the issue number # this is almost certainly the issue number
for w in reversed(word_list): for w in reversed(word_list):
if re.match("#[-]?(([0-9]*\.[0-9]+|[0-9]+)(\w*))", w[0]): if re.match(r"#[-]?(([0-9]*\.[0-9]+|[0-9]+)(\w*))", w[0]):
found = True found = True
break break
@ -128,13 +134,13 @@ class FileNameParser:
# list # list
if not found: if not found:
w = word_list[-1] w = word_list[-1]
if re.match("[-]?(([0-9]*\.[0-9]+|[0-9]+)(\w*))", w[0]): if re.match(r"[-]?(([0-9]*\.[0-9]+|[0-9]+)(\w*))", w[0]):
found = True found = True
# now try to look for a # followed by any characters # now try to look for a # followed by any characters
if not found: if not found:
for w in reversed(word_list): for w in reversed(word_list):
if re.match("#\S+", w[0]): if re.match(r"#\S+", w[0]):
found = True found = True
break break
@ -142,12 +148,12 @@ class FileNameParser:
issue = w[0] issue = w[0]
start = w[1] start = w[1]
end = w[2] end = w[2]
if issue[0] == '#': if issue[0] == "#":
issue = issue[1:] issue = issue[1:]
return issue, start, end return issue, start, end
def getSeriesName(self, filename, issue_start): def get_series_name(self, filename, issue_start):
"""Use the issue number string index to split the filename string""" """Use the issue number string index to split the filename string"""
if issue_start != 0: if issue_start != 0:
@ -157,15 +163,15 @@ class FileNameParser:
if "--" in filename: if "--" in filename:
# the pattern seems to be that anything to left of the first "--" # the pattern seems to be that anything to left of the first "--"
# is the series name followed by issue # is the series name followed by issue
filename = re.sub("--.*", self.repl, filename) filename = re.sub(r"--.*", self.repl, filename)
elif "__" in filename: elif "__" in filename:
# the pattern seems to be that anything to left of the first "__" # the pattern seems to be that anything to left of the first "__"
# is the series name followed by issue # is the series name followed by issue
filename = re.sub("__.*", self.repl, filename) filename = re.sub(r"__.*", self.repl, filename)
filename = filename.replace("+", " ") filename = filename.replace("+", " ")
tmpstr = self.fixSpaces(filename, remove_dashes=False) tmpstr = self.fix_spaces(filename, remove_dashes=False)
series = tmpstr series = tmpstr
volume = "" volume = ""
@ -177,10 +183,10 @@ class FileNameParser:
last_word = "" last_word = ""
# remove any parenthetical phrases # remove any parenthetical phrases
series = re.sub("\(.*?\)", "", series) series = re.sub(r"\(.*?\)", "", series)
# search for volume number # search for volume number
match = re.search('(.+)([vV]|[Vv][oO][Ll]\.?\s?)(\d+)\s*$', series) match = re.search(r"(.+)([vV]|[Vv][oO][Ll]\.?\s?)(\d+)\s*$", series)
if match: if match:
series = match.group(1) series = match.group(1)
volume = match.group(3) volume = match.group(3)
@ -189,7 +195,7 @@ class FileNameParser:
# since that's a common way to designate the volume # since that's a common way to designate the volume
if volume == "": if volume == "":
# match either (YEAR), (YEAR-), or (YEAR-YEAR2) # match either (YEAR), (YEAR-), or (YEAR-YEAR2)
match = re.search("(\()(\d{4})(-(\d{4}|)|)(\))", last_word) match = re.search(r"(\()(\d{4})(-(\d{4}|)|)(\))", last_word)
if match: if match:
volume = match.group(2) volume = match.group(2)
@ -203,26 +209,26 @@ class FileNameParser:
try: try:
last_word = series.split()[-1] last_word = series.split()[-1]
if last_word.lower() in one_shot_words: if last_word.lower() in one_shot_words:
series = series.rsplit(' ', 1)[0] series = series.rsplit(" ", 1)[0]
except: except:
pass pass
return series, volume.strip() return series, volume.strip()
def getYear(self, filename, issue_end): def get_year(self, filename, issue_end):
filename = filename[issue_end:] filename = filename[issue_end:]
year = "" year = ""
# look for four digit number with "(" ")" or "--" around it # look for four digit number with "(" ")" or "--" around it
match = re.search('(\(\d\d\d\d\))|(--\d\d\d\d--)', filename) match = re.search(r"(\(\d\d\d\d\))|(--\d\d\d\d--)", filename)
if match: if match:
year = match.group() year = match.group()
# remove non-digits # remove non-digits
year = re.sub("[^0-9]", "", year) year = re.sub(r"[^0-9]", "", year)
return year return year
def getRemainder(self, filename, year, count, volume, issue_end): def get_remainder(self, filename, year, count, volume, issue_end):
"""Make a guess at where the the non-interesting stuff begins""" """Make a guess at where the the non-interesting stuff begins"""
remainder = "" remainder = ""
@ -234,7 +240,7 @@ class FileNameParser:
elif issue_end != 0: elif issue_end != 0:
remainder = filename[issue_end:] remainder = filename[issue_end:]
remainder = self.fixSpaces(remainder, remove_dashes=False) remainder = self.fix_spaces(remainder, remove_dashes=False)
if volume != "": if volume != "":
remainder = remainder.replace("Vol." + volume, "", 1) remainder = remainder.replace("Vol." + volume, "", 1)
if year != "": if year != "":
@ -243,13 +249,11 @@ class FileNameParser:
remainder = remainder.replace("of " + count, "", 1) remainder = remainder.replace("of " + count, "", 1)
remainder = remainder.replace("()", "") remainder = remainder.replace("()", "")
remainder = remainder.replace( remainder = remainder.replace(" ", " ") # cleans some whitespace mess
" ",
" ") # cleans some whitespace mess
return remainder.strip() return remainder.strip()
def parseFilename(self, filename): def parse_filename(self, filename):
# remove the path # remove the path
filename = os.path.basename(filename) filename = os.path.basename(filename)
@ -267,21 +271,16 @@ class FileNameParser:
filename = filename.replace("_28", "(") filename = filename.replace("_28", "(")
filename = filename.replace("_29", ")") filename = filename.replace("_29", ")")
self.issue, issue_start, issue_end = self.getIssueNumber(filename) self.issue, issue_start, issue_end = self.get_issue_number(filename)
self.series, self.volume = self.getSeriesName(filename, issue_start) self.series, self.volume = self.get_series_name(filename, issue_start)
# provides proper value when the filename doesn't have a issue number # provides proper value when the filename doesn't have a issue number
if issue_end == 0: if issue_end == 0:
issue_end = len(self.series) issue_end = len(self.series)
self.year = self.getYear(filename, issue_end) self.year = self.get_year(filename, issue_end)
self.issue_count = self.getIssueCount(filename, issue_end) self.issue_count = self.get_issue_count(filename, issue_end)
self.remainder = self.getRemainder( self.remainder = self.get_remainder(filename, self.year, self.issue_count, self.volume, issue_end)
filename,
self.year,
self.issue_count,
self.volume,
issue_end)
if self.issue != "": if self.issue != "":
# strip off leading zeros # strip off leading zeros

View File

@ -20,7 +20,9 @@ possible, however lossy it might be
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
from . import utils from typing import List, TypedDict
from comicapi import utils
class PageType: class PageType:
@ -42,24 +44,34 @@ class PageType:
Other = "Other" Other = "Other"
Deleted = "Deleted" Deleted = "Deleted"
"""
class PageInfo: class ImageMetadata(TypedDict):
Image = 0 Type: PageType
Type = PageType.Story Image: int
DoublePage = False ImageSize: str
ImageSize = 0 ImageHeight: str
Key = "" ImageWidth: str
ImageWidth = 0
ImageHeight = 0
""" class CreditMetadata(TypedDict):
person: str
role: str
primary: bool
class GenericMetadata: class GenericMetadata:
writer_synonyms = ["writer", "plotter", "scripter"]
penciller_synonyms = ["artist", "penciller", "penciler", "breakdowns"]
inker_synonyms = ["inker", "artist", "finishes"]
colorist_synonyms = ["colorist", "colourist", "colorer", "colourer"]
letterer_synonyms = ["letterer"]
cover_synonyms = ["cover", "covers", "coverartist", "cover artist"]
editor_synonyms = ["editor"]
def __init__(self): def __init__(self):
self.isEmpty = True self.is_empty = True
self.tagOrigin = None self.tag_origin = None
self.series = None self.series = None
self.issue = None self.issue = None
@ -68,47 +80,47 @@ class GenericMetadata:
self.month = None self.month = None
self.year = None self.year = None
self.day = None self.day = None
self.issueCount = None self.issue_count = None
self.volume = None self.volume = None
self.genre = None self.genre = None
self.language = None # 2 letter iso code self.language = None # 2 letter iso code
self.comments = None # use same way as Summary in CIX self.comments = None # use same way as Summary in CIX
self.volumeCount = None self.volume_count = None
self.criticalRating = None self.critical_rating = None
self.country = None self.country = None
self.alternateSeries = None self.alternate_series = None
self.alternateNumber = None self.alternate_number = None
self.alternateCount = None self.alternate_count = None
self.imprint = None self.imprint = None
self.notes = None self.notes = None
self.webLink = None self.web_link = None
self.format = None self.format = None
self.manga = None self.manga = None
self.blackAndWhite = None self.black_and_white = None
self.pageCount = None self.page_count = None
self.maturityRating = None self.maturity_rating = None
self.storyArc = None self.story_arc = None
self.seriesGroup = None self.series_group = None
self.scanInfo = None self.scan_info = None
self.characters = None self.characters = None
self.teams = None self.teams = None
self.locations = None self.locations = None
self.credits = list() self.credits: List[CreditMetadata] = []
self.tags = list() self.tags: List[str] = []
self.pages = list() self.pages: List[ImageMetadata] = []
# Some CoMet-only items # Some CoMet-only items
self.price = None self.price = None
self.isVersionOf = None self.is_version_of = None
self.rights = None self.rights = None
self.identifier = None self.identifier = None
self.lastMark = None self.last_mark = None
self.coverImage = None self.cover_image = None
def overlay(self, new_md): def overlay(self, new_md):
"""Overlay a metadata object on this one """Overlay a metadata object on this one
@ -124,35 +136,36 @@ class GenericMetadata:
else: else:
setattr(self, cur, new) setattr(self, cur, new)
if not new_md.isEmpty: new_md: GenericMetadata
self.isEmpty = False if not new_md.is_empty:
self.is_empty = False
assign('series', new_md.series) assign("series", new_md.series)
assign("issue", new_md.issue) assign("issue", new_md.issue)
assign("issueCount", new_md.issueCount) assign("issue_count", new_md.issue_count)
assign("title", new_md.title) assign("title", new_md.title)
assign("publisher", new_md.publisher) assign("publisher", new_md.publisher)
assign("day", new_md.day) assign("day", new_md.day)
assign("month", new_md.month) assign("month", new_md.month)
assign("year", new_md.year) assign("year", new_md.year)
assign("volume", new_md.volume) assign("volume", new_md.volume)
assign("volumeCount", new_md.volumeCount) assign("volume_count", new_md.volume_count)
assign("genre", new_md.genre) assign("genre", new_md.genre)
assign("language", new_md.language) assign("language", new_md.language)
assign("country", new_md.country) assign("country", new_md.country)
assign("criticalRating", new_md.criticalRating) assign("critical_rating", new_md.critical_rating)
assign("alternateSeries", new_md.alternateSeries) assign("alternate_series", new_md.alternate_series)
assign("alternateNumber", new_md.alternateNumber) assign("alternate_number", new_md.alternate_number)
assign("alternateCount", new_md.alternateCount) assign("alternate_count", new_md.alternate_count)
assign("imprint", new_md.imprint) assign("imprint", new_md.imprint)
assign("webLink", new_md.webLink) assign("web_link", new_md.web_link)
assign("format", new_md.format) assign("format", new_md.format)
assign("manga", new_md.manga) assign("manga", new_md.manga)
assign("blackAndWhite", new_md.blackAndWhite) assign("black_and_white", new_md.black_and_white)
assign("maturityRating", new_md.maturityRating) assign("maturity_rating", new_md.maturity_rating)
assign("storyArc", new_md.storyArc) assign("story_arc", new_md.story_arc)
assign("seriesGroup", new_md.seriesGroup) assign("series_group", new_md.series_group)
assign("scanInfo", new_md.scanInfo) assign("scan_info", new_md.scan_info)
assign("characters", new_md.characters) assign("characters", new_md.characters)
assign("teams", new_md.teams) assign("teams", new_md.teams)
assign("locations", new_md.locations) assign("locations", new_md.locations)
@ -160,12 +173,12 @@ class GenericMetadata:
assign("notes", new_md.notes) assign("notes", new_md.notes)
assign("price", new_md.price) assign("price", new_md.price)
assign("isVersionOf", new_md.isVersionOf) assign("is_version_of", new_md.is_version_of)
assign("rights", new_md.rights) assign("rights", new_md.rights)
assign("identifier", new_md.identifier) assign("identifier", new_md.identifier)
assign("lastMark", new_md.lastMark) assign("last_mark", new_md.last_mark)
self.overlayCredits(new_md.credits) self.overlay_credits(new_md.credits)
# TODO # TODO
# not sure if the tags and pages should broken down, or treated # not sure if the tags and pages should broken down, or treated
@ -179,66 +192,62 @@ class GenericMetadata:
if len(new_md.pages) > 0: if len(new_md.pages) > 0:
assign("pages", new_md.pages) assign("pages", new_md.pages)
def overlayCredits(self, new_credits): def overlay_credits(self, new_credits):
for c in new_credits: for c in new_credits:
if 'primary' in c and c['primary']: primary = bool("primary" in c and c["primary"])
primary = True
else:
primary = False
# Remove credit role if person is blank # Remove credit role if person is blank
if c['person'] == "": if c["person"] == "":
for r in reversed(self.credits): for r in reversed(self.credits):
if r['role'].lower() == c['role'].lower(): if r["role"].lower() == c["role"].lower():
self.credits.remove(r) self.credits.remove(r)
# otherwise, add it! # otherwise, add it!
else: else:
self.addCredit(c['person'], c['role'], primary) self.add_credit(c["person"], c["role"], primary)
def setDefaultPageList(self, count): def set_default_page_list(self, count):
# generate a default page list, with the first page marked as the cover # generate a default page list, with the first page marked as the cover
for i in range(count): for i in range(count):
page_dict = dict() page_dict = {}
page_dict['Image'] = str(i) page_dict["Image"] = str(i)
if i == 0: if i == 0:
page_dict['Type'] = PageType.FrontCover page_dict["Type"] = PageType.FrontCover
self.pages.append(page_dict) self.pages.append(page_dict)
def getArchivePageIndex(self, pagenum): def get_archive_page_index(self, pagenum):
# convert the displayed page number to the page index of the file in # convert the displayed page number to the page index of the file in
# the archive # the archive
if pagenum < len(self.pages): if pagenum < len(self.pages):
return int(self.pages[pagenum]['Image']) return int(self.pages[pagenum]["Image"])
else:
return 0 return 0
def getCoverPageIndexList(self): def get_cover_page_index_list(self):
# return a list of archive page indices of cover pages # return a list of archive page indices of cover pages
coverlist = [] coverlist = []
for p in self.pages: for p in self.pages:
if 'Type' in p and p['Type'] == PageType.FrontCover: if "Type" in p and p["Type"] == PageType.FrontCover:
coverlist.append(int(p['Image'])) coverlist.append(int(p["Image"]))
if len(coverlist) == 0: if len(coverlist) == 0:
coverlist.append(0) coverlist.append(0)
return coverlist return coverlist
def addCredit(self, person, role, primary=False): def add_credit(self, person, role, primary=False):
credit = dict() credit = {}
credit['person'] = person credit["person"] = person
credit['role'] = role credit["role"] = role
if primary: if primary:
credit['primary'] = primary credit["primary"] = primary
# look to see if it's not already there... # look to see if it's not already there...
found = False found = False
for c in self.credits: for c in self.credits:
if (c['person'].lower() == person.lower() and if c["person"].lower() == person.lower() and c["role"].lower() == role.lower():
c['role'].lower() == role.lower()):
# no need to add it. just adjust the "primary" flag as needed # no need to add it. just adjust the "primary" flag as needed
c['primary'] = primary c["primary"] = primary
found = True found = True
break break
@ -247,64 +256,63 @@ class GenericMetadata:
def __str__(self): def __str__(self):
vals = [] vals = []
if self.isEmpty: if self.is_empty:
return "No metadata" return "No metadata"
def add_string(tag, val): def add_string(tag, val):
if val is not None and "{0}".format(val) != "": if val is not None and str(val) != "":
vals.append((tag, val)) vals.append((tag, val))
def add_attr_string(tag): def add_attr_string(tag):
val = getattr(self, tag)
add_string(tag, getattr(self, tag)) add_string(tag, getattr(self, tag))
add_attr_string("series") add_attr_string("series")
add_attr_string("issue") add_attr_string("issue")
add_attr_string("issueCount") add_attr_string("issue_count")
add_attr_string("title") add_attr_string("title")
add_attr_string("publisher") add_attr_string("publisher")
add_attr_string("year") add_attr_string("year")
add_attr_string("month") add_attr_string("month")
add_attr_string("day") add_attr_string("day")
add_attr_string("volume") add_attr_string("volume")
add_attr_string("volumeCount") add_attr_string("volume_count")
add_attr_string("genre") add_attr_string("genre")
add_attr_string("language") add_attr_string("language")
add_attr_string("country") add_attr_string("country")
add_attr_string("criticalRating") add_attr_string("critical_rating")
add_attr_string("alternateSeries") add_attr_string("alternate_series")
add_attr_string("alternateNumber") add_attr_string("alternate_number")
add_attr_string("alternateCount") add_attr_string("alternate_count")
add_attr_string("imprint") add_attr_string("imprint")
add_attr_string("webLink") add_attr_string("web_link")
add_attr_string("format") add_attr_string("format")
add_attr_string("manga") add_attr_string("manga")
add_attr_string("price") add_attr_string("price")
add_attr_string("isVersionOf") add_attr_string("is_version_of")
add_attr_string("rights") add_attr_string("rights")
add_attr_string("identifier") add_attr_string("identifier")
add_attr_string("lastMark") add_attr_string("last_mark")
if self.blackAndWhite: if self.black_and_white:
add_attr_string("blackAndWhite") add_attr_string("black_and_white")
add_attr_string("maturityRating") add_attr_string("maturity_rating")
add_attr_string("storyArc") add_attr_string("story_arc")
add_attr_string("seriesGroup") add_attr_string("series_group")
add_attr_string("scanInfo") add_attr_string("scan_info")
add_attr_string("characters") add_attr_string("characters")
add_attr_string("teams") add_attr_string("teams")
add_attr_string("locations") add_attr_string("locations")
add_attr_string("comments") add_attr_string("comments")
add_attr_string("notes") add_attr_string("notes")
add_string("tags", utils.listToString(self.tags)) add_string("tags", utils.list_to_string(self.tags))
for c in self.credits: for c in self.credits:
primary = "" primary = ""
if 'primary' in c and c['primary']: if "primary" in c and c["primary"]:
primary = " [P]" primary = " [P]"
add_string("credit", c['role'] + ": " + c['person'] + primary) add_string("credit", c["role"] + ": " + c["person"] + primary)
# find the longest field name # find the longest field name
flen = 0 flen = 0

View File

@ -1,4 +1,3 @@
# coding=utf-8
"""Support for mixed digit/string type Issue field """Support for mixed digit/string type Issue field
Class for handling the odd permutations of an 'issue number' that the Class for handling the odd permutations of an 'issue number' that the
@ -20,13 +19,8 @@ comics industry throws at us.
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
#import utils
#import math
#import re
class IssueString: class IssueString:
def __init__(self, text): def __init__(self, text):
# break up the issue number string into 2 parts: the numeric and suffix string. # break up the issue number string into 2 parts: the numeric and suffix string.
@ -43,10 +37,8 @@ class IssueString:
if len(text) == 0: if len(text) == 0:
return return
text = str(text)
# skip the minus sign if it's first # skip the minus sign if it's first
if text[0] == '-': if text[0] == "-":
start = 1 start = 1
else: else:
start = 0 start = 0
@ -78,7 +70,7 @@ class IssueString:
idx = 0 idx = 0
part1 = text[0:idx] part1 = text[0:idx]
part2 = text[idx:len(text)] part2 = text[idx : len(text)]
if part1 != "": if part1 != "":
self.num = float(part1) self.num = float(part1)
@ -86,9 +78,7 @@ class IssueString:
else: else:
self.suffix = text self.suffix = text
# print "num: {0} suf: {1}".format(self.num, self.suffix) def as_string(self, pad=0):
def asString(self, pad=0):
# return the float, left side zero-padded, with suffix attached # return the float, left side zero-padded, with suffix attached
if self.num is None: if self.num is None:
return self.suffix return self.suffix
@ -106,9 +96,9 @@ class IssueString:
# create padding # create padding
padding = "" padding = ""
l = len(str(num_int)) length = len(str(num_int))
if l < pad: if length < pad:
padding = "0" * (pad - l) padding = "0" * (pad - length)
num_s = padding + num_s num_s = padding + num_s
if negative: if negative:
@ -116,16 +106,16 @@ class IssueString:
return num_s return num_s
def asFloat(self): def as_float(self):
# return the float, with no suffix # return the float, with no suffix
if self.suffix == "½": if self.suffix == "½":
if self.num is not None: if self.num is not None:
return self.num + .5 return self.num + 0.5
else:
return .5 return 0.5
return self.num return self.num
def asInt(self): def as_int(self):
# return the int version of the float # return the int version of the float
if self.num is None: if self.num is None:
return None return None

View File

@ -1,4 +1,3 @@
# coding=utf-8
"""Some generic utilities""" """Some generic utilities"""
# Copyright 2012-2014 Anthony Beville # Copyright 2012-2014 Anthony Beville
@ -15,19 +14,39 @@
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
import sys
import os
import re
import platform
import locale
import codecs import codecs
import locale
import os
import platform
import re
import sys
import unicodedata import unicodedata
from collections import defaultdict
import pycountry
class UtilsVars: class UtilsVars:
already_fixed_encoding = False already_fixed_encoding = False
def indent(elem, level=0):
# for making the XML output readable
i = "\n" + level * " "
if len(elem):
if not elem.text or not elem.text.strip():
elem.text = i + " "
if not elem.tail or not elem.tail.strip():
elem.tail = i
for ele in elem:
indent(ele, level + 1)
if not elem.tail or not elem.tail.strip():
elem.tail = i
else:
if level and (not elem.tail or not elem.tail.strip()):
elem.tail = i
def get_actual_preferred_encoding(): def get_actual_preferred_encoding():
preferred_encoding = locale.getpreferredencoding() preferred_encoding = locale.getpreferredencoding()
if platform.system() == "Darwin": if platform.system() == "Darwin":
@ -50,26 +69,17 @@ def fix_output_encoding():
def get_recursive_filelist(pathlist): def get_recursive_filelist(pathlist):
"""Get a recursive list of of all files under all path items in the list""" """Get a recursive list of of all files under all path items in the list"""
filename_encoding = sys.getfilesystemencoding()
filelist = [] filelist = []
for p in pathlist: for p in pathlist:
# if path is a folder, walk it recursively, and all files underneath # if path is a folder, walk it recursively, and all files underneath
if isinstance(p, str): if not isinstance(p, str):
# make sure string is unicode
#p = p.decode(filename_encoding) # , 'replace')
pass
elif not isinstance(p, str):
# it's probably a QString # it's probably a QString
p = str(p) p = str(p)
if os.path.isdir(p): if os.path.isdir(p):
for root, dirs, files in os.walk(p): for root, _, files in os.walk(p):
for f in files: for f in files:
if isinstance(f, str): if not isinstance(f, str):
# make sure string is unicode
#f = f.decode(filename_encoding, 'replace')
pass
elif not isinstance(f, str):
# it's probably a QString # it's probably a QString
f = str(f) f = str(f)
filelist.append(os.path.join(root, f)) filelist.append(os.path.join(root, f))
@ -79,28 +89,26 @@ def get_recursive_filelist(pathlist):
return filelist return filelist
def listToString(l): def list_to_string(lst):
string = "" string = ""
if l is not None: if lst is not None:
for item in l: for item in lst:
if len(string) > 0: if len(string) > 0:
string += ", " string += ", "
string += item string += item
return string return string
def addtopath(dirname): def add_to_path(dirname):
if dirname is not None and dirname != "": if dirname is not None and dirname != "":
# verify that path doesn't already contain the given dirname # verify that path doesn't already contain the given dirname
tmpdirname = re.escape(dirname) tmpdirname = re.escape(dirname)
pattern = r"{sep}{dir}$|^{dir}{sep}|{sep}{dir}{sep}|^{dir}$".format( pattern = r"(^|{sep}){dir}({sep}|$)".format(dir=tmpdirname, sep=os.pathsep)
dir=tmpdirname,
sep=os.pathsep)
match = re.search(pattern, os.environ['PATH']) match = re.search(pattern, os.environ["PATH"])
if not match: if not match:
os.environ['PATH'] = dirname + os.pathsep + os.environ['PATH'] os.environ["PATH"] = dirname + os.pathsep + os.environ["PATH"]
def which(program): def which(program):
@ -109,7 +117,7 @@ def which(program):
def is_exe(fpath): def is_exe(fpath):
return os.path.isfile(fpath) and os.access(fpath, os.X_OK) return os.path.isfile(fpath) and os.access(fpath, os.X_OK)
fpath, fname = os.path.split(program) fpath, _ = os.path.split(program)
if fpath: if fpath:
if is_exe(program): if is_exe(program):
return program return program
@ -122,496 +130,109 @@ def which(program):
return None return None
def xlate(data, isInt=False): def xlate(data, is_int=False):
class Default(dict):
def __missing__(self, key):
return None
if data is None or data == "": if data is None or data == "":
return None return None
if isInt: if is_int:
i = str(data).translate(Default(zip((ord(c) for c in "1234567890"),"1234567890"))) i = str(data).translate(defaultdict(lambda: None, zip((ord(c) for c in "1234567890"), "1234567890")))
if i == "0": if i == "0":
return "0" return "0"
if i is "": if i == "":
return None return None
return int(i) return int(i)
else:
return str(data) return str(data)
def removearticles(text): def remove_articles(text):
text = text.lower() text = text.lower()
articles = ['and', 'a', '&', 'issue', 'the'] articles = [
newText = '' "&",
for word in text.split(' '): "a",
"am",
"an",
"and",
"as",
"at",
"be",
"but",
"by",
"for",
"if",
"is",
"issue",
"it",
"it's",
"its",
"itself",
"of",
"or",
"so",
"the",
"the",
"with",
]
new_text = ""
for word in text.split(" "):
if word not in articles: if word not in articles:
newText += word + ' ' new_text += word + " "
newText = newText[:-1] new_text = new_text[:-1]
return newText return new_text
def sanitize_title(text): def sanitize_title(text):
# normalize unicode and convert to ascii. Does not work for everything eg ½ to 12 not 1/2 # normalize unicode and convert to ascii. Does not work for everything eg ½ to 12 not 1/2
# this will probably cause issues with titles in other character sets e.g. chinese, japanese # this will probably cause issues with titles in other character sets e.g. chinese, japanese
text = unicodedata.normalize('NFKD', text).encode('ascii', 'ignore').decode('ascii') text = unicodedata.normalize("NFKD", text).encode("ascii", "ignore").decode("ascii")
# comicvine keeps apostrophes a part of the word # comicvine keeps apostrophes a part of the word
text = text.replace("'", "") text = text.replace("'", "")
text = text.replace("\"", "") text = text.replace('"', "")
# comicvine ignores punctuation and accents # comicvine ignores punctuation and accents
text = re.sub(r'[^A-Za-z0-9]+',' ', text) text = re.sub(r"[^A-Za-z0-9]+", " ", text)
# remove extra space and articles and all lower case # remove extra space and articles and all lower case
text = removearticles(text).lower().strip() text = remove_articles(text).lower().strip()
return text return text
def unique_file(file_name): def unique_file(file_name):
counter = 1 counter = 1
# returns ('/path/file', '.ext')
file_name_parts = os.path.splitext(file_name) file_name_parts = os.path.splitext(file_name)
while True: while True:
if not os.path.lexists(file_name): if not os.path.lexists(file_name):
return file_name return file_name
file_name = file_name_parts[ file_name = file_name_parts[0] + " (" + str(counter) + ")" + file_name_parts[1]
0] + ' (' + str(counter) + ')' + file_name_parts[1]
counter += 1 counter += 1
# -o- coding: utf-8 -o- languages = defaultdict(lambda: None)
# ISO639 python dict
# official list in http://www.loc.gov/standards/iso639-2/php/code_list.php
lang_dict = { countries = defaultdict(lambda: None)
'ab': 'Abkhaz',
'aa': 'Afar', for c in pycountry.countries:
'af': 'Afrikaans', if "alpha_2" in c._fields:
'ak': 'Akan', countries[c.alpha_2] = c.name
'sq': 'Albanian',
'am': 'Amharic', for lng in pycountry.languages:
'ar': 'Arabic', if "alpha_2" in lng._fields:
'an': 'Aragonese', languages[lng.alpha_2] = lng.name
'hy': 'Armenian',
'as': 'Assamese',
'av': 'Avaric',
'ae': 'Avestan',
'ay': 'Aymara',
'az': 'Azerbaijani',
'bm': 'Bambara',
'ba': 'Bashkir',
'eu': 'Basque',
'be': 'Belarusian',
'bn': 'Bengali',
'bh': 'Bihari',
'bi': 'Bislama',
'bs': 'Bosnian',
'br': 'Breton',
'bg': 'Bulgarian',
'my': 'Burmese',
'ca': 'Catalan; Valencian',
'ch': 'Chamorro',
'ce': 'Chechen',
'ny': 'Chichewa; Chewa; Nyanja',
'zh': 'Chinese',
'cv': 'Chuvash',
'kw': 'Cornish',
'co': 'Corsican',
'cr': 'Cree',
'hr': 'Croatian',
'cs': 'Czech',
'da': 'Danish',
'dv': 'Divehi; Maldivian;',
'nl': 'Dutch',
'dz': 'Dzongkha',
'en': 'English',
'eo': 'Esperanto',
'et': 'Estonian',
'ee': 'Ewe',
'fo': 'Faroese',
'fj': 'Fijian',
'fi': 'Finnish',
'fr': 'French',
'ff': 'Fula',
'gl': 'Galician',
'ka': 'Georgian',
'de': 'German',
'el': 'Greek, Modern',
'gn': 'Guaraní',
'gu': 'Gujarati',
'ht': 'Haitian',
'ha': 'Hausa',
'he': 'Hebrew (modern)',
'hz': 'Herero',
'hi': 'Hindi',
'ho': 'Hiri Motu',
'hu': 'Hungarian',
'ia': 'Interlingua',
'id': 'Indonesian',
'ie': 'Interlingue',
'ga': 'Irish',
'ig': 'Igbo',
'ik': 'Inupiaq',
'io': 'Ido',
'is': 'Icelandic',
'it': 'Italian',
'iu': 'Inuktitut',
'ja': 'Japanese',
'jv': 'Javanese',
'kl': 'Kalaallisut',
'kn': 'Kannada',
'kr': 'Kanuri',
'ks': 'Kashmiri',
'kk': 'Kazakh',
'km': 'Khmer',
'ki': 'Kikuyu, Gikuyu',
'rw': 'Kinyarwanda',
'ky': 'Kirghiz, Kyrgyz',
'kv': 'Komi',
'kg': 'Kongo',
'ko': 'Korean',
'ku': 'Kurdish',
'kj': 'Kwanyama, Kuanyama',
'la': 'Latin',
'lb': 'Luxembourgish',
'lg': 'Luganda',
'li': 'Limburgish',
'ln': 'Lingala',
'lo': 'Lao',
'lt': 'Lithuanian',
'lu': 'Luba-Katanga',
'lv': 'Latvian',
'gv': 'Manx',
'mk': 'Macedonian',
'mg': 'Malagasy',
'ms': 'Malay',
'ml': 'Malayalam',
'mt': 'Maltese',
'mi': 'Māori',
'mr': 'Marathi (Marāṭhī)',
'mh': 'Marshallese',
'mn': 'Mongolian',
'na': 'Nauru',
'nv': 'Navajo, Navaho',
'nb': 'Norwegian Bokmål',
'nd': 'North Ndebele',
'ne': 'Nepali',
'ng': 'Ndonga',
'nn': 'Norwegian Nynorsk',
'no': 'Norwegian',
'ii': 'Nuosu',
'nr': 'South Ndebele',
'oc': 'Occitan',
'oj': 'Ojibwe, Ojibwa',
'cu': 'Old Church Slavonic',
'om': 'Oromo',
'or': 'Oriya',
'os': 'Ossetian, Ossetic',
'pa': 'Panjabi, Punjabi',
'pi': 'Pāli',
'fa': 'Persian',
'pl': 'Polish',
'ps': 'Pashto, Pushto',
'pt': 'Portuguese',
'qu': 'Quechua',
'rm': 'Romansh',
'rn': 'Kirundi',
'ro': 'Romanian, Moldavan',
'ru': 'Russian',
'sa': 'Sanskrit (Saṁskṛta)',
'sc': 'Sardinian',
'sd': 'Sindhi',
'se': 'Northern Sami',
'sm': 'Samoan',
'sg': 'Sango',
'sr': 'Serbian',
'gd': 'Scottish Gaelic',
'sn': 'Shona',
'si': 'Sinhala, Sinhalese',
'sk': 'Slovak',
'sl': 'Slovene',
'so': 'Somali',
'st': 'Southern Sotho',
'es': 'Spanish; Castilian',
'su': 'Sundanese',
'sw': 'Swahili',
'ss': 'Swati',
'sv': 'Swedish',
'ta': 'Tamil',
'te': 'Telugu',
'tg': 'Tajik',
'th': 'Thai',
'ti': 'Tigrinya',
'bo': 'Tibetan',
'tk': 'Turkmen',
'tl': 'Tagalog',
'tn': 'Tswana',
'to': 'Tonga',
'tr': 'Turkish',
'ts': 'Tsonga',
'tt': 'Tatar',
'tw': 'Twi',
'ty': 'Tahitian',
'ug': 'Uighur, Uyghur',
'uk': 'Ukrainian',
'ur': 'Urdu',
'uz': 'Uzbek',
've': 'Venda',
'vi': 'Vietnamese',
'vo': 'Volapük',
'wa': 'Walloon',
'cy': 'Welsh',
'wo': 'Wolof',
'fy': 'Western Frisian',
'xh': 'Xhosa',
'yi': 'Yiddish',
'yo': 'Yoruba',
'za': 'Zhuang, Chuang',
'zu': 'Zulu',
}
countries = [ def get_language_from_iso(iso: str):
('AF', 'Afghanistan'), return languages[iso]
('AL', 'Albania'),
('DZ', 'Algeria'),
('AS', 'American Samoa'),
('AD', 'Andorra'),
('AO', 'Angola'),
('AI', 'Anguilla'),
('AQ', 'Antarctica'),
('AG', 'Antigua And Barbuda'),
('AR', 'Argentina'),
('AM', 'Armenia'),
('AW', 'Aruba'),
('AU', 'Australia'),
('AT', 'Austria'),
('AZ', 'Azerbaijan'),
('BS', 'Bahamas'),
('BH', 'Bahrain'),
('BD', 'Bangladesh'),
('BB', 'Barbados'),
('BY', 'Belarus'),
('BE', 'Belgium'),
('BZ', 'Belize'),
('BJ', 'Benin'),
('BM', 'Bermuda'),
('BT', 'Bhutan'),
('BO', 'Bolivia'),
('BA', 'Bosnia And Herzegowina'),
('BW', 'Botswana'),
('BV', 'Bouvet Island'),
('BR', 'Brazil'),
('BN', 'Brunei Darussalam'),
('BG', 'Bulgaria'),
('BF', 'Burkina Faso'),
('BI', 'Burundi'),
('KH', 'Cambodia'),
('CM', 'Cameroon'),
('CA', 'Canada'),
('CV', 'Cape Verde'),
('KY', 'Cayman Islands'),
('CF', 'Central African Rep'),
('TD', 'Chad'),
('CL', 'Chile'),
('CN', 'China'),
('CX', 'Christmas Island'),
('CC', 'Cocos Islands'),
('CO', 'Colombia'),
('KM', 'Comoros'),
('CG', 'Congo'),
('CK', 'Cook Islands'),
('CR', 'Costa Rica'),
('CI', 'Cote D`ivoire'),
('HR', 'Croatia'),
('CU', 'Cuba'),
('CY', 'Cyprus'),
('CZ', 'Czech Republic'),
('DK', 'Denmark'),
('DJ', 'Djibouti'),
('DM', 'Dominica'),
('DO', 'Dominican Republic'),
('TP', 'East Timor'),
('EC', 'Ecuador'),
('EG', 'Egypt'),
('SV', 'El Salvador'),
('GQ', 'Equatorial Guinea'),
('ER', 'Eritrea'),
('EE', 'Estonia'),
('ET', 'Ethiopia'),
('FK', 'Falkland Islands (Malvinas)'),
('FO', 'Faroe Islands'),
('FJ', 'Fiji'),
('FI', 'Finland'),
('FR', 'France'),
('GF', 'French Guiana'),
('PF', 'French Polynesia'),
('TF', 'French S. Territories'),
('GA', 'Gabon'),
('GM', 'Gambia'),
('GE', 'Georgia'),
('DE', 'Germany'),
('GH', 'Ghana'),
('GI', 'Gibraltar'),
('GR', 'Greece'),
('GL', 'Greenland'),
('GD', 'Grenada'),
('GP', 'Guadeloupe'),
('GU', 'Guam'),
('GT', 'Guatemala'),
('GN', 'Guinea'),
('GW', 'Guinea-bissau'),
('GY', 'Guyana'),
('HT', 'Haiti'),
('HN', 'Honduras'),
('HK', 'Hong Kong'),
('HU', 'Hungary'),
('IS', 'Iceland'),
('IN', 'India'),
('ID', 'Indonesia'),
('IR', 'Iran'),
('IQ', 'Iraq'),
('IE', 'Ireland'),
('IL', 'Israel'),
('IT', 'Italy'),
('JM', 'Jamaica'),
('JP', 'Japan'),
('JO', 'Jordan'),
('KZ', 'Kazakhstan'),
('KE', 'Kenya'),
('KI', 'Kiribati'),
('KP', 'Korea (North)'),
('KR', 'Korea (South)'),
('KW', 'Kuwait'),
('KG', 'Kyrgyzstan'),
('LA', 'Laos'),
('LV', 'Latvia'),
('LB', 'Lebanon'),
('LS', 'Lesotho'),
('LR', 'Liberia'),
('LY', 'Libya'),
('LI', 'Liechtenstein'),
('LT', 'Lithuania'),
('LU', 'Luxembourg'),
('MO', 'Macau'),
('MK', 'Macedonia'),
('MG', 'Madagascar'),
('MW', 'Malawi'),
('MY', 'Malaysia'),
('MV', 'Maldives'),
('ML', 'Mali'),
('MT', 'Malta'),
('MH', 'Marshall Islands'),
('MQ', 'Martinique'),
('MR', 'Mauritania'),
('MU', 'Mauritius'),
('YT', 'Mayotte'),
('MX', 'Mexico'),
('FM', 'Micronesia'),
('MD', 'Moldova'),
('MC', 'Monaco'),
('MN', 'Mongolia'),
('MS', 'Montserrat'),
('MA', 'Morocco'),
('MZ', 'Mozambique'),
('MM', 'Myanmar'),
('NA', 'Namibia'),
('NR', 'Nauru'),
('NP', 'Nepal'),
('NL', 'Netherlands'),
('AN', 'Netherlands Antilles'),
('NC', 'New Caledonia'),
('NZ', 'New Zealand'),
('NI', 'Nicaragua'),
('NE', 'Niger'),
('NG', 'Nigeria'),
('NU', 'Niue'),
('NF', 'Norfolk Island'),
('MP', 'Northern Mariana Islands'),
('NO', 'Norway'),
('OM', 'Oman'),
('PK', 'Pakistan'),
('PW', 'Palau'),
('PA', 'Panama'),
('PG', 'Papua New Guinea'),
('PY', 'Paraguay'),
('PE', 'Peru'),
('PH', 'Philippines'),
('PN', 'Pitcairn'),
('PL', 'Poland'),
('PT', 'Portugal'),
('PR', 'Puerto Rico'),
('QA', 'Qatar'),
('RE', 'Reunion'),
('RO', 'Romania'),
('RU', 'Russian Federation'),
('RW', 'Rwanda'),
('KN', 'Saint Kitts And Nevis'),
('LC', 'Saint Lucia'),
('VC', 'St Vincent/Grenadines'),
('WS', 'Samoa'),
('SM', 'San Marino'),
('ST', 'Sao Tome'),
('SA', 'Saudi Arabia'),
('SN', 'Senegal'),
('SC', 'Seychelles'),
('SL', 'Sierra Leone'),
('SG', 'Singapore'),
('SK', 'Slovakia'),
('SI', 'Slovenia'),
('SB', 'Solomon Islands'),
('SO', 'Somalia'),
('ZA', 'South Africa'),
('ES', 'Spain'),
('LK', 'Sri Lanka'),
('SH', 'St. Helena'),
('PM', 'St.Pierre'),
('SD', 'Sudan'),
('SR', 'Suriname'),
('SZ', 'Swaziland'),
('SE', 'Sweden'),
('CH', 'Switzerland'),
('SY', 'Syrian Arab Republic'),
('TW', 'Taiwan'),
('TJ', 'Tajikistan'),
('TZ', 'Tanzania'),
('TH', 'Thailand'),
('TG', 'Togo'),
('TK', 'Tokelau'),
('TO', 'Tonga'),
('TT', 'Trinidad And Tobago'),
('TN', 'Tunisia'),
('TR', 'Turkey'),
('TM', 'Turkmenistan'),
('TV', 'Tuvalu'),
('UG', 'Uganda'),
('UA', 'Ukraine'),
('AE', 'United Arab Emirates'),
('UK', 'United Kingdom'),
('US', 'United States'),
('UY', 'Uruguay'),
('UZ', 'Uzbekistan'),
('VU', 'Vanuatu'),
('VA', 'Vatican City State'),
('VE', 'Venezuela'),
('VN', 'Viet Nam'),
('VG', 'Virgin Islands (British)'),
('VI', 'Virgin Islands (U.S.)'),
('EH', 'Western Sahara'),
('YE', 'Yemen'),
('YU', 'Yugoslavia'),
('ZR', 'Zaire'),
('ZM', 'Zambia'),
('ZW', 'Zimbabwe')
]
def getLanguageDict(): def get_language(string):
return lang_dict if string is None:
def getLanguageFromISO(iso):
if iso is None:
return None return None
else:
return lang_dict[iso] lang = get_language_from_iso(string)
if lang is None:
try:
return pycountry.languages.lookup(string).name
except:
return None
return lang