diff --git a/comicarchive.py b/comicarchive.py index 91eeaf3..9752b5e 100644 --- a/comicarchive.py +++ b/comicarchive.py @@ -448,24 +448,32 @@ class ComicArchive: def getCoverPage(self): - if self.getNumberOfPages() == 0: + # assume first page is the cover (for now) + return self.getPage( 0 ) + + def getPage( self, index ): + + num_pages = self.getNumberOfPages() + if num_pages == 0 or index >= num_pages: return None # get the list file names in the archive, and sort files = self.archiver.getArchiveFilenameList() - # seems like the scanners are on Windows, and don't know about case-sensitivity! + # seems like some archive creators are on Windows, and don't know about case-sensitivity! files.sort(key=lambda x: x.lower()) - # find the first image file, assume it's the cover + # make a sub-list of image files + page_list = [] for name in files: if ( name[-4:].lower() in [ ".jpg", "jpeg", ".png" ] ): - break + page_list.append(name) - image_data = self.archiver.readArchiveFile( name ) + image_data = self.archiver.readArchiveFile( page_list[index] ) return image_data + def getNumberOfPages(self): count = 0 diff --git a/ctversion.py b/ctversion.py new file mode 100644 index 0000000..09d629e --- /dev/null +++ b/ctversion.py @@ -0,0 +1,3 @@ +# This file should contan only these comments, and the line below. +# Used by packaging makefiles and app +version="0.0.1" \ No newline at end of file diff --git a/options.py b/options.py index 97803aa..72084df 100644 --- a/options.py +++ b/options.py @@ -20,6 +20,7 @@ limitations under the License. import sys import getopt +import platform class Enum(set): def __getattr__(self, name): @@ -47,7 +48,7 @@ class Options: def parseCmdLineArgs(self): # mac no likey this from .app bundle - if getattr(sys, 'frozen', None): + if platform.system() == "Darwin" and getattr(sys, 'frozen', None): return # parse command line options @@ -83,9 +84,8 @@ class Options: print( __doc__ ) sys.exit(0) - # process arguments - for arg in args: - process(arg) # process() is defined elsewhere - + if self.filename == "" and len(args) > 0: + self.filename = args[0] + return opts \ No newline at end of file diff --git a/pagebrowser.py b/pagebrowser.py new file mode 100644 index 0000000..506f5f2 --- /dev/null +++ b/pagebrowser.py @@ -0,0 +1,100 @@ +""" +A PyQT4 dialog to show pages of a comic archive +""" + +""" +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 PageBrowserWindow(QtGui.QDialog): + + + def __init__(self, parent): + super(PageBrowserWindow, self).__init__(None) + + uic.loadUi(os.path.join(ComicTaggerSettings.baseDir(), 'pagebrowser.ui' ), self) + + self.lblPage.setPixmap(QtGui.QPixmap(os.path.join(ComicTaggerSettings.baseDir(), 'nocover.png' ))) + self.lblPage.setSizePolicy(QtGui.QSizePolicy.Ignored, QtGui.QSizePolicy.Ignored) + self.comic_archive = None + self.current_pixmap = None + self.page_count = 0 + self.current_page_num = 0 + + self.btnNext.clicked.connect( self.nextPage ) + self.btnPrev.clicked.connect( self.prevPage ) + self.show() + + def setComicArchive(self, ca): + + self.comic_archive = ca + self.page_count = ca.getNumberOfPages() + self.current_page_num = 0 + + self.setPage() + + def nextPage(self): + + if self.current_page_num + 1 < self.page_count: + self.current_page_num += 1 + self.setPage() + + def prevPage(self): + + if self.current_page_num - 1 >= 0: + self.current_page_num -= 1 + self.setPage() + + def setPage( self ): + image_data = self.comic_archive.getPage( self.current_page_num ) + + if image_data is not None: + self.setCurrentPixmap( image_data ) + self.setDisplayPixmap( 0, 0) + self.setWindowTitle("Page Browser - Page {0} (of {1}) ".format(self.current_page_num+1, self.page_count ) ) + + 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 ) + + + \ No newline at end of file diff --git a/pagebrowser.ui b/pagebrowser.ui new file mode 100644 index 0000000..cc1f499 --- /dev/null +++ b/pagebrowser.ui @@ -0,0 +1,129 @@ + + + dialogPageBrowser + + + + 0 + 0 + 429 + 637 + + + + + 0 + 0 + + + + Page Browser + + + + + + + + + 0 + 0 + + + + + 100 + 150 + + + + QFrame::Box + + + QFrame::Sunken + + + + + + false + + + Qt::AlignCenter + + + + + + + QLayout::SetMaximumSize + + + + + << + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Close + + + true + + + + + + + >> + + + + + + + + + + + + + buttonBox + accepted() + dialogPageBrowser + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + dialogPageBrowser + reject() + + + 316 + 260 + + + 286 + 274 + + + + + diff --git a/settings.py b/settings.py index 87de3c3..a4bad7c 100644 --- a/settings.py +++ b/settings.py @@ -44,10 +44,12 @@ class ComicTaggerSettings: @staticmethod def baseDir(): - if getattr(sys, 'frozen', None): - return sys._MEIPASS + if platform.system() == "Darwin" and getattr(sys, 'frozen', None): + return sys._MEIPASS + elif platform.system() == "Windows": + return "." else: - return os.path.dirname(__file__) + return os.path.dirname(__file__) def __init__(self): diff --git a/tagger.py b/tagger.py index 3a029d6..41d6e3d 100755 --- a/tagger.py +++ b/tagger.py @@ -23,6 +23,8 @@ limitations under the License. import sys import signal import os +import traceback +import time from PyQt4 import QtCore, QtGui @@ -82,10 +84,22 @@ def main(): app = QtGui.QApplication(sys.argv) - tagger_window = TaggerWindow( opts, settings ) - tagger_window.show() - sys.exit(app.exec_()) + img = QtGui.QPixmap(os.path.join(ComicTaggerSettings.baseDir(), 'graphics/tags.png' )) + splash = QtGui.QSplashScreen(img) + splash.show() + splash.raise_() + app.processEvents() + app.processEvents() + try: + tagger_window = TaggerWindow( opts, settings ) + tagger_window.show() + splash.finish( tagger_window ) + sys.exit(app.exec_()) + except Exception, e: + QtGui.QMessageBox.critical(QtGui.QMainWindow(), "Error", "Unhandled exception in app:\n" + traceback.format_exc() ) + + if __name__ == "__main__": main() diff --git a/taggerwindow.py b/taggerwindow.py index e1f1970..db4ef96 100644 --- a/taggerwindow.py +++ b/taggerwindow.py @@ -19,8 +19,11 @@ limitations under the License. """ from PyQt4 import QtCore, QtGui, uic +from PyQt4.QtCore import QUrl,pyqtSignal + import locale import platform +import os from volumeselectionwindow import VolumeSelectionWindow from options import Options, MetaDataStyle @@ -30,17 +33,39 @@ from comicarchive import ComicArchive from crediteditorwindow import CreditEditorWindow from settingswindow import SettingsWindow from settings import ComicTaggerSettings +from pagebrowser import PageBrowserWindow import utils +import ctversion # this reads the environment and inits the right locale locale.setlocale(locale.LC_ALL, "") -import os +# 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" - version = "1.0" # TODO read in from a file?? + version = ctversion.version def __init__(self, opts, settings, parent = None): super(TaggerWindow, self).__init__(parent) @@ -69,6 +94,7 @@ class TaggerWindow( QtGui.QMainWindow): self.setAcceptDrops(True) self.droppedFile=None + self.page_browser = None self.populateComboBoxes() @@ -78,6 +104,7 @@ 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.updateStyleTweaks() @@ -260,6 +287,9 @@ class TaggerWindow( QtGui.QMainWindow): img.loadFromData( image_data ) self.lblCover.setPixmap(QtGui.QPixmap(img)) self.lblCover.setScaledContents(True) + + if self.page_browser is not None: + self.page_browser.setComicArchive( self.comic_archive ) self.metadataToForm() self.clearDirtyFlag() # also updates the app title @@ -565,6 +595,7 @@ class TaggerWindow( QtGui.QMainWindow): selector = VolumeSelectionWindow( self, self.settings.cv_api_key, series_name, issue_number, self.comic_archive, self.settings ) selector.setModal(True) selector.exec_() + if selector.result(): #we should now have a volume ID @@ -848,3 +879,13 @@ class TaggerWindow( QtGui.QMainWindow): else: event.ignore() + def showPageBrowser( self ): + if self.page_browser is None: + self.page_browser = PageBrowserWindow( self ) + if self.comic_archive is not None: + self.page_browser.setComicArchive( self.comic_archive ) + self.page_browser.finished.connect(self.pageBrowserClosed) + + def pageBrowserClosed( self ): + self.page_browser = None + \ No newline at end of file diff --git a/taggerwindow.ui b/taggerwindow.ui index 5d257de..02b0a1d 100644 --- a/taggerwindow.ui +++ b/taggerwindow.ui @@ -6,10 +6,16 @@ 0 0 - 943 - 507 + 924 + 549 + + + 0 + 0 + + ComicTagger @@ -20,672 +26,740 @@ 0 - - - - 20 - 150 - 200 - 300 - - - - QFrame::Panel - - - QFrame::Sunken - - - - - - true - - - - - - 20 - 20 - 211 - 32 - - - - - - - Tag Style: - - - - - - - - - - - - 250 - 10 - 671 - 431 - - - - - QLayout::SetDefaultConstraint - - - - - 0 - - - - Details - - - - - 10 - 0 - 371 - 284 - - - - - QFormLayout::AllNonFixedFieldsGrow + + + + + + + + + Qt::AlignHCenter|Qt::AlignTop - + - Series + Tag Style: - - - - - - Title - - - - - - - - - - Publisher - - - - - - - - - - - - - Series Group - - - - - - - - - - Imprint - - - - - - - - - - Story Arc - - - - - - - - - - Genre - - - - - - - Format - - - - - + - - - - - 390 - 0 - 151 - 140 - - - - - QFormLayout::AllNonFixedFieldsGrow + + + + + + 0 + 0 + - - - - Issue - - - - - - - - - - # Issues - - - - - - - - - - Volume - - - - - - - - - - # Volumes - - - - - - - - - - - - 390 - 140 - 112 - 71 - - - - - QFormLayout::AllNonFixedFieldsGrow + + QFrame::Panel - - - - - - - Month - - - - - - - - - - Year - - - - - - - - - 390 - 210 - 251 - 81 - - - - - - - - - - Language - - - - - - - - - - Country - - - - - - - - - 550 - 0 - 107 - 120 - - - - - QFormLayout::AllNonFixedFieldsGrow + + QFrame::Sunken - - QFormLayout::WrapAllRows - - - - - Maturity Rating - - - - - - - Critical Rating - - - - - - - - - - true - - - true - - - - - - - - - 10 - 290 - 371 - 111 - - - - - QFormLayout::AllNonFixedFieldsGrow - - - - - Alt. Series - - - - - - - - - - Alt. Issue - - - - - - - - - - Alt. # Issues - - - - - - - - - - - - 390 - 300 - 211 - 71 - - - - - QFormLayout::AllNonFixedFieldsGrow - - - - - true - - - - - - - Manga - - - - - - - true - - - Black && White - - - - - - - - - Credits - - - - - 30 - 350 - 481 - 32 - - - - - QFormLayout::ExpandingFieldsGrow - - - - - Scan Info - - - - - - - - - - - - 30 - 10 - 481 - 321 - - - - QAbstractItemView::SingleSelection - - - QAbstractItemView::SelectRows - - - 0 - - - 2 - - - true - - - false - - - Credit + + + + + + + + + 0 + 0 + + + + + 200 + 300 + + + + + 200 + 300 + + + + QFrame::Panel + + + QFrame::Sunken - - - Name + - - - - - - 520 - 20 - 121 - 30 - - - - Add Credit - - - - - - 520 - 60 - 121 - 30 - - - - Remove Credit - - - - - - 520 - 100 - 121 - 30 - - - - Edit Credit - - - - - - Notes - - - - - 30 - 10 - 591 - 371 - - - - - QFormLayout::ExpandingFieldsGrow + + true - - - - Comments - - - - - - - Notes - - - - - - - - - - - - - - - - Web - - - - - - - - - Other - - - - - 20 - 17 - 581 - 371 - - - - - QFormLayout::AllNonFixedFieldsGrow + + Qt::AlignCenter - - - - Teams + + + + + + + + QLayout::SetDefaultConstraint + + + + + 0 + + + + Details + + + + + 10 + 0 + 371 + 284 + + + + QLayout::SetNoConstraint + + + QFormLayout::AllNonFixedFieldsGrow + + + + + Series + + + + + + + + + + Title + + + + + + + + + + Publisher + + + + + + + + + + + + + Series Group + + + + + + + + + + Imprint + + + + + + + + + + Story Arc + + + + + + + + + + Genre + + + + + + + Format + + + + + + + - - - - - Characters + + + + 390 + 0 + 151 + 140 + + + + QFormLayout::AllNonFixedFieldsGrow + + + + + Issue + + + + + + + + + + # Issues + + + + + + + + + + Volume + + + + + + + + + + # Volumes + + + + + + + - - - - - Locations + + + + 390 + 140 + 112 + 71 + + + + QFormLayout::AllNonFixedFieldsGrow + + + + + + + + Month + + + + + + + + + + Year + + + + - - - - - Other Tags + + + + 390 + 210 + 251 + 94 + + + + QFormLayout::AllNonFixedFieldsGrow + + + + + + + + Country + + + + + + + + + + Language + + + + - - - - - - - - - - - - - - - - - - - Pages - - - - - - - - - - 20 - 60 - 211 - 81 - - - - QFrame::Panel - - - QFrame::Sunken - - - - - + + + + 550 + 0 + 107 + 120 + + + + + QFormLayout::AllNonFixedFieldsGrow + + + QFormLayout::WrapAllRows + + + + + Maturity Rating + + + + + + + Critical Rating + + + + + + + + + + true + + + true + + + + + + + + + 10 + 290 + 371 + 111 + + + + + QFormLayout::AllNonFixedFieldsGrow + + + + + Alt. Series + + + + + + + + + + Alt. Issue + + + + + + + + + + Alt. # Issues + + + + + + + + + + + + 390 + 300 + 211 + 71 + + + + + QFormLayout::AllNonFixedFieldsGrow + + + + + true + + + + + + + Manga + + + + + + + true + + + Black && White + + + + + + + + + Credits + + + + + + + + + + QAbstractItemView::SingleSelection + + + QAbstractItemView::SelectRows + + + 0 + + + 2 + + + true + + + false + + + + Credit + + + + + Name + + + + + + + + QFormLayout::ExpandingFieldsGrow + + + + + Scan Info + + + + + + + + + + + + + + + + Add Credit + + + + + + + Remove Credit + + + + + + + Edit Credit + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + + + Notes + + + + + + QFormLayout::ExpandingFieldsGrow + + + + + Comments + + + + + + + + + + Notes + + + + + + + + + + Web + + + + + + + + + + + + + Other + + + + + + QFormLayout::ExpandingFieldsGrow + + + + + Characters + + + + + + + true + + + + 0 + 0 + + + + + 0 + 0 + + + + + 16777215 + 16777215 + + + + + + + + Teams + + + + + + + + 0 + 0 + + + + + 16777215 + 16777215 + + + + + + + + Locations + + + + + + + + 0 + 0 + + + + + 16777215 + 16777215 + + + + + + + + Other Tags + + + + + + + + 0 + 0 + + + + + 16777215 + 16777215 + + + + + 0 + 100 + + + + + + + + + + + Pages + + + + + + + + + 0 0 - 943 - 25 + 924 + 28 diff --git a/todo.txt b/todo.txt index 74cb356..df019fc 100644 --- a/todo.txt +++ b/todo.txt @@ -9,10 +9,13 @@ Infobox needs fixing up Page Browser, mode-less dialog -Multi-match dialog - -More auto-select logic using metadata - +Auto-select: + msgbox on autoselect failure, or warning + Multi-match dialog + More auto-select logic using metadata + Page search on only match, but bad image match, to find cover?? + Check aspect ratio, and maybe break cover into two parts for hashing? + Stand-alone CLI Info dump optionless args @@ -53,7 +56,8 @@ Lots of error checking Other settings possibilities: Last tag style Last "Open" folder (include dragged) - Keep a history of queries somewhere?? + Clear caches + Content Hashes!! diff --git a/volumeselectionwindow.py b/volumeselectionwindow.py index ac55722..156723f 100644 --- a/volumeselectionwindow.py +++ b/volumeselectionwindow.py @@ -101,13 +101,9 @@ class VolumeSelectionWindow(QtGui.QDialog): self.btnIssues.clicked.connect(self.showIssues) self.btnAutoSelect.clicked.connect(self.autoSelect) - self.show() - QtCore.QCoreApplication.processEvents() - self.performQuery() self.twList.selectRow(0) - def requery( self, ): self.performQuery( refresh=True ) self.twList.selectRow(0) @@ -188,9 +184,10 @@ class VolumeSelectionWindow(QtGui.QDialog): self.search_thread.progressUpdate.connect( self.searchProgressUpdate ) self.search_thread.start() - QtCore.QCoreApplication.processEvents() + #QtCore.QCoreApplication.processEvents() self.progdialog.exec_() + def searchCanceled( self ): print "query cancelled" self.search_thread.searchComplete.disconnect( self.searchComplete ) diff --git a/windows/Makefile b/windows/Makefile index d5ac5cb..37f72e6 100644 --- a/windows/Makefile +++ b/windows/Makefile @@ -1,25 +1,37 @@ +# This Makefile expects that certain GNU utils are available: +# rm, cp, grep, cut, cat + TAGGER_BASE:= c:\Users\tony\Dropbox\tagger\comictagger DIST_DIR := $(TAGGER_BASE)\windows\dist NSIS_CMD := "C:\Program Files (x86)\NSIS\makensis.exe" +VERSION := $(shell grep version "$(TAGGER_BASE)\ctversion.py" | cut -d= -f2) -all: clean +all: clean dist package -win_dist: +dist: cd "$(TAGGER_BASE)" & - "C:\Python27\Scripts\cxfreeze.bat" $(TAGGER_BASE)\tagger.py --icon nsis\app.ico --base-name=Win32GUI + "C:\Python27\Scripts\cxfreeze.bat" $(TAGGER_BASE)\tagger.py --icon nsis\app.ico +# --base-name=Win32GUI cp -R C:\Python27\Lib\site-packages\PyQt4\plugins\imageformats $(DIST_DIR) cp "$(TAGGER_BASE)\UnRAR2\UnRARDLL\unrar.dll" $(DIST_DIR) cp "$(TAGGER_BASE)\*.ui" $(DIST_DIR) cp "$(TAGGER_BASE)\nocover.png" $(DIST_DIR) cp "$(TAGGER_BASE)\app.png" $(DIST_DIR) + cp -r "$(TAGGER_BASE)\graphics" $(DIST_DIR) rm "$(DIST_DIR)\QtWebKit4.dll" rm "$(DIST_DIR)\PyQt4.QtWebKit.pyd" +package: + echo !define RELEASE_STR $(VERSION) > $(TAGGER_BASE)\windows\nsis\release.nsh $(NSIS_CMD) "$(TAGGER_BASE)\windows\nsis\comictagger.nsi" mv "$(TAGGER_BASE)\windows\nsis\ComicTagger*.exe" "$(TAGGER_BASE)\windows" clean: - rm -rf dist + -rm -rf dist -rm -f "*~" *.pyc *.pyo - -rm "$(TAGGER_BASE)\windows\*.exe" + -rm -f "$(TAGGER_BASE)\windows\*.exe" + +test: + echo !define RELEASE_STR $(VERSION) > test.nsh + \ No newline at end of file diff --git a/windows/build.bat b/windows/build.bat index 63e0e1d..c176ce0 100644 --- a/windows/build.bat +++ b/windows/build.bat @@ -1 +1 @@ -make clean win_dist \ No newline at end of file +make clean dist package \ No newline at end of file diff --git a/windows/nsis/release.nsh b/windows/nsis/release.nsh deleted file mode 100644 index 0f9f9b5..0000000 --- a/windows/nsis/release.nsh +++ /dev/null @@ -1 +0,0 @@ -!define RELEASE_STR "1.0"