Compare commits

...

35 Commits

Author SHA1 Message Date
1b3feaa167 made zip building use export instead of checkout
git-svn-id: http://comictagger.googlecode.com/svn/trunk@386 6c5673fe-1810-88d6-992b-cd32ca31540c
2013-02-01 03:48:53 +00:00
2526fa0ca8 Made retry count for fail rar reads 7
git-svn-id: http://comictagger.googlecode.com/svn/trunk@385 6c5673fe-1810-88d6-992b-cd32ca31540c
2013-02-01 03:46:16 +00:00
a878d36dcf GUI tweak
git-svn-id: http://comictagger.googlecode.com/svn/trunk@384 6c5673fe-1810-88d6-992b-cd32ca31540c
2013-01-31 20:36:57 +00:00
90de6433b6 GUI goodness, better adjusted forms and dialogs
git-svn-id: http://comictagger.googlecode.com/svn/trunk@383 6c5673fe-1810-88d6-992b-cd32ca31540c
2013-01-31 05:12:12 +00:00
d9abc364f1 Version bump
git-svn-id: http://comictagger.googlecode.com/svn/trunk@382 6c5673fe-1810-88d6-992b-cd32ca31540c
2013-01-31 01:14:21 +00:00
e542b6df1f Added more options to auto tag start:
Use specific search string
 Change length tolerance

git-svn-id: http://comictagger.googlecode.com/svn/trunk@381 6c5673fe-1810-88d6-992b-cd32ca31540c
2013-01-31 01:14:03 +00:00
a7a6b085f1 Added more options to auto tag start:
Use specific search string
 Change length tolerance

git-svn-id: http://comictagger.googlecode.com/svn/trunk@380 6c5673fe-1810-88d6-992b-cd32ca31540c
2013-01-31 01:06:19 +00:00
0078f76e8c Use an RE to look for #issue before anything else
git-svn-id: http://comictagger.googlecode.com/svn/trunk@379 6c5673fe-1810-88d6-992b-cd32ca31540c
2013-01-31 01:05:16 +00:00
1a01cb60d9 added autotag options to remove after success, and ignore leading digits
git-svn-id: http://comictagger.googlecode.com/svn/trunk@375 6c5673fe-1810-88d6-992b-cd32ca31540c
2013-01-30 18:40:53 +00:00
0f81ce4c24 make sure filenames are unicode
git-svn-id: http://comictagger.googlecode.com/svn/trunk@374 6c5673fe-1810-88d6-992b-cd32ca31540c
2013-01-30 18:40:03 +00:00
c4ef4137d0 removed dead comment line
git-svn-id: http://comictagger.googlecode.com/svn/trunk@373 6c5673fe-1810-88d6-992b-cd32ca31540c
2013-01-30 18:39:33 +00:00
cdc6d71356 parser tweaks
git-svn-id: http://comictagger.googlecode.com/svn/trunk@372 6c5673fe-1810-88d6-992b-cd32ca31540c
2013-01-30 18:39:13 +00:00
2357a6378e resized window
git-svn-id: http://comictagger.googlecode.com/svn/trunk@371 6c5673fe-1810-88d6-992b-cd32ca31540c
2013-01-30 18:38:48 +00:00
9503d0fef4 Add some new options
git-svn-id: http://comictagger.googlecode.com/svn/trunk@370 6c5673fe-1810-88d6-992b-cd32ca31540c
2013-01-30 18:38:30 +00:00
c46dda4540 Added tooltip strigs to credit table
Explicitly refresh the archive cache after modify operation
added summary after zip export, tag copy and tag remove

git-svn-id: http://comictagger.googlecode.com/svn/trunk@369 6c5673fe-1810-88d6-992b-cd32ca31540c
2013-01-30 02:19:18 +00:00
894c23f64f Added tooltip strigs to table
git-svn-id: http://comictagger.googlecode.com/svn/trunk@368 6c5673fe-1810-88d6-992b-cd32ca31540c
2013-01-30 02:17:55 +00:00
9360fa954c Added tooltip strigs to table
git-svn-id: http://comictagger.googlecode.com/svn/trunk@367 6c5673fe-1810-88d6-992b-cd32ca31540c
2013-01-30 02:17:40 +00:00
74408e56fd make sure to decode strings straight from filesystem
git-svn-id: http://comictagger.googlecode.com/svn/trunk@366 6c5673fe-1810-88d6-992b-cd32ca31540c
2013-01-30 02:17:16 +00:00
dd8e54fa6b another fix for badly formatted issue string
git-svn-id: http://comictagger.googlecode.com/svn/trunk@365 6c5673fe-1810-88d6-992b-cd32ca31540c
2013-01-30 02:16:38 +00:00
b378840878 Rar fix for zero length entries
make sure archive object clears cache on any write operation

git-svn-id: http://comictagger.googlecode.com/svn/trunk@364 6c5673fe-1810-88d6-992b-cd32ca31540c
2013-01-30 02:16:10 +00:00
c0a6406dc9 Unicode fix.
Added tooltip strigs to table

git-svn-id: http://comictagger.googlecode.com/svn/trunk@363 6c5673fe-1810-88d6-992b-cd32ca31540c
2013-01-30 02:15:16 +00:00
df3544e734 Make sure the page list is populated even if it's not in the existing metadata
git-svn-id: http://comictagger.googlecode.com/svn/trunk@362 6c5673fe-1810-88d6-992b-cd32ca31540c
2013-01-28 20:59:08 +00:00
d40de5b67e added copyright/license info to CLI version output
git-svn-id: http://comictagger.googlecode.com/svn/trunk@361 6c5673fe-1810-88d6-992b-cd32ca31540c
2013-01-28 20:58:26 +00:00
25b63dfc65 Also check for unicode when converting to int value for output
git-svn-id: http://comictagger.googlecode.com/svn/trunk@360 6c5673fe-1810-88d6-992b-cd32ca31540c
2013-01-28 17:38:59 +00:00
70f50c8595 reduced rar failure retry max to 5
git-svn-id: http://comictagger.googlecode.com/svn/trunk@359 6c5673fe-1810-88d6-992b-cd32ca31540c
2013-01-28 17:37:18 +00:00
e9aba4e119 DId a premature tag. Interim version string bump
git-svn-id: http://comictagger.googlecode.com/svn/trunk@355 6c5673fe-1810-88d6-992b-cd32ca31540c
2013-01-25 17:22:43 +00:00
53aca0ee08 version bump
git-svn-id: http://comictagger.googlecode.com/svn/trunk@354 6c5673fe-1810-88d6-992b-cd32ca31540c
2013-01-25 17:21:36 +00:00
9aa41823b4 more vebose output for zip errors
git-svn-id: http://comictagger.googlecode.com/svn/trunk@353 6c5673fe-1810-88d6-992b-cd32ca31540c
2013-01-25 17:20:59 +00:00
8d4a336b50 Fixed logic bug for low confidence save
git-svn-id: http://comictagger.googlecode.com/svn/trunk@352 6c5673fe-1810-88d6-992b-cd32ca31540c
2013-01-25 17:20:20 +00:00
3c96e68fde Remove "?" from renamer output
git-svn-id: http://comictagger.googlecode.com/svn/trunk@351 6c5673fe-1810-88d6-992b-cd32ca31540c
2013-01-25 17:19:42 +00:00
93f316b820 more robust dealing with read errors in rar archives
more logging in auto-tag process

