diff options
-rwxr-xr-x | kcc-c2e.py | 3 | ||||
-rwxr-xr-x | kcc-c2p.py | 3 | ||||
-rwxr-xr-x | kcc/comic2ebook.py | 657 | ||||
-rw-r--r-- | kcc/comic2panel.py | 6 | ||||
-rwxr-xr-x | kcc/image.py | 405 |
5 files changed, 446 insertions, 628 deletions
diff --git a/kcc-c2e.py b/kcc-c2e.py index 0e0fb6a..0d3fbf6 100755 --- a/kcc-c2e.py +++ b/kcc-c2e.py @@ -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:])) diff --git a/kcc-c2p.py b/kcc-c2p.py index 7709306..a1ded61 100755 --- a/kcc-c2p.py +++ b/kcc-c2p.py @@ -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:])) diff --git a/kcc/comic2ebook.py b/kcc/comic2ebook.py index 27b6753..199ab66 100755 --- a/kcc/comic2ebook.py +++ b/kcc/comic2ebook.py @@ -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') diff --git a/kcc/comic2panel.py b/kcc/comic2panel.py index 8625fb8..fa57535 100644 --- a/kcc/comic2panel.py +++ b/kcc/comic2panel.py @@ -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: diff --git a/kcc/image.py b/kcc/image.py index b98bc2a..dc6dbee 100755 --- a/kcc/image.py +++ b/kcc/image.py @@ -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 + 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 getImageHistogram(self, image): + histogram = image.histogram() + if histogram[0] == 0: + return -1 + elif histogram[255] == 0: + return 1 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 + 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: - self.color = self.isImageColor() + 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, targetdir): + 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): |