diff --git a/.gitignore b/.gitignore
index 644da8d..43d9acc 100644
--- a/.gitignore
+++ b/.gitignore
@@ -2,3 +2,5 @@
/nbproject/
/dist
*.pyc
+/.vscode
+venv
\ No newline at end of file
diff --git a/MANIFEST.in b/MANIFEST.in
index bca9634..b3a5c6e 100644
--- a/MANIFEST.in
+++ b/MANIFEST.in
@@ -1,4 +1,8 @@
-include README.txt
+include README.md
include release_notes.txt
include requirements.txt
-recursive-include scripts *.py *.txt
\ No newline at end of file
+include unrar/*
+recursive-include scripts *.py *.txt
+recursive-include desktop-integration *
+include windows/app.ico
+include mac/app.icns
diff --git a/Makefile b/Makefile
index a5e25ee..0beb6df 100644
--- a/Makefile
+++ b/Makefile
@@ -1,4 +1,4 @@
-VERSION_STR := $(shell python -c 'import comictaggerlib.ctversion; print comictaggerlib.ctversion.version')
+VERSION_STR := $(shell python -c 'import comictaggerlib.ctversion; print( comictaggerlib.ctversion.version)')
ifeq ($(OS),Windows_NT)
APP_NAME=comictagger.exe
@@ -25,12 +25,17 @@ clean:
$(MAKE) -C mac clean
rm -rf build
$(MAKE) -C unrar clean
+ rm -f unrar/libunrar.so unrar/libunrar.a unrar/unrar
+ rm -f comictaggerlib/libunrar.so
+ rm -rf comictaggerlib/ui/__pycache__
pydist:
- mkdir -p release
- rm -f release/*.zip
+ make clean
+ mkdir -p piprelease
+ rm -f comictagger-$(VERSION_STR).zip
python setup.py sdist --formats=zip #,gztar
- mv dist/comictagger-$(VERSION_STR).zip release
+ mv dist/comictagger-$(VERSION_STR).zip piprelease
+ rm -rf comictagger.egg-info dist
upload:
python setup.py register
@@ -47,4 +52,4 @@ endif
dist: unrar
pyinstaller -y comictagger.spec
- mv dist/$(APP_NAME) dist/$(FINAL_NAME)
\ No newline at end of file
+ mv dist/$(APP_NAME) dist/$(FINAL_NAME)
diff --git a/README.md b/README.md
index 74c17e2..324ec43 100644
--- a/README.md
+++ b/README.md
@@ -1,53 +1,17 @@
-This is a fork derived from google code:
+A fork from the primary dev branch at https://github.com/davide-romanini/comictagger
- https://code.google.com/p/comictagger/
+Changes:
+ - Ported to Python 3
+ - Ported to PyQt5
+ - Added more application and GUI awareness of the unrar library, and removed references to the old scheme that used the unrar executable.
+ - Got setup.py working again to build sdist packages, suitable (I think) for PyPI. An install from the package will attempt to build unrar library. It should work on most Linux distros, and was tested on a Mac OSX system with dev tools from homebrew. If the library doesn't build, the GUI has instructions on where to download the library.
+ - Removed/changes obsolete links to old Google code website.
+ - Set a environment variable to scale the GUI on 4k displays
+
+Notes:
+- I did some testing with the pyinstaller build, and it worked on both platforms. I did encounter two problems:
+ - Mac build showed the wrong widget set. I found a solution here that seemed to work: https://stackoverflow.com/questions/48626999/packaging-with-pyinstaller-pyqt5-setstyle-ignored
+ - Windows build had problems grabbing images from ComicVine using SSL. It think that some libraries are missing from the monolithic exe, but I couldn't figure out how to fix the problem.
+- In setup.py you can also find the remains of an attempt to do some desktop integration from a pip install. It does work, but can cause problems with wheel installs, and I don't know if it's worth the bother. I kept the commented-out code in place, just in case.
-
-Changes in this fork:
- - using different unrar library https://pypi.python.org/pypi/unrar/. The previous one used unrar.dll on windows and
- hackish wrapping of unrar command on linux, while this new one should use unrarlib on both platforms. From my tests
- it is more stable and faster. *Requires unrarlib availability, check unrar module documentation for more
- information*.
- - extracted core libraries in its own package comicapi, shared in a new repository using git subtree for better
- alignment with comicstreamer
- - support for *day of month* field in the GUI
- - merge of changes from fcanc fork
-
-Todo:
- - more tests in non-linux platforms
- - repackage for simple user installation
-
-Follows original readme:
-
-ComicTagger is a multi-platform app for writing metadata to digital comics, written in Python and PyQt.
-
-Features:
-
-* Runs on Mac OSX, Microsoft Windows, and Linux systems
-* Communicates with an online database (Comic Vine) for acquiring metadata
-* Uses image processing to automatically match a given archive with the correct issue data
-* Batch processing in the GUI for tagging hundreds or more comics at a time
-* Reads and writes multiple tagging schemes ( ComicBookLover and ComicRack, with more planned).
-* Reads and writes RAR and Zip archives (external tools needed for writing RAR)
-* Command line interface (CLI) on all platforms (including Windows), which supports batch operations, and which can be
- used in native scripts for complex operations. For example, to recursively scrape and tag all archives in a folder
- comictagger.py -R -s -o -f -t cr -v -i --nooverwrite /path/to/comics/
-
-For details, screen-shots, release notes, and more, visit http://code.google.com/p/comictagger/
-
-Requires:
-
-* python 2.6 or 2.7
-* configparser
-* python imaging (PIL) >= 1.1.6
-* beautifulsoup > 4.1
-
-Optional requirement (for GUI):
-
-* pyqt4
-
-Install and run:
-
-* ComicTagger can be run directly from this directory, using the launcher script "comictagger.py"
-
-* To install on your system use: "python setup.py install". Take note in the output where comictagger.py goes!
+With Python 3, it's much easier to get the app working from scratch on a new distro, as all of the dependencies are available as wheels, including PyQt5, so just a simple "pip install comictagger.zip" is all that's needed.
diff --git a/comicapi/comet.py b/comicapi/comet.py
index 2b3d97b..d1741c5 100644
--- a/comicapi/comet.py
+++ b/comicapi/comet.py
@@ -19,8 +19,8 @@ import xml.etree.ElementTree as ET
#from pprint import pprint
#import zipfile
-from genericmetadata import GenericMetadata
-import utils
+from .genericmetadata import GenericMetadata
+from . import utils
class CoMet:
@@ -76,7 +76,7 @@ class CoMet:
# helper func
def assign(comet_entry, md_entry):
if md_entry is not None:
- ET.SubElement(root, comet_entry).text = u"{0}".format(md_entry)
+ ET.SubElement(root, comet_entry).text = "{0}".format(md_entry)
# title is manditory
if md.title is None:
@@ -131,43 +131,43 @@ class CoMet:
if credit['role'].lower() in set(self.writer_synonyms):
ET.SubElement(
root,
- 'writer').text = u"{0}".format(
+ 'writer').text = "{0}".format(
credit['person'])
if credit['role'].lower() in set(self.penciller_synonyms):
ET.SubElement(
root,
- 'penciller').text = u"{0}".format(
+ 'penciller').text = "{0}".format(
credit['person'])
if credit['role'].lower() in set(self.inker_synonyms):
ET.SubElement(
root,
- 'inker').text = u"{0}".format(
+ 'inker').text = "{0}".format(
credit['person'])
if credit['role'].lower() in set(self.colorist_synonyms):
ET.SubElement(
root,
- 'colorist').text = u"{0}".format(
+ 'colorist').text = "{0}".format(
credit['person'])
if credit['role'].lower() in set(self.letterer_synonyms):
ET.SubElement(
root,
- 'letterer').text = u"{0}".format(
+ 'letterer').text = "{0}".format(
credit['person'])
if credit['role'].lower() in set(self.cover_synonyms):
ET.SubElement(
root,
- 'coverDesigner').text = u"{0}".format(
+ 'coverDesigner').text = "{0}".format(
credit['person'])
if credit['role'].lower() in set(self.editor_synonyms):
ET.SubElement(
root,
- 'editor').text = u"{0}".format(
+ 'editor').text = "{0}".format(
credit['person'])
# self pretty-print
diff --git a/comicapi/comicarchive.py b/comicapi/comicarchive.py
index cac1e01..ac7c83d 100644
--- a/comicapi/comicarchive.py
+++ b/comicapi/comicarchive.py
@@ -23,7 +23,7 @@ import subprocess
import platform
import ctypes
import time
-import StringIO
+import io
#import io
#import locale
#import shutil
@@ -65,12 +65,13 @@ try:
self._data += chunk
return 1
rarfile._ReadIntoMemory._callback = _rar_cb
-except:
- print "WARNING: cannot find libunrar, rar support is disabled"
+except Exception as e:
+ print(e)
+ print("WARNING: cannot find libunrar, rar support is disabled")
pass
-if platform.system() == "Windows":
- import _subprocess
+#if platform.system() == "Windows":
+# import _subprocess
try:
import Image
@@ -78,11 +79,11 @@ try:
except ImportError:
pil_available = False
-from comicinfoxml import ComicInfoXml
-from comicbookinfo import ComicBookInfo
-from comet import CoMet
-from genericmetadata import GenericMetadata, PageType
-from filenameparser import FileNameParser
+from .comicinfoxml import ComicInfoXml
+from .comicbookinfo import ComicBookInfo
+from .comet import CoMet
+from .genericmetadata import GenericMetadata, PageType
+from .filenameparser import FileNameParser
#from settings import ComicTaggerSettings
@@ -109,7 +110,10 @@ class ZipArchiver:
return comment
def setArchiveComment(self, comment):
- return self.writeZipComment(self.path, comment)
+ zf = zipfile.ZipFile(self.path, 'a')
+ zf.comment = bytes(comment, 'utf-8')
+ zf.close()
+ return True
def readArchiveFile(self, archive_file):
data = ""
@@ -118,14 +122,13 @@ class ZipArchiver:
try:
data = zf.read(archive_file)
except zipfile.BadZipfile as e:
- print >> sys.stderr, u"bad zipfile [{0}]: {1} :: {2}".format(
- e, self.path, archive_file)
+ print("bad zipfile [{0}]: {1} :: {2}".format(e, self.path, archive_file), file=sys.stderr)
zf.close()
raise IOError
except Exception as e:
zf.close()
- print >> sys.stderr, u"bad zipfile [{0}]: {1} :: {2}".format(
- e, self.path, archive_file)
+ print("bad zipfile [{0}]: {1} :: {2}".format(
+ e, self.path, archive_file), file=sys.stderr)
raise IOError
finally:
zf.close()
@@ -164,8 +167,8 @@ class ZipArchiver:
zf.close()
return namelist
except Exception as e:
- print >> sys.stderr, u"Unable to get zipfile list [{0}]: {1}".format(
- e, self.path)
+ print("Unable to get zipfile list [{0}]: {1}".format(
+ e, self.path), file=sys.stderr)
return []
def rebuildZipFile(self, exclude_list):
@@ -220,7 +223,7 @@ class ZipArchiver:
found = False
value = bytearray()
-
+
# walk backwards to find the "End of Central Directory" record
while (not found) and (-pos != file_length):
# seek, relative to EOF
@@ -253,12 +256,12 @@ class ZipArchiver:
fo.seek(pos + 2, 2)
# write out the comment itself
- fo.write(comment)
+ fo.write(bytes(comment))
fo.truncate()
fo.close()
else:
raise Exception('Failed to write comment to zip file!')
- except:
+ except Exception as e:
return False
else:
return True
@@ -280,8 +283,8 @@ class ZipArchiver:
if not self.writeZipComment(self.path, comment):
return False
except Exception as e:
- print >> sys.stderr, u"Error while copying to {0}: {1}".format(
- self.path, e)
+ print("Error while copying to {0}: {1}".format(
+ self.path, e), file=sys.stderr)
return False
else:
return True
@@ -305,7 +308,7 @@ class RarArchiver:
# windows only, keeps the cmd.exe from popping up
if platform.system() == "Windows":
self.startupinfo = subprocess.STARTUPINFO()
- self.startupinfo.dwFlags |= _subprocess.STARTF_USESHOWWINDOW
+ self.startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW
else:
self.startupinfo = None
@@ -314,37 +317,38 @@ class RarArchiver:
pass
def getArchiveComment(self):
-
rarc = self.getRARObj()
return rarc.comment
def setArchiveComment(self, comment):
-
if self.rar_exe_path is not None:
try:
# write comment to temp file
tmp_fd, tmp_name = tempfile.mkstemp()
- f = os.fdopen(tmp_fd, 'w+b')
+ f = os.fdopen(tmp_fd, 'w+')
f.write(comment)
f.close()
working_dir = os.path.dirname(os.path.abspath(self.path))
-
+
# use external program to write comment to Rar archive
- subprocess.call([self.rar_exe_path,
+ proc_args = [self.rar_exe_path,
'c',
'-w' + working_dir,
'-c-',
'-z' + tmp_name,
- self.path],
+ self.path]
+ subprocess.call(proc_args,
startupinfo=self.startupinfo,
- stdout=RarArchiver.devnull)
+ stdout=RarArchiver.devnull,
+ stdin=RarArchiver.devnull,
+ stderr=RarArchiver.devnull)
if platform.system() == "Darwin":
time.sleep(1)
-
os.remove(tmp_name)
- except:
+ except Exception as e:
+ print(e)
return False
else:
return True
@@ -376,26 +380,26 @@ class RarArchiver:
#entries = rarc.read_files( archive_file )
if entries[0][0].file_size != len(entries[0][1]):
- print >> sys.stderr, u"readArchiveFile(): [file is not expected size: {0} vs {1}] {2}:{3} [attempt # {4}]".format(
+ print("readArchiveFile(): [file is not expected size: {0} vs {1}] {2}:{3} [attempt # {4}]".format(
entries[0][0].file_size, len(
- entries[0][1]), self.path, archive_file, tries)
+ entries[0][1]), self.path, archive_file, tries), file=sys.stderr)
continue
except (OSError, IOError) as e:
- print >> sys.stderr, u"readArchiveFile(): [{0}] {1}:{2} attempt#{3}".format(
- str(e), self.path, archive_file, tries)
+ print("readArchiveFile(): [{0}] {1}:{2} attempt#{3}".format(
+ str(e), self.path, archive_file, tries), file=sys.stderr)
time.sleep(1)
except Exception as e:
- print >> sys.stderr, u"Unexpected exception in readArchiveFile(): [{0}] for {1}:{2} attempt#{3}".format(
- str(e), self.path, archive_file, tries)
+ print("Unexpected exception in readArchiveFile(): [{0}] for {1}:{2} attempt#{3}".format(
+ str(e), self.path, archive_file, tries), file=sys.stderr)
break
else:
# Success"
# entries is a list of of tuples: ( rarinfo, filedata)
if tries > 1:
- print >> sys.stderr, u"Attempted read_files() {0} times".format(
- tries)
+ print("Attempted read_files() {0} times".format(
+ tries), file=sys.stderr)
if (len(entries) == 1):
return entries[0][1]
else:
@@ -428,7 +432,9 @@ class RarArchiver:
self.path,
tmp_file],
startupinfo=self.startupinfo,
- stdout=RarArchiver.devnull)
+ stdout=RarArchiver.devnull,
+ stdin=RarArchiver.devnull,
+ stderr=RarArchiver.devnull)
if platform.system() == "Darwin":
time.sleep(1)
@@ -451,7 +457,9 @@ class RarArchiver:
self.path,
archive_file],
startupinfo=self.startupinfo,
- stdout=RarArchiver.devnull)
+ stdout=RarArchiver.devnull,
+ stdin=RarArchiver.devnull,
+ stderr=RarArchiver.devnull)
if platform.system() == "Darwin":
time.sleep(1)
@@ -479,8 +487,8 @@ class RarArchiver:
namelist.append(item.filename)
except (OSError, IOError) as e:
- print >> sys.stderr, u"getArchiveFilenameList(): [{0}] {1} attempt#{2}".format(
- str(e), self.path, tries)
+ print("getArchiveFilenameList(): [{0}] {1} attempt#{2}".format(
+ str(e), self.path, tries), file=sys.stderr)
time.sleep(1)
else:
@@ -497,8 +505,8 @@ class RarArchiver:
rarc = rarfile.RarFile( self.path )
except (OSError, IOError) as e:
- print >> sys.stderr, u"getRARObj(): [{0}] {1} attempt#{2}".format(
- str(e), self.path, tries)
+ print("getRARObj(): [{0}] {1} attempt#{2}".format(
+ str(e), self.path, tries), file=sys.stderr)
time.sleep(1)
else:
@@ -634,7 +642,7 @@ class ComicArchive:
logo_data = None
class ArchiveType:
- Zip, Rar, Folder, Pdf, Unknown = range(5)
+ Zip, Rar, Folder, Pdf, Unknown = list(range(5))
def __init__(self, path, rar_exe_path=None, default_image_path=None):
self.path = path
@@ -729,7 +737,7 @@ class ComicArchive:
if self.archive_type == self.ArchiveType.Unknown:
return False
- elif check_rar_status and self.isRar() and self.rar_exe_path is None:
+ elif check_rar_status and self.isRar() and not self.rar_exe_path:
return False
elif not os.access(self.path, os.W_OK):
@@ -817,7 +825,7 @@ class ComicArchive:
try:
image_data = self.archiver.readArchiveFile(filename)
except IOError:
- print >> sys.stderr, u"Error reading in page. Substituting logo page."
+ print("Error reading in page. Substituting logo page.", file=sys.stderr)
image_data = ComicArchive.logo_data
return image_data
@@ -859,7 +867,7 @@ class ComicArchive:
# sort by most common
sorted_buckets = sorted(
- length_buckets.iteritems(),
+ iter(length_buckets.items()),
key=lambda k_v: (
k_v[1],
k_v[0]),
@@ -1006,7 +1014,7 @@ class ComicArchive:
try:
raw_cix = self.archiver.readArchiveFile(self.ci_xml_filename)
except IOError:
- print "Error reading in raw CIX!"
+ print("Error reading in raw CIX!")
raw_cix = ""
return raw_cix
@@ -1075,13 +1083,13 @@ class ComicArchive:
def readRawCoMet(self):
if not self.hasCoMet():
- print >> sys.stderr, self.path, "doesn't have CoMet data!"
+ print(self.path, "doesn't have CoMet data!", file=sys.stderr)
return None
try:
raw_comet = self.archiver.readArchiveFile(self.comet_filename)
except IOError:
- print >> sys.stderr, u"Error reading in raw CoMet!"
+ print("Error reading in raw CoMet!", file=sys.stderr)
raw_comet = ""
return raw_comet
@@ -1136,7 +1144,7 @@ class ComicArchive:
data = self.archiver.readArchiveFile(n)
except:
data = ""
- print >> sys.stderr, u"Error reading in Comet XML for validation!"
+ print("Error reading in Comet XML for validation!", file=sys.stderr)
if CoMet().validateString(data):
# since we found it, save it!
self.comet_filename = n
@@ -1156,7 +1164,7 @@ class ComicArchive:
data = self.getPage(idx)
if data is not None:
try:
- im = Image.open(StringIO.StringIO(data))
+ im = Image.open(io.StringIO(data))
w, h = im.size
p['ImageSize'] = str(len(data))
diff --git a/comicapi/comicbookinfo.py b/comicapi/comicbookinfo.py
index 80e80f4..cb2d9e2 100644
--- a/comicapi/comicbookinfo.py
+++ b/comicapi/comicbookinfo.py
@@ -18,8 +18,8 @@ import json
from datetime import datetime
#import zipfile
-from genericmetadata import GenericMetadata
-import utils
+from .genericmetadata import GenericMetadata
+from . import utils
#import ctversion
@@ -27,7 +27,7 @@ class ComicBookInfo:
def metadataFromString(self, string):
- cbi_container = json.loads(unicode(string, 'utf-8'))
+ cbi_container = json.loads(str(string, 'utf-8'))
metadata = GenericMetadata()
@@ -109,7 +109,7 @@ class ComicBookInfo:
# helper func
def toInt(s):
i = None
- if type(s) in [str, unicode, int]:
+ if type(s) in [str, str, int]:
try:
i = int(s)
except ValueError:
diff --git a/comicapi/comicinfoxml.py b/comicapi/comicinfoxml.py
index 02cc61e..757fa46 100644
--- a/comicapi/comicinfoxml.py
+++ b/comicapi/comicinfoxml.py
@@ -19,8 +19,8 @@ import xml.etree.ElementTree as ET
#from pprint import pprint
#import zipfile
-from genericmetadata import GenericMetadata
-import utils
+from .genericmetadata import GenericMetadata
+from . import utils
class ComicInfoXml:
@@ -54,7 +54,8 @@ class ComicInfoXml:
header = '\n'
tree = self.convertMetadataToXML(self, metadata)
- return header + ET.tostring(tree.getroot())
+ tree_str = ET.tostring(tree.getroot()).decode()
+ return header + tree_str
def indent(self, elem, level=0):
# for making the XML output readable
@@ -85,7 +86,7 @@ class ComicInfoXml:
def assign(cix_entry, md_entry):
if md_entry is not None:
- ET.SubElement(root, cix_entry).text = u"{0}".format(md_entry)
+ ET.SubElement(root, cix_entry).text = "{0}".format(md_entry)
assign('Title', md.title)
assign('Series', md.series)
diff --git a/comicapi/filenameparser.py b/comicapi/filenameparser.py
index 5bfbf26..476d14b 100644
--- a/comicapi/filenameparser.py
+++ b/comicapi/filenameparser.py
@@ -22,7 +22,7 @@ This should probably be re-written, but, well, it mostly works!
import re
import os
-from urllib import unquote
+from urllib.parse import unquote
class FileNameParser:
diff --git a/comicapi/genericmetadata.py b/comicapi/genericmetadata.py
index f9d5a4d..b3679a2 100644
--- a/comicapi/genericmetadata.py
+++ b/comicapi/genericmetadata.py
@@ -20,7 +20,7 @@ possible, however lossy it might be
# See the License for the specific language governing permissions and
# limitations under the License.
-import utils
+from . import utils
class PageType:
@@ -251,7 +251,7 @@ class GenericMetadata:
return "No metadata"
def add_string(tag, val):
- if val is not None and u"{0}".format(val) != "":
+ if val is not None and "{0}".format(val) != "":
vals.append((tag, val))
def add_attr_string(tag):
@@ -314,7 +314,7 @@ class GenericMetadata:
# format the data nicely
outstr = ""
- fmt_str = u"{0: <" + str(flen) + "} {1}\n"
+ fmt_str = "{0: <" + str(flen) + "} {1}\n"
for i in vals:
outstr += fmt_str.format(i[0] + ":", i[1])
diff --git a/comicapi/issuestring.py b/comicapi/issuestring.py
index 1a561c0..2f441c1 100644
--- a/comicapi/issuestring.py
+++ b/comicapi/issuestring.py
@@ -44,7 +44,7 @@ class IssueString:
if len(text) == 0:
return
- text = unicode(text)
+ text = str(text)
# skip the minus sign if it's first
if text[0] == '-':
@@ -119,7 +119,7 @@ class IssueString:
def asFloat(self):
# return the float, with no suffix
- if self.suffix == u"½":
+ if self.suffix == "½":
if self.num is not None:
return self.num + .5
else:
diff --git a/comicapi/utils.py b/comicapi/utils.py
index f82309f..bd6facb 100644
--- a/comicapi/utils.py
+++ b/comicapi/utils.py
@@ -55,20 +55,22 @@ def get_recursive_filelist(pathlist):
# if path is a folder, walk it recursively, and all files underneath
if isinstance(p, str):
# make sure string is unicode
- p = p.decode(filename_encoding) # , 'replace')
- elif not isinstance(p, unicode):
+ #p = p.decode(filename_encoding) # , 'replace')
+ pass
+ elif not isinstance(p, str):
# it's probably a QString
- p = unicode(p)
+ p = str(p)
if os.path.isdir(p):
for root, dirs, files in os.walk(p):
for f in files:
if isinstance(f, str):
# make sure string is unicode
- f = f.decode(filename_encoding, 'replace')
- elif not isinstance(f, unicode):
+ #f = f.decode(filename_encoding, 'replace')
+ pass
+ elif not isinstance(f, str):
# it's probably a QString
- f = unicode(f)
+ f = str(f)
filelist.append(os.path.join(root, f))
else:
filelist.append(p)
@@ -121,7 +123,7 @@ def which(program):
def removearticles(text):
text = text.lower()
- articles = ['and', 'the', 'a', '&', 'issue']
+ articles = ['and', 'a', '&', 'issue']
newText = ''
for word in text.split(' '):
if word not in articles:
diff --git a/comictagger.py b/comictagger.py
index 3525092..e85ab3f 100755
--- a/comictagger.py
+++ b/comictagger.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
from comictaggerlib.main import ctmain
if __name__ == '__main__':
diff --git a/comictagger.spec b/comictagger.spec
index 117a547..518dcd3 100644
--- a/comictagger.spec
+++ b/comictagger.spec
@@ -1,10 +1,22 @@
# -*- mode: python -*-
+import platform
+
block_cipher = None
+binaries = [
+ ('./unrar/libunrar.so', './'),
+]
+
+if platform.system() == "Windows":
+ # add ssl qt libraries not discovered automatically
+ binaries.extend([
+ ('./venv/Lib/site-packages/PyQt5/Qt/bin/libeay32.dll', './PyQt5/Qt/bin'),
+ ('./venv/Lib/site-packages/PyQt5/Qt/bin/ssleay32.dll', './PyQt5/Qt/bin')
+ ])
a = Analysis(['comictagger.py'],
- binaries=[('./unrar/libunrar.so', './')],
+ binaries=binaries,
datas=[('comictaggerlib/ui/*.ui', 'ui'), ('comictaggerlib/graphics', 'graphics')],
hiddenimports=['PIL'],
hookspath=[],
diff --git a/comictaggerlib/autotagmatchwindow.py b/comictaggerlib/autotagmatchwindow.py
index 4c2c2bb..4338176 100644
--- a/comictaggerlib/autotagmatchwindow.py
+++ b/comictaggerlib/autotagmatchwindow.py
@@ -17,19 +17,19 @@
import os
#import sys
-from PyQt4 import QtCore, QtGui, uic
-#from PyQt4.QtCore import QUrl, pyqtSignal, QByteArray
+from PyQt5 import QtCore, QtGui, QtWidgets, uic
+#from PyQt5.QtCore import QUrl, pyqtSignal, QByteArray
-from settings import ComicTaggerSettings
-from comicarchive import MetaDataStyle
-from coverimagewidget import CoverImageWidget
+from .settings import ComicTaggerSettings
+from .comicarchive import MetaDataStyle
+from .coverimagewidget import CoverImageWidget
from comictaggerlib.ui.qtutils import reduceWidgetFontSize
#from imagefetcher import ImageFetcher
#from comicvinetalker import ComicVineTalker
#import utils
-class AutoTagMatchWindow(QtGui.QDialog):
+class AutoTagMatchWindow(QtWidgets.QDialog):
volume_id = 0
@@ -41,13 +41,13 @@ class AutoTagMatchWindow(QtGui.QDialog):
self.altCoverWidget = CoverImageWidget(
self.altCoverContainer, CoverImageWidget.AltCoverMode)
- gridlayout = QtGui.QGridLayout(self.altCoverContainer)
+ gridlayout = QtWidgets.QGridLayout(self.altCoverContainer)
gridlayout.addWidget(self.altCoverWidget)
gridlayout.setContentsMargins(0, 0, 0, 0)
self.archiveCoverWidget = CoverImageWidget(
self.archiveCoverContainer, CoverImageWidget.ArchiveMode)
- gridlayout = QtGui.QGridLayout(self.archiveCoverContainer)
+ gridlayout = QtWidgets.QGridLayout(self.archiveCoverContainer)
gridlayout.addWidget(self.archiveCoverWidget)
gridlayout.setContentsMargins(0, 0, 0, 0)
@@ -58,10 +58,10 @@ class AutoTagMatchWindow(QtGui.QDialog):
QtCore.Qt.WindowSystemMenuHint |
QtCore.Qt.WindowMaximizeButtonHint)
- self.skipButton = QtGui.QPushButton(self.tr("Skip to Next"))
+ self.skipButton = QtWidgets.QPushButton(self.tr("Skip to Next"))
self.buttonBox.addButton(
- self.skipButton, QtGui.QDialogButtonBox.ActionRole)
- self.buttonBox.button(QtGui.QDialogButtonBox.Ok).setText(
+ self.skipButton, QtWidgets.QDialogButtonBox.ActionRole)
+ self.buttonBox.button(QtWidgets.QDialogButtonBox.Ok).setText(
"Accept and Write Tags")
self.match_set_list = match_set_list
@@ -83,8 +83,8 @@ class AutoTagMatchWindow(QtGui.QDialog):
if self.current_match_set_idx + 1 == len(self.match_set_list):
self.buttonBox.button(
- QtGui.QDialogButtonBox.Cancel).setDisabled(True)
- # self.buttonBox.button(QtGui.QDialogButtonBox.Ok).setText("Accept")
+ QtWidgets.QDialogButtonBox.Cancel).setDisabled(True)
+ # self.buttonBox.button(QtWidgets.QDialogButtonBox.Ok).setText("Accept")
self.skipButton.setText(self.tr("Skip"))
self.setCoverImage()
@@ -94,7 +94,7 @@ class AutoTagMatchWindow(QtGui.QDialog):
path = self.current_match_set.ca.path
self.setWindowTitle(
- u"Select correct match or skip ({0} of {1}): {2}".format(
+ "Select correct match or skip ({0} of {1}): {2}".format(
self.current_match_set_idx + 1,
len(self.match_set_list),
os.path.split(path)[1])
@@ -112,30 +112,30 @@ class AutoTagMatchWindow(QtGui.QDialog):
self.twList.insertRow(row)
item_text = match['series']
- item = QtGui.QTableWidgetItem(item_text)
+ item = QtWidgets.QTableWidgetItem(item_text)
item.setData(QtCore.Qt.ToolTipRole, item_text)
item.setData(QtCore.Qt.UserRole, (match,))
item.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled)
self.twList.setItem(row, 0, item)
if match['publisher'] is not None:
- item_text = u"{0}".format(match['publisher'])
+ item_text = "{0}".format(match['publisher'])
else:
- item_text = u"Unknown"
- item = QtGui.QTableWidgetItem(item_text)
+ item_text = "Unknown"
+ item = QtWidgets.QTableWidgetItem(item_text)
item.setData(QtCore.Qt.ToolTipRole, item_text)
item.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled)
self.twList.setItem(row, 1, item)
- month_str = u""
- year_str = u"????"
+ month_str = ""
+ year_str = "????"
if match['month'] is not None:
- month_str = u"-{0:02d}".format(int(match['month']))
+ month_str = "-{0:02d}".format(int(match['month']))
if match['year'] is not None:
- year_str = u"{0}".format(match['year'])
+ year_str = "{0}".format(match['year'])
item_text = year_str + month_str
- item = QtGui.QTableWidgetItem(item_text)
+ item = QtWidgets.QTableWidgetItem(item_text)
item.setData(QtCore.Qt.ToolTipRole, item_text)
item.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled)
self.twList.setItem(row, 2, item)
@@ -143,7 +143,7 @@ class AutoTagMatchWindow(QtGui.QDialog):
item_text = match['issue_title']
if item_text is None:
item_text = ""
- item = QtGui.QTableWidgetItem(item_text)
+ item = QtWidgets.QTableWidgetItem(item_text)
item.setData(QtCore.Qt.ToolTipRole, item_text)
item.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled)
self.twList.setItem(row, 3, item)
@@ -180,7 +180,7 @@ class AutoTagMatchWindow(QtGui.QDialog):
def currentMatch(self):
row = self.twList.currentRow()
match = self.twList.item(row, 0).data(
- QtCore.Qt.UserRole).toPyObject()[0]
+ QtCore.Qt.UserRole)[0]
return match
def accept(self):
@@ -190,7 +190,7 @@ class AutoTagMatchWindow(QtGui.QDialog):
if self.current_match_set_idx == len(self.match_set_list):
# no more items
- QtGui.QDialog.accept(self)
+ QtWidgets.QDialog.accept(self)
else:
self.updateData()
@@ -199,22 +199,22 @@ class AutoTagMatchWindow(QtGui.QDialog):
if self.current_match_set_idx == len(self.match_set_list):
# no more items
- QtGui.QDialog.reject(self)
+ QtWidgets.QDialog.reject(self)
else:
self.updateData()
def reject(self):
- reply = QtGui.QMessageBox.question(
+ reply = QtWidgets.QMessageBox.question(
self,
self.tr("Cancel Matching"),
self.tr("Are you sure you wish to cancel the matching process?"),
- QtGui.QMessageBox.Yes,
- QtGui.QMessageBox.No)
+ QtWidgets.QMessageBox.Yes,
+ QtWidgets.QMessageBox.No)
- if reply == QtGui.QMessageBox.No:
+ if reply == QtWidgets.QMessageBox.No:
return
- QtGui.QDialog.reject(self)
+ QtWidgets.QDialog.reject(self)
def saveMatch(self):
@@ -228,18 +228,18 @@ class AutoTagMatchWindow(QtGui.QDialog):
# now get the particular issue data
cv_md = self.fetch_func(match)
if cv_md is None:
- QtGui.QMessageBox.critical(self, self.tr("Network Issue"), self.tr(
+ QtWidgets.QMessageBox.critical(self, self.tr("Network Issue"), self.tr(
"Could not connect to Comic Vine to get issue details!"))
return
- QtGui.QApplication.setOverrideCursor(
+ QtWidgets.QApplication.setOverrideCursor(
QtGui.QCursor(QtCore.Qt.WaitCursor))
md.overlay(cv_md)
success = ca.writeMetadata(md, self.style)
ca.loadCache([MetaDataStyle.CBI, MetaDataStyle.CIX])
- QtGui.QApplication.restoreOverrideCursor()
+ QtWidgets.QApplication.restoreOverrideCursor()
if not success:
- QtGui.QMessageBox.warning(self, self.tr("Write Error"), self.tr(
+ QtWidgets.QMessageBox.warning(self, self.tr("Write Error"), self.tr(
"Saving the tags to the archive seemed to fail!"))
diff --git a/comictaggerlib/autotagprogresswindow.py b/comictaggerlib/autotagprogresswindow.py
index 2142c4b..a399546 100644
--- a/comictaggerlib/autotagprogresswindow.py
+++ b/comictaggerlib/autotagprogresswindow.py
@@ -17,15 +17,15 @@
#import sys
#import os
-from PyQt4 import QtCore, QtGui, uic
+from PyQt5 import QtCore, QtGui, QtWidgets, uic
-from settings import ComicTaggerSettings
-from coverimagewidget import CoverImageWidget
+from .settings import ComicTaggerSettings
+from .coverimagewidget import CoverImageWidget
from comictaggerlib.ui.qtutils import reduceWidgetFontSize
#import utils
-class AutoTagProgressWindow(QtGui.QDialog):
+class AutoTagProgressWindow(QtWidgets.QDialog):
def __init__(self, parent):
super(AutoTagProgressWindow, self).__init__(parent)
@@ -35,13 +35,13 @@ class AutoTagProgressWindow(QtGui.QDialog):
self.archiveCoverWidget = CoverImageWidget(
self.archiveCoverContainer, CoverImageWidget.DataMode, False)
- gridlayout = QtGui.QGridLayout(self.archiveCoverContainer)
+ gridlayout = QtWidgets.QGridLayout(self.archiveCoverContainer)
gridlayout.addWidget(self.archiveCoverWidget)
gridlayout.setContentsMargins(0, 0, 0, 0)
self.testCoverWidget = CoverImageWidget(
self.testCoverContainer, CoverImageWidget.DataMode, False)
- gridlayout = QtGui.QGridLayout(self.testCoverContainer)
+ gridlayout = QtWidgets.QGridLayout(self.testCoverContainer)
gridlayout.addWidget(self.testCoverWidget)
gridlayout.setContentsMargins(0, 0, 0, 0)
@@ -65,5 +65,5 @@ class AutoTagProgressWindow(QtGui.QDialog):
QtCore.QCoreApplication.processEvents()
def reject(self):
- QtGui.QDialog.reject(self)
+ QtWidgets.QDialog.reject(self)
self.isdone = True
diff --git a/comictaggerlib/autotagstartwindow.py b/comictaggerlib/autotagstartwindow.py
index bf1a584..653c369 100644
--- a/comictaggerlib/autotagstartwindow.py
+++ b/comictaggerlib/autotagstartwindow.py
@@ -16,15 +16,15 @@
#import os
-from PyQt4 import QtCore, QtGui, uic
+from PyQt5 import QtCore, QtGui, QtWidgets, uic
-from settings import ComicTaggerSettings
+from .settings import ComicTaggerSettings
#from settingswindow import SettingsWindow
#from filerenamer import FileRenamer
#import utils
-class AutoTagStartWindow(QtGui.QDialog):
+class AutoTagStartWindow(QtWidgets.QDialog):
def __init__(self, parent, settings, msg):
super(AutoTagStartWindow, self).__init__(parent)
@@ -102,7 +102,7 @@ class AutoTagStartWindow(QtGui.QDialog):
self.leSearchString.setEnabled(enable)
def accept(self):
- QtGui.QDialog.accept(self)
+ QtWidgets.QDialog.accept(self)
self.autoSaveOnLow = self.cbxSaveOnLowConfidence.isChecked()
self.dontUseYear = self.cbxDontUseYear.isChecked()
@@ -122,6 +122,6 @@ class AutoTagStartWindow(QtGui.QDialog):
self.settings.wait_and_retry_on_rate_limit = self.waitAndRetryOnRateLimit
if self.cbxSpecifySearchString.isChecked():
- self.searchString = unicode(self.leSearchString.text())
+ self.searchString = str(self.leSearchString.text())
if len(self.searchString) == 0:
self.searchString = None
diff --git a/comictaggerlib/cli.py b/comictaggerlib/cli.py
index 4061b5f..688907d 100644
--- a/comictaggerlib/cli.py
+++ b/comictaggerlib/cli.py
@@ -29,15 +29,15 @@ import json
filename_encoding = sys.getfilesystemencoding()
-from settings import ComicTaggerSettings
-from options import Options
-from comicarchive import ComicArchive, MetaDataStyle
-from issueidentifier import IssueIdentifier
-from genericmetadata import GenericMetadata
-from comicvinetalker import ComicVineTalker, ComicVineTalkerException
-from filerenamer import FileRenamer
-from cbltransformer import CBLTransformer
-import utils
+from .settings import ComicTaggerSettings
+from .options import Options
+from .comicarchive import ComicArchive, MetaDataStyle
+from .issueidentifier import IssueIdentifier
+from .genericmetadata import GenericMetadata
+from .comicvinetalker import ComicVineTalker, ComicVineTalkerException
+from .filerenamer import FileRenamer
+from .cbltransformer import CBLTransformer
+from . import utils
class MultipleMatch():
@@ -69,7 +69,7 @@ def actual_issue_data_fetch(match, settings, opts):
cv_md = comicVine.fetchIssueData(
match['volume_id'], match['issue_number'], settings)
except ComicVineTalkerException:
- print >> sys.stderr, "Network error while getting issue details. Save aborted"
+ print("Network error while getting issue details. Save aborted", file=sys.stderr)
return None
if settings.apply_cbl_transform_on_cv_import:
@@ -83,39 +83,39 @@ def actual_metadata_save(ca, opts, md):
if not opts.dryrun:
# write out the new data
if not ca.writeMetadata(md, opts.data_style):
- print >> sys.stderr, "The tag save seemed to fail!"
+ print("The tag save seemed to fail!", file=sys.stderr)
return False
else:
- print >> sys.stderr, "Save complete."
+ print("Save complete.", file=sys.stderr)
else:
if opts.terse:
- print >> sys.stderr, "dry-run option was set, so nothing was written"
+ print("dry-run option was set, so nothing was written", file=sys.stderr)
else:
- print >> sys.stderr, "dry-run option was set, so nothing was written, but here is the final set of tags:"
- print(u"{0}".format(md))
+ print("dry-run option was set, so nothing was written, but here is the final set of tags:", file=sys.stderr)
+ print(("{0}".format(md)))
return True
def display_match_set_for_choice(label, match_set, opts, settings):
- print(u"{0} -- {1}:".format(match_set.filename, label))
+ print(("{0} -- {1}:".format(match_set.filename, label)))
# sort match list by year
match_set.matches.sort(key=lambda k: k['year'])
for (counter, m) in enumerate(match_set.matches):
counter += 1
- print(
- u" {0}. {1} #{2} [{3}] ({4}/{5}) - {6}".format(
+ print((
+ " {0}. {1} #{2} [{3}] ({4}/{5}) - {6}".format(
counter,
m['series'],
m['issue_number'],
m['publisher'],
m['month'],
m['year'],
- m['issue_title']))
+ m['issue_title'])))
if opts.interactive:
while True:
- i = raw_input("Choose a match #, or 's' to skip: ")
+ i = input("Choose a match #, or 's' to skip: ")
if (i.isdigit() and int(i) in range(
1, len(match_set.matches) + 1)) or i == 's':
break
@@ -182,14 +182,14 @@ def post_process_matches(match_results, opts, settings):
def cli_mode(opts, settings):
if len(opts.file_list) < 1:
- print >> sys.stderr, "You must specify at least one filename. Use the -h option for more info"
+ print("You must specify at least one filename. Use the -h option for more info", file=sys.stderr)
return
match_results = OnlineMatchResults()
for f in opts.file_list:
if isinstance(f, str):
- f = f.decode(filename_encoding, 'replace')
+ pass
process_file_cli(f, opts, settings, match_results)
sys.stdout.flush()
@@ -225,19 +225,19 @@ def process_file_cli(filename, opts, settings, match_results):
ComicTaggerSettings.getGraphic('nocover.png'))
if not os.path.lexists(filename):
- print >> sys.stderr, "Cannot find " + filename
+ print("Cannot find " + filename, file=sys.stderr)
return
if not ca.seemsToBeAComicArchive():
- print >> sys.stderr, "Sorry, but " + \
- filename + " is not a comic archive!"
+ print("Sorry, but " + \
+ filename + " is not a comic archive!", file=sys.stderr)
return
# if not ca.isWritableForStyle(opts.data_style) and (opts.delete_tags or
# opts.save_tags or opts.rename_file):
if not ca.isWritable() and (
opts.delete_tags or opts.copy_tags or opts.save_tags or opts.rename_file):
- print >> sys.stderr, "This archive is not writable for that tag type"
+ print("This archive is not writable for that tag type", file=sys.stderr)
return
has = [False, False, False]
@@ -256,7 +256,7 @@ def process_file_cli(filename, opts, settings, match_results):
brief = ""
if batch_mode:
- brief = u"{0}: ".format(filename)
+ brief = "{0}: ".format(filename)
if ca.isZip():
brief += "ZIP archive "
@@ -280,24 +280,24 @@ def process_file_cli(filename, opts, settings, match_results):
brief += "CoMet "
brief += "]"
- print brief
+ print(brief)
if opts.terse:
return
- print
+ print()
if opts.data_style is None or opts.data_style == MetaDataStyle.CIX:
if has[MetaDataStyle.CIX]:
print("--------- ComicRack tags ---------")
if opts.raw:
- print(
- u"{0}".format(
- unicode(
+ print((
+ "{0}".format(
+ str(
ca.readRawCIX(),
- errors='ignore')))
+ errors='ignore'))))
else:
- print(u"{0}".format(ca.readCIX()))
+ print(("{0}".format(ca.readCIX())))
if opts.data_style is None or opts.data_style == MetaDataStyle.CBI:
if has[MetaDataStyle.CBI]:
@@ -305,43 +305,43 @@ def process_file_cli(filename, opts, settings, match_results):
if opts.raw:
pprint(json.loads(ca.readRawCBI()))
else:
- print(u"{0}".format(ca.readCBI()))
+ print(("{0}".format(ca.readCBI())))
if opts.data_style is None or opts.data_style == MetaDataStyle.COMET:
if has[MetaDataStyle.COMET]:
print("----------- CoMet tags -----------")
if opts.raw:
- print(u"{0}".format(ca.readRawCoMet()))
+ print(("{0}".format(ca.readRawCoMet())))
else:
- print(u"{0}".format(ca.readCoMet()))
+ print(("{0}".format(ca.readCoMet())))
elif opts.delete_tags:
style_name = MetaDataStyle.name[opts.data_style]
if has[opts.data_style]:
if not opts.dryrun:
if not ca.removeMetadata(opts.data_style):
- print(u"{0}: Tag removal seemed to fail!".format(filename))
+ print(("{0}: Tag removal seemed to fail!".format(filename)))
else:
- print(
- u"{0}: Removed {1} tags.".format(filename, style_name))
+ print((
+ "{0}: Removed {1} tags.".format(filename, style_name)))
else:
- print(
- u"{0}: dry-run. {1} tags not removed".format(filename, style_name))
+ print((
+ "{0}: dry-run. {1} tags not removed".format(filename, style_name)))
else:
- print(u"{0}: This archive doesn't have {1} tags to remove.".format(
- filename, style_name))
+ print(("{0}: This archive doesn't have {1} tags to remove.".format(
+ filename, style_name)))
elif opts.copy_tags:
dst_style_name = MetaDataStyle.name[opts.data_style]
if opts.no_overwrite and has[opts.data_style]:
- print(u"{0}: Already has {1} tags. Not overwriting.".format(
- filename, dst_style_name))
+ print(("{0}: Already has {1} tags. Not overwriting.".format(
+ filename, dst_style_name)))
return
if opts.copy_source == opts.data_style:
- print(
- u"{0}: Destination and source are same: {1}. Nothing to do.".format(
+ print((
+ "{0}: Destination and source are same: {1}. Nothing to do.".format(
filename,
- dst_style_name))
+ dst_style_name)))
return
src_style_name = MetaDataStyle.name[opts.copy_source]
@@ -353,26 +353,26 @@ def process_file_cli(filename, opts, settings, match_results):
md = CBLTransformer(md, settings).apply()
if not ca.writeMetadata(md, opts.data_style):
- print(u"{0}: Tag copy seemed to fail!".format(filename))
+ print(("{0}: Tag copy seemed to fail!".format(filename)))
else:
- print(u"{0}: Copied {1} tags to {2} .".format(
- filename, src_style_name, dst_style_name))
+ print(("{0}: Copied {1} tags to {2} .".format(
+ filename, src_style_name, dst_style_name)))
else:
- print(
- u"{0}: dry-run. {1} tags not copied".format(filename, src_style_name))
+ print((
+ "{0}: dry-run. {1} tags not copied".format(filename, src_style_name)))
else:
- print(u"{0}: This archive doesn't have {1} tags to copy.".format(
- filename, src_style_name))
+ print(("{0}: This archive doesn't have {1} tags to copy.".format(
+ filename, src_style_name)))
elif opts.save_tags:
if opts.no_overwrite and has[opts.data_style]:
- print(u"{0}: Already has {1} tags. Not overwriting.".format(
- filename, MetaDataStyle.name[opts.data_style]))
+ print(("{0}: Already has {1} tags. Not overwriting.".format(
+ filename, MetaDataStyle.name[opts.data_style])))
return
if batch_mode:
- print(u"Processing {0}...".format(filename))
+ print(("Processing {0}...".format(filename)))
md = create_local_metadata(opts, ca, has[opts.data_style])
if md.issue is None or md.issue == "":
@@ -389,13 +389,13 @@ def process_file_cli(filename, opts, settings, match_results):
cv_md = comicVine.fetchIssueDataByIssueID(
opts.issue_id, settings)
except ComicVineTalkerException:
- print >> sys.stderr, "Network error while getting issue details. Save aborted"
+ print("Network error while getting issue details. Save aborted", file=sys.stderr)
match_results.fetchDataFailures.append(filename)
return
if cv_md is None:
- print >> sys.stderr, "No match for ID {0} was found.".format(
- opts.issue_id)
+ print("No match for ID {0} was found.".format(
+ opts.issue_id), file=sys.stderr)
match_results.noMatches.append(filename)
return
@@ -405,7 +405,7 @@ def process_file_cli(filename, opts, settings, match_results):
ii = IssueIdentifier(ca, settings)
if md is None or md.isEmpty:
- print >> sys.stderr, "No metadata given to search online with!"
+ print("No metadata given to search online with!", file=sys.stderr)
match_results.noMatches.append(filename)
return
@@ -444,22 +444,22 @@ def process_file_cli(filename, opts, settings, match_results):
if choices:
if low_confidence:
- print >> sys.stderr, "Online search: Multiple low confidence matches. Save aborted"
+ print("Online search: Multiple low confidence matches. Save aborted", file=sys.stderr)
match_results.lowConfidenceMatches.append(
MultipleMatch(filename, matches))
return
else:
- print >> sys.stderr, "Online search: Multiple good matches. Save aborted"
+ print("Online search: Multiple good matches. Save aborted", file=sys.stderr)
match_results.multipleMatches.append(
MultipleMatch(filename, matches))
return
if low_confidence and opts.abortOnLowConfidence:
- print >> sys.stderr, "Online search: Low confidence match. Save aborted"
+ print("Online search: Low confidence match. Save aborted", file=sys.stderr)
match_results.lowConfidenceMatches.append(
MultipleMatch(filename, matches))
return
if not found_match:
- print >> sys.stderr, "Online search: No match found. Save aborted"
+ print("Online search: No match found. Save aborted", file=sys.stderr)
match_results.noMatches.append(filename)
return
@@ -483,7 +483,7 @@ def process_file_cli(filename, opts, settings, match_results):
msg_hdr = ""
if batch_mode:
- msg_hdr = u"{0}: ".format(filename)
+ msg_hdr = "{0}: ".format(filename)
if opts.data_style is not None:
use_tags = has[opts.data_style]
@@ -493,7 +493,7 @@ def process_file_cli(filename, opts, settings, match_results):
md = create_local_metadata(opts, ca, use_tags)
if md.series is None:
- print >> sys.stderr, msg_hdr + "Can't rename without series name"
+ print(msg_hdr + "Can't rename without series name", file=sys.stderr)
return
new_ext = None # default
@@ -511,7 +511,7 @@ def process_file_cli(filename, opts, settings, match_results):
new_name = renamer.determineName(filename, ext=new_ext)
if new_name == os.path.basename(filename):
- print >> sys.stderr, msg_hdr + "Filename is already good!"
+ print(msg_hdr + "Filename is already good!", file=sys.stderr)
return
folder = os.path.dirname(os.path.abspath(filename))
@@ -524,23 +524,23 @@ def process_file_cli(filename, opts, settings, match_results):
else:
suffix = " (dry-run, no change)"
- print(
- u"renamed '{0}' -> '{1}' {2}".format(os.path.basename(filename), new_name, suffix))
+ print((
+ "renamed '{0}' -> '{1}' {2}".format(os.path.basename(filename), new_name, suffix)))
elif opts.export_to_zip:
msg_hdr = ""
if batch_mode:
- msg_hdr = u"{0}: ".format(filename)
+ msg_hdr = "{0}: ".format(filename)
if not ca.isRar():
- print >> sys.stderr, msg_hdr + "Archive is not a RAR."
+ print(msg_hdr + "Archive is not a RAR.", file=sys.stderr)
return
rar_file = os.path.abspath(os.path.abspath(filename))
new_file = os.path.splitext(rar_file)[0] + ".cbz"
if opts.abort_export_on_conflict and os.path.lexists(new_file):
- print msg_hdr + "{0} already exists in the that folder.".format(os.path.split(new_file)[1])
+ print(msg_hdr + "{0} already exists in the that folder.".format(os.path.split(new_file)[1]))
return
new_file = utils.unique_file(os.path.join(new_file))
@@ -554,8 +554,8 @@ def process_file_cli(filename, opts, settings, match_results):
try:
os.unlink(rar_file)
except:
- print >> sys.stderr, msg_hdr + \
- "Error deleting original RAR after export"
+ print(msg_hdr + \
+ "Error deleting original RAR after export", file=sys.stderr)
delete_success = False
else:
delete_success = True
@@ -565,20 +565,20 @@ def process_file_cli(filename, opts, settings, match_results):
os.remove(new_file)
else:
msg = msg_hdr + \
- u"Dry-run: Would try to create {0}".format(
+ "Dry-run: Would try to create {0}".format(
os.path.split(new_file)[1])
if opts.delete_rar_after_export:
- msg += u" and delete orginal."
+ msg += " and delete orginal."
print(msg)
return
msg = msg_hdr
if export_success:
- msg += u"Archive exported successfully to: {0}".format(
+ msg += "Archive exported successfully to: {0}".format(
os.path.split(new_file)[1])
if opts.delete_rar_after_export and delete_success:
- msg += u" (Original deleted) "
+ msg += " (Original deleted) "
else:
- msg += u"Archive failed to export!"
+ msg += "Archive failed to export!"
print(msg)
diff --git a/comictaggerlib/comicvinecacher.py b/comictaggerlib/comicvinecacher.py
index b9dd4f3..de834a0 100644
--- a/comictaggerlib/comicvinecacher.py
+++ b/comictaggerlib/comicvinecacher.py
@@ -20,9 +20,9 @@ import datetime
#import sys
#from pprint import pprint
-import ctversion
-from settings import ComicTaggerSettings
-import utils
+from . import ctversion
+from .settings import ComicTaggerSettings
+from . import utils
class ComicVineCacher:
@@ -37,7 +37,7 @@ class ComicVineCacher:
data = ""
try:
with open(self.version_file, 'rb') as f:
- data = f.read()
+ data = f.read().decode("utf-8")
f.close()
except:
pass
@@ -121,7 +121,7 @@ class ComicVineCacher:
con = lite.connect(self.db_file)
with con:
- con.text_factory = unicode
+ con.text_factory = str
cur = con.cursor()
# remove all previous entries with this search term
@@ -161,7 +161,7 @@ class ComicVineCacher:
results = list()
con = lite.connect(self.db_file)
with con:
- con.text_factory = unicode
+ con.text_factory = str
cur = con.cursor()
# purge stale search results
@@ -197,7 +197,7 @@ class ComicVineCacher:
con = lite.connect(self.db_file)
with con:
- con.text_factory = unicode
+ con.text_factory = str
cur = con.cursor()
# remove all previous entries with this search term
@@ -217,7 +217,7 @@ class ComicVineCacher:
con = lite.connect(self.db_file)
with con:
cur = con.cursor()
- con.text_factory = unicode
+ con.text_factory = str
# purge stale issue info - probably issue data won't change
# much....
@@ -300,7 +300,7 @@ class ComicVineCacher:
con = lite.connect(self.db_file)
with con:
cur = con.cursor()
- con.text_factory = unicode
+ con.text_factory = str
# purge stale volume info
a_week_ago = datetime.datetime.today() - datetime.timedelta(days=7)
@@ -337,7 +337,7 @@ class ComicVineCacher:
con = lite.connect(self.db_file)
with con:
cur = con.cursor()
- con.text_factory = unicode
+ con.text_factory = str
# purge stale issue info - probably issue data won't change
# much....
@@ -386,7 +386,7 @@ class ComicVineCacher:
with con:
cur = con.cursor()
- con.text_factory = unicode
+ con.text_factory = str
timestamp = datetime.datetime.now()
data = {
@@ -403,7 +403,7 @@ class ComicVineCacher:
con = lite.connect(self.db_file)
with con:
cur = con.cursor()
- con.text_factory = unicode
+ con.text_factory = str
cur.execute(
"SELECT super_url,thumb_url,cover_date,site_detail_url FROM Issues WHERE id=?",
diff --git a/comictaggerlib/comicvinetalker.py b/comictaggerlib/comicvinetalker.py
index 021d27a..7069a29 100644
--- a/comictaggerlib/comicvinetalker.py
+++ b/comictaggerlib/comicvinetalker.py
@@ -15,8 +15,8 @@
# limitations under the License.
import json
-import urllib2
-import urllib
+import urllib.request, urllib.error, urllib.parse
+import urllib.request, urllib.parse, urllib.error
import re
import time
import datetime
@@ -28,8 +28,8 @@ import ssl
from bs4 import BeautifulSoup
try:
- from PyQt4.QtNetwork import QNetworkAccessManager, QNetworkRequest
- from PyQt4.QtCore import QUrl, pyqtSignal, QObject, QByteArray
+ from PyQt5.QtNetwork import QNetworkAccessManager, QNetworkRequest
+ from PyQt5.QtCore import QUrl, pyqtSignal, QObject, QByteArray
except ImportError:
# No Qt, so define a few dummy QObjects to help us compile
class QObject():
@@ -45,11 +45,11 @@ except ImportError:
def emit(a, b, c):
pass
-import ctversion
-import utils
-from comicvinecacher import ComicVineCacher
-from genericmetadata import GenericMetadata
-from issuestring import IssueString
+from . import ctversion
+from . import utils
+from .comicvinecacher import ComicVineCacher
+from .genericmetadata import GenericMetadata
+from .issuestring import IssueString
#from settings import ComicTaggerSettings
@@ -114,7 +114,7 @@ class ComicVineTalker(QObject):
if self.log_func is None:
# sys.stdout.write(text.encode(errors='replace'))
# sys.stdout.flush()
- print >> sys.stderr, text
+ print(text, file=sys.stderr)
else:
self.log_func(text)
@@ -133,16 +133,19 @@ class ComicVineTalker(QObject):
def testKey(self, key):
- test_url = self.api_base_url + "/issue/1/?api_key=" + \
- key + "&format=json&field_list=name"
- resp = urllib2.urlopen(test_url, context=self.ssl)
- content = resp.read()
-
- cv_response = json.loads(content)
-
- # Bogus request, but if the key is wrong, you get error 100: "Invalid
- # API Key"
- return cv_response['status_code'] != 100
+ try:
+ test_url = self.api_base_url + "/issue/1/?api_key=" + \
+ key + "&format=json&field_list=name"
+ resp = urllib.request.urlopen(test_url, context=self.ssl)
+ content = resp.read()
+
+ cv_response = json.loads(content.decode('utf-8'))
+
+ # Bogus request, but if the key is wrong, you get error 100: "Invalid
+ # API Key"
+ return cv_response['status_code'] != 100
+ except:
+ return False
"""
Get the contect from the CV server. If we're in "wait mode" and status code is a rate limit error
@@ -156,7 +159,7 @@ class ComicVineTalker(QObject):
wait_times = [1, 2, 3, 4]
while True:
content = self.getUrlContent(url)
- cv_response = json.loads(content)
+ cv_response = json.loads(content.decode('utf-8'))
if self.wait_for_rate_limit and cv_response[
'status_code'] == ComicVineTalkerException.RateLimit:
self.writeLog(
@@ -188,9 +191,9 @@ class ComicVineTalker(QObject):
# print "ATB---", url
for tries in range(3):
try:
- resp = urllib2.urlopen(url, context=self.ssl)
+ resp = urllib.request.urlopen(url, context=self.ssl)
return resp.read()
- except urllib2.HTTPError as e:
+ except urllib.error.HTTPError as e:
if e.getcode() == 500:
self.writeLog("Try #{0}: ".format(tries + 1))
time.sleep(1)
@@ -223,19 +226,12 @@ class ComicVineTalker(QObject):
original_series_name = series_name
- # We need to make the series name into an "AND"ed query list
+ # Split and rejoin to remove extra internal spaces
query_word_list = series_name.split()
- and_list = ['AND'] * (len(query_word_list) - 1)
- and_list.append('')
- # zipper up the two lists
- query_list = zip(query_word_list, and_list)
- # flatten the list
- query_list = [item for sublist in query_list for item in sublist]
- # convert back to a string
- query_string = " ".join(query_list).strip()
- # print "Query string = ", query_string
+ query_string = " ".join( query_word_list ).strip()
+ #print "Query string = ", query_string
- query_string = urllib.quote_plus(query_string.encode("utf-8"))
+ query_string = urllib.parse.quote_plus(query_string.encode("utf-8"))
search_url = self.api_base_url + "/search/?api_key=" + self.api_key + "&format=json&resources=volume&query=" + \
query_string + \
@@ -369,7 +365,7 @@ class ComicVineTalker(QObject):
year_filter = ",cover_date:{0}-1-1|{1}-1-1".format(
year, int(year) + 1)
- issue_number = urllib.quote_plus(unicode(issue_number).encode("utf-8"))
+ issue_number = urllib.parse.quote_plus(str(issue_number).encode("utf-8"))
filter = "&filter=" + volume_filter + \
year_filter + ",issue_number:" + issue_number
@@ -532,7 +528,7 @@ class ComicVineTalker(QObject):
if string is None:
return ""
# find any tables
- soup = BeautifulSoup(string)
+ soup = BeautifulSoup(string, "html.parser")
tables = soup.findAll('table')
# remove all newlines first
@@ -592,7 +588,7 @@ class ComicVineTalker(QObject):
for w in col_widths:
fmtstr += " {{:{}}}|".format(w + 1)
width = sum(col_widths) + len(col_widths) * 2
- print "width=", width
+ print("width=", width)
table_text = ""
counter = 0
for row in rows:
@@ -678,7 +674,7 @@ class ComicVineTalker(QObject):
return url_list
# scrape the CV issue page URL to get the alternate cover URLs
- resp = urllib2.urlopen(issue_page_url, context=self.ssl)
+ resp = urllib.request.urlopen(issue_page_url, context=self.ssl)
content = resp.read()
alt_cover_url_list = self.parseOutAltCoverUrls(content)
@@ -688,23 +684,27 @@ class ComicVineTalker(QObject):
return alt_cover_url_list
def parseOutAltCoverUrls(self, page_html):
- soup = BeautifulSoup(page_html)
-
+ soup = BeautifulSoup(page_html, "html.parser")
+
alt_cover_url_list = []
-
+
# Using knowledge of the layout of the Comic Vine issue page here:
- # look for the divs that are in the classes 'content-pod' and
- # 'alt-cover'
+ # look for the divs that are in the classes 'imgboxart' and
+ # 'issue-cover'
div_list = soup.find_all('div')
covers_found = 0
for d in div_list:
- if 'class' in d:
+ if 'class' in d.attrs:
c = d['class']
- if 'imgboxart' in c and 'issue-cover' in c:
+ if ('imgboxart' in c and
+ 'issue-cover' in c and
+ d.img['src'].startswith("http")
+ ):
+
covers_found += 1
if covers_found != 1:
- alt_cover_url_list.append(d.img['src'])
-
+ alt_cover_url_list.append(d.img['src'])
+
return alt_cover_url_list
def fetchCachedAlternateCoverURLs(self, issue_id):
@@ -749,15 +749,15 @@ class ComicVineTalker(QObject):
data = reply.readAll()
try:
- cv_response = json.loads(str(data))
- except:
- print >> sys.stderr, "Comic Vine query failed to get JSON data"
- print >> sys.stderr, str(data)
+ cv_response = json.loads(bytes(data))
+ except Exception as e:
+ print("Comic Vine query failed to get JSON data", file=sys.stderr)
+ print(str(data), file=sys.stderr)
return
if cv_response['status_code'] != 1:
- print >> sys.stderr, "Comic Vine query failed with error: [{0}]. ".format(
- cv_response['error'])
+ print("Comic Vine query failed with error: [{0}]. ".format(
+ cv_response['error']), file=sys.stderr)
return
image_url = cv_response['results']['image']['super_url']
diff --git a/comictaggerlib/coverimagewidget.py b/comictaggerlib/coverimagewidget.py
index e05ae83..866c779 100644
--- a/comictaggerlib/coverimagewidget.py
+++ b/comictaggerlib/coverimagewidget.py
@@ -1,4 +1,4 @@
-"""A PyQt4 widget to display cover images
+"""A PyQt5 widget to display cover images
Display cover images from either a local archive, or from Comic Vine.
TODO: This should be re-factored using subclasses!
@@ -20,15 +20,16 @@ TODO: This should be re-factored using subclasses!
#import os
-from PyQt4.QtCore import *
-from PyQt4.QtGui import *
-from PyQt4 import uic
+from PyQt5.QtCore import *
+from PyQt5.QtWidgets import *
+from PyQt5.QtGui import *
+from PyQt5 import uic
-from settings import ComicTaggerSettings
-from comicvinetalker import ComicVineTalker, ComicVineTalkerException
-from imagefetcher import ImageFetcher
-from pageloader import PageLoader
-from imagepopup import ImagePopup
+from .settings import ComicTaggerSettings
+from .comicvinetalker import ComicVineTalker, ComicVineTalkerException
+from .imagefetcher import ImageFetcher
+from .pageloader import PageLoader
+from .imagepopup import ImagePopup
from comictaggerlib.ui.qtutils import reduceWidgetFontSize, getQImageFromData
#from genericmetadata import GenericMetadata, PageType
#from comicarchive import MetaDataStyle
diff --git a/comictaggerlib/crediteditorwindow.py b/comictaggerlib/crediteditorwindow.py
index 0f69ca0..08e33ae 100644
--- a/comictaggerlib/crediteditorwindow.py
+++ b/comictaggerlib/crediteditorwindow.py
@@ -16,12 +16,12 @@
#import os
-from PyQt4 import QtCore, QtGui, uic
+from PyQt5 import QtCore, QtGui, QtWidgets, uic
-from settings import ComicTaggerSettings
+from .settings import ComicTaggerSettings
-class CreditEditorWindow(QtGui.QDialog):
+class CreditEditorWindow(QtWidgets.QDialog):
ModeEdit = 0
ModeNew = 1
@@ -90,7 +90,7 @@ class CreditEditorWindow(QtGui.QDialog):
def accept(self):
if self.cbRole.currentText() == "" or self.leName.text() == "":
- QtGui.QMessageBox.warning(self, self.tr("Whoops"), self.tr(
+ QtWidgets.QMessageBox.warning(self, self.tr("Whoops"), self.tr(
"You need to enter both role and name for a credit."))
else:
- QtGui.QDialog.accept(self)
+ QtWidgets.QDialog.accept(self)
diff --git a/comictaggerlib/ctversion.py b/comictaggerlib/ctversion.py
index 2d513cb..642eb17 100644
--- a/comictaggerlib/ctversion.py
+++ b/comictaggerlib/ctversion.py
@@ -1,3 +1,3 @@
# This file should contain only these comments, and the line below.
# Used by packaging makefiles and app
-version = "1.1.20-SNAPSHOT"
+version = "1.1.30-rc1"
diff --git a/comictaggerlib/exportwindow.py b/comictaggerlib/exportwindow.py
index b85318b..4fd7b44 100644
--- a/comictaggerlib/exportwindow.py
+++ b/comictaggerlib/exportwindow.py
@@ -16,9 +16,9 @@
#import os
-from PyQt4 import QtCore, QtGui, uic
+from PyQt5 import QtCore, QtGui, QtWidgets, uic
-from settings import ComicTaggerSettings
+from .settings import ComicTaggerSettings
#from settingswindow import SettingsWindow
#from filerenamer import FileRenamer
#import utils
@@ -30,7 +30,7 @@ class ExportConflictOpts:
createUnique = 3
-class ExportWindow(QtGui.QDialog):
+class ExportWindow(QtWidgets.QDialog):
def __init__(self, parent, settings, msg):
super(ExportWindow, self).__init__(parent)
@@ -52,7 +52,7 @@ class ExportWindow(QtGui.QDialog):
self.fileConflictBehavior = ExportConflictOpts.dontCreate
def accept(self):
- QtGui.QDialog.accept(self)
+ QtWidgets.QDialog.accept(self)
self.deleteOriginal = self.cbxDeleteOriginal.isChecked()
self.addToList = self.cbxAddToList.isChecked()
diff --git a/comictaggerlib/filerenamer.py b/comictaggerlib/filerenamer.py
index 5c2e99c..0f18151 100644
--- a/comictaggerlib/filerenamer.py
+++ b/comictaggerlib/filerenamer.py
@@ -18,8 +18,8 @@ import os
import re
import datetime
-import utils
-from issuestring import IssueString
+from . import utils
+from .issuestring import IssueString
class FileRenamer:
@@ -49,7 +49,7 @@ class FileRenamer:
return (word[0] == "%" and word[-1:] == "%")
if value is not None:
- return text.replace(token, unicode(value))
+ return text.replace(token, str(value))
else:
if self.smart_cleanup:
# smart cleanup means we want to remove anything appended to token if it's empty
@@ -81,7 +81,7 @@ class FileRenamer:
new_name = self.replaceToken(new_name, md.volume, '%volume%')
if md.issue is not None:
- issue_str = u"{0}".format(
+ issue_str = "{0}".format(
IssueString(md.issue).asString(pad=self.issue_zero_padding))
else:
issue_str = None
@@ -98,8 +98,8 @@ class FileRenamer:
md.month, int):
if int(md.month) in range(1, 13):
dt = datetime.datetime(1970, int(md.month), 1, 0, 0)
- month_name = dt.strftime(
- u"%B".encode(preferred_encoding)).decode(preferred_encoding)
+ #month_name = dt.strftime("%B".encode(preferred_encoding)).decode(preferred_encoding)
+ month_name = dt.strftime("%B")
new_name = self.replaceToken(new_name, month_name, '%month_name%')
new_name = self.replaceToken(new_name, md.genre, '%genre%')
@@ -128,7 +128,7 @@ class FileRenamer:
new_name = re.sub("\{\s*[-:]*\s*\}", "", new_name)
# remove duplicate spaces
- new_name = u" ".join(new_name.split())
+ new_name = " ".join(new_name.split())
# remove remove duplicate -, _,
new_name = re.sub("[-_]{2,}\s+", "-- ", new_name)
@@ -139,7 +139,7 @@ class FileRenamer:
new_name = re.sub("[-]{1,2}\s*$", "", new_name)
# remove duplicate spaces (again!)
- new_name = u" ".join(new_name.split())
+ new_name = " ".join(new_name.split())
if ext is None:
ext = os.path.splitext(filename)[1]
diff --git a/comictaggerlib/fileselectionlist.py b/comictaggerlib/fileselectionlist.py
index 37228fa..cb2901c 100644
--- a/comictaggerlib/fileselectionlist.py
+++ b/comictaggerlib/fileselectionlist.py
@@ -1,5 +1,5 @@
# coding=utf-8
-"""A PyQt4 widget for managing list of comic archive files"""
+"""A PyQt5 widget for managing list of comic archive files"""
# Copyright 2012-2014 Anthony Beville
@@ -18,18 +18,19 @@
import platform
import os
#import os
-#import sys
+import sys
-from PyQt4.QtCore import *
-from PyQt4.QtGui import *
-from PyQt4 import uic
-from PyQt4.QtCore import pyqtSignal
+from PyQt5.QtCore import *
+from PyQt5.QtGui import *
+from PyQt5.QtWidgets import *
+from PyQt5 import uic
+from PyQt5.QtCore import pyqtSignal
-from settings import ComicTaggerSettings
-from comicarchive import ComicArchive
-from optionalmsgdialog import OptionalMessageDialog
+from .settings import ComicTaggerSettings
+from .comicarchive import ComicArchive
+from .optionalmsgdialog import OptionalMessageDialog
from comictaggerlib.ui.qtutils import reduceWidgetFontSize, centerWindowOnParent
-import utils
+from . import utils
#from comicarchive import MetaDataStyle
#from genericmetadata import GenericMetadata, PageType
@@ -37,8 +38,10 @@ import utils
class FileTableWidgetItem(QTableWidgetItem):
def __lt__(self, other):
- return (self.data(Qt.UserRole).toBool() <
- other.data(Qt.UserRole).toBool())
+ #return (self.data(Qt.UserRole).toBool() <
+ # other.data(Qt.UserRole).toBool())
+ return (self.data(Qt.UserRole) <
+ other.data(Qt.UserRole))
class FileInfo():
@@ -139,7 +142,7 @@ class FileSelectionList(QWidget):
def getArchiveByRow(self, row):
fi = self.twList.item(row, FileSelectionList.dataColNum).data(
- Qt.UserRole).toPyObject()
+ Qt.UserRole)
return fi.ca
def getCurrentArchive(self):
@@ -186,40 +189,46 @@ class FileSelectionList(QWidget):
filelist = utils.get_recursive_filelist(pathlist)
# we now have a list of files to add
- progdialog = QProgressDialog("", "Cancel", 0, len(filelist), self)
+ # Prog dialog on Linux flakes out for small range, so scale up
+ progdialog = QProgressDialog("", "Cancel", 0, len(filelist), parent=self)
progdialog.setWindowTitle("Adding Files")
- # progdialog.setWindowModality(Qt.WindowModal)
progdialog.setWindowModality(Qt.ApplicationModal)
- progdialog.show()
-
+ progdialog.setMinimumDuration(300)
+ centerWindowOnParent(progdialog)
+ #QCoreApplication.processEvents()
+ #progdialog.show()
+
+ QCoreApplication.processEvents()
firstAdded = None
self.twList.setSortingEnabled(False)
for idx, f in enumerate(filelist):
QCoreApplication.processEvents()
if progdialog.wasCanceled():
break
- progdialog.setValue(idx)
+ progdialog.setValue(idx+1)
progdialog.setLabelText(f)
centerWindowOnParent(progdialog)
QCoreApplication.processEvents()
row = self.addPathItem(f)
if firstAdded is None and row is not None:
firstAdded = row
+
+ progdialog.hide()
+ QCoreApplication.processEvents()
- progdialog.close()
if (self.settings.show_no_unrar_warning and
- self.settings.unrar_exe_path == "" and
- self.settings.rar_exe_path == "" and
- platform.system() != "Windows"):
+ self.settings.unrar_lib_path == "" and
+ not ComicTaggerSettings.haveOwnUnrarLib()):
for f in filelist:
ext = os.path.splitext(f)[1].lower()
if ext == ".rar" or ext == ".cbr":
- checked = OptionalMessageDialog.msg(self, "No unrar tool",
+ checked = OptionalMessageDialog.msg(self, "No UnRAR Ability",
"""
It looks like you've tried to open at least one CBR or RAR file.
In order for ComicTagger to read this kind of file, you will have to configure
- the location of the unrar tool in the settings. Until then, ComicTagger
- will not be able recognize these kind of files.
+ the location of the unrar library in the settings. Until then, ComicTagger
+ will not be able read these kind of files. See the "RAR Tools" tab in the
+ settings/preferences for more info.
"""
)
self.settings.show_no_unrar_warning = not checked
@@ -229,13 +238,19 @@ class FileSelectionList(QWidget):
self.twList.selectRow(firstAdded)
else:
if len(pathlist) == 1 and os.path.isfile(pathlist[0]):
- QMessageBox.information(self, self.tr("File Open"), self.tr(
- "Selected file doesn't seem to be a comic archive."))
+ ext = os.path.splitext(pathlist[0])[1].lower()
+ if ext == ".rar" or ext == ".cbr" and self.settings.unrar_lib_path == "":
+ QMessageBox.information(self, self.tr("File Open"), self.tr(
+ "Selected file seems to be a rar file, "
+ "and can't be read until the unrar library is configured."))
+ else:
+ QMessageBox.information(self, self.tr("File Open"), self.tr(
+ "Selected file doesn't seem to be a comic archive."))
else:
QMessageBox.information(
self,
self.tr("File/Folder Open"),
- self.tr("No comic archives were found."))
+ self.tr("No readable comic archives were found."))
self.twList.setSortingEnabled(True)
@@ -271,7 +286,7 @@ class FileSelectionList(QWidget):
return -1
def addPathItem(self, path):
- path = unicode(path)
+ path = str(path)
path = os.path.abspath(path)
# print "processing", path
@@ -327,7 +342,7 @@ class FileSelectionList(QWidget):
def updateRow(self, row):
fi = self.twList.item(row, FileSelectionList.dataColNum).data(
- Qt.UserRole).toPyObject()
+ Qt.UserRole) #.toPyObject()
filename_item = self.twList.item(row, FileSelectionList.fileColNum)
folder_item = self.twList.item(row, FileSelectionList.folderColNum)
@@ -382,8 +397,8 @@ class FileSelectionList(QWidget):
ca_list = []
for r in range(self.twList.rowCount()):
item = self.twList.item(r, FileSelectionList.dataColNum)
- if self.twList.isItemSelected(item):
- fi = item.data(Qt.UserRole).toPyObject()
+ if item.isSelected():
+ fi = item.data(Qt.UserRole)
ca_list.append(fi.ca)
return ca_list
@@ -395,7 +410,7 @@ class FileSelectionList(QWidget):
self.twList.setSortingEnabled(False)
for r in range(self.twList.rowCount()):
item = self.twList.item(r, FileSelectionList.dataColNum)
- if self.twList.isItemSelected(item):
+ if item.isSelected():
self.updateRow(r)
self.twList.setSortingEnabled(True)
@@ -425,7 +440,7 @@ class FileSelectionList(QWidget):
return
fi = self.twList.item(new_idx, FileSelectionList.dataColNum).data(
- Qt.UserRole).toPyObject()
+ Qt.UserRole) #.toPyObject()
self.selectionChanged.emit(QVariant(fi))
def revertSelection(self):
diff --git a/comictaggerlib/graphics/autotag.png b/comictaggerlib/graphics/autotag.png
index db34967..1536c89 100644
Binary files a/comictaggerlib/graphics/autotag.png and b/comictaggerlib/graphics/autotag.png differ
diff --git a/comictaggerlib/graphics/parse.png b/comictaggerlib/graphics/parse.png
index c6b2d7f..8e09948 100644
Binary files a/comictaggerlib/graphics/parse.png and b/comictaggerlib/graphics/parse.png differ
diff --git a/comictaggerlib/imagefetcher.py b/comictaggerlib/imagefetcher.py
index 26903e2..48baa5b 100644
--- a/comictaggerlib/imagefetcher.py
+++ b/comictaggerlib/imagefetcher.py
@@ -19,14 +19,14 @@ import os
import datetime
import shutil
import tempfile
-import urllib
+import urllib.request, urllib.parse, urllib.error
import ssl
#import urllib2
try:
- from PyQt4.QtNetwork import QNetworkAccessManager, QNetworkRequest
- from PyQt4.QtCore import QUrl, pyqtSignal, QObject, QByteArray
- from PyQt4 import QtGui
+ from PyQt5.QtNetwork import QNetworkAccessManager, QNetworkRequest
+ from PyQt5.QtCore import QUrl, pyqtSignal, QObject, QByteArray
+ from PyQt5 import QtGui
except ImportError:
# No Qt, so define a few dummy QObjects to help us compile
class QObject():
@@ -45,7 +45,7 @@ except ImportError:
def emit(a, b, c):
pass
-from settings import ComicTaggerSettings
+from .settings import ComicTaggerSettings
class ImageFetcherException(Exception):
@@ -87,11 +87,10 @@ class ImageFetcher(QObject):
# first look in the DB
image_data = self.get_image_from_cache(url)
-
if blocking:
if image_data is None:
try:
- image_data = urllib.urlopen(url, context=self.ssl).read()
+ image_data = urllib.request.urlopen(url, context=self.ssl).read()
except Exception as e:
print(e)
raise ImageFetcherException("Network Error!")
diff --git a/comictaggerlib/imagehasher.py b/comictaggerlib/imagehasher.py
index f68cb23..21c4172 100755
--- a/comictaggerlib/imagehasher.py
+++ b/comictaggerlib/imagehasher.py
@@ -14,7 +14,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-import StringIO
+import io
import sys
from functools import reduce
@@ -40,9 +40,9 @@ class ImageHasher(object):
if path is not None:
self.image = Image.open(path)
else:
- self.image = Image.open(StringIO.StringIO(data))
- except:
- print("Image data seems corrupted!")
+ self.image = Image.open(io.BytesIO(data))
+ except Exception as e:
+ print("Image data seems corrupted! [{}]".format(e))
# just generate a bogus image
self.image = Image.new("L", (1, 1))
@@ -52,8 +52,8 @@ class ImageHasher(object):
(self.width, self.height), Image.ANTIALIAS).convert("L")
except Exception as e:
sys.exc_clear()
- print "average_hash error:", e
- return long(0)
+ print("average_hash error:", e)
+ return int(0)
pixels = list(image.getdata())
avg = sum(pixels) / len(pixels)
@@ -61,7 +61,7 @@ class ImageHasher(object):
def compare_value_to_avg(i):
return (1 if i > avg else 0)
- bitlist = map(compare_value_to_avg, pixels)
+ bitlist = list(map(compare_value_to_avg, pixels))
# build up an int value from the bit list, one bit at a time
def set_bit(x, idx_val):
@@ -178,13 +178,13 @@ class ImageHasher(object):
@staticmethod
def hamming_distance(h1, h2):
- if isinstance(h1, long) or isinstance(h1, int):
+ if isinstance(h1, int) or isinstance(h1, int):
n1 = h1
n2 = h2
else:
# convert hex strings to ints
- n1 = long(h1, 16)
- n2 = long(h2, 16)
+ n1 = int(h1, 16)
+ n2 = int(h2, 16)
# xor the two numbers
n = n1 ^ n2
diff --git a/comictaggerlib/imagepopup.py b/comictaggerlib/imagepopup.py
index 827c041..e216484 100644
--- a/comictaggerlib/imagepopup.py
+++ b/comictaggerlib/imagepopup.py
@@ -17,19 +17,19 @@
#import sys
#import os
-from PyQt4 import QtCore, QtGui, uic
+from PyQt5 import QtCore, QtGui, QtWidgets, uic
-from settings import ComicTaggerSettings
+from .settings import ComicTaggerSettings
-class ImagePopup(QtGui.QDialog):
+class ImagePopup(QtWidgets.QDialog):
def __init__(self, parent, image_pixmap):
super(ImagePopup, self).__init__(parent)
uic.loadUi(ComicTaggerSettings.getUIFile('imagepopup.ui'), self)
- QtGui.QApplication.setOverrideCursor(
+ QtWidgets.QApplication.setOverrideCursor(
QtGui.QCursor(QtCore.Qt.WaitCursor))
# self.setWindowModality(QtCore.Qt.WindowModal)
@@ -38,15 +38,16 @@ class ImagePopup(QtGui.QDialog):
self.imagePixmap = image_pixmap
- screen_size = QtGui.QDesktopWidget().screenGeometry()
+ screen_size = QtWidgets.QDesktopWidget().screenGeometry()
self.resize(screen_size.width(), screen_size.height())
self.move(0, 0)
# This is a total hack. Uses a snapshot of the desktop, and overlays a
# translucent screen over it. Probably can do it better by setting opacity of a
# widget
- self.desktopBg = QtGui.QPixmap.grabWindow(
- QtGui.QApplication.desktop().winId(),
+ screen = QtWidgets.QApplication.primaryScreen()
+ self.desktopBg = screen.grabWindow(
+ QtWidgets.QApplication.desktop().winId(),
0,
0,
screen_size.width(),
@@ -59,7 +60,7 @@ class ImagePopup(QtGui.QDialog):
self.applyImagePixmap()
self.showFullScreen()
self.raise_()
- QtGui.QApplication.restoreOverrideCursor()
+ QtWidgets.QApplication.restoreOverrideCursor()
def paintEvent(self, event):
self.painter = QtGui.QPainter(self)
diff --git a/comictaggerlib/issueidentifier.py b/comictaggerlib/issueidentifier.py
index 7184006..47b51af 100644
--- a/comictaggerlib/issueidentifier.py
+++ b/comictaggerlib/issueidentifier.py
@@ -15,7 +15,7 @@
# limitations under the License.
import sys
-import StringIO
+import io
#import math
#import urllib2
#import urllib
@@ -27,12 +27,12 @@ try:
except ImportError:
pil_available = False
-from genericmetadata import GenericMetadata
-from comicvinetalker import ComicVineTalker, ComicVineTalkerException
-from imagehasher import ImageHasher
-from imagefetcher import ImageFetcher, ImageFetcherException
-from issuestring import IssueString
-import utils
+from .genericmetadata import GenericMetadata
+from .comicvinetalker import ComicVineTalker, ComicVineTalkerException
+from .imagehasher import ImageHasher
+from .imagefetcher import ImageFetcher, ImageFetcherException
+from .issuestring import IssueString
+from . import utils
#from settings import ComicTaggerSettings
#from comicvinecacher import ComicVineCacher
@@ -124,7 +124,7 @@ class IssueIdentifier:
def getAspectRatio(self, image_data):
try:
- im = Image.open(StringIO.StringIO(image_data))
+ im = Image.open(io.StringIO(image_data))
w, h = im.size
return float(h) / float(w)
except:
@@ -132,17 +132,17 @@ class IssueIdentifier:
def cropCover(self, image_data):
- im = Image.open(StringIO.StringIO(image_data))
+ im = Image.open(io.StringIO(image_data))
w, h = im.size
try:
cropped_im = im.crop((int(w / 2), 0, w, h))
except Exception as e:
sys.exc_clear()
- print "cropCover() error:", e
+ print("cropCover() error:", e)
return None
- output = StringIO.StringIO()
+ output = io.StringIO()
cropped_im.save(output, format="PNG")
cropped_image_data = output.getvalue()
output.close()
@@ -405,7 +405,7 @@ class IssueIdentifier:
comicVine.setLogFunc(self.output_function)
# self.log_msg(("Searching for " + keys['series'] + "...")
- self.log_msg(u"Searching for {0} #{1} ...".format(
+ self.log_msg("Searching for {0} #{1} ...".format(
keys['series'], keys['issue_number']))
try:
cv_search_results = comicVine.searchForSeries(keys['series'])
@@ -492,11 +492,11 @@ class IssueIdentifier:
break
if keys['year'] is None:
- self.log_msg(u"Found {0} series that have an issue #{1}".format(
+ self.log_msg("Found {0} series that have an issue #{1}".format(
len(shortlist), keys['issue_number']))
else:
self.log_msg(
- u"Found {0} series that have an issue #{1} from {2}".format(
+ "Found {0} series that have an issue #{1} from {2}".format(
len(shortlist),
keys['issue_number'],
keys['year']))
@@ -509,7 +509,7 @@ class IssueIdentifier:
self.callback(counter, len(shortlist) * 3)
counter += 1
- self.log_msg(u"Examining covers for ID: {0} {1} ({2}) ...".format(
+ self.log_msg("Examining covers for ID: {0} {1} ({2}) ...".format(
series['id'],
series['name'],
series['start_year']), newline=False)
@@ -540,7 +540,7 @@ class IssueIdentifier:
return self.match_list
match = dict()
- match['series'] = u"{0} ({1})".format(
+ match['series'] = "{0} ({1})".format(
series['name'], series['start_year'])
match['distance'] = score_item['score']
match['issue_number'] = keys['issue_number']
@@ -582,7 +582,7 @@ class IssueIdentifier:
self.log_msg(str(l))
def print_match(item):
- self.log_msg(u"-----> {0} #{1} {2} ({3}/{4}) -- score: {5}".format(
+ self.log_msg("-----> {0} #{1} {2} ({3}/{4}) -- score: {5}".format(
item['series'],
item['issue_number'],
item['issue_title'],
@@ -613,7 +613,7 @@ class IssueIdentifier:
self.callback(counter, len(self.match_list) * 3)
counter += 1
self.log_msg(
- u"Examining alternate covers for ID: {0} {1} ...".format(
+ "Examining alternate covers for ID: {0} {1} ...".format(
m['volume_id'],
m['series']),
newline=False)
@@ -640,18 +640,18 @@ class IssueIdentifier:
if len(self.match_list) == 1:
self.log_msg("No matching pages in the issue.")
self.log_msg(
- u"--------------------------------------------------------------------------")
+ "--------------------------------------------------------------------------")
print_match(self.match_list[0])
self.log_msg(
- u"--------------------------------------------------------------------------")
+ "--------------------------------------------------------------------------")
self.search_result = self.ResultFoundMatchButBadCoverScore
else:
self.log_msg(
- u"--------------------------------------------------------------------------")
+ "--------------------------------------------------------------------------")
self.log_msg(
- u"Multiple bad cover matches! Need to use other info...")
+ "Multiple bad cover matches! Need to use other info...")
self.log_msg(
- u"--------------------------------------------------------------------------")
+ "--------------------------------------------------------------------------")
self.search_result = self.ResultMultipleMatchesWithBadImageScores
return self.match_list
else:
@@ -695,28 +695,28 @@ class IssueIdentifier:
if len(self.match_list) == 1:
self.log_msg(
- u"--------------------------------------------------------------------------")
+ "--------------------------------------------------------------------------")
print_match(self.match_list[0])
self.log_msg(
- u"--------------------------------------------------------------------------")
+ "--------------------------------------------------------------------------")
self.search_result = self.ResultOneGoodMatch
elif len(self.match_list) == 0:
self.log_msg(
- u"--------------------------------------------------------------------------")
+ "--------------------------------------------------------------------------")
self.log_msg("No matches found :(")
self.log_msg(
- u"--------------------------------------------------------------------------")
+ "--------------------------------------------------------------------------")
self.search_result = self.ResultNoMatches
else:
# we've got multiple good matches:
self.log_msg("More than one likely candidate.")
self.search_result = self.ResultMultipleGoodMatches
self.log_msg(
- u"--------------------------------------------------------------------------")
+ "--------------------------------------------------------------------------")
for item in self.match_list:
print_match(item)
self.log_msg(
- u"--------------------------------------------------------------------------")
+ "--------------------------------------------------------------------------")
return self.match_list
diff --git a/comictaggerlib/issueselectionwindow.py b/comictaggerlib/issueselectionwindow.py
index 0e493bc..cdb9464 100644
--- a/comictaggerlib/issueselectionwindow.py
+++ b/comictaggerlib/issueselectionwindow.py
@@ -18,29 +18,29 @@
#import os
#import re
-from PyQt4 import QtCore, QtGui, uic
-#from PyQt4.QtCore import QUrl, pyqtSignal, QByteArray
-#from PyQt4.QtNetwork import QNetworkAccessManager, QNetworkRequest
+from PyQt5 import QtCore, QtGui, QtWidgets, uic
+#from PyQt5.QtCore import QUrl, pyqtSignal, QByteArray
+#from PyQt5.QtNetwork import QNetworkAccessManager, QNetworkRequest
-from comicvinetalker import ComicVineTalker, ComicVineTalkerException
-from settings import ComicTaggerSettings
-from issuestring import IssueString
-from coverimagewidget import CoverImageWidget
+from .comicvinetalker import ComicVineTalker, ComicVineTalkerException
+from .settings import ComicTaggerSettings
+from .issuestring import IssueString
+from .coverimagewidget import CoverImageWidget
from comictaggerlib.ui.qtutils import reduceWidgetFontSize
#from imagefetcher import ImageFetcher
#import utils
-class IssueNumberTableWidgetItem(QtGui.QTableWidgetItem):
+class IssueNumberTableWidgetItem(QtWidgets.QTableWidgetItem):
def __lt__(self, other):
- selfStr = self.data(QtCore.Qt.DisplayRole).toString()
- otherStr = other.data(QtCore.Qt.DisplayRole).toString()
+ selfStr = self.data(QtCore.Qt.DisplayRole)
+ otherStr = other.data(QtCore.Qt.DisplayRole)
return (IssueString(selfStr).asFloat() <
IssueString(otherStr).asFloat())
-class IssueSelectionWindow(QtGui.QDialog):
+class IssueSelectionWindow(QtWidgets.QDialog):
volume_id = 0
@@ -52,7 +52,7 @@ class IssueSelectionWindow(QtGui.QDialog):
self.coverWidget = CoverImageWidget(
self.coverImageContainer, CoverImageWidget.AltCoverMode)
- gridlayout = QtGui.QGridLayout(self.coverImageContainer)
+ gridlayout = QtWidgets.QGridLayout(self.coverImageContainer)
gridlayout.addWidget(self.coverWidget)
gridlayout.setContentsMargins(0, 0, 0, 0)
@@ -85,15 +85,14 @@ class IssueSelectionWindow(QtGui.QDialog):
self.twList.selectRow(0)
else:
for r in range(0, self.twList.rowCount()):
- issue_id, b = self.twList.item(
- r, 0).data(QtCore.Qt.UserRole).toInt()
+ issue_id = self.twList.item(r, 0).data(QtCore.Qt.UserRole)
if (issue_id == self.initial_id):
self.twList.selectRow(r)
break
def performQuery(self):
- QtGui.QApplication.setOverrideCursor(
+ QtWidgets.QApplication.setOverrideCursor(
QtGui.QCursor(QtCore.Qt.WaitCursor))
try:
@@ -101,14 +100,14 @@ class IssueSelectionWindow(QtGui.QDialog):
volume_data = comicVine.fetchVolumeData(self.series_id)
self.issue_list = comicVine.fetchIssuesByVolume(self.series_id)
except ComicVineTalkerException as e:
- QtGui.QApplication.restoreOverrideCursor()
+ QtWidgets.QApplication.restoreOverrideCursor()
if e.code == ComicVineTalkerException.RateLimit:
- QtGui.QMessageBox.critical(
+ QtWidgets.QMessageBox.critical(
self,
self.tr("Comic Vine Error"),
ComicVineTalker.getRateLimitMessage())
else:
- QtGui.QMessageBox.critical(
+ QtWidgets.QMessageBox.critical(
self,
self.tr("Network Issue"),
self.tr("Could not connect to Comic Vine to list issues!"))
@@ -139,7 +138,7 @@ class IssueSelectionWindow(QtGui.QDialog):
if len(parts) > 1:
item_text = parts[0] + "-" + parts[1]
- item = QtGui.QTableWidgetItem(item_text)
+ item = QtWidgets.QTableWidgetItem(item_text)
item.setData(QtCore.Qt.ToolTipRole, item_text)
item.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled)
self.twList.setItem(row, 1, item)
@@ -147,7 +146,7 @@ class IssueSelectionWindow(QtGui.QDialog):
item_text = record['name']
if item_text is None:
item_text = ""
- item = QtGui.QTableWidgetItem(item_text)
+ item = QtWidgets.QTableWidgetItem(item_text)
item.setData(QtCore.Qt.ToolTipRole, item_text)
item.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled)
self.twList.setItem(row, 2, item)
@@ -162,7 +161,7 @@ class IssueSelectionWindow(QtGui.QDialog):
self.twList.setSortingEnabled(True)
self.twList.sortItems(0, QtCore.Qt.AscendingOrder)
- QtGui.QApplication.restoreOverrideCursor()
+ QtWidgets.QApplication.restoreOverrideCursor()
def cellDoubleClicked(self, r, c):
self.accept()
@@ -174,8 +173,7 @@ class IssueSelectionWindow(QtGui.QDialog):
if prev is not None and prev.row() == curr.row():
return
- self.issue_id, b = self.twList.item(
- curr.row(), 0).data(QtCore.Qt.UserRole).toInt()
+ self.issue_id = self.twList.item(curr.row(), 0).data(QtCore.Qt.UserRole)
# list selection was changed, update the the issue cover
for record in self.issue_list:
diff --git a/comictaggerlib/logwindow.py b/comictaggerlib/logwindow.py
index 21fa425..ff263eb 100644
--- a/comictaggerlib/logwindow.py
+++ b/comictaggerlib/logwindow.py
@@ -17,12 +17,12 @@
#import sys
#import os
-from PyQt4 import QtCore, QtGui, uic
+from PyQt5 import QtCore, QtGui, QtWidgets, uic
-from settings import ComicTaggerSettings
+from .settings import ComicTaggerSettings
-class LogWindow(QtGui.QDialog):
+class LogWindow(QtWidgets.QDialog):
def __init__(self, parent):
super(LogWindow, self).__init__(parent)
@@ -34,4 +34,8 @@ class LogWindow(QtGui.QDialog):
QtCore.Qt.WindowMaximizeButtonHint)
def setText(self, text):
+ try:
+ text = text.decode()
+ except:
+ pass
self.textEdit.setPlainText(text)
diff --git a/comictaggerlib/main.py b/comictaggerlib/main.py
index 23022f1..0a647ae 100755
--- a/comictaggerlib/main.py
+++ b/comictaggerlib/main.py
@@ -20,63 +20,80 @@ import signal
import traceback
import platform
-from settings import ComicTaggerSettings
-
-# setup libunrar
-if not os.environ.get("UNRAR_LIB_PATH", None):
- os.environ["UNRAR_LIB_PATH"] = ComicTaggerSettings.libunrarPath()
+from .settings import ComicTaggerSettings
+# Need to load setting before anything else
+SETTINGS = ComicTaggerSettings()
try:
qt_available = True
- from PyQt4 import QtCore, QtGui
- from taggerwindow import TaggerWindow
+ from PyQt5 import QtCore, QtGui, QtWidgets
+ from .taggerwindow import TaggerWindow
except ImportError as e:
qt_available = False
-import utils
-import cli
-from options import Options
-from comicvinetalker import ComicVineTalker
+
+from . import utils
+from . import cli
+from .options import Options
+from .comicvinetalker import ComicVineTalker
def ctmain():
- utils.fix_output_encoding()
- settings = ComicTaggerSettings()
-
opts = Options()
opts.parseCmdLineArgs()
# manage the CV API key
if opts.cv_api_key:
- if opts.cv_api_key != settings.cv_api_key:
- settings.cv_api_key = opts.cv_api_key
- settings.save()
+ if opts.cv_api_key != SETTINGS.cv_api_key:
+ SETTINGS.cv_api_key = opts.cv_api_key
+ SETTINGS.save()
if opts.only_set_key:
print("Key set")
return
- ComicVineTalker.api_key = settings.cv_api_key
+ ComicVineTalker.api_key = SETTINGS.cv_api_key
signal.signal(signal.SIGINT, signal.SIG_DFL)
if not qt_available and not opts.no_gui:
opts.no_gui = True
- print >> sys.stderr, "PyQt4 is not available. ComicTagger is limited to command-line mode."
+ print("PyQt5 is not available. ComicTagger is limited to command-line mode.", file=sys.stderr)
if opts.no_gui:
- cli.cli_mode(opts, settings)
+ cli.cli_mode(opts, SETTINGS)
else:
- app = QtGui.QApplication(sys.argv)
+
+ os.environ['QT_AUTO_SCREEN_SCALE_FACTOR'] = '1'
+
+ #if platform.system() == "Darwin":
+ # QtWidgets.QApplication.setStyle("macintosh")
+ #else:
+ # QtWidgets.QApplication.setStyle("Fusion")
+
+ app = QtWidgets.QApplication(sys.argv)
+ if platform.system() == "Darwin":
+ # Set the MacOS dock icon
+ app.setWindowIcon(
+ QtGui.QIcon(ComicTaggerSettings.getGraphic('app.png')))
+
+ if platform.system() == "Windows":
+ # For pure python, tell windows that we're not python,
+ # so we can have our own taskbar icon
+ import ctypes
+ myappid = u'comictagger' # arbitrary string
+ ctypes.windll.shell32.SetCurrentProcessExplicitAppUserModelID(myappid)
if platform.system() != "Linux":
img = QtGui.QPixmap(ComicTaggerSettings.getGraphic('tags.png'))
- splash = QtGui.QSplashScreen(img)
+ splash = QtWidgets.QSplashScreen(img)
splash.show()
splash.raise_()
app.processEvents()
try:
- tagger_window = TaggerWindow(opts.file_list, settings, opts=opts)
+ tagger_window = TaggerWindow(opts.file_list, SETTINGS, opts=opts)
+ tagger_window.setWindowIcon(
+ QtGui.QIcon(ComicTaggerSettings.getGraphic('app.png')))
tagger_window.show()
if platform.system() != "Linux":
@@ -84,8 +101,8 @@ def ctmain():
sys.exit(app.exec_())
except Exception as e:
- QtGui.QMessageBox.critical(
- QtGui.QMainWindow(),
+ QtWidgets.QMessageBox.critical(
+ QtWidgets.QMainWindow(),
"Error",
"Unhandled exception in app:\n" +
traceback.format_exc())
diff --git a/comictaggerlib/matchselectionwindow.py b/comictaggerlib/matchselectionwindow.py
index 3f5a66f..cc9483f 100644
--- a/comictaggerlib/matchselectionwindow.py
+++ b/comictaggerlib/matchselectionwindow.py
@@ -17,11 +17,11 @@
import os
#import sys
-from PyQt4 import QtCore, QtGui, uic
-#from PyQt4.QtCore import QUrl, pyqtSignal, QByteArray
+from PyQt5 import QtCore, QtGui, QtWidgets, uic
+#from PyQt5.QtCore import QUrl, pyqtSignal, QByteArray
-from settings import ComicTaggerSettings
-from coverimagewidget import CoverImageWidget
+from .settings import ComicTaggerSettings
+from .coverimagewidget import CoverImageWidget
from comictaggerlib.ui.qtutils import reduceWidgetFontSize
#from imagefetcher import ImageFetcher
#from comicarchive import MetaDataStyle
@@ -29,7 +29,7 @@ from comictaggerlib.ui.qtutils import reduceWidgetFontSize
#import utils
-class MatchSelectionWindow(QtGui.QDialog):
+class MatchSelectionWindow(QtWidgets.QDialog):
volume_id = 0
@@ -41,13 +41,13 @@ class MatchSelectionWindow(QtGui.QDialog):
self.altCoverWidget = CoverImageWidget(
self.altCoverContainer, CoverImageWidget.AltCoverMode)
- gridlayout = QtGui.QGridLayout(self.altCoverContainer)
+ gridlayout = QtWidgets.QGridLayout(self.altCoverContainer)
gridlayout.addWidget(self.altCoverWidget)
gridlayout.setContentsMargins(0, 0, 0, 0)
self.archiveCoverWidget = CoverImageWidget(
self.archiveCoverContainer, CoverImageWidget.ArchiveMode)
- gridlayout = QtGui.QGridLayout(self.archiveCoverContainer)
+ gridlayout = QtWidgets.QGridLayout(self.archiveCoverContainer)
gridlayout.addWidget(self.archiveCoverWidget)
gridlayout.setContentsMargins(0, 0, 0, 0)
@@ -74,7 +74,7 @@ class MatchSelectionWindow(QtGui.QDialog):
self.twList.selectRow(0)
path = self.comic_archive.path
- self.setWindowTitle(u"Select correct match: {0}".format(
+ self.setWindowTitle("Select correct match: {0}".format(
os.path.split(path)[1]))
def populateTable(self):
@@ -89,30 +89,30 @@ class MatchSelectionWindow(QtGui.QDialog):
self.twList.insertRow(row)
item_text = match['series']
- item = QtGui.QTableWidgetItem(item_text)
+ item = QtWidgets.QTableWidgetItem(item_text)
item.setData(QtCore.Qt.ToolTipRole, item_text)
item.setData(QtCore.Qt.UserRole, (match,))
item.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled)
self.twList.setItem(row, 0, item)
if match['publisher'] is not None:
- item_text = u"{0}".format(match['publisher'])
+ item_text = "{0}".format(match['publisher'])
else:
- item_text = u"Unknown"
- item = QtGui.QTableWidgetItem(item_text)
+ item_text = "Unknown"
+ item = QtWidgets.QTableWidgetItem(item_text)
item.setData(QtCore.Qt.ToolTipRole, item_text)
item.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled)
self.twList.setItem(row, 1, item)
- month_str = u""
- year_str = u"????"
+ month_str = ""
+ year_str = "????"
if match['month'] is not None:
- month_str = u"-{0:02d}".format(int(match['month']))
+ month_str = "-{0:02d}".format(int(match['month']))
if match['year'] is not None:
- year_str = u"{0}".format(match['year'])
+ year_str = "{0}".format(match['year'])
item_text = year_str + month_str
- item = QtGui.QTableWidgetItem(item_text)
+ item = QtWidgets.QTableWidgetItem(item_text)
item.setData(QtCore.Qt.ToolTipRole, item_text)
item.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled)
self.twList.setItem(row, 2, item)
@@ -120,7 +120,7 @@ class MatchSelectionWindow(QtGui.QDialog):
item_text = match['issue_title']
if item_text is None:
item_text = ""
- item = QtGui.QTableWidgetItem(item_text)
+ item = QtWidgets.QTableWidgetItem(item_text)
item.setData(QtCore.Qt.ToolTipRole, item_text)
item.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled)
self.twList.setItem(row, 3, item)
@@ -156,5 +156,5 @@ class MatchSelectionWindow(QtGui.QDialog):
def currentMatch(self):
row = self.twList.currentRow()
match = self.twList.item(row, 0).data(
- QtCore.Qt.UserRole).toPyObject()[0]
+ QtCore.Qt.UserRole)[0]
return match
diff --git a/comictaggerlib/optionalmsgdialog.py b/comictaggerlib/optionalmsgdialog.py
index b42a304..d06053c 100644
--- a/comictaggerlib/optionalmsgdialog.py
+++ b/comictaggerlib/optionalmsgdialog.py
@@ -1,4 +1,4 @@
-"""A PyQt4 dialog to show a message and let the user check a box
+"""A PyQt5 dialog to show a message and let the user check a box
Example usage:
@@ -25,8 +25,9 @@ said_yes, checked = OptionalMessageDialog.question(self, "Question",
# See the License for the specific language governing permissions and
# limitations under the License.
-from PyQt4.QtCore import *
-from PyQt4.QtGui import *
+from PyQt5.QtCore import *
+from PyQt5.QtGui import *
+from PyQt5.QtWidgets import *
StyleMessage = 0
diff --git a/comictaggerlib/options.py b/comictaggerlib/options.py
index fca3f86..a4e055b 100644
--- a/comictaggerlib/options.py
+++ b/comictaggerlib/options.py
@@ -25,11 +25,11 @@ try:
except ImportError:
pass
-from genericmetadata import GenericMetadata
-from comicarchive import MetaDataStyle
-from versionchecker import VersionChecker
-import ctversion
-import utils
+from .genericmetadata import GenericMetadata
+from .comicarchive import MetaDataStyle
+from .versionchecker import VersionChecker
+from . import ctversion
+from . import utils
class Options:
@@ -144,7 +144,7 @@ For more help visit the wiki at: http://code.google.com/p/comictagger/
if msg is not None:
print(msg)
if show_help:
- print(self.help_text.format(appname))
+ print((self.help_text.format(appname)))
else:
print("For more help, run with '--help'")
sys.exit(code)
@@ -196,7 +196,7 @@ For more help visit the wiki at: http://code.google.com/p/comictagger/
# Map the dict to the metadata object
for key in md_dict:
if not hasattr(md, key):
- print("Warning: '{0}' is not a valid tag name".format(key))
+ print(("Warning: '{0}' is not a valid tag name".format(key)))
else:
md.isEmpty = False
setattr(md, key, md_dict[key])
@@ -216,7 +216,7 @@ For more help visit the wiki at: http://code.google.com/p/comictagger/
break
sys.argv = script_args
if not os.path.exists(scriptfile):
- print("Can't find {0}".format(scriptfile))
+ print(("Can't find {0}".format(scriptfile)))
else:
# I *think* this makes sense:
# assume the base name of the file is the module name
@@ -232,11 +232,11 @@ For more help visit the wiki at: http://code.google.com/p/comictagger/
if "main" in dir(script):
script.main()
else:
- print(
- "Can't find entry point \"main()\" in module \"{0}\"".format(module_name))
+ print((
+ "Can't find entry point \"main()\" in module \"{0}\"".format(module_name)))
except Exception as e:
- print "Script raised an unhandled exception: ", e
- print(traceback.format_exc())
+ print("Script raised an unhandled exception: ", e)
+ print((traceback.format_exc()))
sys.exit(0)
@@ -340,8 +340,8 @@ For more help visit the wiki at: http://code.google.com/p/comictagger/
if o == "--only-set-cv-key":
self.only_set_key = True
if o == "--version":
- print(
- "ComicTagger {0}: Copyright (c) 2012-2014 Anthony Beville".format(ctversion.version))
+ print((
+ "ComicTagger {0}: Copyright (c) 2012-2014 Anthony Beville".format(ctversion.version)))
print(
"Distributed under Apache License 2.0 (http://www.apache.org/licenses/LICENSE-2.0)")
sys.exit(0)
diff --git a/comictaggerlib/pagebrowser.py b/comictaggerlib/pagebrowser.py
index 39b514c..0bf07ec 100644
--- a/comictaggerlib/pagebrowser.py
+++ b/comictaggerlib/pagebrowser.py
@@ -18,13 +18,13 @@ import platform
#import sys
#import os
-from PyQt4 import QtCore, QtGui, uic
+from PyQt5 import QtCore, QtGui, QtWidgets, uic
-from settings import ComicTaggerSettings
-from coverimagewidget import CoverImageWidget
+from .settings import ComicTaggerSettings
+from .coverimagewidget import CoverImageWidget
-class PageBrowserWindow(QtGui.QDialog):
+class PageBrowserWindow(QtWidgets.QDialog):
def __init__(self, parent, metadata):
super(PageBrowserWindow, self).__init__(parent)
@@ -33,7 +33,7 @@ class PageBrowserWindow(QtGui.QDialog):
self.pageWidget = CoverImageWidget(
self.pageContainer, CoverImageWidget.ArchiveMode)
- gridlayout = QtGui.QGridLayout(self.pageContainer)
+ gridlayout = QtWidgets.QGridLayout(self.pageContainer)
gridlayout.addWidget(self.pageWidget)
gridlayout.setContentsMargins(0, 0, 0, 0)
self.pageWidget.showControls = False
@@ -47,7 +47,7 @@ class PageBrowserWindow(QtGui.QDialog):
self.current_page_num = 0
self.metadata = metadata
- self.buttonBox.button(QtGui.QDialogButtonBox.Close).setDefault(True)
+ self.buttonBox.button(QtWidgets.QDialogButtonBox.Close).setDefault(True)
if platform.system() == "Darwin":
self.btnPrev.setText("<<")
self.btnNext.setText(">>")
diff --git a/comictaggerlib/pagelisteditor.py b/comictaggerlib/pagelisteditor.py
index 87953a2..dc602d3 100644
--- a/comictaggerlib/pagelisteditor.py
+++ b/comictaggerlib/pagelisteditor.py
@@ -1,4 +1,4 @@
-"""A PyQt4 widget for editing the page list info"""
+"""A PyQt5 widget for editing the page list info"""
# Copyright 2012-2014 Anthony Beville
@@ -16,14 +16,15 @@
#import os
-from PyQt4.QtCore import *
-from PyQt4.QtGui import *
-from PyQt4 import uic
+from PyQt5.QtCore import *
+from PyQt5.QtGui import *
+from PyQt5.QtWidgets import *
+from PyQt5 import uic
-from settings import ComicTaggerSettings
-from genericmetadata import GenericMetadata, PageType
-from comicarchive import MetaDataStyle
-from coverimagewidget import CoverImageWidget
+from .settings import ComicTaggerSettings
+from .genericmetadata import GenericMetadata, PageType
+from .comicarchive import MetaDataStyle
+from .coverimagewidget import CoverImageWidget
#from pageloader import PageLoader
@@ -171,7 +172,7 @@ class PageListEditor(QWidget):
#idx = int(str (self.listWidget.item(row).text()))
idx = int(self.listWidget.item(row).data(
- Qt.UserRole).toPyObject()[0]['Image'])
+ Qt.UserRole)[0]['Image'])
if self.comic_archive is not None:
self.pageWidget.setArchive(self.comic_archive, idx)
@@ -180,7 +181,7 @@ class PageListEditor(QWidget):
frontCover = 0
for i in range(self.listWidget.count()):
item = self.listWidget.item(i)
- page_dict = item.data(Qt.UserRole).toPyObject()[0]
+ page_dict = item.data(Qt.UserRole)[0] #.toPyObject()[0]
if 'Type' in page_dict and page_dict[
'Type'] == PageType.FrontCover:
frontCover = int(page_dict['Image'])
@@ -189,7 +190,7 @@ class PageListEditor(QWidget):
def getCurrentPageType(self):
row = self.listWidget.currentRow()
- page_dict = self.listWidget.item(row).data(Qt.UserRole).toPyObject()[0]
+ page_dict = self.listWidget.item(row).data(Qt.UserRole)[0] #.toPyObject()[0]
if 'Type' in page_dict:
return page_dict['Type']
else:
@@ -197,7 +198,7 @@ class PageListEditor(QWidget):
def setCurrentPageType(self, t):
row = self.listWidget.currentRow()
- page_dict = self.listWidget.item(row).data(Qt.UserRole).toPyObject()[0]
+ page_dict = self.listWidget.item(row).data(Qt.UserRole)[0] #.toPyObject()[0]
if t == "":
if 'Type' in page_dict:
@@ -239,7 +240,7 @@ class PageListEditor(QWidget):
page_list = []
for i in range(self.listWidget.count()):
item = self.listWidget.item(i)
- page_list.append(item.data(Qt.UserRole).toPyObject()[0])
+ page_list.append(item.data(Qt.UserRole)[0]) #.toPyObject()[0]
return page_list
def emitFrontCoverChange(self):
diff --git a/comictaggerlib/pageloader.py b/comictaggerlib/pageloader.py
index f7899e3..ebe8838 100644
--- a/comictaggerlib/pageloader.py
+++ b/comictaggerlib/pageloader.py
@@ -14,8 +14,8 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-from PyQt4 import QtCore, QtGui, uic
-from PyQt4.QtCore import pyqtSignal
+from PyQt5 import QtCore, QtGui, uic
+from PyQt5.QtCore import pyqtSignal
from comictaggerlib.ui.qtutils import getQImageFromData
#from comicarchive import ComicArchive
diff --git a/comictaggerlib/progresswindow.py b/comictaggerlib/progresswindow.py
index dcb1396..6dfe16d 100644
--- a/comictaggerlib/progresswindow.py
+++ b/comictaggerlib/progresswindow.py
@@ -1,4 +1,4 @@
-"""A PyQT4 dialog to show ID log and progress"""
+"""A PyQT5 dialog to show ID log and progress"""
# Copyright 2012-2014 Anthony Beville
@@ -17,14 +17,14 @@
#import sys
#import os
-from PyQt4 import QtCore, QtGui, uic
+from PyQt5 import QtCore, QtGui, QtWidgets, uic
from comictaggerlib.ui.qtutils import reduceWidgetFontSize
-from settings import ComicTaggerSettings
+from .settings import ComicTaggerSettings
#import utils
-class IDProgressWindow(QtGui.QDialog):
+class IDProgressWindow(QtWidgets.QDialog):
def __init__(self, parent):
super(IDProgressWindow, self).__init__(parent)
diff --git a/comictaggerlib/renamewindow.py b/comictaggerlib/renamewindow.py
index 2c7494c..9d1e2e1 100644
--- a/comictaggerlib/renamewindow.py
+++ b/comictaggerlib/renamewindow.py
@@ -16,16 +16,17 @@
import os
-from PyQt4 import QtCore, QtGui, uic
+from PyQt5 import QtCore, QtGui, QtWidgets, uic
-from settings import ComicTaggerSettings
-from settingswindow import SettingsWindow
-from filerenamer import FileRenamer
-from comicarchive import MetaDataStyle
-import utils
+from .settings import ComicTaggerSettings
+from .settingswindow import SettingsWindow
+from .filerenamer import FileRenamer
+from .comicarchive import MetaDataStyle
+from comictaggerlib.ui.qtutils import centerWindowOnParent
+from . import utils
-class RenameWindow(QtGui.QDialog):
+class RenameWindow(QtWidgets.QDialog):
def __init__(self, parent, comic_archive_list, data_style, settings):
super(RenameWindow, self).__init__(parent)
@@ -79,9 +80,9 @@ class RenameWindow(QtGui.QDialog):
row = self.twList.rowCount()
self.twList.insertRow(row)
- folder_item = QtGui.QTableWidgetItem()
- old_name_item = QtGui.QTableWidgetItem()
- new_name_item = QtGui.QTableWidgetItem()
+ folder_item = QtWidgets.QTableWidgetItem()
+ old_name_item = QtWidgets.QTableWidgetItem()
+ new_name_item = QtWidgets.QTableWidgetItem()
item_text = os.path.split(ca.path)[0]
folder_item.setFlags(
@@ -128,23 +129,28 @@ class RenameWindow(QtGui.QDialog):
def accept(self):
- progdialog = QtGui.QProgressDialog(
+ progdialog = QtWidgets.QProgressDialog(
"", "Cancel", 0, len(self.rename_list), self)
progdialog.setWindowTitle("Renaming Archives")
progdialog.setWindowModality(QtCore.Qt.WindowModal)
- progdialog.show()
+ progdialog.setMinimumDuration(100)
+ centerWindowOnParent(progdialog)
+ #progdialog.show()
+ QtCore.QCoreApplication.processEvents()
for idx, item in enumerate(self.rename_list):
QtCore.QCoreApplication.processEvents()
if progdialog.wasCanceled():
break
- progdialog.setValue(idx)
idx += 1
+ progdialog.setValue(idx)
progdialog.setLabelText(item['new_name'])
+ centerWindowOnParent(progdialog)
+ QtCore.QCoreApplication.processEvents()
if item['new_name'] == os.path.basename(item['archive'].path):
- print item['new_name'], "Filename is already good!"
+ print(item['new_name'], "Filename is already good!")
continue
if not item['archive'].isWritable(check_rar_status=False):
@@ -158,6 +164,7 @@ class RenameWindow(QtGui.QDialog):
item['archive'].rename(new_abs_path)
- progdialog.close()
+ progdialog.hide()
+ QtCore.QCoreApplication.processEvents()
- QtGui.QDialog.accept(self)
+ QtWidgets.QDialog.accept(self)
diff --git a/comictaggerlib/settings.py b/comictaggerlib/settings.py
index 6fad6e4..3879445 100644
--- a/comictaggerlib/settings.py
+++ b/comictaggerlib/settings.py
@@ -21,7 +21,7 @@ import platform
import codecs
import uuid
-import utils
+from . import utils
class ComicTaggerSettings:
@@ -34,13 +34,17 @@ class ComicTaggerSettings:
else:
folder = os.path.join(os.path.expanduser('~'), '.ComicTagger')
if folder is not None:
- folder = folder.decode(filename_encoding)
+ folder = folder
return folder
@staticmethod
- def libunrarPath():
+ def defaultLibunrarPath():
return ComicTaggerSettings.baseDir() + "/libunrar.so"
+ @staticmethod
+ def haveOwnUnrarLib():
+ return os.path.exists(ComicTaggerSettings.defaultLibunrarPath())
+
@staticmethod
def baseDir():
if getattr(sys, 'frozen', None):
@@ -60,12 +64,11 @@ class ComicTaggerSettings:
return os.path.join(ui_folder, filename)
def setDefaultValues(self):
-
# General Settings
self.rar_exe_path = ""
- self.unrar_exe_path = ""
+ self.unrar_lib_path = ""
self.allow_cbi_in_rar = True
- self.check_for_new_version = True
+ self.check_for_new_version = False
self.send_usage_stats = False
# automatic settings
@@ -84,7 +87,7 @@ class ComicTaggerSettings:
# identifier settings
self.id_length_delta_thresh = 5
- self.id_publisher_blacklist = "Panini Comics, Abril, Planeta DeAgostini, Editorial Televisa"
+ self.id_publisher_blacklist = "Panini Comics, Abril, Planeta DeAgostini, Editorial Televisa, Dino Comics"
# Show/ask dialog flags
self.ask_about_cbi_in_rar = True
@@ -148,7 +151,7 @@ class ComicTaggerSettings:
else:
self.load()
- # take a crack at finding rar exes, if not set already
+ # take a crack at finding rar exe, if not set already
if self.rar_exe_path == "":
if platform.system() == "Windows":
# look in some likely places for Windows machines
@@ -162,19 +165,51 @@ class ComicTaggerSettings:
self.rar_exe_path = utils.which("rar")
if self.rar_exe_path != "":
self.save()
-
- if self.unrar_exe_path == "":
- if platform.system() != "Windows":
- # see if it's in the path of unix user
- if utils.which("unrar") is not None:
- self.unrar_exe_path = utils.which("unrar")
- if self.unrar_exe_path != "":
+ if self.rar_exe_path != "":
+ # make sure rar program is now in the path for the rar class
+ utils.addtopath(os.path.dirname(self.rar_exe_path))
+
+ if self.haveOwnUnrarLib():
+ # We have a 'personal' copy of the unrar lib in the basedir, so
+ # don't search and change the setting
+ # NOTE: a manual edit of the settings file overrides this below
+ os.environ["UNRAR_LIB_PATH"] = self.defaultLibunrarPath()
+
+ elif self.unrar_lib_path == "":
+ # Priority is for unrar lib search is:
+ # 1. explicit setting in settings file
+ # 2. UNRAR_LIB_PATH in environment
+ # 3. check some likely platform specific places
+ if "UNRAR_LIB_PATH" in os.environ:
+ self.unrar_lib_path = os.environ["UNRAR_LIB_PATH"]
+ else:
+ # look in some platform specific places:
+ if platform.system() == "Windows":
+ # Default location for the RARLab DLL installer
+ if (platform.architecture()[0] == '64bit' and
+ os.path.exists("C:\\Program Files (x86)\\UnrarDLL\\x64\\UnRAR64.dll")
+ ):
+ self.unrar_lib_path = "C:\\Program Files (x86)\\UnrarDLL\\x64\\UnRAR64.dll"
+ elif (platform.architecture()[0] == '32bit' and
+ os.path.exists("C:\\Program Files\\UnrarDLL\\UnRAR.dll")
+ ):
+ self.unrar_lib_path = "C:\\Program Files\\UnrarDLL\\UnRAR.dll"
+ elif platform.system() == "Darwin":
+ # Look for the brew unrar library
+ if os.path.exists("/usr/local/lib/libunrar.dylib"):
+ self.unrar_lib_path = "/usr/local/lib/libunrar.dylib"
+ elif platform.system() == "Linux":
+ if os.path.exists("/usr/local/lib/libunrar.so"):
+ self.unrar_lib_path = "/usr/local/lib/libunrar.so"
+ elif os.path.exists("/usr/lib/libunrar.so"):
+ self.unrar_lib_path = "/usr/lib/libunrar.so"
+
+ if self.unrar_lib_path != "":
self.save()
-
- # make sure unrar/rar programs are now in the path for the UnRAR class to
- # use
- utils.addtopath(os.path.dirname(self.unrar_exe_path))
- utils.addtopath(os.path.dirname(self.rar_exe_path))
+
+ if self.unrar_lib_path != "":
+ # This needs to occur before the unrar module is loaded for the first time
+ os.environ["UNRAR_LIB_PATH"] = self.unrar_lib_path
def reset(self):
os.unlink(self.settings_file)
@@ -193,7 +228,8 @@ class ComicTaggerSettings:
readline_generator(codecs.open(self.settings_file, "r", "utf8")))
self.rar_exe_path = self.config.get('settings', 'rar_exe_path')
- self.unrar_exe_path = self.config.get('settings', 'unrar_exe_path')
+ if self.config.has_option('settings', 'unrar_lib_path'):
+ self.unrar_lib_path = self.config.get('settings', 'unrar_lib_path')
if self.config.has_option('settings', 'check_for_new_version'):
self.check_for_new_version = self.config.getboolean(
'settings', 'check_for_new_version')
@@ -353,7 +389,7 @@ class ComicTaggerSettings:
self.config.set(
'settings', 'check_for_new_version', self.check_for_new_version)
self.config.set('settings', 'rar_exe_path', self.rar_exe_path)
- self.config.set('settings', 'unrar_exe_path', self.unrar_exe_path)
+ self.config.set('settings', 'unrar_lib_path', self.unrar_lib_path)
self.config.set('settings', 'send_usage_stats', self.send_usage_stats)
if not self.config.has_section('auto'):
diff --git a/comictaggerlib/settingswindow.py b/comictaggerlib/settingswindow.py
index 1dc89a4..857b885 100644
--- a/comictaggerlib/settingswindow.py
+++ b/comictaggerlib/settingswindow.py
@@ -16,42 +16,71 @@
import platform
import os
+import sys
-from PyQt4 import QtCore, QtGui, uic
+from PyQt5 import QtCore, QtGui, QtWidgets, uic
-from settings import ComicTaggerSettings
-from comicvinecacher import ComicVineCacher
-from comicvinetalker import ComicVineTalker
-from imagefetcher import ImageFetcher
-import utils
+from .settings import ComicTaggerSettings
+from .comicvinecacher import ComicVineCacher
+from .comicvinetalker import ComicVineTalker
+from .imagefetcher import ImageFetcher
+from . import utils
windowsRarHelp = """
-
In order to write to CBR/RAR archives, +
To write to CBR/RAR archives, you will need to have the tools from - - WinRAR - installed.
+ + WINRar + installed. (ComicTagger only uses the command-line rar tool, + which is free to use.) """ linuxRarHelp = """ -In order to read/write to CBR/RAR archives, - you will need to have the shareware tools from WinRar installed. - Your package manager should have unrar, and probably rar. - If not, download them - here - , and install in your path.
+To write to CBR/RAR archives, + you will need to have the shareware rar tool from RARLab installed. + Your package manager should have rar (e.g. "apt-get install rar"). If not, download it + + here, + and install in your path.
""" + macRarHelp = """ -In order to read/write to CBR/RAR archives, - you will need the shareware tools from - - WinRAR - .
+To write to CBR/RAR archives, + you will need the rar tool. The easiest way to get this is + to install + homebrew. +
Once homebrew is installed, run: brew install caskroom/cask/rar + """ + +windowsUnrarHelp = """ +To read CBR/RAR archives, + you will need to have the unrar DLL from + + + RARLab installed.
+ """ + +linuxUnrarHelp = """ +To read CBR/RAR archives, + you will need to have the unrar library from RARLab installed. + Look + here + for pre-compiled binaries, or + here + for the UnRAR source (which is easy to compile on Linux).
+ """ + +macUnrarHelp = """ +To read CBR/RAR archives, + you will need the unrar library. The easiest way to get this is + to install + homebrew. +
Once homebrew is installed, run: brew install unrar """ -class SettingsWindow(QtGui.QDialog): +class SettingsWindow(QtWidgets.QDialog): def __init__(self, parent, settings): super(SettingsWindow, self).__init__(parent) @@ -63,18 +92,28 @@ class SettingsWindow(QtGui.QDialog): self.settings = settings self.name = "Settings" + + self.priorUnrarLibPath = self.settings.unrar_lib_path + if self.settings.haveOwnUnrarLib(): + # We have our own unrarlib, so no need for this GUI + self.grpBoxUnrar.hide() + if platform.system() == "Windows": - self.lblUnrar.hide() - self.leUnrarExePath.hide() - self.btnBrowseUnrar.hide() self.lblRarHelp.setText(windowsRarHelp) + self.lblUnrarHelp.setText(windowsUnrarHelp) elif platform.system() == "Linux": self.lblRarHelp.setText(linuxRarHelp) + self.lblUnrarHelp.setText(linuxUnrarHelp) elif platform.system() == "Darwin": + # Mac file dialog hides "/usr" and others, so allow user to type + self.leUnrarLibPath.setReadOnly(False) + self.leRarExePath.setReadOnly(False) + self.lblRarHelp.setText(macRarHelp) + self.lblUnrarHelp.setText(macUnrarHelp) self.name = "Preferences" self.setWindowTitle("ComicTagger " + self.name) @@ -118,7 +157,7 @@ class SettingsWindow(QtGui.QDialog): # Copy values from settings to form self.leRarExePath.setText(self.settings.rar_exe_path) - self.leUnrarExePath.setText(self.settings.unrar_exe_path) + self.leUnrarLibPath.setText(self.settings.unrar_lib_path) self.leNameLengthDeltaThresh.setText( str(self.settings.id_length_delta_thresh)) self.tePublisherBlacklist.setPlainText( @@ -171,12 +210,19 @@ class SettingsWindow(QtGui.QDialog): # Copy values from form to settings and save self.settings.rar_exe_path = str(self.leRarExePath.text()) - self.settings.unrar_exe_path = str(self.leUnrarExePath.text()) + + # Don't accept the form info if we have our own unrar lib + if not self.settings.haveOwnUnrarLib(): + self.settings.unrar_lib_path = str(self.leUnrarLibPath.text()) - # make sure unrar/rar program is now in the path for the UnRAR class - utils.addtopath(os.path.dirname(self.settings.unrar_exe_path)) - utils.addtopath(os.path.dirname(self.settings.rar_exe_path)) + # make sure rar program is now in the path for the rar class + if self.settings.rar_exe_path: + utils.addtopath(os.path.dirname(self.settings.rar_exe_path)) + if self.settings.unrar_lib_path: + os.environ["UNRAR_LIB_PATH"] = self.settings.unrar_lib_path + # This doesn't do anything... we need to restart! + if not str(self.leNameLengthDeltaThresh.text()).isdigit(): self.leNameLengthDeltaThresh.setText("0") @@ -195,8 +241,8 @@ class SettingsWindow(QtGui.QDialog): self.settings.use_series_start_as_volume = self.cbxUseSeriesStartAsVolume.isChecked() self.settings.clear_form_before_populating_from_cv = self.cbxClearFormBeforePopulating.isChecked() self.settings.remove_html_tables = self.cbxRemoveHtmlTables.isChecked() - self.settings.cv_api_key = unicode(self.leKey.text()) - ComicVineTalker.api_key = self.settings.cv_api_key + self.settings.cv_api_key = str(self.leKey.text()) + ComicVineTalker.api_key = self.settings.cv_api_key.strip() self.settings.assume_lone_credit_is_primary = self.cbxAssumeLoneCreditIsPrimary.isChecked() self.settings.copy_characters_to_tags = self.cbxCopyCharactersToTags.isChecked() self.settings.copy_teams_to_tags = self.cbxCopyTeamsToTags.isChecked() @@ -214,32 +260,37 @@ class SettingsWindow(QtGui.QDialog): self.settings.rename_extension_based_on_archive = self.cbxChangeExtension.isChecked() self.settings.save() - QtGui.QDialog.accept(self) + QtWidgets.QDialog.accept(self) + if self.priorUnrarLibPath != self.settings.unrar_lib_path: + QtWidgets.QMessageBox.information( + self, "UnRar Library Change", + "ComicTagger will need to be restarted for changes to take effect.") + def selectRar(self): self.selectFile(self.leRarExePath, "RAR") def selectUnrar(self): - self.selectFile(self.leUnrarExePath, "UnRAR") + self.selectFile(self.leUnrarLibPath, "UnRAR") def clearCache(self): ImageFetcher().clearCache() ComicVineCacher().clearCache() - QtGui.QMessageBox.information( + QtWidgets.QMessageBox.information( self, self.name, "Cache has been cleared.") def testAPIKey(self): - if ComicVineTalker().testKey(unicode(self.leKey.text())): - QtGui.QMessageBox.information( + if ComicVineTalker().testKey(str(self.leKey.text()).strip()): + QtWidgets.QMessageBox.information( self, "API Key Test", "Key is valid!") else: - QtGui.QMessageBox.warning( + QtWidgets.QMessageBox.warning( self, "API Key Test", "Key is NOT valid.") def resetSettings(self): self.settings.reset() self.settingsToForm() - QtGui.QMessageBox.information( + QtWidgets.QMessageBox.information( self, self.name, self.name + @@ -247,14 +298,14 @@ class SettingsWindow(QtGui.QDialog): def selectFile(self, control, name): - dialog = QtGui.QFileDialog(self) - dialog.setFileMode(QtGui.QFileDialog.ExistingFile) + dialog = QtWidgets.QFileDialog(self) + dialog.setFileMode(QtWidgets.QFileDialog.ExistingFile) if platform.system() == "Windows": if name == "RAR": filter = self.tr("Rar Program (Rar.exe)") else: - filter = self.tr("Programs (*.exe)") + filter = self.tr("Libraries (*.dll)") dialog.setNameFilter(filter) else: # QtCore.QDir.Executable | QtCore.QDir.Files) @@ -262,8 +313,11 @@ class SettingsWindow(QtGui.QDialog): pass dialog.setDirectory(os.path.dirname(str(control.text()))) - dialog.setWindowTitle("Find " + name + " program") - + if name == "RAR": + dialog.setWindowTitle("Find " + name + " program") + else: + dialog.setWindowTitle("Find " + name + " library") + if (dialog.exec_()): fileList = dialog.selectedFiles() control.setText(str(fileList[0])) diff --git a/comictaggerlib/taggerwindow.py b/comictaggerlib/taggerwindow.py index 764bc4f..5b2bfab 100644 --- a/comictaggerlib/taggerwindow.py +++ b/comictaggerlib/taggerwindow.py @@ -17,6 +17,7 @@ import sys import locale +import functools import platform import os import pprint @@ -26,38 +27,38 @@ import re import pickle #import signal -from PyQt4 import QtCore, QtGui, uic -from PyQt4 import QtNetwork -from PyQt4.QtCore import QString, QUrl +from PyQt5 import QtCore, QtGui, QtWidgets, uic +from PyQt5 import QtNetwork +from PyQt5.QtCore import QUrl #from comicarchive import ComicArchive #from pageloader import PageLoader -from volumeselectionwindow import VolumeSelectionWindow -from comicarchive import MetaDataStyle -from comicinfoxml import ComicInfoXml -from genericmetadata import GenericMetadata -from comicvinetalker import ComicVineTalker, ComicVineTalkerException -from crediteditorwindow import CreditEditorWindow -from settingswindow import SettingsWindow -from settings import ComicTaggerSettings -from pagebrowser import PageBrowserWindow -from filenameparser import FileNameParser -from logwindow import LogWindow -from optionalmsgdialog import OptionalMessageDialog -from pagelisteditor import PageListEditor -from fileselectionlist import FileSelectionList -from cbltransformer import CBLTransformer -from renamewindow import RenameWindow -from exportwindow import ExportWindow, ExportConflictOpts -from issueidentifier import IssueIdentifier -from autotagstartwindow import AutoTagStartWindow -from autotagprogresswindow import AutoTagProgressWindow -from autotagmatchwindow import AutoTagMatchWindow -from coverimagewidget import CoverImageWidget -from versionchecker import VersionChecker +from .volumeselectionwindow import VolumeSelectionWindow +from .comicarchive import MetaDataStyle +from .comicinfoxml import ComicInfoXml +from .genericmetadata import GenericMetadata +from .comicvinetalker import ComicVineTalker, ComicVineTalkerException +from .crediteditorwindow import CreditEditorWindow +from .settingswindow import SettingsWindow +from .settings import ComicTaggerSettings +from .pagebrowser import PageBrowserWindow +from .filenameparser import FileNameParser +from .logwindow import LogWindow +from .optionalmsgdialog import OptionalMessageDialog +from .pagelisteditor import PageListEditor +from .fileselectionlist import FileSelectionList +from .cbltransformer import CBLTransformer +from .renamewindow import RenameWindow +from .exportwindow import ExportWindow, ExportConflictOpts +from .issueidentifier import IssueIdentifier +from .autotagstartwindow import AutoTagStartWindow +from .autotagprogresswindow import AutoTagProgressWindow +from .autotagmatchwindow import AutoTagMatchWindow +from .coverimagewidget import CoverImageWidget +from .versionchecker import VersionChecker from comictaggerlib.ui.qtutils import reduceWidgetFontSize, centerWindowOnParent -import utils -import ctversion +from . import utils +from . import ctversion class OnlineMatchResults(): @@ -78,14 +79,14 @@ class MultipleMatch(): self.matches = match_list -class TaggerWindow(QtGui.QMainWindow): +class TaggerWindow(QtWidgets.QMainWindow): appName = "ComicTagger" version = ctversion.version def __init__(self, file_list, settings, parent=None, opts=None): super(TaggerWindow, self).__init__(parent) - + uic.loadUi(ComicTaggerSettings.getUIFile('taggerwindow.ui'), self) self.settings = settings @@ -95,14 +96,14 @@ class TaggerWindow(QtGui.QMainWindow): socket.connectToServer(settings.install_id) alive = socket.waitForConnected(3000) if alive: - print( + print(( "Another application with key [{}] is already running".format( - settings.install_id)) + settings.install_id))) # send file list to other instance if len(file_list) > 0: socket.write(pickle.dumps(file_list)) if not socket.waitForBytesWritten(3000): - print(socket.errorString().toLatin1()) + print((socket.errorString().toLatin1())) socket.disconnectFromServer() sys.exit() else: @@ -118,28 +119,28 @@ class TaggerWindow(QtGui.QMainWindow): self.socketServer.removeServer(settings.install_id) ok = self.socketServer.listen(settings.install_id) if not ok: - print( + print(( "Cannot start local socket with key [{}]. Reason: %s ".format( settings.install_id, - self.socketServer.errorString())) + self.socketServer.errorString()))) sys.exit() #print("Registering as single instance with key [{}]".format(settings.install_id)) #---------------------------------- self.archiveCoverWidget = CoverImageWidget( self.coverImageContainer, CoverImageWidget.ArchiveMode) - gridlayout = QtGui.QGridLayout(self.coverImageContainer) + gridlayout = QtWidgets.QGridLayout(self.coverImageContainer) gridlayout.addWidget(self.archiveCoverWidget) gridlayout.setContentsMargins(0, 0, 0, 0) self.pageListEditor = PageListEditor(self.tabPages) - gridlayout = QtGui.QGridLayout(self.tabPages) + gridlayout = QtWidgets.QGridLayout(self.tabPages) gridlayout.addWidget(self.pageListEditor) #--------------------------- self.fileSelectionList = FileSelectionList( self.widgetListHolder, self.settings) - gridlayout = QtGui.QGridLayout(self.widgetListHolder) + gridlayout = QtWidgets.QGridLayout(self.widgetListHolder) gridlayout.addWidget(self.fileSelectionList) self.fileSelectionList.selectionChanged.connect( @@ -153,7 +154,7 @@ class TaggerWindow(QtGui.QMainWindow): # walk through all the lablels in the main form, and make them # a smidge smaller for child in self.scrollAreaWidgetContents.children(): - if (isinstance(child, QtGui.QLabel)): + if (isinstance(child, QtWidgets.QLabel)): f = child.font() if f.pointSize() > 10: f.setPointSize(f.pointSize() - 2) @@ -264,20 +265,6 @@ class TaggerWindow(QtGui.QMainWindow): ) self.settings.show_disclaimer = not checked - if self.settings.ask_about_usage_stats: - reply = QtGui.QMessageBox.question( - self, - self.tr("Anonymous Stats"), - self.tr( - "Is it okay if ComicTagger occasionally sends some anonymous usage statistics? Nothing nefarious, " - "just trying to get a better idea of how the app is being used.\n\nThanks for your support!"), - QtGui.QMessageBox.Yes | QtGui.QMessageBox.Default, - QtGui.QMessageBox.No) - - if reply == QtGui.QMessageBox.Yes: - self.settings.send_usage_stats = True - self.settings.ask_about_usage_stats = False - if self.settings.check_for_new_version: #self.checkLatestVersionOnline() pass @@ -304,6 +291,9 @@ class TaggerWindow(QtGui.QMainWindow): def updateAppTitle(self): + self.setWindowIcon( + QtGui.QIcon(ComicTaggerSettings.getGraphic('app.png'))) + if self.comic_archive is None: self.setWindowTitle(self.appName) else: @@ -456,7 +446,7 @@ class TaggerWindow(QtGui.QMainWindow): rar_count += 1 if rar_count == 0: - QtGui.QMessageBox.information( + QtWidgets.QMessageBox.information( self, self.tr("Export as Zip Archive"), self.tr("No RAR archives selected!")) @@ -478,11 +468,13 @@ class TaggerWindow(QtGui.QMainWindow): if not dlg.exec_(): return - progdialog = QtGui.QProgressDialog( + progdialog = QtWidgets.QProgressDialog( "", "Cancel", 0, rar_count, self) progdialog.setWindowTitle("Exporting as ZIP") progdialog.setWindowModality(QtCore.Qt.ApplicationModal) - progdialog.show() + progdialog.setMinimumDuration(300) + centerWindowOnParent(progdialog) + QtCore.QCoreApplication.processEvents() prog_idx = 0 new_archives_to_add = [] @@ -496,8 +488,8 @@ class TaggerWindow(QtGui.QMainWindow): QtCore.QCoreApplication.processEvents() if progdialog.wasCanceled(): break - progdialog.setValue(prog_idx) prog_idx += 1 + progdialog.setValue(prog_idx) progdialog.setLabelText(ca.path) centerWindowOnParent(progdialog) QtCore.QCoreApplication.processEvents() @@ -528,23 +520,23 @@ class TaggerWindow(QtGui.QMainWindow): if os.path.lexists(export_name): os.remove(export_name) - progdialog.close() - + progdialog.hide() + QtCore.QCoreApplication.processEvents() self.fileSelectionList.addPathList(new_archives_to_add) self.fileSelectionList.removeArchiveList(archives_to_remove) - summary = u"Successfully created {0} Zip archive(s).".format( + summary = "Successfully created {0} Zip archive(s).".format( success_count) if len(skipped_list) > 0: - summary += u"\n\nThe following {0} RAR archive(s) were skipped due to file name conflicts:\n".format( + summary += "\n\nThe following {0} RAR archive(s) were skipped due to file name conflicts:\n".format( len(skipped_list)) for f in skipped_list: - summary += u"\t{0}\n".format(f) + summary += "\t{0}\n".format(f) if len(failed_list) > 0: - summary += u"\n\nThe following {0} RAR archive(s) failed to export due to read/write errors:\n".format( + summary += "\n\nThe following {0} RAR archive(s) failed to export due to read/write errors:\n".format( len(failed_list)) for f in failed_list: - summary += u"\t{0}\n".format(f) + summary += "\t{0}\n".format(f) dlg = LogWindow(self) dlg.setText(summary) @@ -553,12 +545,12 @@ class TaggerWindow(QtGui.QMainWindow): def aboutApp(self): - website = "http://code.google.com/p/comictagger" + website = "https://github.com/davide-romanini/comictagger" email = "comictagger@gmail.com" license_link = "http://www.apache.org/licenses/LICENSE-2.0" license_name = "Apache License 2.0" - msgBox = QtGui.QMessageBox() + msgBox = QtWidgets.QMessageBox() msgBox.setWindowTitle(self.tr("About " + self.appName)) msgBox.setTextFormat(QtCore.Qt.RichText) msgBox.setIconPixmap( @@ -568,12 +560,12 @@ class TaggerWindow(QtGui.QMainWindow): " v" + self.version + "