1
0
mirror of https://github.com/ciromattia/kcc synced 2026-04-27 03:19:06 +00:00

Compare commits

...

6 Commits

Author SHA1 Message Date
dependabot[bot]
48e4c50c70 Update python-slugify requirement from <9.0.0,>=1.2.1 to >=8.0.4,<9.0.0
Updates the requirements on [python-slugify](https://github.com/un33k/python-slugify) to permit the latest version.
- [Changelog](https://github.com/un33k/python-slugify/blob/master/CHANGELOG.md)
- [Commits](https://github.com/un33k/python-slugify/compare/1.2.1...v8.0.4)

---
updated-dependencies:
- dependency-name: python-slugify
  dependency-version: 8.0.4
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-04-27 01:43:24 +00:00
Alex Xu
a3672f7a1e bump to 10.1.0 2026-04-26 14:54:09 -07:00
Alex Xu
1b48a9fc5e Replace KFX (does not work) with KFX (Send to Kindle EPUB) (#1309)
* add better kfx output

* fix kfx res

* fix bug

* fix bug

* no errors

* kfx defaults to PNG

* KFX 200 MB default

* clarify send to kindle

* fit close images

* adjust

* refactor contain

* initial fixes
2026-04-26 14:53:28 -07:00
Alex Xu
d5dde46989 CBZ defaults to partially checked w/b borders (#1310) 2026-04-24 16:40:44 -07:00
Alex Xu
92c85c18e9 rename autocontrast box to custom autocontrast (#1308) 2026-04-24 12:11:38 -07:00
Alex Xu
e5122cc188 prevent creating non Kindle MOBI (#1307) 2026-04-24 07:44:56 -07:00
11 changed files with 105 additions and 18 deletions

View File

@@ -866,7 +866,7 @@ Useful if you plan to crop a little off the top and bottom to fill screen.</stri
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-weight:600; text-decoration: underline;&quot;&gt;Unchecked - BW only&lt;br/&gt;&lt;/span&gt;Only autocontrast bw pages. Ignored for pages where near blacks or whites don't exist.&lt;/p&gt;&lt;p&gt;&lt;span style=&quot; font-weight:600; text-decoration: underline;&quot;&gt;Indeterminate - Disabled&lt;br/&gt;&lt;/span&gt;Disable autocontrast&lt;/p&gt;&lt;p&gt;&lt;span style=&quot; font-weight:600; text-decoration: underline;&quot;&gt;Checked - BW and Color&lt;br/&gt;&lt;/span&gt;BW and color images will be autocontrasted. Ignored for pages where near blacks or whites don't exist.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="text">
<string>Autocontrast</string>
<string>Custom Autocontrast</string>
</property>
<property name="tristate">
<bool>true</bool>

View File

@@ -946,10 +946,15 @@ 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():
self.addMessage("Partially check W/B Margins if you don't want KCC to extend the image margins.", 'info')
GUI.borderBox.setCheckState(Qt.CheckState.PartiallyChecked)
else:
GUI.borderBox.setCheckState(Qt.CheckState.Unchecked)
def stripTags(self, html):
s = HTMLStripper()
@@ -1231,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'},

View File

@@ -819,7 +819,7 @@ class Ui_mainWindow(object):
#if QT_CONFIG(tooltip)
self.autocontrastBox.setToolTip(QCoreApplication.translate("mainWindow", u"<html><head/><body><p><span style=\" font-weight:600; text-decoration: underline;\">Unchecked - BW only<br/></span>Only autocontrast bw pages. Ignored for pages where near blacks or whites don't exist.</p><p><span style=\" font-weight:600; text-decoration: underline;\">Indeterminate - Disabled<br/></span>Disable autocontrast</p><p><span style=\" font-weight:600; text-decoration: underline;\">Checked - BW and Color<br/></span>BW and color images will be autocontrasted. Ignored for pages where near blacks or whites don't exist.</p></body></html>", None))
#endif // QT_CONFIG(tooltip)
self.autocontrastBox.setText(QCoreApplication.translate("mainWindow", u"Autocontrast", None))
self.autocontrastBox.setText(QCoreApplication.translate("mainWindow", u"Custom Autocontrast", None))
#if QT_CONFIG(tooltip)
self.webpBox.setToolTip(QCoreApplication.translate("mainWindow", u"Replace JPG with lossy WebP and PNG with lossless WebP. This includes the JPG Quality.\n"
"\n"

View File

@@ -1,4 +1,4 @@
__version__ = '10.0.1'
__version__ = '10.1.0'
__license__ = 'ISC'
__copyright__ = '2012-2022, Ciro Mattia Gonano <ciromattia@gmail.com>, Pawel Jastrzebski <pawelj@iosphe.re>, darodi'
__docformat__ = 'restructuredtext en'

View File

@@ -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("<meta name=\"cover\" content=\"cover\"/>\n")
if options.iskindle and options.profile != 'Custom':
f.writelines(["<meta name=\"fixed-layout\" content=\"true\"/>\n",
"<meta name=\"original-resolution\" content=\"",
str(deviceres[0]) + "x" + str(deviceres[1]) + "\"/>\n",
])
if not options.kfx_resolution:
f.writelines([
"<meta name=\"original-resolution\" content=\"",
str(deviceres[0]) + "x" + str(deviceres[1]) + "\"/>\n",
])
else:
x, y = options.kfx_resolution
f.writelines([
"<meta name=\"original-resolution\" content=\"",
str(x) + "x" + str(y) + "\"/>\n",
])
f.writelines([
"<meta name=\"book-type\" content=\"comic\"/>\n",
"<meta name=\"primary-writing-mode\" content=\"" + writingmode + "\"/>\n",
"<meta name=\"zero-gutter\" content=\"true\"/>\n",
@@ -1470,6 +1482,15 @@ def checkOptions(options):
options.isKobo = False
options.bordersColor = None
options.keep_epub = False
if options.profile in image.ProfileData.ProfilesKindle.keys():
options.iskindle = True
else:
options.isKobo = True
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
options.format = 'PDF'
@@ -1501,10 +1522,7 @@ def checkOptions(options):
options.format = 'PDF'
else:
options.format = 'EPUB'
if options.profile in image.ProfileData.ProfilesKindle.keys():
options.iskindle = True
else:
options.isKobo = True
if options.white_borders:
options.bordersColor = 'white'
if options.black_borders:
@@ -1535,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
@@ -1556,6 +1575,7 @@ def checkOptions(options):
options.jpegquality = 90
else:
options.jpegquality = 85
options.kindle_azw3 = options.iskindle and ('MOBI' in options.format or 'EPUB' in options.format)
options.kindle_scribe_azw3 = options.profile.startswith('KS') and options.kindle_azw3
@@ -1671,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:

View File

@@ -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)

View File

@@ -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

View File

@@ -1,7 +1,7 @@
Pillow>=11.3.0
psutil>=5.9.5
requests>=2.31.0
python-slugify>=1.2.1
python-slugify>=8.0.4
packaging>=23.2
mozjpeg-lossless-optimization>=1.2.0
natsort>=8.4.0

View File

@@ -2,7 +2,7 @@ PySide6==6.4.3
Pillow>=11.3.0
psutil>=5.9.5
requests>=2.31.0
python-slugify>=1.2.1
python-slugify>=8.0.4
raven>=6.0.0
packaging>=23.2
mozjpeg-lossless-optimization>=1.2.0

View File

@@ -2,7 +2,7 @@ PySide6==6.1.3
Pillow>=9
psutil>=5.9.5
requests>=2.31.0
python-slugify>=1.2.1
python-slugify>=8.0.4
raven>=6.0.0
packaging>=23.2
mozjpeg-lossless-optimization>=1.2.0

View File

@@ -2,7 +2,7 @@ PySide6<6.10
Pillow>=11.3.0
psutil>=5.9.5
requests>=2.31.0
python-slugify>=1.2.1,<9.0.0
python-slugify>=8.0.4,<9.0.0
raven>=6.0.0
packaging>=23.2
mozjpeg-lossless-optimization>=1.2.0