From 8c57fbf318b27863dd7b689c7e2cca5da1235d41 Mon Sep 17 00:00:00 2001 From: Alex Xu Date: Sun, 14 Dec 2025 18:59:28 -0800 Subject: [PATCH 001/107] fix add folder button sizePolicy --- gui/KCC.ui | 8 +++++++- kindlecomicconverter/KCC_ui.py | 21 +++++++++++++-------- 2 files changed, 20 insertions(+), 9 deletions(-) diff --git a/gui/KCC.ui b/gui/KCC.ui index 3bc01a5..205a76b 100644 --- a/gui/KCC.ui +++ b/gui/KCC.ui @@ -7,7 +7,7 @@ 0 0 566 - 573 + 581 @@ -802,6 +802,12 @@ + + + 0 + 0 + + <html><head/><body><p style='white-space:pre'>Add directory containing JPG, PNG or GIF files to queue.<br/><span style=" font-weight:600;">CBR, CBZ and CB7 files inside will not be processed!</span></p></body></html> diff --git a/kindlecomicconverter/KCC_ui.py b/kindlecomicconverter/KCC_ui.py index 36f09d6..9cdd2d6 100644 --- a/kindlecomicconverter/KCC_ui.py +++ b/kindlecomicconverter/KCC_ui.py @@ -26,7 +26,7 @@ class Ui_mainWindow(object): def setupUi(self, mainWindow): if not mainWindow.objectName(): mainWindow.setObjectName(u"mainWindow") - mainWindow.resize(566, 573) + mainWindow.resize(566, 581) icon = QIcon() icon.addFile(u":/Icon/icons/comic2ebook.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off) mainWindow.setWindowIcon(icon) @@ -420,6 +420,11 @@ class Ui_mainWindow(object): self.directoryButton = QPushButton(self.buttonWidget) self.directoryButton.setObjectName(u"directoryButton") + sizePolicy4 = QSizePolicy(QSizePolicy.Policy.Minimum, QSizePolicy.Policy.Minimum) + sizePolicy4.setHorizontalStretch(0) + sizePolicy4.setVerticalStretch(0) + sizePolicy4.setHeightForWidth(self.directoryButton.sizePolicy().hasHeightForWidth()) + self.directoryButton.setSizePolicy(sizePolicy4) self.directoryButton.setIcon(icon1) self.gridLayout_4.addWidget(self.directoryButton, 0, 4, 1, 1) @@ -450,11 +455,11 @@ class Ui_mainWindow(object): self.horizontalLayout_4.setContentsMargins(0, 0, 0, 0) self.chunkSizeLabel = QLabel(self.chunkSizeWidget) self.chunkSizeLabel.setObjectName(u"chunkSizeLabel") - sizePolicy4 = QSizePolicy(QSizePolicy.Policy.Minimum, QSizePolicy.Policy.Preferred) - sizePolicy4.setHorizontalStretch(0) - sizePolicy4.setVerticalStretch(0) - sizePolicy4.setHeightForWidth(self.chunkSizeLabel.sizePolicy().hasHeightForWidth()) - self.chunkSizeLabel.setSizePolicy(sizePolicy4) + sizePolicy5 = QSizePolicy(QSizePolicy.Policy.Minimum, QSizePolicy.Policy.Preferred) + sizePolicy5.setHorizontalStretch(0) + sizePolicy5.setVerticalStretch(0) + sizePolicy5.setHeightForWidth(self.chunkSizeLabel.sizePolicy().hasHeightForWidth()) + self.chunkSizeLabel.setSizePolicy(sizePolicy5) self.horizontalLayout_4.addWidget(self.chunkSizeLabel) @@ -468,8 +473,8 @@ class Ui_mainWindow(object): self.chunkSizeWarnLabel = QLabel(self.chunkSizeWidget) self.chunkSizeWarnLabel.setObjectName(u"chunkSizeWarnLabel") - sizePolicy4.setHeightForWidth(self.chunkSizeWarnLabel.sizePolicy().hasHeightForWidth()) - self.chunkSizeWarnLabel.setSizePolicy(sizePolicy4) + sizePolicy5.setHeightForWidth(self.chunkSizeWarnLabel.sizePolicy().hasHeightForWidth()) + self.chunkSizeWarnLabel.setSizePolicy(sizePolicy5) self.horizontalLayout_4.addWidget(self.chunkSizeWarnLabel) From 7228055bca2c0bfc00c2a923ed25124cb366b7d1 Mon Sep 17 00:00:00 2001 From: Alex Xu Date: Sun, 14 Dec 2025 23:44:30 -0800 Subject: [PATCH 002/107] reduce file operations in webtoon and file fusion (#1191) * reduce file operations in webtoon * reduce file operations of file fusion * fix file fusion failed to prepare * close webtoon image before remove * use temp directory --- kindlecomicconverter/comic2ebook.py | 28 ++++++++++++---------------- kindlecomicconverter/comic2panel.py | 21 +++++++++------------ 2 files changed, 21 insertions(+), 28 deletions(-) diff --git a/kindlecomicconverter/comic2ebook.py b/kindlecomicconverter/comic2ebook.py index 8cb784f..357b67e 100755 --- a/kindlecomicconverter/comic2ebook.py +++ b/kindlecomicconverter/comic2ebook.py @@ -851,14 +851,16 @@ def mupdf_pdf_process_pages_parallel(filename, output_dir, target_height): -def getWorkFolder(afile): +def getWorkFolder(afile, workdir=None): + if not workdir: + workdir = mkdtemp('', 'KCC-') + fullPath = os.path.join(workdir, 'OEBPS', 'Images') + else: + fullPath = workdir if os.path.isdir(afile): if disk_usage(gettempdir())[2] < getDirectorySize(afile) * 2.5: raise UserWarning("Not enough disk space to perform conversion.") - workdir = mkdtemp('', 'KCC-', os.path.dirname(afile)) try: - os.rmdir(workdir) - fullPath = os.path.join(workdir, 'OEBPS', 'Images') copytree(afile, fullPath) sanitizePermissions(fullPath) return workdir @@ -869,8 +871,6 @@ def getWorkFolder(afile): if disk_usage(gettempdir())[2] < os.path.getsize(afile) * 2.5: raise UserWarning("Not enough disk space to perform conversion.") if afile.lower().endswith('.pdf'): - workdir = mkdtemp('', 'KCC-', os.path.dirname(afile)) - fullPath = os.path.join(workdir, 'OEBPS', 'Images') if not os.path.exists(fullPath): os.makedirs(fullPath) path = workdir @@ -887,8 +887,6 @@ def getWorkFolder(afile): raise UserWarning(f"Failed to extract images from PDF file. {e}") return workdir else: - workdir = mkdtemp('', 'KCC-', os.path.dirname(afile)) - fullPath = os.path.join(workdir, 'OEBPS', 'Images') if not os.path.exists(fullPath): os.makedirs(fullPath) try: @@ -1532,17 +1530,15 @@ def makeFusion(sources: List[str]): print(f"Processing {source}...") checkPre(source) print("Checking images...") - path = getWorkFolder(source) - pathfinder = os.path.join(path, "OEBPS", "Images") - sanitizeTree(pathfinder) - # TODO: remove flattenTree when subchapters are supported - flattenTree(pathfinder) source_path = Path(source) if source_path.is_file(): - os.renames(pathfinder, fusion_path.joinpath(source_path.stem)) + targetpath = fusion_path.joinpath(source_path.stem) else: - os.renames(pathfinder, fusion_path.joinpath(source_path.name)) - + targetpath = fusion_path.joinpath(source_path.name) + getWorkFolder(source, str(targetpath)) + sanitizeTree(targetpath) + # TODO: remove flattenTree when subchapters are supported + flattenTree(targetpath) end = perf_counter() print(f"makefusion: {end - start} seconds") diff --git a/kindlecomicconverter/comic2panel.py b/kindlecomicconverter/comic2panel.py index ccdbb06..f45cc49 100644 --- a/kindlecomicconverter/comic2panel.py +++ b/kindlecomicconverter/comic2panel.py @@ -67,13 +67,14 @@ def mergeDirectory(work): result = Image.new('RGB', (targetWidth, targetHeight)) y = 0 for i in imagesValid: - img = Image.open(i).convert('RGB') - if img.size[0] < targetWidth or img.size[0] > targetWidth: - widthPercent = (targetWidth / float(img.size[0])) - heightSize = int((float(img.size[1]) * float(widthPercent))) - img = ImageOps.fit(img, (targetWidth, heightSize), method=Image.BICUBIC, centering=(0.5, 0.5)) - result.paste(img, (0, y)) - y += img.size[1] + with Image.open(i) as img: + img = img.convert('RGB') + if img.size[0] < targetWidth or img.size[0] > targetWidth: + widthPercent = (targetWidth / float(img.size[0])) + heightSize = int((float(img.size[1]) * float(widthPercent))) + img = ImageOps.fit(img, (targetWidth, heightSize), method=Image.BICUBIC, centering=(0.5, 0.5)) + result.paste(img, (0, y)) + y += img.size[1] os.remove(i) savePath = os.path.split(imagesValid[0]) result.save(os.path.join(savePath[0], os.path.splitext(savePath[1])[0] + '.png'), 'PNG') @@ -253,10 +254,8 @@ def main(argv=None, job_progress='', qtgui=None): return 1 if args.height > 0: for sourceDir in args.input: - targetDir = sourceDir + "-Splitted" + targetDir = sourceDir if os.path.isdir(sourceDir): - rmtree(targetDir, True) - os.renames(sourceDir, targetDir) work = [] pagenumber = 1 splitWorkerOutput = [] @@ -313,8 +312,6 @@ def main(argv=None, job_progress='', qtgui=None): rmtree(targetDir, True) raise RuntimeError("One of workers crashed. Cause: " + splitWorkerOutput[0][0], splitWorkerOutput[0][1]) - if args.inPlace: - os.renames(targetDir, sourceDir) else: rmtree(targetDir, True) raise UserWarning("C2P: Source directory is empty.") From b767d5dc2cdf442ded8e232ba561c8e3183908af Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 14 Dec 2025 17:05:33 -0800 Subject: [PATCH 003/107] Bump actions/upload-artifact from 5 to 6 (#1196) Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 5 to 6. - [Release notes](https://github.com/actions/upload-artifact/releases) - [Commits](https://github.com/actions/upload-artifact/compare/v5...v6) --- updated-dependencies: - dependency-name: actions/upload-artifact dependency-version: '6' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/package-linux.yml | 2 +- .github/workflows/package-macos.yml | 2 +- .github/workflows/package-osx-legacy.yml | 2 +- .github/workflows/package-windows.yml | 2 +- .github/workflows/package-windows7.yml | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/package-linux.yml b/.github/workflows/package-linux.yml index afa1c42..72b95a4 100644 --- a/.github/workflows/package-linux.yml +++ b/.github/workflows/package-linux.yml @@ -59,7 +59,7 @@ jobs: env: UPDATE_INFO: gh-releases-zsync|ciromattia|kcc|latest|*x86_64.AppImage.zsync - name: upload artifact - uses: actions/upload-artifact@v5 + uses: actions/upload-artifact@v6 with: name: AppImage path: './*.AppImage*' diff --git a/.github/workflows/package-macos.yml b/.github/workflows/package-macos.yml index a132ce0..66187a6 100644 --- a/.github/workflows/package-macos.yml +++ b/.github/workflows/package-macos.yml @@ -80,7 +80,7 @@ jobs: run: | python setup.py build_binary - name: upload build - uses: actions/upload-artifact@v5 + uses: actions/upload-artifact@v6 with: name: mac-os-build-${{ runner.arch }} path: dist/*.dmg diff --git a/.github/workflows/package-osx-legacy.yml b/.github/workflows/package-osx-legacy.yml index 2422229..086e7f4 100644 --- a/.github/workflows/package-osx-legacy.yml +++ b/.github/workflows/package-osx-legacy.yml @@ -51,7 +51,7 @@ jobs: run: | python3 setup.py build_binary - name: upload build - uses: actions/upload-artifact@v5 + uses: actions/upload-artifact@v6 with: name: osx-build-${{ runner.arch }} path: dist/*.dmg diff --git a/.github/workflows/package-windows.yml b/.github/workflows/package-windows.yml index 1d4bf92..34148a9 100644 --- a/.github/workflows/package-windows.yml +++ b/.github/workflows/package-windows.yml @@ -53,7 +53,7 @@ jobs: python setup.py ${{ matrix.command }} - name: upload-unsigned-artifact id: upload-unsigned-artifact - uses: actions/upload-artifact@v5 + uses: actions/upload-artifact@v6 with: name: windows-build-${{ matrix.entry }} path: dist/*.exe diff --git a/.github/workflows/package-windows7.yml b/.github/workflows/package-windows7.yml index cfa2df0..9ea0782 100644 --- a/.github/workflows/package-windows7.yml +++ b/.github/workflows/package-windows7.yml @@ -46,7 +46,7 @@ jobs: python setup.py build_binary - name: upload-unsigned-artifact id: upload-unsigned-artifact - uses: actions/upload-artifact@v5 + uses: actions/upload-artifact@v6 with: name: windows7-build path: dist/*.exe From 8e5d57364d70239c0d259e1d14431c35f08139f1 Mon Sep 17 00:00:00 2001 From: jaroslawjanas Date: Mon, 15 Dec 2025 03:55:02 +0100 Subject: [PATCH 004/107] Remove environment.yml --- environment.yml | 16 ---------------- 1 file changed, 16 deletions(-) delete mode 100644 environment.yml diff --git a/environment.yml b/environment.yml deleted file mode 100644 index 0a9ba52..0000000 --- a/environment.yml +++ /dev/null @@ -1,16 +0,0 @@ -name: kcc -channels: - - conda-forge - - defaults -dependencies: - - python=3.11 - - Pillow>=11.3.0 - - psutil>=5.9.5 - - python-slugify>=1.2.1 - - raven>=6.0.0 - - distro - - natsort>=8.4.0 - - pip - - pip: - - mozjpeg-lossless-optimization>=1.1.2 - - pyside6>=6.5.1 From f088ad732ed1a67e625f42e42370881a49f167a8 Mon Sep 17 00:00:00 2001 From: Alex Xu Date: Tue, 23 Dec 2025 13:19:40 -0800 Subject: [PATCH 005/107] default MACOSX_DEPLOYMENT_TARGET --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index bd17012..f791414 100644 --- a/setup.py +++ b/setup.py @@ -40,7 +40,7 @@ class BuildBinaryCommand(setuptools.Command): if sys.platform == 'darwin': os.system('pyinstaller --hidden-import=_cffi_backend -y -D -i icons/comic2ebook.icns -n "Kindle Comic Converter" -w -s kcc.py') # TODO /usr/bin/codesign --force -s "$MACOS_CERTIFICATE_NAME" --options runtime dist/Applications/Kindle\ Comic\ Converter.app -v - min_os = os.getenv('MACOSX_DEPLOYMENT_TARGET') + min_os = os.getenv('MACOSX_DEPLOYMENT_TARGET', '') if min_os.startswith('10.1'): os.system(f'appdmg kcc.json dist/kcc_osx_{min_os.replace(".", "_")}_legacy_{VERSION}.dmg') else: From f74e108a3e2df3b33f9b0d04edaf31625573e686 Mon Sep 17 00:00:00 2001 From: tokyis Date: Sat, 27 Dec 2025 21:19:20 +0100 Subject: [PATCH 006/107] Fixed resizing bug caused by misplaced closing parenthesis. --- kindlecomicconverter/image.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kindlecomicconverter/image.py b/kindlecomicconverter/image.py index fc7e07b..582b4b7 100755 --- a/kindlecomicconverter/image.py +++ b/kindlecomicconverter/image.py @@ -485,7 +485,7 @@ class ComicPage: if self.opt.kindle_azw3 and any(dim > 1920 for dim in self.image.size): self.image = ImageOps.contain(self.image, (1920, 1920), Image.Resampling.LANCZOS) elif self.image.size[0] > self.size[0] * 2 or self.image.size[1] > self.size[1]: - self.image = ImageOps.contain(self.image, (self.size[0] * 2, self.size[1], Image.Resampling.LANCZOS)) + self.image = ImageOps.contain(self.image, (self.size[0] * 2, self.size[1]), Image.Resampling.LANCZOS) return ratio_device = float(self.size[1]) / float(self.size[0]) From 9b63b7af2cb181907e70cbaa861dea368b64b21e Mon Sep 17 00:00:00 2001 From: Alex Xu Date: Sat, 27 Dec 2025 12:23:39 -0800 Subject: [PATCH 007/107] Bump version to 9.3.8 --- kindlecomicconverter/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kindlecomicconverter/__init__.py b/kindlecomicconverter/__init__.py index 749d3e7..1431e2f 100644 --- a/kindlecomicconverter/__init__.py +++ b/kindlecomicconverter/__init__.py @@ -1,4 +1,4 @@ -__version__ = '9.3.7' +__version__ = '9.3.8' __license__ = 'ISC' __copyright__ = '2012-2022, Ciro Mattia Gonano , Pawel Jastrzebski , darodi' __docformat__ = 'restructuredtext en' From c58387f4f47f1b2c82a82095e0740d749223ea81 Mon Sep 17 00:00:00 2001 From: Alex Xu Date: Sat, 27 Dec 2025 17:18:05 -0800 Subject: [PATCH 008/107] partial support for Kindle Scribe 2025 models (#1203) * partial Kindle Scribe 2025 support * make variables better * remove quad --- kindlecomicconverter/KCC_gui.py | 18 +++++++++++++----- kindlecomicconverter/comic2ebook.py | 4 ++-- kindlecomicconverter/image.py | 5 ++++- 3 files changed, 19 insertions(+), 8 deletions(-) diff --git a/kindlecomicconverter/KCC_gui.py b/kindlecomicconverter/KCC_gui.py index f86b02d..186ed93 100644 --- a/kindlecomicconverter/KCC_gui.py +++ b/kindlecomicconverter/KCC_gui.py @@ -772,7 +772,7 @@ class KCCGUI(KCC_ui.Ui_mainWindow): GUI.rotateBox.setEnabled(True) GUI.borderBox.setEnabled(True) profile = GUI.profiles[str(GUI.deviceBox.currentText())] - if profile['Label'] != 'KS': + if not profile['Label'].startswith('KS'): GUI.upscaleBox.setEnabled(True) GUI.croppingBox.setEnabled(True) GUI.interPanelCropBox.setEnabled(True) @@ -797,7 +797,7 @@ class KCCGUI(KCC_ui.Ui_mainWindow): def toggleImageFormatBox(self, value): profile = GUI.profiles[str(GUI.deviceBox.currentText())] if value == 1: - if profile['Label'] == 'KS': + if profile['Label'].startswith('KS'): current_format = GUI.formats[str(GUI.formatBox.currentText())]['format'] for bad_format in ('MOBI', 'EPUB'): if bad_format in current_format: @@ -859,7 +859,7 @@ class KCCGUI(KCC_ui.Ui_mainWindow): if not GUI.webtoonBox.isChecked(): GUI.qualityBox.setEnabled(profile['PVOptions']) GUI.upscaleBox.setChecked(profile['DefaultUpscale']) - if profile['Label'] == 'KS': + if profile['Label'].startswith('KS'): GUI.upscaleBox.setDisabled(True) else: if not GUI.webtoonBox.isChecked(): @@ -1180,9 +1180,15 @@ class KCCGUI(KCC_ui.Ui_mainWindow): 'DefaultUpscale': True, 'ForceColor': False, 'Label': 'KPW34'}, "Kindle Voyage": {'PVOptions': True, 'ForceExpert': False, 'DefaultFormat': 0, 'DefaultUpscale': True, 'ForceColor': False, 'Label': 'KV'}, - "Kindle Scribe": { + "Kindle Scribe 1/2": { 'PVOptions': True, 'ForceExpert': False, 'DefaultFormat': 0, 'DefaultUpscale': False, 'ForceColor': False, 'Label': 'KS', }, + "Kindle Scribe 3": { + 'PVOptions': True, 'ForceExpert': False, 'DefaultFormat': 0, 'DefaultUpscale': False, 'ForceColor': False, 'Label': 'KS3', + }, + "Kindle Scribe Colorsoft": { + 'PVOptions': True, 'ForceExpert': False, 'DefaultFormat': 0, 'DefaultUpscale': False, 'ForceColor': True, 'Label': 'KSCS', + }, "Kindle 11": { 'PVOptions': True, 'ForceExpert': False, 'DefaultFormat': 0, 'DefaultUpscale': True, 'ForceColor': False, 'Label': 'K11', }, @@ -1257,9 +1263,11 @@ class KCCGUI(KCC_ui.Ui_mainWindow): 'Label': 'OTHER'}, } profilesGUI = [ + "Kindle Scribe Colorsoft", + "Kindle Scribe 3", "Kindle Colorsoft", "Kindle Paperwhite 12", - "Kindle Scribe", + "Kindle Scribe 1/2", "Kindle Paperwhite 11", "Kindle 11", "Kindle Oasis 9/10", diff --git a/kindlecomicconverter/comic2ebook.py b/kindlecomicconverter/comic2ebook.py index 357b67e..1f1f35a 100755 --- a/kindlecomicconverter/comic2ebook.py +++ b/kindlecomicconverter/comic2ebook.py @@ -1237,7 +1237,7 @@ def detectSuboptimalProcessing(tmppath, orgpath): GUI.addMessage.emit('Source files are probably created by KCC. The second conversion will decrease quality.' , 'warning', False) GUI.addMessage.emit('', '', False) - if imageSmaller > imageNumber * 0.25 and not options.upscale and not options.stretch and options.profile != 'KS': + if imageSmaller > imageNumber * 0.25 and not options.upscale and not options.stretch and not options.profile.startswith('KS'): print("WARNING: More than 25% of images are smaller than target device resolution. " "Consider enabling stretching or upscaling to improve readability.") if GUI: @@ -1556,7 +1556,7 @@ def makeBook(source, qtgui=None, job_progress=''): else: checkTools(source) options.kindle_azw3 = options.iskindle and ('MOBI' in options.format or 'EPUB' in options.format) - options.kindle_scribe_azw3 = options.profile == 'KS' and ('MOBI' in options.format or 'EPUB' in options.format) + options.kindle_scribe_azw3 = options.profile.startswith('KS') and options.kindle_azw3 checkPre(source) print(f"{job_progress}Preparing source images...") path = getWorkFolder(source) diff --git a/kindlecomicconverter/image.py b/kindlecomicconverter/image.py index 582b4b7..c4e5ed2 100755 --- a/kindlecomicconverter/image.py +++ b/kindlecomicconverter/image.py @@ -101,8 +101,10 @@ class ProfileData: 'KO': ("Kindle Oasis 2/3/Paperwhite 12", (1264, 1680), Palette16, 1.0), 'K11': ("Kindle 11", (1072, 1448), Palette16, 1.0), 'KPW5': ("Kindle Paperwhite 5/Signature Edition", (1236, 1648), Palette16, 1.0), - 'KS': ("Kindle Scribe", (1860, 2480), Palette16, 1.0), + 'KS': ("Kindle Scribe 1/2", (1860, 2480), Palette16, 1.0), 'KCS': ("Kindle Colorsoft", (1264, 1680), Palette16, 1.0), + 'KS3': ("Kindle Scribe 3", (1920, 2640), Palette16, 1.0), + 'KSCS': ("Kindle Scribe Colorsoft", (1920, 2640), Palette16, 1.0), } ProfilesKindle = { @@ -562,6 +564,7 @@ class Cover: size = list(self.options.profileData[1]) if self.options.kindle_scribe_azw3: + size[0] = min(size[0], 1920) size[1] = min(size[1], 1920) self.image.thumbnail(tuple(size), Image.Resampling.LANCZOS) From e14abe1787a462cfcef3622e61ae5eb4d81ea344 Mon Sep 17 00:00:00 2001 From: Alex Xu Date: Sun, 28 Dec 2025 14:54:12 -0800 Subject: [PATCH 009/107] default jpg quality of 90 for scribe colorsoft (#1204) --- README.md | 1 + kindlecomicconverter/comic2ebook.py | 7 +++++++ kindlecomicconverter/image.py | 10 +++++----- 3 files changed, 13 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 0055e6c..8e90038 100644 --- a/README.md +++ b/README.md @@ -255,6 +255,7 @@ PROCESSING: --forcecolor Don't convert images to grayscale --forcepng Create PNG files instead JPEG --mozjpeg Create JPEG files using mozJpeg + --jpeg-quality The JPEG quality, on a scale from 0 (worst) to 95 (best). Default 85 for most devices. --maximizestrips Turn 1x4 strips to 2x2 strips -d, --delete Delete source file(s) or a directory. It's not recoverable. diff --git a/kindlecomicconverter/comic2ebook.py b/kindlecomicconverter/comic2ebook.py index 1f1f35a..3c79bcb 100755 --- a/kindlecomicconverter/comic2ebook.py +++ b/kindlecomicconverter/comic2ebook.py @@ -1380,6 +1380,8 @@ def makeParser(): help="Create PNG files instead JPEG") processing_options.add_argument("--mozjpeg", action="store_true", dest="mozjpeg", default=False, help="Create JPEG files using mozJpeg") + processing_options.add_argument("--jpeg-quality", type=int, dest="jpegquality", + help="The JPEG quality, on a scale from 0 (worst) to 95 (best). Default 85 for most devices.") processing_options.add_argument("--maximizestrips", action="store_true", dest="maximizestrips", default=False, help="Turn 1x4 strips to 2x2 strips") processing_options.add_argument("-d", "--delete", action="store_true", dest="delete", default=False, @@ -1479,6 +1481,11 @@ def checkOptions(options): image.ProfileData.Profiles["Custom"] = newProfile options.profile = "Custom" options.profileData = image.ProfileData.Profiles[options.profile] + if not options.jpegquality: + if options.profile.startswith('KS') or options.profile == 'KCS': + options.jpegquality = 90 + else: + options.jpegquality = 85 return options diff --git a/kindlecomicconverter/image.py b/kindlecomicconverter/image.py index c4e5ed2..3199c23 100755 --- a/kindlecomicconverter/image.py +++ b/kindlecomicconverter/image.py @@ -408,13 +408,13 @@ class ComicPage: targetPath += '.jpg' if self.opt.mozjpeg: with io.BytesIO() as output: - image.save(output, format="JPEG", optimize=1, quality=85) + image.save(output, format="JPEG", optimize=1, quality=self.opt.jpegquality) input_jpeg_bytes = output.getvalue() output_jpeg_bytes = mozjpeg_lossless_optimization.optimize(input_jpeg_bytes) with open(targetPath, "wb") as output_jpeg_file: output_jpeg_file.write(output_jpeg_bytes) else: - image.save(targetPath, 'JPEG', optimize=1, quality=85) + image.save(targetPath, 'JPEG', optimize=1, quality=self.opt.jpegquality) return targetPath def gammaCorrectImage(self): @@ -584,7 +584,7 @@ class Cover: def save_to_epub(self, target, tomeid, len_tomes=0): try: if tomeid == 0: - self.image.save(target, "JPEG", optimize=1, quality=85) + self.image.save(target, "JPEG", optimize=1, quality=self.options.jpegquality) else: copy = self.image.copy() draw = ImageDraw.Draw(copy) @@ -598,7 +598,7 @@ class Cover: stroke_fill=0, stroke_width=25 ) - copy.save(target, "JPEG", optimize=1, quality=85) + copy.save(target, "JPEG", optimize=1, quality=self.options.jpegquality) except IOError: raise RuntimeError('Failed to save cover.') @@ -606,6 +606,6 @@ class Cover: self.image = ImageOps.contain(self.image, (300, 470), Image.Resampling.LANCZOS) try: self.image.save(os.path.join(kindle.path.split('documents')[0], 'system', 'thumbnails', - 'thumbnail_' + asin + '_EBOK_portrait.jpg'), 'JPEG', optimize=1, quality=85) + 'thumbnail_' + asin + '_EBOK_portrait.jpg'), 'JPEG', optimize=1, quality=self.options.jpegquality) except IOError: raise RuntimeError('Failed to upload cover.') From f9064ef0e419219a7f1e3ad548406221721d3bdb Mon Sep 17 00:00:00 2001 From: jaroslawjanas Date: Mon, 29 Dec 2025 10:37:52 -0800 Subject: [PATCH 010/107] configurable jpg quality (#1205) --- gui/KCC.ui | 945 +++++++++++++++++--------------- kindlecomicconverter/KCC_gui.py | 10 + kindlecomicconverter/KCC_ui.py | 636 +++++++++++---------- 3 files changed, 850 insertions(+), 741 deletions(-) diff --git a/gui/KCC.ui b/gui/KCC.ui index 205a76b..431873f 100644 --- a/gui/KCC.ui +++ b/gui/KCC.ui @@ -7,7 +7,7 @@ 0 0 566 - 581 + 658 @@ -22,6 +22,483 @@ 5 + + + + + 0 + 30 + + + + + true + + + + false + + + Qt::AlignmentFlag::AlignJustify|Qt::AlignmentFlag::AlignVCenter + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + 30 + + + + <html><head/><body><p style='white-space:pre'>Shift+Click to edit directory.</p></body></html> + + + Metadata Editor + + + + :/Other/icons/editor.png:/Other/icons/editor.png + + + + + + + + 0 + 30 + + + + Support me on Ko-fi + + + + :/Brand/icons/kofi_symbol.png:/Brand/icons/kofi_symbol.png + + + + 19 + 16 + + + + + + + + + 0 + 30 + + + + Wiki + + + + :/Other/icons/wiki.png:/Other/icons/wiki.png + + + + + + + + + + false + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + 300 + + + 1 + + + Qt::Orientation::Horizontal + + + + + + + + 0 + 0 + + + + 99 + + + 5 + + + 0 + + + + + + + <html><head/><body><p>After calculating the cropping boundaries, &quot;back up&quot; a specified percentage amount.</p></body></html> + + + Preserve Margin % + + + + + + + Cropping power: + + + + + + + + + + false + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + 0 + + + + <html><head/><body><p style='white-space:pre'>Resolution of the target device.</p></body></html> + + + Custom height: + + + + + + + <html><head/><body><p style='white-space:pre'>Resolution of the target device.</p></body></html> + + + 6000 + + + + + + + + 0 + 0 + + + + <html><head/><body><p style='white-space:pre'>Resolution of the target device.</p></body></html> + + + Custom width: + + + + + + + <html><head/><body><p style='white-space:pre'>Resolution of the target device.</p></body></html> + + + 8000 + + + + + + + + + + + 0 + 0 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + 30 + + + + + true + + + + <html><head/><body><p style='white-space:pre'>Shift+Click to select the output directory for this list.</p></body></html> + + + Convert + + + + :/Other/icons/convert.png:/Other/icons/convert.png + + + + + + + + 0 + 30 + + + + Clear list + + + + :/Other/icons/clear.png:/Other/icons/clear.png + + + + + + + + 0 + 28 + + + + <html><head/><body><p style='white-space:pre'>Target device.</p></body></html> + + + + + + + + 0 + 30 + + + + <html><head/><body><p style='white-space:pre'>Add CBR, CBZ, CB7 or PDF file to queue.</p></body></html> + + + Add input file(s) + + + + :/Other/icons/document_new.png:/Other/icons/document_new.png + + + + + + + + 0 + 0 + + + + <html><head/><body><p style='white-space:pre'>Add directory containing JPG, PNG or GIF files to queue.<br/><span style=" font-weight:600;">CBR, CBZ and CB7 files inside will not be processed!</span></p></body></html> + + + Add input folder(s) + + + + :/Other/icons/folder_new.png:/Other/icons/folder_new.png + + + + + + + + 0 + 28 + + + + <html><head/><body><p style='white-space:pre'>Output format.</p></body></html> + + + + + clearButton + deviceBox + convertButton + fileButton + directoryButton + formatBox + + + + + + + 0 + 150 + + + + <html><head/><body><p>Double click on source to open it in metadata editor.</p></body></html> + + + + + + QAbstractItemView::SelectionMode::NoSelection + + + QAbstractItemView::ScrollMode::ScrollPerPixel + + + QAbstractItemView::ScrollMode::ScrollPerPixel + + + + + + + + 0 + 0 + + + + false + + + <html><head/><body><p>Warning: chunk size greater than default may cause<br/>performance/battery issues, especially on older devices.</p></body></html> + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + 0 + + + + Chunk size MB: + + + + + + + 100 + + + 600 + + + 400 + + + + + + + + 0 + 0 + + + + Greater than default may cause performance issues on older ereaders. + + + + + + @@ -393,101 +870,17 @@ - - - - - - - - 0 - 150 - - - - <html><head/><body><p>Double click on source to open it in metadata editor.</p></body></html> - - - - - - QAbstractItemView::SelectionMode::NoSelection - - - QAbstractItemView::ScrollMode::ScrollPerPixel - - - QAbstractItemView::ScrollMode::ScrollPerPixel - - - - - - - false - - - - 0 - - - 0 - - - 0 - - - 0 - - - - - - 0 - 0 - - + + - <html><head/><body><p style='white-space:pre'>Resolution of the target device.</p></body></html> + The JPEG quality, on a scale from 0 (worst) to 95 (best). + +Default is 85 for most devices besides Kindle Scribe and Colorsoft, which are 90. + +Higher values are larger and higher quality, and may resolve blank page issues. - Custom height: - - - - - - - <html><head/><body><p style='white-space:pre'>Resolution of the target device.</p></body></html> - - - 6000 - - - - - - - - 0 - 0 - - - - <html><head/><body><p style='white-space:pre'>Resolution of the target device.</p></body></html> - - - Custom width: - - - - - - - <html><head/><body><p style='white-space:pre'>Resolution of the target device.</p></body></html> - - - 8000 + Custom JPEG Quality @@ -535,9 +928,18 @@ - - - + + + + + 0 + 0 + + + + false + + 0 @@ -551,363 +953,22 @@ 0 - - - - 0 - 30 - - - - <html><head/><body><p style='white-space:pre'>Shift+Click to edit directory.</p></body></html> - + - Metadata Editor - - - - :/Other/icons/editor.png:/Other/icons/editor.png + JPEG Quality: - - - - 0 - 30 - - - - Support me on Ko-fi - - - - :/Brand/icons/kofi_symbol.png:/Brand/icons/kofi_symbol.png - - - - 19 - 16 - - - - - - - - - 0 - 30 - - - - Wiki - - - - :/Other/icons/wiki.png:/Other/icons/wiki.png - - - - - - - - - - - 0 - 30 - - - - - true - - - - false - - - Qt::AlignmentFlag::AlignJustify|Qt::AlignmentFlag::AlignVCenter - - - - - - - false - - - - 0 - - - 0 - - - 0 - - - 0 - - - - - <html><head/><body><p>After calculating the cropping boundaries, &quot;back up&quot; a specified percentage amount.</p></body></html> - - - Preserve Margin % - - - - - - - Cropping power: - - - - - + - 300 - - - 1 - - - Qt::Orientation::Horizontal - - - - - - - - 0 - 0 - - - - 99 + 95 5 - 0 - - - - - - - - - - - 0 - 0 - - - - - 0 - - - 0 - - - 0 - - - 0 - - - - - - 0 - 30 - - - - - true - - - - <html><head/><body><p style='white-space:pre'>Shift+Click to select the output directory for this list.</p></body></html> - - - Convert - - - - :/Other/icons/convert.png:/Other/icons/convert.png - - - - - - - - 0 - 30 - - - - Clear list - - - - :/Other/icons/clear.png:/Other/icons/clear.png - - - - - - - - 0 - 28 - - - - <html><head/><body><p style='white-space:pre'>Target device.</p></body></html> - - - - - - - - 0 - 30 - - - - <html><head/><body><p style='white-space:pre'>Add CBR, CBZ, CB7 or PDF file to queue.</p></body></html> - - - Add input file(s) - - - - :/Other/icons/document_new.png:/Other/icons/document_new.png - - - - - - - - 0 - 0 - - - - <html><head/><body><p style='white-space:pre'>Add directory containing JPG, PNG or GIF files to queue.<br/><span style=" font-weight:600;">CBR, CBZ and CB7 files inside will not be processed!</span></p></body></html> - - - Add input folder(s) - - - - :/Other/icons/folder_new.png:/Other/icons/folder_new.png - - - - - - - - 0 - 28 - - - - <html><head/><body><p style='white-space:pre'>Output format.</p></body></html> - - - - - clearButton - deviceBox - convertButton - fileButton - directoryButton - formatBox - - - - - - - 0 - 0 - - - - false - - - <html><head/><body><p>Warning: chunk size greater than default may cause<br/>performance/battery issues, especially on older devices.</p></body></html> - - - - 0 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - - 0 - 0 - - - - Chunk size MB: - - - - - - - 100 - - - 600 - - - 400 - - - - - - - - 0 - 0 - - - - Greater than default may cause performance issues on older ereaders. + 85 diff --git a/kindlecomicconverter/KCC_gui.py b/kindlecomicconverter/KCC_gui.py index 186ed93..f17d13a 100644 --- a/kindlecomicconverter/KCC_gui.py +++ b/kindlecomicconverter/KCC_gui.py @@ -347,6 +347,8 @@ class WorkerThread(QThread): options.forcepng = True elif GUI.mozJpegBox.checkState() == Qt.CheckState.Checked: options.mozjpeg = True + if GUI.jpegQualityBox.isChecked(): + options.jpegquality = GUI.jpegQualitySpinBox.value() if GUI.currentMode > 2: options.customwidth = str(GUI.widthBox.value()) options.customheight = str(GUI.heightBox.value()) @@ -742,6 +744,12 @@ class KCCGUI(KCC_ui.Ui_mainWindow): GUI.croppingWidget.setVisible(False) self.changeCroppingPower(100) # 1.0 + def togglejpegqualityBox(self, value): + if value: + GUI.jpegQualityWidget.setVisible(True) + else: + GUI.jpegQualityWidget.setVisible(False) + def togglewebtoonBox(self, value): if value: self.addMessage('You can choose a taller device profile to get taller cuts in webtoon mode.', 'info') @@ -1026,6 +1034,7 @@ class KCCGUI(KCC_ui.Ui_mainWindow): 'disableProcessingBox': GUI.disableProcessingBox.checkState(), 'metadataTitleBox': GUI.metadataTitleBox.checkState(), 'mozJpegBox': GUI.mozJpegBox.checkState(), + 'jpegQualityBox': GUI.jpegQualityBox.checkState(), 'widthBox': GUI.widthBox.value(), 'heightBox': GUI.heightBox.value(), 'deleteBox': GUI.deleteBox.checkState(), @@ -1354,6 +1363,7 @@ class KCCGUI(KCC_ui.Ui_mainWindow): GUI.gammaBox.stateChanged.connect(self.togglegammaBox) GUI.croppingBox.stateChanged.connect(self.togglecroppingBox) GUI.croppingPowerSlider.valueChanged.connect(self.changeCroppingPower) + GUI.jpegQualityBox.stateChanged.connect(self.togglejpegqualityBox) GUI.webtoonBox.stateChanged.connect(self.togglewebtoonBox) GUI.qualityBox.stateChanged.connect(self.togglequalityBox) GUI.mozJpegBox.stateChanged.connect(self.toggleImageFormatBox) diff --git a/kindlecomicconverter/KCC_ui.py b/kindlecomicconverter/KCC_ui.py index 9cdd2d6..daa5092 100644 --- a/kindlecomicconverter/KCC_ui.py +++ b/kindlecomicconverter/KCC_ui.py @@ -26,7 +26,7 @@ class Ui_mainWindow(object): def setupUi(self, mainWindow): if not mainWindow.objectName(): mainWindow.setObjectName(u"mainWindow") - mainWindow.resize(566, 581) + mainWindow.resize(566, 658) icon = QIcon() icon.addFile(u":/Icon/icons/comic2ebook.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off) mainWindow.setWindowIcon(icon) @@ -35,6 +35,253 @@ class Ui_mainWindow(object): self.gridLayout = QGridLayout(self.centralWidget) self.gridLayout.setObjectName(u"gridLayout") self.gridLayout.setContentsMargins(-1, -1, -1, 5) + self.progressBar = QProgressBar(self.centralWidget) + self.progressBar.setObjectName(u"progressBar") + self.progressBar.setMinimumSize(QSize(0, 30)) + font = QFont() + font.setBold(True) + self.progressBar.setFont(font) + self.progressBar.setVisible(False) + self.progressBar.setAlignment(Qt.AlignmentFlag.AlignJustify|Qt.AlignmentFlag.AlignVCenter) + + self.gridLayout.addWidget(self.progressBar, 1, 0, 1, 2) + + self.toolWidget = QWidget(self.centralWidget) + self.toolWidget.setObjectName(u"toolWidget") + self.horizontalLayout = QHBoxLayout(self.toolWidget) + self.horizontalLayout.setObjectName(u"horizontalLayout") + self.horizontalLayout.setContentsMargins(0, 0, 0, 0) + self.editorButton = QPushButton(self.toolWidget) + self.editorButton.setObjectName(u"editorButton") + self.editorButton.setMinimumSize(QSize(0, 30)) + icon1 = QIcon() + icon1.addFile(u":/Other/icons/editor.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off) + self.editorButton.setIcon(icon1) + + self.horizontalLayout.addWidget(self.editorButton) + + self.kofiButton = QPushButton(self.toolWidget) + self.kofiButton.setObjectName(u"kofiButton") + self.kofiButton.setMinimumSize(QSize(0, 30)) + icon2 = QIcon() + icon2.addFile(u":/Brand/icons/kofi_symbol.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off) + self.kofiButton.setIcon(icon2) + self.kofiButton.setIconSize(QSize(19, 16)) + + self.horizontalLayout.addWidget(self.kofiButton) + + self.wikiButton = QPushButton(self.toolWidget) + self.wikiButton.setObjectName(u"wikiButton") + self.wikiButton.setMinimumSize(QSize(0, 30)) + icon3 = QIcon() + icon3.addFile(u":/Other/icons/wiki.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off) + self.wikiButton.setIcon(icon3) + + self.horizontalLayout.addWidget(self.wikiButton) + + + self.gridLayout.addWidget(self.toolWidget, 0, 0, 1, 2) + + self.croppingWidget = QWidget(self.centralWidget) + self.croppingWidget.setObjectName(u"croppingWidget") + self.croppingWidget.setVisible(False) + self.gridLayout_5 = QGridLayout(self.croppingWidget) + self.gridLayout_5.setObjectName(u"gridLayout_5") + self.gridLayout_5.setContentsMargins(0, 0, 0, 0) + self.croppingPowerSlider = QSlider(self.croppingWidget) + self.croppingPowerSlider.setObjectName(u"croppingPowerSlider") + self.croppingPowerSlider.setMaximum(300) + self.croppingPowerSlider.setSingleStep(1) + self.croppingPowerSlider.setOrientation(Qt.Orientation.Horizontal) + + self.gridLayout_5.addWidget(self.croppingPowerSlider, 0, 1, 1, 1) + + self.preserveMarginBox = QSpinBox(self.croppingWidget) + self.preserveMarginBox.setObjectName(u"preserveMarginBox") + sizePolicy = QSizePolicy(QSizePolicy.Policy.Fixed, QSizePolicy.Policy.Fixed) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.preserveMarginBox.sizePolicy().hasHeightForWidth()) + self.preserveMarginBox.setSizePolicy(sizePolicy) + self.preserveMarginBox.setMaximum(99) + self.preserveMarginBox.setSingleStep(5) + self.preserveMarginBox.setValue(0) + + self.gridLayout_5.addWidget(self.preserveMarginBox, 1, 1, 1, 1) + + self.preserveMarginLabel = QLabel(self.croppingWidget) + self.preserveMarginLabel.setObjectName(u"preserveMarginLabel") + + self.gridLayout_5.addWidget(self.preserveMarginLabel, 1, 0, 1, 1) + + self.croppingPowerLabel = QLabel(self.croppingWidget) + self.croppingPowerLabel.setObjectName(u"croppingPowerLabel") + + self.gridLayout_5.addWidget(self.croppingPowerLabel, 0, 0, 1, 1) + + + self.gridLayout.addWidget(self.croppingWidget, 9, 0, 1, 2) + + self.customWidget = QWidget(self.centralWidget) + self.customWidget.setObjectName(u"customWidget") + self.customWidget.setVisible(False) + self.gridLayout_3 = QGridLayout(self.customWidget) + self.gridLayout_3.setObjectName(u"gridLayout_3") + self.gridLayout_3.setContentsMargins(0, 0, 0, 0) + self.hLabel = QLabel(self.customWidget) + self.hLabel.setObjectName(u"hLabel") + sizePolicy1 = QSizePolicy(QSizePolicy.Policy.Fixed, QSizePolicy.Policy.Preferred) + sizePolicy1.setHorizontalStretch(0) + sizePolicy1.setVerticalStretch(0) + sizePolicy1.setHeightForWidth(self.hLabel.sizePolicy().hasHeightForWidth()) + self.hLabel.setSizePolicy(sizePolicy1) + + self.gridLayout_3.addWidget(self.hLabel, 0, 2, 1, 1) + + self.widthBox = QSpinBox(self.customWidget) + self.widthBox.setObjectName(u"widthBox") + self.widthBox.setMaximum(6000) + + self.gridLayout_3.addWidget(self.widthBox, 0, 1, 1, 1) + + self.wLabel = QLabel(self.customWidget) + self.wLabel.setObjectName(u"wLabel") + sizePolicy1.setHeightForWidth(self.wLabel.sizePolicy().hasHeightForWidth()) + self.wLabel.setSizePolicy(sizePolicy1) + + self.gridLayout_3.addWidget(self.wLabel, 0, 0, 1, 1) + + self.heightBox = QSpinBox(self.customWidget) + self.heightBox.setObjectName(u"heightBox") + self.heightBox.setMaximum(8000) + + self.gridLayout_3.addWidget(self.heightBox, 0, 3, 1, 1) + + + self.gridLayout.addWidget(self.customWidget, 8, 0, 1, 2) + + self.buttonWidget = QWidget(self.centralWidget) + self.buttonWidget.setObjectName(u"buttonWidget") + sizePolicy2 = QSizePolicy(QSizePolicy.Policy.Preferred, QSizePolicy.Policy.Fixed) + sizePolicy2.setHorizontalStretch(0) + sizePolicy2.setVerticalStretch(0) + sizePolicy2.setHeightForWidth(self.buttonWidget.sizePolicy().hasHeightForWidth()) + self.buttonWidget.setSizePolicy(sizePolicy2) + self.gridLayout_4 = QGridLayout(self.buttonWidget) + self.gridLayout_4.setObjectName(u"gridLayout_4") + self.gridLayout_4.setContentsMargins(0, 0, 0, 0) + self.convertButton = QPushButton(self.buttonWidget) + self.convertButton.setObjectName(u"convertButton") + self.convertButton.setMinimumSize(QSize(0, 30)) + self.convertButton.setFont(font) + icon4 = QIcon() + icon4.addFile(u":/Other/icons/convert.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off) + self.convertButton.setIcon(icon4) + + self.gridLayout_4.addWidget(self.convertButton, 1, 3, 1, 1) + + self.clearButton = QPushButton(self.buttonWidget) + self.clearButton.setObjectName(u"clearButton") + self.clearButton.setMinimumSize(QSize(0, 30)) + icon5 = QIcon() + icon5.addFile(u":/Other/icons/clear.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off) + self.clearButton.setIcon(icon5) + + self.gridLayout_4.addWidget(self.clearButton, 0, 3, 1, 1) + + self.deviceBox = QComboBox(self.buttonWidget) + self.deviceBox.setObjectName(u"deviceBox") + self.deviceBox.setMinimumSize(QSize(0, 28)) + + self.gridLayout_4.addWidget(self.deviceBox, 1, 1, 1, 1) + + self.fileButton = QPushButton(self.buttonWidget) + self.fileButton.setObjectName(u"fileButton") + self.fileButton.setMinimumSize(QSize(0, 30)) + icon6 = QIcon() + icon6.addFile(u":/Other/icons/document_new.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off) + self.fileButton.setIcon(icon6) + + self.gridLayout_4.addWidget(self.fileButton, 0, 1, 1, 1) + + self.directoryButton = QPushButton(self.buttonWidget) + self.directoryButton.setObjectName(u"directoryButton") + sizePolicy3 = QSizePolicy(QSizePolicy.Policy.Minimum, QSizePolicy.Policy.Minimum) + sizePolicy3.setHorizontalStretch(0) + sizePolicy3.setVerticalStretch(0) + sizePolicy3.setHeightForWidth(self.directoryButton.sizePolicy().hasHeightForWidth()) + self.directoryButton.setSizePolicy(sizePolicy3) + icon7 = QIcon() + icon7.addFile(u":/Other/icons/folder_new.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off) + self.directoryButton.setIcon(icon7) + + self.gridLayout_4.addWidget(self.directoryButton, 0, 4, 1, 1) + + self.formatBox = QComboBox(self.buttonWidget) + self.formatBox.setObjectName(u"formatBox") + self.formatBox.setMinimumSize(QSize(0, 28)) + + self.gridLayout_4.addWidget(self.formatBox, 1, 4, 1, 1) + + self.clearButton.raise_() + self.deviceBox.raise_() + self.convertButton.raise_() + self.fileButton.raise_() + self.directoryButton.raise_() + self.formatBox.raise_() + + self.gridLayout.addWidget(self.buttonWidget, 3, 0, 1, 2) + + self.jobList = QListWidget(self.centralWidget) + self.jobList.setObjectName(u"jobList") + self.jobList.setMinimumSize(QSize(0, 150)) + self.jobList.setStyleSheet(u"") + self.jobList.setSelectionMode(QAbstractItemView.SelectionMode.NoSelection) + self.jobList.setVerticalScrollMode(QAbstractItemView.ScrollMode.ScrollPerPixel) + self.jobList.setHorizontalScrollMode(QAbstractItemView.ScrollMode.ScrollPerPixel) + + self.gridLayout.addWidget(self.jobList, 2, 0, 1, 2) + + self.chunkSizeWidget = QWidget(self.centralWidget) + self.chunkSizeWidget.setObjectName(u"chunkSizeWidget") + sizePolicy4 = QSizePolicy(QSizePolicy.Policy.Minimum, QSizePolicy.Policy.Fixed) + sizePolicy4.setHorizontalStretch(0) + sizePolicy4.setVerticalStretch(0) + sizePolicy4.setHeightForWidth(self.chunkSizeWidget.sizePolicy().hasHeightForWidth()) + self.chunkSizeWidget.setSizePolicy(sizePolicy4) + self.chunkSizeWidget.setVisible(False) + self.horizontalLayout_4 = QHBoxLayout(self.chunkSizeWidget) + self.horizontalLayout_4.setSpacing(0) + self.horizontalLayout_4.setObjectName(u"horizontalLayout_4") + self.horizontalLayout_4.setContentsMargins(0, 0, 0, 0) + self.chunkSizeLabel = QLabel(self.chunkSizeWidget) + self.chunkSizeLabel.setObjectName(u"chunkSizeLabel") + sizePolicy5 = QSizePolicy(QSizePolicy.Policy.Minimum, QSizePolicy.Policy.Preferred) + sizePolicy5.setHorizontalStretch(0) + sizePolicy5.setVerticalStretch(0) + sizePolicy5.setHeightForWidth(self.chunkSizeLabel.sizePolicy().hasHeightForWidth()) + self.chunkSizeLabel.setSizePolicy(sizePolicy5) + + self.horizontalLayout_4.addWidget(self.chunkSizeLabel) + + self.chunkSizeBox = QSpinBox(self.chunkSizeWidget) + self.chunkSizeBox.setObjectName(u"chunkSizeBox") + self.chunkSizeBox.setMinimum(100) + self.chunkSizeBox.setMaximum(600) + self.chunkSizeBox.setValue(400) + + self.horizontalLayout_4.addWidget(self.chunkSizeBox) + + self.chunkSizeWarnLabel = QLabel(self.chunkSizeWidget) + self.chunkSizeWarnLabel.setObjectName(u"chunkSizeWarnLabel") + sizePolicy5.setHeightForWidth(self.chunkSizeWarnLabel.sizePolicy().hasHeightForWidth()) + self.chunkSizeWarnLabel.setSizePolicy(sizePolicy5) + + self.horizontalLayout_4.addWidget(self.chunkSizeWarnLabel) + + + self.gridLayout.addWidget(self.chunkSizeWidget, 6, 0, 1, 1) + self.optionWidget = QWidget(self.centralWidget) self.optionWidget.setObjectName(u"optionWidget") self.gridLayout_2 = QGridLayout(self.optionWidget) @@ -42,11 +289,8 @@ class Ui_mainWindow(object): self.gridLayout_2.setContentsMargins(0, 0, 0, 0) self.titleEdit = QLineEdit(self.optionWidget) self.titleEdit.setObjectName(u"titleEdit") - sizePolicy = QSizePolicy(QSizePolicy.Policy.Minimum, QSizePolicy.Policy.Fixed) - sizePolicy.setHorizontalStretch(0) - sizePolicy.setVerticalStretch(0) - sizePolicy.setHeightForWidth(self.titleEdit.sizePolicy().hasHeightForWidth()) - self.titleEdit.setSizePolicy(sizePolicy) + sizePolicy4.setHeightForWidth(self.titleEdit.sizePolicy().hasHeightForWidth()) + self.titleEdit.setSizePolicy(sizePolicy4) self.titleEdit.setFocusPolicy(Qt.FocusPolicy.ClickFocus) self.titleEdit.setClearButtonEnabled(False) @@ -148,8 +392,8 @@ class Ui_mainWindow(object): self.authorEdit = QLineEdit(self.optionWidget) self.authorEdit.setObjectName(u"authorEdit") - sizePolicy.setHeightForWidth(self.authorEdit.sizePolicy().hasHeightForWidth()) - self.authorEdit.setSizePolicy(sizePolicy) + sizePolicy4.setHeightForWidth(self.authorEdit.sizePolicy().hasHeightForWidth()) + self.authorEdit.setSizePolicy(sizePolicy4) self.authorEdit.setFocusPolicy(Qt.FocusPolicy.ClickFocus) self.authorEdit.setClearButtonEnabled(False) @@ -196,11 +440,8 @@ class Ui_mainWindow(object): self.horizontalLayout_3.setObjectName(u"horizontalLayout_3") self.defaultOutputFolderBox = QCheckBox(self.outputFolderWidget) self.defaultOutputFolderBox.setObjectName(u"defaultOutputFolderBox") - sizePolicy1 = QSizePolicy(QSizePolicy.Policy.Fixed, QSizePolicy.Policy.Fixed) - sizePolicy1.setHorizontalStretch(0) - sizePolicy1.setVerticalStretch(0) - sizePolicy1.setHeightForWidth(self.defaultOutputFolderBox.sizePolicy().hasHeightForWidth()) - self.defaultOutputFolderBox.setSizePolicy(sizePolicy1) + sizePolicy.setHeightForWidth(self.defaultOutputFolderBox.sizePolicy().hasHeightForWidth()) + self.defaultOutputFolderBox.setSizePolicy(sizePolicy) self.defaultOutputFolderBox.setTristate(True) self.horizontalLayout_3.addWidget(self.defaultOutputFolderBox) @@ -208,66 +449,21 @@ class Ui_mainWindow(object): self.defaultOutputFolderButton = QPushButton(self.outputFolderWidget) self.defaultOutputFolderButton.setObjectName(u"defaultOutputFolderButton") self.defaultOutputFolderButton.setMinimumSize(QSize(0, 30)) - icon1 = QIcon() - icon1.addFile(u":/Other/icons/folder_new.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off) - self.defaultOutputFolderButton.setIcon(icon1) + self.defaultOutputFolderButton.setIcon(icon7) self.horizontalLayout_3.addWidget(self.defaultOutputFolderButton) self.gridLayout_2.addWidget(self.outputFolderWidget, 0, 2, 1, 1) + self.jpegQualityBox = QCheckBox(self.optionWidget) + self.jpegQualityBox.setObjectName(u"jpegQualityBox") + + self.gridLayout_2.addWidget(self.jpegQualityBox, 8, 0, 1, 1) + self.gridLayout.addWidget(self.optionWidget, 5, 0, 1, 2) - self.jobList = QListWidget(self.centralWidget) - self.jobList.setObjectName(u"jobList") - self.jobList.setMinimumSize(QSize(0, 150)) - self.jobList.setStyleSheet(u"") - self.jobList.setSelectionMode(QAbstractItemView.SelectionMode.NoSelection) - self.jobList.setVerticalScrollMode(QAbstractItemView.ScrollMode.ScrollPerPixel) - self.jobList.setHorizontalScrollMode(QAbstractItemView.ScrollMode.ScrollPerPixel) - - self.gridLayout.addWidget(self.jobList, 2, 0, 1, 2) - - self.customWidget = QWidget(self.centralWidget) - self.customWidget.setObjectName(u"customWidget") - self.customWidget.setVisible(False) - self.gridLayout_3 = QGridLayout(self.customWidget) - self.gridLayout_3.setObjectName(u"gridLayout_3") - self.gridLayout_3.setContentsMargins(0, 0, 0, 0) - self.hLabel = QLabel(self.customWidget) - self.hLabel.setObjectName(u"hLabel") - sizePolicy2 = QSizePolicy(QSizePolicy.Policy.Fixed, QSizePolicy.Policy.Preferred) - sizePolicy2.setHorizontalStretch(0) - sizePolicy2.setVerticalStretch(0) - sizePolicy2.setHeightForWidth(self.hLabel.sizePolicy().hasHeightForWidth()) - self.hLabel.setSizePolicy(sizePolicy2) - - self.gridLayout_3.addWidget(self.hLabel, 0, 2, 1, 1) - - self.widthBox = QSpinBox(self.customWidget) - self.widthBox.setObjectName(u"widthBox") - self.widthBox.setMaximum(6000) - - self.gridLayout_3.addWidget(self.widthBox, 0, 1, 1, 1) - - self.wLabel = QLabel(self.customWidget) - self.wLabel.setObjectName(u"wLabel") - sizePolicy2.setHeightForWidth(self.wLabel.sizePolicy().hasHeightForWidth()) - self.wLabel.setSizePolicy(sizePolicy2) - - self.gridLayout_3.addWidget(self.wLabel, 0, 0, 1, 1) - - self.heightBox = QSpinBox(self.customWidget) - self.heightBox.setObjectName(u"heightBox") - self.heightBox.setMaximum(8000) - - self.gridLayout_3.addWidget(self.heightBox, 0, 3, 1, 1) - - - self.gridLayout.addWidget(self.customWidget, 8, 0, 1, 2) - self.gammaWidget = QWidget(self.centralWidget) self.gammaWidget.setObjectName(u"gammaWidget") self.gammaWidget.setVisible(False) @@ -290,196 +486,29 @@ class Ui_mainWindow(object): self.gridLayout.addWidget(self.gammaWidget, 7, 0, 1, 2) - self.toolWidget = QWidget(self.centralWidget) - self.toolWidget.setObjectName(u"toolWidget") - self.horizontalLayout = QHBoxLayout(self.toolWidget) - self.horizontalLayout.setObjectName(u"horizontalLayout") - self.horizontalLayout.setContentsMargins(0, 0, 0, 0) - self.editorButton = QPushButton(self.toolWidget) - self.editorButton.setObjectName(u"editorButton") - self.editorButton.setMinimumSize(QSize(0, 30)) - icon2 = QIcon() - icon2.addFile(u":/Other/icons/editor.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off) - self.editorButton.setIcon(icon2) + self.jpegQualityWidget = QWidget(self.centralWidget) + self.jpegQualityWidget.setObjectName(u"jpegQualityWidget") + sizePolicy1.setHeightForWidth(self.jpegQualityWidget.sizePolicy().hasHeightForWidth()) + self.jpegQualityWidget.setSizePolicy(sizePolicy1) + self.jpegQualityWidget.setVisible(False) + self.horizontalLayout_12 = QHBoxLayout(self.jpegQualityWidget) + self.horizontalLayout_12.setObjectName(u"horizontalLayout_12") + self.horizontalLayout_12.setContentsMargins(0, 0, 0, 0) + self.jpegQualityLabel = QLabel(self.jpegQualityWidget) + self.jpegQualityLabel.setObjectName(u"jpegQualityLabel") - self.horizontalLayout.addWidget(self.editorButton) + self.horizontalLayout_12.addWidget(self.jpegQualityLabel) - self.kofiButton = QPushButton(self.toolWidget) - self.kofiButton.setObjectName(u"kofiButton") - self.kofiButton.setMinimumSize(QSize(0, 30)) - icon3 = QIcon() - icon3.addFile(u":/Brand/icons/kofi_symbol.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off) - self.kofiButton.setIcon(icon3) - self.kofiButton.setIconSize(QSize(19, 16)) + self.jpegQualitySpinBox = QSpinBox(self.jpegQualityWidget) + self.jpegQualitySpinBox.setObjectName(u"jpegQualitySpinBox") + self.jpegQualitySpinBox.setMaximum(95) + self.jpegQualitySpinBox.setSingleStep(5) + self.jpegQualitySpinBox.setValue(85) - self.horizontalLayout.addWidget(self.kofiButton) - - self.wikiButton = QPushButton(self.toolWidget) - self.wikiButton.setObjectName(u"wikiButton") - self.wikiButton.setMinimumSize(QSize(0, 30)) - icon4 = QIcon() - icon4.addFile(u":/Other/icons/wiki.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off) - self.wikiButton.setIcon(icon4) - - self.horizontalLayout.addWidget(self.wikiButton) + self.horizontalLayout_12.addWidget(self.jpegQualitySpinBox) - self.gridLayout.addWidget(self.toolWidget, 0, 0, 1, 2) - - self.progressBar = QProgressBar(self.centralWidget) - self.progressBar.setObjectName(u"progressBar") - self.progressBar.setMinimumSize(QSize(0, 30)) - font = QFont() - font.setBold(True) - self.progressBar.setFont(font) - self.progressBar.setVisible(False) - self.progressBar.setAlignment(Qt.AlignmentFlag.AlignJustify|Qt.AlignmentFlag.AlignVCenter) - - self.gridLayout.addWidget(self.progressBar, 1, 0, 1, 2) - - self.croppingWidget = QWidget(self.centralWidget) - self.croppingWidget.setObjectName(u"croppingWidget") - self.croppingWidget.setVisible(False) - self.gridLayout_5 = QGridLayout(self.croppingWidget) - self.gridLayout_5.setObjectName(u"gridLayout_5") - self.gridLayout_5.setContentsMargins(0, 0, 0, 0) - self.preserveMarginLabel = QLabel(self.croppingWidget) - self.preserveMarginLabel.setObjectName(u"preserveMarginLabel") - - self.gridLayout_5.addWidget(self.preserveMarginLabel, 1, 0, 1, 1) - - self.croppingPowerLabel = QLabel(self.croppingWidget) - self.croppingPowerLabel.setObjectName(u"croppingPowerLabel") - - self.gridLayout_5.addWidget(self.croppingPowerLabel, 0, 0, 1, 1) - - self.croppingPowerSlider = QSlider(self.croppingWidget) - self.croppingPowerSlider.setObjectName(u"croppingPowerSlider") - self.croppingPowerSlider.setMaximum(300) - self.croppingPowerSlider.setSingleStep(1) - self.croppingPowerSlider.setOrientation(Qt.Orientation.Horizontal) - - self.gridLayout_5.addWidget(self.croppingPowerSlider, 0, 1, 1, 1) - - self.preserveMarginBox = QSpinBox(self.croppingWidget) - self.preserveMarginBox.setObjectName(u"preserveMarginBox") - sizePolicy1.setHeightForWidth(self.preserveMarginBox.sizePolicy().hasHeightForWidth()) - self.preserveMarginBox.setSizePolicy(sizePolicy1) - self.preserveMarginBox.setMaximum(99) - self.preserveMarginBox.setSingleStep(5) - self.preserveMarginBox.setValue(0) - - self.gridLayout_5.addWidget(self.preserveMarginBox, 1, 1, 1, 1) - - - self.gridLayout.addWidget(self.croppingWidget, 9, 0, 1, 2) - - self.buttonWidget = QWidget(self.centralWidget) - self.buttonWidget.setObjectName(u"buttonWidget") - sizePolicy3 = QSizePolicy(QSizePolicy.Policy.Preferred, QSizePolicy.Policy.Fixed) - sizePolicy3.setHorizontalStretch(0) - sizePolicy3.setVerticalStretch(0) - sizePolicy3.setHeightForWidth(self.buttonWidget.sizePolicy().hasHeightForWidth()) - self.buttonWidget.setSizePolicy(sizePolicy3) - self.gridLayout_4 = QGridLayout(self.buttonWidget) - self.gridLayout_4.setObjectName(u"gridLayout_4") - self.gridLayout_4.setContentsMargins(0, 0, 0, 0) - self.convertButton = QPushButton(self.buttonWidget) - self.convertButton.setObjectName(u"convertButton") - self.convertButton.setMinimumSize(QSize(0, 30)) - self.convertButton.setFont(font) - icon5 = QIcon() - icon5.addFile(u":/Other/icons/convert.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off) - self.convertButton.setIcon(icon5) - - self.gridLayout_4.addWidget(self.convertButton, 1, 3, 1, 1) - - self.clearButton = QPushButton(self.buttonWidget) - self.clearButton.setObjectName(u"clearButton") - self.clearButton.setMinimumSize(QSize(0, 30)) - icon6 = QIcon() - icon6.addFile(u":/Other/icons/clear.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off) - self.clearButton.setIcon(icon6) - - self.gridLayout_4.addWidget(self.clearButton, 0, 3, 1, 1) - - self.deviceBox = QComboBox(self.buttonWidget) - self.deviceBox.setObjectName(u"deviceBox") - self.deviceBox.setMinimumSize(QSize(0, 28)) - - self.gridLayout_4.addWidget(self.deviceBox, 1, 1, 1, 1) - - self.fileButton = QPushButton(self.buttonWidget) - self.fileButton.setObjectName(u"fileButton") - self.fileButton.setMinimumSize(QSize(0, 30)) - icon7 = QIcon() - icon7.addFile(u":/Other/icons/document_new.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off) - self.fileButton.setIcon(icon7) - - self.gridLayout_4.addWidget(self.fileButton, 0, 1, 1, 1) - - self.directoryButton = QPushButton(self.buttonWidget) - self.directoryButton.setObjectName(u"directoryButton") - sizePolicy4 = QSizePolicy(QSizePolicy.Policy.Minimum, QSizePolicy.Policy.Minimum) - sizePolicy4.setHorizontalStretch(0) - sizePolicy4.setVerticalStretch(0) - sizePolicy4.setHeightForWidth(self.directoryButton.sizePolicy().hasHeightForWidth()) - self.directoryButton.setSizePolicy(sizePolicy4) - self.directoryButton.setIcon(icon1) - - self.gridLayout_4.addWidget(self.directoryButton, 0, 4, 1, 1) - - self.formatBox = QComboBox(self.buttonWidget) - self.formatBox.setObjectName(u"formatBox") - self.formatBox.setMinimumSize(QSize(0, 28)) - - self.gridLayout_4.addWidget(self.formatBox, 1, 4, 1, 1) - - self.clearButton.raise_() - self.deviceBox.raise_() - self.convertButton.raise_() - self.fileButton.raise_() - self.directoryButton.raise_() - self.formatBox.raise_() - - self.gridLayout.addWidget(self.buttonWidget, 3, 0, 1, 2) - - self.chunkSizeWidget = QWidget(self.centralWidget) - self.chunkSizeWidget.setObjectName(u"chunkSizeWidget") - sizePolicy.setHeightForWidth(self.chunkSizeWidget.sizePolicy().hasHeightForWidth()) - self.chunkSizeWidget.setSizePolicy(sizePolicy) - self.chunkSizeWidget.setVisible(False) - self.horizontalLayout_4 = QHBoxLayout(self.chunkSizeWidget) - self.horizontalLayout_4.setSpacing(0) - self.horizontalLayout_4.setObjectName(u"horizontalLayout_4") - self.horizontalLayout_4.setContentsMargins(0, 0, 0, 0) - self.chunkSizeLabel = QLabel(self.chunkSizeWidget) - self.chunkSizeLabel.setObjectName(u"chunkSizeLabel") - sizePolicy5 = QSizePolicy(QSizePolicy.Policy.Minimum, QSizePolicy.Policy.Preferred) - sizePolicy5.setHorizontalStretch(0) - sizePolicy5.setVerticalStretch(0) - sizePolicy5.setHeightForWidth(self.chunkSizeLabel.sizePolicy().hasHeightForWidth()) - self.chunkSizeLabel.setSizePolicy(sizePolicy5) - - self.horizontalLayout_4.addWidget(self.chunkSizeLabel) - - self.chunkSizeBox = QSpinBox(self.chunkSizeWidget) - self.chunkSizeBox.setObjectName(u"chunkSizeBox") - self.chunkSizeBox.setMinimum(100) - self.chunkSizeBox.setMaximum(600) - self.chunkSizeBox.setValue(400) - - self.horizontalLayout_4.addWidget(self.chunkSizeBox) - - self.chunkSizeWarnLabel = QLabel(self.chunkSizeWidget) - self.chunkSizeWarnLabel.setObjectName(u"chunkSizeWarnLabel") - sizePolicy5.setHeightForWidth(self.chunkSizeWarnLabel.sizePolicy().hasHeightForWidth()) - self.chunkSizeWarnLabel.setSizePolicy(sizePolicy5) - - self.horizontalLayout_4.addWidget(self.chunkSizeWarnLabel) - - - self.gridLayout.addWidget(self.chunkSizeWidget, 6, 0, 1, 1) + self.gridLayout.addWidget(self.jpegQualityWidget, 10, 0, 1, 1) mainWindow.setCentralWidget(self.centralWidget) self.statusBar = QStatusBar(mainWindow) @@ -531,6 +560,58 @@ class Ui_mainWindow(object): def retranslateUi(self, mainWindow): mainWindow.setWindowTitle(QCoreApplication.translate("mainWindow", u"Kindle Comic Converter", None)) +#if QT_CONFIG(tooltip) + self.editorButton.setToolTip(QCoreApplication.translate("mainWindow", u"

Shift+Click to edit directory.

", None)) +#endif // QT_CONFIG(tooltip) + self.editorButton.setText(QCoreApplication.translate("mainWindow", u"Metadata Editor", None)) + self.kofiButton.setText(QCoreApplication.translate("mainWindow", u"Support me on Ko-fi", None)) + self.wikiButton.setText(QCoreApplication.translate("mainWindow", u"Wiki", None)) +#if QT_CONFIG(tooltip) + self.preserveMarginLabel.setToolTip(QCoreApplication.translate("mainWindow", u"

After calculating the cropping boundaries, "back up" a specified percentage amount.

", None)) +#endif // QT_CONFIG(tooltip) + self.preserveMarginLabel.setText(QCoreApplication.translate("mainWindow", u"Preserve Margin %", None)) + self.croppingPowerLabel.setText(QCoreApplication.translate("mainWindow", u"Cropping power:", None)) +#if QT_CONFIG(tooltip) + self.hLabel.setToolTip(QCoreApplication.translate("mainWindow", u"

Resolution of the target device.

", None)) +#endif // QT_CONFIG(tooltip) + self.hLabel.setText(QCoreApplication.translate("mainWindow", u"Custom height:", None)) +#if QT_CONFIG(tooltip) + self.widthBox.setToolTip(QCoreApplication.translate("mainWindow", u"

Resolution of the target device.

", None)) +#endif // QT_CONFIG(tooltip) +#if QT_CONFIG(tooltip) + self.wLabel.setToolTip(QCoreApplication.translate("mainWindow", u"

Resolution of the target device.

", None)) +#endif // QT_CONFIG(tooltip) + self.wLabel.setText(QCoreApplication.translate("mainWindow", u"Custom width:", None)) +#if QT_CONFIG(tooltip) + self.heightBox.setToolTip(QCoreApplication.translate("mainWindow", u"

Resolution of the target device.

", None)) +#endif // QT_CONFIG(tooltip) +#if QT_CONFIG(tooltip) + self.convertButton.setToolTip(QCoreApplication.translate("mainWindow", u"

Shift+Click to select the output directory for this list.

", None)) +#endif // QT_CONFIG(tooltip) + self.convertButton.setText(QCoreApplication.translate("mainWindow", u"Convert", None)) + self.clearButton.setText(QCoreApplication.translate("mainWindow", u"Clear list", None)) +#if QT_CONFIG(tooltip) + self.deviceBox.setToolTip(QCoreApplication.translate("mainWindow", u"

Target device.

", None)) +#endif // QT_CONFIG(tooltip) +#if QT_CONFIG(tooltip) + self.fileButton.setToolTip(QCoreApplication.translate("mainWindow", u"

Add CBR, CBZ, CB7 or PDF file to queue.

", None)) +#endif // QT_CONFIG(tooltip) + self.fileButton.setText(QCoreApplication.translate("mainWindow", u"Add input file(s)", None)) +#if QT_CONFIG(tooltip) + self.directoryButton.setToolTip(QCoreApplication.translate("mainWindow", u"

Add directory containing JPG, PNG or GIF files to queue.
CBR, CBZ and CB7 files inside will not be processed!

", None)) +#endif // QT_CONFIG(tooltip) + self.directoryButton.setText(QCoreApplication.translate("mainWindow", u"Add input folder(s)", None)) +#if QT_CONFIG(tooltip) + self.formatBox.setToolTip(QCoreApplication.translate("mainWindow", u"

Output format.

", None)) +#endif // QT_CONFIG(tooltip) +#if QT_CONFIG(tooltip) + self.jobList.setToolTip(QCoreApplication.translate("mainWindow", u"

Double click on source to open it in metadata editor.

", None)) +#endif // QT_CONFIG(tooltip) +#if QT_CONFIG(tooltip) + self.chunkSizeWidget.setToolTip(QCoreApplication.translate("mainWindow", u"

Warning: chunk size greater than default may cause
performance/battery issues, especially on older devices.

", None)) +#endif // QT_CONFIG(tooltip) + self.chunkSizeLabel.setText(QCoreApplication.translate("mainWindow", u"Chunk size MB:", None)) + self.chunkSizeWarnLabel.setText(QCoreApplication.translate("mainWindow", u"Greater than default may cause performance issues on older ereaders.", None)) #if QT_CONFIG(tooltip) self.titleEdit.setToolTip(QCoreApplication.translate("mainWindow", u"

Default Title

", None)) #endif // QT_CONFIG(tooltip) @@ -644,57 +725,14 @@ class Ui_mainWindow(object): #endif // QT_CONFIG(tooltip) self.defaultOutputFolderButton.setText("") #if QT_CONFIG(tooltip) - self.jobList.setToolTip(QCoreApplication.translate("mainWindow", u"

Double click on source to open it in metadata editor.

", None)) -#endif // QT_CONFIG(tooltip) -#if QT_CONFIG(tooltip) - self.hLabel.setToolTip(QCoreApplication.translate("mainWindow", u"

Resolution of the target device.

", None)) -#endif // QT_CONFIG(tooltip) - self.hLabel.setText(QCoreApplication.translate("mainWindow", u"Custom height:", None)) -#if QT_CONFIG(tooltip) - self.widthBox.setToolTip(QCoreApplication.translate("mainWindow", u"

Resolution of the target device.

", None)) -#endif // QT_CONFIG(tooltip) -#if QT_CONFIG(tooltip) - self.wLabel.setToolTip(QCoreApplication.translate("mainWindow", u"

Resolution of the target device.

", None)) -#endif // QT_CONFIG(tooltip) - self.wLabel.setText(QCoreApplication.translate("mainWindow", u"Custom width:", None)) -#if QT_CONFIG(tooltip) - self.heightBox.setToolTip(QCoreApplication.translate("mainWindow", u"

Resolution of the target device.

", None)) + self.jpegQualityBox.setToolTip(QCoreApplication.translate("mainWindow", u"The JPEG quality, on a scale from 0 (worst) to 95 (best). \n" +"\n" +"Default is 85 for most devices besides Kindle Scribe and Colorsoft, which are 90.\n" +"\n" +"Higher values are larger and higher quality, and may resolve blank page issues.", None)) #endif // QT_CONFIG(tooltip) + self.jpegQualityBox.setText(QCoreApplication.translate("mainWindow", u"Custom JPEG Quality", None)) self.gammaLabel.setText(QCoreApplication.translate("mainWindow", u"Gamma: Auto", None)) -#if QT_CONFIG(tooltip) - self.editorButton.setToolTip(QCoreApplication.translate("mainWindow", u"

Shift+Click to edit directory.

", None)) -#endif // QT_CONFIG(tooltip) - self.editorButton.setText(QCoreApplication.translate("mainWindow", u"Metadata Editor", None)) - self.kofiButton.setText(QCoreApplication.translate("mainWindow", u"Support me on Ko-fi", None)) - self.wikiButton.setText(QCoreApplication.translate("mainWindow", u"Wiki", None)) -#if QT_CONFIG(tooltip) - self.preserveMarginLabel.setToolTip(QCoreApplication.translate("mainWindow", u"

After calculating the cropping boundaries, "back up" a specified percentage amount.

", None)) -#endif // QT_CONFIG(tooltip) - self.preserveMarginLabel.setText(QCoreApplication.translate("mainWindow", u"Preserve Margin %", None)) - self.croppingPowerLabel.setText(QCoreApplication.translate("mainWindow", u"Cropping power:", None)) -#if QT_CONFIG(tooltip) - self.convertButton.setToolTip(QCoreApplication.translate("mainWindow", u"

Shift+Click to select the output directory for this list.

", None)) -#endif // QT_CONFIG(tooltip) - self.convertButton.setText(QCoreApplication.translate("mainWindow", u"Convert", None)) - self.clearButton.setText(QCoreApplication.translate("mainWindow", u"Clear list", None)) -#if QT_CONFIG(tooltip) - self.deviceBox.setToolTip(QCoreApplication.translate("mainWindow", u"

Target device.

", None)) -#endif // QT_CONFIG(tooltip) -#if QT_CONFIG(tooltip) - self.fileButton.setToolTip(QCoreApplication.translate("mainWindow", u"

Add CBR, CBZ, CB7 or PDF file to queue.

", None)) -#endif // QT_CONFIG(tooltip) - self.fileButton.setText(QCoreApplication.translate("mainWindow", u"Add input file(s)", None)) -#if QT_CONFIG(tooltip) - self.directoryButton.setToolTip(QCoreApplication.translate("mainWindow", u"

Add directory containing JPG, PNG or GIF files to queue.
CBR, CBZ and CB7 files inside will not be processed!

", None)) -#endif // QT_CONFIG(tooltip) - self.directoryButton.setText(QCoreApplication.translate("mainWindow", u"Add input folder(s)", None)) -#if QT_CONFIG(tooltip) - self.formatBox.setToolTip(QCoreApplication.translate("mainWindow", u"

Output format.

", None)) -#endif // QT_CONFIG(tooltip) -#if QT_CONFIG(tooltip) - self.chunkSizeWidget.setToolTip(QCoreApplication.translate("mainWindow", u"

Warning: chunk size greater than default may cause
performance/battery issues, especially on older devices.

", None)) -#endif // QT_CONFIG(tooltip) - self.chunkSizeLabel.setText(QCoreApplication.translate("mainWindow", u"Chunk size MB:", None)) - self.chunkSizeWarnLabel.setText(QCoreApplication.translate("mainWindow", u"Greater than default may cause performance issues on older ereaders.", None)) + self.jpegQualityLabel.setText(QCoreApplication.translate("mainWindow", u"JPEG Quality:", None)) # retranslateUi From aa5f4991dde1766e8efdff3666078e72ea0c1034 Mon Sep 17 00:00:00 2001 From: Alex Xu Date: Mon, 29 Dec 2025 10:52:34 -0800 Subject: [PATCH 011/107] Bump version to 9.4.0 --- kindlecomicconverter/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kindlecomicconverter/__init__.py b/kindlecomicconverter/__init__.py index 1431e2f..f25bc4f 100644 --- a/kindlecomicconverter/__init__.py +++ b/kindlecomicconverter/__init__.py @@ -1,4 +1,4 @@ -__version__ = '9.3.8' +__version__ = '9.4.0' __license__ = 'ISC' __copyright__ = '2012-2022, Ciro Mattia Gonano , Pawel Jastrzebski , darodi' __docformat__ = 'restructuredtext en' From af189ed2656055a50e5a11565ce2a9e34166b1da Mon Sep 17 00:00:00 2001 From: Alex Xu Date: Thu, 1 Jan 2026 22:26:04 -0800 Subject: [PATCH 012/107] add Kindle 1920 profiles (7.4 Scribe behavior) (#1206) --- kindlecomicconverter/KCC_gui.py | 8 ++++++++ kindlecomicconverter/comic2ebook.py | 8 ++++++-- kindlecomicconverter/image.py | 6 ++++-- 3 files changed, 18 insertions(+), 4 deletions(-) diff --git a/kindlecomicconverter/KCC_gui.py b/kindlecomicconverter/KCC_gui.py index f17d13a..504ab76 100644 --- a/kindlecomicconverter/KCC_gui.py +++ b/kindlecomicconverter/KCC_gui.py @@ -1189,6 +1189,12 @@ class KCCGUI(KCC_ui.Ui_mainWindow): 'DefaultUpscale': True, 'ForceColor': False, 'Label': 'KPW34'}, "Kindle Voyage": {'PVOptions': True, 'ForceExpert': False, 'DefaultFormat': 0, 'DefaultUpscale': True, 'ForceColor': False, 'Label': 'KV'}, + "Kindle 1860x1920": { + 'PVOptions': True, 'ForceExpert': False, 'DefaultFormat': 0, 'DefaultUpscale': False, 'ForceColor': False, 'Label': 'KS1860', + }, + "Kindle 1920x1920": { + 'PVOptions': True, 'ForceExpert': False, 'DefaultFormat': 0, 'DefaultUpscale': False, 'ForceColor': False, 'Label': 'KS1920', + }, "Kindle Scribe 1/2": { 'PVOptions': True, 'ForceExpert': False, 'DefaultFormat': 0, 'DefaultUpscale': False, 'ForceColor': False, 'Label': 'KS', }, @@ -1274,9 +1280,11 @@ class KCCGUI(KCC_ui.Ui_mainWindow): profilesGUI = [ "Kindle Scribe Colorsoft", "Kindle Scribe 3", + "Kindle 1920x1920", "Kindle Colorsoft", "Kindle Paperwhite 12", "Kindle Scribe 1/2", + "Kindle 1860x1920", "Kindle Paperwhite 11", "Kindle 11", "Kindle Oasis 9/10", diff --git a/kindlecomicconverter/comic2ebook.py b/kindlecomicconverter/comic2ebook.py index 3c79bcb..914f403 100755 --- a/kindlecomicconverter/comic2ebook.py +++ b/kindlecomicconverter/comic2ebook.py @@ -1486,6 +1486,12 @@ 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 + if options.kindle_scribe_azw3: + options.profileData = list(image.ProfileData.Profiles[options.profile]) + options.profileData[1] = list(options.profileData[1]) + options.profileData[1][0] = min(1920, options.profileData[1][0]) return options @@ -1562,8 +1568,6 @@ def makeBook(source, qtgui=None, job_progress=''): GUI.progressBarTick.emit('1') else: checkTools(source) - 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 checkPre(source) print(f"{job_progress}Preparing source images...") path = getWorkFolder(source) diff --git a/kindlecomicconverter/image.py b/kindlecomicconverter/image.py index 3199c23..f2383ff 100755 --- a/kindlecomicconverter/image.py +++ b/kindlecomicconverter/image.py @@ -101,10 +101,12 @@ class ProfileData: 'KO': ("Kindle Oasis 2/3/Paperwhite 12", (1264, 1680), Palette16, 1.0), 'K11': ("Kindle 11", (1072, 1448), Palette16, 1.0), 'KPW5': ("Kindle Paperwhite 5/Signature Edition", (1236, 1648), Palette16, 1.0), + 'KS1860': ("Kindle 1920", (1860, 1920), Palette16, 1.0), + 'KS1920': ("Kindle 1920", (1920, 1920), Palette16, 1.0), 'KS': ("Kindle Scribe 1/2", (1860, 2480), Palette16, 1.0), 'KCS': ("Kindle Colorsoft", (1264, 1680), Palette16, 1.0), - 'KS3': ("Kindle Scribe 3", (1920, 2640), Palette16, 1.0), - 'KSCS': ("Kindle Scribe Colorsoft", (1920, 2640), Palette16, 1.0), + 'KS3': ("Kindle Scribe 3", (1980, 2640), Palette16, 1.0), + 'KSCS': ("Kindle Scribe Colorsoft", (1980, 2640), Palette16, 1.0), } ProfilesKindle = { From d6b0e43d70d274ded5a332feaab6a41ea6337e7c Mon Sep 17 00:00:00 2001 From: Alex Xu Date: Thu, 1 Jan 2026 22:26:23 -0800 Subject: [PATCH 013/107] Bump version to 9.4.1 --- kindlecomicconverter/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kindlecomicconverter/__init__.py b/kindlecomicconverter/__init__.py index f25bc4f..f9e9a9b 100644 --- a/kindlecomicconverter/__init__.py +++ b/kindlecomicconverter/__init__.py @@ -1,4 +1,4 @@ -__version__ = '9.4.0' +__version__ = '9.4.1' __license__ = 'ISC' __copyright__ = '2012-2022, Ciro Mattia Gonano , Pawel Jastrzebski , darodi' __docformat__ = 'restructuredtext en' From 8e42fc11629f6682cf4b4db84510dc8b73f76f44 Mon Sep 17 00:00:00 2001 From: Alex Xu Date: Thu, 1 Jan 2026 22:55:54 -0800 Subject: [PATCH 014/107] remove padding from output folder GUI (#1207) --- gui/KCC.ui | 12 ++++++++++++ kindlecomicconverter/KCC_ui.py | 1 + 2 files changed, 13 insertions(+) diff --git a/gui/KCC.ui b/gui/KCC.ui index 431873f..e2be86d 100644 --- a/gui/KCC.ui +++ b/gui/KCC.ui @@ -828,6 +828,18 @@ + + 0 + + + 0 + + + 0 + + + 0 + diff --git a/kindlecomicconverter/KCC_ui.py b/kindlecomicconverter/KCC_ui.py index daa5092..a9c7ecc 100644 --- a/kindlecomicconverter/KCC_ui.py +++ b/kindlecomicconverter/KCC_ui.py @@ -438,6 +438,7 @@ class Ui_mainWindow(object): self.outputFolderWidget.setObjectName(u"outputFolderWidget") self.horizontalLayout_3 = QHBoxLayout(self.outputFolderWidget) self.horizontalLayout_3.setObjectName(u"horizontalLayout_3") + self.horizontalLayout_3.setContentsMargins(0, 0, 0, 0) self.defaultOutputFolderBox = QCheckBox(self.outputFolderWidget) self.defaultOutputFolderBox.setObjectName(u"defaultOutputFolderBox") sizePolicy.setHeightForWidth(self.defaultOutputFolderBox.sizePolicy().hasHeightForWidth()) From 7897627c43b5dfb2137d6695e41530a140eb72bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=83=95=E3=82=A3=E3=83=AB=E3=82=BF=E3=83=BC=E3=83=9A?= =?UTF-8?q?=E3=83=BC=E3=83=91=E3=83=BC?= <76888457+filterpaper@users.noreply.github.com> Date: Tue, 6 Jan 2026 12:52:53 +0800 Subject: [PATCH 015/107] Preserve input file order using list instead of set (#1209) * Preserve input file order using list instead of set * Simplify list comprehension and fix append for sources * Remove redundant list() conversion --- kindlecomicconverter/comic2ebook.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/kindlecomicconverter/comic2ebook.py b/kindlecomicconverter/comic2ebook.py index 914f403..1dd6c10 100755 --- a/kindlecomicconverter/comic2ebook.py +++ b/kindlecomicconverter/comic2ebook.py @@ -65,16 +65,16 @@ def main(argv=None): parser.print_help() return 0 if sys.platform.startswith('win'): - sources = set([source for option in options.input for source in glob(escape(option))]) + sources = [source for option in options.input for source in glob(escape(option))] else: - sources = set(options.input) + sources = options.input if len(sources) == 0: print('No matching files found.') return 1 if options.filefusion: fusion_path = makeFusion(list(sources)) sources.clear() - sources.add(fusion_path) + sources.append(fusion_path) for source in sources: source = source.rstrip('\\').rstrip('/') options = copy(args) From 42d94d820216246e983b6e6a1dabf76ab00628e7 Mon Sep 17 00:00:00 2001 From: Alex Xu Date: Thu, 8 Jan 2026 18:21:32 -0800 Subject: [PATCH 016/107] increase max Windows path length from 180 to 220 (#1211) --- kindlecomicconverter/comic2ebook.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kindlecomicconverter/comic2ebook.py b/kindlecomicconverter/comic2ebook.py index 1dd6c10..855e05a 100755 --- a/kindlecomicconverter/comic2ebook.py +++ b/kindlecomicconverter/comic2ebook.py @@ -1125,7 +1125,7 @@ def chunk_directory(path): for root, _, files in os.walk(os.path.join(path, 'OEBPS', 'Images')): for f in files: # Windows MAX_LEN = 260 plus some buffer - if os.name == 'nt' and len(os.path.join(root, f)) > 180: + if os.name == 'nt' and len(os.path.join(root, f)) > 220: flattenTree(os.path.join(path, 'OEBPS', 'Images')) level = 1 break From 5cbc07e65d6f36c74c76165df790bf4ee38fe6e5 Mon Sep 17 00:00:00 2001 From: Alex Xu Date: Thu, 8 Jan 2026 18:21:45 -0800 Subject: [PATCH 017/107] increase webtoon max height (#1213) --- kindlecomicconverter/comic2panel.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kindlecomicconverter/comic2panel.py b/kindlecomicconverter/comic2panel.py index f45cc49..3cb2e9f 100644 --- a/kindlecomicconverter/comic2panel.py +++ b/kindlecomicconverter/comic2panel.py @@ -62,7 +62,7 @@ def mergeDirectory(work): imagesValid.append(i[0]) # Silently drop directories that contain too many images # 131072 = GIMP_MAX_IMAGE_SIZE / 4 - if targetHeight > 131072 * 3: + if targetHeight > 131072 * 4: raise RuntimeError(f'Image too tall at {targetHeight} pixels. {targetWidth} pixels wide. Try using separate chapter folders or file fusion.') result = Image.new('RGB', (targetWidth, targetHeight)) y = 0 From be86bcbf6a46fca2b93123878efeac6029405d40 Mon Sep 17 00:00:00 2001 From: Alex Xu Date: Thu, 8 Jan 2026 20:20:10 -0800 Subject: [PATCH 018/107] Add macOS 15 unsigned instructions --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 8e90038..5e4424e 100644 --- a/README.md +++ b/README.md @@ -98,7 +98,7 @@ There are also legacy macOS 10.14+ and Windows 7 experimental versions available The `c2e` and `c2p` versions are command line tools for power users. -On Mac, right click open to get past the security warning. +On Mac, follow: https://support.apple.com/guide/mac-help/open-a-mac-app-from-an-unknown-developer-mh40616/mac For flatpak, Docker, and AppImage versions, refer to the wiki: https://github.com/ciromattia/kcc/wiki/Installation From 6d7a635c3dbe77c670e894dd516f55a7e4711454 Mon Sep 17 00:00:00 2001 From: Alex Xu Date: Fri, 9 Jan 2026 13:46:16 -0800 Subject: [PATCH 019/107] Fix Kindle Scribe 2025 resolutions (#1217) --- kindlecomicconverter/image.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/kindlecomicconverter/image.py b/kindlecomicconverter/image.py index f2383ff..e27ec43 100755 --- a/kindlecomicconverter/image.py +++ b/kindlecomicconverter/image.py @@ -105,8 +105,8 @@ class ProfileData: 'KS1920': ("Kindle 1920", (1920, 1920), Palette16, 1.0), 'KS': ("Kindle Scribe 1/2", (1860, 2480), Palette16, 1.0), 'KCS': ("Kindle Colorsoft", (1264, 1680), Palette16, 1.0), - 'KS3': ("Kindle Scribe 3", (1980, 2640), Palette16, 1.0), - 'KSCS': ("Kindle Scribe Colorsoft", (1980, 2640), Palette16, 1.0), + 'KS3': ("Kindle Scribe 3", (1986, 2648), Palette16, 1.0), + 'KSCS': ("Kindle Scribe Colorsoft", (1986, 2648), Palette16, 1.0), } ProfilesKindle = { From c99444b96a7f53f739d2449a2aaa22e8008ce221 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20L=C3=A1zaro=20Costa?= Date: Sun, 11 Jan 2026 01:53:40 +0100 Subject: [PATCH 020/107] docs: Update Profiles (#1218) * Update README Profiles Align the README Profiles section with the profile definitions in image.py. - Add missing Kindle Scribe 3, Colorsoft and reMarkable profiles - Update gamma values to default (from 1.8 to 1.0) * Update profile name KS1860 Fix typo (1920 -> 1860) --- README.md | 70 +++++++++++++++++++---------------- kindlecomicconverter/image.py | 2 +- 2 files changed, 39 insertions(+), 33 deletions(-) diff --git a/README.md b/README.md index 5e4424e..a58bff5 100644 --- a/README.md +++ b/README.md @@ -177,38 +177,44 @@ sudo apt-get install python3 p7zip-full python3-pil python3-psutil python3-slugi ### Profiles: ``` - 'K1': ("Kindle 1", (600, 670), Palette4, 1.8), - 'K11': ("Kindle 11", (1072, 1448), Palette16, 1.8), - 'K2': ("Kindle 2", (600, 670), Palette15, 1.8), - 'K34': ("Kindle Keyboard/Touch", (600, 800), Palette16, 1.8), - 'K57': ("Kindle 5/7", (600, 800), Palette16, 1.8), - 'K810': ("Kindle 8/10", (600, 800), Palette16, 1.8), - 'KDX': ("Kindle DX/DXG", (824, 1000), Palette16, 1.8), - 'KPW': ("Kindle Paperwhite 1/2", (758, 1024), Palette16, 1.8), - 'KV': ("Kindle Voyage, (1072, 1448), Palette16, 1.8), - 'KPW34': ("Kindle Paperwhite 3/4/Oasis", (1072, 1448), Palette16, 1.8), - 'KPW5': ("Kindle Paperwhite 5/Signature Edition", (1236, 1648), Palette16, 1.8), - 'KO': ("Kindle Oasis 2/3/Paperwhite 12/Colorsoft 12", (1264, 1680), Palette16, 1.8), - 'KS': ("Kindle Scribe", (1860, 2480), Palette16, 1.8), - 'KoMT': ("Kobo Mini/Touch", (600, 800), Palette16, 1.8), - 'KoG': ("Kobo Glo", (768, 1024), Palette16, 1.8), - 'KoGHD': ("Kobo Glo HD", (1072, 1448), Palette16, 1.8), - 'KoA': ("Kobo Aura", (758, 1024), Palette16, 1.8), - 'KoAHD': ("Kobo Aura HD", (1080, 1440), Palette16, 1.8), - 'KoAH2O': ("Kobo Aura H2O", (1080, 1430), Palette16, 1.8), - 'KoAO': ("Kobo Aura ONE", (1404, 1872), Palette16, 1.8), - 'KoN': ("Kobo Nia", (758, 1024), Palette16, 1.8), - 'KoC': ("Kobo Clara HD/Kobo Clara 2E", (1072, 1448), Palette16, 1.8), - 'KoCC': ("Kobo Clara Colour", (1072, 1448), Palette16, 1.8), - 'KoL': ("Kobo Libra H2O/Kobo Libra 2", (1264, 1680), Palette16, 1.8), - 'KoLC': ("Kobo Libra Colour", (1264, 1680), Palette16, 1.8), - 'KoF': ("Kobo Forma", (1440, 1920), Palette16, 1.8), - 'KoS': ("Kobo Sage", (1440, 1920), Palette16, 1.8), - 'KoE': ("Kobo Elipsa", (1404, 1872), Palette16, 1.8), - 'Rmk1': ("reMarkable 1", (1404, 1872), Palette16, 1.8), - 'Rmk2': ("reMarkable 2", (1404, 1872), Palette16, 1.8), - 'RmkPP': ("reMarkable Paper Pro", (1620, 2160), Palette16, 1.8), - 'OTHER': ("Other", (0, 0), Palette16, 1.8), + 'K1': ("Kindle 1", (600, 670), Palette4, 1.0), + 'K2': ("Kindle 2", (600, 670), Palette15, 1.0), + 'K11': ("Kindle 11", (1072, 1448), Palette16, 1.0), + 'K34': ("Kindle Keyboard/Touch", (600, 800), Palette16, 1.0), + 'K57': ("Kindle 5/7", (600, 800), Palette16, 1.0), + 'K810': ("Kindle 8/10", (600, 800), Palette16, 1.0), + 'KDX': ("Kindle DX/DXG", (824, 1000), Palette16, 1.0), + 'KPW': ("Kindle Paperwhite 1/2", (758, 1024), Palette16, 1.0), + 'KV': ("Kindle Voyage", (1072, 1448), Palette16, 1.0), + 'KPW34': ("Kindle Paperwhite 3/4", (1072, 1448), Palette16, 1.0), + 'KPW5': ("Kindle Paperwhite 5/Signature Edition", (1236, 1648), Palette16, 1.0), + 'KO': ("Kindle Oasis 2/3/Paperwhite 12", (1264, 1680), Palette16, 1.0), + 'KCS': ("Kindle Colorsoft", (1264, 1680), Palette16, 1.0), + 'KS1860': ("Kindle 1860", (1860, 1920), Palette16, 1.0), + 'KS1920': ("Kindle 1920", (1920, 1920), Palette16, 1.0), + 'KS': ("Kindle Scribe 1/2", (1860, 2480), Palette16, 1.0), + 'KS3': ("Kindle Scribe 3", (1986, 2648), Palette16, 1.0), + 'KSCS': ("Kindle Scribe Colorsoft", (1986, 2648), Palette16, 1.0), + 'KoMT': ("Kobo Mini/Touch", (600, 800), Palette16, 1.0), + 'KoG': ("Kobo Glo", (768, 1024), Palette16, 1.0), + 'KoGHD': ("Kobo Glo HD", (1072, 1448), Palette16, 1.0), + 'KoA': ("Kobo Aura", (758, 1024), Palette16, 1.0), + 'KoAHD': ("Kobo Aura HD", (1080, 1440), Palette16, 1.0), + 'KoAH2O': ("Kobo Aura H2O", (1080, 1430), Palette16, 1.0), + 'KoAO': ("Kobo Aura ONE", (1404, 1872), Palette16, 1.0), + 'KoN': ("Kobo Nia", (758, 1024), Palette16, 1.0), + 'KoC': ("Kobo Clara HD/Kobo Clara 2E", (1072, 1448), Palette16, 1.0), + 'KoCC': ("Kobo Clara Colour", (1072, 1448), Palette16, 1.0), + 'KoL': ("Kobo Libra H2O/Kobo Libra 2", (1264, 1680), Palette16, 1.0), + 'KoLC': ("Kobo Libra Colour", (1264, 1680), Palette16, 1.0), + 'KoF': ("Kobo Forma", (1440, 1920), Palette16, 1.0), + 'KoS': ("Kobo Sage", (1440, 1920), Palette16, 1.0), + 'KoE': ("Kobo Elipsa", (1404, 1872), Palette16, 1.0), + 'Rmk1': ("reMarkable 1", (1404, 1872), Palette16, 1.0), + 'Rmk2': ("reMarkable 2", (1404, 1872), Palette16, 1.0), + 'RmkPP': ("reMarkable Paper Pro", (1620, 2160), Palette16, 1.0), + 'RmkPPMove': ("reMarkable Paper Pro Move", (954, 1696), Palette16, 1.0), + 'OTHER': ("Other", (0, 0), Palette16, 1.0), ``` ### Standalone `kcc-c2e.py` usage: diff --git a/kindlecomicconverter/image.py b/kindlecomicconverter/image.py index e27ec43..769025c 100755 --- a/kindlecomicconverter/image.py +++ b/kindlecomicconverter/image.py @@ -101,7 +101,7 @@ class ProfileData: 'KO': ("Kindle Oasis 2/3/Paperwhite 12", (1264, 1680), Palette16, 1.0), 'K11': ("Kindle 11", (1072, 1448), Palette16, 1.0), 'KPW5': ("Kindle Paperwhite 5/Signature Edition", (1236, 1648), Palette16, 1.0), - 'KS1860': ("Kindle 1920", (1860, 1920), Palette16, 1.0), + 'KS1860': ("Kindle 1860", (1860, 1920), Palette16, 1.0), 'KS1920': ("Kindle 1920", (1920, 1920), Palette16, 1.0), 'KS': ("Kindle Scribe 1/2", (1860, 2480), Palette16, 1.0), 'KCS': ("Kindle Colorsoft", (1264, 1680), Palette16, 1.0), From 7de212dca3de83001e6a95510ef85cbf6f1bc0e8 Mon Sep 17 00:00:00 2001 From: Alex Xu Date: Sat, 10 Jan 2026 20:40:09 -0800 Subject: [PATCH 021/107] move fixed resolution Kindle profiles down --- kindlecomicconverter/KCC_gui.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/kindlecomicconverter/KCC_gui.py b/kindlecomicconverter/KCC_gui.py index 504ab76..464d8fb 100644 --- a/kindlecomicconverter/KCC_gui.py +++ b/kindlecomicconverter/KCC_gui.py @@ -1280,11 +1280,9 @@ class KCCGUI(KCC_ui.Ui_mainWindow): profilesGUI = [ "Kindle Scribe Colorsoft", "Kindle Scribe 3", - "Kindle 1920x1920", "Kindle Colorsoft", "Kindle Paperwhite 12", "Kindle Scribe 1/2", - "Kindle 1860x1920", "Kindle Paperwhite 11", "Kindle 11", "Kindle Oasis 9/10", @@ -1304,6 +1302,8 @@ class KCCGUI(KCC_ui.Ui_mainWindow): "Separator", "Other", "Separator", + "Kindle 1920x1920", + "Kindle 1860x1920", "Kindle 8/10", "Kindle Oasis 8", "Kindle Paperwhite 7/10", From 4baca03214922fd70b43b896dc51120e4106a7a7 Mon Sep 17 00:00:00 2001 From: Alex Xu Date: Sun, 11 Jan 2026 13:19:41 -0800 Subject: [PATCH 022/107] Bump version to 9.4.2 --- kindlecomicconverter/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kindlecomicconverter/__init__.py b/kindlecomicconverter/__init__.py index f9e9a9b..bbbeaa0 100644 --- a/kindlecomicconverter/__init__.py +++ b/kindlecomicconverter/__init__.py @@ -1,4 +1,4 @@ -__version__ = '9.4.1' +__version__ = '9.4.2' __license__ = 'ISC' __copyright__ = '2012-2022, Ciro Mattia Gonano , Pawel Jastrzebski , darodi' __docformat__ = 'restructuredtext en' From f5fd2bb7fe80d1d44ab622c59a2d7a67e79995e8 Mon Sep 17 00:00:00 2001 From: Alex Xu Date: Tue, 20 Jan 2026 20:47:56 -0800 Subject: [PATCH 023/107] fix cropping divide by zero (#1220) --- kindlecomicconverter/page_number_crop_alg.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/kindlecomicconverter/page_number_crop_alg.py b/kindlecomicconverter/page_number_crop_alg.py index f92bcb9..78f182c 100644 --- a/kindlecomicconverter/page_number_crop_alg.py +++ b/kindlecomicconverter/page_number_crop_alg.py @@ -160,6 +160,8 @@ def ignore_pixels_near_edge(bw_img): 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) From f63387cae46dc778bcd08e480cd2aadca973f891 Mon Sep 17 00:00:00 2001 From: Alex Xu Date: Tue, 20 Jan 2026 20:48:18 -0800 Subject: [PATCH 024/107] remove corrupt image checking (#1221) * remove corrupt image checking Removed image verification step before copying the image. * Update image.py --- kindlecomicconverter/image.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kindlecomicconverter/image.py b/kindlecomicconverter/image.py index 769025c..5ba738d 100755 --- a/kindlecomicconverter/image.py +++ b/kindlecomicconverter/image.py @@ -157,7 +157,7 @@ class ComicPageParser: # Detect corruption in source image, let caller catch any exceptions triggered. srcImgPath = os.path.join(source[0], source[1]) - Image.open(srcImgPath).verify() + # Image.open(srcImgPath).verify() with Image.open(srcImgPath) as im: self.image = im.copy() From 9a4143ce62a4444dd4a47178072ec2371c3c17f2 Mon Sep 17 00:00:00 2001 From: Alex Xu Date: Sun, 25 Jan 2026 13:41:43 -0800 Subject: [PATCH 025/107] Add legacy pdf image extract option (#1225) --- README.md | 1 + gui/KCC.ui | 12 +++++ kindlecomicconverter/KCC_gui.py | 3 ++ kindlecomicconverter/KCC_ui.py | 11 ++++ kindlecomicconverter/comic2ebook.py | 9 ++++ kindlecomicconverter/pdfjpgextract.py | 75 +++++++++++++++++++++++++++ 6 files changed, 111 insertions(+) create mode 100644 kindlecomicconverter/pdfjpgextract.py diff --git a/README.md b/README.md index a58bff5..575ffe9 100644 --- a/README.md +++ b/README.md @@ -238,6 +238,7 @@ MAIN: PROCESSING: -n, --noprocessing Do not modify image and ignore any profile or processing option + --pdfextract Use legacy PDF image extraction method from KCC 8 and earlier. -u, --upscale Resize images smaller than device's resolution -s, --stretch Stretch images to device's resolution -r SPLITTER, --splitter SPLITTER diff --git a/gui/KCC.ui b/gui/KCC.ui index e2be86d..63dd857 100644 --- a/gui/KCC.ui +++ b/gui/KCC.ui @@ -896,6 +896,18 @@ Higher values are larger and higher quality, and may resolve blank page issues.< + + + + Use the PDF image extraction method from KCC 8 and earlier. + +Useful for really weird PDFs. + + + PDF Legacy Extract + + + diff --git a/kindlecomicconverter/KCC_gui.py b/kindlecomicconverter/KCC_gui.py index 464d8fb..46650fa 100644 --- a/kindlecomicconverter/KCC_gui.py +++ b/kindlecomicconverter/KCC_gui.py @@ -327,6 +327,8 @@ class WorkerThread(QThread): options.maximizestrips = True if GUI.disableProcessingBox.isChecked(): options.noprocessing = True + if GUI.pdfExtractBox.isChecked(): + options.pdfextract = True if GUI.metadataTitleBox.checkState() == Qt.CheckState.PartiallyChecked: options.metadatatitle = 1 elif GUI.metadataTitleBox.checkState() == Qt.CheckState.Checked: @@ -1032,6 +1034,7 @@ class KCCGUI(KCC_ui.Ui_mainWindow): 'colorBox': GUI.colorBox.checkState(), 'eraseRainbowBox': GUI.eraseRainbowBox.checkState(), 'disableProcessingBox': GUI.disableProcessingBox.checkState(), + 'pdfExtractBox': GUI.pdfExtractBox.checkState(), 'metadataTitleBox': GUI.metadataTitleBox.checkState(), 'mozJpegBox': GUI.mozJpegBox.checkState(), 'jpegQualityBox': GUI.jpegQualityBox.checkState(), diff --git a/kindlecomicconverter/KCC_ui.py b/kindlecomicconverter/KCC_ui.py index a9c7ecc..3574d15 100644 --- a/kindlecomicconverter/KCC_ui.py +++ b/kindlecomicconverter/KCC_ui.py @@ -462,6 +462,11 @@ class Ui_mainWindow(object): self.gridLayout_2.addWidget(self.jpegQualityBox, 8, 0, 1, 1) + self.pdfExtractBox = QCheckBox(self.optionWidget) + self.pdfExtractBox.setObjectName(u"pdfExtractBox") + + self.gridLayout_2.addWidget(self.pdfExtractBox, 9, 0, 1, 1) + self.gridLayout.addWidget(self.optionWidget, 5, 0, 1, 2) @@ -733,6 +738,12 @@ class Ui_mainWindow(object): "Higher values are larger and higher quality, and may resolve blank page issues.", None)) #endif // QT_CONFIG(tooltip) self.jpegQualityBox.setText(QCoreApplication.translate("mainWindow", u"Custom JPEG Quality", None)) +#if QT_CONFIG(tooltip) + self.pdfExtractBox.setToolTip(QCoreApplication.translate("mainWindow", u"Use the PDF image extraction method from KCC 8 and earlier.\n" +"\n" +"Useful for really weird PDFs.", None)) +#endif // QT_CONFIG(tooltip) + self.pdfExtractBox.setText(QCoreApplication.translate("mainWindow", u"PDF Legacy Extract", None)) self.gammaLabel.setText(QCoreApplication.translate("mainWindow", u"Gamma: Auto", None)) self.jpegQualityLabel.setText(QCoreApplication.translate("mainWindow", u"JPEG Quality:", None)) # retranslateUi diff --git a/kindlecomicconverter/comic2ebook.py b/kindlecomicconverter/comic2ebook.py index 855e05a..11da62e 100755 --- a/kindlecomicconverter/comic2ebook.py +++ b/kindlecomicconverter/comic2ebook.py @@ -48,6 +48,7 @@ from .comicarchive import SEVENZIP, available_archive_tools from . import comic2panel from . import image from . import comicarchive +from . import pdfjpgextract from . import dualmetafix from . import metadata from . import kindle @@ -875,6 +876,12 @@ def getWorkFolder(afile, workdir=None): os.makedirs(fullPath) path = workdir sanitizePermissions(path) + if options.pdfextract: + pdf = pdfjpgextract.PdfJpgExtract(afile, fullPath) + njpg = pdf.extract() + if njpg == 0: + raise UserWarning("Failed to extract images from PDF file.") + return workdir target_height = options.profileData[1][1] if options.cropping == 1: target_height = target_height + target_height*0.20 #Account for possible margin at the top and bottom @@ -1342,6 +1349,8 @@ def makeParser(): processing_options.add_argument("-n", "--noprocessing", action="store_true", dest="noprocessing", default=False, help="Do not modify image and ignore any profile or processing option") + processing_options.add_argument("--pdfextract", action="store_true", dest="pdfextract", default=False, + help="Use the legacy PDF image extraction method from KCC 8 and earlier") processing_options.add_argument("-u", "--upscale", action="store_true", dest="upscale", default=False, help="Resize images smaller than device's resolution") processing_options.add_argument("-s", "--stretch", action="store_true", dest="stretch", default=False, diff --git a/kindlecomicconverter/pdfjpgextract.py b/kindlecomicconverter/pdfjpgextract.py new file mode 100644 index 0000000..751a68e --- /dev/null +++ b/kindlecomicconverter/pdfjpgextract.py @@ -0,0 +1,75 @@ +# -*- coding: utf-8 -*- +# +# Copyright (c) 2012-2014 Ciro Mattia Gonano +# Copyright (c) 2013-2019 Pawel Jastrzebski +# +# Based upon the code snippet by Ned Batchelder +# (http://nedbatchelder.com/blog/200712/extracting_jpgs_from_pdfs.html) +# +# Permission to use, copy, modify, and/or distribute this software for +# any purpose with or without fee is hereby granted, provided that the +# above copyright notice and this permission notice appear in all +# copies. +# +# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL +# WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED +# WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE +# AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL +# DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA +# OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER +# TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +# PERFORMANCE OF THIS SOFTWARE. +# + +import os + +# skip stray images a few pixels in size in some PDFs +# typical images are many thousands in length +# https://github.com/ciromattia/kcc/pull/546 +STRAY_IMAGE_LENGTH_THRESHOLD = 300 + + +class PdfJpgExtract: + def __init__(self, fname, fullPath): + self.fname = fname + self.path = fullPath + + def getPath(self): + return self.path + + def extract(self): + pdf = open(self.fname, "rb").read() + startmark = b"\xff\xd8" + startfix = 0 + endmark = b"\xff\xd9" + endfix = 2 + i = 0 + njpg = 0 + while True: + istream = pdf.find(b"stream", i) + if istream < 0: + break + istart = pdf.find(startmark, istream, istream + 20) + if istart < 0: + i = istream + 20 + continue + iend = pdf.find(b"endstream", istart) + if iend < 0: + raise Exception("Didn't find end of stream!") + iend = pdf.find(endmark, iend - 20) + if iend < 0: + raise Exception("Didn't find end of JPG!") + istart += startfix + iend += endfix + i = iend + + if iend - istart < STRAY_IMAGE_LENGTH_THRESHOLD: + continue + + jpg = pdf[istart:iend] + jpgfile = open(os.path.join(self.path, "jpg%d.jpg" % njpg), "wb") + jpgfile.write(jpg) + jpgfile.close() + njpg += 1 + + return njpg From 667d702b8aa96eb74033dca00a58451d4de5e83e Mon Sep 17 00:00:00 2001 From: Alex Xu Date: Sun, 25 Jan 2026 13:45:15 -0800 Subject: [PATCH 026/107] Kindle Scribe 2025 warning --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 575ffe9..c5d1f6e 100644 --- a/README.md +++ b/README.md @@ -14,6 +14,8 @@ with proper fixed layout support. Supported input formats include JPG/PNG image files in folders, archives, or PDFs. Supported output formats include MOBI/AZW3, EPUB, KEPUB, CBZ, and PDF. +**WARNING**: Kindle Scribe 2025 support may not be possible. Does not work well currently. + **NEW**: PDF output is now supported for direct conversion to reMarkable devices! When using a reMarkable profile (Rmk1, Rmk2, RmkPP), the format automatically defaults to PDF for optimal compatibility with your device's native PDF reader. From fbd5980b9bee6e58f397796d842224ccd417fb3b Mon Sep 17 00:00:00 2001 From: Alex Xu Date: Mon, 26 Jan 2026 09:41:49 -0800 Subject: [PATCH 027/107] add Kindle 1240x1860 profile (#1227) --- kindlecomicconverter/KCC_gui.py | 4 ++++ kindlecomicconverter/image.py | 1 + 2 files changed, 5 insertions(+) diff --git a/kindlecomicconverter/KCC_gui.py b/kindlecomicconverter/KCC_gui.py index 46650fa..da86339 100644 --- a/kindlecomicconverter/KCC_gui.py +++ b/kindlecomicconverter/KCC_gui.py @@ -1198,6 +1198,9 @@ class KCCGUI(KCC_ui.Ui_mainWindow): "Kindle 1920x1920": { 'PVOptions': True, 'ForceExpert': False, 'DefaultFormat': 0, 'DefaultUpscale': False, 'ForceColor': False, 'Label': 'KS1920', }, + "Kindle 1240x1860": { + 'PVOptions': True, 'ForceExpert': False, 'DefaultFormat': 0, 'DefaultUpscale': False, 'ForceColor': False, 'Label': 'KS1240', + }, "Kindle Scribe 1/2": { 'PVOptions': True, 'ForceExpert': False, 'DefaultFormat': 0, 'DefaultUpscale': False, 'ForceColor': False, 'Label': 'KS', }, @@ -1307,6 +1310,7 @@ class KCCGUI(KCC_ui.Ui_mainWindow): "Separator", "Kindle 1920x1920", "Kindle 1860x1920", + "Kindle 1240x1860", "Kindle 8/10", "Kindle Oasis 8", "Kindle Paperwhite 7/10", diff --git a/kindlecomicconverter/image.py b/kindlecomicconverter/image.py index 5ba738d..2acb60d 100755 --- a/kindlecomicconverter/image.py +++ b/kindlecomicconverter/image.py @@ -103,6 +103,7 @@ class ProfileData: 'KPW5': ("Kindle Paperwhite 5/Signature Edition", (1236, 1648), Palette16, 1.0), 'KS1860': ("Kindle 1860", (1860, 1920), Palette16, 1.0), 'KS1920': ("Kindle 1920", (1920, 1920), Palette16, 1.0), + 'KS1240': ("Kindle 1240", (1240, 1860), Palette16, 1.0), 'KS': ("Kindle Scribe 1/2", (1860, 2480), Palette16, 1.0), 'KCS': ("Kindle Colorsoft", (1264, 1680), Palette16, 1.0), 'KS3': ("Kindle Scribe 3", (1986, 2648), Palette16, 1.0), From 38acc3bf05d90b22683132cff9504b353b3d7a22 Mon Sep 17 00:00:00 2001 From: Alex Xu Date: Mon, 26 Jan 2026 09:56:44 -0800 Subject: [PATCH 028/107] skip blanks on image based pdfs (#1228) --- kindlecomicconverter/comic2ebook.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/kindlecomicconverter/comic2ebook.py b/kindlecomicconverter/comic2ebook.py index 11da62e..b3010ab 100755 --- a/kindlecomicconverter/comic2ebook.py +++ b/kindlecomicconverter/comic2ebook.py @@ -801,9 +801,7 @@ def extract_page(vector): if len(image_list) > 1: raise UserWarning("mupdf_pdf_extract_page_image() function can be used only with single image pages.") if not image_list: - width, height = int(page.rect.width), int(page.rect.height) - blank_page = Image.new("RGB", (width, height), "white") - blank_page.save(output_path) + continue else: xref = image_list[0][0] d = doc.extract_image(xref) From b0f8f1c63397ee52857165e711888a014a5a6de5 Mon Sep 17 00:00:00 2001 From: Alex Xu Date: Mon, 26 Jan 2026 14:39:05 -0800 Subject: [PATCH 029/107] fix file fusion bugs (#1230) --- kindlecomicconverter/comic2ebook.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/kindlecomicconverter/comic2ebook.py b/kindlecomicconverter/comic2ebook.py index b3010ab..988d3ff 100755 --- a/kindlecomicconverter/comic2ebook.py +++ b/kindlecomicconverter/comic2ebook.py @@ -853,6 +853,7 @@ def mupdf_pdf_process_pages_parallel(filename, output_dir, target_height): def getWorkFolder(afile, workdir=None): if not workdir: workdir = mkdtemp('', 'KCC-') + # workdir = mkdtemp('', 'KCC-', os.path.dirname(afile)) fullPath = os.path.join(workdir, 'OEBPS', 'Images') else: fullPath = workdir @@ -1071,7 +1072,7 @@ def removeNonImages(filetree): raise UserWarning('No images detected, nested archives are not supported.') -def sanitizeTree(filetree): +def sanitizeTree(filetree, prefix='kcc'): chapterNames = {} page = 1 cover_path = None @@ -1081,7 +1082,7 @@ def sanitizeTree(filetree): _, ext = getImageFileName(name) # 9999 page limit - unique_name = f'kcc-{page:04}' + unique_name = f'{prefix}-{page:04}' page += 1 newKey = os.path.join(root, unique_name + ext) @@ -1556,7 +1557,7 @@ def makeFusion(sources: List[str]): else: targetpath = fusion_path.joinpath(source_path.name) getWorkFolder(source, str(targetpath)) - sanitizeTree(targetpath) + sanitizeTree(targetpath, prefix='fusion') # TODO: remove flattenTree when subchapters are supported flattenTree(targetpath) From 56e8e24176272ae0f61d9ba851f6d47e8b686e24 Mon Sep 17 00:00:00 2001 From: Alex Xu Date: Mon, 26 Jan 2026 14:44:36 -0800 Subject: [PATCH 030/107] fix release notes (#1231) --- .github/workflows/package-linux.yml | 2 +- .github/workflows/package-macos.yml | 2 +- .github/workflows/package-osx-legacy.yml | 2 +- .github/workflows/package-windows.yml | 2 +- .github/workflows/package-windows7.yml | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/package-linux.yml b/.github/workflows/package-linux.yml index 72b95a4..d54fb33 100644 --- a/.github/workflows/package-linux.yml +++ b/.github/workflows/package-linux.yml @@ -68,7 +68,7 @@ jobs: if: startsWith(github.ref, 'refs/tags/') with: prerelease: true - generate_release_notes: true + generate_release_notes: false files: | LICENSE.txt *.AppImage* diff --git a/.github/workflows/package-macos.yml b/.github/workflows/package-macos.yml index 66187a6..60fd7b8 100644 --- a/.github/workflows/package-macos.yml +++ b/.github/workflows/package-macos.yml @@ -89,7 +89,7 @@ jobs: if: startsWith(github.ref, 'refs/tags/') with: prerelease: true - generate_release_notes: true + generate_release_notes: false files: | dist/*.dmg - name: Clean up keychain and provisioning profile diff --git a/.github/workflows/package-osx-legacy.yml b/.github/workflows/package-osx-legacy.yml index 086e7f4..5b2a83d 100644 --- a/.github/workflows/package-osx-legacy.yml +++ b/.github/workflows/package-osx-legacy.yml @@ -60,7 +60,7 @@ jobs: if: startsWith(github.ref, 'refs/tags/') with: prerelease: true - generate_release_notes: true + generate_release_notes: false files: | LICENSE.txt dist/*.dmg diff --git a/.github/workflows/package-windows.yml b/.github/workflows/package-windows.yml index 34148a9..75624db 100644 --- a/.github/workflows/package-windows.yml +++ b/.github/workflows/package-windows.yml @@ -73,6 +73,6 @@ jobs: if: startsWith(github.ref, 'refs/tags/') with: prerelease: true - generate_release_notes: true + generate_release_notes: false files: | dist/*.exe diff --git a/.github/workflows/package-windows7.yml b/.github/workflows/package-windows7.yml index 9ea0782..431b577 100644 --- a/.github/workflows/package-windows7.yml +++ b/.github/workflows/package-windows7.yml @@ -55,6 +55,6 @@ jobs: if: startsWith(github.ref, 'refs/tags/') with: prerelease: true - generate_release_notes: true + generate_release_notes: false files: | dist/*.exe From 4b4860b976f7906506de10fee9618a4cfb7e91d2 Mon Sep 17 00:00:00 2001 From: Alex Xu Date: Mon, 26 Jan 2026 14:45:28 -0800 Subject: [PATCH 031/107] Bump to 9.4.3 --- kindlecomicconverter/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kindlecomicconverter/__init__.py b/kindlecomicconverter/__init__.py index bbbeaa0..4ddc0ca 100644 --- a/kindlecomicconverter/__init__.py +++ b/kindlecomicconverter/__init__.py @@ -1,4 +1,4 @@ -__version__ = '9.4.2' +__version__ = '9.4.3' __license__ = 'ISC' __copyright__ = '2012-2022, Ciro Mattia Gonano , Pawel Jastrzebski , darodi' __docformat__ = 'restructuredtext en' From 095694e9cfd0824bc49685983d164f5595ba1cc7 Mon Sep 17 00:00:00 2001 From: Alex Xu Date: Tue, 27 Jan 2026 09:03:01 -0800 Subject: [PATCH 032/107] use bsdtar on linux (#1234) --- kindlecomicconverter/KCC_gui.py | 4 ++-- kindlecomicconverter/comicarchive.py | 5 +++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/kindlecomicconverter/KCC_gui.py b/kindlecomicconverter/KCC_gui.py index da86339..5d525eb 100644 --- a/kindlecomicconverter/KCC_gui.py +++ b/kindlecomicconverter/KCC_gui.py @@ -42,7 +42,7 @@ from raven import Client from tempfile import gettempdir from .shared import HTMLStripper, sanitizeTrace, walkLevel, subprocess_run -from .comicarchive import SEVENZIP, available_archive_tools +from .comicarchive import SEVENZIP, TAR, available_archive_tools from . import __version__ from . import comic2ebook from . import metadata @@ -1358,7 +1358,7 @@ class KCCGUI(KCC_ui.Ui_mainWindow): 'important tips.', 'info') - self.tar = 'tar' in available_archive_tools() + self.tar = TAR in available_archive_tools() self.sevenzip = SEVENZIP in available_archive_tools() if not any([self.tar, self.sevenzip]): self.addMessage('Install 7z (link)' diff --git a/kindlecomicconverter/comicarchive.py b/kindlecomicconverter/comicarchive.py index f3b6caa..0acfc76 100644 --- a/kindlecomicconverter/comicarchive.py +++ b/kindlecomicconverter/comicarchive.py @@ -30,6 +30,7 @@ from .shared import IMAGE_TYPES, subprocess_run EXTRACTION_ERROR = 'Failed to extract archive. Try extracting file outside of KCC.' SEVENZIP = '7zz' if platform.system() == 'Darwin' else '7z' +TAR = 'bsdtar' if platform.system() == 'Linux' else 'tar' class ComicArchive: @@ -73,7 +74,7 @@ class ComicArchive: missing = [] extraction_commands = [ - ['tar', '--exclude', '__MACOSX', '--exclude', '.DS_Store', '--exclude', 'thumbs.db', '--exclude', 'Thumbs.db', '-xf', self.basename, '-C', targetdir], + [TAR, '--exclude', '__MACOSX', '--exclude', '.DS_Store', '--exclude', 'thumbs.db', '--exclude', 'Thumbs.db', '-xf', self.basename, '-C', targetdir], [SEVENZIP, 'x', '-y', '-xr!__MACOSX', '-xr!.DS_Store', '-xr!thumbs.db', '-xr!Thumbs.db', '-o' + targetdir, self.basename], ] @@ -125,7 +126,7 @@ class ComicArchive: def available_archive_tools(): available = [] - for tool in ['tar', SEVENZIP, 'unar', 'unrar']: + for tool in [TAR, SEVENZIP, 'unar', 'unrar']: try: subprocess_run([tool], stdout=PIPE, stderr=STDOUT) available.append(tool) From a344dd73bff7cef7bbc818de554fd2b3d74ab041 Mon Sep 17 00:00:00 2001 From: Alex Xu Date: Wed, 28 Jan 2026 10:44:22 -0800 Subject: [PATCH 033/107] add OS support to beginning --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index c5d1f6e..16aea1e 100644 --- a/README.md +++ b/README.md @@ -13,6 +13,7 @@ Pages display in fullscreen without margins, with proper fixed layout support. Supported input formats include JPG/PNG image files in folders, archives, or PDFs. Supported output formats include MOBI/AZW3, EPUB, KEPUB, CBZ, and PDF. +KCC runs on Windows, macOS, and Linux. **WARNING**: Kindle Scribe 2025 support may not be possible. Does not work well currently. From 123d603cbdb7f32413734e2481f36ce2b600c417 Mon Sep 17 00:00:00 2001 From: Alex Xu Date: Sun, 1 Feb 2026 10:10:19 -0800 Subject: [PATCH 034/107] clarify mac can't be opened --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 16aea1e..6f874c7 100644 --- a/README.md +++ b/README.md @@ -101,7 +101,7 @@ There are also legacy macOS 10.14+ and Windows 7 experimental versions available The `c2e` and `c2p` versions are command line tools for power users. -On Mac, follow: https://support.apple.com/guide/mac-help/open-a-mac-app-from-an-unknown-developer-mh40616/mac +On macOS, if you get a `can't be opened` error, follow: https://support.apple.com/guide/mac-help/open-a-mac-app-from-an-unknown-developer-mh40616/mac For flatpak, Docker, and AppImage versions, refer to the wiki: https://github.com/ciromattia/kcc/wiki/Installation From 981c556550738c128a2d8496d36b682b549f36b3 Mon Sep 17 00:00:00 2001 From: Alex Xu Date: Mon, 2 Feb 2026 09:14:41 -0800 Subject: [PATCH 035/107] add new tutorial --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 6f874c7..1d6498f 100644 --- a/README.md +++ b/README.md @@ -49,7 +49,8 @@ You can change the default output directory by holding `Shift` while clicking th Then just drag and drop the generated output files onto your device's documents folder via USB. If you are on macOS and use a 2022+ Kindle, you may need to use Amazon USB File Manager for Mac. -YouTube tutorial (please subscribe): https://www.youtube.com/watch?v=IR2Fhcm9658 +YouTube tutorial (please subscribe): https://www.youtube.com/watch?v=QQ6zJcMF2Iw +Installation tutorial: https://www.youtube.com/watch?v=IR2Fhcm9658 ### 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. From 87c6e3a35e7af4ad830a8462345b4a245a3c8ecf Mon Sep 17 00:00:00 2001 From: Alex Xu Date: Mon, 2 Feb 2026 09:15:00 -0800 Subject: [PATCH 036/107] Update README.md --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 1d6498f..d6c55ed 100644 --- a/README.md +++ b/README.md @@ -50,6 +50,7 @@ Then just drag and drop the generated output files onto your device's documents If you are on macOS and use a 2022+ Kindle, you may need to use Amazon USB File Manager for Mac. YouTube tutorial (please subscribe): https://www.youtube.com/watch?v=QQ6zJcMF2Iw + Installation tutorial: https://www.youtube.com/watch?v=IR2Fhcm9658 ### A word of warning From 3660f2370f8d155a5b9f192021c0e444b6b75e9e Mon Sep 17 00:00:00 2001 From: Alex Xu Date: Tue, 3 Feb 2026 08:10:29 -0800 Subject: [PATCH 037/107] add kindlegen error to GUI (#1237) --- kindlecomicconverter/KCC_gui.py | 1 + 1 file changed, 1 insertion(+) diff --git a/kindlecomicconverter/KCC_gui.py b/kindlecomicconverter/KCC_gui.py index 5d525eb..08bedf4 100644 --- a/kindlecomicconverter/KCC_gui.py +++ b/kindlecomicconverter/KCC_gui.py @@ -523,6 +523,7 @@ class WorkerThread(QThread): if os.path.exists(item.replace('.epub', '.mobi')): os.remove(item.replace('.epub', '.mobi')) MW.addMessage.emit('KindleGen failed to create MOBI!', 'error', False) + MW.addMessage.emit(self.kindlegenErrorCode[1], 'error', False) MW.addTrayMessage.emit('KindleGen failed to create MOBI!', 'Critical') if self.kindlegenErrorCode[0] == 1 and self.kindlegenErrorCode[1] != '': MW.showDialog.emit("KindleGen error:\n\n" + self.kindlegenErrorCode[1], 'error') From d2dc089c6241fce7f20e199c9b47095bd439a299 Mon Sep 17 00:00:00 2001 From: Alex Xu Date: Tue, 3 Feb 2026 09:59:51 -0800 Subject: [PATCH 038/107] add workers crashed restart pc message (#1239) --- kindlecomicconverter/comic2ebook.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kindlecomicconverter/comic2ebook.py b/kindlecomicconverter/comic2ebook.py index 988d3ff..7c119fc 100755 --- a/kindlecomicconverter/comic2ebook.py +++ b/kindlecomicconverter/comic2ebook.py @@ -660,7 +660,7 @@ def imgDirectoryProcessing(path, job_progress=''): raise UserWarning("Conversion interrupted.") if len(workerOutput) > 0: rmtree(os.path.join(path, '..', '..'), True) - raise RuntimeError("One of workers crashed. Cause: " + workerOutput[0][0], workerOutput[0][1]) + raise RuntimeError("One of workers crashed. Maybe restart PC. Cause: " + workerOutput[0][0], workerOutput[0][1]) else: rmtree(os.path.join(path, '..', '..'), True) raise UserWarning("C2E: Source directory is empty.") From 58aab0cb65752ed3e5f84380406c753fc65a3a15 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20CHEMIN?= Date: Fri, 6 Feb 2026 18:46:42 +0100 Subject: [PATCH 039/107] Lower minimum chunk size to 50 MB, Remarkable chunk size default of 100 MB (#1240) * accept smaller chunks size on gui * add default target size for Remarkable to 95 * remove rc changes --- gui/KCC.ui | 2 +- kindlecomicconverter/KCC_ui.py | 2 +- kindlecomicconverter/comic2ebook.py | 3 ++- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/gui/KCC.ui b/gui/KCC.ui index 63dd857..3359be8 100644 --- a/gui/KCC.ui +++ b/gui/KCC.ui @@ -473,7 +473,7 @@ - 100 + 50 600 diff --git a/kindlecomicconverter/KCC_ui.py b/kindlecomicconverter/KCC_ui.py index 3574d15..c781a97 100644 --- a/kindlecomicconverter/KCC_ui.py +++ b/kindlecomicconverter/KCC_ui.py @@ -266,7 +266,7 @@ class Ui_mainWindow(object): self.chunkSizeBox = QSpinBox(self.chunkSizeWidget) self.chunkSizeBox.setObjectName(u"chunkSizeBox") - self.chunkSizeBox.setMinimum(100) + self.chunkSizeBox.setMinimum(50) self.chunkSizeBox.setMaximum(600) self.chunkSizeBox.setValue(400) diff --git a/kindlecomicconverter/comic2ebook.py b/kindlecomicconverter/comic2ebook.py index 7c119fc..fa5a7f0 100755 --- a/kindlecomicconverter/comic2ebook.py +++ b/kindlecomicconverter/comic2ebook.py @@ -1423,6 +1423,8 @@ def checkOptions(options): options.format = 'MOBI' if options.batchsplit != 2: options.batchsplit = 1 + if not options.targetsize and options.profile.startswith('Rmk'): + options.targetsize = 95 if options.format == 'MOBI+EPUB': options.keep_epub = True options.format = 'MOBI' @@ -1775,4 +1777,3 @@ def makeMOBI(work, qtgui=None): makeMOBIWorkerPool.close() makeMOBIWorkerPool.join() return makeMOBIWorkerOutput - From 1dce4f8d2c6826c43feec3ff79c116273fbe556b Mon Sep 17 00:00:00 2001 From: Alex Xu Date: Sun, 8 Feb 2026 13:43:43 -0800 Subject: [PATCH 040/107] support latest pyside6 6.10.2 (#1245) --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 8edccd8..4e7fdfb 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ -PySide6<6.10 +PySide6>6 Pillow>=11.3.0 psutil>=5.9.5 requests>=2.31.0 From d189f9909d7c512db51d28587e3f4e593c58cd9b Mon Sep 17 00:00:00 2001 From: Alex Xu Date: Mon, 9 Feb 2026 11:30:59 -0800 Subject: [PATCH 041/107] don't overwrite mobi output with same name (#1246) --- kindlecomicconverter/comic2ebook.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/kindlecomicconverter/comic2ebook.py b/kindlecomicconverter/comic2ebook.py index fa5a7f0..4f1f200 100755 --- a/kindlecomicconverter/comic2ebook.py +++ b/kindlecomicconverter/comic2ebook.py @@ -963,6 +963,13 @@ def getOutputFilename(srcpath, wantedname, ext, tomenumber): while os.path.isfile(basename + '_kcc' + str(counter) + ext): counter += 1 filename = basename + '_kcc' + str(counter) + ext + elif options.format == 'MOBI' and ext == '.epub': + counter = 0 + basename = os.path.splitext(filename)[0] + if os.path.isfile(basename + '.mobi'): + while os.path.isfile(basename + '_kcc' + str(counter) + '.mobi'): + counter += 1 + filename = basename + '_kcc' + str(counter) + ext return filename From 541b1d876bb69e9b9bd6813852ec2881e717b92b Mon Sep 17 00:00:00 2001 From: Alex Xu Date: Mon, 9 Feb 2026 19:08:26 -0800 Subject: [PATCH 042/107] save jpeg quality value (#1248) --- kindlecomicconverter/KCC_gui.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/kindlecomicconverter/KCC_gui.py b/kindlecomicconverter/KCC_gui.py index 08bedf4..00c2e8f 100644 --- a/kindlecomicconverter/KCC_gui.py +++ b/kindlecomicconverter/KCC_gui.py @@ -1039,6 +1039,7 @@ class KCCGUI(KCC_ui.Ui_mainWindow): 'metadataTitleBox': GUI.metadataTitleBox.checkState(), 'mozJpegBox': GUI.mozJpegBox.checkState(), 'jpegQualityBox': GUI.jpegQualityBox.checkState(), + 'jpegQuality': GUI.jpegQualitySpinBox.value(), 'widthBox': GUI.widthBox.value(), 'heightBox': GUI.heightBox.value(), 'deleteBox': GUI.deleteBox.checkState(), @@ -1441,6 +1442,8 @@ class KCCGUI(KCC_ui.Ui_mainWindow): GUI.croppingPowerSlider.setValue(int(self.options[option])) self.changeCroppingPower(int(self.options[option])) GUI.preserveMarginBox.setValue(self.options.get('preserveMarginBox', 0)) + elif str(option) == "jpegQuality": + GUI.jpegQualitySpinBox.setValue(int(self.options[option])) elif str(option) == "chunkSizeBox": GUI.chunkSizeBox.setValue(int(self.options[option])) else: From ab93c03838b5306242655cf52d03f429b0ceed19 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=83=95=E3=82=A3=E3=83=AB=E3=82=BF=E3=83=BC=E3=83=9A?= =?UTF-8?q?=E3=83=BC=E3=83=91=E3=83=BC?= <76888457+filterpaper@users.noreply.github.com> Date: Tue, 10 Feb 2026 13:34:11 +0800 Subject: [PATCH 043/107] Preserve file fusion input order with prefix (#1242) * Preserve file fusion input order with prefix Prepend a sequence index to temporary directory names to ensure user-specified order is preserved. * don't sort items in GUI * Add prefix only if sorted order is different * Simplified sort prefix that will be removed from chapter name * Restore adding prefix only when sorted order differs * simplify prefix code --------- Co-authored-by: Alex Xu --- kindlecomicconverter/KCC_gui.py | 3 ++- kindlecomicconverter/comic2ebook.py | 18 +++++++++++++++--- 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/kindlecomicconverter/KCC_gui.py b/kindlecomicconverter/KCC_gui.py index 00c2e8f..047f977 100644 --- a/kindlecomicconverter/KCC_gui.py +++ b/kindlecomicconverter/KCC_gui.py @@ -1091,7 +1091,8 @@ class KCCGUI(KCC_ui.Ui_mainWindow): if message[-1] == '/': message = message[:-1] self.handleMessage(message) - GUI.jobList.sortItems() + # sorting may conflict with manual file fusion order + # GUI.jobList.sortItems() def forceShutdown(self): self.saveSettings(None) diff --git a/kindlecomicconverter/comic2ebook.py b/kindlecomicconverter/comic2ebook.py index 4f1f200..b7cb97e 100755 --- a/kindlecomicconverter/comic2ebook.py +++ b/kindlecomicconverter/comic2ebook.py @@ -1556,15 +1556,24 @@ def makeFusion(sources: List[str]): fusion_path = first_path.parent.joinpath(first_path.name + ' [fused]') print("Running Fusion") - for source in sources: + # Check if prefix is needed when user-specified ordering differs from OS natural sorting + path_names = [Path(s).stem if Path(s).is_file() else Path(s).name for s in sources] + needs_prefix = os_sorted(path_names) != path_names + + for index, source in enumerate(sources, start=1): print(f"Processing {source}...") checkPre(source) print("Checking images...") source_path = Path(source) + # Add the fusion_0001_ prefix to maintain user-specified order if needed + prefix = '' + if needs_prefix: + prefix = f'fusion_{index:04d}_' if source_path.is_file(): - targetpath = fusion_path.joinpath(source_path.stem) + targetpath = fusion_path.joinpath(f'{prefix}{source_path.stem}') else: - targetpath = fusion_path.joinpath(source_path.name) + targetpath = fusion_path.joinpath(f'{prefix}{source_path.name}') + getWorkFolder(source, str(targetpath)) sanitizeTree(targetpath, prefix='fusion') # TODO: remove flattenTree when subchapters are supported @@ -1593,6 +1602,9 @@ def makeBook(source, qtgui=None, job_progress=''): removeNonImages(os.path.join(path, "OEBPS", "Images")) detectSuboptimalProcessing(os.path.join(path, "OEBPS", "Images"), source) chapterNames, cover_path = sanitizeTree(os.path.join(path, 'OEBPS', 'Images')) + if options.filefusion: + # Strip the fusion_0001_ sort prefix from makeFusion if present + chapterNames = {k: sub(r'^fusion_\d{4}_', '', v) for k, v in chapterNames.items()} cover = None if not options.webtoon: cover = image.Cover(cover_path, options) From f7ce1cf2710e61a4dc45f7fc5778f769786505e0 Mon Sep 17 00:00:00 2001 From: Alex Xu Date: Sat, 14 Feb 2026 23:43:34 -0800 Subject: [PATCH 044/107] add demo video --- README.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/README.md b/README.md index d6c55ed..db2aa11 100644 --- a/README.md +++ b/README.md @@ -15,6 +15,10 @@ Supported input formats include JPG/PNG image files in folders, archives, or PDF Supported output formats include MOBI/AZW3, EPUB, KEPUB, CBZ, and PDF. KCC runs on Windows, macOS, and Linux. +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 + **WARNING**: Kindle Scribe 2025 support may not be possible. Does not work well currently. **NEW**: PDF output is now supported for direct conversion to reMarkable devices! @@ -37,6 +41,7 @@ KCC avoids many common formatting issues (some of which occur [even on the Kindl 3) Not utilizing the full 1860x2480 resolution of the 10" Kindle Scribe 4) incorrect page turn direction for manga that's read right to left 5) unaligned two page spreads in landscape, where pages are shifted over by 1 +6) Rainbow effect on color eink Kaleido 3 due to manga screentones The GUI looks like this, built in Qt6, with my most commonly used settings: From 94e4937566573e36fd2f4df469dd4ad446c40156 Mon Sep 17 00:00:00 2001 From: Alex Xu Date: Sun, 15 Feb 2026 13:01:43 -0800 Subject: [PATCH 045/107] ensure mimetype is first when using 7zip (#1251) --- kindlecomicconverter/comic2ebook.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/kindlecomicconverter/comic2ebook.py b/kindlecomicconverter/comic2ebook.py index b7cb97e..cb23fbd 100755 --- a/kindlecomicconverter/comic2ebook.py +++ b/kindlecomicconverter/comic2ebook.py @@ -1282,10 +1282,13 @@ def makeZIP(zipfilename, basedir, job_progress='', isepub=False): zipfilename = os.path.abspath(zipfilename) + '.zip' if SEVENZIP in available_archive_tools(): if isepub: - mimetypeFile = open(os.path.join(basedir, 'mimetype'), 'w') + mimetypeFile = open(os.path.join(basedir, '!mimetype'), 'w') mimetypeFile.write('application/epub+zip') mimetypeFile.close() subprocess_run([SEVENZIP, 'a', '-tzip', zipfilename, "*"], capture_output=True, check=True, cwd=basedir) + # crazy hack to ensure mimetype is first when using 7zip + if isepub: + subprocess_run([SEVENZIP, 'rn', zipfilename, '!mimetype', 'mimetype'], capture_output=True, check=True, cwd=basedir) else: zipOutput = ZipFile(zipfilename, 'w', ZIP_DEFLATED) if isepub: From 2632d18e2c3e446318207ae21dada5268f3e57b6 Mon Sep 17 00:00:00 2001 From: Alex Xu Date: Mon, 16 Feb 2026 15:15:09 -0800 Subject: [PATCH 046/107] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index db2aa11..055fc6d 100644 --- a/README.md +++ b/README.md @@ -41,7 +41,7 @@ KCC avoids many common formatting issues (some of which occur [even on the Kindl 3) Not utilizing the full 1860x2480 resolution of the 10" Kindle Scribe 4) incorrect page turn direction for manga that's read right to left 5) unaligned two page spreads in landscape, where pages are shifted over by 1 -6) Rainbow effect on color eink Kaleido 3 due to manga screentones +6) Removing without blur the rainbow effect on color eink Kaleido 3 due to manga screentones The GUI looks like this, built in Qt6, with my most commonly used settings: From 723fa4c0b83e6b7f2949e8737c51989587043d52 Mon Sep 17 00:00:00 2001 From: tom <120112635+tswh0@users.noreply.github.com> Date: Sun, 22 Feb 2026 20:34:10 +0100 Subject: [PATCH 047/107] Add exact cover fit option for device-sized cover cropping (#1254) * Add exact cover fit option for device-sized cover cropping * Update README to move cover cropping from FAQ to USAGE * rename to coverfill * edit readme --- README.md | 1 + gui/KCC.ui | 12 ++++++++++++ kindlecomicconverter/KCC_gui.py | 3 +++ kindlecomicconverter/KCC_ui.py | 13 ++++++++++++- kindlecomicconverter/comic2ebook.py | 2 ++ kindlecomicconverter/image.py | 5 ++++- 6 files changed, 34 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 055fc6d..72e6609 100644 --- a/README.md +++ b/README.md @@ -269,6 +269,7 @@ PROCESSING: Crop empty sections. 0: Disabled 1: Horizontally 2: Both [Default=0] --blackborders Disable autodetection and force black borders --whiteborders Disable autodetection and force white borders + --coverfill Center-crop only the cover to fill target device screen --forcecolor Don't convert images to grayscale --forcepng Create PNG files instead JPEG --mozjpeg Create JPEG files using mozJpeg diff --git a/gui/KCC.ui b/gui/KCC.ui index 3359be8..9ea9ad7 100644 --- a/gui/KCC.ui +++ b/gui/KCC.ui @@ -908,6 +908,17 @@ Useful for really weird PDFs. + + + + Resize cover to exact device resolution by center-cropping to aspect ratio first. +May crop top/bottom or left/right depending on source aspect ratio. + + + Cover Fill + + +
@@ -1037,6 +1048,7 @@ Useful for really weird PDFs. noRotateBox interPanelCropBox metadataTitleBox + coverFillBox chunkSizeCheckBox chunkSizeBox eraseRainbowBox diff --git a/kindlecomicconverter/KCC_gui.py b/kindlecomicconverter/KCC_gui.py index 047f977..456d529 100644 --- a/kindlecomicconverter/KCC_gui.py +++ b/kindlecomicconverter/KCC_gui.py @@ -329,6 +329,8 @@ class WorkerThread(QThread): options.noprocessing = True if GUI.pdfExtractBox.isChecked(): options.pdfextract = True + if GUI.coverFillBox.isChecked(): + options.coverfill = True if GUI.metadataTitleBox.checkState() == Qt.CheckState.PartiallyChecked: options.metadatatitle = 1 elif GUI.metadataTitleBox.checkState() == Qt.CheckState.Checked: @@ -1036,6 +1038,7 @@ class KCCGUI(KCC_ui.Ui_mainWindow): 'eraseRainbowBox': GUI.eraseRainbowBox.checkState(), 'disableProcessingBox': GUI.disableProcessingBox.checkState(), 'pdfExtractBox': GUI.pdfExtractBox.checkState(), + 'coverFillBox': GUI.coverFillBox.checkState(), 'metadataTitleBox': GUI.metadataTitleBox.checkState(), 'mozJpegBox': GUI.mozJpegBox.checkState(), 'jpegQualityBox': GUI.jpegQualityBox.checkState(), diff --git a/kindlecomicconverter/KCC_ui.py b/kindlecomicconverter/KCC_ui.py index c781a97..98cf7f1 100644 --- a/kindlecomicconverter/KCC_ui.py +++ b/kindlecomicconverter/KCC_ui.py @@ -467,6 +467,11 @@ class Ui_mainWindow(object): self.gridLayout_2.addWidget(self.pdfExtractBox, 9, 0, 1, 1) + self.coverFillBox = QCheckBox(self.optionWidget) + self.coverFillBox.setObjectName(u"coverFillBox") + + self.gridLayout_2.addWidget(self.coverFillBox, 9, 1, 1, 1) + self.gridLayout.addWidget(self.optionWidget, 5, 0, 1, 2) @@ -549,7 +554,8 @@ class Ui_mainWindow(object): QWidget.setTabOrder(self.fileFusionBox, self.noRotateBox) QWidget.setTabOrder(self.noRotateBox, self.interPanelCropBox) QWidget.setTabOrder(self.interPanelCropBox, self.metadataTitleBox) - QWidget.setTabOrder(self.metadataTitleBox, self.chunkSizeCheckBox) + QWidget.setTabOrder(self.metadataTitleBox, self.coverFillBox) + QWidget.setTabOrder(self.coverFillBox, self.chunkSizeCheckBox) QWidget.setTabOrder(self.chunkSizeCheckBox, self.chunkSizeBox) QWidget.setTabOrder(self.chunkSizeBox, self.eraseRainbowBox) QWidget.setTabOrder(self.eraseRainbowBox, self.rotateFirstBox) @@ -744,6 +750,11 @@ class Ui_mainWindow(object): "Useful for really weird PDFs.", None)) #endif // QT_CONFIG(tooltip) self.pdfExtractBox.setText(QCoreApplication.translate("mainWindow", u"PDF Legacy Extract", None)) +#if QT_CONFIG(tooltip) + self.coverFillBox.setToolTip(QCoreApplication.translate("mainWindow", u"Resize cover to exact device resolution by center-cropping to aspect ratio first.\n" +"May crop top/bottom or left/right depending on source aspect ratio.", None)) +#endif // QT_CONFIG(tooltip) + self.coverFillBox.setText(QCoreApplication.translate("mainWindow", u"Cover Fill", None)) self.gammaLabel.setText(QCoreApplication.translate("mainWindow", u"Gamma: Auto", None)) self.jpegQualityLabel.setText(QCoreApplication.translate("mainWindow", u"JPEG Quality:", None)) # retranslateUi diff --git a/kindlecomicconverter/comic2ebook.py b/kindlecomicconverter/comic2ebook.py index cb23fbd..a9c9e38 100755 --- a/kindlecomicconverter/comic2ebook.py +++ b/kindlecomicconverter/comic2ebook.py @@ -1360,6 +1360,8 @@ def makeParser(): help="Do not modify image and ignore any profile or processing option") processing_options.add_argument("--pdfextract", action="store_true", dest="pdfextract", default=False, help="Use the legacy PDF image extraction method from KCC 8 and earlier") + processing_options.add_argument("--coverfill", action="store_true", dest="coverfill", default=False, + help="Crop cover to fill screen") processing_options.add_argument("-u", "--upscale", action="store_true", dest="upscale", default=False, help="Resize images smaller than device's resolution") processing_options.add_argument("-s", "--stretch", action="store_true", dest="stretch", default=False, diff --git a/kindlecomicconverter/image.py b/kindlecomicconverter/image.py index 2acb60d..44a7532 100755 --- a/kindlecomicconverter/image.py +++ b/kindlecomicconverter/image.py @@ -569,7 +569,10 @@ class Cover: if self.options.kindle_scribe_azw3: size[0] = min(size[0], 1920) size[1] = min(size[1], 1920) - self.image.thumbnail(tuple(size), Image.Resampling.LANCZOS) + if self.options.coverfill: + self.image = ImageOps.fit(self.image, tuple(size), Image.Resampling.LANCZOS, centering=(0.5, 0.5)) + else: + self.image.thumbnail(tuple(size), Image.Resampling.LANCZOS) def crop_main_cover(self): w, h = self.image.size From adf48d24f978ead73a0b5d7dfc94463f8a4fef18 Mon Sep 17 00:00:00 2001 From: Alex Xu Date: Sun, 22 Feb 2026 11:49:27 -0800 Subject: [PATCH 048/107] clarify coverfill is not implemented for kindle scribe (#1255) --- gui/KCC.ui | 2 +- kindlecomicconverter/KCC_ui.py | 2 +- kindlecomicconverter/image.py | 3 ++- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/gui/KCC.ui b/gui/KCC.ui index 9ea9ad7..2ce5f4a 100644 --- a/gui/KCC.ui +++ b/gui/KCC.ui @@ -912,7 +912,7 @@ Useful for really weird PDFs. Resize cover to exact device resolution by center-cropping to aspect ratio first. -May crop top/bottom or left/right depending on source aspect ratio. +May crop top/bottom or left/right depending on source aspect ratio. Not implemented for Kindle Scribe. Cover Fill diff --git a/kindlecomicconverter/KCC_ui.py b/kindlecomicconverter/KCC_ui.py index 98cf7f1..f746aa9 100644 --- a/kindlecomicconverter/KCC_ui.py +++ b/kindlecomicconverter/KCC_ui.py @@ -752,7 +752,7 @@ class Ui_mainWindow(object): self.pdfExtractBox.setText(QCoreApplication.translate("mainWindow", u"PDF Legacy Extract", None)) #if QT_CONFIG(tooltip) self.coverFillBox.setToolTip(QCoreApplication.translate("mainWindow", u"Resize cover to exact device resolution by center-cropping to aspect ratio first.\n" -"May crop top/bottom or left/right depending on source aspect ratio.", None)) +"May crop top/bottom or left/right depending on source aspect ratio. Not implemented for Kindle Scribe.", None)) #endif // QT_CONFIG(tooltip) self.coverFillBox.setText(QCoreApplication.translate("mainWindow", u"Cover Fill", None)) self.gammaLabel.setText(QCoreApplication.translate("mainWindow", u"Gamma: Auto", None)) diff --git a/kindlecomicconverter/image.py b/kindlecomicconverter/image.py index 44a7532..3341940 100755 --- a/kindlecomicconverter/image.py +++ b/kindlecomicconverter/image.py @@ -569,7 +569,8 @@ class Cover: if self.options.kindle_scribe_azw3: size[0] = min(size[0], 1920) size[1] = min(size[1], 1920) - if self.options.coverfill: + if self.options.coverfill and not self.options.kindle_scribe_azw3: + # TODO: Kindle Scribe case self.image = ImageOps.fit(self.image, tuple(size), Image.Resampling.LANCZOS, centering=(0.5, 0.5)) else: self.image.thumbnail(tuple(size), Image.Resampling.LANCZOS) From b42f05686e132032ecd48358c67294f30aa13aae Mon Sep 17 00:00:00 2001 From: Alex Xu Date: Sun, 8 Mar 2026 17:37:47 -0700 Subject: [PATCH 049/107] bump to 9.5.0 --- kindlecomicconverter/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kindlecomicconverter/__init__.py b/kindlecomicconverter/__init__.py index 4ddc0ca..697e741 100644 --- a/kindlecomicconverter/__init__.py +++ b/kindlecomicconverter/__init__.py @@ -1,4 +1,4 @@ -__version__ = '9.4.3' +__version__ = '9.5.0' __license__ = 'ISC' __copyright__ = '2012-2022, Ciro Mattia Gonano , Pawel Jastrzebski , darodi' __docformat__ = 'restructuredtext en' From 1dead9af8f3f438fdc36e5810b8402a18cebca2a Mon Sep 17 00:00:00 2001 From: Alex Xu Date: Tue, 10 Mar 2026 13:29:46 -0700 Subject: [PATCH 050/107] add PDF 200 MB option (#1264) --- kindlecomicconverter/KCC_gui.py | 1 + kindlecomicconverter/comic2ebook.py | 5 +++++ 2 files changed, 6 insertions(+) diff --git a/kindlecomicconverter/KCC_gui.py b/kindlecomicconverter/KCC_gui.py index 456d529..ba4f3df 100644 --- a/kindlecomicconverter/KCC_gui.py +++ b/kindlecomicconverter/KCC_gui.py @@ -1182,6 +1182,7 @@ class KCCGUI(KCC_ui.Ui_mainWindow): "EPUB": {'icon': 'EPUB', 'format': 'EPUB'}, "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'}, "MOBI + EPUB": {'icon': 'MOBI', 'format': 'MOBI+EPUB'}, "EPUB (200MB limit)": {'icon': 'EPUB', 'format': 'EPUB-200MB'}, diff --git a/kindlecomicconverter/comic2ebook.py b/kindlecomicconverter/comic2ebook.py index a9c9e38..c29bbc7 100755 --- a/kindlecomicconverter/comic2ebook.py +++ b/kindlecomicconverter/comic2ebook.py @@ -1424,6 +1424,11 @@ def checkOptions(options): options.isKobo = False options.bordersColor = None options.keep_epub = False + if options.format == 'PDF-200MB': + options.targetsize = 195 + options.format = 'PDF' + if options.batchsplit != 2: + options.batchsplit = 1 if options.format == 'EPUB-200MB': options.targetsize = 195 options.format = 'EPUB' From b4d72cd581a695927a280745d98ddaa9629968cf Mon Sep 17 00:00:00 2001 From: Alex Xu Date: Tue, 10 Mar 2026 13:37:59 -0700 Subject: [PATCH 051/107] remove setuptools and wheel (#1265) --- .github/workflows/package-linux.yml | 2 +- .github/workflows/package-macos.yml | 2 +- .github/workflows/package-osx-legacy.yml | 2 +- .github/workflows/package-windows.yml | 2 +- .github/workflows/package-windows7.yml | 2 +- Dockerfile | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/package-linux.yml b/.github/workflows/package-linux.yml index d54fb33..b1d22d8 100644 --- a/.github/workflows/package-linux.yml +++ b/.github/workflows/package-linux.yml @@ -35,7 +35,7 @@ jobs: run: | sudo apt-get update sudo apt-get install -y libpng-dev libjpeg-dev p7zip-full p7zip-rar python3-pip squashfs-tools libfuse2 libxcb-cursor0 - python -m pip install --upgrade pip setuptools wheel certifi pyinstaller --no-binary pyinstaller + python -m pip install --upgrade pip certifi pyinstaller --no-binary pyinstaller python -m pip install -r requirements.txt - name: build binary run: | diff --git a/.github/workflows/package-macos.yml b/.github/workflows/package-macos.yml index 60fd7b8..7d56f76 100644 --- a/.github/workflows/package-macos.yml +++ b/.github/workflows/package-macos.yml @@ -38,7 +38,7 @@ jobs: cache: 'pip' - name: Install python dependencies run: | - python -m pip install --upgrade pip setuptools wheel pyinstaller certifi + python -m pip install --upgrade pip pyinstaller certifi pip install -r requirements.txt - name: Install the Apple certificate and provisioning profile # TODO signing diff --git a/.github/workflows/package-osx-legacy.yml b/.github/workflows/package-osx-legacy.yml index 5b2a83d..8094d3b 100644 --- a/.github/workflows/package-osx-legacy.yml +++ b/.github/workflows/package-osx-legacy.yml @@ -40,7 +40,7 @@ jobs: - name: Install Python dependencies run: | python3 --version - pip3 install --upgrade pip setuptools wheel pyinstaller certifi + pip3 install --upgrade pip pyinstaller certifi pip3 install --upgrade -r requirements-osx-legacy.txt ./gen_ui_files.sh - uses: actions/setup-node@v6 diff --git a/.github/workflows/package-windows.yml b/.github/workflows/package-windows.yml index 75624db..7653218 100644 --- a/.github/workflows/package-windows.yml +++ b/.github/workflows/package-windows.yml @@ -45,7 +45,7 @@ jobs: env: PYINSTALLER_COMPILE_BOOTLOADER: 1 run: | - python -m pip install --upgrade pip setuptools wheel + python -m pip install --upgrade pip pip install -r requirements.txt pip install certifi pyinstaller --no-binary pyinstaller - name: build binary diff --git a/.github/workflows/package-windows7.yml b/.github/workflows/package-windows7.yml index 431b577..02fc62a 100644 --- a/.github/workflows/package-windows7.yml +++ b/.github/workflows/package-windows7.yml @@ -37,7 +37,7 @@ jobs: env: PYINSTALLER_COMPILE_BOOTLOADER: 1 run: | - python -m pip install --upgrade pip setuptools wheel + python -m pip install --upgrade pip pip install -r requirements-win7.txt pip install certifi pyinstaller --no-binary pyinstaller .\gen_ui_files.bat diff --git a/Dockerfile b/Dockerfile index 76b8786..398d417 100644 --- a/Dockerfile +++ b/Dockerfile @@ -4,7 +4,7 @@ FROM python:3.13-slim-bullseye AS builder # Install system dependencies RUN set -x && \ - BUILD_DEPS="build-essential cmake libffi-dev libfreetype6-dev libfontconfig1-dev libpng-dev libjpeg-dev libssl-dev libxft-dev make python3-dev python3-setuptools python3-wheel" && \ + BUILD_DEPS="build-essential cmake libffi-dev libfreetype6-dev libfontconfig1-dev libpng-dev libjpeg-dev libssl-dev libxft-dev make python3-dev" && \ RUNTIME_DEPS="bash ca-certificates chrpath locales locales-all libfreetype6 libfontconfig1 p7zip-full python3 python3-pip libgl1" && \ DEBIAN_FRONTEND=noninteractive apt-get update -y && \ apt-get install -y --no-install-recommends ${BUILD_DEPS} ${RUNTIME_DEPS} From 34fb68ac65f67d81b354d960504e2087b130aaa4 Mon Sep 17 00:00:00 2001 From: Alex Xu Date: Tue, 10 Mar 2026 13:50:59 -0700 Subject: [PATCH 052/107] downgrade to pyside 6.9 --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 4e7fdfb..8edccd8 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ -PySide6>6 +PySide6<6.10 Pillow>=11.3.0 psutil>=5.9.5 requests>=2.31.0 From 6fdfddd7d9bae2859edde663dab7a7d84d9a7996 Mon Sep 17 00:00:00 2001 From: Alex Xu Date: Tue, 10 Mar 2026 14:00:54 -0700 Subject: [PATCH 053/107] bump to 9.5.1 --- kindlecomicconverter/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kindlecomicconverter/__init__.py b/kindlecomicconverter/__init__.py index 697e741..64d56a9 100644 --- a/kindlecomicconverter/__init__.py +++ b/kindlecomicconverter/__init__.py @@ -1,4 +1,4 @@ -__version__ = '9.5.0' +__version__ = '9.5.1' __license__ = 'ISC' __copyright__ = '2012-2022, Ciro Mattia Gonano , Pawel Jastrzebski , darodi' __docformat__ = 'restructuredtext en' From 472fdc97b5ea2556319e9978c7628e5989d3e641 Mon Sep 17 00:00:00 2001 From: Alex Xu Date: Fri, 13 Mar 2026 12:17:38 -0700 Subject: [PATCH 054/107] PDF PNG half size, Kindle DX PNG CBZ fixed (#1267) * PNG for PDF or Kindle DX CBZ is dithered to 16 colors for ~1/2 filesize reduction * Kindle DX default no borders --- kindlecomicconverter/KCC_gui.py | 4 +++- kindlecomicconverter/comic2ebook.py | 17 ++++++++++++----- 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/kindlecomicconverter/KCC_gui.py b/kindlecomicconverter/KCC_gui.py index ba4f3df..96780c7 100644 --- a/kindlecomicconverter/KCC_gui.py +++ b/kindlecomicconverter/KCC_gui.py @@ -883,6 +883,8 @@ class KCCGUI(KCC_ui.Ui_mainWindow): if bad_format in current_format: self.addMessage('Colorsoft MOBI/EPUB can have blank pages. Just go back a few pages, exit, and reenter book.', 'info') break + elif profile['Label'] == 'KDX': + GUI.borderBox.setCheckState(Qt.CheckState.PartiallyChecked) if not profile['PVOptions']: GUI.qualityBox.setChecked(False) if str(GUI.deviceBox.currentText()) == 'Other': @@ -909,7 +911,7 @@ class KCCGUI(KCC_ui.Ui_mainWindow): 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') + self.addMessage("Partially check W/B Margins if you don't want KCC to extend the image margins.", 'info') def stripTags(self, html): s = HTMLStripper() diff --git a/kindlecomicconverter/comic2ebook.py b/kindlecomicconverter/comic2ebook.py index c29bbc7..30c99c6 100755 --- a/kindlecomicconverter/comic2ebook.py +++ b/kindlecomicconverter/comic2ebook.py @@ -707,8 +707,11 @@ def imgFileProcessing(work): pass elif opt.forcepng: img.convertToGrayscale() - if opt.format != 'PDF': - img.quantizeImage() + img.quantizeImage() + if opt.format == 'PDF': + img.convertToGrayscale() + elif opt.profile == 'KDX' and opt.format == 'CBZ': + img.convertToGrayscale() else: img.convertToGrayscale() output.append(img.saveToDir()) @@ -1487,9 +1490,6 @@ def checkOptions(options): if 'Ko' in options.profile: options.panelview = False options.hq = False - # CBZ files on Kindle DX/DXG support higher resolution - if options.profile == 'KDX' and options.format == 'CBZ': - options.customheight = 1200 # KFX output create EPUB that might be can be by jhowell KFX Output Calibre plugin if options.format == 'KFX': options.format = 'EPUB' @@ -1515,6 +1515,13 @@ def checkOptions(options): 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 + + # CBZ files on Kindle DX/DXG support higher resolution + if options.profile == 'KDX' and options.format == 'CBZ': + options.profileData = list(image.ProfileData.Profiles[options.profile]) + options.profileData[1] = list(options.profileData[1]) + options.profileData[1][1] = 1200 + if options.kindle_scribe_azw3: options.profileData = list(image.ProfileData.Profiles[options.profile]) options.profileData[1] = list(options.profileData[1]) From 9b9181a715d18dc88bb60140a36926af0cf15e7c Mon Sep 17 00:00:00 2001 From: Alex Xu Date: Fri, 13 Mar 2026 14:15:14 -0700 Subject: [PATCH 055/107] Add rotate right option (#1268) --- README.md | 1 + gui/KCC.ui | 612 ++++++++++++++-------------- kindlecomicconverter/KCC_gui.py | 3 + kindlecomicconverter/KCC_ui.py | 499 ++++++++++++----------- kindlecomicconverter/comic2ebook.py | 2 + kindlecomicconverter/image.py | 10 +- 6 files changed, 579 insertions(+), 548 deletions(-) diff --git a/README.md b/README.md index 72e6609..52fbf64 100644 --- a/README.md +++ b/README.md @@ -292,6 +292,7 @@ OUTPUT SETTINGS: Split output into multiple files. 0: Don't split 1: Automatic mode 2: Consider every subdirectory as separate volume [Default=0] --spreadshift Shift first page to opposite side in landscape for two page spread alignment --norotate Do not rotate double page spreads in spread splitter option. + --rotateright Rotate double page spreads in opposite direction. --rotatefirst Put rotated spread first in spread splitter option. --filefusion Combines all input files into a single file. --eraserainbow Erase rainbow effect on color eink screen by attenuating interfering frequencies diff --git a/gui/KCC.ui b/gui/KCC.ui index 2ce5f4a..fcc5f07 100644 --- a/gui/KCC.ui +++ b/gui/KCC.ui @@ -514,137 +514,6 @@ 0 - - - - - 0 - 0 - - - - Qt::FocusPolicy::ClickFocus - - - <html><head/><body><p>Default Title</p></body></html> - - - Default Title - - - false - - - - - - - <html><head/><body><p style='white-space:pre'>Enable right-to-left reading.</p></body></html> - - - Right-to-left (manga) - - - - - - - <html><head/><body><p style='white-space:pre'>Enable special parsing mode for Korean Webtoons.</p></body></html> - - - Webtoon mode - - - - - - - <html><head/><body><p><span style=" font-weight:600; text-decoration: underline;">Unchecked - Disabled</span></p><p>Disabled</p><p><span style=" font-weight:600; text-decoration: underline;">Indeterminate - Margins<br/></span>Margins</p><p><span style=" font-weight:600; text-decoration: underline;">Checked - Margins + page numbers<br/></span>Margins +page numbers</p></body></html> - - - Cropping mode - - - true - - - - - - - <html><head/><body><p><span style=" font-weight:600; text-decoration: underline;">Unchecked - 1x4<br/></span>Keep format 1x4 panels strips.</p><p><span style=" font-weight:600; text-decoration: underline;">Checked - 2x2<br/></span>Turn 1x4 strips to 2x2 to maximize screen usage.</p></body></html> - - - 1x4 to 2x2 strips - - - - - - - Delete input file(s) or directory. It's not recoverable! - - - Delete input - - - - - - - <html><head/><body><p>When the spread splitter option is partially checked,</p><p><span style=" font-weight:600; text-decoration: underline;">Unchecked - Rotate Last<br/></span>Put the rotated 2 page spread after the split spreads.</p><p><span style=" font-weight:600; text-decoration: underline;">Checked - Rotate First<br/></span>Put the rotated 2 page spread before the split spreads.</p></body></html> - - - Rotate First - - - - - - - <html><head/><body><p style='white-space:pre'><span style=" font-weight:600; text-decoration: underline;">Unchecked - Automatic mode<br/></span>The output will be split automatically.</p><p style='white-space:pre'><span style=" font-weight:600; text-decoration: underline;">Checked - Volume mode<br/></span>Every subdirectory will be considered as a separate volume.</p></body></html> - - - Output split - - - - - - - <html><head/><body><p><span style=" font-weight:600; text-decoration: underline;">Unchecked - Autodetection<br/></span>The color of margins fill will be detected automatically.</p><p><span style=" font-weight:600; text-decoration: underline;">Indeterminate - White<br/></span>Margins will be untouched.</p><p><span style=" font-weight:600; text-decoration: underline;">Checked - Black<br/></span>Margins will be filled with black color.</p></body></html> - - - W/B margins - - - true - - - - - - - <html><head/><body><p>Set a custom gamma correction.</p><p>1.0 is default (disabled).<br/>&lt; 1.0 makes the image brighter.<br/>&gt; 1.0 makes the image darker. </p><p>1.8 was the default in KCC 9.1.0 and earlier.</p><p>Use if you want to make midtones darker.</p></body></html> - - - Custom gamma - - - - - - - <html><head/><body><p><span style=" font-weight:600; text-decoration: underline;">Unchecked - Nothing<br/></span>Images smaller than device resolution will not be resized.</p><p><span style=" font-weight:600; text-decoration: underline;">Indeterminate - Stretching<br/></span>Images smaller than device resolution will be resized. Aspect ratio will be not preserved.</p><p><span style=" font-weight:600; text-decoration: underline;">Checked - Upscaling<br/></span>Images smaller than device resolution will be resized. Aspect ratio will be preserved.</p></body></html> - - - Stretch/Upscale - - - true - - - @@ -655,101 +524,6 @@ - - - - <html><head/><body><p style='white-space:pre'>Disable conversion to grayscale.</p></body></html> - - - Color mode - - - - - - - <html><head/><body><p><span style=" font-weight:600; text-decoration: underline;">Unchecked - Split<br/></span>Double page spreads will be cut into two separate pages.</p><p><span style=" font-weight:600; text-decoration: underline;">Indeterminate - Split and rotate<br/></span>Double page spreads will be displayed twice. First split and then rotated. </p><p><span style=" font-weight:600; text-decoration: underline;">Checked - Rotate<br/></span>Double page spreads will be rotated.</p></body></html> - - - Spread splitter - - - true - - - - - - - Shift first page to opposite side in landscape for two page spread alignment - - - Spread shift - - - - - - - <html><head/><body><p style='white-space:pre'>Do not process any image, ignore profile and processing options.</p></body></html> - - - Disable processing - - - - - - - Erase rainbow effect on color eink screen by attenuating interfering frequencies - - - Rainbow eraser - - - - - - - Do not rotate double page spreads in spread splitter option. - - - No rotate - - - - - - - <html><head/><body><p>Combines all selected files into a single file. (Helpful for combining chapters into volumes.)</p></body></html> - - - File Fusion - - - - - - - - 0 - 0 - - - - Qt::FocusPolicy::ClickFocus - - - Default Author is KCC - - - Default Author - - - false - - - @@ -763,68 +537,6 @@ - - - - <html><head/><body><p><span style=" font-weight:600; text-decoration: underline;">Unchecked - Disabled<br/></span>Disabled</p><p><span style=" font-weight:600; text-decoration: underline;">Indeterminate - Horizontal<br/></span>Crop empty horizontal lines.</p><p><span style=" font-weight:600; text-decoration: underline;">Checked - Both<br/></span>Crop empty horizontal and vertical lines.</p></body></html> - - - Inter-panel crop - - - true - - - - - - - <html><head/><body><p><span style=" font-weight:600; text-decoration: underline;">Unchecked - Don't use metadata Title<br/></span>Write default title.</p><p><span style=" font-weight:600; text-decoration: underline;">Indeterminate - Add metadata Title to the default schema<br/></span>Write default title with Title from ComicInfo.xml or other embedded metadata.</p><p><span style=" font-weight:600; text-decoration: underline;">Checked - Use metadata Title only<br/></span>Write Title from ComicInfo.xml or other embedded metadata.</p></body></html> - - - Metadata Title - - - true - - - - - - - <html><head/><body><p><span style=" font-weight:600; text-decoration: underline;">Unchecked - JPEG<br/></span>Use JPEG files</p><p><span style=" font-weight:600; text-decoration: underline;">Indeterminate - force PNG<br/></span>Create PNG files instead JPEG</p><p><span style=" font-weight:600; text-decoration: underline;">Checked - mozJpeg<br/></span>10-20% smaller JPEG file, with the same image quality, but processing time multiplied by 2</p></body></html> - - - JPEG/PNG/mozJpeg - - - true - - - - - - - <html><head/><body><p>By default, KCC maps the darkest pixel value to pure black (the black point.)</p><p>Extreme black point sets the black point to be the most common dark pixel value.</p><p>Useful when text is black but artwork is gray.</p></body></html> - - - Extreme Black Point - - - - - - - <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> - - - Autocontrast - - - true - - - @@ -882,17 +594,186 @@ - - + + - The JPEG quality, on a scale from 0 (worst) to 95 (best). - -Default is 85 for most devices besides Kindle Scribe and Colorsoft, which are 90. - -Higher values are larger and higher quality, and may resolve blank page issues. + Delete input file(s) or directory. It's not recoverable! - Custom JPEG Quality + Delete input + + + + + + + <html><head/><body><p><span style=" font-weight:600; text-decoration: underline;">Unchecked - Don't use metadata Title<br/></span>Write default title.</p><p><span style=" font-weight:600; text-decoration: underline;">Indeterminate - Add metadata Title to the default schema<br/></span>Write default title with Title from ComicInfo.xml or other embedded metadata.</p><p><span style=" font-weight:600; text-decoration: underline;">Checked - Use metadata Title only<br/></span>Write Title from ComicInfo.xml or other embedded metadata.</p></body></html> + + + Metadata Title + + + true + + + + + + + <html><head/><body><p><span style=" font-weight:600; text-decoration: underline;">Unchecked - JPEG<br/></span>Use JPEG files</p><p><span style=" font-weight:600; text-decoration: underline;">Indeterminate - force PNG<br/></span>Create PNG files instead JPEG</p><p><span style=" font-weight:600; text-decoration: underline;">Checked - mozJpeg<br/></span>10-20% smaller JPEG file, with the same image quality, but processing time multiplied by 2</p></body></html> + + + JPEG/PNG/mozJpeg + + + true + + + + + + + <html><head/><body><p><span style=" font-weight:600; text-decoration: underline;">Unchecked - Disabled<br/></span>Disabled</p><p><span style=" font-weight:600; text-decoration: underline;">Indeterminate - Horizontal<br/></span>Crop empty horizontal lines.</p><p><span style=" font-weight:600; text-decoration: underline;">Checked - Both<br/></span>Crop empty horizontal and vertical lines.</p></body></html> + + + Inter-panel crop + + + true + + + + + + + <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> + + + Autocontrast + + + true + + + + + + + Do not rotate double page spreads in spread splitter option. + + + No rotate + + + + + + + Resize cover to exact device resolution by center-cropping to aspect ratio first. +May crop top/bottom or left/right depending on source aspect ratio. Not implemented for Kindle Scribe. + + + Cover Fill + + + + + + + <html><head/><body><p><span style=" font-weight:600; text-decoration: underline;">Unchecked - Autodetection<br/></span>The color of margins fill will be detected automatically.</p><p><span style=" font-weight:600; text-decoration: underline;">Indeterminate - White<br/></span>Margins will be untouched.</p><p><span style=" font-weight:600; text-decoration: underline;">Checked - Black<br/></span>Margins will be filled with black color.</p></body></html> + + + W/B margins + + + true + + + + + + + Erase rainbow effect on color eink screen by attenuating interfering frequencies + + + Rainbow eraser + + + + + + + + 0 + 0 + + + + Qt::FocusPolicy::ClickFocus + + + <html><head/><body><p>Default Title</p></body></html> + + + Default Title + + + false + + + + + + + <html><head/><body><p><span style=" font-weight:600; text-decoration: underline;">Unchecked - Split<br/></span>Double page spreads will be cut into two separate pages.</p><p><span style=" font-weight:600; text-decoration: underline;">Indeterminate - Split and rotate<br/></span>Double page spreads will be displayed twice. First split and then rotated. </p><p><span style=" font-weight:600; text-decoration: underline;">Checked - Rotate<br/></span>Double page spreads will be rotated.</p></body></html> + + + Spread splitter + + + true + + + + + + + + 0 + 0 + + + + Qt::FocusPolicy::ClickFocus + + + Default Author is KCC + + + Default Author + + + false + + + + + + + <html><head/><body><p>By default, KCC maps the darkest pixel value to pure black (the black point.)</p><p>Extreme black point sets the black point to be the most common dark pixel value.</p><p>Useful when text is black but artwork is gray.</p></body></html> + + + Extreme Black Point + + + + + + + <html><head/><body><p>When the spread splitter option is partially checked,</p><p><span style=" font-weight:600; text-decoration: underline;">Unchecked - Rotate Last<br/></span>Put the rotated 2 page spread after the split spreads.</p><p><span style=" font-weight:600; text-decoration: underline;">Checked - Rotate First<br/></span>Put the rotated 2 page spread before the split spreads.</p></body></html> + + + Rotate First @@ -908,14 +789,143 @@ Useful for really weird PDFs. - - + + - Resize cover to exact device resolution by center-cropping to aspect ratio first. -May crop top/bottom or left/right depending on source aspect ratio. Not implemented for Kindle Scribe. + <html><head/><body><p style='white-space:pre'><span style=" font-weight:600; text-decoration: underline;">Unchecked - Automatic mode<br/></span>The output will be split automatically.</p><p style='white-space:pre'><span style=" font-weight:600; text-decoration: underline;">Checked - Volume mode<br/></span>Every subdirectory will be considered as a separate volume.</p></body></html> - Cover Fill + Output split + + + + + + + The JPEG quality, on a scale from 0 (worst) to 95 (best). + +Default is 85 for most devices besides Kindle Scribe and Colorsoft, which are 90. + +Higher values are larger and higher quality, and may resolve blank page issues. + + + Custom JPEG Quality + + + + + + + <html><head/><body><p><span style=" font-weight:600; text-decoration: underline;">Unchecked - Nothing<br/></span>Images smaller than device resolution will not be resized.</p><p><span style=" font-weight:600; text-decoration: underline;">Indeterminate - Stretching<br/></span>Images smaller than device resolution will be resized. Aspect ratio will be not preserved.</p><p><span style=" font-weight:600; text-decoration: underline;">Checked - Upscaling<br/></span>Images smaller than device resolution will be resized. Aspect ratio will be preserved.</p></body></html> + + + Stretch/Upscale + + + true + + + + + + + <html><head/><body><p>Set a custom gamma correction.</p><p>1.0 is default (disabled).<br/>&lt; 1.0 makes the image brighter.<br/>&gt; 1.0 makes the image darker. </p><p>1.8 was the default in KCC 9.1.0 and earlier.</p><p>Use if you want to make midtones darker.</p></body></html> + + + Custom gamma + + + + + + + Shift first page to opposite side in landscape for two page spread alignment + + + Spread shift + + + + + + + <html><head/><body><p>Combines all selected files into a single file. (Helpful for combining chapters into volumes.)</p></body></html> + + + File Fusion + + + + + + + <html><head/><body><p style='white-space:pre'>Disable conversion to grayscale.</p></body></html> + + + Color mode + + + + + + + <html><head/><body><p><span style=" font-weight:600; text-decoration: underline;">Unchecked - Disabled</span></p><p>Disabled</p><p><span style=" font-weight:600; text-decoration: underline;">Indeterminate - Margins<br/></span>Margins</p><p><span style=" font-weight:600; text-decoration: underline;">Checked - Margins + page numbers<br/></span>Margins +page numbers</p></body></html> + + + Cropping mode + + + true + + + + + + + <html><head/><body><p style='white-space:pre'>Enable special parsing mode for Korean Webtoons.</p></body></html> + + + Webtoon mode + + + + + + + <html><head/><body><p style='white-space:pre'>Do not process any image, ignore profile and processing options.</p></body></html> + + + Disable processing + + + + + + + <html><head/><body><p style='white-space:pre'>Enable right-to-left reading.</p></body></html> + + + Right-to-left (manga) + + + + + + + <html><head/><body><p><span style=" font-weight:600; text-decoration: underline;">Unchecked - 1x4<br/></span>Keep format 1x4 panels strips.</p><p><span style=" font-weight:600; text-decoration: underline;">Checked - 2x2<br/></span>Turn 1x4 strips to 2x2 to maximize screen usage.</p></body></html> + + + 1x4 to 2x2 strips + + + + + + + Rotate 2 page spreads in opposite direction than normal. + + + Rotate Right diff --git a/kindlecomicconverter/KCC_gui.py b/kindlecomicconverter/KCC_gui.py index 96780c7..73e8f91 100644 --- a/kindlecomicconverter/KCC_gui.py +++ b/kindlecomicconverter/KCC_gui.py @@ -345,6 +345,8 @@ class WorkerThread(QThread): options.filefusion = False if GUI.noRotateBox.isChecked(): options.norotate = True + if GUI.rotateRightBox.isChecked(): + options.rotateright = True if GUI.rotateFirstBox.isChecked(): options.rotatefirst = True if GUI.mozJpegBox.checkState() == Qt.CheckState.PartiallyChecked: @@ -1052,6 +1054,7 @@ class KCCGUI(KCC_ui.Ui_mainWindow): 'fileFusionBox': GUI.fileFusionBox.checkState(), 'defaultOutputFolderBox': GUI.defaultOutputFolderBox.checkState(), 'noRotateBox': GUI.noRotateBox.checkState(), + 'rotateRightBox': GUI.rotateRightBox.checkState(), 'rotateFirstBox': GUI.rotateFirstBox.checkState(), 'maximizeStrips': GUI.maximizeStrips.checkState(), 'gammaSlider': float(self.gammaValue) * 100, diff --git a/kindlecomicconverter/KCC_ui.py b/kindlecomicconverter/KCC_ui.py index f746aa9..1cdb648 100644 --- a/kindlecomicconverter/KCC_ui.py +++ b/kindlecomicconverter/KCC_ui.py @@ -287,153 +287,17 @@ class Ui_mainWindow(object): self.gridLayout_2 = QGridLayout(self.optionWidget) self.gridLayout_2.setObjectName(u"gridLayout_2") self.gridLayout_2.setContentsMargins(0, 0, 0, 0) - self.titleEdit = QLineEdit(self.optionWidget) - self.titleEdit.setObjectName(u"titleEdit") - sizePolicy4.setHeightForWidth(self.titleEdit.sizePolicy().hasHeightForWidth()) - self.titleEdit.setSizePolicy(sizePolicy4) - self.titleEdit.setFocusPolicy(Qt.FocusPolicy.ClickFocus) - self.titleEdit.setClearButtonEnabled(False) - - self.gridLayout_2.addWidget(self.titleEdit, 0, 0, 1, 1) - - self.mangaBox = QCheckBox(self.optionWidget) - self.mangaBox.setObjectName(u"mangaBox") - - self.gridLayout_2.addWidget(self.mangaBox, 1, 0, 1, 1) - - self.webtoonBox = QCheckBox(self.optionWidget) - self.webtoonBox.setObjectName(u"webtoonBox") - - self.gridLayout_2.addWidget(self.webtoonBox, 2, 0, 1, 1) - - self.croppingBox = QCheckBox(self.optionWidget) - self.croppingBox.setObjectName(u"croppingBox") - self.croppingBox.setTristate(True) - - self.gridLayout_2.addWidget(self.croppingBox, 4, 2, 1, 1) - - self.maximizeStrips = QCheckBox(self.optionWidget) - self.maximizeStrips.setObjectName(u"maximizeStrips") - - self.gridLayout_2.addWidget(self.maximizeStrips, 4, 1, 1, 1) - - self.deleteBox = QCheckBox(self.optionWidget) - self.deleteBox.setObjectName(u"deleteBox") - - self.gridLayout_2.addWidget(self.deleteBox, 5, 1, 1, 1) - - self.rotateFirstBox = QCheckBox(self.optionWidget) - self.rotateFirstBox.setObjectName(u"rotateFirstBox") - - self.gridLayout_2.addWidget(self.rotateFirstBox, 8, 1, 1, 1) - - self.outputSplit = QCheckBox(self.optionWidget) - self.outputSplit.setObjectName(u"outputSplit") - - self.gridLayout_2.addWidget(self.outputSplit, 3, 1, 1, 1) - - self.borderBox = QCheckBox(self.optionWidget) - self.borderBox.setObjectName(u"borderBox") - self.borderBox.setTristate(True) - - self.gridLayout_2.addWidget(self.borderBox, 3, 0, 1, 1) - - self.gammaBox = QCheckBox(self.optionWidget) - self.gammaBox.setObjectName(u"gammaBox") - - self.gridLayout_2.addWidget(self.gammaBox, 2, 2, 1, 1) - - self.upscaleBox = QCheckBox(self.optionWidget) - self.upscaleBox.setObjectName(u"upscaleBox") - self.upscaleBox.setTristate(True) - - self.gridLayout_2.addWidget(self.upscaleBox, 2, 1, 1, 1) - self.chunkSizeCheckBox = QCheckBox(self.optionWidget) self.chunkSizeCheckBox.setObjectName(u"chunkSizeCheckBox") self.gridLayout_2.addWidget(self.chunkSizeCheckBox, 7, 1, 1, 1) - self.colorBox = QCheckBox(self.optionWidget) - self.colorBox.setObjectName(u"colorBox") - - self.gridLayout_2.addWidget(self.colorBox, 3, 2, 1, 1) - - self.rotateBox = QCheckBox(self.optionWidget) - self.rotateBox.setObjectName(u"rotateBox") - self.rotateBox.setTristate(True) - - self.gridLayout_2.addWidget(self.rotateBox, 1, 1, 1, 1) - - self.spreadShiftBox = QCheckBox(self.optionWidget) - self.spreadShiftBox.setObjectName(u"spreadShiftBox") - - self.gridLayout_2.addWidget(self.spreadShiftBox, 5, 0, 1, 1) - - self.disableProcessingBox = QCheckBox(self.optionWidget) - self.disableProcessingBox.setObjectName(u"disableProcessingBox") - - self.gridLayout_2.addWidget(self.disableProcessingBox, 5, 2, 1, 1) - - self.eraseRainbowBox = QCheckBox(self.optionWidget) - self.eraseRainbowBox.setObjectName(u"eraseRainbowBox") - - self.gridLayout_2.addWidget(self.eraseRainbowBox, 7, 2, 1, 1) - - self.noRotateBox = QCheckBox(self.optionWidget) - self.noRotateBox.setObjectName(u"noRotateBox") - - self.gridLayout_2.addWidget(self.noRotateBox, 6, 1, 1, 1) - - self.fileFusionBox = QCheckBox(self.optionWidget) - self.fileFusionBox.setObjectName(u"fileFusionBox") - - self.gridLayout_2.addWidget(self.fileFusionBox, 6, 0, 1, 1) - - self.authorEdit = QLineEdit(self.optionWidget) - self.authorEdit.setObjectName(u"authorEdit") - sizePolicy4.setHeightForWidth(self.authorEdit.sizePolicy().hasHeightForWidth()) - self.authorEdit.setSizePolicy(sizePolicy4) - self.authorEdit.setFocusPolicy(Qt.FocusPolicy.ClickFocus) - self.authorEdit.setClearButtonEnabled(False) - - self.gridLayout_2.addWidget(self.authorEdit, 0, 1, 1, 1) - self.qualityBox = QCheckBox(self.optionWidget) self.qualityBox.setObjectName(u"qualityBox") self.qualityBox.setTristate(True) self.gridLayout_2.addWidget(self.qualityBox, 1, 2, 1, 1) - self.interPanelCropBox = QCheckBox(self.optionWidget) - self.interPanelCropBox.setObjectName(u"interPanelCropBox") - self.interPanelCropBox.setTristate(True) - - self.gridLayout_2.addWidget(self.interPanelCropBox, 6, 2, 1, 1) - - self.metadataTitleBox = QCheckBox(self.optionWidget) - self.metadataTitleBox.setObjectName(u"metadataTitleBox") - self.metadataTitleBox.setTristate(True) - - self.gridLayout_2.addWidget(self.metadataTitleBox, 7, 0, 1, 1) - - self.mozJpegBox = QCheckBox(self.optionWidget) - self.mozJpegBox.setObjectName(u"mozJpegBox") - self.mozJpegBox.setTristate(True) - - self.gridLayout_2.addWidget(self.mozJpegBox, 4, 0, 1, 1) - - self.autoLevelBox = QCheckBox(self.optionWidget) - self.autoLevelBox.setObjectName(u"autoLevelBox") - - self.gridLayout_2.addWidget(self.autoLevelBox, 8, 2, 1, 1) - - self.autocontrastBox = QCheckBox(self.optionWidget) - self.autocontrastBox.setObjectName(u"autocontrastBox") - self.autocontrastBox.setTristate(True) - - self.gridLayout_2.addWidget(self.autocontrastBox, 9, 2, 1, 1) - self.outputFolderWidget = QWidget(self.optionWidget) self.outputFolderWidget.setObjectName(u"outputFolderWidget") self.horizontalLayout_3 = QHBoxLayout(self.outputFolderWidget) @@ -457,20 +321,161 @@ class Ui_mainWindow(object): self.gridLayout_2.addWidget(self.outputFolderWidget, 0, 2, 1, 1) - self.jpegQualityBox = QCheckBox(self.optionWidget) - self.jpegQualityBox.setObjectName(u"jpegQualityBox") + self.deleteBox = QCheckBox(self.optionWidget) + self.deleteBox.setObjectName(u"deleteBox") - self.gridLayout_2.addWidget(self.jpegQualityBox, 8, 0, 1, 1) + self.gridLayout_2.addWidget(self.deleteBox, 5, 1, 1, 1) + + self.metadataTitleBox = QCheckBox(self.optionWidget) + self.metadataTitleBox.setObjectName(u"metadataTitleBox") + self.metadataTitleBox.setTristate(True) + + self.gridLayout_2.addWidget(self.metadataTitleBox, 7, 0, 1, 1) + + self.mozJpegBox = QCheckBox(self.optionWidget) + self.mozJpegBox.setObjectName(u"mozJpegBox") + self.mozJpegBox.setTristate(True) + + self.gridLayout_2.addWidget(self.mozJpegBox, 4, 0, 1, 1) + + self.interPanelCropBox = QCheckBox(self.optionWidget) + self.interPanelCropBox.setObjectName(u"interPanelCropBox") + self.interPanelCropBox.setTristate(True) + + self.gridLayout_2.addWidget(self.interPanelCropBox, 6, 2, 1, 1) + + self.autocontrastBox = QCheckBox(self.optionWidget) + self.autocontrastBox.setObjectName(u"autocontrastBox") + self.autocontrastBox.setTristate(True) + + self.gridLayout_2.addWidget(self.autocontrastBox, 9, 2, 1, 1) + + self.noRotateBox = QCheckBox(self.optionWidget) + self.noRotateBox.setObjectName(u"noRotateBox") + + self.gridLayout_2.addWidget(self.noRotateBox, 6, 1, 1, 1) + + self.coverFillBox = QCheckBox(self.optionWidget) + self.coverFillBox.setObjectName(u"coverFillBox") + + self.gridLayout_2.addWidget(self.coverFillBox, 9, 1, 1, 1) + + self.borderBox = QCheckBox(self.optionWidget) + self.borderBox.setObjectName(u"borderBox") + self.borderBox.setTristate(True) + + self.gridLayout_2.addWidget(self.borderBox, 3, 0, 1, 1) + + self.eraseRainbowBox = QCheckBox(self.optionWidget) + self.eraseRainbowBox.setObjectName(u"eraseRainbowBox") + + self.gridLayout_2.addWidget(self.eraseRainbowBox, 7, 2, 1, 1) + + self.titleEdit = QLineEdit(self.optionWidget) + self.titleEdit.setObjectName(u"titleEdit") + sizePolicy4.setHeightForWidth(self.titleEdit.sizePolicy().hasHeightForWidth()) + self.titleEdit.setSizePolicy(sizePolicy4) + self.titleEdit.setFocusPolicy(Qt.FocusPolicy.ClickFocus) + self.titleEdit.setClearButtonEnabled(False) + + self.gridLayout_2.addWidget(self.titleEdit, 0, 0, 1, 1) + + self.rotateBox = QCheckBox(self.optionWidget) + self.rotateBox.setObjectName(u"rotateBox") + self.rotateBox.setTristate(True) + + self.gridLayout_2.addWidget(self.rotateBox, 1, 1, 1, 1) + + self.authorEdit = QLineEdit(self.optionWidget) + self.authorEdit.setObjectName(u"authorEdit") + sizePolicy4.setHeightForWidth(self.authorEdit.sizePolicy().hasHeightForWidth()) + self.authorEdit.setSizePolicy(sizePolicy4) + self.authorEdit.setFocusPolicy(Qt.FocusPolicy.ClickFocus) + self.authorEdit.setClearButtonEnabled(False) + + self.gridLayout_2.addWidget(self.authorEdit, 0, 1, 1, 1) + + self.autoLevelBox = QCheckBox(self.optionWidget) + self.autoLevelBox.setObjectName(u"autoLevelBox") + + self.gridLayout_2.addWidget(self.autoLevelBox, 8, 2, 1, 1) + + self.rotateFirstBox = QCheckBox(self.optionWidget) + self.rotateFirstBox.setObjectName(u"rotateFirstBox") + + self.gridLayout_2.addWidget(self.rotateFirstBox, 8, 1, 1, 1) self.pdfExtractBox = QCheckBox(self.optionWidget) self.pdfExtractBox.setObjectName(u"pdfExtractBox") self.gridLayout_2.addWidget(self.pdfExtractBox, 9, 0, 1, 1) - self.coverFillBox = QCheckBox(self.optionWidget) - self.coverFillBox.setObjectName(u"coverFillBox") + self.outputSplit = QCheckBox(self.optionWidget) + self.outputSplit.setObjectName(u"outputSplit") - self.gridLayout_2.addWidget(self.coverFillBox, 9, 1, 1, 1) + self.gridLayout_2.addWidget(self.outputSplit, 3, 1, 1, 1) + + self.jpegQualityBox = QCheckBox(self.optionWidget) + self.jpegQualityBox.setObjectName(u"jpegQualityBox") + + self.gridLayout_2.addWidget(self.jpegQualityBox, 8, 0, 1, 1) + + self.upscaleBox = QCheckBox(self.optionWidget) + self.upscaleBox.setObjectName(u"upscaleBox") + self.upscaleBox.setTristate(True) + + self.gridLayout_2.addWidget(self.upscaleBox, 2, 1, 1, 1) + + self.gammaBox = QCheckBox(self.optionWidget) + self.gammaBox.setObjectName(u"gammaBox") + + self.gridLayout_2.addWidget(self.gammaBox, 2, 2, 1, 1) + + self.spreadShiftBox = QCheckBox(self.optionWidget) + self.spreadShiftBox.setObjectName(u"spreadShiftBox") + + self.gridLayout_2.addWidget(self.spreadShiftBox, 5, 0, 1, 1) + + self.fileFusionBox = QCheckBox(self.optionWidget) + self.fileFusionBox.setObjectName(u"fileFusionBox") + + self.gridLayout_2.addWidget(self.fileFusionBox, 6, 0, 1, 1) + + self.colorBox = QCheckBox(self.optionWidget) + self.colorBox.setObjectName(u"colorBox") + + self.gridLayout_2.addWidget(self.colorBox, 3, 2, 1, 1) + + self.croppingBox = QCheckBox(self.optionWidget) + self.croppingBox.setObjectName(u"croppingBox") + self.croppingBox.setTristate(True) + + self.gridLayout_2.addWidget(self.croppingBox, 4, 2, 1, 1) + + self.webtoonBox = QCheckBox(self.optionWidget) + self.webtoonBox.setObjectName(u"webtoonBox") + + self.gridLayout_2.addWidget(self.webtoonBox, 2, 0, 1, 1) + + self.disableProcessingBox = QCheckBox(self.optionWidget) + self.disableProcessingBox.setObjectName(u"disableProcessingBox") + + self.gridLayout_2.addWidget(self.disableProcessingBox, 5, 2, 1, 1) + + self.mangaBox = QCheckBox(self.optionWidget) + self.mangaBox.setObjectName(u"mangaBox") + + self.gridLayout_2.addWidget(self.mangaBox, 1, 0, 1, 1) + + self.maximizeStrips = QCheckBox(self.optionWidget) + self.maximizeStrips.setObjectName(u"maximizeStrips") + + self.gridLayout_2.addWidget(self.maximizeStrips, 4, 1, 1, 1) + + self.rotateRightBox = QCheckBox(self.optionWidget) + self.rotateRightBox.setObjectName(u"rotateRightBox") + + self.gridLayout_2.addWidget(self.rotateRightBox, 10, 1, 1, 1) self.gridLayout.addWidget(self.optionWidget, 5, 0, 1, 2) @@ -624,110 +629,14 @@ class Ui_mainWindow(object): #endif // QT_CONFIG(tooltip) self.chunkSizeLabel.setText(QCoreApplication.translate("mainWindow", u"Chunk size MB:", None)) self.chunkSizeWarnLabel.setText(QCoreApplication.translate("mainWindow", u"Greater than default may cause performance issues on older ereaders.", None)) -#if QT_CONFIG(tooltip) - self.titleEdit.setToolTip(QCoreApplication.translate("mainWindow", u"

Default Title

", None)) -#endif // QT_CONFIG(tooltip) - self.titleEdit.setPlaceholderText(QCoreApplication.translate("mainWindow", u"Default Title", None)) -#if QT_CONFIG(tooltip) - self.mangaBox.setToolTip(QCoreApplication.translate("mainWindow", u"

Enable right-to-left reading.

", None)) -#endif // QT_CONFIG(tooltip) - self.mangaBox.setText(QCoreApplication.translate("mainWindow", u"Right-to-left (manga)", None)) -#if QT_CONFIG(tooltip) - self.webtoonBox.setToolTip(QCoreApplication.translate("mainWindow", u"

Enable special parsing mode for Korean Webtoons.

", None)) -#endif // QT_CONFIG(tooltip) - self.webtoonBox.setText(QCoreApplication.translate("mainWindow", u"Webtoon mode", None)) -#if QT_CONFIG(tooltip) - self.croppingBox.setToolTip(QCoreApplication.translate("mainWindow", u"

Unchecked - Disabled

Disabled

Indeterminate - Margins
Margins

Checked - Margins + page numbers
Margins +page numbers

", None)) -#endif // QT_CONFIG(tooltip) - self.croppingBox.setText(QCoreApplication.translate("mainWindow", u"Cropping mode", None)) -#if QT_CONFIG(tooltip) - self.maximizeStrips.setToolTip(QCoreApplication.translate("mainWindow", u"

Unchecked - 1x4
Keep format 1x4 panels strips.

Checked - 2x2
Turn 1x4 strips to 2x2 to maximize screen usage.

", None)) -#endif // QT_CONFIG(tooltip) - self.maximizeStrips.setText(QCoreApplication.translate("mainWindow", u"1x4 to 2x2 strips", None)) -#if QT_CONFIG(tooltip) - self.deleteBox.setToolTip(QCoreApplication.translate("mainWindow", u"Delete input file(s) or directory. It's not recoverable!", None)) -#endif // QT_CONFIG(tooltip) - self.deleteBox.setText(QCoreApplication.translate("mainWindow", u"Delete input", None)) -#if QT_CONFIG(tooltip) - self.rotateFirstBox.setToolTip(QCoreApplication.translate("mainWindow", u"

When the spread splitter option is partially checked,

Unchecked - Rotate Last
Put the rotated 2 page spread after the split spreads.

Checked - Rotate First
Put the rotated 2 page spread before the split spreads.

", None)) -#endif // QT_CONFIG(tooltip) - self.rotateFirstBox.setText(QCoreApplication.translate("mainWindow", u"Rotate First", None)) -#if QT_CONFIG(tooltip) - self.outputSplit.setToolTip(QCoreApplication.translate("mainWindow", u"

Unchecked - Automatic mode
The output will be split automatically.

Checked - Volume mode
Every subdirectory will be considered as a separate volume.

", None)) -#endif // QT_CONFIG(tooltip) - self.outputSplit.setText(QCoreApplication.translate("mainWindow", u"Output split", None)) -#if QT_CONFIG(tooltip) - self.borderBox.setToolTip(QCoreApplication.translate("mainWindow", u"

Unchecked - Autodetection
The color of margins fill will be detected automatically.

Indeterminate - White
Margins will be untouched.

Checked - Black
Margins will be filled with black color.

", None)) -#endif // QT_CONFIG(tooltip) - self.borderBox.setText(QCoreApplication.translate("mainWindow", u"W/B margins", None)) -#if QT_CONFIG(tooltip) - self.gammaBox.setToolTip(QCoreApplication.translate("mainWindow", u"

Set a custom gamma correction.

1.0 is default (disabled).
< 1.0 makes the image brighter.
> 1.0 makes the image darker.

1.8 was the default in KCC 9.1.0 and earlier.

Use if you want to make midtones darker.

", None)) -#endif // QT_CONFIG(tooltip) - self.gammaBox.setText(QCoreApplication.translate("mainWindow", u"Custom gamma", None)) -#if QT_CONFIG(tooltip) - self.upscaleBox.setToolTip(QCoreApplication.translate("mainWindow", u"

Unchecked - Nothing
Images smaller than device resolution will not be resized.

Indeterminate - Stretching
Images smaller than device resolution will be resized. Aspect ratio will be not preserved.

Checked - Upscaling
Images smaller than device resolution will be resized. Aspect ratio will be preserved.

", None)) -#endif // QT_CONFIG(tooltip) - self.upscaleBox.setText(QCoreApplication.translate("mainWindow", u"Stretch/Upscale", None)) #if QT_CONFIG(tooltip) self.chunkSizeCheckBox.setToolTip(QCoreApplication.translate("mainWindow", u"

Unchecked
Maximal output file size is 100 MB for Webtoon, 400 MB for others before split occurs.

Checked
Output file size specified in "Chunk size MB" before split occurs.

", None)) #endif // QT_CONFIG(tooltip) self.chunkSizeCheckBox.setText(QCoreApplication.translate("mainWindow", u"Chunk size", None)) -#if QT_CONFIG(tooltip) - self.colorBox.setToolTip(QCoreApplication.translate("mainWindow", u"

Disable conversion to grayscale.

", None)) -#endif // QT_CONFIG(tooltip) - self.colorBox.setText(QCoreApplication.translate("mainWindow", u"Color mode", None)) -#if QT_CONFIG(tooltip) - self.rotateBox.setToolTip(QCoreApplication.translate("mainWindow", u"

Unchecked - Split
Double page spreads will be cut into two separate pages.

Indeterminate - Split and rotate
Double page spreads will be displayed twice. First split and then rotated.

Checked - Rotate
Double page spreads will be rotated.

", None)) -#endif // QT_CONFIG(tooltip) - self.rotateBox.setText(QCoreApplication.translate("mainWindow", u"Spread splitter", None)) -#if QT_CONFIG(tooltip) - self.spreadShiftBox.setToolTip(QCoreApplication.translate("mainWindow", u"Shift first page to opposite side in landscape for two page spread alignment", None)) -#endif // QT_CONFIG(tooltip) - self.spreadShiftBox.setText(QCoreApplication.translate("mainWindow", u"Spread shift", None)) -#if QT_CONFIG(tooltip) - self.disableProcessingBox.setToolTip(QCoreApplication.translate("mainWindow", u"

Do not process any image, ignore profile and processing options.

", None)) -#endif // QT_CONFIG(tooltip) - self.disableProcessingBox.setText(QCoreApplication.translate("mainWindow", u"Disable processing", None)) -#if QT_CONFIG(tooltip) - self.eraseRainbowBox.setToolTip(QCoreApplication.translate("mainWindow", u"Erase rainbow effect on color eink screen by attenuating interfering frequencies", None)) -#endif // QT_CONFIG(tooltip) - self.eraseRainbowBox.setText(QCoreApplication.translate("mainWindow", u"Rainbow eraser", None)) -#if QT_CONFIG(tooltip) - self.noRotateBox.setToolTip(QCoreApplication.translate("mainWindow", u"Do not rotate double page spreads in spread splitter option.", None)) -#endif // QT_CONFIG(tooltip) - self.noRotateBox.setText(QCoreApplication.translate("mainWindow", u"No rotate", None)) -#if QT_CONFIG(tooltip) - self.fileFusionBox.setToolTip(QCoreApplication.translate("mainWindow", u"

Combines all selected files into a single file. (Helpful for combining chapters into volumes.)

", None)) -#endif // QT_CONFIG(tooltip) - self.fileFusionBox.setText(QCoreApplication.translate("mainWindow", u"File Fusion", None)) -#if QT_CONFIG(tooltip) - self.authorEdit.setToolTip(QCoreApplication.translate("mainWindow", u"Default Author is KCC", None)) -#endif // QT_CONFIG(tooltip) - self.authorEdit.setPlaceholderText(QCoreApplication.translate("mainWindow", u"Default Author", None)) #if QT_CONFIG(tooltip) self.qualityBox.setToolTip(QCoreApplication.translate("mainWindow", u"

Unchecked - 4 panels
Zoom each corner separately.

Indeterminate - 2 panels
Zoom only the top and bottom of the page.

Checked - 4 high-quality panels
Zoom each corner separately. Try to increase the quality of magnification. Check wiki for more details.

", None)) #endif // QT_CONFIG(tooltip) self.qualityBox.setText(QCoreApplication.translate("mainWindow", u"Panel View 4/2/HQ", None)) -#if QT_CONFIG(tooltip) - self.interPanelCropBox.setToolTip(QCoreApplication.translate("mainWindow", u"

Unchecked - Disabled
Disabled

Indeterminate - Horizontal
Crop empty horizontal lines.

Checked - Both
Crop empty horizontal and vertical lines.

", None)) -#endif // QT_CONFIG(tooltip) - self.interPanelCropBox.setText(QCoreApplication.translate("mainWindow", u"Inter-panel crop", None)) -#if QT_CONFIG(tooltip) - self.metadataTitleBox.setToolTip(QCoreApplication.translate("mainWindow", u"

Unchecked - Don't use metadata Title
Write default title.

Indeterminate - Add metadata Title to the default schema
Write default title with Title from ComicInfo.xml or other embedded metadata.

Checked - Use metadata Title only
Write Title from ComicInfo.xml or other embedded metadata.

", None)) -#endif // QT_CONFIG(tooltip) - self.metadataTitleBox.setText(QCoreApplication.translate("mainWindow", u"Metadata Title", None)) -#if QT_CONFIG(tooltip) - self.mozJpegBox.setToolTip(QCoreApplication.translate("mainWindow", u"

Unchecked - JPEG
Use JPEG files

Indeterminate - force PNG
Create PNG files instead JPEG

Checked - mozJpeg
10-20% smaller JPEG file, with the same image quality, but processing time multiplied by 2

", None)) -#endif // QT_CONFIG(tooltip) - self.mozJpegBox.setText(QCoreApplication.translate("mainWindow", u"JPEG/PNG/mozJpeg", None)) -#if QT_CONFIG(tooltip) - self.autoLevelBox.setToolTip(QCoreApplication.translate("mainWindow", u"

By default, KCC maps the darkest pixel value to pure black (the black point.)

Extreme black point sets the black point to be the most common dark pixel value.

Useful when text is black but artwork is gray.

", None)) -#endif // QT_CONFIG(tooltip) - self.autoLevelBox.setText(QCoreApplication.translate("mainWindow", u"Extreme Black Point", None)) -#if QT_CONFIG(tooltip) - self.autocontrastBox.setToolTip(QCoreApplication.translate("mainWindow", u"

Unchecked - BW only
Only autocontrast bw pages. Ignored for pages where near blacks or whites don't exist.

Indeterminate - Disabled
Disable autocontrast

Checked - BW and Color
BW and color images will be autocontrasted. Ignored for pages where near blacks or whites don't exist.

", None)) -#endif // QT_CONFIG(tooltip) - self.autocontrastBox.setText(QCoreApplication.translate("mainWindow", u"Autocontrast", None)) #if QT_CONFIG(tooltip) self.defaultOutputFolderBox.setToolTip(QCoreApplication.translate("mainWindow", u"

Unchecked - next to source
Place output files next to source files

Indeterminate - folder next to source
Place output files in a folder next to source files

Checked - Custom
Place output files in custom directory specified by right button

", None)) #endif // QT_CONFIG(tooltip) @@ -736,6 +645,73 @@ class Ui_mainWindow(object): self.defaultOutputFolderButton.setToolTip(QCoreApplication.translate("mainWindow", u"

Use this to select the default output directory.

", None)) #endif // QT_CONFIG(tooltip) self.defaultOutputFolderButton.setText("") +#if QT_CONFIG(tooltip) + self.deleteBox.setToolTip(QCoreApplication.translate("mainWindow", u"Delete input file(s) or directory. It's not recoverable!", None)) +#endif // QT_CONFIG(tooltip) + self.deleteBox.setText(QCoreApplication.translate("mainWindow", u"Delete input", None)) +#if QT_CONFIG(tooltip) + self.metadataTitleBox.setToolTip(QCoreApplication.translate("mainWindow", u"

Unchecked - Don't use metadata Title
Write default title.

Indeterminate - Add metadata Title to the default schema
Write default title with Title from ComicInfo.xml or other embedded metadata.

Checked - Use metadata Title only
Write Title from ComicInfo.xml or other embedded metadata.

", None)) +#endif // QT_CONFIG(tooltip) + self.metadataTitleBox.setText(QCoreApplication.translate("mainWindow", u"Metadata Title", None)) +#if QT_CONFIG(tooltip) + self.mozJpegBox.setToolTip(QCoreApplication.translate("mainWindow", u"

Unchecked - JPEG
Use JPEG files

Indeterminate - force PNG
Create PNG files instead JPEG

Checked - mozJpeg
10-20% smaller JPEG file, with the same image quality, but processing time multiplied by 2

", None)) +#endif // QT_CONFIG(tooltip) + self.mozJpegBox.setText(QCoreApplication.translate("mainWindow", u"JPEG/PNG/mozJpeg", None)) +#if QT_CONFIG(tooltip) + self.interPanelCropBox.setToolTip(QCoreApplication.translate("mainWindow", u"

Unchecked - Disabled
Disabled

Indeterminate - Horizontal
Crop empty horizontal lines.

Checked - Both
Crop empty horizontal and vertical lines.

", None)) +#endif // QT_CONFIG(tooltip) + self.interPanelCropBox.setText(QCoreApplication.translate("mainWindow", u"Inter-panel crop", None)) +#if QT_CONFIG(tooltip) + self.autocontrastBox.setToolTip(QCoreApplication.translate("mainWindow", u"

Unchecked - BW only
Only autocontrast bw pages. Ignored for pages where near blacks or whites don't exist.

Indeterminate - Disabled
Disable autocontrast

Checked - BW and Color
BW and color images will be autocontrasted. Ignored for pages where near blacks or whites don't exist.

", None)) +#endif // QT_CONFIG(tooltip) + self.autocontrastBox.setText(QCoreApplication.translate("mainWindow", u"Autocontrast", None)) +#if QT_CONFIG(tooltip) + self.noRotateBox.setToolTip(QCoreApplication.translate("mainWindow", u"Do not rotate double page spreads in spread splitter option.", None)) +#endif // QT_CONFIG(tooltip) + self.noRotateBox.setText(QCoreApplication.translate("mainWindow", u"No rotate", None)) +#if QT_CONFIG(tooltip) + self.coverFillBox.setToolTip(QCoreApplication.translate("mainWindow", u"Resize cover to exact device resolution by center-cropping to aspect ratio first.\n" +"May crop top/bottom or left/right depending on source aspect ratio. Not implemented for Kindle Scribe.", None)) +#endif // QT_CONFIG(tooltip) + self.coverFillBox.setText(QCoreApplication.translate("mainWindow", u"Cover Fill", None)) +#if QT_CONFIG(tooltip) + self.borderBox.setToolTip(QCoreApplication.translate("mainWindow", u"

Unchecked - Autodetection
The color of margins fill will be detected automatically.

Indeterminate - White
Margins will be untouched.

Checked - Black
Margins will be filled with black color.

", None)) +#endif // QT_CONFIG(tooltip) + self.borderBox.setText(QCoreApplication.translate("mainWindow", u"W/B margins", None)) +#if QT_CONFIG(tooltip) + self.eraseRainbowBox.setToolTip(QCoreApplication.translate("mainWindow", u"Erase rainbow effect on color eink screen by attenuating interfering frequencies", None)) +#endif // QT_CONFIG(tooltip) + self.eraseRainbowBox.setText(QCoreApplication.translate("mainWindow", u"Rainbow eraser", None)) +#if QT_CONFIG(tooltip) + self.titleEdit.setToolTip(QCoreApplication.translate("mainWindow", u"

Default Title

", None)) +#endif // QT_CONFIG(tooltip) + self.titleEdit.setPlaceholderText(QCoreApplication.translate("mainWindow", u"Default Title", None)) +#if QT_CONFIG(tooltip) + self.rotateBox.setToolTip(QCoreApplication.translate("mainWindow", u"

Unchecked - Split
Double page spreads will be cut into two separate pages.

Indeterminate - Split and rotate
Double page spreads will be displayed twice. First split and then rotated.

Checked - Rotate
Double page spreads will be rotated.

", None)) +#endif // QT_CONFIG(tooltip) + self.rotateBox.setText(QCoreApplication.translate("mainWindow", u"Spread splitter", None)) +#if QT_CONFIG(tooltip) + self.authorEdit.setToolTip(QCoreApplication.translate("mainWindow", u"Default Author is KCC", None)) +#endif // QT_CONFIG(tooltip) + self.authorEdit.setPlaceholderText(QCoreApplication.translate("mainWindow", u"Default Author", None)) +#if QT_CONFIG(tooltip) + self.autoLevelBox.setToolTip(QCoreApplication.translate("mainWindow", u"

By default, KCC maps the darkest pixel value to pure black (the black point.)

Extreme black point sets the black point to be the most common dark pixel value.

Useful when text is black but artwork is gray.

", None)) +#endif // QT_CONFIG(tooltip) + self.autoLevelBox.setText(QCoreApplication.translate("mainWindow", u"Extreme Black Point", None)) +#if QT_CONFIG(tooltip) + self.rotateFirstBox.setToolTip(QCoreApplication.translate("mainWindow", u"

When the spread splitter option is partially checked,

Unchecked - Rotate Last
Put the rotated 2 page spread after the split spreads.

Checked - Rotate First
Put the rotated 2 page spread before the split spreads.

", None)) +#endif // QT_CONFIG(tooltip) + self.rotateFirstBox.setText(QCoreApplication.translate("mainWindow", u"Rotate First", None)) +#if QT_CONFIG(tooltip) + self.pdfExtractBox.setToolTip(QCoreApplication.translate("mainWindow", u"Use the PDF image extraction method from KCC 8 and earlier.\n" +"\n" +"Useful for really weird PDFs.", None)) +#endif // QT_CONFIG(tooltip) + self.pdfExtractBox.setText(QCoreApplication.translate("mainWindow", u"PDF Legacy Extract", None)) +#if QT_CONFIG(tooltip) + self.outputSplit.setToolTip(QCoreApplication.translate("mainWindow", u"

Unchecked - Automatic mode
The output will be split automatically.

Checked - Volume mode
Every subdirectory will be considered as a separate volume.

", None)) +#endif // QT_CONFIG(tooltip) + self.outputSplit.setText(QCoreApplication.translate("mainWindow", u"Output split", None)) #if QT_CONFIG(tooltip) self.jpegQualityBox.setToolTip(QCoreApplication.translate("mainWindow", u"The JPEG quality, on a scale from 0 (worst) to 95 (best). \n" "\n" @@ -745,16 +721,49 @@ class Ui_mainWindow(object): #endif // QT_CONFIG(tooltip) self.jpegQualityBox.setText(QCoreApplication.translate("mainWindow", u"Custom JPEG Quality", None)) #if QT_CONFIG(tooltip) - self.pdfExtractBox.setToolTip(QCoreApplication.translate("mainWindow", u"Use the PDF image extraction method from KCC 8 and earlier.\n" -"\n" -"Useful for really weird PDFs.", None)) + self.upscaleBox.setToolTip(QCoreApplication.translate("mainWindow", u"

Unchecked - Nothing
Images smaller than device resolution will not be resized.

Indeterminate - Stretching
Images smaller than device resolution will be resized. Aspect ratio will be not preserved.

Checked - Upscaling
Images smaller than device resolution will be resized. Aspect ratio will be preserved.

", None)) #endif // QT_CONFIG(tooltip) - self.pdfExtractBox.setText(QCoreApplication.translate("mainWindow", u"PDF Legacy Extract", None)) + self.upscaleBox.setText(QCoreApplication.translate("mainWindow", u"Stretch/Upscale", None)) #if QT_CONFIG(tooltip) - self.coverFillBox.setToolTip(QCoreApplication.translate("mainWindow", u"Resize cover to exact device resolution by center-cropping to aspect ratio first.\n" -"May crop top/bottom or left/right depending on source aspect ratio. Not implemented for Kindle Scribe.", None)) + self.gammaBox.setToolTip(QCoreApplication.translate("mainWindow", u"

Set a custom gamma correction.

1.0 is default (disabled).
< 1.0 makes the image brighter.
> 1.0 makes the image darker.

1.8 was the default in KCC 9.1.0 and earlier.

Use if you want to make midtones darker.

", None)) #endif // QT_CONFIG(tooltip) - self.coverFillBox.setText(QCoreApplication.translate("mainWindow", u"Cover Fill", None)) + self.gammaBox.setText(QCoreApplication.translate("mainWindow", u"Custom gamma", None)) +#if QT_CONFIG(tooltip) + self.spreadShiftBox.setToolTip(QCoreApplication.translate("mainWindow", u"Shift first page to opposite side in landscape for two page spread alignment", None)) +#endif // QT_CONFIG(tooltip) + self.spreadShiftBox.setText(QCoreApplication.translate("mainWindow", u"Spread shift", None)) +#if QT_CONFIG(tooltip) + self.fileFusionBox.setToolTip(QCoreApplication.translate("mainWindow", u"

Combines all selected files into a single file. (Helpful for combining chapters into volumes.)

", None)) +#endif // QT_CONFIG(tooltip) + self.fileFusionBox.setText(QCoreApplication.translate("mainWindow", u"File Fusion", None)) +#if QT_CONFIG(tooltip) + self.colorBox.setToolTip(QCoreApplication.translate("mainWindow", u"

Disable conversion to grayscale.

", None)) +#endif // QT_CONFIG(tooltip) + self.colorBox.setText(QCoreApplication.translate("mainWindow", u"Color mode", None)) +#if QT_CONFIG(tooltip) + self.croppingBox.setToolTip(QCoreApplication.translate("mainWindow", u"

Unchecked - Disabled

Disabled

Indeterminate - Margins
Margins

Checked - Margins + page numbers
Margins +page numbers

", None)) +#endif // QT_CONFIG(tooltip) + self.croppingBox.setText(QCoreApplication.translate("mainWindow", u"Cropping mode", None)) +#if QT_CONFIG(tooltip) + self.webtoonBox.setToolTip(QCoreApplication.translate("mainWindow", u"

Enable special parsing mode for Korean Webtoons.

", None)) +#endif // QT_CONFIG(tooltip) + self.webtoonBox.setText(QCoreApplication.translate("mainWindow", u"Webtoon mode", None)) +#if QT_CONFIG(tooltip) + self.disableProcessingBox.setToolTip(QCoreApplication.translate("mainWindow", u"

Do not process any image, ignore profile and processing options.

", None)) +#endif // QT_CONFIG(tooltip) + self.disableProcessingBox.setText(QCoreApplication.translate("mainWindow", u"Disable processing", None)) +#if QT_CONFIG(tooltip) + self.mangaBox.setToolTip(QCoreApplication.translate("mainWindow", u"

Enable right-to-left reading.

", None)) +#endif // QT_CONFIG(tooltip) + self.mangaBox.setText(QCoreApplication.translate("mainWindow", u"Right-to-left (manga)", None)) +#if QT_CONFIG(tooltip) + self.maximizeStrips.setToolTip(QCoreApplication.translate("mainWindow", u"

Unchecked - 1x4
Keep format 1x4 panels strips.

Checked - 2x2
Turn 1x4 strips to 2x2 to maximize screen usage.

", None)) +#endif // QT_CONFIG(tooltip) + self.maximizeStrips.setText(QCoreApplication.translate("mainWindow", u"1x4 to 2x2 strips", None)) +#if QT_CONFIG(tooltip) + self.rotateRightBox.setToolTip(QCoreApplication.translate("mainWindow", u"Rotate 2 page spreads in opposite direction than normal.", None)) +#endif // QT_CONFIG(tooltip) + self.rotateRightBox.setText(QCoreApplication.translate("mainWindow", u"Rotate Right", None)) self.gammaLabel.setText(QCoreApplication.translate("mainWindow", u"Gamma: Auto", None)) self.jpegQualityLabel.setText(QCoreApplication.translate("mainWindow", u"JPEG Quality:", None)) # retranslateUi diff --git a/kindlecomicconverter/comic2ebook.py b/kindlecomicconverter/comic2ebook.py index 30c99c6..1cfc5c9 100755 --- a/kindlecomicconverter/comic2ebook.py +++ b/kindlecomicconverter/comic2ebook.py @@ -1356,6 +1356,8 @@ def makeParser(): help="Shift first page to opposite side in landscape for spread alignment") output_options.add_argument("--norotate", action="store_true", dest="norotate", default=False, help="Do not rotate double page spreads in spread splitter option.") + output_options.add_argument("--rotateright", action="store_true", dest="rotateright", default=False, + help="Rotate double page spreads in opposite direction.") output_options.add_argument("--rotatefirst", action="store_true", dest="rotatefirst", default=False, help="Put rotated 2 page spread first in spread splitter option.") diff --git a/kindlecomicconverter/image.py b/kindlecomicconverter/image.py index 3341940..635886f 100755 --- a/kindlecomicconverter/image.py +++ b/kindlecomicconverter/image.py @@ -197,7 +197,10 @@ class ComicPageParser: and not self.opt.webtoon and self.opt.splitter == 1: spread = self.image if not self.opt.norotate: - spread = spread.rotate(90, Image.Resampling.BICUBIC, True) + if not self.opt.rotateright: + spread = spread.rotate(90, Image.Resampling.BICUBIC, True) + else: + spread = spread.rotate(-90, Image.Resampling.BICUBIC, True) self.payload.append(['R', self.source, spread, self.fill]) elif (width > height) != (dstwidth > dstheight) and not self.opt.webtoon: if self.opt.splitter != 1: @@ -218,7 +221,10 @@ class ComicPageParser: if self.opt.splitter > 0: spread = self.image if not self.opt.norotate: - spread = spread.rotate(90, Image.Resampling.BICUBIC, True) + if not self.opt.rotateright: + spread = spread.rotate(90, Image.Resampling.BICUBIC, True) + else: + spread = spread.rotate(-90, Image.Resampling.BICUBIC, True) self.payload.append(['R', self.source, spread, self.fill]) else: self.payload.append(['N', self.source, self.image, self.fill]) From b4b9e41a0c7fafe2f48b6872785679141ce17041 Mon Sep 17 00:00:00 2001 From: Alex Xu Date: Fri, 13 Mar 2026 15:07:41 -0700 Subject: [PATCH 056/107] add no quantize option (#1269) --- README.md | 1 + gui/KCC.ui | 14 ++++++++++++++ kindlecomicconverter/KCC_gui.py | 3 +++ kindlecomicconverter/KCC_ui.py | 13 +++++++++++++ kindlecomicconverter/comic2ebook.py | 5 ++++- 5 files changed, 35 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 52fbf64..9f40d6a 100644 --- a/README.md +++ b/README.md @@ -272,6 +272,7 @@ PROCESSING: --coverfill Center-crop only the cover to fill target device screen --forcecolor Don't convert images to grayscale --forcepng Create PNG files instead JPEG + --noquantize Don't quantize PNG images to 16 colors --mozjpeg Create JPEG files using mozJpeg --jpeg-quality The JPEG quality, on a scale from 0 (worst) to 95 (best). Default 85 for most devices. --maximizestrips Turn 1x4 strips to 2x2 strips diff --git a/gui/KCC.ui b/gui/KCC.ui index fcc5f07..d938ed0 100644 --- a/gui/KCC.ui +++ b/gui/KCC.ui @@ -929,6 +929,20 @@ Higher values are larger and higher quality, and may resolve blank page issues.<
+ + + + Don't quantize PNG images to 16 colors (4 bit) + +This will double file size but preserve all 256 colors (8 bit). + +Eink only has 16 shades of gray so you probably don't want this. + + + No Quantize + + + diff --git a/kindlecomicconverter/KCC_gui.py b/kindlecomicconverter/KCC_gui.py index 73e8f91..e11419d 100644 --- a/kindlecomicconverter/KCC_gui.py +++ b/kindlecomicconverter/KCC_gui.py @@ -353,6 +353,8 @@ class WorkerThread(QThread): options.forcepng = True elif GUI.mozJpegBox.checkState() == Qt.CheckState.Checked: options.mozjpeg = True + if GUI.noQuantizeBox.isChecked(): + options.noquantize = True if GUI.jpegQualityBox.isChecked(): options.jpegquality = GUI.jpegQualitySpinBox.value() if GUI.currentMode > 2: @@ -1045,6 +1047,7 @@ class KCCGUI(KCC_ui.Ui_mainWindow): 'coverFillBox': GUI.coverFillBox.checkState(), 'metadataTitleBox': GUI.metadataTitleBox.checkState(), 'mozJpegBox': GUI.mozJpegBox.checkState(), + 'noQuantizeBox': GUI.noQuantizeBox.checkState(), 'jpegQualityBox': GUI.jpegQualityBox.checkState(), 'jpegQuality': GUI.jpegQualitySpinBox.value(), 'widthBox': GUI.widthBox.value(), diff --git a/kindlecomicconverter/KCC_ui.py b/kindlecomicconverter/KCC_ui.py index 1cdb648..115ded1 100644 --- a/kindlecomicconverter/KCC_ui.py +++ b/kindlecomicconverter/KCC_ui.py @@ -477,6 +477,11 @@ class Ui_mainWindow(object): self.gridLayout_2.addWidget(self.rotateRightBox, 10, 1, 1, 1) + self.noQuantizeBox = QCheckBox(self.optionWidget) + self.noQuantizeBox.setObjectName(u"noQuantizeBox") + + self.gridLayout_2.addWidget(self.noQuantizeBox, 10, 2, 1, 1) + self.gridLayout.addWidget(self.optionWidget, 5, 0, 1, 2) @@ -764,6 +769,14 @@ class Ui_mainWindow(object): self.rotateRightBox.setToolTip(QCoreApplication.translate("mainWindow", u"Rotate 2 page spreads in opposite direction than normal.", None)) #endif // QT_CONFIG(tooltip) self.rotateRightBox.setText(QCoreApplication.translate("mainWindow", u"Rotate Right", None)) +#if QT_CONFIG(tooltip) + self.noQuantizeBox.setToolTip(QCoreApplication.translate("mainWindow", u"Don't quantize PNG images to 16 colors (4 bit)\n" +"\n" +"This will double file size but preserve all 256 colors (8 bit).\n" +"\n" +"Eink only has 16 shades of gray so you probably don't want this.", None)) +#endif // QT_CONFIG(tooltip) + self.noQuantizeBox.setText(QCoreApplication.translate("mainWindow", u"No Quantize", None)) self.gammaLabel.setText(QCoreApplication.translate("mainWindow", u"Gamma: Auto", None)) self.jpegQualityLabel.setText(QCoreApplication.translate("mainWindow", u"JPEG Quality:", None)) # retranslateUi diff --git a/kindlecomicconverter/comic2ebook.py b/kindlecomicconverter/comic2ebook.py index 1cfc5c9..9aa1052 100755 --- a/kindlecomicconverter/comic2ebook.py +++ b/kindlecomicconverter/comic2ebook.py @@ -707,7 +707,8 @@ def imgFileProcessing(work): pass elif opt.forcepng: img.convertToGrayscale() - img.quantizeImage() + if not opt.noquantize: + img.quantizeImage() if opt.format == 'PDF': img.convertToGrayscale() elif opt.profile == 'KDX' and opt.format == 'CBZ': @@ -1403,6 +1404,8 @@ def makeParser(): help="Erase rainbow effect on color eink screen by attenuating interfering frequencies") processing_options.add_argument("--forcepng", action="store_true", dest="forcepng", default=False, help="Create PNG files instead JPEG") + processing_options.add_argument("--noquantize", action="store_true", dest="noquantize", default=False, + help="Don't quantize to 16 color PNG") processing_options.add_argument("--mozjpeg", action="store_true", dest="mozjpeg", default=False, help="Create JPEG files using mozJpeg") processing_options.add_argument("--jpeg-quality", type=int, dest="jpegquality", From b5de6fd39d79e4d3666d8ad88195af2776065a19 Mon Sep 17 00:00:00 2001 From: Alex Xu Date: Fri, 13 Mar 2026 16:28:53 -0700 Subject: [PATCH 057/107] add pdf width box (#1270) --- README.md | 1 + gui/KCC.ui | 12 ++++++++++++ kindlecomicconverter/KCC_gui.py | 4 ++++ kindlecomicconverter/KCC_ui.py | 11 +++++++++++ kindlecomicconverter/comic2ebook.py | 19 +++++++++++++------ kindlecomicconverter/image.py | 4 +++- 6 files changed, 44 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 9f40d6a..d228a64 100644 --- a/README.md +++ b/README.md @@ -249,6 +249,7 @@ MAIN: PROCESSING: -n, --noprocessing Do not modify image and ignore any profile or processing option --pdfextract Use legacy PDF image extraction method from KCC 8 and earlier. + --pdfwidth Render vector PDFs based on device width instead of height. -u, --upscale Resize images smaller than device's resolution -s, --stretch Stretch images to device's resolution -r SPLITTER, --splitter SPLITTER diff --git a/gui/KCC.ui b/gui/KCC.ui index d938ed0..3fed46e 100644 --- a/gui/KCC.ui +++ b/gui/KCC.ui @@ -943,6 +943,18 @@ Eink only has 16 shades of gray so you probably don't want this. + + + + Render vector PDFs to device width instead of height. + +Useful if you plan to crop a little off the top and bottom to fill screen. + + + PDF Width Render + + + diff --git a/kindlecomicconverter/KCC_gui.py b/kindlecomicconverter/KCC_gui.py index e11419d..7dc05a9 100644 --- a/kindlecomicconverter/KCC_gui.py +++ b/kindlecomicconverter/KCC_gui.py @@ -329,6 +329,8 @@ class WorkerThread(QThread): options.noprocessing = True if GUI.pdfExtractBox.isChecked(): options.pdfextract = True + if GUI.pdfWidthBox.isChecked(): + options.pdfwidth = True if GUI.coverFillBox.isChecked(): options.coverfill = True if GUI.metadataTitleBox.checkState() == Qt.CheckState.PartiallyChecked: @@ -888,6 +890,7 @@ class KCCGUI(KCC_ui.Ui_mainWindow): self.addMessage('Colorsoft MOBI/EPUB can have blank pages. Just go back a few pages, exit, and reenter book.', 'info') break elif profile['Label'] == 'KDX': + GUI.mozJpegBox.setCheckState(Qt.CheckState.PartiallyChecked) GUI.borderBox.setCheckState(Qt.CheckState.PartiallyChecked) if not profile['PVOptions']: GUI.qualityBox.setChecked(False) @@ -1044,6 +1047,7 @@ class KCCGUI(KCC_ui.Ui_mainWindow): 'eraseRainbowBox': GUI.eraseRainbowBox.checkState(), 'disableProcessingBox': GUI.disableProcessingBox.checkState(), 'pdfExtractBox': GUI.pdfExtractBox.checkState(), + 'pdfWidthBox': GUI.pdfWidthBox.checkState(), 'coverFillBox': GUI.coverFillBox.checkState(), 'metadataTitleBox': GUI.metadataTitleBox.checkState(), 'mozJpegBox': GUI.mozJpegBox.checkState(), diff --git a/kindlecomicconverter/KCC_ui.py b/kindlecomicconverter/KCC_ui.py index 115ded1..4a999c9 100644 --- a/kindlecomicconverter/KCC_ui.py +++ b/kindlecomicconverter/KCC_ui.py @@ -482,6 +482,11 @@ class Ui_mainWindow(object): self.gridLayout_2.addWidget(self.noQuantizeBox, 10, 2, 1, 1) + self.pdfWidthBox = QCheckBox(self.optionWidget) + self.pdfWidthBox.setObjectName(u"pdfWidthBox") + + self.gridLayout_2.addWidget(self.pdfWidthBox, 10, 0, 1, 1) + self.gridLayout.addWidget(self.optionWidget, 5, 0, 1, 2) @@ -777,6 +782,12 @@ class Ui_mainWindow(object): "Eink only has 16 shades of gray so you probably don't want this.", None)) #endif // QT_CONFIG(tooltip) self.noQuantizeBox.setText(QCoreApplication.translate("mainWindow", u"No Quantize", None)) +#if QT_CONFIG(tooltip) + self.pdfWidthBox.setToolTip(QCoreApplication.translate("mainWindow", u"Render vector PDFs to device width instead of height.\n" +"\n" +"Useful if you plan to crop a little off the top and bottom to fill screen.", None)) +#endif // QT_CONFIG(tooltip) + self.pdfWidthBox.setText(QCoreApplication.translate("mainWindow", u"PDF Width Render", None)) self.gammaLabel.setText(QCoreApplication.translate("mainWindow", u"Gamma: Auto", None)) self.jpegQualityLabel.setText(QCoreApplication.translate("mainWindow", u"JPEG Quality:", None)) # retranslateUi diff --git a/kindlecomicconverter/comic2ebook.py b/kindlecomicconverter/comic2ebook.py index 9aa1052..325c8c5 100755 --- a/kindlecomicconverter/comic2ebook.py +++ b/kindlecomicconverter/comic2ebook.py @@ -744,7 +744,9 @@ def render_page(vector): cpu = vector[1] # number of CPUs filename = vector[2] # document filename output_dir = vector[3] - target_height = vector[4] + target_width = vector[4] + target_height = vector[5] + pdf_width = vector[6] with pymupdf.open(filename) as doc: # open the document num_pages = doc.page_count # get number of pages @@ -755,7 +757,10 @@ def render_page(vector): for i in range(seg_from, seg_to): # work through our page segment page = doc[i] - zoom = target_height / page.rect.height + if not pdf_width or page.rect.width > page.rect.height: + zoom = target_height / page.rect.height + else: + zoom = target_width / page.rect.width mat = pymupdf.Matrix(zoom, zoom) # TODO: decide colorspace earlier so later color check is cheaper. # This is actually pretty hard when you have to deal with color vector text @@ -820,7 +825,7 @@ def extract_page(vector): -def mupdf_pdf_process_pages_parallel(filename, output_dir, target_height): +def mupdf_pdf_process_pages_parallel(filename, output_dir, target_width, target_height): render = False with pymupdf.open(filename) as doc: for page in doc: @@ -840,7 +845,7 @@ def mupdf_pdf_process_pages_parallel(filename, output_dir, target_height): cpu = cpu_count() # make vectors of arguments for the processes - vectors = [(i, cpu, filename, output_dir, target_height) for i in range(cpu)] + vectors = [(i, cpu, filename, output_dir, target_width, target_height, options.pdfwidth) for i in range(cpu)] print("Starting %i processes for '%s'." % (cpu, filename)) @@ -885,13 +890,13 @@ def getWorkFolder(afile, workdir=None): if njpg == 0: raise UserWarning("Failed to extract images from PDF file.") return workdir - target_height = options.profileData[1][1] + target_width, target_height = options.profileData[1] if options.cropping == 1: target_height = target_height + target_height*0.20 #Account for possible margin at the top and bottom elif options.cropping == 2: target_height = target_height + target_height*0.25 #Account for possible margin at the top and bottom with page number try: - mupdf_pdf_process_pages_parallel(afile, fullPath, target_height) + mupdf_pdf_process_pages_parallel(afile, fullPath, target_width, target_height) except Exception as e: rmtree(path, True) raise UserWarning(f"Failed to extract images from PDF file. {e}") @@ -1366,6 +1371,8 @@ def makeParser(): help="Do not modify image and ignore any profile or processing option") processing_options.add_argument("--pdfextract", action="store_true", dest="pdfextract", default=False, help="Use the legacy PDF image extraction method from KCC 8 and earlier") + processing_options.add_argument("--pdfwidth", action="store_true", dest="pdfwidth", default=False, + help="Render vector PDFs to device width instead of height.") processing_options.add_argument("--coverfill", action="store_true", dest="coverfill", default=False, help="Crop cover to fill screen") processing_options.add_argument("-u", "--upscale", action="store_true", dest="upscale", default=False, diff --git a/kindlecomicconverter/image.py b/kindlecomicconverter/image.py index 635886f..6583ace 100755 --- a/kindlecomicconverter/image.py +++ b/kindlecomicconverter/image.py @@ -507,7 +507,9 @@ class ComicPage: elif method == Image.Resampling.BICUBIC and not self.opt.upscale: pass else: # if image bigger than device resolution or smaller with upscaling - if abs(ratio_image - ratio_device) < AUTO_CROP_THRESHOLD: + if self.opt.profile == 'KDX' and abs(ratio_image - ratio_device) < AUTO_CROP_THRESHOLD * 3: + 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: self.image = ImageOps.pad(self.image, self.size, method=method, color=self.fill) From 3a9d4f274d507895bf4053e8e625d5dcb0038493 Mon Sep 17 00:00:00 2001 From: Alex Xu Date: Fri, 13 Mar 2026 16:29:50 -0700 Subject: [PATCH 058/107] bump to 9.6.0 --- kindlecomicconverter/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kindlecomicconverter/__init__.py b/kindlecomicconverter/__init__.py index 64d56a9..a26eba8 100644 --- a/kindlecomicconverter/__init__.py +++ b/kindlecomicconverter/__init__.py @@ -1,4 +1,4 @@ -__version__ = '9.5.1' +__version__ = '9.6.0' __license__ = 'ISC' __copyright__ = '2012-2022, Ciro Mattia Gonano , Pawel Jastrzebski , darodi' __docformat__ = 'restructuredtext en' From 249f823f01dff14588f7f82cd5eb9c8bcd38251d Mon Sep 17 00:00:00 2001 From: Alex Xu Date: Fri, 13 Mar 2026 21:45:05 -0700 Subject: [PATCH 059/107] add PNG legacy option (#1271) --- README.md | 1 + gui/KCC.ui | 502 ++++++++++++++-------------- kindlecomicconverter/KCC_gui.py | 4 + kindlecomicconverter/KCC_ui.py | 465 +++++++++++++------------- kindlecomicconverter/comic2ebook.py | 4 + 5 files changed, 502 insertions(+), 474 deletions(-) diff --git a/README.md b/README.md index d228a64..e77da46 100644 --- a/README.md +++ b/README.md @@ -273,6 +273,7 @@ PROCESSING: --coverfill Center-crop only the cover to fill target device screen --forcecolor Don't convert images to grayscale --forcepng Create PNG files instead JPEG + --pnglegacy Use a more compatible 8 bit PNG instead of 4 bit. --noquantize Don't quantize PNG images to 16 colors --mozjpeg Create JPEG files using mozJpeg --jpeg-quality The JPEG quality, on a scale from 0 (worst) to 95 (best). Default 85 for most devices. diff --git a/gui/KCC.ui b/gui/KCC.ui index 3fed46e..1020b6a 100644 --- a/gui/KCC.ui +++ b/gui/KCC.ui @@ -514,29 +514,159 @@ 0 - - + + - <html><head/><body><p><span style=" font-weight:700; text-decoration: underline;">Unchecked<br/></span>Maximal output file size is 100 MB for Webtoon, 400 MB for others before split occurs.</p><p><span style=" font-weight:700; text-decoration: underline;">Checked</span><br/>Output file size specified in &quot;Chunk size MB&quot; before split occurs.</p></body></html> + <html><head/><body><p>When the spread splitter option is partially checked,</p><p><span style=" font-weight:600; text-decoration: underline;">Unchecked - Rotate Last<br/></span>Put the rotated 2 page spread after the split spreads.</p><p><span style=" font-weight:600; text-decoration: underline;">Checked - Rotate First<br/></span>Put the rotated 2 page spread before the split spreads.</p></body></html> - Chunk size + Rotate First - - + + - <html><head/><body><p style='white-space:pre'><span style=" font-weight:600; text-decoration: underline;">Unchecked - 4 panels<br/></span>Zoom each corner separately.</p><p style='white-space:pre'><span style=" font-weight:600; text-decoration: underline;">Indeterminate - 2 panels<br/></span>Zoom only the top and bottom of the page.</p><p><span style=" font-weight:600; text-decoration: underline;">Checked - 4 high-quality panels<br/></span>Zoom each corner separately. Try to increase the quality of magnification. Check wiki for more details.</p></body></html> + <html><head/><body><p style='white-space:pre'><span style=" font-weight:600; text-decoration: underline;">Unchecked - Automatic mode<br/></span>The output will be split automatically.</p><p style='white-space:pre'><span style=" font-weight:600; text-decoration: underline;">Checked - Volume mode<br/></span>Every subdirectory will be considered as a separate volume.</p></body></html> - Panel View 4/2/HQ + Output split + + + + + + + Render vector PDFs to device width instead of height. + +Useful if you plan to crop a little off the top and bottom to fill screen. + + + PDF Width Render + + + + + + + <html><head/><body><p><span style=" font-weight:600; text-decoration: underline;">Unchecked - Autodetection<br/></span>The color of margins fill will be detected automatically.</p><p><span style=" font-weight:600; text-decoration: underline;">Indeterminate - White<br/></span>Margins will be untouched.</p><p><span style=" font-weight:600; text-decoration: underline;">Checked - Black<br/></span>Margins will be filled with black color.</p></body></html> + + + W/B margins true + + + + Delete input file(s) or directory. It's not recoverable! + + + Delete input + + + + + + + Shift first page to opposite side in landscape for two page spread alignment + + + Spread shift + + + + + + + <html><head/><body><p><span style=" font-weight:600; text-decoration: underline;">Unchecked - 1x4<br/></span>Keep format 1x4 panels strips.</p><p><span style=" font-weight:600; text-decoration: underline;">Checked - 2x2<br/></span>Turn 1x4 strips to 2x2 to maximize screen usage.</p></body></html> + + + 1x4 to 2x2 strips + + + + + + + <html><head/><body><p style='white-space:pre'>Enable special parsing mode for Korean Webtoons.</p></body></html> + + + Webtoon mode + + + + + + + Do not rotate double page spreads in spread splitter option. + + + No rotate + + + + + + + <html><head/><body><p style='white-space:pre'>Enable right-to-left reading.</p></body></html> + + + Right-to-left (manga) + + + + + + + Don't quantize PNG images to 16 colors (4 bit) + +This will double file size but preserve all 256 colors (8 bit). + +Eink only has 16 shades of gray so you probably don't want this. + + + No Quantize + + + + + + + The JPEG quality, on a scale from 0 (worst) to 95 (best). + +Default is 85 for most devices besides Kindle Scribe and Colorsoft, which are 90. + +Higher values are larger and higher quality, and may resolve blank page issues. + + + Custom JPEG Quality + + + + + + + <html><head/><body><p style='white-space:pre'>Disable conversion to grayscale.</p></body></html> + + + Color mode + + + + + + + <html><head/><body><p style='white-space:pre'>Do not process any image, ignore profile and processing options.</p></body></html> + + + Disable processing + + + @@ -594,16 +724,6 @@ - - - - Delete input file(s) or directory. It's not recoverable! - - - Delete input - - - @@ -617,32 +737,6 @@ - - - - <html><head/><body><p><span style=" font-weight:600; text-decoration: underline;">Unchecked - JPEG<br/></span>Use JPEG files</p><p><span style=" font-weight:600; text-decoration: underline;">Indeterminate - force PNG<br/></span>Create PNG files instead JPEG</p><p><span style=" font-weight:600; text-decoration: underline;">Checked - mozJpeg<br/></span>10-20% smaller JPEG file, with the same image quality, but processing time multiplied by 2</p></body></html> - - - JPEG/PNG/mozJpeg - - - true - - - - - - - <html><head/><body><p><span style=" font-weight:600; text-decoration: underline;">Unchecked - Disabled<br/></span>Disabled</p><p><span style=" font-weight:600; text-decoration: underline;">Indeterminate - Horizontal<br/></span>Crop empty horizontal lines.</p><p><span style=" font-weight:600; text-decoration: underline;">Checked - Both<br/></span>Crop empty horizontal and vertical lines.</p></body></html> - - - Inter-panel crop - - - true - - - @@ -656,47 +750,64 @@ - - + + - Do not rotate double page spreads in spread splitter option. + Use the PDF image extraction method from KCC 8 and earlier. + +Useful for really weird PDFs. - No rotate + PDF Legacy Extract - - + + - Resize cover to exact device resolution by center-cropping to aspect ratio first. -May crop top/bottom or left/right depending on source aspect ratio. Not implemented for Kindle Scribe. + <html><head/><body><p><span style=" font-weight:600; text-decoration: underline;">Unchecked - Nothing<br/></span>Images smaller than device resolution will not be resized.</p><p><span style=" font-weight:600; text-decoration: underline;">Indeterminate - Stretching<br/></span>Images smaller than device resolution will be resized. Aspect ratio will be not preserved.</p><p><span style=" font-weight:600; text-decoration: underline;">Checked - Upscaling<br/></span>Images smaller than device resolution will be resized. Aspect ratio will be preserved.</p></body></html> - Cover Fill - - - - - - - <html><head/><body><p><span style=" font-weight:600; text-decoration: underline;">Unchecked - Autodetection<br/></span>The color of margins fill will be detected automatically.</p><p><span style=" font-weight:600; text-decoration: underline;">Indeterminate - White<br/></span>Margins will be untouched.</p><p><span style=" font-weight:600; text-decoration: underline;">Checked - Black<br/></span>Margins will be filled with black color.</p></body></html> - - - W/B margins + Stretch/Upscale true - - + + - Erase rainbow effect on color eink screen by attenuating interfering frequencies + <html><head/><body><p><span style=" font-weight:700; text-decoration: underline;">Unchecked<br/></span>Maximal output file size is 100 MB for Webtoon, 400 MB for others before split occurs.</p><p><span style=" font-weight:700; text-decoration: underline;">Checked</span><br/>Output file size specified in &quot;Chunk size MB&quot; before split occurs.</p></body></html> - Rainbow eraser + Chunk size + + + + + + + <html><head/><body><p style='white-space:pre'><span style=" font-weight:600; text-decoration: underline;">Unchecked - 4 panels<br/></span>Zoom each corner separately.</p><p style='white-space:pre'><span style=" font-weight:600; text-decoration: underline;">Indeterminate - 2 panels<br/></span>Zoom only the top and bottom of the page.</p><p><span style=" font-weight:600; text-decoration: underline;">Checked - 4 high-quality panels<br/></span>Zoom each corner separately. Try to increase the quality of magnification. Check wiki for more details.</p></body></html> + + + Panel View 4/2/HQ + + + true + + + + + + + <html><head/><body><p><span style=" font-weight:600; text-decoration: underline;">Unchecked - JPEG<br/></span>Use JPEG files</p><p><span style=" font-weight:600; text-decoration: underline;">Indeterminate - force PNG<br/></span>Create PNG files instead JPEG</p><p><span style=" font-weight:600; text-decoration: underline;">Checked - mozJpeg<br/></span>10-20% smaller JPEG file, with the same image quality, but processing time multiplied by 2</p></body></html> + + + JPEG/PNG/mozJpeg + + + true @@ -722,6 +833,80 @@ May crop top/bottom or left/right depending on source aspect ratio. Not implemen + + + + Erase rainbow effect on color eink screen by attenuating interfering frequencies + + + Rainbow eraser + + + + + + + <html><head/><body><p>Combines all selected files into a single file. (Helpful for combining chapters into volumes.)</p></body></html> + + + File Fusion + + + + + + + Resize cover to exact device resolution by center-cropping to aspect ratio first. +May crop top/bottom or left/right depending on source aspect ratio. Not implemented for Kindle Scribe. + + + Cover Fill + + + + + + + Rotate 2 page spreads in opposite direction than normal. + + + Rotate Right + + + + + + + <html><head/><body><p>Set a custom gamma correction.</p><p>1.0 is default (disabled).<br/>&lt; 1.0 makes the image brighter.<br/>&gt; 1.0 makes the image darker. </p><p>1.8 was the default in KCC 9.1.0 and earlier.</p><p>Use if you want to make midtones darker.</p></body></html> + + + Custom gamma + + + + + + + <html><head/><body><p>By default, KCC maps the darkest pixel value to pure black (the black point.)</p><p>Extreme black point sets the black point to be the most common dark pixel value.</p><p>Useful when text is black but artwork is gray.</p></body></html> + + + Extreme Black Point + + + + + + + <html><head/><body><p><span style=" font-weight:600; text-decoration: underline;">Unchecked - Disabled<br/></span>Disabled</p><p><span style=" font-weight:600; text-decoration: underline;">Indeterminate - Horizontal<br/></span>Crop empty horizontal lines.</p><p><span style=" font-weight:600; text-decoration: underline;">Checked - Both<br/></span>Crop empty horizontal and vertical lines.</p></body></html> + + + Inter-panel crop + + + true + + + @@ -757,115 +942,6 @@ May crop top/bottom or left/right depending on source aspect ratio. Not implemen - - - - <html><head/><body><p>By default, KCC maps the darkest pixel value to pure black (the black point.)</p><p>Extreme black point sets the black point to be the most common dark pixel value.</p><p>Useful when text is black but artwork is gray.</p></body></html> - - - Extreme Black Point - - - - - - - <html><head/><body><p>When the spread splitter option is partially checked,</p><p><span style=" font-weight:600; text-decoration: underline;">Unchecked - Rotate Last<br/></span>Put the rotated 2 page spread after the split spreads.</p><p><span style=" font-weight:600; text-decoration: underline;">Checked - Rotate First<br/></span>Put the rotated 2 page spread before the split spreads.</p></body></html> - - - Rotate First - - - - - - - Use the PDF image extraction method from KCC 8 and earlier. - -Useful for really weird PDFs. - - - PDF Legacy Extract - - - - - - - <html><head/><body><p style='white-space:pre'><span style=" font-weight:600; text-decoration: underline;">Unchecked - Automatic mode<br/></span>The output will be split automatically.</p><p style='white-space:pre'><span style=" font-weight:600; text-decoration: underline;">Checked - Volume mode<br/></span>Every subdirectory will be considered as a separate volume.</p></body></html> - - - Output split - - - - - - - The JPEG quality, on a scale from 0 (worst) to 95 (best). - -Default is 85 for most devices besides Kindle Scribe and Colorsoft, which are 90. - -Higher values are larger and higher quality, and may resolve blank page issues. - - - Custom JPEG Quality - - - - - - - <html><head/><body><p><span style=" font-weight:600; text-decoration: underline;">Unchecked - Nothing<br/></span>Images smaller than device resolution will not be resized.</p><p><span style=" font-weight:600; text-decoration: underline;">Indeterminate - Stretching<br/></span>Images smaller than device resolution will be resized. Aspect ratio will be not preserved.</p><p><span style=" font-weight:600; text-decoration: underline;">Checked - Upscaling<br/></span>Images smaller than device resolution will be resized. Aspect ratio will be preserved.</p></body></html> - - - Stretch/Upscale - - - true - - - - - - - <html><head/><body><p>Set a custom gamma correction.</p><p>1.0 is default (disabled).<br/>&lt; 1.0 makes the image brighter.<br/>&gt; 1.0 makes the image darker. </p><p>1.8 was the default in KCC 9.1.0 and earlier.</p><p>Use if you want to make midtones darker.</p></body></html> - - - Custom gamma - - - - - - - Shift first page to opposite side in landscape for two page spread alignment - - - Spread shift - - - - - - - <html><head/><body><p>Combines all selected files into a single file. (Helpful for combining chapters into volumes.)</p></body></html> - - - File Fusion - - - - - - - <html><head/><body><p style='white-space:pre'>Disable conversion to grayscale.</p></body></html> - - - Color mode - - - @@ -879,79 +955,13 @@ Higher values are larger and higher quality, and may resolve blank page issues.< - - + + - <html><head/><body><p style='white-space:pre'>Enable special parsing mode for Korean Webtoons.</p></body></html> + Use a more compatible 8 bit PNG instead of 4 bit. - Webtoon mode - - - - - - - <html><head/><body><p style='white-space:pre'>Do not process any image, ignore profile and processing options.</p></body></html> - - - Disable processing - - - - - - - <html><head/><body><p style='white-space:pre'>Enable right-to-left reading.</p></body></html> - - - Right-to-left (manga) - - - - - - - <html><head/><body><p><span style=" font-weight:600; text-decoration: underline;">Unchecked - 1x4<br/></span>Keep format 1x4 panels strips.</p><p><span style=" font-weight:600; text-decoration: underline;">Checked - 2x2<br/></span>Turn 1x4 strips to 2x2 to maximize screen usage.</p></body></html> - - - 1x4 to 2x2 strips - - - - - - - Rotate 2 page spreads in opposite direction than normal. - - - Rotate Right - - - - - - - Don't quantize PNG images to 16 colors (4 bit) - -This will double file size but preserve all 256 colors (8 bit). - -Eink only has 16 shades of gray so you probably don't want this. - - - No Quantize - - - - - - - Render vector PDFs to device width instead of height. - -Useful if you plan to crop a little off the top and bottom to fill screen. - - - PDF Width Render + PNG Legacy Mode diff --git a/kindlecomicconverter/KCC_gui.py b/kindlecomicconverter/KCC_gui.py index 7dc05a9..925a623 100644 --- a/kindlecomicconverter/KCC_gui.py +++ b/kindlecomicconverter/KCC_gui.py @@ -355,6 +355,8 @@ class WorkerThread(QThread): options.forcepng = True elif GUI.mozJpegBox.checkState() == Qt.CheckState.Checked: options.mozjpeg = True + if GUI.pngLegacyBox.isChecked(): + options.pnglegacy = True if GUI.noQuantizeBox.isChecked(): options.noquantize = True if GUI.jpegQualityBox.isChecked(): @@ -892,6 +894,7 @@ class KCCGUI(KCC_ui.Ui_mainWindow): elif profile['Label'] == 'KDX': GUI.mozJpegBox.setCheckState(Qt.CheckState.PartiallyChecked) GUI.borderBox.setCheckState(Qt.CheckState.PartiallyChecked) + GUI.pngLegacyBox.setChecked(True) if not profile['PVOptions']: GUI.qualityBox.setChecked(False) if str(GUI.deviceBox.currentText()) == 'Other': @@ -1051,6 +1054,7 @@ class KCCGUI(KCC_ui.Ui_mainWindow): 'coverFillBox': GUI.coverFillBox.checkState(), 'metadataTitleBox': GUI.metadataTitleBox.checkState(), 'mozJpegBox': GUI.mozJpegBox.checkState(), + 'pngLegacyBox': GUI.pngLegacyBox.checkState(), 'noQuantizeBox': GUI.noQuantizeBox.checkState(), 'jpegQualityBox': GUI.jpegQualityBox.checkState(), 'jpegQuality': GUI.jpegQualitySpinBox.value(), diff --git a/kindlecomicconverter/KCC_ui.py b/kindlecomicconverter/KCC_ui.py index 4a999c9..b2e2fba 100644 --- a/kindlecomicconverter/KCC_ui.py +++ b/kindlecomicconverter/KCC_ui.py @@ -287,16 +287,76 @@ class Ui_mainWindow(object): self.gridLayout_2 = QGridLayout(self.optionWidget) self.gridLayout_2.setObjectName(u"gridLayout_2") self.gridLayout_2.setContentsMargins(0, 0, 0, 0) - self.chunkSizeCheckBox = QCheckBox(self.optionWidget) - self.chunkSizeCheckBox.setObjectName(u"chunkSizeCheckBox") + self.rotateFirstBox = QCheckBox(self.optionWidget) + self.rotateFirstBox.setObjectName(u"rotateFirstBox") - self.gridLayout_2.addWidget(self.chunkSizeCheckBox, 7, 1, 1, 1) + self.gridLayout_2.addWidget(self.rotateFirstBox, 8, 1, 1, 1) - self.qualityBox = QCheckBox(self.optionWidget) - self.qualityBox.setObjectName(u"qualityBox") - self.qualityBox.setTristate(True) + self.outputSplit = QCheckBox(self.optionWidget) + self.outputSplit.setObjectName(u"outputSplit") - self.gridLayout_2.addWidget(self.qualityBox, 1, 2, 1, 1) + self.gridLayout_2.addWidget(self.outputSplit, 3, 1, 1, 1) + + self.pdfWidthBox = QCheckBox(self.optionWidget) + self.pdfWidthBox.setObjectName(u"pdfWidthBox") + + self.gridLayout_2.addWidget(self.pdfWidthBox, 10, 0, 1, 1) + + self.borderBox = QCheckBox(self.optionWidget) + self.borderBox.setObjectName(u"borderBox") + self.borderBox.setTristate(True) + + self.gridLayout_2.addWidget(self.borderBox, 3, 0, 1, 1) + + self.deleteBox = QCheckBox(self.optionWidget) + self.deleteBox.setObjectName(u"deleteBox") + + self.gridLayout_2.addWidget(self.deleteBox, 5, 1, 1, 1) + + self.spreadShiftBox = QCheckBox(self.optionWidget) + self.spreadShiftBox.setObjectName(u"spreadShiftBox") + + self.gridLayout_2.addWidget(self.spreadShiftBox, 5, 0, 1, 1) + + self.maximizeStrips = QCheckBox(self.optionWidget) + self.maximizeStrips.setObjectName(u"maximizeStrips") + + self.gridLayout_2.addWidget(self.maximizeStrips, 4, 1, 1, 1) + + self.webtoonBox = QCheckBox(self.optionWidget) + self.webtoonBox.setObjectName(u"webtoonBox") + + self.gridLayout_2.addWidget(self.webtoonBox, 2, 0, 1, 1) + + self.noRotateBox = QCheckBox(self.optionWidget) + self.noRotateBox.setObjectName(u"noRotateBox") + + self.gridLayout_2.addWidget(self.noRotateBox, 6, 1, 1, 1) + + self.mangaBox = QCheckBox(self.optionWidget) + self.mangaBox.setObjectName(u"mangaBox") + + self.gridLayout_2.addWidget(self.mangaBox, 1, 0, 1, 1) + + self.noQuantizeBox = QCheckBox(self.optionWidget) + self.noQuantizeBox.setObjectName(u"noQuantizeBox") + + self.gridLayout_2.addWidget(self.noQuantizeBox, 10, 2, 1, 1) + + self.jpegQualityBox = QCheckBox(self.optionWidget) + self.jpegQualityBox.setObjectName(u"jpegQualityBox") + + self.gridLayout_2.addWidget(self.jpegQualityBox, 8, 0, 1, 1) + + self.colorBox = QCheckBox(self.optionWidget) + self.colorBox.setObjectName(u"colorBox") + + self.gridLayout_2.addWidget(self.colorBox, 3, 2, 1, 1) + + self.disableProcessingBox = QCheckBox(self.optionWidget) + self.disableProcessingBox.setObjectName(u"disableProcessingBox") + + self.gridLayout_2.addWidget(self.disableProcessingBox, 5, 2, 1, 1) self.outputFolderWidget = QWidget(self.optionWidget) self.outputFolderWidget.setObjectName(u"outputFolderWidget") @@ -321,55 +381,45 @@ class Ui_mainWindow(object): self.gridLayout_2.addWidget(self.outputFolderWidget, 0, 2, 1, 1) - self.deleteBox = QCheckBox(self.optionWidget) - self.deleteBox.setObjectName(u"deleteBox") - - self.gridLayout_2.addWidget(self.deleteBox, 5, 1, 1, 1) - self.metadataTitleBox = QCheckBox(self.optionWidget) self.metadataTitleBox.setObjectName(u"metadataTitleBox") self.metadataTitleBox.setTristate(True) self.gridLayout_2.addWidget(self.metadataTitleBox, 7, 0, 1, 1) - self.mozJpegBox = QCheckBox(self.optionWidget) - self.mozJpegBox.setObjectName(u"mozJpegBox") - self.mozJpegBox.setTristate(True) - - self.gridLayout_2.addWidget(self.mozJpegBox, 4, 0, 1, 1) - - self.interPanelCropBox = QCheckBox(self.optionWidget) - self.interPanelCropBox.setObjectName(u"interPanelCropBox") - self.interPanelCropBox.setTristate(True) - - self.gridLayout_2.addWidget(self.interPanelCropBox, 6, 2, 1, 1) - self.autocontrastBox = QCheckBox(self.optionWidget) self.autocontrastBox.setObjectName(u"autocontrastBox") self.autocontrastBox.setTristate(True) self.gridLayout_2.addWidget(self.autocontrastBox, 9, 2, 1, 1) - self.noRotateBox = QCheckBox(self.optionWidget) - self.noRotateBox.setObjectName(u"noRotateBox") + self.pdfExtractBox = QCheckBox(self.optionWidget) + self.pdfExtractBox.setObjectName(u"pdfExtractBox") - self.gridLayout_2.addWidget(self.noRotateBox, 6, 1, 1, 1) + self.gridLayout_2.addWidget(self.pdfExtractBox, 9, 0, 1, 1) - self.coverFillBox = QCheckBox(self.optionWidget) - self.coverFillBox.setObjectName(u"coverFillBox") + self.upscaleBox = QCheckBox(self.optionWidget) + self.upscaleBox.setObjectName(u"upscaleBox") + self.upscaleBox.setTristate(True) - self.gridLayout_2.addWidget(self.coverFillBox, 9, 1, 1, 1) + self.gridLayout_2.addWidget(self.upscaleBox, 2, 1, 1, 1) - self.borderBox = QCheckBox(self.optionWidget) - self.borderBox.setObjectName(u"borderBox") - self.borderBox.setTristate(True) + self.chunkSizeCheckBox = QCheckBox(self.optionWidget) + self.chunkSizeCheckBox.setObjectName(u"chunkSizeCheckBox") - self.gridLayout_2.addWidget(self.borderBox, 3, 0, 1, 1) + self.gridLayout_2.addWidget(self.chunkSizeCheckBox, 7, 1, 1, 1) - self.eraseRainbowBox = QCheckBox(self.optionWidget) - self.eraseRainbowBox.setObjectName(u"eraseRainbowBox") + self.qualityBox = QCheckBox(self.optionWidget) + self.qualityBox.setObjectName(u"qualityBox") + self.qualityBox.setTristate(True) - self.gridLayout_2.addWidget(self.eraseRainbowBox, 7, 2, 1, 1) + self.gridLayout_2.addWidget(self.qualityBox, 1, 2, 1, 1) + + self.mozJpegBox = QCheckBox(self.optionWidget) + self.mozJpegBox.setObjectName(u"mozJpegBox") + self.mozJpegBox.setTristate(True) + + self.gridLayout_2.addWidget(self.mozJpegBox, 4, 0, 1, 1) self.titleEdit = QLineEdit(self.optionWidget) self.titleEdit.setObjectName(u"titleEdit") @@ -380,6 +430,42 @@ class Ui_mainWindow(object): self.gridLayout_2.addWidget(self.titleEdit, 0, 0, 1, 1) + self.eraseRainbowBox = QCheckBox(self.optionWidget) + self.eraseRainbowBox.setObjectName(u"eraseRainbowBox") + + self.gridLayout_2.addWidget(self.eraseRainbowBox, 7, 2, 1, 1) + + self.fileFusionBox = QCheckBox(self.optionWidget) + self.fileFusionBox.setObjectName(u"fileFusionBox") + + self.gridLayout_2.addWidget(self.fileFusionBox, 6, 0, 1, 1) + + self.coverFillBox = QCheckBox(self.optionWidget) + self.coverFillBox.setObjectName(u"coverFillBox") + + self.gridLayout_2.addWidget(self.coverFillBox, 9, 1, 1, 1) + + self.rotateRightBox = QCheckBox(self.optionWidget) + self.rotateRightBox.setObjectName(u"rotateRightBox") + + self.gridLayout_2.addWidget(self.rotateRightBox, 10, 1, 1, 1) + + self.gammaBox = QCheckBox(self.optionWidget) + self.gammaBox.setObjectName(u"gammaBox") + + self.gridLayout_2.addWidget(self.gammaBox, 2, 2, 1, 1) + + self.autoLevelBox = QCheckBox(self.optionWidget) + self.autoLevelBox.setObjectName(u"autoLevelBox") + + self.gridLayout_2.addWidget(self.autoLevelBox, 8, 2, 1, 1) + + self.interPanelCropBox = QCheckBox(self.optionWidget) + self.interPanelCropBox.setObjectName(u"interPanelCropBox") + self.interPanelCropBox.setTristate(True) + + self.gridLayout_2.addWidget(self.interPanelCropBox, 6, 2, 1, 1) + self.rotateBox = QCheckBox(self.optionWidget) self.rotateBox.setObjectName(u"rotateBox") self.rotateBox.setTristate(True) @@ -395,97 +481,16 @@ class Ui_mainWindow(object): self.gridLayout_2.addWidget(self.authorEdit, 0, 1, 1, 1) - self.autoLevelBox = QCheckBox(self.optionWidget) - self.autoLevelBox.setObjectName(u"autoLevelBox") - - self.gridLayout_2.addWidget(self.autoLevelBox, 8, 2, 1, 1) - - self.rotateFirstBox = QCheckBox(self.optionWidget) - self.rotateFirstBox.setObjectName(u"rotateFirstBox") - - self.gridLayout_2.addWidget(self.rotateFirstBox, 8, 1, 1, 1) - - self.pdfExtractBox = QCheckBox(self.optionWidget) - self.pdfExtractBox.setObjectName(u"pdfExtractBox") - - self.gridLayout_2.addWidget(self.pdfExtractBox, 9, 0, 1, 1) - - self.outputSplit = QCheckBox(self.optionWidget) - self.outputSplit.setObjectName(u"outputSplit") - - self.gridLayout_2.addWidget(self.outputSplit, 3, 1, 1, 1) - - self.jpegQualityBox = QCheckBox(self.optionWidget) - self.jpegQualityBox.setObjectName(u"jpegQualityBox") - - self.gridLayout_2.addWidget(self.jpegQualityBox, 8, 0, 1, 1) - - self.upscaleBox = QCheckBox(self.optionWidget) - self.upscaleBox.setObjectName(u"upscaleBox") - self.upscaleBox.setTristate(True) - - self.gridLayout_2.addWidget(self.upscaleBox, 2, 1, 1, 1) - - self.gammaBox = QCheckBox(self.optionWidget) - self.gammaBox.setObjectName(u"gammaBox") - - self.gridLayout_2.addWidget(self.gammaBox, 2, 2, 1, 1) - - self.spreadShiftBox = QCheckBox(self.optionWidget) - self.spreadShiftBox.setObjectName(u"spreadShiftBox") - - self.gridLayout_2.addWidget(self.spreadShiftBox, 5, 0, 1, 1) - - self.fileFusionBox = QCheckBox(self.optionWidget) - self.fileFusionBox.setObjectName(u"fileFusionBox") - - self.gridLayout_2.addWidget(self.fileFusionBox, 6, 0, 1, 1) - - self.colorBox = QCheckBox(self.optionWidget) - self.colorBox.setObjectName(u"colorBox") - - self.gridLayout_2.addWidget(self.colorBox, 3, 2, 1, 1) - self.croppingBox = QCheckBox(self.optionWidget) self.croppingBox.setObjectName(u"croppingBox") self.croppingBox.setTristate(True) self.gridLayout_2.addWidget(self.croppingBox, 4, 2, 1, 1) - self.webtoonBox = QCheckBox(self.optionWidget) - self.webtoonBox.setObjectName(u"webtoonBox") + self.pngLegacyBox = QCheckBox(self.optionWidget) + self.pngLegacyBox.setObjectName(u"pngLegacyBox") - self.gridLayout_2.addWidget(self.webtoonBox, 2, 0, 1, 1) - - self.disableProcessingBox = QCheckBox(self.optionWidget) - self.disableProcessingBox.setObjectName(u"disableProcessingBox") - - self.gridLayout_2.addWidget(self.disableProcessingBox, 5, 2, 1, 1) - - self.mangaBox = QCheckBox(self.optionWidget) - self.mangaBox.setObjectName(u"mangaBox") - - self.gridLayout_2.addWidget(self.mangaBox, 1, 0, 1, 1) - - self.maximizeStrips = QCheckBox(self.optionWidget) - self.maximizeStrips.setObjectName(u"maximizeStrips") - - self.gridLayout_2.addWidget(self.maximizeStrips, 4, 1, 1, 1) - - self.rotateRightBox = QCheckBox(self.optionWidget) - self.rotateRightBox.setObjectName(u"rotateRightBox") - - self.gridLayout_2.addWidget(self.rotateRightBox, 10, 1, 1, 1) - - self.noQuantizeBox = QCheckBox(self.optionWidget) - self.noQuantizeBox.setObjectName(u"noQuantizeBox") - - self.gridLayout_2.addWidget(self.noQuantizeBox, 10, 2, 1, 1) - - self.pdfWidthBox = QCheckBox(self.optionWidget) - self.pdfWidthBox.setObjectName(u"pdfWidthBox") - - self.gridLayout_2.addWidget(self.pdfWidthBox, 10, 0, 1, 1) + self.gridLayout_2.addWidget(self.pngLegacyBox, 11, 0, 1, 1) self.gridLayout.addWidget(self.optionWidget, 5, 0, 1, 2) @@ -639,141 +644,48 @@ class Ui_mainWindow(object): #endif // QT_CONFIG(tooltip) self.chunkSizeLabel.setText(QCoreApplication.translate("mainWindow", u"Chunk size MB:", None)) self.chunkSizeWarnLabel.setText(QCoreApplication.translate("mainWindow", u"Greater than default may cause performance issues on older ereaders.", None)) -#if QT_CONFIG(tooltip) - self.chunkSizeCheckBox.setToolTip(QCoreApplication.translate("mainWindow", u"

Unchecked
Maximal output file size is 100 MB for Webtoon, 400 MB for others before split occurs.

Checked
Output file size specified in "Chunk size MB" before split occurs.

", None)) -#endif // QT_CONFIG(tooltip) - self.chunkSizeCheckBox.setText(QCoreApplication.translate("mainWindow", u"Chunk size", None)) -#if QT_CONFIG(tooltip) - self.qualityBox.setToolTip(QCoreApplication.translate("mainWindow", u"

Unchecked - 4 panels
Zoom each corner separately.

Indeterminate - 2 panels
Zoom only the top and bottom of the page.

Checked - 4 high-quality panels
Zoom each corner separately. Try to increase the quality of magnification. Check wiki for more details.

", None)) -#endif // QT_CONFIG(tooltip) - self.qualityBox.setText(QCoreApplication.translate("mainWindow", u"Panel View 4/2/HQ", None)) -#if QT_CONFIG(tooltip) - self.defaultOutputFolderBox.setToolTip(QCoreApplication.translate("mainWindow", u"

Unchecked - next to source
Place output files next to source files

Indeterminate - folder next to source
Place output files in a folder next to source files

Checked - Custom
Place output files in custom directory specified by right button

", None)) -#endif // QT_CONFIG(tooltip) - self.defaultOutputFolderBox.setText(QCoreApplication.translate("mainWindow", u"Output Folder", None)) -#if QT_CONFIG(tooltip) - self.defaultOutputFolderButton.setToolTip(QCoreApplication.translate("mainWindow", u"

Use this to select the default output directory.

", None)) -#endif // QT_CONFIG(tooltip) - self.defaultOutputFolderButton.setText("") -#if QT_CONFIG(tooltip) - self.deleteBox.setToolTip(QCoreApplication.translate("mainWindow", u"Delete input file(s) or directory. It's not recoverable!", None)) -#endif // QT_CONFIG(tooltip) - self.deleteBox.setText(QCoreApplication.translate("mainWindow", u"Delete input", None)) -#if QT_CONFIG(tooltip) - self.metadataTitleBox.setToolTip(QCoreApplication.translate("mainWindow", u"

Unchecked - Don't use metadata Title
Write default title.

Indeterminate - Add metadata Title to the default schema
Write default title with Title from ComicInfo.xml or other embedded metadata.

Checked - Use metadata Title only
Write Title from ComicInfo.xml or other embedded metadata.

", None)) -#endif // QT_CONFIG(tooltip) - self.metadataTitleBox.setText(QCoreApplication.translate("mainWindow", u"Metadata Title", None)) -#if QT_CONFIG(tooltip) - self.mozJpegBox.setToolTip(QCoreApplication.translate("mainWindow", u"

Unchecked - JPEG
Use JPEG files

Indeterminate - force PNG
Create PNG files instead JPEG

Checked - mozJpeg
10-20% smaller JPEG file, with the same image quality, but processing time multiplied by 2

", None)) -#endif // QT_CONFIG(tooltip) - self.mozJpegBox.setText(QCoreApplication.translate("mainWindow", u"JPEG/PNG/mozJpeg", None)) -#if QT_CONFIG(tooltip) - self.interPanelCropBox.setToolTip(QCoreApplication.translate("mainWindow", u"

Unchecked - Disabled
Disabled

Indeterminate - Horizontal
Crop empty horizontal lines.

Checked - Both
Crop empty horizontal and vertical lines.

", None)) -#endif // QT_CONFIG(tooltip) - self.interPanelCropBox.setText(QCoreApplication.translate("mainWindow", u"Inter-panel crop", None)) -#if QT_CONFIG(tooltip) - self.autocontrastBox.setToolTip(QCoreApplication.translate("mainWindow", u"

Unchecked - BW only
Only autocontrast bw pages. Ignored for pages where near blacks or whites don't exist.

Indeterminate - Disabled
Disable autocontrast

Checked - BW and Color
BW and color images will be autocontrasted. Ignored for pages where near blacks or whites don't exist.

", None)) -#endif // QT_CONFIG(tooltip) - self.autocontrastBox.setText(QCoreApplication.translate("mainWindow", u"Autocontrast", None)) -#if QT_CONFIG(tooltip) - self.noRotateBox.setToolTip(QCoreApplication.translate("mainWindow", u"Do not rotate double page spreads in spread splitter option.", None)) -#endif // QT_CONFIG(tooltip) - self.noRotateBox.setText(QCoreApplication.translate("mainWindow", u"No rotate", None)) -#if QT_CONFIG(tooltip) - self.coverFillBox.setToolTip(QCoreApplication.translate("mainWindow", u"Resize cover to exact device resolution by center-cropping to aspect ratio first.\n" -"May crop top/bottom or left/right depending on source aspect ratio. Not implemented for Kindle Scribe.", None)) -#endif // QT_CONFIG(tooltip) - self.coverFillBox.setText(QCoreApplication.translate("mainWindow", u"Cover Fill", None)) -#if QT_CONFIG(tooltip) - self.borderBox.setToolTip(QCoreApplication.translate("mainWindow", u"

Unchecked - Autodetection
The color of margins fill will be detected automatically.

Indeterminate - White
Margins will be untouched.

Checked - Black
Margins will be filled with black color.

", None)) -#endif // QT_CONFIG(tooltip) - self.borderBox.setText(QCoreApplication.translate("mainWindow", u"W/B margins", None)) -#if QT_CONFIG(tooltip) - self.eraseRainbowBox.setToolTip(QCoreApplication.translate("mainWindow", u"Erase rainbow effect on color eink screen by attenuating interfering frequencies", None)) -#endif // QT_CONFIG(tooltip) - self.eraseRainbowBox.setText(QCoreApplication.translate("mainWindow", u"Rainbow eraser", None)) -#if QT_CONFIG(tooltip) - self.titleEdit.setToolTip(QCoreApplication.translate("mainWindow", u"

Default Title

", None)) -#endif // QT_CONFIG(tooltip) - self.titleEdit.setPlaceholderText(QCoreApplication.translate("mainWindow", u"Default Title", None)) -#if QT_CONFIG(tooltip) - self.rotateBox.setToolTip(QCoreApplication.translate("mainWindow", u"

Unchecked - Split
Double page spreads will be cut into two separate pages.

Indeterminate - Split and rotate
Double page spreads will be displayed twice. First split and then rotated.

Checked - Rotate
Double page spreads will be rotated.

", None)) -#endif // QT_CONFIG(tooltip) - self.rotateBox.setText(QCoreApplication.translate("mainWindow", u"Spread splitter", None)) -#if QT_CONFIG(tooltip) - self.authorEdit.setToolTip(QCoreApplication.translate("mainWindow", u"Default Author is KCC", None)) -#endif // QT_CONFIG(tooltip) - self.authorEdit.setPlaceholderText(QCoreApplication.translate("mainWindow", u"Default Author", None)) -#if QT_CONFIG(tooltip) - self.autoLevelBox.setToolTip(QCoreApplication.translate("mainWindow", u"

By default, KCC maps the darkest pixel value to pure black (the black point.)

Extreme black point sets the black point to be the most common dark pixel value.

Useful when text is black but artwork is gray.

", None)) -#endif // QT_CONFIG(tooltip) - self.autoLevelBox.setText(QCoreApplication.translate("mainWindow", u"Extreme Black Point", None)) #if QT_CONFIG(tooltip) self.rotateFirstBox.setToolTip(QCoreApplication.translate("mainWindow", u"

When the spread splitter option is partially checked,

Unchecked - Rotate Last
Put the rotated 2 page spread after the split spreads.

Checked - Rotate First
Put the rotated 2 page spread before the split spreads.

", None)) #endif // QT_CONFIG(tooltip) self.rotateFirstBox.setText(QCoreApplication.translate("mainWindow", u"Rotate First", None)) -#if QT_CONFIG(tooltip) - self.pdfExtractBox.setToolTip(QCoreApplication.translate("mainWindow", u"Use the PDF image extraction method from KCC 8 and earlier.\n" -"\n" -"Useful for really weird PDFs.", None)) -#endif // QT_CONFIG(tooltip) - self.pdfExtractBox.setText(QCoreApplication.translate("mainWindow", u"PDF Legacy Extract", None)) #if QT_CONFIG(tooltip) self.outputSplit.setToolTip(QCoreApplication.translate("mainWindow", u"

Unchecked - Automatic mode
The output will be split automatically.

Checked - Volume mode
Every subdirectory will be considered as a separate volume.

", None)) #endif // QT_CONFIG(tooltip) self.outputSplit.setText(QCoreApplication.translate("mainWindow", u"Output split", None)) #if QT_CONFIG(tooltip) - self.jpegQualityBox.setToolTip(QCoreApplication.translate("mainWindow", u"The JPEG quality, on a scale from 0 (worst) to 95 (best). \n" + self.pdfWidthBox.setToolTip(QCoreApplication.translate("mainWindow", u"Render vector PDFs to device width instead of height.\n" "\n" -"Default is 85 for most devices besides Kindle Scribe and Colorsoft, which are 90.\n" -"\n" -"Higher values are larger and higher quality, and may resolve blank page issues.", None)) +"Useful if you plan to crop a little off the top and bottom to fill screen.", None)) #endif // QT_CONFIG(tooltip) - self.jpegQualityBox.setText(QCoreApplication.translate("mainWindow", u"Custom JPEG Quality", None)) + self.pdfWidthBox.setText(QCoreApplication.translate("mainWindow", u"PDF Width Render", None)) #if QT_CONFIG(tooltip) - self.upscaleBox.setToolTip(QCoreApplication.translate("mainWindow", u"

Unchecked - Nothing
Images smaller than device resolution will not be resized.

Indeterminate - Stretching
Images smaller than device resolution will be resized. Aspect ratio will be not preserved.

Checked - Upscaling
Images smaller than device resolution will be resized. Aspect ratio will be preserved.

", None)) + self.borderBox.setToolTip(QCoreApplication.translate("mainWindow", u"

Unchecked - Autodetection
The color of margins fill will be detected automatically.

Indeterminate - White
Margins will be untouched.

Checked - Black
Margins will be filled with black color.

", None)) #endif // QT_CONFIG(tooltip) - self.upscaleBox.setText(QCoreApplication.translate("mainWindow", u"Stretch/Upscale", None)) + self.borderBox.setText(QCoreApplication.translate("mainWindow", u"W/B margins", None)) #if QT_CONFIG(tooltip) - self.gammaBox.setToolTip(QCoreApplication.translate("mainWindow", u"

Set a custom gamma correction.

1.0 is default (disabled).
< 1.0 makes the image brighter.
> 1.0 makes the image darker.

1.8 was the default in KCC 9.1.0 and earlier.

Use if you want to make midtones darker.

", None)) + self.deleteBox.setToolTip(QCoreApplication.translate("mainWindow", u"Delete input file(s) or directory. It's not recoverable!", None)) #endif // QT_CONFIG(tooltip) - self.gammaBox.setText(QCoreApplication.translate("mainWindow", u"Custom gamma", None)) + self.deleteBox.setText(QCoreApplication.translate("mainWindow", u"Delete input", None)) #if QT_CONFIG(tooltip) self.spreadShiftBox.setToolTip(QCoreApplication.translate("mainWindow", u"Shift first page to opposite side in landscape for two page spread alignment", None)) #endif // QT_CONFIG(tooltip) self.spreadShiftBox.setText(QCoreApplication.translate("mainWindow", u"Spread shift", None)) #if QT_CONFIG(tooltip) - self.fileFusionBox.setToolTip(QCoreApplication.translate("mainWindow", u"

Combines all selected files into a single file. (Helpful for combining chapters into volumes.)

", None)) + self.maximizeStrips.setToolTip(QCoreApplication.translate("mainWindow", u"

Unchecked - 1x4
Keep format 1x4 panels strips.

Checked - 2x2
Turn 1x4 strips to 2x2 to maximize screen usage.

", None)) #endif // QT_CONFIG(tooltip) - self.fileFusionBox.setText(QCoreApplication.translate("mainWindow", u"File Fusion", None)) -#if QT_CONFIG(tooltip) - self.colorBox.setToolTip(QCoreApplication.translate("mainWindow", u"

Disable conversion to grayscale.

", None)) -#endif // QT_CONFIG(tooltip) - self.colorBox.setText(QCoreApplication.translate("mainWindow", u"Color mode", None)) -#if QT_CONFIG(tooltip) - self.croppingBox.setToolTip(QCoreApplication.translate("mainWindow", u"

Unchecked - Disabled

Disabled

Indeterminate - Margins
Margins

Checked - Margins + page numbers
Margins +page numbers

", None)) -#endif // QT_CONFIG(tooltip) - self.croppingBox.setText(QCoreApplication.translate("mainWindow", u"Cropping mode", None)) + self.maximizeStrips.setText(QCoreApplication.translate("mainWindow", u"1x4 to 2x2 strips", None)) #if QT_CONFIG(tooltip) self.webtoonBox.setToolTip(QCoreApplication.translate("mainWindow", u"

Enable special parsing mode for Korean Webtoons.

", None)) #endif // QT_CONFIG(tooltip) self.webtoonBox.setText(QCoreApplication.translate("mainWindow", u"Webtoon mode", None)) #if QT_CONFIG(tooltip) - self.disableProcessingBox.setToolTip(QCoreApplication.translate("mainWindow", u"

Do not process any image, ignore profile and processing options.

", None)) + self.noRotateBox.setToolTip(QCoreApplication.translate("mainWindow", u"Do not rotate double page spreads in spread splitter option.", None)) #endif // QT_CONFIG(tooltip) - self.disableProcessingBox.setText(QCoreApplication.translate("mainWindow", u"Disable processing", None)) + self.noRotateBox.setText(QCoreApplication.translate("mainWindow", u"No rotate", None)) #if QT_CONFIG(tooltip) self.mangaBox.setToolTip(QCoreApplication.translate("mainWindow", u"

Enable right-to-left reading.

", None)) #endif // QT_CONFIG(tooltip) self.mangaBox.setText(QCoreApplication.translate("mainWindow", u"Right-to-left (manga)", None)) -#if QT_CONFIG(tooltip) - self.maximizeStrips.setToolTip(QCoreApplication.translate("mainWindow", u"

Unchecked - 1x4
Keep format 1x4 panels strips.

Checked - 2x2
Turn 1x4 strips to 2x2 to maximize screen usage.

", None)) -#endif // QT_CONFIG(tooltip) - self.maximizeStrips.setText(QCoreApplication.translate("mainWindow", u"1x4 to 2x2 strips", None)) -#if QT_CONFIG(tooltip) - self.rotateRightBox.setToolTip(QCoreApplication.translate("mainWindow", u"Rotate 2 page spreads in opposite direction than normal.", None)) -#endif // QT_CONFIG(tooltip) - self.rotateRightBox.setText(QCoreApplication.translate("mainWindow", u"Rotate Right", None)) #if QT_CONFIG(tooltip) self.noQuantizeBox.setToolTip(QCoreApplication.translate("mainWindow", u"Don't quantize PNG images to 16 colors (4 bit)\n" "\n" @@ -783,11 +695,108 @@ class Ui_mainWindow(object): #endif // QT_CONFIG(tooltip) self.noQuantizeBox.setText(QCoreApplication.translate("mainWindow", u"No Quantize", None)) #if QT_CONFIG(tooltip) - self.pdfWidthBox.setToolTip(QCoreApplication.translate("mainWindow", u"Render vector PDFs to device width instead of height.\n" + self.jpegQualityBox.setToolTip(QCoreApplication.translate("mainWindow", u"The JPEG quality, on a scale from 0 (worst) to 95 (best). \n" "\n" -"Useful if you plan to crop a little off the top and bottom to fill screen.", None)) +"Default is 85 for most devices besides Kindle Scribe and Colorsoft, which are 90.\n" +"\n" +"Higher values are larger and higher quality, and may resolve blank page issues.", None)) #endif // QT_CONFIG(tooltip) - self.pdfWidthBox.setText(QCoreApplication.translate("mainWindow", u"PDF Width Render", None)) + self.jpegQualityBox.setText(QCoreApplication.translate("mainWindow", u"Custom JPEG Quality", None)) +#if QT_CONFIG(tooltip) + self.colorBox.setToolTip(QCoreApplication.translate("mainWindow", u"

Disable conversion to grayscale.

", None)) +#endif // QT_CONFIG(tooltip) + self.colorBox.setText(QCoreApplication.translate("mainWindow", u"Color mode", None)) +#if QT_CONFIG(tooltip) + self.disableProcessingBox.setToolTip(QCoreApplication.translate("mainWindow", u"

Do not process any image, ignore profile and processing options.

", None)) +#endif // QT_CONFIG(tooltip) + self.disableProcessingBox.setText(QCoreApplication.translate("mainWindow", u"Disable processing", None)) +#if QT_CONFIG(tooltip) + self.defaultOutputFolderBox.setToolTip(QCoreApplication.translate("mainWindow", u"

Unchecked - next to source
Place output files next to source files

Indeterminate - folder next to source
Place output files in a folder next to source files

Checked - Custom
Place output files in custom directory specified by right button

", None)) +#endif // QT_CONFIG(tooltip) + self.defaultOutputFolderBox.setText(QCoreApplication.translate("mainWindow", u"Output Folder", None)) +#if QT_CONFIG(tooltip) + self.defaultOutputFolderButton.setToolTip(QCoreApplication.translate("mainWindow", u"

Use this to select the default output directory.

", None)) +#endif // QT_CONFIG(tooltip) + self.defaultOutputFolderButton.setText("") +#if QT_CONFIG(tooltip) + self.metadataTitleBox.setToolTip(QCoreApplication.translate("mainWindow", u"

Unchecked - Don't use metadata Title
Write default title.

Indeterminate - Add metadata Title to the default schema
Write default title with Title from ComicInfo.xml or other embedded metadata.

Checked - Use metadata Title only
Write Title from ComicInfo.xml or other embedded metadata.

", None)) +#endif // QT_CONFIG(tooltip) + self.metadataTitleBox.setText(QCoreApplication.translate("mainWindow", u"Metadata Title", None)) +#if QT_CONFIG(tooltip) + self.autocontrastBox.setToolTip(QCoreApplication.translate("mainWindow", u"

Unchecked - BW only
Only autocontrast bw pages. Ignored for pages where near blacks or whites don't exist.

Indeterminate - Disabled
Disable autocontrast

Checked - BW and Color
BW and color images will be autocontrasted. Ignored for pages where near blacks or whites don't exist.

", None)) +#endif // QT_CONFIG(tooltip) + self.autocontrastBox.setText(QCoreApplication.translate("mainWindow", u"Autocontrast", None)) +#if QT_CONFIG(tooltip) + self.pdfExtractBox.setToolTip(QCoreApplication.translate("mainWindow", u"Use the PDF image extraction method from KCC 8 and earlier.\n" +"\n" +"Useful for really weird PDFs.", None)) +#endif // QT_CONFIG(tooltip) + self.pdfExtractBox.setText(QCoreApplication.translate("mainWindow", u"PDF Legacy Extract", None)) +#if QT_CONFIG(tooltip) + self.upscaleBox.setToolTip(QCoreApplication.translate("mainWindow", u"

Unchecked - Nothing
Images smaller than device resolution will not be resized.

Indeterminate - Stretching
Images smaller than device resolution will be resized. Aspect ratio will be not preserved.

Checked - Upscaling
Images smaller than device resolution will be resized. Aspect ratio will be preserved.

", None)) +#endif // QT_CONFIG(tooltip) + self.upscaleBox.setText(QCoreApplication.translate("mainWindow", u"Stretch/Upscale", None)) +#if QT_CONFIG(tooltip) + self.chunkSizeCheckBox.setToolTip(QCoreApplication.translate("mainWindow", u"

Unchecked
Maximal output file size is 100 MB for Webtoon, 400 MB for others before split occurs.

Checked
Output file size specified in "Chunk size MB" before split occurs.

", None)) +#endif // QT_CONFIG(tooltip) + self.chunkSizeCheckBox.setText(QCoreApplication.translate("mainWindow", u"Chunk size", None)) +#if QT_CONFIG(tooltip) + self.qualityBox.setToolTip(QCoreApplication.translate("mainWindow", u"

Unchecked - 4 panels
Zoom each corner separately.

Indeterminate - 2 panels
Zoom only the top and bottom of the page.

Checked - 4 high-quality panels
Zoom each corner separately. Try to increase the quality of magnification. Check wiki for more details.

", None)) +#endif // QT_CONFIG(tooltip) + self.qualityBox.setText(QCoreApplication.translate("mainWindow", u"Panel View 4/2/HQ", None)) +#if QT_CONFIG(tooltip) + self.mozJpegBox.setToolTip(QCoreApplication.translate("mainWindow", u"

Unchecked - JPEG
Use JPEG files

Indeterminate - force PNG
Create PNG files instead JPEG

Checked - mozJpeg
10-20% smaller JPEG file, with the same image quality, but processing time multiplied by 2

", None)) +#endif // QT_CONFIG(tooltip) + self.mozJpegBox.setText(QCoreApplication.translate("mainWindow", u"JPEG/PNG/mozJpeg", None)) +#if QT_CONFIG(tooltip) + self.titleEdit.setToolTip(QCoreApplication.translate("mainWindow", u"

Default Title

", None)) +#endif // QT_CONFIG(tooltip) + self.titleEdit.setPlaceholderText(QCoreApplication.translate("mainWindow", u"Default Title", None)) +#if QT_CONFIG(tooltip) + self.eraseRainbowBox.setToolTip(QCoreApplication.translate("mainWindow", u"Erase rainbow effect on color eink screen by attenuating interfering frequencies", None)) +#endif // QT_CONFIG(tooltip) + self.eraseRainbowBox.setText(QCoreApplication.translate("mainWindow", u"Rainbow eraser", None)) +#if QT_CONFIG(tooltip) + self.fileFusionBox.setToolTip(QCoreApplication.translate("mainWindow", u"

Combines all selected files into a single file. (Helpful for combining chapters into volumes.)

", None)) +#endif // QT_CONFIG(tooltip) + self.fileFusionBox.setText(QCoreApplication.translate("mainWindow", u"File Fusion", None)) +#if QT_CONFIG(tooltip) + self.coverFillBox.setToolTip(QCoreApplication.translate("mainWindow", u"Resize cover to exact device resolution by center-cropping to aspect ratio first.\n" +"May crop top/bottom or left/right depending on source aspect ratio. Not implemented for Kindle Scribe.", None)) +#endif // QT_CONFIG(tooltip) + self.coverFillBox.setText(QCoreApplication.translate("mainWindow", u"Cover Fill", None)) +#if QT_CONFIG(tooltip) + self.rotateRightBox.setToolTip(QCoreApplication.translate("mainWindow", u"Rotate 2 page spreads in opposite direction than normal.", None)) +#endif // QT_CONFIG(tooltip) + self.rotateRightBox.setText(QCoreApplication.translate("mainWindow", u"Rotate Right", None)) +#if QT_CONFIG(tooltip) + self.gammaBox.setToolTip(QCoreApplication.translate("mainWindow", u"

Set a custom gamma correction.

1.0 is default (disabled).
< 1.0 makes the image brighter.
> 1.0 makes the image darker.

1.8 was the default in KCC 9.1.0 and earlier.

Use if you want to make midtones darker.

", None)) +#endif // QT_CONFIG(tooltip) + self.gammaBox.setText(QCoreApplication.translate("mainWindow", u"Custom gamma", None)) +#if QT_CONFIG(tooltip) + self.autoLevelBox.setToolTip(QCoreApplication.translate("mainWindow", u"

By default, KCC maps the darkest pixel value to pure black (the black point.)

Extreme black point sets the black point to be the most common dark pixel value.

Useful when text is black but artwork is gray.

", None)) +#endif // QT_CONFIG(tooltip) + self.autoLevelBox.setText(QCoreApplication.translate("mainWindow", u"Extreme Black Point", None)) +#if QT_CONFIG(tooltip) + self.interPanelCropBox.setToolTip(QCoreApplication.translate("mainWindow", u"

Unchecked - Disabled
Disabled

Indeterminate - Horizontal
Crop empty horizontal lines.

Checked - Both
Crop empty horizontal and vertical lines.

", None)) +#endif // QT_CONFIG(tooltip) + self.interPanelCropBox.setText(QCoreApplication.translate("mainWindow", u"Inter-panel crop", None)) +#if QT_CONFIG(tooltip) + self.rotateBox.setToolTip(QCoreApplication.translate("mainWindow", u"

Unchecked - Split
Double page spreads will be cut into two separate pages.

Indeterminate - Split and rotate
Double page spreads will be displayed twice. First split and then rotated.

Checked - Rotate
Double page spreads will be rotated.

", None)) +#endif // QT_CONFIG(tooltip) + self.rotateBox.setText(QCoreApplication.translate("mainWindow", u"Spread splitter", None)) +#if QT_CONFIG(tooltip) + self.authorEdit.setToolTip(QCoreApplication.translate("mainWindow", u"Default Author is KCC", None)) +#endif // QT_CONFIG(tooltip) + self.authorEdit.setPlaceholderText(QCoreApplication.translate("mainWindow", u"Default Author", None)) +#if QT_CONFIG(tooltip) + self.croppingBox.setToolTip(QCoreApplication.translate("mainWindow", u"

Unchecked - Disabled

Disabled

Indeterminate - Margins
Margins

Checked - Margins + page numbers
Margins +page numbers

", None)) +#endif // QT_CONFIG(tooltip) + self.croppingBox.setText(QCoreApplication.translate("mainWindow", u"Cropping mode", None)) +#if QT_CONFIG(tooltip) + self.pngLegacyBox.setToolTip(QCoreApplication.translate("mainWindow", u"Use a more compatible 8 bit PNG instead of 4 bit.", None)) +#endif // QT_CONFIG(tooltip) + self.pngLegacyBox.setText(QCoreApplication.translate("mainWindow", u"PNG Legacy Mode", None)) self.gammaLabel.setText(QCoreApplication.translate("mainWindow", u"Gamma: Auto", None)) self.jpegQualityLabel.setText(QCoreApplication.translate("mainWindow", u"JPEG Quality:", None)) # retranslateUi diff --git a/kindlecomicconverter/comic2ebook.py b/kindlecomicconverter/comic2ebook.py index 325c8c5..40fe26d 100755 --- a/kindlecomicconverter/comic2ebook.py +++ b/kindlecomicconverter/comic2ebook.py @@ -713,6 +713,8 @@ def imgFileProcessing(work): img.convertToGrayscale() elif opt.profile == 'KDX' and opt.format == 'CBZ': img.convertToGrayscale() + elif opt.pnglegacy: + img.convertToGrayscale() else: img.convertToGrayscale() output.append(img.saveToDir()) @@ -1411,6 +1413,8 @@ def makeParser(): help="Erase rainbow effect on color eink screen by attenuating interfering frequencies") processing_options.add_argument("--forcepng", action="store_true", dest="forcepng", default=False, help="Create PNG files instead JPEG") + processing_options.add_argument("--pnglegacy", action="store_true", dest="pnglegacy", default=False, + help="Use a more compatible 8 bit png instead of 4 bit") processing_options.add_argument("--noquantize", action="store_true", dest="noquantize", default=False, help="Don't quantize to 16 color PNG") processing_options.add_argument("--mozjpeg", action="store_true", dest="mozjpeg", default=False, From f5fe8d93b0e0c65d5583593c3434df4b11ffecc4 Mon Sep 17 00:00:00 2001 From: Alex Xu Date: Tue, 17 Mar 2026 13:41:41 -0700 Subject: [PATCH 060/107] color images are always saved as JPG by default (#1272) * use jpg for color images always * add colorOutput variable * fix typos * remove dither * add box * clarify png * remove debug code * remove unneeded check --- README.md | 3 ++- gui/KCC.ui | 12 +++++++++++- kindlecomicconverter/KCC_gui.py | 3 +++ kindlecomicconverter/KCC_ui.py | 11 ++++++++++- kindlecomicconverter/comic2ebook.py | 9 +++++---- kindlecomicconverter/image.py | 7 ++++--- 6 files changed, 35 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index e77da46..14501b2 100644 --- a/README.md +++ b/README.md @@ -272,7 +272,8 @@ PROCESSING: --whiteborders Disable autodetection and force white borders --coverfill Center-crop only the cover to fill target device screen --forcecolor Don't convert images to grayscale - --forcepng Create PNG files instead JPEG + --forcepng Create PNG files instead JPEG for black and white images + --force-png-rgb Force color images to be saved as PNG --pnglegacy Use a more compatible 8 bit PNG instead of 4 bit. --noquantize Don't quantize PNG images to 16 colors --mozjpeg Create JPEG files using mozJpeg diff --git a/gui/KCC.ui b/gui/KCC.ui index 1020b6a..4e75811 100644 --- a/gui/KCC.ui +++ b/gui/KCC.ui @@ -801,7 +801,7 @@ Useful for really weird PDFs. - <html><head/><body><p><span style=" font-weight:600; text-decoration: underline;">Unchecked - JPEG<br/></span>Use JPEG files</p><p><span style=" font-weight:600; text-decoration: underline;">Indeterminate - force PNG<br/></span>Create PNG files instead JPEG</p><p><span style=" font-weight:600; text-decoration: underline;">Checked - mozJpeg<br/></span>10-20% smaller JPEG file, with the same image quality, but processing time multiplied by 2</p></body></html> + <html><head/><body><p><span style=" font-weight:600; text-decoration: underline;">Unchecked - JPEG<br/></span>Use JPEG files</p><p><span style=" font-weight:600; text-decoration: underline;">Indeterminate - force PNG<br/></span>Create PNG files instead JPEG for black and white images</p><p><span style=" font-weight:600; text-decoration: underline;">Checked - mozJpeg<br/></span>10-20% smaller JPEG file, with the same image quality, but processing time multiplied by 2</p></body></html> JPEG/PNG/mozJpeg @@ -965,6 +965,16 @@ May crop top/bottom or left/right depending on source aspect ratio. Not implemen + + + + Force full color images to be saved in lossless PNG format, dramatically increases the filesize. + + + Force PNG RGB + + +
diff --git a/kindlecomicconverter/KCC_gui.py b/kindlecomicconverter/KCC_gui.py index 925a623..87910d5 100644 --- a/kindlecomicconverter/KCC_gui.py +++ b/kindlecomicconverter/KCC_gui.py @@ -351,6 +351,8 @@ class WorkerThread(QThread): options.rotateright = True if GUI.rotateFirstBox.isChecked(): options.rotatefirst = True + if GUI.forcePngRgbBox.isChecked(): + options.force_png_rgb = True if GUI.mozJpegBox.checkState() == Qt.CheckState.PartiallyChecked: options.forcepng = True elif GUI.mozJpegBox.checkState() == Qt.CheckState.Checked: @@ -1054,6 +1056,7 @@ class KCCGUI(KCC_ui.Ui_mainWindow): 'coverFillBox': GUI.coverFillBox.checkState(), 'metadataTitleBox': GUI.metadataTitleBox.checkState(), 'mozJpegBox': GUI.mozJpegBox.checkState(), + 'forcePngRgbBox': GUI.forcePngRgbBox.checkState(), 'pngLegacyBox': GUI.pngLegacyBox.checkState(), 'noQuantizeBox': GUI.noQuantizeBox.checkState(), 'jpegQualityBox': GUI.jpegQualityBox.checkState(), diff --git a/kindlecomicconverter/KCC_ui.py b/kindlecomicconverter/KCC_ui.py index b2e2fba..5f0c473 100644 --- a/kindlecomicconverter/KCC_ui.py +++ b/kindlecomicconverter/KCC_ui.py @@ -492,6 +492,11 @@ class Ui_mainWindow(object): self.gridLayout_2.addWidget(self.pngLegacyBox, 11, 0, 1, 1) + self.forcePngRgbBox = QCheckBox(self.optionWidget) + self.forcePngRgbBox.setObjectName(u"forcePngRgbBox") + + self.gridLayout_2.addWidget(self.forcePngRgbBox, 11, 2, 1, 1) + self.gridLayout.addWidget(self.optionWidget, 5, 0, 1, 2) @@ -745,7 +750,7 @@ class Ui_mainWindow(object): #endif // QT_CONFIG(tooltip) self.qualityBox.setText(QCoreApplication.translate("mainWindow", u"Panel View 4/2/HQ", None)) #if QT_CONFIG(tooltip) - self.mozJpegBox.setToolTip(QCoreApplication.translate("mainWindow", u"

Unchecked - JPEG
Use JPEG files

Indeterminate - force PNG
Create PNG files instead JPEG

Checked - mozJpeg
10-20% smaller JPEG file, with the same image quality, but processing time multiplied by 2

", None)) + self.mozJpegBox.setToolTip(QCoreApplication.translate("mainWindow", u"

Unchecked - JPEG
Use JPEG files

Indeterminate - force PNG
Create PNG files instead JPEG for black and white images

Checked - mozJpeg
10-20% smaller JPEG file, with the same image quality, but processing time multiplied by 2

", None)) #endif // QT_CONFIG(tooltip) self.mozJpegBox.setText(QCoreApplication.translate("mainWindow", u"JPEG/PNG/mozJpeg", None)) #if QT_CONFIG(tooltip) @@ -797,6 +802,10 @@ class Ui_mainWindow(object): self.pngLegacyBox.setToolTip(QCoreApplication.translate("mainWindow", u"Use a more compatible 8 bit PNG instead of 4 bit.", None)) #endif // QT_CONFIG(tooltip) self.pngLegacyBox.setText(QCoreApplication.translate("mainWindow", u"PNG Legacy Mode", None)) +#if QT_CONFIG(tooltip) + self.forcePngRgbBox.setToolTip(QCoreApplication.translate("mainWindow", u"Force full color images to be saved in lossless PNG format, dramatically increases the filesize.", None)) +#endif // QT_CONFIG(tooltip) + self.forcePngRgbBox.setText(QCoreApplication.translate("mainWindow", u"Force PNG RGB", None)) self.gammaLabel.setText(QCoreApplication.translate("mainWindow", u"Gamma: Auto", None)) self.jpegQualityLabel.setText(QCoreApplication.translate("mainWindow", u"JPEG Quality:", None)) # retranslateUi diff --git a/kindlecomicconverter/comic2ebook.py b/kindlecomicconverter/comic2ebook.py index 40fe26d..9b017d4 100755 --- a/kindlecomicconverter/comic2ebook.py +++ b/kindlecomicconverter/comic2ebook.py @@ -689,7 +689,6 @@ def imgFileProcessing(work): workImg = image.ComicPageParser((dirpath, afile), opt) for i in workImg.payload: img = image.ComicPage(opt, *i) - is_color = (opt.forcecolor and img.color) if opt.cropping == 2 and not opt.webtoon: img.cropPageNumber(opt.croppingp, opt.croppingm) if opt.cropping == 1 and not opt.webtoon: @@ -701,9 +700,9 @@ def imgFileProcessing(work): img.autocontrastImage() img.resizeImage() - img.optimizeForDisplay(opt.eraserainbow, is_color) + img.optimizeForDisplay(opt.eraserainbow, img.colorOutput) - if is_color: + if img.colorOutput: pass elif opt.forcepng: img.convertToGrayscale() @@ -1412,7 +1411,9 @@ def makeParser(): output_options.add_argument("--eraserainbow", action="store_true", dest="eraserainbow", default=False, help="Erase rainbow effect on color eink screen by attenuating interfering frequencies") processing_options.add_argument("--forcepng", action="store_true", dest="forcepng", default=False, - help="Create PNG files instead JPEG") + help="Create PNG files instead JPEG for black and white images") + processing_options.add_argument("--force-png-rgb", action="store_true", dest="force_png_rgb", default=False, + help="Force color images to be saved as PNG") processing_options.add_argument("--pnglegacy", action="store_true", dest="pnglegacy", default=False, help="Use a more compatible 8 bit png instead of 4 bit") processing_options.add_argument("--noquantize", action="store_true", dest="noquantize", default=False, diff --git a/kindlecomicconverter/image.py b/kindlecomicconverter/image.py index 6583ace..376203c 100755 --- a/kindlecomicconverter/image.py +++ b/kindlecomicconverter/image.py @@ -277,6 +277,8 @@ class ComicPage: self.original_color_mode = image.mode # TODO: color check earlier self.image = image.convert("RGB") + self.color = self.colorCheck() + self.colorOutput = self.color and self.opt.forcecolor self.fill = fill self.rotated = False self.orgPath = os.path.join(path[0], path[1]) @@ -295,8 +297,7 @@ class ComicPage: if not hasattr(Image, 'Resampling'): Image.Resampling = Image - @cached_property - def color(self): + def colorCheck(self): if self.original_color_mode in ("L", "1"): return False if self.opt.webtoon: @@ -405,7 +406,7 @@ class ComicPage: raise RuntimeError('Cannot save image. ' + str(err)) def save_with_codec(self, image, targetPath): - if self.opt.forcepng: + if self.opt.forcepng and (not self.colorOutput or self.opt.force_png_rgb): image.info.pop('transparency', None) if self.opt.iskindle and ('MOBI' in self.opt.format or 'EPUB' in self.opt.format): targetPath += '.gif' From 87987c9ebfc9253afa2e93e59ee2fce0d7db37cb Mon Sep 17 00:00:00 2001 From: Alex Xu Date: Tue, 17 Mar 2026 15:09:02 -0700 Subject: [PATCH 061/107] fix humble bundle pdf png autocontrast (#1273) --- kindlecomicconverter/comic2ebook.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/kindlecomicconverter/comic2ebook.py b/kindlecomicconverter/comic2ebook.py index 9b017d4..3f6fd44 100755 --- a/kindlecomicconverter/comic2ebook.py +++ b/kindlecomicconverter/comic2ebook.py @@ -698,6 +698,9 @@ def imgFileProcessing(work): img.gammaCorrectImage() + if not img.colorOutput: + img.convertToGrayscale() + img.autocontrastImage() img.resizeImage() img.optimizeForDisplay(opt.eraserainbow, img.colorOutput) @@ -705,7 +708,6 @@ def imgFileProcessing(work): if img.colorOutput: pass elif opt.forcepng: - img.convertToGrayscale() if not opt.noquantize: img.quantizeImage() if opt.format == 'PDF': @@ -714,8 +716,6 @@ def imgFileProcessing(work): img.convertToGrayscale() elif opt.pnglegacy: img.convertToGrayscale() - else: - img.convertToGrayscale() output.append(img.saveToDir()) return output except Exception: From a87eb318cf8e403d5cc21645619bd365363c3718 Mon Sep 17 00:00:00 2001 From: Alex Xu Date: Tue, 17 Mar 2026 15:10:02 -0700 Subject: [PATCH 062/107] bump 9.6.1 --- kindlecomicconverter/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kindlecomicconverter/__init__.py b/kindlecomicconverter/__init__.py index a26eba8..aecd40c 100644 --- a/kindlecomicconverter/__init__.py +++ b/kindlecomicconverter/__init__.py @@ -1,4 +1,4 @@ -__version__ = '9.6.0' +__version__ = '9.6.1' __license__ = 'ISC' __copyright__ = '2012-2022, Ciro Mattia Gonano , Pawel Jastrzebski , darodi' __docformat__ = 'restructuredtext en' From e0471b2dc94a6bc42ec3a13f4aad18432ea70b41 Mon Sep 17 00:00:00 2001 From: Alex Xu Date: Tue, 17 Mar 2026 16:11:40 -0700 Subject: [PATCH 063/107] fix autocrop in certain situations (#1274) --- kindlecomicconverter/image.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kindlecomicconverter/image.py b/kindlecomicconverter/image.py index 376203c..816952d 100755 --- a/kindlecomicconverter/image.py +++ b/kindlecomicconverter/image.py @@ -518,7 +518,7 @@ class ComicPage: self.image = ImageOps.contain(self.image, self.size, method=method) def resize_method(self): - if self.image.size[0] < self.size[0] and self.image.size[1] < self.size[1]: + if self.image.size[0] <= self.size[0] and self.image.size[1] <= self.size[1]: return Image.Resampling.BICUBIC else: return Image.Resampling.LANCZOS From 9a93cc4b17633b88eee67e92faee24722b5e3420 Mon Sep 17 00:00:00 2001 From: Alex Xu Date: Tue, 17 Mar 2026 16:11:57 -0700 Subject: [PATCH 064/107] Update __init__.py --- kindlecomicconverter/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kindlecomicconverter/__init__.py b/kindlecomicconverter/__init__.py index aecd40c..3325716 100644 --- a/kindlecomicconverter/__init__.py +++ b/kindlecomicconverter/__init__.py @@ -1,4 +1,4 @@ -__version__ = '9.6.1' +__version__ = '9.6.2' __license__ = 'ISC' __copyright__ = '2012-2022, Ciro Mattia Gonano , Pawel Jastrzebski , darodi' __docformat__ = 'restructuredtext en' From d19a4754fa257fd584f894fabcf347df8b64940c Mon Sep 17 00:00:00 2001 From: Alex Xu Date: Thu, 26 Mar 2026 08:09:25 -0700 Subject: [PATCH 065/107] if cropping, render pdf to 2x (#1276) --- kindlecomicconverter/comic2ebook.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/kindlecomicconverter/comic2ebook.py b/kindlecomicconverter/comic2ebook.py index 3f6fd44..f87f6b5 100755 --- a/kindlecomicconverter/comic2ebook.py +++ b/kindlecomicconverter/comic2ebook.py @@ -892,10 +892,10 @@ def getWorkFolder(afile, workdir=None): raise UserWarning("Failed to extract images from PDF file.") return workdir target_width, target_height = options.profileData[1] - if options.cropping == 1: - target_height = target_height + target_height*0.20 #Account for possible margin at the top and bottom - elif options.cropping == 2: - target_height = target_height + target_height*0.25 #Account for possible margin at the top and bottom with page number + #Account for possible margin at the top and bottom + if options.cropping: + target_width *= 2 + target_height *= 2 try: mupdf_pdf_process_pages_parallel(afile, fullPath, target_width, target_height) except Exception as e: From 232bac00a9923589c98c43c59abe64ebdc13dab9 Mon Sep 17 00:00:00 2001 From: Alex Xu Date: Mon, 6 Apr 2026 21:03:17 -0700 Subject: [PATCH 066/107] ZIP stored instead of deflate (#1283) --- kindlecomicconverter/comic2ebook.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/kindlecomicconverter/comic2ebook.py b/kindlecomicconverter/comic2ebook.py index f87f6b5..aa7e495 100755 --- a/kindlecomicconverter/comic2ebook.py +++ b/kindlecomicconverter/comic2ebook.py @@ -1295,12 +1295,12 @@ def makeZIP(zipfilename, basedir, job_progress='', isepub=False): mimetypeFile = open(os.path.join(basedir, '!mimetype'), 'w') mimetypeFile.write('application/epub+zip') mimetypeFile.close() - subprocess_run([SEVENZIP, 'a', '-tzip', zipfilename, "*"], capture_output=True, check=True, cwd=basedir) + subprocess_run([SEVENZIP, 'a', '-mx0', '-tzip', zipfilename, "*"], capture_output=True, check=True, cwd=basedir) # crazy hack to ensure mimetype is first when using 7zip if isepub: subprocess_run([SEVENZIP, 'rn', zipfilename, '!mimetype', 'mimetype'], capture_output=True, check=True, cwd=basedir) else: - zipOutput = ZipFile(zipfilename, 'w', ZIP_DEFLATED) + zipOutput = ZipFile(zipfilename, 'w', ZIP_STORED) if isepub: zipOutput.writestr('mimetype', 'application/epub+zip', ZIP_STORED) for dirpath, _, filenames in os.walk(basedir): From fa33ef8f8997d936091cd1960a1b23294ef301b8 Mon Sep 17 00:00:00 2001 From: Alex Xu Date: Sat, 11 Apr 2026 18:06:03 -0700 Subject: [PATCH 067/107] revert 2x render, too expensive (#1287) --- kindlecomicconverter/comic2ebook.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/kindlecomicconverter/comic2ebook.py b/kindlecomicconverter/comic2ebook.py index aa7e495..82ceba6 100755 --- a/kindlecomicconverter/comic2ebook.py +++ b/kindlecomicconverter/comic2ebook.py @@ -892,10 +892,12 @@ def getWorkFolder(afile, workdir=None): raise UserWarning("Failed to extract images from PDF file.") return workdir target_width, target_height = options.profileData[1] - #Account for possible margin at the top and bottom - if options.cropping: - target_width *= 2 - target_height *= 2 + if options.cropping == 1: + target_height *= 1.2 #Account for possible margin at the top and bottom + target_width *= 1.2 + elif options.cropping == 2: + target_height *= 1.25 #Account for possible margin at the top and bottom with page number + target_width *= 1.25 try: mupdf_pdf_process_pages_parallel(afile, fullPath, target_width, target_height) except Exception as e: From 61f3097be5eb8ba75e03c072e8837e59df96d72e Mon Sep 17 00:00:00 2001 From: Alex Xu Date: Sat, 11 Apr 2026 19:09:04 -0700 Subject: [PATCH 068/107] smart cover crop is now an option default off (#1288) --- README.md | 1 + gui/KCC.ui | 10 ++++++++++ kindlecomicconverter/KCC_gui.py | 3 +++ kindlecomicconverter/KCC_ui.py | 9 +++++++++ kindlecomicconverter/comic2ebook.py | 2 ++ kindlecomicconverter/image.py | 3 ++- 6 files changed, 27 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 14501b2..a01159d 100644 --- a/README.md +++ b/README.md @@ -270,6 +270,7 @@ PROCESSING: Crop empty sections. 0: Disabled 1: Horizontally 2: Both [Default=0] --blackborders Disable autodetection and force black borders --whiteborders Disable autodetection and force white borders + --smartcovercrop Attempt to crop main cover from wide image --coverfill Center-crop only the cover to fill target device screen --forcecolor Don't convert images to grayscale --forcepng Create PNG files instead JPEG for black and white images diff --git a/gui/KCC.ui b/gui/KCC.ui index 4e75811..1767b25 100644 --- a/gui/KCC.ui +++ b/gui/KCC.ui @@ -975,6 +975,16 @@ May crop top/bottom or left/right depending on source aspect ratio. Not implemen
+ + + + Attempt to crop main cover from wide image. + + + Smart Cover Crop + + +
diff --git a/kindlecomicconverter/KCC_gui.py b/kindlecomicconverter/KCC_gui.py index 87910d5..c67e2bb 100644 --- a/kindlecomicconverter/KCC_gui.py +++ b/kindlecomicconverter/KCC_gui.py @@ -331,6 +331,8 @@ class WorkerThread(QThread): options.pdfextract = True if GUI.pdfWidthBox.isChecked(): options.pdfwidth = True + if GUI.smartCoverCropBox.isChecked(): + options.smartcovercrop = True if GUI.coverFillBox.isChecked(): options.coverfill = True if GUI.metadataTitleBox.checkState() == Qt.CheckState.PartiallyChecked: @@ -1053,6 +1055,7 @@ class KCCGUI(KCC_ui.Ui_mainWindow): 'disableProcessingBox': GUI.disableProcessingBox.checkState(), 'pdfExtractBox': GUI.pdfExtractBox.checkState(), 'pdfWidthBox': GUI.pdfWidthBox.checkState(), + 'smartCoverCropBox': GUI.smartCoverCropBox.checkState(), 'coverFillBox': GUI.coverFillBox.checkState(), 'metadataTitleBox': GUI.metadataTitleBox.checkState(), 'mozJpegBox': GUI.mozJpegBox.checkState(), diff --git a/kindlecomicconverter/KCC_ui.py b/kindlecomicconverter/KCC_ui.py index 5f0c473..473a2e9 100644 --- a/kindlecomicconverter/KCC_ui.py +++ b/kindlecomicconverter/KCC_ui.py @@ -497,6 +497,11 @@ class Ui_mainWindow(object): self.gridLayout_2.addWidget(self.forcePngRgbBox, 11, 2, 1, 1) + self.smartCoverCropBox = QCheckBox(self.optionWidget) + self.smartCoverCropBox.setObjectName(u"smartCoverCropBox") + + self.gridLayout_2.addWidget(self.smartCoverCropBox, 11, 1, 1, 1) + self.gridLayout.addWidget(self.optionWidget, 5, 0, 1, 2) @@ -806,6 +811,10 @@ class Ui_mainWindow(object): self.forcePngRgbBox.setToolTip(QCoreApplication.translate("mainWindow", u"Force full color images to be saved in lossless PNG format, dramatically increases the filesize.", None)) #endif // QT_CONFIG(tooltip) self.forcePngRgbBox.setText(QCoreApplication.translate("mainWindow", u"Force PNG RGB", None)) +#if QT_CONFIG(tooltip) + self.smartCoverCropBox.setToolTip(QCoreApplication.translate("mainWindow", u"Attempt to crop main cover from wide image.", None)) +#endif // QT_CONFIG(tooltip) + self.smartCoverCropBox.setText(QCoreApplication.translate("mainWindow", u"Smart Cover Crop", None)) self.gammaLabel.setText(QCoreApplication.translate("mainWindow", u"Gamma: Auto", None)) self.jpegQualityLabel.setText(QCoreApplication.translate("mainWindow", u"JPEG Quality:", None)) # retranslateUi diff --git a/kindlecomicconverter/comic2ebook.py b/kindlecomicconverter/comic2ebook.py index 82ceba6..00ba009 100755 --- a/kindlecomicconverter/comic2ebook.py +++ b/kindlecomicconverter/comic2ebook.py @@ -1376,6 +1376,8 @@ def makeParser(): help="Use the legacy PDF image extraction method from KCC 8 and earlier") processing_options.add_argument("--pdfwidth", action="store_true", dest="pdfwidth", default=False, help="Render vector PDFs to device width instead of height.") + processing_options.add_argument("--smartcovercrop", action="store_true", dest="smartcovercrop", default=False, + help="Attempt to crop main cover from wide image") processing_options.add_argument("--coverfill", action="store_true", dest="coverfill", default=False, help="Crop cover to fill screen") processing_options.add_argument("-u", "--upscale", action="store_true", dest="upscale", default=False, diff --git a/kindlecomicconverter/image.py b/kindlecomicconverter/image.py index 816952d..1795a0f 100755 --- a/kindlecomicconverter/image.py +++ b/kindlecomicconverter/image.py @@ -572,7 +572,8 @@ class Cover: self.image = ImageOps.autocontrast(self.image, preserve_tone=True) if not self.options.forcecolor: self.image = self.image.convert('L') - self.crop_main_cover() + if self.options.smartcovercrop: + self.crop_main_cover() size = list(self.options.profileData[1]) if self.options.kindle_scribe_azw3: From 8f4072bfabe0d9bbba554bd75868768380f97669 Mon Sep 17 00:00:00 2001 From: Alex Xu Date: Sat, 11 Apr 2026 19:32:49 -0700 Subject: [PATCH 069/107] disable extra png options when png is not selected (#1289) --- kindlecomicconverter/KCC_gui.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/kindlecomicconverter/KCC_gui.py b/kindlecomicconverter/KCC_gui.py index c67e2bb..54aff4e 100644 --- a/kindlecomicconverter/KCC_gui.py +++ b/kindlecomicconverter/KCC_gui.py @@ -828,6 +828,14 @@ class KCCGUI(KCC_ui.Ui_mainWindow): if bad_format in current_format: self.addMessage('Scribe PNG MOBI/EPUB has a lot of problems like blank pages/sections. Use JPG instead.', 'warning') break + GUI.pngLegacyBox.setEnabled(True) + GUI.noQuantizeBox.setEnabled(True) + GUI.forcePngRgbBox.setEnabled(True) + else: + GUI.pngLegacyBox.setEnabled(False) + GUI.noQuantizeBox.setEnabled(False) + GUI.forcePngRgbBox.setEnabled(False) + def togglechunkSizeCheckBox(self, value): GUI.chunkSizeWidget.setVisible(value) From e4c918f0f380e929d2f6416c2ab7977d03b8c5d1 Mon Sep 17 00:00:00 2001 From: Alex Xu Date: Sun, 12 Apr 2026 09:04:21 -0700 Subject: [PATCH 070/107] add webp output support (ignored for Kindle MOBI/EPUB and all PDF) (#1290) * add webp output support (ignored for Kindle MOBI/EPUB) * disable png extra optons by default * pdf webp is not supported --- README.md | 1 + gui/KCC.ui | 633 ++++++++++++++-------------- kindlecomicconverter/KCC_gui.py | 3 + kindlecomicconverter/KCC_ui.py | 532 +++++++++++------------ kindlecomicconverter/comic2ebook.py | 6 + kindlecomicconverter/image.py | 13 +- 6 files changed, 620 insertions(+), 568 deletions(-) diff --git a/README.md b/README.md index a01159d..a65f357 100644 --- a/README.md +++ b/README.md @@ -274,6 +274,7 @@ PROCESSING: --coverfill Center-crop only the cover to fill target device screen --forcecolor Don't convert images to grayscale --forcepng Create PNG files instead JPEG for black and white images + --webp Replace JPG with lossy WEBP and PNG with lossless WEBP --force-png-rgb Force color images to be saved as PNG --pnglegacy Use a more compatible 8 bit PNG instead of 4 bit. --noquantize Don't quantize PNG images to 16 colors diff --git a/gui/KCC.ui b/gui/KCC.ui index 1767b25..f7311c6 100644 --- a/gui/KCC.ui +++ b/gui/KCC.ui @@ -514,68 +514,13 @@ 0 - - + + - <html><head/><body><p>When the spread splitter option is partially checked,</p><p><span style=" font-weight:600; text-decoration: underline;">Unchecked - Rotate Last<br/></span>Put the rotated 2 page spread after the split spreads.</p><p><span style=" font-weight:600; text-decoration: underline;">Checked - Rotate First<br/></span>Put the rotated 2 page spread before the split spreads.</p></body></html> + Do not rotate double page spreads in spread splitter option. - Rotate First - - - - - - - <html><head/><body><p style='white-space:pre'><span style=" font-weight:600; text-decoration: underline;">Unchecked - Automatic mode<br/></span>The output will be split automatically.</p><p style='white-space:pre'><span style=" font-weight:600; text-decoration: underline;">Checked - Volume mode<br/></span>Every subdirectory will be considered as a separate volume.</p></body></html> - - - Output split - - - - - - - Render vector PDFs to device width instead of height. - -Useful if you plan to crop a little off the top and bottom to fill screen. - - - PDF Width Render - - - - - - - <html><head/><body><p><span style=" font-weight:600; text-decoration: underline;">Unchecked - Autodetection<br/></span>The color of margins fill will be detected automatically.</p><p><span style=" font-weight:600; text-decoration: underline;">Indeterminate - White<br/></span>Margins will be untouched.</p><p><span style=" font-weight:600; text-decoration: underline;">Checked - Black<br/></span>Margins will be filled with black color.</p></body></html> - - - W/B margins - - - true - - - - - - - Delete input file(s) or directory. It's not recoverable! - - - Delete input - - - - - - - Shift first page to opposite side in landscape for two page spread alignment - - - Spread shift + No rotate @@ -589,6 +534,89 @@ Useful if you plan to crop a little off the top and bottom to fill screen. + + + + <html><head/><body><p><span style=" font-weight:600; text-decoration: underline;">Unchecked - Split<br/></span>Double page spreads will be cut into two separate pages.</p><p><span style=" font-weight:600; text-decoration: underline;">Indeterminate - Split and rotate<br/></span>Double page spreads will be displayed twice. First split and then rotated. </p><p><span style=" font-weight:600; text-decoration: underline;">Checked - Rotate<br/></span>Double page spreads will be rotated.</p></body></html> + + + Spread splitter + + + true + + + + + + + false + + + Use a more compatible 8 bit PNG instead of 4 bit. + + + PNG Legacy Mode + + + + + + + <html><head/><body><p><span style=" font-weight:600; text-decoration: underline;">Unchecked - Disabled<br/></span>Disabled</p><p><span style=" font-weight:600; text-decoration: underline;">Indeterminate - Horizontal<br/></span>Crop empty horizontal lines.</p><p><span style=" font-weight:600; text-decoration: underline;">Checked - Both<br/></span>Crop empty horizontal and vertical lines.</p></body></html> + + + Inter-panel crop + + + true + + + + + + + + 0 + 0 + + + + Qt::FocusPolicy::ClickFocus + + + <html><head/><body><p>Default Title</p></body></html> + + + Default Title + + + false + + + + + + + + 0 + 0 + + + + Qt::FocusPolicy::ClickFocus + + + Default Author is KCC + + + Default Author + + + false + + + @@ -599,13 +627,81 @@ Useful if you plan to crop a little off the top and bottom to fill screen. - - + + - Do not rotate double page spreads in spread splitter option. + <html><head/><body><p>Combines all selected files into a single file. (Helpful for combining chapters into volumes.)</p></body></html> - No rotate + File Fusion + + + + + + + Delete input file(s) or directory. It's not recoverable! + + + Delete input + + + + + + + <html><head/><body><p>Set a custom gamma correction.</p><p>1.0 is default (disabled).<br/>&lt; 1.0 makes the image brighter.<br/>&gt; 1.0 makes the image darker. </p><p>1.8 was the default in KCC 9.1.0 and earlier.</p><p>Use if you want to make midtones darker.</p></body></html> + + + Custom gamma + + + + + + + false + + + Don't quantize PNG images to 16 colors (4 bit) + +This will double file size but preserve all 256 colors (8 bit). + +Eink only has 16 shades of gray so you probably don't want this. + + + No Quantize + + + + + + + Erase rainbow effect on color eink screen by attenuating interfering frequencies + + + Rainbow eraser + + + + + + + Resize cover to exact device resolution by center-cropping to aspect ratio first. +May crop top/bottom or left/right depending on source aspect ratio. Not implemented for Kindle Scribe. + + + Cover Fill + + + + + + + Rotate 2 page spreads in opposite direction than normal. + + + Rotate Right @@ -619,17 +715,26 @@ Useful if you plan to crop a little off the top and bottom to fill screen. - - + + - Don't quantize PNG images to 16 colors (4 bit) - -This will double file size but preserve all 256 colors (8 bit). - -Eink only has 16 shades of gray so you probably don't want this. + Shift first page to opposite side in landscape for two page spread alignment - No Quantize + Spread shift + + + + + + + <html><head/><body><p><span style=" font-weight:600; text-decoration: underline;">Unchecked - Disabled</span></p><p>Disabled</p><p><span style=" font-weight:600; text-decoration: underline;">Indeterminate - Margins<br/></span>Margins</p><p><span style=" font-weight:600; text-decoration: underline;">Checked - Margins + page numbers<br/></span>Margins +page numbers</p></body></html> + + + Cropping mode + + + true @@ -647,6 +752,136 @@ Higher values are larger and higher quality, and may resolve blank page issues.< + + + + <html><head/><body><p style='white-space:pre'><span style=" font-weight:600; text-decoration: underline;">Unchecked - Automatic mode<br/></span>The output will be split automatically.</p><p style='white-space:pre'><span style=" font-weight:600; text-decoration: underline;">Checked - Volume mode<br/></span>Every subdirectory will be considered as a separate volume.</p></body></html> + + + Output split + + + + + + + <html><head/><body><p><span style=" font-weight:600; text-decoration: underline;">Unchecked - Don't use metadata Title<br/></span>Write default title.</p><p><span style=" font-weight:600; text-decoration: underline;">Indeterminate - Add metadata Title to the default schema<br/></span>Write default title with Title from ComicInfo.xml or other embedded metadata.</p><p><span style=" font-weight:600; text-decoration: underline;">Checked - Use metadata Title only<br/></span>Write Title from ComicInfo.xml or other embedded metadata.</p></body></html> + + + Metadata Title + + + true + + + + + + + Attempt to crop main cover from wide image. + + + Smart Cover Crop + + + + + + + <html><head/><body><p>When the spread splitter option is partially checked,</p><p><span style=" font-weight:600; text-decoration: underline;">Unchecked - Rotate Last<br/></span>Put the rotated 2 page spread after the split spreads.</p><p><span style=" font-weight:600; text-decoration: underline;">Checked - Rotate First<br/></span>Put the rotated 2 page spread before the split spreads.</p></body></html> + + + Rotate First + + + + + + + <html><head/><body><p><span style=" font-weight:600; text-decoration: underline;">Unchecked - JPEG<br/></span>Use JPEG files</p><p><span style=" font-weight:600; text-decoration: underline;">Indeterminate - force PNG<br/></span>Create PNG files instead JPEG for black and white images</p><p><span style=" font-weight:600; text-decoration: underline;">Checked - mozJpeg<br/></span>10-20% smaller JPEG file, with the same image quality, but processing time multiplied by 2</p></body></html> + + + JPEG/PNG/mozJpeg + + + true + + + + + + + <html><head/><body><p>By default, KCC maps the darkest pixel value to pure black (the black point.)</p><p>Extreme black point sets the black point to be the most common dark pixel value.</p><p>Useful when text is black but artwork is gray.</p></body></html> + + + Extreme Black Point + + + + + + + false + + + Force full color images to be saved in lossless PNG format, dramatically increases the filesize. + + + Force PNG RGB + + + + + + + <html><head/><body><p><span style=" font-weight:600; text-decoration: underline;">Unchecked - Nothing<br/></span>Images smaller than device resolution will not be resized.</p><p><span style=" font-weight:600; text-decoration: underline;">Indeterminate - Stretching<br/></span>Images smaller than device resolution will be resized. Aspect ratio will be not preserved.</p><p><span style=" font-weight:600; text-decoration: underline;">Checked - Upscaling<br/></span>Images smaller than device resolution will be resized. Aspect ratio will be preserved.</p></body></html> + + + Stretch/Upscale + + + true + + + + + + + <html><head/><body><p><span style=" font-weight:600; text-decoration: underline;">Unchecked - Autodetection<br/></span>The color of margins fill will be detected automatically.</p><p><span style=" font-weight:600; text-decoration: underline;">Indeterminate - White<br/></span>Margins will be untouched.</p><p><span style=" font-weight:600; text-decoration: underline;">Checked - Black<br/></span>Margins will be filled with black color.</p></body></html> + + + W/B margins + + + true + + + + + + + <html><head/><body><p style='white-space:pre'><span style=" font-weight:600; text-decoration: underline;">Unchecked - 4 panels<br/></span>Zoom each corner separately.</p><p style='white-space:pre'><span style=" font-weight:600; text-decoration: underline;">Indeterminate - 2 panels<br/></span>Zoom only the top and bottom of the page.</p><p><span style=" font-weight:600; text-decoration: underline;">Checked - 4 high-quality panels<br/></span>Zoom each corner separately. Try to increase the quality of magnification. Check wiki for more details.</p></body></html> + + + Panel View 4/2/HQ + + + true + + + + + + + Use the PDF image extraction method from KCC 8 and earlier. + +Useful for really weird PDFs. + + + PDF Legacy Extract + + + @@ -657,6 +892,18 @@ Higher values are larger and higher quality, and may resolve blank page issues.< + + + + Render vector PDFs to device width instead of height. + +Useful if you plan to crop a little off the top and bottom to fill screen. + + + PDF Width Render + + + @@ -724,16 +971,13 @@ Higher values are larger and higher quality, and may resolve blank page issues.< - - + + - <html><head/><body><p><span style=" font-weight:600; text-decoration: underline;">Unchecked - Don't use metadata Title<br/></span>Write default title.</p><p><span style=" font-weight:600; text-decoration: underline;">Indeterminate - Add metadata Title to the default schema<br/></span>Write default title with Title from ComicInfo.xml or other embedded metadata.</p><p><span style=" font-weight:600; text-decoration: underline;">Checked - Use metadata Title only<br/></span>Write Title from ComicInfo.xml or other embedded metadata.</p></body></html> + <html><head/><body><p><span style=" font-weight:700; text-decoration: underline;">Unchecked<br/></span>Maximal output file size is 100 MB for Webtoon, 400 MB for others before split occurs.</p><p><span style=" font-weight:700; text-decoration: underline;">Checked</span><br/>Output file size specified in &quot;Chunk size MB&quot; before split occurs.</p></body></html> - Metadata Title - - - true + Chunk size @@ -750,238 +994,15 @@ Higher values are larger and higher quality, and may resolve blank page issues.< - - + + - Use the PDF image extraction method from KCC 8 and earlier. + Replace JPG with lossy WebP and PNG with lossless WebP. This includes the JPG Quality. -Useful for really weird PDFs. +Ignored for Kindle EPUB/MOBI and all PDF. - PDF Legacy Extract - - - - - - - <html><head/><body><p><span style=" font-weight:600; text-decoration: underline;">Unchecked - Nothing<br/></span>Images smaller than device resolution will not be resized.</p><p><span style=" font-weight:600; text-decoration: underline;">Indeterminate - Stretching<br/></span>Images smaller than device resolution will be resized. Aspect ratio will be not preserved.</p><p><span style=" font-weight:600; text-decoration: underline;">Checked - Upscaling<br/></span>Images smaller than device resolution will be resized. Aspect ratio will be preserved.</p></body></html> - - - Stretch/Upscale - - - true - - - - - - - <html><head/><body><p><span style=" font-weight:700; text-decoration: underline;">Unchecked<br/></span>Maximal output file size is 100 MB for Webtoon, 400 MB for others before split occurs.</p><p><span style=" font-weight:700; text-decoration: underline;">Checked</span><br/>Output file size specified in &quot;Chunk size MB&quot; before split occurs.</p></body></html> - - - Chunk size - - - - - - - <html><head/><body><p style='white-space:pre'><span style=" font-weight:600; text-decoration: underline;">Unchecked - 4 panels<br/></span>Zoom each corner separately.</p><p style='white-space:pre'><span style=" font-weight:600; text-decoration: underline;">Indeterminate - 2 panels<br/></span>Zoom only the top and bottom of the page.</p><p><span style=" font-weight:600; text-decoration: underline;">Checked - 4 high-quality panels<br/></span>Zoom each corner separately. Try to increase the quality of magnification. Check wiki for more details.</p></body></html> - - - Panel View 4/2/HQ - - - true - - - - - - - <html><head/><body><p><span style=" font-weight:600; text-decoration: underline;">Unchecked - JPEG<br/></span>Use JPEG files</p><p><span style=" font-weight:600; text-decoration: underline;">Indeterminate - force PNG<br/></span>Create PNG files instead JPEG for black and white images</p><p><span style=" font-weight:600; text-decoration: underline;">Checked - mozJpeg<br/></span>10-20% smaller JPEG file, with the same image quality, but processing time multiplied by 2</p></body></html> - - - JPEG/PNG/mozJpeg - - - true - - - - - - - - 0 - 0 - - - - Qt::FocusPolicy::ClickFocus - - - <html><head/><body><p>Default Title</p></body></html> - - - Default Title - - - false - - - - - - - Erase rainbow effect on color eink screen by attenuating interfering frequencies - - - Rainbow eraser - - - - - - - <html><head/><body><p>Combines all selected files into a single file. (Helpful for combining chapters into volumes.)</p></body></html> - - - File Fusion - - - - - - - Resize cover to exact device resolution by center-cropping to aspect ratio first. -May crop top/bottom or left/right depending on source aspect ratio. Not implemented for Kindle Scribe. - - - Cover Fill - - - - - - - Rotate 2 page spreads in opposite direction than normal. - - - Rotate Right - - - - - - - <html><head/><body><p>Set a custom gamma correction.</p><p>1.0 is default (disabled).<br/>&lt; 1.0 makes the image brighter.<br/>&gt; 1.0 makes the image darker. </p><p>1.8 was the default in KCC 9.1.0 and earlier.</p><p>Use if you want to make midtones darker.</p></body></html> - - - Custom gamma - - - - - - - <html><head/><body><p>By default, KCC maps the darkest pixel value to pure black (the black point.)</p><p>Extreme black point sets the black point to be the most common dark pixel value.</p><p>Useful when text is black but artwork is gray.</p></body></html> - - - Extreme Black Point - - - - - - - <html><head/><body><p><span style=" font-weight:600; text-decoration: underline;">Unchecked - Disabled<br/></span>Disabled</p><p><span style=" font-weight:600; text-decoration: underline;">Indeterminate - Horizontal<br/></span>Crop empty horizontal lines.</p><p><span style=" font-weight:600; text-decoration: underline;">Checked - Both<br/></span>Crop empty horizontal and vertical lines.</p></body></html> - - - Inter-panel crop - - - true - - - - - - - <html><head/><body><p><span style=" font-weight:600; text-decoration: underline;">Unchecked - Split<br/></span>Double page spreads will be cut into two separate pages.</p><p><span style=" font-weight:600; text-decoration: underline;">Indeterminate - Split and rotate<br/></span>Double page spreads will be displayed twice. First split and then rotated. </p><p><span style=" font-weight:600; text-decoration: underline;">Checked - Rotate<br/></span>Double page spreads will be rotated.</p></body></html> - - - Spread splitter - - - true - - - - - - - - 0 - 0 - - - - Qt::FocusPolicy::ClickFocus - - - Default Author is KCC - - - Default Author - - - false - - - - - - - <html><head/><body><p><span style=" font-weight:600; text-decoration: underline;">Unchecked - Disabled</span></p><p>Disabled</p><p><span style=" font-weight:600; text-decoration: underline;">Indeterminate - Margins<br/></span>Margins</p><p><span style=" font-weight:600; text-decoration: underline;">Checked - Margins + page numbers<br/></span>Margins +page numbers</p></body></html> - - - Cropping mode - - - true - - - - - - - Use a more compatible 8 bit PNG instead of 4 bit. - - - PNG Legacy Mode - - - - - - - Force full color images to be saved in lossless PNG format, dramatically increases the filesize. - - - Force PNG RGB - - - - - - - Attempt to crop main cover from wide image. - - - Smart Cover Crop + WebP (experimental) diff --git a/kindlecomicconverter/KCC_gui.py b/kindlecomicconverter/KCC_gui.py index 54aff4e..4dc4d01 100644 --- a/kindlecomicconverter/KCC_gui.py +++ b/kindlecomicconverter/KCC_gui.py @@ -359,6 +359,8 @@ class WorkerThread(QThread): options.forcepng = True elif GUI.mozJpegBox.checkState() == Qt.CheckState.Checked: options.mozjpeg = True + if GUI.webpBox.isChecked(): + options.webp = True if GUI.pngLegacyBox.isChecked(): options.pnglegacy = True if GUI.noQuantizeBox.isChecked(): @@ -1068,6 +1070,7 @@ class KCCGUI(KCC_ui.Ui_mainWindow): 'metadataTitleBox': GUI.metadataTitleBox.checkState(), 'mozJpegBox': GUI.mozJpegBox.checkState(), 'forcePngRgbBox': GUI.forcePngRgbBox.checkState(), + 'webpBox': GUI.webpBox.checkState(), 'pngLegacyBox': GUI.pngLegacyBox.checkState(), 'noQuantizeBox': GUI.noQuantizeBox.checkState(), 'jpegQualityBox': GUI.jpegQualityBox.checkState(), diff --git a/kindlecomicconverter/KCC_ui.py b/kindlecomicconverter/KCC_ui.py index 473a2e9..7d5b38c 100644 --- a/kindlecomicconverter/KCC_ui.py +++ b/kindlecomicconverter/KCC_ui.py @@ -287,20 +287,157 @@ class Ui_mainWindow(object): self.gridLayout_2 = QGridLayout(self.optionWidget) self.gridLayout_2.setObjectName(u"gridLayout_2") self.gridLayout_2.setContentsMargins(0, 0, 0, 0) - self.rotateFirstBox = QCheckBox(self.optionWidget) - self.rotateFirstBox.setObjectName(u"rotateFirstBox") + self.noRotateBox = QCheckBox(self.optionWidget) + self.noRotateBox.setObjectName(u"noRotateBox") - self.gridLayout_2.addWidget(self.rotateFirstBox, 8, 1, 1, 1) + self.gridLayout_2.addWidget(self.noRotateBox, 6, 1, 1, 1) + + self.maximizeStrips = QCheckBox(self.optionWidget) + self.maximizeStrips.setObjectName(u"maximizeStrips") + + self.gridLayout_2.addWidget(self.maximizeStrips, 4, 1, 1, 1) + + self.rotateBox = QCheckBox(self.optionWidget) + self.rotateBox.setObjectName(u"rotateBox") + self.rotateBox.setTristate(True) + + self.gridLayout_2.addWidget(self.rotateBox, 1, 1, 1, 1) + + self.pngLegacyBox = QCheckBox(self.optionWidget) + self.pngLegacyBox.setObjectName(u"pngLegacyBox") + self.pngLegacyBox.setEnabled(False) + + self.gridLayout_2.addWidget(self.pngLegacyBox, 11, 0, 1, 1) + + self.interPanelCropBox = QCheckBox(self.optionWidget) + self.interPanelCropBox.setObjectName(u"interPanelCropBox") + self.interPanelCropBox.setTristate(True) + + self.gridLayout_2.addWidget(self.interPanelCropBox, 6, 2, 1, 1) + + self.titleEdit = QLineEdit(self.optionWidget) + self.titleEdit.setObjectName(u"titleEdit") + sizePolicy4.setHeightForWidth(self.titleEdit.sizePolicy().hasHeightForWidth()) + self.titleEdit.setSizePolicy(sizePolicy4) + self.titleEdit.setFocusPolicy(Qt.FocusPolicy.ClickFocus) + self.titleEdit.setClearButtonEnabled(False) + + self.gridLayout_2.addWidget(self.titleEdit, 0, 0, 1, 1) + + self.authorEdit = QLineEdit(self.optionWidget) + self.authorEdit.setObjectName(u"authorEdit") + sizePolicy4.setHeightForWidth(self.authorEdit.sizePolicy().hasHeightForWidth()) + self.authorEdit.setSizePolicy(sizePolicy4) + self.authorEdit.setFocusPolicy(Qt.FocusPolicy.ClickFocus) + self.authorEdit.setClearButtonEnabled(False) + + self.gridLayout_2.addWidget(self.authorEdit, 0, 1, 1, 1) + + self.webtoonBox = QCheckBox(self.optionWidget) + self.webtoonBox.setObjectName(u"webtoonBox") + + self.gridLayout_2.addWidget(self.webtoonBox, 2, 0, 1, 1) + + self.fileFusionBox = QCheckBox(self.optionWidget) + self.fileFusionBox.setObjectName(u"fileFusionBox") + + self.gridLayout_2.addWidget(self.fileFusionBox, 6, 0, 1, 1) + + self.deleteBox = QCheckBox(self.optionWidget) + self.deleteBox.setObjectName(u"deleteBox") + + self.gridLayout_2.addWidget(self.deleteBox, 5, 1, 1, 1) + + self.gammaBox = QCheckBox(self.optionWidget) + self.gammaBox.setObjectName(u"gammaBox") + + self.gridLayout_2.addWidget(self.gammaBox, 2, 2, 1, 1) + + self.noQuantizeBox = QCheckBox(self.optionWidget) + self.noQuantizeBox.setObjectName(u"noQuantizeBox") + self.noQuantizeBox.setEnabled(False) + + self.gridLayout_2.addWidget(self.noQuantizeBox, 10, 2, 1, 1) + + self.eraseRainbowBox = QCheckBox(self.optionWidget) + self.eraseRainbowBox.setObjectName(u"eraseRainbowBox") + + self.gridLayout_2.addWidget(self.eraseRainbowBox, 7, 2, 1, 1) + + self.coverFillBox = QCheckBox(self.optionWidget) + self.coverFillBox.setObjectName(u"coverFillBox") + + self.gridLayout_2.addWidget(self.coverFillBox, 9, 1, 1, 1) + + self.rotateRightBox = QCheckBox(self.optionWidget) + self.rotateRightBox.setObjectName(u"rotateRightBox") + + self.gridLayout_2.addWidget(self.rotateRightBox, 10, 1, 1, 1) + + self.mangaBox = QCheckBox(self.optionWidget) + self.mangaBox.setObjectName(u"mangaBox") + + self.gridLayout_2.addWidget(self.mangaBox, 1, 0, 1, 1) + + self.spreadShiftBox = QCheckBox(self.optionWidget) + self.spreadShiftBox.setObjectName(u"spreadShiftBox") + + self.gridLayout_2.addWidget(self.spreadShiftBox, 5, 0, 1, 1) + + self.croppingBox = QCheckBox(self.optionWidget) + self.croppingBox.setObjectName(u"croppingBox") + self.croppingBox.setTristate(True) + + self.gridLayout_2.addWidget(self.croppingBox, 4, 2, 1, 1) + + self.jpegQualityBox = QCheckBox(self.optionWidget) + self.jpegQualityBox.setObjectName(u"jpegQualityBox") + + self.gridLayout_2.addWidget(self.jpegQualityBox, 8, 0, 1, 1) self.outputSplit = QCheckBox(self.optionWidget) self.outputSplit.setObjectName(u"outputSplit") self.gridLayout_2.addWidget(self.outputSplit, 3, 1, 1, 1) - self.pdfWidthBox = QCheckBox(self.optionWidget) - self.pdfWidthBox.setObjectName(u"pdfWidthBox") + self.metadataTitleBox = QCheckBox(self.optionWidget) + self.metadataTitleBox.setObjectName(u"metadataTitleBox") + self.metadataTitleBox.setTristate(True) - self.gridLayout_2.addWidget(self.pdfWidthBox, 10, 0, 1, 1) + self.gridLayout_2.addWidget(self.metadataTitleBox, 7, 0, 1, 1) + + self.smartCoverCropBox = QCheckBox(self.optionWidget) + self.smartCoverCropBox.setObjectName(u"smartCoverCropBox") + + self.gridLayout_2.addWidget(self.smartCoverCropBox, 11, 1, 1, 1) + + self.rotateFirstBox = QCheckBox(self.optionWidget) + self.rotateFirstBox.setObjectName(u"rotateFirstBox") + + self.gridLayout_2.addWidget(self.rotateFirstBox, 8, 1, 1, 1) + + self.mozJpegBox = QCheckBox(self.optionWidget) + self.mozJpegBox.setObjectName(u"mozJpegBox") + self.mozJpegBox.setTristate(True) + + self.gridLayout_2.addWidget(self.mozJpegBox, 4, 0, 1, 1) + + self.autoLevelBox = QCheckBox(self.optionWidget) + self.autoLevelBox.setObjectName(u"autoLevelBox") + + self.gridLayout_2.addWidget(self.autoLevelBox, 8, 2, 1, 1) + + self.forcePngRgbBox = QCheckBox(self.optionWidget) + self.forcePngRgbBox.setObjectName(u"forcePngRgbBox") + self.forcePngRgbBox.setEnabled(False) + + self.gridLayout_2.addWidget(self.forcePngRgbBox, 11, 2, 1, 1) + + self.upscaleBox = QCheckBox(self.optionWidget) + self.upscaleBox.setObjectName(u"upscaleBox") + self.upscaleBox.setTristate(True) + + self.gridLayout_2.addWidget(self.upscaleBox, 2, 1, 1, 1) self.borderBox = QCheckBox(self.optionWidget) self.borderBox.setObjectName(u"borderBox") @@ -308,51 +445,27 @@ class Ui_mainWindow(object): self.gridLayout_2.addWidget(self.borderBox, 3, 0, 1, 1) - self.deleteBox = QCheckBox(self.optionWidget) - self.deleteBox.setObjectName(u"deleteBox") + self.qualityBox = QCheckBox(self.optionWidget) + self.qualityBox.setObjectName(u"qualityBox") + self.qualityBox.setTristate(True) - self.gridLayout_2.addWidget(self.deleteBox, 5, 1, 1, 1) + self.gridLayout_2.addWidget(self.qualityBox, 1, 2, 1, 1) - self.spreadShiftBox = QCheckBox(self.optionWidget) - self.spreadShiftBox.setObjectName(u"spreadShiftBox") + self.pdfExtractBox = QCheckBox(self.optionWidget) + self.pdfExtractBox.setObjectName(u"pdfExtractBox") - self.gridLayout_2.addWidget(self.spreadShiftBox, 5, 0, 1, 1) - - self.maximizeStrips = QCheckBox(self.optionWidget) - self.maximizeStrips.setObjectName(u"maximizeStrips") - - self.gridLayout_2.addWidget(self.maximizeStrips, 4, 1, 1, 1) - - self.webtoonBox = QCheckBox(self.optionWidget) - self.webtoonBox.setObjectName(u"webtoonBox") - - self.gridLayout_2.addWidget(self.webtoonBox, 2, 0, 1, 1) - - self.noRotateBox = QCheckBox(self.optionWidget) - self.noRotateBox.setObjectName(u"noRotateBox") - - self.gridLayout_2.addWidget(self.noRotateBox, 6, 1, 1, 1) - - self.mangaBox = QCheckBox(self.optionWidget) - self.mangaBox.setObjectName(u"mangaBox") - - self.gridLayout_2.addWidget(self.mangaBox, 1, 0, 1, 1) - - self.noQuantizeBox = QCheckBox(self.optionWidget) - self.noQuantizeBox.setObjectName(u"noQuantizeBox") - - self.gridLayout_2.addWidget(self.noQuantizeBox, 10, 2, 1, 1) - - self.jpegQualityBox = QCheckBox(self.optionWidget) - self.jpegQualityBox.setObjectName(u"jpegQualityBox") - - self.gridLayout_2.addWidget(self.jpegQualityBox, 8, 0, 1, 1) + self.gridLayout_2.addWidget(self.pdfExtractBox, 9, 0, 1, 1) self.colorBox = QCheckBox(self.optionWidget) self.colorBox.setObjectName(u"colorBox") self.gridLayout_2.addWidget(self.colorBox, 3, 2, 1, 1) + self.pdfWidthBox = QCheckBox(self.optionWidget) + self.pdfWidthBox.setObjectName(u"pdfWidthBox") + + self.gridLayout_2.addWidget(self.pdfWidthBox, 10, 0, 1, 1) + self.disableProcessingBox = QCheckBox(self.optionWidget) self.disableProcessingBox.setObjectName(u"disableProcessingBox") @@ -381,11 +494,10 @@ class Ui_mainWindow(object): self.gridLayout_2.addWidget(self.outputFolderWidget, 0, 2, 1, 1) - self.metadataTitleBox = QCheckBox(self.optionWidget) - self.metadataTitleBox.setObjectName(u"metadataTitleBox") - self.metadataTitleBox.setTristate(True) + self.chunkSizeCheckBox = QCheckBox(self.optionWidget) + self.chunkSizeCheckBox.setObjectName(u"chunkSizeCheckBox") - self.gridLayout_2.addWidget(self.metadataTitleBox, 7, 0, 1, 1) + self.gridLayout_2.addWidget(self.chunkSizeCheckBox, 7, 1, 1, 1) self.autocontrastBox = QCheckBox(self.optionWidget) self.autocontrastBox.setObjectName(u"autocontrastBox") @@ -393,114 +505,10 @@ class Ui_mainWindow(object): self.gridLayout_2.addWidget(self.autocontrastBox, 9, 2, 1, 1) - self.pdfExtractBox = QCheckBox(self.optionWidget) - self.pdfExtractBox.setObjectName(u"pdfExtractBox") + self.webpBox = QCheckBox(self.optionWidget) + self.webpBox.setObjectName(u"webpBox") - self.gridLayout_2.addWidget(self.pdfExtractBox, 9, 0, 1, 1) - - self.upscaleBox = QCheckBox(self.optionWidget) - self.upscaleBox.setObjectName(u"upscaleBox") - self.upscaleBox.setTristate(True) - - self.gridLayout_2.addWidget(self.upscaleBox, 2, 1, 1, 1) - - self.chunkSizeCheckBox = QCheckBox(self.optionWidget) - self.chunkSizeCheckBox.setObjectName(u"chunkSizeCheckBox") - - self.gridLayout_2.addWidget(self.chunkSizeCheckBox, 7, 1, 1, 1) - - self.qualityBox = QCheckBox(self.optionWidget) - self.qualityBox.setObjectName(u"qualityBox") - self.qualityBox.setTristate(True) - - self.gridLayout_2.addWidget(self.qualityBox, 1, 2, 1, 1) - - self.mozJpegBox = QCheckBox(self.optionWidget) - self.mozJpegBox.setObjectName(u"mozJpegBox") - self.mozJpegBox.setTristate(True) - - self.gridLayout_2.addWidget(self.mozJpegBox, 4, 0, 1, 1) - - self.titleEdit = QLineEdit(self.optionWidget) - self.titleEdit.setObjectName(u"titleEdit") - sizePolicy4.setHeightForWidth(self.titleEdit.sizePolicy().hasHeightForWidth()) - self.titleEdit.setSizePolicy(sizePolicy4) - self.titleEdit.setFocusPolicy(Qt.FocusPolicy.ClickFocus) - self.titleEdit.setClearButtonEnabled(False) - - self.gridLayout_2.addWidget(self.titleEdit, 0, 0, 1, 1) - - self.eraseRainbowBox = QCheckBox(self.optionWidget) - self.eraseRainbowBox.setObjectName(u"eraseRainbowBox") - - self.gridLayout_2.addWidget(self.eraseRainbowBox, 7, 2, 1, 1) - - self.fileFusionBox = QCheckBox(self.optionWidget) - self.fileFusionBox.setObjectName(u"fileFusionBox") - - self.gridLayout_2.addWidget(self.fileFusionBox, 6, 0, 1, 1) - - self.coverFillBox = QCheckBox(self.optionWidget) - self.coverFillBox.setObjectName(u"coverFillBox") - - self.gridLayout_2.addWidget(self.coverFillBox, 9, 1, 1, 1) - - self.rotateRightBox = QCheckBox(self.optionWidget) - self.rotateRightBox.setObjectName(u"rotateRightBox") - - self.gridLayout_2.addWidget(self.rotateRightBox, 10, 1, 1, 1) - - self.gammaBox = QCheckBox(self.optionWidget) - self.gammaBox.setObjectName(u"gammaBox") - - self.gridLayout_2.addWidget(self.gammaBox, 2, 2, 1, 1) - - self.autoLevelBox = QCheckBox(self.optionWidget) - self.autoLevelBox.setObjectName(u"autoLevelBox") - - self.gridLayout_2.addWidget(self.autoLevelBox, 8, 2, 1, 1) - - self.interPanelCropBox = QCheckBox(self.optionWidget) - self.interPanelCropBox.setObjectName(u"interPanelCropBox") - self.interPanelCropBox.setTristate(True) - - self.gridLayout_2.addWidget(self.interPanelCropBox, 6, 2, 1, 1) - - self.rotateBox = QCheckBox(self.optionWidget) - self.rotateBox.setObjectName(u"rotateBox") - self.rotateBox.setTristate(True) - - self.gridLayout_2.addWidget(self.rotateBox, 1, 1, 1, 1) - - self.authorEdit = QLineEdit(self.optionWidget) - self.authorEdit.setObjectName(u"authorEdit") - sizePolicy4.setHeightForWidth(self.authorEdit.sizePolicy().hasHeightForWidth()) - self.authorEdit.setSizePolicy(sizePolicy4) - self.authorEdit.setFocusPolicy(Qt.FocusPolicy.ClickFocus) - self.authorEdit.setClearButtonEnabled(False) - - self.gridLayout_2.addWidget(self.authorEdit, 0, 1, 1, 1) - - self.croppingBox = QCheckBox(self.optionWidget) - self.croppingBox.setObjectName(u"croppingBox") - self.croppingBox.setTristate(True) - - self.gridLayout_2.addWidget(self.croppingBox, 4, 2, 1, 1) - - self.pngLegacyBox = QCheckBox(self.optionWidget) - self.pngLegacyBox.setObjectName(u"pngLegacyBox") - - self.gridLayout_2.addWidget(self.pngLegacyBox, 11, 0, 1, 1) - - self.forcePngRgbBox = QCheckBox(self.optionWidget) - self.forcePngRgbBox.setObjectName(u"forcePngRgbBox") - - self.gridLayout_2.addWidget(self.forcePngRgbBox, 11, 2, 1, 1) - - self.smartCoverCropBox = QCheckBox(self.optionWidget) - self.smartCoverCropBox.setObjectName(u"smartCoverCropBox") - - self.gridLayout_2.addWidget(self.smartCoverCropBox, 11, 1, 1, 1) + self.gridLayout_2.addWidget(self.webpBox, 12, 0, 1, 1) self.gridLayout.addWidget(self.optionWidget, 5, 0, 1, 2) @@ -655,47 +663,49 @@ class Ui_mainWindow(object): self.chunkSizeLabel.setText(QCoreApplication.translate("mainWindow", u"Chunk size MB:", None)) self.chunkSizeWarnLabel.setText(QCoreApplication.translate("mainWindow", u"Greater than default may cause performance issues on older ereaders.", None)) #if QT_CONFIG(tooltip) - self.rotateFirstBox.setToolTip(QCoreApplication.translate("mainWindow", u"

When the spread splitter option is partially checked,

Unchecked - Rotate Last
Put the rotated 2 page spread after the split spreads.

Checked - Rotate First
Put the rotated 2 page spread before the split spreads.

", None)) + self.noRotateBox.setToolTip(QCoreApplication.translate("mainWindow", u"Do not rotate double page spreads in spread splitter option.", None)) #endif // QT_CONFIG(tooltip) - self.rotateFirstBox.setText(QCoreApplication.translate("mainWindow", u"Rotate First", None)) -#if QT_CONFIG(tooltip) - self.outputSplit.setToolTip(QCoreApplication.translate("mainWindow", u"

Unchecked - Automatic mode
The output will be split automatically.

Checked - Volume mode
Every subdirectory will be considered as a separate volume.

", None)) -#endif // QT_CONFIG(tooltip) - self.outputSplit.setText(QCoreApplication.translate("mainWindow", u"Output split", None)) -#if QT_CONFIG(tooltip) - self.pdfWidthBox.setToolTip(QCoreApplication.translate("mainWindow", u"Render vector PDFs to device width instead of height.\n" -"\n" -"Useful if you plan to crop a little off the top and bottom to fill screen.", None)) -#endif // QT_CONFIG(tooltip) - self.pdfWidthBox.setText(QCoreApplication.translate("mainWindow", u"PDF Width Render", None)) -#if QT_CONFIG(tooltip) - self.borderBox.setToolTip(QCoreApplication.translate("mainWindow", u"

Unchecked - Autodetection
The color of margins fill will be detected automatically.

Indeterminate - White
Margins will be untouched.

Checked - Black
Margins will be filled with black color.

", None)) -#endif // QT_CONFIG(tooltip) - self.borderBox.setText(QCoreApplication.translate("mainWindow", u"W/B margins", None)) -#if QT_CONFIG(tooltip) - self.deleteBox.setToolTip(QCoreApplication.translate("mainWindow", u"Delete input file(s) or directory. It's not recoverable!", None)) -#endif // QT_CONFIG(tooltip) - self.deleteBox.setText(QCoreApplication.translate("mainWindow", u"Delete input", None)) -#if QT_CONFIG(tooltip) - self.spreadShiftBox.setToolTip(QCoreApplication.translate("mainWindow", u"Shift first page to opposite side in landscape for two page spread alignment", None)) -#endif // QT_CONFIG(tooltip) - self.spreadShiftBox.setText(QCoreApplication.translate("mainWindow", u"Spread shift", None)) + self.noRotateBox.setText(QCoreApplication.translate("mainWindow", u"No rotate", None)) #if QT_CONFIG(tooltip) self.maximizeStrips.setToolTip(QCoreApplication.translate("mainWindow", u"

Unchecked - 1x4
Keep format 1x4 panels strips.

Checked - 2x2
Turn 1x4 strips to 2x2 to maximize screen usage.

", None)) #endif // QT_CONFIG(tooltip) self.maximizeStrips.setText(QCoreApplication.translate("mainWindow", u"1x4 to 2x2 strips", None)) +#if QT_CONFIG(tooltip) + self.rotateBox.setToolTip(QCoreApplication.translate("mainWindow", u"

Unchecked - Split
Double page spreads will be cut into two separate pages.

Indeterminate - Split and rotate
Double page spreads will be displayed twice. First split and then rotated.

Checked - Rotate
Double page spreads will be rotated.

", None)) +#endif // QT_CONFIG(tooltip) + self.rotateBox.setText(QCoreApplication.translate("mainWindow", u"Spread splitter", None)) +#if QT_CONFIG(tooltip) + self.pngLegacyBox.setToolTip(QCoreApplication.translate("mainWindow", u"Use a more compatible 8 bit PNG instead of 4 bit.", None)) +#endif // QT_CONFIG(tooltip) + self.pngLegacyBox.setText(QCoreApplication.translate("mainWindow", u"PNG Legacy Mode", None)) +#if QT_CONFIG(tooltip) + self.interPanelCropBox.setToolTip(QCoreApplication.translate("mainWindow", u"

Unchecked - Disabled
Disabled

Indeterminate - Horizontal
Crop empty horizontal lines.

Checked - Both
Crop empty horizontal and vertical lines.

", None)) +#endif // QT_CONFIG(tooltip) + self.interPanelCropBox.setText(QCoreApplication.translate("mainWindow", u"Inter-panel crop", None)) +#if QT_CONFIG(tooltip) + self.titleEdit.setToolTip(QCoreApplication.translate("mainWindow", u"

Default Title

", None)) +#endif // QT_CONFIG(tooltip) + self.titleEdit.setPlaceholderText(QCoreApplication.translate("mainWindow", u"Default Title", None)) +#if QT_CONFIG(tooltip) + self.authorEdit.setToolTip(QCoreApplication.translate("mainWindow", u"Default Author is KCC", None)) +#endif // QT_CONFIG(tooltip) + self.authorEdit.setPlaceholderText(QCoreApplication.translate("mainWindow", u"Default Author", None)) #if QT_CONFIG(tooltip) self.webtoonBox.setToolTip(QCoreApplication.translate("mainWindow", u"

Enable special parsing mode for Korean Webtoons.

", None)) #endif // QT_CONFIG(tooltip) self.webtoonBox.setText(QCoreApplication.translate("mainWindow", u"Webtoon mode", None)) #if QT_CONFIG(tooltip) - self.noRotateBox.setToolTip(QCoreApplication.translate("mainWindow", u"Do not rotate double page spreads in spread splitter option.", None)) + self.fileFusionBox.setToolTip(QCoreApplication.translate("mainWindow", u"

Combines all selected files into a single file. (Helpful for combining chapters into volumes.)

", None)) #endif // QT_CONFIG(tooltip) - self.noRotateBox.setText(QCoreApplication.translate("mainWindow", u"No rotate", None)) + self.fileFusionBox.setText(QCoreApplication.translate("mainWindow", u"File Fusion", None)) #if QT_CONFIG(tooltip) - self.mangaBox.setToolTip(QCoreApplication.translate("mainWindow", u"

Enable right-to-left reading.

", None)) + self.deleteBox.setToolTip(QCoreApplication.translate("mainWindow", u"Delete input file(s) or directory. It's not recoverable!", None)) #endif // QT_CONFIG(tooltip) - self.mangaBox.setText(QCoreApplication.translate("mainWindow", u"Right-to-left (manga)", None)) + self.deleteBox.setText(QCoreApplication.translate("mainWindow", u"Delete input", None)) +#if QT_CONFIG(tooltip) + self.gammaBox.setToolTip(QCoreApplication.translate("mainWindow", u"

Set a custom gamma correction.

1.0 is default (disabled).
< 1.0 makes the image brighter.
> 1.0 makes the image darker.

1.8 was the default in KCC 9.1.0 and earlier.

Use if you want to make midtones darker.

", None)) +#endif // QT_CONFIG(tooltip) + self.gammaBox.setText(QCoreApplication.translate("mainWindow", u"Custom gamma", None)) #if QT_CONFIG(tooltip) self.noQuantizeBox.setToolTip(QCoreApplication.translate("mainWindow", u"Don't quantize PNG images to 16 colors (4 bit)\n" "\n" @@ -704,6 +714,31 @@ class Ui_mainWindow(object): "Eink only has 16 shades of gray so you probably don't want this.", None)) #endif // QT_CONFIG(tooltip) self.noQuantizeBox.setText(QCoreApplication.translate("mainWindow", u"No Quantize", None)) +#if QT_CONFIG(tooltip) + self.eraseRainbowBox.setToolTip(QCoreApplication.translate("mainWindow", u"Erase rainbow effect on color eink screen by attenuating interfering frequencies", None)) +#endif // QT_CONFIG(tooltip) + self.eraseRainbowBox.setText(QCoreApplication.translate("mainWindow", u"Rainbow eraser", None)) +#if QT_CONFIG(tooltip) + self.coverFillBox.setToolTip(QCoreApplication.translate("mainWindow", u"Resize cover to exact device resolution by center-cropping to aspect ratio first.\n" +"May crop top/bottom or left/right depending on source aspect ratio. Not implemented for Kindle Scribe.", None)) +#endif // QT_CONFIG(tooltip) + self.coverFillBox.setText(QCoreApplication.translate("mainWindow", u"Cover Fill", None)) +#if QT_CONFIG(tooltip) + self.rotateRightBox.setToolTip(QCoreApplication.translate("mainWindow", u"Rotate 2 page spreads in opposite direction than normal.", None)) +#endif // QT_CONFIG(tooltip) + self.rotateRightBox.setText(QCoreApplication.translate("mainWindow", u"Rotate Right", None)) +#if QT_CONFIG(tooltip) + self.mangaBox.setToolTip(QCoreApplication.translate("mainWindow", u"

Enable right-to-left reading.

", None)) +#endif // QT_CONFIG(tooltip) + self.mangaBox.setText(QCoreApplication.translate("mainWindow", u"Right-to-left (manga)", None)) +#if QT_CONFIG(tooltip) + self.spreadShiftBox.setToolTip(QCoreApplication.translate("mainWindow", u"Shift first page to opposite side in landscape for two page spread alignment", None)) +#endif // QT_CONFIG(tooltip) + self.spreadShiftBox.setText(QCoreApplication.translate("mainWindow", u"Spread shift", None)) +#if QT_CONFIG(tooltip) + self.croppingBox.setToolTip(QCoreApplication.translate("mainWindow", u"

Unchecked - Disabled

Disabled

Indeterminate - Margins
Margins

Checked - Margins + page numbers
Margins +page numbers

", None)) +#endif // QT_CONFIG(tooltip) + self.croppingBox.setText(QCoreApplication.translate("mainWindow", u"Cropping mode", None)) #if QT_CONFIG(tooltip) self.jpegQualityBox.setToolTip(QCoreApplication.translate("mainWindow", u"The JPEG quality, on a scale from 0 (worst) to 95 (best). \n" "\n" @@ -712,10 +747,62 @@ class Ui_mainWindow(object): "Higher values are larger and higher quality, and may resolve blank page issues.", None)) #endif // QT_CONFIG(tooltip) self.jpegQualityBox.setText(QCoreApplication.translate("mainWindow", u"Custom JPEG Quality", None)) +#if QT_CONFIG(tooltip) + self.outputSplit.setToolTip(QCoreApplication.translate("mainWindow", u"

Unchecked - Automatic mode
The output will be split automatically.

Checked - Volume mode
Every subdirectory will be considered as a separate volume.

", None)) +#endif // QT_CONFIG(tooltip) + self.outputSplit.setText(QCoreApplication.translate("mainWindow", u"Output split", None)) +#if QT_CONFIG(tooltip) + self.metadataTitleBox.setToolTip(QCoreApplication.translate("mainWindow", u"

Unchecked - Don't use metadata Title
Write default title.

Indeterminate - Add metadata Title to the default schema
Write default title with Title from ComicInfo.xml or other embedded metadata.

Checked - Use metadata Title only
Write Title from ComicInfo.xml or other embedded metadata.

", None)) +#endif // QT_CONFIG(tooltip) + self.metadataTitleBox.setText(QCoreApplication.translate("mainWindow", u"Metadata Title", None)) +#if QT_CONFIG(tooltip) + self.smartCoverCropBox.setToolTip(QCoreApplication.translate("mainWindow", u"Attempt to crop main cover from wide image.", None)) +#endif // QT_CONFIG(tooltip) + self.smartCoverCropBox.setText(QCoreApplication.translate("mainWindow", u"Smart Cover Crop", None)) +#if QT_CONFIG(tooltip) + self.rotateFirstBox.setToolTip(QCoreApplication.translate("mainWindow", u"

When the spread splitter option is partially checked,

Unchecked - Rotate Last
Put the rotated 2 page spread after the split spreads.

Checked - Rotate First
Put the rotated 2 page spread before the split spreads.

", None)) +#endif // QT_CONFIG(tooltip) + self.rotateFirstBox.setText(QCoreApplication.translate("mainWindow", u"Rotate First", None)) +#if QT_CONFIG(tooltip) + self.mozJpegBox.setToolTip(QCoreApplication.translate("mainWindow", u"

Unchecked - JPEG
Use JPEG files

Indeterminate - force PNG
Create PNG files instead JPEG for black and white images

Checked - mozJpeg
10-20% smaller JPEG file, with the same image quality, but processing time multiplied by 2

", None)) +#endif // QT_CONFIG(tooltip) + self.mozJpegBox.setText(QCoreApplication.translate("mainWindow", u"JPEG/PNG/mozJpeg", None)) +#if QT_CONFIG(tooltip) + self.autoLevelBox.setToolTip(QCoreApplication.translate("mainWindow", u"

By default, KCC maps the darkest pixel value to pure black (the black point.)

Extreme black point sets the black point to be the most common dark pixel value.

Useful when text is black but artwork is gray.

", None)) +#endif // QT_CONFIG(tooltip) + self.autoLevelBox.setText(QCoreApplication.translate("mainWindow", u"Extreme Black Point", None)) +#if QT_CONFIG(tooltip) + self.forcePngRgbBox.setToolTip(QCoreApplication.translate("mainWindow", u"Force full color images to be saved in lossless PNG format, dramatically increases the filesize.", None)) +#endif // QT_CONFIG(tooltip) + self.forcePngRgbBox.setText(QCoreApplication.translate("mainWindow", u"Force PNG RGB", None)) +#if QT_CONFIG(tooltip) + self.upscaleBox.setToolTip(QCoreApplication.translate("mainWindow", u"

Unchecked - Nothing
Images smaller than device resolution will not be resized.

Indeterminate - Stretching
Images smaller than device resolution will be resized. Aspect ratio will be not preserved.

Checked - Upscaling
Images smaller than device resolution will be resized. Aspect ratio will be preserved.

", None)) +#endif // QT_CONFIG(tooltip) + self.upscaleBox.setText(QCoreApplication.translate("mainWindow", u"Stretch/Upscale", None)) +#if QT_CONFIG(tooltip) + self.borderBox.setToolTip(QCoreApplication.translate("mainWindow", u"

Unchecked - Autodetection
The color of margins fill will be detected automatically.

Indeterminate - White
Margins will be untouched.

Checked - Black
Margins will be filled with black color.

", None)) +#endif // QT_CONFIG(tooltip) + self.borderBox.setText(QCoreApplication.translate("mainWindow", u"W/B margins", None)) +#if QT_CONFIG(tooltip) + self.qualityBox.setToolTip(QCoreApplication.translate("mainWindow", u"

Unchecked - 4 panels
Zoom each corner separately.

Indeterminate - 2 panels
Zoom only the top and bottom of the page.

Checked - 4 high-quality panels
Zoom each corner separately. Try to increase the quality of magnification. Check wiki for more details.

", None)) +#endif // QT_CONFIG(tooltip) + self.qualityBox.setText(QCoreApplication.translate("mainWindow", u"Panel View 4/2/HQ", None)) +#if QT_CONFIG(tooltip) + self.pdfExtractBox.setToolTip(QCoreApplication.translate("mainWindow", u"Use the PDF image extraction method from KCC 8 and earlier.\n" +"\n" +"Useful for really weird PDFs.", None)) +#endif // QT_CONFIG(tooltip) + self.pdfExtractBox.setText(QCoreApplication.translate("mainWindow", u"PDF Legacy Extract", None)) #if QT_CONFIG(tooltip) self.colorBox.setToolTip(QCoreApplication.translate("mainWindow", u"

Disable conversion to grayscale.

", None)) #endif // QT_CONFIG(tooltip) self.colorBox.setText(QCoreApplication.translate("mainWindow", u"Color mode", None)) +#if QT_CONFIG(tooltip) + self.pdfWidthBox.setToolTip(QCoreApplication.translate("mainWindow", u"Render vector PDFs to device width instead of height.\n" +"\n" +"Useful if you plan to crop a little off the top and bottom to fill screen.", None)) +#endif // QT_CONFIG(tooltip) + self.pdfWidthBox.setText(QCoreApplication.translate("mainWindow", u"PDF Width Render", None)) #if QT_CONFIG(tooltip) self.disableProcessingBox.setToolTip(QCoreApplication.translate("mainWindow", u"

Do not process any image, ignore profile and processing options.

", None)) #endif // QT_CONFIG(tooltip) @@ -729,92 +816,19 @@ class Ui_mainWindow(object): #endif // QT_CONFIG(tooltip) self.defaultOutputFolderButton.setText("") #if QT_CONFIG(tooltip) - self.metadataTitleBox.setToolTip(QCoreApplication.translate("mainWindow", u"

Unchecked - Don't use metadata Title
Write default title.

Indeterminate - Add metadata Title to the default schema
Write default title with Title from ComicInfo.xml or other embedded metadata.

Checked - Use metadata Title only
Write Title from ComicInfo.xml or other embedded metadata.

", None)) + self.chunkSizeCheckBox.setToolTip(QCoreApplication.translate("mainWindow", u"

Unchecked
Maximal output file size is 100 MB for Webtoon, 400 MB for others before split occurs.

Checked
Output file size specified in "Chunk size MB" before split occurs.

", None)) #endif // QT_CONFIG(tooltip) - self.metadataTitleBox.setText(QCoreApplication.translate("mainWindow", u"Metadata Title", None)) + self.chunkSizeCheckBox.setText(QCoreApplication.translate("mainWindow", u"Chunk size", None)) #if QT_CONFIG(tooltip) self.autocontrastBox.setToolTip(QCoreApplication.translate("mainWindow", u"

Unchecked - BW only
Only autocontrast bw pages. Ignored for pages where near blacks or whites don't exist.

Indeterminate - Disabled
Disable autocontrast

Checked - BW and Color
BW and color images will be autocontrasted. Ignored for pages where near blacks or whites don't exist.

", None)) #endif // QT_CONFIG(tooltip) self.autocontrastBox.setText(QCoreApplication.translate("mainWindow", u"Autocontrast", None)) #if QT_CONFIG(tooltip) - self.pdfExtractBox.setToolTip(QCoreApplication.translate("mainWindow", u"Use the PDF image extraction method from KCC 8 and earlier.\n" + self.webpBox.setToolTip(QCoreApplication.translate("mainWindow", u"Replace JPG with lossy WebP and PNG with lossless WebP. This includes the JPG Quality.\n" "\n" -"Useful for really weird PDFs.", None)) +"Ignored for Kindle EPUB/MOBI and all PDF.", None)) #endif // QT_CONFIG(tooltip) - self.pdfExtractBox.setText(QCoreApplication.translate("mainWindow", u"PDF Legacy Extract", None)) -#if QT_CONFIG(tooltip) - self.upscaleBox.setToolTip(QCoreApplication.translate("mainWindow", u"

Unchecked - Nothing
Images smaller than device resolution will not be resized.

Indeterminate - Stretching
Images smaller than device resolution will be resized. Aspect ratio will be not preserved.

Checked - Upscaling
Images smaller than device resolution will be resized. Aspect ratio will be preserved.

", None)) -#endif // QT_CONFIG(tooltip) - self.upscaleBox.setText(QCoreApplication.translate("mainWindow", u"Stretch/Upscale", None)) -#if QT_CONFIG(tooltip) - self.chunkSizeCheckBox.setToolTip(QCoreApplication.translate("mainWindow", u"

Unchecked
Maximal output file size is 100 MB for Webtoon, 400 MB for others before split occurs.

Checked
Output file size specified in "Chunk size MB" before split occurs.

", None)) -#endif // QT_CONFIG(tooltip) - self.chunkSizeCheckBox.setText(QCoreApplication.translate("mainWindow", u"Chunk size", None)) -#if QT_CONFIG(tooltip) - self.qualityBox.setToolTip(QCoreApplication.translate("mainWindow", u"

Unchecked - 4 panels
Zoom each corner separately.

Indeterminate - 2 panels
Zoom only the top and bottom of the page.

Checked - 4 high-quality panels
Zoom each corner separately. Try to increase the quality of magnification. Check wiki for more details.

", None)) -#endif // QT_CONFIG(tooltip) - self.qualityBox.setText(QCoreApplication.translate("mainWindow", u"Panel View 4/2/HQ", None)) -#if QT_CONFIG(tooltip) - self.mozJpegBox.setToolTip(QCoreApplication.translate("mainWindow", u"

Unchecked - JPEG
Use JPEG files

Indeterminate - force PNG
Create PNG files instead JPEG for black and white images

Checked - mozJpeg
10-20% smaller JPEG file, with the same image quality, but processing time multiplied by 2

", None)) -#endif // QT_CONFIG(tooltip) - self.mozJpegBox.setText(QCoreApplication.translate("mainWindow", u"JPEG/PNG/mozJpeg", None)) -#if QT_CONFIG(tooltip) - self.titleEdit.setToolTip(QCoreApplication.translate("mainWindow", u"

Default Title

", None)) -#endif // QT_CONFIG(tooltip) - self.titleEdit.setPlaceholderText(QCoreApplication.translate("mainWindow", u"Default Title", None)) -#if QT_CONFIG(tooltip) - self.eraseRainbowBox.setToolTip(QCoreApplication.translate("mainWindow", u"Erase rainbow effect on color eink screen by attenuating interfering frequencies", None)) -#endif // QT_CONFIG(tooltip) - self.eraseRainbowBox.setText(QCoreApplication.translate("mainWindow", u"Rainbow eraser", None)) -#if QT_CONFIG(tooltip) - self.fileFusionBox.setToolTip(QCoreApplication.translate("mainWindow", u"

Combines all selected files into a single file. (Helpful for combining chapters into volumes.)

", None)) -#endif // QT_CONFIG(tooltip) - self.fileFusionBox.setText(QCoreApplication.translate("mainWindow", u"File Fusion", None)) -#if QT_CONFIG(tooltip) - self.coverFillBox.setToolTip(QCoreApplication.translate("mainWindow", u"Resize cover to exact device resolution by center-cropping to aspect ratio first.\n" -"May crop top/bottom or left/right depending on source aspect ratio. Not implemented for Kindle Scribe.", None)) -#endif // QT_CONFIG(tooltip) - self.coverFillBox.setText(QCoreApplication.translate("mainWindow", u"Cover Fill", None)) -#if QT_CONFIG(tooltip) - self.rotateRightBox.setToolTip(QCoreApplication.translate("mainWindow", u"Rotate 2 page spreads in opposite direction than normal.", None)) -#endif // QT_CONFIG(tooltip) - self.rotateRightBox.setText(QCoreApplication.translate("mainWindow", u"Rotate Right", None)) -#if QT_CONFIG(tooltip) - self.gammaBox.setToolTip(QCoreApplication.translate("mainWindow", u"

Set a custom gamma correction.

1.0 is default (disabled).
< 1.0 makes the image brighter.
> 1.0 makes the image darker.

1.8 was the default in KCC 9.1.0 and earlier.

Use if you want to make midtones darker.

", None)) -#endif // QT_CONFIG(tooltip) - self.gammaBox.setText(QCoreApplication.translate("mainWindow", u"Custom gamma", None)) -#if QT_CONFIG(tooltip) - self.autoLevelBox.setToolTip(QCoreApplication.translate("mainWindow", u"

By default, KCC maps the darkest pixel value to pure black (the black point.)

Extreme black point sets the black point to be the most common dark pixel value.

Useful when text is black but artwork is gray.

", None)) -#endif // QT_CONFIG(tooltip) - self.autoLevelBox.setText(QCoreApplication.translate("mainWindow", u"Extreme Black Point", None)) -#if QT_CONFIG(tooltip) - self.interPanelCropBox.setToolTip(QCoreApplication.translate("mainWindow", u"

Unchecked - Disabled
Disabled

Indeterminate - Horizontal
Crop empty horizontal lines.

Checked - Both
Crop empty horizontal and vertical lines.

", None)) -#endif // QT_CONFIG(tooltip) - self.interPanelCropBox.setText(QCoreApplication.translate("mainWindow", u"Inter-panel crop", None)) -#if QT_CONFIG(tooltip) - self.rotateBox.setToolTip(QCoreApplication.translate("mainWindow", u"

Unchecked - Split
Double page spreads will be cut into two separate pages.

Indeterminate - Split and rotate
Double page spreads will be displayed twice. First split and then rotated.

Checked - Rotate
Double page spreads will be rotated.

", None)) -#endif // QT_CONFIG(tooltip) - self.rotateBox.setText(QCoreApplication.translate("mainWindow", u"Spread splitter", None)) -#if QT_CONFIG(tooltip) - self.authorEdit.setToolTip(QCoreApplication.translate("mainWindow", u"Default Author is KCC", None)) -#endif // QT_CONFIG(tooltip) - self.authorEdit.setPlaceholderText(QCoreApplication.translate("mainWindow", u"Default Author", None)) -#if QT_CONFIG(tooltip) - self.croppingBox.setToolTip(QCoreApplication.translate("mainWindow", u"

Unchecked - Disabled

Disabled

Indeterminate - Margins
Margins

Checked - Margins + page numbers
Margins +page numbers

", None)) -#endif // QT_CONFIG(tooltip) - self.croppingBox.setText(QCoreApplication.translate("mainWindow", u"Cropping mode", None)) -#if QT_CONFIG(tooltip) - self.pngLegacyBox.setToolTip(QCoreApplication.translate("mainWindow", u"Use a more compatible 8 bit PNG instead of 4 bit.", None)) -#endif // QT_CONFIG(tooltip) - self.pngLegacyBox.setText(QCoreApplication.translate("mainWindow", u"PNG Legacy Mode", None)) -#if QT_CONFIG(tooltip) - self.forcePngRgbBox.setToolTip(QCoreApplication.translate("mainWindow", u"Force full color images to be saved in lossless PNG format, dramatically increases the filesize.", None)) -#endif // QT_CONFIG(tooltip) - self.forcePngRgbBox.setText(QCoreApplication.translate("mainWindow", u"Force PNG RGB", None)) -#if QT_CONFIG(tooltip) - self.smartCoverCropBox.setToolTip(QCoreApplication.translate("mainWindow", u"Attempt to crop main cover from wide image.", None)) -#endif // QT_CONFIG(tooltip) - self.smartCoverCropBox.setText(QCoreApplication.translate("mainWindow", u"Smart Cover Crop", None)) + self.webpBox.setText(QCoreApplication.translate("mainWindow", u"WebP (experimental)", None)) self.gammaLabel.setText(QCoreApplication.translate("mainWindow", u"Gamma: Auto", None)) self.jpegQualityLabel.setText(QCoreApplication.translate("mainWindow", u"JPEG Quality:", None)) # retranslateUi diff --git a/kindlecomicconverter/comic2ebook.py b/kindlecomicconverter/comic2ebook.py index 00ba009..79e1e4f 100755 --- a/kindlecomicconverter/comic2ebook.py +++ b/kindlecomicconverter/comic2ebook.py @@ -353,6 +353,8 @@ def buildOPF(dstdir, title, filelist, originalpath, cover=None): mt = 'image/png' elif '.gif' == filename[1]: mt = 'image/gif' + elif '.webp' == filename[1]: + mt = 'image/webp' else: mt = 'image/jpeg' f.write(" Date: Sun, 12 Apr 2026 09:17:23 -0700 Subject: [PATCH 071/107] sign windows 7 version (#1291) --- .github/workflows/package-windows7.yml | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/.github/workflows/package-windows7.yml b/.github/workflows/package-windows7.yml index 02fc62a..5e55c27 100644 --- a/.github/workflows/package-windows7.yml +++ b/.github/workflows/package-windows7.yml @@ -50,6 +50,17 @@ jobs: with: name: windows7-build path: dist/*.exe + - id: optional_step_id + uses: signpath/github-action-submit-signing-request@v2.0 + if: ${{ github.repository == 'ciromattia/kcc' }} + with: + api-token: '${{ secrets.SIGNPATH_API_TOKEN }}' + organization-id: '1dc1bad6-4a8c-4f85-af30-5c5d3d392ea6' + project-slug: 'kcc' + signing-policy-slug: 'release-signing' + github-artifact-id: '${{ steps.upload-unsigned-artifact.outputs.artifact-id }}' + wait-for-completion: true + output-artifact-directory: 'dist/' - name: Release uses: softprops/action-gh-release@v2 if: startsWith(github.ref, 'refs/tags/') From 8aaedf274d99d494e3235ae6e47cecf5eb0a57fb Mon Sep 17 00:00:00 2001 From: Alex Xu Date: Sun, 12 Apr 2026 11:30:18 -0700 Subject: [PATCH 072/107] add youtube, discord, humble buttons (#1292) * add youtube, discord, humble buttons * restore margins of top buttons * fix button height on windows * don't bold messages --- gui/KCC.ui | 685 +++++++++++++++++--------------- kindlecomicconverter/KCC_gui.py | 19 +- kindlecomicconverter/KCC_ui.py | 498 ++++++++++++----------- 3 files changed, 642 insertions(+), 560 deletions(-) diff --git a/gui/KCC.ui b/gui/KCC.ui index f7311c6..63cd15d 100644 --- a/gui/KCC.ui +++ b/gui/KCC.ui @@ -7,7 +7,7 @@ 0 0 566 - 658 + 671 @@ -22,105 +22,6 @@ 5 - - - - - 0 - 30 - - - - - true - - - - false - - - Qt::AlignmentFlag::AlignJustify|Qt::AlignmentFlag::AlignVCenter - - - - - - - - 0 - - - 0 - - - 0 - - - 0 - - - - - - 0 - 30 - - - - <html><head/><body><p style='white-space:pre'>Shift+Click to edit directory.</p></body></html> - - - Metadata Editor - - - - :/Other/icons/editor.png:/Other/icons/editor.png - - - - - - - - 0 - 30 - - - - Support me on Ko-fi - - - - :/Brand/icons/kofi_symbol.png:/Brand/icons/kofi_symbol.png - - - - 19 - 16 - - - - - - - - - 0 - 30 - - - - Wiki - - - - :/Other/icons/wiki.png:/Other/icons/wiki.png - - - - - - @@ -191,88 +92,39 @@ - - + + + + + 0 + 30 + + + + + true + + false - - - 0 - - - 0 - - - 0 - - - 0 - - - - - - 0 - 0 - - - - <html><head/><body><p style='white-space:pre'>Resolution of the target device.</p></body></html> - - - Custom height: - - - - - - - <html><head/><body><p style='white-space:pre'>Resolution of the target device.</p></body></html> - - - 6000 - - - - - - - - 0 - 0 - - - - <html><head/><body><p style='white-space:pre'>Resolution of the target device.</p></body></html> - - - Custom width: - - - - - - - <html><head/><body><p style='white-space:pre'>Resolution of the target device.</p></body></html> - - - 8000 - - - - + + Qt::AlignmentFlag::AlignJustify|Qt::AlignmentFlag::AlignVCenter + - - + + - + 0 0 - + + false + + 0 @@ -285,146 +137,27 @@ 0 - - - - - 0 - 30 - - - - - true - - - - <html><head/><body><p style='white-space:pre'>Shift+Click to select the output directory for this list.</p></body></html> - + + - Convert - - - - :/Other/icons/convert.png:/Other/icons/convert.png + JPEG Quality: - - - - - 0 - 30 - + + + + 95 - - Clear list + + 5 - - - :/Other/icons/clear.png:/Other/icons/clear.png - - - - - - - - 0 - 28 - - - - <html><head/><body><p style='white-space:pre'>Target device.</p></body></html> - - - - - - - - 0 - 30 - - - - <html><head/><body><p style='white-space:pre'>Add CBR, CBZ, CB7 or PDF file to queue.</p></body></html> - - - Add input file(s) - - - - :/Other/icons/document_new.png:/Other/icons/document_new.png - - - - - - - - 0 - 0 - - - - <html><head/><body><p style='white-space:pre'>Add directory containing JPG, PNG or GIF files to queue.<br/><span style=" font-weight:600;">CBR, CBZ and CB7 files inside will not be processed!</span></p></body></html> - - - Add input folder(s) - - - - :/Other/icons/folder_new.png:/Other/icons/folder_new.png - - - - - - - - 0 - 28 - - - - <html><head/><body><p style='white-space:pre'>Output format.</p></body></html> + + 85 - clearButton - deviceBox - convertButton - fileButton - directoryButton - formatBox - - - - - - - 0 - 150 - - - - <html><head/><body><p>Double click on source to open it in metadata editor.</p></body></html> - - - - - - QAbstractItemView::SelectionMode::NoSelection - - - QAbstractItemView::ScrollMode::ScrollPerPixel - - - QAbstractItemView::ScrollMode::ScrollPerPixel - @@ -499,6 +232,152 @@ + + + + + 0 + 150 + + + + <html><head/><body><p>Double click on source to open it in metadata editor.</p></body></html> + + + + + + QAbstractItemView::SelectionMode::NoSelection + + + QAbstractItemView::ScrollMode::ScrollPerPixel + + + QAbstractItemView::ScrollMode::ScrollPerPixel + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + 30 + + + + <html><head/><body><p style='white-space:pre'>Shift+Click to edit directory.</p></body></html> + + + Metadata Editor + + + + :/Other/icons/editor.png:/Other/icons/editor.png + + + + + + + + 0 + 30 + + + + Support me on Ko-fi + + + + :/Brand/icons/kofi_symbol.png:/Brand/icons/kofi_symbol.png + + + + 19 + 16 + + + + + + + + + 0 + 30 + + + + Wiki + + + + :/Other/icons/wiki.png:/Other/icons/wiki.png + + + + + + + + 0 + 30 + + + + YouTube + + + + + + + + 0 + 30 + + + + Humble Bundle Referral + + + + :/Brand/icons/Humble_H-Red.png:/Brand/icons/Humble_H-Red.png + + + + + + + + 0 + 30 + + + + Discord + + + + + + @@ -1009,6 +888,144 @@ Ignored for Kindle EPUB/MOBI and all PDF. + + + + + 0 + 0 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + 30 + + + + + true + + + + <html><head/><body><p style='white-space:pre'>Shift+Click to select the output directory for this list.</p></body></html> + + + Convert + + + + :/Other/icons/convert.png:/Other/icons/convert.png + + + + + + + + 0 + 30 + + + + Clear list + + + + :/Other/icons/clear.png:/Other/icons/clear.png + + + + + + + + 0 + 28 + + + + <html><head/><body><p style='white-space:pre'>Target device.</p></body></html> + + + + + + + + 0 + 30 + + + + <html><head/><body><p style='white-space:pre'>Add CBR, CBZ, CB7 or PDF file to queue.</p></body></html> + + + Add input file(s) + + + + :/Other/icons/document_new.png:/Other/icons/document_new.png + + + + + + + + 0 + 0 + + + + <html><head/><body><p style='white-space:pre'>Add directory containing JPG, PNG or GIF files to queue.<br/><span style=" font-weight:600;">CBR, CBZ and CB7 files inside will not be processed!</span></p></body></html> + + + Add input folder(s) + + + + :/Other/icons/folder_new.png:/Other/icons/folder_new.png + + + + + + + + 0 + 28 + + + + <html><head/><body><p style='white-space:pre'>Output format.</p></body></html> + + + + + clearButton + deviceBox + convertButton + fileButton + directoryButton + formatBox + + @@ -1050,18 +1067,12 @@ Ignored for Kindle EPUB/MOBI and all PDF. - - - - - 0 - 0 - - + + false - + 0 @@ -1074,23 +1085,55 @@ Ignored for Kindle EPUB/MOBI and all PDF. 0 - - + + + + + 0 + 0 + + + + <html><head/><body><p style='white-space:pre'>Resolution of the target device.</p></body></html> + - JPEG Quality: + Custom height: - - + + + + <html><head/><body><p style='white-space:pre'>Resolution of the target device.</p></body></html> + - 95 + 6000 - - 5 + + + + + + + 0 + 0 + - - 85 + + <html><head/><body><p style='white-space:pre'>Resolution of the target device.</p></body></html> + + + Custom width: + + + + + + + <html><head/><body><p style='white-space:pre'>Resolution of the target device.</p></body></html> + + + 8000 diff --git a/kindlecomicconverter/KCC_gui.py b/kindlecomicconverter/KCC_gui.py index 4dc4d01..0a2826c 100644 --- a/kindlecomicconverter/KCC_gui.py +++ b/kindlecomicconverter/KCC_gui.py @@ -195,7 +195,7 @@ class VersionThread(QThread): icon = 'bindle' if category == 'kofi': icon = 'kofi' - message = f"{payload.get('name')}" + message = f"{payload.get('name')}" if payload.get('link'): message = '{}'.format(payload.get('link'), payload.get('name')) if payload.get('showDeadline'): @@ -696,6 +696,18 @@ class KCCGUI(KCC_ui.Ui_mainWindow): # noinspection PyCallByClass QDesktopServices.openUrl(QUrl('https://ko-fi.com/eink_dude')) + def openHumble(self): + # noinspection PyCallByClass + QDesktopServices.openUrl(QUrl('https://humblebundleinc.sjv.io/3JaR3A')) + + def openYouTube(self): + # noinspection PyCallByClass + QDesktopServices.openUrl(QUrl('https://www.youtube.com/@eink-dude')) + + def openDiscord(self): + # noinspection PyCallByClass + QDesktopServices.openUrl(QUrl('https://discord.gg/um5JRKwmGT')) + def modeChange(self, mode): if mode == 1: self.currentMode = 1 @@ -1205,7 +1217,7 @@ class KCCGUI(KCC_ui.Ui_mainWindow): 'convertButton', 'formatBox']: getattr(GUI, element).setMinimumSize(QSize(0, 0)) GUI.gridLayout.setContentsMargins(-1, -1, -1, -1) - for element in ['gridLayout_2', 'gridLayout_3', 'gridLayout_4', 'horizontalLayout', 'horizontalLayout_2']: + for element in ['gridLayout_2', 'gridLayout_3', 'gridLayout_4', 'gridLayout_6', 'horizontalLayout_2']: getattr(GUI, element).setContentsMargins(-1, 0, -1, 0) if self.windowSize == '0x0': MW.resize(500, 500) @@ -1413,6 +1425,9 @@ class KCCGUI(KCC_ui.Ui_mainWindow): GUI.editorButton.clicked.connect(self.selectFileMetaEditor) GUI.wikiButton.clicked.connect(self.openWiki) GUI.kofiButton.clicked.connect(self.openKofi) + GUI.humbleButton.clicked.connect(self.openHumble) + GUI.youtubeButton.clicked.connect(self.openYouTube) + GUI.discordButton.clicked.connect(self.openDiscord) GUI.convertButton.clicked.connect(self.convertStart) GUI.gammaSlider.valueChanged.connect(self.changeGamma) GUI.gammaBox.stateChanged.connect(self.togglegammaBox) diff --git a/kindlecomicconverter/KCC_ui.py b/kindlecomicconverter/KCC_ui.py index 7d5b38c..b5f5c9d 100644 --- a/kindlecomicconverter/KCC_ui.py +++ b/kindlecomicconverter/KCC_ui.py @@ -26,7 +26,7 @@ class Ui_mainWindow(object): def setupUi(self, mainWindow): if not mainWindow.objectName(): mainWindow.setObjectName(u"mainWindow") - mainWindow.resize(566, 658) + mainWindow.resize(566, 671) icon = QIcon() icon.addFile(u":/Icon/icons/comic2ebook.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off) mainWindow.setWindowIcon(icon) @@ -35,53 +35,6 @@ class Ui_mainWindow(object): self.gridLayout = QGridLayout(self.centralWidget) self.gridLayout.setObjectName(u"gridLayout") self.gridLayout.setContentsMargins(-1, -1, -1, 5) - self.progressBar = QProgressBar(self.centralWidget) - self.progressBar.setObjectName(u"progressBar") - self.progressBar.setMinimumSize(QSize(0, 30)) - font = QFont() - font.setBold(True) - self.progressBar.setFont(font) - self.progressBar.setVisible(False) - self.progressBar.setAlignment(Qt.AlignmentFlag.AlignJustify|Qt.AlignmentFlag.AlignVCenter) - - self.gridLayout.addWidget(self.progressBar, 1, 0, 1, 2) - - self.toolWidget = QWidget(self.centralWidget) - self.toolWidget.setObjectName(u"toolWidget") - self.horizontalLayout = QHBoxLayout(self.toolWidget) - self.horizontalLayout.setObjectName(u"horizontalLayout") - self.horizontalLayout.setContentsMargins(0, 0, 0, 0) - self.editorButton = QPushButton(self.toolWidget) - self.editorButton.setObjectName(u"editorButton") - self.editorButton.setMinimumSize(QSize(0, 30)) - icon1 = QIcon() - icon1.addFile(u":/Other/icons/editor.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off) - self.editorButton.setIcon(icon1) - - self.horizontalLayout.addWidget(self.editorButton) - - self.kofiButton = QPushButton(self.toolWidget) - self.kofiButton.setObjectName(u"kofiButton") - self.kofiButton.setMinimumSize(QSize(0, 30)) - icon2 = QIcon() - icon2.addFile(u":/Brand/icons/kofi_symbol.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off) - self.kofiButton.setIcon(icon2) - self.kofiButton.setIconSize(QSize(19, 16)) - - self.horizontalLayout.addWidget(self.kofiButton) - - self.wikiButton = QPushButton(self.toolWidget) - self.wikiButton.setObjectName(u"wikiButton") - self.wikiButton.setMinimumSize(QSize(0, 30)) - icon3 = QIcon() - icon3.addFile(u":/Other/icons/wiki.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off) - self.wikiButton.setIcon(icon3) - - self.horizontalLayout.addWidget(self.wikiButton) - - - self.gridLayout.addWidget(self.toolWidget, 0, 0, 1, 2) - self.croppingWidget = QWidget(self.centralWidget) self.croppingWidget.setObjectName(u"croppingWidget") self.croppingWidget.setVisible(False) @@ -122,133 +75,51 @@ class Ui_mainWindow(object): self.gridLayout.addWidget(self.croppingWidget, 9, 0, 1, 2) - self.customWidget = QWidget(self.centralWidget) - self.customWidget.setObjectName(u"customWidget") - self.customWidget.setVisible(False) - self.gridLayout_3 = QGridLayout(self.customWidget) - self.gridLayout_3.setObjectName(u"gridLayout_3") - self.gridLayout_3.setContentsMargins(0, 0, 0, 0) - self.hLabel = QLabel(self.customWidget) - self.hLabel.setObjectName(u"hLabel") + self.progressBar = QProgressBar(self.centralWidget) + self.progressBar.setObjectName(u"progressBar") + self.progressBar.setMinimumSize(QSize(0, 30)) + font = QFont() + font.setBold(True) + self.progressBar.setFont(font) + self.progressBar.setVisible(False) + self.progressBar.setAlignment(Qt.AlignmentFlag.AlignJustify|Qt.AlignmentFlag.AlignVCenter) + + self.gridLayout.addWidget(self.progressBar, 1, 0, 1, 2) + + self.jpegQualityWidget = QWidget(self.centralWidget) + self.jpegQualityWidget.setObjectName(u"jpegQualityWidget") sizePolicy1 = QSizePolicy(QSizePolicy.Policy.Fixed, QSizePolicy.Policy.Preferred) sizePolicy1.setHorizontalStretch(0) sizePolicy1.setVerticalStretch(0) - sizePolicy1.setHeightForWidth(self.hLabel.sizePolicy().hasHeightForWidth()) - self.hLabel.setSizePolicy(sizePolicy1) + sizePolicy1.setHeightForWidth(self.jpegQualityWidget.sizePolicy().hasHeightForWidth()) + self.jpegQualityWidget.setSizePolicy(sizePolicy1) + self.jpegQualityWidget.setVisible(False) + self.horizontalLayout_12 = QHBoxLayout(self.jpegQualityWidget) + self.horizontalLayout_12.setObjectName(u"horizontalLayout_12") + self.horizontalLayout_12.setContentsMargins(0, 0, 0, 0) + self.jpegQualityLabel = QLabel(self.jpegQualityWidget) + self.jpegQualityLabel.setObjectName(u"jpegQualityLabel") - self.gridLayout_3.addWidget(self.hLabel, 0, 2, 1, 1) + self.horizontalLayout_12.addWidget(self.jpegQualityLabel) - self.widthBox = QSpinBox(self.customWidget) - self.widthBox.setObjectName(u"widthBox") - self.widthBox.setMaximum(6000) + self.jpegQualitySpinBox = QSpinBox(self.jpegQualityWidget) + self.jpegQualitySpinBox.setObjectName(u"jpegQualitySpinBox") + self.jpegQualitySpinBox.setMaximum(95) + self.jpegQualitySpinBox.setSingleStep(5) + self.jpegQualitySpinBox.setValue(85) - self.gridLayout_3.addWidget(self.widthBox, 0, 1, 1, 1) - - self.wLabel = QLabel(self.customWidget) - self.wLabel.setObjectName(u"wLabel") - sizePolicy1.setHeightForWidth(self.wLabel.sizePolicy().hasHeightForWidth()) - self.wLabel.setSizePolicy(sizePolicy1) - - self.gridLayout_3.addWidget(self.wLabel, 0, 0, 1, 1) - - self.heightBox = QSpinBox(self.customWidget) - self.heightBox.setObjectName(u"heightBox") - self.heightBox.setMaximum(8000) - - self.gridLayout_3.addWidget(self.heightBox, 0, 3, 1, 1) + self.horizontalLayout_12.addWidget(self.jpegQualitySpinBox) - self.gridLayout.addWidget(self.customWidget, 8, 0, 1, 2) - - self.buttonWidget = QWidget(self.centralWidget) - self.buttonWidget.setObjectName(u"buttonWidget") - sizePolicy2 = QSizePolicy(QSizePolicy.Policy.Preferred, QSizePolicy.Policy.Fixed) - sizePolicy2.setHorizontalStretch(0) - sizePolicy2.setVerticalStretch(0) - sizePolicy2.setHeightForWidth(self.buttonWidget.sizePolicy().hasHeightForWidth()) - self.buttonWidget.setSizePolicy(sizePolicy2) - self.gridLayout_4 = QGridLayout(self.buttonWidget) - self.gridLayout_4.setObjectName(u"gridLayout_4") - self.gridLayout_4.setContentsMargins(0, 0, 0, 0) - self.convertButton = QPushButton(self.buttonWidget) - self.convertButton.setObjectName(u"convertButton") - self.convertButton.setMinimumSize(QSize(0, 30)) - self.convertButton.setFont(font) - icon4 = QIcon() - icon4.addFile(u":/Other/icons/convert.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off) - self.convertButton.setIcon(icon4) - - self.gridLayout_4.addWidget(self.convertButton, 1, 3, 1, 1) - - self.clearButton = QPushButton(self.buttonWidget) - self.clearButton.setObjectName(u"clearButton") - self.clearButton.setMinimumSize(QSize(0, 30)) - icon5 = QIcon() - icon5.addFile(u":/Other/icons/clear.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off) - self.clearButton.setIcon(icon5) - - self.gridLayout_4.addWidget(self.clearButton, 0, 3, 1, 1) - - self.deviceBox = QComboBox(self.buttonWidget) - self.deviceBox.setObjectName(u"deviceBox") - self.deviceBox.setMinimumSize(QSize(0, 28)) - - self.gridLayout_4.addWidget(self.deviceBox, 1, 1, 1, 1) - - self.fileButton = QPushButton(self.buttonWidget) - self.fileButton.setObjectName(u"fileButton") - self.fileButton.setMinimumSize(QSize(0, 30)) - icon6 = QIcon() - icon6.addFile(u":/Other/icons/document_new.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off) - self.fileButton.setIcon(icon6) - - self.gridLayout_4.addWidget(self.fileButton, 0, 1, 1, 1) - - self.directoryButton = QPushButton(self.buttonWidget) - self.directoryButton.setObjectName(u"directoryButton") - sizePolicy3 = QSizePolicy(QSizePolicy.Policy.Minimum, QSizePolicy.Policy.Minimum) - sizePolicy3.setHorizontalStretch(0) - sizePolicy3.setVerticalStretch(0) - sizePolicy3.setHeightForWidth(self.directoryButton.sizePolicy().hasHeightForWidth()) - self.directoryButton.setSizePolicy(sizePolicy3) - icon7 = QIcon() - icon7.addFile(u":/Other/icons/folder_new.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off) - self.directoryButton.setIcon(icon7) - - self.gridLayout_4.addWidget(self.directoryButton, 0, 4, 1, 1) - - self.formatBox = QComboBox(self.buttonWidget) - self.formatBox.setObjectName(u"formatBox") - self.formatBox.setMinimumSize(QSize(0, 28)) - - self.gridLayout_4.addWidget(self.formatBox, 1, 4, 1, 1) - - self.clearButton.raise_() - self.deviceBox.raise_() - self.convertButton.raise_() - self.fileButton.raise_() - self.directoryButton.raise_() - self.formatBox.raise_() - - self.gridLayout.addWidget(self.buttonWidget, 3, 0, 1, 2) - - self.jobList = QListWidget(self.centralWidget) - self.jobList.setObjectName(u"jobList") - self.jobList.setMinimumSize(QSize(0, 150)) - self.jobList.setStyleSheet(u"") - self.jobList.setSelectionMode(QAbstractItemView.SelectionMode.NoSelection) - self.jobList.setVerticalScrollMode(QAbstractItemView.ScrollMode.ScrollPerPixel) - self.jobList.setHorizontalScrollMode(QAbstractItemView.ScrollMode.ScrollPerPixel) - - self.gridLayout.addWidget(self.jobList, 2, 0, 1, 2) + self.gridLayout.addWidget(self.jpegQualityWidget, 10, 0, 1, 1) self.chunkSizeWidget = QWidget(self.centralWidget) self.chunkSizeWidget.setObjectName(u"chunkSizeWidget") - sizePolicy4 = QSizePolicy(QSizePolicy.Policy.Minimum, QSizePolicy.Policy.Fixed) - sizePolicy4.setHorizontalStretch(0) - sizePolicy4.setVerticalStretch(0) - sizePolicy4.setHeightForWidth(self.chunkSizeWidget.sizePolicy().hasHeightForWidth()) - self.chunkSizeWidget.setSizePolicy(sizePolicy4) + sizePolicy2 = QSizePolicy(QSizePolicy.Policy.Minimum, QSizePolicy.Policy.Fixed) + sizePolicy2.setHorizontalStretch(0) + sizePolicy2.setVerticalStretch(0) + sizePolicy2.setHeightForWidth(self.chunkSizeWidget.sizePolicy().hasHeightForWidth()) + self.chunkSizeWidget.setSizePolicy(sizePolicy2) self.chunkSizeWidget.setVisible(False) self.horizontalLayout_4 = QHBoxLayout(self.chunkSizeWidget) self.horizontalLayout_4.setSpacing(0) @@ -256,11 +127,11 @@ class Ui_mainWindow(object): self.horizontalLayout_4.setContentsMargins(0, 0, 0, 0) self.chunkSizeLabel = QLabel(self.chunkSizeWidget) self.chunkSizeLabel.setObjectName(u"chunkSizeLabel") - sizePolicy5 = QSizePolicy(QSizePolicy.Policy.Minimum, QSizePolicy.Policy.Preferred) - sizePolicy5.setHorizontalStretch(0) - sizePolicy5.setVerticalStretch(0) - sizePolicy5.setHeightForWidth(self.chunkSizeLabel.sizePolicy().hasHeightForWidth()) - self.chunkSizeLabel.setSizePolicy(sizePolicy5) + sizePolicy3 = QSizePolicy(QSizePolicy.Policy.Minimum, QSizePolicy.Policy.Preferred) + sizePolicy3.setHorizontalStretch(0) + sizePolicy3.setVerticalStretch(0) + sizePolicy3.setHeightForWidth(self.chunkSizeLabel.sizePolicy().hasHeightForWidth()) + self.chunkSizeLabel.setSizePolicy(sizePolicy3) self.horizontalLayout_4.addWidget(self.chunkSizeLabel) @@ -274,14 +145,81 @@ class Ui_mainWindow(object): self.chunkSizeWarnLabel = QLabel(self.chunkSizeWidget) self.chunkSizeWarnLabel.setObjectName(u"chunkSizeWarnLabel") - sizePolicy5.setHeightForWidth(self.chunkSizeWarnLabel.sizePolicy().hasHeightForWidth()) - self.chunkSizeWarnLabel.setSizePolicy(sizePolicy5) + sizePolicy3.setHeightForWidth(self.chunkSizeWarnLabel.sizePolicy().hasHeightForWidth()) + self.chunkSizeWarnLabel.setSizePolicy(sizePolicy3) self.horizontalLayout_4.addWidget(self.chunkSizeWarnLabel) self.gridLayout.addWidget(self.chunkSizeWidget, 6, 0, 1, 1) + self.jobList = QListWidget(self.centralWidget) + self.jobList.setObjectName(u"jobList") + self.jobList.setMinimumSize(QSize(0, 150)) + self.jobList.setStyleSheet(u"") + self.jobList.setSelectionMode(QAbstractItemView.SelectionMode.NoSelection) + self.jobList.setVerticalScrollMode(QAbstractItemView.ScrollMode.ScrollPerPixel) + self.jobList.setHorizontalScrollMode(QAbstractItemView.ScrollMode.ScrollPerPixel) + + self.gridLayout.addWidget(self.jobList, 2, 0, 1, 2) + + self.toolWidget = QWidget(self.centralWidget) + self.toolWidget.setObjectName(u"toolWidget") + self.gridLayout_6 = QGridLayout(self.toolWidget) + self.gridLayout_6.setObjectName(u"gridLayout_6") + self.gridLayout_6.setContentsMargins(0, 0, 0, 0) + self.editorButton = QPushButton(self.toolWidget) + self.editorButton.setObjectName(u"editorButton") + self.editorButton.setMinimumSize(QSize(0, 30)) + icon1 = QIcon() + icon1.addFile(u":/Other/icons/editor.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off) + self.editorButton.setIcon(icon1) + + self.gridLayout_6.addWidget(self.editorButton, 0, 0, 1, 1) + + self.kofiButton = QPushButton(self.toolWidget) + self.kofiButton.setObjectName(u"kofiButton") + self.kofiButton.setMinimumSize(QSize(0, 30)) + icon2 = QIcon() + icon2.addFile(u":/Brand/icons/kofi_symbol.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off) + self.kofiButton.setIcon(icon2) + self.kofiButton.setIconSize(QSize(19, 16)) + + self.gridLayout_6.addWidget(self.kofiButton, 0, 1, 1, 1) + + self.wikiButton = QPushButton(self.toolWidget) + self.wikiButton.setObjectName(u"wikiButton") + self.wikiButton.setMinimumSize(QSize(0, 30)) + icon3 = QIcon() + icon3.addFile(u":/Other/icons/wiki.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off) + self.wikiButton.setIcon(icon3) + + self.gridLayout_6.addWidget(self.wikiButton, 0, 2, 1, 1) + + self.youtubeButton = QPushButton(self.toolWidget) + self.youtubeButton.setObjectName(u"youtubeButton") + self.youtubeButton.setMinimumSize(QSize(0, 30)) + + self.gridLayout_6.addWidget(self.youtubeButton, 1, 0, 1, 1) + + self.humbleButton = QPushButton(self.toolWidget) + self.humbleButton.setObjectName(u"humbleButton") + self.humbleButton.setMinimumSize(QSize(0, 30)) + icon4 = QIcon() + icon4.addFile(u":/Brand/icons/Humble_H-Red.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off) + self.humbleButton.setIcon(icon4) + + self.gridLayout_6.addWidget(self.humbleButton, 1, 1, 1, 1) + + self.discordButton = QPushButton(self.toolWidget) + self.discordButton.setObjectName(u"discordButton") + self.discordButton.setMinimumSize(QSize(0, 30)) + + self.gridLayout_6.addWidget(self.discordButton, 1, 2, 1, 1) + + + self.gridLayout.addWidget(self.toolWidget, 0, 0, 1, 2) + self.optionWidget = QWidget(self.centralWidget) self.optionWidget.setObjectName(u"optionWidget") self.gridLayout_2 = QGridLayout(self.optionWidget) @@ -317,8 +255,8 @@ class Ui_mainWindow(object): self.titleEdit = QLineEdit(self.optionWidget) self.titleEdit.setObjectName(u"titleEdit") - sizePolicy4.setHeightForWidth(self.titleEdit.sizePolicy().hasHeightForWidth()) - self.titleEdit.setSizePolicy(sizePolicy4) + sizePolicy2.setHeightForWidth(self.titleEdit.sizePolicy().hasHeightForWidth()) + self.titleEdit.setSizePolicy(sizePolicy2) self.titleEdit.setFocusPolicy(Qt.FocusPolicy.ClickFocus) self.titleEdit.setClearButtonEnabled(False) @@ -326,8 +264,8 @@ class Ui_mainWindow(object): self.authorEdit = QLineEdit(self.optionWidget) self.authorEdit.setObjectName(u"authorEdit") - sizePolicy4.setHeightForWidth(self.authorEdit.sizePolicy().hasHeightForWidth()) - self.authorEdit.setSizePolicy(sizePolicy4) + sizePolicy2.setHeightForWidth(self.authorEdit.sizePolicy().hasHeightForWidth()) + self.authorEdit.setSizePolicy(sizePolicy2) self.authorEdit.setFocusPolicy(Qt.FocusPolicy.ClickFocus) self.authorEdit.setClearButtonEnabled(False) @@ -487,7 +425,9 @@ class Ui_mainWindow(object): self.defaultOutputFolderButton = QPushButton(self.outputFolderWidget) self.defaultOutputFolderButton.setObjectName(u"defaultOutputFolderButton") self.defaultOutputFolderButton.setMinimumSize(QSize(0, 30)) - self.defaultOutputFolderButton.setIcon(icon7) + icon5 = QIcon() + icon5.addFile(u":/Other/icons/folder_new.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off) + self.defaultOutputFolderButton.setIcon(icon5) self.horizontalLayout_3.addWidget(self.defaultOutputFolderButton) @@ -513,6 +453,76 @@ class Ui_mainWindow(object): self.gridLayout.addWidget(self.optionWidget, 5, 0, 1, 2) + self.buttonWidget = QWidget(self.centralWidget) + self.buttonWidget.setObjectName(u"buttonWidget") + sizePolicy4 = QSizePolicy(QSizePolicy.Policy.Preferred, QSizePolicy.Policy.Fixed) + sizePolicy4.setHorizontalStretch(0) + sizePolicy4.setVerticalStretch(0) + sizePolicy4.setHeightForWidth(self.buttonWidget.sizePolicy().hasHeightForWidth()) + self.buttonWidget.setSizePolicy(sizePolicy4) + self.gridLayout_4 = QGridLayout(self.buttonWidget) + self.gridLayout_4.setObjectName(u"gridLayout_4") + self.gridLayout_4.setContentsMargins(0, 0, 0, 0) + self.convertButton = QPushButton(self.buttonWidget) + self.convertButton.setObjectName(u"convertButton") + self.convertButton.setMinimumSize(QSize(0, 30)) + self.convertButton.setFont(font) + icon6 = QIcon() + icon6.addFile(u":/Other/icons/convert.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off) + self.convertButton.setIcon(icon6) + + self.gridLayout_4.addWidget(self.convertButton, 1, 3, 1, 1) + + self.clearButton = QPushButton(self.buttonWidget) + self.clearButton.setObjectName(u"clearButton") + self.clearButton.setMinimumSize(QSize(0, 30)) + icon7 = QIcon() + icon7.addFile(u":/Other/icons/clear.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off) + self.clearButton.setIcon(icon7) + + self.gridLayout_4.addWidget(self.clearButton, 0, 3, 1, 1) + + self.deviceBox = QComboBox(self.buttonWidget) + self.deviceBox.setObjectName(u"deviceBox") + self.deviceBox.setMinimumSize(QSize(0, 28)) + + self.gridLayout_4.addWidget(self.deviceBox, 1, 1, 1, 1) + + self.fileButton = QPushButton(self.buttonWidget) + self.fileButton.setObjectName(u"fileButton") + self.fileButton.setMinimumSize(QSize(0, 30)) + icon8 = QIcon() + icon8.addFile(u":/Other/icons/document_new.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off) + self.fileButton.setIcon(icon8) + + self.gridLayout_4.addWidget(self.fileButton, 0, 1, 1, 1) + + self.directoryButton = QPushButton(self.buttonWidget) + self.directoryButton.setObjectName(u"directoryButton") + sizePolicy5 = QSizePolicy(QSizePolicy.Policy.Minimum, QSizePolicy.Policy.Minimum) + sizePolicy5.setHorizontalStretch(0) + sizePolicy5.setVerticalStretch(0) + sizePolicy5.setHeightForWidth(self.directoryButton.sizePolicy().hasHeightForWidth()) + self.directoryButton.setSizePolicy(sizePolicy5) + self.directoryButton.setIcon(icon5) + + self.gridLayout_4.addWidget(self.directoryButton, 0, 4, 1, 1) + + self.formatBox = QComboBox(self.buttonWidget) + self.formatBox.setObjectName(u"formatBox") + self.formatBox.setMinimumSize(QSize(0, 28)) + + self.gridLayout_4.addWidget(self.formatBox, 1, 4, 1, 1) + + self.clearButton.raise_() + self.deviceBox.raise_() + self.convertButton.raise_() + self.fileButton.raise_() + self.directoryButton.raise_() + self.formatBox.raise_() + + self.gridLayout.addWidget(self.buttonWidget, 3, 0, 1, 2) + self.gammaWidget = QWidget(self.centralWidget) self.gammaWidget.setObjectName(u"gammaWidget") self.gammaWidget.setVisible(False) @@ -535,29 +545,40 @@ class Ui_mainWindow(object): self.gridLayout.addWidget(self.gammaWidget, 7, 0, 1, 2) - self.jpegQualityWidget = QWidget(self.centralWidget) - self.jpegQualityWidget.setObjectName(u"jpegQualityWidget") - sizePolicy1.setHeightForWidth(self.jpegQualityWidget.sizePolicy().hasHeightForWidth()) - self.jpegQualityWidget.setSizePolicy(sizePolicy1) - self.jpegQualityWidget.setVisible(False) - self.horizontalLayout_12 = QHBoxLayout(self.jpegQualityWidget) - self.horizontalLayout_12.setObjectName(u"horizontalLayout_12") - self.horizontalLayout_12.setContentsMargins(0, 0, 0, 0) - self.jpegQualityLabel = QLabel(self.jpegQualityWidget) - self.jpegQualityLabel.setObjectName(u"jpegQualityLabel") + self.customWidget = QWidget(self.centralWidget) + self.customWidget.setObjectName(u"customWidget") + self.customWidget.setVisible(False) + self.gridLayout_3 = QGridLayout(self.customWidget) + self.gridLayout_3.setObjectName(u"gridLayout_3") + self.gridLayout_3.setContentsMargins(0, 0, 0, 0) + self.hLabel = QLabel(self.customWidget) + self.hLabel.setObjectName(u"hLabel") + sizePolicy1.setHeightForWidth(self.hLabel.sizePolicy().hasHeightForWidth()) + self.hLabel.setSizePolicy(sizePolicy1) - self.horizontalLayout_12.addWidget(self.jpegQualityLabel) + self.gridLayout_3.addWidget(self.hLabel, 0, 2, 1, 1) - self.jpegQualitySpinBox = QSpinBox(self.jpegQualityWidget) - self.jpegQualitySpinBox.setObjectName(u"jpegQualitySpinBox") - self.jpegQualitySpinBox.setMaximum(95) - self.jpegQualitySpinBox.setSingleStep(5) - self.jpegQualitySpinBox.setValue(85) + self.widthBox = QSpinBox(self.customWidget) + self.widthBox.setObjectName(u"widthBox") + self.widthBox.setMaximum(6000) - self.horizontalLayout_12.addWidget(self.jpegQualitySpinBox) + self.gridLayout_3.addWidget(self.widthBox, 0, 1, 1, 1) + + self.wLabel = QLabel(self.customWidget) + self.wLabel.setObjectName(u"wLabel") + sizePolicy1.setHeightForWidth(self.wLabel.sizePolicy().hasHeightForWidth()) + self.wLabel.setSizePolicy(sizePolicy1) + + self.gridLayout_3.addWidget(self.wLabel, 0, 0, 1, 1) + + self.heightBox = QSpinBox(self.customWidget) + self.heightBox.setObjectName(u"heightBox") + self.heightBox.setMaximum(8000) + + self.gridLayout_3.addWidget(self.heightBox, 0, 3, 1, 1) - self.gridLayout.addWidget(self.jpegQualityWidget, 10, 0, 1, 1) + self.gridLayout.addWidget(self.customWidget, 8, 0, 1, 2) mainWindow.setCentralWidget(self.centralWidget) self.statusBar = QStatusBar(mainWindow) @@ -610,58 +631,29 @@ class Ui_mainWindow(object): def retranslateUi(self, mainWindow): mainWindow.setWindowTitle(QCoreApplication.translate("mainWindow", u"Kindle Comic Converter", None)) -#if QT_CONFIG(tooltip) - self.editorButton.setToolTip(QCoreApplication.translate("mainWindow", u"

Shift+Click to edit directory.

", None)) -#endif // QT_CONFIG(tooltip) - self.editorButton.setText(QCoreApplication.translate("mainWindow", u"Metadata Editor", None)) - self.kofiButton.setText(QCoreApplication.translate("mainWindow", u"Support me on Ko-fi", None)) - self.wikiButton.setText(QCoreApplication.translate("mainWindow", u"Wiki", None)) #if QT_CONFIG(tooltip) self.preserveMarginLabel.setToolTip(QCoreApplication.translate("mainWindow", u"

After calculating the cropping boundaries, "back up" a specified percentage amount.

", None)) #endif // QT_CONFIG(tooltip) self.preserveMarginLabel.setText(QCoreApplication.translate("mainWindow", u"Preserve Margin %", None)) self.croppingPowerLabel.setText(QCoreApplication.translate("mainWindow", u"Cropping power:", None)) -#if QT_CONFIG(tooltip) - self.hLabel.setToolTip(QCoreApplication.translate("mainWindow", u"

Resolution of the target device.

", None)) -#endif // QT_CONFIG(tooltip) - self.hLabel.setText(QCoreApplication.translate("mainWindow", u"Custom height:", None)) -#if QT_CONFIG(tooltip) - self.widthBox.setToolTip(QCoreApplication.translate("mainWindow", u"

Resolution of the target device.

", None)) -#endif // QT_CONFIG(tooltip) -#if QT_CONFIG(tooltip) - self.wLabel.setToolTip(QCoreApplication.translate("mainWindow", u"

Resolution of the target device.

", None)) -#endif // QT_CONFIG(tooltip) - self.wLabel.setText(QCoreApplication.translate("mainWindow", u"Custom width:", None)) -#if QT_CONFIG(tooltip) - self.heightBox.setToolTip(QCoreApplication.translate("mainWindow", u"

Resolution of the target device.

", None)) -#endif // QT_CONFIG(tooltip) -#if QT_CONFIG(tooltip) - self.convertButton.setToolTip(QCoreApplication.translate("mainWindow", u"

Shift+Click to select the output directory for this list.

", None)) -#endif // QT_CONFIG(tooltip) - self.convertButton.setText(QCoreApplication.translate("mainWindow", u"Convert", None)) - self.clearButton.setText(QCoreApplication.translate("mainWindow", u"Clear list", None)) -#if QT_CONFIG(tooltip) - self.deviceBox.setToolTip(QCoreApplication.translate("mainWindow", u"

Target device.

", None)) -#endif // QT_CONFIG(tooltip) -#if QT_CONFIG(tooltip) - self.fileButton.setToolTip(QCoreApplication.translate("mainWindow", u"

Add CBR, CBZ, CB7 or PDF file to queue.

", None)) -#endif // QT_CONFIG(tooltip) - self.fileButton.setText(QCoreApplication.translate("mainWindow", u"Add input file(s)", None)) -#if QT_CONFIG(tooltip) - self.directoryButton.setToolTip(QCoreApplication.translate("mainWindow", u"

Add directory containing JPG, PNG or GIF files to queue.
CBR, CBZ and CB7 files inside will not be processed!

", None)) -#endif // QT_CONFIG(tooltip) - self.directoryButton.setText(QCoreApplication.translate("mainWindow", u"Add input folder(s)", None)) -#if QT_CONFIG(tooltip) - self.formatBox.setToolTip(QCoreApplication.translate("mainWindow", u"

Output format.

", None)) -#endif // QT_CONFIG(tooltip) -#if QT_CONFIG(tooltip) - self.jobList.setToolTip(QCoreApplication.translate("mainWindow", u"

Double click on source to open it in metadata editor.

", None)) -#endif // QT_CONFIG(tooltip) + self.jpegQualityLabel.setText(QCoreApplication.translate("mainWindow", u"JPEG Quality:", None)) #if QT_CONFIG(tooltip) self.chunkSizeWidget.setToolTip(QCoreApplication.translate("mainWindow", u"

Warning: chunk size greater than default may cause
performance/battery issues, especially on older devices.

", None)) #endif // QT_CONFIG(tooltip) self.chunkSizeLabel.setText(QCoreApplication.translate("mainWindow", u"Chunk size MB:", None)) self.chunkSizeWarnLabel.setText(QCoreApplication.translate("mainWindow", u"Greater than default may cause performance issues on older ereaders.", None)) +#if QT_CONFIG(tooltip) + self.jobList.setToolTip(QCoreApplication.translate("mainWindow", u"

Double click on source to open it in metadata editor.

", None)) +#endif // QT_CONFIG(tooltip) +#if QT_CONFIG(tooltip) + self.editorButton.setToolTip(QCoreApplication.translate("mainWindow", u"

Shift+Click to edit directory.

", None)) +#endif // QT_CONFIG(tooltip) + self.editorButton.setText(QCoreApplication.translate("mainWindow", u"Metadata Editor", None)) + self.kofiButton.setText(QCoreApplication.translate("mainWindow", u"Support me on Ko-fi", None)) + self.wikiButton.setText(QCoreApplication.translate("mainWindow", u"Wiki", None)) + self.youtubeButton.setText(QCoreApplication.translate("mainWindow", u"YouTube", None)) + self.humbleButton.setText(QCoreApplication.translate("mainWindow", u"Humble Bundle Referral", None)) + self.discordButton.setText(QCoreApplication.translate("mainWindow", u"Discord", None)) #if QT_CONFIG(tooltip) self.noRotateBox.setToolTip(QCoreApplication.translate("mainWindow", u"Do not rotate double page spreads in spread splitter option.", None)) #endif // QT_CONFIG(tooltip) @@ -829,7 +821,39 @@ class Ui_mainWindow(object): "Ignored for Kindle EPUB/MOBI and all PDF.", None)) #endif // QT_CONFIG(tooltip) self.webpBox.setText(QCoreApplication.translate("mainWindow", u"WebP (experimental)", None)) +#if QT_CONFIG(tooltip) + self.convertButton.setToolTip(QCoreApplication.translate("mainWindow", u"

Shift+Click to select the output directory for this list.

", None)) +#endif // QT_CONFIG(tooltip) + self.convertButton.setText(QCoreApplication.translate("mainWindow", u"Convert", None)) + self.clearButton.setText(QCoreApplication.translate("mainWindow", u"Clear list", None)) +#if QT_CONFIG(tooltip) + self.deviceBox.setToolTip(QCoreApplication.translate("mainWindow", u"

Target device.

", None)) +#endif // QT_CONFIG(tooltip) +#if QT_CONFIG(tooltip) + self.fileButton.setToolTip(QCoreApplication.translate("mainWindow", u"

Add CBR, CBZ, CB7 or PDF file to queue.

", None)) +#endif // QT_CONFIG(tooltip) + self.fileButton.setText(QCoreApplication.translate("mainWindow", u"Add input file(s)", None)) +#if QT_CONFIG(tooltip) + self.directoryButton.setToolTip(QCoreApplication.translate("mainWindow", u"

Add directory containing JPG, PNG or GIF files to queue.
CBR, CBZ and CB7 files inside will not be processed!

", None)) +#endif // QT_CONFIG(tooltip) + self.directoryButton.setText(QCoreApplication.translate("mainWindow", u"Add input folder(s)", None)) +#if QT_CONFIG(tooltip) + self.formatBox.setToolTip(QCoreApplication.translate("mainWindow", u"

Output format.

", None)) +#endif // QT_CONFIG(tooltip) self.gammaLabel.setText(QCoreApplication.translate("mainWindow", u"Gamma: Auto", None)) - self.jpegQualityLabel.setText(QCoreApplication.translate("mainWindow", u"JPEG Quality:", None)) +#if QT_CONFIG(tooltip) + self.hLabel.setToolTip(QCoreApplication.translate("mainWindow", u"

Resolution of the target device.

", None)) +#endif // QT_CONFIG(tooltip) + self.hLabel.setText(QCoreApplication.translate("mainWindow", u"Custom height:", None)) +#if QT_CONFIG(tooltip) + self.widthBox.setToolTip(QCoreApplication.translate("mainWindow", u"

Resolution of the target device.

", None)) +#endif // QT_CONFIG(tooltip) +#if QT_CONFIG(tooltip) + self.wLabel.setToolTip(QCoreApplication.translate("mainWindow", u"

Resolution of the target device.

", None)) +#endif // QT_CONFIG(tooltip) + self.wLabel.setText(QCoreApplication.translate("mainWindow", u"Custom width:", None)) +#if QT_CONFIG(tooltip) + self.heightBox.setToolTip(QCoreApplication.translate("mainWindow", u"

Resolution of the target device.

", None)) +#endif // QT_CONFIG(tooltip) # retranslateUi From 8d61a9e55806c23524dfaaecc5a09257ce0bb5ee Mon Sep 17 00:00:00 2001 From: Alex Xu Date: Sun, 12 Apr 2026 11:31:27 -0700 Subject: [PATCH 073/107] bump to 9.7.0 --- kindlecomicconverter/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kindlecomicconverter/__init__.py b/kindlecomicconverter/__init__.py index 3325716..b91a660 100644 --- a/kindlecomicconverter/__init__.py +++ b/kindlecomicconverter/__init__.py @@ -1,4 +1,4 @@ -__version__ = '9.6.2' +__version__ = '9.7.0' __license__ = 'ISC' __copyright__ = '2012-2022, Ciro Mattia Gonano , Pawel Jastrzebski , darodi' __docformat__ = 'restructuredtext en' From d5ca8fb4073d25f5faa02b40cffaf7c95ed0c86d Mon Sep 17 00:00:00 2001 From: Alex Xu Date: Mon, 13 Apr 2026 23:03:26 -0700 Subject: [PATCH 074/107] don't bisect images with aspect ratio > 2 (#1293) --- kindlecomicconverter/image.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/kindlecomicconverter/image.py b/kindlecomicconverter/image.py index 57c2a95..892dc77 100755 --- a/kindlecomicconverter/image.py +++ b/kindlecomicconverter/image.py @@ -203,7 +203,7 @@ class ComicPageParser: spread = spread.rotate(-90, Image.Resampling.BICUBIC, True) self.payload.append(['R', self.source, spread, self.fill]) elif (width > height) != (dstwidth > dstheight) and not self.opt.webtoon: - if self.opt.splitter != 1: + if self.opt.splitter != 1 and width / height < 2: if width > height: leftbox = (0, 0, int(width / 2), height) rightbox = (int(width / 2), 0, width, height) @@ -218,7 +218,7 @@ class ComicPageParser: pagetwo = self.image.crop(rightbox) self.payload.append(['S1', self.source, pageone, self.fill]) self.payload.append(['S2', self.source, pagetwo, self.fill]) - if self.opt.splitter > 0: + if self.opt.splitter > 0 or (self.opt.splitter == 0 and width / height >= 2): spread = self.image if not self.opt.norotate: if not self.opt.rotateright: From 6f26bd58740ea57737f5a8bff86c979d63f40b14 Mon Sep 17 00:00:00 2001 From: Alex Xu Date: Wed, 15 Apr 2026 17:22:00 -0700 Subject: [PATCH 075/107] add epub series metadata (#1294) --- kindlecomicconverter/comic2ebook.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/kindlecomicconverter/comic2ebook.py b/kindlecomicconverter/comic2ebook.py index 79e1e4f..59a1408 100755 --- a/kindlecomicconverter/comic2ebook.py +++ b/kindlecomicconverter/comic2ebook.py @@ -310,6 +310,15 @@ def buildOPF(dstdir, title, filelist, originalpath, cover=None): f.writelines(["", hescape(options.summary), "\n"]) for author in options.authors: f.writelines(["", hescape(author), "\n"]) + if not options.iskindle and options.series: + f.writelines(['', hescape(options.series), "\n"]) + f.writelines(['', "series", "\n"]) + if options.volume and options.number: + f.writelines(['', hescape(f"{options.volume}.{options.number}"), "\n"]) + elif options.volume: + f.writelines(['', hescape(options.volume), "\n"]) + elif options.number: + f.writelines(['', hescape(options.number), "\n"]) f.write("" + strftime("%Y-%m-%dT%H:%M:%SZ", gmtime()) + "\n") if cover: f.write("\n") @@ -992,6 +1001,9 @@ def getMetadata(path, originalpath): options.comicinfo_chapters = [] options.summary = '' titleSuffix = '' + options.volume = '' + options.number = '' + options.series = '' if options.title == 'defaulttitle': defaultTitle = True if os.path.isdir(originalpath): @@ -1020,8 +1032,10 @@ def getMetadata(path, originalpath): options.title = xml.data['Series'] if xml.data['Volume']: titleSuffix += ' Vol. ' + xml.data['Volume'].zfill(2) + options.volume = xml.data['Volume'] if xml.data['Number']: titleSuffix += ' #' + xml.data['Number'].zfill(3) + options.number = xml.data['Number'] if options.metadatatitle == 1 and xml.data['Title']: titleSuffix += ': ' + xml.data['Title'] options.title += titleSuffix @@ -1039,6 +1053,8 @@ def getMetadata(path, originalpath): options.comicinfo_chapters = xml.data['Bookmarks'] if xml.data['Summary']: options.summary = xml.data['Summary'] + if xml.data['Series']: + options.series = xml.data['Series'] os.remove(xmlPath) if originalpath.lower().endswith('.pdf'): From e7b7054b0e27330e475e548ef35918f8ff953934 Mon Sep 17 00:00:00 2001 From: Alex Xu Date: Wed, 15 Apr 2026 21:25:08 -0700 Subject: [PATCH 076/107] smart cover crop is default on and implemented for all formats (#1295) --- README.md | 2 +- gui/KCC.ui | 6 +++--- kindlecomicconverter/KCC_gui.py | 6 +++--- kindlecomicconverter/KCC_ui.py | 10 +++++----- kindlecomicconverter/comic2ebook.py | 10 +++++++--- kindlecomicconverter/image.py | 7 +++++-- 6 files changed, 24 insertions(+), 17 deletions(-) diff --git a/README.md b/README.md index a65f357..01cebcc 100644 --- a/README.md +++ b/README.md @@ -270,7 +270,7 @@ PROCESSING: Crop empty sections. 0: Disabled 1: Horizontally 2: Both [Default=0] --blackborders Disable autodetection and force black borders --whiteborders Disable autodetection and force white borders - --smartcovercrop Attempt to crop main cover from wide image + --nosmartcovercrop Disable attempt to crop main cover from wide image --coverfill Center-crop only the cover to fill target device screen --forcecolor Don't convert images to grayscale --forcepng Create PNG files instead JPEG for black and white images diff --git a/gui/KCC.ui b/gui/KCC.ui index 63cd15d..b2c960d 100644 --- a/gui/KCC.ui +++ b/gui/KCC.ui @@ -655,12 +655,12 @@ Higher values are larger and higher quality, and may resolve blank page issues.<
- + - Attempt to crop main cover from wide image. + Disable attempt to crop main cover from wide image. - Smart Cover Crop + No Smart Cover Crop diff --git a/kindlecomicconverter/KCC_gui.py b/kindlecomicconverter/KCC_gui.py index 0a2826c..089af51 100644 --- a/kindlecomicconverter/KCC_gui.py +++ b/kindlecomicconverter/KCC_gui.py @@ -331,8 +331,8 @@ class WorkerThread(QThread): options.pdfextract = True if GUI.pdfWidthBox.isChecked(): options.pdfwidth = True - if GUI.smartCoverCropBox.isChecked(): - options.smartcovercrop = True + if GUI.noSmartCoverCropBox.isChecked(): + options.nosmartcovercrop = True if GUI.coverFillBox.isChecked(): options.coverfill = True if GUI.metadataTitleBox.checkState() == Qt.CheckState.PartiallyChecked: @@ -1077,7 +1077,7 @@ class KCCGUI(KCC_ui.Ui_mainWindow): 'disableProcessingBox': GUI.disableProcessingBox.checkState(), 'pdfExtractBox': GUI.pdfExtractBox.checkState(), 'pdfWidthBox': GUI.pdfWidthBox.checkState(), - 'smartCoverCropBox': GUI.smartCoverCropBox.checkState(), + 'noSmartCoverCropBox': GUI.noSmartCoverCropBox.checkState(), 'coverFillBox': GUI.coverFillBox.checkState(), 'metadataTitleBox': GUI.metadataTitleBox.checkState(), 'mozJpegBox': GUI.mozJpegBox.checkState(), diff --git a/kindlecomicconverter/KCC_ui.py b/kindlecomicconverter/KCC_ui.py index b5f5c9d..cf11c6e 100644 --- a/kindlecomicconverter/KCC_ui.py +++ b/kindlecomicconverter/KCC_ui.py @@ -344,10 +344,10 @@ class Ui_mainWindow(object): self.gridLayout_2.addWidget(self.metadataTitleBox, 7, 0, 1, 1) - self.smartCoverCropBox = QCheckBox(self.optionWidget) - self.smartCoverCropBox.setObjectName(u"smartCoverCropBox") + self.noSmartCoverCropBox = QCheckBox(self.optionWidget) + self.noSmartCoverCropBox.setObjectName(u"noSmartCoverCropBox") - self.gridLayout_2.addWidget(self.smartCoverCropBox, 11, 1, 1, 1) + self.gridLayout_2.addWidget(self.noSmartCoverCropBox, 11, 1, 1, 1) self.rotateFirstBox = QCheckBox(self.optionWidget) self.rotateFirstBox.setObjectName(u"rotateFirstBox") @@ -748,9 +748,9 @@ class Ui_mainWindow(object): #endif // QT_CONFIG(tooltip) self.metadataTitleBox.setText(QCoreApplication.translate("mainWindow", u"Metadata Title", None)) #if QT_CONFIG(tooltip) - self.smartCoverCropBox.setToolTip(QCoreApplication.translate("mainWindow", u"Attempt to crop main cover from wide image.", None)) + self.noSmartCoverCropBox.setToolTip(QCoreApplication.translate("mainWindow", u"Disable attempt to crop main cover from wide image.", None)) #endif // QT_CONFIG(tooltip) - self.smartCoverCropBox.setText(QCoreApplication.translate("mainWindow", u"Smart Cover Crop", None)) + self.noSmartCoverCropBox.setText(QCoreApplication.translate("mainWindow", u"No Smart Cover Crop", None)) #if QT_CONFIG(tooltip) self.rotateFirstBox.setToolTip(QCoreApplication.translate("mainWindow", u"

When the spread splitter option is partially checked,

Unchecked - Rotate Last
Put the rotated 2 page spread after the split spreads.

Checked - Rotate First
Put the rotated 2 page spread before the split spreads.

", None)) #endif // QT_CONFIG(tooltip) diff --git a/kindlecomicconverter/comic2ebook.py b/kindlecomicconverter/comic2ebook.py index 59a1408..11ffabd 100755 --- a/kindlecomicconverter/comic2ebook.py +++ b/kindlecomicconverter/comic2ebook.py @@ -556,7 +556,7 @@ def buildEPUB(path, chapternames, tomenumber, ischunked, cover: image.Cover, ori f.close() build_html_start = perf_counter() if cover: - cover.save_to_epub(os.path.join(path, 'OEBPS', 'Images', 'cover.jpg'), tomenumber, len_tomes) + cover.save_to_folder(os.path.join(path, 'OEBPS', 'Images', 'cover.jpg'), tomenumber, len_tomes) dot_clean(path) options.covers.append((cover, options.uuid)) for dirpath, dirnames, filenames in os.walk(os.path.join(path, 'OEBPS', 'Images')): @@ -1394,8 +1394,8 @@ def makeParser(): help="Use the legacy PDF image extraction method from KCC 8 and earlier") processing_options.add_argument("--pdfwidth", action="store_true", dest="pdfwidth", default=False, help="Render vector PDFs to device width instead of height.") - processing_options.add_argument("--smartcovercrop", action="store_true", dest="smartcovercrop", default=False, - help="Attempt to crop main cover from wide image") + processing_options.add_argument("--nosmartcovercrop", action="store_true", dest="nosmartcovercrop", default=False, + help="Disable attempt to crop main cover from wide image") processing_options.add_argument("--coverfill", action="store_true", dest="coverfill", default=False, help="Crop cover to fill screen") processing_options.add_argument("-u", "--upscale", action="store_true", dest="upscale", default=False, @@ -1712,12 +1712,16 @@ def makeBook(source, qtgui=None, job_progress=''): filepath.append(getOutputFilename(source, options.output, '.cbz', ' ' + str(tomeNumber))) else: filepath.append(getOutputFilename(source, options.output, '.cbz', '')) + if cover.smartcover: + cover.save_to_folder(os.path.join(tome, 'OEBPS', 'Images', 'cover.jpg'), tomeNumber, len(tomes)) makeZIP(tome + '_comic', os.path.join(tome, "OEBPS", "Images"), job_progress) elif options.format == 'PDF': print(f"{job_progress}Creating PDF file with PyMuPDF...") # determine output filename based on source and tome count suffix = (' ' + str(tomeNumber)) if len(tomes) > 1 else '' output_file = getOutputFilename(source, options.output, '.pdf', suffix) + if cover.smartcover: + cover.save_to_folder(os.path.join(tome, 'OEBPS', 'Images', 'cover.jpg'), tomeNumber, len(tomes)) # use optimized buildPDF logic with streaming and compression output_pdf = buildPDF(tome, options.title, job_progress, None, output_file) filepath.append(output_pdf) diff --git a/kindlecomicconverter/image.py b/kindlecomicconverter/image.py index 892dc77..98ec5de 100755 --- a/kindlecomicconverter/image.py +++ b/kindlecomicconverter/image.py @@ -569,6 +569,7 @@ class Cover: self.options = opt self.source = source self.image = Image.open(source) + self.smartcover = False # backwards compatibility for Pillow >9.1.0 if not hasattr(Image, 'Resampling'): Image.Resampling = Image @@ -579,7 +580,7 @@ class Cover: self.image = ImageOps.autocontrast(self.image, preserve_tone=True) if not self.options.forcecolor: self.image = self.image.convert('L') - if self.options.smartcovercrop: + if not self.options.nosmartcovercrop: self.crop_main_cover() size = list(self.options.profileData[1]) @@ -595,17 +596,19 @@ class Cover: def crop_main_cover(self): w, h = self.image.size if w / h > 2: + self.smartcover = True if self.options.righttoleft: self.image = self.image.crop((w/6, 0, w/2 - w * 0.02, h)) else: self.image = self.image.crop((w/2 + w * 0.02, 0, 5/6 * w, h)) elif w / h > 1.34: + self.smartcover = True if self.options.righttoleft: self.image = self.image.crop((0, 0, w/2 - w * 0.03, h)) else: self.image = self.image.crop((w/2 + w * 0.03, 0, w, h)) - def save_to_epub(self, target, tomeid, len_tomes=0): + def save_to_folder(self, target, tomeid, len_tomes=0): try: if tomeid == 0: self.image.save(target, "JPEG", optimize=1, quality=self.options.jpegquality) From cd2eeb4d0f0e372e4efc3046da61b512d12f973b Mon Sep 17 00:00:00 2001 From: Alex Xu Date: Wed, 15 Apr 2026 21:40:03 -0700 Subject: [PATCH 077/107] option: create temporary files directory on source file drive (#1296) --- README.md | 1 + gui/KCC.ui | 10 ++++++++++ kindlecomicconverter/KCC_gui.py | 3 +++ kindlecomicconverter/KCC_ui.py | 9 +++++++++ kindlecomicconverter/comic2ebook.py | 5 ++++- 5 files changed, 27 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 01cebcc..4d74f0f 100644 --- a/README.md +++ b/README.md @@ -282,6 +282,7 @@ PROCESSING: --jpeg-quality The JPEG quality, on a scale from 0 (worst) to 95 (best). Default 85 for most devices. --maximizestrips Turn 1x4 strips to 2x2 strips -d, --delete Delete source file(s) or a directory. It's not recoverable. + --tempdir Create temporary files directory on source file drive. OUTPUT SETTINGS: -o OUTPUT, --output OUTPUT diff --git a/gui/KCC.ui b/gui/KCC.ui index b2c960d..7b4e57f 100644 --- a/gui/KCC.ui +++ b/gui/KCC.ui @@ -885,6 +885,16 @@ Ignored for Kindle EPUB/MOBI and all PDF.
+ + + + <html><head/><body><p><span style=" font-weight:600; text-decoration: underline;">Unchecked - Main Drive<br/></span>Use dedicated temporary directory on main OS drive.</p><p><span style=" font-weight:600; text-decoration: underline;">Checked - Source File Drive<br/></span>Create temporary file directory on source file drive.</p></body></html> + + + Temp Directory + + +
diff --git a/kindlecomicconverter/KCC_gui.py b/kindlecomicconverter/KCC_gui.py index 089af51..11b797b 100644 --- a/kindlecomicconverter/KCC_gui.py +++ b/kindlecomicconverter/KCC_gui.py @@ -341,6 +341,8 @@ class WorkerThread(QThread): options.metadatatitle = 2 if GUI.deleteBox.isChecked(): options.delete = True + if GUI.tempDirBox.isChecked(): + options.tempdir = True if GUI.spreadShiftBox.isChecked(): options.spreadshift = True if GUI.fileFusionBox.isChecked(): @@ -1090,6 +1092,7 @@ class KCCGUI(KCC_ui.Ui_mainWindow): 'widthBox': GUI.widthBox.value(), 'heightBox': GUI.heightBox.value(), 'deleteBox': GUI.deleteBox.checkState(), + 'tempDirBox': GUI.tempDirBox.checkState(), 'spreadShiftBox': GUI.spreadShiftBox.checkState(), 'fileFusionBox': GUI.fileFusionBox.checkState(), 'defaultOutputFolderBox': GUI.defaultOutputFolderBox.checkState(), diff --git a/kindlecomicconverter/KCC_ui.py b/kindlecomicconverter/KCC_ui.py index cf11c6e..25fc4fc 100644 --- a/kindlecomicconverter/KCC_ui.py +++ b/kindlecomicconverter/KCC_ui.py @@ -450,6 +450,11 @@ class Ui_mainWindow(object): self.gridLayout_2.addWidget(self.webpBox, 12, 0, 1, 1) + self.tempDirBox = QCheckBox(self.optionWidget) + self.tempDirBox.setObjectName(u"tempDirBox") + + self.gridLayout_2.addWidget(self.tempDirBox, 12, 1, 1, 1) + self.gridLayout.addWidget(self.optionWidget, 5, 0, 1, 2) @@ -821,6 +826,10 @@ class Ui_mainWindow(object): "Ignored for Kindle EPUB/MOBI and all PDF.", None)) #endif // QT_CONFIG(tooltip) self.webpBox.setText(QCoreApplication.translate("mainWindow", u"WebP (experimental)", None)) +#if QT_CONFIG(tooltip) + self.tempDirBox.setToolTip(QCoreApplication.translate("mainWindow", u"

Unchecked - Main Drive
Use dedicated temporary directory on main OS drive.

Checked - Source File Drive
Create temporary file directory on source file drive.

", None)) +#endif // QT_CONFIG(tooltip) + self.tempDirBox.setText(QCoreApplication.translate("mainWindow", u"Temp Directory", None)) #if QT_CONFIG(tooltip) self.convertButton.setToolTip(QCoreApplication.translate("mainWindow", u"

Shift+Click to select the output directory for this list.

", None)) #endif // QT_CONFIG(tooltip) diff --git a/kindlecomicconverter/comic2ebook.py b/kindlecomicconverter/comic2ebook.py index 11ffabd..c95820c 100755 --- a/kindlecomicconverter/comic2ebook.py +++ b/kindlecomicconverter/comic2ebook.py @@ -874,7 +874,8 @@ def mupdf_pdf_process_pages_parallel(filename, output_dir, target_width, target_ def getWorkFolder(afile, workdir=None): if not workdir: workdir = mkdtemp('', 'KCC-') - # workdir = mkdtemp('', 'KCC-', os.path.dirname(afile)) + if options.tempdir: + workdir = mkdtemp('', 'KCC-', os.path.dirname(afile)) fullPath = os.path.join(workdir, 'OEBPS', 'Images') else: fullPath = workdir @@ -1450,6 +1451,8 @@ def makeParser(): help="Turn 1x4 strips to 2x2 strips") processing_options.add_argument("-d", "--delete", action="store_true", dest="delete", default=False, help="Delete source file(s) or a directory. It's not recoverable.") + processing_options.add_argument("--tempdir", action="store_true", dest="tempdir", default=False, + help="Create temporary files directory on source file drive.") custom_profile_options.add_argument("--customwidth", type=int, dest="customwidth", default=0, help="Replace screen width provided by device profile") From c5744117e32e69cd1536a7cebd9e05a58253c79e Mon Sep 17 00:00:00 2001 From: Alex Xu Date: Wed, 15 Apr 2026 21:59:27 -0700 Subject: [PATCH 078/107] bump to 9.7.1 --- kindlecomicconverter/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kindlecomicconverter/__init__.py b/kindlecomicconverter/__init__.py index b91a660..df3e4b3 100644 --- a/kindlecomicconverter/__init__.py +++ b/kindlecomicconverter/__init__.py @@ -1,4 +1,4 @@ -__version__ = '9.7.0' +__version__ = '9.7.1' __license__ = 'ISC' __copyright__ = '2012-2022, Ciro Mattia Gonano , Pawel Jastrzebski , darodi' __docformat__ = 'restructuredtext en' From c3030e8bd1d672d45735c23efd39e69708ff0816 Mon Sep 17 00:00:00 2001 From: Alex Xu Date: Thu, 16 Apr 2026 11:17:31 -0700 Subject: [PATCH 079/107] Paperwhite 12th Gen is actually 1272x1696 --- kindlecomicconverter/KCC_gui.py | 2 +- kindlecomicconverter/image.py | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/kindlecomicconverter/KCC_gui.py b/kindlecomicconverter/KCC_gui.py index 11b797b..78314fe 100644 --- a/kindlecomicconverter/KCC_gui.py +++ b/kindlecomicconverter/KCC_gui.py @@ -1272,7 +1272,7 @@ class KCCGUI(KCC_ui.Ui_mainWindow): 'PVOptions': True, 'ForceExpert': False, 'DefaultFormat': 0, 'DefaultUpscale': True, 'ForceColor': False, 'Label': 'KPW5', }, "Kindle Paperwhite 12": { - 'PVOptions': True, 'ForceExpert': False, 'DefaultFormat': 0, 'DefaultUpscale': True, 'ForceColor': False, 'Label': 'KO', + 'PVOptions': True, 'ForceExpert': False, 'DefaultFormat': 0, 'DefaultUpscale': True, 'ForceColor': False, 'Label': 'KPW6', }, "Kindle Colorsoft": { 'PVOptions': True, 'ForceExpert': False, 'DefaultFormat': 0, 'DefaultUpscale': True, 'ForceColor': True, 'Label': 'KCS', diff --git a/kindlecomicconverter/image.py b/kindlecomicconverter/image.py index 98ec5de..893c1d6 100755 --- a/kindlecomicconverter/image.py +++ b/kindlecomicconverter/image.py @@ -98,9 +98,10 @@ class ProfileData: 'KV': ("Kindle Voyage", (1072, 1448), Palette16, 1.0), 'KPW34': ("Kindle Paperwhite 3/4/Oasis", (1072, 1448), Palette16, 1.0), 'K810': ("Kindle 8/10", (600, 800), Palette16, 1.0), - 'KO': ("Kindle Oasis 2/3/Paperwhite 12", (1264, 1680), Palette16, 1.0), + 'KO': ("Kindle Oasis 2/3", (1264, 1680), Palette16, 1.0), 'K11': ("Kindle 11", (1072, 1448), Palette16, 1.0), 'KPW5': ("Kindle Paperwhite 5/Signature Edition", (1236, 1648), Palette16, 1.0), + 'KPW6': ("Kindle Paperwhite 6", (1272, 1696), Palette16, 1.0), 'KS1860': ("Kindle 1860", (1860, 1920), Palette16, 1.0), 'KS1920': ("Kindle 1920", (1920, 1920), Palette16, 1.0), 'KS1240': ("Kindle 1240", (1240, 1860), Palette16, 1.0), From d4e1565e4a4bec7d2b54ba44411143dab92908c3 Mon Sep 17 00:00:00 2001 From: Alex Xu Date: Thu, 16 Apr 2026 13:13:38 -0700 Subject: [PATCH 080/107] colorsoft is wrong too --- kindlecomicconverter/image.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kindlecomicconverter/image.py b/kindlecomicconverter/image.py index 893c1d6..9ebde0f 100755 --- a/kindlecomicconverter/image.py +++ b/kindlecomicconverter/image.py @@ -106,7 +106,7 @@ class ProfileData: 'KS1920': ("Kindle 1920", (1920, 1920), Palette16, 1.0), 'KS1240': ("Kindle 1240", (1240, 1860), Palette16, 1.0), 'KS': ("Kindle Scribe 1/2", (1860, 2480), Palette16, 1.0), - 'KCS': ("Kindle Colorsoft", (1264, 1680), Palette16, 1.0), + 'KCS': ("Kindle Colorsoft", (1272, 1696), Palette16, 1.0), 'KS3': ("Kindle Scribe 3", (1986, 2648), Palette16, 1.0), 'KSCS': ("Kindle Scribe Colorsoft", (1986, 2648), Palette16, 1.0), } From 96bf14d38615aebbcc48d66c1c2c47d4a114835b Mon Sep 17 00:00:00 2001 From: Alex Xu Date: Thu, 16 Apr 2026 13:26:04 -0700 Subject: [PATCH 081/107] Kindle Scribe 2025 default is PDF (#1297) --- kindlecomicconverter/KCC_gui.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/kindlecomicconverter/KCC_gui.py b/kindlecomicconverter/KCC_gui.py index 78314fe..a702563 100644 --- a/kindlecomicconverter/KCC_gui.py +++ b/kindlecomicconverter/KCC_gui.py @@ -1260,10 +1260,10 @@ class KCCGUI(KCC_ui.Ui_mainWindow): 'PVOptions': True, 'ForceExpert': False, 'DefaultFormat': 0, 'DefaultUpscale': False, 'ForceColor': False, 'Label': 'KS', }, "Kindle Scribe 3": { - 'PVOptions': True, 'ForceExpert': False, 'DefaultFormat': 0, 'DefaultUpscale': False, 'ForceColor': False, 'Label': 'KS3', + 'PVOptions': True, 'ForceExpert': False, 'DefaultFormat': 3, 'DefaultUpscale': False, 'ForceColor': False, 'Label': 'KS3', }, "Kindle Scribe Colorsoft": { - 'PVOptions': True, 'ForceExpert': False, 'DefaultFormat': 0, 'DefaultUpscale': False, 'ForceColor': True, 'Label': 'KSCS', + 'PVOptions': True, 'ForceExpert': False, 'DefaultFormat': 3, 'DefaultUpscale': False, 'ForceColor': True, 'Label': 'KSCS', }, "Kindle 11": { 'PVOptions': True, 'ForceExpert': False, 'DefaultFormat': 0, 'DefaultUpscale': True, 'ForceColor': False, 'Label': 'K11', From 72f98bb0320ff6d01cadc370a97fbed051fe18ec Mon Sep 17 00:00:00 2001 From: Alex Xu Date: Thu, 16 Apr 2026 13:30:08 -0700 Subject: [PATCH 082/107] bump to 9.7.2 --- kindlecomicconverter/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kindlecomicconverter/__init__.py b/kindlecomicconverter/__init__.py index df3e4b3..e4ccd5e 100644 --- a/kindlecomicconverter/__init__.py +++ b/kindlecomicconverter/__init__.py @@ -1,4 +1,4 @@ -__version__ = '9.7.1' +__version__ = '9.7.2' __license__ = 'ISC' __copyright__ = '2012-2022, Ciro Mattia Gonano , Pawel Jastrzebski , darodi' __docformat__ = 'restructuredtext en' From 894dbfc8a24db431608b2890c5621cd5a417e452 Mon Sep 17 00:00:00 2001 From: Alex Xu Date: Fri, 17 Apr 2026 18:03:31 -0700 Subject: [PATCH 083/107] don't bisect images with < 1.16 or > 1.75 aspect ratios (#1301) --- kindlecomicconverter/image.py | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/kindlecomicconverter/image.py b/kindlecomicconverter/image.py index 9ebde0f..11b5655 100755 --- a/kindlecomicconverter/image.py +++ b/kindlecomicconverter/image.py @@ -194,8 +194,10 @@ class ComicPageParser: new_image.paste(pageone, (0, 0)) new_image.paste(pagetwo, (0, height)) self.payload.append(['N', self.source, new_image, self.fill]) - elif (width > height) != (dstwidth > dstheight) and width <= dstheight and height <= dstwidth \ - and not self.opt.webtoon and self.opt.splitter == 1: + elif self.opt.webtoon: + self.payload.append(['N', self.source, self.image, self.fill]) + # rotate only TODO dead code? + elif (width > height) != (dstwidth > dstheight) and width <= dstheight and height <= dstwidth and self.opt.splitter == 1: spread = self.image if not self.opt.norotate: if not self.opt.rotateright: @@ -203,8 +205,10 @@ class ComicPageParser: else: spread = spread.rotate(-90, Image.Resampling.BICUBIC, True) self.payload.append(['R', self.source, spread, self.fill]) - elif (width > height) != (dstwidth > dstheight) and not self.opt.webtoon: - if self.opt.splitter != 1 and width / height < 2: + # elif wide enough to split + elif (width > height) != (dstwidth > dstheight) and width / height > 1.16: + # if (split) or (split and rotate) + if self.opt.splitter != 1 and width / height < 1.75: if width > height: leftbox = (0, 0, int(width / 2), height) rightbox = (int(width / 2), 0, width, height) @@ -219,7 +223,9 @@ class ComicPageParser: pagetwo = self.image.crop(rightbox) self.payload.append(['S1', self.source, pageone, self.fill]) self.payload.append(['S2', self.source, pagetwo, self.fill]) - if self.opt.splitter > 0 or (self.opt.splitter == 0 and width / height >= 2): + + # if (rotate) or (split and rotate) + if self.opt.splitter > 0 or (self.opt.splitter == 0 and width / height >= 1.75): spread = self.image if not self.opt.norotate: if not self.opt.rotateright: From b0374e127dacc75de6d8188e7cc3845059681fe0 Mon Sep 17 00:00:00 2001 From: Alex Xu Date: Sat, 18 Apr 2026 07:39:23 -0700 Subject: [PATCH 084/107] Smarter covers (#1300) * smart crop semi-wide covers * make it even smarter * make it even smarter * adjust ratio --- kindlecomicconverter/image.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/kindlecomicconverter/image.py b/kindlecomicconverter/image.py index 11b5655..82b93de 100755 --- a/kindlecomicconverter/image.py +++ b/kindlecomicconverter/image.py @@ -608,12 +608,30 @@ class Cover: self.image = self.image.crop((w/6, 0, w/2 - w * 0.02, h)) else: self.image = self.image.crop((w/2 + w * 0.02, 0, 5/6 * w, h)) + elif w / h > 1.83: + self.smartcover = True + if self.options.righttoleft: + self.image = self.image.crop((w * .19, 0, w * .575, h)) + else: + self.image = self.image.crop((w * .425, 0, .81 * w, h)) + elif w / h > 1.7: + self.smartcover = True + if self.options.righttoleft: + self.image = self.image.crop((w * .2, 0, w * .583, h)) + else: + self.image = self.image.crop((w * .417, 0, .8 * w, h)) elif w / h > 1.34: self.smartcover = True if self.options.righttoleft: self.image = self.image.crop((0, 0, w/2 - w * 0.03, h)) else: self.image = self.image.crop((w/2 + w * 0.03, 0, w, h)) + elif w / h > 1.0: + self.smartcover = True + if self.options.righttoleft: + self.image = self.image.crop((w * .36, 0, w, h)) + else: + self.image = self.image.crop((w, 0, .64 * w, h)) def save_to_folder(self, target, tomeid, len_tomes=0): try: From d5146d02fc596394bd4870596ffa922296c077a9 Mon Sep 17 00:00:00 2001 From: Alex Xu Date: Sun, 19 Apr 2026 10:10:18 -0700 Subject: [PATCH 085/107] Update README.md --- README.md | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 4d74f0f..123d428 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ like Kindle, Kobo, ReMarkable, and more. Pages display in fullscreen without margins, with proper fixed layout support. -Supported input formats include JPG/PNG image files in folders, archives, or PDFs. +Supported input formats include JPG/PNG image files in folders, archives like CBZ, or PDFs. Supported output formats include MOBI/AZW3, EPUB, KEPUB, CBZ, and PDF. KCC runs on Windows, macOS, and Linux. @@ -115,15 +115,14 @@ For flatpak, Docker, and AppImage versions, refer to the wiki: https://github.co ## FAQ - Should I use Calibre? - No. Calibre doesn't properly support fixed layout EPUB/MOBI, so modifying KCC output (even just metadata!) in Calibre can break the formatting. + Additionally, it will break page numbers. Viewing KCC output in Calibre will also not work properly. - On 7th gen and later Kindles running firmware 5.15.1+, you can get cover thumbnails simply by USB dropping into documents folder. - On 6th gen and older, you can get cover thumbnails by keeping Kindle plugged in during conversion. If you are careful to not modify the file however, you can still use Calibre, but direct USB dropping is reccomended. - Blank pages? - - May happen when [using PNG with Kindle Scribe](https://github.com/ciromattia/kcc/issues/665) or [any format with a Kindle Colorsoft](https://github.com/ciromattia/kcc/issues/768). Solve by using JPG with Kindle Scribe or buying a Kobo Colour. Happens more often when turning pages really fast. + - May happen when [using PNG with Kindle Scribe](https://github.com/ciromattia/kcc/issues/665) or [any format with a Kindle Colorsoft](https://github.com/ciromattia/kcc/issues/768). Solve by using JPG with Kindle Scribe or buying a Kobo Colour. Happens more often when turning pages really fast. You can try PDF output. Going back a few pages and exiting and re-entering book should fix it temporarily. - What output format should I use? - - MOBI for Kindles. CBZ for Kindle DX. CBZ for Koreader. KEPUB for Kobo. PDF for ReMarkable. + - MOBI for Kindles. CBZ for Kindle DX. CBZ for Koreader. KEPUB for Kobo. PDF for ReMarkable or Kindle Scribe 2025. - All options have additional information in tooltips if you hover over the option. - To get the converted book onto your Kindle/Kobo, just drag and drop the mobi/kepub into the documents folder on your Kindle/Kobo via USB - Kindle panel view not working? @@ -137,9 +136,6 @@ For flatpak, Docker, and AppImage versions, refer to the wiki: https://github.co (no login required). Works much better than previously recommended Android File Transfer. Cannot run simutaneously with other transfer apps. - How to make AZW3 instead of MOBI? - The `.mobi` file generated by KCC is a dual filetype, it's both MOBI and AZW3. The file extension is `.mobi` for compatibility reasons. -- [Windows 7 support](https://github.com/ciromattia/kcc/issues/678) -- Image too dark? - - The default gamma correction of 1.8 makes the image darker, and is useful for faded/gray artwork/text. Disable by setting gamma = 1.0 - Huge margins / slow page turns? - You likely modified the file during transfer using a 3rd party app. Try simply dragging and dropping the final mobi/kepub file into the Kindle documents folder via USB. @@ -198,8 +194,9 @@ sudo apt-get install python3 p7zip-full python3-pil python3-psutil python3-slugi 'KV': ("Kindle Voyage", (1072, 1448), Palette16, 1.0), 'KPW34': ("Kindle Paperwhite 3/4", (1072, 1448), Palette16, 1.0), 'KPW5': ("Kindle Paperwhite 5/Signature Edition", (1236, 1648), Palette16, 1.0), - 'KO': ("Kindle Oasis 2/3/Paperwhite 12", (1264, 1680), Palette16, 1.0), - 'KCS': ("Kindle Colorsoft", (1264, 1680), Palette16, 1.0), + 'KPW6': ("Kindle Paperwhite 6", (1272, 1696), Palette16, 1.0), + 'KO': ("Kindle Oasis 2/3", (1264, 1680), Palette16, 1.0), + 'KCS': ("Kindle Colorsoft", (1272, 1696), Palette16, 1.0), 'KS1860': ("Kindle 1860", (1860, 1920), Palette16, 1.0), 'KS1920': ("Kindle 1920", (1920, 1920), Palette16, 1.0), 'KS': ("Kindle Scribe 1/2", (1860, 2480), Palette16, 1.0), From a7a9f35686c4aeac8c5c8948df6f3cece06ee456 Mon Sep 17 00:00:00 2001 From: Alex Xu Date: Sun, 19 Apr 2026 10:27:52 -0700 Subject: [PATCH 086/107] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 123d428..4d7d4ae 100644 --- a/README.md +++ b/README.md @@ -117,7 +117,7 @@ For flatpak, Docker, and AppImage versions, refer to the wiki: https://github.co - No. Calibre doesn't properly support fixed layout EPUB/MOBI, so modifying KCC output (even just metadata!) in Calibre can break the formatting. Additionally, it will break page numbers. Viewing KCC output in Calibre will also not work properly. - If you are careful to not modify the file however, you can still use Calibre, but direct USB dropping is reccomended. + Direct USB dropping is reccomended. - Blank pages? - May happen when [using PNG with Kindle Scribe](https://github.com/ciromattia/kcc/issues/665) or [any format with a Kindle Colorsoft](https://github.com/ciromattia/kcc/issues/768). Solve by using JPG with Kindle Scribe or buying a Kobo Colour. Happens more often when turning pages really fast. You can try PDF output. Going back a few pages and exiting and re-entering book should fix it temporarily. From f97398d481eed94c7ac3f98c41a6f6c9e53fb5cb Mon Sep 17 00:00:00 2001 From: Alex Xu Date: Wed, 22 Apr 2026 16:25:15 -0700 Subject: [PATCH 087/107] remove getTopMargin from EPUBs --- kindlecomicconverter/comic2ebook.py | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/kindlecomicconverter/comic2ebook.py b/kindlecomicconverter/comic2ebook.py index c95820c..57f30a9 100755 --- a/kindlecomicconverter/comic2ebook.py +++ b/kindlecomicconverter/comic2ebook.py @@ -136,7 +136,7 @@ def buildHTML(path, imgfile, imgfilepath, imgfile2=None): "content=\"width=" + str(imgsizeframe[0]) + ", height=" + str(imgsizeframe[1]) + "\"/>\n" "\n", "\n", - "
\n", + "
\n", ]) if options.iskindle: # this display none div fixes formatting issues with virtual panel mode, for some reason @@ -1075,11 +1075,6 @@ def getDirectorySize(start_path='.'): return total_size -def getTopMargin(deviceres, size): - y = int((deviceres[1] - size[1]) / 2) / deviceres[1] * 100 - return str(round(y, 1)) - - def getPanelViewResolution(imagesize, deviceres): scale = float(deviceres[0]) / float(imagesize[0]) return int(deviceres[0]), int(scale * imagesize[1]) From 290578d66e1374835c03a13fa34c804047734d49 Mon Sep 17 00:00:00 2001 From: Alex Xu Date: Wed, 22 Apr 2026 17:56:53 -0700 Subject: [PATCH 088/107] enable upscale box for webtoon and scribe --- kindlecomicconverter/KCC_gui.py | 10 +++++----- kindlecomicconverter/comic2ebook.py | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/kindlecomicconverter/KCC_gui.py b/kindlecomicconverter/KCC_gui.py index a702563..ca714ac 100644 --- a/kindlecomicconverter/KCC_gui.py +++ b/kindlecomicconverter/KCC_gui.py @@ -795,8 +795,8 @@ class KCCGUI(KCC_ui.Ui_mainWindow): GUI.rotateBox.setChecked(False) GUI.borderBox.setEnabled(False) GUI.borderBox.setCheckState(Qt.CheckState.PartiallyChecked) - GUI.upscaleBox.setEnabled(False) - GUI.upscaleBox.setChecked(False) + # GUI.upscaleBox.setEnabled(False) + # GUI.upscaleBox.setChecked(False) GUI.croppingBox.setEnabled(False) GUI.croppingBox.setChecked(False) GUI.interPanelCropBox.setEnabled(False) @@ -813,7 +813,7 @@ class KCCGUI(KCC_ui.Ui_mainWindow): GUI.rotateBox.setEnabled(True) GUI.borderBox.setEnabled(True) profile = GUI.profiles[str(GUI.deviceBox.currentText())] - if not profile['Label'].startswith('KS'): + if not profile['Label'].startswith('KS') or True: GUI.upscaleBox.setEnabled(True) GUI.croppingBox.setEnabled(True) GUI.interPanelCropBox.setEnabled(True) @@ -908,10 +908,10 @@ class KCCGUI(KCC_ui.Ui_mainWindow): if not GUI.webtoonBox.isChecked(): GUI.qualityBox.setEnabled(profile['PVOptions']) GUI.upscaleBox.setChecked(profile['DefaultUpscale']) - if profile['Label'].startswith('KS'): + if profile['Label'].startswith('KS') and False: GUI.upscaleBox.setDisabled(True) else: - if not GUI.webtoonBox.isChecked(): + if not GUI.webtoonBox.isChecked() or True: GUI.upscaleBox.setEnabled(True) if profile['Label'] == 'KCS': current_format = GUI.formats[str(GUI.formatBox.currentText())]['format'] diff --git a/kindlecomicconverter/comic2ebook.py b/kindlecomicconverter/comic2ebook.py index 57f30a9..63e96fb 100755 --- a/kindlecomicconverter/comic2ebook.py +++ b/kindlecomicconverter/comic2ebook.py @@ -143,7 +143,7 @@ def buildHTML(path, imgfile, imgfilepath, imgfile2=None): f.write('
.
\n') f.write(f'\n') if imgfile2: - f.write(f'\n') + f.write(f'\n') f.write("
\n") if options.iskindle and options.panelview: if options.autoscale: From a0a194ecf12191246040e0f94d1aeee24303656b Mon Sep 17 00:00:00 2001 From: Alex Xu Date: Wed, 22 Apr 2026 18:01:43 -0700 Subject: [PATCH 089/107] bump to 10.0.0 --- kindlecomicconverter/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kindlecomicconverter/__init__.py b/kindlecomicconverter/__init__.py index e4ccd5e..820bf07 100644 --- a/kindlecomicconverter/__init__.py +++ b/kindlecomicconverter/__init__.py @@ -1,4 +1,4 @@ -__version__ = '9.7.2' +__version__ = '10.0.0' __license__ = 'ISC' __copyright__ = '2012-2022, Ciro Mattia Gonano , Pawel Jastrzebski , darodi' __docformat__ = 'restructuredtext en' From 1a8d74de4aae55b26498e2c00bb47ef28b267497 Mon Sep 17 00:00:00 2001 From: Alex Xu Date: Thu, 23 Apr 2026 13:36:49 -0700 Subject: [PATCH 090/107] fix webtoon smartcover (#1305) --- kindlecomicconverter/comic2ebook.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/kindlecomicconverter/comic2ebook.py b/kindlecomicconverter/comic2ebook.py index 63e96fb..8000c55 100755 --- a/kindlecomicconverter/comic2ebook.py +++ b/kindlecomicconverter/comic2ebook.py @@ -1710,7 +1710,7 @@ def makeBook(source, qtgui=None, job_progress=''): filepath.append(getOutputFilename(source, options.output, '.cbz', ' ' + str(tomeNumber))) else: filepath.append(getOutputFilename(source, options.output, '.cbz', '')) - if cover.smartcover: + if cover and cover.smartcover: cover.save_to_folder(os.path.join(tome, 'OEBPS', 'Images', 'cover.jpg'), tomeNumber, len(tomes)) makeZIP(tome + '_comic', os.path.join(tome, "OEBPS", "Images"), job_progress) elif options.format == 'PDF': @@ -1718,7 +1718,7 @@ def makeBook(source, qtgui=None, job_progress=''): # determine output filename based on source and tome count suffix = (' ' + str(tomeNumber)) if len(tomes) > 1 else '' output_file = getOutputFilename(source, options.output, '.pdf', suffix) - if cover.smartcover: + if cover and cover.smartcover: cover.save_to_folder(os.path.join(tome, 'OEBPS', 'Images', 'cover.jpg'), tomeNumber, len(tomes)) # use optimized buildPDF logic with streaming and compression output_pdf = buildPDF(tome, options.title, job_progress, None, output_file) From d6834063c1f3eb731bc695fc1b42db6ba67ee920 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=83=95=E3=82=A3=E3=83=AB=E3=82=BF=E3=83=BC=E3=83=9A?= =?UTF-8?q?=E3=83=BC=E3=83=91=E3=83=BC?= <76888457+filterpaper@users.noreply.github.com> Date: Fri, 24 Apr 2026 05:53:45 +0800 Subject: [PATCH 091/107] avoid orphan dir in tempdir and fix disk size check with tempdir (#1302) * Use tempdir option for fusion path * Update makeFusion to use the same temporary directory location * Avoid creating an orphan "KCC-" in TMPDIR when --tempdir is set * Ensure disk space check follows --tempdir setting * revert some things * revert some things --- kindlecomicconverter/comic2ebook.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/kindlecomicconverter/comic2ebook.py b/kindlecomicconverter/comic2ebook.py index 8000c55..7af4e54 100755 --- a/kindlecomicconverter/comic2ebook.py +++ b/kindlecomicconverter/comic2ebook.py @@ -873,14 +873,18 @@ def mupdf_pdf_process_pages_parallel(filename, output_dir, target_width, target_ def getWorkFolder(afile, workdir=None): if not workdir: - workdir = mkdtemp('', 'KCC-') if options.tempdir: workdir = mkdtemp('', 'KCC-', os.path.dirname(afile)) + else: + workdir = mkdtemp('', 'KCC-') fullPath = os.path.join(workdir, 'OEBPS', 'Images') else: fullPath = workdir + check_path = gettempdir() + if options.tempdir: + check_path = os.path.dirname(afile) if os.path.isdir(afile): - if disk_usage(gettempdir())[2] < getDirectorySize(afile) * 2.5: + if disk_usage(check_path)[2] < getDirectorySize(afile) * 2.5: raise UserWarning("Not enough disk space to perform conversion.") try: copytree(afile, fullPath) @@ -890,7 +894,7 @@ def getWorkFolder(afile, workdir=None): rmtree(workdir, True) raise UserWarning("Failed to prepare a workspace.") elif os.path.isfile(afile): - if disk_usage(gettempdir())[2] < os.path.getsize(afile) * 2.5: + if disk_usage(check_path)[2]< os.path.getsize(afile) * 2.5: raise UserWarning("Not enough disk space to perform conversion.") if afile.lower().endswith('.pdf'): if not os.path.exists(fullPath): From 61be6aa78e36a2f6015c9f37b33fb0c9cda81517 Mon Sep 17 00:00:00 2001 From: Alex Xu Date: Thu, 23 Apr 2026 14:56:04 -0700 Subject: [PATCH 092/107] bump to 10.0.1 --- kindlecomicconverter/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kindlecomicconverter/__init__.py b/kindlecomicconverter/__init__.py index 820bf07..268b45e 100644 --- a/kindlecomicconverter/__init__.py +++ b/kindlecomicconverter/__init__.py @@ -1,4 +1,4 @@ -__version__ = '10.0.0' +__version__ = '10.0.1' __license__ = 'ISC' __copyright__ = '2012-2022, Ciro Mattia Gonano , Pawel Jastrzebski , darodi' __docformat__ = 'restructuredtext en' From e5122cc18811f6431b7c47459efc807bc219a9ff Mon Sep 17 00:00:00 2001 From: Alex Xu Date: Fri, 24 Apr 2026 07:44:56 -0700 Subject: [PATCH 093/107] prevent creating non Kindle MOBI (#1307) --- kindlecomicconverter/comic2ebook.py | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/kindlecomicconverter/comic2ebook.py b/kindlecomicconverter/comic2ebook.py index 7af4e54..22a0604 100755 --- a/kindlecomicconverter/comic2ebook.py +++ b/kindlecomicconverter/comic2ebook.py @@ -1470,6 +1470,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): + raise UserWarning('MOBI/EPUB-200MB not supported for non-Kindle profiles') + if options.format == 'PDF-200MB': options.targetsize = 195 options.format = 'PDF' @@ -1501,10 +1510,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: @@ -1556,6 +1562,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 From 92c85c18e997a0e089443b0fff210fbb35cbcaba Mon Sep 17 00:00:00 2001 From: Alex Xu Date: Fri, 24 Apr 2026 12:11:38 -0700 Subject: [PATCH 094/107] rename autocontrast box to custom autocontrast (#1308) --- gui/KCC.ui | 2 +- kindlecomicconverter/KCC_ui.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/gui/KCC.ui b/gui/KCC.ui index 7b4e57f..df25af3 100644 --- a/gui/KCC.ui +++ b/gui/KCC.ui @@ -866,7 +866,7 @@ Useful if you plan to crop a little off the top and bottom to fill screen.<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> - Autocontrast + Custom Autocontrast true diff --git a/kindlecomicconverter/KCC_ui.py b/kindlecomicconverter/KCC_ui.py index 25fc4fc..407607d 100644 --- a/kindlecomicconverter/KCC_ui.py +++ b/kindlecomicconverter/KCC_ui.py @@ -819,7 +819,7 @@ class Ui_mainWindow(object): #if QT_CONFIG(tooltip) self.autocontrastBox.setToolTip(QCoreApplication.translate("mainWindow", u"

Unchecked - BW only
Only autocontrast bw pages. Ignored for pages where near blacks or whites don't exist.

Indeterminate - Disabled
Disable autocontrast

Checked - BW and Color
BW and color images will be autocontrasted. Ignored for pages where near blacks or whites don't exist.

", 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" From d5dde46989f56a75607496fe5ea3ef1505002ed2 Mon Sep 17 00:00:00 2001 From: Alex Xu Date: Fri, 24 Apr 2026 16:40:44 -0700 Subject: [PATCH 095/107] CBZ defaults to partially checked w/b borders (#1310) --- kindlecomicconverter/KCC_gui.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/kindlecomicconverter/KCC_gui.py b/kindlecomicconverter/KCC_gui.py index ca714ac..f984801 100644 --- a/kindlecomicconverter/KCC_gui.py +++ b/kindlecomicconverter/KCC_gui.py @@ -950,6 +950,9 @@ class KCCGUI(KCC_ui.Ui_mainWindow): 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() From 1b48a9fc5ed9886b038cd85aaac98bc731be6538 Mon Sep 17 00:00:00 2001 From: Alex Xu Date: Sun, 26 Apr 2026 14:53:28 -0700 Subject: [PATCH 096/107] 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 --- kindlecomicconverter/KCC_gui.py | 4 +- kindlecomicconverter/comic2ebook.py | 59 ++++++++++++++++++++++++++--- kindlecomicconverter/image.py | 15 +++++++- kindlecomicconverter/shared.py | 17 +++++++++ 4 files changed, 86 insertions(+), 9 deletions(-) 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 From a3672f7a1e3be978980a379890ddbb279c430fc0 Mon Sep 17 00:00:00 2001 From: Alex Xu Date: Sun, 26 Apr 2026 14:54:09 -0700 Subject: [PATCH 097/107] bump to 10.1.0 --- kindlecomicconverter/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kindlecomicconverter/__init__.py b/kindlecomicconverter/__init__.py index 268b45e..f1815bf 100644 --- a/kindlecomicconverter/__init__.py +++ b/kindlecomicconverter/__init__.py @@ -1,4 +1,4 @@ -__version__ = '10.0.1' +__version__ = '10.1.0' __license__ = 'ISC' __copyright__ = '2012-2022, Ciro Mattia Gonano , Pawel Jastrzebski , darodi' __docformat__ = 'restructuredtext en' From 997a514e2a3bf0b8498e63c383769fd4a29d9b13 Mon Sep 17 00:00:00 2001 From: Alex Xu Date: Sat, 2 May 2026 14:42:46 -0700 Subject: [PATCH 098/107] fix list index out of range for kfx (#1321) --- kindlecomicconverter/comic2ebook.py | 38 ++++++++++++++--------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/kindlecomicconverter/comic2ebook.py b/kindlecomicconverter/comic2ebook.py index 278368f..975a280 100755 --- a/kindlecomicconverter/comic2ebook.py +++ b/kindlecomicconverter/comic2ebook.py @@ -1706,27 +1706,27 @@ def makeBook(source, qtgui=None, job_progress=''): size = get_contain_resolution(imagef, (x, y)) normalized_resolutions.append(size) - counter = Counter(normalized_resolutions) + 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)) + 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') + 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") From d4aeb798c73fac043309bc17d7d45b995f1cc01f Mon Sep 17 00:00:00 2001 From: Alex Xu Date: Sat, 2 May 2026 14:43:12 -0700 Subject: [PATCH 099/107] bump to 10.1.1 --- kindlecomicconverter/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kindlecomicconverter/__init__.py b/kindlecomicconverter/__init__.py index f1815bf..3fe993b 100644 --- a/kindlecomicconverter/__init__.py +++ b/kindlecomicconverter/__init__.py @@ -1,4 +1,4 @@ -__version__ = '10.1.0' +__version__ = '10.1.1' __license__ = 'ISC' __copyright__ = '2012-2022, Ciro Mattia Gonano , Pawel Jastrzebski , darodi' __docformat__ = 'restructuredtext en' From bd691989a9764a78433bfc620e12504baa9a1afc Mon Sep 17 00:00:00 2001 From: Alex Xu Date: Sat, 2 May 2026 15:13:21 -0700 Subject: [PATCH 100/107] fix KFX on Windows 7 (#1323) --- kindlecomicconverter/comic2ebook.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kindlecomicconverter/comic2ebook.py b/kindlecomicconverter/comic2ebook.py index 975a280..916c268 100755 --- a/kindlecomicconverter/comic2ebook.py +++ b/kindlecomicconverter/comic2ebook.py @@ -1719,7 +1719,7 @@ def makeBook(source, qtgui=None, job_progress=''): most_common_res, most_common_count = counter.most_common(1)[0] options.kfx_resolution = most_common_res - if most_common_count / counter.total() > .6: + if most_common_count / sum(counter.values()) > .6: pass #elif max(aspect_ratios) - min(aspect_ratios) < .2: else: From 2878e5d41b2ecc9c7b65813fa9a733d495381c2a Mon Sep 17 00:00:00 2001 From: Alex Xu Date: Sat, 2 May 2026 15:17:36 -0700 Subject: [PATCH 101/107] bump to 10.1.2 --- kindlecomicconverter/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kindlecomicconverter/__init__.py b/kindlecomicconverter/__init__.py index 3fe993b..73583b4 100644 --- a/kindlecomicconverter/__init__.py +++ b/kindlecomicconverter/__init__.py @@ -1,4 +1,4 @@ -__version__ = '10.1.1' +__version__ = '10.1.2' __license__ = 'ISC' __copyright__ = '2012-2022, Ciro Mattia Gonano , Pawel Jastrzebski , darodi' __docformat__ = 'restructuredtext en' From f149ae23f38650a7b2d8b16e3d6846ec7350af19 Mon Sep 17 00:00:00 2001 From: Alex Xu Date: Sun, 3 May 2026 16:48:05 -0700 Subject: [PATCH 102/107] add Kindle Scribe 2025 landscape 1324x1986 profile (#1325) --- README.md | 2 ++ kindlecomicconverter/KCC_gui.py | 4 ++++ kindlecomicconverter/image.py | 1 + 3 files changed, 7 insertions(+) diff --git a/README.md b/README.md index 4d7d4ae..f4b29a7 100644 --- a/README.md +++ b/README.md @@ -199,6 +199,8 @@ sudo apt-get install python3 p7zip-full python3-pil python3-psutil python3-slugi 'KCS': ("Kindle Colorsoft", (1272, 1696), Palette16, 1.0), 'KS1860': ("Kindle 1860", (1860, 1920), Palette16, 1.0), 'KS1920': ("Kindle 1920", (1920, 1920), Palette16, 1.0), + 'KS1240': ("Kindle 1240", (1240, 1860), Palette16, 1.0), + 'KS1324': ("Kindle 1324", (1324, 1986), Palette16, 1.0), 'KS': ("Kindle Scribe 1/2", (1860, 2480), Palette16, 1.0), 'KS3': ("Kindle Scribe 3", (1986, 2648), Palette16, 1.0), 'KSCS': ("Kindle Scribe Colorsoft", (1986, 2648), Palette16, 1.0), diff --git a/kindlecomicconverter/KCC_gui.py b/kindlecomicconverter/KCC_gui.py index 309b1a8..ff457d4 100644 --- a/kindlecomicconverter/KCC_gui.py +++ b/kindlecomicconverter/KCC_gui.py @@ -1261,6 +1261,9 @@ class KCCGUI(KCC_ui.Ui_mainWindow): "Kindle 1240x1860": { 'PVOptions': True, 'ForceExpert': False, 'DefaultFormat': 0, 'DefaultUpscale': False, 'ForceColor': False, 'Label': 'KS1240', }, + "Kindle 1324x1986": { + 'PVOptions': True, 'ForceExpert': False, 'DefaultFormat': 0, 'DefaultUpscale': False, 'ForceColor': False, 'Label': 'KS1324', + }, "Kindle Scribe 1/2": { 'PVOptions': True, 'ForceExpert': False, 'DefaultFormat': 0, 'DefaultUpscale': False, 'ForceColor': False, 'Label': 'KS', }, @@ -1368,6 +1371,7 @@ class KCCGUI(KCC_ui.Ui_mainWindow): "Separator", "Other", "Separator", + "Kindle 1324x1986", "Kindle 1920x1920", "Kindle 1860x1920", "Kindle 1240x1860", diff --git a/kindlecomicconverter/image.py b/kindlecomicconverter/image.py index 16d9095..2a00c75 100755 --- a/kindlecomicconverter/image.py +++ b/kindlecomicconverter/image.py @@ -106,6 +106,7 @@ class ProfileData: 'KS1860': ("Kindle 1860", (1860, 1920), Palette16, 1.0), 'KS1920': ("Kindle 1920", (1920, 1920), Palette16, 1.0), 'KS1240': ("Kindle 1240", (1240, 1860), Palette16, 1.0), + 'KS1324': ("Kindle 1324", (1324, 1986), Palette16, 1.0), 'KS': ("Kindle Scribe 1/2", (1860, 2480), Palette16, 1.0), 'KCS': ("Kindle Colorsoft", (1272, 1696), Palette16, 1.0), 'KS3': ("Kindle Scribe 3", (1986, 2648), Palette16, 1.0), From 5a1e2dafcb2e30713682a0ca4a3b24ff4952d7cb Mon Sep 17 00:00:00 2001 From: Alex Xu Date: Mon, 4 May 2026 15:22:37 -0700 Subject: [PATCH 103/107] fix thin horizontal line in landscape in certain situations (#1326) --- kindlecomicconverter/comic2ebook.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kindlecomicconverter/comic2ebook.py b/kindlecomicconverter/comic2ebook.py index 916c268..8b8d571 100755 --- a/kindlecomicconverter/comic2ebook.py +++ b/kindlecomicconverter/comic2ebook.py @@ -144,7 +144,7 @@ def buildHTML(path, imgfile, imgfilepath, imgfile2=None): f.write('
.
\n') f.write(f'\n') if imgfile2: - f.write(f'\n') + f.write(f'\n') f.write("
\n") if options.iskindle and options.panelview: if options.autoscale: From 19ce14eeee6815b5deade14834358960dff2ec50 Mon Sep 17 00:00:00 2001 From: Alex Xu Date: Mon, 4 May 2026 16:40:40 -0700 Subject: [PATCH 104/107] KFX default upscale (#1329) --- kindlecomicconverter/KCC_gui.py | 1 + 1 file changed, 1 insertion(+) diff --git a/kindlecomicconverter/KCC_gui.py b/kindlecomicconverter/KCC_gui.py index ff457d4..2e99199 100644 --- a/kindlecomicconverter/KCC_gui.py +++ b/kindlecomicconverter/KCC_gui.py @@ -948,6 +948,7 @@ class KCCGUI(KCC_ui.Ui_mainWindow): GUI.chunkSizeCheckBox.setChecked(False) elif GUI.formats[str(GUI.formatBox.currentText())]['format'] == 'KFX': GUI.mozJpegBox.setCheckState(Qt.CheckState.PartiallyChecked) + GUI.upscaleBox.setChecked(True) 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(): From 8798d71bfa6e8020ba54d813e6fdcd10f4d9cea6 Mon Sep 17 00:00:00 2001 From: Alex Xu Date: Wed, 6 May 2026 08:12:19 -0700 Subject: [PATCH 105/107] smart cover crop is default off (#1331) --- README.md | 2 +- gui/KCC.ui | 6 +++--- kindlecomicconverter/KCC_gui.py | 6 +++--- kindlecomicconverter/KCC_ui.py | 10 +++++----- kindlecomicconverter/comic2ebook.py | 4 ++-- kindlecomicconverter/image.py | 2 +- 6 files changed, 15 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index f4b29a7..7624d58 100644 --- a/README.md +++ b/README.md @@ -269,7 +269,7 @@ PROCESSING: Crop empty sections. 0: Disabled 1: Horizontally 2: Both [Default=0] --blackborders Disable autodetection and force black borders --whiteborders Disable autodetection and force white borders - --nosmartcovercrop Disable attempt to crop main cover from wide image + --smartcovercrop Attempt to crop main cover from wide image --coverfill Center-crop only the cover to fill target device screen --forcecolor Don't convert images to grayscale --forcepng Create PNG files instead JPEG for black and white images diff --git a/gui/KCC.ui b/gui/KCC.ui index df25af3..9cc41d3 100644 --- a/gui/KCC.ui +++ b/gui/KCC.ui @@ -655,12 +655,12 @@ Higher values are larger and higher quality, and may resolve blank page issues.<
- + - Disable attempt to crop main cover from wide image. + <html><head/><body><p>Attempt to crop main cover from wide image.</p></body></html> - No Smart Cover Crop + Smart Cover Crop diff --git a/kindlecomicconverter/KCC_gui.py b/kindlecomicconverter/KCC_gui.py index 2e99199..9d9fb5b 100644 --- a/kindlecomicconverter/KCC_gui.py +++ b/kindlecomicconverter/KCC_gui.py @@ -331,8 +331,8 @@ class WorkerThread(QThread): options.pdfextract = True if GUI.pdfWidthBox.isChecked(): options.pdfwidth = True - if GUI.noSmartCoverCropBox.isChecked(): - options.nosmartcovercrop = True + if GUI.smartCoverCropBox.isChecked(): + options.smartcovercrop = True if GUI.coverFillBox.isChecked(): options.coverfill = True if GUI.metadataTitleBox.checkState() == Qt.CheckState.PartiallyChecked: @@ -1085,7 +1085,7 @@ class KCCGUI(KCC_ui.Ui_mainWindow): 'disableProcessingBox': GUI.disableProcessingBox.checkState(), 'pdfExtractBox': GUI.pdfExtractBox.checkState(), 'pdfWidthBox': GUI.pdfWidthBox.checkState(), - 'noSmartCoverCropBox': GUI.noSmartCoverCropBox.checkState(), + 'smartCoverCropBox': GUI.smartCoverCropBox.checkState(), 'coverFillBox': GUI.coverFillBox.checkState(), 'metadataTitleBox': GUI.metadataTitleBox.checkState(), 'mozJpegBox': GUI.mozJpegBox.checkState(), diff --git a/kindlecomicconverter/KCC_ui.py b/kindlecomicconverter/KCC_ui.py index 407607d..62e9d29 100644 --- a/kindlecomicconverter/KCC_ui.py +++ b/kindlecomicconverter/KCC_ui.py @@ -344,10 +344,10 @@ class Ui_mainWindow(object): self.gridLayout_2.addWidget(self.metadataTitleBox, 7, 0, 1, 1) - self.noSmartCoverCropBox = QCheckBox(self.optionWidget) - self.noSmartCoverCropBox.setObjectName(u"noSmartCoverCropBox") + self.smartCoverCropBox = QCheckBox(self.optionWidget) + self.smartCoverCropBox.setObjectName(u"smartCoverCropBox") - self.gridLayout_2.addWidget(self.noSmartCoverCropBox, 11, 1, 1, 1) + self.gridLayout_2.addWidget(self.smartCoverCropBox, 11, 1, 1, 1) self.rotateFirstBox = QCheckBox(self.optionWidget) self.rotateFirstBox.setObjectName(u"rotateFirstBox") @@ -753,9 +753,9 @@ class Ui_mainWindow(object): #endif // QT_CONFIG(tooltip) self.metadataTitleBox.setText(QCoreApplication.translate("mainWindow", u"Metadata Title", None)) #if QT_CONFIG(tooltip) - self.noSmartCoverCropBox.setToolTip(QCoreApplication.translate("mainWindow", u"Disable attempt to crop main cover from wide image.", None)) + self.smartCoverCropBox.setToolTip(QCoreApplication.translate("mainWindow", u"

Attempt to crop main cover from wide image.

", None)) #endif // QT_CONFIG(tooltip) - self.noSmartCoverCropBox.setText(QCoreApplication.translate("mainWindow", u"No Smart Cover Crop", None)) + self.smartCoverCropBox.setText(QCoreApplication.translate("mainWindow", u"Smart Cover Crop", None)) #if QT_CONFIG(tooltip) self.rotateFirstBox.setToolTip(QCoreApplication.translate("mainWindow", u"

When the spread splitter option is partially checked,

Unchecked - Rotate Last
Put the rotated 2 page spread after the split spreads.

Checked - Rotate First
Put the rotated 2 page spread before the split spreads.

", None)) #endif // QT_CONFIG(tooltip) diff --git a/kindlecomicconverter/comic2ebook.py b/kindlecomicconverter/comic2ebook.py index 8b8d571..8cd316c 100755 --- a/kindlecomicconverter/comic2ebook.py +++ b/kindlecomicconverter/comic2ebook.py @@ -1406,8 +1406,8 @@ def makeParser(): help="Use the legacy PDF image extraction method from KCC 8 and earlier") processing_options.add_argument("--pdfwidth", action="store_true", dest="pdfwidth", default=False, help="Render vector PDFs to device width instead of height.") - processing_options.add_argument("--nosmartcovercrop", action="store_true", dest="nosmartcovercrop", default=False, - help="Disable attempt to crop main cover from wide image") + processing_options.add_argument("--smartcovercrop", action="store_true", dest="smartcovercrop", default=False, + help="Attempt to crop main cover from wide image") processing_options.add_argument("--coverfill", action="store_true", dest="coverfill", default=False, help="Crop cover to fill screen") processing_options.add_argument("-u", "--upscale", action="store_true", dest="upscale", default=False, diff --git a/kindlecomicconverter/image.py b/kindlecomicconverter/image.py index 2a00c75..8b5486e 100755 --- a/kindlecomicconverter/image.py +++ b/kindlecomicconverter/image.py @@ -599,7 +599,7 @@ class Cover: self.image = ImageOps.autocontrast(self.image, preserve_tone=True) if not self.options.forcecolor: self.image = self.image.convert('L') - if not self.options.nosmartcovercrop: + if self.options.smartcovercrop: self.crop_main_cover() size = list(self.options.profileData[1]) From f2a806a42a8bda814ed39fbd9fc4bae742889b6f Mon Sep 17 00:00:00 2001 From: Alex Xu Date: Wed, 6 May 2026 08:39:27 -0700 Subject: [PATCH 106/107] bumpy to 10.1.3 --- kindlecomicconverter/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kindlecomicconverter/__init__.py b/kindlecomicconverter/__init__.py index 73583b4..c8f615d 100644 --- a/kindlecomicconverter/__init__.py +++ b/kindlecomicconverter/__init__.py @@ -1,4 +1,4 @@ -__version__ = '10.1.2' +__version__ = '10.1.3' __license__ = 'ISC' __copyright__ = '2012-2022, Ciro Mattia Gonano , Pawel Jastrzebski , darodi' __docformat__ = 'restructuredtext en' From e6ef7c173289844bfb01d4b9e936592019cabce7 Mon Sep 17 00:00:00 2001 From: Alex Xu Date: Fri, 8 May 2026 11:59:01 -0700 Subject: [PATCH 107/107] Bump actions/upload-artifact from 5 to 6 (#1332) Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 5 to 6. - [Release notes](https://github.com/actions/upload-artifact/releases) - [Commits](https://github.com/actions/upload-artifact/compare/v5...v6) --- updated-dependencies: - dependency-name: actions/upload-artifact dependency-version: '6' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>