git-svn-id: http://comictagger.googlecode.com/svn/trunk@349 6c5673fe-1810-88d6-992b-cd32ca31540c
2013-01-25 06:17:45 +00:00
ccde71f9d0 fixed mac text encoding at always utf-8
git-svn-id: http://comictagger.googlecode.com/svn/trunk@348 6c5673fe-1810-88d6-992b-cd32ca31540c
2013-01-25 06:14:51 +00:00
7186c6792a rename takes month variables
git-svn-id: http://comictagger.googlecode.com/svn/trunk@347 6c5673fe-1810-88d6-992b-cd32ca31540c
2013-01-25 06:14:11 +00:00
e8961ed299 make clean at root does all below
git-svn-id: http://comictagger.googlecode.com/svn/trunk@346 6c5673fe-1810-88d6-992b-cd32ca31540c
2013-01-25 06:13:33 +00:00
1f050436d3 better volume number parsing
fixed case of more or less no filename

git-svn-id: http://comictagger.googlecode.com/svn/trunk@345 6c5673fe-1810-88d6-992b-cd32ca31540c
2013-01-25 06:13:09 +00:00
25 changed files with 583 additions and 194 deletions

View File

@ -6,12 +6,13 @@ all: clean
clean:
rm -f *~ *.pyc *.pyo
rm -f logdict*.log
make -C mac clean
make -C windows clean
zip:
cd release; \
rm -rf *zip comictagger-src-$(VERSION_STR) ; \
svn checkout https://comictagger.googlecode.com/svn/trunk/ comictagger-src-$(VERSION_STR); \
svn export https://comictagger.googlecode.com/svn/trunk/ comictagger-src-$(VERSION_STR); \
zip -r comictagger-src-$(VERSION_STR).zip comictagger-src-$(VERSION_STR); \
rm -rf comictagger-src-$(VERSION_STR)
@ -21,4 +22,4 @@ zip:
svn_tag:
svn copy https://comictagger.googlecode.com/svn/trunk \
https://comictagger.googlecode.com/svn/tags/$(VERSION_STR) -m "Release $(VERSION_STR)"

View File

