Compare commits

..

74 Commits

Author SHA1 Message Date
85728d33bb New filename template variables
git-svn-id: http://comictagger.googlecode.com/svn/trunk@435 6c5673fe-1810-88d6-992b-cd32ca31540c
2013-02-06 05:59:52 +00:00
2ade08aa89 Bumped version
git-svn-id: http://comictagger.googlecode.com/svn/trunk@434 6c5673fe-1810-88d6-992b-cd32ca31540c
2013-02-06 05:59:08 +00:00
50909962d3 Implemented export to zip on command line
git-svn-id: http://comictagger.googlecode.com/svn/trunk@430 6c5673fe-1810-88d6-992b-cd32ca31540c
2013-02-06 00:14:37 +00:00
cc02023730 Fixed an issue in rar directory reading when the first char in the path is a space.
git-svn-id: http://comictagger.googlecode.com/svn/trunk@429 6c5673fe-1810-88d6-992b-cd32ca31540c
2013-02-05 22:48:12 +00:00
5bdc40b9f5 Make sure to change codec for stderror too
git-svn-id: http://comictagger.googlecode.com/svn/trunk@428 6c5673fe-1810-88d6-992b-cd32ca31540c
2013-02-05 22:47:45 +00:00
4f3e63db07 Make a lot of print statements go to stderr
git-svn-id: http://comictagger.googlecode.com/svn/trunk@427 6c5673fe-1810-88d6-992b-cd32ca31540c
2013-02-05 22:27:35 +00:00
b8893b853f Release notes update
git-svn-id: http://comictagger.googlecode.com/svn/trunk@426 6c5673fe-1810-88d6-992b-cd32ca31540c
2013-02-05 06:42:24 +00:00
6da6f38673 Text tweak
git-svn-id: http://comictagger.googlecode.com/svn/trunk@425 6c5673fe-1810-88d6-992b-cd32ca31540c
2013-02-05 06:37:21 +00:00
369dcbb5a1 Tweaked the pagebrowser layout
Added arrow icons for some buttons

git-svn-id: http://comictagger.googlecode.com/svn/trunk@424 6c5673fe-1810-88d6-992b-cd32ca31540c
2013-02-05 06:37:04 +00:00
ec010f29e8 Center progress dialogs on update to keep from drifting due to growth
git-svn-id: http://comictagger.googlecode.com/svn/trunk@423 6c5673fe-1810-88d6-992b-cd32ca31540c
2013-02-05 05:14:26 +00:00
22867bc9e6 Addded popup screen image
git-svn-id: http://comictagger.googlecode.com/svn/trunk@422 6c5673fe-1810-88d6-992b-cd32ca31540c
2013-02-05 04:50:26 +00:00
dde1913e07 Font tweaks
git-svn-id: http://comictagger.googlecode.com/svn/trunk@421 6c5673fe-1810-88d6-992b-cd32ca31540c
2013-02-05 04:49:44 +00:00
5b5842a5f8 tweaked the dialogs window flags to enable maximize on some, and remove the help button on others
git-svn-id: http://comictagger.googlecode.com/svn/trunk@420 6c5673fe-1810-88d6-992b-cd32ca31540c
2013-02-05 03:51:50 +00:00
fbf086886f Made selection list font a little smaller
git-svn-id: http://comictagger.googlecode.com/svn/trunk@419 6c5673fe-1810-88d6-992b-cd32ca31540c
2013-02-05 03:51:05 +00:00
c1ff6c4b26 Fixed form resizing bug
git-svn-id: http://comictagger.googlecode.com/svn/trunk@418 6c5673fe-1810-88d6-992b-cd32ca31540c
2013-02-05 03:50:03 +00:00
99b110d052 PageListEditor now uses CoverImageWidget
git-svn-id: http://comictagger.googlecode.com/svn/trunk@417 6c5673fe-1810-88d6-992b-cd32ca31540c
2013-02-05 00:00:18 +00:00
3df498eed4 Page list editor displays 1-based list
git-svn-id: http://comictagger.googlecode.com/svn/trunk@416 6c5673fe-1810-88d6-992b-cd32ca31540c
2013-02-04 22:20:04 +00:00
b5ab2a6ac9 Updated page browser to use coverimagewidget
git-svn-id: http://comictagger.googlecode.com/svn/trunk@415 6c5673fe-1810-88d6-992b-cd32ca31540c
2013-02-04 22:19:31 +00:00
5c91960f04 Added option to not show controls in widget
git-svn-id: http://comictagger.googlecode.com/svn/trunk@414 6c5673fe-1810-88d6-992b-cd32ca31540c
2013-02-04 22:19:08 +00:00
3b52fd3213 Main window now uses the CoverImageWidget
git-svn-id: http://comictagger.googlecode.com/svn/trunk@413 6c5673fe-1810-88d6-992b-cd32ca31540c
2013-02-04 21:05:31 +00:00
9366457b88 MatchSelectionWindow now uses CoverImageWidget
git-svn-id: http://comictagger.googlecode.com/svn/trunk@412 6c5673fe-1810-88d6-992b-cd32ca31540c
2013-02-04 19:54:04 +00:00
1cb7ef66db Added tool tip about double-clicking
git-svn-id: http://comictagger.googlecode.com/svn/trunk@411 6c5673fe-1810-88d6-992b-cd32ca31540c
2013-02-04 19:53:32 +00:00
ee6a05deae UI tweaks
git-svn-id: http://comictagger.googlecode.com/svn/trunk@410 6c5673fe-1810-88d6-992b-cd32ca31540c
2013-02-04 19:53:03 +00:00
c978883584 Volume selection widget now uses CoverImageWidget
git-svn-id: http://comictagger.googlecode.com/svn/trunk@409 6c5673fe-1810-88d6-992b-cd32ca31540c
2013-02-04 19:11:57 +00:00
9b5508ecba Added URL (singe image) mode.
Tweakd resize logic

git-svn-id: http://comictagger.googlecode.com/svn/trunk@408 6c5673fe-1810-88d6-992b-cd32ca31540c
2013-02-04 19:11:28 +00:00
8e1c6fae7c Mac OS X acts weird with modality settings
git-svn-id: http://comictagger.googlecode.com/svn/trunk@407 6c5673fe-1810-88d6-992b-cd32ca31540c
2013-02-04 19:10:53 +00:00
59e662f5a7 Fixed window modality of issue selection window
git-svn-id: http://comictagger.googlecode.com/svn/trunk@406 6c5673fe-1810-88d6-992b-cd32ca31540c
2013-02-04 17:25:39 +00:00
6486d97ee3 Added modal image quick popup
git-svn-id: http://comictagger.googlecode.com/svn/trunk@405 6c5673fe-1810-88d6-992b-cd32ca31540c
2013-02-04 17:24:48 +00:00
8c088440c5 updated coverimagewidget to manage background loading of alt cover URLs
git-svn-id: http://comictagger.googlecode.com/svn/trunk@404 6c5673fe-1810-88d6-992b-cd32ca31540c
2013-02-04 05:15:23 +00:00
320ee1c5d1 Updated todo
git-svn-id: http://comictagger.googlecode.com/svn/trunk@403 6c5673fe-1810-88d6-992b-cd32ca31540c
2013-02-04 05:14:29 +00:00
e123720354 Issue selection dialog now uses the coverimagewidget
git-svn-id: http://comictagger.googlecode.com/svn/trunk@402 6c5673fe-1810-88d6-992b-cd32ca31540c
2013-02-04 05:13:23 +00:00
d39d4e79ad Added async version of the alt cover URL fetcher
git-svn-id: http://comictagger.googlecode.com/svn/trunk@401 6c5673fe-1810-88d6-992b-cd32ca31540c
2013-02-04 05:09:48 +00:00
8d7eeece30 No need to pre-fetch now, since the cover widget manages this itself
git-svn-id: http://comictagger.googlecode.com/svn/trunk@400 6c5673fe-1810-88d6-992b-cd32ca31540c
2013-02-04 05:08:22 +00:00
3b64e1a3ec Added a new default publisher blacklist item
git-svn-id: http://comictagger.googlecode.com/svn/trunk@399 6c5673fe-1810-88d6-992b-cd32ca31540c
2013-02-03 18:16:16 +00:00
81ae9bd635 Change the post auto-tag dialog to also show low-confidence single matches
git-svn-id: http://comictagger.googlecode.com/svn/trunk@398 6c5673fe-1810-88d6-992b-cd32ca31540c
2013-02-03 18:15:48 +00:00
27846772e9 Reworked the post auto-tag selection dialog:
New display image widgets
  Sorting
  Added issue title

git-svn-id: http://comictagger.googlecode.com/svn/trunk@397 6c5673fe-1810-88d6-992b-cd32ca31540c
2013-02-03 18:14:16 +00:00
baf697b919 New widget for managing the loading and displaying of archive pages and covers from Comic Vine
git-svn-id: http://comictagger.googlecode.com/svn/trunk@396 6c5673fe-1810-88d6-992b-cd32ca31540c
2013-02-03 18:13:18 +00:00
59ede8d446 Clean up the strings from the alt cover URL list
git-svn-id: http://comictagger.googlecode.com/svn/trunk@395 6c5673fe-1810-88d6-992b-cd32ca31540c
2013-02-03 18:12:10 +00:00
8b748a3343 Made the alt cover threshold more stringent
git-svn-id: http://comictagger.googlecode.com/svn/trunk@394 6c5673fe-1810-88d6-992b-cd32ca31540c
2013-02-03 18:10:59 +00:00
75471aaddc Added caching of the alt cover image URL list
git-svn-id: http://comictagger.googlecode.com/svn/trunk@393 6c5673fe-1810-88d6-992b-cd32ca31540c
2013-02-02 18:41:06 +00:00
7225f261f1 Tuned the cover score thresholds a bit
Fixed a "one-shot" bug where sometimes there is a zero issue but not a "1"

git-svn-id: http://comictagger.googlecode.com/svn/trunk@392 6c5673fe-1810-88d6-992b-cd32ca31540c
2013-02-02 18:40:40 +00:00
c466264d43 UI tweaks for auto tag match window
git-svn-id: http://comictagger.googlecode.com/svn/trunk@391 6c5673fe-1810-88d6-992b-cd32ca31540c
2013-02-02 18:39:39 +00:00
14e801b717 Added support for alternate covers from comicvine
git-svn-id: http://comictagger.googlecode.com/svn/trunk@390 6c5673fe-1810-88d6-992b-cd32ca31540c
2013-02-02 06:03:58 +00:00
af4b467814 Added support for gif in archive
git-svn-id: http://comictagger.googlecode.com/svn/trunk@389 6c5673fe-1810-88d6-992b-cd32ca31540c
2013-02-02 06:02:25 +00:00
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
49 changed files with 2125 additions and 834 deletions

View File

@ -12,7 +12,7 @@ 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)

View File

@ -120,7 +120,9 @@ class RarFileImplementation(object):
if len(accum)==2:
data = {}
data['index'] = i
data['filename'] = accum[0].strip()
#!!!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]

View File

@ -26,6 +26,10 @@ from PyQt4.QtCore import QUrl, pyqtSignal, QByteArray
from imagefetcher import ImageFetcher
from settings import ComicTaggerSettings
from options import MetaDataStyle
from coverimagewidget import CoverImageWidget
from comicvinetalker import ComicVineTalker
import utils
class AutoTagMatchWindow(QtGui.QDialog):
@ -36,9 +40,25 @@ class AutoTagMatchWindow(QtGui.QDialog):
uic.loadUi(os.path.join(ComicTaggerSettings.baseDir(), 'autotagmatchwindow.ui' ), self)
self.skipButton = QtGui.QPushButton(self.tr("Skip"))
self.altCoverWidget = CoverImageWidget( self.altCoverContainer, CoverImageWidget.AltCoverMode )
gridlayout = QtGui.QGridLayout( self.altCoverContainer )
gridlayout.addWidget( self.altCoverWidget )
gridlayout.setContentsMargins(0,0,0,0)
self.archiveCoverWidget = CoverImageWidget( self.archiveCoverContainer, CoverImageWidget.ArchiveMode )
gridlayout = QtGui.QGridLayout( self.archiveCoverContainer )
gridlayout.addWidget( self.archiveCoverWidget )
gridlayout.setContentsMargins(0,0,0,0)
utils.reduceWidgetFontSize( self.twList )
self.setWindowFlags(self.windowFlags() |
QtCore.Qt.WindowSystemMenuHint |
QtCore.Qt.WindowMaximizeButtonHint)
self.skipButton = QtGui.QPushButton(self.tr("Skip to Next"))
self.buttonBox.addButton(self.skipButton, QtGui.QDialogButtonBox.ActionRole)
self.buttonBox.button(QtGui.QDialogButtonBox.Ok).setText("Accept and Next")
self.buttonBox.button(QtGui.QDialogButtonBox.Ok).setText("Accept and Write Tags")
self.match_set_list = match_set_list
self.style = style
@ -56,18 +76,18 @@ class AutoTagMatchWindow(QtGui.QDialog):
self.current_match_set = self.match_set_list[ self.current_match_set_idx ]
if self.current_match_set_idx + 1 == len( self.match_set_list ):
self.skipButton.setDisabled(True)
self.buttonBox.button(QtGui.QDialogButtonBox.Cancel).setDisabled(True)
#self.buttonBox.button(QtGui.QDialogButtonBox.Ok).setText("Accept")
self.skipButton.setText(self.tr("Skip"))
self.setCoverImage()
self.populateTable()
self.twList.resizeColumnsToContents()
self.current_row = 0
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 or skip ({0} of {1}): {2}".format(
self.current_match_set_idx+1,
len( self.match_set_list ),
os.path.split(path)[1] ))
@ -85,6 +105,8 @@ class AutoTagMatchWindow(QtGui.QDialog):
item_text = match['series']
item = QtGui.QTableWidgetItem(item_text)
item.setData( QtCore.Qt.ToolTipRole, item_text )
item.setData( QtCore.Qt.UserRole, (match,))
item.setFlags(QtCore.Qt.ItemIsSelectable| QtCore.Qt.ItemIsEnabled)
self.twList.setItem(row, 0, item)
@ -93,21 +115,37 @@ 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)
item_text = ""
month_str = u""
year_str = u"????"
if match['month'] is not None:
item_text = u"{0}/".format(match['month'])
month_str = u"-{0:02d}".format(int(match['month']))
if match['year'] is not None:
item_text += u"{0}".format(match['year'])
else:
item_text += u"????"
year_str = u"{0}".format(match['year'])
item_text = year_str + month_str
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)
item_text = match['issue_title']
item = QtGui.QTableWidgetItem(item_text)
item.setData( QtCore.Qt.ToolTipRole, item_text )
item.setFlags(QtCore.Qt.ItemIsSelectable| QtCore.Qt.ItemIsEnabled)
self.twList.setItem(row, 3, item)
row += 1
self.twList.resizeColumnsToContents()
self.twList.setSortingEnabled(True)
self.twList.sortItems( 2 , QtCore.Qt.AscendingOrder )
self.twList.selectRow(0)
self.twList.resizeColumnsToContents()
self.twList.horizontalHeader().setStretchLastSection(True)
def cellDoubleClicked( self, r, c ):
@ -119,34 +157,18 @@ class AutoTagMatchWindow(QtGui.QDialog):
return
if prev is not None and prev.row() == curr.row():
return
self.current_row = curr.row()
# list selection was changed, update the the issue cover
self.labelThumbnail.setPixmap(QtGui.QPixmap(os.path.join(ComicTaggerSettings.baseDir(), 'graphics/nocover.png' )))
self.cover_fetcher = ImageFetcher( )
self.cover_fetcher.fetchComplete.connect(self.coverFetchComplete)
self.cover_fetcher.fetch( self.current_match_set.matches[self.current_row]['img_url'] )
# called when the image is done loading
def coverFetchComplete( self, image_data, issue_id ):
img = QtGui.QImage()
img.loadFromData( image_data )
self.labelThumbnail.setPixmap(QtGui.QPixmap(img))
self.altCoverWidget.setIssueID( self.currentMatch()['issue_id'] )
def setCoverImage( self ):
ca = self.current_match_set.ca
cover_idx = ca.readMetadata(self.style).getCoverPageIndexList()[0]
image_data = ca.getPage( cover_idx )
self.labelCover.setScaledContents(True)
if image_data is not None:
img = QtGui.QImage()
img.loadFromData( image_data )
self.labelCover.setPixmap(QtGui.QPixmap(img))
else:
self.labelCover.setPixmap(QtGui.QPixmap(os.path.join(ComicTaggerSettings.baseDir(), 'graphics/nocover.png' )))
self.archiveCoverWidget.setArchive(ca)
def currentMatch( self ):
row = self.twList.currentRow()
match = self.twList.item(row, 0).data( QtCore.Qt.UserRole ).toPyObject()[0]
return match
def accept(self):
self.saveMatch()
@ -180,7 +202,7 @@ class AutoTagMatchWindow(QtGui.QDialog):
def saveMatch( self ):
match = self.current_match_set.matches[self.current_row]
match = self.currentMatch()
ca = self.current_match_set.ca
md = ca.readMetadata( self.style )
@ -196,6 +218,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

