From f45e7b887fc0d30979c7c88a0dc2cc94ab3edb1f Mon Sep 17 00:00:00 2001 From: "beville@gmail.com" Date: Mon, 19 Nov 2012 01:01:17 +0000 Subject: [PATCH] First cut at real CLI feature git-svn-id: http://comictagger.googlecode.com/svn/trunk@58 6c5673fe-1810-88d6-992b-cd32ca31540c --- genericmetadata.py | 67 +++++++++++++++-- issueidentifier.py | 1 - options.py | 138 ++++++++++++++++++++++++++--------- pagebrowser.py | 1 - tagger.py | 126 +++++++++++++++++++++++--------- taggerwindow.py | 175 ++------------------------------------------- todo.txt | 18 ++--- 7 files changed, 274 insertions(+), 252 deletions(-) diff --git a/genericmetadata.py b/genericmetadata.py index 38e4ba7..74476b1 100644 --- a/genericmetadata.py +++ b/genericmetadata.py @@ -23,6 +23,8 @@ See the License for the specific language governing permissions and limitations under the License. """ +import utils + # These page info classes are exactly the same as the CIX scheme, since it's unique class PageType: FrontCover = "FrontCover" @@ -68,7 +70,7 @@ class GenericMetadata: self.comments = None # use same way as Summary in CIX self.volumeCount = None - self.criticalRating = None + self.criticalRating = None self.country = None self.alternateSeries = None @@ -94,8 +96,7 @@ class GenericMetadata: self.credits = list() self.tags = list() self.pages = list() - - + def addCredit( self, person, role, primary = False ): credit = dict() @@ -105,5 +106,61 @@ class GenericMetadata: credit['primary'] = primary self.credits.append(credit) - - \ No newline at end of file + + + def __str__( self ): + vals = [] + if self.isEmpty: + return "No metadata" + + def add( tag, val ): + if val is not None and str(val) != "": + vals.append( (tag, val) ) + + add( "series", self.series ) + add( "issue number", self.issueNumber ) + add( "issue count", self.issueCount ) + add( "title", self.title ) + add( "publisher", self.publisher ) + add( "month", self.publicationMonth ) + add( "year", self.publicationYear ) + add( "volume number", self.volumeNumber ) + add( "volume count", self.volumeCount ) + add( "genre", self.genre ) + add( "language", self.language ) + add( "country", self.country ) + add( "user rating", self.criticalRating ) + add( "alt. series", self.alternateSeries ) + add( "alt. number", self.alternateNumber ) + add( "alt. count", self.alternateCount ) + add( "imprint", self.imprint ) + add( "web", self.webLink ) + add( "format", self.format ) + add( "manga", self.manga ) + add( "B&W", self.blackAndWhite ) + add( "age rating", self.maturityRating ) + add( "story arc", self.storyArc ) + add( "series group", self.seriesGroup ) + add( "scan info", self.scanInfo ) + add( "characters", self.characters ) + add( "teams", self.teams ) + add( "locations", self.locations ) + add( "comments", self.comments ) + add( "notes", self.notes ) + add( "tags", utils.listToString( self.tags ) ) + for c in self.credits: + add( "credit", c['role']+": "+c['person'] ) + + + # find the longest field name + flen = 0 + for i in vals: + flen = max( flen, len(i[0]) ) + flen += 1 + + #format the data nicely + outstr = "" + for i in vals: + outstr += ("{0: <" + str(flen) + "}: {1}\n").format( i[0], i[1] ) + + return outstr diff --git a/issueidentifier.py b/issueidentifier.py index d01e90a..9d89fb1 100644 --- a/issueidentifier.py +++ b/issueidentifier.py @@ -261,7 +261,6 @@ class IssueIdentifier: # remove any series that starts after the issue year if keys['year'] is not None and keys['year'].isdigit(): - print "ATB", keys['year'] , item['start_year'] if int(keys['year']) < item['start_year']: date_approved = False diff --git a/options.py b/options.py index 72084df..1cdc813 100644 --- a/options.py +++ b/options.py @@ -21,6 +21,7 @@ limitations under the License. import sys import getopt import platform +import os class Enum(set): def __getattr__(self, name): @@ -35,57 +36,128 @@ class MetaDataStyle: class Options: + help_text = """ +Usage: {0} [OPTION]... [FILE] + +A utility for read and writing metadata to comic archives. + +If no options are given, {0} will run in windowed mode + + -p, --print Print out tag info from file. Specify type + (via -t) to get only info of that tag type + -d, --delete Deletes the tag block of specified type (via -t) + -s, --save Save out tags as specified type (via -t) + Must specify also at least -o, -p, or -m + -n, --dryrun Don't actually modify file (only relevent for -d, -s, or -r) + -t, --type=TYPE Specify TYPE as either "CR" or "CBL", (as either + ComicRack or ComicBookLover style tags, respectivly) + -f, --parsefilename Parse the filename to get some info, specifically + series name, issue number, volume, and publication + year + -o, --online Search online and attempt to identify file using + existing metadata and images in archive. May be used + in conjuntion with -p and -m + -m, --metadata=LIST Explicity define some tags to be used as a list + ....TBD........ + ....TBD........ + -r, --rename Rename the file based on metadata as indicated. TBD! + -a, --abort Abort save operation when online match is of low confidence TBD! + -v, --verbose Be noisy when doing what it does + -h, --help Display this message + """ + def __init__(self): - self.data_style = MetaDataStyle.CIX + self.data_style = None self.no_gui = False + self.filename = None + self.verbose = False + self.md_settings = None + self.print_tags = False + self.delete_tags = False + self.search_online = False + self.dryrun = True # keep this true for now! + self.save_tags = False + self.parse_filename = False + self.rename_file = False + + def display_help_and_quit( self, msg, code ): + appname = os.path.basename(sys.argv[0]) + if msg is not None: + print( msg ) + print self.help_text.format(appname) + sys.exit(code) + - self.series_name = '' - self.issue_number = '' - self.filename = '' - self.image_hasher = 1 - def parseCmdLineArgs(self): + # mac no likey this from .app bundle if platform.system() == "Darwin" and getattr(sys, 'frozen', None): return + # parse command line options try: - opts, args = getopt.getopt(sys.argv[1:], "cht:s:i:vf:m:", ["cli", "help", "type=", "series=", "issue=", "verbose", "file", "imagehasher=" ]) - except (getopt.error, msg): - print( msg ) - print( "for help use --help" ) - sys.exit(2) + opts, args = getopt.getopt(sys.argv[1:], + "hpdt:fm:vonsr", + [ "help", "print", "delete", "type=", "parsefilename", "metadata=", "verbose", "online", "dryrun", "save", "rename" ]) + + except getopt.GetoptError as err: + self.display_help_and_quit( str(err), 2 ) + # process options for o, a in opts: if o in ("-h", "--help"): - print( __doc__ ) - sys.exit(0) + self.display_help_and_quit( None, 0 ) if o in ("-v", "--verbose"): - print( "Verbose output!" ) - if o in ("-c", "--cli"): - self.no_gui = True - if o in ("-m", "--imagehasher"): - self.image_hasher = a - if o in ("-s", "--series"): - self.series_name = a - if o in ("-i", "--issue"): - self.issue_number = a - if o in ("-f", "--file"): - self.filename = a + self.verbose = True + if o in ("-p", "--print"): + self.print_tags = True + if o in ("-d", "--delete"): + self.delete_tags = True + if o in ("-o", "--online"): + self.search_online = True + if o in ("-n", "--dryrun"): + self.dryrun = True + if o in ("-m", "--metadata"): + self.md_settings = a + if o in ("-s", "--save"): + self.save_tags = True + if o in ("-r", "--rename"): + self.rename_file = True + if o in ("-f", "--parsefilename"): + self.parse_filename = True if o in ("-t", "--type"): - if a == "cr": + if a.lower() == "cr": self.data_style = MetaDataStyle.CIX - elif a == "cbl": + elif a.lower() == "cbl": self.data_style = MetaDataStyle.CBI else: - print( __doc__ ) - sys.exit(0) - - if self.filename == "" and len(args) > 0: - self.filename = args[0] + self.display_help_and_quit( "Invalid tag type", 1 ) - return opts - \ No newline at end of file + if self.print_tags or self.delete_tags or self.save_tags or self.rename_file: + self.no_gui = True + + count = 0 + if self.print_tags: count += 1 + if self.delete_tags: count += 1 + if self.save_tags: count += 1 + if self.rename_file: count += 1 + + if count > 1: + self.display_help_and_quit( "Must choose only one action of print, delete, save, or rename", 1 ) + + if len(args) > 0: + self.filename = args[0] + + if self.no_gui and self.filename is None: + self.display_help_and_quit( "Command requires a filename!", 1 ) + + if self.delete_tags and self.data_style is None: + self.display_help_and_quit( "Please specify the type to delete with -t", 1 ) + + if self.save_tags and self.data_style is None: + self.display_help_and_quit( "Please specify the type to save with -t", 1 ) + + diff --git a/pagebrowser.py b/pagebrowser.py index 0c9c337..d714931 100644 --- a/pagebrowser.py +++ b/pagebrowser.py @@ -26,7 +26,6 @@ from settings import ComicTaggerSettings class PageBrowserWindow(QtGui.QDialog): - def __init__(self, parent): super(PageBrowserWindow, self).__init__(parent) diff --git a/tagger.py b/tagger.py index d116bfc..13bac9a 100755 --- a/tagger.py +++ b/tagger.py @@ -40,30 +40,102 @@ import utils #----------------------------- def cli_mode( opts, settings ): + if opts.filename is None: + return ca = ComicArchive(opts.filename) + if settings.rar_exe_path != "": + ca.setExternalRarProgram( settings.rar_exe_path ) + if not ca.seemsToBeAComicArchive(): print "Sorry, but "+ opts.filename + " is not a comic archive!" return - - ii = IssueIdentifier( ca, settings.cv_api_key ) - matches = ii.search() - - """ - if len(matches) == 1: - - # now get the particular issue data - metadata = comicVine.fetchIssueData( match[0]['series'], match[0]['issue_number'] ) - - # write out the new data - ca.writeMetadata( metadata, opts.data_style ) - - elif len(matches) == 0: - pass - elif len(matches) == 0: - # print match options, with CV issue ID's - pass - """ + cix = False + cbi = False + if ca.hasCIX(): cix = True + if ca.hasCBI(): cbi = True + + if opts.print_tags: + + if opts.data_style is None: + page_count = ca.getNumberOfPages() + + brief = "" + if ca.isZip(): brief = "ZIP archive " + elif ca.isRar(): brief = "RAR archive " + elif ca.isFolder(): brief = "Folder archive " + + brief += "({0: >3} pages)".format(page_count) + brief += " tags:[ " + + if not (cbi or cix): + brief += "none" + else: + if cbi: brief += "CBL " + if cix: brief += "CR " + brief += "]" + + print brief + print + + if opts.data_style is None or opts.data_style == MetaDataStyle.CIX: + if cix: + print "------ComicRack tags--------" + print ca.readCIX() + if opts.data_style is None or opts.data_style == MetaDataStyle.CBI: + if cbi: + print "------ComicBookLover tags--------" + print ca.readCBI() + + + elif opts.delete_tags: + if not ca.isWritable(): + print "This archive is not writable." + return + + if opts.data_style == MetaDataStyle.CIX: + if cix: + ca.removeCIX() + print "Removed ComicRack tags." + else: + print "This archive doesn't have ComicRack tags." + + if opts.data_style == MetaDataStyle.CBI: + if cbi: + ca.removeCBI() + print "Removed ComicBookLover tags." + else: + print "This archive doesn't have ComicBookLover tags." + + #elif opt.rename: + # print "Gonna rename file" + + elif opts.save_tags: + if opts.data_style == MetaDataStyle.CIX: + print "Gonna save ComicRack tags" + if opts.data_style == MetaDataStyle.CBI: + print "Gonna save ComicBookLover tags" + + """ + ii = IssueIdentifier( ca, settings.cv_api_key ) + matches = ii.search() + + + if len(matches) == 1: + + # now get the particular issue data + metadata = comicVine.fetchIssueData( match[0]['series'], match[0]['issue_number'] ) + + # write out the new data + ca.writeMetadata( metadata, opts.data_style ) + + elif len(matches) == 0: + pass + + elif len(matches) == 0: + # print match options, with CV issue ID's + pass + """ #----------------------------- def main(): @@ -90,22 +162,8 @@ def main(): splash.raise_() app.processEvents() - """ - lw = QtGui.QListWidget() - icon = QtGui.QIcon('app.png') - for i in range(10): - lw.addItem( QtGui.QListWidgetItem( icon, "Item {0}".format(i) ) ) - - lw.setDragDropMode(QtGui.QAbstractItemView.InternalMove) - #lw.setViewMode(QtGui.QListView.IconMode) - lw.setMovement(QtGui.QListView.Snap) - lw.setGridSize(QtCore.QSize(100,100)) - lw.show() - sys.exit(app.exec_()) - """ - try: - tagger_window = TaggerWindow( opts, settings ) + tagger_window = TaggerWindow( opts.filename, settings ) tagger_window.show() splash.finish( tagger_window ) sys.exit(app.exec_()) diff --git a/taggerwindow.py b/taggerwindow.py index 5baed15..82a9dcc 100644 --- a/taggerwindow.py +++ b/taggerwindow.py @@ -27,7 +27,7 @@ import platform import os from volumeselectionwindow import VolumeSelectionWindow -from options import Options, MetaDataStyle +from options import MetaDataStyle from comicinfoxml import ComicInfoXml from genericmetadata import GenericMetadata from comicvinetalker import ComicVineTalker @@ -64,162 +64,6 @@ def clickable(widget): widget.installEventFilter(filter) return filter.dblclicked -""" -class PageTableModel(QtCore.QAbstractTableModel): - - def __init__(self, comic_archive, parent=None, *args): - QtCore.QAbstractTableModel.__init__(self, parent, *args) - - self.comic_archive = comic_archive - page_list = comic_archive.getPageNameList() - - self.page_model = [] - i = 0 - for page in page_list: - item = dict() - item['number'] = i - item['filename'] = page - item['thumb'] = None - - self.page_model.append( item ) - i +=1 - - - def rowCount(self, parent): - return len(self.page_model) - - def columnCount(self, parent): - return 3 - - def data(self, index, role): - - if not index.isValid(): - return QtCore.QVariant() - - elif role == QtCore.Qt.DisplayRole: - # page num - if index.column() == 0: - return QtCore.QVariant(self.page_model[index.row()]['number']) - - # page filename - if index.column() == 1: - return QtCore.QVariant(self.page_model[index.row()]['filename']) - - elif role == QtCore.Qt.DecorationRole: - - if index.column() == 2: - if self.page_model[index.row()]['thumb'] is None: - - image_data = self.comic_archive.getPage( self.page_model[index.row()]['number'] ) - img = QtGui.QImage() - img.loadFromData( image_data ) - pixmap = QtGui.QPixmap(QtGui.QPixmap(img)) - #scaled_pixmap = pixmap.scaled(100, 150, QtCore.Qt.KeepAspectRatio) - - self.page_model[index.row()]['thumb'] = pixmap #scaled_pixmap - - return QtCore.QVariant(self.page_model[index.row()]['thumb']) - - else: - return QtCore.QVariant() -""" -class PageListModel(QtCore.QAbstractListModel): - - def __init__(self, comic_archive, parent=None, *args): - QtCore.QAbstractTableModel.__init__(self, parent, *args) - - self.comic_archive = comic_archive - page_list = comic_archive.getPageNameList() - - self.page_model = [] - i = 0 - for page in page_list: - item = dict() - item['number'] = i - item['filename'] = page - item['thumb'] = None - - self.page_model.append( item ) - i +=1 - - def rowCount(self, parent): - return len(self.page_model) - - def data(self, index, role): - - if not index.isValid(): - return QtCore.QVariant() - - elif role == QtCore.Qt.DisplayRole: - # page num - return QtCore.QVariant(self.page_model[index.row()]['number']) - - elif role == QtCore.Qt.DecorationRole: - - if self.page_model[index.row()]['thumb'] is None: - - #timestamp = datetime.datetime.now() - - image_data = self.comic_archive.getPage( self.page_model[index.row()]['number'] ) - img = QtGui.QImage() - img.loadFromData( image_data ) - pixmap = QtGui.QPixmap(QtGui.QPixmap(img)) - scaled_pixmap = pixmap.scaled(100, 150, QtCore.Qt.KeepAspectRatio) - - self.page_model[index.row()]['thumb'] = scaled_pixmap - - return QtCore.QVariant(self.page_model[index.row()]['thumb']) - - else: - return QtCore.QVariant() - - def flags( self, index): - defaultFlags = QtCore.QAbstractTableModel.flags(self, index) - if index.isValid(): - return QtCore.Qt.ItemIsDragEnabled | QtCore.Qt.ItemIsDropEnabled | defaultFlags - else: - return QtCore.Qt.ItemIsDropEnabled | defaultFlags - - def removeRows(self, row, count, parent=QtCore.QModelIndex()): - - print "removeRows", row, count - return True - - def insertRows(self, row, count, parent=QtCore.QModelIndex()): - - print "insertRows", row, count - return False - - def beginRemoveRows(self, sourceParent, start, end, destinationParent, dest): - print "beginRemoveRows" - - def dropMimeData(self,data, action, row, col, parent): - print "dropMimeData", action, row, col - - - if (row != -1): - beginRow = row - - elif (parent.isValid()): - beginRow = parent.row() - - print beginRow - - return True - if (action == QtCore.Qt.IgnoreAction): - return True - - #if ( not data.hasFormat("application/vnd.text.list")) - # return False - - if (column > 0): - return False - #def beginMoveRows(self, sourceParent, start, end, destinationParent, dest): - # print "rowsMoved" - - def supportedDropActions(self): - #print "supportedDropActions" - return QtCore.Qt.CopyAction | QtCore.Qt.MoveAction class TaggerWindow( QtGui.QMainWindow): @@ -227,7 +71,7 @@ class TaggerWindow( QtGui.QMainWindow): appName = "ComicTagger" version = ctversion.version - def __init__(self, opts, settings, parent = None): + def __init__(self, filename, settings, parent = None): super(TaggerWindow, self).__init__(parent) uic.loadUi(os.path.join(ComicTaggerSettings.baseDir(), 'taggerwindow.ui' ), self) @@ -240,9 +84,8 @@ class TaggerWindow( QtGui.QMainWindow): #print platform.system(), platform.release() self.dirtyFlag = False - self.opts = opts self.settings = settings - self.data_style = opts.data_style + self.data_style = MetaDataStyle.CIX #set up a default metadata object self.metadata = GenericMetadata() @@ -270,16 +113,8 @@ class TaggerWindow( QtGui.QMainWindow): self.updateStyleTweaks() - - self.openArchive( opts.filename ) - - # fill in some explicit metadata stuff from our options - # this overrides what we just read in - if self.metadata.series is None: - self.metadata.series = opts.series_name - if self.metadata.issueNumber is None: - self.metadata.issueNumber = opts.issue_number - + if filename is not None: + self.openArchive( filename ) def updateAppTitle( self ): diff --git a/todo.txt b/todo.txt index ee8efad..3d51762 100644 --- a/todo.txt +++ b/todo.txt @@ -1,11 +1,9 @@ ----------------- Features ---------------- -Stand-alone CLI - Info dump - optionless args - remove tags - copy tags +CLI + rename + save Settings/Preferences Dialog Remove API Key @@ -13,6 +11,7 @@ Settings/Preferences Dialog Add reset settings Tab w/Identifier Settings Add publisher blacklist + other Identifier tunings Add class for warning/info messages with "Don't show again" checkbox. Add list of these flags to settings @@ -22,18 +21,21 @@ TaggerWindow entry fields Pages Info - maybe a custom painted widget At minimum, preserve the page data +File rename + -Dialog?? Style sheets for windows/mac/linux +Better stripping of html from CV text + ----------------- Bugs ---------------- - -SQLite chokes on "Batman\ Li'l Gotham 001.cbr" name -- Doesn't like single quote ' - SERIOUS BUG: rebuilding zips! http://stackoverflow.com/questions/11578443/trigger-io-errno-18-cross-device-link +Test ComicRack android + OSX: toolbar weird unrar complaints