From 487c8a5bf44aefed20d20c33697c98328f5f4385 Mon Sep 17 00:00:00 2001 From: "beville@gmail.com" Date: Sat, 8 Dec 2012 19:57:51 +0000 Subject: [PATCH] Page list management work git-svn-id: http://comictagger.googlecode.com/svn/trunk@238 6c5673fe-1810-88d6-992b-cd32ca31540c --- comet.py | 8 +-- comicarchive.py | 92 ++++++++++++++++++++++++++++------ comicinfoxml.py | 1 + comictagger.py | 8 +-- genericmetadata.py | 36 ++++++++++++-- issueidentifier.py | 5 +- options.py | 9 +++- pagebrowser.py | 6 ++- pagelisteditor.py | 36 ++++++++++++++ pagelisteditor.ui | 103 +++++++++++++++++++++++++++++++++++++++ taggerwindow.py | 32 ++++++++---- taggerwindow.ui | 5 ++ volumeselectionwindow.py | 7 ++- 13 files changed, 306 insertions(+), 42 deletions(-) create mode 100644 pagelisteditor.py create mode 100644 pagelisteditor.ui diff --git a/comet.py b/comet.py index 7baca54..128be43 100644 --- a/comet.py +++ b/comet.py @@ -101,8 +101,8 @@ class CoMet: if md.characters is not None: char_list = [ c.strip() for c in md.characters.split(',') ] - for c in char_list: - assign( 'character', c ) + for c in char_list: + assign( 'character', c ) if md.manga is not None and md.manga == "YesAndRightToLeft": assign( 'readingDirection', "rtl") @@ -114,7 +114,7 @@ class CoMet: date_str += "-" + str(md.month).zfill(2) assign( 'date', date_str ) - #assign( 'coverImage', md.??? ) #TODO Need to use pages list, eventually... + assign( 'coverImage', md.coverImage ) # need to specially process the credits, since they are structured differently than CIX credit_writer_list = list() @@ -202,7 +202,7 @@ class CoMet: if len( parts) > 1: md.month = parts[1] - coverImage = xlate( 'coverImage' ) # TODO - do something with this! + md.coverImage = xlate( 'coverImage' ) readingDirection = xlate( 'readingDirection' ) if readingDirection is not None and readingDirection == "rtl": diff --git a/comicarchive.py b/comicarchive.py index 8a75b1e..527532e 100644 --- a/comicarchive.py +++ b/comicarchive.py @@ -29,6 +29,13 @@ if platform.system() == "Windows": import _subprocess import time +import StringIO +try: + import Image + pil_available = True +except ImportError: + pil_available = False + sys.path.insert(0, os.path.abspath(".") ) import UnRAR2 from UnRAR2.rar_exceptions import * @@ -37,7 +44,7 @@ from options import Options, MetaDataStyle from comicinfoxml import ComicInfoXml from comicbookinfo import ComicBookInfo from comet import CoMet -from genericmetadata import GenericMetadata +from genericmetadata import GenericMetadata, PageType from filenameparser import FileNameParser @@ -524,11 +531,6 @@ class ComicArchive: return self.removeCBI() elif style == MetaDataStyle.COMET: return self.removeCoMet() - - def getCoverPage(self): - - # assume first page is the cover (for now) - return self.getPage( 0 ) def getPage( self, index ): @@ -575,9 +577,13 @@ class ComicArchive: def readCBI( self ): raw_cbi = self.readRawCBI() if raw_cbi is None: - return GenericMetadata() + md =GenericMetadata() + else: + md = ComicBookInfo().metadataFromString( raw_cbi ) - return ComicBookInfo().metadataFromString( raw_cbi ) + md.setDefaultPageList( self.getNumberOfPages() ) + + return md def readRawCBI( self ): if ( not self.hasCBI() ): @@ -594,6 +600,7 @@ class ComicArchive: return ComicBookInfo().validateString( comment ) def writeCBI( self, metadata ): + self.applyArchiveInfoToMetadata( metadata ) cbi_string = ComicBookInfo().stringFromMetadata( metadata ) return self.archiver.setArchiveComment( cbi_string ) @@ -603,9 +610,20 @@ class ComicArchive: def readCIX( self ): raw_cix = self.readRawCIX() if raw_cix is None: - return GenericMetadata() + md = GenericMetadata() + else: + md = ComicInfoXml().metadataFromString( raw_cix ) + + #validate the existing page list (make sure count is correct) + if len ( md.pages ) != 0 : + if len ( md.pages ) != self.getNumberOfPages(): + # pages array doesn't match the actual number of images we're seeing + # in the archive, so discard the data + md.pages = [] - return ComicInfoXml().metadataFromString( raw_cix ) + if len( md.pages ) == 0: + md.setDefaultPageList( self.getNumberOfPages() ) + return md def readRawCIX( self ): if not self.hasCIX(): @@ -617,7 +635,7 @@ class ComicArchive: def writeCIX(self, metadata): if metadata is not None: - metadata.pageCount = self.getNumberOfPages() + self.applyArchiveInfoToMetadata( metadata, calc_page_sizes=True ) cix_string = ComicInfoXml().stringFromMetadata( metadata ) return self.archiver.writeArchiveFile( self.ci_xml_filename, cix_string ) else: @@ -639,9 +657,26 @@ class ComicArchive: def readCoMet( self ): raw_comet = self.readRawCoMet() if raw_comet is None: - return GenericMetadata() - - return CoMet().metadataFromString( raw_comet ) + md = GenericMetadata() + else: + md = CoMet().metadataFromString( raw_comet ) + + md.setDefaultPageList( self.getNumberOfPages() ) + #use the coverImage value from the comet_data to mark the cover in this struct + # walk through list of images in file, and find the matching one for md.coverImage + # need to remove the existing one in the default + if md.coverImage is not None: + cover_idx = 0 + for idx,f in enumerate(self.getPageNameList()): + if md.coverImage == f: + cover_idx = idx + break + if cover_idx != 0: + del (md.pages[0]['Type'] ) + md.pages[ cover_idx ]['Type'] = PageType.FrontCover + + + return md def readRawCoMet( self ): if not self.hasCoMet(): @@ -656,7 +691,12 @@ class ComicArchive: if not self.hasCoMet(): self.comet_filename = self.comet_default_filename - metadata.pageCount = self.getNumberOfPages() + self.applyArchiveInfoToMetadata( metadata ) + # Set the coverImage value, if it's not the first page + cover_idx = int(metadata.getCoverPageIndexList()[0]) + if cover_idx != 0: + metadata.coverImage = self.getPageName( cover_idx ) + comet_string = CoMet().stringFromMetadata( metadata ) return self.archiver.writeArchiveFile( self.comet_filename, comet_string ) else: @@ -691,7 +731,29 @@ class ComicArchive: else: return True + def applyArchiveInfoToMetadata( self, md, calc_page_sizes=False): + md.pageCount = self.getNumberOfPages() + + if calc_page_sizes: + for p in md.pages: + idx = int( p['Image'] ) + if pil_available: + if 'ImageSize' not in p or 'ImageHeight' not in p or 'ImageWidth' not in p: + data = self.getPage( idx ) + + im = Image.open(StringIO.StringIO(data)) + w,h = im.size + + p['ImageSize'] = str(len(data)) + p['ImageHeight'] = str(h) + p['ImageWidth'] = str(w) + else: + if 'ImageSize' not in p: + data = self.getPage( idx ) + p['ImageSize'] = str(len(data)) + + def metadataFromFilename( self ): metadata = GenericMetadata() diff --git a/comicinfoxml.py b/comicinfoxml.py index b9c8a1a..1ba0c48 100644 --- a/comicinfoxml.py +++ b/comicinfoxml.py @@ -270,6 +270,7 @@ class ComicInfoXml: if pages_node is not None: for page in pages_node: metadata.pages.append( page.attrib ) + print page.attrib metadata.isEmpty = False diff --git a/comictagger.py b/comictagger.py index 535b2f4..49ddab8 100755 --- a/comictagger.py +++ b/comictagger.py @@ -162,7 +162,8 @@ def cli_mode( opts, settings ): def create_local_metadata( opts, ca, has_desired_tags ): md = GenericMetadata() - + md.setDefaultPageList( ca.getNumberOfPages() ) + if has_desired_tags: md = ca.readMetadata( opts.data_style ) @@ -235,7 +236,7 @@ def process_file_cli( filename, opts, settings, match_results ): if has[ MetaDataStyle.CIX ]: print "------ComicRack tags--------" if opts.raw: - print u"{0}".format(ca.readRawCIX()) + print u"{0}".format(unicode(ca.readRawCIX(), errors='ignore')) else: print u"{0}".format(ca.readCIX()) @@ -307,7 +308,7 @@ def process_file_cli( filename, opts, settings, match_results ): if opts.search_online: ii = IssueIdentifier( ca, settings ) - + if md is None or md.isEmpty: print "No metadata given to search online with!" return @@ -320,6 +321,7 @@ def process_file_cli( filename, opts, settings, match_results ): ii.setAdditionalMetadata( md ) ii.onlyUseAdditionalMetaData = True ii.setOutputFunction( myoutput ) + ii.cover_page_index = md.getCoverPageIndexList()[0] matches = ii.search() result = ii.search_result diff --git a/genericmetadata.py b/genericmetadata.py index 145942c..e888861 100644 --- a/genericmetadata.py +++ b/genericmetadata.py @@ -104,7 +104,7 @@ class GenericMetadata: self.rights = None self.identifier = None self.lastMark = None - + self.coverImage = None def overlay( self, new_md ): # Overlay a metadata object on this one @@ -167,7 +167,9 @@ class GenericMetadata: # For now, go the easy route, where any overlay # value wipes out the whole list if len(new_md.tags) > 0: - assign( "tags", new_md.tags ) + assign( "tags", new_md.tags ) + + print "MD overlay len of old pages = {0} len of new pages = {1}".format(len(self.pages), len(new_md.pages)) if len(new_md.pages) > 0: assign( "pages", new_md.pages ) @@ -187,7 +189,35 @@ class GenericMetadata: # otherwise, add it! else: self.addCredit( c['person'], c['role'], primary ) - + + def setDefaultPageList( self, count ): + # generate a default page list, with the first page marked as the cover + for i in range(count): + page_dict = dict() + page_dict['Image'] = str(i) + if i == 0: + page_dict['Type'] = PageType.FrontCover + self.pages.append( page_dict ) + + def getArchivePageIndex( self, pagenum ): + # convert the displayed page number to the page index of the file in the archive + if pagenum < len( self.pages ): + return int( self.pages[pagenum]['Image'] ) + else: + return 0 + + def getCoverPageIndexList( self ): + # return a list of archive page indices of cover pages + coverlist = [] + for p in self.pages: + if 'Type' in p and p['Type'] == PageType.FrontCover: + coverlist.append( int(p['Image'])) + + if len(coverlist) == 0: + coverlist.append( 0 ) + + return coverlist + def addCredit( self, person, role, primary = False ): credit = dict() diff --git a/issueidentifier.py b/issueidentifier.py index 1749fba..53c525c 100644 --- a/issueidentifier.py +++ b/issueidentifier.py @@ -73,7 +73,8 @@ class IssueIdentifier: self.output_function = IssueIdentifier.defaultWriteOutput self.callback = None self.search_result = self.ResultNoMatches - + self.cover_page_index = 0 + def setScoreMinThreshold( self, thresh ): self.min_score_thresh = thresh @@ -218,7 +219,7 @@ class IssueIdentifier: self.log_msg( "Sorry, but "+ opts.filename + " is not a comic archive!") return self.match_list - cover_image_data = ca.getCoverPage() + cover_image_data = ca.getPage( self.cover_page_index ) cover_hash = self.calculateHash( cover_image_data ) #check the apect ratio diff --git a/options.py b/options.py index 0932bae..5a17653 100644 --- a/options.py +++ b/options.py @@ -23,6 +23,7 @@ import getopt import platform import os +import ctversion from genericmetadata import GenericMetadata class Enum(set): @@ -55,7 +56,7 @@ If no options are given, {0} will run in windowed mode specified via via -t (potentially lossy operation) -s, --save Save out tags as specified type (via -t) Must specify also at least -o, -p, or -m - --nooverwrite Don't modify tag block if it already exists ( relevent for -s or -c ) + --nooverwrite Don't modify tag block if it already exists ( relevent for -s or -c ) -n, --dryrun Don't actually modify file (only relevent for -d, -s, or -r) -t, --type=TYPE Specify TYPE as either "CR", "CBL", or "COMET" (as either ComicRack, ComicBookLover, or CoMet style tags, respectivly) @@ -79,6 +80,7 @@ If no options are given, {0} will run in windowed mode --noabort Don't abort save operation when online match is of low confidence -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 """ @@ -175,7 +177,7 @@ If no options are given, {0} will run in windowed mode "hpdt:fm:vonsrc:i", [ "help", "print", "delete", "type=", "copy=", "parsefilename", "metadata=", "verbose", "online", "dryrun", "save", "rename" , "raw", "noabort", "terse", "nooverwrite", - "interactive", "nosummary" ]) + "interactive", "nosummary", "version" ]) except getopt.GetoptError as err: self.display_help_and_quit( str(err), 2 ) @@ -224,6 +226,9 @@ If no options are given, {0} will run in windowed mode self.show_save_summary = False if o == "--nooverwrite": self.no_overwrite = True + if o == "--version": + print "ComicTagger version: ", ctversion.version + quit() if o in ("-t", "--type"): if a.lower() == "cr": self.data_style = MetaDataStyle.CIX diff --git a/pagebrowser.py b/pagebrowser.py index 3797638..c772714 100644 --- a/pagebrowser.py +++ b/pagebrowser.py @@ -26,7 +26,7 @@ from settings import ComicTaggerSettings class PageBrowserWindow(QtGui.QDialog): - def __init__(self, parent): + def __init__(self, parent, metadata): super(PageBrowserWindow, self).__init__(parent) uic.loadUi(os.path.join(ComicTaggerSettings.baseDir(), 'pagebrowser.ui' ), self) @@ -37,6 +37,7 @@ class PageBrowserWindow(QtGui.QDialog): self.current_pixmap = None self.page_count = 0 self.current_page_num = 0 + self.metadata = metadata self.btnNext.clicked.connect( self.nextPage ) self.btnPrev.clicked.connect( self.prevPage ) @@ -66,7 +67,8 @@ class PageBrowserWindow(QtGui.QDialog): self.setPage() def setPage( self ): - image_data = self.comic_archive.getPage( self.current_page_num ) + 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 ) diff --git a/pagelisteditor.py b/pagelisteditor.py new file mode 100644 index 0000000..3036f0e --- /dev/null +++ b/pagelisteditor.py @@ -0,0 +1,36 @@ +""" +A PyQt4 widget for editing the page list info +""" + +""" +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 + +class PageListEditor(QWidget): + + def __init__(self, parent ): + super(PageListEditor, self).__init__(parent) + + uic.loadUi(os.path.join(ComicTaggerSettings.baseDir(), 'pagelisteditor.ui' ), self ) + + diff --git a/pagelisteditor.ui b/pagelisteditor.ui new file mode 100644 index 0000000..4a818f0 --- /dev/null +++ b/pagelisteditor.ui @@ -0,0 +1,103 @@ + + + pageListEditor + + + + 0 + 0 + 514 + 328 + + + + Form + + + + + + + + + 0 + 0 + + + + + + + + + + + 0 + 0 + + + + + 50 + 16777215 + + + + ^ + + + + + + + + 0 + 0 + + + + + 50 + 16777215 + + + + v + + + + + + + + + + + + 0 + 0 + + + + QFrame::Panel + + + QFrame::Sunken + + + TextLabel + + + + + + + + + + + + + + + diff --git a/taggerwindow.py b/taggerwindow.py index 4bde6fb..eda8079 100644 --- a/taggerwindow.py +++ b/taggerwindow.py @@ -43,6 +43,7 @@ from pagebrowser import PageBrowserWindow from filenameparser import FileNameParser from logwindow import LogWindow from optionalmsgdialog import OptionalMessageDialog +from pagelisteditor import PageListEditor import utils import ctversion @@ -88,6 +89,11 @@ class TaggerWindow( QtGui.QMainWindow): #signal.signal(signal.SIGINT, self.sigint_handler) uic.loadUi(os.path.join(ComicTaggerSettings.baseDir(), 'taggerwindow.ui' ), self) + + self.pageListEditor = PageListEditor( self.tabPages ) + gridlayout = QtGui.QGridLayout( self.tabPages ) + gridlayout.addWidget( self.pageListEditor ) + 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' ))) @@ -339,8 +345,6 @@ class TaggerWindow( QtGui.QMainWindow): msgBox.exec_() - - def dragEnterEvent(self, event): self.droppedFile=None if event.mimeData().hasUrls(): @@ -382,6 +386,7 @@ class TaggerWindow( QtGui.QMainWindow): # no style indicated, so try to choose if hasNeither: self.metadata = self.comic_archive.metadataFromFilename( ) + self.metadata.setDefaultPageList( self.comic_archive.getNumberOfPages() ) else: if hasCBI and not hasCIX: self.data_style = MetaDataStyle.CBI @@ -410,8 +415,10 @@ class TaggerWindow( QtGui.QMainWindow): if self.metadata.isEmpty: self.metadata = self.comic_archive.metadataFromFilename( ) - - image_data = self.comic_archive.getCoverPage() + self.metadata.setDefaultPageList( self.comic_archive.getNumberOfPages() ) + + cover_idx = self.metadata.getCoverPageIndexList()[0] + image_data = self.comic_archive.getPage( cover_idx ) if not image_data is None: img = QtGui.QImage() img.loadFromData( image_data ) @@ -420,6 +427,7 @@ class TaggerWindow( QtGui.QMainWindow): if self.page_browser is not None: self.page_browser.setComicArchive( self.comic_archive ) + self.page_browser.metadata = self.metadata self.metadataToForm() self.clearDirtyFlag() # also updates the app title @@ -574,6 +582,8 @@ class TaggerWindow( QtGui.QMainWindow): # get a minty fresh metadata object self.metadata = GenericMetadata() + if self.comic_archive is not None: + self.metadata.setDefaultPageList( self.comic_archive.getNumberOfPages() ) # recursivly clear the tab form self.clearChildren( self.tabWidget ) @@ -789,11 +799,14 @@ class TaggerWindow( QtGui.QMainWindow): md.addCredit( name, role, bool(primary_flag) ) row += 1 - def useFilename( self ): if self.comic_archive is not None: - self.metadata = self.comic_archive.metadataFromFilename( ) - self.metadataToForm() + #copy the form onto metadata object + self.formToMetadata() + new_metadata = self.comic_archive.metadataFromFilename( ) + if new_metadata is not None: + self.metadata.overlay( new_metadata ) + self.metadataToForm() def selectFile( self ): @@ -850,7 +863,8 @@ class TaggerWindow( QtGui.QMainWindow): if year == "": year = None - selector = VolumeSelectionWindow( self, series_name, issue_number, year, self.comic_archive, self.settings, autoselect ) + cover_index_list = self.metadata.getCoverPageIndexList() + selector = VolumeSelectionWindow( self, series_name, issue_number, year, cover_index_list, self.comic_archive, self.settings, autoselect ) title = "Search: '" + series_name + "' - " selector.setWindowTitle( title + "Select Series") @@ -1318,7 +1332,7 @@ class TaggerWindow( QtGui.QMainWindow): def showPageBrowser( self ): if self.page_browser is None: - self.page_browser = PageBrowserWindow( self ) + self.page_browser = PageBrowserWindow( self, self.metadata ) if self.comic_archive is not None: self.page_browser.setComicArchive( self.comic_archive ) self.page_browser.finished.connect(self.pageBrowserClosed) diff --git a/taggerwindow.ui b/taggerwindow.ui index 9f421d8..2fba210 100644 --- a/taggerwindow.ui +++ b/taggerwindow.ui @@ -1028,6 +1028,11 @@ + + + Pages + + diff --git a/volumeselectionwindow.py b/volumeselectionwindow.py index c877213..32bfa4f 100644 --- a/volumeselectionwindow.py +++ b/volumeselectionwindow.py @@ -85,7 +85,7 @@ class IdentifyThread( QtCore.QThread): class VolumeSelectionWindow(QtGui.QDialog): - def __init__(self, parent, series_name, issue_number, year, comic_archive, settings, autoselect=False): + def __init__(self, parent, series_name, issue_number, year, cover_index_list, comic_archive, settings, autoselect=False): super(VolumeSelectionWindow, self).__init__(parent) uic.loadUi(os.path.join(ComicTaggerSettings.baseDir(), 'volumeselectionwindow.ui' ), self) @@ -97,7 +97,8 @@ class VolumeSelectionWindow(QtGui.QDialog): self.volume_id = 0 self.comic_archive = comic_archive self.immediate_autoselect = autoselect - + self.cover_index_list = cover_index_list + self.twList.resizeColumnsToContents() self.twList.currentItemChanged.connect(self.currentItemChanged) self.twList.cellDoubleClicked.connect(self.cellDoubleClicked) @@ -136,6 +137,8 @@ class VolumeSelectionWindow(QtGui.QDialog): self.ii.setAdditionalMetadata( md ) self.ii.onlyUseAdditionalMetaData = True + print self.cover_index_list + self.ii.cover_page_index = int(self.cover_index_list[0]) self.id_thread = IdentifyThread( self.ii ) self.id_thread.identifyComplete.connect( self.identifyComplete )