@ -6,8 +6,8 @@
<rect>
<x>0</x>
<y>0</y>
<width>831</width>
<height>506</height>
<width>907</width>
<height>507</height>
</rect>
</property>
<property name="windowTitle">
@ -18,32 +18,27 @@
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<property name="spacing">
<number>0</number>
</property>
<item>
<widget class="QLabel" name="labelCover">
<widget class="QWidget" name="archiveCoverContainer" native="true">
<property name="minimumSize">
<size>
<width>200</width>
<height>0</height>
<height>350</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>200</width>
<height>300</height>
<height>350</height>
</size>
</property>
<property name="text">
<string>TextLabel</string>
</property>
</widget>
</item>
<item>
<widget class="QTableWidget" name="twList">
<property name="font">
<font>
<pointsize>9</pointsize>
</font>
</property>
<property name="selectionMode">
<enum>QAbstractItemView::SingleSelection</enum>
</property>
@ -54,7 +49,7 @@
<number>0</number>
</property>
<property name="columnCount">
<number>3</number>
<number>4</number>
</property>
<attribute name="horizontalHeaderStretchLastSection">
<bool>true</bool>
@ -77,34 +72,27 @@
<string>Date</string>
</property>
</column>
<column>
<property name="text">
<string>Title</string>
</property>
</column>
</widget>
</item>
<item>
<widget class="QLabel" name="labelThumbnail">
<widget class="QWidget" name="altCoverContainer" native="true">
<property name="minimumSize">
<size>
<width>200</width>
<height>0</height>
<height>350</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>200</width>
<height>300</height>
<height>350</height>
</size>
</property>
<property name="frameShape">
<enum>QFrame::Panel</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Sunken</enum>
</property>
<property name="text">
<string/>
</property>
<property name="scaledContents">
<bool>true</bool>
</property>
</widget>
</item>
</layout>

View File

@ -22,7 +22,7 @@ import sys
from PyQt4 import QtCore, QtGui, uic
import os
from settings import ComicTaggerSettings
import utils
class AutoTagProgressWindow(QtGui.QDialog):
@ -34,13 +34,12 @@ class AutoTagProgressWindow(QtGui.QDialog):
self.lblTest.setPixmap(QtGui.QPixmap(os.path.join(ComicTaggerSettings.baseDir(), 'graphics/nocover.png' )))
self.lblArchive.setPixmap(QtGui.QPixmap(os.path.join(ComicTaggerSettings.baseDir(), 'graphics/nocover.png' )))
self.isdone = False
# we can't specify relative font sizes in the UI designer, so
# make font for scroll window a smidge smaller
f = self.textEdit.font()
if f.pointSize() > 10:
f.setPointSize( f.pointSize() - 2 )
self.textEdit.setFont( f )
self.setWindowFlags(self.windowFlags() |
QtCore.Qt.WindowSystemMenuHint |
QtCore.Qt.WindowMaximizeButtonHint)
utils.reduceWidgetFontSize( self.textEdit )
def setArchiveImage( self, img_data):
self.setCoverImage( img_data, self.lblArchive )

View File

@ -35,21 +35,70 @@ class AutoTagStartWindow(QtGui.QDialog):
uic.loadUi(os.path.join(ComicTaggerSettings.baseDir(), 'autotagstartwindow.ui' ), self)
self.label.setText( msg )
self.setWindowFlags(self.windowFlags() &
~QtCore.Qt.WindowContextHelpButtonHint )
self.settings = settings
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 when trying to match archives with hard-to-parse or incorrect 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

@ -67,11 +67,13 @@ 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 zipfile.BadZipfile as e:
print >> sys.stderr, "bad zipfile [{0}]: {1} :: {2}".format(e, self.path, archive_file)
zf.close()
raise IOError
except Exception:
print "bad zipfile: {0} :: {1}".format(self.path, archive_file)
except Exception as e:
zf.close()
print >> sys.stderr, "bad zipfile [{0}]: {1} :: {2}".format(e, self.path, archive_file)
raise IOError
finally:
zf.close()
@ -110,7 +112,7 @@ class ZipArchiver:
def rebuildZipFile( self, exclude_list ):
# this recompresses the zip archive, without the files in the exclude_list
#print "Rebuilding zip {0} without {1}".format( self.path, exclude_list )
#print ">> sys.stderr, Rebuilding zip {0} without {1}".format( self.path, exclude_list )
# generate temp file
tmp_fd, tmp_name = tempfile.mkstemp( dir=os.path.dirname(self.path) )
@ -213,7 +215,7 @@ class ZipArchiver:
if not self.writeZipComment( self.path, comment ):
return False
except Exception as e:
print "Error while copying to {0}: {1}".format(self.path, e)
print >> sys.stderr, "Error while copying to {0}: {1}".format(self.path, e)
return False
else:
return True
@ -286,28 +288,28 @@ 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(
print >> sys.stderr, "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 "readArchiveFile(): [{0}] {1}:{2} attempt#{3}".format(str(e), self.path, archive_file, tries)
print >> sys.stderr, "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}] for {1}:{2} attempt#{3}".format(str(e), self.path, archive_file, tries)
print >> sys.stderr, "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)
print >> sys.stderr, "Attempted read_files() {0} times".format(tries)
if (len(entries) == 1):
return entries[0][1]
else:
@ -373,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 "getArchiveFilenameList(): [{0}] {1} attempt#{2}".format(str(e), self.path, tries)
print >> sys.stderr, "getArchiveFilenameList(): [{0}] {1} attempt#{2}".format(str(e), self.path, tries)
time.sleep(1)
else:
@ -391,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 "getRARObj(): [{0}] {1} attempt#{2}".format(str(e), self.path, tries)
print >> sys.stderr, "getRARObj(): [{0}] {1} attempt#{2}".format(str(e), self.path, tries)
time.sleep(1)
else:
@ -491,7 +497,9 @@ class UnknownArchiver:
#------------------------------------------------------------------
class ComicArchive:
logo_data = None
class ArchiveType:
Zip, Rar, Folder, Unknown = range(4)
@ -516,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
@ -527,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
@ -649,10 +666,8 @@ class ComicArchive:
try:
image_data = self.archiver.readArchiveFile( filename )
except IOError:
print "Error reading in page. Substituting logo page."
fname = os.path.join(ComicTaggerSettings.baseDir(), 'graphics/nocover.png' )
with open(fname) as x:
image_data = x.read()
print >> sys.stderr, "Error reading in page. Substituting logo page."
image_data = ComicArchive.logo_data
return image_data
@ -679,7 +694,7 @@ class ComicArchive:
# make a sub-list of image files
self.page_list = []
for name in files:
if ( name[-4:].lower() in [ ".jpg", "jpeg", ".png" ] and os.path.basename(name)[0] != "." ):
if ( name[-4:].lower() in [ ".jpg", "jpeg", ".png", ".gif" ] and os.path.basename(name)[0] != "." ):
self.page_list.append(name)
return self.page_list
@ -728,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
@ -740,8 +754,7 @@ class ComicArchive:
if write_success:
self.has_cbi = False
self.cbi_md = None
else:
self.resetCache()
self.resetCache()
return write_success
return True
@ -784,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
@ -796,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
@ -840,13 +851,13 @@ class ComicArchive:
def readRawCoMet( self ):
if not self.hasCoMet():
print self.path, "doesn't have CoMet data!"
print >> sys.stderr, self.path, "doesn't have CoMet data!"
return None
try:
raw_comet = self.archiver.readArchiveFile( self.comet_filename )
except IOError:
print "Error reading in raw CoMet!"
print >> sys.stderr, "Error reading in raw CoMet!"
raw_comet = ""
return raw_comet
@ -867,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
@ -879,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
@ -899,7 +908,7 @@ class ComicArchive:
data = self.archiver.readArchiveFile( n )
except:
data = ""
print "Error reading in Comet XML for validation!"
print >> sys.stderr, "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

@ -62,8 +62,10 @@ class OnlineMatchResults():
self.goodMatches = []
self.noMatches = []
self.multipleMatches = []
self.writeFailures = []
self.lowConfidenceMatches = []
self.writeFailures = []
self.fetchDataFailures = []
#-----------------------------
def actual_issue_data_fetch( match, settings ):
@ -72,7 +74,7 @@ def actual_issue_data_fetch( match, settings ):
try:
cv_md = ComicVineTalker().fetchIssueData( match['volume_id'], match['issue_number'], settings )
except ComicVineTalkerException:
print "Network error while getting issue details. Save aborted"
print >> sys.stderr, "Network error while getting issue details. Save aborted"
return None
if settings.apply_cbl_transform_on_cv_import:
@ -85,18 +87,50 @@ def actual_metadata_save( ca, opts, md ):
if not opts.dryrun:
# write out the new data
if not ca.writeMetadata( md, opts.data_style ):
print "The tag save seemed to fail!"
print >> sys.stderr,"The tag save seemed to fail!"
return False
else:
print "Save complete."
print >> sys.stderr,"Save complete."
else:
if opts.terse:
print "dry-run option was set, so nothing was written"
print >> sys.stderr,"dry-run option was set, so nothing was written"
else:
print "dry-run option was set, so nothing was written, but here is the final set of tags:"
print >> sys.stderr,"dry-run option was set, so nothing was written, but here is the final set of tags:"
print u"{0}".format(md)
return True
def display_match_set_for_choice( label, match_set, opts, settings ):
print "{0} -- {1}:".format(match_set.filename, label )
# sort match list by year
match_set.matches.sort(key=lambda k: k['year'])
for (counter,m) in enumerate(match_set.matches):
counter += 1
print u" {0}. {1} #{2} [{3}] ({4}/{5}) - {6}".format(counter,
m['series'],
m['issue_number'],
m['publisher'],
m['month'],
m['year'],
m['issue_title'])
if opts.interactive:
while True:
i = raw_input("Choose a match #, or 's' to skip: ")
if (i.isdigit() and int(i) in range(1,len(match_set.matches)+1)) or i == 's':
break
if i != 's':
i = int(i) - 1
# save the data!
# we know at this point, that the file is all good to go
ca = ComicArchive( match_set.filename )
if settings.rar_exe_path != "":
ca.setExternalRarProgram( settings.rar_exe_path )
md = create_local_metadata( opts, ca, ca.hasMetadata(opts.data_style) )
cv_md = actual_issue_data_fetch(match_set.matches[int(i)], settings)
md.overlay( cv_md )
actual_metadata_save( ca, opts, md )
def post_process_matches( match_results, opts, settings ):
# now go through the match results
@ -119,44 +153,37 @@ def post_process_matches( match_results, opts, settings ):
for f in match_results.writeFailures:
print f
if len( match_results.fetchDataFailures ) > 0:
print "\nNetwork Data Fetch Failures:"
print "------------------"
for f in match_results.fetchDataFailures:
print f
if not opts.show_save_summary and not opts.interactive:
#jusr quit if we're not interactive or showing the summary
#just quit if we're not interactive or showing the summary
return
if len( match_results.multipleMatches ) > 0:
print "\nMultiple matches:"
print "\nArchives with multiple high-confidence matches:"
print "------------------"
for mm in match_results.multipleMatches:
print mm.filename
for (counter,m) in enumerate(mm.matches):
print u" {0}. {1} #{2} [{3}] ({4}/{5}) - {6}".format(counter,
m['series'],
m['issue_number'],
m['publisher'],
m['month'],
m['year'],
m['issue_title'])
if opts.interactive:
while True:
i = raw_input("Choose a match #, or 's' to skip: ")
if (i.isdigit() and int(i) in range(len(mm.matches))) or i == 's':
break
if i != 's':
# save the data!
# we know at this point, that the file is all good to go
ca = ComicArchive( mm.filename )
md = create_local_metadata( opts, ca, ca.hasMetadata(opts.data_style) )
cv_md = actual_issue_data_fetch(mm.matches[int(i)], settings)
md.overlay( cv_md )
actual_metadata_save( ca, opts, md )
print
for match_set in match_results.multipleMatches:
display_match_set_for_choice( "Multiple high-confidence matches", match_set, opts, settings )
if len( match_results.lowConfidenceMatches ) > 0:
print "\nArchives with low-confidence matches:"
print "------------------"
for match_set in match_results.lowConfidenceMatches:
if len( match_set.matches) == 1:
label = "Single low-confidence match"
else:
label = "Multiple low-confidence matches"
display_match_set_for_choice( label, match_set, opts, settings )
def cli_mode( opts, settings ):
if len( opts.file_list ) < 1:
print "You must specify at least one filename. Use the -h option for more info"
print >> sys.stderr,"You must specify at least one filename. Use the -h option for more info"
return
match_results = OnlineMatchResults()
@ -195,13 +222,17 @@ def process_file_cli( filename, opts, settings, match_results ):
if settings.rar_exe_path != "":
ca.setExternalRarProgram( settings.rar_exe_path )
if not os.path.lexists( filename ):
print >> sys.stderr,"Cannot find "+ filename
return
if not ca.seemsToBeAComicArchive():
print "Sorry, but "+ filename + " is not a comic archive!"
print >> sys.stderr,"Sorry, but "+ filename + " is not a comic archive!"
return
#if not ca.isWritableForStyle( opts.data_style ) and ( opts.delete_tags or opts.save_tags or opts.rename_file ):
if not ca.isWritable( ) and ( opts.delete_tags or opts.copy_tags or opts.save_tags or opts.rename_file ):
print "This archive is not writable for that tag type"
print >> sys.stderr,"This archive is not writable for that tag type"
return
has = [ False, False, False ]
@ -314,7 +345,7 @@ def process_file_cli( filename, opts, settings, match_results ):
return
if batch_mode:
print u"Processing {0}: ".format(filename)
print u"Processing {0}...".format(filename)
md = create_local_metadata( opts, ca, has[ opts.data_style ] )
@ -325,12 +356,14 @@ def process_file_cli( filename, opts, settings, match_results ):
try:
cv_md = ComicVineTalker().fetchIssueDataByIssueID( opts.issue_id, settings )
except ComicVineTalkerException:
print "Network error while getting issue details. Save aborted"
return None
print >> sys.stderr,"Network error while getting issue details. Save aborted"
match_results.fetchDataFailures.append(filename)
return
if cv_md is None:
print "No match for ID {0} was found.".format(opts.issue_id)
return None
print >> sys.stderr,"No match for ID {0} was found.".format(opts.issue_id)
match_results.noMatches.append(filename)
return
if settings.apply_cbl_transform_on_cv_import:
cv_md = CBLTransformer( cv_md, settings ).apply()
@ -338,7 +371,8 @@ def process_file_cli( filename, opts, settings, match_results ):
ii = IssueIdentifier( ca, settings )
if md is None or md.isEmpty:
print "No metadata given to search online with!"
print >> sys.stderr,"No metadata given to search online with!"
match_results.noMatches.append(filename)
return
def myoutput( text ):
@ -374,15 +408,20 @@ def process_file_cli( filename, opts, settings, match_results ):
choices = True
if choices:
print "Online search: Multiple matches. Save aborted"
match_results.multipleMatches.append(MultipleMatch(filename,matches))
return
if low_confidence:
print >> sys.stderr,"Online search: Multiple low confidence matches. Save aborted"
match_results.lowConfidenceMatches.append(MultipleMatch(filename,matches))
return
else:
print >> sys.stderr,"Online search: Multiple good matches. Save aborted"
match_results.multipleMatches.append(MultipleMatch(filename,matches))
return
if low_confidence and opts.abortOnLowConfidence:
print "Online search: Low confidence match. Save aborted"
match_results.noMatches.append(filename)
print >> sys.stderr,"Online search: Low confidence match. Save aborted"
match_results.lowConfidenceMatches.append(MultipleMatch(filename,matches))
return
if not found_match:
print "Online search: No match found. Save aborted"
print >> sys.stderr,"Online search: No match found. Save aborted"
match_results.noMatches.append(filename)
return
@ -392,6 +431,7 @@ def process_file_cli( filename, opts, settings, match_results ):
# now get the particular issue data
cv_md = actual_issue_data_fetch(matches[0], settings)
if cv_md is None:
match_results.fetchDataFailures.append(filename)
return
md.overlay( cv_md )
@ -416,7 +456,7 @@ def process_file_cli( filename, opts, settings, match_results ):
md = create_local_metadata( opts, ca, use_tags )
if md.series is None:
print msg_hdr + "Can't rename without series name"
print >> sys.stderr, msg_hdr + "Can't rename without series name"
return
new_ext = None # default
@ -434,7 +474,7 @@ def process_file_cli( filename, opts, settings, match_results ):
new_name = renamer.determineName( filename, ext=new_ext )
if new_name == os.path.basename(filename):
print msg_hdr + "Filename is already good!"
print >> sys.stderr, msg_hdr + "Filename is already good!"
return
folder = os.path.dirname( os.path.abspath( filename ) )
@ -449,6 +489,59 @@ def process_file_cli( filename, opts, settings, match_results ):
print u"renamed '{0}' -> '{1}' {2}".format(os.path.basename(filename), new_name, suffix)
elif opts.export_to_zip:
msg_hdr = ""
if batch_mode:
msg_hdr = u"{0}: ".format(filename)
if not ca.isRar():
print >> sys.stderr, msg_hdr + "Archive is not a RAR."
return
rar_file = os.path.abspath( os.path.abspath( filename ) )
new_file = os.path.splitext(rar_file)[0] + ".cbz"
if opts.abort_export_on_conflict and os.path.lexists( new_file ):
print msg_hdr + "{0} already exists in the that folder.".format(os.path.split(new_file)[1])
return
new_file = utils.unique_file( os.path.join( new_file ) )
delete_success = False
export_success = False
if not opts.dryrun:
if ca.exportAsZip( new_file ):
export_success = True
if opts.delete_rar_after_export:
try:
os.unlink( rar_file )
except:
print >> sys.stderr, msg_hdr + "Error deleting original RAR after export"
delete_success = False
else:
delete_success = True
else:
# last export failed, so remove the zip, if it exists
if os.path.lexists( new_file ):
os.remove( new_file )
else:
msg = msg_hdr + u"Dry-run: Would try to create {0}".format(os.path.split(new_file)[1])
if opts.delete_rar_after_export:
msg += u" and delete orginal."
print msg
return
msg = msg_hdr
if export_success:
msg += u"Archive exported successfully to: {0}".format( os.path.split(new_file)[1] )
if opts.delete_rar_after_export and delete_success:
msg += u" (Original deleted) "
else:
msg += u"Archive failed to export!"
print msg
#-----------------------------
def main():
@ -459,6 +552,7 @@ def main():
else:
preferred_encoding = locale.getpreferredencoding()
sys.stdout = codecs.getwriter(preferred_encoding)(sys.stdout)
sys.stderr = codecs.getwriter(preferred_encoding)(sys.stderr)
opts = Options()
opts.parseCmdLineArgs()
@ -471,7 +565,7 @@ def main():
if not qt_available and not opts.no_gui:
opts.no_gui = True
print "QT is not available."
print >> sys.stderr, "QT is not available."
if opts.no_gui:
cli_mode( opts, settings )

View File

@ -27,6 +27,7 @@ import datetime
import ctversion
from settings import ComicTaggerSettings
import utils
class ComicVineCacher:
@ -97,6 +98,13 @@ class ComicVineCacher:
"PRIMARY KEY (id) )"
)
cur.execute("CREATE TABLE AltCovers(" +
"issue_id INT," +
"url_list TEXT," +
"timestamp DATE DEFAULT (datetime('now','localtime')), " +
"PRIMARY KEY (issue_id) )"
)
cur.execute("CREATE TABLE Issues(" +
"id INT," +
"volume_id INT," +
@ -108,6 +116,7 @@ class ComicVineCacher:
"thumb_image_hash TEXT," +
"publish_month TEXT," +
"publish_year TEXT," +
"site_detail_url TEXT," +
"timestamp DATE DEFAULT (datetime('now','localtime')), " +
"PRIMARY KEY (id ) )"
)
@ -149,7 +158,7 @@ class ComicVineCacher:
url,
record['description'])
)
def get_search_results( self, search_term ):
results = list()
@ -184,7 +193,52 @@ class ComicVineCacher:
return results
def add_alt_covers( self, issue_id, url_list ):
con = lite.connect( self.db_file )
with con:
con.text_factory = unicode
cur = con.cursor()
# remove all previous entries with this search term
cur.execute("DELETE FROM AltCovers WHERE issue_id = ?", [ issue_id ])
url_list_str = utils.listToString(url_list)
# now add in new record
cur.execute("INSERT INTO AltCovers " +
"(issue_id, url_list ) " +
"VALUES( ?, ? )" ,
( issue_id,
url_list_str)
)
def get_alt_covers( self, issue_id ):
con = lite.connect( self.db_file )
with con:
cur = con.cursor()
con.text_factory = unicode
# purge stale issue info - probably issue data won't change much....
a_month_ago = datetime.datetime.today()-datetime.timedelta(days=30)
cur.execute( "DELETE FROM AltCovers WHERE timestamp < ?", [ str(a_month_ago) ] )
cur.execute("SELECT url_list FROM AltCovers WHERE issue_id=?", [ issue_id ])
row = cur.fetchone()
if row is None :
return None
else:
url_list_str = row[0]
if len(url_list_str) == 0:
return []
raw_list = url_list_str.split(",")
url_list = []
for item in raw_list:
url_list.append( str(item).strip())
return url_list
def add_volume_info( self, cv_volume_record ):
con = lite.connect( self.db_file )
@ -275,7 +329,7 @@ class ComicVineCacher:
return result
def add_issue_select_details( self, issue_id, image_url, thumb_image_url, publish_month, publish_year ):
def add_issue_select_details( self, issue_id, image_url, thumb_image_url, publish_month, publish_year, site_detail_url ):
con = lite.connect( self.db_file )
@ -289,6 +343,7 @@ class ComicVineCacher:
"thumb_image_url": thumb_image_url,
"publish_month": publish_month,
"publish_year": publish_year,
"site_detail_url": site_detail_url,
"timestamp": timestamp
}
self.upsert( cur, "issues" , "id", issue_id, data)
@ -302,13 +357,25 @@ class ComicVineCacher:
cur = con.cursor()
con.text_factory = unicode
cur.execute("SELECT image_url,thumb_image_url,publish_month,publish_year FROM Issues WHERE id=?", [ issue_id ])
cur.execute("SELECT image_url,thumb_image_url,publish_month,publish_year,site_detail_url FROM Issues WHERE id=?", [ issue_id ])
row = cur.fetchone()
details = dict()
if row[0] is None :
return None, None, None, None
details['image_url'] = None
details['thumb_image_url'] = None
details['publish_month'] = None
details['publish_year'] = None
details['site_detail_url'] = None
else:
return row[0],row[1],row[2],row[3]
details['image_url'] = row[0]
details['thumb_image_url'] = row[1]
details['publish_month'] = row[2]
details['publish_year'] = row[3]
details['site_detail_url'] = row[4]
return details
def upsert( self, cur, tablename, pkname, pkval, data):

