Compare commits

..

40 Commits

Author SHA1 Message Date
719c711484 Language tweak
git-svn-id: http://comictagger.googlecode.com/svn/trunk@668 6c5673fe-1810-88d6-992b-cd32ca31540c
2014-03-23 18:03:08 +00:00
afbbc9d00c git-svn-id: http://comictagger.googlecode.com/svn/trunk@667 6c5673fe-1810-88d6-992b-cd32ca31540c 2014-03-23 17:48:59 +00:00
b8e0a45fc8 bumped version and release notes
git-svn-id: http://comictagger.googlecode.com/svn/trunk@665 6c5673fe-1810-88d6-992b-cd32ca31540c
2014-03-23 17:31:00 +00:00
b7360dd33e Updated copyright dates
git-svn-id: http://comictagger.googlecode.com/svn/trunk@664 6c5673fe-1810-88d6-992b-cd32ca31540c
2014-03-23 17:30:23 +00:00
d9f1956426 handle a crash bug when file starts with --
git-svn-id: http://comictagger.googlecode.com/svn/trunk@663 6c5673fe-1810-88d6-992b-cd32ca31540c
2014-03-23 16:56:04 +00:00
b5c7f36410 New pyunrar version to handle rar tools 5.x
git-svn-id: http://comictagger.googlecode.com/svn/trunk@662 6c5673fe-1810-88d6-992b-cd32ca31540c
2014-03-22 21:43:03 +00:00
0b0663d935 Update copyright date
git-svn-id: http://comictagger.googlecode.com/svn/trunk@661 6c5673fe-1810-88d6-992b-cd32ca31540c
2014-03-22 21:42:07 +00:00
eee1f65436 handle corner case of non-numeric issue ending in "."
git-svn-id: http://comictagger.googlecode.com/svn/trunk@660 6c5673fe-1810-88d6-992b-cd32ca31540c
2014-03-22 21:41:38 +00:00
9a8d4149f2 fixed spelling error
git-svn-id: http://comictagger.googlecode.com/svn/trunk@659 6c5673fe-1810-88d6-992b-cd32ca31540c
2014-03-22 21:40:01 +00:00
b02a205668 Make sure all error print outs are unicode
Catch error when zipfile list fails

git-svn-id: http://comictagger.googlecode.com/svn/trunk@658 6c5673fe-1810-88d6-992b-cd32ca31540c
2014-03-22 21:38:36 +00:00
57284dfbed fixed typo in makefile
git-svn-id: http://comictagger.googlecode.com/svn/trunk@657 6c5673fe-1810-88d6-992b-cd32ca31540c
2014-03-22 21:37:19 +00:00
afcbde7fc6 update todo
git-svn-id: http://comictagger.googlecode.com/svn/trunk@651 6c5673fe-1810-88d6-992b-cd32ca31540c
2014-01-31 04:45:15 +00:00
151fac5bf1 updated release notes
git-svn-id: http://comictagger.googlecode.com/svn/trunk@650 6c5673fe-1810-88d6-992b-cd32ca31540c
2014-01-31 04:45:06 +00:00
57c1efdab9 makefile TAGGER_BASE can be set in the environment
git-svn-id: http://comictagger.googlecode.com/svn/trunk@649 6c5673fe-1810-88d6-992b-cd32ca31540c
2014-01-31 04:43:22 +00:00
6b272cef87 When searching for a title, convert the string to a list of words separated by "ANDS", and then back to a string
git-svn-id: http://comictagger.googlecode.com/svn/trunk@648 6c5673fe-1810-88d6-992b-cd32ca31540c
2014-01-31 04:40:58 +00:00
1cdc732739 Added a message when not able to open selected folder or file
git-svn-id: http://comictagger.googlecode.com/svn/trunk@647 6c5673fe-1810-88d6-992b-cd32ca31540c
2014-01-31 04:39:47 +00:00
d1b00d162d Allow any size archive to be considered a comic
git-svn-id: http://comictagger.googlecode.com/svn/trunk@646 6c5673fe-1810-88d6-992b-cd32ca31540c
2014-01-31 04:37:13 +00:00
3dd3980bc1 update todo file
git-svn-id: http://comictagger.googlecode.com/svn/trunk@645 6c5673fe-1810-88d6-992b-cd32ca31540c
2013-08-18 18:01:01 +00:00
cbf475eb26 removed filtering out of period (".")
git-svn-id: http://comictagger.googlecode.com/svn/trunk@644 6c5673fe-1810-88d6-992b-cd32ca31540c
2013-08-18 18:00:04 +00:00
ac8b575659 bumped version number
git-svn-id: http://comictagger.googlecode.com/svn/trunk@643 6c5673fe-1810-88d6-992b-cd32ca31540c
2013-08-18 17:56:38 +00:00
ac8ef286a4 Perform the rar test first, since some rars can be falsly identified as zips, somehow...
git-svn-id: http://comictagger.googlecode.com/svn/trunk@641 6c5673fe-1810-88d6-992b-cd32ca31540c
2013-07-23 17:06:35 +00:00
f567dc37be Handle case of None value credit tags in XML
git-svn-id: http://comictagger.googlecode.com/svn/trunk@640 6c5673fe-1810-88d6-992b-cd32ca31540c
2013-07-08 23:32:24 +00:00
15c5fc5258 release notes update
git-svn-id: http://comictagger.googlecode.com/svn/trunk@637 6c5673fe-1810-88d6-992b-cd32ca31540c
2013-05-09 01:31:26 +00:00
cc985b52a5 Do the limited series check/elimination after cover matching
git-svn-id: http://comictagger.googlecode.com/svn/trunk@636 6c5673fe-1810-88d6-992b-cd32ca31540c
2013-05-08 02:39:06 +00:00
910b0386be Remove tooltip if not expandable
git-svn-id: http://comictagger.googlecode.com/svn/trunk@635 6c5673fe-1810-88d6-992b-cd32ca31540c
2013-05-08 02:38:06 +00:00
0fece23405 Allow rename w/smart cleanup to have "--"
git-svn-id: http://comictagger.googlecode.com/svn/trunk@634 6c5673fe-1810-88d6-992b-cd32ca31540c
2013-05-06 22:30:32 +00:00
eee320e0c7 bumped version number
git-svn-id: http://comictagger.googlecode.com/svn/trunk@632 6c5673fe-1810-88d6-992b-cd32ca31540c
2013-05-06 21:07:00 +00:00
accabf8e21 Added keyboard shortcut for form clear
git-svn-id: http://comictagger.googlecode.com/svn/trunk@631 6c5673fe-1810-88d6-992b-cd32ca31540c
2013-05-06 21:06:48 +00:00
acc253d35c todo update
git-svn-id: http://comictagger.googlecode.com/svn/trunk@630 6c5673fe-1810-88d6-992b-cd32ca31540c
2013-05-06 18:36:30 +00:00
ede0154efe issueCount now gets passed to issueidentifier.
a possible technique for eliminating potential volumes is coded, but commented out for now

