Python3 and QT5 upgrade (#109)

* Tweaked search string based on new comic vine search behavior
Placated Beaufitul Soup by passing the parser

* First cut at porting to Python 3 and PyQt5

* remove debug print

* tweaked progress dialog handling for issues on ubuntu gui

* Handle bad key more gracefullu

* More integration of unrarlib into settings and rest of app

* Better handling of "personal" unrar lib setting

* PEP 440-compliant version string

* Tuned linux rar help strings

* Got setup working again
* Attempts to build unrar on install
* Some minimal desktop integration on various platforms

* Fix wrong shortfile

* More setup.py enhancements
* Use proper temp file
* Added comment block at top

* Comment out desktop integration attempt for now

* Updated some links and info

* Fixed the html a bit

* Repaired some images that caused libpng to complain

* update readme re:  py3qt5 branch changes

* another note

* #108 feat: try to simplify windows build using only pip and python3

* #108 feat: fix python location on appveyor (try 1)

* #108 feat: use venv (try 2)

* #108 feat: use venv (try 3)

* #108 feat: update to latest pyinstaller develop branch

* #108 feat: update to latest pyinstaller develop branch (again)

* #108: add ssl libraries for windows packaging

* #108: refresh env in win build to pick the right mingw

* #108: change order of win build script operations

* #113: fix subprocess usage in pyinstaller package

* bump version
This commit is contained in:
davide-romanini 2018-09-19 22:05:39 +02:00 committed by GitHub
parent cf43513d52
commit 91f82fd6d3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
58 changed files with 1471 additions and 1039 deletions

2
.gitignore vendored
View File

@ -2,3 +2,5 @@
/nbproject/
/dist
*.pyc
/.vscode
venv

View File

@ -1,4 +1,8 @@
include README.txt
include README.md
include release_notes.txt
include requirements.txt
recursive-include scripts *.py *.txt
include unrar/*
recursive-include scripts *.py *.txt
recursive-include desktop-integration *
include windows/app.ico
include mac/app.icns

View File

@ -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)
mv dist/$(APP_NAME) dist/$(FINAL_NAME)

View File

@ -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.

View File

@ -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

View File

@ -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))

View File

@ -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:

View File

@ -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 = '<?xml version="1.0"?>\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)

View File

@ -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:

View File

@ -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])

View File

@ -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:

View File

@ -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:

View File

@ -1,4 +1,4 @@
#!/usr/bin/env python
#!/usr/bin/env python3
from comictaggerlib.main import ctmain
if __name__ == '__main__':

View File

@ -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=[],

View File

@ -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!"))

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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=?",

View File

@ -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']

View File

@ -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

View File

@ -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)

View File

@ -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"

View File

@ -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()

View File

@ -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]

View File

@ -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.<br><br>
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):

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.5 KiB

After

Width:  |  Height:  |  Size: 4.2 KiB

View File

@ -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!")

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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:

View File

@ -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)

View File

@ -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())

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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(">>")

View File

@ -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):

View File

@ -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

View File

@ -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)

View File

@ -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)

View File

@ -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'):

View File

@ -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 = """
<html><head/><body><p>In order to write to CBR/RAR archives,
<html><head/><body><p>To write to CBR/RAR archives,
you will need to have the tools from
<a href="http://www.win-rar.com/download.html">
<span style=" text-decoration: underline; color:#0000ff;">WinRAR</span>
</a> installed. </p></body></html>
<span style=" text-decoration: underline; color:#0000ff;">
<a href="http://www.win-rar.com/download.html">WINRar</a></span>
installed. (ComicTagger only uses the command-line rar tool,
which is free to use.)</p></body></html>
"""
linuxRarHelp = """
<html><head/><body><p>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 <a href="http://www.win-rar.com/download.html">
<span style=" text-decoration: underline; color:#0000ff;">here</span>
</a>, and install in your path. </p></body></html>
<html><head/><body><p>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
<span style=" text-decoration: underline; color:#0000ff;">
<a href="https://www.rarlab.com/download.htm">here</a></span>,
and install in your path. </p></body></html>
"""
macRarHelp = """
<html><head/><body><p>In order to read/write to CBR/RAR archives,
you will need the shareware tools from
<a href="http://www.win-rar.com/download.html">
<span style=" text-decoration: underline; color:#0000ff;">WinRAR</span>
</a>. </p></body></html>
<html><head/><body><p>To write to CBR/RAR archives,
you will need the rar tool. The easiest way to get this is
to install <span style=" text-decoration: underline; color:#0000ff;">
<a href="https://brew.sh/">homebrew</a></span>.
</p>Once homebrew is installed, run: <b>brew install caskroom/cask/rar</b></body></html>
"""
windowsUnrarHelp = """
<html><head/><body><p>To read CBR/RAR archives,
you will need to have the unrar DLL from
<span style=" text-decoration: underline; color:#0000ff;">
<a href="https://www.rarlab.com/rar_add.htm">
RARLab</a></span> installed. </p></body></html>
"""
linuxUnrarHelp = """
<html><head/><body><p>To read CBR/RAR archives,
you will need to have the unrar library from RARLab installed.
Look <span style=" text-decoration: underline; color:#0000ff;">
<a href="https://github.com/beville/libunrar-binaries/releases">here</a></span>
for pre-compiled binaries, or <span style=" text-decoration: underline; color:#0000ff;">
<a href="https://www.rarlab.com/rar_add.htm">here</a></span>
for the UnRAR source (which is easy to compile on Linux). </p></body></html>
"""
macUnrarHelp = """
<html><head/><body><p>To read CBR/RAR archives,
you will need the unrar library. The easiest way to get this is
to install <span style=" text-decoration: underline; color:#0000ff;">
<a href="https://brew.sh/homebrew">homebrew</a></span>.
</p>Once homebrew is installed, run: <b>brew install unrar</b></body></html>
"""
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]))

View File

@ -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 +
"<br>" +
"&copy;2014 Anthony Beville<br><br>" +
"&copy;2014-2018 ComicTagger Devs<br><br>" +
"<a href='{0}'>{0}</a><br><br>".format(website) +
"<a href='mailto:{0}'>{0}</a><br><br>".format(email) +
"License: <a href='{0}'>{1}</a>".format(license_link, license_name))
msgBox.setStandardButtons(QtGui.QMessageBox.Ok)
msgBox.setStandardButtons(QtWidgets.QMessageBox.Ok)
msgBox.exec_()
def dragEnterEvent(self, event):
@ -723,11 +715,11 @@ class TaggerWindow(QtGui.QMainWindow):
tag_info = ""
if ca.hasCIX():
tag_info = u"• ComicRack tags"
tag_info = "• ComicRack tags"
if ca.hasCBI():
if tag_info != "":
tag_info += "\n"
tag_info += u"• ComicBookLover tags"
tag_info += "• ComicBookLover tags"
self.lblTagList.setText(tag_info)
@ -749,13 +741,13 @@ class TaggerWindow(QtGui.QMainWindow):
def connectChildDirtyFlagSignals(self, widget):
if (isinstance(widget, QtGui.QLineEdit)):
if (isinstance(widget, QtWidgets.QLineEdit)):
widget.textEdited.connect(self.setDirtyFlag)
if (isinstance(widget, QtGui.QTextEdit)):
if (isinstance(widget, QtWidgets.QTextEdit)):
widget.textChanged.connect(self.setDirtyFlag)
if (isinstance(widget, QtGui.QComboBox)):
if (isinstance(widget, QtWidgets.QComboBox)):
widget.currentIndexChanged.connect(self.setDirtyFlag)
if (isinstance(widget, QtGui.QCheckBox)):
if (isinstance(widget, QtWidgets.QCheckBox)):
widget.stateChanged.connect(self.setDirtyFlag)
# recursive call on chillun
@ -781,14 +773,14 @@ class TaggerWindow(QtGui.QMainWindow):
def clearChildren(self, widget):
if (isinstance(widget, QtGui.QLineEdit) or
isinstance(widget, QtGui.QTextEdit)):
if (isinstance(widget, QtWidgets.QLineEdit) or
isinstance(widget, QtWidgets.QTextEdit)):
widget.setText("")
if (isinstance(widget, QtGui.QComboBox)):
if (isinstance(widget, QtWidgets.QComboBox)):
widget.setCurrentIndex(0)
if (isinstance(widget, QtGui.QCheckBox)):
if (isinstance(widget, QtWidgets.QCheckBox)):
widget.setChecked(False)
if (isinstance(widget, QtGui.QTableWidget)):
if (isinstance(widget, QtWidgets.QTableWidget)):
while widget.rowCount() > 0:
widget.removeRow(0)
@ -802,7 +794,7 @@ class TaggerWindow(QtGui.QMainWindow):
# helper func
def assignText(field, value):
if value is not None:
field.setText(unicode(value))
field.setText(str(value))
md = self.metadata
@ -893,18 +885,18 @@ class TaggerWindow(QtGui.QMainWindow):
self.twCredits.insertRow(row)
item_text = role
item = QtGui.QTableWidgetItem(item_text)
item = QtWidgets.QTableWidgetItem(item_text)
item.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled)
item.setData(QtCore.Qt.ToolTipRole, item_text)
self.twCredits.setItem(row, 1, item)
item_text = name
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.twCredits.setItem(row, 2, item)
item = QtGui.QTableWidgetItem("")
item = QtWidgets.QTableWidgetItem("")
item.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled)
self.twCredits.setItem(row, 0, item)
self.updateCreditPrimaryFlag(row, primary_flag)
@ -923,7 +915,7 @@ class TaggerWindow(QtGui.QMainWindow):
# helper func
def xlate(data, type_str):
s = u"{0}".format(data).strip()
s = "{0}".format(data).strip()
if s == "":
return None
elif type_str == "str":
@ -964,12 +956,10 @@ class TaggerWindow(QtGui.QMainWindow):
md.format = xlate(self.cbFormat.currentText(), "str")
md.country = xlate(self.cbCountry.currentText(), "str")
langiso = self.cbLanguage.itemData(
self.cbLanguage.currentIndex()).toString()
langiso = self.cbLanguage.itemData(self.cbLanguage.currentIndex())
md.language = xlate(langiso, "str")
manga_code = self.cbManga.itemData(
self.cbManga.currentIndex()).toString()
manga_code = self.cbManga.itemData(self.cbManga.currentIndex())
md.manga = xlate(manga_code, "str")
# Make a list from the coma delimited tags string
@ -989,8 +979,8 @@ class TaggerWindow(QtGui.QMainWindow):
md.credits = list()
row = 0
while row < self.twCredits.rowCount():
role = u"{0}".format(self.twCredits.item(row, 1).text())
name = u"{0}".format(self.twCredits.item(row, 2).text())
role = "{0}".format(self.twCredits.item(row, 1).text())
name = "{0}".format(self.twCredits.item(row, 2).text())
primary_flag = self.twCredits.item(row, 0).text() != ""
md.addCredit(name, role, bool(primary_flag))
@ -1013,15 +1003,15 @@ class TaggerWindow(QtGui.QMainWindow):
def selectFile(self, folder_mode=False):
dialog = QtGui.QFileDialog(self)
dialog = QtWidgets.QFileDialog(self)
if folder_mode:
dialog.setFileMode(QtGui.QFileDialog.Directory)
dialog.setFileMode(QtWidgets.QFileDialog.Directory)
else:
dialog.setFileMode(QtGui.QFileDialog.ExistingFiles)
dialog.setFileMode(QtWidgets.QFileDialog.ExistingFiles)
if self.settings.last_opened_folder is not None:
dialog.setDirectory(self.settings.last_opened_folder)
# dialog.setFileMode(QtGui.QFileDialog.Directory)
# dialog.setFileMode(QtWidgets.QFileDialog.Directory)
if not folder_mode:
if platform.system() != "Windows" and utils.which("unrar") is None:
@ -1042,7 +1032,7 @@ class TaggerWindow(QtGui.QMainWindow):
def autoIdentifySearch(self):
if self.comic_archive is None:
QtGui.QMessageBox.warning(
QtWidgets.QMessageBox.warning(
self,
self.tr("Automatic Identify Search"),
self.tr("You need to load a comic first!"))
@ -1052,19 +1042,19 @@ class TaggerWindow(QtGui.QMainWindow):
def queryOnline(self, autoselect=False):
issue_number = unicode(self.leIssueNum.text()).strip()
issue_number = str(self.leIssueNum.text()).strip()
if autoselect and issue_number == "":
QtGui.QMessageBox.information(
QtWidgets.QMessageBox.information(
self,
"Automatic Identify Search",
"Can't auto-identify without an issue number (yet!)")
return
if unicode(self.leSeries.text()).strip() != "":
series_name = unicode(self.leSeries.text()).strip()
if str(self.leSeries.text()).strip() != "":
series_name = str(self.leSeries.text()).strip()
else:
QtGui.QMessageBox.information(
QtWidgets.QMessageBox.information(
self,
self.tr("Online Search"),
self.tr("Need to enter a series name to search."))
@ -1098,7 +1088,7 @@ class TaggerWindow(QtGui.QMainWindow):
if selector.result():
# we should now have a volume ID
QtGui.QApplication.setOverrideCursor(
QtWidgets.QApplication.setOverrideCursor(
QtGui.QCursor(QtCore.Qt.WaitCursor))
# copy the form onto metadata object
@ -1109,19 +1099,19 @@ class TaggerWindow(QtGui.QMainWindow):
new_metadata = comicVine.fetchIssueData(
selector.volume_id, selector.issue_number, self.settings)
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 get issue details.!"))
else:
QtGui.QApplication.restoreOverrideCursor()
QtWidgets.QApplication.restoreOverrideCursor()
if new_metadata is not None:
if self.settings.apply_cbl_transform_on_cv_import:
@ -1135,7 +1125,7 @@ class TaggerWindow(QtGui.QMainWindow):
# Now push the new combined data into the edit controls
self.metadataToForm()
else:
QtGui.QMessageBox.critical(
QtWidgets.QMessageBox.critical(
self, self.tr("Search"), self.tr(
"Could not find an issue {0} for that series".format(
selector.issue_number)))
@ -1143,7 +1133,7 @@ class TaggerWindow(QtGui.QMainWindow):
def commitMetadata(self):
if (self.metadata is not None and self.comic_archive is not None):
reply = QtGui.QMessageBox.question(
reply = QtWidgets.QMessageBox.question(
self,
self.tr("Save Tags"),
self.tr(
@ -1151,11 +1141,11 @@ class TaggerWindow(QtGui.QMainWindow):
MetaDataStyle.name[
self.save_data_style] +
" tags to this archive?"),
QtGui.QMessageBox.Yes,
QtGui.QMessageBox.No)
QtWidgets.QMessageBox.Yes,
QtWidgets.QMessageBox.No)
if reply == QtGui.QMessageBox.Yes:
QtGui.QApplication.setOverrideCursor(
if reply == QtWidgets.QMessageBox.Yes:
QtWidgets.QApplication.setOverrideCursor(
QtGui.QCursor(QtCore.Qt.WaitCursor))
self.formToMetadata()
@ -1163,10 +1153,10 @@ class TaggerWindow(QtGui.QMainWindow):
self.metadata, self.save_data_style)
self.comic_archive.loadCache(
[MetaDataStyle.CBI, MetaDataStyle.CIX])
QtGui.QApplication.restoreOverrideCursor()
QtWidgets.QApplication.restoreOverrideCursor()
if not success:
QtGui.QMessageBox.warning(
QtWidgets.QMessageBox.warning(
self,
self.tr("Save failed"),
self.tr("The tag save operation seemed to fail!"))
@ -1174,18 +1164,18 @@ class TaggerWindow(QtGui.QMainWindow):
self.clearDirtyFlag()
self.updateInfoBox()
self.updateMenus()
#QtGui.QMessageBox.information(self, self.tr("Yeah!"), self.tr("File written."))
#QtWidgets.QMessageBox.information(self, self.tr("Yeah!"), self.tr("File written."))
self.fileSelectionList.updateCurrentRow()
else:
QtGui.QMessageBox.information(
QtWidgets.QMessageBox.information(
self, self.tr("Whoops!"), self.tr("No data to commit!"))
def setLoadDataStyle(self, s):
if self.dirtyFlagVerification(
"Change Tag Read Style",
"If you change read tag style now, data in the form will be lost. Are you sure?"):
self.load_data_style, b = self.cbLoadDataStyle.itemData(s).toInt()
self.load_data_style = self.cbLoadDataStyle.itemData(s)
self.settings.last_selected_load_data_style = self.load_data_style
self.updateMenus()
if self.comic_archive is not None:
@ -1198,16 +1188,21 @@ class TaggerWindow(QtGui.QMainWindow):
self.setLoadDataStyle)
def setSaveDataStyle(self, s):
self.save_data_style, b = self.cbSaveDataStyle.itemData(s).toInt()
self.save_data_style = self.cbSaveDataStyle.itemData(s)
self.settings.last_selected_save_data_style = self.save_data_style
self.updateStyleTweaks()
self.updateMenus()
def updateCreditColors(self):
#!!!ATB qt5 porting TODO
#return
inactive_color = QtGui.QColor(255, 170, 150)
active_palette = self.leSeries.palette()
active_color = active_palette.color(QtGui.QPalette.Base)
inactive_brush = QtGui.QBrush(inactive_color)
active_brush = QtGui.QBrush(active_color)
cix_credits = ComicInfoXml().getParseableCredits()
@ -1218,19 +1213,19 @@ class TaggerWindow(QtGui.QMainWindow):
if str(self.twCredits.item(r, 1).text()
).lower() not in cix_credits:
self.twCredits.item(
r, 1).setBackgroundColor(inactive_color)
r, 1).setBackground(inactive_brush)
else:
self.twCredits.item(r, 1).setBackgroundColor(active_color)
self.twCredits.item(r, 1).setBackground(active_brush)
# turn off entire primary column
self.twCredits.item(r, 0).setBackgroundColor(inactive_color)
self.twCredits.item(r, 0).setBackground(inactive_brush)
r = r + 1
if self.save_data_style == MetaDataStyle.CBI:
# loop over credit table, make all active color
r = 0
while r < self.twCredits.rowCount():
self.twCredits.item(r, 0).setBackgroundColor(active_color)
self.twCredits.item(r, 1).setBackgroundColor(active_color)
self.twCredits.item(r, 0).setBackground(active_brush)
self.twCredits.item(r, 1).setBackground(active_brush)
r = r + 1
def updateStyleTweaks(self):
@ -1259,18 +1254,18 @@ class TaggerWindow(QtGui.QMainWindow):
if enable:
item.setPalette(active_palette)
item.setAutoFillBackground(False)
if isinstance(item, QtGui.QCheckBox):
if isinstance(item, QtWidgets.QCheckBox):
item.setEnabled(True)
elif isinstance(item, QtGui.QComboBox):
elif isinstance(item, QtWidgets.QComboBox):
item.setEnabled(True)
else:
item.setReadOnly(False)
else:
item.setAutoFillBackground(True)
if isinstance(item, QtGui.QCheckBox):
if isinstance(item, QtWidgets.QCheckBox):
item.setPalette(inactive_palette2)
item.setEnabled(False)
elif isinstance(item, QtGui.QComboBox):
elif isinstance(item, QtWidgets.QComboBox):
item.setPalette(inactive_palette3)
item.setEnabled(False)
else:
@ -1364,7 +1359,7 @@ class TaggerWindow(QtGui.QMainWindow):
ok_to_mod = True
if self.isDupeCredit(new_role, new_name):
# delete the dupe credit from list
reply = QtGui.QMessageBox.question(
reply = QtWidgets.QMessageBox.question(
self,
self.tr("Duplicate Credit!"),
self.tr(
@ -1420,7 +1415,7 @@ class TaggerWindow(QtGui.QMainWindow):
self.settings.last_main_window_width,
self.settings.last_main_window_height)
else:
screen = QtGui.QDesktopWidget().screenGeometry()
screen = QtWidgets.QDesktopWidget().screenGeometry()
size = self.frameGeometry()
self.move((screen.width() - size.width()) / 2,
(screen.height() - size.height()) / 2)
@ -1459,7 +1454,7 @@ class TaggerWindow(QtGui.QMainWindow):
# Add the entries to the language combobox
self.cbLanguage.addItem("", "")
lang_dict = utils.getLanguageDict()
for key in sorted(lang_dict, cmp=locale.strcoll, key=lang_dict.get):
for key in sorted(lang_dict, key=functools.cmp_to_key(locale.strcoll)):
self.cbLanguage.addItem(lang_dict[key], key)
# Add the entries to the manga combobox
@ -1558,7 +1553,7 @@ class TaggerWindow(QtGui.QMainWindow):
has_md_count += 1
if has_md_count == 0:
QtGui.QMessageBox.information(
QtWidgets.QMessageBox.information(
self, self.tr("Remove Tags"), self.tr(
"No archives with {0} tags selected!".format(
MetaDataStyle.name[style])))
@ -1570,22 +1565,26 @@ class TaggerWindow(QtGui.QMainWindow):
return
if has_md_count != 0:
reply = QtGui.QMessageBox.question(
reply = QtWidgets.QMessageBox.question(
self,
self.tr("Remove Tags"),
self.tr(
"Are you sure you wish to remove the {0} tags from {1} archive(s)?".format(
MetaDataStyle.name[style],
has_md_count)),
QtGui.QMessageBox.Yes,
QtGui.QMessageBox.No)
QtWidgets.QMessageBox.Yes,
QtWidgets.QMessageBox.No)
if reply == QtGui.QMessageBox.Yes:
progdialog = QtGui.QProgressDialog(
if reply == QtWidgets.QMessageBox.Yes:
progdialog = QtWidgets.QProgressDialog(
"", "Cancel", 0, has_md_count, self)
progdialog.setWindowTitle("Removing Tags")
progdialog.setWindowModality(QtCore.Qt.ApplicationModal)
progdialog.show()
progdialog.setMinimumDuration(300)
centerWindowOnParent(progdialog)
QtCore.QCoreApplication.processEvents()
#progdialog.show()
prog_idx = 0
failed_list = []
@ -1595,8 +1594,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()
@ -1608,18 +1607,19 @@ class TaggerWindow(QtGui.QMainWindow):
success_count += 1
ca.loadCache([MetaDataStyle.CBI, MetaDataStyle.CIX])
progdialog.close()
progdialog.hide()
QtCore.QCoreApplication.processEvents()
self.fileSelectionList.updateSelectedRows()
self.updateInfoBox()
self.updateMenus()
summary = u"Successfully removed tags in {0} archive(s).".format(
summary = "Successfully removed tags in {0} archive(s).".format(
success_count)
if len(failed_list) > 0:
summary += u"\n\nThe remove operation failed in the following {0} archive(s):\n".format(
summary += "\n\nThe remove operation failed in the following {0} archive(s):\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)
@ -1636,7 +1636,7 @@ class TaggerWindow(QtGui.QMainWindow):
dest_style = self.save_data_style
if src_style == dest_style:
QtGui.QMessageBox.information(
QtWidgets.QMessageBox.information(
self,
self.tr("Copy Tags"),
self.tr(
@ -1649,7 +1649,7 @@ class TaggerWindow(QtGui.QMainWindow):
has_src_count += 1
if has_src_count == 0:
QtGui.QMessageBox.information(
QtWidgets.QMessageBox.information(
self, self.tr("Copy Tags"), self.tr(
"No archives with {0} tags selected!".format(
MetaDataStyle.name[src_style])))
@ -1661,7 +1661,7 @@ class TaggerWindow(QtGui.QMainWindow):
return
if has_src_count != 0:
reply = QtGui.QMessageBox.question(
reply = QtWidgets.QMessageBox.question(
self,
self.tr("Copy Tags"),
self.tr(
@ -1669,15 +1669,17 @@ class TaggerWindow(QtGui.QMainWindow):
MetaDataStyle.name[src_style],
MetaDataStyle.name[dest_style],
has_src_count)),
QtGui.QMessageBox.Yes,
QtGui.QMessageBox.No)
QtWidgets.QMessageBox.Yes,
QtWidgets.QMessageBox.No)
if reply == QtGui.QMessageBox.Yes:
progdialog = QtGui.QProgressDialog(
if reply == QtWidgets.QMessageBox.Yes:
progdialog = QtWidgets.QProgressDialog(
"", "Cancel", 0, has_src_count, self)
progdialog.setWindowTitle("Copying Tags")
progdialog.setWindowModality(QtCore.Qt.ApplicationModal)
progdialog.show()
progdialog.setMinimumDuration(300)
centerWindowOnParent(progdialog)
QtCore.QCoreApplication.processEvents()
prog_idx = 0
failed_list = []
@ -1687,8 +1689,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()
@ -1706,18 +1708,19 @@ class TaggerWindow(QtGui.QMainWindow):
ca.loadCache([MetaDataStyle.CBI, MetaDataStyle.CIX])
progdialog.close()
progdialog.hide()
QtCore.QCoreApplication.processEvents()
self.fileSelectionList.updateSelectedRows()
self.updateInfoBox()
self.updateMenus()
summary = u"Successfully copied tags in {0} archive(s).".format(
summary = "Successfully copied tags in {0} archive(s).".format(
success_count)
if len(failed_list) > 0:
summary += u"\n\nThe copy operation failed in the following {0} archive(s):\n".format(
summary += "\n\nThe copy operation failed in the following {0} archive(s):\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)
@ -1728,7 +1731,7 @@ class TaggerWindow(QtGui.QMainWindow):
# now get the particular issue data
cv_md = None
QtGui.QApplication.setOverrideCursor(
QtWidgets.QApplication.setOverrideCursor(
QtGui.QCursor(QtCore.Qt.WaitCursor))
try:
@ -1743,7 +1746,7 @@ class TaggerWindow(QtGui.QMainWindow):
if self.settings.apply_cbl_transform_on_cv_import:
cv_md = CBLTransformer(cv_md, self.settings).apply()
QtGui.QApplication.restoreOverrideCursor()
QtWidgets.QApplication.restoreOverrideCursor()
return cv_md
@ -1861,7 +1864,7 @@ class TaggerWindow(QtGui.QMainWindow):
style = self.save_data_style
if len(ca_list) == 0:
QtGui.QMessageBox.information(
QtWidgets.QMessageBox.information(
self, self.tr("Auto-Tag"), self.tr("No archives selected!"))
return
@ -1891,9 +1894,9 @@ class TaggerWindow(QtGui.QMainWindow):
self.atprogdialog.setWindowTitle("Auto-Tagging")
self.autoTagLog(
u"==========================================================================\n")
"==========================================================================\n")
self.autoTagLog(
u"Auto-Tagging Started for {0} items\n".format(len(ca_list)))
"Auto-Tagging Started for {0} items\n".format(len(ca_list)))
prog_idx = 0
@ -1901,10 +1904,10 @@ class TaggerWindow(QtGui.QMainWindow):
archives_to_remove = []
for ca in ca_list:
self.autoTagLog(
u"==========================================================================\n")
"==========================================================================\n")
self.autoTagLog(
u"Auto-Tagging {0} of {1}\n".format(prog_idx + 1, len(ca_list)))
self.autoTagLog(u"{0}\n".format(ca.path))
"Auto-Tagging {0} of {1}\n".format(prog_idx + 1, len(ca_list)))
self.autoTagLog("{0}\n".format(ca.path))
cover_idx = ca.readMetadata(style).getCoverPageIndexList()[0]
image_data = ca.getPage(cover_idx)
self.atprogdialog.setArchiveImage(image_data)
@ -1935,24 +1938,24 @@ class TaggerWindow(QtGui.QMainWindow):
self.loadArchive(self.fileSelectionList.getCurrentArchive())
self.atprogdialog = None
summary = u""
summary += u"Successfully tagged archives: {0}\n".format(
summary = ""
summary += "Successfully tagged archives: {0}\n".format(
len(match_results.goodMatches))
if len(match_results.multipleMatches) > 0:
summary += u"Archives with multiple matches: {0}\n".format(
summary += "Archives with multiple matches: {0}\n".format(
len(match_results.multipleMatches))
if len(match_results.lowConfidenceMatches) > 0:
summary += u"Archives with one or more low-confidence matches: {0}\n".format(
summary += "Archives with one or more low-confidence matches: {0}\n".format(
len(match_results.lowConfidenceMatches))
if len(match_results.noMatches) > 0:
summary += u"Archives with no matches: {0}\n".format(
summary += "Archives with no matches: {0}\n".format(
len(match_results.noMatches))
if len(match_results.fetchDataFailures) > 0:
summary += u"Archives that failed due to data fetch errors: {0}\n".format(
summary += "Archives that failed due to data fetch errors: {0}\n".format(
len(match_results.fetchDataFailures))
if len(match_results.writeFailures) > 0:
summary += u"Archives that failed due to file writing errors: {0}\n".format(
summary += "Archives that failed due to file writing errors: {0}\n".format(
len(match_results.writeFailures))
self.autoTagLog(summary)
@ -1960,18 +1963,18 @@ class TaggerWindow(QtGui.QMainWindow):
sum_selectable = len(
match_results.multipleMatches) + len(match_results.lowConfidenceMatches)
if sum_selectable > 0:
summary += u"\n\nDo you want to manually select the ones with multiple matches and/or low-confidence matches now?"
summary += "\n\nDo you want to manually select the ones with multiple matches and/or low-confidence matches now?"
reply = QtGui.QMessageBox.question(
reply = QtWidgets.QMessageBox.question(
self,
self.tr(u"Auto-Tag Summary"),
self.tr("Auto-Tag Summary"),
self.tr(summary),
QtGui.QMessageBox.Yes,
QtGui.QMessageBox.No)
QtWidgets.QMessageBox.Yes,
QtWidgets.QMessageBox.No)
match_results.multipleMatches.extend(
match_results.lowConfidenceMatches)
if reply == QtGui.QMessageBox.Yes:
if reply == QtWidgets.QMessageBox.Yes:
matchdlg = AutoTagMatchWindow(
self,
match_results.multipleMatches,
@ -1983,19 +1986,19 @@ class TaggerWindow(QtGui.QMainWindow):
self.loadArchive(self.fileSelectionList.getCurrentArchive())
else:
QtGui.QMessageBox.information(
QtWidgets.QMessageBox.information(
self, self.tr("Auto-Tag Summary"), self.tr(summary))
def dirtyFlagVerification(self, title, desc):
if self.dirtyFlag:
reply = QtGui.QMessageBox.question(
reply = QtWidgets.QMessageBox.question(
self,
self.tr(title),
self.tr(desc),
QtGui.QMessageBox.Yes,
QtGui.QMessageBox.No)
QtWidgets.QMessageBox.Yes,
QtWidgets.QMessageBox.No)
if reply != QtGui.QMessageBox.Yes:
if reply != QtWidgets.QMessageBox.Yes:
return False
return True
@ -2046,10 +2049,10 @@ class TaggerWindow(QtGui.QMainWindow):
dlg.exec_()
def showWiki(self):
webbrowser.open("http://code.google.com/p/comictagger/wiki/Home?tm=6")
webbrowser.open("https://github.com/davide-romanini/comictagger/wiki")
def reportBug(self):
webbrowser.open("http://code.google.com/p/comictagger/issues/list")
webbrowser.open("https://github.com/davide-romanini/comictagger/issues")
def showForum(self):
webbrowser.open("http://comictagger.forumotion.com/")
@ -2070,7 +2073,7 @@ class TaggerWindow(QtGui.QMainWindow):
ca_list = self.fileSelectionList.getSelectedArchiveList()
if len(ca_list) == 0:
QtGui.QMessageBox.information(
QtWidgets.QMessageBox.information(
self, self.tr("Rename"), self.tr("No archives selected!"))
return
@ -2086,7 +2089,7 @@ class TaggerWindow(QtGui.QMainWindow):
self.loadArchive(self.comic_archive)
def fileListSelectionChanged(self, qvarFI):
fi = qvarFI.toPyObject()
fi = qvarFI #.toPyObject()
self.loadArchive(fi.ca)
def loadArchive(self, comic_archive):
@ -2183,7 +2186,7 @@ class TaggerWindow(QtGui.QMainWindow):
win32gui.SetWindowPos(
hwnd, win32con.HWND_NOTOPMOST, x, y, w, h, 0)
except Exception as e:
print "Whoops", e
print("Whoops", e)
elif platform.system() == "Darwin":
self.raise_()
self.showNormal()

View File

@ -8,7 +8,7 @@ from comictaggerlib.settings import ComicTaggerSettings
try:
from PyQt4 import QtGui
from PyQt5 import QtGui
qt_available = True
except ImportError:
qt_available = False
@ -64,7 +64,7 @@ if qt_available:
try:
from PIL import Image
from PIL import WebPImagePlugin
import StringIO
import io
pil_available = True
except ImportError:
pil_available = False
@ -78,8 +78,8 @@ if qt_available:
# Qt doesn't understand the format, but maybe PIL does
# so try to convert the image data to uncompressed tiff
# format
im = Image.open(StringIO.StringIO(image_data))
output = StringIO.StringIO()
im = Image.open(io.StringIO(image_data))
output = io.StringIO()
im.save(output, format="PNG")
success = img.loadFromData(output.getvalue())
except Exception as e:

View File

@ -116,118 +116,6 @@
</property>
</widget>
</item>
<item>
<widget class="QGroupBox" name="grpBoxRar">
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="0" colspan="3">
<widget class="QLabel" name="lblRarHelp">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;In order to read/write to CBR/RAR archives, you will need to have the shareware tools from &lt;a href=&quot;www.win-rar.com/download.html&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#0000ff;&quot;&gt;WinRAR&lt;/span&gt;&lt;/a&gt; installed. &lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="textFormat">
<enum>Qt::RichText</enum>
</property>
<property name="alignment">
<set>Qt::AlignBottom|Qt::AlignLeading|Qt::AlignLeft</set>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
<property name="openExternalLinks">
<bool>true</bool>
</property>
<property name="textInteractionFlags">
<set>Qt::LinksAccessibleByKeyboard|Qt::LinksAccessibleByMouse</set>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_7">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>120</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>200</width>
<height>16777215</height>
</size>
</property>
<property name="text">
<string>RAR program</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QLineEdit" name="leRarExePath">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Maximum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
<item row="1" column="2">
<widget class="QPushButton" name="btnBrowseRar">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Maximum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>...</string>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="lblUnrar">
<property name="minimumSize">
<size>
<width>120</width>
<height>0</height>
</size>
</property>
<property name="text">
<string>UnRAR program</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QLineEdit" name="leUnrarExePath">
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
<item row="2" column="2">
<widget class="QPushButton" name="btnBrowseUnrar">
<property name="text">
<string>...</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
@ -529,7 +417,7 @@
<x>11</x>
<y>21</y>
<width>251</width>
<height>192</height>
<height>199</height>
</rect>
</property>
<layout class="QGridLayout" name="gridLayout_7">
@ -688,6 +576,172 @@
</item>
</layout>
</widget>
<widget class="QWidget" name="tab_7">
<attribute name="title">
<string>RAR Tools</string>
</attribute>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<widget class="QGroupBox" name="grpBoxUnrar">
<layout class="QGridLayout" name="gridLayout">
<item row="1" column="0">
<widget class="QLabel" name="lblUnrar">
<property name="minimumSize">
<size>
<width>120</width>
<height>0</height>
</size>
</property>
<property name="text">
<string>UnRAR library</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QLineEdit" name="leUnrarLibPath">
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
<item row="1" column="2">
<widget class="QPushButton" name="btnBrowseUnrar">
<property name="text">
<string>...</string>
</property>
</widget>
</item>
<item row="0" column="0" colspan="3">
<widget class="QLabel" name="lblUnrarHelp">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;In order to read CBR/RAR archives, you will need to have the unrar library from &lt;a href=&quot;www.win-rar.com/download.html&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#0000ff;&quot;&gt;WinRAR&lt;/span&gt;&lt;/a&gt; installed. &lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="textFormat">
<enum>Qt::RichText</enum>
</property>
<property name="alignment">
<set>Qt::AlignBottom|Qt::AlignLeading|Qt::AlignLeft</set>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
<property name="openExternalLinks">
<bool>true</bool>
</property>
<property name="textInteractionFlags">
<set>Qt::LinksAccessibleByKeyboard|Qt::LinksAccessibleByMouse</set>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="grpBoxRar">
<layout class="QGridLayout" name="gridLayout">
<item row="1" column="0">
<widget class="QLabel" name="label_7">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>120</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>200</width>
<height>16777215</height>
</size>
</property>
<property name="text">
<string>RAR program</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QLineEdit" name="leRarExePath">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Maximum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
<item row="1" column="2">
<widget class="QPushButton" name="btnBrowseRar">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Maximum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>...</string>
</property>
</widget>
</item>
<item row="0" column="0" colspan="3">
<widget class="QLabel" name="lblRarHelp">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;In order to write to CBR/RAR archives, you will need to have the shareware tools from &lt;a href=&quot;www.win-rar.com/download.html&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#0000ff;&quot;&gt;WinRAR&lt;/span&gt;&lt;/a&gt; installed. &lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="textFormat">
<enum>Qt::RichText</enum>
</property>
<property name="alignment">
<set>Qt::AlignBottom|Qt::AlignLeading|Qt::AlignLeft</set>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
<property name="openExternalLinks">
<bool>true</bool>
</property>
<property name="textInteractionFlags">
<set>Qt::LinksAccessibleByKeyboard|Qt::LinksAccessibleByMouse</set>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
</widget>
</item>
<item>

View File

@ -16,13 +16,13 @@
import sys
import platform
import urllib2
import urllib.request, urllib.error, urllib.parse
#import os
#import urllib
try:
from PyQt4.QtNetwork import QNetworkAccessManager, QNetworkRequest, QNetworkReply
from PyQt4.QtCore import QUrl, pyqtSignal, QObject, QByteArray
from PyQt5.QtNetwork import QNetworkAccessManager, QNetworkRequest, QNetworkReply
from PyQt5.QtCore import QUrl, pyqtSignal, QObject, QByteArray
except ImportError:
# No Qt, so define a few dummy QObjects to help us compile
class QObject():
@ -38,7 +38,7 @@ except ImportError:
def emit(a, b, c):
pass
import ctversion
from . import ctversion
class VersionChecker(QObject):
@ -67,7 +67,7 @@ class VersionChecker(QObject):
def getLatestVersion(self, uuid, use_stats=True):
try:
resp = urllib2.urlopen(self.getRequestUrl(uuid, use_stats))
resp = urllib.request.urlopen(self.getRequestUrl(uuid, use_stats))
new_version = resp.read()
except Exception as e:
return None

View File

@ -18,20 +18,20 @@
#import time
#import os
from PyQt4 import QtCore, QtGui, uic
from PyQt4.QtCore import QUrl, pyqtSignal
from PyQt5 import QtCore, QtGui, QtWidgets, uic
from PyQt5.QtCore import QUrl, pyqtSignal
#from PyQt4.QtCore import QObject
#from PyQt4.QtNetwork import QNetworkAccessManager, QNetworkRequest
from comicvinetalker import ComicVineTalker, ComicVineTalkerException
from issueselectionwindow import IssueSelectionWindow
from issueidentifier import IssueIdentifier
from genericmetadata import GenericMetadata
from progresswindow import IDProgressWindow
from settings import ComicTaggerSettings
from matchselectionwindow import MatchSelectionWindow
from coverimagewidget import CoverImageWidget
from comictaggerlib.ui.qtutils import reduceWidgetFontSize
from .comicvinetalker import ComicVineTalker, ComicVineTalkerException
from .issueselectionwindow import IssueSelectionWindow
from .issueidentifier import IssueIdentifier
from .genericmetadata import GenericMetadata
from .progresswindow import IDProgressWindow
from .settings import ComicTaggerSettings
from .matchselectionwindow import MatchSelectionWindow
from .coverimagewidget import CoverImageWidget
from comictaggerlib.ui.qtutils import reduceWidgetFontSize, centerWindowOnParent
#from imagefetcher import ImageFetcher
#import utils
@ -90,7 +90,7 @@ class IdentifyThread(QtCore.QThread):
self.identifyComplete.emit()
class VolumeSelectionWindow(QtGui.QDialog):
class VolumeSelectionWindow(QtWidgets.QDialog):
def __init__(self, parent, series_name, issue_number, year, issue_count,
cover_index_list, comic_archive, settings, autoselect=False):
@ -101,7 +101,7 @@ class VolumeSelectionWindow(QtGui.QDialog):
self.imageWidget = CoverImageWidget(
self.imageContainer, CoverImageWidget.URLMode)
gridlayout = QtGui.QGridLayout(self.imageContainer)
gridlayout = QtWidgets.QGridLayout(self.imageContainer)
gridlayout.addWidget(self.imageWidget)
gridlayout.setContentsMargins(0, 0, 0, 0)
@ -113,6 +113,7 @@ class VolumeSelectionWindow(QtGui.QDialog):
QtCore.Qt.WindowMaximizeButtonHint)
self.settings = settings
self.parent = parent
self.series_name = series_name
self.issue_number = issue_number
self.year = year
@ -144,7 +145,7 @@ class VolumeSelectionWindow(QtGui.QDialog):
self.btnRequery.setEnabled(enabled)
self.btnIssues.setEnabled(enabled)
self.btnAutoSelect.setEnabled(enabled)
self.buttonBox.button(QtGui.QDialogButtonBox.Ok).setEnabled(enabled)
self.buttonBox.button(QtWidgets.QDialogButtonBox.Ok).setEnabled(enabled)
def requery(self,):
self.performQuery(refresh=True)
@ -153,12 +154,12 @@ class VolumeSelectionWindow(QtGui.QDialog):
def autoSelect(self):
if self.comic_archive is None:
QtGui.QMessageBox.information(
QtWidgets.QMessageBox.information(
self, "Auto-Select", "You need to load a comic first!")
return
if self.issue_number is None or self.issue_number == "":
QtGui.QMessageBox.information(
QtWidgets.QMessageBox.information(
self,
"Auto-Select",
"Can't auto-select without an issue number (yet!)")
@ -192,7 +193,7 @@ class VolumeSelectionWindow(QtGui.QDialog):
self.iddialog.exec_()
def logIDOutput(self, text):
print unicode(text),
print(str(text), end=' ')
self.iddialog.textEdit.ensureCursorVisible()
self.iddialog.textEdit.insertPlainText(text)
@ -212,22 +213,22 @@ class VolumeSelectionWindow(QtGui.QDialog):
found_match = None
choices = False
if result == self.ii.ResultNoMatches:
QtGui.QMessageBox.information(
QtWidgets.QMessageBox.information(
self, "Auto-Select Result", " No matches found :-(")
elif result == self.ii.ResultFoundMatchButBadCoverScore:
QtGui.QMessageBox.information(
QtWidgets.QMessageBox.information(
self,
"Auto-Select Result",
" Found a match, but cover doesn't seem the same. Verify before commiting!")
found_match = matches[0]
elif result == self.ii.ResultFoundMatchButNotFirstPage:
QtGui.QMessageBox.information(
QtWidgets.QMessageBox.information(
self,
"Auto-Select Result",
" Found a match, but not with the first page of the archive.")
found_match = matches[0]
elif result == self.ii.ResultMultipleMatchesWithBadImageScores:
QtGui.QMessageBox.information(
QtWidgets.QMessageBox.information(
self,
"Auto-Select Result",
" Found some possibilities, but no confidence. Proceed manually.")
@ -235,7 +236,7 @@ class VolumeSelectionWindow(QtGui.QDialog):
elif result == self.ii.ResultOneGoodMatch:
found_match = matches[0]
elif result == self.ii.ResultMultipleGoodMatches:
QtGui.QMessageBox.information(
QtWidgets.QMessageBox.information(
self,
"Auto-Select Result",
" Found multiple likely matches. Please select.")
@ -264,7 +265,7 @@ class VolumeSelectionWindow(QtGui.QDialog):
for record in self.cv_search_results:
if record['id'] == self.volume_id:
title = record['name']
title += " (" + unicode(record['start_year']) + ")"
title += " (" + str(record['start_year']) + ")"
title += " - "
break
@ -279,26 +280,24 @@ class VolumeSelectionWindow(QtGui.QDialog):
def selectByID(self):
for r in range(0, self.twList.rowCount()):
volume_id, b = self.twList.item(
r, 0).data(QtCore.Qt.UserRole).toInt()
volume_id = self.twList.item(r, 0).data(QtCore.Qt.UserRole)
if (volume_id == self.volume_id):
self.twList.selectRow(r)
break
def performQuery(self, refresh=False):
self.progdialog = QtGui.QProgressDialog(
self.progdialog = QtWidgets.QProgressDialog(
"Searching Online", "Cancel", 0, 100, self)
self.progdialog.setWindowTitle("Online Search")
self.progdialog.canceled.connect(self.searchCanceled)
self.progdialog.setModal(True)
self.progdialog.setMinimumDuration(300)
QtCore.QCoreApplication.processEvents()
self.search_thread = SearchThread(self.series_name, refresh)
self.search_thread.searchComplete.connect(self.searchComplete)
self.search_thread.progressUpdate.connect(self.searchProgressUpdate)
self.search_thread.start()
# QtCore.QCoreApplication.processEvents()
self.progdialog.exec_()
def searchCanceled(self):
@ -315,18 +314,20 @@ class VolumeSelectionWindow(QtGui.QDialog):
def searchProgressUpdate(self, current, total):
self.progdialog.setMaximum(total)
self.progdialog.setValue(current)
self.progdialog.setValue(current+1)
def searchComplete(self):
self.progdialog.accept()
del self.progdialog
QtCore.QCoreApplication.processEvents()
if self.search_thread.cv_error:
if self.search_thread.error_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 search for series!"))
@ -345,20 +346,20 @@ class VolumeSelectionWindow(QtGui.QDialog):
self.twList.insertRow(row)
item_text = record['name']
item = QtGui.QTableWidgetItem(item_text)
item = QtWidgets.QTableWidgetItem(item_text)
item.setData(QtCore.Qt.ToolTipRole, item_text)
item.setData(QtCore.Qt.UserRole, record['id'])
item.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled)
self.twList.setItem(row, 0, item)
item_text = str(record['start_year'])
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)
item_text = record['count_of_issues']
item = QtGui.QTableWidgetItem(item_text)
item = QtWidgets.QTableWidgetItem(item_text)
item.setData(QtCore.Qt.ToolTipRole, item_text)
item.setData(QtCore.Qt.DisplayRole, record['count_of_issues'])
item.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled)
@ -367,7 +368,7 @@ class VolumeSelectionWindow(QtGui.QDialog):
if record['publisher'] is not None:
item_text = record['publisher']['name']
item.setData(QtCore.Qt.ToolTipRole, item_text)
item = QtGui.QTableWidgetItem(item_text)
item = QtWidgets.QTableWidgetItem(item_text)
item.setFlags(
QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled)
self.twList.setItem(row, 3, item)
@ -382,7 +383,7 @@ class VolumeSelectionWindow(QtGui.QDialog):
if len(self.cv_search_results) == 0:
QtCore.QCoreApplication.processEvents()
QtGui.QMessageBox.information(
QtWidgets.QMessageBox.information(
self, "Search Result", "No matches found!")
if self.immediate_autoselect and len(self.cv_search_results) > 0:
@ -404,9 +405,8 @@ class VolumeSelectionWindow(QtGui.QDialog):
if prev is not None and prev.row() == curr.row():
return
self.volume_id, b = self.twList.item(
curr.row(), 0).data(QtCore.Qt.UserRole).toInt()
self.volume_id = self.twList.item(curr.row(), 0).data(QtCore.Qt.UserRole)
# list selection was changed, update the info on the volume
for record in self.cv_search_results:
if record['id'] == self.volume_id:

View File

@ -0,0 +1,11 @@
[Desktop Entry]
Encoding=UTF-8
Name=ComicTagger
GenericName=Comic Metadata Editor
Comment=A cross-platform GUI/CLI app for writing metadata to comic archives
Exec=%%CTSCRIPT%% %F
Icon=/usr/local/share/comictagger/app.png
Terminal=false
Type=Application
MimeType=text/plain;
Categories=Application;

View File

@ -0,0 +1,4 @@
This file is a placeholder that will automaticlly be replaced with a symlink to
the local machine's Python framework python binary.
When pip does an uninstall, it will remove the link.

View File

@ -0,0 +1,32 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>English</string>
<key>CFBundleExecutable</key>
<string>main.sh</string>
<key>CFBundleIconFile</key>
<string>app.icns</string>
<key>CFBundleIdentifier</key>
<string>org.comictagger.comictagger</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>ComicTagger</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>%%CTVERSION%%</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>%%CTVERSION%%</string>
<key>NSAppleScriptEnabled</key>
<string>YES</string>
<key>NSMainNibFile</key>
<string>MainMenu</string>
<key>NSPrincipalClass</key>
<string>NSApplication</string>
</dict>
</plist>

17
desktop-integration/mac/main.sh Executable file
View File

@ -0,0 +1,17 @@
#!/bin/sh
# This is a lot of hoop-jumping to get the absolute path
# of this script, so that we can use the Symlinked python
# binary to call the CT script. This is all so that the
# Mac menu doesn't say "Python".
realpath()
{
[[ $1 = /* ]] && echo "$1" || echo "$PWD/${1#./}"
}
CTSCRIPT=%%CTSCRIPT%%
THIS=$(realpath $0)
THIS_FOLDER=$(dirname $THIS)
"$THIS_FOLDER/ComicTagger" "$CTSCRIPT"

View File

@ -0,0 +1,4 @@
This file is a placeholder that will automatically be replaced with a Windows
shortcut on the user's desktop.
When pip does an uninstall, it will remove the shortcut file.

View File

@ -3,5 +3,6 @@ beautifulsoup4 >= 4.1
unrar==0.3
natsort==3.5.2
PyPDF2==1.24
Pillow==4.3.0
PyInstaller==3.3.1
pillow>=4.3.0
PyQt5>=5.10.1
git+https://github.com/pyinstaller/pyinstaller@develop

243
setup.py
View File

@ -1,42 +1,227 @@
#!/usr/bin/env python
# Setup file for comictagger python source (no wheels yet)
#
# The install process will attempt to compile the unrar lib from source.
# If it succeeds, the unrar lib binary will be installed with the python
# source. If it fails, install will just continue. On most Linux systems it
# should just work. (Tested on a Mac system with homebrew, as well)
#
# An entry point script called "comictagger" will be created
#
# Currently commented out, an experiment at desktop integration.
# It seems that post installation tweaks are broken by wheel files.
# Kept here for further research
from __future__ import print_function
from setuptools import setup
from setuptools import dist
from setuptools import Command
import setuptools.command.build_py
import setuptools.command.install
import subprocess
import os
import sys
import shutil
import platform
import tempfile
import comictaggerlib.ctversion
python_requires='>=3',
with open('requirements.txt') as f:
required = f.read().splitlines()
# Always require PyQt5 on Windows and Mac
if platform.system() in [ "Windows", "Darwin" ]:
required.append("PyQt5")
platform_data_files = []
"""
if platform.system() in [ "Windows" ]:
required.append("winshell")
# Some files to install on different platforms
if platform.system() == "Linux":
linux_desktop_shortcut = "/usr/local/share/applications/ComicTagger.desktop"
platform_data_files = [("/usr/local/share/applications",
["desktop-integration/linux/ComicTagger.desktop"]),
("/usr/local/share/comictagger",
["comictaggerlib/graphics/app.png"]),
]
if platform.system() == "Windows":
win_desktop_folder = os.path.join(os.environ["USERPROFILE"], "Desktop")
win_appdata_folder = os.path.join(os.environ["APPDATA"], "comictagger")
win_desktop_shortcut = os.path.join(win_desktop_folder, "ComicTagger-pip.lnk")
platform_data_files = [(win_desktop_folder,
["desktop-integration/windows/ComicTagger-pip.lnk"]),
(win_appdata_folder,
["windows/app.ico"]),
]
if platform.system() == "Darwin":
mac_app_folder = "/Applications"
ct_app_name = "ComicTagger-pip.app"
mac_app_infoplist = os.path.join(mac_app_folder, ct_app_name, "Contents", "Info.plist")
mac_app_main = os.path.join(mac_app_folder, ct_app_name, "MacOS", "main.sh")
mac_python_link = os.path.join(mac_app_folder, ct_app_name, "MacOS", "ComicTagger")
platform_data_files = [(os.path.join(mac_app_folder, ct_app_name, "Contents"),
["desktop-integration/mac/Info.plist"]),
(os.path.join(mac_app_folder, ct_app_name, "Contents/Resources"),
["mac/app.icns"]),
(os.path.join(mac_app_folder, ct_app_name, "Contents/MacOS"),
["desktop-integration/mac/main.sh",
"desktop-integration/mac/ComicTagger"]),
]
def fileTokenReplace(filename, token, replacement):
with open(filename, "rt") as fin:
fd, tmpfile = tempfile.mkstemp()
with open(tmpfile, "wt") as fout:
for line in fin:
fout.write(line.replace('%%{}%%'.format(token), replacement))
os.close(fd)
# fix permissions of temp file
os.chmod(tmpfile, 420) #Octal 0o644
os.rename(tmpfile, filename)
def postInstall(scripts_folder):
entry_point_script = os.path.join(scripts_folder, "comictagger")
if platform.system() == "Windows":
# doctor the shortcut for this windows system after deployment
import winshell
winshell.CreateShortcut(
Path=os.path.abspath(win_desktop_shortcut),
Target=entry_point_script + ".exe",
Icon=(os.path.join(win_appdata_folder, 'app.ico'), 0),
Description="Launch ComicTagger as installed by PIP"
)
if platform.system() == "Linux":
# doctor the script path in the desktop file
fileTokenReplace(linux_desktop_shortcut,
"CTSCRIPT",
entry_point_script)
if platform.system() == "Darwin":
# doctor the plist app version
fileTokenReplace(mac_app_infoplist,
"CTVERSION",
comictaggerlib.ctversion.version)
# doctor the script path in main.sh
fileTokenReplace(mac_app_main,
"CTSCRIPT",
entry_point_script)
# Make the launcher script executable
os.chmod(mac_app_main, 509) #Octal 0o775
# Final install step: create a symlink to Python OS X application
punt = False
pythonpath,top = os.path.split(os.path.realpath(sys.executable))
while top:
if 'Resources' in pythonpath:
pass
elif os.path.exists(os.path.join(pythonpath,'Resources')):
break
pythonpath,top = os.path.split(pythonpath)
else:
print("Failed to find a Resources directory associated with ", str(sys.executable))
punt = True
if not punt:
pythonapp = os.path.join(pythonpath, 'Resources','Python.app','Contents','MacOS','Python')
if not os.path.exists(pythonapp):
print("Failed to find a Python app in ", str(pythonapp))
punt = True
# remove the placeholder
os.remove(mac_python_link)
if not punt:
os.symlink(pythonapp, mac_python_link)
else:
# We failed, but we can still be functional
os.symlink(sys.executable, mac_python_link)
"""
class BuildUnrarCommand(Command):
description = 'build unrar library'
user_options = []
def initialize_options(self):
pass
def finalize_options(self):
pass
def run(self):
try:
if not os.path.exists("comictaggerlib/libunrar.so"):
if not os.path.exists("unrar/libunrar.so"):
print("Building C++ unrar library....")
subprocess.call(['make', '-C', 'unrar', 'lib'])
print("Copying .so file to comictaggerlib folder")
shutil.copyfile("unrar/libunrar.so", "comictaggerlib/libunrar.so")
except Exception as e:
print(e)
print("WARNING ---- Unrar library build/deploy failed. User will have to self-install libunrar.")
class BuildPyCommand(setuptools.command.build_py.build_py):
"""Custom build command."""
def run(self):
self.run_command('build_unrar')
setuptools.command.build_py.build_py.run(self)
class customInstall(setuptools.command.install.install):
"""Custom install command."""
def run(self):
# Do the standard install
setuptools.command.install.install.run(self)
# Custom post install
#postInstall(self.install_scripts)
#----------------------------------------------------
setup(name="comictagger",
install_requires=required,
cmdclass={
'build_unrar': BuildUnrarCommand,
'build_py': BuildPyCommand,
'install': customInstall,
},
version=comictaggerlib.ctversion.version,
description="A cross-platform GUI/CLI app for writing metadata to comic archives",
author="Anthony Beville",
author="ComicTagger team",
author_email="comictagger@gmail.com",
url="http://code.google.com/p/comictagger/",
url="https://github.com/davide-romanini/comictagger",
download_url="https://pypi.python.org/packages/source/c/comictagger/comictagger-{0}.zip".format(comictaggerlib.ctversion.version),
packages=["comictaggerlib", "comicapi", "comicapi/UnRAR2"],
packages=["comictaggerlib", "comicapi"],
package_data={
'comictaggerlib': ['ui/*.ui', 'graphics/*'],
'comicapi/UnRAR2': ['UnRARDLL/*.*', 'UnRARDLL/x64/*.*'],
'comictaggerlib': ['ui/*', 'graphics/*', '*.so'],
},
scripts=["comictagger.py"],
entry_points=dict(console_scripts=['comictagger=comictaggerlib.main:ctmain']),
data_files=platform_data_files,
classifiers=[
"Development Status :: 4 - Beta",
"Environment :: Console",
"Environment :: Win32 (MS Windows)",
"Environment :: MacOS X",
"Environment :: X11 Applications :: Qt",
"Intended Audience :: End Users/Desktop",
"License :: OSI Approved :: Apache Software License",
"Natural Language :: English",
"Operating System :: OS Independent",
"Programming Language :: Python",
"Programming Language :: Python :: 2.6",
"Programming Language :: Python :: 2.7",
"Topic :: Utilities",
"Topic :: Other/Nonlisted Topic",
"Topic :: Multimedia :: Graphics"
"Development Status :: 4 - Beta",
"Environment :: Console",
"Environment :: Win32 (MS Windows)",
"Environment :: MacOS X",
"Environment :: X11 Applications :: Qt",
"Intended Audience :: End Users/Desktop",
"License :: OSI Approved :: Apache Software License",
"Natural Language :: English",
"Operating System :: OS Independent",
"Programming Language :: Python :: 3.5",
"Programming Language :: Python :: 3.6",
"Topic :: Utilities",
"Topic :: Other/Nonlisted Topic",
"Topic :: Multimedia :: Graphics"
],
keywords=['comictagger', 'comics', 'comic', 'metadata', 'tagging', 'tagger'],
license="Apache License 2.0",
long_description="""
@ -48,19 +233,9 @@ Features:
* 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 multiple tagging schemes ( ComicBookLover and ComicRack).
* 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.
Requires:
* python 2.6 or 2.7
* configparser
* python imaging (PIL) >= 1.1.6
* beautifulsoup > 4.1
Optional requirement (for GUI):
* pyqt4
* Can run without PyQt5 installed
"""
)

View File

@ -1,9 +1,8 @@
# Script to be run inside appveyor for a full build
$env:PATH="C:\tools\mingw64\bin;C:\Miniconda-x64;C:\Miniconda-x64\Scripts;$env:path"
choco install -y mingw
C:\Miniconda-x64\Scripts\conda create -y --name comictagger python=2
C:\Miniconda-x64\Scripts\activate comictagger
C:\Miniconda-x64\Scripts\conda install -y pyqt=4
C:\Miniconda-x64\Scripts\pip install -r .\requirements.txt
refreshenv
$env:PATH="C:\Python36-x64;$env:path"
python -m venv venv
.\venv\Scripts\Activate.ps1
pip install -r .\requirements.txt
mingw32-make dist
objdump -af unrar/libunrar.so