View File

@ -27,6 +27,7 @@ import re
import datetime
import ctversion
import sys
from bs4 import BeautifulSoup
try:
from PyQt4.QtNetwork import QNetworkAccessManager, QNetworkRequest
@ -67,8 +68,9 @@ class ComicVineTalker(QObject):
def writeLog( self , text ):
if self.log_func is None:
sys.stdout.write(text.encode( errors='replace') )
sys.stdout.flush()
#sys.stdout.write(text.encode( errors='replace') )
#sys.stdout.flush()
print >> sys.stderr, text
else:
self.log_func( text )
@ -182,7 +184,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' ] ))
print >> sys.stderr, "Comic Vine query failed with error: [{0}]. ".format( cv_response[ 'error' ] )
return None
volume_results = cv_response['results']
@ -200,7 +202,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
@ -210,7 +212,7 @@ class ComicVineTalker(QObject):
content = self.getUrlContent(issue_url)
cv_response = json.loads(content)
if cv_response[ 'status_code' ] != 1:
print ( "Comic Vine query failed with error: [{0}]. ".format( cv_response[ 'error' ] ))
print >> sys.stderr, "Comic Vine query failed with error: [{0}]. ".format( cv_response[ 'error' ] )
return None
issue_results = cv_response['results']
@ -226,7 +228,7 @@ class ComicVineTalker(QObject):
content = self.getUrlContent(issue_url)
cv_response = json.loads(content)
if cv_response[ 'status_code' ] != 1:
print ( "Comic Vine query failed with error: [{0}]. ".format( cv_response[ 'error' ] ))
print >> sys.stderr, "Comic Vine query failed with error: [{0}]. ".format( cv_response[ 'error' ] )
return None
issue_results = cv_response['results']
@ -323,36 +325,55 @@ class ComicVineTalker(QObject):
return newstring
def fetchIssueDate( self, issue_id ):
image_url, thumb_url, month,year = self.fetchIssueSelectDetails( issue_id )
return month, year
details = self.fetchIssueSelectDetails( issue_id )
return details['publish_month'], details['publish_year']
def fetchIssueCoverURLs( self, issue_id ):
image_url, thumb_url, month,year = self.fetchIssueSelectDetails( issue_id )
return image_url, thumb_url
details = self.fetchIssueSelectDetails( issue_id )
return details['image_url'], details['thumb_image_url']
def fetchIssuePageURL( self, issue_id ):
details = self.fetchIssueSelectDetails( issue_id )
return details['site_detail_url']
def fetchIssueSelectDetails( self, issue_id ):
cached_image_url,cached_thumb_url,cached_month,cached_year = self.fetchCachedIssueSelectDetails( issue_id )
if cached_image_url is not None:
return cached_image_url,cached_thumb_url, cached_month, cached_year
#cached_image_url,cached_thumb_url,cached_month,cached_year = self.fetchCachedIssueSelectDetails( issue_id )
cached_details = self.fetchCachedIssueSelectDetails( issue_id )
if cached_details['image_url'] is not None:
return cached_details
issue_url = "http://api.comicvine.com/issue/" + str(issue_id) + "/?api_key=" + self.api_key + "&format=json&field_list=image,publish_month,publish_year"
issue_url = "http://api.comicvine.com/issue/" + str(issue_id) + "/?api_key=" + self.api_key + "&format=json&field_list=image,publish_month,publish_year,site_detail_url"
content = self.getUrlContent(issue_url)
details = dict()
details['image_url'] = None
details['thumb_image_url'] = None
details['publish_month'] = None
details['publish_year'] = None
details['site_detail_url'] = None
cv_response = json.loads(content)
if cv_response[ 'status_code' ] != 1:
print ( "Comic Vine query failed with error: [{0}]. ".format( cv_response[ 'error' ] ))
return None, None,None,None
print >> sys.stderr, "Comic Vine query failed with error: [{0}]. ".format( cv_response[ 'error' ] )
return details
image_url = cv_response['results']['image']['super_url']
thumb_url = cv_response['results']['image']['thumb_url']
year = cv_response['results']['publish_year']
month = cv_response['results']['publish_month']
details['image_url'] = cv_response['results']['image']['super_url']
details['thumb_image_url'] = cv_response['results']['image']['thumb_url']
details['publish_year'] = cv_response['results']['publish_year']
details['publish_month'] = cv_response['results']['publish_month']
details['site_detail_url'] = cv_response['results']['site_detail_url']
if image_url is not None:
self.cacheIssueSelectDetails( issue_id, image_url,thumb_url, month, year )
return image_url,thumb_url,month,year
if details['image_url'] is not None:
self.cacheIssueSelectDetails( issue_id,
details['image_url'],
details['thumb_image_url'],
details['publish_month'],
details['publish_year'],
details['site_detail_url'] )
#print details['site_detail_url']
return details
def fetchCachedIssueSelectDetails( self, issue_id ):
@ -361,23 +382,71 @@ class ComicVineTalker(QObject):
cvc = ComicVineCacher( )
return cvc.get_issue_select_details( issue_id )
def cacheIssueSelectDetails( self, issue_id, image_url, thumb_url, month, year ):
def cacheIssueSelectDetails( self, issue_id, image_url, thumb_url, month, year, page_url ):
cvc = ComicVineCacher( )
cvc.add_issue_select_details( issue_id, image_url, thumb_url, month, year )
cvc.add_issue_select_details( issue_id, image_url, thumb_url, month, year, page_url )
def fetchAlternateCoverURLs(self, issue_id):
url_list = self.fetchCachedAlternateCoverURLs( issue_id )
if url_list is not None:
return url_list
issue_page_url = self.fetchIssuePageURL( issue_id )
#---------------------------------------------------------------------------
# scrape the CV issue page URL to get the alternate cover URLs
resp = urllib2.urlopen( issue_page_url )
content = resp.read()
alt_cover_url_list = self.parseOutAltCoverUrls( content)
# cache this alt cover URL list
self.cacheAlternateCoverURLs( issue_id, alt_cover_url_list )
return alt_cover_url_list
def parseOutAltCoverUrls( self, page_html ):
soup = BeautifulSoup( page_html )
alt_cover_url_list = []
# Using knowledge of the layout of the ComicVine issue page here:
# look for the divs that are in the classes 'content-pod' and 'alt-cover'
div_list = soup.find_all( 'div')
for d in div_list:
if d.has_key('class'):
c = d['class']
if 'content-pod' in c and 'alt-cover' in c:
alt_cover_url_list.append( d.img['src'] )
return alt_cover_url_list
def fetchCachedAlternateCoverURLs( self, issue_id ):
# before we search online, look in our cache, since we might already
# have this info
cvc = ComicVineCacher( )
url_list = cvc.get_alt_covers( issue_id )
if url_list is not None:
return url_list
else:
return None
def cacheAlternateCoverURLs( self, issue_id, url_list ):
cvc = ComicVineCacher( )
cvc.add_alt_covers( issue_id, url_list )
#---------------------------------------------------------------------------
urlFetchComplete = pyqtSignal( str , str, int)
def asyncFetchIssueCoverURLs( self, issue_id ):
self.issue_id = issue_id
cached_image_url,cached_thumb_url,month,year = self.fetchCachedIssueSelectDetails( issue_id )
if cached_image_url is not None:
self.urlFetchComplete.emit( cached_image_url,cached_thumb_url, self.issue_id )
details = self.fetchCachedIssueSelectDetails( issue_id )
if details['image_url'] is not None:
self.urlFetchComplete.emit( details['image_url'],details['thumb_image_url'], self.issue_id )
return
issue_url = "http://api.comicvine.com/issue/" + str(issue_id) + "/?api_key=" + self.api_key + "&format=json&field_list=image,publish_month,publish_year"
issue_url = "http://api.comicvine.com/issue/" + str(issue_id) + "/?api_key=" + self.api_key + "&format=json&field_list=image,publish_month,publish_year,site_detail_url"
self.nam = QNetworkAccessManager()
self.nam.finished.connect( self.asyncFetchIssueCoverURLComplete )
self.nam.get(QNetworkRequest(QUrl(issue_url)))
@ -388,16 +457,41 @@ class ComicVineTalker(QObject):
data = reply.readAll()
cv_response = json.loads(str(data))
if cv_response[ 'status_code' ] != 1:
print ( "Comic Vine query failed with error: [{0}]. ".format( cv_response[ 'error' ] ))
print >> sys.stderr, "Comic Vine query failed with error: [{0}]. ".format( cv_response[ 'error' ] )
return
image_url = cv_response['results']['image']['super_url']
thumb_url = cv_response['results']['image']['thumb_url']
year = cv_response['results']['publish_year']
month = cv_response['results']['publish_month']
page_url = cv_response['results']['site_detail_url']
self.cacheIssueSelectDetails( self.issue_id, image_url, thumb_url, month, year )
self.cacheIssueSelectDetails( self.issue_id, image_url, thumb_url, month, year, page_url )
self.urlFetchComplete.emit( image_url, thumb_url, self.issue_id )
altUrlListFetchComplete = pyqtSignal( list, int)
def asyncFetchAlternateCoverURLs( self, issue_id, issue_page_url ):
# This async version requires the issue page url to be provided!
self.issue_id = issue_id
url_list = self.fetchCachedAlternateCoverURLs( issue_id )
if url_list is not None:
self.altUrlListFetchComplete.emit( url_list, int(self.issue_id) )
return
self.nam = QNetworkAccessManager()
self.nam.finished.connect( self.asyncFetchAlternateCoverURLsComplete )
self.nam.get(QNetworkRequest(QUrl(str(issue_page_url))))
def asyncFetchAlternateCoverURLsComplete( self, reply ):
# read in the response
html = str(reply.readAll())
alt_cover_url_list = self.parseOutAltCoverUrls( html )
# cache this alt cover URL list
self.cacheAlternateCoverURLs( self.issue_id, alt_cover_url_list )
self.altUrlListFetchComplete.emit( alt_cover_url_list, int(self.issue_id) )

