Compare commits

...

7 Commits

Author SHA1 Message Date
41f730a558 Version update for 0.9.5
git-svn-id: http://comictagger.googlecode.com/svn/trunk@305 6c5673fe-1810-88d6-992b-cd32ca31540c
2013-01-17 00:05:09 +00:00
550b84361c Create a list of story arcs
git-svn-id: http://comictagger.googlecode.com/svn/trunk@304 6c5673fe-1810-88d6-992b-cd32ca31540c
2013-01-16 23:59:09 +00:00
fb4248fda2 Fixed some typos
git-svn-id: http://comictagger.googlecode.com/svn/trunk@303 6c5673fe-1810-88d6-992b-cd32ca31540c
2013-01-16 23:58:54 +00:00
9626c3fd77 Use the CT version in JSON
Make sure certain fields are ints

git-svn-id: http://comictagger.googlecode.com/svn/trunk@302 6c5673fe-1810-88d6-992b-cd32ca31540c
2013-01-16 23:29:32 +00:00
3f305c6788 Made sure to reset the cache on a tag block delete
git-svn-id: http://comictagger.googlecode.com/svn/trunk@301 6c5673fe-1810-88d6-992b-cd32ca31540c
2013-01-16 23:20:59 +00:00
9e68516dac Work on multi-file processing
git-svn-id: http://comictagger.googlecode.com/svn/trunk@300 6c5673fe-1810-88d6-992b-cd32ca31540c
2013-01-16 22:46:22 +00:00
8f45994b9a Added a CLI option for searching by CV issue ID, that can be used when being called by Mylar
git-svn-id: http://comictagger.googlecode.com/svn/trunk@299 6c5673fe-1810-88d6-992b-cd32ca31540c
2013-01-12 01:48:34 +00:00
15 changed files with 1584 additions and 1045 deletions

View File