git-svn-id: http://comictagger.googlecode.com/svn/trunk@629 6c5673fe-1810-88d6-992b-cd32ca31540c
2013-05-06 18:24:57 +00:00
5b805b1428 auto-tag progress window now uses coverimagewidget
git-svn-id: http://comictagger.googlecode.com/svn/trunk@628 6c5673fe-1810-88d6-992b-cd32ca31540c
2013-05-06 18:22:14 +00:00
2e6b2a89db Added a raw image data mode for the coverimagewidget
git-svn-id: http://comictagger.googlecode.com/svn/trunk@627 6c5673fe-1810-88d6-992b-cd32ca31540c
2013-05-06 18:21:34 +00:00
c028bb4ddc Make sure to catch all non-numeric characters after a # for the issue number
git-svn-id: http://comictagger.googlecode.com/svn/trunk@626 6c5673fe-1810-88d6-992b-cd32ca31540c
2013-05-04 01:48:42 +00:00
b70beb5684 more file name parser enhancements
git-svn-id: http://comictagger.googlecode.com/svn/trunk@625 6c5673fe-1810-88d6-992b-cd32ca31540c
2013-05-04 01:22:39 +00:00
128af4521b better filename parsing
git-svn-id: http://comictagger.googlecode.com/svn/trunk@623 6c5673fe-1810-88d6-992b-cd32ca31540c
2013-05-02 16:31:50 +00:00
43cf7a80c8 remove print
git-svn-id: http://comictagger.googlecode.com/svn/trunk@622 6c5673fe-1810-88d6-992b-cd32ca31540c
2013-05-01 22:33:05 +00:00
3223ed190c Make sure form is updated when removing top item from list
git-svn-id: http://comictagger.googlecode.com/svn/trunk@621 6c5673fe-1810-88d6-992b-cd32ca31540c
2013-05-01 22:32:20 +00:00
9e2817c037 deal with CV bug (wrong result set count) when not specifying page=1
git-svn-id: http://comictagger.googlecode.com/svn/trunk@620 6c5673fe-1810-88d6-992b-cd32ca31540c
2013-05-01 22:31:25 +00:00
6e7bd10fb9 deal with pagination bug on comicvine side reporting wrong result set size when not specifiying page=1
git-svn-id: http://comictagger.googlecode.com/svn/trunk@619 6c5673fe-1810-88d6-992b-cd32ca31540c
2013-05-01 22:30:30 +00:00
c099205779 Reworked the issue string parsing
git-svn-id: http://comictagger.googlecode.com/svn/trunk@618 6c5673fe-1810-88d6-992b-cd32ca31540c
2013-04-30 18:05:10 +00:00
49 changed files with 541 additions and 367 deletions

View File

@ -1,4 +1,4 @@
TAGGER_BASE := $(HOME)/Dropbox/tagger/comictagger
TAGGER_BASE ?= $(HOME)/Dropbox/tagger/comictagger
TAGGER_SRC := $(TAGGER_BASE)/comictaggerlib
VERSION_STR := $(shell grep version $(TAGGER_SRC)/ctversion.py| cut -d= -f2 | sed 's/\"//g')
PASSWORD := $(shell cat $(TAGGER_BASE)/project_password.txt)

View File

@ -1,4 +1,4 @@
ComicTagger is a multi-platform app for writing metadata to comic archives, written in Python and PyQt.
ComicTagger is a multi-platform app for writing metadata to digital comics, written in Python and PyQt.
Features:

View File