290
coverimagewidget.py Normal file
View File

@ -0,0 +1,290 @@
"""
A PyQt4 widget display cover images from either local archive, or from ComicVine
"""
"""
Copyright 2012 Anthony Beville
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
"""
import os
from PyQt4.QtCore import *
from PyQt4.QtGui import *
from PyQt4 import uic
from settings import ComicTaggerSettings
from genericmetadata import GenericMetadata, PageType
from options import MetaDataStyle
from comicvinetalker import ComicVineTalker, ComicVineTalkerException
from imagefetcher import ImageFetcher
from pageloader import PageLoader
from imagepopup import ImagePopup
import utils
# helper func to allow a label to be clickable
def clickable(widget):
class Filter(QObject):
dblclicked = pyqtSignal()
def eventFilter(self, obj, event):
if obj == widget:
if event.type() == QEvent.MouseButtonDblClick:
self.dblclicked.emit()
return True
return False
filter = Filter(widget)
widget.installEventFilter(filter)
return filter.dblclicked
class CoverImageWidget(QWidget):
ArchiveMode = 0
AltCoverMode = 1
URLMode = 1
def __init__(self, parent, mode ):
super(CoverImageWidget, self).__init__(parent)
uic.loadUi(os.path.join(ComicTaggerSettings.baseDir(), 'coverimagewidget.ui' ), self )
utils.reduceWidgetFontSize( self.label )
self.mode = mode
self.comicVine = ComicVineTalker()
self.page_loader = None
self.showControls = True
self.btnLeft.setIcon(QIcon(os.path.join(ComicTaggerSettings.baseDir(), 'graphics/left.png' )))
self.btnRight.setIcon(QIcon(os.path.join(ComicTaggerSettings.baseDir(), 'graphics/right.png' )))
self.btnLeft.clicked.connect( self.decrementImage )
self.btnRight.clicked.connect( self.incrementImage )
self.resetWidget()
clickable(self.lblImage).connect(self.showPopup)
self.updateContent()
def resetWidget(self):
self.comic_archive = None
self.issue_id = None
self.comicVine = None
self.cover_fetcher = None
self.url_list = []
if self.page_loader is not None:
self.page_loader.abandoned = True
self.page_loader = None
self.imageIndex = -1
self.imageCount = 1
def clear( self ):
self.resetWidget()
self.updateContent()
def incrementImage( self ):
self.imageIndex += 1
if self.imageIndex == self.imageCount:
self.imageIndex = 0
self.updateContent()
def decrementImage( self ):
self.imageIndex -= 1
if self.imageIndex == -1:
self.imageIndex = self.imageCount -1
self.updateContent()
def setArchive( self, ca, page=0 ):
if self.mode == CoverImageWidget.ArchiveMode:
self.resetWidget()
self.comic_archive = ca
self.imageIndex = page
self.imageCount = ca.getNumberOfPages()
self.updateContent()
def setURL( self, url ):
if self.mode == CoverImageWidget.URLMode:
self.resetWidget()
self.updateContent()
self.url_list = [ url ]
self.imageIndex = 0
self.imageCount = 1
self.updateContent()
def setIssueID( self, issue_id ):
if self.mode == CoverImageWidget.AltCoverMode:
self.resetWidget()
self.updateContent()
self.issue_id = issue_id
self.comicVine = ComicVineTalker()
self.comicVine.urlFetchComplete.connect( self.primaryUrlFetchComplete )
self.comicVine.asyncFetchIssueCoverURLs( int(self.issue_id) )
def primaryUrlFetchComplete( self, primary_url, thumb_url, issue_id ):
self.url_list.append(str(primary_url))
self.imageIndex = 0
self.imageCount = len(self.url_list)
self.updateContent()
#defer the alt cover search
QTimer.singleShot(1, self.startAltCoverSearch)
def startAltCoverSearch( self ):
# now we need to get the list of alt cover URLs
self.label.setText("Searching for alt. covers...")
# page URL should already be cached, so no need to defer
self.comicVine = ComicVineTalker()
issue_page_url = self.comicVine.fetchIssuePageURL( self.issue_id )
self.comicVine.altUrlListFetchComplete.connect( self.altCoverUrlListFetchComplete )
self.comicVine.asyncFetchAlternateCoverURLs( int(self.issue_id), issue_page_url)
def altCoverUrlListFetchComplete( self, url_list, issue_id ):
if len(url_list) > 0:
self.url_list.extend(url_list)
self.imageCount = len(self.url_list)
self.updateControls()
def setPage( self, pagenum ):
if self.mode == CoverImageWidget.ArchiveMode:
self.imageIndex = pagenum
self.updateContent()
def updateContent( self ):
self.updateImage()
self.updateControls()
def updateImage( self ):
if self.imageIndex == -1:
self.loadDefault()
elif self.mode in [ CoverImageWidget.AltCoverMode, CoverImageWidget.URLMode ]:
self.loadURL()
else:
self.loadPage()
def updateControls( self ):
if not self.showControls:
self.btnLeft.hide()
self.btnRight.hide()
self.label.hide()
return
if self.imageIndex == -1 or self.imageCount == 1:
self.btnLeft.setEnabled(False)
self.btnRight.setEnabled(False)
self.btnLeft.hide()
self.btnRight.hide()
else:
self.btnLeft.setEnabled(True)
self.btnRight.setEnabled(True)
self.btnLeft.show()
self.btnRight.show()
if self.imageIndex == -1 or self.imageCount == 1:
self.label.setText("")
elif self.mode == CoverImageWidget.AltCoverMode:
self.label.setText("Cover {0} ( of {1} )".format(self.imageIndex+1, self.imageCount))
else:
self.label.setText("Page {0} ( of {1} )".format(self.imageIndex+1, self.imageCount))
def loadURL( self ):
self.loadDefault()
self.cover_fetcher = ImageFetcher( )
self.cover_fetcher.fetchComplete.connect(self.coverRemoteFetchComplete)
self.cover_fetcher.fetch( self.url_list[self.imageIndex] )
#print "ATB cover fetch started...."
# called when the image is done loading from internet
def coverRemoteFetchComplete( self, image_data, issue_id ):
img = QImage()
img.loadFromData( image_data )
self.current_pixmap = QPixmap(img)
self.setDisplayPixmap( 0, 0)
#print "ATB cover fetch complete!"
def loadPage( self ):
if self.comic_archive is not None:
if self.page_loader is not None:
self.page_loader.abandoned = True
self.page_loader = PageLoader( self.comic_archive, self.imageIndex )
self.page_loader.loadComplete.connect( self.pageLoadComplete )
self.page_loader.start()
def pageLoadComplete( self, img ):
self.current_pixmap = QPixmap(img)
self.setDisplayPixmap( 0, 0)
self.page_loader = None
def loadDefault( self ):
self.current_pixmap = QPixmap(os.path.join(ComicTaggerSettings.baseDir(), 'graphics/nocover.png' ))
#print "loadDefault called"
self.setDisplayPixmap( 0, 0)
def resizeEvent( self, resize_event ):
if self.current_pixmap is not None:
delta_w = resize_event.size().width() - resize_event.oldSize().width()
delta_h = resize_event.size().height() - resize_event.oldSize().height()
#print "ATB resizeEvent deltas", resize_event.size().width(), resize_event.size().height()
self.setDisplayPixmap( delta_w , delta_h )
def setDisplayPixmap( self, delta_w , delta_h ):
# the deltas let us know what the new width and height of the label will be
"""
new_h = self.frame.height() + delta_h
new_w = self.frame.width() + delta_w
print "ATB setDisplayPixmap deltas", delta_w , delta_h
print "ATB self.frame", self.frame.width(), self.frame.height()
print "ATB self.", self.width(), self.height()
frame_w = new_w
frame_h = new_h
"""
new_h = self.frame.height()
new_w = self.frame.width()
frame_w = self.frame.width()
frame_h = self.frame.height()
new_h -= 4
new_w -= 4
if new_h < 0:
new_h = 0;
if new_w < 0:
new_w = 0;
#print "ATB setDisplayPixmap deltas", delta_w , delta_h
#print "ATB self.frame", frame_w, frame_h
#print "ATB new size", new_w, new_h
# scale the pixmap to fit in the frame
scaled_pixmap = self.current_pixmap.scaled(new_w, new_h, Qt.KeepAspectRatio)
self.lblImage.setPixmap( scaled_pixmap )
# move and resize the label to be centered in the fame
img_w = scaled_pixmap.width()
img_h = scaled_pixmap.height()
self.lblImage.resize( img_w, img_h )
self.lblImage.move( (frame_w - img_w)/2, (frame_h - img_h)/2 )
def showPopup( self ):
self.popup = ImagePopup(self, self.current_pixmap)

123
coverimagewidget.ui Normal file
View File

@ -0,0 +1,123 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>coverImageWidget</class>
<widget class="QWidget" name="coverImageWidget">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>292</width>
<height>353</height>
</rect>
</property>
<property name="windowTitle">
<string>Form</string>
</property>
<layout class="QGridLayout" name="gridLayout">
<property name="horizontalSpacing">
<number>0</number>
</property>
<property name="verticalSpacing">
<number>4</number>
</property>
<property name="margin">
<number>0</number>
</property>
<item row="1" column="0">
<layout class="QHBoxLayout" name="horizontalLayout">
<property name="spacing">
<number>2</number>
</property>
<item>
<widget class="QPushButton" name="btnLeft">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="maximumSize">
<size>
<width>30</width>
<height>16777215</height>
</size>
</property>
<property name="text">
<string/>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label">
<property name="sizePolicy">
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string/>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="btnRight">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="maximumSize">
<size>
<width>30</width>
<height>16777215</height>
</size>
</property>
<property name="text">
<string/>
</property>
</widget>
</item>
</layout>
</item>
<item row="0" column="0">
<widget class="QFrame" name="frame">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="frameShape">
<enum>QFrame::NoFrame</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Raised</enum>
</property>
<widget class="QLabel" name="lblImage">
<property name="geometry">
<rect>
<x>60</x>
<y>50</y>
<width>91</width>
<height>61</height>
</rect>
</property>
<property name="toolTip">
<string>Double-click to expand</string>
</property>
<property name="text">
<string>TextLabel</string>
</property>
</widget>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>

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.1.0-beta"

View File

@ -39,6 +39,9 @@ class ExportWindow(QtGui.QDialog):
uic.loadUi(os.path.join(ComicTaggerSettings.baseDir(), 'exportwindow.ui' ), self)
self.label.setText( msg )
self.setWindowFlags(self.windowFlags() &
~QtCore.Qt.WindowContextHelpButtonHint )
self.settings = settings
self.cbxDeleteOriginal.setCheckState( QtCore.Qt.Unchecked )

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 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:
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.
@ -195,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

@ -95,6 +95,19 @@ class FileRenamer:
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%')
new_name = self.replaceToken( new_name, md.genre, '%genre%')
new_name = self.replaceToken( new_name, md.language, '%language_code%')
new_name = self.replaceToken( new_name, md.criticalRating , '%criticalrating%')
new_name = self.replaceToken( new_name, md.alternateSeries, '%alternateseries%')
new_name = self.replaceToken( new_name, md.alternateNumber, '%alternatenumber%')
new_name = self.replaceToken( new_name, md.alternateCount, '%alternatecount%')
new_name = self.replaceToken( new_name, md.imprint, '%imprint%')
new_name = self.replaceToken( new_name, md.format, '%format%')
new_name = self.replaceToken( new_name, md.maturityRating, '%maturityrating%')
new_name = self.replaceToken( new_name, md.storyArc, '%storyarc%')
new_name = self.replaceToken( new_name, md.seriesGroup, '%seriesgroup%')
new_name = self.replaceToken( new_name, md.scanInfo, '%scaninfo%')
if self.smart_cleanup:
@ -118,6 +131,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 *
@ -30,6 +31,7 @@ from settings import ComicTaggerSettings
from comicarchive import ComicArchive
from genericmetadata import GenericMetadata, PageType
from options import MetaDataStyle
import utils
class FileTableWidget( QTableWidget ):
@ -72,11 +74,9 @@ class FileSelectionList(QWidget):
uic.loadUi(os.path.join(ComicTaggerSettings.baseDir(), 'fileselectionlist.ui' ), self)
self.settings = settings
#self.twList = FileTableWidget( self )
#gridlayout = QGridLayout( self )
#gridlayout.addWidget( self.twList )
utils.reduceWidgetFontSize( self.twList )
#self.twList.itemSelectionChanged.connect( self.itemSelectionChangedCB )
self.twList.currentItemChanged.connect( self.currentItemChangedCB )
self.currentItem = None
@ -162,6 +162,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:
@ -173,7 +178,8 @@ class FileSelectionList(QWidget):
progdialog = QProgressDialog("", "Cancel", 0, len(filelist), self)
progdialog.setWindowTitle( "Adding Files" )
progdialog.setWindowModality(Qt.WindowModal)
#progdialog.setWindowModality(Qt.WindowModal)
progdialog.setWindowModality(Qt.ApplicationModal)
progdialog.show()
firstAdded = None
@ -184,6 +190,7 @@ class FileSelectionList(QWidget):
break
progdialog.setValue(idx)
progdialog.setLabelText(f)
utils.centerWindowOnParent( progdialog )
QCoreApplication.processEvents()
row = self.addPathItem( f )
if firstAdded is None and row is not None:

BIN
graphics/left.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 46 KiB

BIN
graphics/popup_bg.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 362 B

BIN
graphics/right.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

86
imagepopup.py Normal file
View File

@ -0,0 +1,86 @@
"""
A PyQT4 widget to display a popup image
"""
"""
Copyright 2012 Anthony Beville
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
"""
import sys
from PyQt4 import QtCore, QtGui, uic
import os
from settings import ComicTaggerSettings
class ImagePopup(QtGui.QDialog):
def __init__(self, parent, image_pixmap):
super(ImagePopup, self).__init__(parent)
uic.loadUi(os.path.join(ComicTaggerSettings.baseDir(), 'imagepopup.ui' ), self)
QtGui.QApplication.setOverrideCursor(QtGui.QCursor(QtCore.Qt.WaitCursor))
#self.setWindowModality(QtCore.Qt.WindowModal)
self.setWindowFlags(QtCore.Qt.Popup)
self.setWindowState(QtCore.Qt.WindowFullScreen)
self.imagePixmap = image_pixmap
screen_size = QtGui.QDesktopWidget().screenGeometry()
self.resize(screen_size.width(), screen_size.height())
self.move( 0, 0)
# This is a total hack. Uses a snapshot of the desktop, and overlays a
# translucent screen over it. Probably can do it better by setting opacity of a
# widget
self.desktopBg = QtGui.QPixmap.grabWindow(QtGui.QApplication.desktop ().winId(),
0,0, screen_size.width(), screen_size.height())
bg = QtGui.QPixmap(os.path.join(ComicTaggerSettings.baseDir(), 'graphics/popup_bg.png' ))
self.clientBgPixmap = bg.scaled(screen_size.width(), screen_size.height())
self.setMask(self.clientBgPixmap.mask())
self.applyImagePixmap()
self.showFullScreen()
self.raise_( )
QtGui.QApplication.restoreOverrideCursor()
def paintEvent (self, event):
self.painter = QtGui.QPainter(self)
self.painter.setRenderHint(QtGui.QPainter.Antialiasing)
self.painter.drawPixmap(0, 0, self.desktopBg)
self.painter.drawPixmap(0, 0, self.clientBgPixmap)
self.painter.end()
def applyImagePixmap( self ):
win_h = self.height()
win_w = self.width()
if self.imagePixmap.width() > win_w or self.imagePixmap.height() > win_h:
# scale the pixmap to fit in the frame
display_pixmap = self.imagePixmap.scaled(win_w, win_h, QtCore.Qt.KeepAspectRatio)
self.lblImage.setPixmap( display_pixmap )
else:
display_pixmap = self.imagePixmap
self.lblImage.setPixmap( display_pixmap )
# move and resize the label to be centered in the fame
img_w = display_pixmap.width()
img_h = display_pixmap.height()
self.lblImage.resize( img_w, img_h )
self.lblImage.move( (win_w - img_w)/2, (win_h - img_h)/2 )
def mousePressEvent( self , event):
self.close()