@ -419,8 +419,8 @@ class ComicArchive:
self.path = path
self.ci_xml_filename = 'ComicInfo.xml'
self.comet_default_filename = 'CoMet.xml'
self.comet_filename = None
self.resetCache()
if self.zipTest():
self.archive_type = self.ArchiveType.Zip
self.archiver = ZipArchiver( self.path )
@ -436,6 +436,14 @@ class ComicArchive:
self.archive_type = self.ArchiveType.Unknown
self.archiver = UnknownArchiver( self.path )
# Clears the cached data
def resetCache( self ):
self.has_cix = None
self.has_cbi = None
self.comet_filename = None
self.page_count = None
self.page_list = None
def setExternalRarProgram( self, rar_exe_path ):
if self.isRar():
self.archiver.rar_exe_path = rar_exe_path
@ -512,12 +520,16 @@ class ComicArchive:
def writeMetadata( self, metadata, style ):
retcode = None
if style == MetaDataStyle.CIX:
return self.writeCIX( metadata )
retcode = self.writeCIX( metadata )
elif style == MetaDataStyle.CBI:
return self.writeCBI( metadata )
retcode = self.writeCBI( metadata )
elif style == MetaDataStyle.COMET:
return self.writeCoMet( metadata )
retcode = self.writeCoMet( metadata )
self.resetCache()
return retcode
def hasMetadata( self, style ):
@ -531,13 +543,16 @@ class ComicArchive:
return False
def removeMetadata( self, style ):
retcode = True
if style == MetaDataStyle.CIX:
return self.removeCIX()
retcode = self.removeCIX()
elif style == MetaDataStyle.CBI:
return self.removeCBI()
retcode = self.removeCBI()
elif style == MetaDataStyle.COMET:
return self.removeCoMet()
retcode = self.removeCoMet()
self.resetCache()
return retcode
def getPage( self, index ):
image_data = None
@ -561,29 +576,32 @@ class ComicArchive:
def getPageNameList( self , sort_list=True):
# get the list file names in the archive, and sort
files = self.archiver.getArchiveFilenameList()
# seems like some archive creators are on Windows, and don't know about case-sensitivity!
if sort_list:
files.sort(key=lambda x: x.lower())
# make a sub-list of image files
page_list = []
for name in files:
if ( name[-4:].lower() in [ ".jpg", "jpeg", ".png" ] and os.path.basename(name)[0] != "." ):
page_list.append(name)
if self.page_list is None:
# get the list file names in the archive, and sort
files = self.archiver.getArchiveFilenameList()
# seems like some archive creators are on Windows, and don't know about case-sensitivity!
if sort_list:
files.sort(key=lambda x: x.lower())
# 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] != "." ):
self.page_list.append(name)
return page_list
return self.page_list
def getNumberOfPages( self ):
return len( self.getPageNameList( sort_list=False ) )
if self.page_count is None:
self.page_count = len( self.getPageNameList( ) )
return self.page_count
def readCBI( self ):
raw_cbi = self.readRawCBI()
if raw_cbi is None:
md =GenericMetadata()
md = GenericMetadata()
else:
md = ComicBookInfo().metadataFromString( raw_cbi )
@ -598,12 +616,16 @@ class ComicArchive:
return self.archiver.getArchiveComment()
def hasCBI(self):
#if ( not ( self.isZip() or self.isRar()) or not self.seemsToBeAComicArchive() ):
if not self.seemsToBeAComicArchive():
return False
if self.has_cbi is None:
comment = self.archiver.getArchiveComment()
return ComicBookInfo().validateString( comment )
#if ( not ( self.isZip() or self.isRar()) or not self.seemsToBeAComicArchive() ):
if not self.seemsToBeAComicArchive():
self.has_cbi = False
else:
comment = self.archiver.getArchiveComment()
self.has_cbi = ComicBookInfo().validateString( comment )
return self.has_cbi
def writeCBI( self, metadata ):
self.applyArchiveInfoToMetadata( metadata )
@ -633,7 +655,6 @@ class ComicArchive:
def readRawCIX( self ):
if not self.hasCIX():
print self.path, "doesn't have ComicInfo.xml data!"
return None
return self.archiver.readArchiveFile( self.ci_xml_filename )
@ -652,12 +673,15 @@ class ComicArchive:
return self.archiver.removeArchiveFile( self.ci_xml_filename )
def hasCIX(self):
if not self.seemsToBeAComicArchive():
return False
elif self.ci_xml_filename in self.archiver.getArchiveFilenameList():
return True
else:
return False
if self.has_cix is None:
if not self.seemsToBeAComicArchive():
self.has_cix = False
elif self.ci_xml_filename in self.archiver.getArchiveFilenameList():
self.has_cix = True
else:
self.has_cix = False
return self.has_cix
def readCoMet( self ):

View File

