Compare commits

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

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

Signed-off-by: dependabot[bot] <support@github.com>
2026-09-14 01:42:51 +00:00
Alex Xu cb7592dcd0 fix cropping with edge imperfections (#1435)
* improve detection of edge imperfections for cropping

* increase imperfections ratio

* fil in edge too

* fine tune the constants
2026-09-12 17:35:05 -07:00
Alex Xu ea532c709b Update README.md 2026-09-08 13:19:05 -07:00
Alex Xu 1ce92bac83 Update README.md 2026-09-08 11:53:00 -07:00
Alex Xu 5b67d4019e Update README.md 2026-09-06 22:25:38 -07:00
Alex Xu 2423e2e44a Add files via upload 2026-09-06 22:22:59 -07:00
Alex Xu eb694b2dc3 bump to 11.2.0 2026-09-06 22:14:10 -07:00
Alex Xu 04aac3ffc0 add simple mode (#1434)
* add expert mode

* add helper text
2026-09-06 22:13:48 -07:00
Alex Xu 0f630c5804 fix 7zip warning 2026-09-06 20:56:32 -07:00
Alex Xu d272073748 Update README.md 2026-09-06 20:50:29 -07:00
14 changed files with 1567 additions and 1416 deletions
+17 -11
View File
@@ -55,19 +55,23 @@ You can view the split halves either before or after combined spreads:
Just drop your input files into the KCC window, hit convert, and USB drop the output files onto your device's `documents` folder!
https://github.com/user-attachments/assets/da73d625-e082-482d-91a4-ae4765e96fd7
![settings](images/easy.png)
There are a lot of options, but most people only care about the ones checked above.
Hover over each option to see details in a tooltip.
NOTE: some checkboxes can have a half-check marks.
Then all your usb loaded comic files will show up next to all your normal books in your ereader library!
Here's a quick video demo of how the process works (using expert mode):
https://github.com/user-attachments/assets/da73d625-e082-482d-91a4-ae4765e96fd7
The final result looks incredible on my 10" Kindle Scribe: https://www.youtube.com/watch?v=2CIHW2N9Enc
**WARNING**: Kindle Scribe 2025 MOBI support may have blank pages. Use PDF instead.
The GUI looks like this, built in Qt6.
There are a lot of options, but most people only care about the ones checked below.
Hover over each option to see details in a tooltip.
![settings](images/settings.png)
**WARNING**: Kindle Scribe 2025 MOBI support is limited, converted books may have blank pages. Use PDF instead.
If you are on macOS and use a 2022+ Kindle, you may need to use Amazon USB File Manager for Mac or OpenMTP.
@@ -77,7 +81,7 @@ Installation tutorial: https://www.youtube.com/watch?v=IR2Fhcm9658
## Kindle Jailbreak for KOreader
Jailbreaking to use KOreader is optional. KCC works just fine with the native mobi manga viewer.
Jailbreaking to use KOreader is optional. MOBI manga converted by KCC can be opened in Kindles native reader just fine
But if you jailbreak your Kindle, you'll unlock additional features like:
@@ -86,6 +90,10 @@ But if you jailbreak your Kindle, you'll unlock additional features like:
2) no dependency on kindlegen, which can mean **twice as fast conversions** since you can convert to CBZ directly.
3) You can dither by partially checking the JPG/PNG box and can crop in KCC instead of KOreader. This may increase page turn speed and battery.
Common KOreader manga mistakes: https://youtu.be/gaakIh9p8tw?si=X7OgPJlAvL9XGbLR
Set per folder setting defaults: https://youtu.be/O9p6HVWIQ6s?si=Jtx607erCBA_auWM
### A word of warning
**KCC** _is not_ [Amazon's Kindle Comic Creator](http://www.amazon.com/gp/feature.html?ie=UTF8&docId=1001103761) nor is in any way endorsed by Amazon.
Amazon's tool is for comic publishers and involves a lot of manual effort, while **KCC** is for comic/manga readers.
@@ -175,8 +183,6 @@ You'll need to install various tools to access important but optional features.
### KindleGen
(This is not needed for Koreader users on jailbroken Kindle. Just set output to CBZ instead.)
On Windows and macOS, install [Kindle Previewer](https://www.amazon.com/Kindle-Previewer/b?ie=UTF8&node=21381691011) and `kindlegen` will be autodetected from it.
If you have issues detecting it, get stuck on the MOBI conversion step, or use Linux AppImage or Flatpak, refer to the wiki: https://github.com/ciromattia/kcc/wiki/Installation#kindlegen
+686 -670
View File
File diff suppressed because it is too large Load Diff
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 536 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 619 KiB

+102 -3
View File
@@ -155,17 +155,21 @@ class Icons:
class VersionThread(QThread):
def __init__(self):
def __init__(self, startNumber2):
QThread.__init__(self)
self.newVersion = ''
self.md5 = ''
self.barProgress = 0
self.answer = None
self.startNumber2 = startNumber2
def __del__(self):
self.wait()
def run(self):
if self.startNumber2 < 5:
return
try:
# unauthenticated API requests limit is 60 req/hour
if getattr(sys, 'frozen', False):
@@ -823,6 +827,91 @@ class KCCGUI(KCC_ui.Ui_mainWindow):
# noinspection PyCallByClass
QDesktopServices.openUrl(QUrl('https://humblebundleinc.sjv.io/3JaR3A'))
def toggleExpert(self):
# noinspection PyCallByClass
if self.expertMode:
self.hide_expert_options()
self.expertMode = False
else:
self.show_expert_options()
self.expertMode = True
def show_expert_options(self):
GUI.spreadShiftBox.show()
GUI.fileFusionBox.show()
GUI.jpegQualityBox.show()
GUI.pngLegacyBox.show()
GUI.webpBox.show()
GUI.lightnovelBox.show()
GUI.wallpaperBox.show()
GUI.outputSplit.show()
GUI.maximizeStrips.show()
GUI.noRotateBox.show()
GUI.rotateFirstBox.show()
GUI.coverFillBox.show()
GUI.rotateRightBox.show()
GUI.smartCoverCropBox.show()
GUI.onePageLandscapeBox.show()
GUI.qualityBox.show()
GUI.gammaBox.show()
GUI.interPanelCropBox.show()
GUI.autoLevelBox.show()
GUI.autocontrastBox.show()
GUI.vertical4PanelBox.show()
GUI.tempDirBox.show()
GUI.deleteBox.show()
GUI.metadataTitleBox.show()
GUI.keepComicInfoBox.show()
GUI.pdfWidthBox.show()
GUI.chunkSizeCheckBox.show()
GUI.disableProcessingBox.show()
GUI.noQuantizeBox.show()
GUI.forcePngRgbBox.show()
GUI.invertDirectionBox.show()
GUI.ebokBox.show()
GUI.languageEdit.show()
GUI.labelSpreadsButton.show()
GUI.editorButton.show()
GUI.easyLabel.hide()
def hide_expert_options(self):
GUI.spreadShiftBox.hide()
GUI.fileFusionBox.hide()
GUI.jpegQualityBox.hide()
GUI.pngLegacyBox.hide()
GUI.webpBox.hide()
GUI.lightnovelBox.hide()
GUI.wallpaperBox.hide()
GUI.outputSplit.hide()
GUI.maximizeStrips.hide()
GUI.noRotateBox.hide()
GUI.rotateFirstBox.hide()
GUI.coverFillBox.hide()
GUI.rotateRightBox.hide()
GUI.smartCoverCropBox.hide()
GUI.onePageLandscapeBox.hide()
GUI.qualityBox.hide()
GUI.gammaBox.hide()
GUI.interPanelCropBox.hide()
GUI.autoLevelBox.hide()
GUI.autocontrastBox.hide()
GUI.vertical4PanelBox.hide()
GUI.tempDirBox.hide()
GUI.deleteBox.hide()
GUI.metadataTitleBox.hide()
GUI.keepComicInfoBox.hide()
GUI.pdfWidthBox.hide()
GUI.chunkSizeCheckBox.hide()
GUI.disableProcessingBox.hide()
GUI.noQuantizeBox.hide()
GUI.forcePngRgbBox.hide()
GUI.invertDirectionBox.hide()
GUI.ebokBox.hide()
GUI.languageEdit.hide()
GUI.labelSpreadsButton.hide()
GUI.editorButton.hide()
GUI.easyLabel.show()
def openYouTube(self):
# noinspection PyCallByClass
QDesktopServices.openUrl(QUrl('https://www.youtube.com/@eink-dude'))
@@ -1188,11 +1277,13 @@ class KCCGUI(KCC_ui.Ui_mainWindow):
if not GUI.convertButton.isEnabled():
event.ignore()
self.settings.setValue('settingsVersion', __version__)
self.settings.setValue('expertMode', self.expertMode)
self.settings.setValue('lastPath', self.lastPath)
self.settings.setValue('defaultOutputFolder', self.defaultOutputFolder)
self.settings.setValue('lastDevice', GUI.deviceBox.currentIndex())
self.settings.setValue('currentFormat', GUI.formatBox.currentIndex())
self.settings.setValue('startNumber', self.startNumber + 1)
self.settings.setValue('startNumber2', self.startNumber2 + 1)
self.settings.setValue('windowSize', str(MW.size().width()) + 'x' + str(MW.size().height()))
self.settings.setValue('options', {'mangaBox': GUI.mangaBox.checkState(),
'lightnovelBox': GUI.lightnovelBox.checkState(),
@@ -1332,11 +1423,16 @@ class KCCGUI(KCC_ui.Ui_mainWindow):
if not os.path.exists(self.defaultOutputFolder):
self.defaultOutputFolder = ''
self.expertMode = self.settings.value('expertMode', False, type=bool)
if not self.expertMode:
GUI.hide_expert_options()
# default is Kindle Paperwhite 12th Gen
self.lastDevice = self.settings.value('lastDevice', 3, type=int)
self.currentFormat = self.settings.value('currentFormat', 0, type=int)
self.startNumber = self.settings.value('startNumber', 0, type=int)
self.startNumber2 = self.settings.value('startNumber2', 0, type=int)
self.windowSize = self.settings.value('windowSize', '0x0', type=str)
default_options = {'gammaSlider': 0, 'croppingBox': 2, 'croppingPowerSlider': 100, 'rotateBox': 1, 'mangaBox': 2}
try:
@@ -1344,7 +1440,7 @@ class KCCGUI(KCC_ui.Ui_mainWindow):
except Exception:
self.options = default_options
self.worker = WorkerThread()
self.versionCheck = VersionThread()
self.versionCheck = VersionThread(self.startNumber2)
self.progress = ProgressThread()
self.tray = SystemTrayIcon()
self.conversionAlive = False
@@ -1562,8 +1658,10 @@ class KCCGUI(KCC_ui.Ui_mainWindow):
statusBarLabel.setOpenExternalLinks(True)
GUI.statusBar.addPermanentWidget(statusBarLabel, 1)
self.addMessage('<b>Tip:</b> Hover mouse over options to see additional information in tooltips.', 'info')
self.addMessage('<b>Tip:</b> Hover mouse over options/buttons to see explanations. Boxes can be partially/fully checked.', 'info')
self.addMessage('<b>Tip:</b> You can drag and drop image folders or comic files/archives into this window to convert.', 'info')
self.addMessage("<b>Tip:</b> Calibre may add margins! USB drop directly into the device's documents folder instead.", 'info')
self.addMessage("<b>Tip:</b> You can toggle easy/expert mode using button at top right.", 'info')
if self.startNumber < 5:
self.addMessage('Since you are a new user of <b>KCC</b> please see few '
'<a href="https://github.com/ciromattia/kcc/wiki/Important-tips">important tips</a>.',
@@ -1584,6 +1682,7 @@ class KCCGUI(KCC_ui.Ui_mainWindow):
GUI.editorButton.clicked.connect(self.selectFileMetaEditor)
GUI.kofiButton.clicked.connect(self.openKofi)
GUI.humbleButton.clicked.connect(self.openHumble)
GUI.expertButton.clicked.connect(self.toggleExpert)
GUI.convertButton.clicked.connect(self.convertStart)
GUI.labelSpreadsButton.clicked.connect(self.labelSpreadsStart)
GUI.gammaSlider.valueChanged.connect(self.changeGamma)
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -1,4 +1,4 @@
__version__ = '11.1.0'
__version__ = '11.2.0'
__license__ = 'ISC'
__copyright__ = '2012-2022, Ciro Mattia Gonano <ciromattia@gmail.com>, Pawel Jastrzebski <pawelj@iosphe.re>, darodi'
__docformat__ = 'restructuredtext en'
+1 -1
View File
@@ -99,7 +99,7 @@ class ComicArchive:
pass
if missing:
raise OSError(f'Extraction failed, install <a href="https://github.com/ciromattia/kcc#7-zip">specialized extraction software.</a> ')
raise UserWarning(f'Extraction failed, install <a href="https://github.com/ciromattia/kcc#7-zip">specialized extraction software.</a> ')
else:
raise OSError(EXTRACTION_ERROR)
+27 -9
View File
@@ -1,4 +1,5 @@
from PIL import ImageOps, ImageFilter, ImageFile
from PIL.Image import Image
import numpy as np
from .common_crop import threshold_from_power, group_close_values
@@ -149,22 +150,39 @@ def get_bbox_crop_margin(img, power=1, background_color='white'):
return bw_img.getbbox()
def ignore_pixels_near_edge(bw_img):
def ignore_pixels_near_edge(bw_img: Image):
w, h = bw_img.size
# going 0.02-0.03 was too much
if int(0.02 * h) == int(0.025 * h):
return
if int(0.02 * w) == int(0.025 * w):
return
edge_bbox = [
(0, 0, w, int(0.02 * h)),
(0, int(0.98 * h), w, h),
(0, 0, int(0.02 * w), h),
(int(0.98 * w), 0, w, h)
]
for box in edge_bbox:
edge = bw_img.crop(box)
h = edge.histogram()
if not edge.height or not edge.width:
continue
imperfections = h[255] / (edge.height * edge.width)
if imperfections > 0 and imperfections < .02:
bw_img.paste(im=0, box=box)
inner_bbox = [
(int(0.02 * w), int(0.02 * h), int(0.98 * w), int(0.025 * h)), # top
(int(0.02 * w), int(0.975 * h), int(0.98 * w), int(0.98 * h)), # lower
(int(0.02 * w), int(0.02 * h), int(0.025 * w), int(0.98 * h)), # left
(int(0.975 * w), int(0.02 * h), int(0.98 * w), int(0.98 * h)), # right
]
for edge_box, inner_box in zip(edge_bbox, inner_bbox):
inner_edge = bw_img.crop(inner_box)
h = inner_edge.histogram()
imperfections = h[255] / (inner_edge.height * inner_edge.width)
if imperfections > 0 and imperfections < .001:
bw_img.paste(im=0, box=inner_box)
if imperfections < .001:
edge = bw_img.crop(edge_box)
edge_h = edge.histogram()
if edge_h[-1] != 0:
bw_img.paste(im=0, box=edge_box)
def box_intersect(box1, box2, max_dist):
+1 -1
View File
@@ -1,7 +1,7 @@
Pillow>=11.3.0
psutil>=7.2.2
requests>=2.34.2
python-slugify>=8.0.4
python-slugify>=9.0.0
packaging>=26.2
mozjpeg-lossless-optimization>=1.2.0
natsort>=8.4.0
+1 -1
View File
@@ -2,7 +2,7 @@ PySide6==6.4.3
Pillow>=11.3.0
psutil>=7.2.2
requests>=2.34.2
python-slugify>=8.0.4
python-slugify>=9.0.0
packaging>=26.2
mozjpeg-lossless-optimization>=1.2.0
natsort>=8.4.0
+1 -1
View File
@@ -2,7 +2,7 @@ PySide6==6.1.3
Pillow>=9
psutil>=7.2.2
requests>=2.32.4
python-slugify>=8.0.4
python-slugify>=9.0.0
packaging>=26.2
mozjpeg-lossless-optimization>=1.2.0
natsort>=8.4.0
+1 -1
View File
@@ -2,7 +2,7 @@ PySide6<6.10
Pillow>=11.3.0
psutil>=7.2.2
requests>=2.34.2
python-slugify>=8.0.4,<9.0.0
python-slugify>=9.0.0,<10.0.0
packaging>=26.2
mozjpeg-lossless-optimization>=1.2.0
natsort>=8.4.0
+1 -1
View File
@@ -152,7 +152,7 @@ setuptools.setup(
'Pillow>=9.3.0',
'psutil>=5.9.5',
'requests>=2.31.0',
'python-slugify>=1.2.1,<9.0.0',
'python-slugify>=1.2.1,<10.0.0',
'mozjpeg-lossless-optimization>=1.2.0',
'natsort>=8.4.0',
'numpy>=1.22.4',