38
imagepopup.ui Normal file
View File

@ -0,0 +1,38 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>Form</class>
<widget class="QDialog" name="Form">
<property name="windowModality">
<enum>Qt::ApplicationModal</enum>
</property>
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>817</width>
<height>455</height>
</rect>
</property>
<property name="windowTitle">
<string>Form</string>
</property>
<property name="windowOpacity">
<double>1.000000000000000</double>
</property>
<widget class="QLabel" name="lblImage">
<property name="geometry">
<rect>
<x>300</x>
<y>120</y>
<width>66</width>
<height>17</height>
</rect>
</property>
<property name="text">
<string/>
</property>
</widget>
</widget>
<resources/>
<connections/>
</ui>

View File

@ -38,6 +38,11 @@ from issuestring import IssueString
import utils
class IssueIdentifierNetworkError(Exception):
pass
class IssueIdentifierCancelled(Exception):
pass
class IssueIdentifier:
ResultNoMatches = 0
@ -54,7 +59,10 @@ class IssueIdentifier:
self.onlyUseAdditionalMetaData = False
# a decent hamming score, good enough to call it a match
self.min_score_thresh = 20
self.min_score_thresh = 16
# for alternate covers, be more stringent, since we're a bit more scattershot in comparisons
self.min_alternate_score_thresh = 12
# the min distance a hamming score must be to separate itself from closest neighbor
self.min_score_distance = 4
@ -66,7 +74,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 +82,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 +93,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
@ -216,6 +223,104 @@ class IssueIdentifier:
if newline:
self.output_function("\n")
def getIssueCoverMatchScore( self, comicVine, issue_id, localCoverHashList, useRemoteAlternates = False , use_log=True):
# localHashes is a list of pre-calculated hashs.
# useRemoteAlternates - indicates to use alternate covers from CV
# first get the primary cover image
primary_img_url, primary_thumb_url = comicVine.fetchIssueCoverURLs( issue_id )
try:
url_image_data = ImageFetcher().fetch(primary_thumb_url, blocking=True)
except ImageFetcherException:
self.log_msg( "Network issue while fetching cover image from ComicVine. Aborting...")
raise IssueIdentifierNetworkError
if self.cancel == True:
raise IssueIdentifierCancelled
# alert the GUI, if needed
if self.coverUrlCallback is not None:
self.coverUrlCallback( url_image_data )
remote_cover_list = []
item = dict()
item['url'] = primary_img_url
item['hash'] = self.calculateHash( url_image_data )
remote_cover_list.append( item )
if self.cancel == True:
raise IssueIdentifierCancelled
if useRemoteAlternates:
alt_img_url_list = comicVine.fetchAlternateCoverURLs( issue_id )
for alt_url in alt_img_url_list:
try:
alt_url_image_data = ImageFetcher().fetch(alt_url, blocking=True)
except ImageFetcherException:
self.log_msg( "Network issue while fetching alt. cover image from ComicVine. Aborting...")
raise IssueIdentifierNetworkError
if self.cancel == True:
raise IssueIdentifierCancelled
# alert the GUI, if needed
if self.coverUrlCallback is not None:
self.coverUrlCallback( alt_url_image_data )
item = dict()
item['url'] = alt_url
item['hash'] = self.calculateHash( alt_url_image_data )
remote_cover_list.append( item )
if self.cancel == True:
raise IssueIdentifierCancelled
if use_log and useRemoteAlternates:
self.log_msg( "[{0} alt. covers]".format(len(remote_cover_list)-1), False )
if use_log:
self.log_msg( "[ ", False )
score_list = []
done = False
for local_cover_hash in localCoverHashList:
for remote_cover_item in remote_cover_list:
score = ImageHasher.hamming_distance(local_cover_hash, remote_cover_item['hash'] )
score_item = dict()
score_item['score'] = score
score_item['url'] = remote_cover_item['url']
score_item['hash'] = remote_cover_item['hash']
score_list.append( score_item )
if use_log:
self.log_msg( "{0} ".format(score), False )
if score <= self.strong_score_thresh:
# such a good score, we can quit now, since for sure we have a winner
done = True
break
if done:
break
if use_log:
self.log_msg( " ]", False )
best_score_item = min(score_list, key=lambda x:x['score'])
return best_score_item
"""
def validate( self, issue_id ):
# create hash list
score = self.getIssueMatchScore( issue_id, hash_list, useRemoteAlternates = True )
if score < 20:
return True
else:
return False
"""
def search( self ):
ca = self.comic_archive
@ -280,7 +385,7 @@ class IssueIdentifier:
if self.cancel == True:
return []
series_shortlist = []
series_second_round_list = []
#self.log_msg( "Removing results with too long names, banned publishers, or future start dates" )
for item in cv_search_results:
@ -306,34 +411,33 @@ class IssueIdentifier:
publisher_approved = False
if length_approved and publisher_approved and date_approved:
series_shortlist.append(item)
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'] != '1':
if keys['issue_number'] not in [ '1', '0' ]:
#self.log_msg( "Removing one-shots" )
series_shortlist[:] = [x for x in series_shortlist if not x['count_of_issues'] == 1]
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_shortlist)) +" series" )
self.log_msg( "Searching in " + str(len(series_second_round_list)) +" series" )
if self.callback is not None:
self.callback( 0, len(series_shortlist))
self.callback( 0, len(series_second_round_list))
# now sort the list by name length
series_shortlist.sort(key=lambda x: len(x['name']), reverse=False)
# Now we've got a list of series that we can dig into,
# and look for matching issue number, date, and cover image
series_second_round_list.sort(key=lambda x: len(x['name']), reverse=False)
# Now we've got a list of series that we can dig into look for matching issue number
counter = 0
for series in series_shortlist:
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.callback( counter, len(series_shortlist))
self.log_msg( u"Fetching info for ID: {0} {1} ({2}) ...".format(
series['id'],
series['name'],
series['start_year']), newline=False )
series['start_year']), newline=True )
try:
cv_series_results = comicVine.fetchVolumeData( series['id'] )
@ -347,59 +451,67 @@ class IssueIdentifier:
# look for a matching issue number
if num_s == keys['issue_number']:
# found a matching issue number! now get the issue data
img_url, thumb_url = comicVine.fetchIssueCoverURLs( issue['id'] )
month, year = comicVine.fetchIssueDate( issue['id'] )
if self.cancel == True:
self.match_list = []
return self.match_list
# 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 keys['year'] != year:
if unicode(keys['year']) != unicode(year):
break
try:
url_image_data = ImageFetcher().fetch(thumb_url, blocking=True)
except ImageFetcherException:
self.log_msg( "Network issue while fetching cover image from ComicVine. Aborting...")
return []
if self.cancel == True:
self.match_list = []
return self.match_list
if self.coverUrlCallback is not None:
self.coverUrlCallback( url_image_data )
url_image_hash = self.calculateHash( url_image_data )
score = ImageHasher.hamming_distance(cover_hash, url_image_hash)
# found a matching issue number! add it to short list
shortlist.append( (series, cv_series_results, issue) )
# if we have a cropped version of the cover, check that one also, and use the best score
if narrow_cover_hash is not None:
score2 = ImageHasher.hamming_distance(narrow_cover_hash, url_image_hash)
score = min( score, score2 )
if keys['year'] is None:
self.log_msg( "Found {0} series that have an issue #{1}".format(len(shortlist), keys['issue_number']) )
else:
self.log_msg( "Found {0} series that have an issue #{1} from {2}".format(len(shortlist), keys['issue_number'], keys['year'] ))
# now we have a shortlist of volumes with the desired issue number
# Do first round of cover matching
counter = len(shortlist)
for series, cv_series_results, issue in shortlist:
if self.callback is not None:
self.callback( counter, len(shortlist)*3)
counter += 1
self.log_msg( u"Examining covers for ID: {0} {1} ({2}) ...".format(
series['id'],
series['name'],
series['start_year']), newline=False )
# now, if we have an issue year key given, reject this one if not a match
month, year = comicVine.fetchIssueDate( issue['id'] )
match = dict()
match['series'] = u"{0} ({1})".format(series['name'], series['start_year'])
match['distance'] = score
match['issue_number'] = num_s
match['url_image_hash'] = url_image_hash
match['issue_title'] = issue['name']
match['img_url'] = img_url
match['issue_id'] = issue['id']
match['volume_id'] = series['id']
match['month'] = month
match['year'] = year
match['publisher'] = None
if series['publisher'] is not None:
match['publisher'] = series['publisher']['name']
self.match_list.append(match)
# Now check the cover match against the primary image
hash_list = [ cover_hash ]
if narrow_cover_hash is not None:
hash_list.append(narrow_cover_hash)
try:
score_item = self.getIssueCoverMatchScore( comicVine, issue['id'], hash_list, useRemoteAlternates = False )
except:
self.match_list = []
return self.match_list
match = dict()
match['series'] = u"{0} ({1})".format(series['name'], series['start_year'])
match['distance'] = score_item['score']
match['issue_number'] = keys['issue_number']
match['url_image_hash'] = score_item['hash']
match['issue_title'] = issue['name']
match['img_url'] = score_item['url']
match['issue_id'] = issue['id']
match['volume_id'] = series['id']
match['month'] = month
match['year'] = year
match['publisher'] = None
if series['publisher'] is not None:
match['publisher'] = series['publisher']['name']
self.match_list.append(match)
self.log_msg( " --> {0}".format(match['distance']), newline=False )
self.log_msg( " --> {0}".format(match['distance']), newline=False )
break
self.log_msg( "" )
if len(self.match_list) == 0:
@ -415,7 +527,7 @@ class IssueIdentifier:
for i in self.match_list:
l.append( i['distance'] )
self.log_msg( "Compared {0} covers".format(len(self.match_list)), newline=False)
self.log_msg( "Compared to covers in {0} issue(s):".format(len(self.match_list)), newline=False)
self.log_msg( str(l))
def print_match(item):
@ -429,43 +541,66 @@ class IssueIdentifier:
best_score = self.match_list[0]['distance']
if len(self.match_list) == 1:
self.search_result = self.ResultOneGoodMatch
if best_score > self.min_score_thresh:
self.log_msg( "Very weak score for the cover. Maybe it's not the cover..." )
self.log_msg( "Comparing to some other archive pages now..." )
found = False
for i in range( min(3, ca.getNumberOfPages())):
image_data = ca.getPage(i)
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 (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 (score = {0}) on page {1}".format(distance, i) )
found = True
self.log_msg( ".", newline=False )
if best_score >= self.min_score_thresh:
# we have 1 or more low-confidence matches (all bad cover scores)
# look at a few more pages in the archive, and also alternate covers online
self.log_msg( "Very weak scores for the cover. Analyzing alternate pages and covers..." )
hash_list = [ cover_hash ]
if narrow_cover_hash is not None:
hash_list.append(narrow_cover_hash)
for i in range( 1, min(3, ca.getNumberOfPages())):
image_data = ca.getPage(i)
page_hash = self.calculateHash( image_data )
hash_list.append( page_hash )
second_match_list = []
counter = 2*len(self.match_list)
for m in self.match_list:
if self.callback is not None:
self.callback( counter, len(self.match_list)*3)
counter += 1
self.log_msg( u"Examining alternate covers for ID: {0} {1} ...".format(
m['volume_id'],
m['series']), newline=False )
try:
score_item = self.getIssueCoverMatchScore( comicVine, m['issue_id'], hash_list, useRemoteAlternates = True )
except:
self.match_list = []
return self.match_list
self.log_msg("--->{0}".format(score_item['score']))
self.log_msg( "" )
if not found:
self.log_msg( "No matching pages in the issue." )
self.search_result = self.ResultFoundMatchButBadCoverScore
self.log_msg( u"--------------------------------------------------")
print_match(self.match_list[0])
self.log_msg( u"--------------------------------------------------")
return self.match_list
elif best_score > self.min_score_thresh and len(self.match_list) > 1:
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
if score_item['score'] < self.min_alternate_score_thresh:
second_match_list.append(m)
m['distance'] = score_item['score']
return self.match_list
if len( second_match_list ) == 0:
if len( self.match_list) == 1:
self.log_msg( "No matching pages in the issue." )
self.log_msg( u"--------------------------------------------------")
print_match(self.match_list[0])
self.log_msg( u"--------------------------------------------------")
self.search_result = self.ResultFoundMatchButBadCoverScore
else:
self.log_msg( u"--------------------------------------------------")
self.log_msg( u"Multiple bad cover matches! Need to use other info..." )
self.log_msg( u"--------------------------------------------------")
self.search_result = self.ResultMultipleMatchesWithBadImageScores
return self.match_list
else:
# We did good, found something!
self.log_msg( "Success in secondary/alternate cover matching!" )
self.match_list = second_match_list
# sort new list by image match scores
self.match_list.sort(key=lambda k: k['distance'])
best_score = self.match_list[0]['distance']
self.log_msg("[Second round cover matching: best score = {0}]".format(best_score))
# now drop down into the rest of the processing
if self.callback is not None:
self.callback( 99, 100)
#now pare down list, remove any item more than specified distant from the top scores
for item in reversed(self.match_list):
if item['distance'] > best_score + self.min_score_distance:
@ -483,7 +618,6 @@ class IssueIdentifier:
self.log_msg( u"--------------------------------------------------")
self.search_result = self.ResultNoMatches
else:
print
self.log_msg( "More than one likley candiate." )
self.search_result = self.ResultMultipleGoodMatches
self.log_msg( u"--------------------------------------------------")

View File

@ -29,6 +29,8 @@ from comicvinetalker import ComicVineTalker, ComicVineTalkerException
from imagefetcher import ImageFetcher
from settings import ComicTaggerSettings
from issuestring import IssueString
from coverimagewidget import CoverImageWidget
import utils
class IssueSelectionWindow(QtGui.QDialog):
@ -38,7 +40,18 @@ class IssueSelectionWindow(QtGui.QDialog):
super(IssueSelectionWindow, self).__init__(parent)
uic.loadUi(os.path.join(ComicTaggerSettings.baseDir(), 'issueselectionwindow.ui' ), self)
self.coverWidget = CoverImageWidget( self.coverImageContainer, CoverImageWidget.AltCoverMode )
gridlayout = QtGui.QGridLayout( self.coverImageContainer )
gridlayout.addWidget( self.coverWidget )
gridlayout.setContentsMargins(0,0,0,0)
utils.reduceWidgetFontSize( self.twList )
self.setWindowFlags(self.windowFlags() |
QtCore.Qt.WindowSystemMenuHint |
QtCore.Qt.WindowMaximizeButtonHint)
self.series_id = series_id
self.settings = settings
self.url_fetch_thread = None
@ -90,6 +103,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 +111,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)
@ -119,35 +134,13 @@ class IssueSelectionWindow(QtGui.QDialog):
return
if prev is not None and prev.row() == curr.row():
return
self.issue_id, b = self.twList.item( curr.row(), 0 ).data( QtCore.Qt.UserRole ).toInt()
# list selection was changed, update the the issue cover
for record in self.issue_list:
if record['id'] == self.issue_id:
if record['id'] == self.issue_id:
self.issue_number = record['issue_number']
self.labelThumbnail.setPixmap(QtGui.QPixmap(os.path.join(ComicTaggerSettings.baseDir(), 'graphics/nocover.png' )))
self.cv = ComicVineTalker( )
self.cv.urlFetchComplete.connect( self.urlFetchComplete )
self.cv.asyncFetchIssueCoverURLs( int(self.issue_id) )
self.coverWidget.setIssueID( int(self.issue_id) )
break
# called when the cover URL has been fetched
def urlFetchComplete( self, image_url, thumb_url, issue_id ):
self.cover_fetcher = ImageFetcher( )
self.cover_fetcher.fetchComplete.connect(self.coverFetchComplete)
self.cover_fetcher.fetch( str(image_url), user_data=issue_id )
# called when the image is done loading
def coverFetchComplete( self, image_data, issue_id ):
if self.issue_id == issue_id:
img = QtGui.QImage()
img.loadFromData( image_data )
self.labelThumbnail.setPixmap(QtGui.QPixmap(img))

