Compare commits
38 Commits
1.1.7-beta
...
1.1.10-bet
Author | SHA1 | Date | |
---|---|---|---|
afcbde7fc6 | |||
151fac5bf1 | |||
57c1efdab9 | |||
6b272cef87 | |||
1cdc732739 | |||
d1b00d162d | |||
3dd3980bc1 | |||
cbf475eb26 | |||
ac8b575659 | |||
ac8ef286a4 | |||
f567dc37be | |||
15c5fc5258 | |||
cc985b52a5 | |||
910b0386be | |||
0fece23405 | |||
eee320e0c7 | |||
accabf8e21 | |||
acc253d35c | |||
ede0154efe | |||
5b805b1428 | |||
2e6b2a89db | |||
c028bb4ddc | |||
b70beb5684 | |||
128af4521b | |||
43cf7a80c8 | |||
3223ed190c | |||
9e2817c037 | |||
6e7bd10fb9 | |||
c099205779 | |||
47d8da0e80 | |||
0f7e88e58c | |||
65902a15b1 | |||
a68b2babeb | |||
4098802e43 | |||
9c14258e9f | |||
33bdbe8be8 | |||
a76864c109 | |||
cb68d07751 |
2
Makefile
2
Makefile
@ -1,4 +1,4 @@
|
||||
TAGGER_BASE := $(HOME)/Dropbox/tagger/comictagger
|
||||
TAGGER_BASE ?:= $(HOME)/Dropbox/tagger/comictagger
|
||||
TAGGER_SRC := $(TAGGER_BASE)/comictaggerlib
|
||||
VERSION_STR := $(shell grep version $(TAGGER_SRC)/ctversion.py| cut -d= -f2 | sed 's/\"//g')
|
||||
PASSWORD := $(shell cat $(TAGGER_BASE)/project_password.txt)
|
||||
|
@ -22,6 +22,7 @@ import sys
|
||||
from PyQt4 import QtCore, QtGui, uic
|
||||
import os
|
||||
from settings import ComicTaggerSettings
|
||||
from coverimagewidget import CoverImageWidget
|
||||
import utils
|
||||
|
||||
class AutoTagProgressWindow(QtGui.QDialog):
|
||||
@ -31,8 +32,17 @@ class AutoTagProgressWindow(QtGui.QDialog):
|
||||
super(AutoTagProgressWindow, self).__init__(parent)
|
||||
|
||||
uic.loadUi(ComicTaggerSettings.getUIFile('autotagprogresswindow.ui' ), self)
|
||||
self.lblTest.setPixmap(QtGui.QPixmap(ComicTaggerSettings.getGraphic('nocover.png')))
|
||||
self.lblArchive.setPixmap(QtGui.QPixmap(ComicTaggerSettings.getGraphic('nocover.png')))
|
||||
|
||||
self.archiveCoverWidget = CoverImageWidget( self.archiveCoverContainer, CoverImageWidget.DataMode, False )
|
||||
gridlayout = QtGui.QGridLayout( self.archiveCoverContainer )
|
||||
gridlayout.addWidget( self.archiveCoverWidget )
|
||||
gridlayout.setContentsMargins(0,0,0,0)
|
||||
|
||||
self.testCoverWidget = CoverImageWidget( self.testCoverContainer, CoverImageWidget.DataMode, False )
|
||||
gridlayout = QtGui.QGridLayout( self.testCoverContainer )
|
||||
gridlayout.addWidget( self.testCoverWidget )
|
||||
gridlayout.setContentsMargins(0,0,0,0)
|
||||
|
||||
self.isdone = False
|
||||
|
||||
self.setWindowFlags(self.windowFlags() |
|
||||
@ -42,23 +52,16 @@ class AutoTagProgressWindow(QtGui.QDialog):
|
||||
utils.reduceWidgetFontSize( self.textEdit )
|
||||
|
||||
def setArchiveImage( self, img_data):
|
||||
self.setCoverImage( img_data, self.lblArchive )
|
||||
self.setCoverImage( img_data, self.archiveCoverWidget)
|
||||
|
||||
def setTestImage( self, img_data):
|
||||
self.setCoverImage( img_data, self.lblTest )
|
||||
self.setCoverImage( img_data, self.testCoverWidget)
|
||||
|
||||
def setCoverImage( self, img_data , label):
|
||||
if img_data is not None:
|
||||
img = QtGui.QImage()
|
||||
img.loadFromData( img_data )
|
||||
label.setPixmap(QtGui.QPixmap(img))
|
||||
label.setScaledContents(True)
|
||||
else:
|
||||
label.setPixmap(QtGui.QPixmap(ComicTaggerSettings.getGraphic('nocover.png')))
|
||||
label.setScaledContents(True)
|
||||
def setCoverImage( self, img_data , widget):
|
||||
widget.setImageData( img_data )
|
||||
QtCore.QCoreApplication.processEvents()
|
||||
QtCore.QCoreApplication.processEvents()
|
||||
|
||||
|
||||
def reject(self):
|
||||
QtGui.QDialog.reject(self)
|
||||
self.isdone = True
|
||||
|
@ -517,14 +517,14 @@ class ComicArchive:
|
||||
self.resetCache()
|
||||
self.settings = settings
|
||||
|
||||
if self.zipTest():
|
||||
self.archive_type = self.ArchiveType.Zip
|
||||
self.archiver = ZipArchiver( self.path )
|
||||
|
||||
elif self.rarTest():
|
||||
if self.rarTest():
|
||||
self.archive_type = self.ArchiveType.Rar
|
||||
self.archiver = RarArchiver( self.path, settings )
|
||||
|
||||
elif self.zipTest():
|
||||
self.archive_type = self.ArchiveType.Zip
|
||||
self.archiver = ZipArchiver( self.path )
|
||||
|
||||
elif os.path.isdir( self.path ):
|
||||
self.archive_type = self.ArchiveType.Folder
|
||||
self.archiver = FolderArchiver( self.path )
|
||||
@ -609,7 +609,7 @@ class ComicArchive:
|
||||
if (
|
||||
( self.isZip() or self.isRar() ) #or self.isFolder() )
|
||||
and
|
||||
( self.getNumberOfPages() > 2)
|
||||
( self.getNumberOfPages() > 0)
|
||||
|
||||
):
|
||||
return True
|
||||
|
@ -260,12 +260,14 @@ class ComicInfoXml:
|
||||
n.tag == 'Letterer' or
|
||||
n.tag == 'Editor'
|
||||
):
|
||||
for name in n.text.split(','):
|
||||
metadata.addCredit( name.strip(), n.tag )
|
||||
if n.text is not None:
|
||||
for name in n.text.split(','):
|
||||
metadata.addCredit( name.strip(), n.tag )
|
||||
|
||||
if n.tag == 'CoverArtist':
|
||||
for name in n.text.split(','):
|
||||
metadata.addCredit( name.strip(), "Cover" )
|
||||
if n.text is not None:
|
||||
for name in n.text.split(','):
|
||||
metadata.addCredit( name.strip(), "Cover" )
|
||||
|
||||
# parse page data now
|
||||
pages_node = root.find( "Pages" )
|
||||
|
@ -24,6 +24,7 @@ from pprint import pprint
|
||||
import urllib2, urllib
|
||||
import math
|
||||
import re
|
||||
import time
|
||||
import datetime
|
||||
import ctversion
|
||||
import sys
|
||||
@ -106,12 +107,28 @@ class ComicVineTalker(QObject):
|
||||
return cv_response[ 'status_code' ] != 100
|
||||
|
||||
def getUrlContent( self, url ):
|
||||
try:
|
||||
resp = urllib2.urlopen( url )
|
||||
return resp.read()
|
||||
except Exception as e:
|
||||
self.writeLog( str(e) )
|
||||
raise ComicVineTalkerException("Network Error!")
|
||||
# connect to server:
|
||||
# if there is a 500 error, try a few more times before giving up
|
||||
# any other error, just bail
|
||||
#print "ATB---", url
|
||||
for tries in range(3):
|
||||
try:
|
||||
resp = urllib2.urlopen( url )
|
||||
return resp.read()
|
||||
except urllib2.HTTPError as e:
|
||||
if e.getcode() == 500:
|
||||
self.writeLog( "Try #{0}: ".format(tries+1) )
|
||||
time.sleep(1)
|
||||
self.writeLog( str(e) + "\n" )
|
||||
|
||||
if e.getcode() != 500:
|
||||
break
|
||||
|
||||
except Exception as e:
|
||||
self.writeLog( str(e) + "\n" )
|
||||
raise ComicVineTalkerException("Network Error!")
|
||||
|
||||
raise ComicVineTalkerException("Error on Comic Vine server")
|
||||
|
||||
def searchForSeries( self, series_name , callback=None, refresh_cache=False ):
|
||||
|
||||
@ -128,11 +145,23 @@ class ComicVineTalker(QObject):
|
||||
return cached_search_results
|
||||
|
||||
original_series_name = series_name
|
||||
|
||||
series_name = urllib.quote_plus(series_name.encode("utf-8"))
|
||||
#series_name = urllib.quote_plus(unicode(series_name))
|
||||
search_url = self.api_base_url + "/search/?api_key=" + self.api_key + "&format=json&resources=volume&query=" + series_name + "&field_list=name,id,start_year,publisher,image,description,count_of_issues"
|
||||
content = self.getUrlContent(search_url)
|
||||
|
||||
# We need to make the series name into an "AND"ed query list
|
||||
query_word_list = series_name.split()
|
||||
and_list = ['AND'] * (len(query_word_list)-1)
|
||||
and_list.append('')
|
||||
# zipper up the two lists
|
||||
query_list = zip(query_word_list, and_list)
|
||||
# flatten the list
|
||||
query_list = [ item for sublist in query_list for item in sublist]
|
||||
# convert back to a string
|
||||
query_string = " ".join( query_list ).strip()
|
||||
#print "Query string = ", query_string
|
||||
|
||||
query_string = urllib.quote_plus(query_string.encode("utf-8"))
|
||||
|
||||
search_url = self.api_base_url + "/search/?api_key=" + self.api_key + "&format=json&resources=volume&query=" + query_string + "&field_list=name,id,start_year,publisher,image,description,count_of_issues"
|
||||
content = self.getUrlContent(search_url + "&page=1")
|
||||
|
||||
cv_response = json.loads(content)
|
||||
|
||||
@ -574,7 +603,7 @@ class ComicVineTalker(QObject):
|
||||
self.urlFetchComplete.emit( details['image_url'],details['thumb_image_url'], self.issue_id )
|
||||
return
|
||||
|
||||
issue_url = "http://www.comicvine.com/api/issue/" + CVTypeID.Issue + "-" + str(issue_id) + "/?api_key=" + self.api_key + "&format=json&field_list=image,cover_date,site_detail_url"
|
||||
issue_url = self.api_base_url + "/issue/" + CVTypeID.Issue + "-" + str(issue_id) + "/?api_key=" + self.api_key + "&format=json&field_list=image,cover_date,site_detail_url"
|
||||
self.nam = QNetworkAccessManager()
|
||||
self.nam.finished.connect( self.asyncFetchIssueCoverURLComplete )
|
||||
self.nam.get(QNetworkRequest(QUrl(issue_url)))
|
||||
|
@ -1,5 +1,7 @@
|
||||
"""
|
||||
A PyQt4 widget display cover images from either local archive, or from ComicVine
|
||||
|
||||
(TODO: This should be re-factored using subclasses!)
|
||||
"""
|
||||
|
||||
"""
|
||||
@ -59,8 +61,9 @@ class CoverImageWidget(QWidget):
|
||||
ArchiveMode = 0
|
||||
AltCoverMode = 1
|
||||
URLMode = 1
|
||||
DataMode = 3
|
||||
|
||||
def __init__(self, parent, mode ):
|
||||
def __init__(self, parent, mode, expand_on_click = True ):
|
||||
super(CoverImageWidget, self).__init__(parent)
|
||||
|
||||
uic.loadUi(ComicTaggerSettings.getUIFile('coverimagewidget.ui' ), self)
|
||||
@ -78,7 +81,10 @@ class CoverImageWidget(QWidget):
|
||||
self.btnLeft.clicked.connect( self.decrementImage )
|
||||
self.btnRight.clicked.connect( self.incrementImage )
|
||||
self.resetWidget()
|
||||
clickable(self.lblImage).connect(self.showPopup)
|
||||
if expand_on_click:
|
||||
clickable(self.lblImage).connect(self.showPopup)
|
||||
else:
|
||||
self.lblImage.setToolTip( "" )
|
||||
|
||||
self.updateContent()
|
||||
|
||||
@ -93,11 +99,12 @@ class CoverImageWidget(QWidget):
|
||||
self.page_loader = None
|
||||
self.imageIndex = -1
|
||||
self.imageCount = 1
|
||||
self.imageData = None
|
||||
|
||||
def clear( self ):
|
||||
self.resetWidget()
|
||||
self.updateContent()
|
||||
|
||||
|
||||
def incrementImage( self ):
|
||||
self.imageIndex += 1
|
||||
if self.imageIndex == self.imageCount:
|
||||
@ -138,7 +145,19 @@ class CoverImageWidget(QWidget):
|
||||
self.comicVine = ComicVineTalker()
|
||||
self.comicVine.urlFetchComplete.connect( self.primaryUrlFetchComplete )
|
||||
self.comicVine.asyncFetchIssueCoverURLs( int(self.issue_id) )
|
||||
|
||||
|
||||
def setImageData( self, image_data ):
|
||||
if self.mode == CoverImageWidget.DataMode:
|
||||
self.resetWidget()
|
||||
|
||||
if image_data is None:
|
||||
self.imageIndex = -1
|
||||
else:
|
||||
self.imageIndex = 0
|
||||
self.imageData = image_data
|
||||
|
||||
self.updateContent()
|
||||
|
||||
def primaryUrlFetchComplete( self, primary_url, thumb_url, issue_id ):
|
||||
self.url_list.append(str(primary_url))
|
||||
self.imageIndex = 0
|
||||
@ -179,11 +198,13 @@ class CoverImageWidget(QWidget):
|
||||
self.loadDefault()
|
||||
elif self.mode in [ CoverImageWidget.AltCoverMode, CoverImageWidget.URLMode ]:
|
||||
self.loadURL()
|
||||
elif self.mode == CoverImageWidget.DataMode:
|
||||
self.coverRemoteFetchComplete( self.imageData, 0 )
|
||||
else:
|
||||
self.loadPage()
|
||||
|
||||
def updateControls( self ):
|
||||
if not self.showControls:
|
||||
if not self.showControls or self.mode == CoverImageWidget.DataMode:
|
||||
self.btnLeft.hide()
|
||||
self.btnRight.hide()
|
||||
self.label.hide()
|
||||
@ -288,3 +309,4 @@ class CoverImageWidget(QWidget):
|
||||
|
||||
def showPopup( self ):
|
||||
self.popup = ImagePopup(self, self.current_pixmap)
|
||||
|
||||
|
@ -1,3 +1,3 @@
|
||||
# This file should contan only these comments, and the line below.
|
||||
# Used by packaging makefiles and app
|
||||
version="1.1.7-beta"
|
||||
version="1.1.10-beta"
|
||||
|
@ -30,31 +30,25 @@ import os
|
||||
from urllib import unquote
|
||||
|
||||
class FileNameParser:
|
||||
|
||||
def repl(self, m):
|
||||
return ' ' * len(m.group())
|
||||
|
||||
def fixSpaces( self, string, remove_dashes=True ):
|
||||
if remove_dashes:
|
||||
placeholders = ['[-_]',' +']
|
||||
else:
|
||||
placeholders = ['[_]',' +']
|
||||
for ph in placeholders:
|
||||
string = re.sub(ph, ' ', string )
|
||||
return string.strip()
|
||||
|
||||
# check for silly .1 or .5 style issue strings
|
||||
# allow up to 5 chars total
|
||||
def isPointIssue( self, word ):
|
||||
ret = False
|
||||
try:
|
||||
float(word)
|
||||
if (len(word) < 5 and not word.isdigit()):
|
||||
ret = True
|
||||
except ValueError:
|
||||
pass
|
||||
return ret
|
||||
string = re.sub(ph, self.repl, string )
|
||||
return string #.strip()
|
||||
|
||||
|
||||
def getIssueCount( self,filename ):
|
||||
def getIssueCount( self,filename, issue_end ):
|
||||
|
||||
count = ""
|
||||
filename = filename[issue_end:]
|
||||
|
||||
# replace any name seperators with spaces
|
||||
tmpstr = self.fixSpaces(filename)
|
||||
found = False
|
||||
@ -74,113 +68,144 @@ class FileNameParser:
|
||||
count = count.lstrip("0")
|
||||
|
||||
return count
|
||||
|
||||
|
||||
|
||||
def getIssueNumber( self, filename ):
|
||||
|
||||
# Returns a tuple of issue number string, and start and end indexs in the filename
|
||||
# (The indexes will be used to split the string up for further parsing)
|
||||
|
||||
found = False
|
||||
issue = ''
|
||||
start = 0
|
||||
end = 0
|
||||
|
||||
# first, look for multiple "--", this means it's formatted differently from most:
|
||||
if "--" in filename:
|
||||
# the pattern seems to be that anything to left of the first "--" is the series name followed by issue
|
||||
filename = filename.split("--")[0]
|
||||
elif "___" in filename:
|
||||
filename = re.sub("--.*", self.repl, filename)
|
||||
|
||||
elif "__" in filename:
|
||||
# the pattern seems to be that anything to left of the first "__" is the series name followed by issue
|
||||
filename = filename.split("__")[0]
|
||||
filename = re.sub("__.*", self.repl, filename)
|
||||
|
||||
filename = filename.replace("+", " ")
|
||||
|
||||
# remove parenthetical phrases
|
||||
filename = re.sub( "\(.*\)", "", filename)
|
||||
filename = re.sub( "\[.*\]", "", filename)
|
||||
|
||||
# guess based on position
|
||||
# replace parenthetical phrases with spaces
|
||||
filename = re.sub( "\(.*?\)", self.repl, filename)
|
||||
filename = re.sub( "\[.*?\]", self.repl, filename)
|
||||
|
||||
# replace any name seperators with spaces
|
||||
tmpstr = self.fixSpaces(filename)
|
||||
word_list = tmpstr.split(' ')
|
||||
filename = self.fixSpaces(filename)
|
||||
|
||||
# remove any "of NN" phrase with spaces (problem: this could break on some titles)
|
||||
filename = re.sub( "of [\d]+", self.repl, filename)
|
||||
|
||||
#print u"[{0}]".format(filename)
|
||||
|
||||
#before we search, remove any kind of likely "of X" phrase
|
||||
for i in range(0, len(word_list)-2):
|
||||
if ( word_list[i].isdigit() and
|
||||
word_list[i+1] == "of" and
|
||||
word_list[i+2].isdigit() ):
|
||||
word_list[i+1] ="XXX"
|
||||
word_list[i+2] ="XXX"
|
||||
|
||||
|
||||
# first look for the last "#" followed by a digit in the filename. this is almost certainly the issue number
|
||||
#issnum = re.search('#\d+', filename)
|
||||
matchlist = re.findall("#[-+]?(([0-9]*\.[0-9]+|[0-9]+)(\w*))", filename)
|
||||
if len(matchlist) > 0:
|
||||
#get the last item
|
||||
issue = matchlist[ len(matchlist) - 1][0]
|
||||
found = True
|
||||
|
||||
# assume the last number in the filename that is under 4 digits is the issue number
|
||||
if not found:
|
||||
for word in reversed(word_list):
|
||||
if len(word) > 0 and word[0] == "#":
|
||||
word = word[1:]
|
||||
if (
|
||||
(word.isdigit() and len(word) < 4) or
|
||||
(self.isPointIssue(word))
|
||||
):
|
||||
issue = word
|
||||
found = True
|
||||
#print 'Assuming issue number is ' + str(issue) + ' based on the position.'
|
||||
break
|
||||
|
||||
if not found:
|
||||
# try a regex
|
||||
issnum = re.search('(?<=[_#\s-])(\d+[a-zA-Z]+|\d+\.\d|\d+)', filename)
|
||||
if issnum:
|
||||
issue = issnum.group()
|
||||
# we should now have a cleaned up filename version with all the words in
|
||||
# the same positions as original filename
|
||||
|
||||
# make a list of each word and its position
|
||||
word_list = list()
|
||||
for m in re.finditer("\S+", filename):
|
||||
word_list.append( (m.group(0), m.start(), m.end()) )
|
||||
|
||||
# remove the first word, since it can't be the issue number
|
||||
if len(word_list) > 1:
|
||||
word_list = word_list[1:]
|
||||
else:
|
||||
#only one word?? just bail.
|
||||
return issue, start, end
|
||||
|
||||
# Now try to search for the likely issue number word in the list
|
||||
|
||||
# first look for a word with "#" followed by digits with optional sufix
|
||||
# this is almost certainly the issue number
|
||||
for w in reversed(word_list):
|
||||
if re.match("#[-]?(([0-9]*\.[0-9]+|[0-9]+)(\w*))", w[0]):
|
||||
found = True
|
||||
#print 'Got the issue using regex. Issue is ' + issue
|
||||
break
|
||||
|
||||
# same as above but w/o a '#', and only look at the last word in the list
|
||||
if not found:
|
||||
w = word_list[-1]
|
||||
if re.match("[-]?(([0-9]*\.[0-9]+|[0-9]+)(\w*))", w[0]):
|
||||
found = True
|
||||
|
||||
# now try to look for a # followed by any characters
|
||||
if not found:
|
||||
for w in reversed(word_list):
|
||||
if re.match("#\S+", w[0]):
|
||||
found = True
|
||||
break
|
||||
|
||||
if found:
|
||||
issue = w[0]
|
||||
start = w[1]
|
||||
end = w[2]
|
||||
if issue[0] == '#':
|
||||
issue = issue[1:]
|
||||
|
||||
return issue, start, end
|
||||
|
||||
return issue.strip()
|
||||
|
||||
def getSeriesName(self, filename, issue ):
|
||||
|
||||
# use the issue number string to split the filename string
|
||||
# assume first element of list is the series name, plus cruft
|
||||
#!!! this could fail in the case of small numerics in the series name!!!
|
||||
|
||||
# TODO: we really should pass in the *INDEX* of the issue, that makes
|
||||
# finding it easier
|
||||
def getSeriesName(self, filename, issue_start ):
|
||||
|
||||
# use the issue number string index to split the filename string
|
||||
|
||||
if issue_start != 0:
|
||||
filename = filename[:issue_start]
|
||||
|
||||
# in case there is no issue number, remove some obvious stuff
|
||||
if "--" in filename:
|
||||
# the pattern seems to be that anything to left of the first "--" is the series name followed by issue
|
||||
filename = re.sub("--.*", self.repl, filename)
|
||||
|
||||
elif "__" in filename:
|
||||
# the pattern seems to be that anything to left of the first "__" is the series name followed by issue
|
||||
filename = re.sub("__.*", self.repl, filename)
|
||||
|
||||
filename = filename.replace("+", " ")
|
||||
tmpstr = self.fixSpaces(filename, remove_dashes=False)
|
||||
|
||||
#remove pound signs. this might mess up the series name if there is a# in it.
|
||||
tmpstr = tmpstr.replace("#", " ")
|
||||
|
||||
if issue != "":
|
||||
# assume that issue substr has at least one space before it
|
||||
issue_str = " " + str(issue)
|
||||
series = tmpstr.split(issue_str)[0]
|
||||
else:
|
||||
# no issue to work off of
|
||||
#!!! TODO we should look for the year, and split from that
|
||||
# and if that doesn't exist, remove parenthetical phrases
|
||||
series = tmpstr
|
||||
series = re.sub( "\(.*\)", "", tmpstr)
|
||||
|
||||
series = tmpstr
|
||||
volume = ""
|
||||
|
||||
#save the last word
|
||||
last_word = series.split()[-1]
|
||||
|
||||
series = series.rstrip("#")
|
||||
# remove any parenthetical phrases
|
||||
series = re.sub( "\(.*?\)", "", series)
|
||||
|
||||
# search for volume number
|
||||
match = re.search('(.+)([vV]|[Vv][oO][Ll]\.?\s?)(\d+)\s*$', series)
|
||||
if match:
|
||||
series = match.group(1)
|
||||
volume = match.group(3)
|
||||
|
||||
return series.strip(), volume.strip()
|
||||
|
||||
# if a volume wasn't found, see if the last word is a year in parentheses
|
||||
# since that's a common way to designate the volume
|
||||
if volume == "":
|
||||
#match either (YEAR), (YEAR-), or (YEAR-YEAR2)
|
||||
match = re.search("(\()(\d{4})(-(\d{4}|)|)(\))", last_word)
|
||||
if match:
|
||||
volume = match.group(2)
|
||||
|
||||
def getYear( self,filename):
|
||||
series = series.strip()
|
||||
|
||||
# if we don't have an issue number (issue_start==0), look
|
||||
# for hints i.e. "TPB", "one-shot", "OS", "OGN", etc that might
|
||||
# be removed to help search online
|
||||
if issue_start == 0:
|
||||
one_shot_words = [ "tpb", "os", "one-shot", "ogn", "gn" ]
|
||||
last_word = series.split()[-1]
|
||||
if last_word.lower() in one_shot_words:
|
||||
series = series.rsplit(' ', 1)[0]
|
||||
|
||||
return series, volume.strip()
|
||||
|
||||
def getYear( self,filename, issue_end):
|
||||
|
||||
filename = filename[issue_end:]
|
||||
|
||||
year = ""
|
||||
# look for four digit number with "(" ")" or "--" around it
|
||||
@ -191,17 +216,17 @@ class FileNameParser:
|
||||
year = re.sub("[^0-9]", "", year)
|
||||
return year
|
||||
|
||||
def getRemainder( self, filename, year, count ):
|
||||
#make a guess at where the the non-interesting stuff begins
|
||||
def getRemainder( self, filename, year, count, issue_end ):
|
||||
|
||||
#make a guess at where the the non-interesting stuff begins
|
||||
remainder = ""
|
||||
|
||||
if "--" in filename:
|
||||
remainder = filename.split("--",1)[1]
|
||||
elif "__" in filename:
|
||||
remainder = filename.split("__",1)[1]
|
||||
elif "(" in filename:
|
||||
remainder = "(" + filename.split("(",1)[1]
|
||||
elif issue_end != 0:
|
||||
remainder = filename[issue_end:]
|
||||
|
||||
remainder = self.fixSpaces(remainder, remove_dashes=False)
|
||||
if year != "":
|
||||
@ -231,11 +256,11 @@ class FileNameParser:
|
||||
filename = filename.replace("_28", "(")
|
||||
filename = filename.replace("_29", ")")
|
||||
|
||||
self.issue = self.getIssueNumber(filename)
|
||||
self.series, self.volume = self.getSeriesName(filename, self.issue)
|
||||
self.year = self.getYear(filename)
|
||||
self.issue_count = self.getIssueCount(filename)
|
||||
self.remainder = self.getRemainder( filename, self.year, self.issue_count )
|
||||
self.issue, issue_start, issue_end = self.getIssueNumber(filename)
|
||||
self.series, self.volume = self.getSeriesName(filename, issue_start)
|
||||
self.year = self.getYear(filename, issue_end)
|
||||
self.issue_count = self.getIssueCount(filename, issue_end)
|
||||
self.remainder = self.getRemainder( filename, self.year, self.issue_count, issue_end )
|
||||
|
||||
if self.issue != "":
|
||||
# strip off leading zeros
|
||||
|
@ -118,12 +118,20 @@ class FileRenamer:
|
||||
new_name = re.sub("\[\s*[-:]*\s*\]", "", new_name )
|
||||
new_name = re.sub("\{\s*[-:]*\s*\}", "", new_name )
|
||||
|
||||
# remove remove duplicate -, _,
|
||||
new_name = re.sub("[-_]+\s+", "- ", new_name )
|
||||
new_name = re.sub("(\s-)+", " -", new_name )
|
||||
|
||||
# remove duplicate spaces
|
||||
new_name = u" ".join(new_name.split())
|
||||
|
||||
# remove remove duplicate -, _,
|
||||
new_name = re.sub("[-_]{2,}\s+", "-- ", new_name )
|
||||
new_name = re.sub("(\s--)+", " --", new_name )
|
||||
new_name = re.sub("(\s-)+", " -", new_name )
|
||||
|
||||
# remove dash or double dash at end of line
|
||||
new_name = re.sub("[-]{1,2}\s*$", "", new_name )
|
||||
|
||||
# remove duplicate spaces (again!)
|
||||
new_name = u" ".join(new_name.split())
|
||||
|
||||
|
||||
if ext is None:
|
||||
ext = os.path.splitext( filename )[1]
|
||||
@ -132,7 +140,9 @@ class FileRenamer:
|
||||
|
||||
# some tweaks to keep various filesystems happy
|
||||
new_name = new_name.replace("/", "-")
|
||||
new_name = new_name.replace(":", "-")
|
||||
new_name = new_name.replace(" :", " -")
|
||||
new_name = new_name.replace(": ", " - ")
|
||||
new_name = new_name.replace(":", "-")
|
||||
new_name = new_name.replace("?", "")
|
||||
|
||||
return new_name
|
||||
|
@ -162,6 +162,9 @@ class FileSelectionList(QWidget):
|
||||
self.twList.currentItemChanged.connect( self.currentItemChangedCB )
|
||||
|
||||
if self.twList.rowCount() > 0:
|
||||
# since on a removal, we select row 0, make sure callback occurs if we're already there
|
||||
if self.twList.currentRow() == 0:
|
||||
self.currentItemChangedCB( self.twList.currentItem(), None)
|
||||
self.twList.selectRow(0)
|
||||
else:
|
||||
self.listCleared.emit()
|
||||
@ -195,7 +198,13 @@ class FileSelectionList(QWidget):
|
||||
progdialog.close()
|
||||
if firstAdded is not None:
|
||||
self.twList.selectRow(firstAdded)
|
||||
else:
|
||||
if len(pathlist) == 1 and os.path.isfile(pathlist[0]):
|
||||
QMessageBox.information(self, self.tr("File Open"), self.tr("Selected file doesn't seem to be a comic archive."))
|
||||
else:
|
||||
QMessageBox.information(self, self.tr("File/Folder Open"), self.tr("No comic archives were found."))
|
||||
|
||||
|
||||
self.twList.setSortingEnabled(True)
|
||||
|
||||
# Adjust column size
|
||||
|
@ -157,6 +157,7 @@ class IssueIdentifier:
|
||||
search_keys['issue_number'] = None
|
||||
search_keys['month'] = None
|
||||
search_keys['year'] = None
|
||||
search_keys['issue_count'] = None
|
||||
|
||||
if ca is None:
|
||||
return
|
||||
@ -166,6 +167,7 @@ class IssueIdentifier:
|
||||
search_keys['issue_number'] = self.additional_metadata.issue
|
||||
search_keys['year'] = self.additional_metadata.year
|
||||
search_keys['month'] = self.additional_metadata.month
|
||||
search_keys['issue_count'] = self.additional_metadata.issueCount
|
||||
return search_keys
|
||||
|
||||
# see if the archive has any useful meta data for searching with
|
||||
@ -211,6 +213,13 @@ class IssueIdentifier:
|
||||
search_keys['month'] = internal_metadata.month
|
||||
else:
|
||||
search_keys['month'] = md_from_filename.month
|
||||
|
||||
if self.additional_metadata.issueCount is not None:
|
||||
search_keys['issue_count'] = self.additional_metadata.issueCount
|
||||
elif internal_metadata.issueCount is not None:
|
||||
search_keys['issue_count'] = internal_metadata.issueCount
|
||||
else:
|
||||
search_keys['issue_count'] = md_from_filename.issueCount
|
||||
|
||||
return search_keys
|
||||
|
||||
@ -360,6 +369,8 @@ class IssueIdentifier:
|
||||
self.log_msg( "Going to search for:" )
|
||||
self.log_msg( "\tSeries: " + keys['series'] )
|
||||
self.log_msg( "\tIssue : " + keys['issue_number'] )
|
||||
if keys['issue_count'] is not None:
|
||||
self.log_msg( "\tCount : " + str(keys['issue_count']) )
|
||||
if keys['year'] is not None:
|
||||
self.log_msg( "\tYear : " + str(keys['year']) )
|
||||
if keys['month'] is not None:
|
||||
@ -409,11 +420,6 @@ class IssueIdentifier:
|
||||
|
||||
if length_approved and publisher_approved and date_approved:
|
||||
series_second_round_list.append(item)
|
||||
|
||||
# if we don't think it's an issue number 1, remove any series' that are one-shots
|
||||
#if keys['issue_number'] not in [ '1', '0', '0.1' ]:
|
||||
# #self.log_msg( "Removing one-shots" )
|
||||
# series_second_round_list[:] = [x for x in series_second_round_list if not x['count_of_issues'] == 1]
|
||||
|
||||
self.log_msg( "Searching in " + str(len(series_second_round_list)) +" series" )
|
||||
|
||||
@ -423,7 +429,6 @@ class IssueIdentifier:
|
||||
# now sort the list by name length
|
||||
series_second_round_list.sort(key=lambda x: len(x['name']), reverse=False)
|
||||
|
||||
#--------new way---------------
|
||||
#build a list of volume IDs
|
||||
volume_id_list = list()
|
||||
for series in series_second_round_list:
|
||||
@ -446,47 +451,6 @@ class IssueIdentifier:
|
||||
shortlist.append( (series, issue) )
|
||||
break
|
||||
|
||||
#--------new way---------------
|
||||
|
||||
"""
|
||||
#--vvvv---old way---------------
|
||||
# Now we've got a list of series that we can dig into look for matching issue number
|
||||
counter = 0
|
||||
shortlist = []
|
||||
for series in series_second_round_list:
|
||||
if self.callback is not None:
|
||||
self.callback( counter, len(series_second_round_list)*3)
|
||||
counter += 1
|
||||
|
||||
self.log_msg( u"Fetching info for ID: {0} {1} ({2}) ...".format(
|
||||
series['id'],
|
||||
series['name'],
|
||||
series['start_year']), newline=True )
|
||||
|
||||
try:
|
||||
issue_list = comicVine.fetchIssuesByVolume( series['id'] )
|
||||
|
||||
except ComicVineTalkerException:
|
||||
self.log_msg( "Network issue while searching for series details. Aborting...")
|
||||
return []
|
||||
|
||||
for issue in issue_list:
|
||||
num_s = IssueString(issue['issue_number']).asString()
|
||||
|
||||
# look for a matching issue number
|
||||
if num_s.lower() == keys['issue_number'].lower():
|
||||
|
||||
# now, if we have an issue year key given, reject this one if not a match
|
||||
month, year = comicVine.fetchIssueDate( issue['id'] )
|
||||
if keys['year'] is not None:
|
||||
if unicode(keys['year']) != unicode(year):
|
||||
break
|
||||
|
||||
# found a matching issue number! add it to short list
|
||||
shortlist.append( (series, issue) )
|
||||
#--^^^^---old way---------------
|
||||
"""
|
||||
|
||||
if keys['year'] is None:
|
||||
self.log_msg( u"Found {0} series that have an issue #{1}".format(len(shortlist), keys['issue_number']) )
|
||||
else:
|
||||
@ -528,6 +492,7 @@ class IssueIdentifier:
|
||||
match['series'] = u"{0} ({1})".format(series['name'], series['start_year'])
|
||||
match['distance'] = score_item['score']
|
||||
match['issue_number'] = keys['issue_number']
|
||||
match['cv_issue_count'] = series['count_of_issues']
|
||||
match['url_image_hash'] = score_item['hash']
|
||||
match['issue_title'] = issue['name']
|
||||
match['issue_id'] = issue['id']
|
||||
@ -640,6 +605,19 @@ class IssueIdentifier:
|
||||
if item['distance'] > best_score + self.min_score_distance:
|
||||
self.match_list.remove(item)
|
||||
|
||||
# One more test for the case choosing limited series first issue vs a trade with the same cover:
|
||||
# if we have a given issue count > 1 and the volume from CV has count==1, remove it from match list
|
||||
if len(self.match_list) >= 2 and keys['issue_count'] is not None and keys['issue_count'] != 1:
|
||||
new_list = list()
|
||||
for match in self.match_list:
|
||||
if match['cv_issue_count'] != 1:
|
||||
new_list.append(match)
|
||||
else:
|
||||
self.log_msg("Removing volume {0} [{1}] from consideration (only 1 issue)".format(match['series'], match['volume_id']))
|
||||
|
||||
if len(new_list) > 0:
|
||||
self.match_list = new_list
|
||||
|
||||
if len(self.match_list) == 1:
|
||||
self.log_msg( u"--------------------------------------------------")
|
||||
print_match(self.match_list[0])
|
||||
@ -652,6 +630,7 @@ class IssueIdentifier:
|
||||
self.log_msg( u"--------------------------------------------------")
|
||||
self.search_result = self.ResultNoMatches
|
||||
else:
|
||||
# we've got multiple good matches:
|
||||
self.log_msg( "More than one likley candiate." )
|
||||
self.search_result = self.ResultMultipleGoodMatches
|
||||
self.log_msg( u"--------------------------------------------------")
|
||||
|
@ -8,6 +8,7 @@ e.g.:
|
||||
"0"
|
||||
"-1"
|
||||
"5AU"
|
||||
"100-2"
|
||||
|
||||
"""
|
||||
|
||||
@ -34,31 +35,53 @@ import re
|
||||
class IssueString:
|
||||
def __init__(self, text):
|
||||
|
||||
if text is None:
|
||||
self.num = None
|
||||
self.suffix = ""
|
||||
return
|
||||
|
||||
self.text = unicode(text)
|
||||
#strip out non float-y stuff
|
||||
tmp_num_str = re.sub('[^0-9.-]',"", self.text )
|
||||
# break up the issue number string into 2 parts: the numeric and suffix string.
|
||||
# ( assumes that the numeric portion is always first )
|
||||
|
||||
self.num = None
|
||||
self.suffix = ""
|
||||
|
||||
if tmp_num_str == "":
|
||||
self.num = None
|
||||
self.suffix = self.text
|
||||
|
||||
if text is None:
|
||||
return
|
||||
|
||||
text = unicode(text)
|
||||
|
||||
#skip the minus sign if it's first
|
||||
if text[0] == '-':
|
||||
start = 1
|
||||
else:
|
||||
if tmp_num_str.count(".") > 1:
|
||||
#make sure it's a valid float or int.
|
||||
parts = tmp_num_str.split('.')
|
||||
self.num = float( parts[0] + '.' + parts[1] )
|
||||
else:
|
||||
self.num = float( tmp_num_str )
|
||||
|
||||
self.suffix = ""
|
||||
parts = self.text.split(tmp_num_str)
|
||||
if len( parts ) > 1 :
|
||||
self.suffix = parts[1]
|
||||
start = 0
|
||||
|
||||
# walk through the string, look for split point (the first non-numeric)
|
||||
decimal_count = 0
|
||||
for idx in range( start, len(text) ):
|
||||
if text[idx] not in "0123456789.":
|
||||
break
|
||||
# special case: also split on second "."
|
||||
if text[idx] == ".":
|
||||
decimal_count += 1
|
||||
if decimal_count > 1:
|
||||
break
|
||||
else:
|
||||
idx = len(text)
|
||||
|
||||
# move trailing numeric decimal to suffix
|
||||
# (only if there is other junk after )
|
||||
if text[idx-1] == "." and len(text) != idx:
|
||||
idx = idx -1
|
||||
|
||||
# if there is no numeric after the minus, make the minus part of the suffix
|
||||
if idx == 1 and start == 1:
|
||||
idx = 0
|
||||
|
||||
part1 = text[0:idx]
|
||||
part2 = text[idx:len(text)]
|
||||
|
||||
if part1 != "":
|
||||
self.num = float( part1 )
|
||||
self.suffix = part2
|
||||
|
||||
#print "num: {0} suf: {1}".format(self.num, self.suffix)
|
||||
|
||||
def asString( self, pad = 0 ):
|
||||
#return the float, left side zero-padded, with suffix attached
|
||||
|
@ -25,6 +25,11 @@ import os
|
||||
import traceback
|
||||
import ctversion
|
||||
import utils
|
||||
try:
|
||||
import argparse
|
||||
except:
|
||||
pass
|
||||
|
||||
from genericmetadata import GenericMetadata
|
||||
from comicarchive import MetaDataStyle
|
||||
from versionchecker import VersionChecker
|
||||
@ -175,6 +180,41 @@ For more help visit the wiki at: http://code.google.com/p/comictagger/
|
||||
#print md
|
||||
return md
|
||||
|
||||
def launch_script(self, scriptfile):
|
||||
# we were given a script. special case for the args:
|
||||
# 1. ignore everthing before the -S,
|
||||
# 2. pass all the ones that follow (including script name) to the script
|
||||
script_args = list()
|
||||
for idx, arg in enumerate(sys.argv):
|
||||
if arg in [ '-S', '--script']:
|
||||
#found script!
|
||||
script_args = sys.argv[idx+1:]
|
||||
break
|
||||
sys.argv = script_args
|
||||
if not os.path.exists(scriptfile):
|
||||
print "Can't find {0}".format( scriptfile )
|
||||
else:
|
||||
# I *think* this makes sense:
|
||||
# assume the base name of the file is the module name
|
||||
# add the folder of the given file to the python path
|
||||
# import module
|
||||
dirname = os.path.dirname(scriptfile)
|
||||
module_name = os.path.splitext(os.path.basename(scriptfile))[0]
|
||||
sys.path = [dirname] + sys.path
|
||||
try:
|
||||
script = __import__(module_name)
|
||||
|
||||
# Determine if the entry point exists before trying to run it
|
||||
if "main" in dir(script):
|
||||
script.main()
|
||||
else:
|
||||
print "Can't find entry point \"main()\" in module \"{0}\"".format( module_name )
|
||||
except Exception as e:
|
||||
print "Script raised an unhandled exception: ", e
|
||||
print traceback.format_exc()
|
||||
|
||||
sys.exit(0)
|
||||
|
||||
def parseCmdLineArgs(self):
|
||||
|
||||
if platform.system() == "Darwin" and hasattr(sys, "frozen") and sys.frozen == 1:
|
||||
@ -183,6 +223,15 @@ For more help visit the wiki at: http://code.google.com/p/comictagger/
|
||||
else:
|
||||
input_args = sys.argv[1:]
|
||||
|
||||
# first check if we're launching a script:
|
||||
for n in range(len(input_args)):
|
||||
if ( input_args[n] in [ "-S", "--script" ] and
|
||||
n+1 < len(input_args)):
|
||||
# insert a "--" which will cause getopt to ignore the remaining args
|
||||
# so they will be passed to the script
|
||||
input_args.insert(n+2, "--")
|
||||
break
|
||||
|
||||
# parse command line options
|
||||
try:
|
||||
opts, args = getopt.getopt( input_args,
|
||||
@ -258,7 +307,7 @@ For more help visit the wiki at: http://code.google.com/p/comictagger/
|
||||
print "ComicTagger {0}: Copyright (c) 2012-2013 Anthony Beville".format(ctversion.version)
|
||||
print "Distributed under Apache License 2.0 (http://www.apache.org/licenses/LICENSE-2.0)"
|
||||
new_version = VersionChecker().getLatestVersion("", False)
|
||||
if new_version is not None:
|
||||
if new_version is not None and new_version != ctversion.version:
|
||||
print "----------------------------------------"
|
||||
print "New version available online: {0}".format(new_version)
|
||||
print "----------------------------------------"
|
||||
@ -289,40 +338,8 @@ For more help visit the wiki at: http://code.google.com/p/comictagger/
|
||||
self.display_msg_and_quit( "Must choose only one action of print, delete, save, copy, rename, export, or run script", 1 )
|
||||
|
||||
if self.script is not None:
|
||||
# we were given a script. special case for the args:
|
||||
# 1. ignore everthing before the -S,
|
||||
# 2. pass all the ones that follow (including script name) to the script
|
||||
script_args = list()
|
||||
for idx, arg in enumerate(sys.argv):
|
||||
if arg in [ '-S', '--script']:
|
||||
#found script!
|
||||
script_args = sys.argv[idx+1:]
|
||||
break
|
||||
sys.argv = script_args
|
||||
if not os.path.exists(self.script):
|
||||
print "Can't find {0}".format( self.script )
|
||||
else:
|
||||
# I *think* this makes sense:
|
||||
# assume the base name of the file is the module name
|
||||
# add the folder of the given file to the python path
|
||||
# import module
|
||||
dirname = os.path.dirname(self.script)
|
||||
module_name = os.path.splitext(os.path.basename(self.script))[0]
|
||||
sys.path = [dirname] + sys.path
|
||||
try:
|
||||
script = __import__(module_name)
|
||||
|
||||
# Determine if the entry point exists before trying to run it
|
||||
if "main" in dir(script):
|
||||
script.main()
|
||||
else:
|
||||
print "Can't find entry point \"main()\" in module \"{0}\"".format( module_name )
|
||||
except Exception as e:
|
||||
print "Script raised an unhandled exception: ", e
|
||||
print traceback.format_exc()
|
||||
|
||||
sys.exit(0)
|
||||
|
||||
self.launch_script( self.script )
|
||||
|
||||
if len(args) > 0:
|
||||
if platform.system() == "Windows":
|
||||
# no globbing on windows shell, so do it for them
|
||||
|
@ -220,7 +220,8 @@ class TaggerWindow( QtGui.QMainWindow):
|
||||
self.settings.send_usage_stats = True
|
||||
self.settings.ask_about_usage_stats = False
|
||||
|
||||
self.checkLatestVersionOnline()
|
||||
if self.settings.check_for_new_version:
|
||||
self.checkLatestVersionOnline()
|
||||
|
||||
def sigint_handler(self, *args):
|
||||
# defer the actual close in the app loop thread
|
||||
@ -330,7 +331,7 @@ class TaggerWindow( QtGui.QMainWindow):
|
||||
self.actionApplyCBLTransform.setStatusTip( 'Modify tags specifically for CBL format' )
|
||||
self.actionApplyCBLTransform.triggered.connect( self.applyCBLTransform )
|
||||
|
||||
#self.actionClearEntryForm.setShortcut( 'Ctrl+C' )
|
||||
self.actionClearEntryForm.setShortcut( 'Ctrl+Shift+C' )
|
||||
self.actionClearEntryForm.setStatusTip( 'Clear all the data on the screen' )
|
||||
self.actionClearEntryForm.triggered.connect( self.clearForm )
|
||||
|
||||
@ -926,14 +927,17 @@ class TaggerWindow( QtGui.QMainWindow):
|
||||
else:
|
||||
QtGui.QMessageBox.information(self, self.tr("Online Search"), self.tr("Need to enter a series name to search."))
|
||||
return
|
||||
|
||||
|
||||
|
||||
year = str(self.lePubYear.text()).strip()
|
||||
if year == "":
|
||||
year = None
|
||||
|
||||
issue_count = str(self.leIssueCount.text()).strip()
|
||||
if issue_count == "":
|
||||
issue_count = None
|
||||
|
||||
cover_index_list = self.metadata.getCoverPageIndexList()
|
||||
selector = VolumeSelectionWindow( self, series_name, issue_number, year, cover_index_list, self.comic_archive, self.settings, autoselect )
|
||||
selector = VolumeSelectionWindow( self, series_name, issue_number, year, issue_count, cover_index_list, self.comic_archive, self.settings, autoselect )
|
||||
|
||||
title = "Search: '" + series_name + "' - "
|
||||
selector.setWindowTitle( title + "Select Series")
|
||||
|
@ -21,6 +21,44 @@
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout">
|
||||
<item row="0" column="1">
|
||||
<widget class="QWidget" name="archiveCoverContainer">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Fixed" vsizetype="Expanding">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>110</width>
|
||||
<height>165</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>110</width>
|
||||
<height>165</height>
|
||||
</size>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="4">
|
||||
<widget class="QWidget" name="testCoverContainer">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>110</width>
|
||||
<height>165</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>110</width>
|
||||
<height>165</height>
|
||||
</size>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="2">
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<widget class="QProgressBar" name="progressBar">
|
||||
@ -66,50 +104,6 @@
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="lblArchive">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Fixed" vsizetype="Expanding">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>110</width>
|
||||
<height>165</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>110</width>
|
||||
<height>165</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>TextLabel</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="2">
|
||||
<widget class="QLabel" name="lblTest">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>110</width>
|
||||
<height>165</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>110</width>
|
||||
<height>165</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>TextLabel</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources/>
|
||||
|
@ -126,9 +126,13 @@ def removearticles( text ):
|
||||
|
||||
# now get rid of some other junk
|
||||
newText = newText.replace(":", "")
|
||||
newText = newText.replace(".", "")
|
||||
newText = newText.replace(",", "")
|
||||
newText = newText.replace("-", " ")
|
||||
|
||||
# since the CV api changed, searches for series names with periods
|
||||
# now explicity require the period to be in the search key,
|
||||
# so the line below is removed (for now)
|
||||
#newText = newText.replace(".", "")
|
||||
|
||||
return newText
|
||||
|
||||
|
@ -87,7 +87,7 @@ class IdentifyThread( QtCore.QThread):
|
||||
|
||||
class VolumeSelectionWindow(QtGui.QDialog):
|
||||
|
||||
def __init__(self, parent, series_name, issue_number, year, cover_index_list, comic_archive, settings, autoselect=False):
|
||||
def __init__(self, parent, series_name, issue_number, year, issue_count, cover_index_list, comic_archive, settings, autoselect=False):
|
||||
super(VolumeSelectionWindow, self).__init__(parent)
|
||||
|
||||
uic.loadUi(ComicTaggerSettings.getUIFile('volumeselectionwindow.ui' ), self)
|
||||
@ -108,6 +108,7 @@ class VolumeSelectionWindow(QtGui.QDialog):
|
||||
self.series_name = series_name
|
||||
self.issue_number = issue_number
|
||||
self.year = year
|
||||
self.issue_count = issue_count
|
||||
self.volume_id = 0
|
||||
self.comic_archive = comic_archive
|
||||
self.immediate_autoselect = autoselect
|
||||
@ -161,6 +162,7 @@ class VolumeSelectionWindow(QtGui.QDialog):
|
||||
md.series = self.series_name
|
||||
md.issue = self.issue_number
|
||||
md.year = self.year
|
||||
md.issueCount = self.issue_count
|
||||
|
||||
self.ii.setAdditionalMetadata( md )
|
||||
self.ii.onlyUseAdditionalMetaData = True
|
||||
|
@ -1,6 +1,6 @@
|
||||
#PYINSTALLER_CMD := VERSIONER_PYTHON_PREFER_32_BIT=yes arch -i386 python $(HOME)/pyinstaller-2.0/pyinstaller.py
|
||||
PYINSTALLER_CMD := python $(HOME)/pyinstaller-2.0/pyinstaller.py
|
||||
TAGGER_BASE := $(HOME)/Dropbox/tagger/comictagger
|
||||
TAGGER_BASE ?= $(HOME)/Dropbox/tagger/comictagger
|
||||
TAGGER_SRC := $(TAGGER_BASE)/comictaggerlib
|
||||
|
||||
APP_NAME := ComicTagger
|
||||
|
@ -1,3 +1,25 @@
|
||||
---------------------------------
|
||||
1.1.10-beta - 30-Jan-2014
|
||||
---------------------------------
|
||||
* Updated series query to match changes on Comic Vine side
|
||||
* Added a message when not able to open a file or folder
|
||||
* Fixed an issue where series names with periods would fail on search
|
||||
* Other misc bug fixes
|
||||
|
||||
---------------------------------
|
||||
1.1.9-beta - 8-May-2013
|
||||
---------------------------------
|
||||
* Filename parser and identification enhancements
|
||||
* Misc bug fixes
|
||||
|
||||
---------------------------------
|
||||
1.1.8-beta - 21-Apr-2013
|
||||
---------------------------------
|
||||
* Handle occasional error 500 from Comic Vine by retrying a few times
|
||||
* Nicer handling of colon (":") in file rename
|
||||
* Fixed command-line option parsing issue for add-on scripts
|
||||
* Misc bug fixes
|
||||
|
||||
---------------------------------
|
||||
1.1.7-beta - 12-Apr-2013
|
||||
---------------------------------
|
||||
|
151
scripts/name_fixer.py
Executable file
151
scripts/name_fixer.py
Executable file
@ -0,0 +1,151 @@
|
||||
#!/usr/bin/python
|
||||
"""
|
||||
fix the comic file names using a list of transforms
|
||||
"""
|
||||
|
||||
"""
|
||||
Copyright 2013 Anthony Beville
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
"""
|
||||
|
||||
import sys
|
||||
import os
|
||||
import re
|
||||
import argparse
|
||||
import json
|
||||
|
||||
from comictaggerlib.comicarchive import *
|
||||
from comictaggerlib.settings import *
|
||||
from comictaggerlib.filerenamer import *
|
||||
import comictaggerlib.utils
|
||||
|
||||
def parse_args():
|
||||
|
||||
input_args = sys.argv[1:]
|
||||
|
||||
parser = argparse.ArgumentParser(description='a script to rename comic files')
|
||||
parser.add_argument('-t', '--transforms', metavar='xformfile', help="the file with transforms")
|
||||
parser.add_argument('-n', '--noconfirm', action='store_true', help="don't confirm before rename")
|
||||
parser.add_argument('paths', metavar='PATH', type=str, nargs='+', help='path to look for comic files')
|
||||
parsed_args = parser.parse_args(input_args)
|
||||
|
||||
return parsed_args
|
||||
|
||||
def caclulate_rename(ca, md, settings):
|
||||
|
||||
new_ext = None # default
|
||||
if settings.rename_extension_based_on_archive:
|
||||
if ca.isZip():
|
||||
new_ext = ".cbz"
|
||||
elif ca.isRar():
|
||||
new_ext = ".cbr"
|
||||
|
||||
renamer = FileRenamer( md )
|
||||
renamer.setTemplate( "%series% V%volume% #%issue% (of %issuecount%) (%year%) %scaninfo%" )
|
||||
renamer.setIssueZeroPadding( 0 )
|
||||
renamer.setSmartCleanup( settings.rename_use_smart_string_cleanup )
|
||||
|
||||
return renamer.determineName( ca.path, ext=new_ext )
|
||||
|
||||
def perform_rename(filelist):
|
||||
for old_name,new_name in filelist:
|
||||
folder = os.path.dirname( os.path.abspath( old_name ) )
|
||||
new_abs_path = utils.unique_file( os.path.join( folder, new_name ) )
|
||||
|
||||
os.rename( old_name, new_abs_path )
|
||||
print u"renamed '{0}' -> '{1}'".format(os.path.basename(old_name), new_name)
|
||||
|
||||
def main():
|
||||
|
||||
default_xform_list = [
|
||||
[ "^2000AD$", "2000 AD" ],
|
||||
[ "^G\.{0,1}I\.{0,1}Joe$", "G.I. Joe" ],
|
||||
]
|
||||
|
||||
utils.fix_output_encoding()
|
||||
settings = ComicTaggerSettings()
|
||||
|
||||
style = MetaDataStyle.CIX
|
||||
|
||||
parsed_args = parse_args()
|
||||
|
||||
#parsed_args.noconfirm
|
||||
if parsed_args.transforms is not None:
|
||||
print "Reading in transforms from:", parsed_args.transforms
|
||||
json_data=open(parsed_args.transforms).read()
|
||||
data = json.loads(json_data)
|
||||
xform_list = data['xforms']
|
||||
else:
|
||||
xform_list = default_xform_list
|
||||
|
||||
#pprint( xform_list, indent=4)
|
||||
|
||||
filelist = utils.get_recursive_filelist( parsed_args.paths )
|
||||
|
||||
#first find all comics
|
||||
print "reading in all comics..."
|
||||
comic_list = []
|
||||
max_name_len = 2
|
||||
fmt_str = ""
|
||||
for filename in filelist:
|
||||
ca = ComicArchive(filename, settings )
|
||||
# do we care if it already has metadata?
|
||||
if ca.seemsToBeAComicArchive() and not ca.hasMetadata( style ):
|
||||
|
||||
comic_list.append(ca)
|
||||
|
||||
max_name_len = max ( max_name_len, len(filename))
|
||||
fmt_str = u"{{0:{0}}}".format(max_name_len)
|
||||
print >> sys.stderr, fmt_str.format( filename ) + "\r",
|
||||
sys.stderr.flush()
|
||||
|
||||
print >> sys.stderr, fmt_str.format( "" )
|
||||
print "Found {0} comics.".format( len(comic_list))
|
||||
|
||||
modify_list = list()
|
||||
# walk through the comic list fix the filenames
|
||||
for ca in comic_list:
|
||||
|
||||
# 1. parse the filename into a MD object
|
||||
md = ca.metadataFromFilename()
|
||||
# 2. walk thru list of transforms
|
||||
if md.series is not None and md.series != "":
|
||||
for pattern, replacement in xform_list:
|
||||
# apply each transform
|
||||
new_series = re.sub( pattern, replacement, md.series )
|
||||
if new_series != md.series:
|
||||
md.series = new_series
|
||||
new_name = caclulate_rename(ca, md, settings)
|
||||
|
||||
#found a match. add to proposed list, and bail on this file
|
||||
modify_list.append( (ca.path, new_name ) )
|
||||
break
|
||||
|
||||
print "{0} filenames to modify".format(len(modify_list))
|
||||
if len(modify_list) > 0:
|
||||
if parsed_args.noconfirm:
|
||||
print "Not confirming before rename"
|
||||
else:
|
||||
for old_name, new_name in modify_list:
|
||||
print u"'{0}' -> '{1}'".format(os.path.basename(old_name), new_name)
|
||||
|
||||
i = raw_input("Do you want to proceed with rename? [y/N] ")
|
||||
if i.lower() not in ('y', 'yes'):
|
||||
print "exiting without rename."
|
||||
sys.exit(0)
|
||||
|
||||
perform_rename(modify_list)
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
8
scripts/xforms
Normal file
8
scripts/xforms
Normal file
@ -0,0 +1,8 @@
|
||||
{
|
||||
"_comment": "This file contains JSON data. Any backslashes should be escaped with another backslash",
|
||||
"xforms":
|
||||
[
|
||||
[ "^2000AD$", "2000 AD" ],
|
||||
[ "^G\\.{0,1}I\\.{0,1}Joe$", "G.I. Joe" ]
|
||||
]
|
||||
}
|
28
todo.txt
28
todo.txt
@ -4,13 +4,25 @@ Features
|
||||
Rename dialog:
|
||||
check-box for rows?
|
||||
manual edit the preview?
|
||||
|
||||
|
||||
Maybe replace configparser -- seems to be causing all sorts of problems
|
||||
|
||||
Feature Requests:
|
||||
Move CBR to other folder after conversion to ZIP
|
||||
|
||||
NO - AUto-rename on auto-tag
|
||||
NO- Re-zip (to remove compression)
|
||||
Pre-process series name before identification
|
||||
(using a list of regex transforms)
|
||||
(GC #28) Save auto-tag options
|
||||
(GC #24) Multiple options for -t i.e. "-t cr,cbl"
|
||||
(GC #18 ) Option for handling colon in rename
|
||||
(GC #31 ) Specify CV Series ID for auto-tag
|
||||
Re-org - move to new folder based on template
|
||||
|
||||
Denied Requests (for now):
|
||||
Auto-rename on auto-tag
|
||||
Re-zip (to remove compression)
|
||||
|
||||
|
||||
Selective fields on CLI print (use -m option. Maybe internally remove all but specified fields in MD object before print )
|
||||
|
||||
Docs:
|
||||
Auto-Tagging Tips:
|
||||
@ -20,18 +32,18 @@ Docs:
|
||||
Bugs
|
||||
-----------------------------------------------------
|
||||
|
||||
Non-numeric issues?? Filename parsing... can we only rely on '#'??
|
||||
|
||||
|
||||
Zip flakes out when filename differs from index (or whatever) i.e "\" vs "/". Python issue
|
||||
|
||||
|
||||
|
||||
-----------------------------------------------------
|
||||
Big Future Features
|
||||
-----------------------------------------------------
|
||||
Support for ACBF metatdata in CBZ
|
||||
|
||||
GCD scraper or DB reader
|
||||
|
||||
Batch Edit
|
||||
(GC #29) Batch Edit
|
||||
Form Mode: Single vs Batch
|
||||
|
||||
-----------------------------------------------------
|
||||
|
@ -2,7 +2,7 @@
|
||||
# rm, cp, grep, cut, cat
|
||||
|
||||
HOMEPATH ?= $(HOME)
|
||||
TAGGER_BASE:= $(HOMEPATH)/Dropbox/tagger/comictagger
|
||||
TAGGER_BASE?= $(HOMEPATH)/Dropbox/tagger/comictagger
|
||||
TAGGER_SRC := $(TAGGER_BASE)/comictaggerlib
|
||||
DIST_DIR := $(TAGGER_BASE)\windows\dist
|
||||
NSIS_CMD := "C:\Program Files (x86)\NSIS\makensis.exe"
|
||||
|
Reference in New Issue
Block a user