@ -33,7 +33,7 @@ similar to the C interface provided by UnRAR. There is also a
higher level interface which makes some common operations easier.
"""
__version__ = '0.99.2'
__version__ = '0.99.3'
try:
WindowsError

View File

@ -12,13 +12,11 @@ def cleanup(dir='test'):
os.removedirs(os.path.join(path, dir))
# reuse RarArchive object, en
# basic test
cleanup()
rarc = UnRAR2.RarFile('test.rar')
rarc.infolist()
assert rarc.comment == "This is a test."
for info in rarc.infoiter():
saveinfo = info
assert (str(info)=="""<RarInfo "test" in "test.rar">""")
@ -98,7 +96,8 @@ list(UnRAR2.RarFile('test_nulls.rar').infoiter())
# extract files from an archive with protected files
cleanup()
UnRAR2.RarFile('test_protected_files.rar', password="protected").extract()
rarc = UnRAR2.RarFile('test_protected_files.rar', password="protected")
rarc.extract()
assert os.path.exists('test'+os.sep+'top_secret_xxx_file.txt')
cleanup()
errored = False

View File

@ -33,6 +33,7 @@ from rar_exceptions import *
class UnpackerNotInstalled(Exception): pass
rar_executable_cached = None
rar_executable_version = None
def call_unrar(params):
"Calls rar/unrar command line executable, returns stdout pipe"
@ -59,10 +60,10 @@ def call_unrar(params):
class RarFileImplementation(object):
def init(self, password=None):
global rar_executable_version
self.password = password
stdoutdata, stderrdata = self.call('v', []).communicate()
for line in stderrdata.splitlines():
@ -73,18 +74,42 @@ class RarFileImplementation(object):
accum = []
source = iter(stdoutdata.splitlines())
line = ''
while not (line.startswith('Comment:') or line.startswith('Pathname/Comment')):
if line.strip().endswith('is not RAR archive'):
raise InvalidRARArchive
while not (line.startswith('UNRAR')):
line = source.next()
while not line.startswith('Pathname/Comment'):
accum.append(line.rstrip('\n'))
signature = line
# The code below is mighty flaky
# and will probably crash on localized versions of RAR
# but I see no safe way to rewrite it using a CLI tool
if signature.startswith("UNRAR 4"):
rar_executable_version = 4
while not (line.startswith('Comment:') or line.startswith('Pathname/Comment')):
if line.strip().endswith('is not RAR archive'):
raise InvalidRARArchive
line = source.next()
while not line.startswith('Pathname/Comment'):
accum.append(line.rstrip('\n'))
line = source.next()
if len(accum):
accum[0] = accum[0][9:] # strip out "Comment:" part
self.comment = '\n'.join(accum[:-1])
else:
self.comment = None
elif signature.startswith("UNRAR 5"):
rar_executable_version = 5
line = source.next()
if len(accum):
accum[0] = accum[0][9:]
self.comment = '\n'.join(accum[:-1])
while not line.startswith('Archive:'):
if line.strip().endswith('is not RAR archive'):
raise InvalidRARArchive
accum.append(line.rstrip('\n'))
line = source.next()
if len(accum):
self.comment = '\n'.join(accum[:-1]).strip()
else:
self.comment = None
else:
self.comment = None
raise UnpackerNotInstalled("Unsupported RAR version, expected 4.x or 5.x, found: "
+ signature.split(" ")[1])
def escaped_password(self):
return '-' if self.password == None else self.password
@ -97,7 +122,8 @@ class RarFileImplementation(object):
def infoiter(self):
stdoutdata, stderrdata = self.call('v', ['c-']).communicate()
command = "v" if rar_executable_version == 4 else "l"
stdoutdata, stderrdata = self.call(command, ['c-']).communicate()
for line in stderrdata.splitlines():
if line.strip().startswith("Cannot open"):
@ -106,33 +132,48 @@ class RarFileImplementation(object):
accum = []
source = iter(stdoutdata.splitlines())
line = ''
while not line.startswith('--------------'):
while not line.startswith('-----------'):
if line.strip().endswith('is not RAR archive'):
raise InvalidRARArchive
if line.find("CRC failed")>=0:
if line.startswith("CRC failed") or line.startswith("Checksum error"):
raise IncorrectRARPassword
line = source.next()
line = source.next()
i = 0
re_spaces = re.compile(r"\s+")
while not line.startswith('--------------'):
accum.append(line)
if len(accum)==2:
if rar_executable_version == 4:
while not line.startswith('-----------'):
accum.append(line)
if len(accum)==2:
data = {}
data['index'] = i
# asterisks mark password-encrypted files
data['filename'] = accum[0].strip().lstrip("*") # asterisks marks password-encrypted files
fields = re_spaces.split(accum[1].strip())
data['size'] = int(fields[0])
attr = fields[5]
data['isdir'] = 'd' in attr.lower()
data['datetime'] = time.strptime(fields[3]+" "+fields[4], '%d-%m-%y %H:%M')
data['comment'] = None
yield data
accum = []
i += 1
line = source.next()
elif rar_executable_version == 5:
while not line.startswith('-----------'):
fields = line.strip().lstrip("*").split()
data = {}
data['index'] = i
#!!!ATB - changed this because it was choking when a folder or file started with a space.
#!!! now, just strip off the first char in the string
data['filename'] = accum[0].rstrip()[1:]
info = re_spaces.split(accum[1].strip())
data['size'] = int(info[0])
attr = info[5]
data['filename'] = " ".join(fields[4:])
data['size'] = int(fields[1])
attr = fields[0]
data['isdir'] = 'd' in attr.lower()
data['datetime'] = time.strptime(info[3]+" "+info[4], '%d-%m-%y %H:%M')
data['datetime'] = time.strptime(fields[2]+" "+fields[3], '%d-%m-%y %H:%M')
data['comment'] = None
yield data
accum = []
i += 1
line = source.next()
line = source.next()
def read_files(self, checker):
res = []
@ -153,7 +194,7 @@ class RarFileImplementation(object):
if overwrite:
options.append('o+')
else:
options.append('o-')
options.append('o-')
if not path.endswith(os.sep):
path += os.sep
names = []
@ -167,7 +208,7 @@ class RarFileImplementation(object):
names.append(path)
proc = self.call(command, options, names)
stdoutdata, stderrdata = proc.communicate()
if stderrdata.find("CRC failed")>=0:
if stderrdata.find("CRC failed")>=0 or stderrdata.find("Checksum error")>=0:
raise IncorrectRARPassword
return res

View File

@ -3,7 +3,7 @@ A PyQT4 dialog to select from automated issue matches
"""
"""
Copyright 2012 Anthony Beville
Copyright 2012-2014 Anthony Beville
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.

View File

@ -3,7 +3,7 @@ A PyQT4 dialog to show ID log and progress
"""
"""
Copyright 2012 Anthony Beville
Copyright 2012-2014 Anthony Beville
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@ -22,6 +22,7 @@ import sys
from PyQt4 import QtCore, QtGui, uic
import os
from settings import ComicTaggerSettings
from coverimagewidget import CoverImageWidget
import utils
class AutoTagProgressWindow(QtGui.QDialog):
@ -31,8 +32,17 @@ class AutoTagProgressWindow(QtGui.QDialog):
super(AutoTagProgressWindow, self).__init__(parent)
uic.loadUi(ComicTaggerSettings.getUIFile('autotagprogresswindow.ui' ), self)
self.lblTest.setPixmap(QtGui.QPixmap(ComicTaggerSettings.getGraphic('nocover.png')))
self.lblArchive.setPixmap(QtGui.QPixmap(ComicTaggerSettings.getGraphic('nocover.png')))
self.archiveCoverWidget = CoverImageWidget( self.archiveCoverContainer, CoverImageWidget.DataMode, False )
gridlayout = QtGui.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.addWidget( self.testCoverWidget )
gridlayout.setContentsMargins(0,0,0,0)
self.isdone = False
self.setWindowFlags(self.windowFlags() |
@ -42,23 +52,16 @@ class AutoTagProgressWindow(QtGui.QDialog):
utils.reduceWidgetFontSize( self.textEdit )
def setArchiveImage( self, img_data):
self.setCoverImage( img_data, self.lblArchive )
self.setCoverImage( img_data, self.archiveCoverWidget)
def setTestImage( self, img_data):
self.setCoverImage( img_data, self.lblTest )
self.setCoverImage( img_data, self.testCoverWidget)
def setCoverImage( self, img_data , label):
if img_data is not None:
img = QtGui.QImage()
img.loadFromData( img_data )
label.setPixmap(QtGui.QPixmap(img))
label.setScaledContents(True)
else:
label.setPixmap(QtGui.QPixmap(ComicTaggerSettings.getGraphic('nocover.png')))
label.setScaledContents(True)
def setCoverImage( self, img_data , widget):
widget.setImageData( img_data )
QtCore.QCoreApplication.processEvents()
QtCore.QCoreApplication.processEvents()
def reject(self):
QtGui.QDialog.reject(self)
self.isdone = True

View File

@ -3,7 +3,7 @@ A PyQT4 dialog to confirm and set options for auto-tag
"""
"""
Copyright 2012 Anthony Beville
Copyright 2012-2014 Anthony Beville
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.

View File

@ -3,7 +3,7 @@ Class to manage modifying metadata specifically for CBL/CBI
"""
"""
Copyright 2012 Anthony Beville
Copyright 2012-2014 Anthony Beville
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.

View File

@ -3,7 +3,7 @@ A python class to encapsulate CoMet data
"""
"""
Copyright 2012 Anthony Beville
Copyright 2012-2014 Anthony Beville
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.

View File

@ -3,7 +3,7 @@ A python class to represent a single comic, be it file or folder of images
"""
"""
Copyright 2012 Anthony Beville
Copyright 2012-2014 Anthony Beville
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@ -72,15 +72,16 @@ class ZipArchiver:
def readArchiveFile( self, archive_file ):
data = ""
zf = zipfile.ZipFile( self.path, 'r' )
try:
data = zf.read( archive_file )
except zipfile.BadZipfile as e:
print >> sys.stderr, "bad zipfile [{0}]: {1} :: {2}".format(e, self.path, archive_file)
print >> sys.stderr, u"bad zipfile [{0}]: {1} :: {2}".format(e, self.path, archive_file)
zf.close()
raise IOError
except Exception as e:
zf.close()
print >> sys.stderr, "bad zipfile [{0}]: {1} :: {2}".format(e, self.path, archive_file)
print >> sys.stderr, u"bad zipfile [{0}]: {1} :: {2}".format(e, self.path, archive_file)
raise IOError
finally:
zf.close()
@ -109,12 +110,16 @@ class ZipArchiver:
except:
return False
def getArchiveFilenameList( self ):
zf = zipfile.ZipFile( self.path, 'r' )
namelist = zf.namelist()
zf.close()
return namelist
def getArchiveFilenameList( self ):
try:
zf = zipfile.ZipFile( self.path, 'r' )
namelist = zf.namelist()
zf.close()
return namelist
except Exception as e:
print >> sys.stderr, u"Unable to get zipfile list [{0}]: {1}".format(e, self.path)
return []
# zip helper func
def rebuildZipFile( self, exclude_list ):
@ -222,7 +227,7 @@ class ZipArchiver:
if not self.writeZipComment( self.path, comment ):
return False
except Exception as e:
print >> sys.stderr, "Error while copying to {0}: {1}".format(self.path, e)
print >> sys.stderr, u"Error while copying to {0}: {1}".format(self.path, e)
return False
else:
return True
@ -301,22 +306,22 @@ class RarArchiver:
entries = rarc.read_files( archive_file )
if entries[0][0].size != len(entries[0][1]):
print >> sys.stderr, "readArchiveFile(): [file is not expected size: {0} vs {1}] {2}:{3} [attempt # {4}]".format(
print >> sys.stderr, u"readArchiveFile(): [file is not expected size: {0} vs {1}] {2}:{3} [attempt # {4}]".format(
entries[0][0].size,len(entries[0][1]), self.path, archive_file, tries)
continue
except (OSError, IOError) as e:
print >> sys.stderr, "readArchiveFile(): [{0}] {1}:{2} attempt#{3}".format(str(e), self.path, archive_file, tries)
print >> sys.stderr, u"readArchiveFile(): [{0}] {1}:{2} attempt#{3}".format(str(e), self.path, archive_file, tries)
time.sleep(1)
except Exception as e:
print >> sys.stderr, "Unexpected exception in readArchiveFile(): [{0}] for {1}:{2} attempt#{3}".format(str(e), self.path, archive_file, tries)
print >> sys.stderr, u"Unexpected exception in readArchiveFile(): [{0}] for {1}:{2} attempt#{3}".format(str(e), self.path, archive_file, tries)
break
else:
#Success"
#entries is a list of of tuples: ( rarinfo, filedata)
if tries > 1:
print >> sys.stderr, "Attempted read_files() {0} times".format(tries)
print >> sys.stderr, u"Attempted read_files() {0} times".format(tries)
if (len(entries) == 1):
return entries[0][1]
else:
@ -392,7 +397,7 @@ class RarArchiver:
namelist.append( item.filename )
except (OSError, IOError) as e:
print >> sys.stderr, "getArchiveFilenameList(): [{0}] {1} attempt#{2}".format(str(e), self.path, tries)
print >> sys.stderr, u"getArchiveFilenameList(): [{0}] {1} attempt#{2}".format(str(e), self.path, tries)
time.sleep(1)
else:
@ -410,7 +415,7 @@ class RarArchiver:
rarc = UnRAR2.RarFile( self.path )
except (OSError, IOError) as e:
print >> sys.stderr, "getRARObj(): [{0}] {1} attempt#{2}".format(str(e), self.path, tries)
print >> sys.stderr, u"getRARObj(): [{0}] {1} attempt#{2}".format(str(e), self.path, tries)
time.sleep(1)
else:
@ -512,19 +517,20 @@ class ComicArchive:
def __init__( self, path, settings ):
self.path = path
self.ci_xml_filename = 'ComicInfo.xml'
self.comet_default_filename = 'CoMet.xml'
self.resetCache()
self.settings = settings
if self.zipTest():
self.archive_type = self.ArchiveType.Zip
self.archiver = ZipArchiver( self.path )
elif self.rarTest():
if self.rarTest():
self.archive_type = self.ArchiveType.Rar
self.archiver = RarArchiver( self.path, settings )
elif self.zipTest():
self.archive_type = self.ArchiveType.Zip
self.archiver = ZipArchiver( self.path )
elif os.path.isdir( self.path ):
self.archive_type = self.ArchiveType.Folder
self.archiver = FolderArchiver( self.path )
@ -609,7 +615,7 @@ class ComicArchive:
if (
( self.isZip() or self.isRar() ) #or self.isFolder() )
and
( self.getNumberOfPages() > 2)
( self.getNumberOfPages() > 0)
):
return True
@ -670,7 +676,7 @@ class ComicArchive:
try:
image_data = self.archiver.readArchiveFile( filename )
except IOError:
print >> sys.stderr, "Error reading in page. Substituting logo page."
print >> sys.stderr, u"Error reading in page. Substituting logo page."
image_data = ComicArchive.logo_data
return image_data
@ -921,7 +927,7 @@ class ComicArchive:
try:
raw_comet = self.archiver.readArchiveFile( self.comet_filename )
except IOError:
print >> sys.stderr, "Error reading in raw CoMet!"
print >> sys.stderr, u"Error reading in raw CoMet!"
raw_comet = ""
return raw_comet
@ -972,7 +978,7 @@ class ComicArchive:
data = self.archiver.readArchiveFile( n )
except:
data = ""
print >> sys.stderr, "Error reading in Comet XML for validation!"
print >> sys.stderr, u"Error reading in Comet XML for validation!"
if CoMet().validateString( data ):
# since we found it, save it!
self.comet_filename = n

View File

@ -3,7 +3,7 @@ A python class to encapsulate the ComicBookInfo data
"""
"""
Copyright 2012 Anthony Beville
Copyright 2012-2014 Anthony Beville
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.

View File

@ -3,7 +3,7 @@ A python class to encapsulate ComicRack's ComicInfo.xml data
"""
"""
Copyright 2012 Anthony Beville
Copyright 2012-2014 Anthony Beville
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@ -260,12 +260,14 @@ class ComicInfoXml:
n.tag == 'Letterer' or
n.tag == 'Editor'
):
for name in n.text.split(','):
metadata.addCredit( name.strip(), n.tag )
if n.text is not None:
for name in n.text.split(','):
metadata.addCredit( name.strip(), n.tag )
if n.tag == 'CoverArtist':
for name in n.text.split(','):
metadata.addCredit( name.strip(), "Cover" )
if n.text is not None:
for name in n.text.split(','):
metadata.addCredit( name.strip(), "Cover" )
# parse page data now
pages_node = root.find( "Pages" )

View File

@ -3,7 +3,7 @@ A python class to manage caching of data from Comic Vine
"""
"""
Copyright 2012 Anthony Beville
Copyright 2012-2014 Anthony Beville
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.

View File

@ -3,7 +3,7 @@ A python class to manage communication with Comic Vine's REST API
"""
"""
Copyright 2012 Anthony Beville
Copyright 2012-2014 Anthony Beville
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@ -110,7 +110,7 @@ class ComicVineTalker(QObject):
# connect to server:
# if there is a 500 error, try a few more times before giving up
# any other error, just bail
#print "ATB---", url
for tries in range(3):
try:
resp = urllib2.urlopen( url )
@ -145,11 +145,23 @@ class ComicVineTalker(QObject):
return cached_search_results
original_series_name = series_name
series_name = urllib.quote_plus(series_name.encode("utf-8"))
#series_name = urllib.quote_plus(unicode(series_name))
search_url = self.api_base_url + "/search/?api_key=" + self.api_key + "&format=json&resources=volume&query=" + series_name + "&field_list=name,id,start_year,publisher,image,description,count_of_issues"
content = self.getUrlContent(search_url)
# We need to make the series name into an "AND"ed query list
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 = urllib.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 + "&field_list=name,id,start_year,publisher,image,description,count_of_issues"
content = self.getUrlContent(search_url + "&page=1")
cv_response = json.loads(content)
@ -591,7 +603,7 @@ class ComicVineTalker(QObject):
self.urlFetchComplete.emit( details['image_url'],details['thumb_image_url'], self.issue_id )
return
issue_url = "http://www.comicvine.com/api/issue/" + CVTypeID.Issue + "-" + str(issue_id) + "/?api_key=" + self.api_key + "&format=json&field_list=image,cover_date,site_detail_url"
issue_url = self.api_base_url + "/issue/" + CVTypeID.Issue + "-" + str(issue_id) + "/?api_key=" + self.api_key + "&format=json&field_list=image,cover_date,site_detail_url"
self.nam = QNetworkAccessManager()
self.nam.finished.connect( self.asyncFetchIssueCoverURLComplete )
self.nam.get(QNetworkRequest(QUrl(issue_url)))

View File

@ -1,9 +1,11 @@
"""
A PyQt4 widget display cover images from either local archive, or from ComicVine
(TODO: This should be re-factored using subclasses!)
"""
"""
Copyright 2012 Anthony Beville
Copyright 2012-2014 Anthony Beville
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@ -59,8 +61,9 @@ class CoverImageWidget(QWidget):
ArchiveMode = 0
AltCoverMode = 1
URLMode = 1
DataMode = 3
def __init__(self, parent, mode ):
def __init__(self, parent, mode, expand_on_click = True ):
super(CoverImageWidget, self).__init__(parent)
uic.loadUi(ComicTaggerSettings.getUIFile('coverimagewidget.ui' ), self)
@ -78,7 +81,10 @@ class CoverImageWidget(QWidget):
self.btnLeft.clicked.connect( self.decrementImage )
self.btnRight.clicked.connect( self.incrementImage )
self.resetWidget()
clickable(self.lblImage).connect(self.showPopup)
if expand_on_click:
clickable(self.lblImage).connect(self.showPopup)
else:
self.lblImage.setToolTip( "" )
self.updateContent()
@ -93,11 +99,12 @@ class CoverImageWidget(QWidget):
self.page_loader = None
self.imageIndex = -1
self.imageCount = 1
self.imageData = None
def clear( self ):
self.resetWidget()
self.updateContent()
def incrementImage( self ):
self.imageIndex += 1
if self.imageIndex == self.imageCount:
@ -138,7 +145,19 @@ class CoverImageWidget(QWidget):
self.comicVine = ComicVineTalker()
self.comicVine.urlFetchComplete.connect( self.primaryUrlFetchComplete )
self.comicVine.asyncFetchIssueCoverURLs( int(self.issue_id) )
def setImageData( self, image_data ):
if self.mode == CoverImageWidget.DataMode:
self.resetWidget()
if image_data is None:
self.imageIndex = -1
else:
self.imageIndex = 0
self.imageData = image_data
self.updateContent()
def primaryUrlFetchComplete( self, primary_url, thumb_url, issue_id ):
self.url_list.append(str(primary_url))
self.imageIndex = 0
@ -179,11 +198,13 @@ class CoverImageWidget(QWidget):
self.loadDefault()
elif self.mode in [ CoverImageWidget.AltCoverMode, CoverImageWidget.URLMode ]:
self.loadURL()
elif self.mode == CoverImageWidget.DataMode:
self.coverRemoteFetchComplete( self.imageData, 0 )
else:
self.loadPage()
def updateControls( self ):
if not self.showControls:
if not self.showControls or self.mode == CoverImageWidget.DataMode:
self.btnLeft.hide()
self.btnRight.hide()
self.label.hide()
@ -288,3 +309,4 @@ class CoverImageWidget(QWidget):
def showPopup( self ):
self.popup = ImagePopup(self, self.current_pixmap)