View File

@ -20,11 +20,6 @@
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QTableWidget" name="twList">
<property name="font">
<font>
<pointsize>9</pointsize>
</font>
</property>
<property name="selectionMode">
<enum>QAbstractItemView::SingleSelection</enum>
</property>
@ -59,11 +54,11 @@
</widget>
</item>
<item>
<widget class="QLabel" name="labelThumbnail">
<widget class="QWidget" name="coverImageContainer" native="true">
<property name="minimumSize">
<size>
<width>300</width>
<height>0</height>
<height>450</height>
</size>
</property>
<property name="maximumSize">
@ -72,18 +67,6 @@
<height>450</height>
</size>
</property>
<property name="frameShape">
<enum>QFrame::Panel</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Sunken</enum>
</property>
<property name="text">
<string/>
</property>
<property name="scaledContents">
<bool>true</bool>
</property>
</widget>
</item>
</layout>

View File

@ -32,7 +32,9 @@ class LogWindow(QtGui.QDialog):
uic.loadUi(os.path.join(ComicTaggerSettings.baseDir(), 'logwindow.ui' ), self)
self.setWindowFlags(self.windowFlags() |
QtCore.Qt.WindowSystemMenuHint |
QtCore.Qt.WindowMaximizeButtonHint)
def setText( self, text ):
self.textEdit.setPlainText( text )

View File

@ -26,26 +26,55 @@ from PyQt4.QtCore import QUrl, pyqtSignal, QByteArray
from imagefetcher import ImageFetcher
from settings import ComicTaggerSettings
from options import MetaDataStyle
from coverimagewidget import CoverImageWidget
from comicvinetalker import ComicVineTalker
import utils
class MatchSelectionWindow(QtGui.QDialog):
volume_id = 0
def __init__(self, parent, matches):
def __init__(self, parent, matches, comic_archive):
super(MatchSelectionWindow, self).__init__(parent)
uic.loadUi(os.path.join(ComicTaggerSettings.baseDir(), 'matchselectionwindow.ui' ), self)
self.altCoverWidget = CoverImageWidget( self.altCoverContainer, CoverImageWidget.AltCoverMode )
gridlayout = QtGui.QGridLayout( self.altCoverContainer )
gridlayout.addWidget( self.altCoverWidget )
gridlayout.setContentsMargins(0,0,0,0)
self.archiveCoverWidget = CoverImageWidget( self.archiveCoverContainer, CoverImageWidget.ArchiveMode )
gridlayout = QtGui.QGridLayout( self.archiveCoverContainer )
gridlayout.addWidget( self.archiveCoverWidget )
gridlayout.setContentsMargins(0,0,0,0)
utils.reduceWidgetFontSize( self.twList )
self.setWindowFlags(self.windowFlags() |
QtCore.Qt.WindowSystemMenuHint |
QtCore.Qt.WindowMaximizeButtonHint)
self.matches = matches
self.populateTable( )
self.twList.resizeColumnsToContents()
self.comic_archive = comic_archive
self.twList.currentItemChanged.connect(self.currentItemChanged)
self.twList.cellDoubleClicked.connect(self.cellDoubleClicked)
self.current_row = 0
self.twList.selectRow( 0 )
self.updateData()
def updateData( self):
self.setCoverImage()
self.populateTable()
self.twList.resizeColumnsToContents()
self.twList.selectRow( 0 )
path = self.comic_archive.path
self.setWindowTitle( u"Select correct match: {0}".format(
os.path.split(path)[1] ))
def populateTable( self ):
while self.twList.rowCount() > 0:
@ -59,35 +88,47 @@ class MatchSelectionWindow(QtGui.QDialog):
item_text = match['series']
item = QtGui.QTableWidgetItem(item_text)
item.setData( QtCore.Qt.ToolTipRole, item_text )
item.setData( QtCore.Qt.UserRole, (match,))
item.setFlags(QtCore.Qt.ItemIsSelectable| QtCore.Qt.ItemIsEnabled)
self.twList.setItem(row, 0, item)
"""
item_text = u"{0}".format(match['issue_number'])
item = QtGui.QTableWidgetItem(item_text)
item.setFlags(QtCore.Qt.ItemIsSelectable| QtCore.Qt.ItemIsEnabled)
self.twList.setItem(row, 1, item)
"""
if match['publisher'] is not None:
item_text = u"{0}".format(match['publisher'])
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)
item_text = ""
month_str = u""
year_str = u"????"
if match['month'] is not None:
item_text = u"{0}/".format(match['month'])
month_str = u"-{0:02d}".format(int(match['month']))
if match['year'] is not None:
item_text += u"{0}".format(match['year'])
else:
item_text += u"????"
year_str = u"{0}".format(match['year'])
item_text = year_str + month_str
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)
item_text = match['issue_title']
item = QtGui.QTableWidgetItem(item_text)
item.setData( QtCore.Qt.ToolTipRole, item_text )
item.setFlags(QtCore.Qt.ItemIsSelectable| QtCore.Qt.ItemIsEnabled)
self.twList.setItem(row, 3, item)
row += 1
self.twList.resizeColumnsToContents()
self.twList.setSortingEnabled(True)
self.twList.sortItems( 2 , QtCore.Qt.AscendingOrder )
self.twList.selectRow(0)
self.twList.resizeColumnsToContents()
self.twList.horizontalHeader().setStretchLastSection(True)
def cellDoubleClicked( self, r, c ):
@ -99,19 +140,14 @@ class MatchSelectionWindow(QtGui.QDialog):
return
if prev is not None and prev.row() == curr.row():
return
self.altCoverWidget.setIssueID( self.currentMatch()['issue_id'] )
def setCoverImage( self ):
self.archiveCoverWidget.setArchive( self.comic_archive)
def currentMatch( self ):
row = self.twList.currentRow()
match = self.twList.item(row, 0).data( QtCore.Qt.UserRole ).toPyObject()[0]
return match
self.current_row = curr.row()
# list selection was changed, update the the issue cover
self.labelThumbnail.setPixmap(QtGui.QPixmap(os.path.join(ComicTaggerSettings.baseDir(), 'graphics/nocover.png' )))
self.cover_fetcher = ImageFetcher( )
self.cover_fetcher.fetchComplete.connect(self.coverFetchComplete)
self.cover_fetcher.fetch( self.matches[self.current_row]['img_url'] )
# called when the image is done loading
def coverFetchComplete( self, image_data, issue_id ):
img = QtGui.QImage()
img.loadFromData( image_data )
self.labelThumbnail.setPixmap(QtGui.QPixmap(img))

View File

@ -6,25 +6,39 @@
<rect>
<x>0</x>
<y>0</y>
<width>831</width>
<height>506</height>
<width>907</width>
<height>507</height>
</rect>
</property>
<property name="windowTitle">
<string>Select Match</string>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="0">
<item row="0" column="1">
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<property name="spacing">
<number>0</number>
</property>
<item>
<widget class="QWidget" name="archiveCoverContainer" native="true">
<property name="minimumSize">
<size>
<width>200</width>
<height>350</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>200</width>
<height>350</height>
</size>
</property>
</widget>
</item>
<item>
<widget class="QTableWidget" name="twList">
<property name="font">
<font>
<pointsize>9</pointsize>
</font>
</property>
<property name="selectionMode">
<enum>QAbstractItemView::SingleSelection</enum>
</property>
@ -35,7 +49,7 @@
<number>0</number>
</property>
<property name="columnCount">
<number>3</number>
<number>4</number>
</property>
<attribute name="horizontalHeaderStretchLastSection">
<bool>true</bool>
@ -58,34 +72,27 @@
<string>Date</string>
</property>
</column>
<column>
<property name="text">
<string>Title</string>
</property>
</column>
</widget>
</item>
<item>
<widget class="QLabel" name="labelThumbnail">
<widget class="QWidget" name="altCoverContainer" native="true">
<property name="minimumSize">
<size>
<width>300</width>
<height>0</height>
<width>200</width>
<height>350</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>300</width>
<height>450</height>
<width>200</width>
<height>350</height>
</size>
</property>
<property name="frameShape">
<enum>QFrame::Panel</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Sunken</enum>
</property>
<property name="text">
<string/>
</property>
<property name="scaledContents">
<bool>true</bool>
</property>
</widget>
</item>
</layout>

View File

@ -78,12 +78,16 @@ If no options are given, {0} will run in windowed mode
Some names that can be used:
series, issue, issueCount, year, publisher, title
-r, --rename Rename the file based on specified tag style.
--noabort Don't abort save operation when online match is of low confidence
--noabort Don't abort save operation when online match is of low confidence
-e, --export-to-zip Export RAR archive to Zip format
--delete-rar Delete original RAR archive after successful export to Zip
--abort-on-conflict Don't export to zip if intended new filename exists (Otherwise, creates
a new unique filename)
-v, --verbose Be noisy when doing what it does
--terse Don't say much (for print mode)
--version Display version
-h, --help Display this message
"""
"""
def __init__(self):
@ -96,6 +100,9 @@ If no options are given, {0} will run in windowed mode
self.print_tags = False
self.copy_tags = False
self.delete_tags = False
self.export_to_zip = False
self.abort_export_on_conflict = False
self.delete_rar_after_export = False
self.search_online = False
self.dryrun = False
self.abortOnLowConfidence = True
@ -179,14 +186,15 @@ If no options are given, {0} will run in windowed mode
# parse command line options
try:
opts, args = getopt.getopt( input_args,
"hpdt:fm:vonsrc:i",
"hpdt:fm:vonsrc:ie",
[ "help", "print", "delete", "type=", "copy=", "parsefilename", "metadata=", "verbose",
"online", "dryrun", "save", "rename" , "raw", "noabort", "terse", "nooverwrite",
"interactive", "nosummary", "version", "id=" ])
"interactive", "nosummary", "version", "id="
"export-to-zip", "delete-rar", "abort-on-conflict" ] )
except getopt.GetoptError as err:
self.display_msg_and_quit( str(err), 2 )
# process options
for o, a in opts:
if o in ("-h", "--help"):
@ -219,6 +227,12 @@ If no options are given, {0} will run in windowed mode
self.save_tags = True
if o in ("-r", "--rename"):
self.rename_file = True
if o in ("-e", "--export_to_zip"):
self.export_to_zip = True
if o == "--delete-rar":
self.delete_rar_after_export = True
if o == "--abort-on-conflict":
self.abort_export_on_conflict = True
if o in ("-f", "--parsefilename"):
self.parse_filename = True
if o == "--id":
@ -234,7 +248,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":
@ -246,7 +261,7 @@ If no options are given, {0} will run in windowed mode
else:
self.display_msg_and_quit( "Invalid tag type", 1 )
if self.print_tags or self.delete_tags or self.save_tags or self.copy_tags or self.rename_file:
if self.print_tags or self.delete_tags or self.save_tags or self.copy_tags or self.rename_file or self.export_to_zip:
self.no_gui = True
count = 0
@ -255,9 +270,10 @@ If no options are given, {0} will run in windowed mode
if self.save_tags: count += 1
if self.copy_tags: count += 1
if self.rename_file: count += 1
if self.export_to_zip: count +=1
if count > 1:
self.display_msg_and_quit( "Must choose only one action of print, delete, save, copy, or rename", 1 )
self.display_msg_and_quit( "Must choose only one action of print, delete, save, copy, rename, or export", 1 )
if len(args) > 0:
if platform.system() == "Windows":
@ -272,7 +288,7 @@ If no options are given, {0} will run in windowed mode
self.file_list = args
if self.no_gui and self.filename is None:
self.display_msg_and_quit( "Command requires a filename!", 1 )
self.display_msg_and_quit( "Command requires at least one filename!", 1 )
if self.delete_tags and self.data_style is None:
self.display_msg_and_quit( "Please specify the type to delete with -t", 1 )

View File

