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