View File

@ -3,7 +3,7 @@ A PyQT4 dialog to edit credits
"""
"""
Copyright 2012 Anthony Beville
Copyright 2012-2014 Anthony Beville
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.

View File

@ -1,3 +1,3 @@
# This file should contan only these comments, and the line below.
# Used by packaging makefiles and app
version="1.1.8-beta"
version="1.1.11-beta"

View File

@ -3,7 +3,7 @@ A PyQT4 dialog to confirm and set options for export to zip
"""
"""
Copyright 2012 Anthony Beville
Copyright 2012-2014 Anthony Beville
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.

View File

@ -6,7 +6,7 @@ This should probably be re-written, but, well, it mostly works!
"""
"""
Copyright 2012 Anthony Beville
Copyright 2012-2014 Anthony Beville
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@ -30,31 +30,25 @@ import os
from urllib import unquote
class FileNameParser:
def repl(self, m):
return ' ' * len(m.group())
def fixSpaces( self, string, remove_dashes=True ):
if remove_dashes:
placeholders = ['[-_]',' +']
else:
placeholders = ['[_]',' +']
for ph in placeholders:
string = re.sub(ph, ' ', string )
return string.strip()
# check for silly .1 or .5 style issue strings
# allow up to 5 chars total
def isPointIssue( self, word ):
ret = False
try:
float(word)
if (len(word) < 5 and not word.isdigit()):
ret = True
except ValueError:
pass
return ret
string = re.sub(ph, self.repl, string )
return string #.strip()
def getIssueCount( self,filename ):
def getIssueCount( self,filename, issue_end ):
count = ""
filename = filename[issue_end:]
# replace any name seperators with spaces
tmpstr = self.fixSpaces(filename)
found = False
@ -74,113 +68,150 @@ class FileNameParser:
count = count.lstrip("0")
return count
def getIssueNumber( self, filename ):
# Returns a tuple of issue number string, and start and end indexs in the filename
# (The indexes will be used to split the string up for further parsing)
found = False
issue = ''
start = 0
end = 0
# first, look for multiple "--", this means it's formatted differently from most:
if "--" in filename:
# the pattern seems to be that anything to left of the first "--" is the series name followed by issue
filename = filename.split("--")[0]
elif "___" in filename:
filename = re.sub("--.*", self.repl, filename)
elif "__" in filename:
# the pattern seems to be that anything to left of the first "__" is the series name followed by issue
filename = filename.split("__")[0]
filename = re.sub("__.*", self.repl, filename)
filename = filename.replace("+", " ")
# remove parenthetical phrases
filename = re.sub( "\(.*\)", "", filename)
filename = re.sub( "\[.*\]", "", filename)
# guess based on position
# replace parenthetical phrases with spaces
filename = re.sub( "\(.*?\)", self.repl, filename)
filename = re.sub( "\[.*?\]", self.repl, filename)
# replace any name seperators with spaces
tmpstr = self.fixSpaces(filename)
word_list = tmpstr.split(' ')
filename = self.fixSpaces(filename)
# remove any "of NN" phrase with spaces (problem: this could break on some titles)
filename = re.sub( "of [\d]+", self.repl, filename)
#print u"[{0}]".format(filename)
#before we search, remove any kind of likely "of X" phrase
for i in range(0, len(word_list)-2):
if ( word_list[i].isdigit() and
word_list[i+1] == "of" and
word_list[i+2].isdigit() ):
word_list[i+1] ="XXX"
word_list[i+2] ="XXX"
# first look for the last "#" followed by a digit in the filename. this is almost certainly the issue number
#issnum = re.search('#\d+', filename)
matchlist = re.findall("#[-+]?(([0-9]*\.[0-9]+|[0-9]+)(\w*))", filename)
if len(matchlist) > 0:
#get the last item
issue = matchlist[ len(matchlist) - 1][0]
found = True
# assume the last number in the filename that is under 4 digits is the issue number
if not found:
for word in reversed(word_list):
if len(word) > 0 and word[0] == "#":
word = word[1:]
if (
(word.isdigit() and len(word) < 4) or
(self.isPointIssue(word))
):
issue = word
found = True
#print 'Assuming issue number is ' + str(issue) + ' based on the position.'
break
if not found:
# try a regex
issnum = re.search('(?<=[_#\s-])(\d+[a-zA-Z]+|\d+\.\d|\d+)', filename)
if issnum:
issue = issnum.group()
# we should now have a cleaned up filename version with all the words in
# the same positions as original filename
# make a list of each word and its position
word_list = list()
for m in re.finditer("\S+", filename):
word_list.append( (m.group(0), m.start(), m.end()) )
# remove the first word, since it can't be the issue number
if len(word_list) > 1:
word_list = word_list[1:]
else:
#only one word?? just bail.
return issue, start, end
# Now try to search for the likely issue number word in the list
# first look for a word with "#" followed by digits with optional sufix
# this is almost certainly the issue number
for w in reversed(word_list):
if re.match("#[-]?(([0-9]*\.[0-9]+|[0-9]+)(\w*))", w[0]):
found = True
#print 'Got the issue using regex. Issue is ' + issue
break
# same as above but w/o a '#', and only look at the last word in the list
if not found:
w = word_list[-1]
if re.match("[-]?(([0-9]*\.[0-9]+|[0-9]+)(\w*))", w[0]):
found = True
# now try to look for a # followed by any characters
if not found:
for w in reversed(word_list):
if re.match("#\S+", w[0]):
found = True
break
if found:
issue = w[0]
start = w[1]
end = w[2]
if issue[0] == '#':
issue = issue[1:]
return issue, start, end
return issue.strip()
def getSeriesName(self, filename, issue ):
# use the issue number string to split the filename string
# assume first element of list is the series name, plus cruft
#!!! this could fail in the case of small numerics in the series name!!!
# TODO: we really should pass in the *INDEX* of the issue, that makes
# finding it easier
def getSeriesName(self, filename, issue_start ):
# use the issue number string index to split the filename string
if issue_start != 0:
filename = filename[:issue_start]
# in case there is no issue number, remove some obvious stuff
if "--" in filename:
# the pattern seems to be that anything to left of the first "--" is the series name followed by issue
filename = re.sub("--.*", self.repl, filename)
elif "__" in filename:
# the pattern seems to be that anything to left of the first "__" is the series name followed by issue
filename = re.sub("__.*", self.repl, filename)
filename = filename.replace("+", " ")
tmpstr = self.fixSpaces(filename, remove_dashes=False)
#remove pound signs. this might mess up the series name if there is a# in it.
tmpstr = tmpstr.replace("#", " ")
if issue != "":
# assume that issue substr has at least one space before it
issue_str = " " + str(issue)
series = tmpstr.split(issue_str)[0]
else:
# no issue to work off of
#!!! TODO we should look for the year, and split from that
# and if that doesn't exist, remove parenthetical phrases
series = tmpstr
series = re.sub( "\(.*\)", "", tmpstr)
series = tmpstr
volume = ""
#save the last word
try:
last_word = series.split()[-1]
except:
last_word = ""
series = series.rstrip("#")
# remove any parenthetical phrases
series = re.sub( "\(.*?\)", "", series)
# search for volume number
match = re.search('(.+)([vV]|[Vv][oO][Ll]\.?\s?)(\d+)\s*$', series)
if match:
series = match.group(1)
volume = match.group(3)
return series.strip(), volume.strip()
# if a volume wasn't found, see if the last word is a year in parentheses
# since that's a common way to designate the volume
if volume == "":
#match either (YEAR), (YEAR-), or (YEAR-YEAR2)
match = re.search("(\()(\d{4})(-(\d{4}|)|)(\))", last_word)
if match:
volume = match.group(2)
def getYear( self,filename):
series = series.strip()
# if we don't have an issue number (issue_start==0), look
# for hints i.e. "TPB", "one-shot", "OS", "OGN", etc that might
# be removed to help search online
if issue_start == 0:
one_shot_words = [ "tpb", "os", "one-shot", "ogn", "gn" ]
try:
last_word = series.split()[-1]
if last_word.lower() in one_shot_words:
series = series.rsplit(' ', 1)[0]
except:
pass
return series, volume.strip()
def getYear( self,filename, issue_end):
filename = filename[issue_end:]
year = ""
# look for four digit number with "(" ")" or "--" around it
@ -191,17 +222,17 @@ class FileNameParser:
year = re.sub("[^0-9]", "", year)
return year
def getRemainder( self, filename, year, count ):
#make a guess at where the the non-interesting stuff begins
def getRemainder( self, filename, year, count, issue_end ):
#make a guess at where the the non-interesting stuff begins
remainder = ""
if "--" in filename:
remainder = filename.split("--",1)[1]
elif "__" in filename:
remainder = filename.split("__",1)[1]
elif "(" in filename:
remainder = "(" + filename.split("(",1)[1]
elif issue_end != 0:
remainder = filename[issue_end:]
remainder = self.fixSpaces(remainder, remove_dashes=False)
if year != "":
@ -231,11 +262,11 @@ class FileNameParser:
filename = filename.replace("_28", "(")
filename = filename.replace("_29", ")")
self.issue = self.getIssueNumber(filename)
self.series, self.volume = self.getSeriesName(filename, self.issue)
self.year = self.getYear(filename)
self.issue_count = self.getIssueCount(filename)
self.remainder = self.getRemainder( filename, self.year, self.issue_count )
self.issue, issue_start, issue_end = self.getIssueNumber(filename)
self.series, self.volume = self.getSeriesName(filename, issue_start)
self.year = self.getYear(filename, issue_end)
self.issue_count = self.getIssueCount(filename, issue_end)
self.remainder = self.getRemainder( filename, self.year, self.issue_count, issue_end )
if self.issue != "":
# strip off leading zeros
@ -244,4 +275,3 @@ class FileNameParser:
self.issue = "0"
if self.issue[0] == ".":
self.issue = "0" + self.issue