@ -18,11 +18,12 @@ See the License for the specific language governing permissions and
limitations under the License.
"""
import platform
import sys
from PyQt4 import QtCore, QtGui, uic
import os
from settings import ComicTaggerSettings
from coverimagewidget import CoverImageWidget
class PageBrowserWindow(QtGui.QDialog):
@ -31,14 +32,29 @@ class PageBrowserWindow(QtGui.QDialog):
uic.loadUi(os.path.join(ComicTaggerSettings.baseDir(), 'pagebrowser.ui' ), self)
self.lblPage.setPixmap(QtGui.QPixmap(os.path.join(ComicTaggerSettings.baseDir(), 'graphics/nocover.png' )))
self.lblPage.setSizePolicy(QtGui.QSizePolicy.Ignored, QtGui.QSizePolicy.Ignored)
self.pageWidget = CoverImageWidget( self.pageContainer, CoverImageWidget.ArchiveMode )
gridlayout = QtGui.QGridLayout( self.pageContainer )
gridlayout.addWidget( self.pageWidget )
gridlayout.setContentsMargins(0,0,0,0)
self.pageWidget.showControls = False
self.setWindowFlags(self.windowFlags() |
QtCore.Qt.WindowSystemMenuHint |
QtCore.Qt.WindowMaximizeButtonHint)
self.comic_archive = None
self.current_pixmap = None
self.page_count = 0
self.current_page_num = 0
self.metadata = metadata
self.buttonBox.button(QtGui.QDialogButtonBox.Close).setDefault(True)
if platform.system() == "Darwin":
self.btnPrev.setText("<<")
self.btnNext.setText(">>")
else:
self.btnPrev.setIcon(QtGui.QIcon(os.path.join(ComicTaggerSettings.baseDir(), 'graphics/left.png' )))
self.btnNext.setIcon(QtGui.QIcon(os.path.join(ComicTaggerSettings.baseDir(), 'graphics/right.png' )))
self.btnNext.clicked.connect( self.nextPage )
self.btnPrev.clicked.connect( self.prevPage )
self.show()
@ -46,72 +62,49 @@ class PageBrowserWindow(QtGui.QDialog):
self.btnNext.setEnabled( False )
self.btnPrev.setEnabled( False )
def reset( self ):
self.comic_archive = None
self.page_count = 0
self.current_page_num = 0
self.metadata = None
self.btnNext.setEnabled( False )
self.btnPrev.setEnabled( False )
self.pageWidget.clear()
def setComicArchive(self, ca):
self.comic_archive = ca
self.page_count = ca.getNumberOfPages()
self.current_page_num = 0
self.pageWidget.setArchive( self.comic_archive )
self.setPage()
if self.page_count > 1:
self.btnNext.setEnabled( True )
self.btnPrev.setEnabled( True )
def nextPage(self):
if self.current_page_num + 1 < self.page_count:
self.current_page_num += 1
else:
self.current_page_num = 0
self.setPage()
def prevPage(self):
if self.current_page_num - 1 >= 0:
self.current_page_num -= 1
else:
self.current_page_num = self.page_count - 1
self.setPage()
def setPage( self ):
archive_page_index = self.metadata.getArchivePageIndex( self.current_page_num )
image_data = self.comic_archive.getPage( archive_page_index )
if image_data is not None:
self.setCurrentPixmap( image_data )
self.setDisplayPixmap( 0, 0)
if self.metadata is not None:
archive_page_index = self.metadata.getArchivePageIndex( self.current_page_num )
else:
archive_page_index = self.current_page_num
self.pageWidget.setPage( archive_page_index )
self.setWindowTitle("Page Browser - Page {0} (of {1}) ".format(self.current_page_num+1, self.page_count ) )
if self.current_page_num + 1 < self.page_count:
self.btnNext.setEnabled( True )
else:
self.btnNext.setEnabled( False )
if self.current_page_num - 1 >= 0:
self.btnPrev.setEnabled( True )
else:
self.btnPrev.setEnabled( False )
def setCurrentPixmap( self, image_data ):
if image_data is not None:
img = QtGui.QImage()
img.loadFromData( image_data )
self.current_pixmap = QtGui.QPixmap(QtGui.QPixmap(img))
def resizeEvent( self, resize_event ):
if self.current_pixmap is not None:
delta_w = resize_event.size().width() - resize_event.oldSize().width()
delta_h = resize_event.size().height() - resize_event.oldSize().height()
self.setDisplayPixmap( delta_w , delta_h )
def setDisplayPixmap( self, delta_w , delta_h ):
# the deltas let us know what the new width and height of the label will be
new_h = self.lblPage.height() + delta_h
new_w = self.lblPage.width() + delta_w
if new_h < 0:
new_h = 0;
if new_w < 0:
new_w = 0;
scaled_pixmap = self.current_pixmap.scaled(new_w, new_h, QtCore.Qt.KeepAspectRatio)
self.lblPage.setPixmap( scaled_pixmap )
#QtCore.QCoreApplication.processEvents()

View File

@ -6,8 +6,8 @@
<rect>
<x>0</x>
<y>0</y>
<width>429</width>
<height>637</height>
<width>369</width>
<height>582</height>
</rect>
</property>
<property name="sizePolicy">
@ -20,10 +20,31 @@
<string>Page Browser</string>
</property>
<layout class="QGridLayout" name="gridLayout">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>4</number>
</property>
<property name="horizontalSpacing">
<number>0</number>
</property>
<property name="verticalSpacing">
<number>2</number>
</property>
<item row="0" column="0">
<layout class="QVBoxLayout" name="verticalLayout">
<property name="spacing">
<number>0</number>
</property>
<item>
<widget class="QLabel" name="lblPage">
<widget class="QWidget" name="pageContainer" native="true">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
<horstretch>0</horstretch>
@ -36,37 +57,47 @@
<height>300</height>
</size>
</property>
<property name="frameShape">
<enum>QFrame::Box</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Sunken</enum>
</property>
<property name="text">
<string/>
</property>
<property name="scaledContents">
<bool>false</bool>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<property name="spacing">
<number>20</number>
</property>
<property name="sizeConstraint">
<enum>QLayout::SetMaximumSize</enum>
</property>
<property name="topMargin">
<number>4</number>
</property>
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="btnPrev">
<property name="text">
<string>&lt;&lt;</string>
<string/>
</property>
</widget>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
@ -81,10 +112,26 @@
<item>
<widget class="QPushButton" name="btnNext">
<property name="text">
<string>&gt;&gt;</string>
<string/>
</property>
<property name="autoDefault">
<bool>false</bool>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_2">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
</layout>

View File

@ -28,6 +28,8 @@ from settings import ComicTaggerSettings
from genericmetadata import GenericMetadata, PageType
from options import MetaDataStyle
from pageloader import PageLoader
from coverimagewidget import CoverImageWidget
def itemMoveEvents( widget ):
@ -78,9 +80,11 @@ class PageListEditor(QWidget):
uic.loadUi(os.path.join(ComicTaggerSettings.baseDir(), 'pagelisteditor.ui' ), self )
self.comic_archive = None
self.pages_list = None
self.page_loader = None
self.pageWidget = CoverImageWidget( self.pageContainer, CoverImageWidget.ArchiveMode )
gridlayout = QGridLayout( self.pageContainer )
gridlayout.addWidget( self.pageWidget )
gridlayout.setContentsMargins(0,0,0,0)
self.pageWidget.showControls = False
self.resetPage()
@ -107,8 +111,10 @@ class PageListEditor(QWidget):
self.first_front_page = None
def resetPage( self ):
self.current_pixmap = QPixmap(os.path.join(ComicTaggerSettings.baseDir(), 'graphics/nocover.png' ))
self.setDisplayPixmap( 0, 0)
self.pageWidget.clear()
self.comboBox.setDisabled(True)
self.comic_archive = None
self.pages_list = None
def moveCurrentUp( self ):
row = self.listWidget.currentRow()
@ -157,18 +163,8 @@ class PageListEditor(QWidget):
#idx = int(str (self.listWidget.item( row ).text()))
idx = int(self.listWidget.item( row ).data(Qt.UserRole).toPyObject()[0]['Image'])
if self.page_loader is not None:
self.page_loader.abandoned = True
if self.comic_archive is not None:
self.page_loader = PageLoader( self.comic_archive, idx )
self.page_loader.loadComplete.connect( self.actualChangePageImage )
self.page_loader.start()
def actualChangePageImage( self, img ):
self.page_loader = None
self.current_pixmap = QPixmap(img)
self.setDisplayPixmap( 0, 0)
self.pageWidget.setArchive( self.comic_archive, idx )
def getFirstFrontCover( self ):
frontCover = 0
@ -203,44 +199,13 @@ class PageListEditor(QWidget):
# wrap the dict in a tuple to keep from being converted to QStrings
item.setData(Qt.UserRole, (page_dict,) )
item.setText( self.listEntryText( page_dict ) )
def resizeEvent( self, resize_event ):
if self.current_pixmap is not None:
delta_w = resize_event.size().width() - resize_event.oldSize().width()
delta_h = resize_event.size().height() - resize_event.oldSize().height()
self.setDisplayPixmap( delta_w , delta_h )
def setDisplayPixmap( self, delta_w , delta_h ):
# the deltas let us know what the new width and height of the label will be
new_h = self.frame.height() + delta_h
new_w = self.frame.width() + delta_w
frame_w = new_w
frame_h = new_h
new_h -= 4
new_w -= 4
if new_h < 0:
new_h = 0;
if new_w < 0:
new_w = 0;
# scale the pixmap to fit in the frame
scaled_pixmap = self.current_pixmap.scaled(new_w, new_h, Qt.KeepAspectRatio)
self.label.setPixmap( scaled_pixmap )
# ,pve and resize the label to be centered in the fame
img_w = scaled_pixmap.width()
img_h = scaled_pixmap.height()
self.label.resize( img_w, img_h )
self.label.move( (frame_w - img_w)/2, (frame_h - img_h)/2 )
def setData( self, comic_archive, pages_list ):
self.comic_archive = comic_archive
self.pages_list = pages_list
if pages_list is not None and len(pages_list) > 0:
self.comboBox.setDisabled(False)
self.listWidget.itemSelectionChanged.disconnect( self.changePage )
@ -256,7 +221,7 @@ class PageListEditor(QWidget):
self.listWidget.setCurrentRow ( 0 )
def listEntryText(self, page_dict):
text = page_dict['Image']
text = str(int(page_dict['Image']) + 1)
if 'Type' in page_dict:
text += " (" + self.pageTypeNames[page_dict['Type']] + ")"
return text
@ -278,9 +243,9 @@ class PageListEditor(QWidget):
# depending on the current data style, certain fields are disabled
inactive_color = QColor(255, 170, 150)
active_palette = self.label.palette()
active_palette = self.comboBox.palette()
inactive_palette3 = self.label.palette()
inactive_palette3 = self.comboBox.palette()
inactive_palette3.setColor(QPalette.Base, inactive_color)
@ -302,8 +267,7 @@ class PageListEditor(QWidget):
elif data_style == MetaDataStyle.CoMet:
pass
def showEvent( self, event ):
# make sure to adjust the size and pos of the pixmap based on frame size
self.setDisplayPixmap( 0,0 )
# make sure combo is disabled when no list
if self.comic_archive is None:
self.comboBox.setEnabled( False )

View File

@ -98,32 +98,13 @@
</layout>
</item>
<item>
<widget class="QFrame" name="frame">
<widget class="QWidget" name="pageContainer" native="true">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>90</verstretch>
</sizepolicy>
</property>
<property name="frameShape">
<enum>QFrame::StyledPanel</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Sunken</enum>
</property>
<widget class="QLabel" name="label">
<property name="geometry">
<rect>
<x>10</x>
<y>10</y>
<width>151</width>
<height>141</height>
</rect>
</property>
<property name="text">
<string/>
</property>
</widget>
</widget>
</item>
</layout>

View File

@ -22,7 +22,7 @@ import sys
from PyQt4 import QtCore, QtGui, uic
import os
from settings import ComicTaggerSettings
import utils
class IDProgressWindow(QtGui.QDialog):
@ -31,13 +31,12 @@ class IDProgressWindow(QtGui.QDialog):
super(IDProgressWindow, self).__init__(parent)
uic.loadUi(os.path.join(ComicTaggerSettings.baseDir(), 'progresswindow.ui' ), self)
# we can't specify relative font sizes in the UI designer, so
# make font for scroll window a smidge smaller
f = self.textEdit.font()
if f.pointSize() > 10:
f.setPointSize( f.pointSize() - 2 )
self.textEdit.setFont( f )
self.setWindowFlags(self.windowFlags() |
QtCore.Qt.WindowSystemMenuHint |
QtCore.Qt.WindowMaximizeButtonHint)
utils.reduceWidgetFontSize( self.textEdit )

View File

@ -1,10 +1,38 @@
---------------------------------
1.1.0-beta - 06-Feb-2013
---------------------------------
Changes:
* Enhanced identification process to use alternative covers from ComicVine
* Post auto-tag manual matching now includes single low-confidence matches (CLI & GUI)
* Page and cover view mini-browser available throughout app. Most images can be
double-clicked for embiggened view
* Export-to-zip in CLI (very handy in scripts!)
* More rename template variables
* Misc GUI & CLI Tweaks
---------------------------------
1.0.3-beta - 31-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
@ -75,4 +103,4 @@ Note:
---------------------------------
0.9.0-beta - 30-Nov-2012
---------------------------------
Initial beta release
Initial beta release

View File

@ -36,6 +36,10 @@ class RenameWindow(QtGui.QDialog):
uic.loadUi(os.path.join(ComicTaggerSettings.baseDir(), 'renamewindow.ui' ), self)
self.label.setText("Preview (based on {0} tags):".format(MetaDataStyle.name[data_style]))
self.setWindowFlags(self.windowFlags() |
QtCore.Qt.WindowSystemMenuHint |
QtCore.Qt.WindowMaximizeButtonHint)
self.settings = settings
self.comic_archive_list = comic_archive_list
self.data_style = data_style

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, Editorial Televisa"
self.id_publisher_blacklist = "Panini Comics, Abril, Planeta DeAgostini, Editorial Televisa"
# Show/ask dialog flags
self.ask_about_cbi_in_rar = True

View File

@ -56,8 +56,10 @@ class SettingsWindow(QtGui.QDialog):
uic.loadUi(os.path.join(ComicTaggerSettings.baseDir(), 'settingswindow.ui' ), self)
self.settings = settings
self.setWindowFlags(self.windowFlags() &
~QtCore.Qt.WindowContextHelpButtonHint )
self.settings = settings
self.name = "Settings"
if platform.system() == "Windows":
@ -79,7 +81,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,20 @@
<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;%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>
<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;br/&gt;
%genre%&lt;br/&gt;
%language_code%&lt;br/&gt;
%criticalrating%&lt;br/&gt;
%alternateseries%&lt;br/&gt;
%alternatenumber%&lt;br/&gt;
%alternatecount%&lt;br/&gt;
%imprint%&lt;br/&gt;
%format%&lt;br/&gt;
%maturityrating%&lt;br/&gt;
%storyarc%&lt;br/&gt;
%seriesgroup%&lt;br/&gt;
%scaninfo%
&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

@ -1,6 +1,6 @@
# coding=utf-8
"""
The main window of the comictagger app
The main window of the ComicTagger app
"""
"""
@ -29,6 +29,7 @@ import os
import pprint
import json
import webbrowser
import re
from volumeselectionwindow import VolumeSelectionWindow
from options import MetaDataStyle
@ -53,6 +54,8 @@ from issueidentifier import IssueIdentifier
from autotagstartwindow import AutoTagStartWindow
from autotagprogresswindow import AutoTagProgressWindow
from autotagmatchwindow import AutoTagMatchWindow
from coverimagewidget import CoverImageWidget
import utils
import ctversion
@ -61,6 +64,7 @@ class OnlineMatchResults():
self.goodMatches = []
self.noMatches = []
self.multipleMatches = []
self.lowConfidenceMatches = []
self.writeFailures = []
self.fetchDataFailures = []
@ -72,28 +76,6 @@ class MultipleMatch():
# this reads the environment and inits the right locale
locale.setlocale(locale.LC_ALL, "")
# helper func to allow a label to be clickable
def clickable(widget):
class Filter(QtCore.QObject):
dblclicked = pyqtSignal()
def eventFilter(self, obj, event):
if obj == widget:
if event.type() == QtCore.QEvent.MouseButtonDblClick:
self.dblclicked.emit()
return True
return False
filter = Filter(widget)
widget.installEventFilter(filter)
return filter.dblclicked
class TaggerWindow( QtGui.QMainWindow):
appName = "ComicTagger"
@ -104,7 +86,12 @@ class TaggerWindow( QtGui.QMainWindow):
uic.loadUi(os.path.join(ComicTaggerSettings.baseDir(), 'taggerwindow.ui' ), self)
self.settings = settings
self.archiveCoverWidget = CoverImageWidget( self.coverImageContainer, CoverImageWidget.ArchiveMode )
gridlayout = QtGui.QGridLayout( self.coverImageContainer )
gridlayout.addWidget( self.archiveCoverWidget )
gridlayout.setContentsMargins(0,0,0,0)
self.pageListEditor = PageListEditor( self.tabPages )
gridlayout = QtGui.QGridLayout( self.tabPages )
gridlayout.addWidget( self.pageListEditor )
@ -122,13 +109,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
@ -139,14 +119,11 @@ class TaggerWindow( QtGui.QMainWindow):
f.setPointSize( f.pointSize() - 2 )
f.setItalic( True )
child.setFont( f )
#---------------------------
self.scrollAreaWidgetContents.adjustSize()
self.setWindowIcon(QtGui.QIcon(os.path.join(ComicTaggerSettings.baseDir(), 'graphics/app.png' )))
self.lblCover.setPixmap(QtGui.QPixmap(os.path.join(ComicTaggerSettings.baseDir(), 'graphics/nocover.png' )))
self.save_data_style = settings.last_selected_save_data_style
self.load_data_style = settings.last_selected_load_data_style
@ -155,6 +132,7 @@ class TaggerWindow( QtGui.QMainWindow):
self.statusBar()
self.populateComboBoxes()
self.page_browser = None
self.resetApp()
# set up some basic field validators
@ -185,13 +163,9 @@ class TaggerWindow( QtGui.QMainWindow):
self.btnAddCredit.clicked.connect(self.addCredit)
self.btnRemoveCredit.clicked.connect(self.removeCredit)
self.twCredits.cellDoubleClicked.connect(self.editCredit)
clickable(self.lblCover).connect(self.showPageBrowser)
self.connectDirtyFlagSignals()
self.pageListEditor.modified.connect(self.setDirtyFlag)
self.pageListEditor.firstFrontCoverChanged.connect( self.frontCoverChanged )
self.pageListEditor.listOrderChanged.connect( self.pageListOrderChanged )
self.tabWidget.currentChanged.connect( self.tabChanged )
self.updateStyleTweaks()
@ -201,6 +175,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 )
@ -233,19 +210,18 @@ class TaggerWindow( QtGui.QMainWindow):
def resetApp( self ):
self.lblCover.setPixmap(QtGui.QPixmap(os.path.join(ComicTaggerSettings.baseDir(), 'graphics/nocover.png' )))
self.archiveCoverWidget.clear()
self.comic_archive = None
self.dirtyFlag = False
self.clearForm()
self.pageListEditor.resetPage()
if self.page_browser is not None:
self.page_browser.reset()
self.updateAppTitle()
self.updateMenus()
self.updateInfoBox()
self.droppedFile = None
self.page_browser = None
self.page_loader = None
@ -316,6 +292,7 @@ class TaggerWindow( QtGui.QMainWindow):
self.actionRename.setStatusTip( 'Rename archive based on tags' )
self.actionRename.triggered.connect( self.renameArchive )
self.actionSettings.setShortcut( 'Ctrl+Shift+S' )
self.actionSettings.setStatusTip( 'Configure ComicTagger' )
self.actionSettings.triggered.connect( self.showSettings )
@ -390,18 +367,22 @@ 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
progdialog = QtGui.QProgressDialog("", "Cancel", 0, rar_count, self)
progdialog.setWindowTitle( "Exporting as ZIP" )
progdialog.setWindowModality(QtCore.Qt.WindowModal)
progdialog.setWindowModality(QtCore.Qt.ApplicationModal)
progdialog.show()
prog_idx = 0
new_archives_to_add = []
archives_to_remove = []
skipped_list = []
failed_list = []
success_count = 0
for ca in ca_list:
if ca.isRar():
@ -411,6 +392,7 @@ class TaggerWindow( QtGui.QMainWindow):
progdialog.setValue(prog_idx)
prog_idx += 1
progdialog.setLabelText( ca.path )
utils.centerWindowOnParent( progdialog )
QtCore.QCoreApplication.processEvents()
original_path = os.path.abspath( ca.path )
@ -419,11 +401,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 +415,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 +485,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()
@ -498,21 +499,11 @@ class TaggerWindow( QtGui.QMainWindow):
self.clearDirtyFlag() # also updates the app title
self.updateInfoBox()
self.updateMenus()
self.updateAppTitle()
def updateCoverImage( self ):
if self.page_loader is not None:
self.page_loader.abandoned = True
cover_idx = self.metadata.getCoverPageIndexList()[0]
self.page_loader = PageLoader( self.comic_archive, cover_idx )
self.page_loader.loadComplete.connect( self.actualUpdateCoverImage )
self.page_loader.start()
def actualUpdateCoverImage( self, img ):
self.page_loader = None
self.lblCover.setPixmap(QtGui.QPixmap(img))
self.lblCover.setScaledContents(True)
self.archiveCoverWidget.setArchive( self.comic_archive, cover_idx)
def updateMenus( self ):
@ -754,10 +745,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 +982,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:
@ -1384,10 +1379,12 @@ class TaggerWindow( QtGui.QMainWindow):
if reply == QtGui.QMessageBox.Yes:
progdialog = QtGui.QProgressDialog("", "Cancel", 0, has_md_count, self)
progdialog.setWindowTitle( "Removing Tags" )
progdialog.setWindowModality(QtCore.Qt.WindowModal)
progdialog.setWindowModality(QtCore.Qt.ApplicationModal)
progdialog.show()
prog_idx = 0
failed_list = []
success_count = 0
for ca in ca_list:
if ca.hasMetadata( style ):
QtCore.QCoreApplication.processEvents()
@ -1396,21 +1393,32 @@ class TaggerWindow( QtGui.QMainWindow):
progdialog.setValue(prog_idx)
prog_idx += 1
progdialog.setLabelText( ca.path )
utils.centerWindowOnParent( progdialog )
QtCore.QCoreApplication.processEvents()
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.adjustSize()
dlg.exec_()
def copyTags( self ):
# copy the indicated tags in the archive
@ -1448,10 +1456,12 @@ class TaggerWindow( QtGui.QMainWindow):
if reply == QtGui.QMessageBox.Yes:
progdialog = QtGui.QProgressDialog("", "Cancel", 0, has_src_count, self)
progdialog.setWindowTitle( "Copying Tags" )
progdialog.setWindowModality(QtCore.Qt.WindowModal)
progdialog.setWindowModality(QtCore.Qt.ApplicationModal)
progdialog.show()
prog_idx = 0
failed_list = []
success_count = 0
for ca in ca_list:
if ca.hasMetadata( src_style ):
QtCore.QCoreApplication.processEvents()
@ -1460,6 +1470,7 @@ class TaggerWindow( QtGui.QMainWindow):
progdialog.setValue(prog_idx)
prog_idx += 1
progdialog.setLabelText( ca.path )
utils.centerWindowOnParent( progdialog )
QtCore.QCoreApplication.processEvents()
if ca.hasMetadata( src_style ) and ca.isWritable():
@ -1469,16 +1480,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 ):
@ -1515,14 +1537,19 @@ 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
if dlg.dontUseYear:
md.year = None
if dlg.assumeIssueOne and ( md.issue is None or md.issue == ""):
@ -1531,7 +1558,8 @@ class TaggerWindow( QtGui.QMainWindow):
ii.onlyUseAdditionalMetaData = True
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()
@ -1540,6 +1568,7 @@ class TaggerWindow( QtGui.QMainWindow):
found_match = False
choices = False
low_confidence = False
no_match = False
if result == ii.ResultNoMatches:
pass
@ -1557,35 +1586,40 @@ class TaggerWindow( QtGui.QMainWindow):
choices = True
if choices:
self.autoTagLog( "Online search: Multiple matches. Save aborted\n" )
match_results.multipleMatches.append(MultipleMatch(ca,matches))
elif low_confidence:
if dlg.autoSaveOnLow:
self.autoTagLog( "Online search: Low confidence match, but saving anyways...\n" )
if low_confidence:
self.autoTagLog( "Online search: Multiple low-confidence matches. Save aborted\n" )
match_results.lowConfidenceMatches.append(MultipleMatch(ca,matches))
else:
self.autoTagLog( "Online search: Low confidence match. Save aborted\n" )
match_results.noMatches.append(ca.path)
self.autoTagLog( "Online search: Multiple matches. Save aborted\n" )
match_results.multipleMatches.append(MultipleMatch(ca,matches))
elif low_confidence and not dlg.autoSaveOnLow:
self.autoTagLog( "Online search: Low confidence match. Save aborted\n" )
match_results.lowConfidenceMatches.append(MultipleMatch(ca,matches))
elif not found_match:
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 indicated...\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 ):
@ -1602,25 +1636,25 @@ 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)))
@ -1636,13 +1670,21 @@ class TaggerWindow( QtGui.QMainWindow):
self.atprogdialog.progressBar.setValue( prog_idx )
prog_idx += 1
self.atprogdialog.label.setText( ca.path )
utils.centerWindowOnParent( self.atprogdialog )
QtCore.QCoreApplication.processEvents()
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
@ -1651,6 +1693,8 @@ class TaggerWindow( QtGui.QMainWindow):
if len ( match_results.multipleMatches ) > 0:
summary += u"Archives with multiple matches: {0}\n".format( len(match_results.multipleMatches))
if len ( match_results.lowConfidenceMatches ) > 0:
summary += u"Archives with one or more low-confidence matches: {0}\n".format( len(match_results.lowConfidenceMatches))
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:
@ -1659,15 +1703,17 @@ class TaggerWindow( QtGui.QMainWindow):
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?"
sum_selectable = len ( match_results.multipleMatches ) + len(match_results.lowConfidenceMatches)
if sum_selectable > 0:
summary += u"\n\nDo you want to manually select the ones with multiple matches and/or low-confidence matches now?"
reply = QtGui.QMessageBox.question(self,
self.tr(u"Auto-Tag Summary"),
self.tr(summary),
QtGui.QMessageBox.Yes, QtGui.QMessageBox.No )
match_results.multipleMatches.extend( match_results.lowConfidenceMatches )
if reply == QtGui.QMessageBox.Yes:
matchdlg = AutoTagMatchWindow( self, match_results.multipleMatches, style, self.actualIssueDataFetch)
matchdlg.setModal( True )
@ -1792,4 +1838,20 @@ 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)
def tabChanged( self, idx ):
if idx == 0:
self.splitterMovedEvent( 0, 0)

