From e19714bf9810ee1441b93a9b8257567ba3f1de4d Mon Sep 17 00:00:00 2001 From: "beville@gmail.com" Date: Mon, 19 Nov 2012 19:57:16 +0000 Subject: [PATCH] Setting metadata via CLI basics working git-svn-id: http://comictagger.googlecode.com/svn/trunk@61 6c5673fe-1810-88d6-992b-cd32ca31540c --- comicarchive.py | 6 +-- comicbookinfo.py | 16 +++--- comicinfoxml.py | 24 +++++---- comicvinetalker.py | 6 +-- filenameparser.py | 2 +- genericmetadata.py | 108 +++++++++++++++++++++------------------ issueidentifier.py | 48 ++++++++--------- options.py | 54 ++++++++++++++++++-- tagger.py | 8 +-- taggerwindow.py | 16 +++--- taggerwindow.ui | 2 +- todo.txt | 27 ++++++---- volumeselectionwindow.py | 4 +- 13 files changed, 192 insertions(+), 129 deletions(-) diff --git a/comicarchive.py b/comicarchive.py index b3e35fe..df5fa4c 100644 --- a/comicarchive.py +++ b/comicarchive.py @@ -552,13 +552,13 @@ class ComicArchive: fnp.parseFilename( self.path ) if fnp.issue != "": - metadata.issueNumber = fnp.issue + metadata.issue = fnp.issue if fnp.series != "": metadata.series = fnp.series if fnp.volume != "": - metadata.volumeNumber = fnp.volume + metadata.volume = fnp.volume if fnp.year != "": - metadata.publicationYear = fnp.year + metadata.year = fnp.year if fnp.issue_count != "": metadata.issueCount = fnp.issue_count diff --git a/comicbookinfo.py b/comicbookinfo.py index 23d41ea..c8c56fc 100644 --- a/comicbookinfo.py +++ b/comicbookinfo.py @@ -47,15 +47,15 @@ class ComicBookInfo: metadata.series = xlate( 'series' ) metadata.title = xlate( 'title' ) - metadata.issueNumber = xlate( 'issue' ) + metadata.issue = xlate( 'issue' ) metadata.publisher = xlate( 'publisher' ) - metadata.publicationMonth = xlate( 'publicationMonth' ) - metadata.publicationYear = xlate( 'publicationYear' ) + metadata.month = xlate( 'publicationMonth' ) + metadata.year = xlate( 'publicationYear' ) metadata.issueCount = xlate( 'numberOfIssues' ) metadata.comments = xlate( 'comments' ) metadata.credits = xlate( 'credits' ) metadata.genre = xlate( 'genre' ) - metadata.volumeNumber = xlate( 'volume' ) + metadata.volume = xlate( 'volume' ) metadata.volumeCount = xlate( 'numberOfVolumes' ) metadata.language = xlate( 'language' ) metadata.country = xlate( 'country' ) @@ -107,14 +107,14 @@ class ComicBookInfo: assign( 'series', metadata.series ) assign( 'title', metadata.title ) - assign( 'issue', metadata.issueNumber ) + assign( 'issue', metadata.issue ) assign( 'publisher', metadata.publisher ) - assign( 'publicationMonth', metadata.publicationMonth ) - assign( 'publicationYear', metadata.publicationYear ) + assign( 'publicationMonth', metadata.month ) + assign( 'publicationYear', metadata.year ) assign( 'numberOfIssues', metadata.issueCount ) assign( 'comments', metadata.comments ) assign( 'genre', metadata.genre ) - assign( 'volume', metadata.volumeNumber ) + assign( 'volume', metadata.volume ) assign( 'numberOfVolumes', metadata.volumeCount ) assign( 'language', utils.getLanguageFromISO(metadata.language) ) assign( 'country', metadata.country ) diff --git a/comicinfoxml.py b/comicinfoxml.py index e421c72..1a5b777 100644 --- a/comicinfoxml.py +++ b/comicinfoxml.py @@ -54,8 +54,10 @@ class ComicInfoXml: def stringFromMetadata( self, metadata ): + header = '\n' + tree = self.convertMetadataToXML( self, metadata ) - return ET.tostring(tree.getroot()) + return header + ET.tostring(tree.getroot()) def indent( self, elem, level=0 ): # for making the XML output readable @@ -80,25 +82,25 @@ class ComicInfoXml: # build a tree structure root = ET.Element("ComicInfo") - - + root.attrib['xmlns:xsi']="http://www.w3.org/2001/XMLSchema-instance" + root.attrib['xmlns:xsd']="http://www.w3.org/2001/XMLSchema" #helper func def assign( cix_entry, md_entry): if md_entry is not None: ET.SubElement(root, cix_entry).text = u"{0}".format(md_entry) assign( 'Series', md.series ) - assign( 'Number', md.issueNumber ) + assign( 'Number', md.issue ) assign( 'Title', md.title ) assign( 'Count', md.issueCount ) - assign( 'Volume', md.volumeNumber ) + assign( 'Volume', md.volume ) assign( 'AlternateSeries', md.alternateSeries ) assign( 'AlternateNumber', md.alternateNumber ) assign( 'AlternateCount', md.alternateCount ) assign( 'Summary', md.comments ) assign( 'Notes', md.notes ) - assign( 'Year', md.publicationYear ) - assign( 'Month', md.publicationMonth ) + assign( 'Year', md.year ) + assign( 'Month', md.month ) assign( 'Publisher', md.publisher ) assign( 'Imprint', md.imprint ) assign( 'Genre', md.genre ) @@ -213,16 +215,16 @@ class ComicInfoXml: md.series = xlate( 'Series' ) md.title = xlate( 'Title' ) - md.issueNumber = xlate( 'Number' ) + md.issue = xlate( 'Number' ) md.issueCount = xlate( 'Count' ) - md.volumeNumber = xlate( 'Volume' ) + md.volume = xlate( 'Volume' ) md.alternateSeries = xlate( 'AlternateSeries' ) md.alternateNumber = xlate( 'AlternateNumber' ) md.alternateCount = xlate( 'AlternateCount' ) md.comments = xlate( 'Summary' ) md.notes = xlate( 'Notes' ) - md.publicationYear = xlate( 'Year' ) - md.publicationMonth = xlate( 'Month' ) + md.year = xlate( 'Year' ) + md.month = xlate( 'Month' ) md.publisher = xlate( 'Publisher' ) md.imprint = xlate( 'Imprint' ) md.genre = xlate( 'Genre' ) diff --git a/comicvinetalker.py b/comicvinetalker.py index a4bc584..adf4dcb 100644 --- a/comicvinetalker.py +++ b/comicvinetalker.py @@ -193,11 +193,11 @@ class ComicVineTalker(QObject): if math.floor(num_f) != num_f: num_s = str( num_f ) - metadata.issueNumber = num_s + metadata.issue = num_s metadata.title = issue_results['name'] metadata.publisher = volume_results['publisher']['name'] - metadata.publicationMonth = issue_results['publish_month'] - metadata.publicationYear = issue_results['publish_year'] + metadata.month = issue_results['publish_month'] + metadata.year = issue_results['publish_year'] #metadata.issueCount = volume_results['count_of_issues'] metadata.comments = self.cleanup_html(issue_results['description']) diff --git a/filenameparser.py b/filenameparser.py index 26760e4..8137121 100644 --- a/filenameparser.py +++ b/filenameparser.py @@ -73,7 +73,7 @@ class FileNameParser: return count - def getIssueNumber( self,filename ): + def getIssueNumber( self, filename ): found = False issue = '' diff --git a/genericmetadata.py b/genericmetadata.py index b6df2b9..fd85227 100644 --- a/genericmetadata.py +++ b/genericmetadata.py @@ -58,13 +58,13 @@ class GenericMetadata: self.tagOrigin = None self.series = None - self.issueNumber = None + self.issue = None self.title = None self.publisher = None - self.publicationMonth = None - self.publicationYear = None + self.month = None + self.year = None self.issueCount = None - self.volumeNumber = None + self.volume = None self.genre = None self.language = None # 2 letter iso code self.comments = None # use same way as Summary in CIX @@ -104,25 +104,28 @@ class GenericMetadata: def assign( cur, new ): if new is not None: - setattr(self, cur, new) + if type(new) == str and len(new) == 0: + setattr(self, cur, None) + else: + setattr(self, cur, new) if not new_md.isEmpty: self.isEmpty = False assign( 'series', new_md.series ) - assign( "issueNumber", new_md.issueNumber ) + assign( "issue", new_md.issue ) assign( "issueCount", new_md.issueCount ) assign( "title", new_md.title ) assign( "publisher", new_md.publisher ) - assign( "publicationMonth", new_md.publicationMonth ) - assign( "publicationYear", new_md.publicationYear ) - assign( "volumeNumber", new_md.volumeNumber ) + assign( "month", new_md.month ) + assign( "year", new_md.year ) + assign( "volume", new_md.volume ) assign( "volumeCount", new_md.volumeCount ) assign( "genre", new_md.genre ) assign( "language", new_md.language ) assign( "country", new_md.country ) assign( "alternateSeries", new_md.criticalRating ) - assign( "alt. series", new_md.alternateSeries ) + assign( "alternateSeries", new_md.alternateSeries ) assign( "alternateNumber", new_md.alternateNumber ) assign( "alternateCount", new_md.alternateCount ) assign( "imprint", new_md.imprint ) @@ -144,10 +147,12 @@ class GenericMetadata: # not sure if the tags, credits, and pages should broken down, or treated # as whole lists.... For now, go the easy route, where any overlay # value wipes out the whole list - - assign( "tags", new_md.tags ) - assign( "credits", new_md.credits ) - assign( "pages", new_md.pages ) + if len(new_md.tags) > 0: + assign( "tags", new_md.tags ) + if len(new_md.credits) > 0: + assign( "credits", new_md.credits ) + if len(new_md.pages) > 0: + assign( "pages", new_md.pages ) def addCredit( self, person, role, primary = False ): @@ -166,44 +171,49 @@ class GenericMetadata: if self.isEmpty: return "No metadata" - def add( tag, val ): + def add_string( tag, val ): if val is not None and u"{0}".format(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 ) ) + def add_attr_string( tag ): + val = getattr(self,tag) + add_string( tag, getattr(self,tag) ) + + add_attr_string( "series" ) + add_attr_string( "issue" ) + add_attr_string( "issueCount" ) + add_attr_string( "title" ) + add_attr_string( "publisher" ) + add_attr_string( "month" ) + add_attr_string( "year" ) + add_attr_string( "volume" ) + add_attr_string( "volumeCount" ) + add_attr_string( "genre" ) + add_attr_string( "language" ) + add_attr_string( "country" ) + add_attr_string( "criticalRating" ) + add_attr_string( "alternateSeries" ) + add_attr_string( "alternateNumber" ) + add_attr_string( "alternateCount" ) + add_attr_string( "imprint" ) + add_attr_string( "webLink" ) + add_attr_string( "format" ) + add_attr_string( "manga" ) + add_attr_string( "blackAndWhite" ) + add_attr_string( "maturityRating" ) + add_attr_string( "storyArc" ) + add_attr_string( "seriesGroup" ) + add_attr_string( "scanInfo" ) + add_attr_string( "characters" ) + add_attr_string( "teams" ) + add_attr_string( "locations" ) + add_attr_string( "comments" ) + add_attr_string( "notes" ) + add_string( "tags", utils.listToString( self.tags ) ) + for c in self.credits: - add( "credit", c['role']+": "+c['person'] ) + add_string( "credit", c['role']+": "+c['person'] ) # find the longest field name flen = 0 @@ -213,8 +223,8 @@ class GenericMetadata: #format the data nicely outstr = "" - fmt_str = u"{0: <" + str(flen) + "}: {1}\n" + fmt_str = u"{0: <" + str(flen) + "} {1}\n" for i in vals: - outstr += fmt_str.format( i[0], i[1] ) + outstr += fmt_str.format( i[0]+":", i[1] ) return outstr diff --git a/issueidentifier.py b/issueidentifier.py index 9d89fb1..54de091 100644 --- a/issueidentifier.py +++ b/issueidentifier.py @@ -137,9 +137,9 @@ class IssueIdentifier: if self.onlyUseAdditionalMetaData: search_keys['series'] = self.additional_metadata.series - search_keys['issue_number'] = self.additional_metadata.issueNumber - search_keys['year'] = self.additional_metadata.publicationYear - search_keys['month'] = self.additional_metadata.publicationMonth + search_keys['issue_number'] = self.additional_metadata.issue + search_keys['year'] = self.additional_metadata.year + search_keys['month'] = self.additional_metadata.month return search_keys # see if the archive has any useful meta data for searching with @@ -165,26 +165,26 @@ class IssueIdentifier: else: search_keys['series'] = md_from_filename.series - if self.additional_metadata.issueNumber is not None: - search_keys['issue_number'] = self.additional_metadata.issueNumber - elif internal_metadata.issueNumber is not None: - search_keys['issue_number'] = internal_metadata.issueNumber + if self.additional_metadata.issue is not None: + search_keys['issue_number'] = self.additional_metadata.issue + elif internal_metadata.issue is not None: + search_keys['issue_number'] = internal_metadata.issue else: - search_keys['issue_number'] = md_from_filename.issueNumber + search_keys['issue_number'] = md_from_filename.issue - if self.additional_metadata.publicationYear is not None: - search_keys['year'] = self.additional_metadata.publicationYear - elif internal_metadata.publicationYear is not None: - search_keys['year'] = internal_metadata.publicationYear + if self.additional_metadata.year is not None: + search_keys['year'] = self.additional_metadata.year + elif internal_metadata.year is not None: + search_keys['year'] = internal_metadata.year else: - search_keys['year'] = md_from_filename.publicationYear + search_keys['year'] = md_from_filename.year - if self.additional_metadata.publicationMonth is not None: - search_keys['month'] = self.additional_metadata.publicationMonth - elif internal_metadata.publicationMonth is not None: - search_keys['month'] = internal_metadata.publicationMonth + if self.additional_metadata.month is not None: + search_keys['month'] = self.additional_metadata.month + elif internal_metadata.month is not None: + search_keys['month'] = internal_metadata.month else: - search_keys['month'] = md_from_filename.publicationMonth + search_keys['month'] = md_from_filename.month return search_keys @@ -230,15 +230,15 @@ class IssueIdentifier: self.log_msg("Not enough info for a search!") return [] - """ + self.log_msg( "Going to search for:" ) - self.log_msg( "Series: " + keys['series'] ) - self.log_msg( "Issue : " + keys['issue_number'] ) + self.log_msg( "\tSeries: " + keys['series'] ) + self.log_msg( "\tIssue : " + keys['issue_number'] ) if keys['year'] is not None: - self.log_msg( "Year : " + keys['year'] ) + self.log_msg( "\tYear : " + keys['year'] ) if keys['month'] is not None: - self.log_msg( "Month : " + keys['month'] ) - """ + self.log_msg( "\tMonth : " + keys['month'] ) + comicVine = ComicVineTalker( self.cv_api_key ) diff --git a/options.py b/options.py index e9e2598..ea047f6 100644 --- a/options.py +++ b/options.py @@ -23,6 +23,8 @@ import getopt import platform import os +from genericmetadata import GenericMetadata + class Enum(set): def __getattr__(self, name): if name in self: @@ -57,9 +59,13 @@ If no options are given, {0} will run in windowed mode -o, --online Search online and attempt to identify file using existing metadata and images in archive. May be used in conjuntion with -f and -m - -m, --metadata=LIST Explicity define some tags to be used as a list - ....TBD........ - ....TBD........ + -m, --metadata=LIST Explicity define, as a list, some tags to be used + e.g. "series=Plastic Man , publisher=Quality Comics" + "series=Kickers^, Inc., issue=1, year=1986" + Name-Value pairs are comma separated. Use a "^" to + escape an "=" or a ",", ash show in the example above + Some names that can be used: + series, issue, issueCount, year, publisher, title -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 @@ -90,8 +96,46 @@ If no options are given, {0} will run in windowed mode sys.exit(code) def parseMetadataFromString( self, mdstr ): - print "TBD!!!!!!!!!!!!!!!!!!!!!" - return None + # The metadata string is a comma separated list of name-value pairs + # The names match the attributes of the internal metadata struct (for now) + # The caret is the special "escape character", since it's not common in + # natural language text + + # example = "series=Kickers^, Inc. ,issue=1, year=1986" + + escaped_comma = "^," + escaped_equals = "^=" + replacement_token = "<_~_>" + + md = GenericMetadata() + + # First, replace escaped commas with with a unique token (to be changed back later) + mdstr = mdstr.replace( escaped_comma, replacement_token) + tmp_list = mdstr.split(",") + md_list = [] + for item in tmp_list: + item = item.replace( replacement_token, "," ) + md_list.append(item) + + # Now build a nice dict from the list + md_dict = dict() + for item in md_list: + # Make sure to fix any escaped equal signs + i = item.replace( escaped_equals, replacement_token) + key,value = i.split("=") + value = value.replace( replacement_token, "=" ).strip() + key = key.strip() + md_dict[key] = value + + # Map the dict to the metadata object + for key in md_dict: + if not hasattr(md, key): + print "Warning: '{0}' is not a valid tag name".format(key) + else: + md.isEmpty = False + setattr( md, key, md_dict[key] ) + #print md + return md def parseCmdLineArgs(self): diff --git a/tagger.py b/tagger.py index 06e7721..5155ad0 100755 --- a/tagger.py +++ b/tagger.py @@ -42,7 +42,8 @@ import utils #----------------------------- def cli_mode( opts, settings ): for f in opts.file_list: - print "Processing: ", f + if len( opts.file_list ) > 1: + print "Processing: ", f process_file_cli( f, opts, settings ) def process_file_cli( filename, opts, settings ): @@ -94,11 +95,12 @@ def process_file_cli( filename, opts, settings ): if opts.data_style is None or opts.data_style == MetaDataStyle.CIX: if cix: print "------ComicRack tags--------" - print u"{0}".format(ca.readCIX()) + print u"{0}".format(ca.readCIX()).encode("utf-8") + if opts.data_style is None or opts.data_style == MetaDataStyle.CBI: if cbi: print "------ComicBookLover tags--------" - print u"{0}".format(ca.readCBI()) + print u"{0}".format(ca.readCBI()).encode("utf-8") elif opts.delete_tags: diff --git a/taggerwindow.py b/taggerwindow.py index 82a9dcc..a67de24 100644 --- a/taggerwindow.py +++ b/taggerwindow.py @@ -474,14 +474,14 @@ class TaggerWindow( QtGui.QMainWindow): md = self.metadata assignText( self.leSeries, md.series ) - assignText( self.leIssueNum, md.issueNumber ) + assignText( self.leIssueNum, md.issue ) assignText( self.leIssueCount, md.issueCount ) - assignText( self.leVolumeNum, md.volumeNumber ) + assignText( self.leVolumeNum, md.volume ) assignText( self.leVolumeCount, md.volumeCount ) assignText( self.leTitle, md.title ) assignText( self.lePublisher, md.publisher ) - assignText( self.lePubMonth, md.publicationMonth ) - assignText( self.lePubYear, md.publicationYear ) + assignText( self.lePubMonth, md.month ) + assignText( self.lePubYear, md.year ) assignText( self.leGenre, md.genre ) assignText( self.leImprint, md.imprint ) assignText( self.teComments, md.comments ) @@ -589,14 +589,14 @@ class TaggerWindow( QtGui.QMainWindow): # copy the data from the form into the metadata md = self.metadata md.series = xlate( self.leSeries.text(), "str" ) - md.issueNumber = xlate( self.leIssueNum.text(), "str" ) + md.issue = xlate( self.leIssueNum.text(), "str" ) md.issueCount = xlate( self.leIssueCount.text(), "int" ) - md.volumeNumber = xlate( self.leVolumeNum.text(), "int" ) + md.volume = xlate( self.leVolumeNum.text(), "int" ) md.volumeCount = xlate( self.leVolumeCount.text(), "int" ) md.title = xlate( self.leTitle.text(), "str" ) md.publisher = xlate( self.lePublisher.text(), "str" ) - md.publicationMonth = xlate( self.lePubMonth.text(), "int" ) - md.publicationYear = xlate( self.lePubYear.text(), "int" ) + md.month = xlate( self.lePubMonth.text(), "int" ) + md.year = xlate( self.lePubYear.text(), "int" ) md.genre = xlate( self.leGenre.text(), "str" ) md.imprint = xlate( self.leImprint.text(), "str" ) md.comments = xlate( self.teComments.toPlainText(), "str" ) diff --git a/taggerwindow.ui b/taggerwindow.ui index 002eac5..15209f6 100644 --- a/taggerwindow.ui +++ b/taggerwindow.ui @@ -261,7 +261,7 @@ 0 0 685 - 380 + 384 diff --git a/todo.txt b/todo.txt index 7210a20..b314996 100644 --- a/todo.txt +++ b/todo.txt @@ -1,15 +1,6 @@ ----------------- Features ---------------- -CLI - multiple files? - save log - abort flag - explicit metadata settings option format - just series, issue, year?? - verbose settings for identifier - interactive for choices option? - --- or defer choices to end, by keeping special log of those files Settings/Preferences Dialog Remove API Key @@ -40,6 +31,10 @@ Better stripping of html from CV text ----------------- Bugs ---------------- +Unicode errors when piping or redirecting stdout + +Windows zip and rar: set proper path for writing to archive + SERIOUS BUG: rebuilding zips! http://stackoverflow.com/questions/11578443/trigger-io-errno-18-cross-device-link @@ -52,8 +47,6 @@ OSX: Filename parsing: Rework how series name is separated from issue -Test ComicRack android - Form type validation Ints vs strings for month, year. etc Check all HTTP responses for errors @@ -64,6 +57,16 @@ Lots of error checking ------------- Future ------------ + +CLI + write a log for multiple file processing + override abort on low confidence flag + explicit metadata settings option format + -- figure out how to add credits,tags + interactive for choices option? + --- or defer choices to end, by keeping special log of those files + + Add warning message to allow writing CBI to RAR, and ask them to contact CBL ! :-) Scrape alternate Covers from ComicVine issue pages @@ -91,6 +94,8 @@ Idea: Support only CBI or CIX for any given file, and not both Longer term: Think about mass tagging and (semi) automatic volume selection + +- ---------------------------------------------- COMIC RACK Questions diff --git a/volumeselectionwindow.py b/volumeselectionwindow.py index c66d781..55e77e3 100644 --- a/volumeselectionwindow.py +++ b/volumeselectionwindow.py @@ -129,8 +129,8 @@ class VolumeSelectionWindow(QtGui.QDialog): md = GenericMetadata() md.series = self.series_name - md.issueNumber = self.issue_number - md.publicationYear = self.year + md.issue = self.issue_number + md.year = self.year self.ii.setAdditionalMetadata( md ) self.ii.onlyUseAdditionalMetaData = True