View File

@ -3,7 +3,7 @@ Functions for renaming files based on metadata
"""
"""
Copyright 2012 Anthony Beville
Copyright 2012-2014 Anthony Beville
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@ -118,12 +118,20 @@ class FileRenamer:
new_name = re.sub("\[\s*[-:]*\s*\]", "", new_name )
new_name = re.sub("\{\s*[-:]*\s*\}", "", new_name )
# remove remove duplicate -, _,
new_name = re.sub("[-_]+\s+", "- ", new_name )
new_name = re.sub("(\s-)+", " -", new_name )
# remove duplicate spaces
new_name = u" ".join(new_name.split())
# remove remove duplicate -, _,
new_name = re.sub("[-_]{2,}\s+", "-- ", new_name )
new_name = re.sub("(\s--)+", " --", new_name )
new_name = re.sub("(\s-)+", " -", new_name )
# remove dash or double dash at end of line
new_name = re.sub("[-]{1,2}\s*$", "", new_name )
# remove duplicate spaces (again!)
new_name = u" ".join(new_name.split())
if ext is None:
ext = os.path.splitext( filename )[1]

View File

@ -4,7 +4,7 @@ A PyQt4 widget for managing list of comic archive files
"""
"""
Copyright 2012 Anthony Beville
Copyright 2012-2014 Anthony Beville
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@ -162,6 +162,9 @@ class FileSelectionList(QWidget):
self.twList.currentItemChanged.connect( self.currentItemChangedCB )
if self.twList.rowCount() > 0:
# since on a removal, we select row 0, make sure callback occurs if we're already there
if self.twList.currentRow() == 0:
self.currentItemChangedCB( self.twList.currentItem(), None)
self.twList.selectRow(0)
else:
self.listCleared.emit()
@ -195,7 +198,13 @@ class FileSelectionList(QWidget):
progdialog.close()
if firstAdded is not None:
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."))
else:
QMessageBox.information(self, self.tr("File/Folder Open"), self.tr("No comic archives were found."))
self.twList.setSortingEnabled(True)
# Adjust column size