@ -25,6 +25,7 @@ import zipfile
from genericmetadata import GenericMetadata
import utils
import ctversion
class ComicBookInfo:
@ -102,7 +103,7 @@ class ComicBookInfo:
# Create the dictionary that we will convert to JSON text
cbi = dict()
cbi_container = {'appID' : 'ComicTagger/0.1',
cbi_container = {'appID' : 'ComicTagger/' + ctversion.version,
'lastModified' : str(datetime.now()),
'ComicBookInfo/1.0' : cbi }
@ -110,18 +111,28 @@ class ComicBookInfo:
def assign( cbi_entry, md_entry):
if md_entry is not None:
cbi[cbi_entry] = md_entry
#helper func
def toInt(s):
i = None
if type(s) == str or type(s) == int:
try:
i = int(s)
except ValueError:
pass
return i
assign( 'series', metadata.series )
assign( 'title', metadata.title )
assign( 'issue', metadata.issue )
assign( 'publisher', metadata.publisher )
assign( 'publicationMonth', metadata.month )
assign( 'publicationYear', metadata.year )
assign( 'numberOfIssues', metadata.issueCount )
assign( 'publicationMonth', toInt(metadata.month) )
assign( 'publicationYear', toInt(metadata.year) )
assign( 'numberOfIssues', toInt(metadata.issueCount) )
assign( 'comments', metadata.comments )
assign( 'genre', metadata.genre )
assign( 'volume', metadata.volume )
assign( 'numberOfVolumes', metadata.volumeCount )
assign( 'volume', toInt(metadata.volume) )
assign( 'numberOfVolumes', toInt(metadata.volumeCount) )
assign( 'language', utils.getLanguageFromISO(metadata.language) )
assign( 'country', metadata.country )
assign( 'rating', metadata.criticalRating )

View File

@ -320,65 +320,79 @@ def process_file_cli( filename, opts, settings, match_results ):
# now, search online
if opts.search_online:
ii = IssueIdentifier( ca, settings )
if md is None or md.isEmpty:
print "No metadata given to search online with!"
return
def myoutput( text ):
if opts.verbose:
IssueIdentifier.defaultWriteOutput( text )
if opts.issue_id is not None:
# we were given the actual ID to search with
try:
cv_md = ComicVineTalker().fetchIssueDataByIssueID( opts.issue_id, settings )
except ComicVineTalkerException:
print "Network error while getting issue details. Save aborted"
return None
# use our overlayed MD struct to search
ii.setAdditionalMetadata( md )
ii.onlyUseAdditionalMetaData = True
ii.setOutputFunction( myoutput )
ii.cover_page_index = md.getCoverPageIndexList()[0]
matches = ii.search()
result = ii.search_result
found_match = False
choices = False
low_confidence = False
if result == ii.ResultNoMatches:
pass
elif result == ii.ResultFoundMatchButBadCoverScore:
low_confidence = True
found_match = True
elif result == ii.ResultFoundMatchButNotFirstPage :
found_match = True
elif result == ii.ResultMultipleMatchesWithBadImageScores:
low_confidence = True
choices = True
elif result == ii.ResultOneGoodMatch:
found_match = True
elif result == ii.ResultMultipleGoodMatches:
choices = True
if choices:
print "Online search: Multiple 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)
return
if not found_match:
print "Online search: No match found. Save aborted"
match_results.noMatches.append(filename)
return
# we got here, so we have a single match
# now get the particular issue data
cv_md = actual_issue_data_fetch(matches[0], settings)
if cv_md is None:
return
if cv_md is None:
print "No match for ID {0} was found.".format(opts.issue_id)
return None
if settings.apply_cbl_transform_on_cv_import:
cv_md = CBLTransformer( cv_md, settings ).apply()
else:
ii = IssueIdentifier( ca, settings )
if md is None or md.isEmpty:
print "No metadata given to search online with!"
return
def myoutput( text ):
if opts.verbose:
IssueIdentifier.defaultWriteOutput( text )
# use our overlayed MD struct to search
ii.setAdditionalMetadata( md )
ii.onlyUseAdditionalMetaData = True
ii.setOutputFunction( myoutput )
ii.cover_page_index = md.getCoverPageIndexList()[0]
matches = ii.search()
result = ii.search_result
found_match = False
choices = False
low_confidence = False
if result == ii.ResultNoMatches:
pass
elif result == ii.ResultFoundMatchButBadCoverScore:
low_confidence = True
found_match = True
elif result == ii.ResultFoundMatchButNotFirstPage :
found_match = True
elif result == ii.ResultMultipleMatchesWithBadImageScores:
low_confidence = True
choices = True
elif result == ii.ResultOneGoodMatch:
found_match = True
elif result == ii.ResultMultipleGoodMatches:
choices = True
if choices:
print "Online search: Multiple 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)
return
if not found_match:
print "Online search: No match found. Save aborted"
match_results.noMatches.append(filename)
return
# we got here, so we have a single match
# now get the particular issue data
cv_md = actual_issue_data_fetch(matches[0], settings)
if cv_md is None:
return
md.overlay( cv_md )

View File

@ -201,6 +201,29 @@ class ComicVineTalker(QObject):
else:
return None
# now, map the comicvine data to generic metadata
return self.mapCVDataToMetadata( volume_results, issue_results, settings )
def fetchIssueDataByIssueID( self, issue_id, settings ):
issue_url = "http://api.comicvine.com/issue/" + str(issue_id) + "/?api_key=" + self.api_key + "&format=json"
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' ] ))
return None
issue_results = cv_response['results']
volume_results = self.fetchVolumeData( issue_results['volume']['id'] )
# now, map the comicvine data to generic metadata
md = self.mapCVDataToMetadata( volume_results, issue_results, settings )
md.isEmpty = False
return md
def mapCVDataToMetadata(self, volume_results, issue_results, settings ):
# now, map the comicvine data to generic metadata
metadata = GenericMetadata()
@ -249,11 +272,12 @@ class ComicVineTalker(QObject):
metadata.locations = utils.listToString( location_list )
story_arc_credits = issue_results['story_arc_credits']
for arc in story_arc_credits:
metadata.storyArc = arc['name']
#just use the first one, if at all
break
arc_list = []
for arc in story_arc_credits:
arc_list.append(arc['name'])
if len(arc_list) > 0:
metadata.storyArc = utils.listToString(arc_list)
return metadata
def cleanup_html( self, string):

View File

@ -1,3 +1,3 @@
# This file should contan only these comments, and the line below.
# Used by packaging makefiles and app
version="0.9.4-beta"
version="0.9.5-beta"

217
fileselectionlist.py Normal file
View File

@ -0,0 +1,217 @@
# coding=utf-8
"""
A PyQt4 widget for managing list of files
"""
"""
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 PyQt4.QtCore import pyqtSignal
from settings import ComicTaggerSettings
from comicarchive import ComicArchive
from genericmetadata import GenericMetadata, PageType
from options import MetaDataStyle
class FileTableWidget( QTableWidget ):
def __init__(self, parent ):
super(FileTableWidget, self).__init__(parent)
self.setColumnCount(5)
self.setHorizontalHeaderLabels (["File", "Folder", "CR", "CBL", ""])
self.horizontalHeader().setStretchLastSection( True )
class FileTableWidgetItem(QTableWidgetItem):
def __lt__(self, other):
return (self.data(Qt.UserRole).toBool() <
other.data(Qt.UserRole).toBool())
class FileInfo( ):
def __init__(self, path, ca, cix_md, cbi_md ):
self.path = path
self.cix_md = cix_md
self.cbi_md = cbi_md
self.ca = ca
class FileSelectionList(QWidget):
selectionChanged = pyqtSignal(QVariant)
def __init__(self, parent , settings ):
super(FileSelectionList, self).__init__(parent)
uic.loadUi(os.path.join(ComicTaggerSettings.baseDir(), 'fileselectionlist.ui' ), self)
self.settings = settings
#self.twList = FileTableWidget( self )
#gridlayout = QGridLayout( self )
#gridlayout.addWidget( self.twList )
self.setAcceptDrops(True)
self.twList.itemSelectionChanged.connect( self.itemSelectionChangedCB )
def dragEnterEvent(self, event):
self.droppedFiles = None
if event.mimeData().hasUrls():
# walk through the URL list and build a file list
for url in event.mimeData().urls():
if url.isValid() and url.scheme() == "file":
if self.droppedFiles is None:
self.droppedFiles = []
self.droppedFiles.append(url.toLocalFile())
if self.droppedFiles is not None:
event.accept()
def dropEvent(self, event):
self.addPathList( self.droppedFiles)
event.accept()
def addPathList( self, pathlist ):
filelist = []
for p in pathlist:
# if path is a folder, walk it recursivly, and all files underneath
if os.path.isdir( unicode(p)):
for root,dirs,files in os.walk( unicode(p) ):
for f in files:
filelist.append(os.path.join(root,unicode(f)))
else:
filelist.append(unicode(p))
# we now have a list of files to add
progdialog = QProgressDialog("", "Cancel", 0, len(filelist), self)
progdialog.setWindowTitle( "Adding Files" )
progdialog.setWindowModality(Qt.WindowModal)
self.twList.setSortingEnabled(False)
for idx,f in enumerate(filelist):
QCoreApplication.processEvents()
if progdialog.wasCanceled():
break
progdialog.setValue(idx)
self.addPathItem( f )
progdialog.close()
self.twList.setSortingEnabled(True)
#Maybe set a max size??
self.twList.resizeColumnsToContents()
def isListDupe( self, path ):
r = 0
while r < self.twList.rowCount():
fi = self.twList.item(r, 0).data( Qt.UserRole ).toPyObject()
if fi.path == path:
return True
r = r + 1
return False
def addPathItem( self, path):
path = unicode( path )
#print "processing", path
if self.isListDupe(path):
return
ca = ComicArchive( path )
if self.settings.rar_exe_path != "":
ca.setExternalRarProgram( self.settings.rar_exe_path )
if ca.seemsToBeAComicArchive() :
row = self.twList.rowCount()
self.twList.insertRow( row )
cix_md = None
cbi_md = None
has_cix = ca.hasCIX()
if has_cix:
cix_md = ca.readCIX()
has_cbi = ca.hasCBI()
if has_cbi:
cbi_md = ca.readCBI()
fi = FileInfo( path, ca, cix_md, cbi_md )
item_text = os.path.split(path)[1]
item = QTableWidgetItem(item_text)
item.setFlags(Qt.ItemIsSelectable| Qt.ItemIsEnabled)
item.setData( Qt.UserRole , fi )
item.setData( Qt.ToolTipRole ,item_text)
self.twList.setItem(row, 0, item)
item_text = os.path.split(path)[0]
item = QTableWidgetItem(item_text)
item.setFlags(Qt.ItemIsSelectable| Qt.ItemIsEnabled)
item.setData( Qt.ToolTipRole ,item_text)
self.twList.setItem(row, 1, item)
# Attempt to use a special checkbox widget in the cell.
# Couldn't figure out how to disable it with "enabled" colors
#w = QWidget()
#cb = QCheckBox(w)
#cb.setCheckState(Qt.Checked)
#layout = QHBoxLayout()
#layout.addWidget( cb )
#layout.setAlignment(Qt.AlignHCenter)
#layout.setMargin(2)
#w.setLayout(layout)
#self.twList.setCellWidget( row, 2, w )
item = FileTableWidgetItem()
item.setFlags(Qt.ItemIsSelectable| Qt.ItemIsEnabled)
item.setTextAlignment(Qt.AlignHCenter)
if has_cix:
item.setCheckState(Qt.Checked)
item.setData(Qt.UserRole, True)
else:
item.setData(Qt.UserRole, False)
self.twList.setItem(row, 2, item)
item = FileTableWidgetItem()
item.setFlags(Qt.ItemIsSelectable| Qt.ItemIsEnabled)
item.setTextAlignment(Qt.AlignHCenter)
if has_cbi:
item.setCheckState(Qt.Checked)
item.setData(Qt.UserRole, True)
else:
item.setData(Qt.UserRole, False)
self.twList.setItem(row, 3, item)
def itemSelectionChangedCB( self ):
idx = self.twList.currentRow()
fi = self.twList.item(idx, 0).data( Qt.UserRole ).toPyObject()
#if fi.cix_md is not None:
# print u"{0}".format(fi.cix_md)
self.selectionChanged.emit( QVariant(fi))

69
fileselectionlist.ui Normal file
View File

@ -0,0 +1,69 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>pageListEditor</class>
<widget class="QWidget" name="pageListEditor">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>527</width>
<height>323</height>
</rect>
</property>
<property name="windowTitle">
<string>Form</string>
</property>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QTableWidget" name="twList">
<property name="acceptDrops">
<bool>true</bool>
</property>
<property name="selectionBehavior">
<enum>QAbstractItemView::SelectRows</enum>
</property>
<attribute name="horizontalHeaderDefaultSectionSize">
<number>61</number>
</attribute>
<attribute name="horizontalHeaderMinimumSectionSize">
<number>36</number>
</attribute>
<attribute name="horizontalHeaderStretchLastSection">
<bool>false</bool>
</attribute>
<column>
<property name="text">
<string>File</string>
</property>
</column>
<column>
<property name="text">
<string>Path</string>
</property>
</column>
<column>
<property name="text">
<string>CR</string>
</property>
<property name="toolTip">
<string/>
</property>
<property name="textAlignment">
<set>AlignHCenter|AlignVCenter|AlignCenter</set>
</property>
</column>
<column>
<property name="text">
<string>CBL</string>
</property>
<property name="textAlignment">
<set>AlignHCenter|AlignVCenter|AlignCenter</set>
</property>
</column>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>

View File

@ -132,7 +132,7 @@ class GenericMetadata:
assign( "genre", new_md.genre )
assign( "language", new_md.language )
assign( "country", new_md.country )
assign( "alternateSeries", new_md.criticalRating )
assign( "criticalRating", new_md.criticalRating )
assign( "alternateSeries", new_md.alternateSeries )
assign( "alternateNumber", new_md.alternateNumber )
assign( "alternateCount", new_md.alternateCount )
@ -142,8 +142,8 @@ class GenericMetadata:
assign( "manga", new_md.manga )
assign( "blackAndWhite", new_md.blackAndWhite )
assign( "maturityRating", new_md.maturityRating )
assign( "scanInfo", new_md.scanInfo )
assign( "scanInfo", new_md.scanInfo )
assign( "storyArc", new_md.storyArc )
assign( "seriesGroup", new_md.seriesGroup )
assign( "scanInfo", new_md.scanInfo )
assign( "characters", new_md.characters )
assign( "teams", new_md.teams )

View File

@ -69,6 +69,7 @@ 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
--id=ID Use the issue ID when searching online. Overrides all other metadata
-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"
@ -105,6 +106,7 @@ If no options are given, {0} will run in windowed mode
self.rename_file = False
self.no_overwrite = False
self.interactive = False
self.issue_id = None
self.file_list = []
def display_msg_and_quit( self, msg, code, show_help=False ):
@ -180,7 +182,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", "version" ])
"interactive", "nosummary", "version", "id=" ])
except getopt.GetoptError as err:
self.display_msg_and_quit( str(err), 2 )
@ -219,6 +221,8 @@ If no options are given, {0} will run in windowed mode
self.rename_file = True
if o in ("-f", "--parsefilename"):
self.parse_filename = True
if o == "--id":
self.issue_id = a
if o == "--raw":
self.raw = True
if o == "--noabort":

View File

@ -27,6 +27,7 @@ from PyQt4 import uic
from settings import ComicTaggerSettings
from genericmetadata import GenericMetadata, PageType
from options import MetaDataStyle
from pageloader import PageLoader
def itemMoveEvents( widget ):
@ -79,6 +80,7 @@ class PageListEditor(QWidget):
self.comic_archive = None
self.pages_list = None
self.page_loader = None
self.current_pixmap = QPixmap(os.path.join(ComicTaggerSettings.baseDir(), 'graphics/nocover.png' ))
self.setDisplayPixmap( 0, 0)
@ -151,17 +153,19 @@ 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:
image_data = self.comic_archive.getPage( idx )
else:
image_data = None
if image_data is not None:
img = QImage()
img.loadFromData( image_data )
self.current_pixmap = QPixmap(QPixmap(img))
self.setDisplayPixmap( 0, 0)
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)
def getFirstFrontCover( self ):
frontCover = 0

77
pageloader.py Normal file
View File

@ -0,0 +1,77 @@
"""
A PyQT4 class to load a page image from a ComicArchive in a background thread
"""
"""
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.
"""
from PyQt4 import QtCore, QtGui, uic
from PyQt4.QtCore import pyqtSignal
from comicarchive import ComicArchive
"""
This class holds onto a reference of each instance in a list
since problems occur if the ref count goes to zero and the GC
tries to reap the object while the thread is going.
If the client class wants to stop the thread, they should mark
it as "abandoned", and no signals will be issued
"""
class PageLoader( QtCore.QThread ):
loadComplete = pyqtSignal( QtGui.QImage )
instanceList = []
mutex = QtCore.QMutex()
"""
Remove all finished threads from the list
"""
@staticmethod
def reapInstances():
for obj in reversed(PageLoader.instanceList ):
if obj.isFinished():
PageLoader.instanceList.remove(obj)
def __init__(self, ca, page_num ):
QtCore.QThread.__init__(self)
self.ca = ca
self.page_num = page_num
self.abandoned = False
# remove any old instances, and then add ourself
PageLoader.mutex.lock()
PageLoader.reapInstances()
PageLoader.instanceList.append( self )
PageLoader.mutex.unlock()
def run(self):
image_data = self.ca.getPage( self.page_num )
if self.abandoned:
return
if image_data is not None:
img = QtGui.QImage()
img.loadFromData( image_data )
if self.abandoned:
return
self.loadComplete.emit( img )

View File

@ -1,4 +1,12 @@
---------------------------------
0.9.5-beta - 16-Jan-2013
---------------------------------
Changes:
Added CLI option to search by comicvine issue ID
Some image loading optimizations
Bug Fix: Some CBL fields that should have been ints were written as strings
---------------------------------
0.9.4-beta - 7-Jan-2013
---------------------------------

View File

@ -44,8 +44,10 @@ from filenameparser import FileNameParser
from logwindow import LogWindow
from optionalmsgdialog import OptionalMessageDialog
from pagelisteditor import PageListEditor
from fileselectionlist import FileSelectionList
from cbltransformer import CBLTransformer
from renamewindow import RenameWindow
from pageloader import PageLoader
import utils
import ctversion
@ -91,18 +93,32 @@ class TaggerWindow( QtGui.QMainWindow):
#signal.signal(signal.SIGINT, self.sigint_handler)
uic.loadUi(os.path.join(ComicTaggerSettings.baseDir(), 'taggerwindow.ui' ), self)
self.settings = settings
self.pageListEditor = PageListEditor( self.tabPages )
gridlayout = QtGui.QGridLayout( self.tabPages )
gridlayout.addWidget( self.pageListEditor )
#---------------------------
self.fileSelectionList = FileSelectionList( self.widgetListHolder, self.settings )
gridlayout = QtGui.QGridLayout( self.widgetListHolder )
gridlayout.addWidget( self.fileSelectionList )
self.fileSelectionList.selectionChanged.connect( self.fileListSelectionChanged )
# ATB: Disable the list for now...
self.splitter.setSizes([100,0])
self.splitter.setHandleWidth(0)
self.splitter.handle(1).setDisabled(True)
#---------------------------
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' )))
#print platform.system(), platform.release()
self.dirtyFlag = False
self.settings = settings
self.data_style = settings.last_selected_data_style
#set up a default metadata object
@ -117,6 +133,7 @@ class TaggerWindow( QtGui.QMainWindow):
self.droppedFile = None
self.page_browser = None
self.page_loader = None
self.populateComboBoxes()
@ -362,7 +379,6 @@ class TaggerWindow( QtGui.QMainWindow):
msgBox.setStandardButtons( QtGui.QMessageBox.Ok )
msgBox.exec_()
def dragEnterEvent(self, event):
self.droppedFile=None
if event.mimeData().hasUrls():
@ -371,12 +387,12 @@ class TaggerWindow( QtGui.QMainWindow):
if url.scheme()=="file":
self.droppedFile=url.toLocalFile()
event.accept()
def dropEvent(self, event):
if self.dirtyFlagVerification( "Open Archive",
"If you open a new archive now, data in the form will be lost. Are you sure?"):
self.openArchive( unicode(self.droppedFile))
def openArchive( self, path, explicit_style=None, clear_form=True ):
if path is None or path == "":
@ -431,35 +447,44 @@ class TaggerWindow( QtGui.QMainWindow):
else:
return
if self.metadata.isEmpty:
self.metadata = self.comic_archive.metadataFromFilename( )
self.metadata.setDefaultPageList( self.comic_archive.getNumberOfPages() )
self.updateCoverImage()
if self.page_browser is not None:
self.page_browser.setComicArchive( self.comic_archive )
self.page_browser.metadata = self.metadata
self.metadataToForm()
self.pageListEditor.setData( self.comic_archive, self.metadata.pages )
self.clearDirtyFlag() # also updates the app title
self.updateInfoBox()
self.updateMenus()
#self.updatePagesInfo()
self.loadCurrentArchive()
else:
QtGui.QMessageBox.information(self, self.tr("Whoops!"), self.tr("That file doesn't appear to be a comic archive!"))
def updateCoverImage( self ):
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 )
self.lblCover.setPixmap(QtGui.QPixmap(img))
self.lblCover.setScaledContents(True)
def loadCurrentArchive( self ):
if self.metadata.isEmpty:
self.metadata = self.comic_archive.metadataFromFilename( )
self.metadata.setDefaultPageList( self.comic_archive.getNumberOfPages() )
self.updateCoverImage()
if self.page_browser is not None:
self.page_browser.setComicArchive( self.comic_archive )
self.page_browser.metadata = self.metadata
self.metadataToForm()
self.pageListEditor.setData( self.comic_archive, self.metadata.pages )
self.clearDirtyFlag() # also updates the app title
self.updateInfoBox()
self.updateMenus()
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)
def updateMenus( self ):
# First just disable all the questionable items
@ -1389,5 +1414,19 @@ class TaggerWindow( QtGui.QMainWindow):
self.comic_archive = None
self.openArchive( dlg.new_name )
def fileListSelectionChanged( self, qvarFI ):
fi = qvarFI.toPyObject()
#if fi.cix_md is not None:
# print u"{0}".format(fi.cix_md)
self.comic_archive = None
self.clearForm()
self.comic_archive = fi.ca
if self.data_style == MetaDataStyle.CIX:
self.metadata = fi.cix_md
else:
self.metadata = fi.cbi_md
if self.metadata is None:
self.metadata = GenericMetadata()
self.loadCurrentArchive()

File diff suppressed because it is too large Load Diff

View File

@ -4,13 +4,28 @@ Features
Multi-file:
Does the main UI need to have "View/Read Tag Style" and "Write Tag style" concept?
Create ComicArchiveListWidget
as item is added, new metadata object is attached (for each block??)
How to add items to list?
recursive
drag and drop
Edit functions on list: select, select all, delete,
Batch Functions:
Auto-Select
Start/Options Dialog
Progress Dialog - maybe reuse
Interactive dialog at end
Rename
Start dialog with preview
maybe table with checkboxes?
Copy Block
Verify overwrites
Turn off drop accept for edit lines/boxes
Drop on app goes to list and selects it
Accept multiple files on file open dialog
Warn on moving selection list away from modified form
ComicArchive: cache each metadata block? Need to make sure cache is cleared on file modify
-----------------------------------------------------
Bugs
-----------------------------------------------------