\n",
"
\n"
])
- if options.panelview and not noPV:
+ if (options.panelview or forcePV) and not noPV:
+ options.panelviewused = True
if not noHorizontalPV and not noVerticalPV:
if rotatedPage:
if options.righttoleft:
@@ -167,8 +172,8 @@ def buildHTML(path, imgfile, imgfilepath):
f.writelines(["
\n"])
- if options.quality == 2:
- imgfilepv = str.split(imgfile, ".")
+ if options.quality == 2 and not forcePV:
+ imgfilepv = imgfile.split(".")
imgfilepv[0] += "-hq"
imgfilepv = ".".join(imgfilepv)
else:
@@ -215,10 +220,14 @@ def buildNCX(dstdir, title, chapters, chapterNames):
])
for chapter in chapters:
folder = chapter[0].replace(os.path.join(dstdir, 'OEBPS'), '').lstrip('/').lstrip('\\\\')
- if os.path.basename(folder) != "Text":
- title = chapterNames[os.path.basename(folder)]
filename = getImageFileName(os.path.join(folder, chapter[1]))
- f.write("
"
+ navID = folder.replace('/', '_').replace('\\', '_')
+ if options.chapters:
+ title = chapterNames[chapter[1]]
+ navID = filename[0].replace('/', '_').replace('\\', '_')
+ elif os.path.basename(folder) != "Text":
+ title = chapterNames[os.path.basename(folder)]
+ f.write(""
+ title + "\n")
f.write("\n")
@@ -304,6 +313,7 @@ def buildEPUB(path, chapterNames, tomeNumber):
filelist = []
chapterlist = []
cover = None
+ lastfile = None
_, deviceres, _, _, panelviewsize = options.profileData
os.mkdir(os.path.join(path, 'OEBPS', 'Text'))
f = open(os.path.join(path, 'OEBPS', 'Text', 'style.css'), 'w', encoding='UTF-8')
@@ -416,12 +426,14 @@ def buildEPUB(path, chapterNames, tomeNumber):
"}",
])
f.close()
- for (dirpath, dirnames, filenames) in os.walk(os.path.join(path, 'OEBPS', 'Images')):
+ for (dirpath, dirnames, filenames) in walk(os.path.join(path, 'OEBPS', 'Images')):
chapter = False
+ dirnames, filenames = walkSort(dirnames, filenames)
for afile in filenames:
filename = getImageFileName(afile)
if '-kcc-hq' not in filename[0]:
filelist.append(buildHTML(dirpath, afile, os.path.join(dirpath, afile)))
+ lastfile = (dirpath, afile, os.path.join(dirpath, afile))
if not chapter:
chapterlist.append((dirpath.replace('Images', 'Text'), filelist[-1][1]))
chapter = True
@@ -429,32 +441,40 @@ def buildEPUB(path, chapterNames, tomeNumber):
cover = os.path.join(os.path.join(path, 'OEBPS', 'Images'),
'cover' + getImageFileName(filelist[-1][1])[1])
image.Cover(os.path.join(filelist[-1][0], filelist[-1][1]), cover, options, tomeNumber)
+ # Hack that force Panel View on at last one page
+ if lastfile and not options.panelviewused and 'Ko' not in options.profile \
+ and options.profile not in ['K1', 'K2', 'KDX', 'OTHER']:
+ filelist[-1] = buildHTML(lastfile[0], lastfile[1], lastfile[2], True)
+ # Overwrite chapternames if tree is flat and ComicInfo.xml has bookmarks
+ if not chapterNames and options.chapters:
+ chapterlist = []
+ globaldiff = 0
+ for aChapter in options.chapters:
+ pageid = aChapter[0]
+ for x in range(0, pageid + globaldiff + 1):
+ if '-aaa-kcc' in filelist[x][1]:
+ pageid += 1
+ if '-bbb-kcc' in filelist[pageid][1]:
+ pageid -= 1
+ filename = filelist[pageid][1]
+ chapterlist.append((filelist[pageid][0].replace('Images', 'Text'), filename))
+ chapterNames[filename] = aChapter[1]
+ globaldiff = pageid - (aChapter[0] + globaldiff)
buildNCX(path, options.title, chapterlist, chapterNames)
- # Ensure we're sorting files alphabetically
- convert = lambda text: int(text) if text.isdigit() else text
- alphanum_key = lambda key: [convert(c) for c in split('([0-9]+)', key)]
- filelist.sort(key=lambda name: (alphanum_key(name[0].lower()), alphanum_key(name[1].lower())))
buildOPF(path, options.title, filelist, cover)
-def imgOptimization(img, opt, hqImage=None):
+def imgOptimization(img, opt):
if not img.fill:
img.getImageFill()
if not opt.webtoon:
img.cropWhiteSpace()
if opt.cutpagenumbers and not opt.webtoon:
img.cutPageNumber()
- img.optimizeImage()
- if hqImage:
- img.resizeImage(0)
- img.calculateBorder(hqImage, True)
- else:
- img.resizeImage()
- if opt.panelview:
- if opt.quality == 0:
- img.calculateBorder(img)
- elif opt.quality == 1:
- img.calculateBorder(img, True)
+ img.autocontrastImage()
+ img.resizeImage()
+ if not img.second and opt.panelview:
+ img.calculateBorder()
if opt.forcepng and not opt.forcecolor:
img.quantizeImage()
@@ -467,7 +487,7 @@ def imgDirectoryProcessing(path):
options.imgPurgeIndex = []
work = []
pagenumber = 0
- for (dirpath, dirnames, filenames) in os.walk(path):
+ for (dirpath, dirnames, filenames) in walk(path):
for afile in filenames:
pagenumber += 1
work.append([afile, dirpath, options])
@@ -516,10 +536,6 @@ def imgFileProcessing(work):
opt = work[2]
output = []
img = image.ComicPage(os.path.join(dirpath, afile), opt)
- if opt.quality == 2:
- wipe = False
- else:
- wipe = True
if opt.nosplitrotate:
splitter = None
else:
@@ -527,36 +543,30 @@ def imgFileProcessing(work):
if splitter is not None:
img0 = image.ComicPage(splitter[0], opt)
imgOptimization(img0, opt)
- output.append(img0.saveToDir(dirpath))
+ if not img0.noHQ:
+ output.append(img0.saveToDir(dirpath))
img1 = image.ComicPage(splitter[1], opt)
imgOptimization(img1, opt)
- output.append(img1.saveToDir(dirpath))
- if wipe:
- output.append(img0.origFileName)
- output.append(img1.origFileName)
+ if not img1.noHQ:
+ output.append(img1.saveToDir(dirpath))
+ output.extend([img.origFileName, img0.origFileName, img1.origFileName])
if opt.quality == 2:
- img0b = image.ComicPage(splitter[0], opt, img0.fill)
- imgOptimization(img0b, opt, img0)
+ output.extend([img0.origFileName, img1.origFileName])
+ img0b = image.ComicPage(splitter[0], opt, img0)
+ imgOptimization(img0b, opt)
output.append(img0b.saveToDir(dirpath))
- img1b = image.ComicPage(splitter[1], opt, img1.fill)
- imgOptimization(img1b, opt, img1)
+ img1b = image.ComicPage(splitter[1], opt, img1)
+ imgOptimization(img1b, opt)
output.append(img1b.saveToDir(dirpath))
- output.append(img0.origFileName)
- output.append(img1.origFileName)
- output.append(img.origFileName)
else:
+ output.append(img.origFileName)
imgOptimization(img, opt)
- output.append(img.saveToDir(dirpath))
- if wipe:
- output.append(img.origFileName)
+ if not img.noHQ:
+ output.append(img.saveToDir(dirpath))
if opt.quality == 2:
- img2 = image.ComicPage(os.path.join(dirpath, afile), opt, img.fill)
- if img.rotated:
- img2.image = img2.image.rotate(90, Image.BICUBIC, True)
- img2.rotated = True
- imgOptimization(img2, opt, img)
+ img2 = image.ComicPage(os.path.join(dirpath, afile), opt, img)
+ imgOptimization(img2, opt)
output.append(img2.saveToDir(dirpath))
- output.append(img.origFileName)
return output
except Exception:
return str(sys.exc_info()[1])
@@ -568,7 +578,7 @@ def getWorkFolder(afile):
if os.path.isdir(afile):
workdir = mkdtemp('', 'KCC-TMP-')
try:
- os.rmdir(workdir) # needed for copytree() fails if dst already exists
+ os.rmdir(workdir)
fullPath = os.path.join(workdir, 'OEBPS', 'Images')
if len(fullPath) > 240:
raise UserWarning("Path is too long.")
@@ -633,6 +643,7 @@ def getComicInfo(path, originalPath):
xmlPath = os.path.join(path, 'ComicInfo.xml')
options.authors = ['KCC']
options.remoteCovers = {}
+ options.chapters = []
titleSuffix = ''
if options.title == 'defaulttitle':
defaultTitle = True
@@ -644,57 +655,43 @@ def getComicInfo(path, originalPath):
defaultTitle = False
if os.path.exists(xmlPath):
try:
- xml = parse(xmlPath)
+ xml = metadata.MetadataParser(xmlPath)
except Exception:
os.remove(xmlPath)
return
options.authors = []
if defaultTitle:
- if len(xml.getElementsByTagName('Series')) != 0:
- options.title = xml.getElementsByTagName('Series')[0].firstChild.nodeValue
- if len(xml.getElementsByTagName('Volume')) != 0:
- titleSuffix += ' V' + xml.getElementsByTagName('Volume')[0].firstChild.nodeValue
- if len(xml.getElementsByTagName('Number')) != 0:
- titleSuffix += ' #' + xml.getElementsByTagName('Number')[0].firstChild.nodeValue
+ if xml.data['Series']:
+ options.title = xml.data['Series']
+ if xml.data['Volume']:
+ titleSuffix += ' V' + xml.data['Volume']
+ if xml.data['Number']:
+ titleSuffix += ' #' + xml.data['Number']
options.title += titleSuffix
- if len(xml.getElementsByTagName('Writer')) != 0:
- authorsTemp = str.split(xml.getElementsByTagName('Writer')[0].firstChild.nodeValue, ', ')
- for author in authorsTemp:
- options.authors.append(author)
- if len(xml.getElementsByTagName('Penciller')) != 0:
- authorsTemp = str.split(xml.getElementsByTagName('Penciller')[0].firstChild.nodeValue, ', ')
- for author in authorsTemp:
- options.authors.append(author)
- if len(xml.getElementsByTagName('Inker')) != 0:
- authorsTemp = str.split(xml.getElementsByTagName('Inker')[0].firstChild.nodeValue, ', ')
- for author in authorsTemp:
- options.authors.append(author)
- if len(xml.getElementsByTagName('Colorist')) != 0:
- authorsTemp = str.split(xml.getElementsByTagName('Colorist')[0].firstChild.nodeValue, ', ')
- for author in authorsTemp:
- options.authors.append(author)
+ for field in ['Writers', 'Pencillers', 'Inkers', 'Colorists']:
+ for person in xml.data[field]:
+ options.authors.append(person)
if len(options.authors) > 0:
options.authors = list(set(options.authors))
options.authors.sort()
else:
options.authors = ['KCC']
- # Disabled due to closure of MCD
- # if len(xml.getElementsByTagName('ScanInformation')) != 0:
- # coverId = xml.getElementsByTagName('ScanInformation')[0].firstChild.nodeValue
- # coverId = compile('(MCD\\()(\\d+)(\\))').search(coverId)
- # if coverId:
- # options.remoteCovers = getCoversFromMCB(coverId.group(2))
+ if xml.data['MUid']:
+ options.remoteCovers = getCoversFromMCB(xml.data['MUid'])
+ if xml.data['Bookmarks']:
+ options.chapters = xml.data['Bookmarks']
os.remove(xmlPath)
def getCoversFromMCB(mangaID):
covers = {}
try:
- jsonRaw = urlopen(Request('http://manga.joentjuh.nl/json/series/' + mangaID + '/',
+ jsonRaw = urlopen(Request('http://mcd.iosphe.re/api/v1/series/' + mangaID + '/',
headers={'User-Agent': 'KindleComicConverter/' + __version__}))
jsonData = loads(jsonRaw.readall().decode('utf-8'))
- for volume in jsonData['volumes']:
- covers[int(volume['volume'])] = volume['releases'][0]['files']['front']['url']
+ for volume in jsonData['Covers']['a']:
+ if volume['Side'] == 'front':
+ covers[int(volume['Volume'])] = volume['Raw']
except Exception:
return {}
return covers
@@ -702,7 +699,7 @@ def getCoversFromMCB(mangaID):
def getDirectorySize(start_path='.'):
total_size = 0
- for dirpath, dirnames, filenames in os.walk(start_path):
+ for dirpath, dirnames, filenames in walk(start_path):
for f in filenames:
fp = os.path.join(dirpath, f)
total_size += os.path.getsize(fp)
@@ -711,7 +708,7 @@ def getDirectorySize(start_path='.'):
def sanitizeTree(filetree):
chapterNames = {}
- for root, dirs, files in os.walk(filetree, False):
+ for root, dirs, files in walk(filetree, False):
for name in files:
splitname = os.path.splitext(name)
slugified = slugify(splitname[0])
@@ -721,7 +718,7 @@ def sanitizeTree(filetree):
newKey = os.path.join(root, slugified + splitname[1])
key = os.path.join(root, name)
if key != newKey:
- os.replace(key, newKey)
+ saferReplace(key, newKey)
for name in dirs:
tmpName = name
slugified = slugify(name)
@@ -731,15 +728,14 @@ def sanitizeTree(filetree):
newKey = os.path.join(root, slugified)
key = os.path.join(root, name)
if key != newKey:
- os.replace(key, newKey)
+ saferReplace(key, newKey)
return chapterNames
def sanitizeTreeKobo(filetree):
pageNumber = 0
- for root, dirs, files in os.walk(filetree):
- files.sort()
- dirs.sort()
+ for root, dirs, files in walk(filetree):
+ dirs, files = walkSort(dirs, files)
for name in files:
splitname = os.path.splitext(name)
slugified = str(pageNumber).zfill(5)
@@ -750,11 +746,11 @@ def sanitizeTreeKobo(filetree):
newKey = os.path.join(root, slugified + splitname[1])
key = os.path.join(root, name)
if key != newKey:
- os.replace(key, newKey)
+ saferReplace(key, newKey)
def sanitizePermissions(filetree):
- for root, dirs, files in os.walk(filetree, False):
+ for root, dirs, files in walk(filetree, False):
for name in files:
os.chmod(os.path.join(root, name), S_IWRITE | S_IREAD)
for name in dirs:
@@ -886,7 +882,7 @@ def splitProcess(path, mode):
def detectCorruption(tmpPath, orgPath):
- for root, dirs, files in os.walk(tmpPath, False):
+ for root, dirs, files in walk(tmpPath, False):
for name in files:
if getImageFileName(name) is not None:
path = os.path.join(root, name)
@@ -915,20 +911,20 @@ def detectMargins(path):
yu = flag[2]
xr = flag[3]
yd = flag[4]
- if xl != "0":
- xl = "-" + str(float(xl)/100) + "%"
+ if xl != "0.0":
+ xl = "-" + xl + "%"
else:
xl = "0%"
- if xr != "0":
- xr = "-" + str(float(xr)/100) + "%"
+ if xr != "0.0":
+ xr = "-" + xr + "%"
else:
xr = "0%"
- if yu != "0":
- yu = "-" + str(float(yu)/100) + "%"
+ if yu != "0.0":
+ yu = "-" + yu + "%"
else:
yu = "0%"
- if yd != "0":
- yd = "-" + str(float(yd)/100) + "%"
+ if yd != "0.0":
+ yd = "-" + yd + "%"
else:
yd = "0%"
return xl, yu, xr, yd
@@ -953,7 +949,7 @@ def makeZIP(zipFilename, baseDir, isEPUB=False):
zipOutput = ZipFile(zipFilename, 'w', ZIP_DEFLATED)
if isEPUB:
zipOutput.writestr('mimetype', 'application/epub+zip', ZIP_STORED)
- for dirpath, dirnames, filenames in os.walk(baseDir):
+ for dirpath, dirnames, filenames in walk(baseDir):
for name in filenames:
path = os.path.normpath(os.path.join(dirpath, name))
aPath = os.path.normpath(os.path.join(dirpath.replace(baseDir, ''), name))
@@ -1034,6 +1030,7 @@ def makeParser():
def checkOptions():
global options
options.panelview = True
+ options.panelviewused = False
options.bordersColor = None
if options.format == 'Auto':
if options.profile in ['K1', 'K2', 'K345', 'KPW', 'KV', 'KFHD', 'KFHDX', 'KFHDX8', 'KFA']:
@@ -1131,10 +1128,7 @@ def makeBook(source, qtGUI=None):
getComicInfo(os.path.join(path, "OEBPS", "Images"), source)
detectCorruption(os.path.join(path, "OEBPS", "Images"), source)
if options.webtoon:
- if options.customheight > 0:
- comic2panel.main(['-y ' + str(options.customheight), '-i', '-m', path], qtGUI)
- else:
- comic2panel.main(['-y ' + str(image.ProfileData.Profiles[options.profile][1][1]), '-i', '-m', path], qtGUI)
+ comic2panel.main(['-y ' + str(image.ProfileData.Profiles[options.profile][1][1]), '-i', '-m', path], qtGUI)
if options.imgproc:
print("\nProcessing images...")
if GUI:
diff --git a/kcc/comic2panel.py b/kcc/comic2panel.py
index 277f4c7..920a071 100644
--- a/kcc/comic2panel.py
+++ b/kcc/comic2panel.py
@@ -18,18 +18,14 @@
# PERFORMANCE OF THIS SOFTWARE.
#
-__version__ = '4.4.1'
-__license__ = 'ISC'
-__copyright__ = '2012-2015, Ciro Mattia Gonano , Pawel Jastrzebski '
-__docformat__ = 'restructuredtext en'
-
import os
import sys
from shutil import rmtree, copytree, move
from optparse import OptionParser, OptionGroup
from multiprocessing import Pool
from PIL import Image, ImageStat, ImageOps
-from .shared import getImageFileName, walkLevel
+from scandir import walk
+from .shared import getImageFileName, walkLevel, walkSort
try:
from PyQt5 import QtCore
except ImportError:
@@ -251,7 +247,8 @@ def main(argv=None, qtGUI=None):
mergeWorkerOutput = []
mergeWorkerPool = Pool()
mergeWork.append([options.targetDir])
- for root, dirs, files in os.walk(options.targetDir, False):
+ for root, dirs, files in walk(options.targetDir, False):
+ dirs, files = walkSort(dirs, files)
for directory in dirs:
directoryNumer += 1
mergeWork.append([os.path.join(root, directory)])
@@ -269,7 +266,7 @@ def main(argv=None, qtGUI=None):
rmtree(options.targetDir, True)
raise RuntimeError("One of workers crashed. Cause: " + mergeWorkerOutput[0])
print("\nSplitting images...")
- for root, dirs, files in os.walk(options.targetDir, False):
+ for root, dirs, files in walk(options.targetDir, False):
for name in files:
if getImageFileName(name) is not None:
pagenumber += 1
diff --git a/kcc/image.py b/kcc/image.py
index 618b9ff..d830e79 100755
--- a/kcc/image.py
+++ b/kcc/image.py
@@ -16,11 +16,6 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
-__version__ = '4.4.1'
-__license__ = 'ISC'
-__copyright__ = '2012-2015, Ciro Mattia Gonano , Pawel Jastrzebski '
-__docformat__ = 'restructuredtext en'
-
import os
from io import BytesIO
from urllib.request import Request, urlopen
@@ -28,6 +23,7 @@ from urllib.parse import quote
from functools import reduce
from PIL import Image, ImageOps, ImageStat, ImageChops
from .shared import md5Checksum
+from . import __version__
class ProfileData:
@@ -102,7 +98,7 @@ class ProfileData:
class ComicPage:
- def __init__(self, source, options, fill=None):
+ def __init__(self, source, options, original=None):
try:
self.profile_label, self.size, self.palette, self.gamma, self.panelviewsize = options.profileData
except KeyError:
@@ -111,58 +107,67 @@ class ComicPage:
self.filename = os.path.basename(self.origFileName)
self.image = Image.open(source)
self.image = self.image.convert('RGB')
- self.rotated = None
- self.border = None
- self.noHPV = None
- self.noVPV = None
- self.noPV = None
- self.purge = False
- self.hq = False
self.opt = options
- if fill:
- self.fill = fill
+ if original:
+ self.second = True
+ self.rotated = original.rotated
+ self.border = original.border
+ self.noHPV = original.noHPV
+ self.noVPV = original.noVPV
+ self.noPV = original.noPV
+ self.noHQ = original.noHQ
+ self.fill = original.fill
+ self.color = original.color
+ if self.rotated:
+ self.image = self.image.rotate(90, Image.BICUBIC, True)
+ self.opt.quality = 0
else:
+ self.second = False
+ self.rotated = None
+ self.border = None
+ self.noHPV = None
+ self.noVPV = None
+ self.noPV = None
self.fill = None
- if options.webtoon:
- self.color = True
- else:
- self.color = self.isImageColor()
+ self.noHQ = False
+ if options.webtoon:
+ self.color = True
+ else:
+ self.color = self.isImageColor()
def saveToDir(self, targetdir):
try:
- if not self.purge:
- flags = []
- filename = os.path.join(targetdir, os.path.splitext(self.filename)[0]) + '-KCC'
- if not self.opt.forcecolor and not self.opt.forcepng:
- self.image = self.image.convert('L')
- if self.rotated:
- flags.append('Rotated')
- if self.hq:
- flags.append('HighQuality')
- filename += '-HQ'
- if self.noPV:
- flags.append('NoPanelView')
- else:
- if self.noHPV:
- flags.append('NoHorizontalPanelView')
- if self.noVPV:
- flags.append('NoVerticalPanelView')
- if self.border:
- flags.append('Margins-' + str(self.border[0]) + '-' + str(self.border[1]) + '-'
- + str(self.border[2]) + '-' + str(self.border[3]))
- if self.opt.forcepng:
- filename += '.png'
- self.image.save(filename, 'PNG', optimize=1)
- else:
- filename += '.jpg'
- self.image.save(filename, 'JPEG', optimize=1, quality=80)
- return [md5Checksum(filename), flags]
+ flags = []
+ filename = os.path.join(targetdir, os.path.splitext(self.filename)[0]) + '-KCC'
+ if not self.opt.forcecolor and not self.opt.forcepng:
+ self.image = self.image.convert('L')
+ if self.rotated:
+ flags.append('Rotated')
+ if self.noPV:
+ flags.append('NoPanelView')
else:
- return None
+ if self.noHPV:
+ flags.append('NoHorizontalPanelView')
+ if self.noVPV:
+ flags.append('NoVerticalPanelView')
+ if self.border:
+ flags.append('Margins-' + str(self.border[0]) + '-' + str(self.border[1]) + '-'
+ + str(self.border[2]) + '-' + str(self.border[3]))
+ if self.fill != 'white':
+ flags.append('BlackFill')
+ if self.opt.quality == 2:
+ filename += '-HQ'
+ if self.opt.forcepng:
+ filename += '.png'
+ self.image.save(filename, 'PNG', optimize=1)
+ else:
+ filename += '.jpg'
+ self.image.save(filename, 'JPEG', optimize=1, quality=80)
+ return [md5Checksum(filename), flags]
except IOError as e:
raise RuntimeError('Cannot write image in directory %s: %s' % (targetdir, e))
- def optimizeImage(self):
+ def autocontrastImage(self):
gamma = self.opt.gamma
if gamma < 0.1:
gamma = self.gamma
@@ -184,80 +189,65 @@ class ComicPage:
# Quantize is deprecated but new function call it internally anyway...
self.image = self.image.quantize(palette=palImg)
- def calculateBorderPercent(self, x, img, isWidth):
- if isWidth:
- return int(round(float(x)/float(img.image.size[0]), 4) * 10000 * 1.5)
- else:
- return int(round(float(x)/float(img.image.size[1]), 4) * 10000 * 1.5)
-
- def calculateBorder(self, sourceImage, isHQ=False):
- if (isHQ and sourceImage.purge) or self.noPV:
- self.border = [0, 0, 0, 0]
- self.noPV = True
+ def calculateBorder(self):
+ if self.noPV:
+ self.border = [0.0, 0.0, 0.0, 0.0]
return
if self.fill == 'white':
- # Only already saved files can have P mode. So we can break color quantization.
- if sourceImage.image.mode == 'P':
- sourceImage.image = sourceImage.image.convert('RGB')
- border = ImageChops.invert(sourceImage.image).getbbox()
+ border = ImageChops.invert(self.image).getbbox()
else:
- border = sourceImage.image.getbbox()
+ border = self.image.getbbox()
+ if self.opt.quality == 2:
+ multiplier = 1.0
+ else:
+ multiplier = 1.5
if border is not None:
- if isHQ:
- multiplier = 1.0
- else:
- multiplier = 1.5
- self.border = [self.calculateBorderPercent(border[0], sourceImage, True),
- self.calculateBorderPercent(border[1], sourceImage, False),
- self.calculateBorderPercent((sourceImage.image.size[0] - border[2]), sourceImage, True),
- self.calculateBorderPercent((sourceImage.image.size[1] - border[3]), sourceImage, False)]
- if int((border[2] - border[0]) * multiplier) < self.size[0]:
+ self.border = [round(float(border[0])/float(self.image.size[0])*150, 3),
+ round(float(border[1])/float(self.image.size[1])*150, 3),
+ round(float(self.image.size[0]-border[2])/float(self.image.size[0])*150, 3),
+ round(float(self.image.size[1]-border[3])/float(self.image.size[1])*150, 3)]
+ if int((border[2] - border[0]) * multiplier) < self.size[0] + 10:
self.noHPV = True
- if int((border[3] - border[1]) * multiplier) < self.size[1]:
+ if int((border[3] - border[1]) * multiplier) < self.size[1] + 10:
self.noVPV = True
else:
- self.border = [0, 0, 0, 0]
+ self.border = [0.0, 0.0, 0.0, 0.0]
self.noHPV = True
self.noVPV = True
- def resizeImage(self, qualityMode=None):
- upscale = self.opt.upscale
- stretch = self.opt.stretch
- bordersColor = self.opt.bordersColor
- if qualityMode is None:
- qualityMode = self.opt.quality
- if bordersColor:
- fill = bordersColor
+ def resizeImage(self):
+ if self.opt.bordersColor:
+ fill = self.opt.bordersColor
else:
fill = self.fill
# Set target size
- if qualityMode == 0:
+ if self.opt.quality == 0:
size = (self.size[0], self.size[1])
- elif qualityMode == 1 and not stretch and not upscale and self.image.size[0] <=\
+ elif self.opt.quality == 1 and not self.opt.stretch and not self.opt.upscale and self.image.size[0] <=\
self.size[0] and self.image.size[1] <= self.size[1]:
size = (self.size[0], self.size[1])
- elif qualityMode == 1:
+ elif self.opt.quality == 1:
# Forcing upscale to make sure that margins will be not too big
- if not stretch:
- upscale = True
+ if not self.opt.stretch:
+ self.opt.upscale = True
size = (self.panelviewsize[0], self.panelviewsize[1])
- elif qualityMode == 2 and not stretch and not upscale and self.image.size[0] <=\
+ elif self.opt.quality == 2 and not self.opt.stretch and not self.opt.upscale and self.image.size[0] <=\
self.size[0] and self.image.size[1] <= self.size[1]:
- self.purge = True
- return self.image
+ # HQ version will not be needed
+ self.noHQ = True
+ return
else:
- self.hq = True
size = (self.panelviewsize[0], self.panelviewsize[1])
# If stretching is on - Resize without other considerations
- if stretch:
+ if self.opt.stretch:
if self.image.size[0] <= size[0] and self.image.size[1] <= size[1]:
method = Image.BICUBIC
else:
method = Image.LANCZOS
self.image = self.image.resize(size, method)
- return self.image
+ return
# If image is smaller than target resolution and upscale is off - Just expand it by adding margins
- if self.image.size[0] <= size[0] and self.image.size[1] <= size[1] and not upscale:
+ if self.image.size[0] <= size[0] and self.image.size[1] <= size[1] and not self.opt.upscale:
borderw = int((size[0] - self.image.size[0]) / 2)
borderh = int((size[1] - self.image.size[1]) / 2)
# PV is disabled when source image is smaller than device screen and upscale is off
@@ -267,7 +257,7 @@ class ComicPage:
# Border can't be float so sometimes image might be 1px too small/large
if self.image.size[0] != size[0] or self.image.size[1] != size[1]:
self.image = ImageOps.fit(self.image, size, method=Image.BICUBIC, centering=(0.5, 0.5))
- return self.image
+ return
# Otherwise - Upscale/Downscale
ratioDev = float(size[0]) / float(size[1])
if (float(self.image.size[0]) / float(self.image.size[1])) < ratioDev:
@@ -281,7 +271,7 @@ class ComicPage:
else:
method = Image.LANCZOS
self.image = ImageOps.fit(self.image, size, method=method, centering=(0.5, 0.5))
- return self.image
+ return
def splitPage(self, targetdir):
width, height = self.image.size
@@ -374,7 +364,6 @@ class ComicPage:
else:
diff = pageNumberCut1
self.image = self.image.crop((0, 0, widthImg, heightImg - diff))
- return self.image
def cropWhiteSpace(self):
if ImageChops.invert(self.image).getbbox() is not None:
@@ -410,7 +399,6 @@ class ComicPage:
diff += delta
diff -= delta
self.image = self.image.crop((0, 0, widthImg - diff, heightImg))
- return self.image
def getImageHistogram(self, image):
histogram = image.histogram()
@@ -512,7 +500,7 @@ class Cover:
def processExternal(self):
self.image = self.image.convert('RGB')
self.image.thumbnail(self.options.profileData[1], Image.LANCZOS)
- self.save(True)
+ self.save()
def trim(self):
bg = Image.new(self.image.mode, self.image.size, self.image.getpixel((0, 0)))
@@ -524,15 +512,8 @@ class Cover:
else:
return self.image
- def save(self, external=False):
- if external:
- source = self.options.remoteCovers[self.tomeNumber].split('/')[-1]
- else:
- source = self.source
+ def save(self):
try:
- if os.path.splitext(source)[1].lower() == '.png':
- self.image.save(self.target, "PNG", optimize=1)
- else:
- self.image.save(self.target, "JPEG", optimize=1, quality=80)
+ self.image.save(self.target, "JPEG", optimize=1, quality=80)
except IOError:
raise RuntimeError('Failed to save cover')
diff --git a/kcc/metadata.py b/kcc/metadata.py
new file mode 100644
index 0000000..697a554
--- /dev/null
+++ b/kcc/metadata.py
@@ -0,0 +1,169 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (c) 2013-2015 Pawel Jastrzebski
+#
+# Permission to use, copy, modify, and/or distribute this software for
+# any purpose with or without fee is hereby granted, provided that the
+# above copyright notice and this permission notice appear in all
+# copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
+# WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
+# WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
+# AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
+# DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA
+# OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
+# TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+# PERFORMANCE OF THIS SOFTWARE.
+
+import os
+from xml.dom.minidom import parse, Document
+from re import compile
+from zipfile import is_zipfile, ZipFile, ZIP_DEFLATED
+from subprocess import STDOUT, PIPE
+from psutil import Popen
+from tempfile import mkdtemp
+from shutil import rmtree
+from .shared import removeFromZIP, check7ZFile as is_7zfile
+from . import rarfile
+
+
+class MetadataParser:
+ def __init__(self, source):
+ self.source = source
+ self.data = {'Series': '',
+ 'Volume': '',
+ 'Number': '',
+ 'Writers': [],
+ 'Pencillers': [],
+ 'Inkers': [],
+ 'Colorists': [],
+ 'MUid': '',
+ 'Bookmarks': []}
+ self.rawdata = None
+ self.compressor = None
+ if self.source.endswith('.xml'):
+ self.rawdata = parse(self.source)
+ self.parseXML()
+ else:
+ if is_zipfile(self.source):
+ self.compressor = 'zip'
+ with ZipFile(self.source) as zip_file:
+ for member in zip_file.namelist():
+ if member != 'ComicInfo.xml':
+ continue
+ with zip_file.open(member) as xml_file:
+ self.rawdata = parse(xml_file)
+ elif rarfile.is_rarfile(self.source):
+ self.compressor = 'rar'
+ with rarfile.RarFile(self.source) as rar_file:
+ for member in rar_file.namelist():
+ if member != 'ComicInfo.xml':
+ continue
+ with rar_file.open(member) as xml_file:
+ self.rawdata = parse(xml_file)
+ elif is_7zfile(self.source):
+ self.compressor = '7z'
+ workdir = mkdtemp('', 'KCC-TMP-')
+ tmpXML = os.path.join(workdir, 'ComicInfo.xml')
+ output = Popen('7za e "' + self.source + '" ComicInfo.xml -o"' + workdir + '"',
+ stdout=PIPE, stderr=STDOUT, shell=True)
+ extracted = False
+ for line in output.stdout:
+ if b"Everything is Ok" in line or b"No files to process" in line:
+ extracted = True
+ if not extracted:
+ rmtree(workdir)
+ raise OSError
+ if os.path.isfile(tmpXML):
+ self.rawdata = parse(tmpXML)
+ rmtree(workdir)
+ else:
+ raise OSError
+ if self.rawdata:
+ self.parseXML()
+
+ def parseXML(self):
+ if len(self.rawdata.getElementsByTagName('Series')) != 0:
+ self.data['Series'] = self.rawdata.getElementsByTagName('Series')[0].firstChild.nodeValue
+ if len(self.rawdata.getElementsByTagName('Volume')) != 0:
+ self.data['Volume'] = self.rawdata.getElementsByTagName('Volume')[0].firstChild.nodeValue
+ if len(self.rawdata.getElementsByTagName('Number')) != 0:
+ self.data['Number'] = self.rawdata.getElementsByTagName('Number')[0].firstChild.nodeValue
+ for field in ['Writer', 'Penciller', 'Inker', 'Colorist']:
+ if len(self.rawdata.getElementsByTagName(field)) != 0:
+ for person in self.rawdata.getElementsByTagName(field)[0].firstChild.nodeValue.split(', '):
+ self.data[field + 's'].append(person)
+ self.data[field + 's'] = list(set(self.data[field + 's']))
+ self.data[field + 's'].sort()
+ if len(self.rawdata.getElementsByTagName('ScanInformation')) != 0:
+ coverId = compile('(MCD\\()(\\d+)(\\))')\
+ .search(self.rawdata.getElementsByTagName('ScanInformation')[0].firstChild.nodeValue)
+ if coverId:
+ self.data['MUid'] = coverId.group(2)
+ if len(self.rawdata.getElementsByTagName('Page')) != 0:
+ for page in self.rawdata.getElementsByTagName('Page'):
+ if 'Bookmark' in page.attributes and 'Image' in page.attributes:
+ self.data['Bookmarks'].append((int(page.attributes['Image'].value),
+ page.attributes['Bookmark'].value))
+
+ def saveXML(self):
+ if self.rawdata:
+ root = self.rawdata.getElementsByTagName('ComicInfo')[0]
+ for row in (['Series', self.data['Series']], ['Volume', self.data['Volume']],
+ ['Number', self.data['Number']], ['Writer', ', '.join(self.data['Writers'])],
+ ['Penciller', ', '.join(self.data['Pencillers'])], ['Inker', ', '.join(self.data['Inkers'])],
+ ['Colorist', ', '.join(self.data['Colorists'])],
+ ['ScanInformation', 'MCD(' + self.data['MUid'] + ')' if self.data['MUid'] else '']):
+ if self.rawdata.getElementsByTagName(row[0]):
+ node = self.rawdata.getElementsByTagName(row[0])[0]
+ if row[1]:
+ node.firstChild.replaceWholeText(row[1])
+ else:
+ root.removeChild(node)
+ elif row[1]:
+ main = self.rawdata.createElement(row[0])
+ root.appendChild(main)
+ text = self.rawdata.createTextNode(row[1])
+ main.appendChild(text)
+ else:
+ doc = Document()
+ root = doc.createElement('ComicInfo')
+ root.setAttribute('xmlns:xsd', 'http://www.w3.org/2001/XMLSchema')
+ root.setAttribute('xmlns:xsi', 'http://www.w3.org/2001/XMLSchema-instance')
+ doc.appendChild(root)
+ for row in (['Series', self.data['Series']], ['Volume', self.data['Volume']],
+ ['Number', self.data['Number']], ['Writer', ', '.join(self.data['Writers'])],
+ ['Penciller', ', '.join(self.data['Pencillers'])], ['Inker', ', '.join(self.data['Inkers'])],
+ ['Colorist', ', '.join(self.data['Colorists'])],
+ ['ScanInformation', 'MCD(' + self.data['MUid'] + ')' if self.data['MUid'] else '']):
+ if row[1]:
+ main = doc.createElement(row[0])
+ root.appendChild(main)
+ text = doc.createTextNode(row[1])
+ main.appendChild(text)
+ self.rawdata = doc
+ if self.source.endswith('.xml'):
+ with open(self.source, 'w', encoding='utf-8') as f:
+ self.rawdata.writexml(f, encoding='utf-8')
+ else:
+ workdir = mkdtemp('', 'KCC-TMP-')
+ tmpXML = os.path.join(workdir, 'ComicInfo.xml')
+ with open(tmpXML, 'w', encoding='utf-8') as f:
+ self.rawdata.writexml(f, encoding='utf-8')
+ if is_zipfile(self.source):
+ removeFromZIP(self.source, 'ComicInfo.xml')
+ with ZipFile(self.source, mode='a', compression=ZIP_DEFLATED) as zip_file:
+ zip_file.write(tmpXML, arcname=tmpXML.split(os.sep)[-1])
+ elif rarfile.is_rarfile(self.source):
+ raise NotImplementedError
+ elif is_7zfile(self.source):
+ output = Popen('7za a "' + self.source + '" "' + tmpXML + '"', stdout=PIPE, stderr=STDOUT, shell=True)
+ extracted = False
+ for line in output.stdout:
+ if b"Everything is Ok" in line:
+ extracted = True
+ if not extracted:
+ rmtree(workdir)
+ raise OSError
+ rmtree(workdir)
diff --git a/kcc/pdfjpgextract.py b/kcc/pdfjpgextract.py
index 7ebae06..a4c0203 100644
--- a/kcc/pdfjpgextract.py
+++ b/kcc/pdfjpgextract.py
@@ -19,10 +19,6 @@
# PERFORMANCE OF THIS SOFTWARE.
#
-__license__ = 'ISC'
-__copyright__ = '2012-2015, Ciro Mattia Gonano , Pawel Jastrzebski '
-__docformat__ = 'restructuredtext en'
-
import os
from random import choice
from string import ascii_uppercase, digits
diff --git a/kcc/shared.py b/kcc/shared.py
index 5a6602b..1789749 100644
--- a/kcc/shared.py
+++ b/kcc/shared.py
@@ -16,14 +16,19 @@
# PERFORMANCE OF THIS SOFTWARE.
#
-__license__ = 'ISC'
-__copyright__ = '2012-2015, Ciro Mattia Gonano , Pawel Jastrzebski '
-__docformat__ = 'restructuredtext en'
-
import os
from hashlib import md5
from html.parser import HTMLParser
from distutils.version import StrictVersion
+from time import sleep
+from shutil import rmtree, move
+from tempfile import mkdtemp
+from zipfile import ZipFile, ZIP_DEFLATED
+from re import split
+try:
+ from scandir import walk
+except ImportError:
+ walk = None
class HTMLStripper(HTMLParser):
@@ -49,11 +54,20 @@ def getImageFileName(imgfile):
return [name, ext]
+def walkSort(dirnames, filenames):
+ convert = lambda text: int(text) if text.isdigit() else text
+ alphanum_key = lambda key: [convert(c) for c in split('([0-9]+)', key)]
+ dirnames.sort(key=lambda name: alphanum_key(name.lower()))
+ filenames.sort(key=lambda name: alphanum_key(name.lower()))
+ return dirnames, filenames
+
+
def walkLevel(some_dir, level=1):
some_dir = some_dir.rstrip(os.path.sep)
assert os.path.isdir(some_dir)
num_sep = some_dir.count(os.path.sep)
- for root, dirs, files in os.walk(some_dir):
+ for root, dirs, files in walk(some_dir):
+ dirs, files = walkSort(dirs, files)
yield root, dirs, files
num_sep_this = root.count(os.path.sep)
if num_sep + level <= num_sep_this:
@@ -77,6 +91,32 @@ def check7ZFile(filePath):
return header == b"7z\xbc\xaf'\x1c"
+def saferReplace(old, new):
+ for x in range(5):
+ try:
+ os.replace(old, new)
+ except PermissionError:
+ sleep(5)
+ else:
+ break
+ else:
+ raise PermissionError
+
+
+def removeFromZIP(zipfname, *filenames):
+ tempdir = mkdtemp('', 'KCC-TMP-')
+ try:
+ tempname = os.path.join(tempdir, 'KCC-TMP.zip')
+ with ZipFile(zipfname, 'r') as zipread:
+ with ZipFile(tempname, 'w', compression=ZIP_DEFLATED) as zipwrite:
+ for item in zipread.infolist():
+ if item.filename not in filenames:
+ zipwrite.writestr(item, zipread.read(item.filename))
+ move(tempname, zipfname)
+ finally:
+ rmtree(tempdir)
+
+
# noinspection PyUnresolvedReferences
def dependencyCheck(level):
missing = []
@@ -106,13 +146,12 @@ def dependencyCheck(level):
missing.append('Pillow 2.7.0+')
except ImportError:
missing.append('Pillow 2.7.0+')
+ try:
+ from scandir import __version__ as scandirVersion
+ if StrictVersion('0.9') > StrictVersion(scandirVersion):
+ missing.append('scandir 0.9+')
+ except ImportError:
+ missing.append('scandir 0.9+')
if len(missing) > 0:
- try:
- import tkinter
- import tkinter.messagebox
- importRoot = tkinter.Tk()
- importRoot.withdraw()
- tkinter.messagebox.showerror('KCC - Error', 'ERROR: ' + ', '.join(missing) + ' is not installed!')
- except ImportError:
- print('ERROR: ' + ', '.join(missing) + ' is not installed!')
+ print('ERROR: ' + ', '.join(missing) + ' is not installed!')
exit(1)
\ No newline at end of file
diff --git a/setup.py b/setup.py
index 2621bd0..f650402 100755
--- a/setup.py
+++ b/setup.py
@@ -9,12 +9,13 @@ Usage (Mac OS X):
python setup.py py2app
"""
from sys import platform, version_info
+from kcc import __version__
if version_info[0] != 3:
print('ERROR: This is Python 3 script!')
exit(1)
NAME = "KindleComicConverter"
-VERSION = "4.4.1"
+VERSION = __version__
MAIN = "kcc.py"
if platform == "darwin":
@@ -54,9 +55,9 @@ if platform == "darwin":
elif platform == "win32":
# noinspection PyUnresolvedReferences
import py2exe
- import platform as arch
+ import platform
from distutils.core import setup
- if arch.architecture()[0] == '64bit':
+ if platform.architecture()[0] == '64bit':
suffix = '_64'
else:
suffix = ''
@@ -70,7 +71,6 @@ elif platform == "win32":
'C:\Python34' + suffix + '\Lib\site-packages\PyQt5\libEGL.dll'])]
extra_options = dict(
options={'py2exe': {"bundle_files": 1,
- "dll_excludes": ["tcl85.dll", "tk85.dll"],
"dist_dir": "dist" + suffix,
"compressed": True,
"includes": ["sip"],
diff --git a/setup.sh b/setup.sh
index fa4d85d..c26a53e 100755
--- a/setup.sh
+++ b/setup.sh
@@ -1,7 +1,7 @@
#!/bin/bash
# Linux Python package build script
-VERSION="4.4.1"
+VERSION="4.5"
cp kcc.py __main__.py
zip kcc.zip __main__.py kcc/*.py
@@ -21,5 +21,5 @@ echo "#!/usr/bin/env python3" > kcc-c2p-bin
cat kcc-c2p.zip >> kcc-c2p-bin
chmod +x kcc-c2p-bin
-tar --xform s:^.*/:: --xform s/kcc-bin/kcc/ --xform s/kcc-c2p-bin/kcc-c2p/ --xform s/kcc-c2e-bin/kcc-c2e/ --xform s/comic2ebook/kcc/ -czf KindleComicConverter_linux_$VERSION.tar.gz kcc-bin kcc-c2e-bin kcc-c2p-bin LICENSE.txt icons/comic2ebook.png
+tar --xform s:^.*/:: --xform s/kcc-bin/kcc/ --xform s/kcc-c2p-bin/kcc-c2p/ --xform s/kcc-c2e-bin/kcc-c2e/ --xform s/comic2ebook/kcc/ -czf KindleComicConverter_linux_${VERSION}.tar.gz kcc-bin kcc-c2e-bin kcc-c2p-bin LICENSE.txt icons/comic2ebook.png
rm __main__.py kcc.zip kcc-c2e.zip kcc-c2p.zip kcc-bin kcc-c2e-bin kcc-c2p-bin
\ No newline at end of file