View File

@ -8,7 +8,7 @@
"""
"""
Copyright 2012 Anthony Beville
Copyright 2012-2014 Anthony Beville
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@ -31,7 +31,7 @@ class PageType:
InnerCover = "InnerCover"
Roundup = "Roundup"
Story = "Story"
Advertisment = "Advertisment"
Advertisment = "Advertisement"
Editorial = "Editorial"
Letters = "Letters"
Preview = "Preview"

View File

@ -3,7 +3,7 @@ A python class to manage fetching and caching of images by URL
"""
"""
Copyright 2012 Anthony Beville
Copyright 2012-2014 Anthony Beville
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.

View File

@ -3,7 +3,7 @@ A PyQT4 widget to display a popup image
"""
"""
Copyright 2012 Anthony Beville
Copyright 2012-2014 Anthony Beville
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.

View File

@ -3,7 +3,7 @@ A python class to automatically identify a comic archive
"""
"""
Copyright 2012 Anthony Beville
Copyright 2012-2014 Anthony Beville
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@ -157,6 +157,7 @@ class IssueIdentifier:
search_keys['issue_number'] = None
search_keys['month'] = None
search_keys['year'] = None
search_keys['issue_count'] = None
if ca is None:
return
@ -166,6 +167,7 @@ class IssueIdentifier:
search_keys['issue_number'] = self.additional_metadata.issue
search_keys['year'] = self.additional_metadata.year
search_keys['month'] = self.additional_metadata.month
search_keys['issue_count'] = self.additional_metadata.issueCount
return search_keys
# see if the archive has any useful meta data for searching with
@ -211,6 +213,13 @@ class IssueIdentifier:
search_keys['month'] = internal_metadata.month
else:
search_keys['month'] = md_from_filename.month
if self.additional_metadata.issueCount is not None:
search_keys['issue_count'] = self.additional_metadata.issueCount
elif internal_metadata.issueCount is not None:
search_keys['issue_count'] = internal_metadata.issueCount
else:
search_keys['issue_count'] = md_from_filename.issueCount
return search_keys
@ -360,6 +369,8 @@ class IssueIdentifier:
self.log_msg( "Going to search for:" )
self.log_msg( "\tSeries: " + keys['series'] )
self.log_msg( "\tIssue : " + keys['issue_number'] )
if keys['issue_count'] is not None:
self.log_msg( "\tCount : " + str(keys['issue_count']) )
if keys['year'] is not None:
self.log_msg( "\tYear : " + str(keys['year']) )
if keys['month'] is not None:
@ -409,11 +420,6 @@ class IssueIdentifier:
if length_approved and publisher_approved and date_approved:
series_second_round_list.append(item)
# if we don't think it's an issue number 1, remove any series' that are one-shots
#if keys['issue_number'] not in [ '1', '0', '0.1' ]:
# #self.log_msg( "Removing one-shots" )
# series_second_round_list[:] = [x for x in series_second_round_list if not x['count_of_issues'] == 1]
self.log_msg( "Searching in " + str(len(series_second_round_list)) +" series" )
@ -423,7 +429,6 @@ class IssueIdentifier:
# now sort the list by name length
series_second_round_list.sort(key=lambda x: len(x['name']), reverse=False)
#--------new way---------------
#build a list of volume IDs
volume_id_list = list()
for series in series_second_round_list:
@ -446,47 +451,6 @@ class IssueIdentifier:
shortlist.append( (series, issue) )
break
#--------new way---------------
"""
#--vvvv---old way---------------
# Now we've got a list of series that we can dig into look for matching issue number
counter = 0
shortlist = []
for series in series_second_round_list:
if self.callback is not None:
self.callback( counter, len(series_second_round_list)*3)
counter += 1
self.log_msg( u"Fetching info for ID: {0} {1} ({2}) ...".format(
series['id'],
series['name'],
series['start_year']), newline=True )
try:
issue_list = comicVine.fetchIssuesByVolume( series['id'] )
except ComicVineTalkerException:
self.log_msg( "Network issue while searching for series details. Aborting...")
return []
for issue in issue_list:
num_s = IssueString(issue['issue_number']).asString()
# look for a matching issue number
if num_s.lower() == keys['issue_number'].lower():
# now, if we have an issue year key given, reject this one if not a match
month, year = comicVine.fetchIssueDate( issue['id'] )
if keys['year'] is not None:
if unicode(keys['year']) != unicode(year):
break
# found a matching issue number! add it to short list
shortlist.append( (series, issue) )
#--^^^^---old way---------------
"""
if keys['year'] is None:
self.log_msg( u"Found {0} series that have an issue #{1}".format(len(shortlist), keys['issue_number']) )
else:
@ -528,6 +492,7 @@ class IssueIdentifier:
match['series'] = u"{0} ({1})".format(series['name'], series['start_year'])
match['distance'] = score_item['score']
match['issue_number'] = keys['issue_number']
match['cv_issue_count'] = series['count_of_issues']
match['url_image_hash'] = score_item['hash']
match['issue_title'] = issue['name']
match['issue_id'] = issue['id']
@ -640,6 +605,19 @@ class IssueIdentifier:
if item['distance'] > best_score + self.min_score_distance:
self.match_list.remove(item)
# One more test for the case choosing limited series first issue vs a trade with the same cover:
# if we have a given issue count > 1 and the volume from CV has count==1, remove it from match list
if len(self.match_list) >= 2 and keys['issue_count'] is not None and keys['issue_count'] != 1:
new_list = list()
for match in self.match_list:
if match['cv_issue_count'] != 1:
new_list.append(match)
else:
self.log_msg("Removing volume {0} [{1}] from consideration (only 1 issue)".format(match['series'], match['volume_id']))
if len(new_list) > 0:
self.match_list = new_list
if len(self.match_list) == 1:
self.log_msg( u"--------------------------------------------------")
print_match(self.match_list[0])
@ -652,6 +630,7 @@ class IssueIdentifier:
self.log_msg( u"--------------------------------------------------")
self.search_result = self.ResultNoMatches
else:
# we've got multiple good matches:
self.log_msg( "More than one likley candiate." )
self.search_result = self.ResultMultipleGoodMatches
self.log_msg( u"--------------------------------------------------")