View File

@ -7,7 +7,7 @@
<x>0</x>
<y>0</y>
<width>1096</width>
<height>571</height>
<height>575</height>
</rect>
</property>
<property name="sizePolicy">
@ -202,7 +202,7 @@
</widget>
</item>
<item>
<widget class="QLabel" name="lblCover">
<widget class="QWidget" name="coverImageContainer" native="true">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Expanding">
<horstretch>0</horstretch>
@ -211,31 +211,16 @@
</property>
<property name="minimumSize">
<size>
<width>220</width>
<height>330</height>
<width>230</width>
<height>380</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>220</width>
<height>330</height>
<width>230</width>
<height>380</height>
</size>
</property>
<property name="frameShape">
<enum>QFrame::Panel</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Sunken</enum>
</property>
<property name="text">
<string/>
</property>
<property name="scaledContents">
<bool>true</bool>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
</item>
<item>
@ -301,13 +286,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 +1092,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,38 +1,29 @@
-----------------------------------------------------
Features
-----------------------------------------------------
Rename dialog:
check-box for rows?
manual edit the preview?
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?
Docs:
Auto-Tagging Tips:
Multiple Passes with different options
-----------------------------------------------------
Bugs
spider-man 678 .... ascii print problem. grrrr
Bugs
-----------------------------------------------------
RAR Password -- childrens crusade 3
Zip flakes out when filename differs from index (or whatever) i.e "\" vs "/". Python issue
-----------------------------------------------------
Big Future Features
-----------------------------------------------------
Scrape alternate Covers from ComicVine issue pages
GCD scraper or DB reader
Auto search:
Searching w/o issue #
Form Mode: Single vs Batch
Batch Edit
Form Mode: Single vs Batch
-----------------------------------------------------
Small(er) Future Feature
@ -83,3 +74,4 @@ Release Process
----------------------------------------------
rename 's/([A-Za-z]+)(\d+)(.cb[rz])/$1 $2$3/' *.cb?

View File

@ -520,3 +520,49 @@ def getLanguageFromISO( iso ):
else:
return lang_dict[ iso ]
try:
from PyQt4 import QtGui
qt_available = True
except ImportError:
qt_available = False
if qt_available:
def reduceWidgetFontSize( widget , delta = 2):
f = widget.font()
if f.pointSize() > 10:
f.setPointSize( f.pointSize() - delta )
widget.setFont( f )
def centerWindowOnScreen( window ):
"""
Center the window on screen. This implemention will handle the window
being resized or the screen resolution changing.
"""
# Get the current screens' dimensions...
screen = QtGui.QDesktopWidget().screenGeometry()
# ... and get this windows' dimensions
mysize = window.geometry()
# The horizontal position is calulated as screenwidth - windowwidth /2
hpos = ( screen.width() - window.width() ) / 2
# And vertical position the same, but with the height dimensions
vpos = ( screen.height() - window.height() ) / 2
# And the move call repositions the window
window.move(hpos, vpos)
def centerWindowOnParent( window ):
top_level = window
while top_level.parent() is not None:
top_level = top_level.parent()
# Get the current screens' dimensions...
main_window_size = top_level.geometry()
# ... and get this windows' dimensions
mysize = window.geometry()
# The horizontal position is calulated as screenwidth - windowwidth /2
hpos = ( main_window_size.width() - window.width() ) / 2
# And vertical position the same, but with the height dimensions
vpos = ( main_window_size.height() - window.height() ) / 2
# And the move call repositions the window
window.move(hpos + main_window_size.left(), vpos + main_window_size.top())

View File

@ -34,6 +34,8 @@ from imagefetcher import ImageFetcher
from progresswindow import IDProgressWindow
from settings import ComicTaggerSettings
from matchselectionwindow import MatchSelectionWindow
from coverimagewidget import CoverImageWidget
import utils
class SearchThread( QtCore.QThread):
@ -90,6 +92,18 @@ class VolumeSelectionWindow(QtGui.QDialog):
uic.loadUi(os.path.join(ComicTaggerSettings.baseDir(), 'volumeselectionwindow.ui' ), self)
self.imageWidget = CoverImageWidget( self.imageContainer, CoverImageWidget.URLMode )
gridlayout = QtGui.QGridLayout( self.imageContainer )
gridlayout.addWidget( self.imageWidget )
gridlayout.setContentsMargins(0,0,0,0)
utils.reduceWidgetFontSize( self.teDetails, 1 )
utils.reduceWidgetFontSize( self.twList )
self.setWindowFlags(self.windowFlags() |
QtCore.Qt.WindowSystemMenuHint |
QtCore.Qt.WindowMaximizeButtonHint)
self.settings = settings
self.series_name = series_name
self.issue_number = issue_number
@ -108,7 +122,7 @@ class VolumeSelectionWindow(QtGui.QDialog):
self.btnAutoSelect.clicked.connect(self.autoSelect)
self.updateButtons()
self.performQuery()
self.performQuery()
self.twList.selectRow(0)
def updateButtons( self ):
@ -180,54 +194,43 @@ class VolumeSelectionWindow(QtGui.QDialog):
result = self.ii.search_result
match_index = 0
found_match = False
found_match = None
choices = False
if result == self.ii.ResultNoMatches:
QtGui.QMessageBox.information(self,"Auto-Select Result", " No matches found :-(")
elif result == self.ii.ResultFoundMatchButBadCoverScore:
QtGui.QMessageBox.information(self,"Auto-Select Result", " Found a match, but cover doesn't seem the same. Verify before commiting!")
found_match = True
found_match = matches[0]
elif result == self.ii.ResultFoundMatchButNotFirstPage :
QtGui.QMessageBox.information(self,"Auto-Select Result", " Found a match, but not with the first page of the archive.")
found_match = True
found_match = matches[0]
elif result == self.ii.ResultMultipleMatchesWithBadImageScores:
QtGui.QMessageBox.information(self,"Auto-Select Result", " Found some possibilities, but no confidence. Proceed manually.")
choices = True
elif result == self.ii.ResultOneGoodMatch:
found_match = True
found_match = matches[0]
elif result == self.ii.ResultMultipleGoodMatches:
QtGui.QMessageBox.information(self,"Auto-Select Result", " Found multiple likely matches. Please select.")
choices = True
if choices:
selector = MatchSelectionWindow( self, matches )
selector = MatchSelectionWindow( self, matches, self.comic_archive )
selector.setModal(True)
title = self.series_name
title += " #" + self.issue_number
if self.year is not None:
title += " (" + str(self.year) + ")"
title += " - "
selector.setWindowTitle( title + "Select Match")
selector.exec_()
if selector.result():
#we should now have a list index
found_match = True
match_index = selector.current_row
found_match = selector.currentMatch()
if found_match:
if found_match is not None:
self.iddialog.accept()
self.volume_id = matches[match_index]['volume_id']
self.issue_number = matches[match_index]['issue_number']
self.volume_id = found_match['volume_id']
self.issue_number = found_match['issue_number']
self.selectByID()
self.showIssues()
def showIssues( self ):
selector = IssueSelectionWindow( self, self.settings, self.volume_id, self.issue_number )
selector.setModal(True)
title = ""
for record in self.cv_search_results:
if record['id'] == self.volume_id:
@ -237,6 +240,7 @@ class VolumeSelectionWindow(QtGui.QDialog):
break
selector.setWindowTitle( title + "Select Issue")
selector.setModal( True )
selector.exec_()
if selector.result():
#we should now have a volume ID
@ -304,23 +308,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)
@ -363,21 +371,5 @@ class VolumeSelectionWindow(QtGui.QDialog):
if record['id'] == self.volume_id:
self.teDetails.setText ( record['description'] )
self.labelThumbnail.setPixmap(QtGui.QPixmap(os.path.join(ComicTaggerSettings.baseDir(), 'graphics/nocover.png' )))
url = record['image']['super_url']
self.fetcher = ImageFetcher( )
self.fetcher.fetchComplete.connect(self.finishRequest)
self.fetcher.fetch( url, user_data=record['id'] )
def finishRequest(self, image_data, user_data):
# called when the image is done loading
img = QtGui.QImage()
img.loadFromData( image_data )
self.setCover( img )
def setCover( self, img ):
self.labelThumbnail.setPixmap(QtGui.QPixmap(img))
self.imageWidget.setURL( record['image']['super_url'] )
break

View File

@ -6,8 +6,8 @@
<rect>
<x>0</x>
<y>0</y>
<width>801</width>
<height>470</height>
<width>849</width>
<height>476</height>
</rect>
</property>
<property name="windowTitle">
@ -20,7 +20,7 @@
<item row="0" column="0">
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
<widget class="QLabel" name="labelThumbnail">
<widget class="QWidget" name="imageContainer" native="true">
<property name="minimumSize">
<size>
<width>300</width>
@ -33,18 +33,6 @@
<height>450</height>
</size>
</property>
<property name="frameShape">
<enum>QFrame::Panel</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Sunken</enum>
</property>
<property name="text">
<string/>
</property>
<property name="scaledContents">
<bool>true</bool>
</property>
</widget>
</item>
<item>
@ -67,11 +55,6 @@
<height>250</height>
</size>
</property>
<property name="font">
<font>
<pointsize>9</pointsize>
</font>
</property>
<property name="selectionMode">
<enum>QAbstractItemView::SingleSelection</enum>
</property>
@ -130,11 +113,6 @@
<height>200</height>
</size>
</property>
<property name="font">
<font>
<pointsize>9</pointsize>
</font>
</property>
<property name="readOnly">
<bool>true</bool>
</property>
@ -149,7 +127,7 @@
<item>
<widget class="QPushButton" name="btnAutoSelect">
<property name="text">
<string>Auto-Select</string>
<string>Auto-Identify</string>
</property>
</widget>
</item>