1
0
mirror of https://github.com/ciromattia/kcc synced 2025-12-15 10:46:40 +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__": if __name__ == "__main__":
freeze_support() freeze_support()
print('comic2ebook v' + __version__ + ' - Written by Ciro Mattia Gonano and Pawel Jastrzebski.') print('comic2ebook v' + __version__ + ' - Written by Ciro Mattia Gonano and Pawel Jastrzebski.')
main(sys.argv[1:]) sys.exit(main(sys.argv[1:]))
sys.exit(0)

View File

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

View File

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

View File

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

View File

@@ -20,7 +20,6 @@ import os
from io import BytesIO from io import BytesIO
from urllib.request import Request, urlopen from urllib.request import Request, urlopen
from urllib.parse import quote from urllib.parse import quote
from functools import reduce
from PIL import Image, ImageOps, ImageStat, ImageChops from PIL import Image, ImageOps, ImageStat, ImageChops
from .shared import md5Checksum from .shared import md5Checksum
from . import __version__ from . import __version__
@@ -94,73 +93,167 @@ class ProfileData:
} }
class ComicPage: class ComicPageParser:
def __init__(self, source, options, original=None): def __init__(self, source, options):
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')
self.opt = options self.opt = options
if original: self.source = source
self.second = True self.size = self.opt.profileData[1]
self.rotated = original.rotated self.payload = []
self.border = original.border self.image = Image.open(os.path.join(source[0], source[1])).convert('RGB')
self.noHPV = original.noHPV self.color = self.colorCheck()
self.noVPV = original.noVPV self.fill = self.fillCheck()
self.noPV = original.noPV self.splitCheck()
self.noHQ = original.noHQ if self.opt.hqmode:
self.fill = original.fill self.sizeCheck()
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()
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: try:
flags = [] flags = []
filename = os.path.join(targetdir, os.path.splitext(self.filename)[0]) + '-KCC'
if not self.opt.forcecolor and not self.opt.forcepng: if not self.opt.forcecolor and not self.opt.forcepng:
self.image = self.image.convert('L') self.image = self.image.convert('L')
if self.rotated: if self.rotated:
flags.append('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': if self.fill != 'white':
flags.append('BlackFill') flags.append('BlackFill')
if self.opt.quality == 2: if self.hqMode:
filename += '-HQ' self.targetPath += '-HQ'
if self.opt.forcepng: if self.opt.forcepng:
filename += '.png' self.targetPath += '.png'
self.image.save(filename, 'PNG', optimize=1) self.image.save(self.targetPath, 'PNG', optimize=1)
else: else:
filename += '.jpg' self.targetPath += '.jpg'
self.image.save(filename, 'JPEG', optimize=1, quality=80) self.image.save(self.targetPath, 'JPEG', optimize=1, quality=80)
return [md5Checksum(filename), flags] return [md5Checksum(self.targetPath), flags, self.orgPath]
except IOError as e: except IOError as e:
raise RuntimeError('Cannot write image in directory %s: %s' % (targetdir, 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... # Quantize is deprecated but new function call it internally anyway...
self.image = self.image.quantize(palette=palImg) 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): def resizeImage(self):
if self.opt.bordersColor: if self.hqMode:
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
size = (self.panelviewsize[0], self.panelviewsize[1]) 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] <=\ if self.image.size[0] > size[0] or self.image.size[1] > size[1]:
self.size[0] and self.image.size[1] <= self.size[1]: self.image.thumbnail(size, Image.LANCZOS)
# HQ version will not be needed
self.noHQ = True
return
else: else:
size = (self.panelviewsize[0], self.panelviewsize[1]) size = (self.size[0], self.size[1])
# If stretching is on - Resize without other considerations
if self.opt.stretch:
if self.image.size[0] <= size[0] and self.image.size[1] <= size[1]: if self.image.size[0] <= size[0] and self.image.size[1] <= size[1]:
method = Image.BICUBIC method = Image.BICUBIC
else: else:
method = Image.LANCZOS method = Image.LANCZOS
if self.opt.stretch:
self.image = self.image.resize(size, method) self.image = self.image.resize(size, method)
return elif self.image.size[0] <= size[0] and self.image.size[1] <= size[1] and not self.opt.upscale:
# If image is smaller than target resolution and upscale is off - Just expand it by adding margins if self.opt.format == 'CBZ':
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) borderw = int((size[0] - self.image.size[0]) / 2)
borderh = int((size[1] - self.image.size[1]) / 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 self.image = ImageOps.expand(self.image, border=(borderw, borderh), fill=self.fill)
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]: 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)) self.image = ImageOps.fit(self.image, size, method=Image.BICUBIC, centering=(0.5, 0.5))
return else:
# Otherwise - Upscale/Downscale if self.opt.format == 'CBZ':
ratioDev = float(size[0]) / float(size[1]) ratioDev = float(size[0]) / float(size[1])
if (float(self.image.size[0]) / float(self.image.size[1])) < ratioDev: if (float(self.image.size[0]) / float(self.image.size[1])) < ratioDev:
diff = int(self.image.size[1] * ratioDev) - self.image.size[0] diff = int(self.image.size[1] * ratioDev) - self.image.size[0]
self.image = ImageOps.expand(self.image, border=(int(diff / 2), 0), fill=fill) 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: elif (float(self.image.size[0]) / float(self.image.size[1])) > ratioDev:
diff = int(self.image.size[0] / ratioDev) - self.image.size[1] diff = int(self.image.size[0] / ratioDev) - self.image.size[1]
self.image = ImageOps.expand(self.image, border=(0, int(diff / 2)), fill=fill) self.image = ImageOps.expand(self.image, border=(0, int(diff / 2)), fill=self.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)) 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
else: else:
self.rotated = False hpercent = size[1] / float(self.image.size[1])
if width > height: wsize = int((float(self.image.size[0]) * float(hpercent)))
# Source is landscape, so split by the width self.image = self.image.resize((wsize, size[1]), method)
leftbox = (0, 0, int(width / 2), height) if self.image.size[0] > size[0] or self.image.size[1] > size[1]:
rightbox = (int(width / 2), 0, width, height) self.image.thumbnail(size, Image.LANCZOS)
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
def cutPageNumber(self): def cutPageNumber(self):
if ImageChops.invert(self.image).getbbox() is not None: if ImageChops.invert(self.image).getbbox() is not None:
@@ -397,71 +405,6 @@ class ComicPage:
diff -= delta diff -= delta
self.image = self.image.crop((0, 0, widthImg - diff, heightImg)) 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: class Cover:
def __init__(self, source, target, opt, tomeNumber): def __init__(self, source, target, opt, tomeNumber):