View File

@ -3,7 +3,7 @@ A PyQT4 dialog to select specific issue from list
"""
"""
Copyright 2012 Anthony Beville
Copyright 2012-2014 Anthony Beville
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.

View File

@ -8,11 +8,12 @@ e.g.:
"0"
"-1"
"5AU"
"100-2"
"""
"""
Copyright 2012 Anthony Beville
Copyright 2012-2014 Anthony Beville
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@ -34,31 +35,57 @@ import re
class IssueString:
def __init__(self, text):
if text is None:
self.num = None
self.suffix = ""
return
self.text = unicode(text)
#strip out non float-y stuff
tmp_num_str = re.sub('[^0-9.-]',"", self.text )
# break up the issue number string into 2 parts: the numeric and suffix string.
# ( assumes that the numeric portion is always first )
self.num = None
self.suffix = ""
if tmp_num_str == "":
self.num = None
self.suffix = self.text
if text is None:
return
text = unicode(text)
#skip the minus sign if it's first
if text[0] == '-':
start = 1
else:
if tmp_num_str.count(".") > 1:
#make sure it's a valid float or int.
parts = tmp_num_str.split('.')
self.num = float( parts[0] + '.' + parts[1] )
else:
self.num = float( tmp_num_str )
self.suffix = ""
parts = self.text.split(tmp_num_str)
if len( parts ) > 1 :
self.suffix = parts[1]
start = 0
# if it's still not numeric at start skip it
if text[start].isdigit() or text[start] == ".":
# walk through the string, look for split point (the first non-numeric)
decimal_count = 0
for idx in range( start, len(text) ):
if text[idx] not in "0123456789.":
break
# special case: also split on second "."
if text[idx] == ".":
decimal_count += 1
if decimal_count > 1:
break
else:
idx = len(text)
# move trailing numeric decimal to suffix
# (only if there is other junk after )
if text[idx-1] == "." and len(text) != idx:
idx = idx -1
# if there is no numeric after the minus, make the minus part of the suffix
if idx == 1 and start == 1:
idx = 0
part1 = text[0:idx]
part2 = text[idx:len(text)]
if part1 != "":
self.num = float( part1 )
self.suffix = part2
else:
self.suffix = text
#print "num: {0} suf: {1}".format(self.num, self.suffix)
def asString( self, pad = 0 ):
#return the float, left side zero-padded, with suffix attached

View File

@ -3,7 +3,7 @@ A PyQT4 dialog to a text file or log
"""
"""
Copyright 2012 Anthony Beville
Copyright 2012-2014 Anthony Beville
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.

View File

@ -3,7 +3,7 @@ A python app to (automatically) tag comic archives
"""
"""
Copyright 2012 Anthony Beville
Copyright 2012-2014 Anthony Beville
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.

View File

@ -3,7 +3,7 @@ A PyQT4 dialog to select from automated issue matches
"""
"""
Copyright 2012 Anthony Beville
Copyright 2012-2014 Anthony Beville
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.

View File

@ -3,7 +3,7 @@ A PyQt4 dialog to show a message and let the user check a box
"""
"""
Copyright 2012 Anthony Beville
Copyright 2012-2014 Anthony Beville
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.

View File

@ -3,7 +3,7 @@ CLI options class for comictagger app
"""
"""
Copyright 2012 Anthony Beville
Copyright 2012-2014 Anthony Beville
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@ -304,7 +304,7 @@ For more help visit the wiki at: http://code.google.com/p/comictagger/
if o == "--nooverwrite":
self.no_overwrite = True
if o == "--version":
print "ComicTagger {0}: Copyright (c) 2012-2013 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)"
new_version = VersionChecker().getLatestVersion("", False)
if new_version is not None and new_version != ctversion.version:

View File

@ -3,7 +3,7 @@ A PyQT4 dialog to show pages of a comic archive
"""
"""
Copyright 2012 Anthony Beville
Copyright 2012-2014 Anthony Beville
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.

View File

@ -3,7 +3,7 @@ A PyQt4 widget for editing the page list info
"""
"""
Copyright 2012 Anthony Beville
Copyright 2012-2014 Anthony Beville
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.

View File

@ -3,7 +3,7 @@ A PyQT4 class to load a page image from a ComicArchive in a background thread
"""
"""
Copyright 2012 Anthony Beville
Copyright 2012-2014 Anthony Beville
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.

View File

@ -3,7 +3,7 @@ A PyQT4 dialog to show ID log and progress
"""
"""
Copyright 2012 Anthony Beville
Copyright 2012-2014 Anthony Beville
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.

View File

@ -3,7 +3,7 @@ A PyQT4 dialog to confirm rename
"""
"""
Copyright 2012 Anthony Beville
Copyright 2012-2014 Anthony Beville
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.

View File

@ -3,7 +3,7 @@ Settings class for comictagger app
"""
"""
Copyright 2012 Anthony Beville
Copyright 2012-2014 Anthony Beville
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.

View File

@ -3,7 +3,7 @@ A PyQT4 dialog to enter app settings
"""
"""
Copyright 2012 Anthony Beville
Copyright 2012-2014 Anthony Beville
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.

View File

