1
0
mirror of https://github.com/ciromattia/kcc synced 2025-12-13 01:36:27 +00:00

Overhaul of converter internals

This commit is contained in:
Paweł Jastrzębski
2015-10-20 09:54:22 +02:00
parent 3bedc3b928
commit f317a5c430
5 changed files with 448 additions and 630 deletions

View File

@@ -33,5 +33,4 @@ from kcc.comic2ebook import main
if __name__ == "__main__":
freeze_support()
print('comic2ebook v' + __version__ + ' - Written by Ciro Mattia Gonano and Pawel Jastrzebski.')
main(sys.argv[1:])
sys.exit(0)
sys.exit(main(sys.argv[1:]))

View File

@@ -33,5 +33,4 @@ from kcc.comic2panel import main
if __name__ == "__main__":
freeze_support()
print('comic2panel v' + __version__ + ' - Written by Ciro Mattia Gonano and Pawel Jastrzebski.')
main(sys.argv[1:])
sys.exit(0)
sys.exit(main(sys.argv[1:]))

View File

@@ -62,56 +62,37 @@ def main(argv=None):
optionstemplate, args = parser.parse_args(argv)
if len(args) == 0:
parser.print_help()
return
return 0
sources = set([source for arg in args for source in glob(arg)])
outputPath = []
if len(sources) == 0:
print('No matching files found.')
return
return 1
for source in sources:
source = source.rstrip('\\').rstrip('/')
options = copy(optionstemplate)
checkOptions()
if len(sources) > 1:
print('\nWorking on ' + source)
outputPath = makeBook(source)
return outputPath
print('Working on ' + source + '...')
makeBook(source)
return 0
def buildHTML(path, imgfile, imgfilepath, forcePV=False):
def buildHTML(path, imgfile, imgfilepath):
imgfilepath = md5Checksum(imgfilepath)
filename = getImageFileName(imgfile)
additionalStyle = ''
if options.imgproc:
if "Rotated" in options.imgIndex[imgfilepath]:
rotatedPage = True
else:
rotatedPage = False
if "NoPanelView" in options.imgIndex[imgfilepath]:
noPV = True
else:
noPV = False
if "NoHorizontalPanelView" in options.imgIndex[imgfilepath]:
noHorizontalPV = True
else:
noHorizontalPV = False
if "NoVerticalPanelView" in options.imgIndex[imgfilepath]:
noVerticalPV = True
else:
noVerticalPV = False
if "BlackFill" in options.imgIndex[imgfilepath]:
additionalStyle = ' style="background-color:#000000" '
deviceres = options.profileData[1]
if "Rotated" in options.imgIndex[imgfilepath]:
rotatedPage = True
else:
rotatedPage = False
noPV = False
noHorizontalPV = False
noVerticalPV = False
if forcePV and noPV:
noPV = False
noHorizontalPV = True
noVerticalPV = True
if "BlackFill" in options.imgIndex[imgfilepath]:
additionalStyle = 'background-color:#000000;'
else:
additionalStyle = 'background-color:#FFFFFF;'
htmlpath = ''
postfix = ''
size = ''
imgfilepv = ''
backref = 1
head = path
while True:
@@ -123,100 +104,94 @@ def buildHTML(path, imgfile, imgfilepath, forcePV=False):
backref += 1
if not os.path.exists(htmlpath):
os.makedirs(htmlpath)
htmlfile = os.path.join(htmlpath, filename[0] + '.html')
htmlfile = os.path.join(htmlpath, filename[0] + '.xhtml')
f = open(htmlfile, "w", encoding='UTF-8')
if options.iskindle:
f.writelines(["<?xml version=\"1.0\" encoding=\"utf-8\"?>\n",
"<!DOCTYPE html>\n",
"<html xmlns=\"http://www.w3.org/1999/xhtml\" xmlns:epub=\"http://www.idpf.org/2007/ops\">\n",
"<head>\n",
"<title>", filename[0], "</title>\n",
"<link href=\"", "../" * (backref - 1), "style.css\" type=\"text/css\" rel=\"stylesheet\"/>\n",
"<meta charset=\"utf-8\"/>\n",
"</head>\n",
"<body" + additionalStyle + ">\n",
"<div class=\"fs\">\n",
"<div><img src=\"", "../" * backref, "Images/", postfix, imgfile, "\" alt=\"",
imgfile, "\" class=\"singlePage\"/></div>\n"
])
if (options.panelview or forcePV) and not noPV:
options.panelviewused = True
if not noHorizontalPV and not noVerticalPV:
if rotatedPage:
if options.righttoleft:
order = [1, 3, 2, 4]
else:
order = [2, 4, 1, 3]
f.writelines(["<?xml version=\"1.0\" encoding=\"UTF-8\"?>",
"<!DOCTYPE html>\n",
"<html xmlns=\"http://www.w3.org/1999/xhtml\" xmlns:epub=\"http://www.idpf.org/2007/ops\">\n",
"<head>\n",
"<title>", filename[0], "</title>\n",
"<link href=\"", "../" * (backref - 1), "style.css\" type=\"text/css\" rel=\"stylesheet\"/>\n",
"<meta name=\"viewport\" "
"content=\"width=" + str(deviceres[0]) + ", height=" + str(deviceres[1]) + "\"/>\n"
"</head>\n",
"<body style=\"background-image: ",
"url('", "../" * backref, "Images/", postfix, imgfile, "'); " + additionalStyle + "\">\n"])
if options.iskindle and options.panelview:
if options.hqmode:
imgfilepv = list(os.path.splitext(imgfile))
imgfilepv[0] += "-hq"
imgfilepv = "".join(imgfilepv)
if os.path.isfile(os.path.join(head, "Images", postfix, imgfilepv)):
size = Image.open(os.path.join(head, "Images", postfix, imgfilepv)).size
if not options.hqmode or not size:
imgfilepv = imgfile
sizeTmp = Image.open(os.path.join(head, "Images", postfix, imgfilepv)).size
size = (int(sizeTmp[0] * 1.5), int(sizeTmp[1] * 1.5))
if size[0] <= deviceres[0]:
noHorizontalPV = True
else:
noHorizontalPV = False
if size[1] <= deviceres[1]:
noVerticalPV = True
else:
noVerticalPV = False
x, y = getPanelViewSize(deviceres, size)
boxStyles = {"PV-TL": "position:absolute;left:0;top:0;",
"PV-TR": "position:absolute;right:0;top:0;",
"PV-BL": "position:absolute;left:0;bottom:0;",
"PV-BR": "position:absolute;right:0;bottom:0;",
"PV-T": "position:absolute;top:0;left:" + x + "%;",
"PV-B": "position:absolute;bottom:0;left:" + x + "%;",
"PV-L": "position:absolute;left:0;top:" + y + "%;",
"PV-R": "position:absolute;right:0;top:" + y + "%;"}
f.write("<div id=\"PV\">\n")
if not noHorizontalPV and not noVerticalPV:
if rotatedPage:
if options.righttoleft:
order = [1, 3, 2, 4]
else:
if options.righttoleft:
order = [2, 1, 4, 3]
else:
order = [1, 2, 3, 4]
boxes = ["BoxTL", "BoxTR", "BoxBL", "BoxBR"]
elif noHorizontalPV and not noVerticalPV:
if rotatedPage:
if options.righttoleft:
order = [1, 2]
else:
order = [2, 1]
order = [2, 4, 1, 3]
else:
if options.righttoleft:
order = [2, 1, 4, 3]
else:
order = [1, 2]
boxes = ["BoxT", "BoxB"]
elif not noHorizontalPV and noVerticalPV:
if rotatedPage:
order = [1, 2, 3, 4]
boxes = ["PV-TL", "PV-TR", "PV-BL", "PV-BR"]
elif noHorizontalPV and not noVerticalPV:
if rotatedPage:
if options.righttoleft:
order = [1, 2]
else:
if options.righttoleft:
order = [2, 1]
else:
order = [1, 2]
boxes = ["BoxL", "BoxR"]
order = [2, 1]
else:
order = [1]
boxes = ["BoxC"]
for i in range(0, len(boxes)):
f.writelines(["<div id=\"" + boxes[i] + "\"><a class=\"app-amzn-magnify\" data-app-amzn-magnify=",
"'{\"targetId\":\"" + boxes[i] + "-Panel-Parent\", \"ordinal\":" + str(order[i]),
"}'></a></div>\n"])
if options.quality == 2 and not forcePV:
imgfilepv = imgfile.split(".")
imgfilepv[0] += "-hq"
imgfilepv = ".".join(imgfilepv)
order = [1, 2]
boxes = ["PV-T", "PV-B"]
elif not noHorizontalPV and noVerticalPV:
if rotatedPage:
order = [1, 2]
else:
imgfilepv = imgfile
xl, yu, xr, yd = detectMargins(imgfilepath)
boxStyles = {"BoxTL": "left:" + xl + ";top:" + yu + ";",
"BoxTR": "right:" + xr + ";top:" + yu + ";",
"BoxBL": "left:" + xl + ";bottom:" + yd + ";",
"BoxBR": "right:" + xr + ";bottom:" + yd + ";",
"BoxT": "left:-25%;top:" + yu + ";",
"BoxB": "left:-25%;bottom:" + yd + ";",
"BoxL": "left:" + xl + ";top:-25%;",
"BoxR": "right:" + xr + ";top:-25%;",
"BoxC": "left:-25%;top:-25%;"
}
for box in boxes:
f.writelines(["<div id=\"" + box + "-Panel-Parent\" class=\"target-mag-parent\"><div id=\"",
"Generic-Panel\" class=\"target-mag\"><img style=\"" + boxStyles[box] + "\" src=\"",
"../" * backref, "Images/", postfix, imgfilepv, "\" alt=\"" + imgfilepv,
"\"/></div></div>\n",
])
f.writelines(["</div>\n</body>\n</html>"])
else:
f.writelines(["<?xml version=\"1.0\" encoding=\"utf-8\"?>\n",
"<!DOCTYPE html>\n",
"<html xmlns=\"http://www.w3.org/1999/xhtml\" xmlns:epub=\"http://www.idpf.org/2007/ops\">\n",
"<head>\n",
"<title>", filename[0], "</title>\n",
"<link href=\"", "../" * (backref - 1), "style.css\" type=\"text/css\" rel=\"stylesheet\"/>\n",
"<meta charset=\"utf-8\"/>\n",
"</head>\n",
"<body" + additionalStyle + ">\n",
"<div class=\"epub:type=bodymatter\">\n",
"<img src=\"", "../" * backref, "Images/", postfix, imgfile, "\"/>\n",
"</div>\n",
"</body>\n</html>"
])
if options.righttoleft:
order = [2, 1]
else:
order = [1, 2]
boxes = ["PV-L", "PV-R"]
else:
order = []
boxes = []
for i in range(0, len(boxes)):
f.writelines(["<div id=\"" + boxes[i] + "\">\n",
"<a class=\"app-amzn-magnify\" data-app-amzn-magnify='{\"targetId\":\"" + boxes[i] +
"-P\", \"ordinal\":" + str(order[i]) + "}'></a>\n",
"</div>\n"])
for box in boxes:
f.writelines(["<div class=\"PV-P\" id=\"" + box + "-P\" style=\"" + additionalStyle + "\">\n",
"<img style=\"" + boxStyles[box] + "\" src=\"", "../" * backref, "Images/", postfix,
imgfilepv, "\" width=\"" + str(size[0]) + "\" height=\"" + str(size[1]) + "\"/>\n",
"</div>\n"])
f.write("</div>\n")
f.writelines(["</body>\n",
"</html>\n"])
f.close()
return path, imgfile
@@ -248,7 +223,7 @@ def buildNCX(dstdir, title, chapters, chapterNames):
title = chapterNames[os.path.basename(folder)]
f.write("<navPoint id=\"" + navID + "\"><navLabel><text>" +
title + "</text></navLabel><content src=\"" + filename[0].replace("\\", "/") +
".html\"/></navPoint>\n")
".xhtml\"/></navPoint>\n")
f.write("</navMap>\n</ncx>")
f.close()
@@ -273,7 +248,7 @@ def buildNAV(dstdir, title, chapters, chapterNames):
title = chapterNames[chapter[1]]
elif os.path.basename(folder) != "Text":
title = chapterNames[os.path.basename(folder)]
f.write("<li><a href=\"" + filename[0].replace("\\", "/") + ".html\">" + title + "</a></li>\n")
f.write("<li><a href=\"" + filename[0].replace("\\", "/") + ".xhtml\">" + title + "</a></li>\n")
f.writelines(["</ol>\n",
"</nav>\n",
"<nav epub:type=\"page-list\">\n",
@@ -286,14 +261,14 @@ def buildNAV(dstdir, title, chapters, chapterNames):
title = chapterNames[chapter[1]]
elif os.path.basename(folder) != "Text":
title = chapterNames[os.path.basename(folder)]
f.write("<li><a href=\"" + filename[0].replace("\\", "/") + ".html\">" + title + "</a></li>\n")
f.write("<li><a href=\"" + filename[0].replace("\\", "/") + ".xhtml\">" + title + "</a></li>\n")
f.write("</ol>\n</nav>\n</body>\n</html>")
f.close()
def buildOPF(dstdir, title, filelist, cover=None):
opffile = os.path.join(dstdir, 'OEBPS', 'content.opf')
profilelabel, deviceres, palette, gamma, panelviewsize = options.profileData
deviceres = options.profileData[1]
if options.righttoleft:
writingmode = "horizontal-rl"
else:
@@ -301,15 +276,15 @@ def buildOPF(dstdir, title, filelist, cover=None):
f = open(opffile, "w", encoding='UTF-8')
f.writelines(["<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n",
"<package version=\"3.0\" unique-identifier=\"BookID\" ",
"prefix=\"rendition: http://www.idpf.org/vocab/rendition/#\" ",
"xmlns=\"http://www.idpf.org/2007/opf\">\n",
"<metadata xmlns:opf=\"http://www.idpf.org/2007/opf\" ",
"xmlns:dc=\"http://purl.org/dc/elements/1.1/\">\n",
"<dc:title>", title, "</dc:title>\n",
"<dc:language>en-US</dc:language>\n",
"<dc:identifier id=\"BookID\">urn:uuid:", options.uuid, "</dc:identifier>\n",
"<dc:contributor id=\"contributor\">KindleComicConverter-" + __version__ + "</dc:contributor>\n",
"<dc:description>", options.summary, "</dc:description>\n"])
"<dc:contributor id=\"contributor\">KindleComicConverter-" + __version__ + "</dc:contributor>\n"])
if len(options.summary) > 0:
f.writelines(["<dc:description>", options.summary, "</dc:description>\n"])
for author in options.authors:
f.writelines(["<dc:creator>", author, "</dc:creator>\n"])
f.writelines(["<meta property=\"dcterms:modified\">" + strftime("%Y-%m-%dT%H:%M:%SZ", gmtime()) + "</meta>\n",
@@ -347,7 +322,7 @@ def buildOPF(dstdir, title, filelist, cover=None):
reflist.append(uniqueid)
f.write("<item id=\"page_" + str(uniqueid) + "\" href=\"" +
folder.replace('Images', 'Text') + "/" + filename[0] +
".html\" media-type=\"application/xhtml+xml\"/>\n")
".xhtml\" media-type=\"application/xhtml+xml\"/>\n")
if '.png' == filename[1]:
mt = 'image/png'
else:
@@ -359,6 +334,29 @@ def buildOPF(dstdir, title, filelist, cover=None):
f.write("</manifest>\n<spine page-progression-direction=\"rtl\" toc=\"ncx\">\n")
else:
f.write("</manifest>\n<spine page-progression-direction=\"ltr\" toc=\"ncx\">\n")
# if options.iskindle and options.profile != 'Custom':
# if options.righttoleft:
# nextflow = 'right'
# else:
# nextflow = 'left'
# for entry in reflist:
# if '-kcc-b' in entry:
# if options.righttoleft:
# f.write("<itemref idref=\"page_" + entry + "\" properties=\"page-spread-right\"/>\n")
# else:
# f.write("<itemref idref=\"page_" + entry + "\" properties=\"page-spread-left\"/>\n")
# elif '-kcc-c' in entry:
# if options.righttoleft:
# f.write("<itemref idref=\"page_" + entry + "\" properties=\"page-spread-left\"/>\n")
# else:
# f.write("<itemref idref=\"page_" + entry + "\" properties=\"page-spread-right\"/>\n")
# else:
# f.write("<itemref idref=\"page_" + entry + "\" properties=\"facing-page-" + nextflow + "\"/>\n")
# if nextflow == 'right':
# nextflow = 'left'
# else:
# nextflow = 'right'
# else:
for entry in reflist:
f.write("<itemref idref=\"page_" + entry + "\"/>\n")
f.write("</spine>\n</package>\n")
@@ -378,139 +376,89 @@ 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')
if options.iskindle:
f.writelines(["@page {\n",
"margin-bottom: 0;\n",
"margin-top: 0\n",
"}\n",
"body {\n",
"display: block;\n",
"margin-bottom: 0;\n",
"margin-left: 0;\n",
"margin-right: 0;\n",
"margin-top: 0;\n",
"padding-bottom: 0;\n",
"padding-left: 0;\n",
"padding-right: 0;\n",
"padding-top: 0;\n",
"text-align: left\n",
"}\n",
"div.fs {\n",
"height: ", str(deviceres[1]), "px;\n",
"width: ", str(deviceres[0]), "px;\n",
"position: relative;\n",
"display: block;\n",
"text-align: center\n",
"}\n",
"div.fs a {\n",
"display: block;\n",
"width : 100%;\n",
"height: 100%;\n",
"}\n",
"div.fs div {\n",
"position: absolute;\n",
"}\n",
"img.singlePage {\n",
"position: absolute;\n",
"height: ", str(deviceres[1]), "px;\n",
"width: ", str(deviceres[0]), "px;\n",
"}\n",
"div.target-mag-parent {\n",
"width:100%;\n",
"height:100%;\n",
"display:none;\n",
"}\n",
"div.target-mag {\n",
"position: absolute;\n",
"display: block;\n",
"overflow: hidden;\n",
"}\n",
"div.target-mag img {\n",
"position: absolute;\n",
"height: ", str(panelviewsize[1]), "px;\n",
"width: ", str(panelviewsize[0]), "px;\n",
"}\n",
"#Generic-Panel {\n",
"top: 0;\n",
"height: 100%;\n",
"width: 100%;\n",
"}\n",
"#BoxC {\n",
"top: 0;\n",
"height: 100%;\n",
"width: 100%;\n",
"}\n",
"#BoxT {\n",
"top: 0;\n",
"height: 50%;\n",
"width: 100%;\n",
"}\n",
"#BoxB {\n",
"bottom: 0;\n",
"height: 50%;\n",
"width: 100%;\n",
"}\n",
"#BoxL {\n",
"left: 0;\n",
"height: 100%;\n",
"width: 50%;\n",
"}\n",
"#BoxR {\n",
"right: 0;\n",
"height: 100%;\n",
"width: 50%;\n",
"}\n",
"#BoxTL {\n",
"top: 0;\n",
"left: 0;\n",
"height: 50%;\n",
"width: 50%;\n",
"}\n",
"#BoxTR {\n",
"top: 0;\n",
"right: 0;\n",
"height: 50%;\n",
"width: 50%;\n",
"}\n",
"#BoxBL {\n",
"bottom: 0;\n",
"left: 0;\n",
"height: 50%;\n",
"width: 50%;\n",
"}\n",
"#BoxBR {\n",
"bottom: 0;\n",
"right: 0;\n",
"height: 50%;\n",
"width: 50%;\n",
"}",
])
else:
f.writelines([
"@namespace epub \"http://www.idpf.org/2007/ops\";\n",
"@charset \"UTF-8\";\n",
"body {\n",
"margin: 0;\n",
"}\n",
"img {\n",
"position: absolute;\n",
"margin: 0;\n",
"z-index: 0;\n",
"height: 100%;\n",
"}"])
f.writelines(["@page {\n",
"margin: 0;\n",
"}\n",
"body {\n",
"display: block;\n",
"margin: 0;\n",
"padding: 0;\n",
"background-position: center center;\n",
"background-repeat: no-repeat;\n",
"background-size: auto auto;\n",
"}\n",
"#PV {\n",
"position: absolute;\n",
"width: 100%;\n",
"height: 100%;\n",
"}\n",
"#PV-T {\n",
"top: 0;\n",
"width: 100%;\n",
"height: 50%;\n",
"}\n",
"#PV-B {\n",
"bottom: 0;\n",
"width: 100%;\n",
"height: 50%;\n",
"}\n",
"#PV-L {\n",
"left: 0;\n",
"width: 50%;\n",
"height: 100%;\n",
"float: left;\n",
"}\n",
"#PV-R {\n",
"right: 0;\n",
"width: 50%;\n",
"height: 100%;\n",
"float: right;\n",
"}\n",
"#PV-TL {\n",
"top: 0;\n",
"left: 0;\n",
"width: 50%;\n",
"height: 50%;\n",
"float: left;\n",
"}\n",
"#PV-TR {\n",
"top: 0;\n",
"right: 0;\n",
"width: 50%;\n",
"height: 50%;\n",
"float: right;\n",
"}\n",
"#PV-BL {\n",
"bottom: 0;\n",
"left: 0;\n",
"width: 50%;\n",
"height: 50%;\n",
"float: left;\n",
"}\n",
"#PV-BR {\n",
"bottom: 0;\n",
"right: 0;\n",
"width: 50%;\n",
"height: 50%;\n",
"float: right;\n",
"}\n",
".PV-P {\n",
"width: 100%;\n",
"height: 100%;\n",
"top: 0;\n",
"position: absolute;\n",
"display: none;\n",
"}\n"])
f.close()
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]:
if not filename[0].endswith('-hq'):
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
@@ -518,10 +466,6 @@ 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', 'Custom']:
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 = []
@@ -529,9 +473,9 @@ def buildEPUB(path, chapterNames, tomeNumber):
for aChapter in options.chapters:
pageid = aChapter[0]
for x in range(0, pageid + globaldiff + 1):
if '-aaa-kcc' in filelist[x][1]:
if '-kcc-b' in filelist[x][1]:
pageid += 1
if '-bbb-kcc' in filelist[pageid][1]:
if '-kcc-c' in filelist[pageid][1]:
pageid -= 1
filename = filelist[pageid][1]
chapterlist.append((filelist[pageid][0].replace('Images', 'Text'), filename))
@@ -542,21 +486,6 @@ def buildEPUB(path, chapterNames, tomeNumber):
buildOPF(path, options.title, filelist, cover)
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.autocontrastImage()
img.resizeImage()
if not img.second and opt.panelview:
img.calculateBorder()
if opt.forcepng and not opt.forcecolor:
img.quantizeImage()
def imgDirectoryProcessing(path):
global workerPool, workerOutput
workerPool = Pool()
@@ -597,10 +526,8 @@ def imgFileProcessingTick(output):
else:
for page in output:
if page is not None:
if isinstance(page, str):
options.imgPurgeIndex.append(page)
else:
options.imgIndex[page[0]] = page[1]
options.imgIndex[page[0]] = page[1]
options.imgPurgeIndex.append(page[2])
if GUI:
GUI.progressBarTick.emit('tick')
if not GUI.conversionAlive:
@@ -613,38 +540,18 @@ def imgFileProcessing(work):
dirpath = work[1]
opt = work[2]
output = []
img = image.ComicPage(os.path.join(dirpath, afile), opt)
if opt.nosplitrotate:
splitter = None
else:
splitter = img.splitPage(dirpath)
if splitter is not None:
img0 = image.ComicPage(splitter[0], opt)
imgOptimization(img0, opt)
if not img0.noHQ:
output.append(img0.saveToDir(dirpath))
img1 = image.ComicPage(splitter[1], opt)
imgOptimization(img1, opt)
if not img1.noHQ:
output.append(img1.saveToDir(dirpath))
output.extend([img.origFileName, img0.origFileName, img1.origFileName])
if opt.quality == 2:
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)
imgOptimization(img1b, opt)
output.append(img1b.saveToDir(dirpath))
else:
output.append(img.origFileName)
imgOptimization(img, opt)
if not img.noHQ:
output.append(img.saveToDir(dirpath))
if opt.quality == 2:
img2 = image.ComicPage(os.path.join(dirpath, afile), opt, img)
imgOptimization(img2, opt)
output.append(img2.saveToDir(dirpath))
workImg = image.ComicPageParser((dirpath, afile), opt)
for i in workImg.payload:
img = image.ComicPage(i[0], i[1], i[2], i[3], i[4], opt)
if not opt.webtoon:
img.cropWhiteSpace()
if opt.cutpagenumbers and not opt.webtoon:
img.cutPageNumber()
img.autocontrastImage()
img.resizeImage()
if opt.forcepng and not opt.forcecolor:
img.quantizeImage()
output.append(img.saveToDir())
return output
except Exception:
return str(sys.exc_info()[1])
@@ -789,6 +696,12 @@ def getDirectorySize(start_path='.'):
return total_size
def getPanelViewSize(deviceres, size):
x = int(deviceres[0] / 2 - size[0] / 2) / deviceres[0] * 100
y = int(deviceres[1] / 2 - size[1] / 2) / deviceres[1] * 100
return str(int(x)), str(int(y))
def sanitizeTree(filetree):
chapterNames = {}
for root, dirs, files in walk(filetree, False):
@@ -851,7 +764,7 @@ def splitDirectory(path):
mode = 0
else:
if filesNumber > 0:
print('\nWARNING: Automatic output splitting failed.')
print('WARNING: Automatic output splitting failed.')
if GUI:
GUI.addMessage.emit('Automatic output splitting failed. <a href='
'"https://github.com/ciromattia/kcc/wiki'
@@ -866,7 +779,7 @@ def splitDirectory(path):
if len(dirs) != 0:
detectedSubSubdirectories = True
elif len(dirs) == 0 and detectedSubSubdirectories:
print('\nWARNING: Automatic output splitting failed.')
print('WARNING: Automatic output splitting failed.')
if GUI:
GUI.addMessage.emit('Automatic output splitting failed. <a href='
'"https://github.com/ciromattia/kcc/wiki'
@@ -883,7 +796,7 @@ def splitDirectory(path):
# One level of subdirectories
mode = 1
if detectedFilesInSubdirectories and detectedSubSubdirectories:
print('\nWARNING: Automatic output splitting failed.')
print('WARNING: Automatic output splitting failed.')
if GUI:
GUI.addMessage.emit('Automatic output splitting failed. <a href='
'"https://github.com/ciromattia/kcc/wiki'
@@ -992,43 +905,14 @@ def detectCorruption(tmpPath, orgPath):
else:
saferRemove(os.path.join(root, name))
if imageSmaller > imageNumber * 0.25 and not options.upscale and not options.stretch:
print("\nMore than 25% of images are smaller than target device resolution. "
print("WARNING: More than 1/4 of images are smaller than target device resolution. "
"Consider enabling stretching or upscaling to improve readability.")
if GUI:
GUI.addMessage.emit('More than 25% of images are smaller than target device resolution.', 'warning', False)
GUI.addMessage.emit('More than 1/4 of images are smaller than target device resolution.', 'warning', False)
GUI.addMessage.emit('Consider enabling stretching or upscaling to improve readability.', 'warning', False)
GUI.addMessage.emit('', '', False)
def detectMargins(path):
if options.imgproc:
for flag in options.imgIndex[path]:
if "Margins-" in flag:
flag = flag.split('-')
xl = flag[1]
yu = flag[2]
xr = flag[3]
yd = flag[4]
if xl != "0.0":
xl = "-" + xl + "%"
else:
xl = "0%"
if xr != "0.0":
xr = "-" + xr + "%"
else:
xr = "0%"
if yu != "0.0":
yu = "-" + yu + "%"
else:
yu = "0%"
if yd != "0.0":
yd = "-" + yd + "%"
else:
yd = "0%"
return xl, yu, xr, yd
return '0%', '0%', '0%', '0%'
def createNewTome():
tomePathRoot = mkdtemp('', 'KCC-')
tomePath = os.path.join(tomePathRoot, 'OEBPS', 'Images')
@@ -1058,7 +942,6 @@ def makeZIP(zipFilename, baseDir, isEPUB=False):
def makeParser():
"""Create and return an option parser set up with KCC options."""
psr = OptionParser(usage="Usage: kcc-c2e [options] comic_file|comic_folder", add_help_option=False)
mainOptions = OptionGroup(psr, "MAIN")
@@ -1070,10 +953,8 @@ def makeParser():
mainOptions.add_option("-p", "--profile", action="store", dest="profile", default="KV",
help="Device profile (Available options: K1, K2, K345, KDX, KPW, KV, KoMT, KoG, KoGHD,"
" KoA, KoAHD, KoAH2O) [Default=KV]")
mainOptions.add_option("-q", "--quality", type="int", dest="quality", default="0",
help="Quality of Panel View. 0 - Normal 1 - High 2 - Ultra [Default=0]")
mainOptions.add_option("-m", "--manga-style", action="store_true", dest="righttoleft", default=False,
help="Manga style (Right-to-left reading and splitting)")
help="Manga style (right-to-left reading and splitting)")
mainOptions.add_option("-w", "--webtoon", action="store_true", dest="webtoon", default=False,
help="Webtoon processing mode"),
@@ -1083,9 +964,19 @@ def makeParser():
help="Comic title [Default=filename or directory name]")
outputOptions.add_option("-f", "--format", action="store", dest="format", default="Auto",
help="Output format (Available options: Auto, MOBI, EPUB, CBZ) [Default=Auto]")
outputOptions.add_option("--batchsplit", action="store_true", dest="batchsplit", default=False,
outputOptions.add_option("-b", "--batchsplit", action="store_true", dest="batchsplit", default=False,
help="Split output into multiple files"),
processingOptions.add_option("-u", "--upscale", action="store_true", dest="upscale", default=False,
help="Resize images smaller than device's resolution")
processingOptions.add_option("-s", "--stretch", action="store_true", dest="stretch", default=False,
help="Stretch images to device's resolution")
processingOptions.add_option("-r", "--splitter", type="int", dest="splitter", default="0",
help="Double page parsing mode. 0: Split 1: Rotate 2: Both [Default=0]")
processingOptions.add_option("-g", "--gamma", type="float", dest="gamma", default="0.0",
help="Apply gamma correction to linearize the image [Default=Auto]")
processingOptions.add_option("--hq", action="store_true", dest="hqmode", default=False,
help="Enable high quality Panel View")
processingOptions.add_option("--blackborders", action="store_true", dest="black_borders", default=False,
help="Disable autodetection and force black borders")
processingOptions.add_option("--whiteborders", action="store_true", dest="white_borders", default=False,
@@ -1094,20 +985,8 @@ def makeParser():
help="Don't convert images to grayscale")
processingOptions.add_option("--forcepng", action="store_true", dest="forcepng", default=False,
help="Create PNG files instead JPEG")
processingOptions.add_option("--gamma", type="float", dest="gamma", default="0.0",
help="Apply gamma correction to linearize the image [Default=Auto]")
processingOptions.add_option("--nocutpagenumbers", action="store_false", dest="cutpagenumbers", default=True,
help="Don't try to cut page numbering on images")
processingOptions.add_option("--noprocessing", action="store_false", dest="imgproc", default=True,
help="Don't apply image preprocessing")
processingOptions.add_option("--nosplitrotate", action="store_true", dest="nosplitrotate", default=False,
help="Disable splitting and rotation")
processingOptions.add_option("--rotate", action="store_true", dest="rotate", default=False,
help="Rotate landscape pages instead of splitting them")
processingOptions.add_option("--stretch", action="store_true", dest="stretch", default=False,
help="Stretch images to device's resolution")
processingOptions.add_option("--upscale", action="store_true", dest="upscale", default=False,
help="Resize images smaller than device's resolution")
help="Don't try to cut page numbers from images")
customProfileOptions.add_option("--customwidth", type="int", dest="customwidth", default=0,
help="Replace screen width provided by device profile")
@@ -1128,7 +1007,6 @@ def makeParser():
def checkOptions():
global options
options.panelview = True
options.panelviewused = False
options.iskindle = False
options.bordersColor = None
if options.format == 'Auto':
@@ -1138,7 +1016,7 @@ def checkOptions():
options.format = 'EPUB'
elif options.profile in ['KDX']:
options.format = 'CBZ'
if options.profile in ['K1', 'K2', 'K345', 'KPW', 'KV', 'OTHER']:
if options.profile in ['K1', 'K2', 'K345', 'KPW', 'KV']:
options.iskindle = True
if options.white_borders:
options.bordersColor = 'white'
@@ -1149,28 +1027,24 @@ def checkOptions():
options.batchsplit = True
# Older Kindle don't need higher resolution files due lack of Panel View.
if options.profile == 'K1' or options.profile == 'K2' or options.profile == 'KDX':
options.quality = 0
options.panelview = False
options.hqmode = False
# Webtoon mode mandatory options
if options.webtoon:
options.nosplitrotate = True
options.quality = 0
options.panelview = False
options.hqmode = False
options.righttoleft = False
options.upscale = True
# Disable all Kindle features for other e-readers
if options.profile == 'OTHER':
options.panelview = False
options.quality = 0
options.hqmode = False
if 'Ko' in options.profile:
options.panelview = False
# Kobo models can't use ultra quality mode
if options.quality == 2:
options.quality = 1
options.hqmode = False
# CBZ files on Kindle DX/DXG support higher resolution
if options.profile == 'KDX' and options.format == 'CBZ':
options.customheight = 1200
# Ultra mode don't work with CBZ format
if options.quality == 2 and options.format == 'CBZ':
options.quality = 1
# Override profile data
if options.customwidth != 0 or options.customheight != 0:
X = image.ProfileData.Profiles[options.profile][1][0]
@@ -1192,18 +1066,18 @@ def checkTools(source):
rarExitCode = Popen('unrar', stdout=PIPE, stderr=STDOUT, shell=True)
rarExitCode = rarExitCode.wait()
if rarExitCode != 0 and rarExitCode != 7:
print('\nUnRAR is missing!')
print('ERROR: UnRAR is missing!')
exit(1)
elif source.endswith('.CB7') or source.endswith('.7Z'):
sevenzaExitCode = Popen('7za', stdout=PIPE, stderr=STDOUT, shell=True)
sevenzaExitCode = sevenzaExitCode.wait()
if sevenzaExitCode != 0 and sevenzaExitCode != 7:
print('\n7za is missing!')
print('ERROR: 7za is missing!')
exit(1)
if options.format == 'MOBI':
kindleGenExitCode = Popen('kindlegen -locale en', stdout=PIPE, stderr=STDOUT, shell=True)
if kindleGenExitCode.wait() != 0:
print('\nKindleGen is missing!')
print('ERROR: KindleGen is missing!')
exit(1)
@@ -1226,7 +1100,6 @@ def checkPre(source):
def makeBook(source, qtGUI=None):
"""Generates MOBI/EPUB/CBZ comic ebook from a bunch of images."""
global GUI
GUI = qtGUI
if GUI:
@@ -1234,17 +1107,21 @@ def makeBook(source, qtGUI=None):
else:
checkTools(source)
checkPre(source)
print("Preparing source images...")
path = getWorkFolder(source)
print("\nChecking images...")
print("Checking images...")
getComicInfo(os.path.join(path, "OEBPS", "Images"), source)
detectCorruption(os.path.join(path, "OEBPS", "Images"), source)
if options.webtoon:
comic2panel.main(['-y ' + str(image.ProfileData.Profiles[options.profile][1][1]), '-i', '-m', path], qtGUI)
if options.imgproc:
print("\nProcessing images...")
if GUI:
GUI.progressBarTick.emit('Processing images')
imgDirectoryProcessing(os.path.join(path, "OEBPS", "Images"))
if image.ProfileData.Profiles[options.profile][1][1] > 1000:
y = 1000
else:
y = image.ProfileData.Profiles[options.profile][1][1]
comic2panel.main(['-y ' + str(y), '-i', '-m', path], qtGUI)
print("Processing images...")
if GUI:
GUI.progressBarTick.emit('Processing images')
imgDirectoryProcessing(os.path.join(path, "OEBPS", "Images"))
if GUI:
GUI.progressBarTick.emit('1')
chapterNames = sanitizeTree(os.path.join(path, 'OEBPS', 'Images'))
@@ -1272,14 +1149,14 @@ def makeBook(source, qtGUI=None):
tomeNumber += 1
options.title = options.baseTitle + ' [' + str(tomeNumber) + '/' + str(len(tomes)) + ']'
if options.format == 'CBZ':
print("\nCreating CBZ file...")
print("Creating CBZ file...")
if len(tomes) > 1:
filepath.append(getOutputFilename(source, options.output, '.cbz', ' ' + str(tomeNumber)))
else:
filepath.append(getOutputFilename(source, options.output, '.cbz', ''))
makeZIP(tome + '_comic', os.path.join(tome, "OEBPS", "Images"))
else:
print("\nCreating EPUB file...")
print("Creating EPUB file...")
buildEPUB(tome, chapterNames, tomeNumber)
if len(tomes) > 1:
filepath.append(getOutputFilename(source, options.output, '.epub', ' ' + str(tomeNumber)))
@@ -1291,20 +1168,20 @@ def makeBook(source, qtGUI=None):
if GUI:
GUI.progressBarTick.emit('tick')
if not GUI and options.format == 'MOBI':
print("\nCreating MOBI file...")
print("Creating MOBI file...")
work = []
for i in filepath:
work.append([i])
output = makeMOBI(work, GUI)
for errors in output:
if errors[0] != 0:
print('KINDLEGEN ERROR!')
print('Error: KindleGen failed to create MOBI!')
print(errors)
return filepath
for i in filepath:
output = makeMOBIFix(i)
if not output[0]:
print('DUALMETAFIX ERROR!')
print('Error: Failed to tweak KindleGen output!')
return filepath
else:
os.remove(i.replace('.epub', '.mobi') + '_toclean')

View File

@@ -232,7 +232,7 @@ def main(argv=None, qtGUI=None):
GUI = None
if len(args) != 1:
parser.print_help()
return
return 1
if options.height > 0:
options.sourceDir = args[0]
options.targetDir = args[0] + "-Splitted"
@@ -244,7 +244,7 @@ def main(argv=None, qtGUI=None):
splitWorkerOutput = []
splitWorkerPool = Pool()
if options.merge:
print("\nMerging images...")
print("Merging images...")
directoryNumer = 1
mergeWork = []
mergeWorkerOutput = []
@@ -268,7 +268,7 @@ def main(argv=None, qtGUI=None):
if len(mergeWorkerOutput) > 0:
rmtree(options.targetDir, True)
raise RuntimeError("One of workers crashed. Cause: " + mergeWorkerOutput[0])
print("\nSplitting images...")
print("Splitting images...")
for root, dirs, files in walk(options.targetDir, False):
for name in files:
if getImageFileName(name) is not None:

View File

@@ -20,7 +20,6 @@ import os
from io import BytesIO
from urllib.request import Request, urlopen
from urllib.parse import quote
from functools import reduce
from PIL import Image, ImageOps, ImageStat, ImageChops
from .shared import md5Checksum
from . import __version__
@@ -94,73 +93,167 @@ class ProfileData:
}
class ComicPage:
def __init__(self, source, options, original=None):
try:
self.profile_label, self.size, self.palette, self.gamma, self.panelviewsize = options.profileData
except KeyError:
raise RuntimeError('Unexpected output device %s' % options.profileData)
self.origFileName = source
self.filename = os.path.basename(self.origFileName)
self.image = Image.open(source)
self.image = self.image.convert('RGB')
class ComicPageParser:
def __init__(self, source, options):
self.opt = options
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
self.noHQ = False
if options.webtoon:
self.color = True
else:
self.color = self.isImageColor()
self.source = source
self.size = self.opt.profileData[1]
self.payload = []
self.image = Image.open(os.path.join(source[0], source[1])).convert('RGB')
self.color = self.colorCheck()
self.fill = self.fillCheck()
self.splitCheck()
if self.opt.hqmode:
self.sizeCheck()
def saveToDir(self, targetdir):
def getImageHistogram(self, image):
histogram = image.histogram()
if histogram[0] == 0:
return -1
elif histogram[255] == 0:
return 1
else:
return 0
def splitCheck(self):
width, height = self.image.size
dstwidth, dstheight = self.size
# Only split if origin is not oriented the same as target
if (width > height) != (dstwidth > dstheight) and not self.opt.webtoon:
if self.opt.splitter != 1:
if width > height:
# Source is landscape, so split by the width
leftbox = (0, 0, int(width / 2), height)
rightbox = (int(width / 2), 0, width, height)
else:
# Source is portrait and target is landscape, so split by the height
leftbox = (0, 0, width, int(height / 2))
rightbox = (0, int(height / 2), width, height)
if self.opt.righttoleft:
pageone = self.image.crop(rightbox)
pagetwo = self.image.crop(leftbox)
else:
pageone = self.image.crop(leftbox)
pagetwo = self.image.crop(rightbox)
self.payload.append(['S1', self.source, pageone, self.color, self.fill])
self.payload.append(['S2', self.source, pagetwo, self.color, self.fill])
if self.opt.splitter > 0:
self.payload.append(['R', self.source, self.image.rotate(90, Image.BICUBIC, True),
self.color, self.fill])
else:
self.payload.append(['N', self.source, self.image, self.color, self.fill])
def colorCheck(self):
if self.opt.webtoon:
return True
else:
img = self.image.copy()
bands = img.getbands()
if bands == ('R', 'G', 'B') or bands == ('R', 'G', 'B', 'A'):
thumb = img.resize((40, 40))
SSE, bias = 0, [0, 0, 0]
bias = ImageStat.Stat(thumb).mean[:3]
bias = [b - sum(bias) / 3 for b in bias]
for pixel in thumb.getdata():
mu = sum(pixel) / 3
SSE += sum((pixel[i] - mu - bias[i]) * (pixel[i] - mu - bias[i]) for i in [0, 1, 2])
MSE = float(SSE) / (40 * 40)
if MSE > 22:
return True
else:
return False
else:
return False
def fillCheck(self):
if self.opt.bordersColor:
return self.opt.bordersColor
else:
bw = self.image.convert('L').point(lambda x: 0 if x < 128 else 255, '1')
imageBoxA = bw.getbbox()
imageBoxB = ImageChops.invert(bw).getbbox()
if imageBoxA is None or imageBoxB is None:
surfaceB, surfaceW = 0, 0
diff = 0
else:
surfaceB = (imageBoxA[2] - imageBoxA[0]) * (imageBoxA[3] - imageBoxA[1])
surfaceW = (imageBoxB[2] - imageBoxB[0]) * (imageBoxB[3] - imageBoxB[1])
diff = ((max(surfaceB, surfaceW) - min(surfaceB, surfaceW)) / min(surfaceB, surfaceW)) * 100
if diff > 0.5:
if surfaceW < surfaceB:
return 'white'
elif surfaceW > surfaceB:
return 'black'
else:
fill = 0
startY = 0
while startY < bw.size[1]:
if startY + 5 > bw.size[1]:
startY = bw.size[1] - 5
fill += self.getImageHistogram(bw.crop((0, startY, bw.size[0], startY + 5)))
startY += 5
startX = 0
while startX < bw.size[0]:
if startX + 5 > bw.size[0]:
startX = bw.size[0] - 5
fill += self.getImageHistogram(bw.crop((startX, 0, startX + 5, bw.size[1])))
startX += 5
if fill > 0:
return 'black'
else:
return 'white'
def sizeCheck(self):
additionalPayload = []
width, height = self.image.size
dstwidth, dstheight = self.size
for work in self.payload:
if width > dstwidth and height > dstheight:
additionalPayload.append([work[0] + '+', work[1], work[2].copy(), work[3], work[4]])
self.payload = self.payload + additionalPayload
class ComicPage:
def __init__(self, mode, path, image, color, fill, options):
self.opt = options
_, self.size, self.palette, self.gamma, self.panelviewsize = self.opt.profileData
self.image = image
self.color = color
self.fill = fill
self.rotated = False
self.orgPath = os.path.join(path[0], path[1])
if '+' in mode:
self.hqMode = True
else:
self.hqMode = False
if 'N' in mode:
self.targetPath = os.path.join(path[0], os.path.splitext(path[1])[0]) + '-KCC'
elif 'R' in mode:
self.targetPath = os.path.join(path[0], os.path.splitext(path[1])[0]) + '-KCC-A'
self.rotated = True
elif 'S1' in mode:
self.targetPath = os.path.join(path[0], os.path.splitext(path[1])[0]) + '-KCC-B'
elif 'S2' in mode:
self.targetPath = os.path.join(path[0], os.path.splitext(path[1])[0]) + '-KCC-C'
def saveToDir(self):
try:
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:
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.hqMode:
self.targetPath += '-HQ'
if self.opt.forcepng:
filename += '.png'
self.image.save(filename, 'PNG', optimize=1)
self.targetPath += '.png'
self.image.save(self.targetPath, 'PNG', optimize=1)
else:
filename += '.jpg'
self.image.save(filename, 'JPEG', optimize=1, quality=80)
return [md5Checksum(filename), flags]
self.targetPath += '.jpg'
self.image.save(self.targetPath, 'JPEG', optimize=1, quality=80)
return [md5Checksum(self.targetPath), flags, self.orgPath]
except IOError as e:
raise RuntimeError('Cannot write image in directory %s: %s' % (targetdir, e))
@@ -186,127 +279,42 @@ class ComicPage:
# Quantize is deprecated but new function call it internally anyway...
self.image = self.image.quantize(palette=palImg)
def calculateBorder(self):
if self.noPV:
self.border = [0.0, 0.0, 0.0, 0.0]
return
if self.fill == 'white':
border = ImageChops.invert(self.image).getbbox()
else:
border = self.image.getbbox()
if self.opt.quality == 2:
multiplier = 1.0
else:
multiplier = 1.5
if border is not None:
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] + 10:
self.noVPV = True
else:
self.border = [0.0, 0.0, 0.0, 0.0]
self.noHPV = True
self.noVPV = True
def resizeImage(self):
if self.opt.bordersColor:
fill = self.opt.bordersColor
else:
fill = self.fill
# Set target size
if self.opt.quality == 0:
size = (self.size[0], self.size[1])
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 self.opt.quality == 1:
# Forcing upscale to make sure that margins will be not too big
if not self.opt.stretch:
self.opt.upscale = True
if self.hqMode:
size = (self.panelviewsize[0], self.panelviewsize[1])
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]:
# HQ version will not be needed
self.noHQ = True
return
if self.image.size[0] > size[0] or self.image.size[1] > size[1]:
self.image.thumbnail(size, Image.LANCZOS)
else:
size = (self.panelviewsize[0], self.panelviewsize[1])
# If stretching is on - Resize without other considerations
if self.opt.stretch:
size = (self.size[0], self.size[1])
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
# 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 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
if self.image.size[0] <= self.size[0] and self.image.size[1] <= self.size[1]:
self.noPV = True
self.image = ImageOps.expand(self.image, border=(borderw, borderh), fill=fill)
# 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
# Otherwise - Upscale/Downscale
ratioDev = float(size[0]) / float(size[1])
if (float(self.image.size[0]) / float(self.image.size[1])) < ratioDev:
diff = int(self.image.size[1] * ratioDev) - self.image.size[0]
self.image = ImageOps.expand(self.image, border=(int(diff / 2), 0), fill=fill)
elif (float(self.image.size[0]) / float(self.image.size[1])) > ratioDev:
diff = int(self.image.size[0] / ratioDev) - self.image.size[1]
self.image = ImageOps.expand(self.image, border=(0, int(diff / 2)), fill=fill)
if self.image.size[0] <= size[0] and self.image.size[1] <= size[1]:
method = Image.BICUBIC
else:
method = Image.LANCZOS
self.image = ImageOps.fit(self.image, size, method=method, centering=(0.5, 0.5))
return
def splitPage(self, targetdir):
width, height = self.image.size
dstwidth, dstheight = self.size
# Only split if origin is not oriented the same as target
if (width > height) != (dstwidth > dstheight):
if self.opt.rotate:
self.image = self.image.rotate(90, Image.BICUBIC, True)
self.rotated = True
return None
if self.opt.stretch:
self.image = self.image.resize(size, method)
elif self.image.size[0] <= size[0] and self.image.size[1] <= size[1] and not self.opt.upscale:
if self.opt.format == 'CBZ':
borderw = int((size[0] - self.image.size[0]) / 2)
borderh = int((size[1] - self.image.size[1]) / 2)
self.image = ImageOps.expand(self.image, border=(borderw, borderh), fill=self.fill)
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))
else:
self.rotated = False
if width > height:
# Source is landscape, so split by the width
leftbox = (0, 0, int(width / 2), height)
rightbox = (int(width / 2), 0, width, height)
if self.opt.format == 'CBZ':
ratioDev = float(size[0]) / float(size[1])
if (float(self.image.size[0]) / float(self.image.size[1])) < ratioDev:
diff = int(self.image.size[1] * ratioDev) - self.image.size[0]
self.image = ImageOps.expand(self.image, border=(int(diff / 2), 0), fill=self.fill)
elif (float(self.image.size[0]) / float(self.image.size[1])) > ratioDev:
diff = int(self.image.size[0] / ratioDev) - self.image.size[1]
self.image = ImageOps.expand(self.image, border=(0, int(diff / 2)), fill=self.fill)
self.image = ImageOps.fit(self.image, size, method=method, centering=(0.5, 0.5))
else:
# Source is portrait and target is landscape, so split by the height
leftbox = (0, 0, width, int(height / 2))
rightbox = (0, int(height / 2), width, height)
filename = os.path.splitext(self.filename)[0]
fileone = targetdir + '/' + filename + '-AAA.png'
filetwo = targetdir + '/' + filename + '-BBB.png'
try:
if self.opt.righttoleft:
pageone = self.image.crop(rightbox)
pagetwo = self.image.crop(leftbox)
else:
pageone = self.image.crop(leftbox)
pagetwo = self.image.crop(rightbox)
pageone.save(fileone, 'PNG', optimize=1)
pagetwo.save(filetwo, 'PNG', optimize=1)
except IOError as e:
raise RuntimeError('Cannot write image in directory %s: %s' % (targetdir, e))
return fileone, filetwo
else:
self.rotated = False
return None
hpercent = size[1] / float(self.image.size[1])
wsize = int((float(self.image.size[0]) * float(hpercent)))
self.image = self.image.resize((wsize, size[1]), method)
if self.image.size[0] > size[0] or self.image.size[1] > size[1]:
self.image.thumbnail(size, Image.LANCZOS)
def cutPageNumber(self):
if ImageChops.invert(self.image).getbbox() is not None:
@@ -397,71 +405,6 @@ class ComicPage:
diff -= delta
self.image = self.image.crop((0, 0, widthImg - diff, heightImg))
def getImageHistogram(self, image):
histogram = image.histogram()
if histogram[0] == 0:
return -1
elif histogram[255] == 0:
return 1
else:
return 0
def getImageFill(self):
bw = self.image.convert('L').point(lambda x: 0 if x < 128 else 255, '1')
imageBoxA = bw.getbbox()
imageBoxB = ImageChops.invert(bw).getbbox()
if imageBoxA is None or imageBoxB is None:
surfaceB, surfaceW = 0, 0
diff = 0
else:
surfaceB = (imageBoxA[2] - imageBoxA[0]) * (imageBoxA[3] - imageBoxA[1])
surfaceW = (imageBoxB[2] - imageBoxB[0]) * (imageBoxB[3] - imageBoxB[1])
diff = ((max(surfaceB, surfaceW) - min(surfaceB, surfaceW)) / min(surfaceB, surfaceW)) * 100
if diff > 0.5:
if surfaceW < surfaceB:
self.fill = 'white'
elif surfaceW > surfaceB:
self.fill = 'black'
else:
fill = 0
startY = 0
while startY < bw.size[1]:
if startY + 5 > bw.size[1]:
startY = bw.size[1] - 5
fill += self.getImageHistogram(bw.crop((0, startY, bw.size[0], startY + 5)))
startY += 5
startX = 0
while startX < bw.size[0]:
if startX + 5 > bw.size[0]:
startX = bw.size[0] - 5
fill += self.getImageHistogram(bw.crop((startX, 0, startX + 5, bw.size[1])))
startX += 5
if fill > 0:
self.fill = 'black'
else:
self.fill = 'white'
def isImageColor(self):
img = self.image.copy()
bands = img.getbands()
if bands == ('R', 'G', 'B') or bands == ('R', 'G', 'B', 'A'):
thumb = img.resize((40, 40))
SSE, bias = 0, [0, 0, 0]
bias = ImageStat.Stat(thumb).mean[:3]
bias = [b - sum(bias) / 3 for b in bias]
for pixel in thumb.getdata():
mu = sum(pixel) / 3
SSE += sum((pixel[i] - mu - bias[i]) * (pixel[i] - mu - bias[i]) for i in [0, 1, 2])
MSE = float(SSE) / (40 * 40)
if MSE <= 22:
return False
else:
return True
elif len(bands) == 1:
return False
else:
return False
class Cover:
def __init__(self, source, target, opt, tomeNumber):