@ -26,6 +26,7 @@ from PyQt4.QtCore import QUrl, pyqtSignal, QByteArray
from imagefetcher import ImageFetcher
from settings import ComicTaggerSettings
from options import MetaDataStyle
class AutoTagMatchWindow(QtGui.QDialog):
@ -67,7 +68,7 @@ class AutoTagMatchWindow(QtGui.QDialog):
self.twList.selectRow( 0 )
path = self.current_match_set.ca.path
self.setWindowTitle( "Select correct match ({0} of {1}): {2}".format(
self.setWindowTitle( u"Select correct match ({0} of {1}): {2}".format(
self.current_match_set_idx+1,
len( self.match_set_list ),
os.path.split(path)[1] ))
@ -85,6 +86,7 @@ class AutoTagMatchWindow(QtGui.QDialog):
item_text = match['series']
item = QtGui.QTableWidgetItem(item_text)
item.setData( QtCore.Qt.ToolTipRole, item_text )
item.setFlags(QtCore.Qt.ItemIsSelectable| QtCore.Qt.ItemIsEnabled)
self.twList.setItem(row, 0, item)
@ -93,6 +95,7 @@ class AutoTagMatchWindow(QtGui.QDialog):
else:
item_text = u"Unknown"
item = QtGui.QTableWidgetItem(item_text)
item.setData( QtCore.Qt.ToolTipRole, item_text )
item.setFlags(QtCore.Qt.ItemIsSelectable| QtCore.Qt.ItemIsEnabled)
self.twList.setItem(row, 1, item)
@ -104,6 +107,7 @@ class AutoTagMatchWindow(QtGui.QDialog):
else:
item_text += u"????"
item = QtGui.QTableWidgetItem(item_text)
item.setData( QtCore.Qt.ToolTipRole, item_text )
item.setFlags(QtCore.Qt.ItemIsSelectable| QtCore.Qt.ItemIsEnabled)
self.twList.setItem(row, 2, item)
@ -196,6 +200,8 @@ class AutoTagMatchWindow(QtGui.QDialog):
QtGui.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()
if not success:

View File

@ -40,16 +40,62 @@ class AutoTagStartWindow(QtGui.QDialog):
self.cbxSaveOnLowConfidence.setCheckState( QtCore.Qt.Unchecked )
self.cbxDontUseYear.setCheckState( QtCore.Qt.Unchecked )
self.cbxAssumeIssueOne.setCheckState( QtCore.Qt.Unchecked )
self.cbxIgnoreLeadingDigitsInFilename.setCheckState( QtCore.Qt.Unchecked )
self.cbxRemoveAfterSuccess.setCheckState( QtCore.Qt.Unchecked )
self.cbxSpecifySearchString.setCheckState( QtCore.Qt.Unchecked )
self.leNameLengthMatchTolerance.setText( str(self.settings.id_length_delta_thresh) )
self.leSearchString.setEnabled( False )
nlmtTip = (
""" <html>The <b>Name Length Match Tolerance</b> is for eliminating automatic
search matches that are too long compared to your series name search. The higher
it is, the more likely to have a good match, but each search will take longer and
use more bandwidth. Too low, and only the very closest lexical matches will be
explored.</html>""" )
self.leNameLengthMatchTolerance.setToolTip(nlmtTip)
ssTip = (
"""<html>
The <b>series search string</b> specifies the search string to be used for all selected archives.
Use this only when trying to match archives with hard-to-parse filenames. All archives selected
should be from the same series.
</html>"""
)
self.leSearchString.setToolTip(ssTip)
self.cbxSpecifySearchString.setToolTip(ssTip)
validator = QtGui.QIntValidator(0, 99, self)
self.leNameLengthMatchTolerance.setValidator(validator)
self.cbxSpecifySearchString.stateChanged.connect(self.searchStringToggle)
self.autoSaveOnLow = False
self.dontUseYear = False
self.assumeIssueOne = False
self.ignoreLeadingDigitsInFilename = False
self.removeAfterSuccess = False
self.searchString = None
self.nameLengthMatchTolerance = self.settings.id_length_delta_thresh
def searchStringToggle(self):
enable = self.cbxSpecifySearchString.isChecked()
self.leSearchString.setEnabled( enable )
def accept( self ):
QtGui.QDialog.accept(self)
self.autoSaveOnLow = self.cbxSaveOnLowConfidence.isChecked()
self.dontUseYear = self.cbxDontUseYear.isChecked()
self.assumeIssueOne = self.cbxAssumeIssueOne.isChecked()
self.ignoreLeadingDigitsInFilename = self.cbxIgnoreLeadingDigitsInFilename.isChecked()
self.removeAfterSuccess = self.cbxRemoveAfterSuccess.isChecked()
self.nameLengthMatchTolerance = int(self.leNameLengthMatchTolerance.text())
if self.cbxSpecifySearchString.isChecked():
self.searchString = unicode(self.leSearchString.text())
if len(self.searchString) == 0:
self.searchString = None

View File

@ -9,10 +9,16 @@
<rect>
<x>0</x>
<y>0</y>
<width>524</width>
<height>248</height>
<width>607</width>
<height>319</height>
</rect>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="MinimumExpanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="windowTitle">
<string>Auto-Tag</string>
</property>
@ -40,20 +46,16 @@
</item>
<item>
<layout class="QFormLayout" name="formLayout">
<property name="sizeConstraint">
<enum>QLayout::SetFixedSize</enum>
</property>
<property name="fieldGrowthPolicy">
<enum>QFormLayout::AllNonFixedFieldsGrow</enum>
</property>
<item row="1" column="1">
<widget class="QCheckBox" name="cbxDontUseYear">
<property name="text">
<string>Don't use publication year in indentification process</string>
</property>
</widget>
</item>
<item row="0" column="1">
<item row="0" column="0" colspan="2">
<widget class="QCheckBox" name="cbxSaveOnLowConfidence">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Preferred">
<sizepolicy hsizetype="Minimum" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
@ -63,13 +65,129 @@
</property>
</widget>
</item>
<item row="2" column="1">
<item row="1" column="0" colspan="2">
<widget class="QCheckBox" name="cbxDontUseYear">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Don't use publication year in indentification process</string>
</property>
</widget>
</item>
<item row="2" column="0" colspan="2">
<widget class="QCheckBox" name="cbxAssumeIssueOne">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>If no issue number, assume &quot;1&quot;</string>
</property>
</widget>
</item>
<item row="3" column="0" colspan="2">
<widget class="QCheckBox" name="cbxIgnoreLeadingDigitsInFilename">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Ignore leading (sequence) numbers in filename</string>
</property>
</widget>
</item>
<item row="4" column="0" colspan="2">
<widget class="QCheckBox" name="cbxRemoveAfterSuccess">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Remove archives from list after successful tagging</string>
</property>
</widget>
</item>
<item row="5" column="0" colspan="2">
<widget class="QCheckBox" name="cbxSpecifySearchString">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Specify series search string for all selected archives</string>
</property>
</widget>
</item>
<item row="6" column="0">
<widget class="QLabel" name="label_2">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>40</width>
<height>0</height>
</size>
</property>
<property name="text">
<string/>
</property>
</widget>
</item>
<item row="6" column="1">
<widget class="QLineEdit" name="leSearchString">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
</widget>
</item>
<item row="8" column="1">
<widget class="QLineEdit" name="leNameLengthMatchTolerance">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="maximumSize">
<size>
<width>50</width>
<height>16777215</height>
</size>
</property>
</widget>
</item>
<item row="7" column="0" colspan="2">
<widget class="QLabel" name="label_3">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Adjust Name Length Match Tolerance:</string>
</property>
</widget>
</item>
</layout>
</item>
<item>

View File

@ -46,7 +46,7 @@ from comicbookinfo import ComicBookInfo
from comet import CoMet
from genericmetadata import GenericMetadata, PageType
from filenameparser import FileNameParser
from settings import ComicTaggerSettings
class ZipArchiver:
@ -67,10 +67,14 @@ class ZipArchiver:
zf = zipfile.ZipFile( self.path, 'r' )
try:
data = zf.read( archive_file )
except zipfile.BadZipfile:
print "bad zipfile: {0} :: {1}".format(self.path, archive_file)
except Exception:
print "bad zipfile: {0} :: {1}".format(self.path, archive_file)
except zipfile.BadZipfile as e:
print "bad zipfile [{0}]: {1} :: {2}".format(e, self.path, archive_file)
zf.close()
raise IOError
except Exception as e:
zf.close()
print "bad zipfile [{0}]: {1} :: {2}".format(e, self.path, archive_file)
raise IOError
finally:
zf.close()
return data
@ -200,7 +204,7 @@ class ZipArchiver:
try:
zout = zipfile.ZipFile (self.path, 'w')
for fname in otherArchive.getArchiveFilenameList():
data = otherArchive.readArchiveFile( fname )
data = otherArchive.readArchiveFile( fname )
if data is not None:
zout.writestr( fname, data )
zout.close()
@ -284,27 +288,34 @@ class RarArchiver:
rarc = self.getRARObj()
tries = 0
while tries < 10:
while tries < 7:
try:
tries = tries+1
entries = rarc.read_files( archive_file )
if entries[0][0].size != len(entries[0][1]):
print "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 e, "in readArchiveFile! try %s" % tries
print "readArchiveFile(): [{0}] {1}:{2} attempt#{3}".format(str(e), self.path, archive_file, tries)
time.sleep(1)
except Exception as e:
print "Unexpected exception in readArchiveFile! {0}".format( e )
print "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 "Attempted read_files() {0} times".format(tries)
if (len(entries) == 1):
return entries[0][1]
else:
return None
raise IOError
return None
raise IOError
@ -364,13 +375,17 @@ class RarArchiver:
#return namelist
tries = 0
while tries < 10:
while tries < 7:
try:
tries = tries+1
namelist = [ item.filename for item in rarc.infolist() ]
#namelist = [ item.filename for item in rarc.infolist() ]
namelist = []
for item in rarc.infolist():
if item.size != 0:
namelist.append( item.filename )
except (OSError, IOError) as e:
print e, "in getArchiveFilenameList! try %s" % tries
print "getArchiveFilenameList(): [{0}] {1} attempt#{2}".format(str(e), self.path, tries)
time.sleep(1)
else:
@ -382,13 +397,13 @@ class RarArchiver:
def getRARObj( self ):
tries = 0
while tries < 10:
while tries < 7:
try:
tries = tries+1
rarc = UnRAR2.RarFile( self.path )
except (OSError, IOError) as e:
print e, "in getRARObj! try %s" % tries
print "getRARObj(): [{0}] {1} attempt#{2}".format(str(e), self.path, tries)
time.sleep(1)
else:
@ -471,7 +486,7 @@ class UnknownArchiver:
return ""
def setArchiveComment( self, comment ):
return False
def readArchiveFilen( self ):
def readArchiveFile( self ):
return ""
def writeArchiveFile( self, archive_file, data ):
return False
@ -482,7 +497,9 @@ class UnknownArchiver:
#------------------------------------------------------------------
class ComicArchive:
logo_data = None
class ArchiveType:
Zip, Rar, Folder, Unknown = range(4)
@ -507,6 +524,11 @@ class ComicArchive:
self.archive_type = self.ArchiveType.Unknown
self.archiver = UnknownArchiver( self.path )
if ComicArchive.logo_data is None:
fname = os.path.join(ComicTaggerSettings.baseDir(), 'graphics','nocover.png' )
with open(fname, 'rb') as fd:
ComicArchive.logo_data = fd.read()
# Clears the cached data
def resetCache( self ):
self.has_cix = None
@ -518,7 +540,11 @@ class ComicArchive:
self.cix_md = None
self.cbi_md = None
self.comet_md = None
def loadCache( self, style_list ):
for style in style_list:
self.readMetadata(style)
def rename( self, path ):
self.path = path
self.archiver.path = path
@ -637,7 +663,11 @@ class ComicArchive:
filename = self.getPageName( index )
if filename is not None:
image_data = self.archiver.readArchiveFile( filename )
try:
image_data = self.archiver.readArchiveFile( filename )
except IOError:
print "Error reading in page. Substituting logo page."
image_data = ComicArchive.logo_data
return image_data
@ -713,8 +743,7 @@ class ComicArchive:
if write_success:
self.has_cbi = True
self.cbi_md = metadata
else:
self.resetCache()
self.resetCache()
return write_success
else:
return False
@ -725,15 +754,14 @@ class ComicArchive:
if write_success:
self.has_cbi = False
self.cbi_md = None
else:
self.resetCache()
self.resetCache()
return write_success
return True
def readCIX( self ):
if self.cix_md is None:
raw_cix = self.readRawCIX()
if raw_cix is None:
if raw_cix is None or raw_cix == "":
self.cix_md = GenericMetadata()
else:
self.cix_md = ComicInfoXml().metadataFromString( raw_cix )
@ -753,8 +781,12 @@ class ComicArchive:
def readRawCIX( self ):
if not self.hasCIX():
return None
return self.archiver.readArchiveFile( self.ci_xml_filename )
try:
raw_cix = self.archiver.readArchiveFile( self.ci_xml_filename )
except IOError:
print "Error reading in raw CIX!"
raw_cix = ""
return raw_cix
def writeCIX(self, metadata):
@ -765,8 +797,7 @@ class ComicArchive:
if write_success:
self.has_cix = True
self.cix_md = metadata
else:
self.resetCache()
self.resetCache()
return write_success
else:
return False
@ -777,8 +808,7 @@ class ComicArchive:
if write_success:
self.has_cix = False
self.cix_md = None
else:
self.resetCache()
self.resetCache()
return write_success
return True
@ -798,7 +828,7 @@ class ComicArchive:
def readCoMet( self ):
if self.comet_md is None:
raw_comet = self.readRawCoMet()
if raw_comet is None:
if raw_comet is None or raw_comet == "":
self.comet_md = GenericMetadata()
else:
self.comet_md = CoMet().metadataFromString( raw_comet )
@ -824,7 +854,12 @@ class ComicArchive:
print self.path, "doesn't have CoMet data!"
return None
return self.archiver.readArchiveFile( self.comet_filename )
try:
raw_comet = self.archiver.readArchiveFile( self.comet_filename )
except IOError:
print "Error reading in raw CoMet!"
raw_comet = ""
return raw_comet
def writeCoMet(self, metadata):
@ -843,8 +878,7 @@ class ComicArchive:
if write_success:
self.has_comet = True
self.comet_md = metadata
else:
self.resetCache()
self.resetCache()
return write_success
else:
return False
@ -855,8 +889,7 @@ class ComicArchive:
if write_success:
self.has_comet = False
self.comet_md = None
else:
self.resetCache()
self.resetCache()
return write_success
return True
@ -871,7 +904,11 @@ class ComicArchive:
if ( os.path.dirname(n) == "" and
os.path.splitext(n)[1].lower() == '.xml'):
# read in XML file, and validate it
data = self.archiver.readArchiveFile( n )
try:
data = self.archiver.readArchiveFile( n )
except:
data = ""
print "Error reading in Comet XML for validation!"
if CoMet().validateString( data ):
# since we found it, save it!
self.comet_filename = n

View File

@ -115,7 +115,7 @@ class ComicBookInfo:
#helper func
def toInt(s):
i = None
if type(s) == str or type(s) == int:
if type(s) in [ str, unicode, int ]:
try:
i = int(s)
except ValueError:

View File

@ -449,15 +449,16 @@ def process_file_cli( filename, opts, settings, match_results ):
print u"renamed '{0}' -> '{1}' {2}".format(os.path.basename(filename), new_name, suffix)
#-----------------------------
def main():
# try to make stdout encodings happy for unicode
sys.stdout = codecs.getwriter(locale.getpreferredencoding())(sys.stdout)
if platform.system() == "Darwin":
preferred_encoding = "utf-8"
else:
preferred_encoding = locale.getpreferredencoding()
sys.stdout = codecs.getwriter(preferred_encoding)(sys.stdout)
opts = Options()
opts.parseCmdLineArgs()

View File

@ -26,6 +26,7 @@ import math
import re
import datetime
import ctversion
import sys
try:
from PyQt4.QtNetwork import QNetworkAccessManager, QNetworkRequest
@ -59,6 +60,17 @@ class ComicVineTalker(QObject):
# key that is registered to comictagger
self.api_key = '27431e6787042105bd3e47e169a624521f89f3a4'
self.log_func = None
def setLogFunc( self , log_func ):
self.log_func = log_func
def writeLog( self , text ):
if self.log_func is None:
sys.stdout.write(text.encode( errors='replace') )
sys.stdout.flush()
else:
self.log_func( text )
def testKey( self ):
@ -76,7 +88,7 @@ class ComicVineTalker(QObject):
resp = urllib2.urlopen( url )
return resp.read()
except Exception as e:
print e
self.writeLog( str(e) )
raise ComicVineTalkerException("Network Error!")
def searchForSeries( self, series_name , callback=None, refresh_cache=False ):
@ -104,7 +116,7 @@ class ComicVineTalker(QObject):
cv_response = json.loads(content)
if cv_response[ 'status_code' ] != 1:
print ( "Comic Vine query failed with error: [{0}]. ".format( cv_response[ 'error' ] ))
self.writeLog( "Comic Vine query failed with error: [{0}]. \n".format( cv_response[ 'error' ] ))
return None
search_results = list()
@ -116,7 +128,7 @@ class ComicVineTalker(QObject):
total_result_count = cv_response['number_of_total_results']
if callback is None:
print ("Found {0} of {1} results".format( cv_response['number_of_page_results'], cv_response['number_of_total_results']))
self.writeLog( "Found {0} of {1} results\n".format( cv_response['number_of_page_results'], cv_response['number_of_total_results']))
search_results.extend( cv_response['results'])
offset = 0
@ -126,14 +138,14 @@ class ComicVineTalker(QObject):
# see if we need to keep asking for more pages...
while ( current_result_count < total_result_count ):
if callback is None:
print ("getting another page of results {0} of {1}...".format( current_result_count, total_result_count))
self.writeLog("getting another page of results {0} of {1}...\n".format( current_result_count, total_result_count))
offset += limit
content = self.getUrlContent(search_url + "&offset="+str(offset))
cv_response = json.loads(content)
if cv_response[ 'status_code' ] != 1:
print ( "Comic Vine query failed with error: [{0}]. ".format( cv_response[ 'error' ] ))
self.writeLog( "Comic Vine query failed with error: [{0}]. \n".format( cv_response[ 'error' ] ))
return None
search_results.extend( cv_response['results'])
current_result_count += cv_response['number_of_page_results']
@ -188,7 +200,7 @@ class ComicVineTalker(QObject):
for record in volume_results['issues']:
if IssueString(issue_number).asFloat() is None:
issue_number = 1
if float(record['issue_number']) == float(issue_number):
if float(record['issue_number']) == IssueString(issue_number).asFloat():
found = True
break

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.0.1-beta"
version="1.0.3-beta"

View File

@ -9,18 +9,24 @@
<rect>
<x>0</x>
<y>0</y>
<width>515</width>
<height>307</height>
<width>533</width>
<height>202</height>
</rect>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="windowTitle">
<string>Export to Zip Archive</string>
</property>
<property name="modal">
<bool>false</bool>
</property>
<layout class="QGridLayout" name="gridLayout_3">
<item row="0" column="0">
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QLabel" name="label">

View File

@ -85,7 +85,13 @@ class FileNameParser:
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 = filename.replace("+", " ")
# remove parenthetical phrases
filename = re.sub( "\(.*\)", "", filename)
filename = re.sub( "\[.*\]", "", filename)
# guess based on position
# replace any name seperators with spaces
@ -101,18 +107,29 @@ class FileNameParser:
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("#\d+", filename)
if len(matchlist) > 0:
#get the last item
issue = matchlist[ len(matchlist) - 1]
issue = issue[1:]
found = True
# assume the last number in the filename that is under 4 digits is the issue number
for word in reversed(word_list):
if 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:
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
@ -133,7 +150,7 @@ class FileNameParser:
# TODO: we really should pass in the *INDEX* of the issue, that makes
# finding it easier
filename = filename.replace("+", " ")
tmpstr = self.fixSpaces(filename)
#remove pound signs. this might mess up the series name if there is a# in it.
@ -154,13 +171,11 @@ class FileNameParser:
series = series.rstrip("#")
# search for volume number
match = re.search('(?<= [vV])(\d+)\s*$', series)
# search for volume number
match = re.search('(.+)([vV]|[Vv][oO][Ll]\.?\s?)(\d+)\s*$', series)
if match:
volume = match.group()
series = series.replace(" V"+ volume, " v"+ volume)
series = series.split("v"+volume)[0]
volume = volume.lstrip("0")
series = match.group(1)
volume = match.group(3)
return series.strip(), volume.strip()
@ -197,9 +212,9 @@ class FileNameParser:
# remove the first word that word is a 3 digit number.
# some story arcs collection packs do this, but it's ugly
# this will probably break something, i.e. "100 bullets"
word = filename.split(' ')[0]
if len(word) == 3 and word[0] =='0' and word.isdigit():
filename = filename[4:]
#word = filename.split(' ')[0]
#if len(word) == 3 and word[0] =='0' and word.isdigit():
# filename = filename[4:]
# ----HACK -
self.issue = self.getIssueNumber(filename)

View File

@ -20,6 +20,7 @@ limitations under the License.
import os
import re
import datetime
from issuestring import IssueString
class FileRenamer:
@ -86,6 +87,14 @@ class FileRenamer:
new_name = self.replaceToken( new_name, md.year, '%year%')
new_name = self.replaceToken( new_name, md.publisher, '%publisher%')
new_name = self.replaceToken( new_name, md.title, '%title%')
new_name = self.replaceToken( new_name, md.month, '%month%')
month_name = None
if md.month is not None:
if (type(md.month) == str and md.month.isdigit()) or type(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("%B")
new_name = self.replaceToken( new_name, month_name, '%month_name%')
if self.smart_cleanup:
@ -109,6 +118,7 @@ class FileRenamer:
# some tweaks to keep various filesystems happy
new_name = new_name.replace("/", "-")
new_name = new_name.replace(":", "-")
new_name = new_name.replace("?", "")
return new_name

View File

@ -20,6 +20,7 @@ limitations under the License.
"""
import os
import sys
from PyQt4.QtCore import *
from PyQt4.QtGui import *
@ -162,6 +163,11 @@ class FileSelectionList(QWidget):
filelist = []
for p in pathlist:
# if path is a folder, walk it recursivly, and all files underneath
if type(p) == str:
#make sure string is unicode
filename_encoding = sys.getfilesystemencoding()
p = p.decode(filename_encoding, 'replace')
if os.path.isdir( unicode(p)):
for root,dirs,files in os.walk( unicode(p) ):
for f in files:

View File

@ -66,7 +66,6 @@ class IssueIdentifier:
self.length_delta_thresh = settings.id_length_delta_thresh
# used to eliminate unlikely publishers
#self.publisher_blacklist = [ 'panini comics', 'abril', 'scholastic book services' ]
self.publisher_blacklist = [ s.strip().lower() for s in settings.id_publisher_blacklist.split(',') ]
self.additional_metadata = GenericMetadata()
@ -75,7 +74,7 @@ class IssueIdentifier:
self.coverUrlCallback = None
self.search_result = self.ResultNoMatches
self.cover_page_index = 0
def setScoreMinThreshold( self, thresh ):
self.min_score_thresh = thresh
@ -86,7 +85,7 @@ class IssueIdentifier:
self.additional_metadata = md
def setNameLengthDeltaThreshold( self, delta ):
self.length_delta_thresh = md
self.length_delta_thresh = delta
def setPublisherBlackList( self, blacklist ):
self.publisher_blacklist = blacklist
@ -119,8 +118,14 @@ class IssueIdentifier:
im = Image.open(StringIO.StringIO(image_data))
w,h = im.size
cropped_im = im.crop( (int(w/2), 0, w, h) )
try:
cropped_im = im.crop( (int(w/2), 0, w, h) )
except Exception as e:
sys.exc_clear()
print "cropCover() error:", e
return None
output = StringIO.StringIO()
cropped_im.save(output, format="JPEG")
cropped_image_data = output.getvalue()
@ -202,7 +207,7 @@ class IssueIdentifier:
@staticmethod
def defaultWriteOutput( text ):
sys.stdout.write(text.encode( errors='replace') )
sys.stdout.write( text )
sys.stdout.flush()
def log_msg( self, msg , newline=True ):
@ -235,9 +240,10 @@ class IssueIdentifier:
aspect_ratio = self.getAspectRatio( cover_image_data )
if aspect_ratio < 1.0:
right_side_image_data = self.cropCover( cover_image_data )
narrow_cover_hash = self.calculateHash( right_side_image_data )
print "narrow_cover_hash", narrow_cover_hash
if right_side_image_data is not None:
narrow_cover_hash = self.calculateHash( right_side_image_data )
self.log_msg(unicode(str(narrow_cover_hash)))
#self.log_msg( "Cover hash = {0:016x}".format(cover_hash) )
keys = self.getSearchKeys()
@ -259,6 +265,7 @@ class IssueIdentifier:
#self.log_msg("Publisher Blacklist: " + str(self.publisher_blacklist))
comicVine = ComicVineTalker( )
comicVine.setLogFunc( self.output_function )
#self.log_msg( ( "Searching for " + keys['series'] + "...")
self.log_msg( u"Searching for {0} #{1} ...".format( keys['series'], keys['issue_number']) )
@ -433,16 +440,16 @@ class IssueIdentifier:
page_hash = self.calculateHash( image_data )
distance = ImageHasher.hamming_distance(page_hash, self.match_list[0]['url_image_hash'])
if distance <= self.strong_score_thresh:
self.log_msg( "Found a great match (distance = {0}) on page {1}!".format(distance, i+1) )
self.log_msg( "Found a great match (score = {0}) on page {1}!".format(distance, i+1) )
found = True
break
elif distance < self.min_score_thresh:
self.log_msg( "Found a good match (distance = {0}) on page {1}".format(distance, i) )
self.log_msg( "Found a good match (score = {0}) on page {1}".format(distance, i) )
found = True
self.log_msg( ".", newline=False )
self.log_msg( "" )
if not found:
self.log_msg( "No matching pages in the issue. Bummer" )
self.log_msg( "No matching pages in the issue." )
self.search_result = self.ResultFoundMatchButBadCoverScore
self.log_msg( u"--------------------------------------------------")
@ -451,7 +458,9 @@ class IssueIdentifier:
return self.match_list
elif best_score > self.min_score_thresh and len(self.match_list) > 1:
self.log_msg( "No good image matches! Need to use other info..." )
self.log_msg( u"--------------------------------------------------")
self.log_msg( u"Multiple bad cover matches! Need to use other info..." )
self.log_msg( u"--------------------------------------------------")
self.search_result = self.ResultMultipleMatchesWithBadImageScores
return self.match_list
@ -468,7 +477,9 @@ class IssueIdentifier:
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:
print

View File

@ -90,6 +90,7 @@ class IssueSelectionWindow(QtGui.QDialog):
item_text = record['issue_number']
item = QtGui.QTableWidgetItem(item_text)
item.setData( QtCore.Qt.ToolTipRole, item_text )
item.setData( QtCore.Qt.UserRole ,record['id'])
item.setData(QtCore.Qt.DisplayRole, float(item_text))
item.setFlags(QtCore.Qt.ItemIsSelectable| QtCore.Qt.ItemIsEnabled)
@ -97,6 +98,7 @@ class IssueSelectionWindow(QtGui.QDialog):
item_text = record['name']
item = QtGui.QTableWidgetItem(item_text)
item.setData( QtCore.Qt.ToolTipRole, item_text )
item.setFlags(QtCore.Qt.ItemIsSelectable| QtCore.Qt.ItemIsEnabled)
self.twList.setItem(row, 1, item)

View File

@ -59,6 +59,7 @@ class MatchSelectionWindow(QtGui.QDialog):
item_text = match['series']
item = QtGui.QTableWidgetItem(item_text)
item.setData( QtCore.Qt.ToolTipRole, item_text )
item.setFlags(QtCore.Qt.ItemIsSelectable| QtCore.Qt.ItemIsEnabled)
self.twList.setItem(row, 0, item)
@ -73,6 +74,7 @@ class MatchSelectionWindow(QtGui.QDialog):
else:
item_text = u"Unknown"
item = QtGui.QTableWidgetItem(item_text)
item.setData( QtCore.Qt.ToolTipRole, item_text )
item.setFlags(QtCore.Qt.ItemIsSelectable| QtCore.Qt.ItemIsEnabled)
self.twList.setItem(row, 1, item)
@ -84,6 +86,7 @@ class MatchSelectionWindow(QtGui.QDialog):
else:
item_text += u"????"
item = QtGui.QTableWidgetItem(item_text)
item.setData( QtCore.Qt.ToolTipRole, item_text )
item.setFlags(QtCore.Qt.ItemIsSelectable| QtCore.Qt.ItemIsEnabled)
self.twList.setItem(row, 2, item)

View File

@ -234,7 +234,8 @@ If no options are given, {0} will run in windowed mode
if o == "--nooverwrite":
self.no_overwrite = True
if o == "--version":
print "ComicTagger version: ", ctversion.version
print "ComicTagger {0}: Copyright (c) 2012-2013 Anthony Beville".format(ctversion.version)
print "Distributed under Apache License 2.0 (http://www.apache.org/licenses/LICENSE-2.0)"
sys.exit(0)
if o in ("-t", "--type"):
if a.lower() == "cr":

View File

@ -1,10 +1,28 @@
---------------------------------
1.0.3-beta - ??-Jan-2013
---------------------------------
Changes:
Misc bug fixes and enhancements
---------------------------------
1.0.2-beta - 25-Jan-2013
---------------------------------
Changes:
More verbose logging during auto-tag
Added %month% and %month_name% for renaming
Better parsing of volume numbers in file name
Bugs:
Better exception handling with corrupted image data
Fixed issues with RAR reading on OS X
Other minor bug fixes
---------------------------------
1.0.1-beta - 24-Jan-2013
---------------------------------
Bug Fix:
Fixed an issue where unicode strings can't be printed to OS Console
Fixed an issue where unicode strings can't be printed to OS X Console
---------------------------------
1.0.0-beta - 23-Jan-2013

View File

@ -63,7 +63,7 @@ class ComicTaggerSettings:
# identifier settings
self.id_length_delta_thresh = 5
self.id_publisher_blacklist = "Panini Comics, Abril, Scholastic Book Services"
self.id_publisher_blacklist = "Panini Comics, Abril, Scholastic Book Services, Editorial Televisa"
# Show/ask dialog flags
self.ask_about_cbi_in_rar = True

View File

@ -79,7 +79,7 @@ class SettingsWindow(QtGui.QDialog):
nldtTip = (
""" <html>The <b>Name Length Delta Threshold</b> is for eliminating automatic
""" <html>The <b>Default Name Length Match Tolerance</b> is for eliminating automatic
search matches that are too long compared to your series name search. The higher
it is, the more likely to have a good match, but each search will take longer and
use more bandwidth. Too low, and only the very closest lexical matches will be

View File

@ -263,7 +263,7 @@
<string/>
</property>
<property name="text">
<string>Name Length Delta Threshold:</string>
<string>Default Name Length Match Tolerance:</string>
</property>
</widget>
</item>
@ -275,6 +275,12 @@
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="maximumSize">
<size>
<width>50</width>
<height>16777215</height>
</size>
</property>
<property name="toolTip">
<string/>
</property>
@ -443,7 +449,7 @@
<item row="1" column="1">
<widget class="QLineEdit" name="leRenameTemplate">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;The template for the new filename. Accepts the following variables:&lt;/p&gt;&lt;p&gt;%series%&lt;br/&gt;%issue%&lt;br/&gt;%volume%&lt;br/&gt;%issuecount%&lt;br/&gt;%year%&lt;br/&gt;%publisher%&lt;br/&gt;%title%&lt;/p&gt;&lt;p&gt;Examples:&lt;/p&gt;&lt;p&gt;&lt;span style=&quot; font-style:italic;&quot;&gt;%series% %issue% (%year%)&lt;/span&gt;&lt;br/&gt;&lt;span style=&quot; font-style:italic;&quot;&gt;%series% #%issue% - %title%&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;The template for the new filename. Accepts the following variables:&lt;/p&gt;&lt;p&gt;%series%&lt;br/&gt;%issue%&lt;br/&gt;%volume%&lt;br/&gt;%issuecount%&lt;br/&gt;%year%&lt;br/&gt;%month%&lt;br/&gt;%month_name%&lt;br/&gt;%publisher%&lt;br/&gt;%title%&lt;/p&gt;&lt;p&gt;Examples:&lt;/p&gt;&lt;p&gt;&lt;span style=&quot; font-style:italic;&quot;&gt;%series% %issue% (%year%)&lt;/span&gt;&lt;br/&gt;&lt;span style=&quot; font-style:italic;&quot;&gt;%series% #%issue% - %title%&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
</widget>
</item>

View File

@ -29,6 +29,7 @@ import os
import pprint
import json
import webbrowser
import re
from volumeselectionwindow import VolumeSelectionWindow
from options import MetaDataStyle
@ -122,13 +123,6 @@ class TaggerWindow( QtGui.QMainWindow):
#self.splitter.setHandleWidth(0)
#self.splitter.handle(1).setDisabled(True)
# This is ugly, and should probably be done in a resource file
if platform.system() == "Linux":
self.scrollAreaWidgetContents.resize( self.scrollAreaWidgetContents.width(), 630)
#if platform.system() == "Darwin":
# self.scrollAreaWidgetContents.resize( 550, self.scrollAreaWidgetContents.height())
# we can't specify relative font sizes in the UI designer, so
# walk through all the lablels in the main form, and make them
# a smidge smaller
@ -140,8 +134,8 @@ class TaggerWindow( QtGui.QMainWindow):
f.setItalic( True )
child.setFont( f )
#---------------------------
self.scrollAreaWidgetContents.adjustSize()
self.setWindowIcon(QtGui.QIcon(os.path.join(ComicTaggerSettings.baseDir(), 'graphics/app.png' )))
@ -201,6 +195,9 @@ class TaggerWindow( QtGui.QMainWindow):
self.splitter.setSizes([ self.settings.last_form_side_width , self.settings.last_list_side_width])
self.raise_()
QtCore.QCoreApplication.processEvents()
self.resizeEvent( None )
self.splitter.splitterMoved.connect( self.splitterMovedEvent )
self.fileSelectionList.addAppAction( self.actionAutoIdentify )
self.fileSelectionList.addAppAction( self.actionAutoTag )
@ -390,6 +387,7 @@ class TaggerWindow( QtGui.QMainWindow):
if rar_count != 0:
dlg = ExportWindow( self, self.settings,
self.tr("You have selected {0} archive(s) to export to Zip format. New archives will be created in the same folder as the original.\n\nPlease choose options below, and select OK.\n".format(rar_count) ))
dlg.adjustSize( )
dlg.setModal( True )
if not dlg.exec_():
return
@ -402,6 +400,9 @@ class TaggerWindow( QtGui.QMainWindow):
new_archives_to_add = []
archives_to_remove = []
skipped_list = []
failed_list = []
success_count = 0
for ca in ca_list:
if ca.isRar():
@ -419,11 +420,13 @@ class TaggerWindow( QtGui.QMainWindow):
if os.path.lexists( export_name ):
if dlg.fileConflictBehavior == ExportConflictOpts.dontCreate:
export_name = None
skipped_list.append( ca.path )
elif dlg.fileConflictBehavior == ExportConflictOpts.createUnique:
export_name = utils.unique_file( export_name )
if export_name is not None:
if ca.exportAsZip( export_name ):
success_count += 1
if dlg.addToList:
new_archives_to_add.append( export_name )
if dlg.deleteOriginal:
@ -431,15 +434,31 @@ class TaggerWindow( QtGui.QMainWindow):
os.unlink( ca.path )
else:
QtGui.QMessageBox.information(self, self.tr("Export as Zip Archive"),
self.tr("An error occured while exporting {0} to {1}. Batch operation aborted!".format(original_path, export_name)))
break
# last export failed, so remove the zip, if it exists
failed_list.append( ca.path )
if os.path.lexists( export_name ):
os.remove( export_name )
progdialog.close()
self.fileSelectionList.addPathList( new_archives_to_add )
self.fileSelectionList.removeArchiveList( archives_to_remove )
# ATB maybe show a summary of files created here...?
summary = u"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( len( skipped_list ) )
for f in skipped_list:
summary += u"\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( len( failed_list ) )
for f in failed_list:
summary += u"\t{0}\n".format( f )
dlg = LogWindow( self )
dlg.setText( summary )
dlg.setWindowTitle( "Archive Export to Zip Summary" )
dlg.exec_()
def aboutApp( self ):
@ -485,6 +504,7 @@ class TaggerWindow( QtGui.QMainWindow):
def actualLoadCurrentArchive( self ):
if self.metadata.isEmpty:
self.metadata = self.comic_archive.metadataFromFilename( )
if len(self.metadata.pages) == 0:
self.metadata.setDefaultPageList( self.comic_archive.getNumberOfPages() )
self.updateCoverImage()
@ -754,10 +774,13 @@ class TaggerWindow( QtGui.QMainWindow):
item_text = role
item = QtGui.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.setData( QtCore.Qt.ToolTipRole, item_text )
item.setFlags(QtCore.Qt.ItemIsSelectable| QtCore.Qt.ItemIsEnabled)
self.twCredits.setItem(row, 2, item)
@ -988,6 +1011,7 @@ class TaggerWindow( QtGui.QMainWindow):
self.formToMetadata()
success = self.comic_archive.writeMetadata( self.metadata, self.save_data_style )
self.comic_archive.loadCache( [ MetaDataStyle.CBI, MetaDataStyle.CIX ] )
QtGui.QApplication.restoreOverrideCursor()
if not success:
@ -1388,6 +1412,8 @@ class TaggerWindow( QtGui.QMainWindow):
progdialog.show()
prog_idx = 0
failed_list = []
success_count = 0
for ca in ca_list:
if ca.hasMetadata( style ):
QtCore.QCoreApplication.processEvents()
@ -1400,17 +1426,26 @@ class TaggerWindow( QtGui.QMainWindow):
if ca.hasMetadata( style ) and ca.isWritable():
if not ca.removeMetadata( style ):
if has_md_count == 1:
QtGui.QMessageBox.warning(self, self.tr("Remove failed"), self.tr("The tag removal operation seemed to fail!"))
else:
QtGui.QMessageBox.warning(self, self.tr("Remove failed"),
self.tr("The tag removal operation seemed to fail for {0} Operation aborted!".format(ca.path)))
break
failed_list.append( ca.path )
else:
success_count += 1
ca.loadCache( [ MetaDataStyle.CBI, MetaDataStyle.CIX ] )
progdialog.close()
self.fileSelectionList.updateSelectedRows()
self.updateInfoBox()
self.updateMenus()
summary = u"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( len( failed_list ) )
for f in failed_list:
summary += u"\t{0}\n".format( f )
dlg = LogWindow( self )
dlg.setText( summary )
dlg.setWindowTitle( "Tag Remove Summary" )
dlg.exec_()
def copyTags( self ):
# copy the indicated tags in the archive
@ -1452,6 +1487,8 @@ class TaggerWindow( QtGui.QMainWindow):
progdialog.show()
prog_idx = 0
failed_list = []
success_count = 0
for ca in ca_list:
if ca.hasMetadata( src_style ):
QtCore.QCoreApplication.processEvents()
@ -1469,16 +1506,27 @@ class TaggerWindow( QtGui.QMainWindow):
md = CBLTransformer( md, self.settings ).apply()
if not ca.writeMetadata( md, dest_style ):
if has_md_count == 1:
QtGui.QMessageBox.warning(self, self.tr("Remove failed"), self.tr("The tag removal operation seemed to fail!"))
else:
QtGui.QMessageBox.warning(self, self.tr("Remove failed"),
self.tr("The tag removal operation seemed to fail for {0} Operation aborted!".format(ca.path)))
break
failed_list.append( ca.path )
else:
success_count += 1
ca.loadCache( [ MetaDataStyle.CBI, MetaDataStyle.CIX ] )
progdialog.close()
self.fileSelectionList.updateSelectedRows()
self.updateInfoBox()
self.updateMenus()
summary = u"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( len( failed_list ) )
for f in failed_list:
summary += u"\t{0}\n".format( f )
dlg = LogWindow( self )
dlg.setText( summary )
dlg.setWindowTitle( "Tag Copy Summary" )
dlg.exec_()
def actualIssueDataFetch( self, match ):
@ -1499,7 +1547,15 @@ class TaggerWindow( QtGui.QMainWindow):
return cv_md
def autoTagLog( self, text ):
IssueIdentifier.defaultWriteOutput( text )
if self.atprogdialog is not None:
self.atprogdialog.textEdit.insertPlainText(text)
self.atprogdialog.textEdit.ensureCursorVisible()
QtCore.QCoreApplication.processEvents()
QtCore.QCoreApplication.processEvents()
QtCore.QCoreApplication.processEvents()
def identifyAndTagSingleArchive( self, ca, match_results, dlg):
success = False
ii = IssueIdentifier( ca, self.settings )
@ -1507,29 +1563,29 @@ class TaggerWindow( QtGui.QMainWindow):
# read in metadata, and parse file name if not there
md = ca.readMetadata( self.save_data_style )
if md.isEmpty:
md = ca.metadataFromFilename()
md = ca.metadataFromFilename()
if dlg.ignoreLeadingDigitsInFilename and md.series is not None:
#remove all leading numbers
md.series = re.sub( "([\d.]*)(.*)", "\\2", md.series)
# use the dialog specified search string
if dlg.searchString is not None:
md.series = dlg.searchString
if md is None or md.isEmpty:
print "!!!!No metadata given to search online with!"
return False, match_results
def myoutput( text ):
IssueIdentifier.defaultWriteOutput( text )
self.atprogdialog.textEdit.insertPlainText(text)
self.atprogdialog.textEdit.ensureCursorVisible()
QtCore.QCoreApplication.processEvents()
QtCore.QCoreApplication.processEvents()
QtCore.QCoreApplication.processEvents()
if dlg.dontUseYear:
md.year = None
if dlg.assumeIssueOne and ( md.issue is None or md.issue == ""):
md.issue = "1"
ii.setAdditionalMetadata( md )
ii.onlyUseAdditionalMetaData = True
ii.setOutputFunction( myoutput )
ii.setOutputFunction( self.autoTagLog )
ii.cover_page_index = md.getCoverPageIndexList()[0]
ii.setCoverURLCallback( self.atprogdialog.setTestImage )
ii.setCoverURLCallback( self.atprogdialog.setTestImage )
ii.setNameLengthDeltaThreshold( dlg.nameLengthMatchTolerance )
matches = ii.search()
@ -1555,32 +1611,36 @@ class TaggerWindow( QtGui.QMainWindow):
choices = True
if choices:
print "Online search: Multiple matches. Save aborted"
self.autoTagLog( "Online search: Multiple matches. Save aborted\n" )
match_results.multipleMatches.append(MultipleMatch(ca,matches))
elif low_confidence and not dlg.autoSaveOnLow:
print "Online search: Low confidence match. Save aborted"
self.autoTagLog( "Online search: Low confidence match. Save aborted\n" )
match_results.noMatches.append(ca.path)
elif not found_match:
print "Online search: No match found. Save aborted"
self.autoTagLog( "Online search: No match found. Save aborted\n" )
match_results.noMatches.append(ca.path)
else:
# a single match!
if low_confidence:
self.autoTagLog( "Online search: Low confidence match, but saving anyways, as incdicated...\n" )
# now get the particular issue data
cv_md = self.actualIssueDataFetch( matches[0] )
if cv_md is None:
match_results.fetchDataFailures.append(ca.path)
if cv_md is not None:
if cv_md is not None:
md.overlay( cv_md )
if not ca.writeMetadata( md, self.save_data_style ):
match_results.writeFailures.append(ca.path)
self.autoTagLog( "Save failed ;-(\n" )
else:
match_results.goodMatches.append(ca.path)
success = True
self.autoTagLog( "Save complete!\n" )
ca.loadCache( [ MetaDataStyle.CBI, MetaDataStyle.CIX ] )
return success, match_results
def autoTag( self ):
@ -1597,23 +1657,29 @@ class TaggerWindow( QtGui.QMainWindow):
atstartdlg = AutoTagStartWindow( self, self.settings,
self.tr("You have selected {0} archive(s) to automatically identify and write {1} tags to.\n\n".format(len(ca_list), MetaDataStyle.name[style]) +
"Please choose options below, and select OK.\n" ))
"Please choose options below, and select OK to Auto-Tag.\n" ))
atstartdlg.adjustSize( )
atstartdlg.setModal( True )
if not atstartdlg.exec_():
return
self.atprogdialog = AutoTagProgressWindow( self)
self.atprogdialog.setModal(True)
#self.progdialog.rejected.connect( self.identifyCancel )
self.atprogdialog.show()
self.atprogdialog.progressBar.setMaximum( len(ca_list) )
self.atprogdialog.setWindowTitle( "Auto-Tagging" )
self.autoTagLog( u"========================================================================\n" )
self.autoTagLog( u"Auto-Tagging Started for {0} items\n".format(len(ca_list)))
prog_idx = 0
match_results = OnlineMatchResults()
archives_to_remove = []
for ca in ca_list:
self.autoTagLog( u"============================================================\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) )
cover_idx = ca.readMetadata(style).getCoverPageIndexList()[0]
image_data = ca.getPage( cover_idx )
self.atprogdialog.setArchiveImage( image_data )
@ -1630,28 +1696,37 @@ class TaggerWindow( QtGui.QMainWindow):
if ca.isWritable():
success, match_results = self.identifyAndTagSingleArchive( ca, match_results, atstartdlg )
self.atprogdialog.close()
if success and atstartdlg.removeAfterSuccess:
archives_to_remove.append( ca )
self.atprogdialog.close()
if atstartdlg.removeAfterSuccess:
self.fileSelectionList.removeArchiveList( archives_to_remove )
self.fileSelectionList.updateSelectedRows()
self.loadArchive( self.fileSelectionList.getCurrentArchive() )
self.atprogdialog = None
summary = ""
summary += "Successfully tagged archives: {0}\n".format( len(match_results.goodMatches))
if len ( match_results.multipleMatches ) > 0:
summary += "Archives with multiple matches: {0}\n".format( len(match_results.multipleMatches))
if len ( match_results.noMatches ) > 0:
summary += "Archives with no matches: {0}\n".format( len(match_results.noMatches))
if len ( match_results.fetchDataFailures ) > 0:
summary += "Archives that failed due to data fetch errors: {0}\n".format( len(match_results.fetchDataFailures))
if len ( match_results.writeFailures ) > 0:
summary += "Archives that failed due to file writing errors: {0}\n".format( len(match_results.writeFailures))
summary = u""
summary += u"Successfully tagged archives: {0}\n".format( len(match_results.goodMatches))
if len ( match_results.multipleMatches ) > 0:
summary += "\n\nDo you want to manually select the ones with multiple matches now?"
summary += u"Archives with multiple matches: {0}\n".format( len(match_results.multipleMatches))
if len ( match_results.noMatches ) > 0:
summary += u"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( len(match_results.fetchDataFailures))
if len ( match_results.writeFailures ) > 0:
summary += u"Archives that failed due to file writing errors: {0}\n".format( len(match_results.writeFailures))
self.autoTagLog( summary )
if len ( match_results.multipleMatches ) > 0:
summary += u"\n\nDo you want to manually select the ones with multiple matches now?"
reply = QtGui.QMessageBox.question(self,
self.tr("Auto-Tag Summary"),
self.tr(u"Auto-Tag Summary"),
self.tr(summary),
QtGui.QMessageBox.Yes, QtGui.QMessageBox.No )
@ -1779,4 +1854,16 @@ class TaggerWindow( QtGui.QMainWindow):
self.actualLoadCurrentArchive()
def fileListCleared( self ):
self.resetApp()
self.resetApp()
def splitterMovedEvent( self, w1, w2 ):
scrollbar_w = 0
if self.scrollArea.verticalScrollBar().isVisible():
scrollbar_w = self.scrollArea.verticalScrollBar().width()
new_w = self.scrollArea.width() - scrollbar_w - 5
self.scrollAreaWidgetContents.resize( new_w, self.scrollAreaWidgetContents.height())
def resizeEvent( self, ev ):
self.splitterMovedEvent( 0, 0)

View File

@ -301,13 +301,13 @@
<property name="geometry">
<rect>
<x>0</x>
<y>-64</y>
<y>0</y>
<width>470</width>
<height>520</height>
</rect>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
@ -1107,7 +1107,7 @@
<x>0</x>
<y>0</y>
<width>1096</width>
<height>25</height>
<height>21</height>
</rect>
</property>
<widget class="QMenu" name="menuComicTagger">

View File

@ -1,26 +1,24 @@
-----------------------------------------------------
Features
-----------------------------------------------------
Multi-file:
Batch Functions:
Auto-Tag
Interactive dialog at end
Manually change cover image on left??
Rename
check-box for rows?
manual edit the preview?
Rename dialog:
check-box for rows?
manual edit the preview?
Docs:
Auto-Tagging Tips:
Multiple Passes with different options
Look into using libarchive
-----------------------------------------------------
Bugs
spider-man 678 .... ascii print problem. grrrr
Bugs
-----------------------------------------------------
RAR Password -- childrens crusade 3
Rar flakes out when a space is at the front of the subfolder in the archive.
Zip flakes out when filename differs from index (or whatever) i.e "\" vs "/". Python issue
-----------------------------------------------------
Big Future Features
@ -83,3 +81,4 @@ Release Process
----------------------------------------------
rename 's/([A-Za-z]+)(\d+)(.cb[rz])/$1 $2$3/' *.cb?

View File

@ -304,23 +304,27 @@ class VolumeSelectionWindow(QtGui.QDialog):
item_text = record['name']
item = QtGui.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.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.setData( QtCore.Qt.ToolTipRole, item_text )
item.setData(QtCore.Qt.DisplayRole, record['count_of_issues'])
item.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled)
self.twList.setItem(row, 2, item)
if record['publisher'] is not None:
item_text = record['publisher']['name']
item.setData( QtCore.Qt.ToolTipRole, item_text )
item = QtGui.QTableWidgetItem(item_text)
item.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled)
self.twList.setItem(row, 3, item)