@ -4,7 +4,7 @@ The main window of the ComicTagger app
"""
"""
Copyright 2012 Anthony Beville
Copyright 2012-2014 Anthony Beville
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@ -331,7 +331,7 @@ class TaggerWindow( QtGui.QMainWindow):
self.actionApplyCBLTransform.setStatusTip( 'Modify tags specifically for CBL format' )
self.actionApplyCBLTransform.triggered.connect( self.applyCBLTransform )
#self.actionClearEntryForm.setShortcut( 'Ctrl+C' )
self.actionClearEntryForm.setShortcut( 'Ctrl+Shift+C' )
self.actionClearEntryForm.setStatusTip( 'Clear all the data on the screen' )
self.actionClearEntryForm.triggered.connect( self.clearForm )
@ -473,7 +473,7 @@ class TaggerWindow( QtGui.QMainWindow):
msgBox.setIconPixmap( QtGui.QPixmap(ComicTaggerSettings.getGraphic('about.png')) )
msgBox.setText( "<br><br><br>"
+ self.appName + " v" + self.version + "<br>"
+ "(c)2012 Anthony Beville<br><br>"
+ "(c)2014 Anthony Beville<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) )
@ -927,14 +927,17 @@ class TaggerWindow( QtGui.QMainWindow):
else:
QtGui.QMessageBox.information(self, self.tr("Online Search"), self.tr("Need to enter a series name to search."))
return
year = str(self.lePubYear.text()).strip()
if year == "":
year = None
issue_count = str(self.leIssueCount.text()).strip()
if issue_count == "":
issue_count = None
cover_index_list = self.metadata.getCoverPageIndexList()
selector = VolumeSelectionWindow( self, series_name, issue_number, year, cover_index_list, self.comic_archive, self.settings, autoselect )
selector = VolumeSelectionWindow( self, series_name, issue_number, year, issue_count, cover_index_list, self.comic_archive, self.settings, autoselect )
title = "Search: '" + series_name + "' - "
selector.setWindowTitle( title + "Select Series")

View File

@ -21,6 +21,44 @@
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="1">
<widget class="QWidget" name="archiveCoverContainer">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>110</width>
<height>165</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>110</width>
<height>165</height>
</size>
</property>
</widget>
</item>
<item row="0" column="4">
<widget class="QWidget" name="testCoverContainer">
<property name="minimumSize">
<size>
<width>110</width>
<height>165</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>110</width>
<height>165</height>
</size>
</property>
</widget>
</item>
<item row="0" column="2">
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QProgressBar" name="progressBar">
@ -66,50 +104,6 @@
</item>
</layout>
</item>
<item row="0" column="0">
<widget class="QLabel" name="lblArchive">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>110</width>
<height>165</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>110</width>
<height>165</height>
</size>
</property>
<property name="text">
<string>TextLabel</string>
</property>
</widget>
</item>
<item row="0" column="2">
<widget class="QLabel" name="lblTest">
<property name="minimumSize">
<size>
<width>110</width>
<height>165</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>110</width>
<height>165</height>
</size>
</property>
<property name="text">
<string>TextLabel</string>
</property>
</widget>
</item>
</layout>
</widget>
<resources/>

View File

@ -5,7 +5,7 @@ Some generic utilities
"""
"""
Copyright 2012 Anthony Beville
Copyright 2012-2014 Anthony Beville
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@ -126,9 +126,13 @@ def removearticles( text ):
# now get rid of some other junk
newText = newText.replace(":", "")
newText = newText.replace(".", "")
newText = newText.replace(",", "")
newText = newText.replace("-", " ")
# since the CV api changed, searches for series names with periods
# now explicity require the period to be in the search key,
# so the line below is removed (for now)
#newText = newText.replace(".", "")
return newText

View File

@ -3,7 +3,7 @@ A PyQT4 dialog to select specific series/volume from list
"""
"""
Copyright 2012 Anthony Beville
Copyright 2012-2014 Anthony Beville
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@ -87,7 +87,7 @@ class IdentifyThread( QtCore.QThread):
class VolumeSelectionWindow(QtGui.QDialog):
def __init__(self, parent, series_name, issue_number, year, cover_index_list, comic_archive, settings, autoselect=False):
def __init__(self, parent, series_name, issue_number, year, issue_count, cover_index_list, comic_archive, settings, autoselect=False):
super(VolumeSelectionWindow, self).__init__(parent)
uic.loadUi(ComicTaggerSettings.getUIFile('volumeselectionwindow.ui' ), self)
@ -108,6 +108,7 @@ class VolumeSelectionWindow(QtGui.QDialog):
self.series_name = series_name
self.issue_number = issue_number
self.year = year
self.issue_count = issue_count
self.volume_id = 0
self.comic_archive = comic_archive
self.immediate_autoselect = autoselect
@ -161,6 +162,7 @@ class VolumeSelectionWindow(QtGui.QDialog):
md.series = self.series_name
md.issue = self.issue_number
md.year = self.year
md.issueCount = self.issue_count
self.ii.setAdditionalMetadata( md )
self.ii.onlyUseAdditionalMetaData = True

View File

@ -1,6 +1,6 @@
#PYINSTALLER_CMD := VERSIONER_PYTHON_PREFER_32_BIT=yes arch -i386 python $(HOME)/pyinstaller-2.0/pyinstaller.py
PYINSTALLER_CMD := python $(HOME)/pyinstaller-2.0/pyinstaller.py
TAGGER_BASE := $(HOME)/Dropbox/tagger/comictagger
TAGGER_BASE ?= $(HOME)/Dropbox/tagger/comictagger
TAGGER_SRC := $(TAGGER_BASE)/comictaggerlib
APP_NAME := ComicTagger

View File

@ -1,3 +1,23 @@
---------------------------------
1.1.11-beta - 23-Mar-2014
---------------------------------
* Updated unrar library to hand Rar tools 5.0 and greater
* Other misc bug fixes
---------------------------------
1.1.10-beta - 30-Jan-2014
---------------------------------
* Updated series query to match changes on Comic Vine side
* Added a message when not able to open a file or folder
* Fixed an issue where series names with periods would fail on search
* Other misc bug fixes
---------------------------------
1.1.9-beta - 8-May-2013
---------------------------------
* Filename parser and identification enhancements
* Misc bug fixes
---------------------------------
1.1.8-beta - 21-Apr-2013
---------------------------------

View File

@ -4,13 +4,26 @@ Features
Rename dialog:
check-box for rows?
manual edit the preview?
Maybe replace configparser -- seems to be causing all sorts of problems
Feature Requests:
Move CBR to other folder after conversion to ZIP
NO - AUto-rename on auto-tag
NO- Re-zip (to remove compression)
Pre-process series name before identification
(using a list of regex transforms)
(GC #28) Save auto-tag options
(GC #24) Multiple options for -t i.e. "-t cr,cbl"
(GC #18 ) Option for handling colon in rename
(GC #31 ) Specify CV Series ID for auto-tag
Re-org - move to new folder based on template
repair incorrect extension
Denied Requests (for now):
Auto-rename on auto-tag
Re-zip (to remove compression)
Selective fields on CLI print (use -m option. Maybe internally remove all but specified fields in MD object before print )
Docs:
Auto-Tagging Tips:
@ -20,18 +33,18 @@ Docs:
Bugs
-----------------------------------------------------
Non-numeric issues?? Filename parsing... can we only rely on '#'??
Zip flakes out when filename differs from index (or whatever) i.e "\" vs "/". Python issue
-----------------------------------------------------
Big Future Features
-----------------------------------------------------
Support for ACBF metatdata in CBZ
GCD scraper or DB reader
Batch Edit
(GC #29) Batch Edit
Form Mode: Single vs Batch
-----------------------------------------------------

View File

@ -2,7 +2,7 @@
# rm, cp, grep, cut, cat
HOMEPATH ?= $(HOME)
TAGGER_BASE:= $(HOMEPATH)/Dropbox/tagger/comictagger
TAGGER_BASE?= $(HOMEPATH)/Dropbox/tagger/comictagger
TAGGER_SRC := $(TAGGER_BASE)/comictaggerlib
DIST_DIR := $(TAGGER_BASE)\windows\dist
NSIS_CMD := "C:\Program Files (x86)\NSIS\makensis.exe"

View File

@ -1,4 +1,4 @@
ComicTagger - Copyright 2012 Anthony Beville
ComicTagger - Copyright 2014 Anthony Beville
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.