diff --git a/kindlecomicconverter/KCC_gui.py b/kindlecomicconverter/KCC_gui.py index f984801..309b1a8 100644 --- a/kindlecomicconverter/KCC_gui.py +++ b/kindlecomicconverter/KCC_gui.py @@ -946,6 +946,8 @@ class KCCGUI(KCC_ui.Ui_mainWindow): GUI.formats[str(GUI.formatBox.currentText())]['format'] == 'MOBI+EPUB-200MB'): GUI.chunkSizeCheckBox.setEnabled(False) GUI.chunkSizeCheckBox.setChecked(False) + elif GUI.formats[str(GUI.formatBox.currentText())]['format'] == 'KFX': + GUI.mozJpegBox.setCheckState(Qt.CheckState.PartiallyChecked) elif not GUI.webtoonBox.isChecked(): GUI.chunkSizeCheckBox.setEnabled(True) if GUI.formats[str(GUI.formatBox.currentText())]['format'] in ('CBZ', 'PDF') and not GUI.webtoonBox.isChecked(): @@ -1234,7 +1236,7 @@ class KCCGUI(KCC_ui.Ui_mainWindow): "CBZ": {'icon': 'CBZ', 'format': 'CBZ'}, "PDF": {'icon': 'EPUB', 'format': 'PDF'}, "PDF (200MB limit)": {'icon': 'EPUB', 'format': 'PDF-200MB'}, - "KFX (does not work)": {'icon': 'KFX', 'format': 'KFX'}, + "KFX (Send to Kindle EPUB)": {'icon': 'KFX', 'format': 'KFX'}, "MOBI + EPUB": {'icon': 'MOBI', 'format': 'MOBI+EPUB'}, "EPUB (200MB limit)": {'icon': 'EPUB', 'format': 'EPUB-200MB'}, "MOBI + EPUB (200MB limit)": {'icon': 'MOBI', 'format': 'MOBI+EPUB-200MB'}, diff --git a/kindlecomicconverter/comic2ebook.py b/kindlecomicconverter/comic2ebook.py index 22a0604..278368f 100755 --- a/kindlecomicconverter/comic2ebook.py +++ b/kindlecomicconverter/comic2ebook.py @@ -18,6 +18,7 @@ # PERFORMANCE OF THIS SOFTWARE. # +from collections import Counter import os import pathlib import re @@ -43,7 +44,7 @@ from psutil import virtual_memory, disk_usage from html import escape as hescape import pymupdf -from .shared import IMAGE_TYPES, getImageFileName, walkSort, walkLevel, sanitizeTrace, subprocess_run, dot_clean +from .shared import IMAGE_TYPES, getImageFileName, walkSort, walkLevel, sanitizeTrace, subprocess_run, dot_clean, get_contain_resolution from .comicarchive import SEVENZIP, available_archive_tools from . import comic2panel from . import image @@ -324,8 +325,19 @@ def buildOPF(dstdir, title, filelist, originalpath, cover=None): f.write("\n") if options.iskindle and options.profile != 'Custom': f.writelines(["\n", - "\n", + ]) + if not options.kfx_resolution: + f.writelines([ + "\n", + ]) + else: + x, y = options.kfx_resolution + f.writelines([ + "\n", + ]) + f.writelines([ "\n", "\n", "\n", @@ -1476,8 +1488,8 @@ def checkOptions(options): else: options.isKobo = True - if not options.iskindle and ('MOBI' in options.format or 'EPUB-200MB' in options.format): - raise UserWarning('MOBI/EPUB-200MB not supported for non-Kindle profiles') + if not options.iskindle and ('MOBI' in options.format or 'EPUB-200MB' in options.format or 'KFX' in options.format): + raise UserWarning('MOBI/Send to Kindle not supported for non-Kindle profiles') if options.format == 'PDF-200MB': options.targetsize = 195 @@ -1541,6 +1553,7 @@ def checkOptions(options): options.hq = False # KFX output create EPUB that might be can be by jhowell KFX Output Calibre plugin if options.format == 'KFX': + options.targetsize = 195 options.format = 'EPUB' options.kfx = True options.panelview = False @@ -1678,9 +1691,43 @@ def makeBook(source, qtgui=None, job_progress=''): if not options.webtoon: cover = image.Cover(cover_path, options) + x, y = image.ProfileData.Profiles[options.profile][1] if options.webtoon: - x, y = image.ProfileData.Profiles[options.profile][1] comic2panel.main(['-y ' + str(y), '-x' + str(x), '-i', '-m', path], job_progress, qtgui) + + options.kfx_resolution = None + if options.kfx: + original_resolutions = [] + normalized_resolutions = [] + for root, _, files in os.walk(os.path.join(path, "OEBPS", "Images")): + for file in files: + with Image.open(os.path.join(root, file)) as imagef: + original_resolutions.append(imagef.size) + size = get_contain_resolution(imagef, (x, y)) + normalized_resolutions.append(size) + + counter = Counter(normalized_resolutions) + + aspect_ratios = [] + filtered_resolutions = [] + for w, h in normalized_resolutions: + aspect_ratio = h / w + # page-like aspect ratios, could be improved + if aspect_ratio > 1.3 and aspect_ratio < 1.7: + aspect_ratios.append(aspect_ratio) + filtered_resolutions.append((w, h)) + + most_common_res, most_common_count = counter.most_common(1)[0] + options.kfx_resolution = most_common_res + if most_common_count / counter.total() > .6: + pass + #elif max(aspect_ratios) - min(aspect_ratios) < .2: + else: + # get the widest resolution + options.kfx_resolution = max(filtered_resolutions) + # else: + # raise UserWarning('Aspect ratio of pages too different for KFX conversion') + if options.noprocessing: print(f"{job_progress}Do not process image, ignore any profile or processing option") else: diff --git a/kindlecomicconverter/image.py b/kindlecomicconverter/image.py index 82b93de..16d9095 100755 --- a/kindlecomicconverter/image.py +++ b/kindlecomicconverter/image.py @@ -29,6 +29,7 @@ from PIL import Image, ImageOps, ImageFile, ImageChops, ImageDraw from .rainbow_artifacts_eraser import erase_rainbow_artifacts from .page_number_crop_alg import get_bbox_crop_margin_page_number, get_bbox_crop_margin from .inter_panel_crop_alg import crop_empty_inter_panel +from .shared import get_contain_resolution AUTO_CROP_THRESHOLD = 0.015 ImageFile.LOAD_TRUNCATED_IMAGES = True @@ -517,7 +518,17 @@ class ComicPage: ratio_device = float(self.size[1]) / float(self.size[0]) ratio_image = float(self.image.size[1]) / float(self.image.size[0]) method = self.resize_method() - if self.opt.stretch: + if self.opt.kfx: + ratio_kfx = self.opt.kfx_resolution[1] / self.opt.kfx_resolution[0] + contain_size = get_contain_resolution(self.image, self.size) + if abs(ratio_image - ratio_kfx) < AUTO_CROP_THRESHOLD: + if contain_size[0] > self.opt.kfx_resolution[0] or contain_size[1] > self.opt.kfx_resolution[1]: + self.image = ImageOps.fit(self.image, self.opt.kfx_resolution, method=method) + else: + self.image = ImageOps.pad(self.image, self.opt.kfx_resolution, method=method, color=self.fill) + else: + self.image = ImageOps.pad(self.image, self.opt.kfx_resolution, method=method, color=self.fill) + elif self.opt.stretch: self.image = self.image.resize(self.size, method) elif method == Image.Resampling.BICUBIC and not self.opt.upscale: pass @@ -526,7 +537,7 @@ class ComicPage: self.image = ImageOps.fit(self.image, self.size, method=method) elif abs(ratio_image - ratio_device) < AUTO_CROP_THRESHOLD: self.image = ImageOps.fit(self.image, self.size, method=method) - elif (self.opt.format in ('CBZ', 'PDF') or self.opt.kfx) and not self.opt.white_borders: + elif (self.opt.format in ('CBZ', 'PDF')) and not self.opt.white_borders: self.image = ImageOps.pad(self.image, self.size, method=method, color=self.fill) else: self.image = ImageOps.contain(self.image, self.size, method=method) diff --git a/kindlecomicconverter/shared.py b/kindlecomicconverter/shared.py index 4a82f23..857b6cc 100644 --- a/kindlecomicconverter/shared.py +++ b/kindlecomicconverter/shared.py @@ -61,6 +61,23 @@ def getImageFileName(imgfile): ext = ext.lower() return [name, ext] +def get_contain_resolution(image, size): + '''same code as Pillow ImageOps.contain()''' + im_ratio = image.width / image.height + dest_ratio = size[0] / size[1] + + if im_ratio != dest_ratio: + if im_ratio > dest_ratio: + new_height = round(image.height / image.width * size[0]) + if new_height != size[1]: + size = (size[0], new_height) + else: + new_width = round(image.width / image.height * size[1]) + if new_width != size[0]: + size = (new_width, size[1]) + + return size + def walkSort(dirnames, filenames): convert = lambda text: int(text) if text.isdigit() else text